From 5f3a03f4fbe9752dc4ffebce49cba7f431ee285a Mon Sep 17 00:00:00 2001 From: Abhishek Sharma <66074182+abhixv@users.noreply.github.com> Date: Sat, 20 Dec 2025 03:26:30 +0530 Subject: [PATCH] feat: Add option to clear home screen in settings (#6125) Signed-off-by: abhixv --- .github/release.yml | 5 +- .github/workflows/ci.yml | 5 +- .github/workflows/crowdin.yml | 2 +- .github/workflows/crowdin_download.yml | 2 +- .github/workflows/crowdin_upload.yml | 2 +- .gitmodules | 1 - Android.bp | 307 +- AndroidManifest-common.xml | 8 +- AndroidManifest.xml | 1 - CONTRIBUTING.md | 73 +- GITHUB_CHANGELOG.md | 378 +- OWNERS | 38 +- PREUPLOAD.cfg | 3 +- README.md | 54 +- SECURITY.md | 3 +- TELEGRAM_CHANGELOG.txt | 199 +- aconfig/launcher.aconfig | 432 +- androidx-lib/build.gradle | 2 +- baseline-profile/build.gradle | 12 +- .../baseline/BaselineProfileGenerator.kt | 4 - build.gradle | 63 +- compatLib/README.md | 27 +- compatLib/build.gradle | 1 - docs/assets/README.md | 22 +- .../images/phoneScreenshots/device-frame.jpg | Bin 39464 -> 35920 bytes .../android/en-US/short_description.txt | 2 +- flags/build.gradle | 2 +- .../android/launcher3/CustomFeatureFlags.java | 895 +- .../launcher3/FakeFeatureFlagsImpl.java | 1 + .../com/android/launcher3/FeatureFlags.java | 247 +- .../android/launcher3/FeatureFlagsImpl.java | 1269 +- flags/src/com/android/launcher3/Flags.java | 621 +- .../quickstep/util/DeviceConfigHelper.kt | 62 +- .../android/systemui/CustomFeatureFlags.java | 2301 +- .../systemui/FakeFeatureFlagsImpl.java | 1 + .../com/android/systemui/FeatureFlags.java | 1208 +- .../android/systemui/FeatureFlagsImpl.java | 3971 +- flags/src/com/android/systemui/Flags.java | 2219 +- .../systemui/shared/CustomFeatureFlags.java | 218 +- .../systemui/shared/FakeFeatureFlagsImpl.java | 1 + .../android/systemui/shared/FeatureFlags.java | 103 +- .../systemui/shared/FeatureFlagsImpl.java | 299 +- .../com/android/systemui/shared/Flags.java | 200 +- .../window/flags2/CustomFeatureFlags.java | 2405 +- .../window/flags2/FakeFeatureFlagsImpl.java | 1 + .../android/window/flags2/FeatureFlags.java | 1130 +- .../window/flags2/FeatureFlagsImpl.java | 2656 +- .../src/com/android/window/flags2/Flags.java | 2208 +- .../android/wm/shell/CustomFeatureFlags.java | 261 +- .../wm/shell/FakeFeatureFlagsImpl.java | 1 + .../com/android/wm/shell/FeatureFlags.java | 140 +- .../android/wm/shell/FeatureFlagsImpl.java | 457 +- flags/src/com/android/wm/shell/Flags.java | 244 +- go/AndroidManifest-launcher.xml | 1 - go/quickstep/res/values-af/strings.xml | 6 +- go/quickstep/res/values-be/strings.xml | 2 +- go/quickstep/res/values-fa/strings.xml | 2 +- go/quickstep/res/values-iw/strings.xml | 4 +- go/quickstep/res/values-ne/strings.xml | 6 +- go/quickstep/res/values/styles.xml | 2 +- .../src/com/android/launcher3/AppSharing.java | 17 +- .../quickstep/TaskOverlayFactoryGo.java | 10 +- gradle.properties | 12 +- gradle/libs.versions.toml | 21 +- lawnchair/assets/google_fonts.json | 47237 +++++++++++++++- lawnchair/res/color/widgets_picker_scrim.xml | 2 +- lawnchair/res/drawable/ic_about.xml | 18 +- lawnchair/res/drawable/ic_back.xml | 16 +- .../res/drawable/ic_bug_notification.xml | 29 +- lawnchair/res/drawable/ic_charging.xml | 14 +- lawnchair/res/drawable/ic_dock.xml | 15 +- lawnchair/res/drawable/ic_edit.xml | 13 +- lawnchair/res/drawable/ic_folder.xml | 14 +- lawnchair/res/drawable/ic_home_screen.xml | 13 +- lawnchair/res/drawable/ic_pr_lock.xml | 4 +- lawnchair/res/drawable/ic_quickstep.xml | 8 +- lawnchair/res/drawable/ic_warning.xml | 21 +- lawnchair/res/drawable/ic_work_app_badge.xml | 42 + lawnchair/res/font/inter_bold.ttf | Bin 0 -> 293448 bytes lawnchair/res/font/inter_medium.ttf | Bin 0 -> 292140 bytes lawnchair/res/font/inter_regular.ttf | Bin 0 -> 287928 bytes lawnchair/res/font/inter_semi_bold.ttf | Bin 0 -> 293028 bytes .../layout/all_apps_folder_application.xml | 5 +- lawnchair/res/layout/all_apps_folder_icon.xml | 4 +- .../widgets_two_pane_sheet_paged_view.xml | 58 +- .../widgets_two_pane_sheet_recyclerview.xml | 104 +- lawnchair/res/layout/work_apps_edu.xml | 59 +- lawnchair/res/values/config.xml | 2 +- lawnchair/res/values/dimens.xml | 4 +- lawnchair/res/values/strings.xml | 60 +- lawnchair/res/xml/device_profiles.xml | 17 +- lawnchair/src/app/lawnchair/BlankActivity.kt | 14 +- .../app/lawnchair/DeviceProfileOverrides.kt | 13 +- .../app/lawnchair/HeadlessWidgetsManager.kt | 17 +- .../lawnchair/LauncherActivityCachingLogic.kt | 23 + lawnchair/src/app/lawnchair/LawnchairApp.kt | 6 - .../src/app/lawnchair/LawnchairLauncher.kt | 46 +- .../lawnchair/LawnchairProcessInitializer.kt | 5 +- .../src/app/lawnchair/NotificationManager.kt | 13 +- .../app/lawnchair/SearchBarStateHandler.kt | 80 +- .../allapps/LawnchairAlphabeticalAppsList.kt | 5 +- .../allapps/views/SearchContainerView.kt | 6 +- .../allapps/views/SearchResultIcon.kt | 12 +- .../data/folder/service/FolderService.kt | 15 +- .../iconoverride/IconOverrideRepository.kt | 28 +- .../wallpaper/service/WallpaperService.kt | 13 +- .../lawnchair/deck/AddFoldersWithItemsTask.kt | 15 +- .../factory/LawnchairWidgetHolder.kt | 58 +- lawnchair/src/app/lawnchair/font/FontCache.kt | 21 +- .../src/app/lawnchair/font/FontManager.kt | 13 +- .../font/googlefonts/GoogleFontsListing.kt | 13 +- .../gestures/config/GestureHandlerConfig.kt | 6 - .../gestures/config/GestureHandlerOption.kt | 1 - .../handlers/OpenNotificationsHandler.kt | 20 +- .../handlers/RecentsGestureHandler.kt | 23 +- .../gestures/handlers/SleepGestureHandler.kt | 67 +- .../app/lawnchair/icons/IconPackProvider.kt | 54 +- .../lawnchair/icons/LawnchairIconProvider.kt | 236 +- .../app/lawnchair/icons/shape/IconShape.kt | 122 +- .../lawnchair/icons/shape/IconShapeManager.kt | 17 +- .../nexuslauncher/OverlayCallbackImpl.kt | 18 +- .../nexuslauncher/ThemedSmartSpaceHostView.kt | 2 +- .../app/lawnchair/override/CustomizeDialog.kt | 14 +- .../overview/TaskOverlayFactoryImpl.kt | 61 +- .../preferences/BasePreferenceManager.kt | 4 +- .../preferences/PreferenceManager.kt | 39 +- .../preferences2/PreferenceManager2.kt | 37 +- .../lawnchair/preferences2/ReloadHelper.kt | 6 +- .../src/app/lawnchair/qsb/providers/Google.kt | 9 - .../search/adapter/SearchTargetCompat.kt | 7 +- .../smartspace/DoubleShadowTextView.kt | 45 +- .../lawnchair/smartspace/IcuDateTextView.kt | 60 +- .../smartspace/SmartspaceViewContainer.kt | 3 +- .../smartspace/model/SmartspaceCalendar.kt | 8 +- .../smartspace/model/SmartspaceTarget.kt | 18 - .../provider/BatteryStatusProvider.kt | 62 +- .../smartspace/provider/SmartspaceProvider.kt | 13 +- .../src/app/lawnchair/theme/ThemeProvider.kt | 13 +- .../theme/color/tokens/ColorTokens.kt | 71 +- .../theme/drawable/DrawableTokens.kt | 2 +- .../ui/popup/LauncherOptionsPopup.kt | 21 - .../lawnchair/ui/popup/LawnchairShortcut.kt | 39 +- .../lawnchair/ui/preferences/about/About.kt | 27 +- .../ui/preferences/about/AboutModels.kt | 3 - .../ui/preferences/about/AboutViewModel.kt | 16 +- .../ui/preferences/about/ChangesDialog.kt | 6 +- .../about/NightlyBuildsRepository.kt | 26 +- .../ui/preferences/about/UpdateSection.kt | 57 +- .../components/AnnouncementPreference.kt | 17 +- .../components/DraggablePreference.kt | 103 +- .../components/GestureHandlerPreference.kt | 9 +- .../components/PermissionDialog.kt | 21 +- .../components/SuggestionsPreference.kt | 30 +- .../components/WallpaperPreview.kt | 4 - .../ColorPreferenceModelList.kt | 13 +- .../ColorSelectionPreference.kt | 4 - .../colorpreference/pickers/PresetsList.kt | 10 +- .../colorpreference/pickers/SwatchGrid.kt | 49 +- .../controls/ClickablePreference.kt | 5 - .../components/controls/ListPreference.kt | 8 +- .../controls/MainSwitchPreference.kt | 24 +- .../components/controls/PreferenceCategory.kt | 9 - .../components/controls/SliderPreference.kt | 2 +- .../components/controls/SwitchPreference.kt | 21 - .../components/controls/TextPreference.kt | 5 - .../components/layout/ClickableIcon.kt | 12 +- .../components/layout/DividerColumn.kt | 10 +- .../layout/LazyColumnPreferenceGroup.kt | 36 +- .../components/layout/LoadingScreen.kt | 14 +- .../components/layout/PreferenceGroup.kt | 144 +- .../preferences/components/layout/TopBar.kt | 43 +- .../search/DockSearchPreferences.kt | 95 +- .../search/DrawerSearchPreferences.kt | 73 +- .../components/search/FileSearchProvider.kt | 49 +- .../search/SearchProviderPreference.kt | 147 +- .../components/search/WebSearchProvider.kt | 5 - .../data/liveinfo/LiveInformationManager.kt | 14 +- .../AppDrawerFoldersPreference.kt | 90 +- .../destinations/AppDrawerPreferences.kt | 191 +- .../destinations/CustomIconShapePreference.kt | 146 +- .../destinations/DebugMenuPreferences.kt | 133 +- .../destinations/DockPreferences.kt | 84 +- .../ExperimentalFeaturesPreferences.kt | 227 +- .../destinations/FeatureFlagsPreference.kt | 4 +- .../destinations/FolderPreferences.kt | 80 +- .../destinations/FontSelectionPreference.kt | 19 +- .../destinations/GeneralPreferences.kt | 190 +- .../destinations/GesturePreferences.kt | 50 +- .../destinations/HiddenAppsPreferences.kt | 117 +- .../destinations/HomeScreenGridPreferences.kt | 32 +- .../destinations/HomeScreenPreferences.kt | 254 +- .../destinations/IconPackPreferences.kt | 62 +- .../destinations/IconShapePreference.kt | 54 +- .../destinations/PickAppForGesture.kt | 1 + .../destinations/PreferencesDashboard.kt | 388 +- .../destinations/QuickstepPreferences.kt | 43 +- .../destinations/SearchPreferences.kt | 2 +- .../destinations/SearchProviderPreferences.kt | 76 +- .../destinations/SelectAppsForDrawerFolder.kt | 13 +- .../destinations/SelectIconPreference.kt | 6 +- .../destinations/SmartspacePreferences.kt | 160 +- lawnchair/src/app/lawnchair/ui/theme/Shape.kt | 5 - lawnchair/src/app/lawnchair/ui/theme/Theme.kt | 22 +- lawnchair/src/app/lawnchair/ui/theme/Type.kt | 59 +- .../ui/util/ProvideBottomSheetHandler.kt | 74 +- .../PreferenceGroupPreviewContainer.kt | 3 +- lawnchair/src/app/lawnchair/util/AppsList.kt | 3 +- .../src/app/lawnchair/util/Compatibility.kt | 49 - .../src/app/lawnchair/util/LawnchairUtils.kt | 98 - .../util/LawnchairWindowManagerProxy.kt | 35 +- .../util/MainThreadInitializedObject.java | 48 +- .../util/PackageManagerExtensions.kt | 2 +- .../src/app/lawnchair/util/RecentHelper.kt | 11 +- .../src/app/lawnchair/util/WallpaperUtils.kt | 20 +- .../app/lawnchair/views/ComposeBottomSheet.kt | 2 +- .../lawnchair/views/LauncherPreviewView.kt | 15 +- .../views/LawnchairFloatingSurfaceView.kt | 33 +- prebuilts/libs/README.md | 27 +- proguard.flags | 23 +- protos/launcher_atom.proto | 16 - quickstep/Android.bp | 19 +- quickstep/AndroidManifest-launcher.xml | 3 +- quickstep/AndroidManifest.xml | 6 +- quickstep/res/color/all_set_bg_primary.xml | 2 +- quickstep/res/color/all_set_bg_tertiary.xml | 2 +- .../color/bubblebar_drop_target_bg_color.xml | 4 +- .../res/color/menu_item_hover_state_color.xml | 4 +- .../drawable/bg_bubble_bar_drop_target.xml | 2 +- .../res/drawable/bg_bubble_dismiss_circle.xml | 27 + .../bg_bubble_expanded_view_drop_target.xml | 2 +- quickstep/res/drawable/bg_circle.xml | 20 + .../drawable/bg_floating_desktop_select.xml | 2 +- .../drawable/bg_overview_clear_all_button.xml | 9 +- .../res/drawable/bg_taskbar_edu_tooltip.xml | 3 +- quickstep/res/drawable/bg_wellbeing_toast.xml | 2 +- .../drawable/button_taskbar_edu_bordered.xml | 5 +- .../res/drawable/ic_bubble_dismiss_white.xml | 25 + quickstep/res/drawable/ic_chevron_down.xml | 2 +- quickstep/res/drawable/ic_desktop.xml | 25 +- quickstep/res/drawable/ic_hourglass_top.xml | 19 +- .../drawable/ic_save_app_pair_left_right.xml | 47 +- .../res/drawable/ic_save_app_pair_up_down.xml | 49 +- ...uick_switch_overview_button_background.xml | 21 + .../res/drawable/rotate_tutorial_warning.xml | 2 +- .../split_instructions_background.xml | 2 +- .../keyboard_quick_switch_taskview.xml | 17 +- .../gesture_tutorial_step_menu.xml | 276 +- quickstep/res/layout/activity_allset.xml | 7 +- .../res/layout/digital_wellbeing_toast.xml | 7 +- .../res/layout/floating_header_content.xml | 2 +- .../res/layout/gesture_tutorial_fragment.xml | 10 + .../res/layout/gesture_tutorial_step_menu.xml | 35 +- quickstep/res/layout/icon_app_chip_view.xml | 11 +- .../layout/keyboard_quick_switch_taskview.xml | 17 +- ...eyboard_quick_switch_textonly_taskview.xml | 64 + .../res/layout/keyboard_quick_switch_view.xml | 49 +- .../res/layout/overview_clear_all_button.xml | 7 +- .../redesigned_gesture_tutorial_fragment.xml | 15 +- .../res/layout/split_instructions_view.xml | 22 +- quickstep/res/layout/task.xml | 14 +- quickstep/res/layout/task_desktop.xml | 48 +- quickstep/res/layout/task_grouped.xml | 26 +- quickstep/res/layout/task_menu.xml | 8 +- quickstep/res/layout/task_thumbnail.xml | 43 +- .../res/layout/task_view_menu_option.xml | 10 +- quickstep/res/layout/taskbar.xml | 19 - .../res/layout/taskbar_all_apps_sheet.xml | 6 +- quickstep/res/layout/taskbar_divider.xml | 14 +- .../res/layout/taskbar_divider_popup_menu.xml | 2 +- quickstep/res/layout/taskbar_edu_features.xml | 10 +- quickstep/res/layout/taskbar_edu_pinning.xml | 4 +- quickstep/res/layout/taskbar_edu_search.xml | 4 +- quickstep/res/layout/taskbar_edu_swipe.xml | 4 +- quickstep/res/layout/transient_taskbar.xml | 30 +- quickstep/res/raw-h480dp/all_set_page_bg.json | 1 + quickstep/res/raw-land/all_set_page_bg.json | 1 + .../res/raw-sw600dp-land/all_set_page_bg.json | 1 + .../res/raw-sw600dp/all_set_page_bg.json | 1 + .../res/raw-sw720dp-land/all_set_page_bg.json | 1 + .../res/raw-sw720dp/all_set_page_bg.json | 1 + .../raw-w840dp-h480dp/all_set_page_bg.json | 2 +- quickstep/res/values-af/strings.xml | 43 +- quickstep/res/values-am/strings.xml | 35 +- quickstep/res/values-ar/strings.xml | 37 +- quickstep/res/values-as/strings.xml | 35 +- quickstep/res/values-az/strings.xml | 37 +- quickstep/res/values-b+sr+Latn/strings.xml | 43 +- quickstep/res/values-be/strings.xml | 41 +- quickstep/res/values-bg/strings.xml | 37 +- quickstep/res/values-bn/strings.xml | 41 +- quickstep/res/values-bs/strings.xml | 43 +- quickstep/res/values-ca/strings.xml | 41 +- quickstep/res/values-cs/strings.xml | 49 +- quickstep/res/values-da/strings.xml | 45 +- quickstep/res/values-de/strings.xml | 47 +- quickstep/res/values-el/strings.xml | 39 +- quickstep/res/values-en-rAU/strings.xml | 35 +- quickstep/res/values-en-rCA/strings.xml | 35 +- quickstep/res/values-en-rGB/strings.xml | 35 +- quickstep/res/values-en-rIN/strings.xml | 35 +- quickstep/res/values-en-rXC/strings.xml | 16 +- quickstep/res/values-es-rUS/strings.xml | 47 +- quickstep/res/values-es/strings.xml | 41 +- quickstep/res/values-et/strings.xml | 45 +- quickstep/res/values-eu/strings.xml | 39 +- quickstep/res/values-fa/strings.xml | 39 +- quickstep/res/values-fi/strings.xml | 37 +- quickstep/res/values-fr-rCA/strings.xml | 45 +- quickstep/res/values-fr/strings.xml | 47 +- quickstep/res/values-gl/strings.xml | 41 +- quickstep/res/values-gu/strings.xml | 35 +- quickstep/res/values-hi/strings.xml | 37 +- quickstep/res/values-hr/strings.xml | 55 +- quickstep/res/values-hu/strings.xml | 45 +- quickstep/res/values-hy/strings.xml | 35 +- quickstep/res/values-in/strings.xml | 45 +- quickstep/res/values-is/strings.xml | 39 +- quickstep/res/values-it/strings.xml | 37 +- quickstep/res/values-iw/strings.xml | 51 +- quickstep/res/values-ja/strings.xml | 41 +- quickstep/res/values-ka/strings.xml | 35 +- quickstep/res/values-kk/strings.xml | 35 +- quickstep/res/values-km/strings.xml | 37 +- quickstep/res/values-kn/strings.xml | 43 +- quickstep/res/values-ko/strings.xml | 37 +- quickstep/res/values-ky/strings.xml | 39 +- quickstep/res/values-lo/strings.xml | 37 +- quickstep/res/values-lt/strings.xml | 37 +- quickstep/res/values-lv/strings.xml | 37 +- quickstep/res/values-mk/strings.xml | 41 +- quickstep/res/values-ml/strings.xml | 35 +- quickstep/res/values-mn/strings.xml | 35 +- quickstep/res/values-mr/strings.xml | 35 +- quickstep/res/values-ms/strings.xml | 45 +- quickstep/res/values-my/strings.xml | 39 +- quickstep/res/values-nb/strings.xml | 37 +- quickstep/res/values-ne/strings.xml | 45 +- quickstep/res/values-night/colors.xml | 8 +- quickstep/res/values-night/styles.xml | 31 +- quickstep/res/values-nl/strings.xml | 37 +- quickstep/res/values-or/strings.xml | 45 +- quickstep/res/values-pa/strings.xml | 39 +- quickstep/res/values-pl/strings.xml | 37 +- quickstep/res/values-pt-rPT/strings.xml | 49 +- quickstep/res/values-pt/strings.xml | 45 +- quickstep/res/values-ro/strings.xml | 37 +- quickstep/res/values-ru/strings.xml | 41 +- quickstep/res/values-si/strings.xml | 35 +- quickstep/res/values-sk/strings.xml | 55 +- quickstep/res/values-sl/strings.xml | 35 +- quickstep/res/values-sq/strings.xml | 35 +- quickstep/res/values-sr/strings.xml | 43 +- quickstep/res/values-sv/strings.xml | 37 +- quickstep/res/values-sw/strings.xml | 47 +- quickstep/res/values-sw600dp-land/dimens.xml | 8 +- quickstep/res/values-sw600dp/dimens.xml | 11 +- quickstep/res/values-ta/strings.xml | 39 +- quickstep/res/values-te/strings.xml | 37 +- quickstep/res/values-th/strings.xml | 51 +- quickstep/res/values-tl/strings.xml | 35 +- quickstep/res/values-tr/strings.xml | 39 +- quickstep/res/values-uk/strings.xml | 39 +- quickstep/res/values-ur/strings.xml | 35 +- quickstep/res/values-uz/strings.xml | 37 +- quickstep/res/values-vi/strings.xml | 39 +- quickstep/res/values-zh-rCN/strings.xml | 43 +- quickstep/res/values-zh-rHK/strings.xml | 35 +- quickstep/res/values-zh-rTW/strings.xml | 37 +- quickstep/res/values-zu/strings.xml | 35 +- quickstep/res/values/attrs.xml | 6 - quickstep/res/values/colors.xml | 11 +- quickstep/res/values/config.xml | 15 +- quickstep/res/values/dimens.xml | 85 +- quickstep/res/values/strings.xml | 88 +- quickstep/res/values/styles.xml | 56 +- .../launcher3/LauncherAnimationRunner.java | 5 +- .../launcher3/LauncherInitListener.java | 8 +- .../QuickstepAccessibilityDelegate.java | 62 - .../launcher3/QuickstepTransitionManager.java | 757 +- .../launcher3/WidgetPickerActivity.java | 366 +- .../appprediction/AppsDividerView.java | 12 +- .../appprediction/PredictionRowView.java | 25 +- .../DesktopRecentsTransitionController.kt | 90 +- .../HotseatPredictionController.java | 9 +- .../hybridhotseat/HotseatPredictionModel.java | 23 +- .../launcher3/model/PredictionUpdateTask.java | 16 +- .../model/QuickstepModelDelegate.java | 191 +- .../launcher3/model/WellbeingModel.java | 34 +- .../model/WidgetPredictionsRequester.java | 161 +- .../model/WidgetsPredictionUpdateTask.java | 89 +- .../launcher3/proxy/ProxyActivityStarter.java | 1 - .../statehandlers/DepthController.java | 117 +- .../launcher3/taskbar/BaseTaskbarContext.java | 68 +- .../taskbar/FallbackTaskbarUIController.java | 71 +- .../KeyboardQuickSwitchController.java | 313 +- .../taskbar/KeyboardQuickSwitchTaskView.java | 175 +- .../taskbar/KeyboardQuickSwitchView.java | 444 +- .../KeyboardQuickSwitchViewController.java | 235 +- .../taskbar/LauncherTaskbarUIController.java | 381 +- .../taskbar/NavbarButtonsViewController.java | 773 +- .../launcher3/taskbar/StashedHandleView.java | 5 - .../taskbar/StashedHandleViewController.java | 23 +- .../taskbar/TaskbarActivityContext.java | 1114 +- .../TaskbarAutohideSuspendController.java | 10 - .../taskbar/TaskbarBackgroundRenderer.kt | 45 +- .../launcher3/taskbar/TaskbarControllers.java | 61 +- .../taskbar/TaskbarDividerPopupView.kt | 93 +- .../taskbar/TaskbarDragController.java | 164 +- .../launcher3/taskbar/TaskbarDragLayer.java | 34 +- .../taskbar/TaskbarDragLayerController.java | 80 +- .../launcher3/taskbar/TaskbarEduTooltip.kt | 18 +- .../taskbar/TaskbarEduTooltipController.kt | 121 +- ...askbarForceVisibleImmersiveController.java | 9 +- .../TaskbarHoverToolTipController.java | 100 +- .../taskbar/TaskbarInsetsController.kt | 139 +- .../TaskbarLauncherStateController.java | 491 +- .../launcher3/taskbar/TaskbarManager.java | 1644 +- .../taskbar/TaskbarModelCallbacks.java | 107 +- .../taskbar/TaskbarNavButtonController.java | 122 +- .../taskbar/TaskbarPinningController.kt | 37 +- .../taskbar/TaskbarPopupController.java | 220 +- .../taskbar/TaskbarRecentAppsController.kt | 452 +- .../taskbar/TaskbarScrimViewController.java | 51 +- .../launcher3/taskbar/TaskbarSharedState.java | 25 +- ...kbarShortcutMenuAccessibilityDelegate.java | 28 +- .../TaskbarSpringOnStashController.java | 3 +- .../taskbar/TaskbarStashController.java | 348 +- .../taskbar/TaskbarStashViaTouchController.kt | 7 +- .../taskbar/TaskbarThresholdUtils.java | 5 + .../taskbar/TaskbarTranslationController.java | 6 +- .../taskbar/TaskbarUIController.java | 225 +- .../launcher3/taskbar/TaskbarView.java | 891 +- .../taskbar/TaskbarViewCallbacks.java | 162 +- .../taskbar/TaskbarViewCallbacksFactory.kt | 34 +- .../taskbar/TaskbarViewController.java | 718 +- .../VoiceInteractionWindowController.kt | 11 +- .../allapps/TaskbarAllAppsContainerView.java | 25 + .../allapps/TaskbarAllAppsController.java | 66 +- ...TaskbarAllAppsFallbackSearchContainer.java | 54 + .../allapps/TaskbarAllAppsSlideInView.java | 98 +- .../allapps/TaskbarAllAppsViewController.java | 5 +- .../allapps/TaskbarSearchSessionController.kt | 10 +- .../taskbar/bubbles/BubbleBarBackground.kt | 99 +- .../taskbar/bubbles/BubbleBarController.java | 653 +- .../taskbar/bubbles/BubbleBarItem.kt | 14 +- .../taskbar/bubbles/BubbleBarPinController.kt | 14 +- .../taskbar/bubbles/BubbleBarView.java | 1254 +- .../bubbles/BubbleBarViewController.java | 1172 +- .../taskbar/bubbles/BubbleControllers.java | 77 +- .../bubbles/BubbleDismissController.java | 8 +- .../taskbar/bubbles/BubbleDismissViewExt.kt | 9 +- .../taskbar/bubbles/BubbleDragAnimator.java | 50 +- .../taskbar/bubbles/BubbleDragController.java | 236 +- .../taskbar/bubbles/BubblePinController.kt | 5 +- .../BubbleStashedHandleViewController.java | 199 +- .../launcher3/taskbar/bubbles/BubbleView.java | 382 +- .../animation/BubbleBarViewAnimator.kt | 553 +- .../taskbar/customization/TaskbarContainer.kt | 16 +- .../customization/TaskbarFeatureEvaluator.kt | 43 +- .../taskbar/customization/TaskbarIconSpecs.kt | 32 +- .../customization/TaskbarSpecsEvaluator.kt | 64 +- .../navbutton/AbstractNavButtonLayoutter.kt | 29 +- .../navbutton/NavButtonLayoutFactory.kt | 39 +- .../taskbar/navbutton/NearestTouchFrame.java | 1 - .../navbutton/PhoneGestureLayoutter.kt | 31 +- .../taskbar/navbutton/SetupNavLayoutter.kt | 32 +- .../overlay/TaskbarOverlayContext.java | 52 +- .../overlay/TaskbarOverlayController.java | 99 +- .../overlay/TaskbarOverlayDragLayer.java | 15 +- .../BaseRecentsViewStateController.java | 161 + .../uioverrides/PredictedAppIcon.java | 358 +- .../uioverrides/QuickstepAppWidgetHost.java | 74 + .../QuickstepInteractionHandler.java | 51 +- .../uioverrides/QuickstepLauncher.java | 523 +- .../uioverrides/QuickstepWidgetHolder.java | 136 +- .../RecentsViewStateController.java | 185 + .../launcher3/uioverrides/SystemApiWrapper.kt | 38 +- .../uioverrides/flags/DevOptionsUiHelper.kt | 139 +- .../plugins/PluginManagerWrapperImpl.java | 23 +- .../uioverrides/states/AllAppsState.java | 47 +- .../states/BackgroundAppState.java | 24 +- .../states/OverviewModalTaskState.java | 13 +- .../uioverrides/states/OverviewState.java | 47 +- .../uioverrides/states/QuickSwitchState.java | 3 +- .../QuickstepAtomicAnimationFactory.java | 36 +- .../states/SplitScreenSelectState.java | 10 +- .../NavBarToHomeTouchController.java | 9 +- ...ButtonNavbarToOverviewTouchController.java | 19 +- .../NoButtonQuickSwitchTouchController.java | 72 +- .../PortraitOverviewStateTouchHelper.java | 4 +- .../PortraitStatesTouchController.java | 26 +- .../QuickSwitchTouchController.java | 6 +- .../StatusBarTouchController.java | 59 +- .../TaskViewTouchController.java | 395 + .../android/quickstep/AbsSwipeUpHandler.java | 778 +- .../android/quickstep/AllAppsActionManager.kt | 37 +- .../quickstep/BaseActivityInterface.java | 90 +- .../quickstep/BaseContainerInterface.java | 134 +- .../com/android/quickstep/BinderTracker.java | 32 +- .../quickstep/DesktopSystemShortcut.kt | 55 +- .../android/quickstep/DeviceConfigWrapper.kt | 35 +- .../quickstep/FallbackActivityInterface.java | 43 +- .../quickstep/FallbackSwipeHandler.java | 33 +- .../com/android/quickstep/GestureState.java | 166 +- .../android/quickstep/HomeVisibilityState.kt | 22 +- .../com/android/quickstep/InputConsumer.java | 86 +- .../quickstep/LauncherActivityInterface.java | 93 +- .../LauncherBackAnimationController.java | 226 +- .../LauncherRestoreEventLoggerImpl.kt | 1 - .../quickstep/LauncherSwipeHandlerV2.java | 42 +- .../android/quickstep/MultiStateCallback.java | 8 +- .../android/quickstep/OrientationRectF.java | 4 +- .../OrientationTouchTransformer.java | 56 +- .../quickstep/OverviewCommandHelper.java | 525 + .../quickstep/OverviewComponentObserver.java | 165 +- .../QuickstepProcessInitializer.java | 65 +- .../android/quickstep/RecentTasksList.java | 441 +- .../android/quickstep/RecentsActivity.java | 143 +- .../quickstep/RecentsAnimationCallbacks.java | 102 +- .../quickstep/RecentsAnimationController.java | 115 +- .../RecentsAnimationDeviceState.java | 345 +- .../quickstep/RecentsAnimationTargets.java | 13 +- .../android/quickstep/RecentsFilterState.java | 56 +- .../com/android/quickstep/RecentsModel.java | 191 +- .../android/quickstep/RemoteTargetGluer.java | 35 +- .../quickstep/RotationTouchHelper.java | 222 +- .../SimpleOrientationTouchTransformer.java | 65 +- .../quickstep/SwipeUpAnimationLogic.java | 29 +- .../com/android/quickstep/SystemUiProxy.java | 1655 + .../quickstep/TaskAnimationManager.java | 273 +- .../android/quickstep/TaskOverlayFactory.java | 199 +- .../quickstep/TaskShortcutFactory.java | 251 +- .../android/quickstep/TaskThumbnailCache.java | 279 + .../src/com/android/quickstep/TaskUtils.java | 6 +- .../com/android/quickstep/TaskViewUtils.java | 203 +- .../com/android/quickstep/TopTaskTracker.java | 513 +- .../quickstep/TouchInteractionService.java | 1390 +- .../src/com/android/quickstep/ViewUtils.java | 59 +- .../FallbackRecentsStateController.java | 57 +- .../fallback/FallbackRecentsView.java | 97 +- .../quickstep/fallback/RecentsDragLayer.java | 43 +- .../quickstep/fallback/RecentsState.java | 89 +- .../fallback/RecentsTaskController.java | 36 + .../AccessibilityInputConsumer.java | 20 +- .../AssistantInputConsumer.java | 2 +- .../inputconsumers/DelegateInputConsumer.java | 16 +- .../DeviceLockedInputConsumer.java | 46 +- .../NavHandleLongPressHandler.java | 137 +- .../NavHandleLongPressInputConsumer.java | 94 +- .../OneHandedModeInputConsumer.java | 10 +- .../OtherActivityInputConsumer.java | 82 +- .../inputconsumers/OverviewInputConsumer.java | 17 +- .../OverviewWithoutFocusInputConsumer.java | 19 +- .../ProgressDelegateInputConsumer.java | 17 +- .../ResetGestureInputConsumer.java | 55 + .../ScreenPinnedInputConsumer.java | 8 - .../SysUiOverlayInputConsumer.java | 9 - .../TaskbarUnstashInputConsumer.java | 105 +- .../TrackpadStatusBarInputConsumer.java | 7 +- .../quickstep/interaction/AllSetActivity.java | 172 +- .../interaction/AnimatedTaskView.java | 9 +- .../BackGestureTutorialController.java | 70 +- .../interaction/EdgeBackGestureHandler.java | 17 +- .../interaction/GestureSandboxActivity.java | 38 +- .../HomeGestureTutorialController.java | 49 +- .../interaction/NavBarGestureHandler.java | 18 +- .../OverviewGestureTutorialController.java | 58 +- .../interaction/RootSandboxLayout.java | 65 + .../SwipeUpGestureTutorialController.java | 63 +- .../interaction/TutorialController.java | 408 +- .../interaction/TutorialFragment.java | 22 +- .../interaction/TutorialStepIndicator.java | 118 + .../logging/SettingsChangeLogger.java | 82 +- .../logging/StatsLogCompatManager.java | 235 +- .../orientation/LandscapePagedViewHandler.kt | 191 +- .../orientation/PortraitPagedViewHandler.java | 820 + .../RecentsPagedOrientationHandler.kt | 74 +- .../orientation/SeascapePagedViewHandler.kt | 160 +- .../recents/data/RecentTasksRepository.kt | 17 +- .../quickstep/recents/data/TasksRepository.kt | 260 +- .../recents/viewmodel/RecentsViewData.kt | 17 +- .../task/thumbnail/TaskThumbnailUiState.kt | 47 +- .../task/thumbnail/TaskThumbnailView.kt | 267 +- .../task/thumbnail/TaskThumbnailViewModel.kt | 91 + .../thumbnail/data/TaskThumbnailDataSource.kt | 7 +- .../quickstep/task/viewmodel/TaskViewData.kt | 24 + .../util/ActiveGestureErrorDetector.java | 454 + .../quickstep/util/ActiveGestureLog.java | 376 + .../quickstep/util/ActivityInitListener.java | 73 + .../com/android/quickstep/util/AnimUtils.java | 62 +- .../AnimatorControllerWithResistance.java | 60 +- .../quickstep/util/AppPairsController.java | 169 +- .../util/AssistContentRequester.java | 2 +- .../quickstep/util/AssistStateManager.java | 93 + .../android/quickstep/util/AssistUtils.java | 45 + .../quickstep/util/BaseDepthController.java | 132 +- .../android/quickstep/util/BorderAnimator.kt | 29 +- .../quickstep/util/CachedEventDispatcher.java | 5 - .../android/quickstep/util/DesktopTask.java | 71 + .../com/android/quickstep/util/GroupTask.java | 94 + .../quickstep/util/ImageActionUtils.java | 20 +- .../quickstep/util/InputConsumerProxy.java | 13 +- .../util/InputProxyHandlerFactory.java | 7 +- .../LauncherUnfoldAnimationController.java | 20 +- .../android/quickstep/util/LayoutUtils.java | 22 +- .../quickstep/util/MotionPauseDetector.java | 63 +- .../util/QuickstepOnboardingPrefs.java | 3 +- .../util/RecentsAtomicAnimationFactory.java | 9 +- .../quickstep/util/RecentsOrientedState.java | 75 +- .../util/ScalingWorkspaceRevealAnim.kt | 112 +- .../quickstep/util/SlideInRemoteTransition.kt | 4 - .../util/SplitAnimationController.kt | 367 +- .../quickstep/util/SplitAnimationTimings.java | 15 - .../quickstep/util/SplitScreenUtils.kt | 93 +- .../util/SplitSelectStateController.java | 460 +- .../util/SplitToWorkspaceController.java | 26 +- .../SplitWithKeyboardShortcutController.java | 65 +- .../util/StaggeredWorkspaceAnim.java | 10 +- .../util/SwipePipToHomeAnimator.java | 96 +- .../quickstep/util/SystemUiFlagUtils.kt | 16 - .../util/SystemWindowManagerProxy.java | 91 +- .../android/quickstep/util/TISBindHelper.java | 3 +- .../quickstep/util/TaskGridNavHelper.java | 136 + .../util/TaskKeyByLastActiveTimeCache.java | 2 - .../android/quickstep/util/TaskKeyCache.java | 3 - .../quickstep/util/TaskKeyLruCache.java | 3 - .../util/TaskRemovedDuringLaunchListener.java | 5 +- .../TaskRestartedDuringLaunchListener.java | 5 + .../quickstep/util/TaskViewSimulator.java | 230 +- .../util/TaskVisualsChangeListener.java | 3 +- .../quickstep/util/TransformParams.java | 78 +- .../quickstep/util/WorkspaceRevealAnim.java | 16 - .../LauncherUnfoldTransitionController.kt | 11 +- .../quickstep/views/DesktopTaskView.kt | 576 +- .../views/FloatingAppPairBackground.kt | 15 +- .../views/FloatingWidgetBackgroundView.java | 4 +- .../quickstep/views/FloatingWidgetView.java | 111 +- .../quickstep/views/GroupedTaskView.kt | 238 +- .../quickstep/views/IconAppChipView.java | 464 + .../com/android/quickstep/views/IconView.java | 209 + .../quickstep/views/LauncherRecentsView.java | 111 +- .../quickstep/views/OverviewActionsView.java | 90 +- .../android/quickstep/views/RecentsView.java | 3331 +- .../quickstep/views/RecentsViewContainer.java | 56 +- .../views/SplitInstructionsView.java | 80 +- .../quickstep/views/TaskMenuViewWithArrow.kt | 25 +- .../views/TaskThumbnailViewDeprecated.java | 159 +- .../com/android/quickstep/views/TaskView.kt | 1507 +- .../android/quickstep/views/TaskViewIcon.java | 5 - .../launcher3/model/AppEventProducerTest.java | 23 +- .../TaskbarNavButtonControllerTest.java | 123 +- .../launcher3/taskbar/TaskbarUnitTestRule.kt | 164 + .../allapps/TaskbarAllAppsControllerTest.kt | 153 +- .../animation/BubbleBarViewAnimatorTest.kt | 454 + .../overlay/TaskbarOverlayControllerTest.kt | 170 +- .../quickstep/AllAppsActionManagerTest.kt | 89 +- .../quickstep/FullscreenDrawParamsTest.kt | 141 +- .../LandscapePagedViewHandlerTest.kt | 15 +- .../SeascapePagedViewHandlerTest.kt | 15 +- .../recents/data/FakeRecentTasksDataSource.kt | 6 +- .../data/FakeTaskThumbnailDataSource.kt | 47 +- .../recents/data/FakeTasksRepository.kt | 49 +- .../recents/data/TasksRepositoryTest.kt | 486 +- .../thumbnail/TaskThumbnailViewModelTest.kt | 178 + .../quickstep/util/AppPairsControllerTest.kt | 193 +- .../util/SplitAnimationControllerTest.kt | 60 +- .../util/SplitSelectStateControllerTest.kt | 202 +- .../quickstep/util/TaskGridNavHelperTest.java | 510 + .../quickstep/util/TaskViewSimulatorTest.java | 30 +- .../model/QuickstepModelDelegateTest.kt | 127 + .../WidgetsPredicationUpdateTaskTest.java | 57 +- .../model/WidgetsPredictionsRequesterTest.kt | 221 + .../FallbackTaskbarUIControllerTest.kt | 80 + .../launcher3/taskbar/TaskbarBaseTestCase.kt | 103 + .../TaskbarHoverToolTipControllerTest.java | 219 + .../taskbar/TaskbarKeyguardControllerTest.kt | 111 + .../TaskbarRecentAppsControllerTest.kt | 242 + .../quickstep/AbstractQuickStepTest.java | 49 +- .../quickstep/AbstractTaplTestsTaskbar.java | 16 +- .../quickstep/DesktopSystemShortcutTest.kt | 275 +- .../quickstep/FallbackRecentsTest.java | 75 +- .../quickstep/NavigationModeSwitchRule.java | 14 +- .../OrientationTouchTransformerTest.java | 75 +- .../quickstep/RecentTasksListTest.java | 107 + .../RecentsAnimationDeviceStateTest.kt | 205 + .../android/quickstep/RecentsModelTest.java | 173 + .../TaplDigitalWellBeingToastTest.java | 109 + .../quickstep/TaplOverviewIconTest.java | 41 +- .../quickstep/TaplPrivateSpaceTest.java | 13 +- .../TaplStartLauncherViaGestureTests.java | 72 +- .../TaplTestsKeyboardQuickSwitch.java | 9 - .../quickstep/TaplTestsPersistentTaskbar.java | 6 + .../android/quickstep/TaplTestsQuickstep.java | 433 +- .../quickstep/TaplTestsSplitscreen.java | 53 +- .../android/quickstep/TaplTestsTrackpad.java | 6 + .../quickstep/TaskThumbnailCacheTest.java | 95 + .../com/android/quickstep/TaskViewTest.java | 12 + .../TaskbarPinningControllerTest.kt | 219 + .../TaskbarSpecsEvaluatorTest.kt | 99 + .../util/GestureExclusionManagerTest.kt | 135 + .../shared_x_axis_activity_close_enter.xml | 42 + .../shared_x_axis_activity_close_exit.xml | 41 + .../shared_x_axis_activity_open_enter.xml | 42 + .../shared_x_axis_activity_open_exit.xml | 41 + .../material_color_surface.xml | 19 + .../material_color_surface_bright.xml | 19 + .../material_color_surface_container.xml | 19 + .../material_color_surface_container_high.xml | 19 + ...terial_color_surface_container_highest.xml | 19 + .../material_color_surface_container_low.xml | 19 + ...aterial_color_surface_container_lowest.xml | 6 +- .../material_color_surface_dim.xml | 19 + .../material_color_surface_inverse.xml | 19 + .../material_color_surface_variant.xml | 19 + .../popup_color_background.xml | 20 + res/color-night-v31/popup_shade_first.xml | 5 +- res/color-v31/material_color_surface.xml | 19 + .../material_color_surface_bright.xml | 19 + .../material_color_surface_container.xml | 19 + .../material_color_surface_container_high.xml | 19 + ...terial_color_surface_container_highest.xml | 19 + .../material_color_surface_container_low.xml | 19 + ...aterial_color_surface_container_lowest.xml | 6 +- res/color-v31/material_color_surface_dim.xml | 19 + .../material_color_surface_inverse.xml | 20 + .../material_color_surface_variant.xml | 19 + res/color-v31/popup_color_background.xml | 20 + res/color-v31/popup_shade_first.xml | 5 +- res/color/overview_button.xml | 7 +- res/color/popup_color_background.xml | 18 + res/color/popup_shade_first.xml | 5 +- .../ic_transient_taskbar_all_apps_button.xml | 48 + res/drawable/add_item_dialog_background.xml | 2 +- res/drawable/all_apps_tabs_background.xml | 49 +- res/drawable/bg_ps_header.xml | 2 +- res/drawable/bg_ps_lock_button.xml | 6 +- res/drawable/bg_ps_transition_image.xml | 6 +- res/drawable/bg_ps_unlock_button.xml | 4 +- .../bg_rounded_corner_bottom_sheet_handle.xml | 3 +- .../bg_widgets_header_states_two_pane.xml | 22 +- res/drawable/bg_widgets_header_two_pane.xml | 15 +- .../button_top_rounded_bordered_ripple.xml | 2 +- res/drawable/ic_corp_off.xml | 10 +- res/drawable/ic_info_no_shadow.xml | 2 +- res/drawable/ic_install_no_shadow.xml | 2 +- res/drawable/ic_install_to_private.xml | 2 +- res/drawable/ic_lock.xml | 2 +- .../ic_private_space_with_background.xml | 7 +- res/drawable/ic_ps_settings.xml | 4 +- res/drawable/ic_smartspace.xml | 20 +- res/drawable/ic_split_horizontal.xml | 30 +- res/drawable/ic_split_vertical.xml | 30 +- res/drawable/ic_taskbar_all_apps_button.xml | 48 + .../ic_transient_taskbar_all_apps_button.xml | 48 + res/drawable/ic_uninstall_no_shadow.xml | 2 +- res/drawable/ic_widget.xml | 32 +- res/drawable/icon_menu_arrow_background.xml | 5 +- res/drawable/popup_background.xml | 2 +- res/drawable/private_space_app_divider.xml | 2 +- .../private_space_install_app_icon.xml | 20 +- res/drawable/ps_lock_background.xml | 2 +- res/drawable/ps_settings_background.xml | 2 +- res/drawable/rounded_action_button.xml | 10 +- .../widget_picker_tabs_background.xml | 63 +- res/drawable/work_card.xml | 3 +- res/drawable/work_mode_fab_background.xml | 5 +- res/layout/add_item_confirmation_activity.xml | 3 +- res/layout/all_apps_fast_scroller.xml | 13 - res/layout/all_apps_personal_work_tabs.xml | 4 +- res/layout/launcher.xml | 3 +- res/layout/page_indicator_dots.xml | 22 + res/layout/private_space_header.xml | 10 +- res/layout/user_folder_icon_normalized.xml | 5 +- res/layout/widgets_full_sheet.xml | 10 +- res/layout/widgets_full_sheet_paged_view.xml | 3 +- .../widgets_full_sheet_recyclerview.xml | 1 - res/layout/widgets_list_row_header.xml | 10 +- .../widgets_list_row_header_two_pane.xml | 10 +- res/layout/widgets_two_pane_sheet.xml | 42 +- .../widgets_two_pane_sheet_paged_view.xml | 108 +- .../widgets_two_pane_sheet_recyclerview.xml | 68 +- res/layout/work_apps_edu.xml | 18 +- res/layout/work_mode_fab.xml | 20 +- res/values-af/strings.xml | 49 +- res/values-am/strings.xml | 29 +- res/values-ar/strings.xml | 53 +- res/values-as/strings.xml | 31 +- res/values-az/strings.xml | 31 +- res/values-b+sr+Latn/strings.xml | 29 +- res/values-be/strings.xml | 29 +- res/values-bg/strings.xml | 33 +- res/values-bn/strings.xml | 31 +- res/values-bs/strings.xml | 31 +- res/values-ca/strings.xml | 29 +- res/values-cs/strings.xml | 35 +- res/values-da/strings.xml | 31 +- res/values-de/strings.xml | 29 +- res/values-el/strings.xml | 33 +- res/values-en-rAU/strings.xml | 29 +- res/values-en-rCA/strings.xml | 29 +- res/values-en-rGB/strings.xml | 29 +- res/values-en-rIN/strings.xml | 29 +- res/values-en-rXC/strings.xml | 4 - res/values-es-rUS/strings.xml | 31 +- res/values-es/strings.xml | 29 +- res/values-et/strings.xml | 31 +- res/values-eu/strings.xml | 35 +- res/values-fa/strings.xml | 83 +- res/values-fi/strings.xml | 29 +- res/values-fr-rCA/strings.xml | 33 +- res/values-fr/strings.xml | 33 +- res/values-gl/strings.xml | 33 +- res/values-gu/strings.xml | 37 +- res/values-hi/strings.xml | 33 +- res/values-hr/strings.xml | 31 +- res/values-hu/strings.xml | 31 +- res/values-hy/strings.xml | 33 +- res/values-in/strings.xml | 29 +- res/values-is/strings.xml | 31 +- res/values-it/strings.xml | 35 +- res/values-iw/strings.xml | 43 +- res/values-ja/strings.xml | 31 +- res/values-ka/strings.xml | 31 +- res/values-kk/strings.xml | 31 +- res/values-km/strings.xml | 31 +- res/values-kn/strings.xml | 41 +- res/values-ko/strings.xml | 29 +- res/values-ky/strings.xml | 31 +- res/values-lo/strings.xml | 29 +- res/values-lt/strings.xml | 35 +- res/values-lv/strings.xml | 31 +- res/values-mk/strings.xml | 31 +- res/values-ml/strings.xml | 29 +- res/values-mn/strings.xml | 31 +- res/values-mr/strings.xml | 33 +- res/values-ms/strings.xml | 33 +- res/values-my/strings.xml | 29 +- res/values-nb/strings.xml | 31 +- res/values-ne/strings.xml | 37 +- res/values-night-v31/colors.xml | 42 +- res/values-night-v34/colors.xml | 3 - res/values-night/colors.xml | 124 +- res/values-night/styles.xml | 12 +- res/values-nl/strings.xml | 31 +- res/values-or/strings.xml | 35 +- res/values-pa/strings.xml | 37 +- res/values-pl/strings.xml | 31 +- res/values-pt-rPT/strings.xml | 31 +- res/values-pt/strings.xml | 33 +- res/values-ro/strings.xml | 29 +- res/values-ru/strings.xml | 31 +- res/values-si/strings.xml | 29 +- res/values-sk/strings.xml | 33 +- res/values-sl/strings.xml | 31 +- res/values-sq/strings.xml | 29 +- res/values-sr/strings.xml | 29 +- res/values-sv/strings.xml | 33 +- res/values-sw/strings.xml | 33 +- res/values-sw600dp/styles.xml | 5 +- res/values-ta/strings.xml | 33 +- res/values-te/strings.xml | 37 +- res/values-th/strings.xml | 29 +- res/values-tl/strings.xml | 29 +- res/values-tr/strings.xml | 31 +- res/values-uk/strings.xml | 29 +- res/values-ur/strings.xml | 31 +- res/values-uz/strings.xml | 29 +- res/values-v31/colors.xml | 42 +- res/values-v31/styles.xml | 7 +- res/values-v33/style.xml | 40 + res/values-v34/colors.xml | 104 - res/values-vi/strings.xml | 31 +- res/values-zh-rCN/strings.xml | 35 +- res/values-zh-rHK/strings.xml | 29 +- res/values-zh-rTW/strings.xml | 29 +- res/values-zu/strings.xml | 29 +- res/values/attrs.xml | 78 +- res/values/colors.xml | 223 +- res/values/config.xml | 17 +- res/values/dimens.xml | 72 +- res/values/id.xml | 2 - res/values/strings.xml | 65 +- res/values/styles.xml | 151 +- res/xml/backupscheme.xml | 10 +- res/xml/device_profiles.xml | 41 - res/xml/folder_shapes.xml | 33 + res/xml/launcher_preferences.xml | 9 - res/xml/paddings_6x5.xml | 21 +- settings.gradle | 10 - .../launcher3/AbstractFloatingView.java | 63 +- src/com/android/launcher3/Alarm.java | 11 - src/com/android/launcher3/AppFilter.java | 8 +- .../launcher3/AppWidgetResizeFrame.java | 192 +- .../android/launcher3/AutoInstallsLayout.java | 22 +- src/com/android/launcher3/BaseActivity.java | 135 +- src/com/android/launcher3/BubbleTextView.java | 700 +- .../android/launcher3/ButtonDropTarget.java | 60 +- src/com/android/launcher3/CellLayout.java | 52 +- .../launcher3/CheckLongPressHelper.java | 2 - .../android/launcher3/DeleteDropTarget.java | 6 +- src/com/android/launcher3/DeviceProfile.java | 810 +- src/com/android/launcher3/DropTarget.java | 6 - .../android/launcher3/DropTargetHandler.kt | 45 +- .../launcher3/FastScrollRecyclerView.java | 59 +- .../android/launcher3/GestureNavContract.java | 7 +- src/com/android/launcher3/Hotseat.java | 217 +- .../launcher3/InvariantDeviceProfile.java | 2721 +- src/com/android/launcher3/Launcher.java | 851 +- .../launcher3/LauncherApplication.java | 37 - .../launcher3/LauncherBackupAgent.java | 36 +- .../android/launcher3/LauncherConstants.java | 3 - src/com/android/launcher3/LauncherFiles.java | 14 +- src/com/android/launcher3/LauncherPrefs.kt | 217 +- .../android/launcher3/LauncherProvider.java | 131 +- .../android/launcher3/LauncherRootView.java | 38 +- .../android/launcher3/LauncherSettings.java | 23 +- src/com/android/launcher3/LauncherState.java | 15 +- src/com/android/launcher3/ModelCallbacks.kt | 68 +- .../android/launcher3/MotionEventsUtils.java | 8 +- src/com/android/launcher3/PagedView.java | 25 +- .../launcher3/SecondaryDropTarget.java | 40 +- .../launcher3/SessionCommitReceiver.java | 32 +- .../launcher3/ShortcutAndWidgetContainer.java | 13 +- src/com/android/launcher3/Utilities.java | 473 +- src/com/android/launcher3/Workspace.java | 824 +- .../AccessibleDragListenerAdapter.java | 89 + .../DragAndDropAccessibilityDelegate.java | 13 +- .../LauncherAccessibilityDelegate.java | 54 +- .../ShortcutMenuAccessibilityDelegate.java | 2 + .../WorkspaceAccessibilityHelper.java | 4 +- .../allapps/ActivityAllAppsContainerView.java | 490 +- .../allapps/AllAppsFastScrollHelper.java | 3 - .../launcher3/allapps/AllAppsGridAdapter.java | 7 +- .../allapps/AllAppsRecyclerView.java | 102 +- .../launcher3/allapps/AllAppsStore.java | 32 +- .../allapps/AllAppsTransitionController.java | 296 +- .../allapps/AlphabeticalAppsList.java | 117 +- .../launcher3/allapps/BaseAllAppsAdapter.java | 113 +- .../launcher3/allapps/FloatingHeaderView.java | 45 +- .../launcher3/allapps/FloatingMaskView.java | 21 +- .../allapps/PrivateProfileManager.java | 272 +- .../allapps/SectionDecorationHandler.java | 8 +- .../launcher3/allapps/UserProfileManager.java | 23 +- .../launcher3/allapps/WorkPausedCard.java | 17 +- .../launcher3/allapps/WorkProfileManager.java | 95 +- .../search/AllAppsSearchBarController.java | 26 +- .../anim/AnimatedPropertySetter.java | 25 +- .../launcher3/anim/PendingAnimation.java | 6 +- .../launcher3/apppairs/AppPairIcon.java | 32 - .../apppairs/AppPairIconDrawable.java | 25 - .../apppairs/AppPairIconDrawingParams.kt | 2 - .../launcher3/apppairs/AppPairIconGraphic.kt | 23 +- .../LauncherRestoreEventLogger.kt | 38 +- .../celllayout/ReorderAlgorithm.java | 14 +- .../compat/AlphabeticIndexCompat.java | 7 - .../launcher3/config/FeatureFlags.java | 620 +- .../android/launcher3/dot/FolderDotInfo.java | 13 +- .../launcher3/dragndrop/AddItemActivity.java | 8 +- .../dragndrop/BaseItemDragListener.java | 8 +- .../launcher3/dragndrop/DragController.java | 27 +- .../launcher3/dragndrop/DragLayer.java | 41 +- .../android/launcher3/dragndrop/DragView.java | 33 +- .../dragndrop/FolderAdaptiveIcon.java | 186 +- .../dragndrop/LauncherDragController.java | 46 +- .../PinShortcutRequestActivityInfo.java | 10 +- .../dragndrop/SpringLoadedDragController.java | 75 + .../folder/ClippedFolderIconLayoutRule.java | 41 +- src/com/android/launcher3/folder/Folder.java | 371 +- .../folder/FolderAnimationManager.java | 29 +- .../launcher3/folder/FolderGridOrganizer.java | 9 - .../android/launcher3/folder/FolderIcon.java | 137 +- .../launcher3/folder/FolderNameProvider.java | 14 +- .../launcher3/folder/FolderPagedView.java | 61 +- .../launcher3/folder/PreviewBackground.java | 9 +- .../launcher3/folder/PreviewItemManager.java | 39 +- .../launcher3/graphics/IconPalette.java | 110 + .../graphics/LauncherPreviewRenderer.java | 500 +- .../graphics/PreloadIconDrawable.java | 28 +- .../graphics/PreviewSurfaceRenderer.java | 411 +- .../launcher3/graphics/SysUiScrim.java | 41 +- .../launcher3/icons/ComponentWithLabel.java | 75 + .../android/launcher3/icons/IconCache.java | 509 +- .../icons/MonochromeIconFactory.java | 172 + .../keyboard/ItemFocusIndicatorHelper.java | 4 - .../android/launcher3/logging/FileLog.java | 2 +- .../launcher3/logging/StatsLogManager.java | 356 +- .../model/AddWorkspaceItemsTask.java | 36 +- .../android/launcher3/model/AllAppsList.java | 33 +- .../launcher3/model/BaseLauncherBinder.java | 348 +- .../android/launcher3/model/BgDataModel.java | 333 +- .../launcher3/model/CacheDataUpdatedTask.java | 28 +- .../launcher3/model/DatabaseHelper.java | 24 +- .../launcher3/model/DeviceGridState.java | 95 +- .../launcher3/model/FirstScreenBroadcast.java | 6 +- .../model/FirstScreenBroadcastHelper.kt | 124 +- .../model/GridSizeMigrationUtil.java | 815 + .../launcher3/model/ItemInstallQueue.java | 38 +- .../android/launcher3/model/LoaderCursor.java | 191 +- .../android/launcher3/model/LoaderTask.java | 540 +- .../launcher3/model/ModelDbController.java | 421 +- .../launcher3/model/ModelDelegate.java | 46 +- .../launcher3/model/ModelLauncherCallbacks.kt | 49 +- .../launcher3/model/ModelTaskController.kt | 37 +- .../android/launcher3/model/ModelUtils.java | 65 +- .../android/launcher3/model/ModelWriter.java | 71 +- .../model/PackageInstallStateChangedTask.java | 25 +- .../launcher3/model/PackageUpdatedTask.java | 198 +- .../model/SdCardAvailableReceiver.java | 13 +- .../launcher3/model/ShortcutsChangedTask.java | 136 + .../model/UserLockStateChangedTask.java | 6 +- .../android/launcher3/model/WidgetItem.java | 61 +- .../android/launcher3/model/WidgetsModel.java | 262 +- .../launcher3/model/WorkspaceItemProcessor.kt | 201 +- .../model/WorkspaceItemSpaceFinder.java | 46 +- .../android/launcher3/model/data/AppInfo.java | 32 +- .../launcher3/model/data/AppPairInfo.kt | 24 +- .../launcher3/model/data/CollectionInfo.kt | 6 + .../launcher3/model/data/FolderInfo.java | 156 +- .../launcher3/model/data/IconRequestInfo.java | 20 +- .../launcher3/model/data/ItemInfo.java | 37 +- .../model/data/ItemInfoWithIcon.java | 33 +- .../model/data/LauncherAppWidgetInfo.java | 8 +- .../model/data/SearchActionItemInfo.java | 22 +- .../model/data/WorkspaceItemInfo.java | 64 +- .../notification/NotificationKeyData.java | 10 +- .../notification/NotificationListener.java | 13 - .../pageindicators/PageIndicator.java | 10 - .../pageindicators/PageIndicatorDots.java | 445 +- .../WorkspacePageIndicator.java | 265 + .../launcher3/pm/InstallSessionHelper.java | 33 +- .../launcher3/pm/InstallSessionTracker.java | 24 +- .../launcher3/pm/PinRequestHelper.java | 9 +- .../pm/ShortcutConfigActivityInfo.java | 40 +- src/com/android/launcher3/pm/UserCache.java | 72 +- .../android/launcher3/popup/ArrowPopup.java | 230 +- .../popup/PopupContainerWithArrow.java | 63 +- .../launcher3/popup/PopupDataProvider.java | 155 +- .../popup/PopupLiveUpdateHandler.java | 22 +- .../launcher3/popup/PopupPopulator.java | 13 +- .../launcher3/popup/SystemShortcut.java | 289 +- .../launcher3/provider/RestoreDbTask.java | 193 +- .../launcher3/qsb/QsbContainerView.java | 5 +- .../recyclerview/AllAppsRecyclerViewPool.kt | 96 +- .../search/StringMatcherUtility.java | 22 +- .../SecondaryDisplayLauncher.java | 104 +- .../secondarydisplay/SecondaryDragLayer.java | 36 +- .../launcher3/settings/SettingsActivity.java | 116 +- .../shortcuts/DeepShortcutTextView.java | 7 - .../launcher3/statemanager/BaseState.java | 21 +- .../launcher3/statemanager/StateManager.java | 164 +- .../statemanager/StatefulActivity.java | 12 +- .../statemanager/StatefulContainer.java | 12 +- .../android/launcher3/states/EditModeState.kt | 6 +- .../android/launcher3/states/HintState.java | 5 +- .../launcher3/states/RotationHelper.java | 35 +- .../launcher3/states/SpringLoadedState.java | 3 +- .../states/StateAnimationConfig.java | 4 +- .../testing/TestInformationHandler.java | 68 +- .../testing/TestInformationProvider.java | 69 +- .../testing/shared/TestProtocol.java | 45 +- .../AbstractStateChangeTouchController.java | 6 +- .../touch/AllAppsSwipeController.java | 71 +- .../launcher3/touch/BaseSwipeDetector.java | 9 +- .../launcher3/touch/ItemClickHandler.java | 28 +- .../touch/ItemLongClickListener.java | 2 +- .../touch/WorkspaceTouchListener.java | 11 +- .../util/ActivityOptionsWrapper.java | 1 - .../launcher3/util/ActivityTracker.java | 117 + .../android/launcher3/util/ApiWrapper.java | 88 +- .../launcher3/util/BackPressHandler.java | 3 - .../android/launcher3/util/ContentWriter.java | 3 +- .../android/launcher3/util/DimensionUtils.kt | 5 +- .../launcher3/util/DisplayController.java | 557 +- .../launcher3/util/DynamicResource.java | 26 +- .../launcher3/util/EdgeEffectCompat.java | 15 + src/com/android/launcher3/util/Executors.java | 34 +- .../launcher3/util/IntSparseArrayMap.java | 6 - .../android/launcher3/util/ItemInflater.kt | 10 +- .../util/LauncherBindableItemsContainer.java | 124 + .../android/launcher3/util/LockedUserState.kt | 73 +- src/com/android/launcher3/util/LogConfig.java | 5 +- .../launcher3/util/LooperExecutor.java | 118 + .../util/MainThreadInitializedObject.java | 134 +- .../util/MultiTranslateDelegate.java | 4 +- .../android/launcher3/util/OnboardingPrefs.kt | 3 +- .../launcher3/util/OverlayEdgeEffect.java | 1 - .../launcher3/util/PackageManagerHelper.java | 244 +- .../launcher3/util/PluginManagerWrapper.java | 23 +- .../launcher3/util/ScreenOnTracker.java | 42 +- .../android/launcher3/util/SettingsCache.java | 69 +- .../android/launcher3/util/ShortcutUtil.java | 8 + .../util/SimpleBroadcastReceiver.java | 166 +- .../util/SplitConfigurationOptions.java | 75 +- .../android/launcher3/util/StableViewInfo.kt | 8 +- .../launcher3/util/SystemUiController.java | 13 +- src/com/android/launcher3/util/Themes.java | 51 +- .../launcher3/util/VibratorWrapper.java | 156 +- src/com/android/launcher3/util/ViewCache.java | 5 +- .../launcher3/util/ViewOnDrawExecutor.java | 8 +- src/com/android/launcher3/util/ViewPool.java | 28 +- .../launcher3/util/WallpaperColorHints.kt | 50 +- .../util/WallpaperOffsetInterpolator.java | 71 +- src/com/android/launcher3/util/rects/Rects.kt | 18 - .../util/window/RefreshRateTracker.java | 83 + .../util/window/WindowManagerProxy.java | 212 +- .../launcher3/views/AbstractSlideInView.java | 5 +- .../views/AccessibilityActionsView.java | 5 +- .../launcher3/views/ActivityContext.java | 240 +- .../android/launcher3/views/ArrowTipView.java | 52 +- .../launcher3/views/BaseDragLayer.java | 18 +- .../android/launcher3/views/ClipIconView.java | 28 +- .../launcher3/views/ComposeInitializer.java | 229 + .../views/DoubleShadowBubbleTextView.java | 146 +- .../launcher3/views/FloatingIconView.java | 8 +- .../views/FloatingIconViewCompanion.java | 4 +- .../launcher3/views/FloatingSurfaceView.java | 36 +- .../launcher3/views/OptionsPopupView.java | 40 +- .../views/RecyclerViewFastScroller.java | 104 +- .../android/launcher3/views/ScrimView.java | 7 +- .../launcher3/views/SpringRelativeLayout.java | 8 +- .../launcher3/views/StickyHeaderLayout.java | 14 +- .../widget/AddItemWidgetsBottomSheet.java | 8 +- .../widget/BaseLauncherAppWidgetHostView.java | 6 +- .../launcher3/widget/BaseWidgetSheet.java | 102 +- .../widget/DatabaseWidgetPreviewLoader.java | 112 +- .../widget/LauncherAppWidgetHost.java | 121 +- .../widget/LauncherAppWidgetHostView.java | 63 +- .../widget/LauncherAppWidgetProviderInfo.java | 79 +- .../widget/LauncherWidgetHolder.java | 343 +- .../launcher3/widget/LocalColorExtractor.java | 5 - .../widget/PendingAddWidgetInfo.java | 5 +- .../widget/PendingAppWidgetHostView.java | 191 +- .../widget/PendingItemDragHelper.java | 4 +- .../widget/RoundedCornerEnforcement.java | 16 +- .../android/launcher3/widget/WidgetCell.java | 206 +- .../launcher3/widget/WidgetImageView.java | 7 - .../launcher3/widget/WidgetInflater.kt | 35 +- .../launcher3/widget/WidgetManagerHelper.java | 15 +- .../launcher3/widget/WidgetsBottomSheet.java | 13 +- .../custom/CustomAppWidgetProviderInfo.java | 8 +- .../widget/custom/CustomWidgetManager.java | 126 +- .../widget/model/WidgetListSpaceEntry.java | 5 - .../widget/model/WidgetsListBaseEntry.java | 3 - .../widget/model/WidgetsListContentEntry.java | 5 - .../widget/model/WidgetsListHeaderEntry.java | 6 - .../android/launcher3/widget/picker/OWNERS | 1 + .../picker/WidgetRecommendationCategory.java | 6 - .../WidgetRecommendationCategoryProvider.java | 32 +- .../picker/WidgetRecommendationsView.java | 183 +- .../widget/picker/WidgetsFullSheet.java | 494 +- .../widget/picker/WidgetsListAdapter.java | 111 +- .../widget/picker/WidgetsListHeader.java | 17 +- .../picker/WidgetsListItemAnimator.java | 10 - .../WidgetsListTableViewHolderBinder.java | 96 +- .../WidgetsRecommendationTableLayout.java | 58 +- .../widget/picker/WidgetsTwoPaneSheet.java | 296 +- .../search/LauncherWidgetsSearchBar.java | 4 +- .../search/SimpleWidgetsSearchAlgorithm.java | 10 +- .../picker/search/WidgetsSearchBar.java | 17 +- .../search/WidgetsSearchBarController.java | 10 +- .../util/WidgetPreviewContainerSizes.kt | 1 - .../widget/util/WidgetsTableUtils.java | 18 +- .../PersonalWorkSlidingTabStrip.java | 18 +- .../uioverrides/states/AllAppsState.java | 7 +- .../uioverrides/states/OverviewState.java | 5 +- .../shared/LauncherOverlayManager.java | 36 +- systemUI/README.md | 6 +- systemUI/anim/Android.bp | 14 +- systemUI/anim/build.gradle | 14 +- systemUI/anim/res/values/ids.xml | 5 +- .../animation/ActivityTransitionAnimator.kt | 930 +- .../animation/DialogTransitionAnimator.kt | 89 +- .../android/systemui/animation/Expandable.kt | 12 +- .../systemui/animation/FontInterpolator.kt | 205 +- .../systemui/animation/FontVariationUtils.kt | 69 +- ...GhostedViewTransitionAnimatorController.kt | 110 +- .../RemoteAnimationRunnerCompat.java | 111 +- .../systemui/animation/TextAnimator.kt | 341 +- .../systemui/animation/TextInterpolator.kt | 44 +- .../systemui/animation/TransitionAnimator.kt | 960 +- .../animation/ViewHierarchyAnimator.kt | 233 +- .../animation/back/BackAnimationSpec.kt | 7 +- .../back/OnBackAnimationCallbackExtension.kt | 20 +- .../turbulencenoise/TurbulenceNoiseShader.kt | 36 +- .../src/com/android/systemui/util/Dialog.kt | 3 + systemUI/common/Android.bp | 11 +- systemUI/common/README.md | 8 +- systemUI/common/build.gradle | 3 +- systemUI/log/Android.bp | 6 +- systemUI/log/build.gradle | 3 +- .../src/com/android/systemui/log/LogBuffer.kt | 14 +- .../com/android/systemui/log/core/LogLevel.kt | 2 +- systemUI/plugin/Android.bp | 27 +- .../plugins/BcSmartspaceConfigPlugin.kt | 4 - .../plugins/BcSmartspaceDataPlugin.java | 24 - systemUI/plugin/build.gradle | 3 +- .../systemui/plugins/ActivityStarter.java | 42 +- .../systemui/plugins/DarkIconDispatcher.java | 3 - .../systemui/plugins/VolumeDialog.java | 1 - .../plugins/VolumeDialogController.java | 5 +- .../systemui/plugins/clocks/AlarmData.kt | 6 + .../plugins/clocks/ClockProviderPlugin.kt | 122 + .../systemui/plugins/clocks/WeatherData.kt | 123 + .../systemui/plugins/clocks/ZenData.kt | 22 + .../plugins/log/TableLogBufferBase.kt | 12 +- .../com/android/systemui/plugins/qs/QS.java | 40 +- .../android/systemui/plugins/qs/QSTile.java | 49 +- .../statusbar/NotificationMenuRowPlugin.java | 5 +- systemUI/plugin_core/Android.bp | 65 +- systemUI/plugin_core/build.gradle | 3 +- systemUI/plugin_core/proguard.flags | 5 +- .../com/android/systemui/plugins/Plugin.java | 3 - systemUI/shared/Android.bp | 18 +- systemUI/shared/build.gradle | 12 +- systemUI/shared/res/values/bools.xml | 2 +- .../compat/LawnchairQuickstepCompat.kt | 6 - .../shared/condition/CombinedCondition.kt | 65 +- .../shared/condition/ConditionExtensions.kt | 20 +- .../pip/PipSurfaceTransactionHelper.java | 11 +- .../shared/plugins/PluginEnabler.java | 3 +- .../shared/plugins/PluginInstance.java | 133 +- .../shared/plugins/PluginManagerImpl.java | 8 +- .../shared/recents/ISystemUiProxy.aidl | 23 +- .../systemui/shared/recents/model/Task.java | 93 +- .../utilities/PreviewPositionHelper.java | 23 +- .../shared/recents/utilities/Utilities.java | 60 +- .../recents/view/RecentsTransition.java | 30 + .../shared/regionsampling/RegionSampler.kt | 8 +- .../rotation/FloatingRotationButton.java | 11 +- .../rotation/FloatingRotationButtonView.java | 16 - .../rotation/RotationButtonController.java | 40 +- .../shared/shadow/DoubleShadowTextView.kt | 30 +- .../shared/system/ActivityManagerWrapper.java | 155 +- .../systemui/shared/system/BlurUtils.java | 4 +- .../system/InteractionJankMonitorWrapper.java | 12 +- .../shared/system/QuickStepContract.java | 95 +- .../RecentsAnimationControllerCompat.java | 62 +- .../system/RecentsAnimationListener.java | 22 +- .../system/TaskStackChangeListeners.java | 8 +- .../UncaughtExceptionPreHandlerManager.kt | 11 +- systemUI/unfold/build.gradle | 8 +- .../config/ResourceUnfoldTransitionConfig.kt | 46 +- .../unfold/updates/DeviceFoldStateProvider.kt | 10 +- .../unfold/updates/RotationChangeProvider.kt | 2 +- systemUI/viewcapture/Android.bp | 23 +- systemUI/viewcapture/AndroidManifest.xml | 7 +- systemUI/viewcapture/OWNERS | 1 - systemUI/viewcapture/README.md | 8 +- systemUI/viewcapture/build.gradle | 13 +- .../app/viewcapture/NoOpViewCapture.kt | 2 +- .../viewcapture/SettingsAwareViewCapture.kt | 61 +- .../android/app/viewcapture/ViewCapture.java | 360 +- .../viewcapture/ViewCaptureDataSource.java | 30 +- .../app/viewcapture/ViewCaptureFactory.kt | 76 +- .../viewcapture/tests/AndroidManifest.xml | 5 - .../SettingsAwareViewCaptureTest.kt | 8 + tests/Android.bp | 58 +- tests/AndroidManifest-common.xml | 45 +- tests/AndroidManifest.xml | 4 +- tests/Launcher3Tests.xml | 5 - tests/assets/ReorderWidgets/full_reorder_case | 8 +- tests/assets/ReorderWidgets/push_reorder_case | 20 +- .../assets/ReorderWidgets/simple_reorder_case | 2 +- .../flagged_result5x5to5x8.db | Bin 0 -> 77824 bytes .../GridMigrationTest/result5x5to3x3.db | Bin 77824 -> 77824 bytes .../GridMigrationTest/result5x5to4x7.db | Bin 77824 -> 77824 bytes .../GridMigrationTest/result5x5to5x8.db | Bin 77824 -> 77824 bytes .../DeviceProfileDumpTest/phonePortrait.txt | 8 +- .../phonePortrait3Button.txt | 8 +- .../phoneVerticalBar.txt | 8 +- .../phoneVerticalBar3Button.txt | 8 +- .../DeviceProfileDumpTest/tabletLandscape.txt | 2 - .../tabletLandscape3Button.txt | 2 - .../DeviceProfileDumpTest/tabletPortrait.txt | 2 - .../tabletPortrait3Button.txt | 2 - .../twoPanelLandscape.txt | 2 - .../twoPanelLandscape3Button.txt | 2 - ...twoPanelLandscape3Button_decoupleDepth.txt | 2 - .../twoPanelLandscape_decoupleDepth.txt | 2 - .../twoPanelPortrait.txt | 2 - .../twoPanelPortrait3Button.txt | 2 - .../twoPanelPortrait3Button_decoupleDepth.txt | 2 - .../twoPanelPortrait_decoupleDepth.txt | 2 - .../shared/AndroidManifest.xml | 21 + .../com/android/launcher3/testing/OWNERS | 1 + .../shared/HotseatCellCenterRequest.java | 99 + .../testing/shared/ResourceUtils.java | 85 + .../shared/TestInformationRequest.java | 29 + .../testing/shared/TestProtocol.java | 194 + .../shared/WorkspaceCellCenterRequest.java | 138 + .../launcher3/AbstractDeviceProfileTest.kt | 122 +- .../AppWidgetsRestoredReceiverTest.kt | 4 +- .../launcher3/AutoInstallsLayoutTest.kt | 31 +- .../android/launcher3/DeleteDropTargetTest.kt | 28 - .../FakeInvariantDeviceProfileTest.kt | 77 +- .../android/launcher3/LauncherPrefsTest.kt | 61 +- .../com/android/launcher3/UtilitiesTest.kt | 293 +- .../allapps/AlphabeticalAppsListTest.java | 19 +- .../celllayout/CellLayoutTestCaseReader.java | 2 +- .../celllayout/FavoriteItemsTransaction.java | 14 +- .../celllayout/HotseatReorderUnitTest.kt | 6 +- .../celllayout/ReorderAlgorithmUnitTest.java | 8 +- .../UnitTestCellLayoutBuilderRule.kt | 20 +- .../celllayout/board/CellLayoutBoard.java | 32 +- .../launcher3/celllayout/board/CellType.java | 32 + .../celllayout/board/FolderPoint.java | 37 + .../launcher3/celllayout/board/IconPoint.java | 45 + .../board/IdenticalBoardComparator.kt | 6 +- .../board/TestWorkspaceBuilder.java | 192 + .../celllayout/board/WidgetRect.java | 59 + .../launcher3/icons/IconCacheTest.java | 208 +- .../launcher3/icons/UserBadgeDrawableTest.kt | 30 +- .../launcher3/model/DatabaseHelperTest.kt | 22 +- .../launcher3/model/FactitiousDbController.kt | 60 + .../model/GridSizeMigrationUtilTest.kt | 772 + .../launcher3/ui/BubbleTextViewTest.java | 162 +- .../util/ActivityContextWrapper.java | 23 +- .../launcher3/util/DisplayControllerTest.kt | 173 +- .../launcher3/util/LauncherModelHelper.java | 92 +- .../launcher3/util/LockedUserStateTest.kt | 20 +- .../launcher3/util/ModelTestExtensions.kt | 32 +- .../util/PackageManagerHelperTest.java | 86 + .../util/TestSandboxModelContextWrapper.java | 22 +- .../com/android/launcher3/util/TestUtil.java | 35 +- .../android/launcher3/util/WidgetUtils.java | 22 +- .../util/rule/TestStabilityRule.java | 5 +- .../launcher3/widget/GeneratedPreviewTest.kt | 131 +- .../LauncherAppWidgetProviderInfoTest.java | 26 +- .../android/launcher3/widget/picker/OWNERS | 1 + ...getRecommendationCategoryProviderTest.java | 40 +- ...WidgetsListHeaderViewHolderBinderTest.java | 15 +- .../WidgetsListTableViewHolderBinderTest.java | 16 +- .../model/WidgetsListContentEntryTest.java | 9 +- .../SimpleWidgetsSearchAlgorithmTest.java | 21 +- .../util/WidgetPreviewContainerSizesTest.kt | 10 +- .../picker/util/WidgetsTableUtilsTest.java | 30 +- .../com/android/launcher3/testing/OWNERS | 4 + .../android/launcher3/LauncherIntentTest.java | 19 +- .../allapps/PrivateProfileManagerTest.java | 222 + .../allapps/PrivateSpaceHeaderViewTest.java | 467 + .../PrivateSpaceSettingsButtonTest.java | 56 + .../allapps/TaplKeyboardFocusTest.java | 108 + .../allapps/TaplOpenCloseAllAppsTest.java | 3 + .../BackupAndRestoreDBSelectionTest.kt | 42 +- .../celllayout/TaplReorderWidgetsTest.java | 312 + .../ValidGridMigrationTestCaseGenerator.kt | 175 + .../compat/TaplPromiseIconUiTest.java | 175 + .../launcher3/dragging/TaplDragTest.java | 12 +- .../dragging/TaplUninstallRemoveTest.java | 35 +- .../folder/FolderNameProviderTest.java | 85 + .../folder/PreviewBackgroundTest.java | 325 + .../folder/PreviewItemManagerTest.kt | 223 +- .../model/AddWorkspaceItemsTaskTest.kt | 225 + .../launcher3/model/AsyncBindingTest.kt | 212 + .../model/CacheDataUpdatedTaskTest.java | 161 + .../model/DbDowngradeHelperTest.java | 234 + .../model/DefaultLayoutProviderTest.java | 167 + .../model/FirstScreenBroadcastHelperTest.kt | 386 + .../launcher3/model/FolderIconLoadTest.kt | 184 + .../launcher3/model/GridMigrationTest.kt | 180 +- .../launcher3/model/LoaderCursorTest.java | 236 + .../android/launcher3/model/LoaderTaskTest.kt | 548 +- .../PackageInstallStateChangedTaskTest.java | 122 + .../model/WorkspaceItemProcessorTest.kt | 847 + .../model/WorkspaceItemSpaceFinderTest.kt | 179 + .../model/gridmigration/GridMigrationUtils.kt | 114 + .../ValidGridMigrationUnitTest.kt | 203 + .../nonquickstep/DeviceProfileDumpTest.kt | 35 +- .../launcher3/popup/SystemShortcutTest.java | 99 +- .../launcher3/provider/RestoreDbTaskTest.java | 417 + .../testcomponent/TouchEventGenerator.java | 275 + .../touch/SingleAxisSwipeDetectorTest.java | 200 + .../launcher3/ui/AbstractLauncherUiTest.java | 468 +- .../launcher3/ui/PortraitLandscapeRunner.java | 14 - .../launcher3/ui/TaplTestsLauncher3Test.java | 4 +- .../launcher3/ui/TaplWorkProfileTest.java | 234 + .../ui/widget/TaplAddConfigWidgetTest.java | 173 + .../ui/widget/TaplAddWidgetTest.java | 14 +- .../ui/widget/TaplBindWidgetTest.java | 319 + .../ui/widget/TaplRequestPinItemTest.java | 195 + .../ui/widget/TaplWidgetPickerTest.java | 86 + .../ui/workspace/TaplThemeIconsTest.java | 177 + .../workspace/TaplTwoPanelWorkspaceTest.java | 8 + .../ui/workspace/TaplWorkspaceTest.java | 16 +- .../src/com/android/launcher3/util/Wait.java | 66 + .../rule/ExtendedLongPressTimeoutRule.java | 5 - .../launcher3/util/rule/FailureWatcher.java | 4 +- .../launcher3/util/rule/ShellCommandRule.java | 12 +- .../android/launcher3/widget/picker/OWNERS | 1 + .../com/android/launcher3/tapl/AppIcon.java | 13 +- .../android/launcher3/tapl/AppIconMenu.java | 7 - .../android/launcher3/tapl/Background.java | 36 +- .../android/launcher3/tapl/BaseOverview.java | 166 +- .../tapl/com/android/launcher3/tapl/Home.java | 3 +- .../launcher3/tapl/KeyboardQuickSwitch.java | 26 - .../android/launcher3/tapl/Launchable.java | 6 + .../launcher3/tapl/LaunchedAppState.java | 15 - .../tapl/LauncherInstrumentation.java | 253 +- .../launcher3/tapl/LogEventChecker.java | 14 +- .../com/android/launcher3/tapl/Overview.java | 7 +- .../android/launcher3/tapl/OverviewTask.java | 144 +- .../launcher3/tapl/OverviewTaskMenu.java | 31 +- .../launcher3/tapl/OverviewTaskMenuItem.java | 39 + .../com/android/launcher3/tapl/Taskbar.java | 19 +- .../android/launcher3/tapl/TestHelpers.java | 7 +- .../com/android/launcher3/tapl/Widgets.java | 74 +- .../com/android/launcher3/tapl/Workspace.java | 63 +- .../launcher3/tapl/WorkspaceDragSource.java | 6 + wmshell/build.gradle | 18 +- .../bubble_drop_target_background_color.xml | 2 +- ...de_maximize_menu_button_color_selector.xml | 2 +- .../bubble_drop_target_background.xml | 2 +- wmshell/res/drawable/bubble_manage_btn_bg.xml | 2 +- .../res/drawable/bubble_manage_menu_bg.xml | 2 +- .../res/drawable/decor_back_button_dark.xml | 1 - ...ecor_desktop_mode_maximize_button_dark.xml | 3 +- wmshell/res/drawable/decor_handle_dark.xml | 24 +- .../drawable/decor_maximize_button_dark.xml | 17 +- ...ktop_mode_decor_handle_menu_background.xml | 2 +- .../desktop_mode_maximize_menu_background.xml | 2 +- ...p_mode_maximize_menu_layout_background.xml | 4 +- ...esktop_windowing_transition_background.xml | 6 +- .../letterbox_education_dialog_background.xml | 2 +- ...ation_dismiss_button_background_ripple.xml | 2 +- .../letterbox_education_ic_light_bulb.xml | 15 +- .../letterbox_education_ic_reposition.xml | 14 +- .../letterbox_education_ic_split_screen.xml | 6 +- ...erbox_restart_button_background_ripple.xml | 2 +- .../letterbox_restart_dialog_background.xml | 2 +- ...start_dismiss_button_background_ripple.xml | 4 +- .../letterbox_restart_header_ic_arrows.xml | 16 +- .../res/layout/bubble_bar_expanded_view.xml | 3 +- .../layout/bubble_bar_manage_education.xml | 28 +- wmshell/res/layout/bubble_bar_menu_item.xml | 3 +- wmshell/res/layout/bubble_bar_menu_view.xml | 20 +- .../res/layout/bubble_bar_stack_education.xml | 24 +- wmshell/res/layout/bubble_flyout.xml | 6 +- wmshell/res/layout/bubble_manage_button.xml | 4 +- wmshell/res/layout/bubble_manage_menu.xml | 41 +- wmshell/res/layout/caption_window_decor.xml | 3 + wmshell/res/layout/compat_ui_layout.xml | 32 + .../res/layout/desktop_mode_app_handle.xml | 4 +- .../res/layout/desktop_mode_app_header.xml | 36 +- .../desktop_mode_window_decor_handle_menu.xml | 150 +- ...esktop_mode_window_decor_maximize_menu.xml | 212 +- ...tterbox_education_dialog_action_layout.xml | 10 +- .../letterbox_education_dialog_layout.xml | 19 +- .../letterbox_restart_dialog_layout.xml | 24 +- wmshell/res/layout/maximize_menu_button.xml | 20 +- wmshell/res/values-af/strings.xml | 64 +- wmshell/res/values-am/strings.xml | 56 +- wmshell/res/values-ar/strings.xml | 56 +- wmshell/res/values-as/strings.xml | 56 +- wmshell/res/values-az/strings.xml | 56 +- wmshell/res/values-b+sr+Latn/strings.xml | 56 +- wmshell/res/values-be/strings.xml | 58 +- wmshell/res/values-bg/strings.xml | 56 +- wmshell/res/values-bn/strings.xml | 56 +- wmshell/res/values-bs/strings.xml | 58 +- wmshell/res/values-ca/strings.xml | 56 +- wmshell/res/values-cs/strings.xml | 56 +- wmshell/res/values-da/strings.xml | 60 +- wmshell/res/values-de/strings.xml | 56 +- wmshell/res/values-el/strings.xml | 56 +- wmshell/res/values-en-rAU/strings.xml | 56 +- wmshell/res/values-en-rCA/strings.xml | 56 +- wmshell/res/values-en-rGB/strings.xml | 56 +- wmshell/res/values-en-rIN/strings.xml | 56 +- wmshell/res/values-en-rXC/strings.xml | 24 +- wmshell/res/values-es-rUS/strings.xml | 56 +- wmshell/res/values-es/strings.xml | 56 +- wmshell/res/values-et/strings.xml | 56 +- wmshell/res/values-eu/strings.xml | 58 +- wmshell/res/values-fa/strings.xml | 60 +- wmshell/res/values-fa/strings_tv.xml | 2 +- wmshell/res/values-fi/strings.xml | 56 +- wmshell/res/values-fr-rCA/strings.xml | 58 +- wmshell/res/values-fr/strings.xml | 56 +- wmshell/res/values-gl/strings.xml | 56 +- wmshell/res/values-gu/strings.xml | 56 +- wmshell/res/values-hi/strings.xml | 58 +- wmshell/res/values-hr/strings.xml | 56 +- wmshell/res/values-hu/strings.xml | 56 +- wmshell/res/values-hy/strings.xml | 60 +- wmshell/res/values-in/strings.xml | 56 +- wmshell/res/values-is/strings.xml | 56 +- wmshell/res/values-it/strings.xml | 60 +- wmshell/res/values-iw/strings.xml | 82 +- wmshell/res/values-ja/strings.xml | 58 +- wmshell/res/values-ka/strings.xml | 56 +- wmshell/res/values-kk/strings.xml | 56 +- wmshell/res/values-km/strings.xml | 56 +- wmshell/res/values-kn/strings.xml | 68 +- wmshell/res/values-kn/strings_tv.xml | 2 +- wmshell/res/values-ko/strings.xml | 56 +- wmshell/res/values-ky/strings.xml | 56 +- wmshell/res/values-lo/strings.xml | 56 +- wmshell/res/values-lt/strings.xml | 56 +- wmshell/res/values-lv/strings.xml | 56 +- wmshell/res/values-mk/strings.xml | 56 +- wmshell/res/values-ml/strings.xml | 56 +- wmshell/res/values-mn/strings.xml | 56 +- wmshell/res/values-mr/strings.xml | 56 +- wmshell/res/values-ms/strings.xml | 56 +- wmshell/res/values-my/strings.xml | 56 +- wmshell/res/values-nb/strings.xml | 56 +- wmshell/res/values-ne/strings.xml | 56 +- wmshell/res/values-nl/strings.xml | 58 +- wmshell/res/values-or/strings.xml | 56 +- wmshell/res/values-pa/strings.xml | 56 +- wmshell/res/values-pl/strings.xml | 56 +- wmshell/res/values-pt-rBR/strings.xml | 62 +- wmshell/res/values-pt-rPT/strings.xml | 56 +- wmshell/res/values-pt/strings.xml | 62 +- wmshell/res/values-ro/strings.xml | 56 +- wmshell/res/values-ru/strings.xml | 56 +- wmshell/res/values-si/strings.xml | 56 +- wmshell/res/values-sk/strings.xml | 56 +- wmshell/res/values-sl/strings.xml | 56 +- wmshell/res/values-sq/strings.xml | 56 +- wmshell/res/values-sr/strings.xml | 56 +- wmshell/res/values-sv/strings.xml | 56 +- wmshell/res/values-sw/strings.xml | 56 +- wmshell/res/values-ta/strings.xml | 56 +- wmshell/res/values-te/strings.xml | 60 +- wmshell/res/values-th/strings.xml | 56 +- wmshell/res/values-tl/strings.xml | 56 +- wmshell/res/values-tr/strings.xml | 56 +- wmshell/res/values-uk/strings.xml | 56 +- wmshell/res/values-ur/strings.xml | 56 +- wmshell/res/values-uz/strings.xml | 56 +- wmshell/res/values-vi/strings.xml | 56 +- wmshell/res/values-zh-rCN/strings.xml | 56 +- wmshell/res/values-zh-rHK/strings.xml | 56 +- wmshell/res/values-zh-rTW/strings.xml | 56 +- wmshell/res/values-zu/strings.xml | 56 +- wmshell/res/values/attrs.xml | 7 - wmshell/res/values/colors.xml | 7 +- wmshell/res/values/config.xml | 11 +- wmshell/res/values/dimen.xml | 206 +- wmshell/res/values/ids.xml | 8 - wmshell/res/values/integers.xml | 12 - wmshell/res/values/strings.xml | 105 +- wmshell/res/values/strings_tv.xml | 2 - wmshell/res/values/styles.xml | 45 +- .../shell/shared/IHomeTransitionListener.aidl | 33 + .../wm/shell/shared/IShellTransitions.aidl | 62 + .../wm/shell/shared/ShellTransitions.java | 13 - .../wm/shell/shared/TransitionUtil.java | 139 +- .../shell/shared/animation/PhysicsAnimator.kt | 5 - .../animation/PhysicsAnimatorTestUtils.kt | 30 +- .../wm/shell/bubbles/BubbleStackView.java | 2 +- .../ExpandedAnimationController.java | 2 +- .../animation/StackAnimationController.java | 2 +- .../bubbles/bar/BubbleBarAnimationHelper.java | 2 +- .../BubbleBarExpandedViewDragController.kt | 2 +- .../DesktopModeTransitionSource.aidl | 4 +- .../DesktopModeTransitionSource.kt | 2 +- .../common/magnetictarget/MagnetizedObject.kt | 4 +- .../wm/shell/dagger/WMShellBaseModule.java | 7 +- .../wm/shell/desktopmode/DesktopMode.java | 2 +- .../DesktopModeShellCommandHandler.kt | 2 +- .../desktopmode/DesktopModeTransitionTypes.kt | 2 +- .../desktopmode/DesktopTasksController.kt | 2 +- .../EnterDesktopTaskTransitionHandler.java | 2 +- .../ExitDesktopTaskTransitionHandler.java | 2 +- .../wm/shell/desktopmode/IDesktopMode.aidl | 4 +- ...PipAccessibilityInteractionConnection.java | 2 +- .../pip/phone/PipDismissTargetHandler.java | 2 +- .../wm/shell/pip/phone/PipMotionHelper.java | 2 +- .../pip2/phone/PipDismissTargetHandler.java | 2 +- .../wm/shell/pip2/phone/PipMotionHelper.java | 2 +- .../DesktopModeWindowDecorViewModel.java | 2 +- .../magnetictarget/MagnetizedObjectTest.kt | 4 +- .../DesktopModeTransitionTypesTest.kt | 10 +- .../desktopmode/DesktopTasksControllerTest.kt | 2 +- .../ExitDesktopTaskTransitionHandlerTest.java | 2 +- 1577 files changed, 112563 insertions(+), 80248 deletions(-) create mode 100644 lawnchair/res/drawable/ic_work_app_badge.xml create mode 100644 lawnchair/res/font/inter_bold.ttf create mode 100644 lawnchair/res/font/inter_medium.ttf create mode 100644 lawnchair/res/font/inter_regular.ttf create mode 100644 lawnchair/res/font/inter_semi_bold.ttf create mode 100644 lawnchair/src/app/lawnchair/LauncherActivityCachingLogic.kt create mode 100644 quickstep/res/drawable/bg_bubble_dismiss_circle.xml create mode 100644 quickstep/res/drawable/bg_circle.xml create mode 100644 quickstep/res/drawable/ic_bubble_dismiss_white.xml create mode 100644 quickstep/res/drawable/keyboard_quick_switch_overview_button_background.xml create mode 100644 quickstep/res/layout/keyboard_quick_switch_textonly_taskview.xml create mode 100644 quickstep/res/raw-h480dp/all_set_page_bg.json create mode 100644 quickstep/res/raw-land/all_set_page_bg.json create mode 100644 quickstep/res/raw-sw600dp-land/all_set_page_bg.json create mode 100644 quickstep/res/raw-sw600dp/all_set_page_bg.json create mode 100644 quickstep/res/raw-sw720dp-land/all_set_page_bg.json create mode 100644 quickstep/res/raw-sw720dp/all_set_page_bg.json create mode 100644 quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsFallbackSearchContainer.java create mode 100644 quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java create mode 100644 quickstep/src/com/android/launcher3/uioverrides/QuickstepAppWidgetHost.java create mode 100644 quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java create mode 100644 quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java create mode 100644 quickstep/src/com/android/quickstep/OverviewCommandHelper.java create mode 100644 quickstep/src/com/android/quickstep/TaskThumbnailCache.java create mode 100644 quickstep/src/com/android/quickstep/fallback/RecentsTaskController.java create mode 100644 quickstep/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java create mode 100644 quickstep/src/com/android/quickstep/interaction/TutorialStepIndicator.java create mode 100644 quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java create mode 100644 quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModel.kt create mode 100644 quickstep/src/com/android/quickstep/task/viewmodel/TaskViewData.kt create mode 100644 quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java create mode 100644 quickstep/src/com/android/quickstep/util/ActiveGestureLog.java create mode 100644 quickstep/src/com/android/quickstep/util/ActivityInitListener.java create mode 100644 quickstep/src/com/android/quickstep/util/AssistStateManager.java create mode 100644 quickstep/src/com/android/quickstep/util/AssistUtils.java create mode 100644 quickstep/src/com/android/quickstep/util/DesktopTask.java create mode 100644 quickstep/src/com/android/quickstep/util/GroupTask.java create mode 100644 quickstep/src/com/android/quickstep/util/TaskGridNavHelper.java create mode 100644 quickstep/src/com/android/quickstep/views/IconAppChipView.java create mode 100644 quickstep/src/com/android/quickstep/views/IconView.java create mode 100644 quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarUnitTestRule.kt create mode 100644 quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt create mode 100644 quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelTest.kt create mode 100644 quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskGridNavHelperTest.java create mode 100644 quickstep/tests/src/com/android/launcher3/model/QuickstepModelDelegateTest.kt create mode 100644 quickstep/tests/src/com/android/launcher3/model/WidgetsPredictionsRequesterTest.kt create mode 100644 quickstep/tests/src/com/android/launcher3/taskbar/FallbackTaskbarUIControllerTest.kt create mode 100644 quickstep/tests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt create mode 100644 quickstep/tests/src/com/android/launcher3/taskbar/TaskbarHoverToolTipControllerTest.java create mode 100644 quickstep/tests/src/com/android/launcher3/taskbar/TaskbarKeyguardControllerTest.kt create mode 100644 quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt create mode 100644 quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java create mode 100644 quickstep/tests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt create mode 100644 quickstep/tests/src/com/android/quickstep/RecentsModelTest.java create mode 100644 quickstep/tests/src/com/android/quickstep/TaplDigitalWellBeingToastTest.java create mode 100644 quickstep/tests/src/com/android/quickstep/TaskThumbnailCacheTest.java create mode 100644 quickstep/tests/src/com/android/quickstep/taskbar/controllers/TaskbarPinningControllerTest.kt create mode 100644 quickstep/tests/src/com/android/quickstep/taskbar/customization/TaskbarSpecsEvaluatorTest.kt create mode 100644 quickstep/tests/src/com/android/quickstep/util/GestureExclusionManagerTest.kt create mode 100644 res/anim-v33/shared_x_axis_activity_close_enter.xml create mode 100644 res/anim-v33/shared_x_axis_activity_close_exit.xml create mode 100644 res/anim-v33/shared_x_axis_activity_open_enter.xml create mode 100644 res/anim-v33/shared_x_axis_activity_open_exit.xml create mode 100644 res/color-night-v31/material_color_surface.xml create mode 100644 res/color-night-v31/material_color_surface_bright.xml create mode 100644 res/color-night-v31/material_color_surface_container.xml create mode 100644 res/color-night-v31/material_color_surface_container_high.xml create mode 100644 res/color-night-v31/material_color_surface_container_highest.xml create mode 100644 res/color-night-v31/material_color_surface_container_low.xml create mode 100644 res/color-night-v31/material_color_surface_dim.xml create mode 100644 res/color-night-v31/material_color_surface_inverse.xml create mode 100644 res/color-night-v31/material_color_surface_variant.xml create mode 100644 res/color-night-v31/popup_color_background.xml create mode 100644 res/color-v31/material_color_surface.xml create mode 100644 res/color-v31/material_color_surface_bright.xml create mode 100644 res/color-v31/material_color_surface_container.xml create mode 100644 res/color-v31/material_color_surface_container_high.xml create mode 100644 res/color-v31/material_color_surface_container_highest.xml create mode 100644 res/color-v31/material_color_surface_container_low.xml create mode 100644 res/color-v31/material_color_surface_dim.xml create mode 100644 res/color-v31/material_color_surface_inverse.xml create mode 100644 res/color-v31/material_color_surface_variant.xml create mode 100644 res/color-v31/popup_color_background.xml create mode 100644 res/color/popup_color_background.xml create mode 100644 res/drawable-sw720dp/ic_transient_taskbar_all_apps_button.xml create mode 100644 res/drawable/ic_taskbar_all_apps_button.xml create mode 100644 res/drawable/ic_transient_taskbar_all_apps_button.xml create mode 100644 res/layout/page_indicator_dots.xml create mode 100644 res/values-v33/style.xml create mode 100644 res/xml/folder_shapes.xml create mode 100644 src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.java create mode 100644 src/com/android/launcher3/dragndrop/SpringLoadedDragController.java create mode 100644 src/com/android/launcher3/icons/ComponentWithLabel.java create mode 100644 src/com/android/launcher3/icons/MonochromeIconFactory.java create mode 100644 src/com/android/launcher3/model/GridSizeMigrationUtil.java create mode 100644 src/com/android/launcher3/model/ShortcutsChangedTask.java create mode 100644 src/com/android/launcher3/pageindicators/WorkspacePageIndicator.java create mode 100644 src/com/android/launcher3/util/ActivityTracker.java create mode 100644 src/com/android/launcher3/util/LauncherBindableItemsContainer.java create mode 100644 src/com/android/launcher3/util/LooperExecutor.java create mode 100644 src/com/android/launcher3/util/window/RefreshRateTracker.java create mode 100644 src/com/android/launcher3/views/ComposeInitializer.java create mode 100644 systemUI/plugin/src/com/android/systemui/plugins/clocks/AlarmData.kt create mode 100644 systemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt create mode 100644 systemUI/plugin/src/com/android/systemui/plugins/clocks/WeatherData.kt create mode 100644 systemUI/plugin/src/com/android/systemui/plugins/clocks/ZenData.kt create mode 100644 tests/assets/databases/GridMigrationTest/flagged_result5x5to5x8.db create mode 100644 tests/multivalentTests/shared/AndroidManifest.xml create mode 100644 tests/multivalentTests/shared/com/android/launcher3/testing/shared/HotseatCellCenterRequest.java create mode 100644 tests/multivalentTests/shared/com/android/launcher3/testing/shared/ResourceUtils.java create mode 100644 tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestInformationRequest.java create mode 100644 tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestProtocol.java create mode 100644 tests/multivalentTests/shared/com/android/launcher3/testing/shared/WorkspaceCellCenterRequest.java create mode 100644 tests/multivalentTests/src/com/android/launcher3/celllayout/board/CellType.java create mode 100644 tests/multivalentTests/src/com/android/launcher3/celllayout/board/FolderPoint.java create mode 100644 tests/multivalentTests/src/com/android/launcher3/celllayout/board/IconPoint.java create mode 100644 tests/multivalentTests/src/com/android/launcher3/celllayout/board/TestWorkspaceBuilder.java create mode 100644 tests/multivalentTests/src/com/android/launcher3/celllayout/board/WidgetRect.java create mode 100644 tests/multivalentTests/src/com/android/launcher3/model/FactitiousDbController.kt create mode 100644 tests/multivalentTests/src/com/android/launcher3/model/GridSizeMigrationUtilTest.kt create mode 100644 tests/multivalentTests/src/com/android/launcher3/util/PackageManagerHelperTest.java create mode 100644 tests/shared/com/android/launcher3/testing/OWNERS create mode 100644 tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java create mode 100644 tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewTest.java create mode 100644 tests/src/com/android/launcher3/allapps/PrivateSpaceSettingsButtonTest.java create mode 100644 tests/src/com/android/launcher3/allapps/TaplKeyboardFocusTest.java create mode 100644 tests/src/com/android/launcher3/celllayout/TaplReorderWidgetsTest.java create mode 100644 tests/src/com/android/launcher3/celllayout/testgenerator/ValidGridMigrationTestCaseGenerator.kt create mode 100644 tests/src/com/android/launcher3/compat/TaplPromiseIconUiTest.java create mode 100644 tests/src/com/android/launcher3/folder/FolderNameProviderTest.java create mode 100644 tests/src/com/android/launcher3/folder/PreviewBackgroundTest.java create mode 100644 tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt create mode 100644 tests/src/com/android/launcher3/model/AsyncBindingTest.kt create mode 100644 tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java create mode 100644 tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java create mode 100644 tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java create mode 100644 tests/src/com/android/launcher3/model/FirstScreenBroadcastHelperTest.kt create mode 100644 tests/src/com/android/launcher3/model/FolderIconLoadTest.kt create mode 100644 tests/src/com/android/launcher3/model/LoaderCursorTest.java create mode 100644 tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java create mode 100644 tests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt create mode 100644 tests/src/com/android/launcher3/model/WorkspaceItemSpaceFinderTest.kt create mode 100644 tests/src/com/android/launcher3/model/gridmigration/GridMigrationUtils.kt create mode 100644 tests/src/com/android/launcher3/model/gridmigration/ValidGridMigrationUnitTest.kt create mode 100644 tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java create mode 100644 tests/src/com/android/launcher3/testcomponent/TouchEventGenerator.java create mode 100644 tests/src/com/android/launcher3/touch/SingleAxisSwipeDetectorTest.java create mode 100644 tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java create mode 100644 tests/src/com/android/launcher3/ui/widget/TaplAddConfigWidgetTest.java create mode 100644 tests/src/com/android/launcher3/ui/widget/TaplBindWidgetTest.java create mode 100644 tests/src/com/android/launcher3/ui/widget/TaplRequestPinItemTest.java create mode 100644 tests/src/com/android/launcher3/ui/widget/TaplWidgetPickerTest.java create mode 100644 tests/src/com/android/launcher3/ui/workspace/TaplThemeIconsTest.java create mode 100644 tests/src/com/android/launcher3/util/Wait.java create mode 100644 tests/tapl/com/android/launcher3/tapl/OverviewTaskMenuItem.java create mode 100644 wmshell/shared/src/com/android/wm/shell/shared/IHomeTransitionListener.aidl create mode 100644 wmshell/shared/src/com/android/wm/shell/shared/IShellTransitions.aidl diff --git a/.github/release.yml b/.github/release.yml index d15e13133a..968b64dcc2 100644 --- a/.github/release.yml +++ b/.github/release.yml @@ -3,7 +3,7 @@ changelog: labels: - bot authors: - - renovate[bot] + - renovate - lawnchair-bot - crowdin-bot categories: @@ -18,3 +18,6 @@ changelog: - title: 🧹 Housekeeping labels: - housekeeping + - title: 🧑‍💻 Dependencies + labels: + - dependencies diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c0eaf5a72c..ef32b63e95 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,7 +52,7 @@ jobs: echo ${{ secrets.KEYSTORE }} | base64 --decode > ${{ github.workspace }}/key.jks fi - name: Build debug APK - run: ./gradlew assembleLawnWithQuickstepNightlyRelease assembleLawnWithQuickstepGithubDebug assembleLawnWithQuickstepPlayDebug --no-configuration-cache + run: ./gradlew assembleLawnWithQuickstepGithubDebug assembleLawnWithQuickstepPlayDebug assembleLawnWithQuickstepNightlyRelease --no-configuration-cache - name: Upload artifact uses: actions/upload-artifact@v6 with: @@ -118,8 +118,7 @@ jobs: nightly-release: runs-on: ubuntu-latest - if: false - # if: github.repository_owner == 'LawnchairLauncher' && github.event_name == 'push' && github.ref == 'refs/heads/15-dev' + if: github.repository_owner == 'LawnchairLauncher' && github.event_name == 'push' && github.ref == 'refs/heads/15-dev' needs: build-debug-apk permissions: contents: write diff --git a/.github/workflows/crowdin.yml b/.github/workflows/crowdin.yml index 043c6a8bdc..123e8e8435 100644 --- a/.github/workflows/crowdin.yml +++ b/.github/workflows/crowdin.yml @@ -24,7 +24,7 @@ jobs: upload_translations: false upload_sources: true download_translations: true - localization_branch_name: 16-dev-localization + localization_branch_name: 15-dev-localization create_pull_request: true base_url: 'https://lawnchair.crowdin.com' env: diff --git a/.github/workflows/crowdin_download.yml b/.github/workflows/crowdin_download.yml index ac393db7f4..19fe5136d6 100644 --- a/.github/workflows/crowdin_download.yml +++ b/.github/workflows/crowdin_download.yml @@ -26,7 +26,7 @@ jobs: upload_translations: false upload_sources: false download_translations: true - localization_branch_name: 16-dev-localization + localization_branch_name: 15-dev-localization create_pull_request: true base_url: 'https://lawnchair.crowdin.com' env: diff --git a/.github/workflows/crowdin_upload.yml b/.github/workflows/crowdin_upload.yml index 83091af2a6..f85f080306 100644 --- a/.github/workflows/crowdin_upload.yml +++ b/.github/workflows/crowdin_upload.yml @@ -22,7 +22,7 @@ jobs: upload_translations: false upload_sources: true download_translations: false - localization_branch_name: 16-dev-localization + localization_branch_name: 15-dev-localization create_pull_request: false base_url: 'https://lawnchair.crowdin.com' env: diff --git a/.gitmodules b/.gitmodules index f2284aca65..cf7551ac41 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,3 @@ [submodule "platform_frameworks_libs_systemui"] path = platform_frameworks_libs_systemui url = https://github.com/LawnchairLauncher/platform_frameworks_libs_systemui - branch = 16-dev diff --git a/Android.bp b/Android.bp index 2c4fb37908..eb033ee0da 100644 --- a/Android.bp +++ b/Android.bp @@ -17,19 +17,7 @@ package { default_applicable_licenses: ["Android-Apache-2.0"], } -min_launcher3_sdk_version = "31" - -// Targets that don't inherit framework aconfig libs (i.e., those that don't set -// `platform_apis: true`) must manually link them. -java_defaults { - name: "launcher-non-platform-apis-defaults", - static_libs: [ - "android.os.flags-aconfig-java", - "android.multiuser.flags-aconfig-java", - "android.appwidget.flags-aconfig-java", - "com.android.window.flags.window-aconfig-java", - ], -} +min_launcher3_sdk_version = "30" // Common source files used to build launcher (java and kotlin) // All sources are split so they can be reused in many other libraries/apps in other folders @@ -43,122 +31,12 @@ filegroup { ], } -// Main Launcher source for compose, excluding the build config -filegroup { - name: "launcher-compose-enabled-src", - srcs: [ - "compose/facade/enabled/*.kt", - "compose/facade/core/*.kt", - "compose/features/**/*.kt", - ], -} - -filegroup { - name: "launcher-compose-disabled-src", - srcs: [ - "compose/facade/core/*.kt", - "compose/facade/disabled/*.kt", - ], -} - // Source code for quickstep build, on top of launcher-src filegroup { name: "launcher-quickstep-src", srcs: [ - "quickstep/src/**/*.kt", "quickstep/src/**/*.java", - ], - device_common_srcs: [ - ":launcher-quickstep-processed-protolog-src", - ], -} - -// Launcher ProtoLog support -filegroup { - name: "launcher-quickstep-unprocessed-protolog-src", - srcs: [ - "quickstep/src_protolog/**/*.java", - ], -} - -java_library { - name: "launcher-quickstep_protolog-groups", - srcs: [ - "quickstep/src_protolog/**/*.java", - ], - static_libs: [ - "protolog-group", - "androidx.annotation_annotation", - "com_android_launcher3_flags_lib", - ], -} - -java_genrule { - name: "launcher-quickstep-processed-protolog-src", - srcs: [ - ":protolog-impl", - ":launcher-quickstep-unprocessed-protolog-src", - ":launcher-quickstep_protolog-groups", - ], - tools: ["protologtool"], - cmd: "$(location protologtool) transform-protolog-calls " + - "--protolog-class com.android.internal.protolog.common.ProtoLog " + - "--loggroups-class com.android.quickstep.util.QuickstepProtoLogGroup " + - "--loggroups-jar $(location :launcher-quickstep_protolog-groups) " + - "--viewer-config-file-path /system_ext/etc/launcher.quickstep.protolog.pb " + - "--output-srcjar $(out) " + - "$(locations :launcher-quickstep-unprocessed-protolog-src)", - out: ["launcher.quickstep.protolog.srcjar"], -} - -java_genrule { - name: "gen-launcher.quickstep.protolog.pb", - srcs: [ - ":launcher-quickstep-unprocessed-protolog-src", - ":launcher-quickstep_protolog-groups", - ], - tools: ["protologtool"], - cmd: "$(location protologtool) generate-viewer-config " + - "--protolog-class com.android.internal.protolog.common.ProtoLog " + - "--loggroups-class com.android.quickstep.util.QuickstepProtoLogGroup " + - "--loggroups-jar $(location :launcher-quickstep_protolog-groups) " + - "--viewer-config-type proto " + - "--viewer-config $(out) " + - "$(locations :launcher-quickstep-unprocessed-protolog-src)", - out: ["launcher.quickstep.protolog.pb"], -} - -prebuilt_etc { - name: "launcher.quickstep.protolog.pb", - system_ext_specific: true, - src: ":gen-launcher.quickstep.protolog.pb", - filename_from_src: true, -} - -// Source code for quickstep dagger -filegroup { - name: "launcher-quickstep-dagger", - srcs: [ - "quickstep/dagger/**/*.java", - "quickstep/dagger/**/*.kt", - ], -} - -// Source code for quickstep build with compose enabled, on top of launcher-src -filegroup { - name: "launcher-quickstep-compose-enabled-src", - srcs: [ - "quickstep/compose/facade/core/*.kt", - "quickstep/compose/facade/enabled/*.kt", - "quickstep/compose/features/**/*.kt", - ], -} - -filegroup { - name: "launcher-quickstep-compose-disabled-src", - srcs: [ - "quickstep/compose/facade/core/*.kt", - "quickstep/compose/facade/disabled/*.kt", + "quickstep/src/**/*.kt", ], } @@ -185,118 +63,10 @@ filegroup { srcs: ["proguard.flags"], } -// Opt-in configuration for Launcher3 code depending on Jetpack Compose. -soong_config_module_type { - name: "launcher_compose_java_defaults", - module_type: "java_defaults", - config_namespace: "ANDROID", - bool_variables: ["release_enable_compose_in_launcher"], - properties: [ - "srcs", - "static_libs", - ], -} - -// Opt-in configuration for Launcher Quickstep code depending on Jetpack Compose. -soong_config_bool_variable { - name: "release_enable_compose_in_launcher", -} - -soong_config_module_type { - name: "quickstep_compose_java_defaults", - module_type: "java_defaults", - config_namespace: "ANDROID", - bool_variables: ["release_enable_compose_in_launcher"], - properties: [ - "srcs", - "static_libs", - ], -} - -soong_config_module_type { - name: "launcher_compose_tests_java_defaults", - module_type: "java_defaults", - config_namespace: "ANDROID", - bool_variables: ["release_enable_compose_in_launcher"], - properties: [ - "static_libs", - ], -} - -launcher_compose_java_defaults { - name: "launcher_compose_defaults", - soong_config_variables: { - release_enable_compose_in_launcher: { - srcs: [ - ":launcher-compose-enabled-src", - ], - - // Compose dependencies - static_libs: [ - "androidx.compose.runtime_runtime", - "androidx.compose.material3_material3", - ], - - // By default, Compose is disabled and we compile the ComposeFacade - // in compose/launcher3/facade/disabled/. - conditions_default: { - srcs: [ - ":launcher-compose-disabled-src", - ], - static_libs: [], - }, - }, - }, -} - -quickstep_compose_java_defaults { - name: "quickstep_compose_defaults", - soong_config_variables: { - release_enable_compose_in_launcher: { - srcs: [ - ":launcher-quickstep-compose-enabled-src", - ], - - // Compose dependencies - static_libs: [ - "androidx.compose.runtime_runtime", - "androidx.compose.material3_material3", - ], - - // By default, Compose is disabled and we compile the ComposeFacade - // in compose/quickstep/facade/disabled/. - conditions_default: { - srcs: [ - ":launcher-quickstep-compose-disabled-src", - ], - static_libs: [], - }, - }, - }, -} - -launcher_compose_tests_java_defaults { - name: "launcher_compose_tests_defaults", - soong_config_variables: { - release_enable_compose_in_launcher: { - // Compose dependencies - static_libs: [ - "androidx.compose.runtime_runtime", - "androidx.compose.ui_ui-test-junit4", - "androidx.compose.ui_ui-test-manifest", - ], - - conditions_default: { - static_libs: [], - }, - }, - }, -} - android_library { name: "launcher-aosp-tapl", libs: [ - "framework-statsd.stubs.module_lib", + "framework-statsd", ], static_libs: [ "androidx.annotation_annotation", @@ -306,7 +76,6 @@ android_library { "androidx.preference_preference", "SystemUISharedLib", "//frameworks/libs/systemui:animationlib", - "//frameworks/libs/systemui:contextualeducationlib", "launcher-testing-shared", ], srcs: [ @@ -367,14 +136,12 @@ java_library { // Library with all the dependencies for building Launcher3 android_library { name: "Launcher3ResLib", - defaults: [ - "launcher_compose_defaults", - ], srcs: [], resource_dirs: ["res"], static_libs: [ "LauncherPluginLib", "launcher_quickstep_log_protos_lite", + "android.os.flags-aconfig-java", "androidx-constraintlayout_constraintlayout", "androidx.recyclerview_recyclerview", "androidx.dynamicanimation_dynamicanimation", @@ -387,10 +154,7 @@ android_library { "//frameworks/libs/systemui:iconloader_base", "//frameworks/libs/systemui:view_capture", "//frameworks/libs/systemui:animationlib", - "//frameworks/libs/systemui:contextualeducationlib", - "//frameworks/libs/systemui:msdl", "SystemUI-statsd", - "WindowManager-Shell-shared-AOSP", "launcher-testing-shared", "androidx.lifecycle_lifecycle-common-java8", "androidx.lifecycle_lifecycle-extensions", @@ -399,9 +163,8 @@ android_library { "kotlinx_coroutines", "com_android_launcher3_flags_lib", "com_android_wm_shell_flags_lib", - "dagger2", - "jsr330", - "com_android_systemui_shared_flags_lib", + "android.appwidget.flags-aconfig-java", + "com.android.window.flags.window-aconfig-java", ], manifest: "AndroidManifest-common.xml", sdk_version: "current", @@ -409,9 +172,6 @@ android_library { lint: { baseline_filename: "lint-baseline.xml", }, - flags_packages: [ - "com_android_launcher3_flags", - ], } // @@ -419,7 +179,6 @@ android_library { // android_app { name: "Launcher3", - defaults: ["launcher-non-platform-apis-defaults"], static_libs: [ "Launcher3ResLib", @@ -431,7 +190,7 @@ android_app { ], optimize: { - proguard_flags_files: [":launcher-proguard-rules"], + proguard_flags_files: ["proguard.pro"], // Proguard is disable for testing. Derivarive prjects to keep proguard enabled enabled: false, }, @@ -439,7 +198,6 @@ android_app { sdk_version: "current", min_sdk_version: min_launcher3_sdk_version, target_sdk_version: "current", - plugins: ["dagger2-compiler"], privileged: true, system_ext_specific: true, @@ -456,12 +214,8 @@ android_app { "AndroidManifest-common.xml", ], lint: { - extra_check_modules: ["Launcher3LintChecker"], baseline_filename: "lint-baseline.xml", }, - kotlincflags: [ - "-Xjvm-default=all", - ], } // Library with all the dependencies for building quickstep @@ -472,34 +226,24 @@ android_library { "quickstep/res", ], libs: [ - "framework-statsd.stubs.module_lib", + "framework-statsd", ], static_libs: [ "Launcher3ResLib", "lottie", "SystemUISharedLib", "SettingsLibSettingsTheme", - "dagger2", - "protolog-group", ], manifest: "quickstep/AndroidManifest.xml", min_sdk_version: "current", - lint: { - disabled_checks: ["MissingClass"], - }, } // Library with all the source code and dependencies for building Launcher Go android_library { name: "Launcher3GoLib", - defaults: [ - "launcher_compose_defaults", - "quickstep_compose_defaults", - ], srcs: [ ":launcher-src", ":launcher-quickstep-src", - ":launcher-quickstep-dagger", "go/quickstep/src/**/*.java", "go/quickstep/src/**/*.kt", ], @@ -514,10 +258,7 @@ android_library { "QuickstepResLib", "androidx.room_room-runtime", ], - plugins: [ - "androidx.room_room-compiler-plugin", - "dagger2-compiler", - ], + plugins: ["androidx.room_room-compiler-plugin"], manifest: "quickstep/AndroidManifest.xml", additional_manifests: [ "go/AndroidManifest.xml", @@ -526,27 +267,19 @@ android_library { min_sdk_version: "current", // TODO(b/319712088): re-enable use_resource_processor use_resource_processor: false, - kotlincflags: [ - "-Xjvm-default=all", - ], } // Library with all the source code and dependencies for building Quickstep android_library { name: "Launcher3QuickStepLib", - defaults: [ - "launcher_compose_defaults", - "quickstep_compose_defaults", - ], srcs: [ ":launcher-src", ":launcher-quickstep-src", - ":launcher-quickstep-dagger", ":launcher-build-config", ], resource_dirs: [], libs: [ - "framework-statsd.stubs.module_lib", + "framework-statsd", ], // Note the ordering here is important when it comes to resource // overriding. We want the most specific resource overrides defined @@ -558,23 +291,18 @@ android_library { ], manifest: "quickstep/AndroidManifest.xml", platform_apis: true, - plugins: ["dagger2-compiler"], min_sdk_version: "current", // TODO(b/319712088): re-enable use_resource_processor use_resource_processor: false, - kotlincflags: [ - "-Xjvm-default=all", - ], } // Build rule for Quickstep app. android_app { name: "Launcher3QuickStep", + static_libs: ["Launcher3QuickStepLib"], optimize: { - proguard_flags_files: [":launcher-proguard-rules"], - enabled: true, - shrink_resources: true, + enabled: false, }, platform_apis: true, @@ -588,10 +316,7 @@ android_app { "Launcher2", "Launcher3", ], - required: [ - "privapp_whitelist_com.android.launcher3", - "launcher.quickstep.protolog.pb", - ], + required: ["privapp_whitelist_com.android.launcher3"], resource_dirs: ["quickstep/res"], @@ -607,11 +332,13 @@ android_app { } + // Build rule for Launcher3 Go app with quickstep for Android Go devices. // Note that the following two rules are exactly same, and should // eventually be merged into a single target android_app { name: "Launcher3Go", + static_libs: ["Launcher3GoLib"], resource_dirs: [], @@ -622,7 +349,6 @@ android_app { optimize: { proguard_flags_files: ["proguard.flags"], enabled: true, - shrink_resources: true, }, privileged: true, @@ -646,9 +372,9 @@ android_app { include_filter: ["com.android.launcher3.*"], }, } - android_app { name: "Launcher3QuickStepGo", + static_libs: ["Launcher3GoLib"], resource_dirs: [], @@ -659,7 +385,6 @@ android_app { optimize: { proguard_flags_files: ["proguard.flags"], enabled: true, - shrink_resources: true, }, privileged: true, diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml index da43ea0bc6..e8fa644e1f 100644 --- a/AndroidManifest-common.xml +++ b/AndroidManifest-common.xml @@ -136,11 +136,13 @@ android:writePermission="${applicationId}.permission.WRITE_SETTINGS" android:readPermission="${applicationId}.permission.READ_SETTINGS" /> - + [telegram]: https://t.me/lccommunity [discord]: https://discord.com/invite/3x8qNWxgGZ [nightly]: https://github.com/LawnchairLauncher/lawnchair/releases/tag/nightly -[security-report]: https://github.com/LawnchairLauncher/lawnchair/security/advisories/new -[security-policy]: https://github.com/LawnchairLauncher/lawnchair/security/policy [bug-reports]: https://github.com/LawnchairLauncher/lawnchair/issues/new?assignees=&labels=bug&projects=&template=bug_report.yaml&title=%5BBUG%5D+ [feature-requests]: https://github.com/LawnchairLauncher/lawnchair/issues/new?assignees=&labels=feature%2Cenhancement&projects=&template=feature_request.yaml&title=%5BFEATURE%5D+ [code-of-conduct]: CODE_OF_CONDUCT.md [crowdin]: https://lawnchair.crowdin.com [kotlin-coding-conventions]: https://kotlinlang.org/docs/coding-conventions.html -[lawnchair-package]: https://github.com/LawnchairLauncher/lawnchair/tree/16-dev/lawnchair -[src-package]: https://github.com/LawnchairLauncher/lawnchair/tree/16-dev/src +[lawnchair-package]: https://github.com/LawnchairLauncher/lawnchair/tree/15-dev/lawnchair +[src-package]: https://github.com/LawnchairLauncher/lawnchair/tree/15-dev/src [conventional-commits]: https://www.conventionalcommits.org/en/v1.0.0/ [google-fonts-api-key]: https://developers.google.com/fonts/docs/developer_api#APIKey - diff --git a/GITHUB_CHANGELOG.md b/GITHUB_CHANGELOG.md index 22c3c4a419..775e6e3f0f 100644 --- a/GITHUB_CHANGELOG.md +++ b/GITHUB_CHANGELOG.md @@ -1,349 +1,47 @@ -Lawnchair 16 pE Development 3 is here! Contributors are encouraged to target this branch instead of -older (i.e., Lawnchair `15-dev`). +> [!TIP] +> For the story behind this release, see [the announcement](https://lawnchair.app/blog/lawnchair-15-beta-1) on our website. -### 🏗️ Development 3 Release 3 +Lawnchair 15 Beta 1 is a foundational release based on Launcher3 from Android 15. This version works with QuickSwitch from Android 10 to Android 15 QPR1. Higher Android versions are not yet supported. -Build: BD3.1312 (latest), BD3.0812, BD3.0712 +### New Features +* **Android 15 Support:** Includes core platform features like Private Space and App Archiving. +* **App Drawer Folders:** A major new way to organize your app drawer. + * **Manual Folders:** Create, edit, and re-arrange your own custom folders. + * **Automatic Organization ("Caddy"):** An experimental feature to automatically categorize your entire app drawer into smart folders. +* **Dock Enhancements:** + * Add a background to the dock with options for color and corner radius. + * Place widgets directly in the dock. + * Show icon labels for apps in the dock. +* **Wallpaper Carousel:** A new pop-up menu item to quickly switch between your current and recent wallpapers, similar to the Pixel Launcher. +* **App Pausing:** For rooted users with QuickSwitch, you can now manually pause applications directly from the launcher. +* **Expanded Search Options:** + * Add custom search engines for web suggestions in the app drawer. + * New web search providers added, including Ecosia, Kagi, Firefox, Iceraven, and Mull. +* **"Deck" (Experimental):** An initial implementation of a "no app drawer" mode. *Please create a launcher backup before trying this feature to prevent data loss.* -Compatibility list: +### Improvements +* **UI:** Updated many UI components to better align with Material 3 design principles. +* **Gestures:** Added "Open Recents Screen" and "Open Assistant" as new gesture actions. +* **Pop-Up Menu:** The long-press menu options can now be reordered. +* **Settings:** Reorganized many settings for a more intuitive experience and centralized all search-related settings into a single screen. -| 🏗️ Crash | 🥞 Fully supported | -|-------------|--------------------| -| Android 8.1 | Android 12.0 | -| Android 9 | Android 12.1 | -| Android 10 | Android 13 | -| Android 11 | Android 14 | -| | Android 15 | -| | Android 16 | +### Core & Under-the-Hood +* **Type-Safe Navigation:** The settings infrastructure has been rewritten using modern Jetpack Compose Navigation for enhanced stability. +* **Build & Dependency Updates:** Major updates to dependencies and build scripts improve performance and maintainability. +* **New Translations:** Translations have been updated from Crowdin. +* **Nightly Builds:** A formal nightly build system is now in place for easier access to development versions. +* **Crash & Bug Fixes:** Implemented numerous fixes for various OEM skins (Lenovo, Motorola), custom ROMs, and older Android versions. -#### Features -* [Lawnchair] Features from Lawnchair 15-dev 07122025 -* [Launcher] Google Sans Flex font uses almost the exact same configuration as Pixel -* [Launcher] Enable bulk loading by default -* [Launcher] Tablet support (ish) -* [Launcher] Refreshed Material 3 Expressive -* [Lawnchair] Refreshed Material 3 Expressive -* [Launcher] Foldable support (ish) -* [Lawnchair] Warn when nightly updater is updating to next major version -* [Lawnchair/Smartspace] Add Lunar calendar option -* [Lawnchair/Smartspace] Promote smartspace calendar to stable -* [Lawnchair] Expressive redesign Phase 2 -* [Lawnchair] GestureNavContract toggle in experimental features -* [Lawnchair] Set GestureNavContract on by default on Google device -* [Lawnchair] Set GestureNavContract on by default on Nothing device -* [Lawnchair] Don't show warning on known compatible device -* [Lawnchair] Swipe to dismiss announcement perform haptic on successful dismiss -* [Lawnchair] Remove Inter v3 fonts from Lawnchair entirely (to reduce apk size) -* [Lawnchair] Add Google Sans variable font as fallback to Google Sans Flex (to support the most of the world languages, yes that increases sizes) -* [Launcher] Google Sans variable normal style -* [Lawnchair] Improve Google device compatibility check -* [Lawnchair] Improve Samsung device compatibility check +### Regressions & Known Issues +* **Icon Badges:** Icon badges for work profile apps are temporarily non-functional due to core changes in the A15 rebase. This is a high-priority item for a future update. +* **'Customize Icon' State:** The bottom sheet for customizing an icon may not update its state immediately. Restarting the launcher will apply the change. +* **App Drawer folders:** As of now, you can't edit app drawer folders from the app drawer. Please visit the settings screen to change the contents of each folder. -#### Fixes -* [Launcher3] Widget preview crash for no reason at all on compatible Android version -* [Launcher] Hotseat Google provider failed to open due to Android pending intent restrictions on Android 14/15/16/16.1 -* [Launcher3/DeviceProfile] Positioning of first folder during Lawnchair setup -* [Lawnchair/AllApps] Reimplement app drawer opacity -* [Lawnchair/Recents] Reimplement recents overview opacity -* [Lawnchair/Preference] Misaligned slider and text preference -* [Lawnchair/Smartspace] Allow disabling the smartspace feature -* [Lawnchair] Settings now correctly animate expand/shrink items -* [Lawnchair] Correctly display warning in experimental features (race conditions) -* [Project] Support for Android Studio 2025.2.3 Canary 5 (Bump to AGP 9.0.0-beta05) -* [Lawnchair] Offer a toggle to disable/enable suggestions instead of linking it to ASI if the device is not Google Pixel +Other issues that you may encounter can be found at [our FAQ](https://lawnchair.app/faq/#common-issues). -### 🥞 Development 3 Release 2 +### Community & Thanks +This release marks a new chapter in how we engage with our community. We recently formed the **Lawnchair Triage Team**, a group of dedicated volunteers who have already begun the massive task of organizing our issue tracker. Their early efforts have been invaluable in helping us focus development. -Build: BD3.2211 +Thanks as well to all the people who have [donated to our Open Collective](https://opencollective.com/lawnchair) and [submitted translations on Crowdin](https://lawnchair.crowdin.com/). -Compatibility list: - -| 🏗️ Crash | 💫 Limited features | 🥞 Fully supported | -|-------------|---------------------|--------------------| -| Android 8.1 | | Android 12.0 | -| Android 9 | | Android 12.1 | -| Android 10 | | Android 13 | -| Android 11 | | Android 14 | -| | | Android 15 | -| | | Android 16 | - -#### Features -* [Lawnchair] Updated screenshots compressions and fastlane screenshot -* [Lawnchair] Features from Lawnchair 15-dev -* [Launcher3] Widget preview crash for no reason at all on compatible Android version - -#### Fixes -* [Lawnchair] Conflict from Lawnchair 15-dev - -### Development 3 Release 1 - -Build: BD3.1711 - -The biggest change log ever, this marked the end of Bubble Tea [r2] branch as future development -switched to Bubble Tea [QPR1]. See you at Snapshot 7 or Development 4! - -(Again) Originally going to launch D3 if most of the issue on tracker have been resolved, but hit a -stability milestone instead. - -This release includes 4 new features, and 33 bug fixes, -Reimplemented some of Lawnchair features, better sizing of home screen, updated README.md screenshot -and the inclusion of Bubble Tea project into the official Lawnchair repository as 16-dev! - -This release have been tested with: -* ☁️ Pixel 6 (Android 12.0) -* 📱 Nothing (3a)-series (Android 15, Android 16.0) -* 📱 Vivo Y21 (Android 12.0) -* 📱 HTC Wildfire E3 lite (Android 12.0) -* Many more! Unfortunately I only count build from pE Open testing! - -Compatibility list: - -| 🏗️ Crash | 💫 Limited features | 🥞 Fully supported | -|-------------|---------------------|--------------------| -| Android 8.1 | | Android 12.0 | -| Android 9 | | Android 12.1 | -| Android 10 | | Android 13 | -| Android 11 | | Android 14 | -| | | Android 15 | -| | | Android 16 | - -> [!NOTE] -> QuickSwitch compatibility have not been tested at any time during the development of Bubble Tea! - -#### Features -* [Lawnchair] Complex Clover icon shape -* [Lawnchair] Very Sunny icon shape -* [Lawnchair/Font] Update Google Fonts listing to 25102025 -* [Lawnchair/Gesture] Allow Open Quick Settings* - -#### Fixes -* Disable OEM override on launcher settings, (reimplement `ENABLE_AUTO_INSTALLS_LAYOUT` | c51b2a221838aefb610b7146fc4ef7cb34e5e495) -* [Lawnchair/Iconloaderlib] Reimplement custom app name -* [Lawnchair] Reimplement Launcher3 debug page -* [Lawnchair] Reimplement Caddy and App drawer folder -* [Lawnchair] Reimplement Hotseat toggle -* [Lawnchair] Reimplement Favorite application label -* [Lawnchair] Hotseat positioning with favorite icon label enabled placed the same even if label is disabled -* [Lawnchair] Hotseat background now have a reasonably sized margin compared to D2 -* [Lawnchair] Qsb sizing now correctly estimate the width based on width of the app/widget layout or DeviceProfile on device with inlined Qsb -* [Lawnchair] Reimplement Allapps opacity configuration -* [DeviceProfile] Crash from createWindowContext on less than Android 12.0 -* [QuickstepLauncher] Ignore trying to set SystemUiProxy icon sizes on less than Android 12.1 -* [Lawnchair/BlankActivity] Apply Material 3 Expressive button animations -* [Launcher] Disable add widget button if home screen is locked -* [Lawnchair/Iconloaderlib] Crash when trying to set `null` monochrome icon on less than Android 12.1 -* [SystemUI/Unfold] Crash when getting configuration for foldable-specific resources -* [Lawnchair/Iconloaderlib] Don't parse monochrome drawable in Android 12.1 or less -* [Launcher3/AllApps] Allow theming of Expressive allapps -* ~~[Lawnchair] Lawnchair can now be compiled in release mode~~ - * [Lawnchair] Fix crashes with WM-Shell -* [Lawnchair] Bottom sheet blur will only trigger when your device supported blur* -* [Lawnchair/Lazy] Corner radii of lazy component now matched radius of non-lazy* -* [Lawnchair/Debug] Cleanup the debug menu* -* [Lawnchair/Docs] Warn off danger using 16-dev branch* -* [Launcher3] Crash with predictive back on some device using Android 13/14 -* [Launcher3] WindowInsets crash in Android 11 -* [Launcher3] Widgets crash on some device using Android 12 -* [Launcher3/PrivateSpace] Use custom icons of Private Space lock* -* [Launcher3/Iconloaderlib] App badges for work profile* -* [Lawnchair] Update spacing for dock search settings* -* [Launcher3] Quickstep dispatcher crash on Android 13 -* [Launcher3] Crash due to missing resources for Android 8.0 -* [Lawnchair/Docs] Update screenshot to 16-dev - -### 🥞 Development 2 - -Originally going to launch D2 if most of the comestic bug fixes have been resolved, but hit a -stability milestone instead. - -This release includes 15 new features, and 20 bug fixes, -Lawnchair settings now takes shape of initial material 3 expressive redesign, [(but by no mean finish!)][Lawnget] -launcher should now render icons better than D1 milestone, with auto-adaptive icons feature reimplemented. - -This release have been tested with: -* ☁️ Pixel 6 (Android 12.0) - Build: Ad-hoc -* ☁️ Pixel 6a (Android 12.1) - Build: Ad-hoc -* ☁️ Pixel 7 (Android 13) - Build: Ad-hoc -* ☁️ Pixel 9 (Android 15, Android 16.0) - Build: Ad-hoc -* ☁️ Pixel 9 Pro Fold (Android 14, Android 15) - Build: Ad-hoc -* ☁️ Vivo V40 (Android 15) - Build: Ad-hoc -* ☁️ Xiaomi MIX (Android 15) - Build: Ad-hoc -* 📱 Nothing (3a)-series (Android 15) - Build: pE-`15102025` -* 📱 Pixel 9 Pro XL (Android 16.0 QPR2 Beta 2) - Build: pE-`02102025` -* 📱 BLU View 5 Pro (Android 14) - Build: pE-`02102025` -* 📱🔥 Vivo Y21 (Android 12.0) - Build: pE-`08102025` - -> [!NOTE] -> QuickSwitch compatibility have not been tested at any time during the development of Bubble Tea! - -[Lawnget]: https://www.google.com/teapot - -Compatibility list: - -| 🏗️ Crash | 💫 Limited features | 🥞 Fully supported | -|-------------|---------------------|--------------------| -| Android 8.1 | Android 12.0 | Android 12.1 | -| Android 9 | | Android 13 | -| Android 10 | | Android 14 | -| Android 11 | | Android 15 | -| | | Android 16 | - -#### Features - -* Enable All Apps Blur Flags on Phone (oops, forgot about the allAppsSheetForHandheld flag) -* Make Safe Mode check more reliable -* Smartspace Battery now reports battery charging status of Fast (more than 90% of 20 W) and Slow (less than 90% of 5 W) charging -* Show pseudonym version to Settings -* Resizing workspace calculate items position more accurately -* Update Lawnchair default grid size to 4×7 (or 4×6 with smartspace widget) -* Reimplement Hotseat background customisation -* Make haptic on a locked workspace use Google MSDL vibration -* Make Launcher3 colour more accurate to upstream Android 16 -* ProvideComposeSheetHandler now have expressive blur -* Lawnchair Settings now uses Material 3 Expressive -* Animate keyboard on/off state on app drawer search (Try enabling automatically show keyboard in app drawer settings and swipe up and down or directly tap “Apps list” in popup menu) -> (Backport not possible) -* Add LeakCanary check to all debug variant of the application -* [DEBUG] Launcher3 feature status diagnostic check in debug menu -* [Documentation] Add more visibility into both app certificate and SLSA verification for app authenticity check [VERIFICATION.md](VERIFICATION.md) -* [Documentation] Initial drafting of Improve documentation v6 (pave-path) -* [Launcher] Widget animations during resize -* [Iconloaderlib] Enable second hand for the clock app - -#### Fixes - -* Fix unable to access preview for icon style -* Popup's Arrow Theme now has the correct theme -* Widget should open normally after a workaround (C7evQZDJ) -* Fix (1) Search bar and Dock, (2) Folders and App Drawer settings didn't open due to init problems -* Lawnchair should hopefully remember what grid they should be using -* Most if not all of Lawnchair settings should be usable without crashes -* Correct Baseline Profile from old `market` to `play` variant, and now should calculate profile for `nightly` -* Fix Private Space crash when Lawnchair is set as Launcher due to flags only available on A16 -* Fix crash on a device with strict export receiver requirements on A14 -* Interactable widget crashing due to App Transition Manager being null (C7evQZDJ) -* Icon not responding to mouse cursor -> (Backported to Lawnchair 15) -* Rare NoSuchMethodError crash on IMS canImeRenderGesturalNavButtons -* [Lawnchair] Reimplement Bulk icons toggle -* SettingsCache crashing with SecurityException with unreadable keys (@hide) in Android 12 and newer (assume false) -* Assume flags `enableMovingContentIntoPrivateSpace` is false when ClassNotFoundException on Android 16 devices -* Rare NoSuchMethodError crash on SurfaceControl setEarlyWakeupStart and setEarlyWakeupEnd -* Properly align built-in smartspace in workspace -* Use WM Proxy from Lawnchair instead of System, fix Android 8.1/9/10/11/12.0/12.1 regarding SE, NSME like SystemBarUtils -> (dWkyIGw9), (reworked CllOXHJv) - * LawnchairWindowManagerProxy have been migrated to Dagger - * SystemWindowManagerProxy have been left unused -* [Lawnchair/Iconloaderlib] Update CustomAdaptiveIconDrawable to latest AOSP 13 -* [Iconloaderlib] Reset most of the changes to favour more AOSP 16_r02 code then Lawnchair (need rewrite) - * fix icon loaded in monochrome and always monochrome when it is not supposed to - * fix notification dots being twice the size with notification count -* [Lawnchair/Iconloaderlib] Reimplement Lawnchair Iconloaderlib (adaptive icons, monochrome, regular icon) - -#### Known Bugs -* Preview can't show device wallpaper -> (lIxkAYGg) -* IDP Preview doesn't refresh on settings change -> workaround is to hit apply and re-open the preview -> (ZbLX3438) -* Workspace theme doesn't refresh until restart -> (ZbLX3438) -> Fixed as part of (31lLEflf, 1MevNrzp) -* Lawnchair Colour can't handle restart causing default colour to be used instead -> Fixed? -> Properly fixed as part of (31lLEflf, 1MevNrzp) -* (Investigating) Work profile switch on widget selector *may* have reverted to Lawnchair 15 style -* Full lists: https://trello.com/b/8IdvO81K/pe-lawnchair - -### Development 1 - -First development milestone! Basic launcher functionality should be stable enough. - -* Make Lawnchair Launcher launchable in Android 12.1, 13, 14, 15, 16 -* Remove two deprecated features (Use Material U Popup, and Use dot pagination) -* Add pseudonym version in debug settings -* Adapt Lawnchair code to Launcher3 16 -* Make basic features of Launcher work (App Drawer, Home Screen, Search, Folders, Widgets) -* Enable Material Expressive Flags (Try swiping through launcher page) -* Enable All Apps Blur Flags (Try opening All Apps on supported devices) -* Enable MSDL Haptics Feedback Flags (Try gliding widget or icons across the homescreen) -* Make Predictive Back Gesture work on Android 13, 14, 15, 16 (Try swiping left or right on gesture-based navigational) -* Programmatically set Safe Mode status - -#### Known Bugs - -* App Icon may sometimes render with less than 0 in height/width causing blank icon to be rendered and crashing ISE on customising icons -> (31lLEflf) -* Any Lawnchair settings using IDP will crash the launcher -> Fixed in Lawnchair 16 pE Development 2 -* Icon pack isn't usable -> (DXo69Qzd) -* Dynamic icons will not be themed by launcher -* Full lists: https://trello.com/b/8IdvO81K/pe-lawnchair - -### Snapshot 6 - -This is a developer-focused change log: - -This snapshot marks the first time Lawnchair 16 is able to compile and build an APK! - -* Fix all issues with Java files in both `lawn` and `src` -* Make Lawnchair compilable (with instant crash) -* Move to KSP for Dagger code generation - -### Snapshot 5 - -This is a developer-focused change log: - -This snapshot now able to compile all sources (Kotlin files only) - -* Fix MORE MORE MORE `lawn` issues -* Use Gradle Version Catalog for consistent dependency version across all modules (Full implementation @ LawnchairLauncher/Lawnchair#5753) -* Magically fix ASM Instrumentation issues (I didn't do anything, it just works now) -* Fix ALL the issues in kotlin stage (`compileLawnWithQuickstepNightlyDebugKotlin`) -* Reintroduce some features from Lawnchair -* Add compatibility checks and workarounds for them -* Fix most issues with Java files in both `lawn` and `src` - -### Snapshot 4 - -This is a developer-focused change log: - -This snapshot marks the first time Lawnchair 16 is able to compile all Launcher3 sources! - -* Add `MSDLLib` to `platform_frameworks_libs_systemui` -* Add `contextualeducationlib` to `platform_frameworks_libs_systemui` -* Fix issues in both `lawn` and `src` modules -* Fix AIDL sources -* Resolve Lawnchair/LC-TODO lists -* Merge `wmshell.shared` res with res from `wmshell` -* Consistent build reproducibility by specifying dependencies in `build.gradle` -* Some ASM Instrumentation issues (and re-add some…) -* Update documentations - -### Snapshot 3 - -This is a developer-focused change log: - -Not a lot of errors left to go! - -* Finish correctly implementing all Dagger functions (?) -* Merge Lawnchair 15 Beta 1 into Bubble Tea - * Support for 16-kb page size devices -* Repository rebased and dropped commit - * Switch back from turbine-combined variant to javac variant for prebuilt SystemUI-core-16 because issues with LFS - * MORE MORE fixes regarding turbine-combined to javac -* Publish `platform_frameworks_libs_systemui` to pe 16-dev branch -* ATLEAST check to almost every launcher3 source file -* `Utils` module (stripped) -* Fix Dagger duplicated classes (because of Dagger dependency ksp/kapt mixing) -* Build reproducibility improvements by specifying dependencies in `build.gradle` files -* Fix some of the issues in both `lawn` and `src` modules - -### Snapshot 2 - -This is a developer-focused change log: - -This snapshot milestone marked the first time Lawnchair now able to compile all supplementary -modules, `src` + `lawn` will be in Snapshot 5 or Development 1 milestone. - -* Merge flags -* Fix some issues with launcher3 sources. -* A temporary workaround with framworks.jar not adding in anim module. -* Shared not having access to animationlib. -* **Switch from javac variant to turbine-combined variant for prebuilt SystemUI-core-16**. - -### From Initial snapshot 0 and 1 - -This is a developer-focused change log: -* Prebuilt updated to Android 16-0.0_r2 (Android 16.0.0 Release 2) -* Submodule have also been refreshed to A16r2 -* Baklava Compatlib (QuickSwitch compatibility not guaranteed) -* Refreshed internal documentation like prebuilt, systemUI +And, as always, a huge thanks to all our code contributors for this cycle: @validcube, @Morty0Smith, @benjaminkitt, and @tgex0 diff --git a/OWNERS b/OWNERS index 3f7a780edc..a66bf54b58 100644 --- a/OWNERS +++ b/OWNERS @@ -6,8 +6,11 @@ adamcohen@google.com hyunyoungs@google.com +twickham@google.com vadimt@google.com winsonc@google.com +jonmiranda@google.com +awickham@google.com agvard@google.com # Launcher workspace eng team @@ -20,22 +23,19 @@ fransebas@google.com pinyaoting@google.com andonian@google.com sihua@google.com -abegovic@google.com # Multitasking eng team tracyzhou@google.com peanutbutter@google.com jeremysim@google.com atsjenk@google.com -hwwang@google.com +brianji@google.com # Overview eng team alexchau@google.com samcackett@google.com silvajordan@google.com uwaisashraf@google.com -vinayjoglekar@google.com -willosborn@google.com # Physical Keyboard & Trackpad eng team patmanning@google.com @@ -45,35 +45,11 @@ helencheuk@google.com shamalip@google.com zakcohen@google.com -# System Navigation team -brianji@google.com -jonmiranda@google.com -jagrutdesai@google.com -randypfohl@google.com -saumyaprakash@google.com -sukeshram@google.com -twickham@google.com -victortulias@google.com - -## Note: some of the below overlap and also work on other integrations like Circle to Search. - -# All Apps / QSB team -awickham@google.com -brdayauon@google.com -ganjam@google.com -kylim@google.com - -# Smartspace team -xilei@google.com -davidct@google.com -iamiam@google.com -jiuyu@google.com - per-file FeatureFlags.java, globs = set noparent -per-file FeatureFlags.java = sunnygoyal@google.com, winsonc@google.com, adamcohen@google.com, hyunyoungs@google.com, captaincole@google.com, abegovic@google.com +per-file FeatureFlags.java = sunnygoyal@google.com, winsonc@google.com, adamcohen@google.com, hyunyoungs@google.com, captaincole@google.com per-file DeviceConfigWrapper.java, globs = set noparent -per-file DeviceConfigWrapper.java = sunnygoyal@google.com, winsonc@google.com, adamcohen@google.com, hyunyoungs@google.com, abegovic@google.com +per-file DeviceConfigWrapper.java = sunnygoyal@google.com, winsonc@google.com, adamcohen@google.com, hyunyoungs@google.com # Predictive Back -per-file LauncherBackAnimationController.java = shanh@google.com, gallmann@google.com +per-file LauncherBackAnimationController.java = shanh@google.com, gallmann@google.com \ No newline at end of file diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg index 768ba652de..9051ca8562 100644 --- a/PREUPLOAD.cfg +++ b/PREUPLOAD.cfg @@ -1,10 +1,8 @@ [Builtin Hooks] ktfmt = true -bpfmt = true [Builtin Hooks Options] ktfmt = --kotlinlang-style -bpfmt = -d [Tool Paths] ktfmt = ${REPO_ROOT}/external/ktfmt/ktfmt.sh @@ -12,3 +10,4 @@ ktfmt = ${REPO_ROOT}/external/ktfmt/ktfmt.sh [Hook Scripts] checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --config_xml tools/checkstyle.xml --sha ${PREUPLOAD_COMMIT} +flag_hook = ${REPO_ROOT}/frameworks/base/packages/SystemUI/flag_check.py --msg=${PREUPLOAD_COMMIT_MESSAGE} --files=${PREUPLOAD_FILES} --project=${REPO_PATH} diff --git a/README.md b/README.md index 1c651f2780..694336fea5 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Lawnchair 16 +# Lawnchair 15 [![Build debug APK](https://github.com/LawnchairLauncher/lawnchair/actions/workflows/ci.yml/badge.svg)](https://github.com/LawnchairLauncher/lawnchair/actions/workflows/ci.yml) [![Build release APK](https://github.com/LawnchairLauncher/lawnchair/actions/workflows/release_update.yml/badge.svg)](https://github.com/LawnchairLauncher/lawnchair/actions/workflows/release_update.yml) @@ -9,31 +9,25 @@ [![GitHub Downloads](https://img.shields.io/github/downloads/LawnchairLauncher/lawnchair/total.svg?label=GitHub%20Downloads&logo=github)](https://github.com/LawnchairLauncher/lawnchair/releases) [![Play Store Installs](https://img.shields.io/endpoint?color=green&logo=googleplay&logoColor=green&url=https%3A%2F%2Fplay.cuzi.workers.dev%2Fplay%3Fi%3Dapp.lawnchair.play%26l%3DPlay%2520Store%2520Installs%26m%3D%24shortinstalls)](https://play.google.com/store/apps/details?id=app.lawnchair.play) -> [!WARNING] -> This branch contains major changes from the rebase of Launcher3, including breaking changes and refactors that can cause Lawnchair to break. -> -> If you wish to contribute, read our [contributing guidelines](CONTRIBUTING.md). Note that this branch will undergo many changes as we slowly refactor our codebase, so the `16-dev` branch may be particularly unfriendly to new contributors. It is still possible to submit changes to `15-dev`, but new feature development will be focused on this branch. -> -> For regular users, we recommend staying on `15-dev` for stability purposes. - - + - - A device running Lawnchair Launcher with green flower wallpaper + + Google Pixel running Lawnchair Launcher with green wallpaper Lawnchair is a free, open-source home app for Android. Taking Launcher3—Android’s default home app—as a starting point, it ports Pixel Launcher features and introduces rich customization options. -This branch houses the codebase of Lawnchair 16, which is currently in development and is based on Launcher3 from Android 16. For Lawnchair 9 to 15, see the branches with the `9-` to `15-` prefixes, respectively. +This branch houses the codebase of Lawnchair 15, which is currently in beta and is based on Launcher3 from Android 15. For Lawnchair 9 to 14, see the branches with the `9-` to `14-` prefixes, respectively. ## Features -- **Material Expressive Theming:** Adapts to your wallpaper and system theme. +- **Material You Theming:** Adapts to your wallpaper and system theme. - **At a Glance Widget:** Displays information *at a glance* with support for [Smartspacer](https://github.com/KieronQuinn/Smartspacer). -- **QuickSwitch Support:** Integrates with Android Recents on Android 10-15. (requires root) +- **QuickSwitch Support:** Integrates with Android Recents on Android 10 and newer. (requires root) - **Global Search:** Allows quick access to apps, contacts, and web results from the home screen. - **Customization Options:** Provides options to tweak icons, fonts, and colors to your liking. - And more! @@ -44,26 +38,26 @@ This branch houses the codebase of Lawnchair 16, which is currently in developme - - Get it on Google Play + + Get it on Google Play - - Get it on IzzyOnDroid + + Get it on IzzyOnDroid - - Get it on Obtainium + + Get it on Obtainium - - Get it on GitHub + + Get it on GitHub

@@ -80,11 +74,21 @@ These builds offer the latest features and bug fixes at a cost of being slower a ### Verification -Please visit [Lawnchair Verification](VERIFICATION.md) on way to verify Lawnchair. +Verify the integrity of your Lawnchair download using these SHA-256 hashes: + +###### Google Play +``` +47:AC:92:63:1C:60:35:13:CC:8D:26:DD:9C:FF:E0:71:9A:8B:36:55:44:DC:CE:C2:09:58:24:EC:25:61:20:A7 +``` + +###### Elsewhere +``` +74:7C:36:45:B3:57:25:8B:2E:23:E8:51:E5:3C:96:74:7F:E0:AD:D0:07:E5:BA:2C:D9:7E:8C:85:57:2E:4D:C5 +``` ## Contributing -Please visit the [Lawnchair contributing guidelines](CONTRIBUTING.md) for information and tips on contributing to Lawnchair. +Please visit the [Lawnchair Contributing Guidelines](CONTRIBUTING.md) for information and tips on contributing to Lawnchair. ## Supporting Lawnchair diff --git a/SECURITY.md b/SECURITY.md index 430d3a7682..9aa0b671f6 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -9,9 +9,8 @@ The latest version of Lawnchair is the only supported version. | Version | Supported | -|----------------|--------------------| +| -------------- | ------------------ | | Nightly build | :white_check_mark: | -| 16 | :white_check_mark: | | 15 | :white_check_mark: | | 14 | :x: | | 13 | :x: | diff --git a/TELEGRAM_CHANGELOG.txt b/TELEGRAM_CHANGELOG.txt index 104b91b32e..47db600976 100644 --- a/TELEGRAM_CHANGELOG.txt +++ b/TELEGRAM_CHANGELOG.txt @@ -1,197 +1,10 @@ -Lawnchair 16 pE Development 2 is here! Contributors are encouraged to target this branch instead of -older (i.e., Lawnchair `15-dev`). +*Lawnchair 15 Beta 1 is here!* -### Development 2 +We're excited to release the first beta for Lawnchair 15, a foundational update based on Android 15. -Originally going to launch D2 if comestic bug fixes have been resolved, but hit a -stability milestone instead. +The biggest new feature is one of our most requested ever: **App Drawer Folders**. You can now create and reorder custom folders to finally organize your app drawer. -This release includes 15 new features, and 20 bug fixes, -Lawnchair settings now takes shape of initial material 3 expressive redesign, [(but by no mean finish!)](https://www.google.com/teapot) -launcher should now render icons better than D1 milestone, with auto-adaptive icons feature reimplemented. +This release is packed with many other improvements, from a more powerful dock to dozens of UI refinements. -This release have been tested with: -* ☁️ Pixel 6 (Android 12.0) - Build: Ad-hoc -* ☁️ Pixel 6a (Android 12.1) - Build: Ad-hoc -* ☁️ Pixel 7 (Android 13) - Build: Ad-hoc -* ☁️ Pixel 9 (Android 15, Android 16.0) - Build: Ad-hoc -* ☁️ Pixel 9 Pro Fold (Android 14, Android 15) - Build: Ad-hoc -* ☁️ Vivo V40 (Android 15) - Build: Ad-hoc -* ☁️ Xiaomi MIX (Android 15) - Build: Ad-hoc -* 📱 Nothing (3a)-series (Android 15) - Build: pE-`15102025` -* 📱 Pixel 9 Pro XL (Android 16.0 QPR2 Beta 2) - Build: pE-`02102025` -* 📱 BLU View 5 Pro (Android 14) - Build: pE-`02102025` -* 📱🔥 Vivo Y21 (Android 12.0) - Build: pE-`08102025` - -> [!NOTE] -> QuickSwitch compatibility have not been tested at any time during the development of Bubble Tea! - -Compatibility list: - -| 🏗️ Crash | 💫 Limited features | 🥞 Fully supported | -|-------------|---------------------|--------------------| -| Android 8.1 | Android 12.0 | Android 12.1 | -| Android 9 | | Android 13 | -| Android 10 | | Android 14 | -| Android 11 | | Android 15 | -| | | Android 16 | - -#### Features - -* Enable All Apps Blur Flags on Phone (oops, forgot about the allAppsSheetForHandheld flag) -* Make Safe Mode check more reliable -* Smartspace Battery now reports battery charging status of Fast (more than 90% of 20 W) and Slow (less than 90% of 5 W) charging -* Show pseudonym version to Settings -* Resizing workspace calculate items position more accurately -* Update Lawnchair default grid size to 4×7 (or 4×6 with smartspace widget) -* Reimplement Hotseat background customisation -* Make haptic on a locked workspace use Google MSDL vibration -* Make Launcher3 colour more accurate to upstream Android 16 -* ProvideComposeSheetHandler now have expressive blur -* Lawnchair Settings now uses Material 3 Expressive -* Animate keyboard on/off state on app drawer search (Try enabling automatically show keyboard in app drawer settings and swipe up and down or directly tap “Apps list” in popup menu) -> (Backport not possible) -* Add LeakCanary check to all debug variant of the application -* [DEBUG] Launcher3 feature status diagnostic check in debug menu -* [Documentation] Add more visibility into both app certificate and SLSA verification for app authenticity check [VERIFICATION.md](VERIFICATION.md) -* [Documentation] Initial drafting of Improve documentation v6 (pave-path) -* [Launcher] Widget animations during resize -* [Iconloaderlib] Enable second hand for the clock app - -#### Fixes - -* Fix unable to access preview for icon style -* Popup's Arrow Theme now has the correct theme -* Widget should open normally after a workaround (C7evQZDJ) -* Fix (1) Search bar and Dock, (2) Folders and App Drawer settings didn't open due to init problems -* Lawnchair should hopefully remember what grid they should be using -* Most if not all of Lawnchair settings should be usable without crashes -* Correct Baseline Profile from old `market` to `play` variant, and now should calculate profile for `nightly` -* Fix Private Space crash when Lawnchair is set as Launcher due to flags only available on A16 -* Fix crash on a device with strict export receiver requirements on A14 -* Interactable widget crashing due to App Transition Manager being null (C7evQZDJ) -* Icon not responding to mouse cursor -> (Backported to Lawnchair 15) -* Rare NoSuchMethodError crash on IMS canImeRenderGesturalNavButtons -* [Lawnchair] Reimplement Bulk icons toggle -* SettingsCache crashing with SecurityException with unreadable keys (@hide) in Android 12 and newer (assume false) -* Assume flags `enableMovingContentIntoPrivateSpace` is false when ClassNotFoundException on Android 16 devices -* Rare NoSuchMethodError crash on SurfaceControl setEarlyWakeupStart and setEarlyWakeupEnd -* Properly align built-in smartspace in workspace -* Use WM Proxy from Lawnchair instead of System, fix Android 8.1/9/10/11/12.0/12.1 regarding SE, NSME like SystemBarUtils -> (dWkyIGw9), (reworked CllOXHJv) - * LawnchairWindowManagerProxy have been migrated to Dagger - * SystemWindowManagerProxy have been left unused -* [Lawnchair/Iconloaderlib] Update CustomAdaptiveIconDrawable to latest AOSP 13 -* [Iconloaderlib] Reset most of the changes to favour more AOSP 16_r02 code then Lawnchair (need rewrite) - * fix icon loaded in monochrome and always monochrome when it is not supposed to - * fix notification dots being twice the size with notification count -* [Lawnchair/Iconloaderlib] Reimplement Lawnchair Iconloaderlib (adaptive icons, monochrome, regular icon) - -#### Known Bugs -* Preview can't show device wallpaper -> (lIxkAYGg) -* IDP Preview doesn't refresh on settings change -> workaround is to hit apply and re-open the preview -> (ZbLX3438) -* Workspace theme doesn't refresh until restart -> (ZbLX3438) -> Fixed as part of (31lLEflf, 1MevNrzp) -* Lawnchair Colour can't handle restart causing default colour to be used instead -> Fixed? -> Properly fixed as part of (31lLEflf, 1MevNrzp) -* (Investigating) Work profile switch on widget selector *may* have reverted to Lawnchair 15 style -* Full lists: https://trello.com/b/8IdvO81K/pe-lawnchair - -### 🥞 Development 1 - -First development milestone! Basic launcher functionality should be stable enough. - -* Make Lawnchair Launcher launchable in Android 12.1, 13, 14, 15, 16 -* Remove two deprecated features (Use Material U Popup, and Use dot pagination) -* Add pseudonym version in debug settings -* Adapt Lawnchair code to Launcher3 16 -* Make basic features of Launcher work (App Drawer, Home Screen, Search, Folders, Widgets) -* Enable Material Expressive Flags (Try swiping through launcher page) -* Enable All Apps Blur Flags (Try opening All Apps on supported devices) -* Enable MSDL Haptics Feedback Flags (Try gliding widget or icons across the homescreen) -* Make Predictive Back Gesture work on Android 13, 14, 15, 16 (Try swiping left or right on gesture-based navigational) -* Programmatically set Safe Mode status - -#### Known Bugs - -* App Icon may sometimes render with less than 0 in height/width causing blank icon to be rendered and crashing ISE on customising icons -> (31lLEflf) -* Any Lawnchair settings using IDP will crash the launcher -> Fixed in Lawnchair 16 pE Development 2 -* Icon pack isn't usable -> (DXo69Qzd) -* Dynamic icons will not be themed by launcher -* Full lists: https://trello.com/b/8IdvO81K/pe-lawnchair - -### Snapshot 6 - -This is a developer-focused change log: - -This snapshot marks the first time Lawnchair 16 is able to compile and build an APK! - -* Fix all issues with Java files in both `lawn` and `src` -* Make Lawnchair compilable (with instant crash) -* Move to KSP for Dagger code generation - -### Snapshot 5 - -This is a developer-focused change log: - -This snapshot now able to compile all sources (Kotlin files only) - -* Fix MORE MORE MORE `lawn` issues -* Use Gradle Version Catalog for consistent dependency version across all modules (Full implementation @ LawnchairLauncher/Lawnchair#5753) -* Magically fix ASM Instrumentation issues (I didn't do anything, it just works now) -* Fix ALL the issues in kotlin stage (`compileLawnWithQuickstepNightlyDebugKotlin`) -* Reintroduce some features from Lawnchair -* Add compatibility checks and workarounds for them -* Fix most issues with Java files in both `lawn` and `src` - -### Snapshot 4 - -This is a developer-focused change log: - -This snapshot marks the first time Lawnchair 16 is able to compile all Launcher3 sources! - -* Add `MSDLLib` to `platform_frameworks_libs_systemui` -* Add `contextualeducationlib` to `platform_frameworks_libs_systemui` -* Fix issues in both `lawn` and `src` modules -* Fix AIDL sources -* Resolve Lawnchair/LC-TODO lists -* Merge `wmshell.shared` res with res from `wmshell` -* Consistent build reproducibility by specifying dependencies in `build.gradle` -* Some ASM Instrumentation issues (and re-add some…) -* Update documentations - -### Snapshot 3 - -This is a developer-focused change log: - -Not a lot of errors left to go! - -* Finish correctly implementing all Dagger functions (?) -* Merge Lawnchair 15 Beta 1 into Bubble Tea - * Support for 16-kb page size devices -* Repository rebased and dropped commit - * Switch back from turbine-combined variant to javac variant for prebuilt SystemUI-core-16 because issues with LFS - * MORE MORE fixes regarding turbine-combined to javac -* Publish `platform_frameworks_libs_systemui` to pe 16-dev branch -* ATLEAST check to almost every launcher3 source file -* `Utils` module (stripped) -* Fix Dagger duplicated classes (because of Dagger dependency ksp/kapt mixing) -* Build reproducibility improvements by specifying dependencies in `build.gradle` files -* Fix some of the issues in both `lawn` and `src` modules - -### Snapshot 2 - -This is a developer-focused change log: - -This snapshot milestone marked the first time Lawnchair now able to compile all supplementary -modules, `src` + `lawn` will be in Snapshot 5 or Development 1 milestone. - -* Merge flags -* Fix some issues with launcher3 sources. -* A temporary workaround with framworks.jar not adding in anim module. -* Shared not having access to animationlib. -* **Switch from javac variant to turbine-combined variant for prebuilt SystemUI-core-16**. - -### From Initial snapshot 0 and 1 - -This is a developer-focused change log: -* Prebuilt updated to Android 16-0.0_r2 (Android 16.0.0 Release 2) -* Submodule have also been refreshed to A16r2 -* Baklava Compatlib (QuickSwitch compatibility not guaranteed) -* Refreshed internal documentation like prebuilt, systemUI +**Read the full announcement on our blog:** +https://lawnchair.app/blog/lawnchair-15-beta-1 diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig index b083390b06..f1f9966f78 100644 --- a/aconfig/launcher.aconfig +++ b/aconfig/launcher.aconfig @@ -22,6 +22,13 @@ flag { bug: "316027081" } +flag { + name: "enable_grid_only_overview" + namespace: "launcher" + description: "Enable a grid-only overview without a focused task." + bug: "257950105" +} + flag { name: "enable_cursor_hover_states" namespace: "launcher" @@ -36,6 +43,13 @@ flag { bug: "302189128" } +flag { + name: "enable_overview_icon_menu" + namespace: "launcher" + description: "Enable updated overview icon and menu within task." + bug: "257950105" +} + flag { name: "enable_focus_outline" namespace: "launcher" @@ -223,6 +237,13 @@ flag { bug: "323886237" } +flag { + name: "enable_refactor_task_thumbnail" + namespace: "launcher" + description: "Enables rewritten version of TaskThumbnailViews in Overview" + bug: "331753115" +} + flag { name: "enable_handle_delayed_gesture_callbacks" namespace: "launcher" @@ -235,7 +256,7 @@ flag { flag { name: "enable_fallback_overview_in_window" - namespace: "lse_desktop_experience" + namespace: "launcher" description: "Enables fallback recents opening inside of a window instead of an activity." bug: "292269949" } @@ -289,418 +310,9 @@ flag { } } -flag { - name: "enable_container_return_animations" - namespace: "launcher" - description: "Enables the container return animation mirroring launches." - bug: "341017746" -} - flag { name: "floating_search_bar" namespace: "launcher" description: "Search bar persists at the bottom of the screen across Launcher states" bug: "346408388" } - -flag { - name: "all_apps_sheet_for_handheld" - namespace: "launcher" - description: "All Apps will be presented on a bottom sheet in handheld mode" - bug: "374186088" -} - -flag { - name: "all_apps_blur" - namespace: "launcher" - description: "Content behind the all apps panel in Launcher will be blurred." - bug: "400827727" -} - -flag { - name: "multiline_search_bar" - namespace: "launcher" - description: "Search bar can wrap to multi-line" - bug: "341795751" -} - -flag { - name: "enable_multi_instance_menu_taskbar" - namespace: "launcher" - description: "Menu in Taskbar with options to launch and manage multiple instances of the same app" - bug: "355237285" -} - -flag { - name: "navigate_to_child_preference" - namespace: "launcher" - description: "Settings screen supports navigating to child preference if the key is not on the screen" - bug: "293390881" -} - -flag { - name: "use_new_icon_for_archived_apps" - namespace: "launcher" - description: "Archived apps will use new cloud icon in app title instead of overlay" - bug: "350758155" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { - name: "letter_fast_scroller" - namespace: "launcher" - description: "Change fast scroller to a lettered list" - bug: "358673724" -} - -flag { - name: "enable_desktop_task_alpha_animation" - namespace: "launcher" - description: "Enables the animation of the desktop task's background view" - bug: "320307666" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { - name: "ignore_three_finger_trackpad_for_nav_handle_long_press" - namespace: "launcher" - description: "Ignore three finger trackpad event for nav handle long press" - bug: "342143522" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { - name: "work_scheduler_in_work_profile" - namespace: "launcher" - description: "Enables work scheduler view above the work pause button in work profile." - bug: "361589193" -} - -flag { - name: "one_grid_specs" - namespace: "launcher" - description: "Defines the new specs for grids based on OneGrid" - bug: "364711064" -} - -flag { - name: "one_grid_mounted_mode" - namespace: "launcher" - description: "Support a fixed landscape mode for handheld devices" - bug: "364711735" -} - -flag { - name: "one_grid_rotation_handling" - namespace: "launcher" - description: "New landscape approach for the workspace using different rows and columns in landscape and portrait" - bug: "364711814" -} - -flag { - name: "grid_migration_refactor" - namespace: "launcher" - description: "Refactor grid migration such that the code is simpler to understand and update" - bug: "358399271" -} - -flag { - name: "accessibility_scroll_on_allapps" - namespace: "launcher" - description: "Scroll to item position if accessibility focused" - bug: "265392261" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { - name: "enable_dismiss_prediction_undo" - namespace: "launcher" - description: "Show an 'Undo' snackbar when users dismiss a predicted hotseat item" - bug: "270394476" -} - -flag { - name: "enable_all_apps_button_in_hotseat" - namespace: "launcher" - description: "Enables displaying the all apps button in the hotseat." - bug: "270393897" -} - -flag { - name: "taskbar_quiet_mode_change_support" - namespace: "launcher" - description: "Support changing quiet mode for user profiles in taskbar." - bug: "345760034" -} - -flag { - name: "taskbar_overflow" - namespace: "launcher" - description: "Show recent apps in the taskbar overflow." - bug: "368119679" -} - -flag { - name: "enable_active_gesture_proto_log" - namespace: "launcher" - description: "Enables tracking active gesture logs in ProtoLog" - bug: "293182501" -} - -flag { - name: "enable_recents_window_proto_log" - namespace: "lse_desktop_experience" - description: "Enables tracking recents window logs in ProtoLog" - bug: "292269949" -} - -flag { - name: "enable_state_manager_proto_log" - namespace: "lse_desktop_experience" - description: "Enables tracking state manager logs in ProtoLog" - bug: "292269949" -} - -flag { - name: "coordinate_workspace_scale" - namespace: "launcher" - description: "Ensure that the workspace and hotseat scale doesn't conflict and transitions smoothly between launching and closing apps" - bug: "366403487" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { - name: "enable_tiered_widgets_by_default_in_picker" - namespace: "launcher" - description: "Shows filtered set of widgets by default and an option to show all widgets in the widget picker" - bug: "356127021" -} - -flag { - name: "show_taskbar_pinning_popup_from_anywhere" - namespace: "launcher" - description: "Shows the pinning popup view after long-pressing or right-clicking anywhere on the pinned taskbar" - bug: "297325541" -} - -flag { - name: "enable_launcher_overview_in_window" - namespace: "lse_desktop_experience" - description: "Enables launcher recents opening inside of a window instead of being hosted in launcher activity." - bug: "292269949" -} - -flag { - name: "use_system_radius_for_app_widgets" - namespace: "launcher" - description: "Use system radius for enforced widget corners instead of a separate 16.dp value" - bug: "373351337" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { - name: "enable_contrast_tiles" - namespace: "launcher" - description: "Enable launcher app contrast tiles." - bug: "341217082" -} - -flag { - name: "msdl_feedback" - namespace: "launcher" - description: "Enable MSDL feedback for Launcher interactions" - bug: "377496684" -} - -flag { - name: "enable_pinning_app_with_context_menu" - namespace: "launcher" - description: "Add options to pin/unpin to taskbar to app context menus." - bug: "375648361" -} - -flag { - name: "enable_launcher_icon_shapes" - namespace: "launcher" - description: "Enable launcher icon shape customizations" - bug: "348708061" -} - -flag { - name: "predictive_back_to_home_polish" - namespace: "launcher" - description: "Enables workspace reveal animation for predictive back-to-home" - bug: "382453424" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { - name: "predictive_back_to_home_blur" - namespace: "launcher" - description: "Adds blur for predictive back-to-home" - bug: "342178850" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { - name: "enable_launcher_visual_refresh" - namespace: "launcher" - description: "Adds refresh for font family, app longpress menu icons, and pagination dots" - bug: "395145453" -} - -flag { - name: "gsf_res" - namespace: "launcher" - description: "Adds refresh for font family. Needs to be fixed to be used in resources." - bug: "395145453" - is_fixed_read_only: true - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { - name: "restore_archived_shortcuts" - namespace: "launcher" - description: "Makes sure pre-archived pinned shortcuts also get restored" - bug: "375414891" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { - name: "restore_archived_app_icons_from_db" - namespace: "launcher" - description: "Restores pre-archived icons from db when available, mimicing promise icons" - bug: "391913214" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { - name: "enable_mouse_interaction_changes" - namespace: "launcher" - description: "Changes mouse interaction behavior" - bug: "388897603" -} - -flag { - name: "enable_alt_tab_kqs_on_connected_displays" - namespace: "lse_desktop_experience" - description: "Enable Alt + Tab KQS support on connected displays" - bug: "394007677" -} - -flag { - name: "expressive_theme_in_taskbar_and_navigation" - namespace: "launcher" - description: "Enables the expressive theme and GSF font styles for Taskbar and Gesture Navigation" - bug: "394613212" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { - name: "enable_strict_mode" - namespace: "launcher" - description: "Enable Strict Mode for the Launcher app" - bug: "394651876" -} - -flag { - name: "extendible_theme_manager" - namespace: "launcher" - description: "Enables custom theme manager in Launcher" - bug: "381897614" -} - -flag { - name: "enable_alt_tab_kqs_flatenning" - namespace: "lse_desktop_experience" - description: "Enable Alt + Tab KQS view to show apps in flattened structure" - bug: "382769617" -} - -flag { - name: "enable_gesture_nav_on_connected_displays" - namespace: "lse_desktop_experience" - description: "Enables gesture navigation handling on connected displays" - bug: "382130680" -} - -flag { - name: "enable_taskbar_behind_shade" - namespace: "lse_desktop_experience" - description: "Keeps taskbar behind notification shade when its pulled down" - bug: "343194358" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { - name: "enable_scalability_for_desktop_experience" - namespace: "launcher" - description: "Enable more grid scale options on the launcher for desktop experience" - bug: "375491272" -} - -flag { - name: "enable_gesture_nav_horizontal_touch_slop" - namespace: "launcher" - description: "Enables horizontal touch slop checking in non-vertical fling navigation gestures" - bug: "394364217" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { - name: "sync_app_launch_with_taskbar_stash" - namespace: "launcher" - description: "Syncs the two animations (app launch, taskbar stash) so they play at the same time." - bug: "319162553" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { - name: "remove_apps_refresh_on_right_click" - namespace: "launcher" - description: "Remove predicted apps refresh on right click" - bug: "343650193" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { - name: "enable_taskbar_for_direct_boot" - namespace: "launcher" - description: "Initializes parts of Taskbar before onUserUnlocked" - bug: "324485921" - metadata { - purpose: PURPOSE_BUGFIX - } -} diff --git a/androidx-lib/build.gradle b/androidx-lib/build.gradle index 39d8e88618..ec0c42af04 100644 --- a/androidx-lib/build.gradle +++ b/androidx-lib/build.gradle @@ -12,5 +12,5 @@ android { } } -addFrameworkJar('framework-16.jar') +addFrameworkJar('framework-15.jar') compileOnlyCommonJars() diff --git a/baseline-profile/build.gradle b/baseline-profile/build.gradle index 412c9afb6d..292b9057dd 100644 --- a/baseline-profile/build.gradle +++ b/baseline-profile/build.gradle @@ -20,17 +20,11 @@ android { lawn { dimension = "app" } withQuickstep { dimension = "recents" } github { dimension = "channel" } - nightly { dimension = "channel" } play { dimension = "channel" } } - testOptions.managedDevices.localDevices { - create("pixel7Api36") { - device = "Pixel 7" - apiLevel = 36 - systemImageSource = "google" - } - create("pixel6Api33") { + testOptions.managedDevices.devices { + pixel6Api33(ManagedVirtualDevice) { device = "Pixel 6" apiLevel = 33 systemImageSource = "google" @@ -41,7 +35,7 @@ android { // This is the configuration block for the Baseline Profile plugin. // You can specify to run the generators on a managed devices or connected devices. baselineProfile { - managedDevices += ["pixel6Api33", "pixel7Api36"] + managedDevices += "pixel6Api33" useConnectedDevices = false } diff --git a/baseline-profile/src/main/java/app/lawnchair/baseline/BaselineProfileGenerator.kt b/baseline-profile/src/main/java/app/lawnchair/baseline/BaselineProfileGenerator.kt index c4fdc13af7..a55a86c1bd 100644 --- a/baseline-profile/src/main/java/app/lawnchair/baseline/BaselineProfileGenerator.kt +++ b/baseline-profile/src/main/java/app/lawnchair/baseline/BaselineProfileGenerator.kt @@ -1,7 +1,5 @@ package app.lawnchair.baseline -import android.os.Build -import androidx.annotation.RequiresApi import androidx.benchmark.macro.junit4.BaselineProfileRule import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.LargeTest @@ -35,11 +33,9 @@ import org.junit.runner.RunWith class BaselineProfileGenerator { @get:Rule - @RequiresApi(Build.VERSION_CODES.P) val rule = BaselineProfileRule() @Test - @RequiresApi(Build.VERSION_CODES.P) fun generate() { rule.collect(Constants.PACKAGE_NAME) { // This block defines the app's critical user journey. Here we are interested in diff --git a/build.gradle b/build.gradle index 9b7ac4da96..59550abcfe 100644 --- a/build.gradle +++ b/build.gradle @@ -20,6 +20,7 @@ plugins { alias(libs.plugins.diffplug.spotless) } + allprojects { plugins.withType(AndroidBasePlugin).configureEach { android { @@ -31,7 +32,7 @@ allprojects { } defaultConfig { minSdk 26 - targetSdk 36 + targetSdk 35 vectorDrawables.useSupportLibrary = true } lint { @@ -45,7 +46,6 @@ allprojects { } dependencies { implementation libs.androidx.core.ktx - implementation libs.androidx.core.animation } } @@ -119,9 +119,9 @@ allprojects { compileOnlyCommonJars = { dependencies { - compileOnly fileTree(dir: FRAMEWORK_PREBUILTS_DIR, include: 'SystemUI-core-16.jar') - compileOnly fileTree(dir: FRAMEWORK_PREBUILTS_DIR, include: 'SystemUI-statsd-16.jar') - compileOnly fileTree(dir: FRAMEWORK_PREBUILTS_DIR, include: 'WindowManager-Shell-16.jar') + compileOnly fileTree(dir: FRAMEWORK_PREBUILTS_DIR, include: 'SystemUI-core.jar') + compileOnly fileTree(dir: FRAMEWORK_PREBUILTS_DIR, include: 'SystemUI-statsd.jar') + compileOnly fileTree(dir: FRAMEWORK_PREBUILTS_DIR, include: 'WindowManager-Shell-15.jar') compileOnly projects.compatLib compileOnly projects.compatLib.compatLibVQ @@ -130,7 +130,6 @@ allprojects { compileOnly projects.compatLib.compatLibVT compileOnly projects.compatLib.compatLibVU compileOnly projects.compatLib.compatLibVV - compileOnly projects.compatLib.compatLibVBaklava } } } @@ -145,22 +144,20 @@ final def ciRef = System.getenv("GITHUB_REF") ?: "" final def ciRunNumber = System.getenv("GITHUB_RUN_NUMBER") ?: "" final def isReleaseBuild = ciBuild && ciRef.contains("beta") final def devReleaseName = ciBuild ? "Dev.(#${ciRunNumber})" : "Dev.(${buildCommit})" -final def version = "16" -final def releaseName = "Development 3 Release 2" +final def version = "15" +final def releaseName = "Beta 1" final def versionDisplayName = "${version}.${isReleaseBuild ? releaseName : devReleaseName}" final def majorVersion = versionDisplayName.split("\\.")[0] final def quickstepMinSdk = "29" -final def quickstepMaxSdk = "36" +final def quickstepMaxSdk = "35" android { namespace "com.android.launcher3" defaultConfig { - /* - * Lawnchair Launcher 16.0 Development 3 Release 2 - * see CONTRIBUTING.md#versioning-scheme - */ - versionCode 16_00_01_03_02 + // Lawnchair Launcher 15.0 Beta 1 + // See CONTRIBUTING.md#versioning-scheme + versionCode 15_00_02_01 versionName "${versionDisplayName}" buildConfigField "String", "VERSION_DISPLAY_NAME", "\"${versionDisplayName}\"" buildConfigField "String", "MAJOR_VERSION", "\"${majorVersion}\"" @@ -248,18 +245,12 @@ android { debug { applicationIdSuffix ".debug" resValue("string", "derived_app_name", "Lawnchair (Debug)") - - manifestPlaceholders.quickstepMinSdk = "0" - manifestPlaceholders.quickstepMaxSdk = "100000" - buildConfigField "int", "QUICKSTEP_MIN_SDK", "0" - buildConfigField "int", "QUICKSTEP_MAX_SDK", "100000" } release { resValue("string", "derived_app_name", "Lawnchair") minifyEnabled true shrinkResources true - pseudoLocalesEnabled false proguardFiles proguardFilesFromAosp + "proguard.pro" } } @@ -352,7 +343,7 @@ android { withQuickstep { res.srcDirs = ['quickstep/res', 'quickstep/recents_ui_overrides/res'] - java.srcDirs = ['quickstep/src', 'quickstep/dagger', 'quickstep/recents_ui_overrides/src', 'quickstep/src_protolog'] + java.srcDirs = ['quickstep/src', 'quickstep/recents_ui_overrides/src'] manifest.srcFile "quickstep/AndroidManifest.xml" } } @@ -365,14 +356,12 @@ composeCompiler { reportsDestination = layout.buildDirectory.dir("compose_build_reports") } -addFrameworkJar('framework-16.jar') +addFrameworkJar('framework-15.jar') dependencies { implementation projects.iconloaderlib implementation projects.searchuilib implementation projects.animationlib - implementation projects.msdllib - implementation projects.contextualeducationlib // Recents lib dependency withQuickstepCompileOnly projects.hiddenApi @@ -384,7 +373,6 @@ dependencies { withQuickstepCompileOnly projects.plugin withQuickstepImplementation projects.plugincore withQuickstepCompileOnly projects.common - withQuickstepCompileOnly projects.utils // QuickSwitch Compat withQuickstepImplementation projects.compatLib @@ -394,15 +382,13 @@ dependencies { withQuickstepImplementation projects.compatLib.compatLibVT withQuickstepImplementation projects.compatLib.compatLibVU withQuickstepImplementation projects.compatLib.compatLibVV - withQuickstepImplementation projects.compatLib.compatLibVBaklava withQuickstepImplementation projects.wmshell withQuickstepImplementation projects.flags implementation libs.androidx.dynamicanimation - implementation fileTree(dir: FRAMEWORK_PREBUILTS_DIR, include: 'SystemUI-statsd-16.jar') - implementation fileTree(dir: FRAMEWORK_PREBUILTS_DIR, include: 'WindowManager-Shell-16.jar') - withQuickstepCompileOnly fileTree(dir: FRAMEWORK_PREBUILTS_DIR, include: 'framework-16.jar') - baselineProfile projects.baselineProfile + implementation fileTree(dir: FRAMEWORK_PREBUILTS_DIR, include: 'SystemUI-statsd-15.jar') + implementation fileTree(dir: FRAMEWORK_PREBUILTS_DIR, include: 'WindowManager-Shell-15.jar') + withQuickstepCompileOnly fileTree(dir: FRAMEWORK_PREBUILTS_DIR, include: 'framework-15.jar') coreLibraryDesugaring libs.android.desugarJdkLibs @@ -412,21 +398,14 @@ dependencies { implementation libs.androidx.recyclerview implementation libs.androidx.preference.ktx - implementation libs.javax.inject implementation libs.kotlinx.coroutines.android implementation libs.kotlinx.serialization.json implementation libs.chickenhook.restrictionbypass implementation libs.rikka.refine.runtime - implementation libs.androidx.activity.compose - implementation libs.androidx.constraintlayout - implementation libs.androidx.datastore.preferences - implementation platform(libs.compose.bom) implementation libs.compose.ui implementation libs.compose.ui.util - implementation libs.compose.ui.graphics - implementation libs.bundles.graphics debugImplementation libs.compose.ui.tooling implementation libs.compose.ui.tooling.preview implementation libs.compose.ui.google.fonts @@ -458,6 +437,9 @@ dependencies { implementation libs.libsu.service + // Persian Date + implementation libs.persian.date + implementation libs.airbnb.lottie // Compose drag and drop library @@ -471,17 +453,10 @@ dependencies { implementation libs.hoko.blur implementation libs.androidx.window - - ksp libs.dagger.compiler - implementation libs.dagger.hilt.android - ksp libs.dagger.hilt.compiler - - debugImplementation libs.leakcanary.android } ksp { arg("room.schemaLocation", "$projectDir/schemas") - arg("dagger.hilt.disableModulesHaveInstallInCheck", "true") } diff --git a/compatLib/README.md b/compatLib/README.md index 82a31bfe9a..2698ccba01 100644 --- a/compatLib/README.md +++ b/compatLib/README.md @@ -1,24 +1,15 @@ # Lawnchair Quickstep Compat Library -The `compatLib` library helps integrate Lawnchair with Recents -(also known as QuickSwitch, Quickstep, or sometimes, Lawnstep) -while ensuring backward-compatibility with older Android versions. +The `compatLib` library helps integrate Lawnchair with QuickSwitch while ensuring backward-compatibility with older Android versions. Each subdirectory of the `compatLib`, denoted by a letter (e.g., `compatLibVQ` for Android 10), refers to the compatibility code for that specific Android version. -Starting with Android 16 and above, the `compatLib` will denoted by the codename of the Android version -(e.g., `compatLibVBaklava` for Android 16). - -| Library | Android version | -|-------------------|-----------------| -| compatLibVQ | 10 | -| compatLibVR | 11 | -| compatLibVS | 12 | -| compatLibVT | 13 | -| compatLibVU | 14 | -| compatLibVV | 15 | -| compatLibVBaklava | 16 | - -Keep in mind that this list does not guarantee Recents compatibility with your Android versions, -as the implementation may still be in progress or not fully functional. +| Library | Android version | +|-------------|-----------------| +| compatLibVQ | 10 | +| compatLibVR | 11 | +| compatLibVS | 12 | +| compatLibVT | 13 | +| compatLibVU | 14 | +| compatLibVV | 15 | diff --git a/compatLib/build.gradle b/compatLib/build.gradle index 5c3b5a0e13..7813d645c4 100644 --- a/compatLib/build.gradle +++ b/compatLib/build.gradle @@ -3,7 +3,6 @@ plugins { } android { - buildToolsVersion "36.1.0" namespace "app.lawnchair.compatlib" buildFeatures { diff --git a/docs/assets/README.md b/docs/assets/README.md index 30e6cb7568..51ef527d05 100644 --- a/docs/assets/README.md +++ b/docs/assets/README.md @@ -1,19 +1,21 @@ -# Lawnchair Assets Guidelines +# Lawnchair Visual Guidelines This directory lists all the decoration & visual explainers used in the Lawnchair Documentation. -All assets created should use Material 3 design with `#47B84F` as source color, and +All assets created should use Material 3 design with `#47B84F` as source color and the [Inter](https://fonts.google.com/specimen/Inter) ([OFL v1.1](https://github.com/rsms/inter/?tab=OFL-1.1-1-ov-file#readme)) -typography. +typography. Visit the [Material 3 theme builder][material-theme-builder] for more information. -Visit the [Material 3 theme builder][material-theme-builder] for more information on color. +When creating device mockups for Lawnchair, make sure that you're using the latest commits of +Lawnchair or use Lawnchair Nightly as base. -## Device mockup +## Device Mockup -When creating device mockups for Lawnchair, make sure the latest stable commits of Lawnchair is use -as base. All wallpapers, fonts and icon packs are allowed as long as they're free-to-use -under a permissive license like the licenses from Creative-Common. Using vanilla out-of-the-box -experience is recommended because they show the user what to expect from Lawnchair Launcher when -they first use it. +Use in: [README](/README.md) + +* Icon pack: [Lawnicons](https://github.com/LawnchairLauncher/lawnicons) +* Wallpaper: https://unsplash.com/photos/photography-of-green-leaves-ZVKr8wADhpc +* Color Extraction Technique: Tonal Spot from Lawnchair +* License: https://unsplash.com/license [material-theme-builder]: https://material-foundation.github.io/material-theme-builder/?primary=%2347B84F&bodyFont=Inter&displayFont=Inter&colorMatch=false diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/device-frame.jpg b/fastlane/metadata/android/en-US/images/phoneScreenshots/device-frame.jpg index e67c6542414d740ec720a3050d5481936d0c381f..70a926b3c8d7bf4766ff6ef5a07d9ff865d9f1bc 100644 GIT binary patch literal 35920 zcmb4qWmH>H({6ANuEE`lI}~?!w;-WFa4V(7JpqDiad!_CcP&;N3c;lmX`$ZR_x;v) z*Zq6v{G4-Uo|&`vI{TTK^LO*_0f0nXT}vH+h6Vtj{ks5vcK|8?d>kBH9Bh1CTwDTt zd;%a136O{g$UsR&O2fv)!OqIW%EHMf`HYiCjE99)NJ&^sT2?_pfkRMLOGQpoQeHv! zKSt2-2?&5hKzb4qdRcB(ZrT5D`8y0C#YI0uv&TSV1)!6nVUVKz9S6|=!-<9VAGrT3 zm}ux2Sl9p@+<#hq5&#+oIwm?6E)EtJHa0rOzmU)|FiEkmHjizsM(C2D@4W2Ru0)!0}!`&5-MsY;ApBC4wv?S_UO<682_C8Ki>X117Kod z|I=}k0?_|Q{_ibl=%g4dWWtzA=+~_06qL}>NnIRWPY?hw zH@ze!R?Y%Y+S6c~cOM?4Q~kV%+3eO(7#Wc(zaVVM%@zVrk$R@__q`nXh;ZBs_wi$aBW`O`kP{Zof* z^g2{eT|eiDngxe>?D+PkJW5EIk(rt!Nc+j@yist(WW?lI<%e zn8SlP$Cpx%J0- zs`7v>E z!RKb7&pQ8t&yDa)cxfY_aNM>Ct?OnNn0_uQ@y7OWFz3C~cg}wL(d}@vwlt&f{5H+_ z@UqfL1FG`8D?Yc6SHHMcD}KM$85|mokolLMXL9qI67@yeJM!fJRsHk!`G2E*Kc@-E zD6<1W3DQgb<#Dcd|7R_HZt)$``%gB|$s-l*z4lXm{^8a5_ZIl2V3aED6DFXdQ2YI_ z(T?pU5_`k2m2S4#<>mxvVKDFxcdbcCoudX4ech?OBtQMB-r`TFKWlo0R$WMOtThe$F60Ued|vV zD#(>oLF})(#?ZeJ;nOHpVYhqJQj`1aZL%YCcjoIGKR49U~Fu`DhU z^EfS8jWDrkjtx$@?3b`80W><0md6xWY_0%{Z4$r;}Z%eJQ1Kdu5bjo{r*1RI!`acI#t zVgzHja?&f+w#kuHF-u5dMw5%x$NTAv!2pnhjAcZ0#dwpOUiZjHaaTW2n9^o*aDh8v z1-E?kKu&8PYVO>iN%s_?w&vnfJ#A^4$dR_4kZDe0RQcGbc2inHzrayp<5j}nXk}gu z3ea3iPl1wO#GPSG*j`1DgFt(10Me-Ox;;I%!u+Py$icd0;|wrof&FZ-2BI9hwRe-? z!MI!YMMSdW{?w-%(>^D>NDmy#T>bm8Fq674Z% zAxbZaY4t+WvKp11w7EGFu`9j_?XK}J;b~rMp%c;C5K>1|AjxVZg^tjab0!Nan;o00g?1UCR+^6)5SCq zV+FX}Xr$3yrMm>Xj4!szn!Am0Pw`o0q$&d(*aS~M1)6?)WGYN>0S4%(Ob4hTA>_D& z{V@~P3ZSXn_}TK8ct?ID(+8`g7htg&6@K^TIF=@?@%V39B__JS_U?0O$SK{sOqAh@ zs#V=Vefista-r0L98nZ$TUBdtQRpo$Mu`TsR4(~=9k`~C*(6eSmDdhfhR^5>T0p-i6=DD~vM!j!wx!@lQ z`Y{MC`hAP*q?jmnvGde1-b)!|+#h-{Xka!dNapo`}&b7tWJ5phgURN((d8-C4 zDZSi_&NvqwMX%ONWE?IHMzYTqy{RDWE+`)&p5zgxT0uJ@oyc!VCHGqQUHj+Pi6UN< z8FmsAf?Y^#z3VLXniPJx+2u{eWEA^hNtq*Og<467ov`l`*n@$D^vQ8nP5P!1lT4h1 zy&?pT)j_5VEl)poAC*bS6jH8~q?zOSnTAaOsqHuk1UWbHD&bC~ZyreWcN>AV&YFWZ zS0kRK6T-aDD{PJFtn>5fr5c<(tpmMWTQ~+W8)H)Lg-_y|Li@F-dT*4Hq;BSR*($uo zeUpT>8|@2;qcXn&<~PXBT!#Rm+;=5urcR_Prdwr=<+bc;X8PCjexR6fMpOz6o_W4- z*u?(ZOM?0@VAecTkI881oafGF0-Gg8X>E!#wiEJ{a?P*U2dJEngsXS8Q}s8<$_gf| zM+s~G$UTO=pS>z-;@s!k@N76ojo(?*6}(ffFOWKM9J5LQHZ!BmS}QFPU=$|aS=BYP zd68qD-wyAeJJ-i9h?YBYhIOP{A9;HLsMH|B+7k0cM1oxlQ8Jk$Uafx;mQdn^RaF-Z zgzdd#G_=f{w!1qz{Eyr(7*b4S>a(*l^YIYaqTJx#NU2c1ORC>$%T`x=Ta6PwSkEQK z<(^)C!C0?+RQ_=NUOuW_8pbkG8L2)F7^&P7TL5>nb4n%HWtk>X7edhAM-2c zlt_Q(7@<|Jus$0MTpS#4%&H7f+)cC2WoGg9j_+shVMtWWPW5Her_X`*0jt~9(4 zI^Tz~8C{uf?ndNH<8HKjHNdVf?<&_CuB*eNc3c;{zw*%R?&BNoTvMwoU-YP`K#MHt zMvq!eMEL4lz&02U-ma3Y`r)NA8tC^^UdJs^3#yD-L!lha4IpF-+f&Yu?pyUs@Oxd# z5uxpIEWXLQr1TjWBp`0!Vw6>ytVm2JsBt%MbPn1Zt=0hwt)m99I3DZ8t6uQYEZwi) zT!A26x~Z;okd9E57-4LTr?{MEi5=IulI#0l7YYz{b?eMv$W7}Rp z5ng*=Ip+!Im~uTI8?Bwwh}~K`I0P-oOjKrm*cZQde{q+rnnM;12B%ll&shK?_ea#? z7Wq9so{2q%FC)a<2kp%E^Ops59G5l1Xzq8c#vH8oif6|=_svB;rO2T&O>Mi?8V>MJ zLiFy}$!~|{4_(Vy=S)(6XPA6bU~#GNoh6%pi|CHAYA>Nf)>gZki7h5uE^I%7NDRG0 zoJX%}B%=NTVk|jS3bT4MVGZ_?0ngSqP`K@c;suHUA*efgJ3karpg?I+SS1?Pv3#c=gL=er~^C2E_1sUh5fFSFnyd zq>UISPcCSWAq6!*QFpuP2wPTB(wz0(us#_*SzOKMQ>cV%i0y)yUJ}u1Er-W_Ndt{A zzoLP%2*?@B%I3{k7Fz#+Ys>sH9-^wHl!`O$5+(IokvRscAkd{_3m_XIcWNrR+yhBB zCcyzSo!z$mRhB3EjJ_9lJa*(enLKQw1qv6z$02sX)M-aF^0Ia3o(_bnr$nvHjLtt9 zZ|ipqK&M{s!_fMHxbwNxbTMssAU6K&Rp#^={&*{T`zJEnGY~@mxcs!CuE~qF+Fg^` z5iay8Z@HCmjsC09>Kn-2n?t&+*;m>n#!8%`zjh64l%*CLN=tWc_w;CH`2%@5r&rKC zqdsHtXSS2LWDH|wsa1a=+XGf#9PAj@a!$JXbqWyCd(w4YAP4xj--~c(l_RiPIX}> zGNkMXYMx%O{P;nk8M#8OLG4;(K$eionQFZQqNn>+sg!l9v6}wvI+({f(PKZ+OJFd1 zztUaQWx6Di(+S)d(ih8i<;>C;(Ji64C1zKi9zvcg*e;SrjHq;%&j~n9@d&2XQnSHC z>Z94vGsJa!2uSQ+Hq_MQ6zC!(rlJlEjSb4!5F+PZg2L}XQ+vLA-sDjbE>B47FnR4k zH-#u(ZH4di;0YC(6?f`l22Qf4&6|Xb?=4WC6$T36k?VBWvZ{Y!2lrrtDa)WcT6JPV z@held&y2@+GG*l@WzF$^Y+}s2ls-B}Ct{07NYXk@7tHxc@Z88`O(pMAKa%A()!9~*!rQ5T zWJUeKav zH4o2^kuCOU;zp048!304mmnYmmKrCE7GZPp;uRx^nafvcBMAOO=|T_$Km1f$S)9$T z&FJD*7U=+<-*cFrp*{9b({Q0Ce3KD%e!WmrpHU7+yi8Jb5n0dppzPC-D8nGO;ra%? zYUISgTbX0aT2iW|Lte7k%=1=wX`mFkf9Mp*NNHt|&<^1oLp9bE{W~q?^pZdcY{hZ* ztH$qC5=~8{JC|3-G)L%3S`l| z%nihGGow_F;trqi8BXvswS*D2s|F4i^r~p}5pr**tc!rW7ok`>iSxTUWDVohaZQMj zGv*g)-1DMzt^(w@Kl-?@YIpy%sy|UtDXjO~ic^DPo6Y&#>pbw1J{Vgl@RU|~v&ZG+ zw_SjWfS>5m=t;Z;Qc32nMt`uj$?0uowOJSNLYUnxD1>9&x}SD0wYZE8V^o^Q5bBFo z@>yo-$ZTW425I<%Q5W3%hClVHn0+zgiC05Rb4MJ;Cm>s~_F~^jS=nWkL2TSE`~^}AODtOi_lR2V8>aHMjdMrmPE9-+j7E3u6URyO zkz@RQUO_O|TGi|iwz=G^w)w;?RX-AM&uj|u&cA?gAqCvtEAB7yjyUfqrKKZC3k$wz zP76x3wlg9p=`7I7xVSYY`(cGY*&E$@h`g^1glNQ9RSQRQ;lI!&|U46olro&0A)syHWh;du&qaE7^YN zQKL~fF=;w-@*)*DBR3m9o1jpFw;cLK{k$WAQM<36v8if(*|sZIUDlcrzkxElY?O4& zxX-UAFz6uDzmizRgrAZ$zGNn}Uw@}XPqu}>hHcjQ7kGa_Yv(Ox7-N<(`ee|^rN;d$ z<*BxA08E3jZ6fZrK5gudfP2zwD}$HSfPrDOIvK1m8fhCOvt^Tz`-)1JtiyzK zV|R_q!AC<-Gy3DIQhU;rGjMM^5EDY-?VW+q(Za|u1Mf{g2OO4Dd6E#+^rF$;>O;%N z9T46+g_N@U`cXB1ro$<{msLQGMs0FZ%$DMJs-mJ=`1cxtAL0S|f{Ywk%c;%3@dHC7 z%qfQbj?0gamZwh-JKajA)KMGgxH55-!xxxF5*m`mf`+Rm-3{dZiqktdAzKi~6#ig< zSL)dd*s*&r$0G)RSdo@&<35khnyDM)dgu$Eg{(+)C@;TN{-GeXwBFJbZ+eM@dbP3r z%rjr)QD@G&oqMB4-*YkV(hf^e91;|Em386s&OPP1gKT*-opR1mrJCdwp*uRtiEPs} z>bV%W`qV%_)$L{_yXIVEt`znn)p4jG(d?CPaGB~ci-&MOU-$F!oOM@(_FT!=<$keX z*`A=7?beV?Ut%mwpTB_bxYog32Q{_VTf51OB?>*1PCwgH$A4xH56UzM6rFZPdVf-0 z5O@oo&|&U>09kKip#|6)fc#N;MOqaR`P&@-4vsnyTp4bwjO!1TDd&PEbd!5X6`v6> zaV;b#fB2KLMxA?2j0TB>H8%0yK+(t(?@c3B-%((g3170U_O3$9zp|9W@4}~>&lWCS zqo{F47NfP5LIipyu%KRArn+-bLGtHdr@xZUy@tB2C+ivG*ok8s^Jc8%{zgH20GM<) zC38Xfv_pRy{Z|&MHkMt9e~ax}3^}y`SwqB!Xn49M&Oh}u1y?1Uws7-2Xsu_I?o3!= z?^at$1rq;$Mpf?kr?H{BF0CnQ*qsihezgKWF_)1*4wFWhA>O$!FuqMGKGpw??p$z~ z`G+adDjulfT08z{;qC1&AaSP!}y+A#b}=eY%#87n6$8}ccKVxLKD_Ozd5ce@4ne>C5^FS+E(D86 z>{D&3boUVHi~$l&sfbKZ#u;t=&h`KoGh*-OS`m2xRzIQ*M6-upH&G*)rMy}Zq`-Te zLf}UrY5J$V-r|O!(*S1E{zn99nUT7VTl@9SP*Gf6eM#@Sj?;mSJ<)!;f(MMjwypue znNR|S_ctwA85_I$g;D$M-rpEzYA|YXbPG^&r$%`e&zzdIQo9vVnBEc|_;L4)uS+BC z$_^Lw7kS=iwhy+bWZfwh1P@e7*^)X_#+hKYJHTf-J8b-w$7n6oEyzaqR9{ZoEx&HM zGdnU0>dBFKC!Gf9JXb=sQ+EDQvHlj~_DOys82pw)_v^1$A6aWQr|q5d5k%WpO)!2~ z4(B_s1N9L1ye~;rcUi*kK!8(ydjbql6KIh zQU?>Ht$RkG@J&~ePnQr{UcYAw2`RB7e;eJF`lS#TLu+Rj}bwS{!J-3&)tyj`RHozkW;(D$im<|3Ob<@%v-$Ta2Lmn z_)jv?c{j_?1}}clsx=Ay@Jfx56!8vB%kGDb!qH!4kq)fd6X?BX__~FuKg1e5_!u++ zKj*Q8^jE!zfcd!Eu!gP11Fmq+N}kikP)6&WeY1`majBEv2|i zUlgoF;^8uzoUJk%@hQ1do7@C*#Q}X?V;VZx`=Xy zJ~Wp6_Q5Hjz@{bnmtFsd-riLE6%3=fj9T zjAJ2;tm%X-Kd@Tnw@Yx(_yS(!>k%Bc5zX#n-hJguw$?#Mi7|e~n?G8}&}f(98N*s^ zKD#$>Ri~TtlRppcB*ImlRLReRQlu&P`*VQ#18@LJ`hJ{B0j)^QVcbC{zx{#@98+zd zt~lg*lyI5{j(F#3IrB#$I^qvhr~MpXS`TLkl?0bO_C_#RnU}5#d4Y)4ow_2Rm%`U- zQlIk1$=YHgO%&>p)c#(>~gKAu0hmsj}{6+ISRL9_aVb5QT2CaCRyc2I4xl*dqV>dy@E^ zayoINohWmg$5E|Z1~BymC+~yURB*8b9lt>2;>re*c&p};j*`h5d|AuDtu>cXmS5&2 z--|j1{V5%q4uURvH05@sriIUC)~u6ynCZO)QsGoDtwScC#8RjVLmSk#w>FYrWh>0# z8*g3&K*H6hJ?w1*kslO5-$7xg%4@otmiD1x6$O@$xa!B#o(>sJ^dapzEIIitvKe{x z?rYMY1^wo=)-SiUl#*c3$f)b~ei&P~<*u%CSametC74-zDP`PBR}~E zV%XoDWh?meY>791K=3-v?0t?-pyWAS6sqxGM$Nw=$5Ke85^sRrMf}Yb2dci=K8iWdiu1B z@S1DIjoRAU4Hg0#YQVHCL_s@?>lU%j`R7MdxIK2Me$-1Z8Xe~k=JpqSD8vaf#?NT! z_=DN)XNT8@PcPVT@6_KYO@A~E&SBfxuL!o+4D&olEy@kM*%Ha=T3sKb^|aoQ!wgl8?Q6++kciGu?dqs4F-= z3wujpm*euaUXV+;6!}4)WViqAdS(rf!0=K)pmdno(;5%z9N1mIXQZObmQEE@cQj(( zaQOK0&BJAt!>|I&?@^zzJ1hJ~t@Idr^YEYD1ZJs{5~Rk`9SMn+ldF?^jdCWl!q6=r zR^b4BMHp=5-JlLazlxk*z-h z#Trq@yAH2sX*&oC0rFZX+YRi!k+8EU72BWBd$Zb3Uq!}jxpf<99@ZTrxDXBljS_ci zggHrPVKNA>=vkuO-z{78%g&;?a@%5Fs}p$q@fy-o>M+1TqO=2J{4ap|ZaT5Ni6T$cCG~yKm~!K@E8Dlsfmuu9k-b-? zAE@=>q`L3>xl!0xHAY((k~DmdPA%D6Y#t%L)W-A*9RfR)?=&rpCg_O>lgcX6#QD_L z{uopge-~d7eQW;V4!+OM!{gzW+)Z}6(=Ge0VR-%+hRhOuZzgK@i$ijHKi*!qs{HFs zCm4w(QM$SPGfTBylGB|5SEa4VS1@Q;2HKmub)sZGy`}2{@$DVlJ5O|eWu%D*NEb&H zZ^WlIVdzDlKTJ=#cg<|M?GR+W1v@t6X|9WJP&0N_qB4To>Vpke|gg zNEe1P{LIpO;$YieW+r+b{zZ@3*OnNqO`oXGFi^PZw32Y}s-48gl^I$*CxJbDhxWo- zqPMS+$hP_87{5R~?Y5Mu*T^^-2&%!gY-B;-XejY$jtS94Ee(?E? zfK@$}QH@)HWH-e6up@5th*q$xWt{h#wW(TE_h{RrmwvQ!p=x9U8K7k%S2r=Lhmnhf zA6KStuQ;@S`kAcJnQ|xhy>?5&82q}eUs*b&bi|y0N=_kSOmkUvM&x^qN?(vFG+s!G zyzI2pv#Hd|%cYZI#;0ng&7|vz?wUk^OBjus^cm|fQY!7FF@2leP46JB724-p74DMP zOK0$V$%$&>fzt(9?|cfv6y<8>`PFB6`-c-Fu6Svrr|s`B1pI_tg1w|Jof zLW1CU>1f$gk@`*o)ToFHt#(5YJ?GAKR7SMM_McAE?*%KsDO1AxAit=#j4h1>rXQcx z*__SCT#-b8LV);J9%isz13bpDB0O^GMG?@bR&z$D378Fw*z?ETpRvYp^iGmw-~Ulb z21j29@f;^A(7exQ7#J8~3(`v5Gj;gcD~*aloj|2q>vdYe=2-y4j) z%>71?bP@ml+iMorhY|v#q~C?|L;t$wy3xRmTRnxb6TX5!Ndz4|cIoitlgdx+Swqh# zK@-!yw>)IhsM=6!6Y+QOy40>NgMfm`y>Wmfmue$$KOw3HbH)u$b(%3!foQIsXw-3H z`kiR@IeVpFbl=v1td3sq(V9^am#j7@>U3_F*x<-TO!wILz6v@mk-Je&) zGe&uMPT`?Y3U@>NMPBt1DHj_FWw(I}!!VM3VaVpyg3D2D3HeVX{r=T?W9^-VT;f^R z;^Oz(IdVIC#GP$$w~c{lvHFs&rso>gkIo)va@!{wT-GTOFjG}?6@`*|dZ79rUuxFr zcv*I+%>Fi~iLv%0PJcUa_{NAMT0kJMS&RMw(Z_oVw@4xP+R5mP%X5`xrb?GhZVy27 zli;DF+ldZ3=#e}xpJT5>m&Hr$;fDqofG!)ZAnY~N0nmLQh!5pmdt5}|z>tW^Fk&su zp668J7yRfhborh=w?6&Pjmi*f^NyfJ2&488=?DY!du{b6ey-j4n);l)>ibR z2wAGGvWkFQHS%&Mzma{}vUl|h0Vyd;%mjXV$rW<37>>Z>Cgoa%*DPP0MZi&yK;BtALlcC$>_SnDrQ9h#jH%1uQj*FZ_u2 z)HFm=MATUB(G`E9!?Atj z8G&Hf@LGTWY`We98Te%e(=EsS$R+Sje>SlYp@;Rr?A?udBpPxxSDa@ue=Y||R!*Ynhodq;CvT;pgAuwL=@yRz2j{WegN7PB@qY-@16 z^^_W?^b# zLy=W>Ma!AT$)s{kbeacpCQNJ}&wBgEEBywGa>eX|jOAr?oJsxaJ-p+lFjZ&+k0|4( z{c&Unsv&`naLshtr7xJ$@uK~~YE*zS!9{*}x^kJW|%zijE&b3#L1d z%VC{^1J@{wBp|2w$^NR;-RnH7TM$V{>3>bw;<+O68#`BUgW80=)oZ5wW5fbQo?=N4 zDHsne^7M5PiI_zYf8$4^8u8{)-&4btl(hYKc4r5@_u`oEMW>7Wcp_RqbsjD#33+xvtj(gwJF^_%I{~2A8-P6m+tAQho`T_f`RhsY)bkX!Plghz`A2G%^P^Co$ zd*jz@G*IG8PvMox%+ICa5ilZ7(O``azFAU#G%}l@RFn3CVKPB9H(XfWMV}#1|EfBx zVu7D11O4*&z=A>J(4_|)H9zCq$4kA5FE^%}F?QquVugE_GEVZcE>2vH)SQ=}Jw$6* zPqRi!4#Xs{FZuK7k!*tODaDzL_8Tdr)ydK~Eqg6@IUzA5>Uil`5gWJT1FjL3x3lTwooqXotLg9oRrLsvPa$$cKn4qPy{iEoXJ z$XGJNSHsoR_IDV+*o-3^CW>xt^a-qA-|g|HWN@R3wtzfRugW<~<|IG!4$j(E7CzY8 zIQjQgRB2RElp|C%+Mi9>IDLq(7Nv;4mjsW;89oG5&(NF+k1Y6k#*Yhd-z$}fORb76 zBk!g+h`fyTp4$j}qMe3+c+He(ro|U?(vRV#7T==}N49Gpk}I|y)yZEKAJw>1w2E1e z&q+7?f#5RGF0P>H94iJR((oCN(AD^>mmvmNnwNJh6A}bT5&vDi*?tUHW=ZJRGQN`k zW~mTi%-idyIT4xn|ClVXSFD(WDkqDAwS~lwcROoEXT{kOF4Z0}pjv1Sy*5O{*=fqZ z)r{R6X)+bR_|t86WZ$UuuuUv=g_?X$v4MxJKFoO_S~%9%__)^`2TvL=>FN3C4YyoX zvjB6nB$ie?{aPOu*4hJ3Pt;=rrw;wy9{i48nM!kPa=VPcfqZP!>j36YkUTwv7**7G z3Co}^yzM)`%$N20Ghft8p^84j=FO^)>Q=wB^0)1j3SlrbQ(H)3rpEDasgN|wtAtk9 zCquRw-YSm@;;GQzn2sfIEasbdlWp1jHW9b^aiN(a@d?8)H3hrE5p5yheVA#ObMoBG z6qDl~ZI$S{RL;IcaN3CH<6${5R&)W?9KGHwD##~9-0`RFrXYXE37?$Lvu87i>0MbF zN>b)s23&)HM4a5{xRdST;^DfYI~uj^gAkuflK1W|^6`_G&i1F|5y6H<>(nxSce>VG z>IXVSfh_+zym2!q!DQH&Fgu1PEQ<_#^D$U9I{CUWoMvkCSoZcg7N6~g8|LL6Nejy& zyUf%8m)!^LjUmOGxwJRvC5;tvUql9wD6T|qeBM><2a1C6qrU*2)5c41Kue3O$=ZZx z_mub-s=YvPbzQNgRPUW@YT3lLEjYR&rM+$N-MVPsC;`|ekwlm5G$GviQpJ;?UpC}T(Ks3N0*!eImXrfJ@7FuCyO>e zFYL%~;YAx$$LX)vVe{4uVs#t>92h1GBvoR6~BW>w|s)UqDWHsx_0pLQ50MGsCaDKJ;3u*e>w@c3qM}6u(n-EIi(a_sQB>AKN zjD4IwcgVV2{X}5HuxOu;J8DC_KxJXGW`Wz@H%lX)Pj?nqeu4vGTd@uSvlxxkB5 zx6;arPwhP>KTO14Vi%E`U#$~=(jk3;~p%ZcXv8W_m;NQe>u*U!@U3e|nV% zG>f9u9+cLWoJ2Cj*BP@{cxB(GU;QML(Xa`3KqRBH2b2V3k7%PLewzrF6l46W@+h$> zbE;oNB*#Vuo=s59s>Mi;6G)Ls$BmiWTISR-j_%<~7|F+iV8mh<#kjCDzU1mj79v)2 zB6=_H1=}G9DbIQ4D;5!xft+48mHh^|>0%?<5xbGlsZXf(ur3TkC5ULwuADPxJh$Jm zpD|6_+T4!rGkb!9rxcn~gsth?2cKft)scrEUCxDT5$1w1-iBySGUtge>psC-Ruudh zD=$w(oa%Xp@D|a`M%TxK&Iwm=zl)~<&MWiAn|Q7*1A;nql=^1i+4q06g3-I~QNWy2 zam=0TKP-b$r>iSk^m^amCT<~r!|XD@AWd~`EFqB<4pk_O-}#_&9yy({N9e2HRm%X(H~sr4S+x$J-)!A zhliYmiu{dd`}Xm*X~rBGnyB?X;|j5VI+Z30BYtpgI}dJnSf*D)m=zrNo-FO&M9;F> z%_7B?bcQ=)5e*IrT1-iN+x%l|QyBU&n|)QD1rH7UP?a zNT-=LVRsXB~?^|KOYyex2^Vw zK5dFYEzHa&3^u(hGrwsNQ_vgD$i-X1_9Bp>tT|0-G}b4AaW0%1G8QmK3)<%oJ2ZQlCi$l0)uG#;O1 zPKP-yk1W7bn7rSq^Sl_ms*PoZ3kmLc=j4l>^D!`mB_s-?ka7lM9q2as73Q^bUPpx| zEZNxaJ-dAu~-T*lV=qO0ti zv38r#YYA;5zB`}|ygPIW&s*8~_|hdrqK5SduHGFw

7AGvkI&Ut^2?L-t8y{Jd=C{q zBZ3Z_r}t0kaW~yg!`$U82f6e$1=%Cj2W9Qj&HeBqis`DdMUD>Kedua~=9WAR&c8zL z81f?NeVP;|Nhn#-X;h=*?(H7FsPemk^+4B3>v4x5f=vRw87h!qpovhzNl)lzoqxiM z8#!__oU-fw?G>yUJlhqftRJffzBTM5Ug|m2?ZECZHV=X8+^~0AI_Bd>{@Kj=ypC1w zpY74u7ElqIcz(2(;S|@GTkq3;upCwe!sqX8kB~AItTFZ3J#-Asq`$zwbBv~lPvDi+ z5a?jg*TM5gRhmPm@S4e`!&c;$wqt;oN{1Lj7Kf~K4Pt8;f=5>w%I+~{w?d0OiJB1q zOx@$3Pqej{zVl>w+vsZCFGe*7WLlgEpO;$*K$b zXPI{oyv^xnVV1q|PQR(JgixGt-66s9I^2x6E)JxWLmYQ?lW z&VV67al_U}eUlp0kLbv1W$^nW^fq+4l84iLX2r+R3$YiX7J*ZBQ#3MEKx&WMDhv2PypQL3t2|^ z2a(sm09Sdx_xLyMv0KKLteyXIMsNRyo=zHTN%?z`d*0rS1ja!#5BC@yL17&#wz8s9 zuClG(E=fmua!2)-dT~>YR}-}9d6|ZI6s&bw!*a4f8tr+A$^r7TJ6U0XZga9mB*1AX znkOx+=Vq}AM~~xLzbFIS0_0DiMN>Xp`v|*D8q6_2W`Trro$WIrQc3a)SNx^+f`m}CHTiUS=5ATT9Q>- zg9mYxyQ5-Z0?-q=+2W!S=Q^%Ojl65DDbaT2Co$Xf@W#YhI&mt)O@x+=FFqHUi;G8u zSNN*EtPELI59ZYVFaA~w7iqQbwx?P*kM6?qM2WVqlxWn!HQt%58R0A#iUOlMg46s} zzHIh9VnlM^*h-4EA1buT+sn-OzhNcRKDJ#a{p!y>Qdg)S9{J_j^~5gFSRkL`+vWL{dMQ*)nj) z%#{YJI7~FXK5IO4K|Yg{&8iKd33st1YEC5+1FWZHW9ovjU^6E{qKYz>B&Azpl3pbe z#WNexsrPKQDmG4`$l1L=)^0<(# z$N0UO9kEPh970b_qTTXxY~cncNm>7)X&;S+pa=*0%(J@8RDc>-B)`aPKe>)SSkLNu zMMEFplk1yL_^${t57?#9H75Hc5*@-8$P2Tj5ycD835dq+?ju3)bj}8{sRVIyu8)JW zyZf7oBYMkZ=5+GB?gG2Y*24JkfE{rQEeqnuJsq#FYIQ!>P&{d~`%0@vZ|JBWX-Kbo z!+VYsGMatcmf#%vjMy=OTk0^b(Tnv2cpgws@5zQ{^7Yv^#pxGmfF43y^39h_Prv2;Yp9#RZ$0Kq`tf76t=&B-!#C9@I4}uRm2`f zZafKw9%X*sJdC2t^n?{%vrvI+04wKG+ZUCkl2>9_nKlzzY3 zmxn1r*Y}~GVVR!1M=&EC68ocly1J7qSILZfa9wJM7JZeu9EBPffWR=W@}B zZcOjePeX*KdFPq%qzlaq`6a;y@0y6NpG6?tW;~One|=;_d_$43&tBKSr{nD2HZqfr z^HB^w*KPULrR{L`7og;4(82YOOSeZ2V>Crtdj;L3U^aw@5Uy@hmkfrvKJ06rj08$; z?G;i>TL!tYcut`qQ8)JZtqJ4-0sBRuAU^A2Q$y!0O6G7YUhz^d_2NVEwfWHA>&zLb z(|782Tp9;i#nYCu@&cv&#@5fvSTRPld|ILXmd{>$nzK>?ADg|t^Q=h8z5C^=Sslz^ zg1Al-hcECHn`V5!Lk_Y)%BePztr}keQ{EoGUZgx$DBwN_58a*$5|=9_3jv`g6%xn$ z`lrHAKO!g&yNte>Vs~tUKlP8=-BVQlf#}DLL8dqL^$2(9Q@Au==oky$PLn2@SKAAC zgE{;YumjVZwU8AGP`v%hrest$s=JQ0&gNn(N_C6LDEA@2Rt2gyv-PiO^?&$!%cwY> zH)?S3;I1=R26uM|?(Q07a0{*>xclH5++Bx3f;$8Y4#C|6Bq6r*`@g$;_S1gpQ>Xis zoUX3wNAA7lCsU$Irjej}`f6)em9Uwy==zITlYO3?%CE8^ECx>BpR~@bjt%rbDo7Yt z3cj-%OWP2x!CJ0v;l(`4fFf?&Mr@X|%s%XUG7aycF0-^^3P2BFef{fhB?q3}VT-pf zO1`dN8mr4p{Isj?k+-k!6~bPQgKZl}&y)4XbqB4|Nc##Ah&(tGzq+QRbXRyoTxaXb z)BV{mW*a#cBa^S4J-Iy*%{de(c1V8LWrpP$l0$(Jc&8+o7agYCh7DgePi3Tm%qe(O zW88dnPQJ5@ZfN{U>_=rHZG--cu8m)bpVc9eT{F&ES^lYY)E8~|FUX)@9aUhRo+rkP zv~_O@UixONy^AvR_m)6OXX{rD5wy8X_?|hQjKC??Iza#n0~0gyp0os#c~P;3i3oiv zB`nLm){GrwmV1wN?ql^F^bhdOr}8i94&5*G_B;H5w?Pa2*cG%^tDz%5qED}|Ykh+F zNlR6c9be@-RThSg6Nz3C3CZdrNX3Eh>TRU5jzr|(NVOB>-vBr#OrK=WFKGU|bJZzv zzR6zPonegLi5Pj&udH4M6j^^c9>Fm54)lk%;0okTqzm&0#F##4&1*n?hhkY5s>STn z;juy`yWe#_aaGG$4{;-j&<);6gVmx=#J1SRHUGqPKfSi$#$kX3(CrQ0FF4VyJIWY5 zWo9!Oqi?l`-|1pO*G%SGC02@DID%!+JdrhVW^FoeF642Sg^Tv5^gAppZPvY=tM~_{ z1Ha|_NnZ3`Y^-ysh17onCZwb~;M~`+SzW)QQMDRWEalc=aL@@wE!V)WC7W=6dCmD= zK6hJIGRvlp4i73|oD#B+weO4FG!fgeG2}kD^wD}F<~NdN>MvFZ2ExT`--7Mw zCqu5iZ5xR*UDToK+x?l}ttrHK#2Yw>aKgb_JnVV>8O+gjvnC z?HUg_-M1PM!5y)eZV4ni^J}_#@zb;A37@ESc7{B4IOx-q-Pg=rlaoh*ru#ZW>}4Sn z4eS9fbgz&1hy*bdSjSUb*Ei46TTfFNSv;sBA|5XMB7z35Ijd^?*XH=!g(>2Ak?Bg;-A9qWT!|{(dN_oaG&7Rqr4T@w)f+Skim@Z7Kpz@ z4zS=t)lk{^Zoe`meo_AC*Q|lIfY%c7$z%-BrcF}^*GkeVSC$!vsuaZw(1H$wt@}$c zRt8d&ox}$<@wQHQ>*(KvX>-jC-Z^ne#mkwg;-ihADX&m(B^XsXxkk>x zY7Q|n{)r+N-nlj9qez@&=3wh_90hdj^4_MlhHbu7iHonx8ag5!Jl+f9s>o4{y+6frQW!*qxW4Pa@jBF7LGUZcEr3b+TB zN#b}XnMG!Pzk>e!=yE2j(yPHG?yAqL;*cHg*}}4LZd|!z-sQI@FQf)@aVf357TbP; zM^0m#Q=mE&O8J1lO5WR){HH4221C3qLZv;W(!^l3&mk<`;a$}k zfD-b_E8v`LqC&u_P7SKpgv=EY|D}oPxWWAhcUdTVeO{Gqojk~6g%il*Y+g1ycc5xy zwFGs1bdaQ(1WUANUnpDYV>RTyL(ZrMbG-3>4Q5G zFzR=fDd#+wM0uNXZKV582G8J5Q6DVITJ@-yPoMN%rq=(~y#|Om>#r20CGBkB@QR_6 z1#1c1fA*?Go#uNJe5M-{12jGDaY4y?R!E=Uw|!pzs_Uc0y|U3;3rSExn-8N~PjRCM zC&P^$31yl3d)_YW1rYkQ4p|4+ZVoQCB~^IMhbq1!49|RWPvPHa5jy_xDv3;b<13|< z2keQ}3ipt5Tsa9+qD z$#hW642m7q3AyBy`k-YvdqTbg^pIuJ)i$0A_tv8`);E(_{+t5uXLRF;LB06@a2vzt zVTWKLeBC!`!NU^*=mIfkWD^w2ul~oAQ<&&o(d#gh+7u)FvN~#JwFYFr@B(pMe?fhR zn_h9XhTIHnD*xcbl)`aw#zP>atQN)qq@IqNRJ0J<5m8=?a0dW)G*eVXG&$tP;GhvUN3h>DI%9mJ?{ov;j!n z%erlHvRqRPg|EJ$cwOh{cA!618;PefAZ0$;{}z(h&5YtX8G1MPjEENA5`C$wWgu~i7umU zb#Em+*T;SnW-rC)K&e7sYl~;?QtaB5xfoaO=(t#d23lTHqAkd{(~&Dr82+gMt5qkb z!al38a*27CNGB9PHpw){dfTeZC>q_ll$EU9n@>xyn~i;rfhN@E#n(e)d=@$mmElVSTCT z2%A<4*LyMvU=mFcV>)`nFrhfkJ4oo=gQBmTNq|1rztG?k9o-;WW;_UJcMr@cQz0xO zYTd(ge^ch^>3N`MI*49j{-CPRfIjUSSx;X_9#>3PXL^FXy`2DS$Y2jDtrg2M&}(o% zwsTc_m+BmAZ7>)2#8)6tJSC3YXy*Yg`ZL3^y9hv5G0a?Ck;5+hp0>c9cKHurr=I&| z!*!kbeU3F<%t}kFF$S*aMYn@j-${J?n&AXcdBAld4Ea~H0<&&{zK?Y($pRT~6XAb^ zYU{VlFjO@7_=m~b{q-NwG2RXDiU_^_j%dQS#?1CR(e7;r8kgUilgfApe0{5G>Hw!i zOO zug+8kwxIaTj_|AO!|9b{)Y?|XZ%WhmH(j*^IhIT|v5Q|(TKbD*Txg~TD9&fCc~}AK zDe}IIuBISQmnfS)TUvx9oUBnfc8pKgGI*wKX=5ezm#(324}0ex2*NaY!?^q;bP4q7 zz15nKFfkqOK05oUUFjlO!{%<}-9U0?HVf{7cj_J(7AmZR)H!6|8zT7D0QVMFdX43} z2rj$4s|!X=&4b4RfXiPBiNjY>W-uttSL@OlB(vZssR9<#8q@Jd8*Q1}EncISdxmE< z!wcj=vIoI(o67xNClaJl1{l)5Agt=oHVIWZ<`vtlQ=p$<$KDhxe+8Z}u~lGCU1Rhd zpj$h@OJPhvvt6V4&<@>W`i)GLCp*`x9&tvY{`k(c()$S!6lp)r_a&d?N>o)wRpSc4 zTx;f$Z8Z%gm*^hbUF4sFI{1Vpbdip<+d)^Zfpa_6VOnI1?{z(c*y)(G&FHRCTo7>4 zms+u8Uo^5a3TZ3%FvsW`Vp(w_p#Md^|KjrJjMdl#5A-$Gpd4Y(QaK$PChdI|9J+N8 z*AL2O+;_KckTx|-e!x}u!70zt6X5j;Ql6Zk-TG#X>$lhrG&cYOk1JcJF!y-=pX2Ua{O8`w26SoKa0Aq?H&nt zZPMDgUZB04un&nu9JLkR1@>>&<8hgdM6z^9s)QXcX~>k8R}pxvXoeSkUpI~)W z@LCvhYgOqIw|W6QE8Ky0u*)EAqA_SqAMu0Sl>n7!VEIa--RzPuLHQhe+G*h%{X13J z)&vXg!RR@bWf4BP#Fkj)r8#cTu3kn`X{GXb3GKE|^}Zc|EA4UfW(k?w4KyGrzUHf!bWO-l$EK1laHM!T0gS)1otO16+|HTy z$BE`PVkjB1t|>tBO?{1aKw;==yuiYeI{(B1(&`cG2WN_{%4_dSt!&Glg> z?AL|d2;Yo87;9A#q;+-u|p%97m?LCCz2e*PoRAA;Q4txk)bqZ zxS~JB+q+W@5W)gY-%mXIjUYEheKDM&qr+)9l42oJS;=Avq@tE}a`bC7Pytu5D(F(W z+-oL}3&(m{qz}4gD$xIm*~N@maItmS#O+|EbvcWM%=!aURApE^Tl5YThKu8e)Phaq zm%R#?xaU}si8`KtWzH=PO{~WbIK2H^$**dtDc|5{^q?d|*BNhcx?Ksmk|3#QIGqA= zFi*eu6KUCxn&o4N;}?FrvXGdlcL^*gwezsEqt&lbzytfb8&&Zs zqvIPRp{Kj!lq%oCH;M0A4UBvLEPG_3p@Tg!lveawk73ZBFF2t3R^SKv`JppU{-DN9IxQ;DxKMSjkC?{QUlLs zI3u>{^~MI<^0prP$8Qm#GV&>{6~GIDr$&_>+sIhcxn|E>(`ESj3->;oI?DgO7Y@OW z13dN&zf*RvJPB_xk8&s(&N%q>#R_~^cz{FkTy7Rl?lEzbZ%c2qhks4j_yPl1(RS~~ zkD~ZuuV7YD`^iTzGX=Q{>KH5B5`O*muM8y7 z0;W1`Xdn=uyQb8NM-Kns+jhgs-hOI?4K(4|34Z+Z>g7ToX75%|rnb!L&irEelftWV7uVHPVz}X&tg4D* zcA3b5Z&|DRUZh8C51Ja2;rJFVXIa#CW)1Xp9Xb7;qYoS4L4+M$z}5Gpw5}4z!GtEv zotO#EO4`6CPl!o={1Zc|5xI@-hM!cpWxeRDvG4G zPuX)I6$#|0rqmEBYY|)Z974yYy6uy{bJb~}5GUJ}V0$EZ>ZcK5}^6$=yF;lZa~2X0P4n>6I(e)O=XN>(a8EMU2m;LQ3Vy9eNxhUJ>Ha% z8IY?MLCi;jO5_9WON6`8&s}M$s2`BUel+~5vW6-yUeYX;>-f;O!$8E>w!_y0PI(~Y z4dRf|pLnH$pEwa|cJ z&>MxFvvFJU(#xNnR4rDx!TNng;qV~dQ>S=Ea*%Mc{uu^E>#*~?F0DA@?(I7>bI4Wx z$Dw;{lH-r|pMQa4!U&pznDH}x>hYYc11=-uZm7OK>K__zK{rFFbi2$A>q>`DC>nMT zv|i|5z@+Y}n#~+;3jC)iHBP=$R2XJ$T7G|fne=8)Z5Y#^hy*(FXo0@(?jrn{4aC_L z&D**TT{geptFpaL&a+P04kN=ZNW{mqz^Uzi>o&>s1=+(S5{bFqquu{jodK33uItOD zY|l%?=~p!DPp(i&KAzyTrE^qdT@{)kkv5J&1oUu~a|_E!BS#p)NYL>cKBCx5X2r9nOr&wRc+kYSg6WZG@f}(E<(;WJMlhc(0>N%)m#xV-t zYkMmYQ>Te{2+Q*Y+En#+UTyo+(DFHtU@`f6J*tryL5ZPsK-{^ zVGP2nfs71Asl7q<&T6WOf?bZ9c44i*Ho07Ie;MuATF**!sp;=cD_QzGBA+A{2+Pdz zHIYjJBg-|ex?9+e8mKTL?Td!<%DV{cnyg{ZwFfmAN@rRmb0Oq~?;LsfTladRB5eu> zLCd4mIc7b|$89>DIi7GAjj%5g-pcB>qjuApTctE;&AXL(Yr)BnJx?_>4+gIjhjg&V zQsQcu`4l$4#l$Ijvr-&P5Q*IBJ1;uWD6=V<-Lk}c3Q=Wr2SF;}-DlCno;2C}9nIvB zYP1oUbI7Dm1C~H4w#K2)3NZB;Wg^c`Myi9$Z zr#xY49L<-mH|`%`)`8%?FimG?WnDy9UKYs%=Ib|ncR`J zTX2h&iABZj0t#RqyxrHlb-9AC;XwCEHkQvE1cV^&uP=;ur-S(U=kk%fQ@#8-O_aovlOB||=G}g>FiNVK z+g`;a7Cgu(C8@HjRiqvD%n`mG#2q~Ji@O z)hm4yd}`n-KI$*A0}@e*NNyQtujcH_b72Ktv)$pzAPs zA-VvU2Hfj0)wg_Nr)sobAY2Y2Rb{l~mGrt58lg$#_}SJ)HQN+&%tfEGGd4@4)-QeO zv&Eulo7X?6zc`VTu|^SSd#W#uSGG(~O-_%CZC`2k`+WM9^4!Gqe3B4}_dZPYW)dHd z<$~S%w>5P{9)oyatyM%>$um+nk)I*2^#ad!#JAs*Ehq~493;nCLG*84vp-3D?k6EX zNM!7^Ded2ddz}7~$-4g7X2AFv4i6L_PgDaZIsHN)MnX8~v=?dr0Y2gX1DpdR z4q6UH)JYd$gy3MgM2k^cQwL=p&lGFT@=JBiS(*vcXOGRx)GFn=(K;n%OGc2F5C;?S z-!J+HfV5QjQmAh9Fk9=}CUbr?Tkl21OF0XEHPi8;vBphst|5s9nY;1d_CozOddJ$_ z?X-3ToPc=OFuc))ifT2c0~>%AW;%-zW!;EiXy^*b(WCZ!e!b=?%(P9|jiT3LUIT}u zv?HB8$^Q&1R($g4j`swVlagyJhHvQSuQQQGi*9}AMSJO< zWuBYh*vT1#YIPLfe#?ES`&u+TIPAs|GEeg~AfBnS7`aZy`>dgaAiE@j4LZjDPSyOk z@G7u((3wIvux&VW+X*u8YGUe$oov!1i|KYSBy~``_`7%I5LD4^70I)jKZz8<4hsQH z!qGn6cnM|9k?czD9INl$yUI1(zK)slrDSLQ@o>UJ9{L>Ui2^@Qu4$gn~sL(bJ|s9OBc&`Xbg&?^u7{v+(nv(b9sD)^wTejo!bXN=uwDEy5z5Z?rh_Z5i_|F`IcHDS=bS5pAvM__hN2IT!Tk^K&p#DWKPZA5Mm?~ zmgK3|^?E#GW8S;i{Xj*<_L8f`h!w&CUtOstN%>;6fgRq%rieFb;ijK1|5k7T)r_(q zRYw(^)=>nKjO#%H>mxZiE-P@GfxQZLDqMA?<^#ufO0ql99R=Cj!f6u>MVrVmG*v)8 zcdAOZl-TAnPYfvE2X8&9!92I-l>YpqjgSY7+Uo9GDhcf|vvq3D56*36Qgyn!Ai~6c z{1pEx9Dikn-?~N1`*AZYv_9^&4hpNoj^~lm=?6zwqF__EwkYwej=iE>>JB#C?u`kd zqw9>=dsY<~Y)6_wH0AvJor-*+<7v;JGe=1pbcf`~p8ScW(d`*C?VfR@q<4pZJb7_| zCC}Z==S2nL%WvZriAbKP=Mv$g6?8Eacy0JDa%F2Q!rK`7GQB?TjtbMximx^A=QfgY zD{tmgZV9-9@XBGyLzYs#3B{2Fbt82Jf}HYcFn5S2xf+Y{*$buD{Yl8{$C$j5Ky^K& z>f6_-zZA0E86Pv2^3=*nlcHlGI99aom9NK`Bp@^8>z)mv_=BuCPs=g?05}jMi3g;B z9MvE#y`}f+D>Hs_wDuI?Fsq+gdR+86#ul9V59;4(mWwYxI;l9ynSLvHtJ)fiH-0#( z#anc9~NdLQ( zV-ib@77oj+{8WVbhpWd~@TJ@Plh@|qXO6*(T@n5R@{dA>u#4;9Wuum(<(I$Yv0 z`>!luLn!SAXc&xH%xV?iLCPZ&`;%G+ntpZN|C|7*1xz(oHKP@iun7(gd5Z)l|)=4TvCRH-iLN##5Xa`*fW74LO{P~?q(Rqn4#q%5Qn#55x~0b+yiHl2UV zEGcOL?FjkBXLhM5w!(v*S7g}PA3I!5DGv602HTE2Aeq+){UwDfei-vg+!WKZ zP&HOVm1|Qr@(`K%ce*XXhYJC^_4O!1YjRU84)~1+A42GSA?-N4e{gz7>k?XMz0>wCZ6XS$}v22QEXZ;M4@+J zLp7((^^ixM4#QKbiNs92`0Ii#H@g~zm?mKLRKH~leX>QZ>f9uJ9{5BUk@u7B!aPx8 za->*(T-3BQ=;1X(%VM(IXJi6PatDMJWqP*-afQJvn(<{Z6Vw|C5#;D1X)^tB$F?UZ zHp{`JJtOra5XT(i@{7Zo%!B0M%|mU>h15e-N6uCYh2$lWBD7!{Omc5Ms28y}1UkZ1 zYjC|ZH@`09u2^eHN1c!Km_4=y)OjjS5k&R3^0p07^2heZa{-Y3dZ-+0 zWAbl(C`IWP{Md|lMJM`$Sht%clI-*Ozqext8z6Qb{o6>OB zaji~rCLC0OX3Ol49*g#top7xYRAPO1}}4U-S=Vg*!O6-FR)A1a5pdr z+wd9jl*BQMu-Wmj*o~`CQhelAZaT8=uwMGKe0`o#?mm2p-IDJNXLJ_pq^I z@&S>hb$cD&Fl`JHafU9kyhGMl>YV#EnDUDi$z?2_hQ&C!jc<-iMj3Eimi@~jN(+PI1<2j9G z10tfh7@SoUhJW7!{ILZv*$f%-mAvN^e16WC_I0Z}_LxxmXb`mA>Y==_Y_)j@GT0VR zap{G3_O1jMe2wt-C07N@=Uj8qNmdYg=$Yb8YjAN85S~#Co&>_98Um>^${0+?zz438 z2dUhxJ%7@BR+F~8vU#ijIG1=^07oo7e{jqWGvzG%teBm)=XRkwiHbxS4F!X2pOZ3NO_Nn=Y3F<7t)Nk+V&v76<{w7I+jYpi51vI^-zP=Cq+)`c$W zy_$GkFj}9#%Hu{yMt2{#!hiNYh^5z3zZ*2Y zMp`d>p)!jnDcMm1xo7PsIeEt~pb7l-Y!gR=ga$oq!S_%`IgZDLFtwsY@vDX@3Gj1c z#dDCpDX0LwHYX862^j8=_}9$V}oR+4|Zhe)g*hF zd*t&-G#5(0OO|`-msWd@$pgJ@|U6B$-?BL>~yFiwq{yxB|@Pyirq8A@$mJgC=vyJu-nN!)_Z%`r8eGnh0l#L3bkHm z2{pw@q!2lFOuow&Di(Y+Nj0_Vmd&ZF1fg&bQ1EiKav4;GpmQTO=pbQ=@>k@9?R;&l z8G(fEdS99t6W;6xM&5;cr%zsiVgLaE$Fq~T+{_Qut*WmHmzAX~7SpG=$a}D?B%TN% zF?I>M4XCqXsnJc!2yWzWS@Vyz5?kXlvioi-ubgACO(gnI1wO-=AI%rdM69X945W@< z1+RRA-)M8K*D2MaDMRsZ2|w`OzAd@_obU~#FbV-z0d#|Syb3%*&DgbO~Vcil_ zayCky*6US;ktiE)0Wl7-CdHyeD^HpQay1zpl(v--4z8nTvX5Uh+W4#*g;@6+xmtvT zb7J%y9yCH}L4By$3c`{;YyOtLDkwgc{FmTyMI4QQ(VLQ~)jD?B&_jc+zl zx!B567=jJR99h^ZQ4;CBW6wzR7FKmb11pO0f23#s8b%iZo`;Auam~64X-5QR?pe64 zmO5td0daLzYA`l?DsYD_#pOonxaIv-v8-WYm2j|j_oCPf8ud~{Csk~eFUla-JL0;S zk9ebO1^LA>RrcRfm_OFsH`XyVmW6yyDT?H#hukX6Sl@}LjWmQhRktU9;I^}Eyk;6AH_;xaj{gGWkS;)K^A+Sal#jCaN1C$GQZk*)l+DG@ zf_mN+A43?h5;jQD>b;G3m7WVr{W zY1>=Sv;tP1Y0Oqv-QozOKsE*(>b32cnA$Y(v)Q9k>6aaj29BsFfmXuR`F#)ZSib&p zq%%#7#UEQoD{fE^!D1`Wx35jRmDUkfk1#S)JX*!U^#ix~t|otSnuo;N+L)B0CsHCI zk#chHLLclzCL`vil)xje&>=cX#ry!4I#$+;10Mq(C4LZx>2m~rCrZ>~Oe{rkdDSJ= zNwsDA$0&3Eok{J86T(yzC* zuilismNNg6Yb#t&_Z$+<7)N#HUV(Z&rGW7y%3vi?ZWG7Dka{gX4 zrLelTw7+r^)KfrWwR!3lh}6TL@BOY6_zEE4$RVETtv8}WnG%o(rtW>aAoE<8`VYV{ zl2XPtESli(J6%8oA=RpNbT@sX;SzUj4M`0HTk00^P`X3^P%VBLe<`HEat`BO$yg8d zI6}OWC)^O6+lbnx1V!kY;P5EHK;m2P$JWJht(YYDd>+QIn9tUy0%!PfZ^{}N-E(y{ zmL;B=@f=(Ge?$vt(cQFP2OpBV%!D}o0~oI$f;PWp&wgy3IfM@+<5``g=@Y-Hk3RWL zDm``z?2KJ0+)PN4xsGqVmKP@-LDYig(Jj6btqqpT&RloQ7(zvsYpsxhLU57Mz>mSKN%<9Zl zo}2?lg2VGpzz7jidUXetzlkl|s>~}^*KVZRJvg)*pWxe)YLoSj&K{dnEEoGedZVt- zy%8^VR(~_a1U7%O9Fi=ivQ^ObwR}4~48{G;)tm~3+ku6bB?3?fI)}zL!!Oi4Td+$! zp+X0T9;J*VoHP2oloiv1DolfTETv_}R>q|M%B@V=3rcyXMtwgU-tif=`*@$7q75F} zW*|f#TWhm*Jn+5!UKK|^+R!BL@$$3;nqTABgaXq~exOxscpXh{RW7p5@6c1NwgARs z!dsB17!pKHP1vcP(``UpSalyORygt&mfOm@6oDdo@^6ujLo&`@vZJ}z4uK($x->x%gu1pY>OqXoZ= zhcNmeqIJm7fmi*07KY5)HNEDy}}ejA?}@_(${%l&~yst5h}K!d9!=mXV`;kUCKJllSDU%9JOy}?tw z17-3*hIry#+8~a8jI)Hw*2k(ca+;NuQQ;LCIqr+8qmEjd>GmDQi_=jLxLA&9)-6=! zUF+mlm|?WO4>Up*&T_%Jm0yfU(&~Egvj(x+!bp%E%&i+(qtN-kXqPwv@K~~rMe1# zRy}Y<3E%c+&F5_}C}J#k^H*%eatM>F-ByYtr#ZF{n{cq>Lb60e>!Giv9q{H{0B ztVe#l{8ckWF}apW27OkycOZ4#bLH4MrDc@Ck_0kS^la-b`jp2sx^=EAiL0kad>5C5 z7V)*N;q0a}7X-1Cetbj6ELnd}hb3<@Mo4$Oo3*ut`V1r+zXKe1$iT15 zYfLHB?@UdD8HV={UUx?)b^)xu}0bPb==ENK+Z=j_9!au-uU-!Ceu7K{U>PEdYlY5zp`TiwCO}GUDPj*1zHfHL>7vK4Z zXN2{L+`3S7@E*l7qCVpnzg@@zJp(oGh%?PFnCO^G3|EGh#H^;ZsVoFg1J_|RzhB-L z;PcfpB1irQ*ta5=+ClEu525!gYZ$q=Svsq&;K(2TX}s^cTW&X)Vg=%+4tBB{zMJI` z>mnnRFq)i<6JjX-R->)|UbtzRfkpF#S#ADhIQm zzS;V~2YV6ka`v#gm@T&u?drb%y-{V+w40v~q_M;3@T>Kg|7>fdym$W}2MTWM)R-dV zcXowBo`F13%S&ed#EmxcBi{$SUO%PmtJsf~{&>x3PP&qApr_1`U>M6gVuO_z5GxZD z%&Zt!){K{fm$q$YB(Fs{>y5qP@mn*Iw8~8bP2HDJ#PLMz`5r`$vv)A>q%ewJUq0)r zSI@#$QBvdCJ8MPO_l;A`vj5e5oRUr)UpGk|saDdqU|JWCmav&_hYQrWngf67qcWvR zR5wQS-e-NeO2EY`-UmjL$Lezw?6XJr2ud>pa~a&|nEmRXaa$w4hk{Q}B9%U!olr`q zyfyDsm>1Ui-p%@b;&mjUAMqGFmWLQqciREh8ASj~A>V*GGkJA$?0oqCp7)8Q&6yP} zY4~><(xW@IB|2;Dh=uyzS(TLsIF=`Pm@yzn_Qgq9u}a`utcDDIZ?16CKLGIu`d~6( z!otDhTuo_hiXY*JsTF@hn0Z@dB4LTxZt~))jgC?0gB&}9Ybs&!Y;?8y$j6{=^By`p z4+*duN`m<7%;dU{5yrM-AXSVB&=$Iqg6A9!894+GdarTPF zhO_|_{2Dv}9+EEMPK$|}@q8O-Fc4n%ST0*<(Q2MBuZ1A|69r*(8cY zhA<{CPA;5?5SZ?J$UbnTJSM!yYEh%TrOQyU>2KZ@CFqyK8b9OBMeXqJoJhoMFE}7E zr6N8`fX~N^sXDQ;w5puf+KMg1Fj`!Ku`e)u7YTg4C0eKASEnpi)+Y!ZZQZ&iSiY_{ zt#R7nZP_U;UD?4DFd=>4%*Z z{noX)X{Xq{Vdua=RMgCR1&-U^8H}Aj`oRHBnHE2L5-S0O!S$6TT(NS0+PQzr?RGPJ zwX^~?=kO8El#f4Knz+P=Jc}j#7MC{&j?bz8+t~lN>|E0k4L^$4!(x5Ck&4t-oJdkX z!bGgIGN;A#vn=qHQ9;^D9zU=sySZiQ*`@SF^;}+knpgTxh&#UGSJnsCW%qQO23~!e zc8br1>sNyz+3@md4Ak(7N}YrqBt>=`(CF@Wi63RwnUY;n9&$)^7BaAfs5xWXM)ci7 z{i;#R1kz60QNbI{hY}$tl`-hvu*}kwVw|4=Pl1S?Zs)Z)fwoiJ!5ZXQa+{DOD@#BR z)5!r!i9Vk^O1miY!KuoSKTg`TT?bF9QvKs zjmXEgxu0qXq;zU+aK}4a`k@evNbA&E2qw2Dm-s!S?8};nNhHhZ80I0|gB4c#gF#Fg zzWVhRPh!EYLY$S1o@UKN$T2Dm7Pz{|m*td7xtdG3a^7MNUxi7Kvym~zkBV($aTV8< zL3KC<9^}Bj_z;9g0ssQbz#lvzE)`H_!K)R$Kd*i2(CsMl0%(7wcTQ|7toZheoD06U z7i~2$op3ZG!1<0{M=&g`W>c;60xqR7|4uHEBXXgZJf*?9DzTJ4MQpr4a>w_8tQctk zqzoBLPg4Zkv)Yap)}qVa4RT=W=7ALQ0g@L&$bul^o055ELxJ!dv+ZhmhWGRj@ez;xC?MU*$%Ty+4U zekj3VdBC}C)zRP4_$H{427BQz>>QEpjDwQQoIco%j(Y%v8U=6oD24HrX5VVcPQJJw z-(U-kN(I@SZi}PLmQQp^(XfvWZv%Puo6-LvzzD|Ee#{O0_}CUQ7?vzHiVmtJwlu5ytKH7$u(vq^Q6C2benI@7DG z_)L#t70n8!CW`WHSwPal=`k3ZcY-m1&&+?Kv;#Ms*bJQ}658w)`~f0m8x(s2K&J5# z7}ed49!eU{7E71c)Ow>!%ytog4yDx^t<#(33n9=}0m zpXe?I)I-;~u9#m8!LE9xi^OULbp9x1ft=#WMxxzrL!CMHsP7unDZGJORzh9YxBq!R zlfU>{5aImBy#LKxke!3_r@KE6{{`_6*50Ns(YCO^LB#EZ+xmjU@>cbbtN=O!pdqtI zf7F_DGI5P)J!~oe_1;J-L9QoM<2J0b33*TVY{Q>Y)c7}>p~2#hwvfJWH&o50!%|Tm zY>t~(x>aTHxAfKZ4lOP(jk_2T+K-{wv3MiZKdo`1IHuZN}jQlU*pFk;>Pj=MU{{XN4tJ-Sb;zWTETF{bvGyJBRc~0=F z0yVbZk2g<8KO3RP$;#s&rhRd(c=&R(8BB~SOF;?DFh9eX1y0x@$U4Q+RiPj2~i`Q`)`0({gC zJr7+CopUNrhmIo_!3oTqaFn@G`3*=aJ^N3gkKS+Ym(PfoRPgwJN{y<=wZOyJ#T;5C z3o;aSzOR2ze=pm<2%Xh$y{k{u5}LmJwVEAxP!+wbemA+!W$yPXNWwIt|Hh6}&DL9C z5|A3SZM5)-M!Zhx<6)>g4_sI+VpS4~E6;y{9~m<<*UBN)jMIR~$8*Kl2hMu@QSy2* zv3q&9XdI*zB{>UG@WJJZQF9K>{7wRmcvRv}$%hrA?|VaD7*K^}1x}|_z@Q1T?DdW` znQc-Ea;BH>wT1F65sdGGWBXlGjv>~bL_F^NfG#DkT9o$j^Q`{YzmVGzG?;I`=hXE7 z6%o{Qk7k;bD)#rsmv3Y!ycx%MB^2h+7Ub901ML|Qa)eBdUxM)$IL&}k+=^Om*D2DX z;T|EEJi(G#)?)6ndK9BhcL|i7NFGE0H?M$VE3{r?#Fjl3?h}JGg8ANy;NXVJQ7Iwm zMc|@~Ppf=4RaZwa%U74r0UNW3&h4|uhQ*^_4(3ozUhJY_n~}&i^iFMOuXNP81;;Bv z)M3WD@t89?9wDz2xR<3BJ1-keBofYRx=6zDI{I#NL`Wbc(mvPSw;xfTy z-U9*kcpWEs9nWO*{{Pg(LYnSB2^zAhvc!uFNTq1YTE=S7H|a+oEQLrzJu zT2UcXMpG2cVROio^RbrGH#rpBqMTAT))1v+e+W#>vlBfQ(NqtEVD8qDA6dz!x!7EpmAzGaRuq;d zstvx@z~U}d8oP8{2)RawU6dkGBo&Q7IBlz}P&iNtrEID4Mt@|dazEKu`PSIL#y%&_ z6b;k)^(oSS_ZH5x(wulLqcB%YbHD`Bx3qPss<0qt;&WdYaIbP@)Um30TD7q!C;1ap zI2q661Z2b|`Us|CnPtxPzE3c%GE5^zSeD08-4;CG@3W$J^tbRipP@?C&bGn;kAodP zXeG1-AxG=5V%PnNEeE4sf}67qD(_xxcp9TD{*cK1QABk*G%4E%v6T#fzQ^6+SH0L( zi2vrI!j>-i{tO+tm7?Drol>+MCZ6+KXbrcZJrlFUx^S65-J5^Z^-m^07RQ#Vx3 zX};@bhP^}grN_E&-k*?~GIkDFE2xdbwl@@U#^U?K`^CVmhBv$%wa&?Z+#!vwTpd~+ zjtjA6-v^qYDYBZFu5DcKpSJ}{)t+62OTJ6qbnT2O?*ZOvtJ$X6haaIT!nh%Q=V_Cn zgO+)&+k_nfgR;Yc6T@h6K5#E`wQGW*07#Jpm6{IERG&B{ssPj%0H<+P95B~`gC})iY)4i0c!NbyjRBC%qEF2B@eZ^QIguE!NUUc-u&MY#ytjR8*T<7qRbDQ zsC!;supvS;i!u6heug(a+&WdS!A4+nc(%H^E8`RNs2$J#9JA1N8&$)9UP%9Mpx6VaP=$C)}=t_tmtzIpnL8J~~y;JL^owP}b?hu(D|&&>;D3b?ZCN z4UhGXWTloXI@{Sq?0qE%X(*=E`LX>Ti#U5yJSlqnH)T97p1=ocH%;>0zaBjkv<<-! zTejeHJS%&FE zW64ZGjZ;Y%=0!QoXS*nDi}eCbED6vcY zh!puQbtKv;oWCNu+VvBZ89N;%XR{&YghE`0qCNlmYu|%buPatNmb^n8iq<-KD0XxB ztkr5NHE0sSnJ`&WS+oD}_`zMKK;k&ZD_&bh{sg^KA=8H$toXV$;`onw7#ElZQw{xO zRMt5$98`GoCumX69Qo=B@N1=TNxEMz=M@jQd*CJxGZ2|>%TJ2TAFO-vES@Wid(1<# zM?wX(7wFT08(XOQt4!z^6(xvLN_Hq#DXJj_{`jUK^v^7`o`hrL+ut_*1OfXC7WGV# zjj_}=G&gw`7-oB}Et|}$kfrGA1mFxphq8kFq&1+w+1=o;$!d$X`?_nut4EY*X|OF) z5AIDgO|Tv$Qjm=S*@B?!i)DdnSglE^#uwNL>qm%wKggy zjBxr#kBezmE`xynTEC`cIB0+{blMC1*y9OhPw)0UK1Iw+xbK}m7119C8ExP3$tNHX z(KBjeUl0pkQD$PlgS$%@&*j7MCu3eB(lmPCbdP*!zAa+kt8QCW61hCEk1-ZO<9n5o z?iADXHQcDZb9-eoM?$|WX|zGogWcZL*{s+PTZV2K63@Y`ulQy`Yv{6rsLA8szcSTH zR`WHMHo2q+7kRy5WIS!$4?^uQ*kEu$-qSsK`4rAUJYkdn(bFw69ejCRm+UNf)J}_J zBlf$q7JfAMScpc6HTN`E*Dpb8aYvwi^5XcS|8!(Qs`yMgai(wUf?65J+e+tBZmg0< z37s1}-EwZdc{e-mFX=uN#A|yeRtC-khssqAew0FD99gYc1%!Gf9YWz#p-@F2bV9KP z*1Q4A<+MdJ0i~-)kLb=bw#u~}kq~$V#R_g`i_EhOVZ)GIkL)M*vOQ)1Qv>bKHWwVdM!aD-J+_p2)8t}1Dck%3~v z+tFy&7vRGM%D7GB5iwr~^8YVr>jJz&m=P%t?Di8B3R8%Gi%|yppb{R0NaE&g*Yah^ t#r?WiDFSr-ek>*u%5H8Ol5KAN;s zK6mcSA2U0)&pFT8Ywab!CFjZSh2I+hd^IIiB>)l<0D$!P0{mVEgafet8Q3^D*iZ5C z@Sg&S9tSZqAt5O_DYXDKJv|K_jR4CtHck}<0cL4EF)0pqQC?ZFxQ&feLUck$$9JU1 z`!O-GFfh>3P|?uQ(b3QzCnzW=sE-o{0d!_eI%YZnZecEVE@5t684(c~T?=hBZ3_<@ z8;`#W{Qe9ez(k2dszO0x03Z_}p%5Vb?ga=w@`#4?ht>ZYNRMlxBV%Abp8aS1*98C) z@}En;mjE~@NC0Gf6#U0amsyb^6%n^4liN{sQBay*8nHmk*Ow<$d2#_)GztMnG)e(W z>N{ms1v~py7%6$RG+F_l{6&wE{@)M9En4zwL|m5;d*gGR*~>OA)B+UBvc=wyUkD4? zRDbqjAN0wY$To{zXAjb_+Ksc;9RHuWPFjN-CnDl_cs-A&{@a8KeE?=VK zXKuk~U)KB@S<6~2|PxmU2otZXPQD#TP;YaMvwfQAah*O8U|uOBfyt+15* zo>#(TZ^I6x-%Eun08@k=vLV?PymK}`ObqlErOg#wa{E190!!OPwGv1D_v^peQO!$y z$>ZaxG|pD%=sMFxX-bzGy`l1(w=rAH7(5Y>sAzy4;M)JkKyajJJ)#bz!oQs#7|5zJ zh@GQoiA1rgQc;K7naL;VC>-Lsf6wVB!)SdW#njnbWn9HkdJQ(wF@2?3)Dd$s7O0Kg z5p%&*X|gI|(%HDuY;}1oY5HzjgvPE3F#8XUp3VC|?7ztS5UBu6dQM*F1s=8;F*R8w zcTr^Cp~DX?7%XSeHqH8S{p-PPG>hMI7?$+8CB{jp-VT)09w=zJM<2*1Q1+GLIoY=$ zB`IT*z2lii6Wz4z# zH+v-tspGY?`K|lB*jJmFU#-oGXZ{?O@3iUl(=Tc+F9;y{s;!F1p!F!r*+)@CHm?TH zi8Kez3>L?;c9dIHyS>B$SYCXOla{@a9;cPhhp2?+12FTWA78<}5(+hAJjv47(L*Qv zIrTrHiTKJ&6ap6CRM=`qk4A}FH=TLu*xr48VdU-iHn3?fCka_UybT!RQ<5Z0P+bz_ zQzkZAMVdTBr?qOWc)Wn*{&yd=`w0aT$kqS?MHwU@VTF+}S#!MF9rNc;*#ZpDeA-5` zBwf`G;buO0P?3>J9tBlaLJB*=?wC}wYn#~i2bB5mDzqemp z7B(kgntX25V<)*?;nPk1k+yJ%A$)0Xf6Lfl+4NkHz_a;SG~e_Gub`Ll6l^_FUq z5O3hM>%G|8k`RNM?c_;nmz~*9p9F0s+NZ5Ox05%@W4{3kbf|*>09}4!>AaG;18IUJ zjjxaO*o%pSbKmePxw87HO*=4~*YTybMCU*1 zj(84~w0B?oVz=LdRv9WN1QI5OSi6hvUGa)zU$SRu+UvI#yhP)TfCwDmtHUc z0C|!&tEa@H?|OCpY1q+oz9jBodZo)?Y}NZ_S7Lv{qqD>tVdZ5iu5%9Bo*lE4q-4)y z%H_ruaC+G7c%II&>ckI<&F(xsr)lLe^XL(j9#19D%>8J9-d>DE``{+4H+bNYH=2$KS=^T^WEsv?PjGoH zBy;LM_YK2ERp2@OU)(Mz&&CB@--tt9=!Ozh@@+>liujn1l_fv}AW4tItN@$c>d{|M zjYjinpbLMbIuS&4-7?9B#Q#VRfFz%OWA={c^#RtIm&>YVdKAHW-Pj!DkPE-pS?RKj zVbIkT#VDzw*^g@BTthm0Y=Du~%V|G_Ze(%UAI8GrCNP%^uQKb8J+cT|ruz8V9lS90 zAk;*M_x)cZeV_XwM&?Q4A5zE?p}f37w4;~NgPnFnM|s7oZZ2Urf%Xgj2t7%gaM?TY zS5DAIx1-(V8&z(9uMb&t*O(vR>0`V#N8%&=x4!`%urA29zHNWYc+k`L0W6G@VUi!R zhM-HI-kJg=iJtxy&f0e`D^UUP zQS>ySzp&NkeNSdOmnWX-HMlq8i!4jnPLH}YbXHo$Pu5xGo@a}XOW!=vYaao%*;!>z zSw=o?=U38=_Sm{Y{vY@LGlBtVuF9IB+wnktDX&${Mw^ zWl6bQAK}?7k)J}2O_OWGj`9VL=i@#?1xJ`Abm)}0Z9U%q&5;2BOr*s54gY>K*OvFf ztadFZR*AQ(V@w{p%juJNh+TwHL&n)fX>5g7+t6(ZqOVM);p?KjgvZQsXMaLxsN$y2 zxA~6m6w z!C*mAQNe(K7!@F^BdZ5s#Ew|C4A5Wr;pNrCrE2*bK;qkcQTmetsltN7hb z`>jN5cTo%9tF)auhOw}w?D$OwYT*8@B{ZR+C(k%Ri_GSi0Jt@#nb>9N##mgI?sxfrWSj7CvalN5C(dF_+(cOj({$D`()j6%kr4HQb8KwK;X`o$PVgj*kmwr%$*# z`7q8d&UjU=+cbWlHZkrRFXe{>6awf7B0ithc9iA&{|4myUo*&Sy7#0BW80c4^Hr8O z>YK=#a1Sn5DaV}Y36!20=dRqW^&oIhXGwMOchs``uFrw1+q9JP=q@wQTDF3>t8Wve z^&Ne`AgW?ZUyW#44adXZN0^jfQHV-O*WG_RCEHKAp|Ob2Of-n&A@zw^6<(k&qf&WK z^Wr_%2g+ymM^{O7HkKp|S3>K2$!~nI_H{ERyysTih1$JmuGhtL*f!@S8-=G2IT^JU zdIMPsbH8a+HqSITcUfpz~K`PZD`r<`vNQTVXLMyy{z8=_bs(C)#em7&90GXj6Gjau-W@0_LtQ9 z%OI3=8wGhNDa80&pK90E+}&2rMOUR8%YYUhMLXhSh41x;iWHQ|d7V?kCQkFhi^J}5 z>b_W!(++q~^}r~M!O?vrPTs6_9@utK=2qOApu8p;`JK>1F+T;aUgB0~e|CM$Bv;$| zXJ0q8Qqvxz;cP@AUdLGyTdmdj8%s+&d2Zg!*j)bH^fRU++i9zx*UV?X)pZ=%z4xhh zd>E^B9j*+T>+mWdz~2v7M>m&kO(wf`TvLDM*Cm&MBPZ@MG4-J&}94WJ%={FF}EZ13+2SJ(KM>y|if#pX($8;r>(uRt@OIRt)Tf!p67>AOn5#dICjRJ9TPp6ApDPil}1Sh z_B^d$ITLBUc_(^fQtq__+ABZt7Z=!-stY~v8^x}>)CJPciPdr6X0&0-ZaT5~m+ZrGgp+>h`@ea!G##h>7eML(I2josOO zQ@4B#BwusYuxGc~+CP8B^=7GY{-|e;HEo22nm1@snUrX-2g?z2u16N2jX+G}I6wsqj35R+}ajuGXkVm&S z43e-4PH|kNP_{{=U03dVCzFQ2wdWD1iB?O@rb?rU$?mvS&qkOhVp&rV*CN)x`<@*_?TIA;x8-4xTG zlsMOoEb-!d)3&Hw-aJ&XW9OemY!10e+(~3<`%;GuO~#%UX^&lo7rDDI$lvN4^UF7# zZ;clp^MQ0|&l9Ot zR>_L8H(YkS9XY}^1qI{wC0opCFYw+h8mMNrCSfV#QeW(`TH7)EmGh*ax3cnfnGP-$ zL52I_EaHK3$xC+p-<>+lTjEW#%~tjsqpx8uS{6qBqMK#BuE0F!C>z7FW9P|}^%?_& zLV8lN!CU`;ZIBz`Dg6AlQ|J+;&x>s0Z%W{C9>Ych4QE~vcq5wqwp@ZhsuR~r=Hz9S zMH4RJCq(QeMC9txaL|NJVf5mZib#^y#Sd{-AM-{xVK)x5FE%avkf<15%eKVgd5>yG z->}9(b3f7DdoLdfFQt8wR|*yU-u6Oim6=}>jLldcsUAo+^!{w#t9R-hal0(ov=$jp zf0I#_WTS*J_9680ok8j8Q#oCt)?M>Oo03V;?i%OOK-?1LNA5ebaV0EcuOPK%v-X*= z0#|JiG(_xjMq>5r`nGSC@%t{Ck$EgrH5ai{XK{m7*_16r&y}f`z?=YYbK2HYmcZN6 zj)Q+e{BE4+t|JXU%iLAjaI^NpB764(Y58qv8l|+r%{ccl-@fzr?ZJ7$bGW~DiF=1O z&0S6zs!W->zggMP0we_-8=igA9%XK-w({>u6cPt_V(w`SHcow=7Tcg+ zRqZ{+MqBCzs}^35ucKAvC!o~4xp5Jzw#l1v-%~6}7oHYGD&F!VTK&7W?d5yh$Fyn+LaEWiNK@XO7*v?(Ke`l+4c4q=Z>*p6TnO-?de1eg@v2Cw6=^*k_*4>{RR-$@5c9i}C{@n|TJM~9h8fVUr zF!-zYuhIn84iDl6KeF;a2XaA2EQ}gib8AS~PX_0YEv`zY^#6qJDzF6HNQ>WS7(OEU z51xqTL$S-%5b^(c`{Oagh3(QSO6YxgeVL=0X@&U&)?Y;~bownI9u?`6#mYEIuhg_# z)pgSV_`fp20D37q4kV@!aSbLjV|k2=flm_xafhQd%7g2z*sW#GOy2bFI|rj5UgMT z0G;$n$7k2Pj_r=)T$%9U=z@8=?KsJ2Pp=uC|A?`gqV&|&H2_p9D*WBw6A$L(VBN>6 z2Kn!?bZG{y`iDY%@82BwcLc4*)ateAONd=6& zGWynqb(;e6uRN0{b*z{!1Pu()Q|cev<&Uj&0FpG4W&&j7mqJ~MXi0mK>#cRY*wh&= zc^Yv#nKCG>aqHaWDtS$B~*W`IxkJu`_Kb3Y=AZ9Gbod=Q7%8I3*E$JFoER zd+?M}8pJwmv2HD*)Y&pTg{BzyG4Op29z+?-Xz?fK&suJ0iDW zI-XYb7^eY$E>e}5vU(>AjoW=-6!hTfNYI1$stvAAJ{v-GFEDbwy0w_}D}kEB8|3au znw2teK7lx9?Mdv<%{x_b?YGoIrx{ClM~MekJK5ZjiR%}*0y&xs&I*o>^}z~q$Y`5P zAs>O?Hm$;5a8hiy!ia-f5No7xuv;OXQw-9lr1!>*DlR=7177^@7N@eha^HKZSYV~V z>APxyqo2P4c>1HMBjQ>1K6yrYzJfL8c=lkb-vFU;Ny@R;JcBl&FeNp?hD^uhxB*|) zNo8QWU|ldpQq*0g0d5|L=)AofhC5@xY+in(HU8;liPgdV^D7V-;l%V4G`w`_j+^e)$- ztT3efJ6_i9+ea<3U-ZzUT{aC=8H&bvxwhp)F_`P)ka>in;O4GmIQ$C8Y@RXzHgwFU z((sT-l_o@qjGz{THF2M>IDUs=Vp9%@s+#R>GOuX|NCsb4M^x^CvP2w>GDQXPGv~gmO zQ6sFWQgD?P+icD2tZ|07BU5r+zqsHd&4lgxB$bj19jrWhrNCz|{T%b9j>HbGTsj}4 zeb)2ZFK#m{9K?nCG<1#cJoLaUAK$124WL=KMx3~ZlNS&1|2WOl22-*- ztqYHh$Y!odEK)O{bp<#jB;rPp?aZ)1e|bdJN{WM0o#K06hNghS_)0b-s?8}W>^yl< zk@4INHGe{wZh9fa^+aFJ!z&2LBtCm}u4<(iMSNC4M`e1kKKze3zYC@L*tqEw5^MHZ z&4fBZD%I$8l}@XyX;O?8hY;nav-NbiJL;hgrlyqH)Z<0lADgsQ){~bdL|z%KS%tXh zyRZ^?hjYugv4J!3UY9PJf~n-h1(FH_?N+0YOjX;3)nbaRUU+u(OQ2umq2sxl<^J7S zZM%|yFzc7OUC33;Gi$cyOf;r1R;r@Y%o5^Cgzg>#YdKRZ{Op}NpN`OQyiA4ShoFlR z1j&hRPY|ORUXYv@P6K-Wr4;#jKe(TgP2VV~L4*b8lSMxjZh})_NsSlmRo2x7+7Xkn zv)!`xs5+G!ucnOC2&Tw5?;qHNETh>Z2C>!cclZY+fBY-yn)FHc2}akIm5CrnZMC+F z+yNj9DWcOaC5BuDpmZ#sqxF!l+Ba+`dS0!gIYXp2N=(o>wYq5y(-na)$L}hl71t{n zU;!x4^vt?X4%%GMj^H$Sg1Rc$u3Z*<(r8p6TM=R`SyT)@W&%&DV6C&57k!drL)H1; zDx1S5x)qu0C7A5J-a4E)kS5X{|D%|P!<|K?{Wm};LdDC&BsuJBsR(-kT1%EipZmwm zqka9e=N4spbWyGD9I?y>_=dBl&>GYuk{LRC`{o@A$%<_%b}0KD%f}+_r_be;hq_LT zUWZw=K8WRz^krth2MSrg!_gHR$8HxP7UZ$P+p(oHcYUMtcC@>DhGX1;A4!j2p}nI# zVr8YXkY)3=p^(a$aD;xvg=-bqspcQcKd#PfD&QAF8tcQPP{u2fn3QE+(FS5$lV&=? zj=1x2whheWU$e8g$lB}8v|sM5v8fnt=tmgfRjOlUV3hHH?IgC2+csESZ4+=l8FpIg zmZ`y*5P$TqrXq+JD@Nu~sj5+14VDu=@(tr^7YRYOnsXx)NLHeFvj1vvlvVX!8PFL@ zl>3%*@Rgm2$VO44Bm%J8P2=G+?-M~;BW&~+q36N&uD7g|t!GaV=(({f?|-CdVOo&Q zlsn&b$W*>ix@0yd@okEj6*OKsiklnSK@*{a`m$l!?k49O%*-k%^0T##y1VNEE~tdd zbHIjQf3g&kM~)RWNuDL}H-FvBc5D}$Pw!aNXsR#5Vs$QYk{REt_&P3f?Z>LNXpda{ zcFbH&WxLed9DUDW7UmA)Fo!h_awBL}Sp-kCo9e=FwDszjaf(BiC^-2lRcHS}@!RI8 zbOn`BF*yPDdaPGr_m%F>br=B}Kk=PbCmkdq7$mazC87l?Q~R?9{1v8(pFH|wBI{e; zvY&FFn9wQJhK`C>klK;o^TZAsk2)C8IKiY=+98vzX6*g8bJZ0e1%9H=eVhss2R;{TiP3YX zKdo-ke|1zQ1&4M;%jC4MQ!!TSt3X)vr-V~-O{0SNv{UhBUUW{dGrU<>dD>cllOHDx zUwV;JUbj^a#rh2ZVzN1MXgQ#|>SeENc8Tm1;=Cm-518hfT?==P7Xj@g6CF)qv!7`+ z#>MnuCyWCh`j49YP1c%kduLtDPAFyu*Ip>=$H)_^3{f(9UBeBTLq;9=@~3F!_;lws z5dX#EyUU#u-P+E*B4=mB&G!PR9~$F%YpUs&=yz&%igf~{trWgx&q=vIUVK8*I_t$X)EE{Z|TDeZ2d~!qp6&H0a9HRr`RqR3BVEx&ai}L zEMd^&NGx?OPf>)g3kcEm7Y(bMT7|}$U$ZSLt+5hTY7mX&3_uXYv_bKb24A^pE_Y?b z86VD!x@d{vr*D;mgd>Eab2A5&6GF7DGOsX;LZQz7gB`DBhi~yghU8R6V`6hh-cn=I z7433t5j;=vHi{`HM(vm7)rDAvb{9(GlW}D9hCJpe?r6Rke#7T?)pYS<6DooewN)r% z+RIc9xBg0bP?SQRFf+e2@-k}qZh(5DTj`qgiG`%o2Kpb4r+m2MTorOCf-Bymi)B@C z!Dg{PBoc(6QU_@DetQd(P$7#yfaKrvfsUFmMU7Fhn2pzB{o?f?rPo;ta7fH)$d{lq zFD#3odYJ?5~~E4aN}38?hEyTI>3X^}{n@I!+A3(l0&I;@jdH744D zJHo{rgIjKv!6xSJGS)L3gM~Z&3U|G&7kxe@P8{k;e{Jh}{J9q%{N<{_PQd zfy=SOGHvv-WV;O7)W-s!KXv5dmIUXLd2%yK=u#0`fORrZX=+|pMiA>9l2GMT36OKu zJngM7Y68=H^OJN45@Q;mnQ%Opk7>sWTB2a#E7rR5qy^c9B*z!!@WhX(Xj6@MP+4V1 zY(3DOo3PHMqrIHTM|<_aY{9`o4QDEs z9jvebg1K)42O5|1CK^+@-}nsrT3sPiz$)>PH)Kj^-VVy#3c95DbM8V7i6&576C!o8ntIn!VU;?zqz$n+Imo zn2pngDL!$vr$w+d;j{b0I_tQ9Cr4yiy%GS($S1^Q-j zoiV4v^}*EAY9A>Ipx9g_PpSr6Cs+g}L|9GU>xLY%nph_*qfc*D)iu>dXUDR~wx&r~ zXbKd4WkCW%(Tv~jqSQ#&DNK;D{p%iYlK72QUw7jI&)EjPK2Kw^tf%3e>I7sM z%%f6~ZFg((`3b&kWRh59VSTVKvzt~Cejz9ul9`xG=ZhqEyZg>7^o`FxXe4hEs&--M zhAOYM>lfOKV>q$dCxwAFMjjdll|_Pr`UTr64tzcQ_rFho$RDFe23 zc9?8mm+4%?^qB;GlmwN&Rcq?T%#@RHXWi^POO$hPiVzcVja0-MJ;*HMP*cF=yjJne1wOC$3A{-SGUiBBtDzA zQ|vvl^~|P!Nv9%laMD|WZ1=x7bVqJQ*nda8>1ct|YXAp@_@(^n`i>d*I6SiTK-k{00visau<)-l;$r9Zt~8y!Ucv=QE>3*3?J2vPvE%Lun)nEVoh- z7w_nXif=N4PqI*X8g&%lF;%`AQcltUrCR+jr-GnYudB*|t0YHYpExL_Htk=6~dgZRDAnByoHI^*2MI_3Zz zHD`WMA9&B(vMxb|qEetn0!lpZMBFe4#4G<#o>ve47`iK`40a2Bi-Q75YqSc0Fcc_# zAH!u64#>#Ivd0oozSef^%uPU3!r}ZiBb0)2#OLsZ(X|0-x<+TzUt{Vw0G&~bM|W}X z?UcU%!>K?tBjP&INJabdMQlz%rC{R#A63tWgnAuMZJ1tv(b-@1Er>1i?Z zg;d{HxKPjJt^C5;xp$3((XhwCqWP8LGK3&u%piG9WS*2z7PYyU@FatvW94PvU`3X< zG20dM1TF=$H>**vL~YK@_C=6l#M8%)8xW%9RdZ26^pu| z-JewVpw59{kj@;6AG18KJQO+v<<*~oBul;mV8d7(Kve`~8gz}8nnl<`%2D0==`ZuM z*~ju4ya0Ymbx?H%5z77o*9rbcer5zW59hmCy}}~P#zv9L(@fR&)2kJ1m%HvJq|7op zEuO*cF2LrB*vy}Y3w6`wSQ;4^{ObHqWLlz8AMVhDD?AvL$WVG-tYwE8>Qr!H9lvJ4 zK;4^p30vAa56u7+`7Ggyh9AV%0g8ZOlN?)x$?8S%1{w%vn@pvSdMx%_%Gf2vfN8;4 zelL$-Ifm^MHboTkSaxPW0W!r2G1IwE;)GXDU0`fC{FO8)ZuYZ|((_4^7eO@=7TV7= z1OD`%1W034j@dGB56|E31HtD0e4#>u?wGf%JbiMA!Wmht;5}fH1tS#^C8e_9yDpn* zUrQ+lB<=w?dTToZn<$Og?DRN@nDQN08jPiXT72NHf{>cA!9V{}+5etpiRJ*@A7+01 z>Yyz2PB{1M$!zAYWV z7Yl46oUJsob}y|N;sRgN{v43PQabqujQ-$@zT0~w?xf>l1=?o#Pvuo&K>K^ki`XOC>bh+;acN{ z9MISCh$RB`=V56pu?JFvq#41JeTE{?PWpqQiODXZ=-DsIw+>-WEEA6%sTF?TBR0iD zinaEmrhbcIhymNOr%+4qLpBanTV?{O+ZbTz2wGjk z=P-IugO3FGs#U^?LgTJ%2~|vpB>A)PTNfB5=Jxbu>;cxmD(CoCze*Cq;enTTn@dtP z{}@Cn)w}^p`a44xRtK$OlR$|?02UyuoUMopMZ!?dLl(ht4yLP^C6J5m(KUc819H3y znAAfI-iuf+qZjr9b{^X1r%3LARf z#1XScEi}$Q_RB%786wFc8||}sk~x}g14&Gyad@36Is332{SJMrbwy>#dufFU3IMI% z-oz~fUpJC`v~mQ&UJk*WArYCGVY+yET^Vn)=CP?zStj{M8sd5RTbR@1Qyq#+Hjo^J z%}Y^ZYMbshI19N2l9YMzk8Pzh8PtGKpNHW2SNe=Nw1#bXSu;Bqb-?H61_lPYf`SI6 zVEUJRo63To-eyoZV)VTVpuk?GYzgE_+ygDCG1I(fO(L zk6%aDJ{d()v@Lqo)kmJ4fF}MW(^Y?L4zDvxv=rd*QMgRL>gKL3z7v?obX(Z~F8 zZ(5{gd7N@2yL6zLK83xRN(dyDF2Avzr@6FMWnwc)PpCEC8Puc}ls;*~)X8FpF>5d< zHLF3;^B1xP$x>Tj;qBF5QN%qH|m_L|Ygi zf{lrHpV{Y2Gr1(^)p)wpEm+WSh?>%s%hQ-1bUE#t0Ghvb_$`bX~~_2hBuT3 zGRB5|sD6ycY-LbOeGznmuFnvp21xu$jRB4hriFaeUnUaSCMwJqT69>P7b8_#w-sZ? zb+I7PW%#kd#xb%3kUCN^FtRc{oqg)z68mYnyJ^XX9ETC^$|OfLd_)<@ zOco7S#A>kl-DN2X|7YYs|8Kc4ESE)edbVjG2U!6?X)b*7@$MY;D%S^1Wm~o50jU!L z-hrxgZf7>C|p;QEgT$ch_B&;>$65>Q0Dp@a6ryzOCF{$EjzsIQf(bT zuz9XUhW9VEu9E8)%I+F#YJ9T1g`LXPjt?3WKYR zh#FQ9rpXW;J@)gdOsS;y0XhbvP)rUEhFJxmQuQo*7yYRQRew^SE{S*AlBaWU-MGUS zX`l)h)!(>*>(N}o>$Va_Seypf$MLT=UZi;e{H7{M*``X2!4>OAlso?1r z-s~ajuNd9;#cp&*rS-&o@+O!O$>GDZyrUiSxqigMX2M$deX$9}?R6jn{6#lTxI89Qf^-b#w*p zRvC5E8=H<2UX4J3f=E=&XbX-h8-y9%>(|y;~rC5(S?sUTJLjmA?79Ebf8XGG^eHRLl3cilg>$_PN-vN|J|3RI4y5V&XMS zEJud&mobR48#PzH^2pJUymZupWOD973R2%fr*h8Wa>u|*hn@zDzi`Ilzu^9ycj8%Vc{crg{FkC1@xa z<7S8HnTR|(vM^9Et#s-%HxK{Gnnz*%rJ4PWEJ5;H*@qNNyp~spl^$Nj6 zb&{3brinAasp#eTHo4_7MAzTI>4OOpbd^O;&_iHil`~eWD!8s>CA3LwFD4(-`c~-j z+ipWK@h?NU2kP+OQ(Aaz#Ao%QE(;*R^(o`__FLU_?Aw@&_JWZ5!$3gY&!75*!psfN zOkqbfqCbeTRiB^HgXYcRa)@fgbj}ZF{2W(;4YZhW1Qb-hi7u8lAC&&fgy(>qYaEd( z<%A!Nl4_@cCwd~@Z60Q|yOKG#1Sxv3pOHrFrx>1X=Nd2)2)CI2}sw9Ci+C9{b8p9pKPhN|@)2CSQg3 zyb=a*J_B;L2D`Fw>caV?RULH1x^BM}-OEAQM+4~#Lvw5-TVNA2)MROr&xJU}J}G?a zG_MuXR(`Tsis=36BQMJg+8Hu-6J>oB6;!p?X+AZf@Y$Y_7&iKusoj#A2|v5s3;#a?=$ZU60psrw7zK z`f^NN?XQX_VPOQY59FSizbsoi^_4eD+@qnVW_4U@mw7Or9Ie3RpAJ7MyG27!YS6N$ z_WlhpqBTy=1Eb6opiZ8BN&8fKw#(PzM#ECxyGZ3&0{Ph=N7;>oCMx!i`44mWW+;ai zR0E9IXax(|F2%<-iYA(EC%#-=|Dwkv$1I`%9ATZFuoX|<`FjDSr$ZYTs4*l)Iv~$JSLj(>Y1y;1N0x8taKi2;c1jyQ2#X_w`4FwA>6x+W)_%e~SR*C$ zSszv8X|GJK))~Py>myrv!yooeRw}jpT3IMZ(eTU#cK@pB!_T7!jDU3XbtA+zK%aZdI*d~5j&0IZy3ev|5BSf_ciQIjHP?J2ORH(Ku5DA&u-%@n{4Nx2O4Sjjs zOW_^wr4Ke6qL~lH5LFqVL# zft~j-egkBOT<2d-X$ME|f)Z|Y)rK-@dW`Z^og1k9<2SqfdDpgbeMv0O3~z$ChJATD zP7+{YMT#HO{r>{WT{x?RE=G92kjlXic1UDWQ;`oa9n(56Z@w#|-I!WNpPV|6kLpzj zXof>%D~>u7Kx9BkCHq+@#B44l>^DFv{?|)1RYvDI&&?D{nH)Ef)^m_NTp)j~mt<$m z>99{601M@IuHtep=a{th28xD#Oe%8upvgQyTl8)u{Y8_{X;0CwiTJwNA5u10A^KSy z<5TnjFU+O_!LOYV{{k_Nr@~K0wou#7L-T zbQL{rSD?@7BGohpLj6QgS4SqU$H<3e9{@M#MsOLfVFB|X$OuK!7@PgYN=a||dUt^hpDX;0^|Yv&u5 z?`X3`zEx>sjb_{>9A01=kt=JNdm*|v>^QRe4z zt`3VcHXqPf71Cg2lg1vhs2c{aXFEx5w4Rh$8yc#yL( zE%i|4P0P;H`>>5AePdCkVvPOZa3;tjwE^SdZN16Kw>jkO7761mFv>8qe@zbjX7kon z+ry!-w%>$!JNxpXOZZQvhijAh5^4qMwn}ao?8m5=eZC%+;%yVE@my0Zr=Z^Cq&PE5 z1Y*>r>4SCk`jY+(0NXf1vv2IPiJ9zmyf?1diZsw7BbY5WNHf#X)FR^v>;76MOhw=) z3Zfpam?ekm2~)`-e^(~f&~HzVX_pXpuXX zu-9;o5n-OZGFYUO!WhU;t7{-ITl({{kVizv)=v_~2NId;`o*fjF>t0`=F$TUPqn6u z@^EQ(cL~+Qq3~m^QQX6C#BIoqc%?fn`LD~GlvFKU?}|cn+u;NFl`_@%E-*F^ED2!f zp;)8(*^|La6DRtqG zPpu04k98L1+3w@~P}kuUkNWrfAxbnMCd`GY49C8WYb4c=a3YzLH>3p zdu7ABHfs`S-4m@j*-?j29vtclyfD#)3!_T?U3c71u2>^zlg7TuHEVF1oo$#Tu^(0% z8NBO4GdYva5&2Cuwf&eM7MrR&({a*fW==(Evz0HOMdLT7lw!4lO>-Tk!(7?)n@@vT z-rT}RkIzW%3Lcg?ty~?oLMR{*_d=l8*uE<{ZdSqEnEMg`>$SI)#9% zw8o1%NwLqMltdJt-=t+1Ik6QB%kGu{Iu?zbWpk#A5qY;JMO{7gMERB)$uAWbOG_z{nC9%cC<;Jk19 zoc|tdIs5u(r>)QWbmxj14Pj<&`zRkx7zS1{l0e17D=TZr(7g;6kAdOZtfk6&>RuJn z`|2ISHQ`zD`9_+*^vs}ow(>d@#ojV3>+@l(B%+DTB;eJT3yuU@tA)Mx#Jg(rMbf>_kvny&(Z zP%niDKh>uOhS8`IHK0Dnp0y|F06s+Ccg1r{70pxJVf3Qr<0zxSOt$cLadM}w@ctPn<{9lGOo{3 zwmdJs(nD>v2+EcbNYsJa#_CvQ#Q3xST(wylC3GMNFVh^*PVA*brf9Ifp~F$x!aig~ zVpdd9UGSCtN6m@Irkx6)#;~<_4L@g1t<^VNn1fHlm92>9YoEIq{Wc`e!wjlQvQ=zt z=Op~kg?b~(=kS=8b{KHaIkLvySE9cqKYna{I4})BZ0A3BTZix<2Ze&|ABqnzK~B| zbOOmRnOD*v3pz>Txr-C!CppDLiyf6@Cx;e<>rR<7GaI(&EJZmtFUdXlA_oDg%8AVFvZz%e&Ef1)OY+SFkZ0J`w@%I+ zeqsON=fmwMR^wJk{QB^T{C)n>7C&~Oj#^kzuv#o~xcj_-AYg)_@)O=;aN{1BnoUW* zc{NcRag=`TkCJ2ENB#C)PP|IvrH8$4C$V2p?)l)m6E^CvN>pj&>7(o9#qzJxhB@w9 zy2|2OCpeSPPw!9KOn8OHz=yaaB-6oRAW|qs6!4!b=(FR#gx`DzTRF_j;ixgJu;LPl zX;1b*iw4DLHEX_IBUvt?Ck@t5qM%mk0xHSYN!stWCU2pb(lT(>rMDGkIX??XN)8+= zcps7fT;nD)j!xwTlP9pHDP-aOAHeg|>ei!zfU?D!EnX`+^bz4Rd}qW{l9C-_xAkG5 zkZtWY6hp-x&i>Rki4y#l^b6sneyvpikRC8FnbxF%wIVfvhDi^}Q z9uat#km}$h$`QsUV5mGte|A2keJ3EgQ5Z63O2SRr5%Q$7PS`9Y{84>?qj0MXLiJtd*+51Mt4@OLY7_qfhRiibMXX%mzohak!ys`?C^ ztR6X2en+cMReL^fo|EQS!*LgAJ{O{iaGE-X+IYB3xy)wTsBU>rrn=-DbRlQvm`s(XBFG2ATWR!5 z1b1&*hbT$}GvXrY{Tk_;W@#KZ9?zn+UyC)T5fiVcV$-EdzMUEdE7p%M3y(;ukd%-! zwv1C{V(wOZ@HF;5t0yU*-TV7-^jYx~qA^?-%H)*I;fWJ=-VR_FN^dfG4AXgh4_Rq; z=$a3~EvuWPF}1lnlPkEPQdD|Ma$<^aI>PnbyX$hX#EljzlJ*AxezidQ{FBI_uSIf* zS`Nuw_Z!3c2Vb4x75EaLPJwmv#{oAF&sNz>xeV$h#E+Vy%Fdi$&IS&7A~WNcFl)1sS%g4^$#>c{?xQ)L}k$w73JbR62^*3h^@PQA->p?d@?34Y(=$2pn$ z`+9+|5o^$m9Ow!cZbKP7NQkH~YIPsZ`j9z_cii{1N^#wcXp%s3**<6%5P`2Ag1by; z6Y}}^Df^2TwJ}tZp039H-dpO8#x{+%8&+q;hZyncT56L2avUL&=QZgr<&`C#00WBy zo;CA#*y?TBOYS>02~Y7j4WY8dU=&J5%9FSV8qil$iz8P^*|bjCJ(hX8#Hy_vJUYzx zt#DVXK$u>0n7z#8+Q+PDuX0QaDXaG6e=iWJVL3m7zIRoX(jiu$<6!XK8E0VSgoN~M zeAu%||1l9KOO92_kmLFO>GUMp@6vC`VJZRTQlFSR77dKX78TE(hQx2)#qZAE{-|h5 zw?t#=!mR&hK&@A1{Z%_KaOCaZAF}UDqdd~r{srd@);?^Joty<#C0r69E;c>x32-d& z{2L$S$~J1`h(40z9h)QG&?sN~;%dQ^l2HWD1{k|qAD1kp1JW$ag0(ng?=Tue@%vxy z!DTIG0{q0$^|58|A^WIgEb~wueWq$g6yp|4XZ`IuZv{xW;&z+MYGtEzns&yO!<6Tg zke(0ziL!QSv$w%+LR?>^Sn6}}JjjQ>`XJsY2=xE?AeW{*=re~W z1JZwVcUt>^Xyf= z$Aqybx369Qt8keu;{NztoE4R3i7bbEFBB^@RYCEum!2FR#Pesvqg-Iw=R3P!5!XsR z=OaIW2f6;6DOWgChMzyu?UCiwboc%N(04An|4muBc}j4Fy+*q9Z9o1Gz)MBJ8RLct zv!ySvT>brivwwtN^qlY`Ig+7xR$L2Ct|ZgRD{R(<6ct#ud8hD~jJESwq>Kd5O#-A0 z(mw#C5Xda0!r!&7fm4ycA{=2Y1hTL6gkBZ|G3_QL&rnS{)wy^izYkU!3M78~G(|;H zy!xY#q4}qbkQ-_7QA&LLm;{A9*;|c)gU%dFI^n&mpX^hTKp2CXlWpf-wqzez<;$Sy)*!@xnNa?6 zgV?Z2&_lUbb`%uacTAgT`MtO)tDa+-+puBgUr!`Hv=0qRKx5`_!maqU^V5R2?lbFj zOsVbp-2zv~=13~1Ue}91fC$EdWDXM>&qt}EYxV>yO`_7oh9y@P`&V!>4-^I3VYV-~ zO2Vd}WdEg)AG@c(&Qwns629&?i`3n93c6hOz*d-yv1#Z02Y@LVY@9!iD_3feLledA z0Q8^N5gM$pY7X>g`uq0DTz;C2?>xxn?)Dbb*W&6A0Op(1XGF`qWv*zTi&>D#bs50HgFzchP}=?HO*lycj2q>uPMh`spC9_U_j*{@@>gSP*H` zH|;EqlmoF_oj~QR+Y)&Tfl&CK1d|Iuo) zjbTGUbLl=k1fxQMl!`EJ>xWbN1XTOx@s)72;(J**!TOXLdN- zHr+!V@}n^OqsQUU>On^Iv=e!EsT};yde?LG>vQ9E+i$wuTT8z`fa2@d6VrbHH?KWc z0}qim@cUcKz+W@UimzbS+MuFLNs_<_8Zhjjywp{s#k{~`ZRP2c`$3jT)+ zxenrQww}M){(IYh>hWJ}|EcLe3H#sZ@&D4KJ+ECZ0+6sUkg9Z5t4_Z2coxD(@fAJs zEloh^XvaZ`D-tuy0Y+kEQBhIQk$~C1f=41_c>y2{{Bk-rVDIoWdRe{(Xkp*%<<7-_ zp<=<2J=JKkGNYG5w@MNcH$$PY{OpV9^OW213=aJY-BR`c`Z22c|M&l`MFBA#oq9}a zztqV@kp*T$izrB)vSP{nH;TX5>P-LY$Fk+CZs}_S&ZWP*MQG{Roo>i4F8`a5zu5=} z{uV8VSvoyYZ$a=_fTBXlxpDK~wE3rh&Wp(?m-Ayrd)EnA_B5IE-~BW!RB$ik}7Eyg=x+iiZJ&v->^Qd7v=@$aYM zAHf_(JB$Bze!56FbfgdpoIl_fA2P&Fr3&(6#&k3yj>S!lVwNKSm|t<|evn~NNf%OHoWFOx#bU~x%|Jwz8T0jx z-v*{1$7E-wpZ)=$;ANDKIc74TLoGXI3bU#(3=~kOl-OEXZJ9iUqIXF&Tpd$4n%RCp zEiGxLH{@wr+U9c@KLp?Fn^V91w${@-N31K*zW;h}fDz-YT9#BF80luMEQ&$APUR0r zo2g=0TL|$H)yZ&=7H>C@%WQSKBzD`MzwDtodMH90u`3rb`~=tt_ixG2xhuxsIf#M?6%`jt8?U1))w+hp~D?*nRH zb3`)-!wEuW#=1&9LOQm+)IHb%$O~U@#-r3(t)-c@S6&;#xJn3Q4%j=E}>zfH6F38>yXsUqZUi7FvSK& z+0uZ9<%=kdp79)r?g<3$uUVX=BI06<(|a;{hV4u1%l5>FGBAs+lcy9rSd>J-0}B*b zG9s7hp8qT7l4qYs3@95$=?lmCtfOuXnTW!RbJQSjE~9z8p)kZ6QIbt@kRx{rQlW_1 zE*E$vs(-%*USNwMymOC0e6WT?Q{Wu1&SJtbYK06ichx`gS;?YST!(<^~s)_h){WB8W5h%+4!Z zIg|EqOqVCU8&8xQCfgsn&%<}{L*K}ps+?84uSk%NlI2wJ@9@d^xRYQW>`TdM>A;D7 z#ycCkaNZm(@W#Y2{F|5Z_`dVcR92jhCtkRbdYG1~H0ceypur5$+DnL>a9le%?Hoto z7`k;Fqkuv4!)*VT?oEYi;Q{Dc5*^=7=c(j5%9P}mrT6l4HZdd@usEfmHG7> z)bzW3_S1m$c=-5&uLj#*oCsYFjt+r&FFN;EE1yDtvAuBJngGT^xhHHiGf$hR&_%^Y z2nj1RF{y`#lu!eN#{@6MZ!^&1JK)zaZK?(lqjsjWNM}kb;jn489hh3fs6W0d$J+tQ zN|920`f)3a?d^*n99Fp%<#og^SFAd4b_P<9WNING(_q@OZ?D%_OaeO#%gZH*B@i^4^0oWo)S(R0 zzu586C&@}s>hdQ-JvO3lOa0OZfdz`92~%8?n-zP_;E2T|q)5xG6Q(9j!aklCU>$!t z>N`5KtGDR%_SEAdF%?A(U1yw4w6bp{Y^;(sucx>8XzlrOg0i-az^mT(OhjSJpx3Eg ztM@9wIH9kGUW=)Pl;SwEkA}7Q2nXngmJbJK3@wpzD&a2W^VrF^1ZQ}?%8AaafgC(f ztzL_2s3b6={gVEP7;KAa+T#c4JFiws`v~z}VneSD zbWi}SF@I7qm!cBIzB+C+xtwHcm_8n|OM})H-f9|$mD9S>2jV*&K<9}H&cfwx#7%zJ zb4$)xp}&_;Um5Kd3T-4g-7IZEbS07}>y}%-n=Jv{eNRiD+kXr=+MwgNRJ0@Ig|_x8 zBw0Pn;8n!W((%ie8}Jsin8)SM*>LB9k=L9Y)Yt(XlxzqqlKLuq;Ng=EY6|16y!$Xv zz!HW@gS8L&ImYe=d#iP}1wKnJ@h6{qSy#BdnkhqL0JE%5EVatpgfBXEn!IEY4$>>; z!kloZDr3UexaZIuai-H<392fYe7qWThpASLGEi2oLyXw031_=!Wt|i*XHEDOtk}hi zDj{CUH8c1GP=dkV9?OJECb`UyK|H|O5*Ox$Fx;W_&=pU&GjL4RtK>2nLnXU~{5Ww8 z>%N^h>8c^pHjndoYHnIK#Kd0+ZoQo>;Qp$I4eG7r#2)-1`w|+91||jMjTpFCde4vIHWe%AUfLJVyig%<8#F$Ttgvk zeLsF*+1>SYA$snYcz25$1(V*c#T_u%mrRiG;p<-tewI#vwzlT667T1HuQ|h8-^mo> zxxSQaD$^FjFB?KU8RN)yUn8Io9pY?HB6ZqVLe162FgPA65oC&j+fxz>A%IYHa7lE3 z6p8)`r3iB|=NPw6zgH=4i_fZ$T^sQM1{sOKS*;hdH)!}If>D<{0`TgR6oH^V;9+KP zXtA)Rb>F;#9p>;QaAxV?IzM1Bn>O9f$cipo5mxOIuCu#NZ;s%e?8Mu@23fC2+#{hoIiwL(Y`xPJ;7Q~Rzzbz5<6hD3Ck5r!Fx_BUXUn4-AK(plxM8EZSGKJ z&`+2c#xndgTl*QT9<&idzXnKlpfJC}F?W(GRSA1{0QzD42Vk6qwdarWgkr?=dPu6r zpB_sWH?@qj5!lzzKkPG^Sk;b$&mm~qOg3dpnR0GBanjm24tZvied5=$zo9w?>0*JL zo64%P(H{la;?eUv^dYXLxLO0rOBKb3$CSBcbu|CRw_d6Ht<5 zG}2LM9ZpTZGxs3>I!Rwp9J&EFz7gTfJH2!DZRCX3`}6hi+Xxx{sB!X%K||&dKtSdS zTp4(1!jEnw(|(l-`H~yMkduV7zTxfIF=Y>D+(2Coy{%X%4zk4s)GP9)co4RA4;fQ^Y4@po?vqKr{L zmYJFMh!Sp(cprXHynwJ9p$IDW$NNtj@%-|UOC@(O%Pi)YUC@ZpUw9$KM^)4n7A^(4 zog{(Lt7cAW3_`^O2PjaK;SAn0;#y_`s>h~IQ}y0<3eKccK8@A#o3Y z0wFRHS4g@33HAqY%YyxZ7d}RMN5V-HEo?WZ=+zsm27PoCof!gqWzINMxsa1DUB$9z z(9#>YwC&lL9YMXlvS#;foR|&s=_yNI5Jl0!1*s|nIB%@MTYziqell!BylKqw|6C)4LRKF(1(SE&;uEiR={sFVA zzn;}wa zdauGMj02)y5Z2Em^&UVQ?BsA*tT%ZmT1#gU3HPXe{o<7imGZ(%x*r4IVF^T~zWB{^p}J zOb*=E%@KAxgs4*Y!AHweMbc{Ty%X+=*;Tv*66*t^ypJW2^N?-H$7`MI4Z_s#nu8#g zQ~ICglN498*bx^b+4?BYXVExlyvd9+t2y3AMk@@4v>g#}BRwnT7Z}SMulHlKCY8Ag z9_yGSP)E4aY5xFbCdpfyD_*tNP;H2`R&x-mR56gsQbF2K{ipUaGp)SC3>i%MI-%qGxX4dN#gQv9~JVL?dRihT`gq-Vy(dOJwWfjAuF%NV?p9A$$16#Q4s7 zzRe?Gc936T7heik!}uf0gJAwULr!7B@lF!d?rrSYE_Vce5>O&;)l`FbFyi`*&*+2G zr$#8I9_~}6-u!6>=+(p+mj}nuq_?MEXx+{W%Yu35ybdhEA> zSz^ebM%q_HHS~c9c(@x;w{@|cTbnE=Qy{%b*v{wo+r{+b_54*BjC=wlfRs;Y5b-?(JsLGdL8B5G6RX?3M-m%sQ&Z z3RwQCJfh#*?3JMrXe3#xf6bSjB=sij5JsWWjGAT_K$AutPDyOEmd-L) zIKpCq1Mn(Zz%$&F>VkSKZuBQqb`A@onb+xoL}{X&;%%j zo4LBh_N%uSU0j|<67+lrs}sCG(EO2mcCcA0)@_VLU&o7ilgeR& zN@3q2+;M!)Q!cZywmd~{L-)Y2V0godf802FzjLCFl>p}_7!*cbzbtSM!PQ^q?Hq^^ z;6Aqi4WrI<&N);Jp;lskBu)C2ovi8Dw4p|fbI|v=eLtZISSlw_6t&GFAmXL%q8<o}{=>pRY*~-Ccd5%Gs0x#?Jih62MEqY+wcxurp&&79pTTPxVU!dQ^-KMnR z)(WXme8(puzVxR3SYt3~aC1}OFuGpU=RJa&TFL0^(APCc0K=ya_~E4BLe48NDP)*= zJ2S%&dd#(8iIb%H+aLDwLS&gnF}TLsA6VN%!poE58o@F7CNh`y085cyB(bC07*QoT zDYvJ{p(N4pqUY8Y1Ju_kfP(g_+16TMVs#hy>%9NV3Dl~zOQ1ax*%@k0sThx%uwa`G z>#FH9g0BfMuxn8C_4Sv2_x{fO8TEh`2?xEHw3F~Z5s1!T;?emT^?uL+RpLlT-zdeuCSPIQV!Jk}W4*S~oQnxw41VyDMx<1`vryHjhA zv#P#9dzgU2C(1@FOBY~)iInVM~Mxkx8w_`K?1qO|1tj)shi zA>1FeF7&yno&*CcK};>>s=sjm09GH^cGOSuWCkx@8c(*MDN4(-U^ArkWI<9RiHH$` zc`F}b#1j!#0Jf1Z`m|{M?T(HjT*+NXP?UkWb$qx;0L{RA!HMG_JV_~j%KTW&L=AM5 zAere{*H~Ko{jh}LbG(R*Us#qw%9Zo3$6n!?*#{%BvkGob6`q~$Mdr5SX>IA?49JI? zFEG?7!lMZFYT3P9EX>OMP%W{C*>%#%>c$h|qU*XH%vPOb9-4wU5)>x~-paQegztJ; zF+g>q?2NKp(9A_XaSk5V&BsbMP&47s2N$9@h&ZDcZ3jf|7gQ;8FY2{0i_l3i&QS)v z9x#C~;?lp&nVXA{y>3KQ9m83PN`LE5O64aPh;sMhGtDHcd-Wv2BgDOEuDM174gKGr z^9f)MoND2udw+dhDMDF^DL^5&=Xau0*yl482;M?`!Szz?1Yh6O1IP&$RkqH?dq03d zto7>L9dn+783LvCMTeAIvngCfI*(i zwRrqhJ<;*A5b_C?nGoft3V^!OM8b#i1J?aJ)NC{2p{b*obg5mxWo2XT$S-h2HUr@2 zXu?tWKsPK(p1SF=XaLJFsva}5l=~w~u%gMMB0zyM($Du*cgip-D|AL2nL%bO`OwtS z1T6q7EVprAJ&tG=+p+~~m+?1=B5_9Ec=w5e+c20{EG$cLLwWJ}3uF|5F+T(nfE_lHMIZ3Ai+ zKM=s>jA<{F7O z3{ILEz+@^lb|hOo8-i-6%ffig6&)j~?!=Zt=8*G-*+r7|MOGUdj~6ZdgXmR?bvxsH zEIy&No1p_nzOa74!ZUKOMnY`09=~ z9VM^VYeF@wPCg6Wn}J=GwuZd!C83U(wXR;A6;KwG2zYKW?%NHV(P*zk7t7$673QF% zPPoe*hDw`^H2l~;GJfsgN`*t+J6F?01s{f6_E@JEW<)q8o)u4)r+*(5XOwW?b;ISI z;y+C^C8A^Zm1*anG$K&eJL2~}0xxuy&o<4P@Fq2H99lC%Rpa!#E)uuxMb=yE~0 zeyPvb2L`l9f6kYxcWuP0{%G4OvJAuY{|?jkigtMg?jCuA;p^uByBZ9H`*gT6b3?1J zK_K_=MI#&1x434|6#s=FuJxn(8$9VEE4AXIe#>F7Opdi-mUD){gn;bU(}?*&Iw+;l0-%g!X5J7D2!sCo+pJn(z1$)j+PtlR8nU>3?) z(BYJ{H>61JXyNcm!I$M6%eXf;fpq+!$d3~ozWmJXL~1d&y?q56>|8z>6*3#+P_IC(`k}(?N<& z1D-wm5#3=Vq+DO2wUnwR%B&x{>cfMuu+4oC+;Y)0#KKiA5ioDh+zhz>hF+o5Zd_>a zXM0LJG@|Hu_B^cYc!WkI7Zr*d>((GfYpyO^*pF9r6;|?}_nr${)48O4xeZ1WiQEro z3dj5~qBv@q$ymzFkb`4kS!;ya^&M+uKl6o)DLEr~!W!YVeXZJM2+;?#!O^A&YuERk z-X%_G&ZL^3I36Nb<9a{;DxCA$wb78bBEyC0HmyQLut!INMZvSYq~4^pB$lJ043oZL z#KBFz@Phf{s-V(1&S>#t&_`poYh3NH@gZ-4(p#CnOt}V5A`ZC#KiQygbx>?;0(ODL z6FNQyN>cpS0)vRB<2x&rr1nc%?0K+iZAUv6;sil001hKJ$P$CIjC;yaCU{!Q|f4l~;xCkM^a&`6G{2?7D= zxp8<8Uzms1XMv22A)&+`fe&!wtMU1wWe10hv~634rrnLtO(of!okL~%7gW(gEv#~I z%Vb7^Nf8%5I_jzND)u-fuX|bwvpaT4>9S8>pk_EOjjC6bGXX3slodt4QtB^;y^JO& z7dohWp}ltZ0v++0@Q$6RgR*mEJg0oIxLUI+l311dB2sJ54rF*xxkG7h<|1%oFhr^5 zY1B4XDz(Nza72JUr=S}2$Rw5eG#JKo(|hbfOR z{Ydy|-B=sweTFqNFXTJ(5v@W!6j1ch!ExK@!cjSHZ_<)=#$@bEN6=V}m@TGQT?7}& zO4Ccl5QR~%bJTSF*FjWt&z`QuwojvUI1W2JH6&>r&P#n?G-%P{1m-H=Z09l1h%jXb ze+|&YGKG;FL$3rW+Z_4QI5?6*nudP$M1;FFGMoDZI_B!|Q_Sq!CajA52#6&x37I`y zA}>E|yR^>Tr5EBeQmiz6)tinjOF~Kq|G|!5|Ego(@vA0t3*cNDWm5un#MwzL$c#>7 zR^&UIHHmj-6EMvxH5)XEN5kCI6<}D@k?;wIn5-oHA(`5$@$#rTNaT0O&j^-}AmODzOG2-&0ZTxNWC+(~2paGlPQ%B*9NRU8-pn#&jd}6hNqniB^ zJEe1r9HpC8W#&-|`ydpYDeT?Nb#@NT!cp*xP70&Zty;{e-Z{g*(cm zi~6-F&PJ#nTCZn`oQGlWxNZtR=Fr=|>L3E!cS)%#YQIo?(Pu+(`%URHOvQcKo@4vQ z@*x1m0~$O599Nc?MAYhLTwn_ISdvAjkTmz%S(-G_%jn1rWw!R7d#2Hi0s(}A=B6Xq z1{Hj|sHy*)X*CRq;#x5JIM)v58B<%z7`9NJe3$ZP#=Yhgwj(O9s~Z4~#s z4#N4YfXbKhR-kCl8#VekQsAd12|Zg~furiqFNZw&#%$U4lk|r*+RQH z=-B%en|+)%X=wXORquJEJg#W1R-a#Y$S(dXWBJyo1Kc363Iiny2Kkz)ek3H$;FUr_ z6pw)-v1PrLQErp&XqPjSttS~4SsU}0vyVL7ZE9^{Cjfxr<_=Z`Rp9M+ERD=vBGN~z zj`PZB_cfQqDcjfVZm%_XxB?>YQFx~8txX#SwYin_0KL1j2AAM+v?2wUBJvO6M#&C` z-r-(9`2b|3?1&Xs2Jopm+myi7MoKv1yvAXeWk@mJmV{f@Ert-~Yv^g5mk*KaE?Jb* zOJ|(~*5(c5igi8=tklS+x&XgqKs-<1cKpbAkp@7jP$%gf^HkA&{Jz4hlp&-Q`%M=G zG6!M_mb^I6r~W#o#hC^$m*^!2`jx}xV@ZGZI#7=J{f3}(7LQ(sg^ihJ({eI~UK8Oo z)mWY6E+=MyIo(#ip{&RN7L)!4mqCE#eooa$=S#MYDg#bxClQ~C_$}yVU#5Vu0Q-D6w%c#RKYfHOOK9>efgoAzX$Z$5lWuy@Y&dD!Zo0B} z8m%=ZkHh!@>{zoMqFgN&YDPuQ7-8-LliFV zgr5%B+j$KEH)WQ*&a2 z<3U=|GfLdG7LJx1R;HQyjKKY|l=hfNFG!EOI%oA{9vmJd5|9#vaH8ifqTGozjj)Rm zMSu{%x?d?#MhfJjxc6GUGn1KuuNoF)slF<`^sa@jb~T`WU;>4nMjh#N!zw?1G^5^H zsZTi{rb}`T9jlUI&-rl-P85?oJ5HY21MZ*-AYW$w7XfFbp3X{V{1Jz=W zbF%C7n%z%dw|XAFXWm#69aES~p)9M@uovG(K8+KmQAty~(SYvKE|z71i-|%VR|eYD zGPupr_|o7$yxsRX+k7<%wOx>Q_a2qwgL|elZmD%!2FGgQV|Ci@HAYM0qjkH~CFeyhV$>VYe7nOnxZ|A}N!mvL!UQfi z*hWFqlrvR+Ka;+S&yuU4)~=*>nw2IeR_BGLF-ih{x3%eF47$gSK$#MZP%65@k5p)( zLj#7OkuCIw@$11N!bkgfn_lKT$cqTGQC?m3vJlGEJwiZfs}=3nO(V@RxHh465CrwK zY&!cSVP@?pN7qhDd!G(G$e@?YgH3YDMoD=E%SI_{VO~xL9D`;8$6|A|uMdC$;(ATgr>+ z-h4-XyU)xh^}Y>RQ91~__!%mLSXm8=eCY%UDPk-FezwlXaGtP9XKpC&$U4r^wX~#p zmrC3QfbDyL{mNvD8LgzCJQAxT#)+=KV}A)CEp#EAw=ppm@+X2>mV0v52{wjLciEw9 zu+j?RM?H-Tr$LeS5xqhh z-qP}lxjvJ?u|!+sv6Vb{Xe0Ivo$L#Ej-OTFl}Evz;flTT9n|70IE>pJ27Ka^?T4ib zB@WUD5>pakua#Z9-aJ8L2nb5kssQ%pz+e|^r=hmYZJx!g6JUkbgu zVNnPo_;!D>*lu=1D^kK`6L#%T%|fkYU;*dTI{(^8{?nqfF?r&_p|g1)W&pxX8R?EC z7Vjou7PfRuo1_}8Ty4-DMhxIKM+Y<|d}@8ZS#qz`eX8JFw6A@~Js=kE)ql^1Z&|2% ztTN6C^h=lTKe}b1>~;vY>(tnmw-6;(UnfRYT`+9n*jRd&STp`ss*Ml}#7+(C|8`}^ zfMz(%WJi3OxP9&(`j#9}z81QrKS-K2N%_77aXI=m&Wb;W-}iYpxYm_4H6G>)CdV3g(js>4@@5F(t^uMzNs)x1Wg z&xY)=50&Gd?Vc%0+j2E7JFr^*n;^5OPnIpDa0HMgq`t6(X;LF2W*bcBE5APYf!8kb z=Gr61!DV~t4}f)B#QQi3;Z8WrVlpuc1PQZq8mcyyqi^13A*OAp5Xa!kDE2zt3&q4r zgG_HyZf~3hS9`CHRbt^IpOGsLF^2F&Uz!FW331!R!#zc0bfxVA&&s>2$L;a8{zYG76xEMq%2Le0Lk4K%P3MKE#G7s-bZi=qG-^8 z)r!8u3e0NNtbf5Ecy1BR&hZE=dyOodyx~ayYSbm0OI$QB3HRFr&SQc<09{_qXDz$r zeLDClcPN$gx|T7M_e`a&*V%DJPX+Zvxf;6Y$|E;^@opcF= z%jomWVNF8ovFKr)L^q@Ba1m z2VXSxXX+qABeCgP}BxQ6TH;R%R$7vb6x<#Fil6 z@LsAXOODfm5~UEMRSrS+h!;;Yfo_Tg073S~jvu!lxzuUlicZ*sweZUs|0c+}rNGXSsC(U~*^t^nOq&lyY4mYY&3qSqzRa5GL7kNFR zqFxWTGb%few*bh7V=}iUmGWhLhLzry27l|*eY_k|6w>AL=q4U<ftX_i@^iy94>7pJR+`3UHj_CZ&*7J*AE@u;~u;5OWNd}2UdCP(g!sN4-;~iyt zAR7O!c-0dJL2jfGq#0+Qu6K@z&AoUG^bMZvClyGh<<^0x)qf&J0S7g|t7)K^O+WAy zxGb^qt6TftkLf;r_vC7Asinp>Xz5Zlc}2Y^bY4a9H`?60y#b)^&(K~zjr9;(%Se^I z3X2DVeI%XfQcKrvv_W3Y@5eN3sVdSTeDNqHEDWYP{KKt&*$4^mA15$a+(?-MdELEv zc6K%l2Fr#cfB)Iwd6CV0HL0_GVWIa@1}!=e*O!mV2q)r^Iwagc6oO3Ag^CVD!2$r$ z(f-N{hJ+s=@djvw$dLrS4H9yIPQ=J3Ag^0!8`03WBkS|#f=Te7j9jQFC^CS5*40-} z2uwC{{ZGXv@r8^C!4#>=-Y)90qdVTuGF!|Se#K!u)5xmsRJx&1KGS1#z5 zGWIgjn}M|7nQ={@wO#C4*%j?^NVQI z8W9l*Vy7eaR9z+Ts*CcODNiAx4a)ptq+ z?)Ns?RQ*qQ)%ZLsGuXyd<^wDpghwgbFji@w)1YSad-3i-WOe}d&t9i%?+HB~PiX=( zUdXL%Fy2Cp2n0aWpGVy*u9}*z=<3U0%jGDWm~!v%2$0OSEMI@u6XT=%&;oiRo~W0L zxB}YdewI6lOy9oh>Tw45@T1UZxODxm&M%(DI~HXB;tz| zza`uUwH~$f7aUNIKQzbU5HEc^2i$cm2H>G-q~lsy@gah7SXb2D)AmI;#*Y~1H=YRm zaBW{;_dA?|22!F%h97^1)$6l@xEYszUu4e&0hwnQsK#U(DM~(RHPy^{>=6shUBK%#=qQ1vpq=eRO1KnlXn;@$niZ*OSTQ zjpEMp7=Y=-#_~~f^6n|7(L#!kvm}qdNp-y`R^Fc$_$#+Z%?S)6LIWg`!tytj+syD4 zu-bHv0#FO+E(+8!#BZ=Z?4gQcRPifoH?>4KXT=HQ2`SHYAJX>mX^cu>2I)n*rAS06 z(i5PJWx9|md>Hk|o}1njRqFj+{s3e`+_0sAr3{Iw)j#tho)z1dz0w|vV3Va{;~w%D zRH+dY8NE(pFi?NX7GD7ABBCk09Q_{wbrXv0aGGpL{eJzaT#+;)w;tbicu#1bF z;7KqHm775BGu;NLL zlh43%>i|dbED;SoT;uATVWjj}Gm@epm`$=jCHLmJiVo?lgCx$|BKCt4DfJRS0Uu~= z!$Cc46R`#u{GRA8Jx+*+5MJ*D-OnrCh-lCN2(^>3TGQ^CE{nSD2W5p;&ghBY+avM| z^c(;sKtv4_j$5YU39CTdo(*`2aRXvXds^ob+(znR9$`aap{88p4!D76Ug@R!yDeT# zr}0>|b{jv7G3*v5BUlK`1C4<@>Y0YvBunOi*5w928rK}Lnfc&`jn*b14$KTVkAp%QXozd$BVu6U zVi_<_i!5YoXd5}Y@;1oxyi0nBBSb;fG*J{d9ulQJp~BS(!Y-KafV4CQ-x2ajb46ie z&VHl<;4If4wPJ$YCdQdVs5Gi+kPhGIIW1e*lMhl$rw zw<%yQ%w+5wXcP^-5VQpobKocln7@LYg;TgR7c7TTBUmIDKmZ_63jmzl)+M{d8IESz z6^n_O!1_n*?yy8Me=7P@kh*Pc)bd4Slw1>#vLzTy5Iftq$!(XkJhh>t?~qSH!J-<` zXaj*Ti6@&M8{I&R#OO{}KuyMYml1FVk*Xmy!VcXLPwA`@juAI-@X;v47-Rg)sF{pGhg3l$^6Y^GNY|0JUeoq} zGQ%N&y7~q03BT)gQG1${=YSBz6a7e8YpW^H0ocy%iPz%dnG&P!g<3{5RJ zML|Fybm5Ekw|4~RHIIk5G>SpkRbgVVxHXOr2SIvk$lEVy`#+f)K-&G#HBNJn_^=Bg zy~)+ZTl`UHIuqdsyMOs0=^$_eei+`(hmBVP*Te_3nO^S^uF6>HoT7#aw5$vvguv63 zzVC1oCy}F5!x_YpmrD_Fv9NKDY+xMlV7oK3JQeJYdBizD&d@Sv`yKS4;T0n=0KKg< zmUxRSVODg^Q(9e9L8(@tKx$K*1%&k~dio*Ya0U#l!%jfaQ^*eoI5c;#Ml%BX19|D9 zb`xF<7$(l6$L5E!ur@v!lMiDW$AR@i$lNv-upMqojcTyWv`xhi0cDH!`CqM(aIi(& z)a)#YCic({CP0zlHcjI|fFCA65>WV$dW81NRuu+X?g7~Fd65FAqQWu{Lj+V290isy z+vOe2Is{E1az^UqEdi4YYG!yL^zzUdztK`k-|Y6f<0CpK-`SA(4Z5h zk)_bJENVQ}8}MD&00p_5a}>(q zPSrzR=l4`E{^uC%-V!$yp^XZV2pYQH5Nvhtz*%GcpUj&M6EPPW$_E_SE8uQZOt|%ee9<)3-U#xd-6z!WAha|HE^~>k zw_g{%5_3ce6af8PMVGYwk^D)L2w1q8Fw2GL1ny|P_8>Cn7mv6Yg^P&%#jH6)V%H?q zVL8tPItgS}fNOiTELSt~tfX-d`wav;2Sj}Y1oLb{>(V9Tj zV1_la)0%#wZzDyA02#S5Rv`u<5RVKu=xwJ%!g#sCqECUTCq(fET~|5{D_xU8knNlS z?o&)1%plx;!v5DxKeTZg(*()X3arKEK(&j$kexznn9Kvaw^@L0K~o{mcTu9mCO`&+ z0zjZ8VGVU0&9Z)p!W=;03@SK6GAMi=iJ#RJ5fT%GfOeta!v<7|L1Y^M(O4Rli0}qh znw!Rf6JEdkju2mxHu#RoqbPuf0(grjY2{+b#omDb0A$15PNLx57dC1py0jBJpyrK) zbZ8`}7Q8?QyZ}P0TmkG3g$?~UN+Zys*g*{n1YOD4g!COQQT^QNgp*2?7;HSIJ0LlZ z!J}309_twG3A6)Px$22J1KreL5Ulvo2b)CpK%r#w5KfYw-0m)E1Q>u&1Q;}(>I#8y zdyc5i!AdS^p*@Juphl-i6jIUmA0bP39%zq34`V2f4Nq~kfM3%iLG1j8nH&v}n4)84t|Wr0W}qA>9WcH07Vv1pA2bntSY$4SymIsA%@1?3d&KS9;dn{J6xwY zxXJJ~3Mnb34Qy_j$?c*m3A1_1Eu@>TXvHphV`#>h08keVR#B-tY{nehPe zX9*kyxchK1hI9qM_bIxIY@n84En#@O-YhLca!o&7+hS`wkAFebTa0H?x5a=L?K2z{q!eAG-LeNTw z8UW*^0V@X!FU0Sv%^N5G07x7mKL~@FfRT6?3*xnVLc_x{9Pw&^Zb~IeZ=e?|8wWAX zhf;o~>UyFcC_{l7IiwYZ?C*J(g2wBbj(0=^2owT<4FKRR&i2-t?V^LmQ%4=PRI6}0i)E~>(3y!5@07d0WL5%DY%Sr zhM7AMNBS(LGik^z#riBvqb_?$>LoYby2MBla+X{D5yfC(6D}0UKBDcH=;{!+0d6En zL}=k7P-+Tm7}~X<9rgbJMFMkc5#s0$BHXYx3I@Z_F9U=Hm1PP7AQV{b9qLF!4cfXp z5>Q|po~|dC)0kW3`ma^$y;rLBUcV@FK;Z#EP!t6LKu{C~0YFd`1pz=%6a@i5P&j}8 z!~iuB009F61O);F1_A;D000000RRFKAu$plK~WH4ae<*Qkpm(!u@gX2V!`my;X?6p zlA{0G00;pC0RcY%15&4wo?cY)@~4-TDtUQR%g^M`;vE`#9$T4zIe*NM|K zOZB7@Mu01mfuWO#95Z6D#hedi<3VK84(pcvkF;cLPKZ`%`{VwWI8XEnsRqfS8V4>1Os2=7R;Rx{Nuo%F67mhE6cJ$~m3;sKx-E{1n|M*N zKI%D>zRC{Wwp#TlUceC>fJYOiYLp?P_Zjv;*UAP!AsEo-6Ap|pAej@7-{hz z5+Se&adLOVI78YT@gx`%h7hxjK3LOKNk8CFEHcpUys8i%#}lSwLSsUBM18EgndE4P zTJ_G|LDq#41dGAG;Ryys!VZ?f`dw1lq@X@m4anC(s15NBRFqvT1|Z|4S^|iUU>|-b zOwMg_4(jKzE8MH}AMuD*vZ*s<9Bh5#EWtRdC2i+f~gQ-AB zVKl->w|*x~<{M>u9>DNz{oz3?w|)Z`>7HYYffdvFQ_OH_IHbv%5hk*}APqP`q_*c2 zIM;y#K z0f0#xm@=KGOovZEi;FArU;Tm`< z^$3I_I(G=pgbsE<&sE>G&j~jsn{I(3PNWvkRAQisFaR|u2sP_cuaN2JxWQMn8jX9R zB%w>&x`1`^fQ57$3O~>SNmx`*MW_K8xYVd7rD}2=Jp%cl1aq?c+jJW|xvV`;b%$># zG?@_`(P8~W>>wyQrkSN@p1_Yk|kfJ_MfPtqx=g+L_r*-!#C0anNh(CTc-W0p_5`)QrTi$Xt<(oOT!s{$-xWf_pliXZMtY{}phl5O? z+B}rkHUlU-02joFa>9$8{-;ef|$ku~;54vQ0@oAuxeI~X(V~~(-KG_|ob=F5MROq9K+?X1o zNQSn6h$KxVHya7WITp>)(2qn_I?yx#FZIUO$n86;-9|{znLknF5wJUw3Uz7?z~@zn z#Q-FIka)4C^jMqv+5Z5rSev1ppc5)`j6$*U%SkjAM9$=BdX(X)*0Y42U-fQLg7T;1LsR z_(7uXsqj5rGCNM|q6&h*djw$I0*he$wmL}=QJev~opwP$Bk4a*{2Rpo03kTm zD+6u=(;kC_^%meSknkCXPvoa{qHd>eLLz$b@5K5(27`#YB1rOp0)si=L=#;y8zGX^ z;xCb+OcynvRF@DCFpH1)1Bt{damfO9;4F_$@jzB6qU?U#Ai_ zq22;*$bGaT0)eGQhlIk$1?T?&luVgu9Rj_TeI0_JMov*lB^*W#n-U>cPcbXz{Uo3s zNRR;Lj|XYrKfh)Dx2VxK6dUf3jLy@(Q=Aj8pdA9Hq?6A(wC|LOUB3K5?06G27S!h^ zID@8VY2P*gSbig3*1dUAz_8s_cD~6Ky}_** zUSIhv4*XxlrWoF{^u||d-}7$MzoGrP3OS#39M8WG?aGd7Ir%=Rs0Z^_K*E8y+$bA) UDhA$4fwz;w2HsEP?1`2C+2+{}i~s-t diff --git a/fastlane/metadata/android/en-US/short_description.txt b/fastlane/metadata/android/en-US/short_description.txt index 7a8ca7d1d3..7af858c97b 100644 --- a/fastlane/metadata/android/en-US/short_description.txt +++ b/fastlane/metadata/android/en-US/short_description.txt @@ -1 +1 @@ -Homescreen based on Launcher3 from AOSP +Homescreen based on on Launcher3 from AOSP \ No newline at end of file diff --git a/flags/build.gradle b/flags/build.gradle index 0c48461a59..0dd01c027a 100644 --- a/flags/build.gradle +++ b/flags/build.gradle @@ -12,5 +12,5 @@ android { } } -addFrameworkJar('framework-16.jar') +addFrameworkJar('framework-15.jar') compileOnlyCommonJars() diff --git a/flags/src/com/android/launcher3/CustomFeatureFlags.java b/flags/src/com/android/launcher3/CustomFeatureFlags.java index 2db6820c48..e3b74b8dbc 100644 --- a/flags/src/com/android/launcher3/CustomFeatureFlags.java +++ b/flags/src/com/android/launcher3/CustomFeatureFlags.java @@ -1,12 +1,12 @@ package com.android.launcher3; -// TODO(b/303773055): Remove the annotation after access issue is resolved. import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.function.BiPredicate; import java.util.function.Predicate; + /** @hide */ public class CustomFeatureFlags implements FeatureFlags { @@ -16,807 +16,303 @@ public class CustomFeatureFlags implements FeatureFlags { mGetValueImpl = getValueImpl; } @Override - - public boolean accessibilityScrollOnAllapps() { - return getValue(Flags.FLAG_ACCESSIBILITY_SCROLL_ON_ALLAPPS, - FeatureFlags::accessibilityScrollOnAllapps); - } - - @Override - - public boolean allAppsBlur() { - return getValue(Flags.FLAG_ALL_APPS_BLUR, - FeatureFlags::allAppsBlur); - } - - @Override - - public boolean allAppsSheetForHandheld() { - return getValue(Flags.FLAG_ALL_APPS_SHEET_FOR_HANDHELD, - FeatureFlags::allAppsSheetForHandheld); - } - - @Override - - public boolean coordinateWorkspaceScale() { - return getValue(Flags.FLAG_COORDINATE_WORKSPACE_SCALE, - FeatureFlags::coordinateWorkspaceScale); - } - - @Override - - public boolean enableActiveGestureProtoLog() { - return getValue(Flags.FLAG_ENABLE_ACTIVE_GESTURE_PROTO_LOG, - FeatureFlags::enableActiveGestureProtoLog); - } - - @Override - public boolean enableAddAppWidgetViaConfigActivityV2() { return getValue(Flags.FLAG_ENABLE_ADD_APP_WIDGET_VIA_CONFIG_ACTIVITY_V2, - FeatureFlags::enableAddAppWidgetViaConfigActivityV2); + FeatureFlags::enableAddAppWidgetViaConfigActivityV2); } @Override - public boolean enableAdditionalHomeAnimations() { return getValue(Flags.FLAG_ENABLE_ADDITIONAL_HOME_ANIMATIONS, - FeatureFlags::enableAdditionalHomeAnimations); + FeatureFlags::enableAdditionalHomeAnimations); } @Override - - public boolean enableAllAppsButtonInHotseat() { - return getValue(Flags.FLAG_ENABLE_ALL_APPS_BUTTON_IN_HOTSEAT, - FeatureFlags::enableAllAppsButtonInHotseat); - } - - @Override - - public boolean enableAltTabKqsFlatenning() { - return getValue(Flags.FLAG_ENABLE_ALT_TAB_KQS_FLATENNING, - FeatureFlags::enableAltTabKqsFlatenning); - } - - @Override - - public boolean enableAltTabKqsOnConnectedDisplays() { - return getValue(Flags.FLAG_ENABLE_ALT_TAB_KQS_ON_CONNECTED_DISPLAYS, - FeatureFlags::enableAltTabKqsOnConnectedDisplays); - } - - @Override - public boolean enableCategorizedWidgetSuggestions() { return getValue(Flags.FLAG_ENABLE_CATEGORIZED_WIDGET_SUGGESTIONS, - FeatureFlags::enableCategorizedWidgetSuggestions); + FeatureFlags::enableCategorizedWidgetSuggestions); } @Override - - public boolean enableContainerReturnAnimations() { - return getValue(Flags.FLAG_ENABLE_CONTAINER_RETURN_ANIMATIONS, - FeatureFlags::enableContainerReturnAnimations); - } - - @Override - - public boolean enableContrastTiles() { - return getValue(Flags.FLAG_ENABLE_CONTRAST_TILES, - FeatureFlags::enableContrastTiles); - } - - @Override - public boolean enableCursorHoverStates() { return getValue(Flags.FLAG_ENABLE_CURSOR_HOVER_STATES, - FeatureFlags::enableCursorHoverStates); + FeatureFlags::enableCursorHoverStates); } @Override - - public boolean enableDesktopExplodedView() { - return getValue(Flags.FLAG_ENABLE_DESKTOP_EXPLODED_VIEW, - FeatureFlags::enableDesktopExplodedView); - } - - @Override - - public boolean enableDesktopTaskAlphaAnimation() { - return getValue(Flags.FLAG_ENABLE_DESKTOP_TASK_ALPHA_ANIMATION, - FeatureFlags::enableDesktopTaskAlphaAnimation); - } - - @Override - - public boolean enableDesktopWindowingCarouselDetach() { - return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_CAROUSEL_DETACH, - FeatureFlags::enableDesktopWindowingCarouselDetach); - } - - @Override - - public boolean enableDismissPredictionUndo() { - return getValue(Flags.FLAG_ENABLE_DISMISS_PREDICTION_UNDO, - FeatureFlags::enableDismissPredictionUndo); - } - - @Override - public boolean enableExpandingPauseWorkButton() { return getValue(Flags.FLAG_ENABLE_EXPANDING_PAUSE_WORK_BUTTON, - FeatureFlags::enableExpandingPauseWorkButton); + FeatureFlags::enableExpandingPauseWorkButton); } @Override - - public boolean enableExpressiveDismissTaskMotion() { - return getValue(Flags.FLAG_ENABLE_EXPRESSIVE_DISMISS_TASK_MOTION, - FeatureFlags::enableExpressiveDismissTaskMotion); - } - - @Override - public boolean enableFallbackOverviewInWindow() { return getValue(Flags.FLAG_ENABLE_FALLBACK_OVERVIEW_IN_WINDOW, - FeatureFlags::enableFallbackOverviewInWindow); + FeatureFlags::enableFallbackOverviewInWindow); } @Override - public boolean enableFirstScreenBroadcastArchivingExtras() { return getValue(Flags.FLAG_ENABLE_FIRST_SCREEN_BROADCAST_ARCHIVING_EXTRAS, - FeatureFlags::enableFirstScreenBroadcastArchivingExtras); + FeatureFlags::enableFirstScreenBroadcastArchivingExtras); } @Override - public boolean enableFocusOutline() { return getValue(Flags.FLAG_ENABLE_FOCUS_OUTLINE, - FeatureFlags::enableFocusOutline); + FeatureFlags::enableFocusOutline); } @Override - public boolean enableGeneratedPreviews() { return getValue(Flags.FLAG_ENABLE_GENERATED_PREVIEWS, - FeatureFlags::enableGeneratedPreviews); + FeatureFlags::enableGeneratedPreviews); } @Override - - public boolean enableGestureNavHorizontalTouchSlop() { - return getValue(Flags.FLAG_ENABLE_GESTURE_NAV_HORIZONTAL_TOUCH_SLOP, - FeatureFlags::enableGestureNavHorizontalTouchSlop); - } - - @Override - - public boolean enableGestureNavOnConnectedDisplays() { - return getValue(Flags.FLAG_ENABLE_GESTURE_NAV_ON_CONNECTED_DISPLAYS, - FeatureFlags::enableGestureNavOnConnectedDisplays); - } - - @Override - public boolean enableGridMigrationFix() { return getValue(Flags.FLAG_ENABLE_GRID_MIGRATION_FIX, - FeatureFlags::enableGridMigrationFix); + FeatureFlags::enableGridMigrationFix); } @Override - public boolean enableGridOnlyOverview() { return getValue(Flags.FLAG_ENABLE_GRID_ONLY_OVERVIEW, - FeatureFlags::enableGridOnlyOverview); + FeatureFlags::enableGridOnlyOverview); } @Override - - public boolean enableGrowthNudge() { - return getValue(Flags.FLAG_ENABLE_GROWTH_NUDGE, - FeatureFlags::enableGrowthNudge); - } - - @Override - public boolean enableHandleDelayedGestureCallbacks() { return getValue(Flags.FLAG_ENABLE_HANDLE_DELAYED_GESTURE_CALLBACKS, - FeatureFlags::enableHandleDelayedGestureCallbacks); + FeatureFlags::enableHandleDelayedGestureCallbacks); } @Override - public boolean enableHomeTransitionListener() { return getValue(Flags.FLAG_ENABLE_HOME_TRANSITION_LISTENER, - FeatureFlags::enableHomeTransitionListener); + FeatureFlags::enableHomeTransitionListener); } @Override - - public boolean enableHoverOfChildElementsInTaskview() { - return getValue(Flags.FLAG_ENABLE_HOVER_OF_CHILD_ELEMENTS_IN_TASKVIEW, - FeatureFlags::enableHoverOfChildElementsInTaskview); - } - - @Override - - public boolean enableLargeDesktopWindowingTile() { - return getValue(Flags.FLAG_ENABLE_LARGE_DESKTOP_WINDOWING_TILE, - FeatureFlags::enableLargeDesktopWindowingTile); - } - - @Override - public boolean enableLauncherBrMetricsFixed() { return getValue(Flags.FLAG_ENABLE_LAUNCHER_BR_METRICS_FIXED, - FeatureFlags::enableLauncherBrMetricsFixed); + FeatureFlags::enableLauncherBrMetricsFixed); } @Override - - public boolean enableLauncherIconShapes() { - return getValue(Flags.FLAG_ENABLE_LAUNCHER_ICON_SHAPES, - FeatureFlags::enableLauncherIconShapes); - } - - @Override - - public boolean enableLauncherOverviewInWindow() { - return getValue(Flags.FLAG_ENABLE_LAUNCHER_OVERVIEW_IN_WINDOW, - FeatureFlags::enableLauncherOverviewInWindow); - } - - @Override - - public boolean enableLauncherVisualRefresh() { - return getValue(Flags.FLAG_ENABLE_LAUNCHER_VISUAL_REFRESH, - FeatureFlags::enableLauncherVisualRefresh); - } - - @Override - - public boolean enableMouseInteractionChanges() { - return getValue(Flags.FLAG_ENABLE_MOUSE_INTERACTION_CHANGES, - FeatureFlags::enableMouseInteractionChanges); - } - - @Override - - public boolean enableMultiInstanceMenuTaskbar() { - return getValue(Flags.FLAG_ENABLE_MULTI_INSTANCE_MENU_TASKBAR, - FeatureFlags::enableMultiInstanceMenuTaskbar); - } - - @Override - public boolean enableNarrowGridRestore() { return getValue(Flags.FLAG_ENABLE_NARROW_GRID_RESTORE, - FeatureFlags::enableNarrowGridRestore); + FeatureFlags::enableNarrowGridRestore); } @Override - - public boolean enableOverviewBackgroundWallpaperBlur() { - return getValue(Flags.FLAG_ENABLE_OVERVIEW_BACKGROUND_WALLPAPER_BLUR, - FeatureFlags::enableOverviewBackgroundWallpaperBlur); - } - - @Override - - public boolean enableOverviewCommandHelperTimeout() { - return getValue(Flags.FLAG_ENABLE_OVERVIEW_COMMAND_HELPER_TIMEOUT, - FeatureFlags::enableOverviewCommandHelperTimeout); - } - - @Override - - public boolean enableOverviewDesktopTileWallpaperBackground() { - return getValue(Flags.FLAG_ENABLE_OVERVIEW_DESKTOP_TILE_WALLPAPER_BACKGROUND, - FeatureFlags::enableOverviewDesktopTileWallpaperBackground); - } - - @Override - public boolean enableOverviewIconMenu() { return getValue(Flags.FLAG_ENABLE_OVERVIEW_ICON_MENU, - FeatureFlags::enableOverviewIconMenu); + FeatureFlags::enableOverviewIconMenu); } @Override - - public boolean enableOverviewOnConnectedDisplays() { - return getValue(Flags.FLAG_ENABLE_OVERVIEW_ON_CONNECTED_DISPLAYS, - FeatureFlags::enableOverviewOnConnectedDisplays); - } - - @Override - - public boolean enablePinningAppWithContextMenu() { - return getValue(Flags.FLAG_ENABLE_PINNING_APP_WITH_CONTEXT_MENU, - FeatureFlags::enablePinningAppWithContextMenu); - } - - @Override - public boolean enablePredictiveBackGesture() { return getValue(Flags.FLAG_ENABLE_PREDICTIVE_BACK_GESTURE, - FeatureFlags::enablePredictiveBackGesture); + FeatureFlags::enablePredictiveBackGesture); } @Override - public boolean enablePrivateSpace() { return getValue(Flags.FLAG_ENABLE_PRIVATE_SPACE, - FeatureFlags::enablePrivateSpace); + FeatureFlags::enablePrivateSpace); } @Override - public boolean enablePrivateSpaceInstallShortcut() { return getValue(Flags.FLAG_ENABLE_PRIVATE_SPACE_INSTALL_SHORTCUT, - FeatureFlags::enablePrivateSpaceInstallShortcut); + FeatureFlags::enablePrivateSpaceInstallShortcut); } @Override - public boolean enableRebootUnlockAnimation() { return getValue(Flags.FLAG_ENABLE_REBOOT_UNLOCK_ANIMATION, - FeatureFlags::enableRebootUnlockAnimation); + FeatureFlags::enableRebootUnlockAnimation); } @Override - public boolean enableRecentsInTaskbar() { return getValue(Flags.FLAG_ENABLE_RECENTS_IN_TASKBAR, - FeatureFlags::enableRecentsInTaskbar); + FeatureFlags::enableRecentsInTaskbar); } @Override - - public boolean enableRecentsWindowProtoLog() { - return getValue(Flags.FLAG_ENABLE_RECENTS_WINDOW_PROTO_LOG, - FeatureFlags::enableRecentsWindowProtoLog); - } - - @Override - public boolean enableRefactorTaskThumbnail() { return getValue(Flags.FLAG_ENABLE_REFACTOR_TASK_THUMBNAIL, - FeatureFlags::enableRefactorTaskThumbnail); + FeatureFlags::enableRefactorTaskThumbnail); } @Override - public boolean enableResponsiveWorkspace() { return getValue(Flags.FLAG_ENABLE_RESPONSIVE_WORKSPACE, - FeatureFlags::enableResponsiveWorkspace); + FeatureFlags::enableResponsiveWorkspace); } @Override - - public boolean enableScalabilityForDesktopExperience() { - return getValue(Flags.FLAG_ENABLE_SCALABILITY_FOR_DESKTOP_EXPERIENCE, - FeatureFlags::enableScalabilityForDesktopExperience); - } - - @Override - public boolean enableScalingRevealHomeAnimation() { return getValue(Flags.FLAG_ENABLE_SCALING_REVEAL_HOME_ANIMATION, - FeatureFlags::enableScalingRevealHomeAnimation); + FeatureFlags::enableScalingRevealHomeAnimation); } @Override - - public boolean enableSeparateExternalDisplayTasks() { - return getValue(Flags.FLAG_ENABLE_SEPARATE_EXTERNAL_DISPLAY_TASKS, - FeatureFlags::enableSeparateExternalDisplayTasks); - } - - @Override - public boolean enableShortcutDontSuggestApp() { return getValue(Flags.FLAG_ENABLE_SHORTCUT_DONT_SUGGEST_APP, - FeatureFlags::enableShortcutDontSuggestApp); + FeatureFlags::enableShortcutDontSuggestApp); } @Override - - public boolean enableShowEnabledShortcutsInAccessibilityMenu() { - return getValue(Flags.FLAG_ENABLE_SHOW_ENABLED_SHORTCUTS_IN_ACCESSIBILITY_MENU, - FeatureFlags::enableShowEnabledShortcutsInAccessibilityMenu); - } - - @Override - public boolean enableSmartspaceAsAWidget() { return getValue(Flags.FLAG_ENABLE_SMARTSPACE_AS_A_WIDGET, - FeatureFlags::enableSmartspaceAsAWidget); + FeatureFlags::enableSmartspaceAsAWidget); } @Override - public boolean enableSmartspaceRemovalToggle() { return getValue(Flags.FLAG_ENABLE_SMARTSPACE_REMOVAL_TOGGLE, - FeatureFlags::enableSmartspaceRemovalToggle); + FeatureFlags::enableSmartspaceRemovalToggle); } @Override - - public boolean enableStateManagerProtoLog() { - return getValue(Flags.FLAG_ENABLE_STATE_MANAGER_PROTO_LOG, - FeatureFlags::enableStateManagerProtoLog); - } - - @Override - - public boolean enableStrictMode() { - return getValue(Flags.FLAG_ENABLE_STRICT_MODE, - FeatureFlags::enableStrictMode); - } - - @Override - public boolean enableSupportForArchiving() { return getValue(Flags.FLAG_ENABLE_SUPPORT_FOR_ARCHIVING, - FeatureFlags::enableSupportForArchiving); + FeatureFlags::enableSupportForArchiving); } @Override - public boolean enableTabletTwoPanePickerV2() { return getValue(Flags.FLAG_ENABLE_TABLET_TWO_PANE_PICKER_V2, - FeatureFlags::enableTabletTwoPanePickerV2); + FeatureFlags::enableTabletTwoPanePickerV2); } @Override - - public boolean enableTaskbarBehindShade() { - return getValue(Flags.FLAG_ENABLE_TASKBAR_BEHIND_SHADE, - FeatureFlags::enableTaskbarBehindShade); - } - - @Override - public boolean enableTaskbarCustomization() { return getValue(Flags.FLAG_ENABLE_TASKBAR_CUSTOMIZATION, - FeatureFlags::enableTaskbarCustomization); + FeatureFlags::enableTaskbarCustomization); } @Override - - public boolean enableTaskbarForDirectBoot() { - return getValue(Flags.FLAG_ENABLE_TASKBAR_FOR_DIRECT_BOOT, - FeatureFlags::enableTaskbarForDirectBoot); - } - - @Override - public boolean enableTaskbarNoRecreate() { return getValue(Flags.FLAG_ENABLE_TASKBAR_NO_RECREATE, - FeatureFlags::enableTaskbarNoRecreate); + FeatureFlags::enableTaskbarNoRecreate); } @Override - public boolean enableTaskbarPinning() { return getValue(Flags.FLAG_ENABLE_TASKBAR_PINNING, - FeatureFlags::enableTaskbarPinning); + FeatureFlags::enableTaskbarPinning); } @Override - - public boolean enableTieredWidgetsByDefaultInPicker() { - return getValue(Flags.FLAG_ENABLE_TIERED_WIDGETS_BY_DEFAULT_IN_PICKER, - FeatureFlags::enableTieredWidgetsByDefaultInPicker); - } - - @Override - public boolean enableTwoPaneLauncherSettings() { return getValue(Flags.FLAG_ENABLE_TWO_PANE_LAUNCHER_SETTINGS, - FeatureFlags::enableTwoPaneLauncherSettings); + FeatureFlags::enableTwoPaneLauncherSettings); } @Override - public boolean enableTwolineAllapps() { return getValue(Flags.FLAG_ENABLE_TWOLINE_ALLAPPS, - FeatureFlags::enableTwolineAllapps); + FeatureFlags::enableTwolineAllapps); } @Override - public boolean enableTwolineToggle() { return getValue(Flags.FLAG_ENABLE_TWOLINE_TOGGLE, - FeatureFlags::enableTwolineToggle); + FeatureFlags::enableTwolineToggle); } @Override - public boolean enableUnfoldStateAnimation() { return getValue(Flags.FLAG_ENABLE_UNFOLD_STATE_ANIMATION, - FeatureFlags::enableUnfoldStateAnimation); + FeatureFlags::enableUnfoldStateAnimation); } @Override - public boolean enableUnfoldedTwoPanePicker() { return getValue(Flags.FLAG_ENABLE_UNFOLDED_TWO_PANE_PICKER, - FeatureFlags::enableUnfoldedTwoPanePicker); + FeatureFlags::enableUnfoldedTwoPanePicker); } @Override - - public boolean enableUseTopVisibleActivityForExcludeFromRecentTask() { - return getValue(Flags.FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK, - FeatureFlags::enableUseTopVisibleActivityForExcludeFromRecentTask); - } - - @Override - public boolean enableWidgetTapToAdd() { return getValue(Flags.FLAG_ENABLE_WIDGET_TAP_TO_ADD, - FeatureFlags::enableWidgetTapToAdd); + FeatureFlags::enableWidgetTapToAdd); } @Override - public boolean enableWorkspaceInflation() { return getValue(Flags.FLAG_ENABLE_WORKSPACE_INFLATION, - FeatureFlags::enableWorkspaceInflation); + FeatureFlags::enableWorkspaceInflation); } @Override - public boolean enabledFoldersInAllApps() { return getValue(Flags.FLAG_ENABLED_FOLDERS_IN_ALL_APPS, - FeatureFlags::enabledFoldersInAllApps); + FeatureFlags::enabledFoldersInAllApps); } @Override - - public boolean expressiveThemeInTaskbarAndNavigation() { - return getValue(Flags.FLAG_EXPRESSIVE_THEME_IN_TASKBAR_AND_NAVIGATION, - FeatureFlags::expressiveThemeInTaskbarAndNavigation); - } - - @Override - - public boolean extendibleThemeManager() { - return getValue(Flags.FLAG_EXTENDIBLE_THEME_MANAGER, - FeatureFlags::extendibleThemeManager); - } - - @Override - public boolean floatingSearchBar() { return getValue(Flags.FLAG_FLOATING_SEARCH_BAR, - FeatureFlags::floatingSearchBar); + FeatureFlags::floatingSearchBar); } @Override - public boolean forceMonochromeAppIcons() { return getValue(Flags.FLAG_FORCE_MONOCHROME_APP_ICONS, - FeatureFlags::forceMonochromeAppIcons); + FeatureFlags::forceMonochromeAppIcons); } @Override - - public boolean gridMigrationRefactor() { - return getValue(Flags.FLAG_GRID_MIGRATION_REFACTOR, - FeatureFlags::gridMigrationRefactor); - } - - @Override - - public boolean gsfRes() { - return getValue(Flags.FLAG_GSF_RES, - FeatureFlags::gsfRes); - } - - @Override - - public boolean ignoreThreeFingerTrackpadForNavHandleLongPress() { - return getValue(Flags.FLAG_IGNORE_THREE_FINGER_TRACKPAD_FOR_NAV_HANDLE_LONG_PRESS, - FeatureFlags::ignoreThreeFingerTrackpadForNavHandleLongPress); - } - - @Override - - public boolean letterFastScroller() { - return getValue(Flags.FLAG_LETTER_FAST_SCROLLER, - FeatureFlags::letterFastScroller); - } - - @Override - - public boolean msdlFeedback() { - return getValue(Flags.FLAG_MSDL_FEEDBACK, - FeatureFlags::msdlFeedback); - } - - @Override - - public boolean multilineSearchBar() { - return getValue(Flags.FLAG_MULTILINE_SEARCH_BAR, - FeatureFlags::multilineSearchBar); - } - - @Override - - public boolean navigateToChildPreference() { - return getValue(Flags.FLAG_NAVIGATE_TO_CHILD_PREFERENCE, - FeatureFlags::navigateToChildPreference); - } - - @Override - - public boolean oneGridMountedMode() { - return getValue(Flags.FLAG_ONE_GRID_MOUNTED_MODE, - FeatureFlags::oneGridMountedMode); - } - - @Override - - public boolean oneGridRotationHandling() { - return getValue(Flags.FLAG_ONE_GRID_ROTATION_HANDLING, - FeatureFlags::oneGridRotationHandling); - } - - @Override - - public boolean oneGridSpecs() { - return getValue(Flags.FLAG_ONE_GRID_SPECS, - FeatureFlags::oneGridSpecs); - } - - @Override - - public boolean predictiveBackToHomeBlur() { - return getValue(Flags.FLAG_PREDICTIVE_BACK_TO_HOME_BLUR, - FeatureFlags::predictiveBackToHomeBlur); - } - - @Override - - public boolean predictiveBackToHomePolish() { - return getValue(Flags.FLAG_PREDICTIVE_BACK_TO_HOME_POLISH, - FeatureFlags::predictiveBackToHomePolish); - } - - @Override - public boolean privateSpaceAddFloatingMaskView() { return getValue(Flags.FLAG_PRIVATE_SPACE_ADD_FLOATING_MASK_VIEW, - FeatureFlags::privateSpaceAddFloatingMaskView); + FeatureFlags::privateSpaceAddFloatingMaskView); } @Override - public boolean privateSpaceAnimation() { return getValue(Flags.FLAG_PRIVATE_SPACE_ANIMATION, - FeatureFlags::privateSpaceAnimation); + FeatureFlags::privateSpaceAnimation); } @Override - public boolean privateSpaceAppInstallerButton() { return getValue(Flags.FLAG_PRIVATE_SPACE_APP_INSTALLER_BUTTON, - FeatureFlags::privateSpaceAppInstallerButton); + FeatureFlags::privateSpaceAppInstallerButton); } @Override - public boolean privateSpaceRestrictAccessibilityDrag() { return getValue(Flags.FLAG_PRIVATE_SPACE_RESTRICT_ACCESSIBILITY_DRAG, - FeatureFlags::privateSpaceRestrictAccessibilityDrag); + FeatureFlags::privateSpaceRestrictAccessibilityDrag); } @Override - public boolean privateSpaceRestrictItemDrag() { return getValue(Flags.FLAG_PRIVATE_SPACE_RESTRICT_ITEM_DRAG, - FeatureFlags::privateSpaceRestrictItemDrag); + FeatureFlags::privateSpaceRestrictItemDrag); } @Override - public boolean privateSpaceSysAppsSeparation() { return getValue(Flags.FLAG_PRIVATE_SPACE_SYS_APPS_SEPARATION, - FeatureFlags::privateSpaceSysAppsSeparation); + FeatureFlags::privateSpaceSysAppsSeparation); } @Override - - public boolean removeAppsRefreshOnRightClick() { - return getValue(Flags.FLAG_REMOVE_APPS_REFRESH_ON_RIGHT_CLICK, - FeatureFlags::removeAppsRefreshOnRightClick); - } - - @Override - - public boolean removeExcludeFromScreenMagnificationFlagUsage() { - return getValue(Flags.FLAG_REMOVE_EXCLUDE_FROM_SCREEN_MAGNIFICATION_FLAG_USAGE, - FeatureFlags::removeExcludeFromScreenMagnificationFlagUsage); - } - - @Override - - public boolean restoreArchivedAppIconsFromDb() { - return getValue(Flags.FLAG_RESTORE_ARCHIVED_APP_ICONS_FROM_DB, - FeatureFlags::restoreArchivedAppIconsFromDb); - } - - @Override - - public boolean restoreArchivedShortcuts() { - return getValue(Flags.FLAG_RESTORE_ARCHIVED_SHORTCUTS, - FeatureFlags::restoreArchivedShortcuts); - } - - @Override - - public boolean showTaskbarPinningPopupFromAnywhere() { - return getValue(Flags.FLAG_SHOW_TASKBAR_PINNING_POPUP_FROM_ANYWHERE, - FeatureFlags::showTaskbarPinningPopupFromAnywhere); - } - - @Override - - public boolean syncAppLaunchWithTaskbarStash() { - return getValue(Flags.FLAG_SYNC_APP_LAUNCH_WITH_TASKBAR_STASH, - FeatureFlags::syncAppLaunchWithTaskbarStash); - } - - @Override - - public boolean taskbarOverflow() { - return getValue(Flags.FLAG_TASKBAR_OVERFLOW, - FeatureFlags::taskbarOverflow); - } - - @Override - - public boolean taskbarQuietModeChangeSupport() { - return getValue(Flags.FLAG_TASKBAR_QUIET_MODE_CHANGE_SUPPORT, - FeatureFlags::taskbarQuietModeChangeSupport); - } - - @Override - public boolean useActivityOverlay() { return getValue(Flags.FLAG_USE_ACTIVITY_OVERLAY, - FeatureFlags::useActivityOverlay); - } - - @Override - - public boolean useNewIconForArchivedApps() { - return getValue(Flags.FLAG_USE_NEW_ICON_FOR_ARCHIVED_APPS, - FeatureFlags::useNewIconForArchivedApps); - } - - @Override - - public boolean useSystemRadiusForAppWidgets() { - return getValue(Flags.FLAG_USE_SYSTEM_RADIUS_FOR_APP_WIDGETS, - FeatureFlags::useSystemRadiusForAppWidgets); - } - - @Override - - public boolean workSchedulerInWorkProfile() { - return getValue(Flags.FLAG_WORK_SCHEDULER_IN_WORK_PROFILE, - FeatureFlags::workSchedulerInWorkProfile); + FeatureFlags::useActivityOverlay); } public boolean isFlagReadOnlyOptimized(String flagName) { if (mReadOnlyFlagsSet.contains(flagName) && - isOptimizationEnabled()) { - return true; + isOptimizationEnabled()) { + return true; } return false; } @@ -831,240 +327,65 @@ public class CustomFeatureFlags implements FeatureFlags { public List getFlagNames() { return Arrays.asList( - Flags.FLAG_ACCESSIBILITY_SCROLL_ON_ALLAPPS, - Flags.FLAG_ALL_APPS_BLUR, - Flags.FLAG_ALL_APPS_SHEET_FOR_HANDHELD, - Flags.FLAG_COORDINATE_WORKSPACE_SCALE, - Flags.FLAG_ENABLE_ACTIVE_GESTURE_PROTO_LOG, - Flags.FLAG_ENABLE_ADD_APP_WIDGET_VIA_CONFIG_ACTIVITY_V2, - Flags.FLAG_ENABLE_ADDITIONAL_HOME_ANIMATIONS, - Flags.FLAG_ENABLE_ALL_APPS_BUTTON_IN_HOTSEAT, - Flags.FLAG_ENABLE_ALT_TAB_KQS_FLATENNING, - Flags.FLAG_ENABLE_ALT_TAB_KQS_ON_CONNECTED_DISPLAYS, - Flags.FLAG_ENABLE_CATEGORIZED_WIDGET_SUGGESTIONS, - Flags.FLAG_ENABLE_CONTAINER_RETURN_ANIMATIONS, - Flags.FLAG_ENABLE_CONTRAST_TILES, - Flags.FLAG_ENABLE_CURSOR_HOVER_STATES, - Flags.FLAG_ENABLE_DESKTOP_EXPLODED_VIEW, - Flags.FLAG_ENABLE_DESKTOP_TASK_ALPHA_ANIMATION, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_CAROUSEL_DETACH, - Flags.FLAG_ENABLE_DISMISS_PREDICTION_UNDO, - Flags.FLAG_ENABLE_EXPANDING_PAUSE_WORK_BUTTON, - Flags.FLAG_ENABLE_EXPRESSIVE_DISMISS_TASK_MOTION, - Flags.FLAG_ENABLE_FALLBACK_OVERVIEW_IN_WINDOW, - Flags.FLAG_ENABLE_FIRST_SCREEN_BROADCAST_ARCHIVING_EXTRAS, - Flags.FLAG_ENABLE_FOCUS_OUTLINE, - Flags.FLAG_ENABLE_GENERATED_PREVIEWS, - Flags.FLAG_ENABLE_GESTURE_NAV_HORIZONTAL_TOUCH_SLOP, - Flags.FLAG_ENABLE_GESTURE_NAV_ON_CONNECTED_DISPLAYS, - Flags.FLAG_ENABLE_GRID_MIGRATION_FIX, - Flags.FLAG_ENABLE_GRID_ONLY_OVERVIEW, - Flags.FLAG_ENABLE_GROWTH_NUDGE, - Flags.FLAG_ENABLE_HANDLE_DELAYED_GESTURE_CALLBACKS, - Flags.FLAG_ENABLE_HOME_TRANSITION_LISTENER, - Flags.FLAG_ENABLE_HOVER_OF_CHILD_ELEMENTS_IN_TASKVIEW, - Flags.FLAG_ENABLE_LARGE_DESKTOP_WINDOWING_TILE, - Flags.FLAG_ENABLE_LAUNCHER_BR_METRICS_FIXED, - Flags.FLAG_ENABLE_LAUNCHER_ICON_SHAPES, - Flags.FLAG_ENABLE_LAUNCHER_OVERVIEW_IN_WINDOW, - Flags.FLAG_ENABLE_LAUNCHER_VISUAL_REFRESH, - Flags.FLAG_ENABLE_MOUSE_INTERACTION_CHANGES, - Flags.FLAG_ENABLE_MULTI_INSTANCE_MENU_TASKBAR, - Flags.FLAG_ENABLE_NARROW_GRID_RESTORE, - Flags.FLAG_ENABLE_OVERVIEW_BACKGROUND_WALLPAPER_BLUR, - Flags.FLAG_ENABLE_OVERVIEW_COMMAND_HELPER_TIMEOUT, - Flags.FLAG_ENABLE_OVERVIEW_DESKTOP_TILE_WALLPAPER_BACKGROUND, - Flags.FLAG_ENABLE_OVERVIEW_ICON_MENU, - Flags.FLAG_ENABLE_OVERVIEW_ON_CONNECTED_DISPLAYS, - Flags.FLAG_ENABLE_PINNING_APP_WITH_CONTEXT_MENU, - Flags.FLAG_ENABLE_PREDICTIVE_BACK_GESTURE, - Flags.FLAG_ENABLE_PRIVATE_SPACE, - Flags.FLAG_ENABLE_PRIVATE_SPACE_INSTALL_SHORTCUT, - Flags.FLAG_ENABLE_REBOOT_UNLOCK_ANIMATION, - Flags.FLAG_ENABLE_RECENTS_IN_TASKBAR, - Flags.FLAG_ENABLE_RECENTS_WINDOW_PROTO_LOG, - Flags.FLAG_ENABLE_REFACTOR_TASK_THUMBNAIL, - Flags.FLAG_ENABLE_RESPONSIVE_WORKSPACE, - Flags.FLAG_ENABLE_SCALABILITY_FOR_DESKTOP_EXPERIENCE, - Flags.FLAG_ENABLE_SCALING_REVEAL_HOME_ANIMATION, - Flags.FLAG_ENABLE_SEPARATE_EXTERNAL_DISPLAY_TASKS, - Flags.FLAG_ENABLE_SHORTCUT_DONT_SUGGEST_APP, - Flags.FLAG_ENABLE_SHOW_ENABLED_SHORTCUTS_IN_ACCESSIBILITY_MENU, - Flags.FLAG_ENABLE_SMARTSPACE_AS_A_WIDGET, - Flags.FLAG_ENABLE_SMARTSPACE_REMOVAL_TOGGLE, - Flags.FLAG_ENABLE_STATE_MANAGER_PROTO_LOG, - Flags.FLAG_ENABLE_STRICT_MODE, - Flags.FLAG_ENABLE_SUPPORT_FOR_ARCHIVING, - Flags.FLAG_ENABLE_TABLET_TWO_PANE_PICKER_V2, - Flags.FLAG_ENABLE_TASKBAR_BEHIND_SHADE, - Flags.FLAG_ENABLE_TASKBAR_CUSTOMIZATION, - Flags.FLAG_ENABLE_TASKBAR_FOR_DIRECT_BOOT, - Flags.FLAG_ENABLE_TASKBAR_NO_RECREATE, - Flags.FLAG_ENABLE_TASKBAR_PINNING, - Flags.FLAG_ENABLE_TIERED_WIDGETS_BY_DEFAULT_IN_PICKER, - Flags.FLAG_ENABLE_TWO_PANE_LAUNCHER_SETTINGS, - Flags.FLAG_ENABLE_TWOLINE_ALLAPPS, - Flags.FLAG_ENABLE_TWOLINE_TOGGLE, - Flags.FLAG_ENABLE_UNFOLD_STATE_ANIMATION, - Flags.FLAG_ENABLE_UNFOLDED_TWO_PANE_PICKER, - Flags.FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK, - Flags.FLAG_ENABLE_WIDGET_TAP_TO_ADD, - Flags.FLAG_ENABLE_WORKSPACE_INFLATION, - Flags.FLAG_ENABLED_FOLDERS_IN_ALL_APPS, - Flags.FLAG_EXPRESSIVE_THEME_IN_TASKBAR_AND_NAVIGATION, - Flags.FLAG_EXTENDIBLE_THEME_MANAGER, - Flags.FLAG_FLOATING_SEARCH_BAR, - Flags.FLAG_FORCE_MONOCHROME_APP_ICONS, - Flags.FLAG_GRID_MIGRATION_REFACTOR, - Flags.FLAG_GSF_RES, - Flags.FLAG_IGNORE_THREE_FINGER_TRACKPAD_FOR_NAV_HANDLE_LONG_PRESS, - Flags.FLAG_LETTER_FAST_SCROLLER, - Flags.FLAG_MSDL_FEEDBACK, - Flags.FLAG_MULTILINE_SEARCH_BAR, - Flags.FLAG_NAVIGATE_TO_CHILD_PREFERENCE, - Flags.FLAG_ONE_GRID_MOUNTED_MODE, - Flags.FLAG_ONE_GRID_ROTATION_HANDLING, - Flags.FLAG_ONE_GRID_SPECS, - Flags.FLAG_PREDICTIVE_BACK_TO_HOME_BLUR, - Flags.FLAG_PREDICTIVE_BACK_TO_HOME_POLISH, - Flags.FLAG_PRIVATE_SPACE_ADD_FLOATING_MASK_VIEW, - Flags.FLAG_PRIVATE_SPACE_ANIMATION, - Flags.FLAG_PRIVATE_SPACE_APP_INSTALLER_BUTTON, - Flags.FLAG_PRIVATE_SPACE_RESTRICT_ACCESSIBILITY_DRAG, - Flags.FLAG_PRIVATE_SPACE_RESTRICT_ITEM_DRAG, - Flags.FLAG_PRIVATE_SPACE_SYS_APPS_SEPARATION, - Flags.FLAG_REMOVE_APPS_REFRESH_ON_RIGHT_CLICK, - Flags.FLAG_REMOVE_EXCLUDE_FROM_SCREEN_MAGNIFICATION_FLAG_USAGE, - Flags.FLAG_RESTORE_ARCHIVED_APP_ICONS_FROM_DB, - Flags.FLAG_RESTORE_ARCHIVED_SHORTCUTS, - Flags.FLAG_SHOW_TASKBAR_PINNING_POPUP_FROM_ANYWHERE, - Flags.FLAG_SYNC_APP_LAUNCH_WITH_TASKBAR_STASH, - Flags.FLAG_TASKBAR_OVERFLOW, - Flags.FLAG_TASKBAR_QUIET_MODE_CHANGE_SUPPORT, - Flags.FLAG_USE_ACTIVITY_OVERLAY, - Flags.FLAG_USE_NEW_ICON_FOR_ARCHIVED_APPS, - Flags.FLAG_USE_SYSTEM_RADIUS_FOR_APP_WIDGETS, - Flags.FLAG_WORK_SCHEDULER_IN_WORK_PROFILE + Flags.FLAG_ENABLE_ADD_APP_WIDGET_VIA_CONFIG_ACTIVITY_V2, + Flags.FLAG_ENABLE_ADDITIONAL_HOME_ANIMATIONS, + Flags.FLAG_ENABLE_CATEGORIZED_WIDGET_SUGGESTIONS, + Flags.FLAG_ENABLE_CURSOR_HOVER_STATES, + Flags.FLAG_ENABLE_EXPANDING_PAUSE_WORK_BUTTON, + Flags.FLAG_ENABLE_FALLBACK_OVERVIEW_IN_WINDOW, + Flags.FLAG_ENABLE_FIRST_SCREEN_BROADCAST_ARCHIVING_EXTRAS, + Flags.FLAG_ENABLE_FOCUS_OUTLINE, + Flags.FLAG_ENABLE_GENERATED_PREVIEWS, + Flags.FLAG_ENABLE_GRID_MIGRATION_FIX, + Flags.FLAG_ENABLE_GRID_ONLY_OVERVIEW, + Flags.FLAG_ENABLE_HANDLE_DELAYED_GESTURE_CALLBACKS, + Flags.FLAG_ENABLE_HOME_TRANSITION_LISTENER, + Flags.FLAG_ENABLE_LAUNCHER_BR_METRICS_FIXED, + Flags.FLAG_ENABLE_NARROW_GRID_RESTORE, + Flags.FLAG_ENABLE_OVERVIEW_ICON_MENU, + Flags.FLAG_ENABLE_PREDICTIVE_BACK_GESTURE, + Flags.FLAG_ENABLE_PRIVATE_SPACE, + Flags.FLAG_ENABLE_PRIVATE_SPACE_INSTALL_SHORTCUT, + Flags.FLAG_ENABLE_REBOOT_UNLOCK_ANIMATION, + Flags.FLAG_ENABLE_RECENTS_IN_TASKBAR, + Flags.FLAG_ENABLE_REFACTOR_TASK_THUMBNAIL, + Flags.FLAG_ENABLE_RESPONSIVE_WORKSPACE, + Flags.FLAG_ENABLE_SCALING_REVEAL_HOME_ANIMATION, + Flags.FLAG_ENABLE_SHORTCUT_DONT_SUGGEST_APP, + Flags.FLAG_ENABLE_SMARTSPACE_AS_A_WIDGET, + Flags.FLAG_ENABLE_SMARTSPACE_REMOVAL_TOGGLE, + Flags.FLAG_ENABLE_SUPPORT_FOR_ARCHIVING, + Flags.FLAG_ENABLE_TABLET_TWO_PANE_PICKER_V2, + Flags.FLAG_ENABLE_TASKBAR_CUSTOMIZATION, + Flags.FLAG_ENABLE_TASKBAR_NO_RECREATE, + Flags.FLAG_ENABLE_TASKBAR_PINNING, + Flags.FLAG_ENABLE_TWO_PANE_LAUNCHER_SETTINGS, + Flags.FLAG_ENABLE_TWOLINE_ALLAPPS, + Flags.FLAG_ENABLE_TWOLINE_TOGGLE, + Flags.FLAG_ENABLE_UNFOLD_STATE_ANIMATION, + Flags.FLAG_ENABLE_UNFOLDED_TWO_PANE_PICKER, + Flags.FLAG_ENABLE_WIDGET_TAP_TO_ADD, + Flags.FLAG_ENABLE_WORKSPACE_INFLATION, + Flags.FLAG_ENABLED_FOLDERS_IN_ALL_APPS, + Flags.FLAG_FLOATING_SEARCH_BAR, + Flags.FLAG_FORCE_MONOCHROME_APP_ICONS, + Flags.FLAG_PRIVATE_SPACE_ADD_FLOATING_MASK_VIEW, + Flags.FLAG_PRIVATE_SPACE_ANIMATION, + Flags.FLAG_PRIVATE_SPACE_APP_INSTALLER_BUTTON, + Flags.FLAG_PRIVATE_SPACE_RESTRICT_ACCESSIBILITY_DRAG, + Flags.FLAG_PRIVATE_SPACE_RESTRICT_ITEM_DRAG, + Flags.FLAG_PRIVATE_SPACE_SYS_APPS_SEPARATION, + Flags.FLAG_USE_ACTIVITY_OVERLAY ); } private Set mReadOnlyFlagsSet = new HashSet<>( - Arrays.asList( - Flags.FLAG_ACCESSIBILITY_SCROLL_ON_ALLAPPS, - Flags.FLAG_ALL_APPS_BLUR, - Flags.FLAG_ALL_APPS_SHEET_FOR_HANDHELD, - Flags.FLAG_COORDINATE_WORKSPACE_SCALE, - Flags.FLAG_ENABLE_ACTIVE_GESTURE_PROTO_LOG, - Flags.FLAG_ENABLE_ADD_APP_WIDGET_VIA_CONFIG_ACTIVITY_V2, - Flags.FLAG_ENABLE_ADDITIONAL_HOME_ANIMATIONS, - Flags.FLAG_ENABLE_ALL_APPS_BUTTON_IN_HOTSEAT, - Flags.FLAG_ENABLE_ALT_TAB_KQS_FLATENNING, - Flags.FLAG_ENABLE_ALT_TAB_KQS_ON_CONNECTED_DISPLAYS, - Flags.FLAG_ENABLE_CATEGORIZED_WIDGET_SUGGESTIONS, - Flags.FLAG_ENABLE_CONTAINER_RETURN_ANIMATIONS, - Flags.FLAG_ENABLE_CONTRAST_TILES, - Flags.FLAG_ENABLE_CURSOR_HOVER_STATES, - Flags.FLAG_ENABLE_DESKTOP_EXPLODED_VIEW, - Flags.FLAG_ENABLE_DESKTOP_TASK_ALPHA_ANIMATION, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_CAROUSEL_DETACH, - Flags.FLAG_ENABLE_DISMISS_PREDICTION_UNDO, - Flags.FLAG_ENABLE_EXPANDING_PAUSE_WORK_BUTTON, - Flags.FLAG_ENABLE_EXPRESSIVE_DISMISS_TASK_MOTION, - Flags.FLAG_ENABLE_FALLBACK_OVERVIEW_IN_WINDOW, - Flags.FLAG_ENABLE_FIRST_SCREEN_BROADCAST_ARCHIVING_EXTRAS, - Flags.FLAG_ENABLE_FOCUS_OUTLINE, - Flags.FLAG_ENABLE_GENERATED_PREVIEWS, - Flags.FLAG_ENABLE_GESTURE_NAV_HORIZONTAL_TOUCH_SLOP, - Flags.FLAG_ENABLE_GESTURE_NAV_ON_CONNECTED_DISPLAYS, - Flags.FLAG_ENABLE_GRID_MIGRATION_FIX, - Flags.FLAG_ENABLE_GRID_ONLY_OVERVIEW, - Flags.FLAG_ENABLE_GROWTH_NUDGE, - Flags.FLAG_ENABLE_HANDLE_DELAYED_GESTURE_CALLBACKS, - Flags.FLAG_ENABLE_HOME_TRANSITION_LISTENER, - Flags.FLAG_ENABLE_HOVER_OF_CHILD_ELEMENTS_IN_TASKVIEW, - Flags.FLAG_ENABLE_LARGE_DESKTOP_WINDOWING_TILE, - Flags.FLAG_ENABLE_LAUNCHER_BR_METRICS_FIXED, - Flags.FLAG_ENABLE_LAUNCHER_ICON_SHAPES, - Flags.FLAG_ENABLE_LAUNCHER_OVERVIEW_IN_WINDOW, - Flags.FLAG_ENABLE_LAUNCHER_VISUAL_REFRESH, - Flags.FLAG_ENABLE_MOUSE_INTERACTION_CHANGES, - Flags.FLAG_ENABLE_MULTI_INSTANCE_MENU_TASKBAR, - Flags.FLAG_ENABLE_NARROW_GRID_RESTORE, - Flags.FLAG_ENABLE_OVERVIEW_BACKGROUND_WALLPAPER_BLUR, - Flags.FLAG_ENABLE_OVERVIEW_COMMAND_HELPER_TIMEOUT, - Flags.FLAG_ENABLE_OVERVIEW_DESKTOP_TILE_WALLPAPER_BACKGROUND, - Flags.FLAG_ENABLE_OVERVIEW_ICON_MENU, - Flags.FLAG_ENABLE_OVERVIEW_ON_CONNECTED_DISPLAYS, - Flags.FLAG_ENABLE_PINNING_APP_WITH_CONTEXT_MENU, - Flags.FLAG_ENABLE_PREDICTIVE_BACK_GESTURE, - Flags.FLAG_ENABLE_PRIVATE_SPACE, - Flags.FLAG_ENABLE_PRIVATE_SPACE_INSTALL_SHORTCUT, - Flags.FLAG_ENABLE_REBOOT_UNLOCK_ANIMATION, - Flags.FLAG_ENABLE_RECENTS_IN_TASKBAR, - Flags.FLAG_ENABLE_RECENTS_WINDOW_PROTO_LOG, - Flags.FLAG_ENABLE_REFACTOR_TASK_THUMBNAIL, - Flags.FLAG_ENABLE_RESPONSIVE_WORKSPACE, - Flags.FLAG_ENABLE_SCALABILITY_FOR_DESKTOP_EXPERIENCE, - Flags.FLAG_ENABLE_SCALING_REVEAL_HOME_ANIMATION, - Flags.FLAG_ENABLE_SEPARATE_EXTERNAL_DISPLAY_TASKS, - Flags.FLAG_ENABLE_SHORTCUT_DONT_SUGGEST_APP, - Flags.FLAG_ENABLE_SHOW_ENABLED_SHORTCUTS_IN_ACCESSIBILITY_MENU, - Flags.FLAG_ENABLE_SMARTSPACE_AS_A_WIDGET, - Flags.FLAG_ENABLE_SMARTSPACE_REMOVAL_TOGGLE, - Flags.FLAG_ENABLE_STATE_MANAGER_PROTO_LOG, - Flags.FLAG_ENABLE_STRICT_MODE, - Flags.FLAG_ENABLE_SUPPORT_FOR_ARCHIVING, - Flags.FLAG_ENABLE_TABLET_TWO_PANE_PICKER_V2, - Flags.FLAG_ENABLE_TASKBAR_BEHIND_SHADE, - Flags.FLAG_ENABLE_TASKBAR_CUSTOMIZATION, - Flags.FLAG_ENABLE_TASKBAR_FOR_DIRECT_BOOT, - Flags.FLAG_ENABLE_TASKBAR_NO_RECREATE, - Flags.FLAG_ENABLE_TASKBAR_PINNING, - Flags.FLAG_ENABLE_TIERED_WIDGETS_BY_DEFAULT_IN_PICKER, - Flags.FLAG_ENABLE_TWO_PANE_LAUNCHER_SETTINGS, - Flags.FLAG_ENABLE_TWOLINE_ALLAPPS, - Flags.FLAG_ENABLE_TWOLINE_TOGGLE, - Flags.FLAG_ENABLE_UNFOLD_STATE_ANIMATION, - Flags.FLAG_ENABLE_UNFOLDED_TWO_PANE_PICKER, - Flags.FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK, - Flags.FLAG_ENABLE_WIDGET_TAP_TO_ADD, - Flags.FLAG_ENABLE_WORKSPACE_INFLATION, - Flags.FLAG_ENABLED_FOLDERS_IN_ALL_APPS, - Flags.FLAG_EXPRESSIVE_THEME_IN_TASKBAR_AND_NAVIGATION, - Flags.FLAG_EXTENDIBLE_THEME_MANAGER, - Flags.FLAG_FLOATING_SEARCH_BAR, - Flags.FLAG_FORCE_MONOCHROME_APP_ICONS, - Flags.FLAG_GRID_MIGRATION_REFACTOR, - Flags.FLAG_GSF_RES, - Flags.FLAG_IGNORE_THREE_FINGER_TRACKPAD_FOR_NAV_HANDLE_LONG_PRESS, - Flags.FLAG_LETTER_FAST_SCROLLER, - Flags.FLAG_MSDL_FEEDBACK, - Flags.FLAG_MULTILINE_SEARCH_BAR, - Flags.FLAG_NAVIGATE_TO_CHILD_PREFERENCE, - Flags.FLAG_ONE_GRID_MOUNTED_MODE, - Flags.FLAG_ONE_GRID_ROTATION_HANDLING, - Flags.FLAG_ONE_GRID_SPECS, - Flags.FLAG_PREDICTIVE_BACK_TO_HOME_BLUR, - Flags.FLAG_PREDICTIVE_BACK_TO_HOME_POLISH, - Flags.FLAG_PRIVATE_SPACE_ADD_FLOATING_MASK_VIEW, - Flags.FLAG_PRIVATE_SPACE_ANIMATION, - Flags.FLAG_PRIVATE_SPACE_APP_INSTALLER_BUTTON, - Flags.FLAG_PRIVATE_SPACE_RESTRICT_ACCESSIBILITY_DRAG, - Flags.FLAG_PRIVATE_SPACE_RESTRICT_ITEM_DRAG, - Flags.FLAG_PRIVATE_SPACE_SYS_APPS_SEPARATION, - Flags.FLAG_REMOVE_APPS_REFRESH_ON_RIGHT_CLICK, - Flags.FLAG_REMOVE_EXCLUDE_FROM_SCREEN_MAGNIFICATION_FLAG_USAGE, - Flags.FLAG_RESTORE_ARCHIVED_APP_ICONS_FROM_DB, - Flags.FLAG_RESTORE_ARCHIVED_SHORTCUTS, - Flags.FLAG_SHOW_TASKBAR_PINNING_POPUP_FROM_ANYWHERE, - Flags.FLAG_SYNC_APP_LAUNCH_WITH_TASKBAR_STASH, - Flags.FLAG_TASKBAR_OVERFLOW, - Flags.FLAG_TASKBAR_QUIET_MODE_CHANGE_SUPPORT, - Flags.FLAG_USE_ACTIVITY_OVERLAY, - Flags.FLAG_USE_NEW_ICON_FOR_ARCHIVED_APPS, - Flags.FLAG_USE_SYSTEM_RADIUS_FOR_APP_WIDGETS, - Flags.FLAG_WORK_SCHEDULER_IN_WORK_PROFILE, - "" - ) + Arrays.asList( + Flags.FLAG_ENABLE_FIRST_SCREEN_BROADCAST_ARCHIVING_EXTRAS, + Flags.FLAG_ENABLE_GRID_MIGRATION_FIX, + Flags.FLAG_ENABLE_LAUNCHER_BR_METRICS_FIXED, + Flags.FLAG_ENABLE_NARROW_GRID_RESTORE, + "" + ) ); } diff --git a/flags/src/com/android/launcher3/FakeFeatureFlagsImpl.java b/flags/src/com/android/launcher3/FakeFeatureFlagsImpl.java index 1416ee2007..1dc6e623ae 100644 --- a/flags/src/com/android/launcher3/FakeFeatureFlagsImpl.java +++ b/flags/src/com/android/launcher3/FakeFeatureFlagsImpl.java @@ -3,6 +3,7 @@ package com.android.launcher3; import java.util.HashMap; import java.util.Map; import java.util.function.Predicate; + /** @hide */ public class FakeFeatureFlagsImpl extends CustomFeatureFlags { private final Map mFlagMap = new HashMap<>(); diff --git a/flags/src/com/android/launcher3/FeatureFlags.java b/flags/src/com/android/launcher3/FeatureFlags.java index 6c7a84557f..7cd0b034f2 100644 --- a/flags/src/com/android/launcher3/FeatureFlags.java +++ b/flags/src/com/android/launcher3/FeatureFlags.java @@ -1,348 +1,103 @@ package com.android.launcher3; -// TODO(b/303773055): Remove the annotation after access issue is resolved. /** @hide */ public interface FeatureFlags { - - boolean accessibilityScrollOnAllapps(); - - - boolean allAppsBlur(); - - - boolean allAppsSheetForHandheld(); - - - boolean coordinateWorkspaceScale(); - - - boolean enableActiveGestureProtoLog(); - - boolean enableAddAppWidgetViaConfigActivityV2(); - boolean enableAdditionalHomeAnimations(); - - boolean enableAllAppsButtonInHotseat(); - - - boolean enableAltTabKqsFlatenning(); - - - boolean enableAltTabKqsOnConnectedDisplays(); - - boolean enableCategorizedWidgetSuggestions(); - - boolean enableContainerReturnAnimations(); - - - boolean enableContrastTiles(); - - boolean enableCursorHoverStates(); - - boolean enableDesktopExplodedView(); - - - boolean enableDesktopTaskAlphaAnimation(); - - - boolean enableDesktopWindowingCarouselDetach(); - - - boolean enableDismissPredictionUndo(); - - boolean enableExpandingPauseWorkButton(); - - boolean enableExpressiveDismissTaskMotion(); - - boolean enableFallbackOverviewInWindow(); - boolean enableFirstScreenBroadcastArchivingExtras(); - boolean enableFocusOutline(); - boolean enableGeneratedPreviews(); - - boolean enableGestureNavHorizontalTouchSlop(); - - - boolean enableGestureNavOnConnectedDisplays(); - - boolean enableGridMigrationFix(); - boolean enableGridOnlyOverview(); - - boolean enableGrowthNudge(); - - boolean enableHandleDelayedGestureCallbacks(); - boolean enableHomeTransitionListener(); - - boolean enableHoverOfChildElementsInTaskview(); - - - boolean enableLargeDesktopWindowingTile(); - - boolean enableLauncherBrMetricsFixed(); - - boolean enableLauncherIconShapes(); - - - boolean enableLauncherOverviewInWindow(); - - - boolean enableLauncherVisualRefresh(); - - - boolean enableMouseInteractionChanges(); - - - boolean enableMultiInstanceMenuTaskbar(); - - boolean enableNarrowGridRestore(); - - boolean enableOverviewBackgroundWallpaperBlur(); - - - boolean enableOverviewCommandHelperTimeout(); - - - boolean enableOverviewDesktopTileWallpaperBackground(); - - boolean enableOverviewIconMenu(); - - boolean enableOverviewOnConnectedDisplays(); - - - boolean enablePinningAppWithContextMenu(); - - boolean enablePredictiveBackGesture(); - boolean enablePrivateSpace(); - boolean enablePrivateSpaceInstallShortcut(); - boolean enableRebootUnlockAnimation(); - boolean enableRecentsInTaskbar(); - - boolean enableRecentsWindowProtoLog(); - - boolean enableRefactorTaskThumbnail(); - boolean enableResponsiveWorkspace(); - - boolean enableScalabilityForDesktopExperience(); - - boolean enableScalingRevealHomeAnimation(); - - boolean enableSeparateExternalDisplayTasks(); - - boolean enableShortcutDontSuggestApp(); - - boolean enableShowEnabledShortcutsInAccessibilityMenu(); - - boolean enableSmartspaceAsAWidget(); - boolean enableSmartspaceRemovalToggle(); - - boolean enableStateManagerProtoLog(); - - - boolean enableStrictMode(); - - boolean enableSupportForArchiving(); - boolean enableTabletTwoPanePickerV2(); - - boolean enableTaskbarBehindShade(); - - boolean enableTaskbarCustomization(); - - boolean enableTaskbarForDirectBoot(); - - boolean enableTaskbarNoRecreate(); - boolean enableTaskbarPinning(); - - boolean enableTieredWidgetsByDefaultInPicker(); - - boolean enableTwoPaneLauncherSettings(); - boolean enableTwolineAllapps(); - boolean enableTwolineToggle(); - boolean enableUnfoldStateAnimation(); - boolean enableUnfoldedTwoPanePicker(); - - boolean enableUseTopVisibleActivityForExcludeFromRecentTask(); - - boolean enableWidgetTapToAdd(); - boolean enableWorkspaceInflation(); - boolean enabledFoldersInAllApps(); - - boolean expressiveThemeInTaskbarAndNavigation(); - - - boolean extendibleThemeManager(); - - boolean floatingSearchBar(); - boolean forceMonochromeAppIcons(); - - boolean gridMigrationRefactor(); - - - boolean gsfRes(); - - - boolean ignoreThreeFingerTrackpadForNavHandleLongPress(); - - - boolean letterFastScroller(); - - - boolean msdlFeedback(); - - - boolean multilineSearchBar(); - - - boolean navigateToChildPreference(); - - - boolean oneGridMountedMode(); - - - boolean oneGridRotationHandling(); - - - boolean oneGridSpecs(); - - - boolean predictiveBackToHomeBlur(); - - - boolean predictiveBackToHomePolish(); - - boolean privateSpaceAddFloatingMaskView(); - boolean privateSpaceAnimation(); - boolean privateSpaceAppInstallerButton(); - boolean privateSpaceRestrictAccessibilityDrag(); - boolean privateSpaceRestrictItemDrag(); - boolean privateSpaceSysAppsSeparation(); - - boolean removeAppsRefreshOnRightClick(); - - - boolean removeExcludeFromScreenMagnificationFlagUsage(); - - - boolean restoreArchivedAppIconsFromDb(); - - - boolean restoreArchivedShortcuts(); - - - boolean showTaskbarPinningPopupFromAnywhere(); - - - boolean syncAppLaunchWithTaskbarStash(); - - - boolean taskbarOverflow(); - - - boolean taskbarQuietModeChangeSupport(); - - boolean useActivityOverlay(); - - - boolean useNewIconForArchivedApps(); - - - boolean useSystemRadiusForAppWidgets(); - - - boolean workSchedulerInWorkProfile(); -} +} \ No newline at end of file diff --git a/flags/src/com/android/launcher3/FeatureFlagsImpl.java b/flags/src/com/android/launcher3/FeatureFlagsImpl.java index 1fd17137d9..d0aad19aaa 100644 --- a/flags/src/com/android/launcher3/FeatureFlagsImpl.java +++ b/flags/src/com/android/launcher3/FeatureFlagsImpl.java @@ -1,803 +1,880 @@ package com.android.launcher3; // TODO(b/303773055): Remove the annotation after access issue is resolved. +import androidx.core.os.BuildCompat; + +import com.android.quickstep.util.DeviceConfigHelper; + +import java.nio.file.Files; +import java.nio.file.Paths; /** @hide */ public final class FeatureFlagsImpl implements FeatureFlags { - @Override + private static final boolean isReadFromNew = Files.exists(Paths.get("/metadata/aconfig/boot/enable_only_new_storage")); + private static volatile boolean isCached = false; + private static volatile boolean launcher_is_cached = false; + private static volatile boolean launcher_search_is_cached = false; + private static boolean enableAddAppWidgetViaConfigActivityV2 = true; + private static boolean enableAdditionalHomeAnimations = true; + private static boolean enableCategorizedWidgetSuggestions = true; + private static boolean enableCursorHoverStates = true; + private static boolean enableExpandingPauseWorkButton = true; + private static boolean enableFallbackOverviewInWindow = false; + private static boolean enableFocusOutline = true; + private static boolean enableGeneratedPreviews = true; + private static boolean enableGridOnlyOverview = false; + private static boolean enableHandleDelayedGestureCallbacks = true; + private static boolean enableHomeTransitionListener = true; + private static boolean enableOverviewIconMenu = false; + private static boolean enablePredictiveBackGesture = true; + private static boolean enablePrivateSpace = true; + private static boolean enablePrivateSpaceInstallShortcut = true; + private static boolean enableRebootUnlockAnimation = false; + private static boolean enableRecentsInTaskbar = false; + private static boolean enableRefactorTaskThumbnail = false; + private static boolean enableResponsiveWorkspace = true; + private static boolean enableScalingRevealHomeAnimation = true; + private static boolean enableShortcutDontSuggestApp = true; + private static boolean enableSmartspaceAsAWidget = false; + private static boolean enableSmartspaceRemovalToggle = false; + private static boolean enableSupportForArchiving = false; + private static boolean enableTabletTwoPanePickerV2 = false; + private static boolean enableTaskbarCustomization = false; + private static boolean enableTaskbarNoRecreate = false; + private static boolean enableTaskbarPinning = true; + private static boolean enableTwoPaneLauncherSettings = false; + private static boolean enableTwolineAllapps = false; + private static boolean enableTwolineToggle = true; + private static boolean enableUnfoldStateAnimation = false; + private static boolean enableUnfoldedTwoPanePicker = true; + private static boolean enableWidgetTapToAdd = true; + private static boolean enableWorkspaceInflation = true; + private static boolean enabledFoldersInAllApps = false; + private static boolean floatingSearchBar = false; + private static boolean forceMonochromeAppIcons = false; + private static boolean privateSpaceAddFloatingMaskView = false; + private static boolean privateSpaceAnimation = true; + private static boolean privateSpaceAppInstallerButton = true; + private static boolean privateSpaceRestrictAccessibilityDrag = true; + private static boolean privateSpaceRestrictItemDrag = true; + private static boolean privateSpaceSysAppsSeparation = true; + private static boolean useActivityOverlay = true; - public boolean accessibilityScrollOnAllapps() { - return true; + private void init() { + isCached = true; + } + + private void load_overrides_launcher() { + try { + var properties = DeviceConfigHelper.Companion.getPrefs(); + enableAddAppWidgetViaConfigActivityV2 = + properties.getBoolean(Flags.FLAG_ENABLE_ADD_APP_WIDGET_VIA_CONFIG_ACTIVITY_V2, true); + enableAdditionalHomeAnimations = + properties.getBoolean(Flags.FLAG_ENABLE_ADDITIONAL_HOME_ANIMATIONS, true); + enableCategorizedWidgetSuggestions = + properties.getBoolean(Flags.FLAG_ENABLE_CATEGORIZED_WIDGET_SUGGESTIONS, true); + enableCursorHoverStates = + properties.getBoolean(Flags.FLAG_ENABLE_CURSOR_HOVER_STATES, true); + enableExpandingPauseWorkButton = + properties.getBoolean(Flags.FLAG_ENABLE_EXPANDING_PAUSE_WORK_BUTTON, true); + enableFallbackOverviewInWindow = + properties.getBoolean(Flags.FLAG_ENABLE_FALLBACK_OVERVIEW_IN_WINDOW, false); + enableFocusOutline = + properties.getBoolean(Flags.FLAG_ENABLE_FOCUS_OUTLINE, true); + enableGeneratedPreviews = + properties.getBoolean(Flags.FLAG_ENABLE_GENERATED_PREVIEWS, true); + enableGridOnlyOverview = + properties.getBoolean(Flags.FLAG_ENABLE_GRID_ONLY_OVERVIEW, false); + enableHandleDelayedGestureCallbacks = + properties.getBoolean(Flags.FLAG_ENABLE_HANDLE_DELAYED_GESTURE_CALLBACKS, true); + enableHomeTransitionListener = + properties.getBoolean(Flags.FLAG_ENABLE_HOME_TRANSITION_LISTENER, true); + enableOverviewIconMenu = + properties.getBoolean(Flags.FLAG_ENABLE_OVERVIEW_ICON_MENU, false); + enablePredictiveBackGesture = + properties.getBoolean(Flags.FLAG_ENABLE_PREDICTIVE_BACK_GESTURE, true); + enablePrivateSpaceInstallShortcut = + properties.getBoolean(Flags.FLAG_ENABLE_PRIVATE_SPACE_INSTALL_SHORTCUT, true); + enableRebootUnlockAnimation = + properties.getBoolean(Flags.FLAG_ENABLE_REBOOT_UNLOCK_ANIMATION, false); + enableRecentsInTaskbar = + properties.getBoolean(Flags.FLAG_ENABLE_RECENTS_IN_TASKBAR, false); + enableRefactorTaskThumbnail = + properties.getBoolean(Flags.FLAG_ENABLE_REFACTOR_TASK_THUMBNAIL, false); + enableResponsiveWorkspace = + properties.getBoolean(Flags.FLAG_ENABLE_RESPONSIVE_WORKSPACE, true); + enableScalingRevealHomeAnimation = + properties.getBoolean(Flags.FLAG_ENABLE_SCALING_REVEAL_HOME_ANIMATION, true); + enableShortcutDontSuggestApp = + properties.getBoolean(Flags.FLAG_ENABLE_SHORTCUT_DONT_SUGGEST_APP, true); + enableSmartspaceAsAWidget = + properties.getBoolean(Flags.FLAG_ENABLE_SMARTSPACE_AS_A_WIDGET, false); + enableSmartspaceRemovalToggle = + properties.getBoolean(Flags.FLAG_ENABLE_SMARTSPACE_REMOVAL_TOGGLE, true); + enableSupportForArchiving = + properties.getBoolean(Flags.FLAG_ENABLE_SUPPORT_FOR_ARCHIVING, BuildCompat.isAtLeastU()); + enableTabletTwoPanePickerV2 = + properties.getBoolean(Flags.FLAG_ENABLE_TABLET_TWO_PANE_PICKER_V2, false); + enableTaskbarCustomization = + properties.getBoolean(Flags.FLAG_ENABLE_TASKBAR_CUSTOMIZATION, false); + enableTaskbarNoRecreate = + properties.getBoolean(Flags.FLAG_ENABLE_TASKBAR_NO_RECREATE, false); + enableTaskbarPinning = + properties.getBoolean(Flags.FLAG_ENABLE_TASKBAR_PINNING, true); + enableTwoPaneLauncherSettings = + properties.getBoolean(Flags.FLAG_ENABLE_TWO_PANE_LAUNCHER_SETTINGS, true); + enableTwolineAllapps = + properties.getBoolean(Flags.FLAG_ENABLE_TWOLINE_ALLAPPS, false); + enableTwolineToggle = + properties.getBoolean(Flags.FLAG_ENABLE_TWOLINE_TOGGLE, true); + enableUnfoldStateAnimation = + properties.getBoolean(Flags.FLAG_ENABLE_UNFOLD_STATE_ANIMATION, false); + enableUnfoldedTwoPanePicker = + properties.getBoolean(Flags.FLAG_ENABLE_UNFOLDED_TWO_PANE_PICKER, true); + enableWidgetTapToAdd = + properties.getBoolean(Flags.FLAG_ENABLE_WIDGET_TAP_TO_ADD, true); + enableWorkspaceInflation = + properties.getBoolean(Flags.FLAG_ENABLE_WORKSPACE_INFLATION, true); + enabledFoldersInAllApps = + properties.getBoolean(Flags.FLAG_ENABLED_FOLDERS_IN_ALL_APPS, false); + floatingSearchBar = + properties.getBoolean(Flags.FLAG_FLOATING_SEARCH_BAR, false); + forceMonochromeAppIcons = + properties.getBoolean(Flags.FLAG_FORCE_MONOCHROME_APP_ICONS, true); + useActivityOverlay = + properties.getBoolean(Flags.FLAG_USE_ACTIVITY_OVERLAY, true); + } catch (NullPointerException e) { + // Ignored + } + launcher_is_cached = true; + } + + private void load_overrides_launcher_search() { + try { + var properties = DeviceConfigHelper.Companion.getPrefs(); + enablePrivateSpace = + properties.getBoolean(Flags.FLAG_ENABLE_PRIVATE_SPACE, BuildCompat.isAtLeastU()); + privateSpaceAddFloatingMaskView = + properties.getBoolean(Flags.FLAG_PRIVATE_SPACE_ADD_FLOATING_MASK_VIEW, BuildCompat.isAtLeastU()); + privateSpaceAnimation = + properties.getBoolean(Flags.FLAG_PRIVATE_SPACE_ANIMATION, BuildCompat.isAtLeastU()); + privateSpaceAppInstallerButton = + properties.getBoolean(Flags.FLAG_PRIVATE_SPACE_APP_INSTALLER_BUTTON, BuildCompat.isAtLeastU()); + privateSpaceRestrictAccessibilityDrag = + properties.getBoolean(Flags.FLAG_PRIVATE_SPACE_RESTRICT_ACCESSIBILITY_DRAG, BuildCompat.isAtLeastU()); + privateSpaceRestrictItemDrag = + properties.getBoolean(Flags.FLAG_PRIVATE_SPACE_RESTRICT_ITEM_DRAG, BuildCompat.isAtLeastU()); + privateSpaceSysAppsSeparation = + properties.getBoolean(Flags.FLAG_PRIVATE_SPACE_SYS_APPS_SEPARATION, BuildCompat.isAtLeastU()); + } catch (NullPointerException e) { + throw new RuntimeException( + "Cannot read value from namespace launcher_search " + + "from DeviceConfig. It could be that the code using flag " + + "executed before SettingsProvider initialization. Please use " + + "fixed read-only flag by adding is_fixed_read_only: true in " + + "flag declaration.", + e + ); + } + launcher_search_is_cached = true; } @Override - - - public boolean allAppsBlur() { - return true; - } - - @Override - - - public boolean allAppsSheetForHandheld() { - return true; - } - - @Override - - - public boolean coordinateWorkspaceScale() { - return true; - } - - @Override - - - public boolean enableActiveGestureProtoLog() { - return true; - } - - @Override - - public boolean enableAddAppWidgetViaConfigActivityV2() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!launcher_is_cached) { + load_overrides_launcher(); + } + } + return enableAddAppWidgetViaConfigActivityV2; + } - @Override - - public boolean enableAdditionalHomeAnimations() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!launcher_is_cached) { + load_overrides_launcher(); + } + } + return enableAdditionalHomeAnimations; + } @Override - - - public boolean enableAllAppsButtonInHotseat() { - return false; - } - - @Override - - - public boolean enableAltTabKqsFlatenning() { - return false; - } - - @Override - - - public boolean enableAltTabKqsOnConnectedDisplays() { - return false; - } - - @Override - - public boolean enableCategorizedWidgetSuggestions() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!launcher_is_cached) { + load_overrides_launcher(); + } + } + return enableCategorizedWidgetSuggestions; + } @Override - - - public boolean enableContainerReturnAnimations() { - return true; - } - - @Override - - - public boolean enableContrastTiles() { - return false; - } - - @Override - - public boolean enableCursorHoverStates() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!launcher_is_cached) { + load_overrides_launcher(); + } + } + return enableCursorHoverStates; + } @Override - - - public boolean enableDesktopExplodedView() { - return false; - } - - @Override - - - public boolean enableDesktopTaskAlphaAnimation() { - return true; - } - - @Override - - - public boolean enableDesktopWindowingCarouselDetach() { - return false; - } - - @Override - - - public boolean enableDismissPredictionUndo() { - return true; - } - - @Override - - public boolean enableExpandingPauseWorkButton() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!launcher_is_cached) { + load_overrides_launcher(); + } + } + return enableExpandingPauseWorkButton; + } @Override - - - public boolean enableExpressiveDismissTaskMotion() { - return true; - } - - @Override - - public boolean enableFallbackOverviewInWindow() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!launcher_is_cached) { + load_overrides_launcher(); + } + } + return enableFallbackOverviewInWindow; + } @Override - - public boolean enableFirstScreenBroadcastArchivingExtras() { - return true; + return false; + } @Override - - public boolean enableFocusOutline() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!launcher_is_cached) { + load_overrides_launcher(); + } + } + return enableFocusOutline; + } @Override - - public boolean enableGeneratedPreviews() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!launcher_is_cached) { + load_overrides_launcher(); + } + } + return enableGeneratedPreviews; + } @Override - - - public boolean enableGestureNavHorizontalTouchSlop() { - return false; - } - - @Override - - - public boolean enableGestureNavOnConnectedDisplays() { - return false; - } - - @Override - - public boolean enableGridMigrationFix() { - return true; + return false; + } @Override - - public boolean enableGridOnlyOverview() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!launcher_is_cached) { + load_overrides_launcher(); + } + } + return enableGridOnlyOverview; + } @Override - - - public boolean enableGrowthNudge() { - return false; - } - - @Override - - public boolean enableHandleDelayedGestureCallbacks() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!launcher_is_cached) { + load_overrides_launcher(); + } + } + return enableHandleDelayedGestureCallbacks; + } @Override - - public boolean enableHomeTransitionListener() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!launcher_is_cached) { + load_overrides_launcher(); + } + } + return enableHomeTransitionListener; + } @Override - - - public boolean enableHoverOfChildElementsInTaskview() { - return true; - } - - @Override - - - public boolean enableLargeDesktopWindowingTile() { - return true; - } - - @Override - - public boolean enableLauncherBrMetricsFixed() { return true; + } @Override - - - public boolean enableLauncherIconShapes() { - return true; - } - - @Override - - - public boolean enableLauncherOverviewInWindow() { - return false; - } - - @Override - - - public boolean enableLauncherVisualRefresh() { - return true; - } - - @Override - - - public boolean enableMouseInteractionChanges() { - return true; - } - - @Override - - - public boolean enableMultiInstanceMenuTaskbar() { - return true; - } - - @Override - - public boolean enableNarrowGridRestore() { - return true; - } - - @Override - - - public boolean enableOverviewBackgroundWallpaperBlur() { - return true; - } - - @Override - - - public boolean enableOverviewCommandHelperTimeout() { - return true; - } - - @Override - - - public boolean enableOverviewDesktopTileWallpaperBackground() { return false; + } @Override - - public boolean enableOverviewIconMenu() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!launcher_is_cached) { + load_overrides_launcher(); + } + } + return enableOverviewIconMenu; + } @Override - - - public boolean enableOverviewOnConnectedDisplays() { - return false; - } - - @Override - - - public boolean enablePinningAppWithContextMenu() { - return false; - } - - @Override - - public boolean enablePredictiveBackGesture() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!launcher_is_cached) { + load_overrides_launcher(); + } + } + return enablePredictiveBackGesture; + } @Override - - public boolean enablePrivateSpace() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!launcher_search_is_cached) { + load_overrides_launcher_search(); + } + } + return enablePrivateSpace; + } @Override - - public boolean enablePrivateSpaceInstallShortcut() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!launcher_is_cached) { + load_overrides_launcher(); + } + } + return enablePrivateSpaceInstallShortcut; + } @Override - - public boolean enableRebootUnlockAnimation() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!launcher_is_cached) { + load_overrides_launcher(); + } + } + return enableRebootUnlockAnimation; + } @Override - - public boolean enableRecentsInTaskbar() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!launcher_is_cached) { + load_overrides_launcher(); + } + } + return enableRecentsInTaskbar; + } @Override - - - public boolean enableRecentsWindowProtoLog() { - return false; - } - - @Override - - public boolean enableRefactorTaskThumbnail() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!launcher_is_cached) { + load_overrides_launcher(); + } + } + return enableRefactorTaskThumbnail; + } @Override - - public boolean enableResponsiveWorkspace() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!launcher_is_cached) { + load_overrides_launcher(); + } + } + return enableResponsiveWorkspace; + } @Override - - - public boolean enableScalabilityForDesktopExperience() { - return false; - } - - @Override - - public boolean enableScalingRevealHomeAnimation() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!launcher_is_cached) { + load_overrides_launcher(); + } + } + return enableScalingRevealHomeAnimation; + } @Override - - - public boolean enableSeparateExternalDisplayTasks() { - return true; - } - - @Override - - public boolean enableShortcutDontSuggestApp() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!launcher_is_cached) { + load_overrides_launcher(); + } + } + return enableShortcutDontSuggestApp; + } @Override - - - public boolean enableShowEnabledShortcutsInAccessibilityMenu() { - return true; - } - - @Override - - public boolean enableSmartspaceAsAWidget() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!launcher_is_cached) { + load_overrides_launcher(); + } + } + return enableSmartspaceAsAWidget; + } @Override - - public boolean enableSmartspaceRemovalToggle() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!launcher_is_cached) { + load_overrides_launcher(); + } + } + return enableSmartspaceRemovalToggle; + } @Override - - - public boolean enableStateManagerProtoLog() { - return false; - } - - @Override - - - public boolean enableStrictMode() { - return false; - } - - @Override - - public boolean enableSupportForArchiving() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!launcher_is_cached) { + load_overrides_launcher(); + } + } + return enableSupportForArchiving; + } @Override - - public boolean enableTabletTwoPanePickerV2() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!launcher_is_cached) { + load_overrides_launcher(); + } + } + return enableTabletTwoPanePickerV2; + } @Override - - - public boolean enableTaskbarBehindShade() { - return false; - } - - @Override - - public boolean enableTaskbarCustomization() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!launcher_is_cached) { + load_overrides_launcher(); + } + } + return enableTaskbarCustomization; + } @Override - - - public boolean enableTaskbarForDirectBoot() { - return false; - } - - @Override - - public boolean enableTaskbarNoRecreate() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!launcher_is_cached) { + load_overrides_launcher(); + } + } + return enableTaskbarNoRecreate; + } @Override - - public boolean enableTaskbarPinning() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!launcher_is_cached) { + load_overrides_launcher(); + } + } + return enableTaskbarPinning; + } @Override - - - public boolean enableTieredWidgetsByDefaultInPicker() { - return false; - } - - @Override - - public boolean enableTwoPaneLauncherSettings() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!launcher_is_cached) { + load_overrides_launcher(); + } + } + return enableTwoPaneLauncherSettings; + } @Override - - public boolean enableTwolineAllapps() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!launcher_is_cached) { + load_overrides_launcher(); + } + } + return enableTwolineAllapps; + } @Override - - public boolean enableTwolineToggle() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!launcher_is_cached) { + load_overrides_launcher(); + } + } + return enableTwolineToggle; + } @Override - - public boolean enableUnfoldStateAnimation() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!launcher_is_cached) { + load_overrides_launcher(); + } + } + return enableUnfoldStateAnimation; + } @Override - - public boolean enableUnfoldedTwoPanePicker() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!launcher_is_cached) { + load_overrides_launcher(); + } + } + return enableUnfoldedTwoPanePicker; + } @Override - - - public boolean enableUseTopVisibleActivityForExcludeFromRecentTask() { - return true; - } - - @Override - - public boolean enableWidgetTapToAdd() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!launcher_is_cached) { + load_overrides_launcher(); + } + } + return enableWidgetTapToAdd; + } @Override - - public boolean enableWorkspaceInflation() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!launcher_is_cached) { + load_overrides_launcher(); + } + } + return enableWorkspaceInflation; + } @Override - - public boolean enabledFoldersInAllApps() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!launcher_is_cached) { + load_overrides_launcher(); + } + } + return enabledFoldersInAllApps; + } @Override - - - public boolean expressiveThemeInTaskbarAndNavigation() { - return true; - } - - @Override - - - public boolean extendibleThemeManager() { - return true; - } - - @Override - - public boolean floatingSearchBar() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!launcher_is_cached) { + load_overrides_launcher(); + } + } + return floatingSearchBar; + } @Override - - public boolean forceMonochromeAppIcons() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!launcher_is_cached) { + load_overrides_launcher(); + } + } + return forceMonochromeAppIcons; + } @Override - - - public boolean gridMigrationRefactor() { - return true; - } - - @Override - - - public boolean gsfRes() { - return false; - } - - @Override - - - public boolean ignoreThreeFingerTrackpadForNavHandleLongPress() { - return true; - } - - @Override - - - public boolean letterFastScroller() { - return false; - } - - @Override - - - public boolean msdlFeedback() { - return true; - } - - @Override - - - public boolean multilineSearchBar() { - return true; - } - - @Override - - - public boolean navigateToChildPreference() { - return true; - } - - @Override - - - public boolean oneGridMountedMode() { - return false; - } - - @Override - - - public boolean oneGridRotationHandling() { - return false; - } - - @Override - - - public boolean oneGridSpecs() { - return false; - } - - @Override - - - public boolean predictiveBackToHomeBlur() { - return true; - } - - @Override - - - public boolean predictiveBackToHomePolish() { - return true; - } - - @Override - - public boolean privateSpaceAddFloatingMaskView() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!launcher_search_is_cached) { + load_overrides_launcher_search(); + } + } + return privateSpaceAddFloatingMaskView; + } @Override - - public boolean privateSpaceAnimation() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!launcher_search_is_cached) { + load_overrides_launcher_search(); + } + } + return privateSpaceAnimation; + } @Override - - public boolean privateSpaceAppInstallerButton() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!launcher_search_is_cached) { + load_overrides_launcher_search(); + } + } + return privateSpaceAppInstallerButton; + } @Override - - public boolean privateSpaceRestrictAccessibilityDrag() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!launcher_search_is_cached) { + load_overrides_launcher_search(); + } + } + return privateSpaceRestrictAccessibilityDrag; + } @Override - - public boolean privateSpaceRestrictItemDrag() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!launcher_search_is_cached) { + load_overrides_launcher_search(); + } + } + return privateSpaceRestrictItemDrag; + } @Override - - public boolean privateSpaceSysAppsSeparation() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!launcher_search_is_cached) { + load_overrides_launcher_search(); + } + } + return privateSpaceSysAppsSeparation; + } @Override - - - public boolean removeAppsRefreshOnRightClick() { - return true; - } - - @Override - - - public boolean removeExcludeFromScreenMagnificationFlagUsage() { - return true; - } - - @Override - - - public boolean restoreArchivedAppIconsFromDb() { - return false; - } - - @Override - - - public boolean restoreArchivedShortcuts() { - return false; - } - - @Override - - - public boolean showTaskbarPinningPopupFromAnywhere() { - return false; - } - - @Override - - - public boolean syncAppLaunchWithTaskbarStash() { - return false; - } - - @Override - - - public boolean taskbarOverflow() { - return true; - } - - @Override - - - public boolean taskbarQuietModeChangeSupport() { - return false; - } - - @Override - - public boolean useActivityOverlay() { - return true; - } + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!launcher_is_cached) { + load_overrides_launcher(); + } + } + return useActivityOverlay; - @Override - - - public boolean useNewIconForArchivedApps() { - return true; - } - - @Override - - - public boolean useSystemRadiusForAppWidgets() { - return true; - } - - @Override - - - public boolean workSchedulerInWorkProfile() { - return false; } } + diff --git a/flags/src/com/android/launcher3/Flags.java b/flags/src/com/android/launcher3/Flags.java index 68ce0509a9..3e37a8f2a8 100644 --- a/flags/src/com/android/launcher3/Flags.java +++ b/flags/src/com/android/launcher3/Flags.java @@ -1,49 +1,18 @@ package com.android.launcher3; // TODO(b/303773055): Remove the annotation after access issue is resolved. - /** @hide */ public final class Flags { - /** @hide */ - public static final String FLAG_ACCESSIBILITY_SCROLL_ON_ALLAPPS = "com.android.launcher3.accessibility_scroll_on_allapps"; - /** @hide */ - public static final String FLAG_ALL_APPS_BLUR = "com.android.launcher3.all_apps_blur"; - /** @hide */ - public static final String FLAG_ALL_APPS_SHEET_FOR_HANDHELD = "com.android.launcher3.all_apps_sheet_for_handheld"; - /** @hide */ - public static final String FLAG_COORDINATE_WORKSPACE_SCALE = "com.android.launcher3.coordinate_workspace_scale"; - /** @hide */ - public static final String FLAG_ENABLE_ACTIVE_GESTURE_PROTO_LOG = "com.android.launcher3.enable_active_gesture_proto_log"; /** @hide */ public static final String FLAG_ENABLE_ADD_APP_WIDGET_VIA_CONFIG_ACTIVITY_V2 = "com.android.launcher3.enable_add_app_widget_via_config_activity_v2"; /** @hide */ public static final String FLAG_ENABLE_ADDITIONAL_HOME_ANIMATIONS = "com.android.launcher3.enable_additional_home_animations"; /** @hide */ - public static final String FLAG_ENABLE_ALL_APPS_BUTTON_IN_HOTSEAT = "com.android.launcher3.enable_all_apps_button_in_hotseat"; - /** @hide */ - public static final String FLAG_ENABLE_ALT_TAB_KQS_FLATENNING = "com.android.launcher3.enable_alt_tab_kqs_flatenning"; - /** @hide */ - public static final String FLAG_ENABLE_ALT_TAB_KQS_ON_CONNECTED_DISPLAYS = "com.android.launcher3.enable_alt_tab_kqs_on_connected_displays"; - /** @hide */ public static final String FLAG_ENABLE_CATEGORIZED_WIDGET_SUGGESTIONS = "com.android.launcher3.enable_categorized_widget_suggestions"; /** @hide */ - public static final String FLAG_ENABLE_CONTAINER_RETURN_ANIMATIONS = "com.android.launcher3.enable_container_return_animations"; - /** @hide */ - public static final String FLAG_ENABLE_CONTRAST_TILES = "com.android.launcher3.enable_contrast_tiles"; - /** @hide */ public static final String FLAG_ENABLE_CURSOR_HOVER_STATES = "com.android.launcher3.enable_cursor_hover_states"; /** @hide */ - public static final String FLAG_ENABLE_DESKTOP_EXPLODED_VIEW = "com.android.launcher3.enable_desktop_exploded_view"; - /** @hide */ - public static final String FLAG_ENABLE_DESKTOP_TASK_ALPHA_ANIMATION = "com.android.launcher3.enable_desktop_task_alpha_animation"; - /** @hide */ - public static final String FLAG_ENABLE_DESKTOP_WINDOWING_CAROUSEL_DETACH = "com.android.launcher3.enable_desktop_windowing_carousel_detach"; - /** @hide */ - public static final String FLAG_ENABLE_DISMISS_PREDICTION_UNDO = "com.android.launcher3.enable_dismiss_prediction_undo"; - /** @hide */ public static final String FLAG_ENABLE_EXPANDING_PAUSE_WORK_BUTTON = "com.android.launcher3.enable_expanding_pause_work_button"; /** @hide */ - public static final String FLAG_ENABLE_EXPRESSIVE_DISMISS_TASK_MOTION = "com.android.launcher3.enable_expressive_dismiss_task_motion"; - /** @hide */ public static final String FLAG_ENABLE_FALLBACK_OVERVIEW_IN_WINDOW = "com.android.launcher3.enable_fallback_overview_in_window"; /** @hide */ public static final String FLAG_ENABLE_FIRST_SCREEN_BROADCAST_ARCHIVING_EXTRAS = "com.android.launcher3.enable_first_screen_broadcast_archiving_extras"; @@ -52,50 +21,20 @@ public final class Flags { /** @hide */ public static final String FLAG_ENABLE_GENERATED_PREVIEWS = "com.android.launcher3.enable_generated_previews"; /** @hide */ - public static final String FLAG_ENABLE_GESTURE_NAV_HORIZONTAL_TOUCH_SLOP = "com.android.launcher3.enable_gesture_nav_horizontal_touch_slop"; - /** @hide */ - public static final String FLAG_ENABLE_GESTURE_NAV_ON_CONNECTED_DISPLAYS = "com.android.launcher3.enable_gesture_nav_on_connected_displays"; - /** @hide */ public static final String FLAG_ENABLE_GRID_MIGRATION_FIX = "com.android.launcher3.enable_grid_migration_fix"; /** @hide */ public static final String FLAG_ENABLE_GRID_ONLY_OVERVIEW = "com.android.launcher3.enable_grid_only_overview"; /** @hide */ - public static final String FLAG_ENABLE_GROWTH_NUDGE = "com.android.launcher3.enable_growth_nudge"; - /** @hide */ public static final String FLAG_ENABLE_HANDLE_DELAYED_GESTURE_CALLBACKS = "com.android.launcher3.enable_handle_delayed_gesture_callbacks"; /** @hide */ public static final String FLAG_ENABLE_HOME_TRANSITION_LISTENER = "com.android.launcher3.enable_home_transition_listener"; /** @hide */ - public static final String FLAG_ENABLE_HOVER_OF_CHILD_ELEMENTS_IN_TASKVIEW = "com.android.launcher3.enable_hover_of_child_elements_in_taskview"; - /** @hide */ - public static final String FLAG_ENABLE_LARGE_DESKTOP_WINDOWING_TILE = "com.android.launcher3.enable_large_desktop_windowing_tile"; - /** @hide */ public static final String FLAG_ENABLE_LAUNCHER_BR_METRICS_FIXED = "com.android.launcher3.enable_launcher_br_metrics_fixed"; /** @hide */ - public static final String FLAG_ENABLE_LAUNCHER_ICON_SHAPES = "com.android.launcher3.enable_launcher_icon_shapes"; - /** @hide */ - public static final String FLAG_ENABLE_LAUNCHER_OVERVIEW_IN_WINDOW = "com.android.launcher3.enable_launcher_overview_in_window"; - /** @hide */ - public static final String FLAG_ENABLE_LAUNCHER_VISUAL_REFRESH = "com.android.launcher3.enable_launcher_visual_refresh"; - /** @hide */ - public static final String FLAG_ENABLE_MOUSE_INTERACTION_CHANGES = "com.android.launcher3.enable_mouse_interaction_changes"; - /** @hide */ - public static final String FLAG_ENABLE_MULTI_INSTANCE_MENU_TASKBAR = "com.android.launcher3.enable_multi_instance_menu_taskbar"; - /** @hide */ public static final String FLAG_ENABLE_NARROW_GRID_RESTORE = "com.android.launcher3.enable_narrow_grid_restore"; /** @hide */ - public static final String FLAG_ENABLE_OVERVIEW_BACKGROUND_WALLPAPER_BLUR = "com.android.launcher3.enable_overview_background_wallpaper_blur"; - /** @hide */ - public static final String FLAG_ENABLE_OVERVIEW_COMMAND_HELPER_TIMEOUT = "com.android.launcher3.enable_overview_command_helper_timeout"; - /** @hide */ - public static final String FLAG_ENABLE_OVERVIEW_DESKTOP_TILE_WALLPAPER_BACKGROUND = "com.android.launcher3.enable_overview_desktop_tile_wallpaper_background"; - /** @hide */ public static final String FLAG_ENABLE_OVERVIEW_ICON_MENU = "com.android.launcher3.enable_overview_icon_menu"; /** @hide */ - public static final String FLAG_ENABLE_OVERVIEW_ON_CONNECTED_DISPLAYS = "com.android.launcher3.enable_overview_on_connected_displays"; - /** @hide */ - public static final String FLAG_ENABLE_PINNING_APP_WITH_CONTEXT_MENU = "com.android.launcher3.enable_pinning_app_with_context_menu"; - /** @hide */ public static final String FLAG_ENABLE_PREDICTIVE_BACK_GESTURE = "com.android.launcher3.enable_predictive_back_gesture"; /** @hide */ public static final String FLAG_ENABLE_PRIVATE_SPACE = "com.android.launcher3.enable_private_space"; @@ -106,46 +45,28 @@ public final class Flags { /** @hide */ public static final String FLAG_ENABLE_RECENTS_IN_TASKBAR = "com.android.launcher3.enable_recents_in_taskbar"; /** @hide */ - public static final String FLAG_ENABLE_RECENTS_WINDOW_PROTO_LOG = "com.android.launcher3.enable_recents_window_proto_log"; - /** @hide */ public static final String FLAG_ENABLE_REFACTOR_TASK_THUMBNAIL = "com.android.launcher3.enable_refactor_task_thumbnail"; /** @hide */ public static final String FLAG_ENABLE_RESPONSIVE_WORKSPACE = "com.android.launcher3.enable_responsive_workspace"; /** @hide */ - public static final String FLAG_ENABLE_SCALABILITY_FOR_DESKTOP_EXPERIENCE = "com.android.launcher3.enable_scalability_for_desktop_experience"; - /** @hide */ public static final String FLAG_ENABLE_SCALING_REVEAL_HOME_ANIMATION = "com.android.launcher3.enable_scaling_reveal_home_animation"; /** @hide */ - public static final String FLAG_ENABLE_SEPARATE_EXTERNAL_DISPLAY_TASKS = "com.android.launcher3.enable_separate_external_display_tasks"; - /** @hide */ public static final String FLAG_ENABLE_SHORTCUT_DONT_SUGGEST_APP = "com.android.launcher3.enable_shortcut_dont_suggest_app"; /** @hide */ - public static final String FLAG_ENABLE_SHOW_ENABLED_SHORTCUTS_IN_ACCESSIBILITY_MENU = "com.android.launcher3.enable_show_enabled_shortcuts_in_accessibility_menu"; - /** @hide */ public static final String FLAG_ENABLE_SMARTSPACE_AS_A_WIDGET = "com.android.launcher3.enable_smartspace_as_a_widget"; /** @hide */ public static final String FLAG_ENABLE_SMARTSPACE_REMOVAL_TOGGLE = "com.android.launcher3.enable_smartspace_removal_toggle"; /** @hide */ - public static final String FLAG_ENABLE_STATE_MANAGER_PROTO_LOG = "com.android.launcher3.enable_state_manager_proto_log"; - /** @hide */ - public static final String FLAG_ENABLE_STRICT_MODE = "com.android.launcher3.enable_strict_mode"; - /** @hide */ public static final String FLAG_ENABLE_SUPPORT_FOR_ARCHIVING = "com.android.launcher3.enable_support_for_archiving"; /** @hide */ public static final String FLAG_ENABLE_TABLET_TWO_PANE_PICKER_V2 = "com.android.launcher3.enable_tablet_two_pane_picker_v2"; /** @hide */ - public static final String FLAG_ENABLE_TASKBAR_BEHIND_SHADE = "com.android.launcher3.enable_taskbar_behind_shade"; - /** @hide */ public static final String FLAG_ENABLE_TASKBAR_CUSTOMIZATION = "com.android.launcher3.enable_taskbar_customization"; /** @hide */ - public static final String FLAG_ENABLE_TASKBAR_FOR_DIRECT_BOOT = "com.android.launcher3.enable_taskbar_for_direct_boot"; - /** @hide */ public static final String FLAG_ENABLE_TASKBAR_NO_RECREATE = "com.android.launcher3.enable_taskbar_no_recreate"; /** @hide */ public static final String FLAG_ENABLE_TASKBAR_PINNING = "com.android.launcher3.enable_taskbar_pinning"; /** @hide */ - public static final String FLAG_ENABLE_TIERED_WIDGETS_BY_DEFAULT_IN_PICKER = "com.android.launcher3.enable_tiered_widgets_by_default_in_picker"; - /** @hide */ public static final String FLAG_ENABLE_TWO_PANE_LAUNCHER_SETTINGS = "com.android.launcher3.enable_two_pane_launcher_settings"; /** @hide */ public static final String FLAG_ENABLE_TWOLINE_ALLAPPS = "com.android.launcher3.enable_twoline_allapps"; @@ -156,46 +77,16 @@ public final class Flags { /** @hide */ public static final String FLAG_ENABLE_UNFOLDED_TWO_PANE_PICKER = "com.android.launcher3.enable_unfolded_two_pane_picker"; /** @hide */ - public static final String FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK = "com.android.launcher3.enable_use_top_visible_activity_for_exclude_from_recent_task"; - /** @hide */ public static final String FLAG_ENABLE_WIDGET_TAP_TO_ADD = "com.android.launcher3.enable_widget_tap_to_add"; /** @hide */ public static final String FLAG_ENABLE_WORKSPACE_INFLATION = "com.android.launcher3.enable_workspace_inflation"; /** @hide */ public static final String FLAG_ENABLED_FOLDERS_IN_ALL_APPS = "com.android.launcher3.enabled_folders_in_all_apps"; /** @hide */ - public static final String FLAG_EXPRESSIVE_THEME_IN_TASKBAR_AND_NAVIGATION = "com.android.launcher3.expressive_theme_in_taskbar_and_navigation"; - /** @hide */ - public static final String FLAG_EXTENDIBLE_THEME_MANAGER = "com.android.launcher3.extendible_theme_manager"; - /** @hide */ public static final String FLAG_FLOATING_SEARCH_BAR = "com.android.launcher3.floating_search_bar"; /** @hide */ public static final String FLAG_FORCE_MONOCHROME_APP_ICONS = "com.android.launcher3.force_monochrome_app_icons"; /** @hide */ - public static final String FLAG_GRID_MIGRATION_REFACTOR = "com.android.launcher3.grid_migration_refactor"; - /** @hide */ - public static final String FLAG_GSF_RES = "com.android.launcher3.gsf_res"; - /** @hide */ - public static final String FLAG_IGNORE_THREE_FINGER_TRACKPAD_FOR_NAV_HANDLE_LONG_PRESS = "com.android.launcher3.ignore_three_finger_trackpad_for_nav_handle_long_press"; - /** @hide */ - public static final String FLAG_LETTER_FAST_SCROLLER = "com.android.launcher3.letter_fast_scroller"; - /** @hide */ - public static final String FLAG_MSDL_FEEDBACK = "com.android.launcher3.msdl_feedback"; - /** @hide */ - public static final String FLAG_MULTILINE_SEARCH_BAR = "com.android.launcher3.multiline_search_bar"; - /** @hide */ - public static final String FLAG_NAVIGATE_TO_CHILD_PREFERENCE = "com.android.launcher3.navigate_to_child_preference"; - /** @hide */ - public static final String FLAG_ONE_GRID_MOUNTED_MODE = "com.android.launcher3.one_grid_mounted_mode"; - /** @hide */ - public static final String FLAG_ONE_GRID_ROTATION_HANDLING = "com.android.launcher3.one_grid_rotation_handling"; - /** @hide */ - public static final String FLAG_ONE_GRID_SPECS = "com.android.launcher3.one_grid_specs"; - /** @hide */ - public static final String FLAG_PREDICTIVE_BACK_TO_HOME_BLUR = "com.android.launcher3.predictive_back_to_home_blur"; - /** @hide */ - public static final String FLAG_PREDICTIVE_BACK_TO_HOME_POLISH = "com.android.launcher3.predictive_back_to_home_polish"; - /** @hide */ public static final String FLAG_PRIVATE_SPACE_ADD_FLOATING_MASK_VIEW = "com.android.launcher3.private_space_add_floating_mask_view"; /** @hide */ public static final String FLAG_PRIVATE_SPACE_ANIMATION = "com.android.launcher3.private_space_animation"; @@ -208,714 +99,204 @@ public final class Flags { /** @hide */ public static final String FLAG_PRIVATE_SPACE_SYS_APPS_SEPARATION = "com.android.launcher3.private_space_sys_apps_separation"; /** @hide */ - public static final String FLAG_REMOVE_APPS_REFRESH_ON_RIGHT_CLICK = "com.android.launcher3.remove_apps_refresh_on_right_click"; - /** @hide */ - public static final String FLAG_REMOVE_EXCLUDE_FROM_SCREEN_MAGNIFICATION_FLAG_USAGE = "com.android.launcher3.remove_exclude_from_screen_magnification_flag_usage"; - /** @hide */ - public static final String FLAG_RESTORE_ARCHIVED_APP_ICONS_FROM_DB = "com.android.launcher3.restore_archived_app_icons_from_db"; - /** @hide */ - public static final String FLAG_RESTORE_ARCHIVED_SHORTCUTS = "com.android.launcher3.restore_archived_shortcuts"; - /** @hide */ - public static final String FLAG_SHOW_TASKBAR_PINNING_POPUP_FROM_ANYWHERE = "com.android.launcher3.show_taskbar_pinning_popup_from_anywhere"; - /** @hide */ - public static final String FLAG_SYNC_APP_LAUNCH_WITH_TASKBAR_STASH = "com.android.launcher3.sync_app_launch_with_taskbar_stash"; - /** @hide */ - public static final String FLAG_TASKBAR_OVERFLOW = "com.android.launcher3.taskbar_overflow"; - /** @hide */ - public static final String FLAG_TASKBAR_QUIET_MODE_CHANGE_SUPPORT = "com.android.launcher3.taskbar_quiet_mode_change_support"; - /** @hide */ public static final String FLAG_USE_ACTIVITY_OVERLAY = "com.android.launcher3.use_activity_overlay"; - /** @hide */ - public static final String FLAG_USE_NEW_ICON_FOR_ARCHIVED_APPS = "com.android.launcher3.use_new_icon_for_archived_apps"; - /** @hide */ - public static final String FLAG_USE_SYSTEM_RADIUS_FOR_APP_WIDGETS = "com.android.launcher3.use_system_radius_for_app_widgets"; - /** @hide */ - public static final String FLAG_WORK_SCHEDULER_IN_WORK_PROFILE = "com.android.launcher3.work_scheduler_in_work_profile"; - - - public static boolean accessibilityScrollOnAllapps() { - - return FEATURE_FLAGS.accessibilityScrollOnAllapps(); - } - - - public static boolean allAppsBlur() { - - return FEATURE_FLAGS.allAppsBlur(); - } - - - public static boolean allAppsSheetForHandheld() { - - return FEATURE_FLAGS.allAppsSheetForHandheld(); - } - - - public static boolean coordinateWorkspaceScale() { - - return FEATURE_FLAGS.coordinateWorkspaceScale(); - } - - - public static boolean enableActiveGestureProtoLog() { - - return FEATURE_FLAGS.enableActiveGestureProtoLog(); - } - public static boolean enableAddAppWidgetViaConfigActivityV2() { - return FEATURE_FLAGS.enableAddAppWidgetViaConfigActivityV2(); } - public static boolean enableAdditionalHomeAnimations() { - return FEATURE_FLAGS.enableAdditionalHomeAnimations(); } - - public static boolean enableAllAppsButtonInHotseat() { - - return FEATURE_FLAGS.enableAllAppsButtonInHotseat(); - } - - - public static boolean enableAltTabKqsFlatenning() { - - return FEATURE_FLAGS.enableAltTabKqsFlatenning(); - } - - - public static boolean enableAltTabKqsOnConnectedDisplays() { - - return FEATURE_FLAGS.enableAltTabKqsOnConnectedDisplays(); - } - - public static boolean enableCategorizedWidgetSuggestions() { - return FEATURE_FLAGS.enableCategorizedWidgetSuggestions(); } - - public static boolean enableContainerReturnAnimations() { - - return FEATURE_FLAGS.enableContainerReturnAnimations(); - } - - - public static boolean enableContrastTiles() { - - return FEATURE_FLAGS.enableContrastTiles(); - } - - public static boolean enableCursorHoverStates() { - return FEATURE_FLAGS.enableCursorHoverStates(); } - - public static boolean enableDesktopExplodedView() { - - return FEATURE_FLAGS.enableDesktopExplodedView(); - } - - - public static boolean enableDesktopTaskAlphaAnimation() { - - return FEATURE_FLAGS.enableDesktopTaskAlphaAnimation(); - } - - - public static boolean enableDesktopWindowingCarouselDetach() { - - return FEATURE_FLAGS.enableDesktopWindowingCarouselDetach(); - } - - - public static boolean enableDismissPredictionUndo() { - - return FEATURE_FLAGS.enableDismissPredictionUndo(); - } - - public static boolean enableExpandingPauseWorkButton() { - return FEATURE_FLAGS.enableExpandingPauseWorkButton(); } - - public static boolean enableExpressiveDismissTaskMotion() { - - return FEATURE_FLAGS.enableExpressiveDismissTaskMotion(); - } - - public static boolean enableFallbackOverviewInWindow() { - return FEATURE_FLAGS.enableFallbackOverviewInWindow(); } - public static boolean enableFirstScreenBroadcastArchivingExtras() { - return FEATURE_FLAGS.enableFirstScreenBroadcastArchivingExtras(); } - public static boolean enableFocusOutline() { - return FEATURE_FLAGS.enableFocusOutline(); } - public static boolean enableGeneratedPreviews() { - return FEATURE_FLAGS.enableGeneratedPreviews(); } - - public static boolean enableGestureNavHorizontalTouchSlop() { - - return FEATURE_FLAGS.enableGestureNavHorizontalTouchSlop(); - } - - - public static boolean enableGestureNavOnConnectedDisplays() { - - return FEATURE_FLAGS.enableGestureNavOnConnectedDisplays(); - } - - public static boolean enableGridMigrationFix() { - return FEATURE_FLAGS.enableGridMigrationFix(); } - public static boolean enableGridOnlyOverview() { - return FEATURE_FLAGS.enableGridOnlyOverview(); } - - public static boolean enableGrowthNudge() { - - return FEATURE_FLAGS.enableGrowthNudge(); - } - - public static boolean enableHandleDelayedGestureCallbacks() { - return FEATURE_FLAGS.enableHandleDelayedGestureCallbacks(); } - public static boolean enableHomeTransitionListener() { - return FEATURE_FLAGS.enableHomeTransitionListener(); } - - public static boolean enableHoverOfChildElementsInTaskview() { - - return FEATURE_FLAGS.enableHoverOfChildElementsInTaskview(); - } - - - public static boolean enableLargeDesktopWindowingTile() { - - return FEATURE_FLAGS.enableLargeDesktopWindowingTile(); - } - - public static boolean enableLauncherBrMetricsFixed() { - return FEATURE_FLAGS.enableLauncherBrMetricsFixed(); } - - public static boolean enableLauncherIconShapes() { - - return FEATURE_FLAGS.enableLauncherIconShapes(); - } - - - public static boolean enableLauncherOverviewInWindow() { - - return FEATURE_FLAGS.enableLauncherOverviewInWindow(); - } - - - public static boolean enableLauncherVisualRefresh() { - - return FEATURE_FLAGS.enableLauncherVisualRefresh(); - } - - - public static boolean enableMouseInteractionChanges() { - - return FEATURE_FLAGS.enableMouseInteractionChanges(); - } - - - public static boolean enableMultiInstanceMenuTaskbar() { - - return FEATURE_FLAGS.enableMultiInstanceMenuTaskbar(); - } - - public static boolean enableNarrowGridRestore() { - return FEATURE_FLAGS.enableNarrowGridRestore(); } - - public static boolean enableOverviewBackgroundWallpaperBlur() { - - return FEATURE_FLAGS.enableOverviewBackgroundWallpaperBlur(); - } - - - public static boolean enableOverviewCommandHelperTimeout() { - - return FEATURE_FLAGS.enableOverviewCommandHelperTimeout(); - } - - - public static boolean enableOverviewDesktopTileWallpaperBackground() { - - return FEATURE_FLAGS.enableOverviewDesktopTileWallpaperBackground(); - } - - public static boolean enableOverviewIconMenu() { - return FEATURE_FLAGS.enableOverviewIconMenu(); } - - public static boolean enableOverviewOnConnectedDisplays() { - - return FEATURE_FLAGS.enableOverviewOnConnectedDisplays(); - } - - - public static boolean enablePinningAppWithContextMenu() { - - return FEATURE_FLAGS.enablePinningAppWithContextMenu(); - } - - public static boolean enablePredictiveBackGesture() { - return FEATURE_FLAGS.enablePredictiveBackGesture(); } - public static boolean enablePrivateSpace() { - return FEATURE_FLAGS.enablePrivateSpace(); } - public static boolean enablePrivateSpaceInstallShortcut() { - return FEATURE_FLAGS.enablePrivateSpaceInstallShortcut(); } - public static boolean enableRebootUnlockAnimation() { - return FEATURE_FLAGS.enableRebootUnlockAnimation(); } - public static boolean enableRecentsInTaskbar() { - return FEATURE_FLAGS.enableRecentsInTaskbar(); } - - public static boolean enableRecentsWindowProtoLog() { - - return FEATURE_FLAGS.enableRecentsWindowProtoLog(); - } - - public static boolean enableRefactorTaskThumbnail() { - return FEATURE_FLAGS.enableRefactorTaskThumbnail(); } - public static boolean enableResponsiveWorkspace() { - return FEATURE_FLAGS.enableResponsiveWorkspace(); } - - public static boolean enableScalabilityForDesktopExperience() { - - return FEATURE_FLAGS.enableScalabilityForDesktopExperience(); - } - - public static boolean enableScalingRevealHomeAnimation() { - return FEATURE_FLAGS.enableScalingRevealHomeAnimation(); } - - public static boolean enableSeparateExternalDisplayTasks() { - - return FEATURE_FLAGS.enableSeparateExternalDisplayTasks(); - } - - public static boolean enableShortcutDontSuggestApp() { - return FEATURE_FLAGS.enableShortcutDontSuggestApp(); } - - public static boolean enableShowEnabledShortcutsInAccessibilityMenu() { - - return FEATURE_FLAGS.enableShowEnabledShortcutsInAccessibilityMenu(); - } - - public static boolean enableSmartspaceAsAWidget() { - return FEATURE_FLAGS.enableSmartspaceAsAWidget(); } - public static boolean enableSmartspaceRemovalToggle() { - return FEATURE_FLAGS.enableSmartspaceRemovalToggle(); } - - public static boolean enableStateManagerProtoLog() { - - return FEATURE_FLAGS.enableStateManagerProtoLog(); - } - - - public static boolean enableStrictMode() { - - return FEATURE_FLAGS.enableStrictMode(); - } - - public static boolean enableSupportForArchiving() { - return FEATURE_FLAGS.enableSupportForArchiving(); } - public static boolean enableTabletTwoPanePickerV2() { - return FEATURE_FLAGS.enableTabletTwoPanePickerV2(); } - - public static boolean enableTaskbarBehindShade() { - - return FEATURE_FLAGS.enableTaskbarBehindShade(); - } - - public static boolean enableTaskbarCustomization() { - return FEATURE_FLAGS.enableTaskbarCustomization(); } - - public static boolean enableTaskbarForDirectBoot() { - - return FEATURE_FLAGS.enableTaskbarForDirectBoot(); - } - - public static boolean enableTaskbarNoRecreate() { - return FEATURE_FLAGS.enableTaskbarNoRecreate(); } - public static boolean enableTaskbarPinning() { - return FEATURE_FLAGS.enableTaskbarPinning(); } - - public static boolean enableTieredWidgetsByDefaultInPicker() { - - return FEATURE_FLAGS.enableTieredWidgetsByDefaultInPicker(); - } - - public static boolean enableTwoPaneLauncherSettings() { - return FEATURE_FLAGS.enableTwoPaneLauncherSettings(); } - public static boolean enableTwolineAllapps() { - return FEATURE_FLAGS.enableTwolineAllapps(); } - public static boolean enableTwolineToggle() { - return FEATURE_FLAGS.enableTwolineToggle(); } - public static boolean enableUnfoldStateAnimation() { - return FEATURE_FLAGS.enableUnfoldStateAnimation(); } - public static boolean enableUnfoldedTwoPanePicker() { - return FEATURE_FLAGS.enableUnfoldedTwoPanePicker(); } - - public static boolean enableUseTopVisibleActivityForExcludeFromRecentTask() { - - return FEATURE_FLAGS.enableUseTopVisibleActivityForExcludeFromRecentTask(); - } - - public static boolean enableWidgetTapToAdd() { - return FEATURE_FLAGS.enableWidgetTapToAdd(); } - public static boolean enableWorkspaceInflation() { - return FEATURE_FLAGS.enableWorkspaceInflation(); } - public static boolean enabledFoldersInAllApps() { - return FEATURE_FLAGS.enabledFoldersInAllApps(); } - - public static boolean expressiveThemeInTaskbarAndNavigation() { - - return FEATURE_FLAGS.expressiveThemeInTaskbarAndNavigation(); - } - - - public static boolean extendibleThemeManager() { - - return FEATURE_FLAGS.extendibleThemeManager(); - } - - public static boolean floatingSearchBar() { - return FEATURE_FLAGS.floatingSearchBar(); } - public static boolean forceMonochromeAppIcons() { - return FEATURE_FLAGS.forceMonochromeAppIcons(); } - - public static boolean gridMigrationRefactor() { - - return FEATURE_FLAGS.gridMigrationRefactor(); - } - - - public static boolean gsfRes() { - - return FEATURE_FLAGS.gsfRes(); - } - - - public static boolean ignoreThreeFingerTrackpadForNavHandleLongPress() { - - return FEATURE_FLAGS.ignoreThreeFingerTrackpadForNavHandleLongPress(); - } - - - public static boolean letterFastScroller() { - - return FEATURE_FLAGS.letterFastScroller(); - } - - - public static boolean msdlFeedback() { - - return FEATURE_FLAGS.msdlFeedback(); - } - - - public static boolean multilineSearchBar() { - - return FEATURE_FLAGS.multilineSearchBar(); - } - - - public static boolean navigateToChildPreference() { - - return FEATURE_FLAGS.navigateToChildPreference(); - } - - - public static boolean oneGridMountedMode() { - - return FEATURE_FLAGS.oneGridMountedMode(); - } - - - public static boolean oneGridRotationHandling() { - - return FEATURE_FLAGS.oneGridRotationHandling(); - } - - - public static boolean oneGridSpecs() { - - return FEATURE_FLAGS.oneGridSpecs(); - } - - - public static boolean predictiveBackToHomeBlur() { - - return FEATURE_FLAGS.predictiveBackToHomeBlur(); - } - - - public static boolean predictiveBackToHomePolish() { - - return FEATURE_FLAGS.predictiveBackToHomePolish(); - } - - public static boolean privateSpaceAddFloatingMaskView() { - return FEATURE_FLAGS.privateSpaceAddFloatingMaskView(); } - public static boolean privateSpaceAnimation() { - return FEATURE_FLAGS.privateSpaceAnimation(); } - public static boolean privateSpaceAppInstallerButton() { - return FEATURE_FLAGS.privateSpaceAppInstallerButton(); } - public static boolean privateSpaceRestrictAccessibilityDrag() { - return FEATURE_FLAGS.privateSpaceRestrictAccessibilityDrag(); } - public static boolean privateSpaceRestrictItemDrag() { - return FEATURE_FLAGS.privateSpaceRestrictItemDrag(); } - public static boolean privateSpaceSysAppsSeparation() { - return FEATURE_FLAGS.privateSpaceSysAppsSeparation(); } - - public static boolean removeAppsRefreshOnRightClick() { - - return FEATURE_FLAGS.removeAppsRefreshOnRightClick(); - } - - - public static boolean removeExcludeFromScreenMagnificationFlagUsage() { - - return FEATURE_FLAGS.removeExcludeFromScreenMagnificationFlagUsage(); - } - - - public static boolean restoreArchivedAppIconsFromDb() { - - return FEATURE_FLAGS.restoreArchivedAppIconsFromDb(); - } - - - public static boolean restoreArchivedShortcuts() { - - return FEATURE_FLAGS.restoreArchivedShortcuts(); - } - - - public static boolean showTaskbarPinningPopupFromAnywhere() { - - return FEATURE_FLAGS.showTaskbarPinningPopupFromAnywhere(); - } - - - public static boolean syncAppLaunchWithTaskbarStash() { - - return FEATURE_FLAGS.syncAppLaunchWithTaskbarStash(); - } - - - public static boolean taskbarOverflow() { - - return FEATURE_FLAGS.taskbarOverflow(); - } - - - public static boolean taskbarQuietModeChangeSupport() { - - return FEATURE_FLAGS.taskbarQuietModeChangeSupport(); - } - - public static boolean useActivityOverlay() { - return FEATURE_FLAGS.useActivityOverlay(); } - - public static boolean useNewIconForArchivedApps() { - - return FEATURE_FLAGS.useNewIconForArchivedApps(); - } - - - public static boolean useSystemRadiusForAppWidgets() { - - return FEATURE_FLAGS.useSystemRadiusForAppWidgets(); - } - - - public static boolean workSchedulerInWorkProfile() { - - return FEATURE_FLAGS.workSchedulerInWorkProfile(); - } - private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl(); -} +} \ No newline at end of file diff --git a/flags/src/com/android/quickstep/util/DeviceConfigHelper.kt b/flags/src/com/android/quickstep/util/DeviceConfigHelper.kt index d8716a5e8d..a8a2991fd1 100644 --- a/flags/src/com/android/quickstep/util/DeviceConfigHelper.kt +++ b/flags/src/com/android/quickstep/util/DeviceConfigHelper.kt @@ -20,72 +20,61 @@ import android.app.ActivityThread import android.content.Context import android.content.SharedPreferences import android.content.SharedPreferences.OnSharedPreferenceChangeListener -import android.provider.DeviceConfig -import android.provider.DeviceConfig.OnPropertiesChangedListener -import android.provider.DeviceConfig.Properties import androidx.annotation.WorkerThread -import java.util.concurrent.CopyOnWriteArrayList /** Utility class to manage a set of device configurations */ class DeviceConfigHelper(private val factory: (PropReader) -> ConfigType) { var config: ConfigType private set - private val allKeys: Set - private val propertiesListener = OnPropertiesChangedListener { onDevicePropsChanges(it) } private val sharedPrefChangeListener = OnSharedPreferenceChangeListener { _, _ -> recreateConfig() } - private val changeListeners = CopyOnWriteArrayList() + private val changeListeners = mutableListOf() init { // Initialize the default config once. allKeys = HashSet() - config = - factory( - PropReader( - object : PropProvider { - override fun get(key: String, fallback: T): T { - val prefs = prefs - if (fallback is Int) { - allKeys.add(key) - return prefs.getInt(key, fallback) as T - } else if (fallback is Boolean) { - allKeys.add(key) - return prefs.getBoolean(key, fallback) as T - } else return fallback + config = factory( + PropReader( + object : PropProvider { + override fun get(key: String, fallback: T): T { + val prefs = prefs + allKeys.add(key) + return when (fallback) { + is Int -> prefs.getInt(key, fallback) as T + is Boolean -> prefs.getBoolean(key, fallback) as T + else -> fallback } } - ) + } ) + ) + prefs.registerOnSharedPreferenceChangeListener(sharedPrefChangeListener) } @WorkerThread - private fun onDevicePropsChanges(properties: Properties) { - if (NAMESPACE_LAUNCHER != properties.namespace) return - if (!allKeys.any(properties.keyset::contains)) return + private fun onDevicePropsChanges() { recreateConfig() } private fun recreateConfig() { - val myProps = - DeviceConfig.getProperties(NAMESPACE_LAUNCHER, *allKeys.toTypedArray()) - config = - factory( - PropReader( - object : PropProvider { - override fun get(key: String, fallback: T): T { - if (fallback is Int) return myProps.getInt(key, fallback) as T - else if (fallback is Boolean) - return myProps.getBoolean(key, fallback) as T - else return fallback + config = factory( + PropReader( + object : PropProvider { + override fun get(key: String, fallback: T): T { + return when (fallback) { + is Int -> prefs.getInt(key, fallback) as T + is Boolean -> prefs.getBoolean(key, fallback) as T + else -> fallback } } - ) + } ) + ) } /** Adds a listener for property changes */ @@ -95,7 +84,6 @@ class DeviceConfigHelper(private val factory: (PropReader) -> Config fun removeChangeListener(r: Runnable) = changeListeners.remove(r) fun close() { - DeviceConfig.removeOnPropertiesChangedListener(propertiesListener) prefs.unregisterOnSharedPreferenceChangeListener(sharedPrefChangeListener) } diff --git a/flags/src/com/android/systemui/CustomFeatureFlags.java b/flags/src/com/android/systemui/CustomFeatureFlags.java index 8dbbac2755..bba7a59f65 100644 --- a/flags/src/com/android/systemui/CustomFeatureFlags.java +++ b/flags/src/com/android/systemui/CustomFeatureFlags.java @@ -7,6 +7,7 @@ import java.util.List; import java.util.Set; import java.util.function.BiPredicate; import java.util.function.Predicate; + /** @hide */ public class CustomFeatureFlags implements FeatureFlags { @@ -16,1974 +17,925 @@ public class CustomFeatureFlags implements FeatureFlags { mGetValueImpl = getValueImpl; } @Override - public boolean activityTransitionUseLargestWindow() { return getValue(Flags.FLAG_ACTIVITY_TRANSITION_USE_LARGEST_WINDOW, - FeatureFlags::activityTransitionUseLargestWindow); + FeatureFlags::activityTransitionUseLargestWindow); } @Override - - public boolean addBlackBackgroundForWindowMagnifier() { - return getValue(Flags.FLAG_ADD_BLACK_BACKGROUND_FOR_WINDOW_MAGNIFIER, - FeatureFlags::addBlackBackgroundForWindowMagnifier); - } - - @Override - - public boolean alwaysComposeQsUiFragment() { - return getValue(Flags.FLAG_ALWAYS_COMPOSE_QS_UI_FRAGMENT, - FeatureFlags::alwaysComposeQsUiFragment); - } - - @Override - public boolean ambientTouchMonitorListenToDisplayChanges() { return getValue(Flags.FLAG_AMBIENT_TOUCH_MONITOR_LISTEN_TO_DISPLAY_CHANGES, - FeatureFlags::ambientTouchMonitorListenToDisplayChanges); + FeatureFlags::ambientTouchMonitorListenToDisplayChanges); } @Override - public boolean appClipsBacklinks() { return getValue(Flags.FLAG_APP_CLIPS_BACKLINKS, - FeatureFlags::appClipsBacklinks); + FeatureFlags::appClipsBacklinks); } @Override - - public boolean appShortcutRemovalFix() { - return getValue(Flags.FLAG_APP_SHORTCUT_REMOVAL_FIX, - FeatureFlags::appShortcutRemovalFix); - } - - @Override - - public boolean avalancheReplaceHunWhenCritical() { - return getValue(Flags.FLAG_AVALANCHE_REPLACE_HUN_WHEN_CRITICAL, - FeatureFlags::avalancheReplaceHunWhenCritical); - } - - @Override - public boolean bindKeyguardMediaVisibility() { return getValue(Flags.FLAG_BIND_KEYGUARD_MEDIA_VISIBILITY, - FeatureFlags::bindKeyguardMediaVisibility); + FeatureFlags::bindKeyguardMediaVisibility); } @Override - - public boolean bouncerUiRevamp() { - return getValue(Flags.FLAG_BOUNCER_UI_REVAMP, - FeatureFlags::bouncerUiRevamp); + public boolean bpTalkback() { + return getValue(Flags.FLAG_BP_TALKBACK, + FeatureFlags::bpTalkback); } @Override - - public boolean bouncerUiRevamp2() { - return getValue(Flags.FLAG_BOUNCER_UI_REVAMP_2, - FeatureFlags::bouncerUiRevamp2); - } - - @Override - - public boolean bpColors() { - return getValue(Flags.FLAG_BP_COLORS, - FeatureFlags::bpColors); - } - - @Override - public boolean brightnessSliderFocusState() { return getValue(Flags.FLAG_BRIGHTNESS_SLIDER_FOCUS_STATE, - FeatureFlags::brightnessSliderFocusState); + FeatureFlags::brightnessSliderFocusState); } @Override - - public boolean checkLockscreenGoneTransition() { - return getValue(Flags.FLAG_CHECK_LOCKSCREEN_GONE_TRANSITION, - FeatureFlags::checkLockscreenGoneTransition); + public boolean centralizedStatusBarHeightFix() { + return getValue(Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX, + FeatureFlags::centralizedStatusBarHeightFix); } @Override - - public boolean classicFlagsMultiUser() { - return getValue(Flags.FLAG_CLASSIC_FLAGS_MULTI_USER, - FeatureFlags::classicFlagsMultiUser); - } - - @Override - - public boolean clipboardImageTimeout() { - return getValue(Flags.FLAG_CLIPBOARD_IMAGE_TIMEOUT, - FeatureFlags::clipboardImageTimeout); - } - - @Override - public boolean clipboardNoninteractiveOnLockscreen() { return getValue(Flags.FLAG_CLIPBOARD_NONINTERACTIVE_ON_LOCKSCREEN, - FeatureFlags::clipboardNoninteractiveOnLockscreen); + FeatureFlags::clipboardNoninteractiveOnLockscreen); } @Override - - public boolean clipboardOverlayMultiuser() { - return getValue(Flags.FLAG_CLIPBOARD_OVERLAY_MULTIUSER, - FeatureFlags::clipboardOverlayMultiuser); + public boolean clockReactiveVariants() { + return getValue(Flags.FLAG_CLOCK_REACTIVE_VARIANTS, + FeatureFlags::clockReactiveVariants); } @Override - - public boolean clipboardSharedTransitions() { - return getValue(Flags.FLAG_CLIPBOARD_SHARED_TRANSITIONS, - FeatureFlags::clipboardSharedTransitions); - } - - @Override - - public boolean clipboardUseDescriptionMimetype() { - return getValue(Flags.FLAG_CLIPBOARD_USE_DESCRIPTION_MIMETYPE, - FeatureFlags::clipboardUseDescriptionMimetype); - } - - @Override - - public boolean clockFidgetAnimation() { - return getValue(Flags.FLAG_CLOCK_FIDGET_ANIMATION, - FeatureFlags::clockFidgetAnimation); - } - - @Override - public boolean communalBouncerDoNotModifyPluginOpen() { return getValue(Flags.FLAG_COMMUNAL_BOUNCER_DO_NOT_MODIFY_PLUGIN_OPEN, - FeatureFlags::communalBouncerDoNotModifyPluginOpen); + FeatureFlags::communalBouncerDoNotModifyPluginOpen); } @Override - - public boolean communalEditWidgetsActivityFinishFix() { - return getValue(Flags.FLAG_COMMUNAL_EDIT_WIDGETS_ACTIVITY_FINISH_FIX, - FeatureFlags::communalEditWidgetsActivityFinishFix); - } - - @Override - public boolean communalHub() { return getValue(Flags.FLAG_COMMUNAL_HUB, - FeatureFlags::communalHub); + FeatureFlags::communalHub); } @Override - - public boolean communalHubUseThreadPoolForWidgets() { - return getValue(Flags.FLAG_COMMUNAL_HUB_USE_THREAD_POOL_FOR_WIDGETS, - FeatureFlags::communalHubUseThreadPoolForWidgets); - } - - @Override - - public boolean communalResponsiveGrid() { - return getValue(Flags.FLAG_COMMUNAL_RESPONSIVE_GRID, - FeatureFlags::communalResponsiveGrid); - } - - @Override - - public boolean communalSceneKtfRefactor() { - return getValue(Flags.FLAG_COMMUNAL_SCENE_KTF_REFACTOR, - FeatureFlags::communalSceneKtfRefactor); - } - - @Override - - public boolean communalStandaloneSupport() { - return getValue(Flags.FLAG_COMMUNAL_STANDALONE_SUPPORT, - FeatureFlags::communalStandaloneSupport); - } - - @Override - - public boolean communalTimerFlickerFix() { - return getValue(Flags.FLAG_COMMUNAL_TIMER_FLICKER_FIX, - FeatureFlags::communalTimerFlickerFix); - } - - @Override - - public boolean communalWidgetResizing() { - return getValue(Flags.FLAG_COMMUNAL_WIDGET_RESIZING, - FeatureFlags::communalWidgetResizing); - } - - @Override - - public boolean communalWidgetTrampolineFix() { - return getValue(Flags.FLAG_COMMUNAL_WIDGET_TRAMPOLINE_FIX, - FeatureFlags::communalWidgetTrampolineFix); - } - - @Override - public boolean composeBouncer() { return getValue(Flags.FLAG_COMPOSE_BOUNCER, - FeatureFlags::composeBouncer); + FeatureFlags::composeBouncer); } @Override + public boolean composeLockscreen() { + return getValue(Flags.FLAG_COMPOSE_LOCKSCREEN, + FeatureFlags::composeLockscreen); + } + @Override public boolean confineNotificationTouchToViewWidth() { return getValue(Flags.FLAG_CONFINE_NOTIFICATION_TOUCH_TO_VIEW_WIDTH, - FeatureFlags::confineNotificationTouchToViewWidth); + FeatureFlags::confineNotificationTouchToViewWidth); } @Override - - public boolean contAuthPlugin() { - return getValue(Flags.FLAG_CONT_AUTH_PLUGIN, - FeatureFlags::contAuthPlugin); + public boolean constraintBp() { + return getValue(Flags.FLAG_CONSTRAINT_BP, + FeatureFlags::constraintBp); } @Override - public boolean contextualTipsAssistantDismissFix() { return getValue(Flags.FLAG_CONTEXTUAL_TIPS_ASSISTANT_DISMISS_FIX, - FeatureFlags::contextualTipsAssistantDismissFix); + FeatureFlags::contextualTipsAssistantDismissFix); } @Override - public boolean coroutineTracing() { return getValue(Flags.FLAG_COROUTINE_TRACING, - FeatureFlags::coroutineTracing); + FeatureFlags::coroutineTracing); } @Override - public boolean createWindowlessWindowMagnifier() { return getValue(Flags.FLAG_CREATE_WINDOWLESS_WINDOW_MAGNIFIER, - FeatureFlags::createWindowlessWindowMagnifier); + FeatureFlags::createWindowlessWindowMagnifier); } @Override - - public boolean debugLiveUpdatesPromoteAll() { - return getValue(Flags.FLAG_DEBUG_LIVE_UPDATES_PROMOTE_ALL, - FeatureFlags::debugLiveUpdatesPromoteAll); + public boolean dedicatedNotifInflationThread() { + return getValue(Flags.FLAG_DEDICATED_NOTIF_INFLATION_THREAD, + FeatureFlags::dedicatedNotifInflationThread); } @Override - - public boolean decoupleViewControllerInAnimlib() { - return getValue(Flags.FLAG_DECOUPLE_VIEW_CONTROLLER_IN_ANIMLIB, - FeatureFlags::decoupleViewControllerInAnimlib); - } - - @Override - public boolean delayShowMagnificationButton() { return getValue(Flags.FLAG_DELAY_SHOW_MAGNIFICATION_BUTTON, - FeatureFlags::delayShowMagnificationButton); + FeatureFlags::delayShowMagnificationButton); } @Override - - public boolean desktopEffectsQsTile() { - return getValue(Flags.FLAG_DESKTOP_EFFECTS_QS_TILE, - FeatureFlags::desktopEffectsQsTile); + public boolean delayedWakelockReleaseOnBackgroundThread() { + return getValue(Flags.FLAG_DELAYED_WAKELOCK_RELEASE_ON_BACKGROUND_THREAD, + FeatureFlags::delayedWakelockReleaseOnBackgroundThread); } @Override - public boolean deviceEntryUdfpsRefactor() { return getValue(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR, - FeatureFlags::deviceEntryUdfpsRefactor); + FeatureFlags::deviceEntryUdfpsRefactor); } @Override - - public boolean disableBlurredShadeVisible() { - return getValue(Flags.FLAG_DISABLE_BLURRED_SHADE_VISIBLE, - FeatureFlags::disableBlurredShadeVisible); - } - - @Override - public boolean disableContextualTipsFrequencyCheck() { return getValue(Flags.FLAG_DISABLE_CONTEXTUAL_TIPS_FREQUENCY_CHECK, - FeatureFlags::disableContextualTipsFrequencyCheck); + FeatureFlags::disableContextualTipsFrequencyCheck); } @Override - public boolean disableContextualTipsIosSwitcherCheck() { return getValue(Flags.FLAG_DISABLE_CONTEXTUAL_TIPS_IOS_SWITCHER_CHECK, - FeatureFlags::disableContextualTipsIosSwitcherCheck); + FeatureFlags::disableContextualTipsIosSwitcherCheck); } @Override - - public boolean disableShadeTrackpadTwoFingerSwipe() { - return getValue(Flags.FLAG_DISABLE_SHADE_TRACKPAD_TWO_FINGER_SWIPE, - FeatureFlags::disableShadeTrackpadTwoFingerSwipe); + public boolean dozeuiSchedulingAlarmsBackgroundExecution() { + return getValue(Flags.FLAG_DOZEUI_SCHEDULING_ALARMS_BACKGROUND_EXECUTION, + FeatureFlags::dozeuiSchedulingAlarmsBackgroundExecution); } @Override - - public boolean doubleTapToSleep() { - return getValue(Flags.FLAG_DOUBLE_TAP_TO_SLEEP, - FeatureFlags::doubleTapToSleep); - } - - @Override - public boolean dreamInputSessionPilferOnce() { return getValue(Flags.FLAG_DREAM_INPUT_SESSION_PILFER_ONCE, - FeatureFlags::dreamInputSessionPilferOnce); + FeatureFlags::dreamInputSessionPilferOnce); } @Override - public boolean dreamOverlayBouncerSwipeDirectionFiltering() { return getValue(Flags.FLAG_DREAM_OVERLAY_BOUNCER_SWIPE_DIRECTION_FILTERING, - FeatureFlags::dreamOverlayBouncerSwipeDirectionFiltering); + FeatureFlags::dreamOverlayBouncerSwipeDirectionFiltering); } @Override - - public boolean dreamOverlayUpdatedFont() { - return getValue(Flags.FLAG_DREAM_OVERLAY_UPDATED_FONT, - FeatureFlags::dreamOverlayUpdatedFont); + public boolean dualShade() { + return getValue(Flags.FLAG_DUAL_SHADE, + FeatureFlags::dualShade); } @Override - public boolean edgeBackGestureHandlerThread() { return getValue(Flags.FLAG_EDGE_BACK_GESTURE_HANDLER_THREAD, - FeatureFlags::edgeBackGestureHandlerThread); + FeatureFlags::edgeBackGestureHandlerThread); } @Override - public boolean edgebackGestureHandlerGetRunningTasksBackground() { return getValue(Flags.FLAG_EDGEBACK_GESTURE_HANDLER_GET_RUNNING_TASKS_BACKGROUND, - FeatureFlags::edgebackGestureHandlerGetRunningTasksBackground); + FeatureFlags::edgebackGestureHandlerGetRunningTasksBackground); } @Override - public boolean enableBackgroundKeyguardOndrawnCallback() { return getValue(Flags.FLAG_ENABLE_BACKGROUND_KEYGUARD_ONDRAWN_CALLBACK, - FeatureFlags::enableBackgroundKeyguardOndrawnCallback); + FeatureFlags::enableBackgroundKeyguardOndrawnCallback); } @Override - public boolean enableContextualTipForMuteVolume() { return getValue(Flags.FLAG_ENABLE_CONTEXTUAL_TIP_FOR_MUTE_VOLUME, - FeatureFlags::enableContextualTipForMuteVolume); + FeatureFlags::enableContextualTipForMuteVolume); } @Override - public boolean enableContextualTipForPowerOff() { return getValue(Flags.FLAG_ENABLE_CONTEXTUAL_TIP_FOR_POWER_OFF, - FeatureFlags::enableContextualTipForPowerOff); + FeatureFlags::enableContextualTipForPowerOff); } @Override - public boolean enableContextualTipForTakeScreenshot() { return getValue(Flags.FLAG_ENABLE_CONTEXTUAL_TIP_FOR_TAKE_SCREENSHOT, - FeatureFlags::enableContextualTipForTakeScreenshot); + FeatureFlags::enableContextualTipForTakeScreenshot); } @Override - public boolean enableContextualTips() { return getValue(Flags.FLAG_ENABLE_CONTEXTUAL_TIPS, - FeatureFlags::enableContextualTips); + FeatureFlags::enableContextualTips); } @Override - public boolean enableEfficientDisplayRepository() { return getValue(Flags.FLAG_ENABLE_EFFICIENT_DISPLAY_REPOSITORY, - FeatureFlags::enableEfficientDisplayRepository); + FeatureFlags::enableEfficientDisplayRepository); } @Override - public boolean enableLayoutTracing() { return getValue(Flags.FLAG_ENABLE_LAYOUT_TRACING, - FeatureFlags::enableLayoutTracing); + FeatureFlags::enableLayoutTracing); } @Override - - public boolean enableUnderlay() { - return getValue(Flags.FLAG_ENABLE_UNDERLAY, - FeatureFlags::enableUnderlay); - } - - @Override - public boolean enableViewCaptureTracing() { return getValue(Flags.FLAG_ENABLE_VIEW_CAPTURE_TRACING, - FeatureFlags::enableViewCaptureTracing); + FeatureFlags::enableViewCaptureTracing); } @Override + public boolean enableWidgetPickerSizeFilter() { + return getValue(Flags.FLAG_ENABLE_WIDGET_PICKER_SIZE_FILTER, + FeatureFlags::enableWidgetPickerSizeFilter); + } + @Override public boolean enforceBrightnessBaseUserRestriction() { return getValue(Flags.FLAG_ENFORCE_BRIGHTNESS_BASE_USER_RESTRICTION, - FeatureFlags::enforceBrightnessBaseUserRestriction); + FeatureFlags::enforceBrightnessBaseUserRestriction); } @Override - public boolean exampleFlag() { return getValue(Flags.FLAG_EXAMPLE_FLAG, - FeatureFlags::exampleFlag); + FeatureFlags::exampleFlag); } @Override - - public boolean expandCollapsePrivacyDialog() { - return getValue(Flags.FLAG_EXPAND_COLLAPSE_PRIVACY_DIALOG, - FeatureFlags::expandCollapsePrivacyDialog); + public boolean fastUnlockTransition() { + return getValue(Flags.FLAG_FAST_UNLOCK_TRANSITION, + FeatureFlags::fastUnlockTransition); } @Override - - public boolean expandHeadsUpOnInlineReply() { - return getValue(Flags.FLAG_EXPAND_HEADS_UP_ON_INLINE_REPLY, - FeatureFlags::expandHeadsUpOnInlineReply); - } - - @Override - - public boolean expandedPrivacyIndicatorsOnLargeScreen() { - return getValue(Flags.FLAG_EXPANDED_PRIVACY_INDICATORS_ON_LARGE_SCREEN, - FeatureFlags::expandedPrivacyIndicatorsOnLargeScreen); - } - - @Override - - public boolean extendedAppsShortcutCategory() { - return getValue(Flags.FLAG_EXTENDED_APPS_SHORTCUT_CATEGORY, - FeatureFlags::extendedAppsShortcutCategory); - } - - @Override - - public boolean faceMessageDeferUpdate() { - return getValue(Flags.FLAG_FACE_MESSAGE_DEFER_UPDATE, - FeatureFlags::faceMessageDeferUpdate); - } - - @Override - - public boolean faceScanningAnimationNpeFix() { - return getValue(Flags.FLAG_FACE_SCANNING_ANIMATION_NPE_FIX, - FeatureFlags::faceScanningAnimationNpeFix); - } - - @Override - - public boolean fasterUnlockTransition() { - return getValue(Flags.FLAG_FASTER_UNLOCK_TRANSITION, - FeatureFlags::fasterUnlockTransition); - } - - @Override - - public boolean fetchBookmarksXmlKeyboardShortcuts() { - return getValue(Flags.FLAG_FETCH_BOOKMARKS_XML_KEYBOARD_SHORTCUTS, - FeatureFlags::fetchBookmarksXmlKeyboardShortcuts); - } - - @Override - public boolean fixImageWallpaperCrashSurfaceAlreadyReleased() { return getValue(Flags.FLAG_FIX_IMAGE_WALLPAPER_CRASH_SURFACE_ALREADY_RELEASED, - FeatureFlags::fixImageWallpaperCrashSurfaceAlreadyReleased); + FeatureFlags::fixImageWallpaperCrashSurfaceAlreadyReleased); } @Override - public boolean fixScreenshotActionDismissSystemWindows() { return getValue(Flags.FLAG_FIX_SCREENSHOT_ACTION_DISMISS_SYSTEM_WINDOWS, - FeatureFlags::fixScreenshotActionDismissSystemWindows); + FeatureFlags::fixScreenshotActionDismissSystemWindows); } @Override - public boolean floatingMenuAnimatedTuck() { return getValue(Flags.FLAG_FLOATING_MENU_ANIMATED_TUCK, - FeatureFlags::floatingMenuAnimatedTuck); + FeatureFlags::floatingMenuAnimatedTuck); } @Override - - public boolean floatingMenuDisplayCutoutSupport() { - return getValue(Flags.FLAG_FLOATING_MENU_DISPLAY_CUTOUT_SUPPORT, - FeatureFlags::floatingMenuDisplayCutoutSupport); - } - - @Override - public boolean floatingMenuDragToEdit() { return getValue(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT, - FeatureFlags::floatingMenuDragToEdit); + FeatureFlags::floatingMenuDragToEdit); } @Override - public boolean floatingMenuDragToHide() { return getValue(Flags.FLAG_FLOATING_MENU_DRAG_TO_HIDE, - FeatureFlags::floatingMenuDragToHide); + FeatureFlags::floatingMenuDragToHide); } @Override - - public boolean floatingMenuHearingDeviceStatusIcon() { - return getValue(Flags.FLAG_FLOATING_MENU_HEARING_DEVICE_STATUS_ICON, - FeatureFlags::floatingMenuHearingDeviceStatusIcon); - } - - @Override - public boolean floatingMenuImeDisplacementAnimation() { return getValue(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION, - FeatureFlags::floatingMenuImeDisplacementAnimation); + FeatureFlags::floatingMenuImeDisplacementAnimation); } @Override - public boolean floatingMenuNarrowTargetContentObserver() { return getValue(Flags.FLAG_FLOATING_MENU_NARROW_TARGET_CONTENT_OBSERVER, - FeatureFlags::floatingMenuNarrowTargetContentObserver); + FeatureFlags::floatingMenuNarrowTargetContentObserver); } @Override - - public boolean floatingMenuNotifyTargetsChangedOnStrictDiff() { - return getValue(Flags.FLAG_FLOATING_MENU_NOTIFY_TARGETS_CHANGED_ON_STRICT_DIFF, - FeatureFlags::floatingMenuNotifyTargetsChangedOnStrictDiff); - } - - @Override - public boolean floatingMenuOverlapsNavBarsFlag() { return getValue(Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG, - FeatureFlags::floatingMenuOverlapsNavBarsFlag); + FeatureFlags::floatingMenuOverlapsNavBarsFlag); } @Override - public boolean floatingMenuRadiiAnimation() { return getValue(Flags.FLAG_FLOATING_MENU_RADII_ANIMATION, - FeatureFlags::floatingMenuRadiiAnimation); + FeatureFlags::floatingMenuRadiiAnimation); + } + + @Override + public boolean generatedPreviews() { + return getValue(Flags.FLAG_GENERATED_PREVIEWS, + FeatureFlags::generatedPreviews); } @Override - public boolean getConnectedDeviceNameUnsynchronized() { return getValue(Flags.FLAG_GET_CONNECTED_DEVICE_NAME_UNSYNCHRONIZED, - FeatureFlags::getConnectedDeviceNameUnsynchronized); + FeatureFlags::getConnectedDeviceNameUnsynchronized); } @Override - public boolean glanceableHubAllowKeyguardWhenDreaming() { return getValue(Flags.FLAG_GLANCEABLE_HUB_ALLOW_KEYGUARD_WHEN_DREAMING, - FeatureFlags::glanceableHubAllowKeyguardWhenDreaming); + FeatureFlags::glanceableHubAllowKeyguardWhenDreaming); } @Override - - public boolean glanceableHubBlurredBackground() { - return getValue(Flags.FLAG_GLANCEABLE_HUB_BLURRED_BACKGROUND, - FeatureFlags::glanceableHubBlurredBackground); + public boolean glanceableHubFullscreenSwipe() { + return getValue(Flags.FLAG_GLANCEABLE_HUB_FULLSCREEN_SWIPE, + FeatureFlags::glanceableHubFullscreenSwipe); } @Override - - public boolean glanceableHubDirectEditMode() { - return getValue(Flags.FLAG_GLANCEABLE_HUB_DIRECT_EDIT_MODE, - FeatureFlags::glanceableHubDirectEditMode); + public boolean glanceableHubGestureHandle() { + return getValue(Flags.FLAG_GLANCEABLE_HUB_GESTURE_HANDLE, + FeatureFlags::glanceableHubGestureHandle); } @Override - - public boolean glanceableHubV2() { - return getValue(Flags.FLAG_GLANCEABLE_HUB_V2, - FeatureFlags::glanceableHubV2); + public boolean glanceableHubShortcutButton() { + return getValue(Flags.FLAG_GLANCEABLE_HUB_SHORTCUT_BUTTON, + FeatureFlags::glanceableHubShortcutButton); } @Override - - public boolean glanceableHubV2Resources() { - return getValue(Flags.FLAG_GLANCEABLE_HUB_V2_RESOURCES, - FeatureFlags::glanceableHubV2Resources); + public boolean hapticBrightnessSlider() { + return getValue(Flags.FLAG_HAPTIC_BRIGHTNESS_SLIDER, + FeatureFlags::hapticBrightnessSlider); } @Override - - public boolean hapticsForComposeSliders() { - return getValue(Flags.FLAG_HAPTICS_FOR_COMPOSE_SLIDERS, - FeatureFlags::hapticsForComposeSliders); + public boolean hapticVolumeSlider() { + return getValue(Flags.FLAG_HAPTIC_VOLUME_SLIDER, + FeatureFlags::hapticVolumeSlider); } @Override - - public boolean hardwareColorStyles() { - return getValue(Flags.FLAG_HARDWARE_COLOR_STYLES, - FeatureFlags::hardwareColorStyles); - } - - @Override - public boolean hearingAidsQsTileDialog() { return getValue(Flags.FLAG_HEARING_AIDS_QS_TILE_DIALOG, - FeatureFlags::hearingAidsQsTileDialog); + FeatureFlags::hearingAidsQsTileDialog); } @Override - public boolean hearingDevicesDialogRelatedTools() { return getValue(Flags.FLAG_HEARING_DEVICES_DIALOG_RELATED_TOOLS, - FeatureFlags::hearingDevicesDialogRelatedTools); + FeatureFlags::hearingDevicesDialogRelatedTools); } @Override - - public boolean hideRingerButtonInSingleVolumeMode() { - return getValue(Flags.FLAG_HIDE_RINGER_BUTTON_IN_SINGLE_VOLUME_MODE, - FeatureFlags::hideRingerButtonInSingleVolumeMode); - } - - @Override - - public boolean homeControlsDreamHsum() { - return getValue(Flags.FLAG_HOME_CONTROLS_DREAM_HSUM, - FeatureFlags::homeControlsDreamHsum); - } - - @Override - - public boolean hubEditModeTouchAdjustments() { - return getValue(Flags.FLAG_HUB_EDIT_MODE_TOUCH_ADJUSTMENTS, - FeatureFlags::hubEditModeTouchAdjustments); - } - - @Override - - public boolean hubmodeFullscreenVerticalSwipe() { - return getValue(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE, - FeatureFlags::hubmodeFullscreenVerticalSwipe); - } - - @Override - - public boolean hubmodeFullscreenVerticalSwipeFix() { - return getValue(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX, - FeatureFlags::hubmodeFullscreenVerticalSwipeFix); - } - - @Override - - public boolean iconRefresh2025() { - return getValue(Flags.FLAG_ICON_REFRESH_2025, - FeatureFlags::iconRefresh2025); - } - - @Override - - public boolean ignoreTouchesNextToNotificationShelf() { - return getValue(Flags.FLAG_IGNORE_TOUCHES_NEXT_TO_NOTIFICATION_SHELF, - FeatureFlags::ignoreTouchesNextToNotificationShelf); - } - - @Override - - public boolean indicationTextA11yFix() { - return getValue(Flags.FLAG_INDICATION_TEXT_A11Y_FIX, - FeatureFlags::indicationTextA11yFix); - } - - @Override - public boolean keyboardDockingIndicator() { return getValue(Flags.FLAG_KEYBOARD_DOCKING_INDICATOR, - FeatureFlags::keyboardDockingIndicator); + FeatureFlags::keyboardDockingIndicator); } @Override - public boolean keyboardShortcutHelperRewrite() { return getValue(Flags.FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE, - FeatureFlags::keyboardShortcutHelperRewrite); + FeatureFlags::keyboardShortcutHelperRewrite); } @Override - - public boolean keyboardShortcutHelperShortcutCustomizer() { - return getValue(Flags.FLAG_KEYBOARD_SHORTCUT_HELPER_SHORTCUT_CUSTOMIZER, - FeatureFlags::keyboardShortcutHelperShortcutCustomizer); + public boolean keyguardBottomAreaRefactor() { + return getValue(Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR, + FeatureFlags::keyguardBottomAreaRefactor); } @Override - - public boolean keyboardTouchpadContextualEducation() { - return getValue(Flags.FLAG_KEYBOARD_TOUCHPAD_CONTEXTUAL_EDUCATION, - FeatureFlags::keyboardTouchpadContextualEducation); - } - - @Override - - public boolean keyguardTransitionForceFinishOnScreenOff() { - return getValue(Flags.FLAG_KEYGUARD_TRANSITION_FORCE_FINISH_ON_SCREEN_OFF, - FeatureFlags::keyguardTransitionForceFinishOnScreenOff); - } - - @Override - - public boolean keyguardWmReorderAtmsCalls() { - return getValue(Flags.FLAG_KEYGUARD_WM_REORDER_ATMS_CALLS, - FeatureFlags::keyguardWmReorderAtmsCalls); - } - - @Override - public boolean keyguardWmStateRefactor() { return getValue(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR, - FeatureFlags::keyguardWmStateRefactor); + FeatureFlags::keyguardWmStateRefactor); } @Override - - public boolean lockscreenFont() { - return getValue(Flags.FLAG_LOCKSCREEN_FONT, - FeatureFlags::lockscreenFont); + public boolean lightRevealMigration() { + return getValue(Flags.FLAG_LIGHT_REVEAL_MIGRATION, + FeatureFlags::lightRevealMigration); } @Override - - public boolean lowLightClockDream() { - return getValue(Flags.FLAG_LOW_LIGHT_CLOCK_DREAM, - FeatureFlags::lowLightClockDream); - } - - @Override - - public boolean magneticNotificationSwipes() { - return getValue(Flags.FLAG_MAGNETIC_NOTIFICATION_SWIPES, - FeatureFlags::magneticNotificationSwipes); - } - - @Override - - public boolean mediaControlsA11yColors() { - return getValue(Flags.FLAG_MEDIA_CONTROLS_A11Y_COLORS, - FeatureFlags::mediaControlsA11yColors); - } - - @Override - - public boolean mediaControlsButtonMedia3() { - return getValue(Flags.FLAG_MEDIA_CONTROLS_BUTTON_MEDIA3, - FeatureFlags::mediaControlsButtonMedia3); - } - - @Override - - public boolean mediaControlsButtonMedia3Placement() { - return getValue(Flags.FLAG_MEDIA_CONTROLS_BUTTON_MEDIA3_PLACEMENT, - FeatureFlags::mediaControlsButtonMedia3Placement); - } - - @Override - - public boolean mediaControlsDeviceManagerBackgroundExecution() { - return getValue(Flags.FLAG_MEDIA_CONTROLS_DEVICE_MANAGER_BACKGROUND_EXECUTION, - FeatureFlags::mediaControlsDeviceManagerBackgroundExecution); - } - - @Override - - public boolean mediaControlsDrawablesReuseBugfix() { - return getValue(Flags.FLAG_MEDIA_CONTROLS_DRAWABLES_REUSE_BUGFIX, - FeatureFlags::mediaControlsDrawablesReuseBugfix); - } - - @Override - public boolean mediaControlsLockscreenShadeBugFix() { return getValue(Flags.FLAG_MEDIA_CONTROLS_LOCKSCREEN_SHADE_BUG_FIX, - FeatureFlags::mediaControlsLockscreenShadeBugFix); + FeatureFlags::mediaControlsLockscreenShadeBugFix); } @Override - - public boolean mediaControlsUiUpdate() { - return getValue(Flags.FLAG_MEDIA_CONTROLS_UI_UPDATE, - FeatureFlags::mediaControlsUiUpdate); + public boolean mediaControlsRefactor() { + return getValue(Flags.FLAG_MEDIA_CONTROLS_REFACTOR, + FeatureFlags::mediaControlsRefactor); } @Override - - public boolean mediaControlsUmoInflationInBackground() { - return getValue(Flags.FLAG_MEDIA_CONTROLS_UMO_INFLATION_IN_BACKGROUND, - FeatureFlags::mediaControlsUmoInflationInBackground); - } - - @Override - public boolean mediaControlsUserInitiatedDeleteintent() { return getValue(Flags.FLAG_MEDIA_CONTROLS_USER_INITIATED_DELETEINTENT, - FeatureFlags::mediaControlsUserInitiatedDeleteintent); + FeatureFlags::mediaControlsUserInitiatedDeleteintent); } @Override - - public boolean mediaLoadMetadataViaMediaDataLoader() { - return getValue(Flags.FLAG_MEDIA_LOAD_METADATA_VIA_MEDIA_DATA_LOADER, - FeatureFlags::mediaLoadMetadataViaMediaDataLoader); + public boolean migrateClocksToBlueprint() { + return getValue(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, + FeatureFlags::migrateClocksToBlueprint); } @Override - - public boolean mediaLockscreenLaunchAnimation() { - return getValue(Flags.FLAG_MEDIA_LOCKSCREEN_LAUNCH_ANIMATION, - FeatureFlags::mediaLockscreenLaunchAnimation); - } - - @Override - - public boolean mediaProjectionDialogBehindLockscreen() { - return getValue(Flags.FLAG_MEDIA_PROJECTION_DIALOG_BEHIND_LOCKSCREEN, - FeatureFlags::mediaProjectionDialogBehindLockscreen); - } - - @Override - - public boolean mediaProjectionGreyErrorText() { - return getValue(Flags.FLAG_MEDIA_PROJECTION_GREY_ERROR_TEXT, - FeatureFlags::mediaProjectionGreyErrorText); - } - - @Override - - public boolean mediaProjectionRequestAttributionFix() { - return getValue(Flags.FLAG_MEDIA_PROJECTION_REQUEST_ATTRIBUTION_FIX, - FeatureFlags::mediaProjectionRequestAttributionFix); - } - - @Override - - public boolean modesUiDialogPaging() { - return getValue(Flags.FLAG_MODES_UI_DIALOG_PAGING, - FeatureFlags::modesUiDialogPaging); - } - - @Override - - public boolean moveTransitionAnimationLayer() { - return getValue(Flags.FLAG_MOVE_TRANSITION_ANIMATION_LAYER, - FeatureFlags::moveTransitionAnimationLayer); - } - - @Override - - public boolean msdlFeedback() { - return getValue(Flags.FLAG_MSDL_FEEDBACK, - FeatureFlags::msdlFeedback); - } - - @Override - - public boolean multiuserWifiPickerTrackerSupport() { - return getValue(Flags.FLAG_MULTIUSER_WIFI_PICKER_TRACKER_SUPPORT, - FeatureFlags::multiuserWifiPickerTrackerSupport); - } - - @Override - public boolean newAodTransition() { return getValue(Flags.FLAG_NEW_AOD_TRANSITION, - FeatureFlags::newAodTransition); + FeatureFlags::newAodTransition); } @Override + public boolean newTouchpadGesturesTutorial() { + return getValue(Flags.FLAG_NEW_TOUCHPAD_GESTURES_TUTORIAL, + FeatureFlags::newTouchpadGesturesTutorial); + } + @Override public boolean newVolumePanel() { return getValue(Flags.FLAG_NEW_VOLUME_PANEL, - FeatureFlags::newVolumePanel); + FeatureFlags::newVolumePanel); } @Override - - public boolean nonTouchscreenDevicesBypassFalsing() { - return getValue(Flags.FLAG_NON_TOUCHSCREEN_DEVICES_BYPASS_FALSING, - FeatureFlags::nonTouchscreenDevicesBypassFalsing); - } - - @Override - - public boolean notesRoleQsTile() { - return getValue(Flags.FLAG_NOTES_ROLE_QS_TILE, - FeatureFlags::notesRoleQsTile); - } - - @Override - - public boolean notificationAddXOnHoverToDismiss() { - return getValue(Flags.FLAG_NOTIFICATION_ADD_X_ON_HOVER_TO_DISMISS, - FeatureFlags::notificationAddXOnHoverToDismiss); - } - - @Override - - public boolean notificationAmbientSuppressionAfterInflation() { - return getValue(Flags.FLAG_NOTIFICATION_AMBIENT_SUPPRESSION_AFTER_INFLATION, - FeatureFlags::notificationAmbientSuppressionAfterInflation); - } - - @Override - - public boolean notificationAnimatedActionsTreatment() { - return getValue(Flags.FLAG_NOTIFICATION_ANIMATED_ACTIONS_TREATMENT, - FeatureFlags::notificationAnimatedActionsTreatment); - } - - @Override - - public boolean notificationAppearNonlinear() { - return getValue(Flags.FLAG_NOTIFICATION_APPEAR_NONLINEAR, - FeatureFlags::notificationAppearNonlinear); - } - - @Override - public boolean notificationAsyncGroupHeaderInflation() { return getValue(Flags.FLAG_NOTIFICATION_ASYNC_GROUP_HEADER_INFLATION, - FeatureFlags::notificationAsyncGroupHeaderInflation); + FeatureFlags::notificationAsyncGroupHeaderInflation); } @Override - public boolean notificationAsyncHybridViewInflation() { return getValue(Flags.FLAG_NOTIFICATION_ASYNC_HYBRID_VIEW_INFLATION, - FeatureFlags::notificationAsyncHybridViewInflation); + FeatureFlags::notificationAsyncHybridViewInflation); } @Override - public boolean notificationAvalancheSuppression() { return getValue(Flags.FLAG_NOTIFICATION_AVALANCHE_SUPPRESSION, - FeatureFlags::notificationAvalancheSuppression); + FeatureFlags::notificationAvalancheSuppression); } @Override - public boolean notificationAvalancheThrottleHun() { return getValue(Flags.FLAG_NOTIFICATION_AVALANCHE_THROTTLE_HUN, - FeatureFlags::notificationAvalancheThrottleHun); + FeatureFlags::notificationAvalancheThrottleHun); } @Override - public boolean notificationBackgroundTintOptimization() { return getValue(Flags.FLAG_NOTIFICATION_BACKGROUND_TINT_OPTIMIZATION, - FeatureFlags::notificationBackgroundTintOptimization); + FeatureFlags::notificationBackgroundTintOptimization); } @Override - - public boolean notificationBundleUi() { - return getValue(Flags.FLAG_NOTIFICATION_BUNDLE_UI, - FeatureFlags::notificationBundleUi); - } - - @Override - public boolean notificationColorUpdateLogger() { return getValue(Flags.FLAG_NOTIFICATION_COLOR_UPDATE_LOGGER, - FeatureFlags::notificationColorUpdateLogger); + FeatureFlags::notificationColorUpdateLogger); } @Override - public boolean notificationContentAlphaOptimization() { return getValue(Flags.FLAG_NOTIFICATION_CONTENT_ALPHA_OPTIMIZATION, - FeatureFlags::notificationContentAlphaOptimization); + FeatureFlags::notificationContentAlphaOptimization); } @Override - public boolean notificationFooterBackgroundTintOptimization() { return getValue(Flags.FLAG_NOTIFICATION_FOOTER_BACKGROUND_TINT_OPTIMIZATION, - FeatureFlags::notificationFooterBackgroundTintOptimization); + FeatureFlags::notificationFooterBackgroundTintOptimization); } @Override + public boolean notificationMediaManagerBackgroundExecution() { + return getValue(Flags.FLAG_NOTIFICATION_MEDIA_MANAGER_BACKGROUND_EXECUTION, + FeatureFlags::notificationMediaManagerBackgroundExecution); + } + @Override + public boolean notificationMinimalismPrototype() { + return getValue(Flags.FLAG_NOTIFICATION_MINIMALISM_PROTOTYPE, + FeatureFlags::notificationMinimalismPrototype); + } + + @Override public boolean notificationOverExpansionClippingFix() { return getValue(Flags.FLAG_NOTIFICATION_OVER_EXPANSION_CLIPPING_FIX, - FeatureFlags::notificationOverExpansionClippingFix); + FeatureFlags::notificationOverExpansionClippingFix); } @Override - - public boolean notificationReentrantDismiss() { - return getValue(Flags.FLAG_NOTIFICATION_REENTRANT_DISMISS, - FeatureFlags::notificationReentrantDismiss); + public boolean notificationPulsingFix() { + return getValue(Flags.FLAG_NOTIFICATION_PULSING_FIX, + FeatureFlags::notificationPulsingFix); } @Override - - public boolean notificationRowAccessibilityExpanded() { - return getValue(Flags.FLAG_NOTIFICATION_ROW_ACCESSIBILITY_EXPANDED, - FeatureFlags::notificationRowAccessibilityExpanded); - } - - @Override - public boolean notificationRowContentBinderRefactor() { return getValue(Flags.FLAG_NOTIFICATION_ROW_CONTENT_BINDER_REFACTOR, - FeatureFlags::notificationRowContentBinderRefactor); + FeatureFlags::notificationRowContentBinderRefactor); } @Override - - public boolean notificationRowTransparency() { - return getValue(Flags.FLAG_NOTIFICATION_ROW_TRANSPARENCY, - FeatureFlags::notificationRowTransparency); - } - - @Override - public boolean notificationRowUserContext() { return getValue(Flags.FLAG_NOTIFICATION_ROW_USER_CONTEXT, - FeatureFlags::notificationRowUserContext); + FeatureFlags::notificationRowUserContext); } @Override - - public boolean notificationShadeBlur() { - return getValue(Flags.FLAG_NOTIFICATION_SHADE_BLUR, - FeatureFlags::notificationShadeBlur); - } - - @Override - - public boolean notificationShadeUiThread() { - return getValue(Flags.FLAG_NOTIFICATION_SHADE_UI_THREAD, - FeatureFlags::notificationShadeUiThread); - } - - @Override - - public boolean notificationSkipSilentUpdates() { - return getValue(Flags.FLAG_NOTIFICATION_SKIP_SILENT_UPDATES, - FeatureFlags::notificationSkipSilentUpdates); - } - - @Override - - public boolean notificationTransparentHeaderFix() { - return getValue(Flags.FLAG_NOTIFICATION_TRANSPARENT_HEADER_FIX, - FeatureFlags::notificationTransparentHeaderFix); - } - - @Override - public boolean notificationViewFlipperPausingV2() { return getValue(Flags.FLAG_NOTIFICATION_VIEW_FLIPPER_PAUSING_V2, - FeatureFlags::notificationViewFlipperPausingV2); + FeatureFlags::notificationViewFlipperPausingV2); } @Override - public boolean notificationsBackgroundIcons() { return getValue(Flags.FLAG_NOTIFICATIONS_BACKGROUND_ICONS, - FeatureFlags::notificationsBackgroundIcons); + FeatureFlags::notificationsBackgroundIcons); } @Override - - public boolean notificationsFooterVisibilityFix() { - return getValue(Flags.FLAG_NOTIFICATIONS_FOOTER_VISIBILITY_FIX, - FeatureFlags::notificationsFooterVisibilityFix); + public boolean notificationsFooterViewRefactor() { + return getValue(Flags.FLAG_NOTIFICATIONS_FOOTER_VIEW_REFACTOR, + FeatureFlags::notificationsFooterViewRefactor); } @Override + public boolean notificationsHeadsUpRefactor() { + return getValue(Flags.FLAG_NOTIFICATIONS_HEADS_UP_REFACTOR, + FeatureFlags::notificationsHeadsUpRefactor); + } + @Override public boolean notificationsHideOnDisplaySwitch() { return getValue(Flags.FLAG_NOTIFICATIONS_HIDE_ON_DISPLAY_SWITCH, - FeatureFlags::notificationsHideOnDisplaySwitch); + FeatureFlags::notificationsHideOnDisplaySwitch); } @Override - - public boolean notificationsHunSharedAnimationValues() { - return getValue(Flags.FLAG_NOTIFICATIONS_HUN_SHARED_ANIMATION_VALUES, - FeatureFlags::notificationsHunSharedAnimationValues); - } - - @Override - public boolean notificationsIconContainerRefactor() { return getValue(Flags.FLAG_NOTIFICATIONS_ICON_CONTAINER_REFACTOR, - FeatureFlags::notificationsIconContainerRefactor); + FeatureFlags::notificationsIconContainerRefactor); } @Override - - public boolean notificationsLaunchRadius() { - return getValue(Flags.FLAG_NOTIFICATIONS_LAUNCH_RADIUS, - FeatureFlags::notificationsLaunchRadius); + public boolean notificationsImprovedHunAnimation() { + return getValue(Flags.FLAG_NOTIFICATIONS_IMPROVED_HUN_ANIMATION, + FeatureFlags::notificationsImprovedHunAnimation); } @Override - public boolean notificationsLiveDataStoreRefactor() { return getValue(Flags.FLAG_NOTIFICATIONS_LIVE_DATA_STORE_REFACTOR, - FeatureFlags::notificationsLiveDataStoreRefactor); + FeatureFlags::notificationsLiveDataStoreRefactor); } @Override - - public boolean notificationsPinnedHunInShade() { - return getValue(Flags.FLAG_NOTIFICATIONS_PINNED_HUN_IN_SHADE, - FeatureFlags::notificationsPinnedHunInShade); - } - - @Override - - public boolean notificationsRedesignFooterView() { - return getValue(Flags.FLAG_NOTIFICATIONS_REDESIGN_FOOTER_VIEW, - FeatureFlags::notificationsRedesignFooterView); - } - - @Override - - public boolean notificationsRedesignGuts() { - return getValue(Flags.FLAG_NOTIFICATIONS_REDESIGN_GUTS, - FeatureFlags::notificationsRedesignGuts); - } - - @Override - - public boolean notifyPasswordTextViewUserActivityInBackground() { - return getValue(Flags.FLAG_NOTIFY_PASSWORD_TEXT_VIEW_USER_ACTIVITY_IN_BACKGROUND, - FeatureFlags::notifyPasswordTextViewUserActivityInBackground); - } - - @Override - public boolean notifyPowerManagerUserActivityBackground() { return getValue(Flags.FLAG_NOTIFY_POWER_MANAGER_USER_ACTIVITY_BACKGROUND, - FeatureFlags::notifyPowerManagerUserActivityBackground); + FeatureFlags::notifyPowerManagerUserActivityBackground); } @Override - - public boolean onlyShowMediaStreamSliderInSingleVolumeMode() { - return getValue(Flags.FLAG_ONLY_SHOW_MEDIA_STREAM_SLIDER_IN_SINGLE_VOLUME_MODE, - FeatureFlags::onlyShowMediaStreamSliderInSingleVolumeMode); - } - - @Override - - public boolean outputSwitcherRedesign() { - return getValue(Flags.FLAG_OUTPUT_SWITCHER_REDESIGN, - FeatureFlags::outputSwitcherRedesign); - } - - @Override - - public boolean overrideSuppressOverlayCondition() { - return getValue(Flags.FLAG_OVERRIDE_SUPPRESS_OVERLAY_CONDITION, - FeatureFlags::overrideSuppressOverlayCondition); - } - - @Override - - public boolean permissionHelperInlineUiRichOngoing() { - return getValue(Flags.FLAG_PERMISSION_HELPER_INLINE_UI_RICH_ONGOING, - FeatureFlags::permissionHelperInlineUiRichOngoing); - } - - @Override - - public boolean permissionHelperUiRichOngoing() { - return getValue(Flags.FLAG_PERMISSION_HELPER_UI_RICH_ONGOING, - FeatureFlags::permissionHelperUiRichOngoing); - } - - @Override - - public boolean physicalNotificationMovement() { - return getValue(Flags.FLAG_PHYSICAL_NOTIFICATION_MOVEMENT, - FeatureFlags::physicalNotificationMovement); - } - - @Override - public boolean pinInputFieldStyledFocusState() { return getValue(Flags.FLAG_PIN_INPUT_FIELD_STYLED_FOCUS_STATE, - FeatureFlags::pinInputFieldStyledFocusState); + FeatureFlags::pinInputFieldStyledFocusState); } @Override + public boolean predictiveBackAnimateBouncer() { + return getValue(Flags.FLAG_PREDICTIVE_BACK_ANIMATE_BOUNCER, + FeatureFlags::predictiveBackAnimateBouncer); + } + @Override + public boolean predictiveBackAnimateDialogs() { + return getValue(Flags.FLAG_PREDICTIVE_BACK_ANIMATE_DIALOGS, + FeatureFlags::predictiveBackAnimateDialogs); + } + + @Override public boolean predictiveBackAnimateShade() { return getValue(Flags.FLAG_PREDICTIVE_BACK_ANIMATE_SHADE, - FeatureFlags::predictiveBackAnimateShade); + FeatureFlags::predictiveBackAnimateShade); } @Override - - public boolean predictiveBackDelayWmTransition() { - return getValue(Flags.FLAG_PREDICTIVE_BACK_DELAY_WM_TRANSITION, - FeatureFlags::predictiveBackDelayWmTransition); + public boolean predictiveBackSysui() { + return getValue(Flags.FLAG_PREDICTIVE_BACK_SYSUI, + FeatureFlags::predictiveBackSysui); } @Override - public boolean priorityPeopleSection() { return getValue(Flags.FLAG_PRIORITY_PEOPLE_SECTION, - FeatureFlags::priorityPeopleSection); + FeatureFlags::priorityPeopleSection); } @Override - - public boolean promoteNotificationsAutomatically() { - return getValue(Flags.FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY, - FeatureFlags::promoteNotificationsAutomatically); + public boolean privacyDotUnfoldWrongCornerFix() { + return getValue(Flags.FLAG_PRIVACY_DOT_UNFOLD_WRONG_CORNER_FIX, + FeatureFlags::privacyDotUnfoldWrongCornerFix); } @Override + public boolean pssAppSelectorAbruptExitFix() { + return getValue(Flags.FLAG_PSS_APP_SELECTOR_ABRUPT_EXIT_FIX, + FeatureFlags::pssAppSelectorAbruptExitFix); + } + @Override public boolean pssAppSelectorRecentsSplitScreen() { return getValue(Flags.FLAG_PSS_APP_SELECTOR_RECENTS_SPLIT_SCREEN, - FeatureFlags::pssAppSelectorRecentsSplitScreen); + FeatureFlags::pssAppSelectorRecentsSplitScreen); } @Override - public boolean pssTaskSwitcher() { return getValue(Flags.FLAG_PSS_TASK_SWITCHER, - FeatureFlags::pssTaskSwitcher); + FeatureFlags::pssTaskSwitcher); } @Override - public boolean qsCustomTileClickGuaranteedBugFix() { return getValue(Flags.FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX, - FeatureFlags::qsCustomTileClickGuaranteedBugFix); + FeatureFlags::qsCustomTileClickGuaranteedBugFix); } @Override + public boolean qsNewPipeline() { + return getValue(Flags.FLAG_QS_NEW_PIPELINE, + FeatureFlags::qsNewPipeline); + } + @Override public boolean qsNewTiles() { return getValue(Flags.FLAG_QS_NEW_TILES, - FeatureFlags::qsNewTiles); + FeatureFlags::qsNewTiles); } @Override - public boolean qsNewTilesFuture() { return getValue(Flags.FLAG_QS_NEW_TILES_FUTURE, - FeatureFlags::qsNewTilesFuture); + FeatureFlags::qsNewTilesFuture); } @Override - - public boolean qsQuickRebindActiveTiles() { - return getValue(Flags.FLAG_QS_QUICK_REBIND_ACTIVE_TILES, - FeatureFlags::qsQuickRebindActiveTiles); - } - - @Override - - public boolean qsRegisterSettingObserverOnBgThread() { - return getValue(Flags.FLAG_QS_REGISTER_SETTING_OBSERVER_ON_BG_THREAD, - FeatureFlags::qsRegisterSettingObserverOnBgThread); - } - - @Override - - public boolean qsTileDetailedView() { - return getValue(Flags.FLAG_QS_TILE_DETAILED_VIEW, - FeatureFlags::qsTileDetailedView); - } - - @Override - public boolean qsTileFocusState() { return getValue(Flags.FLAG_QS_TILE_FOCUS_STATE, - FeatureFlags::qsTileFocusState); + FeatureFlags::qsTileFocusState); } @Override - public boolean qsUiRefactor() { return getValue(Flags.FLAG_QS_UI_REFACTOR, - FeatureFlags::qsUiRefactor); + FeatureFlags::qsUiRefactor); } @Override - - public boolean qsUiRefactorComposeFragment() { - return getValue(Flags.FLAG_QS_UI_REFACTOR_COMPOSE_FRAGMENT, - FeatureFlags::qsUiRefactorComposeFragment); + public boolean quickSettingsVisualHapticsLongpress() { + return getValue(Flags.FLAG_QUICK_SETTINGS_VISUAL_HAPTICS_LONGPRESS, + FeatureFlags::quickSettingsVisualHapticsLongpress); } @Override - public boolean recordIssueQsTile() { return getValue(Flags.FLAG_RECORD_ISSUE_QS_TILE, - FeatureFlags::recordIssueQsTile); + FeatureFlags::recordIssueQsTile); } @Override - - public boolean redesignMagnificationWindowSize() { - return getValue(Flags.FLAG_REDESIGN_MAGNIFICATION_WINDOW_SIZE, - FeatureFlags::redesignMagnificationWindowSize); - } - - @Override - public boolean refactorGetCurrentUser() { return getValue(Flags.FLAG_REFACTOR_GET_CURRENT_USER, - FeatureFlags::refactorGetCurrentUser); + FeatureFlags::refactorGetCurrentUser); } @Override - public boolean registerBatteryControllerReceiversInCorestartable() { return getValue(Flags.FLAG_REGISTER_BATTERY_CONTROLLER_RECEIVERS_IN_CORESTARTABLE, - FeatureFlags::registerBatteryControllerReceiversInCorestartable); + FeatureFlags::registerBatteryControllerReceiversInCorestartable); } @Override - - public boolean registerContentObserversAsync() { - return getValue(Flags.FLAG_REGISTER_CONTENT_OBSERVERS_ASYNC, - FeatureFlags::registerContentObserversAsync); - } - - @Override - public boolean registerNewWalletCardInBackground() { return getValue(Flags.FLAG_REGISTER_NEW_WALLET_CARD_IN_BACKGROUND, - FeatureFlags::registerNewWalletCardInBackground); + FeatureFlags::registerNewWalletCardInBackground); } @Override - public boolean registerWallpaperNotifierBackground() { return getValue(Flags.FLAG_REGISTER_WALLPAPER_NOTIFIER_BACKGROUND, - FeatureFlags::registerWallpaperNotifierBackground); + FeatureFlags::registerWallpaperNotifierBackground); } @Override - - public boolean relockWithPowerButtonImmediately() { - return getValue(Flags.FLAG_RELOCK_WITH_POWER_BUTTON_IMMEDIATELY, - FeatureFlags::relockWithPowerButtonImmediately); + public boolean registerZenModeContentObserverBackground() { + return getValue(Flags.FLAG_REGISTER_ZEN_MODE_CONTENT_OBSERVER_BACKGROUND, + FeatureFlags::registerZenModeContentObserverBackground); } @Override - public boolean removeDreamOverlayHideOnTouch() { return getValue(Flags.FLAG_REMOVE_DREAM_OVERLAY_HIDE_ON_TOUCH, - FeatureFlags::removeDreamOverlayHideOnTouch); + FeatureFlags::removeDreamOverlayHideOnTouch); } @Override - - public boolean removeUpdateListenerInQsIconViewImpl() { - return getValue(Flags.FLAG_REMOVE_UPDATE_LISTENER_IN_QS_ICON_VIEW_IMPL, - FeatureFlags::removeUpdateListenerInQsIconViewImpl); - } - - @Override - public boolean restToUnlock() { return getValue(Flags.FLAG_REST_TO_UNLOCK, - FeatureFlags::restToUnlock); + FeatureFlags::restToUnlock); } @Override - public boolean restartDreamOnUnocclude() { return getValue(Flags.FLAG_RESTART_DREAM_ON_UNOCCLUDE, - FeatureFlags::restartDreamOnUnocclude); + FeatureFlags::restartDreamOnUnocclude); } @Override - public boolean revampedBouncerMessages() { return getValue(Flags.FLAG_REVAMPED_BOUNCER_MESSAGES, - FeatureFlags::revampedBouncerMessages); + FeatureFlags::revampedBouncerMessages); } @Override - public boolean runFingerprintDetectOnDismissibleKeyguard() { return getValue(Flags.FLAG_RUN_FINGERPRINT_DETECT_ON_DISMISSIBLE_KEYGUARD, - FeatureFlags::runFingerprintDetectOnDismissibleKeyguard); + FeatureFlags::runFingerprintDetectOnDismissibleKeyguard); } @Override - public boolean saveAndRestoreMagnificationSettingsButtons() { return getValue(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS, - FeatureFlags::saveAndRestoreMagnificationSettingsButtons); + FeatureFlags::saveAndRestoreMagnificationSettingsButtons); } @Override - public boolean sceneContainer() { return getValue(Flags.FLAG_SCENE_CONTAINER, - FeatureFlags::sceneContainer); + FeatureFlags::sceneContainer); } @Override - public boolean screenshareNotificationHidingBugFix() { return getValue(Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX, - FeatureFlags::screenshareNotificationHidingBugFix); + FeatureFlags::screenshareNotificationHidingBugFix); } @Override - public boolean screenshotActionDismissSystemWindows() { return getValue(Flags.FLAG_SCREENSHOT_ACTION_DISMISS_SYSTEM_WINDOWS, - FeatureFlags::screenshotActionDismissSystemWindows); + FeatureFlags::screenshotActionDismissSystemWindows); } @Override - - public boolean screenshotMultidisplayFocusChange() { - return getValue(Flags.FLAG_SCREENSHOT_MULTIDISPLAY_FOCUS_CHANGE, - FeatureFlags::screenshotMultidisplayFocusChange); + public boolean screenshotPrivateProfileAccessibilityAnnouncementFix() { + return getValue(Flags.FLAG_SCREENSHOT_PRIVATE_PROFILE_ACCESSIBILITY_ANNOUNCEMENT_FIX, + FeatureFlags::screenshotPrivateProfileAccessibilityAnnouncementFix); } @Override - - public boolean screenshotPolicySplitAndDesktopMode() { - return getValue(Flags.FLAG_SCREENSHOT_POLICY_SPLIT_AND_DESKTOP_MODE, - FeatureFlags::screenshotPolicySplitAndDesktopMode); + public boolean screenshotPrivateProfileBehaviorFix() { + return getValue(Flags.FLAG_SCREENSHOT_PRIVATE_PROFILE_BEHAVIOR_FIX, + FeatureFlags::screenshotPrivateProfileBehaviorFix); } @Override - public boolean screenshotScrollCropViewCrashFix() { return getValue(Flags.FLAG_SCREENSHOT_SCROLL_CROP_VIEW_CRASH_FIX, - FeatureFlags::screenshotScrollCropViewCrashFix); + FeatureFlags::screenshotScrollCropViewCrashFix); } @Override - - public boolean screenshotUiControllerRefactor() { - return getValue(Flags.FLAG_SCREENSHOT_UI_CONTROLLER_REFACTOR, - FeatureFlags::screenshotUiControllerRefactor); + public boolean screenshotShelfUi2() { + return getValue(Flags.FLAG_SCREENSHOT_SHELF_UI2, + FeatureFlags::screenshotShelfUi2); } @Override - - public boolean secondaryUserWidgetHost() { - return getValue(Flags.FLAG_SECONDARY_USER_WIDGET_HOST, - FeatureFlags::secondaryUserWidgetHost); + public boolean shadeCollapseActivityLaunchFix() { + return getValue(Flags.FLAG_SHADE_COLLAPSE_ACTIVITY_LAUNCH_FIX, + FeatureFlags::shadeCollapseActivityLaunchFix); } @Override - - public boolean settingsExtRegisterContentObserverOnBgThread() { - return getValue(Flags.FLAG_SETTINGS_EXT_REGISTER_CONTENT_OBSERVER_ON_BG_THREAD, - FeatureFlags::settingsExtRegisterContentObserverOnBgThread); - } - - @Override - - public boolean shadeExpandsOnStatusBarLongPress() { - return getValue(Flags.FLAG_SHADE_EXPANDS_ON_STATUS_BAR_LONG_PRESS, - FeatureFlags::shadeExpandsOnStatusBarLongPress); - } - - @Override - - public boolean shadeHeaderFontUpdate() { - return getValue(Flags.FLAG_SHADE_HEADER_FONT_UPDATE, - FeatureFlags::shadeHeaderFontUpdate); - } - - @Override - - public boolean shadeLaunchAccessibility() { - return getValue(Flags.FLAG_SHADE_LAUNCH_ACCESSIBILITY, - FeatureFlags::shadeLaunchAccessibility); - } - - @Override - - public boolean shadeWindowGoesAround() { - return getValue(Flags.FLAG_SHADE_WINDOW_GOES_AROUND, - FeatureFlags::shadeWindowGoesAround); - } - - @Override - public boolean shaderlibLoadingEffectRefactor() { return getValue(Flags.FLAG_SHADERLIB_LOADING_EFFECT_REFACTOR, - FeatureFlags::shaderlibLoadingEffectRefactor); + FeatureFlags::shaderlibLoadingEffectRefactor); } @Override - - public boolean shortcutHelperKeyGlyph() { - return getValue(Flags.FLAG_SHORTCUT_HELPER_KEY_GLYPH, - FeatureFlags::shortcutHelperKeyGlyph); - } - - @Override - - public boolean showAudioSharingSliderInVolumePanel() { - return getValue(Flags.FLAG_SHOW_AUDIO_SHARING_SLIDER_IN_VOLUME_PANEL, - FeatureFlags::showAudioSharingSliderInVolumePanel); - } - - @Override - - public boolean showClipboardIndication() { - return getValue(Flags.FLAG_SHOW_CLIPBOARD_INDICATION, - FeatureFlags::showClipboardIndication); - } - - @Override - - public boolean showLockedByYourWatchKeyguardIndicator() { - return getValue(Flags.FLAG_SHOW_LOCKED_BY_YOUR_WATCH_KEYGUARD_INDICATOR, - FeatureFlags::showLockedByYourWatchKeyguardIndicator); - } - - @Override - - public boolean showToastWhenAppControlBrightness() { - return getValue(Flags.FLAG_SHOW_TOAST_WHEN_APP_CONTROL_BRIGHTNESS, - FeatureFlags::showToastWhenAppControlBrightness); - } - - @Override - - public boolean simPinBouncerReset() { - return getValue(Flags.FLAG_SIM_PIN_BOUNCER_RESET, - FeatureFlags::simPinBouncerReset); - } - - @Override - - public boolean simPinRaceConditionOnRestart() { - return getValue(Flags.FLAG_SIM_PIN_RACE_CONDITION_ON_RESTART, - FeatureFlags::simPinRaceConditionOnRestart); - } - - @Override - - public boolean simPinUseSlotId() { - return getValue(Flags.FLAG_SIM_PIN_USE_SLOT_ID, - FeatureFlags::simPinUseSlotId); - } - - @Override - - public boolean skipHideSensitiveNotifAnimation() { - return getValue(Flags.FLAG_SKIP_HIDE_SENSITIVE_NOTIF_ANIMATION, - FeatureFlags::skipHideSensitiveNotifAnimation); - } - - @Override - public boolean sliceBroadcastRelayInBackground() { return getValue(Flags.FLAG_SLICE_BROADCAST_RELAY_IN_BACKGROUND, - FeatureFlags::sliceBroadcastRelayInBackground); + FeatureFlags::sliceBroadcastRelayInBackground); } @Override - public boolean sliceManagerBinderCallBackground() { return getValue(Flags.FLAG_SLICE_MANAGER_BINDER_CALL_BACKGROUND, - FeatureFlags::sliceManagerBinderCallBackground); + FeatureFlags::sliceManagerBinderCallBackground); } @Override - public boolean smartspaceLockscreenViewmodel() { return getValue(Flags.FLAG_SMARTSPACE_LOCKSCREEN_VIEWMODEL, - FeatureFlags::smartspaceLockscreenViewmodel); + FeatureFlags::smartspaceLockscreenViewmodel); } @Override - public boolean smartspaceRelocateToBottom() { return getValue(Flags.FLAG_SMARTSPACE_RELOCATE_TO_BOTTOM, - FeatureFlags::smartspaceRelocateToBottom); + FeatureFlags::smartspaceRelocateToBottom); } @Override - - public boolean smartspaceRemoteviewsRenderingFix() { - return getValue(Flags.FLAG_SMARTSPACE_REMOTEVIEWS_RENDERING_FIX, - FeatureFlags::smartspaceRemoteviewsRenderingFix); + public boolean smartspaceRemoteviewsRendering() { + return getValue(Flags.FLAG_SMARTSPACE_REMOTEVIEWS_RENDERING, + FeatureFlags::smartspaceRemoteviewsRendering); } @Override - - public boolean smartspaceSwipeEventLoggingFix() { - return getValue(Flags.FLAG_SMARTSPACE_SWIPE_EVENT_LOGGING_FIX, - FeatureFlags::smartspaceSwipeEventLoggingFix); - } - - @Override - - public boolean smartspaceViewpager2() { - return getValue(Flags.FLAG_SMARTSPACE_VIEWPAGER2, - FeatureFlags::smartspaceViewpager2); - } - - @Override - - public boolean sounddoseCustomization() { - return getValue(Flags.FLAG_SOUNDDOSE_CUSTOMIZATION, - FeatureFlags::sounddoseCustomization); - } - - @Override - - public boolean spatialModelAppPushback() { - return getValue(Flags.FLAG_SPATIAL_MODEL_APP_PUSHBACK, - FeatureFlags::spatialModelAppPushback); - } - - @Override - - public boolean stabilizeHeadsUpGroupV2() { - return getValue(Flags.FLAG_STABILIZE_HEADS_UP_GROUP_V2, - FeatureFlags::stabilizeHeadsUpGroupV2); - } - - @Override - - public boolean statusBarAlwaysCheckUnderlyingNetworks() { - return getValue(Flags.FLAG_STATUS_BAR_ALWAYS_CHECK_UNDERLYING_NETWORKS, - FeatureFlags::statusBarAlwaysCheckUnderlyingNetworks); - } - - @Override - - public boolean statusBarAutoStartScreenRecordChip() { - return getValue(Flags.FLAG_STATUS_BAR_AUTO_START_SCREEN_RECORD_CHIP, - FeatureFlags::statusBarAutoStartScreenRecordChip); - } - - @Override - - public boolean statusBarChipsModernization() { - return getValue(Flags.FLAG_STATUS_BAR_CHIPS_MODERNIZATION, - FeatureFlags::statusBarChipsModernization); - } - - @Override - - public boolean statusBarChipsReturnAnimations() { - return getValue(Flags.FLAG_STATUS_BAR_CHIPS_RETURN_ANIMATIONS, - FeatureFlags::statusBarChipsReturnAnimations); - } - - @Override - - public boolean statusBarFontUpdates() { - return getValue(Flags.FLAG_STATUS_BAR_FONT_UPDATES, - FeatureFlags::statusBarFontUpdates); - } - - @Override - - public boolean statusBarMobileIconKairos() { - return getValue(Flags.FLAG_STATUS_BAR_MOBILE_ICON_KAIROS, - FeatureFlags::statusBarMobileIconKairos); - } - - @Override - public boolean statusBarMonochromeIconsFix() { return getValue(Flags.FLAG_STATUS_BAR_MONOCHROME_ICONS_FIX, - FeatureFlags::statusBarMonochromeIconsFix); + FeatureFlags::statusBarMonochromeIconsFix); } @Override - - public boolean statusBarNoHunBehavior() { - return getValue(Flags.FLAG_STATUS_BAR_NO_HUN_BEHAVIOR, - FeatureFlags::statusBarNoHunBehavior); + public boolean statusBarScreenSharingChips() { + return getValue(Flags.FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, + FeatureFlags::statusBarScreenSharingChips); } @Override - - public boolean statusBarPopupChips() { - return getValue(Flags.FLAG_STATUS_BAR_POPUP_CHIPS, - FeatureFlags::statusBarPopupChips); - } - - @Override - - public boolean statusBarRootModernization() { - return getValue(Flags.FLAG_STATUS_BAR_ROOT_MODERNIZATION, - FeatureFlags::statusBarRootModernization); - } - - @Override - - public boolean statusBarShowAudioOnlyProjectionChip() { - return getValue(Flags.FLAG_STATUS_BAR_SHOW_AUDIO_ONLY_PROJECTION_CHIP, - FeatureFlags::statusBarShowAudioOnlyProjectionChip); - } - - @Override - - public boolean statusBarSignalPolicyRefactor() { - return getValue(Flags.FLAG_STATUS_BAR_SIGNAL_POLICY_REFACTOR, - FeatureFlags::statusBarSignalPolicyRefactor); - } - - @Override - - public boolean statusBarSignalPolicyRefactorEthernet() { - return getValue(Flags.FLAG_STATUS_BAR_SIGNAL_POLICY_REFACTOR_ETHERNET, - FeatureFlags::statusBarSignalPolicyRefactorEthernet); - } - - @Override - public boolean statusBarStaticInoutIndicators() { return getValue(Flags.FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS, - FeatureFlags::statusBarStaticInoutIndicators); + FeatureFlags::statusBarStaticInoutIndicators); } @Override - - public boolean statusBarStopUpdatingWindowHeight() { - return getValue(Flags.FLAG_STATUS_BAR_STOP_UPDATING_WINDOW_HEIGHT, - FeatureFlags::statusBarStopUpdatingWindowHeight); - } - - @Override - - public boolean statusBarSwipeOverChip() { - return getValue(Flags.FLAG_STATUS_BAR_SWIPE_OVER_CHIP, - FeatureFlags::statusBarSwipeOverChip); - } - - @Override - - public boolean statusBarSwitchToSpnFromDataSpn() { - return getValue(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN, - FeatureFlags::statusBarSwitchToSpnFromDataSpn); - } - - @Override - - public boolean statusBarUiThread() { - return getValue(Flags.FLAG_STATUS_BAR_UI_THREAD, - FeatureFlags::statusBarUiThread); - } - - @Override - - public boolean statusBarWindowNoCustomTouch() { - return getValue(Flags.FLAG_STATUS_BAR_WINDOW_NO_CUSTOM_TOUCH, - FeatureFlags::statusBarWindowNoCustomTouch); - } - - @Override - - public boolean stoppableFgsSystemApp() { - return getValue(Flags.FLAG_STOPPABLE_FGS_SYSTEM_APP, - FeatureFlags::stoppableFgsSystemApp); - } - - @Override - public boolean switchUserOnBg() { return getValue(Flags.FLAG_SWITCH_USER_ON_BG, - FeatureFlags::switchUserOnBg); + FeatureFlags::switchUserOnBg); } @Override - public boolean sysuiTeamfood() { return getValue(Flags.FLAG_SYSUI_TEAMFOOD, - FeatureFlags::sysuiTeamfood); + FeatureFlags::sysuiTeamfood); } @Override - public boolean themeOverlayControllerWakefulnessDeprecation() { return getValue(Flags.FLAG_THEME_OVERLAY_CONTROLLER_WAKEFULNESS_DEPRECATION, - FeatureFlags::themeOverlayControllerWakefulnessDeprecation); + FeatureFlags::themeOverlayControllerWakefulnessDeprecation); } @Override - - public boolean transitionRaceCondition() { - return getValue(Flags.FLAG_TRANSITION_RACE_CONDITION, - FeatureFlags::transitionRaceCondition); - } - - @Override - public boolean translucentOccludingActivityFix() { return getValue(Flags.FLAG_TRANSLUCENT_OCCLUDING_ACTIVITY_FIX, - FeatureFlags::translucentOccludingActivityFix); + FeatureFlags::translucentOccludingActivityFix); } @Override - - public boolean tvGlobalActionsFocus() { - return getValue(Flags.FLAG_TV_GLOBAL_ACTIONS_FOCUS, - FeatureFlags::tvGlobalActionsFocus); + public boolean truncatedStatusBarIconsFix() { + return getValue(Flags.FLAG_TRUNCATED_STATUS_BAR_ICONS_FIX, + FeatureFlags::truncatedStatusBarIconsFix); } @Override - public boolean udfpsViewPerformance() { return getValue(Flags.FLAG_UDFPS_VIEW_PERFORMANCE, - FeatureFlags::udfpsViewPerformance); + FeatureFlags::udfpsViewPerformance); } @Override - public boolean unfoldAnimationBackgroundProgress() { return getValue(Flags.FLAG_UNFOLD_ANIMATION_BACKGROUND_PROGRESS, - FeatureFlags::unfoldAnimationBackgroundProgress); + FeatureFlags::unfoldAnimationBackgroundProgress); } @Override - - public boolean unfoldLatencyTrackingFix() { - return getValue(Flags.FLAG_UNFOLD_LATENCY_TRACKING_FIX, - FeatureFlags::unfoldLatencyTrackingFix); - } - - @Override - - public boolean updateCornerRadiusOnDisplayChanged() { - return getValue(Flags.FLAG_UPDATE_CORNER_RADIUS_ON_DISPLAY_CHANGED, - FeatureFlags::updateCornerRadiusOnDisplayChanged); - } - - @Override - public boolean updateUserSwitcherBackground() { return getValue(Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND, - FeatureFlags::updateUserSwitcherBackground); + FeatureFlags::updateUserSwitcherBackground); } @Override - - public boolean updateWindowMagnifierBottomBoundary() { - return getValue(Flags.FLAG_UPDATE_WINDOW_MAGNIFIER_BOTTOM_BOUNDARY, - FeatureFlags::updateWindowMagnifierBottomBoundary); + public boolean validateKeyboardShortcutHelperIconUri() { + return getValue(Flags.FLAG_VALIDATE_KEYBOARD_SHORTCUT_HELPER_ICON_URI, + FeatureFlags::validateKeyboardShortcutHelperIconUri); } @Override - - public boolean useAadProxSensor() { - return getValue(Flags.FLAG_USE_AAD_PROX_SENSOR, - FeatureFlags::useAadProxSensor); - } - - @Override - - public boolean useNotifInflationThreadForFooter() { - return getValue(Flags.FLAG_USE_NOTIF_INFLATION_THREAD_FOR_FOOTER, - FeatureFlags::useNotifInflationThreadForFooter); - } - - @Override - - public boolean useNotifInflationThreadForRow() { - return getValue(Flags.FLAG_USE_NOTIF_INFLATION_THREAD_FOR_ROW, - FeatureFlags::useNotifInflationThreadForRow); - } - - @Override - - public boolean useTransitionsForKeyguardOccluded() { - return getValue(Flags.FLAG_USE_TRANSITIONS_FOR_KEYGUARD_OCCLUDED, - FeatureFlags::useTransitionsForKeyguardOccluded); - } - - @Override - - public boolean useVolumeController() { - return getValue(Flags.FLAG_USE_VOLUME_CONTROLLER, - FeatureFlags::useVolumeController); - } - - @Override - - public boolean userAwareSettingsRepositories() { - return getValue(Flags.FLAG_USER_AWARE_SETTINGS_REPOSITORIES, - FeatureFlags::userAwareSettingsRepositories); - } - - @Override - - public boolean userEncryptedSource() { - return getValue(Flags.FLAG_USER_ENCRYPTED_SOURCE, - FeatureFlags::userEncryptedSource); - } - - @Override - - public boolean userSwitcherAddSignOutOption() { - return getValue(Flags.FLAG_USER_SWITCHER_ADD_SIGN_OUT_OPTION, - FeatureFlags::userSwitcherAddSignOutOption); - } - - @Override - public boolean visualInterruptionsRefactor() { return getValue(Flags.FLAG_VISUAL_INTERRUPTIONS_REFACTOR, - FeatureFlags::visualInterruptionsRefactor); - } - - @Override - - public boolean volumeRedesign() { - return getValue(Flags.FLAG_VOLUME_REDESIGN, - FeatureFlags::volumeRedesign); + FeatureFlags::visualInterruptionsRefactor); } public boolean isFlagReadOnlyOptimized(String flagName) { if (mReadOnlyFlagsSet.contains(flagName) && - isOptimizationEnabled()) { - return true; + isOptimizationEnabled()) { + return true; } return false; } - private boolean isOptimizationEnabled() { return false; } @@ -1994,572 +946,163 @@ public class CustomFeatureFlags implements FeatureFlags { public List getFlagNames() { return Arrays.asList( - Flags.FLAG_ACTIVITY_TRANSITION_USE_LARGEST_WINDOW, - Flags.FLAG_ADD_BLACK_BACKGROUND_FOR_WINDOW_MAGNIFIER, - Flags.FLAG_ALWAYS_COMPOSE_QS_UI_FRAGMENT, - Flags.FLAG_AMBIENT_TOUCH_MONITOR_LISTEN_TO_DISPLAY_CHANGES, - Flags.FLAG_APP_CLIPS_BACKLINKS, - Flags.FLAG_APP_SHORTCUT_REMOVAL_FIX, - Flags.FLAG_AVALANCHE_REPLACE_HUN_WHEN_CRITICAL, - Flags.FLAG_BIND_KEYGUARD_MEDIA_VISIBILITY, - Flags.FLAG_BOUNCER_UI_REVAMP, - Flags.FLAG_BOUNCER_UI_REVAMP_2, - Flags.FLAG_BP_COLORS, - Flags.FLAG_BRIGHTNESS_SLIDER_FOCUS_STATE, - Flags.FLAG_CHECK_LOCKSCREEN_GONE_TRANSITION, - Flags.FLAG_CLASSIC_FLAGS_MULTI_USER, - Flags.FLAG_CLIPBOARD_IMAGE_TIMEOUT, - Flags.FLAG_CLIPBOARD_NONINTERACTIVE_ON_LOCKSCREEN, - Flags.FLAG_CLIPBOARD_OVERLAY_MULTIUSER, - Flags.FLAG_CLIPBOARD_SHARED_TRANSITIONS, - Flags.FLAG_CLIPBOARD_USE_DESCRIPTION_MIMETYPE, - Flags.FLAG_CLOCK_FIDGET_ANIMATION, - Flags.FLAG_COMMUNAL_BOUNCER_DO_NOT_MODIFY_PLUGIN_OPEN, - Flags.FLAG_COMMUNAL_EDIT_WIDGETS_ACTIVITY_FINISH_FIX, - Flags.FLAG_COMMUNAL_HUB, - Flags.FLAG_COMMUNAL_HUB_USE_THREAD_POOL_FOR_WIDGETS, - Flags.FLAG_COMMUNAL_RESPONSIVE_GRID, - Flags.FLAG_COMMUNAL_SCENE_KTF_REFACTOR, - Flags.FLAG_COMMUNAL_STANDALONE_SUPPORT, - Flags.FLAG_COMMUNAL_TIMER_FLICKER_FIX, - Flags.FLAG_COMMUNAL_WIDGET_RESIZING, - Flags.FLAG_COMMUNAL_WIDGET_TRAMPOLINE_FIX, - Flags.FLAG_COMPOSE_BOUNCER, - Flags.FLAG_CONFINE_NOTIFICATION_TOUCH_TO_VIEW_WIDTH, - Flags.FLAG_CONT_AUTH_PLUGIN, - Flags.FLAG_CONTEXTUAL_TIPS_ASSISTANT_DISMISS_FIX, - Flags.FLAG_COROUTINE_TRACING, - Flags.FLAG_CREATE_WINDOWLESS_WINDOW_MAGNIFIER, - Flags.FLAG_DEBUG_LIVE_UPDATES_PROMOTE_ALL, - Flags.FLAG_DECOUPLE_VIEW_CONTROLLER_IN_ANIMLIB, - Flags.FLAG_DELAY_SHOW_MAGNIFICATION_BUTTON, - Flags.FLAG_DESKTOP_EFFECTS_QS_TILE, - Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR, - Flags.FLAG_DISABLE_BLURRED_SHADE_VISIBLE, - Flags.FLAG_DISABLE_CONTEXTUAL_TIPS_FREQUENCY_CHECK, - Flags.FLAG_DISABLE_CONTEXTUAL_TIPS_IOS_SWITCHER_CHECK, - Flags.FLAG_DISABLE_SHADE_TRACKPAD_TWO_FINGER_SWIPE, - Flags.FLAG_DOUBLE_TAP_TO_SLEEP, - Flags.FLAG_DREAM_INPUT_SESSION_PILFER_ONCE, - Flags.FLAG_DREAM_OVERLAY_BOUNCER_SWIPE_DIRECTION_FILTERING, - Flags.FLAG_DREAM_OVERLAY_UPDATED_FONT, - Flags.FLAG_EDGE_BACK_GESTURE_HANDLER_THREAD, - Flags.FLAG_EDGEBACK_GESTURE_HANDLER_GET_RUNNING_TASKS_BACKGROUND, - Flags.FLAG_ENABLE_BACKGROUND_KEYGUARD_ONDRAWN_CALLBACK, - Flags.FLAG_ENABLE_CONTEXTUAL_TIP_FOR_MUTE_VOLUME, - Flags.FLAG_ENABLE_CONTEXTUAL_TIP_FOR_POWER_OFF, - Flags.FLAG_ENABLE_CONTEXTUAL_TIP_FOR_TAKE_SCREENSHOT, - Flags.FLAG_ENABLE_CONTEXTUAL_TIPS, - Flags.FLAG_ENABLE_EFFICIENT_DISPLAY_REPOSITORY, - Flags.FLAG_ENABLE_LAYOUT_TRACING, - Flags.FLAG_ENABLE_UNDERLAY, - Flags.FLAG_ENABLE_VIEW_CAPTURE_TRACING, - Flags.FLAG_ENFORCE_BRIGHTNESS_BASE_USER_RESTRICTION, - Flags.FLAG_EXAMPLE_FLAG, - Flags.FLAG_EXPAND_COLLAPSE_PRIVACY_DIALOG, - Flags.FLAG_EXPAND_HEADS_UP_ON_INLINE_REPLY, - Flags.FLAG_EXPANDED_PRIVACY_INDICATORS_ON_LARGE_SCREEN, - Flags.FLAG_EXTENDED_APPS_SHORTCUT_CATEGORY, - Flags.FLAG_FACE_MESSAGE_DEFER_UPDATE, - Flags.FLAG_FACE_SCANNING_ANIMATION_NPE_FIX, - Flags.FLAG_FASTER_UNLOCK_TRANSITION, - Flags.FLAG_FETCH_BOOKMARKS_XML_KEYBOARD_SHORTCUTS, - Flags.FLAG_FIX_IMAGE_WALLPAPER_CRASH_SURFACE_ALREADY_RELEASED, - Flags.FLAG_FIX_SCREENSHOT_ACTION_DISMISS_SYSTEM_WINDOWS, - Flags.FLAG_FLOATING_MENU_ANIMATED_TUCK, - Flags.FLAG_FLOATING_MENU_DISPLAY_CUTOUT_SUPPORT, - Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT, - Flags.FLAG_FLOATING_MENU_DRAG_TO_HIDE, - Flags.FLAG_FLOATING_MENU_HEARING_DEVICE_STATUS_ICON, - Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION, - Flags.FLAG_FLOATING_MENU_NARROW_TARGET_CONTENT_OBSERVER, - Flags.FLAG_FLOATING_MENU_NOTIFY_TARGETS_CHANGED_ON_STRICT_DIFF, - Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG, - Flags.FLAG_FLOATING_MENU_RADII_ANIMATION, - Flags.FLAG_GET_CONNECTED_DEVICE_NAME_UNSYNCHRONIZED, - Flags.FLAG_GLANCEABLE_HUB_ALLOW_KEYGUARD_WHEN_DREAMING, - Flags.FLAG_GLANCEABLE_HUB_BLURRED_BACKGROUND, - Flags.FLAG_GLANCEABLE_HUB_DIRECT_EDIT_MODE, - Flags.FLAG_GLANCEABLE_HUB_V2, - Flags.FLAG_GLANCEABLE_HUB_V2_RESOURCES, - Flags.FLAG_HAPTICS_FOR_COMPOSE_SLIDERS, - Flags.FLAG_HARDWARE_COLOR_STYLES, - Flags.FLAG_HEARING_AIDS_QS_TILE_DIALOG, - Flags.FLAG_HEARING_DEVICES_DIALOG_RELATED_TOOLS, - Flags.FLAG_HIDE_RINGER_BUTTON_IN_SINGLE_VOLUME_MODE, - Flags.FLAG_HOME_CONTROLS_DREAM_HSUM, - Flags.FLAG_HUB_EDIT_MODE_TOUCH_ADJUSTMENTS, - Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE, - Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX, - Flags.FLAG_ICON_REFRESH_2025, - Flags.FLAG_IGNORE_TOUCHES_NEXT_TO_NOTIFICATION_SHELF, - Flags.FLAG_INDICATION_TEXT_A11Y_FIX, - Flags.FLAG_KEYBOARD_DOCKING_INDICATOR, - Flags.FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE, - Flags.FLAG_KEYBOARD_SHORTCUT_HELPER_SHORTCUT_CUSTOMIZER, - Flags.FLAG_KEYBOARD_TOUCHPAD_CONTEXTUAL_EDUCATION, - Flags.FLAG_KEYGUARD_TRANSITION_FORCE_FINISH_ON_SCREEN_OFF, - Flags.FLAG_KEYGUARD_WM_REORDER_ATMS_CALLS, - Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR, - Flags.FLAG_LOCKSCREEN_FONT, - Flags.FLAG_LOW_LIGHT_CLOCK_DREAM, - Flags.FLAG_MAGNETIC_NOTIFICATION_SWIPES, - Flags.FLAG_MEDIA_CONTROLS_A11Y_COLORS, - Flags.FLAG_MEDIA_CONTROLS_BUTTON_MEDIA3, - Flags.FLAG_MEDIA_CONTROLS_BUTTON_MEDIA3_PLACEMENT, - Flags.FLAG_MEDIA_CONTROLS_DEVICE_MANAGER_BACKGROUND_EXECUTION, - Flags.FLAG_MEDIA_CONTROLS_DRAWABLES_REUSE_BUGFIX, - Flags.FLAG_MEDIA_CONTROLS_LOCKSCREEN_SHADE_BUG_FIX, - Flags.FLAG_MEDIA_CONTROLS_UI_UPDATE, - Flags.FLAG_MEDIA_CONTROLS_UMO_INFLATION_IN_BACKGROUND, - Flags.FLAG_MEDIA_CONTROLS_USER_INITIATED_DELETEINTENT, - Flags.FLAG_MEDIA_LOAD_METADATA_VIA_MEDIA_DATA_LOADER, - Flags.FLAG_MEDIA_LOCKSCREEN_LAUNCH_ANIMATION, - Flags.FLAG_MEDIA_PROJECTION_DIALOG_BEHIND_LOCKSCREEN, - Flags.FLAG_MEDIA_PROJECTION_GREY_ERROR_TEXT, - Flags.FLAG_MEDIA_PROJECTION_REQUEST_ATTRIBUTION_FIX, - Flags.FLAG_MODES_UI_DIALOG_PAGING, - Flags.FLAG_MOVE_TRANSITION_ANIMATION_LAYER, - Flags.FLAG_MSDL_FEEDBACK, - Flags.FLAG_MULTIUSER_WIFI_PICKER_TRACKER_SUPPORT, - Flags.FLAG_NEW_AOD_TRANSITION, - Flags.FLAG_NEW_VOLUME_PANEL, - Flags.FLAG_NON_TOUCHSCREEN_DEVICES_BYPASS_FALSING, - Flags.FLAG_NOTES_ROLE_QS_TILE, - Flags.FLAG_NOTIFICATION_ADD_X_ON_HOVER_TO_DISMISS, - Flags.FLAG_NOTIFICATION_AMBIENT_SUPPRESSION_AFTER_INFLATION, - Flags.FLAG_NOTIFICATION_ANIMATED_ACTIONS_TREATMENT, - Flags.FLAG_NOTIFICATION_APPEAR_NONLINEAR, - Flags.FLAG_NOTIFICATION_ASYNC_GROUP_HEADER_INFLATION, - Flags.FLAG_NOTIFICATION_ASYNC_HYBRID_VIEW_INFLATION, - Flags.FLAG_NOTIFICATION_AVALANCHE_SUPPRESSION, - Flags.FLAG_NOTIFICATION_AVALANCHE_THROTTLE_HUN, - Flags.FLAG_NOTIFICATION_BACKGROUND_TINT_OPTIMIZATION, - Flags.FLAG_NOTIFICATION_BUNDLE_UI, - Flags.FLAG_NOTIFICATION_COLOR_UPDATE_LOGGER, - Flags.FLAG_NOTIFICATION_CONTENT_ALPHA_OPTIMIZATION, - Flags.FLAG_NOTIFICATION_FOOTER_BACKGROUND_TINT_OPTIMIZATION, - Flags.FLAG_NOTIFICATION_OVER_EXPANSION_CLIPPING_FIX, - Flags.FLAG_NOTIFICATION_REENTRANT_DISMISS, - Flags.FLAG_NOTIFICATION_ROW_ACCESSIBILITY_EXPANDED, - Flags.FLAG_NOTIFICATION_ROW_CONTENT_BINDER_REFACTOR, - Flags.FLAG_NOTIFICATION_ROW_TRANSPARENCY, - Flags.FLAG_NOTIFICATION_ROW_USER_CONTEXT, - Flags.FLAG_NOTIFICATION_SHADE_BLUR, - Flags.FLAG_NOTIFICATION_SHADE_UI_THREAD, - Flags.FLAG_NOTIFICATION_SKIP_SILENT_UPDATES, - Flags.FLAG_NOTIFICATION_TRANSPARENT_HEADER_FIX, - Flags.FLAG_NOTIFICATION_VIEW_FLIPPER_PAUSING_V2, - Flags.FLAG_NOTIFICATIONS_BACKGROUND_ICONS, - Flags.FLAG_NOTIFICATIONS_FOOTER_VISIBILITY_FIX, - Flags.FLAG_NOTIFICATIONS_HIDE_ON_DISPLAY_SWITCH, - Flags.FLAG_NOTIFICATIONS_HUN_SHARED_ANIMATION_VALUES, - Flags.FLAG_NOTIFICATIONS_ICON_CONTAINER_REFACTOR, - Flags.FLAG_NOTIFICATIONS_LAUNCH_RADIUS, - Flags.FLAG_NOTIFICATIONS_LIVE_DATA_STORE_REFACTOR, - Flags.FLAG_NOTIFICATIONS_PINNED_HUN_IN_SHADE, - Flags.FLAG_NOTIFICATIONS_REDESIGN_FOOTER_VIEW, - Flags.FLAG_NOTIFICATIONS_REDESIGN_GUTS, - Flags.FLAG_NOTIFY_PASSWORD_TEXT_VIEW_USER_ACTIVITY_IN_BACKGROUND, - Flags.FLAG_NOTIFY_POWER_MANAGER_USER_ACTIVITY_BACKGROUND, - Flags.FLAG_ONLY_SHOW_MEDIA_STREAM_SLIDER_IN_SINGLE_VOLUME_MODE, - Flags.FLAG_OUTPUT_SWITCHER_REDESIGN, - Flags.FLAG_OVERRIDE_SUPPRESS_OVERLAY_CONDITION, - Flags.FLAG_PERMISSION_HELPER_INLINE_UI_RICH_ONGOING, - Flags.FLAG_PERMISSION_HELPER_UI_RICH_ONGOING, - Flags.FLAG_PHYSICAL_NOTIFICATION_MOVEMENT, - Flags.FLAG_PIN_INPUT_FIELD_STYLED_FOCUS_STATE, - Flags.FLAG_PREDICTIVE_BACK_ANIMATE_SHADE, - Flags.FLAG_PREDICTIVE_BACK_DELAY_WM_TRANSITION, - Flags.FLAG_PRIORITY_PEOPLE_SECTION, - Flags.FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY, - Flags.FLAG_PSS_APP_SELECTOR_RECENTS_SPLIT_SCREEN, - Flags.FLAG_PSS_TASK_SWITCHER, - Flags.FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX, - Flags.FLAG_QS_NEW_TILES, - Flags.FLAG_QS_NEW_TILES_FUTURE, - Flags.FLAG_QS_QUICK_REBIND_ACTIVE_TILES, - Flags.FLAG_QS_REGISTER_SETTING_OBSERVER_ON_BG_THREAD, - Flags.FLAG_QS_TILE_DETAILED_VIEW, - Flags.FLAG_QS_TILE_FOCUS_STATE, - Flags.FLAG_QS_UI_REFACTOR, - Flags.FLAG_QS_UI_REFACTOR_COMPOSE_FRAGMENT, - Flags.FLAG_RECORD_ISSUE_QS_TILE, - Flags.FLAG_REDESIGN_MAGNIFICATION_WINDOW_SIZE, - Flags.FLAG_REFACTOR_GET_CURRENT_USER, - Flags.FLAG_REGISTER_BATTERY_CONTROLLER_RECEIVERS_IN_CORESTARTABLE, - Flags.FLAG_REGISTER_CONTENT_OBSERVERS_ASYNC, - Flags.FLAG_REGISTER_NEW_WALLET_CARD_IN_BACKGROUND, - Flags.FLAG_REGISTER_WALLPAPER_NOTIFIER_BACKGROUND, - Flags.FLAG_RELOCK_WITH_POWER_BUTTON_IMMEDIATELY, - Flags.FLAG_REMOVE_DREAM_OVERLAY_HIDE_ON_TOUCH, - Flags.FLAG_REMOVE_UPDATE_LISTENER_IN_QS_ICON_VIEW_IMPL, - Flags.FLAG_REST_TO_UNLOCK, - Flags.FLAG_RESTART_DREAM_ON_UNOCCLUDE, - Flags.FLAG_REVAMPED_BOUNCER_MESSAGES, - Flags.FLAG_RUN_FINGERPRINT_DETECT_ON_DISMISSIBLE_KEYGUARD, - Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS, - Flags.FLAG_SCENE_CONTAINER, - Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX, - Flags.FLAG_SCREENSHOT_ACTION_DISMISS_SYSTEM_WINDOWS, - Flags.FLAG_SCREENSHOT_MULTIDISPLAY_FOCUS_CHANGE, - Flags.FLAG_SCREENSHOT_POLICY_SPLIT_AND_DESKTOP_MODE, - Flags.FLAG_SCREENSHOT_SCROLL_CROP_VIEW_CRASH_FIX, - Flags.FLAG_SCREENSHOT_UI_CONTROLLER_REFACTOR, - Flags.FLAG_SECONDARY_USER_WIDGET_HOST, - Flags.FLAG_SETTINGS_EXT_REGISTER_CONTENT_OBSERVER_ON_BG_THREAD, - Flags.FLAG_SHADE_EXPANDS_ON_STATUS_BAR_LONG_PRESS, - Flags.FLAG_SHADE_HEADER_FONT_UPDATE, - Flags.FLAG_SHADE_LAUNCH_ACCESSIBILITY, - Flags.FLAG_SHADE_WINDOW_GOES_AROUND, - Flags.FLAG_SHADERLIB_LOADING_EFFECT_REFACTOR, - Flags.FLAG_SHORTCUT_HELPER_KEY_GLYPH, - Flags.FLAG_SHOW_AUDIO_SHARING_SLIDER_IN_VOLUME_PANEL, - Flags.FLAG_SHOW_CLIPBOARD_INDICATION, - Flags.FLAG_SHOW_LOCKED_BY_YOUR_WATCH_KEYGUARD_INDICATOR, - Flags.FLAG_SHOW_TOAST_WHEN_APP_CONTROL_BRIGHTNESS, - Flags.FLAG_SIM_PIN_BOUNCER_RESET, - Flags.FLAG_SIM_PIN_RACE_CONDITION_ON_RESTART, - Flags.FLAG_SIM_PIN_USE_SLOT_ID, - Flags.FLAG_SKIP_HIDE_SENSITIVE_NOTIF_ANIMATION, - Flags.FLAG_SLICE_BROADCAST_RELAY_IN_BACKGROUND, - Flags.FLAG_SLICE_MANAGER_BINDER_CALL_BACKGROUND, - Flags.FLAG_SMARTSPACE_LOCKSCREEN_VIEWMODEL, - Flags.FLAG_SMARTSPACE_RELOCATE_TO_BOTTOM, - Flags.FLAG_SMARTSPACE_REMOTEVIEWS_RENDERING_FIX, - Flags.FLAG_SMARTSPACE_SWIPE_EVENT_LOGGING_FIX, - Flags.FLAG_SMARTSPACE_VIEWPAGER2, - Flags.FLAG_SOUNDDOSE_CUSTOMIZATION, - Flags.FLAG_SPATIAL_MODEL_APP_PUSHBACK, - Flags.FLAG_STABILIZE_HEADS_UP_GROUP_V2, - Flags.FLAG_STATUS_BAR_ALWAYS_CHECK_UNDERLYING_NETWORKS, - Flags.FLAG_STATUS_BAR_AUTO_START_SCREEN_RECORD_CHIP, - Flags.FLAG_STATUS_BAR_CHIPS_MODERNIZATION, - Flags.FLAG_STATUS_BAR_CHIPS_RETURN_ANIMATIONS, - Flags.FLAG_STATUS_BAR_FONT_UPDATES, - Flags.FLAG_STATUS_BAR_MOBILE_ICON_KAIROS, - Flags.FLAG_STATUS_BAR_MONOCHROME_ICONS_FIX, - Flags.FLAG_STATUS_BAR_NO_HUN_BEHAVIOR, - Flags.FLAG_STATUS_BAR_POPUP_CHIPS, - Flags.FLAG_STATUS_BAR_ROOT_MODERNIZATION, - Flags.FLAG_STATUS_BAR_SHOW_AUDIO_ONLY_PROJECTION_CHIP, - Flags.FLAG_STATUS_BAR_SIGNAL_POLICY_REFACTOR, - Flags.FLAG_STATUS_BAR_SIGNAL_POLICY_REFACTOR_ETHERNET, - Flags.FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS, - Flags.FLAG_STATUS_BAR_STOP_UPDATING_WINDOW_HEIGHT, - Flags.FLAG_STATUS_BAR_SWIPE_OVER_CHIP, - Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN, - Flags.FLAG_STATUS_BAR_UI_THREAD, - Flags.FLAG_STATUS_BAR_WINDOW_NO_CUSTOM_TOUCH, - Flags.FLAG_STOPPABLE_FGS_SYSTEM_APP, - Flags.FLAG_SWITCH_USER_ON_BG, - Flags.FLAG_SYSUI_TEAMFOOD, - Flags.FLAG_THEME_OVERLAY_CONTROLLER_WAKEFULNESS_DEPRECATION, - Flags.FLAG_TRANSITION_RACE_CONDITION, - Flags.FLAG_TRANSLUCENT_OCCLUDING_ACTIVITY_FIX, - Flags.FLAG_TV_GLOBAL_ACTIONS_FOCUS, - Flags.FLAG_UDFPS_VIEW_PERFORMANCE, - Flags.FLAG_UNFOLD_ANIMATION_BACKGROUND_PROGRESS, - Flags.FLAG_UNFOLD_LATENCY_TRACKING_FIX, - Flags.FLAG_UPDATE_CORNER_RADIUS_ON_DISPLAY_CHANGED, - Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND, - Flags.FLAG_UPDATE_WINDOW_MAGNIFIER_BOTTOM_BOUNDARY, - Flags.FLAG_USE_AAD_PROX_SENSOR, - Flags.FLAG_USE_NOTIF_INFLATION_THREAD_FOR_FOOTER, - Flags.FLAG_USE_NOTIF_INFLATION_THREAD_FOR_ROW, - Flags.FLAG_USE_TRANSITIONS_FOR_KEYGUARD_OCCLUDED, - Flags.FLAG_USE_VOLUME_CONTROLLER, - Flags.FLAG_USER_AWARE_SETTINGS_REPOSITORIES, - Flags.FLAG_USER_ENCRYPTED_SOURCE, - Flags.FLAG_USER_SWITCHER_ADD_SIGN_OUT_OPTION, - Flags.FLAG_VISUAL_INTERRUPTIONS_REFACTOR, - Flags.FLAG_VOLUME_REDESIGN + Flags.FLAG_ACTIVITY_TRANSITION_USE_LARGEST_WINDOW, + Flags.FLAG_AMBIENT_TOUCH_MONITOR_LISTEN_TO_DISPLAY_CHANGES, + Flags.FLAG_APP_CLIPS_BACKLINKS, + Flags.FLAG_BIND_KEYGUARD_MEDIA_VISIBILITY, + Flags.FLAG_BP_TALKBACK, + Flags.FLAG_BRIGHTNESS_SLIDER_FOCUS_STATE, + Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX, + Flags.FLAG_CLIPBOARD_NONINTERACTIVE_ON_LOCKSCREEN, + Flags.FLAG_CLOCK_REACTIVE_VARIANTS, + Flags.FLAG_COMMUNAL_BOUNCER_DO_NOT_MODIFY_PLUGIN_OPEN, + Flags.FLAG_COMMUNAL_HUB, + Flags.FLAG_COMPOSE_BOUNCER, + Flags.FLAG_COMPOSE_LOCKSCREEN, + Flags.FLAG_CONFINE_NOTIFICATION_TOUCH_TO_VIEW_WIDTH, + Flags.FLAG_CONSTRAINT_BP, + Flags.FLAG_CONTEXTUAL_TIPS_ASSISTANT_DISMISS_FIX, + Flags.FLAG_COROUTINE_TRACING, + Flags.FLAG_CREATE_WINDOWLESS_WINDOW_MAGNIFIER, + Flags.FLAG_DEDICATED_NOTIF_INFLATION_THREAD, + Flags.FLAG_DELAY_SHOW_MAGNIFICATION_BUTTON, + Flags.FLAG_DELAYED_WAKELOCK_RELEASE_ON_BACKGROUND_THREAD, + Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR, + Flags.FLAG_DISABLE_CONTEXTUAL_TIPS_FREQUENCY_CHECK, + Flags.FLAG_DISABLE_CONTEXTUAL_TIPS_IOS_SWITCHER_CHECK, + Flags.FLAG_DOZEUI_SCHEDULING_ALARMS_BACKGROUND_EXECUTION, + Flags.FLAG_DREAM_INPUT_SESSION_PILFER_ONCE, + Flags.FLAG_DREAM_OVERLAY_BOUNCER_SWIPE_DIRECTION_FILTERING, + Flags.FLAG_DUAL_SHADE, + Flags.FLAG_EDGE_BACK_GESTURE_HANDLER_THREAD, + Flags.FLAG_EDGEBACK_GESTURE_HANDLER_GET_RUNNING_TASKS_BACKGROUND, + Flags.FLAG_ENABLE_BACKGROUND_KEYGUARD_ONDRAWN_CALLBACK, + Flags.FLAG_ENABLE_CONTEXTUAL_TIP_FOR_MUTE_VOLUME, + Flags.FLAG_ENABLE_CONTEXTUAL_TIP_FOR_POWER_OFF, + Flags.FLAG_ENABLE_CONTEXTUAL_TIP_FOR_TAKE_SCREENSHOT, + Flags.FLAG_ENABLE_CONTEXTUAL_TIPS, + Flags.FLAG_ENABLE_EFFICIENT_DISPLAY_REPOSITORY, + Flags.FLAG_ENABLE_LAYOUT_TRACING, + Flags.FLAG_ENABLE_VIEW_CAPTURE_TRACING, + Flags.FLAG_ENABLE_WIDGET_PICKER_SIZE_FILTER, + Flags.FLAG_ENFORCE_BRIGHTNESS_BASE_USER_RESTRICTION, + Flags.FLAG_EXAMPLE_FLAG, + Flags.FLAG_FAST_UNLOCK_TRANSITION, + Flags.FLAG_FIX_IMAGE_WALLPAPER_CRASH_SURFACE_ALREADY_RELEASED, + Flags.FLAG_FIX_SCREENSHOT_ACTION_DISMISS_SYSTEM_WINDOWS, + Flags.FLAG_FLOATING_MENU_ANIMATED_TUCK, + Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT, + Flags.FLAG_FLOATING_MENU_DRAG_TO_HIDE, + Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION, + Flags.FLAG_FLOATING_MENU_NARROW_TARGET_CONTENT_OBSERVER, + Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG, + Flags.FLAG_FLOATING_MENU_RADII_ANIMATION, + Flags.FLAG_GET_CONNECTED_DEVICE_NAME_UNSYNCHRONIZED, + Flags.FLAG_GLANCEABLE_HUB_ALLOW_KEYGUARD_WHEN_DREAMING, + Flags.FLAG_GLANCEABLE_HUB_FULLSCREEN_SWIPE, + Flags.FLAG_GLANCEABLE_HUB_GESTURE_HANDLE, + Flags.FLAG_GLANCEABLE_HUB_SHORTCUT_BUTTON, + Flags.FLAG_HAPTIC_BRIGHTNESS_SLIDER, + Flags.FLAG_HAPTIC_VOLUME_SLIDER, + Flags.FLAG_HEARING_AIDS_QS_TILE_DIALOG, + Flags.FLAG_HEARING_DEVICES_DIALOG_RELATED_TOOLS, + Flags.FLAG_KEYBOARD_DOCKING_INDICATOR, + Flags.FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE, + Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR, + Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR, + Flags.FLAG_LIGHT_REVEAL_MIGRATION, + Flags.FLAG_MEDIA_CONTROLS_LOCKSCREEN_SHADE_BUG_FIX, + Flags.FLAG_MEDIA_CONTROLS_REFACTOR, + Flags.FLAG_MEDIA_CONTROLS_USER_INITIATED_DELETEINTENT, + Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, + Flags.FLAG_NEW_AOD_TRANSITION, + Flags.FLAG_NEW_TOUCHPAD_GESTURES_TUTORIAL, + Flags.FLAG_NEW_VOLUME_PANEL, + Flags.FLAG_NOTIFICATION_ASYNC_GROUP_HEADER_INFLATION, + Flags.FLAG_NOTIFICATION_ASYNC_HYBRID_VIEW_INFLATION, + Flags.FLAG_NOTIFICATION_AVALANCHE_SUPPRESSION, + Flags.FLAG_NOTIFICATION_AVALANCHE_THROTTLE_HUN, + Flags.FLAG_NOTIFICATION_BACKGROUND_TINT_OPTIMIZATION, + Flags.FLAG_NOTIFICATION_COLOR_UPDATE_LOGGER, + Flags.FLAG_NOTIFICATION_CONTENT_ALPHA_OPTIMIZATION, + Flags.FLAG_NOTIFICATION_FOOTER_BACKGROUND_TINT_OPTIMIZATION, + Flags.FLAG_NOTIFICATION_MEDIA_MANAGER_BACKGROUND_EXECUTION, + Flags.FLAG_NOTIFICATION_MINIMALISM_PROTOTYPE, + Flags.FLAG_NOTIFICATION_OVER_EXPANSION_CLIPPING_FIX, + Flags.FLAG_NOTIFICATION_PULSING_FIX, + Flags.FLAG_NOTIFICATION_ROW_CONTENT_BINDER_REFACTOR, + Flags.FLAG_NOTIFICATION_ROW_USER_CONTEXT, + Flags.FLAG_NOTIFICATION_VIEW_FLIPPER_PAUSING_V2, + Flags.FLAG_NOTIFICATIONS_BACKGROUND_ICONS, + Flags.FLAG_NOTIFICATIONS_FOOTER_VIEW_REFACTOR, + Flags.FLAG_NOTIFICATIONS_HEADS_UP_REFACTOR, + Flags.FLAG_NOTIFICATIONS_HIDE_ON_DISPLAY_SWITCH, + Flags.FLAG_NOTIFICATIONS_ICON_CONTAINER_REFACTOR, + Flags.FLAG_NOTIFICATIONS_IMPROVED_HUN_ANIMATION, + Flags.FLAG_NOTIFICATIONS_LIVE_DATA_STORE_REFACTOR, + Flags.FLAG_NOTIFY_POWER_MANAGER_USER_ACTIVITY_BACKGROUND, + Flags.FLAG_PIN_INPUT_FIELD_STYLED_FOCUS_STATE, + Flags.FLAG_PREDICTIVE_BACK_ANIMATE_BOUNCER, + Flags.FLAG_PREDICTIVE_BACK_ANIMATE_DIALOGS, + Flags.FLAG_PREDICTIVE_BACK_ANIMATE_SHADE, + Flags.FLAG_PREDICTIVE_BACK_SYSUI, + Flags.FLAG_PRIORITY_PEOPLE_SECTION, + Flags.FLAG_PRIVACY_DOT_UNFOLD_WRONG_CORNER_FIX, + Flags.FLAG_PSS_APP_SELECTOR_ABRUPT_EXIT_FIX, + Flags.FLAG_PSS_APP_SELECTOR_RECENTS_SPLIT_SCREEN, + Flags.FLAG_PSS_TASK_SWITCHER, + Flags.FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX, + Flags.FLAG_QS_NEW_PIPELINE, + Flags.FLAG_QS_NEW_TILES, + Flags.FLAG_QS_NEW_TILES_FUTURE, + Flags.FLAG_QS_TILE_FOCUS_STATE, + Flags.FLAG_QS_UI_REFACTOR, + Flags.FLAG_QUICK_SETTINGS_VISUAL_HAPTICS_LONGPRESS, + Flags.FLAG_RECORD_ISSUE_QS_TILE, + Flags.FLAG_REFACTOR_GET_CURRENT_USER, + Flags.FLAG_REGISTER_BATTERY_CONTROLLER_RECEIVERS_IN_CORESTARTABLE, + Flags.FLAG_REGISTER_NEW_WALLET_CARD_IN_BACKGROUND, + Flags.FLAG_REGISTER_WALLPAPER_NOTIFIER_BACKGROUND, + Flags.FLAG_REGISTER_ZEN_MODE_CONTENT_OBSERVER_BACKGROUND, + Flags.FLAG_REMOVE_DREAM_OVERLAY_HIDE_ON_TOUCH, + Flags.FLAG_REST_TO_UNLOCK, + Flags.FLAG_RESTART_DREAM_ON_UNOCCLUDE, + Flags.FLAG_REVAMPED_BOUNCER_MESSAGES, + Flags.FLAG_RUN_FINGERPRINT_DETECT_ON_DISMISSIBLE_KEYGUARD, + Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS, + Flags.FLAG_SCENE_CONTAINER, + Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX, + Flags.FLAG_SCREENSHOT_ACTION_DISMISS_SYSTEM_WINDOWS, + Flags.FLAG_SCREENSHOT_PRIVATE_PROFILE_ACCESSIBILITY_ANNOUNCEMENT_FIX, + Flags.FLAG_SCREENSHOT_PRIVATE_PROFILE_BEHAVIOR_FIX, + Flags.FLAG_SCREENSHOT_SCROLL_CROP_VIEW_CRASH_FIX, + Flags.FLAG_SCREENSHOT_SHELF_UI2, + Flags.FLAG_SHADE_COLLAPSE_ACTIVITY_LAUNCH_FIX, + Flags.FLAG_SHADERLIB_LOADING_EFFECT_REFACTOR, + Flags.FLAG_SLICE_BROADCAST_RELAY_IN_BACKGROUND, + Flags.FLAG_SLICE_MANAGER_BINDER_CALL_BACKGROUND, + Flags.FLAG_SMARTSPACE_LOCKSCREEN_VIEWMODEL, + Flags.FLAG_SMARTSPACE_RELOCATE_TO_BOTTOM, + Flags.FLAG_SMARTSPACE_REMOTEVIEWS_RENDERING, + Flags.FLAG_STATUS_BAR_MONOCHROME_ICONS_FIX, + Flags.FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, + Flags.FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS, + Flags.FLAG_SWITCH_USER_ON_BG, + Flags.FLAG_SYSUI_TEAMFOOD, + Flags.FLAG_THEME_OVERLAY_CONTROLLER_WAKEFULNESS_DEPRECATION, + Flags.FLAG_TRANSLUCENT_OCCLUDING_ACTIVITY_FIX, + Flags.FLAG_TRUNCATED_STATUS_BAR_ICONS_FIX, + Flags.FLAG_UDFPS_VIEW_PERFORMANCE, + Flags.FLAG_UNFOLD_ANIMATION_BACKGROUND_PROGRESS, + Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND, + Flags.FLAG_VALIDATE_KEYBOARD_SHORTCUT_HELPER_ICON_URI, + Flags.FLAG_VISUAL_INTERRUPTIONS_REFACTOR ); } private Set mReadOnlyFlagsSet = new HashSet<>( - Arrays.asList( - Flags.FLAG_ACTIVITY_TRANSITION_USE_LARGEST_WINDOW, - Flags.FLAG_ADD_BLACK_BACKGROUND_FOR_WINDOW_MAGNIFIER, - Flags.FLAG_ALWAYS_COMPOSE_QS_UI_FRAGMENT, - Flags.FLAG_AMBIENT_TOUCH_MONITOR_LISTEN_TO_DISPLAY_CHANGES, - Flags.FLAG_APP_CLIPS_BACKLINKS, - Flags.FLAG_APP_SHORTCUT_REMOVAL_FIX, - Flags.FLAG_AVALANCHE_REPLACE_HUN_WHEN_CRITICAL, - Flags.FLAG_BIND_KEYGUARD_MEDIA_VISIBILITY, - Flags.FLAG_BOUNCER_UI_REVAMP, - Flags.FLAG_BOUNCER_UI_REVAMP_2, - Flags.FLAG_BP_COLORS, - Flags.FLAG_BRIGHTNESS_SLIDER_FOCUS_STATE, - Flags.FLAG_CHECK_LOCKSCREEN_GONE_TRANSITION, - Flags.FLAG_CLASSIC_FLAGS_MULTI_USER, - Flags.FLAG_CLIPBOARD_IMAGE_TIMEOUT, - Flags.FLAG_CLIPBOARD_NONINTERACTIVE_ON_LOCKSCREEN, - Flags.FLAG_CLIPBOARD_OVERLAY_MULTIUSER, - Flags.FLAG_CLIPBOARD_SHARED_TRANSITIONS, - Flags.FLAG_CLIPBOARD_USE_DESCRIPTION_MIMETYPE, - Flags.FLAG_CLOCK_FIDGET_ANIMATION, - Flags.FLAG_COMMUNAL_BOUNCER_DO_NOT_MODIFY_PLUGIN_OPEN, - Flags.FLAG_COMMUNAL_EDIT_WIDGETS_ACTIVITY_FINISH_FIX, - Flags.FLAG_COMMUNAL_HUB, - Flags.FLAG_COMMUNAL_HUB_USE_THREAD_POOL_FOR_WIDGETS, - Flags.FLAG_COMMUNAL_RESPONSIVE_GRID, - Flags.FLAG_COMMUNAL_SCENE_KTF_REFACTOR, - Flags.FLAG_COMMUNAL_STANDALONE_SUPPORT, - Flags.FLAG_COMMUNAL_TIMER_FLICKER_FIX, - Flags.FLAG_COMMUNAL_WIDGET_RESIZING, - Flags.FLAG_COMMUNAL_WIDGET_TRAMPOLINE_FIX, - Flags.FLAG_COMPOSE_BOUNCER, - Flags.FLAG_CONFINE_NOTIFICATION_TOUCH_TO_VIEW_WIDTH, - Flags.FLAG_CONT_AUTH_PLUGIN, - Flags.FLAG_CONTEXTUAL_TIPS_ASSISTANT_DISMISS_FIX, - Flags.FLAG_COROUTINE_TRACING, - Flags.FLAG_CREATE_WINDOWLESS_WINDOW_MAGNIFIER, - Flags.FLAG_DEBUG_LIVE_UPDATES_PROMOTE_ALL, - Flags.FLAG_DECOUPLE_VIEW_CONTROLLER_IN_ANIMLIB, - Flags.FLAG_DELAY_SHOW_MAGNIFICATION_BUTTON, - Flags.FLAG_DESKTOP_EFFECTS_QS_TILE, - Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR, - Flags.FLAG_DISABLE_BLURRED_SHADE_VISIBLE, - Flags.FLAG_DISABLE_CONTEXTUAL_TIPS_FREQUENCY_CHECK, - Flags.FLAG_DISABLE_CONTEXTUAL_TIPS_IOS_SWITCHER_CHECK, - Flags.FLAG_DISABLE_SHADE_TRACKPAD_TWO_FINGER_SWIPE, - Flags.FLAG_DOUBLE_TAP_TO_SLEEP, - Flags.FLAG_DREAM_INPUT_SESSION_PILFER_ONCE, - Flags.FLAG_DREAM_OVERLAY_BOUNCER_SWIPE_DIRECTION_FILTERING, - Flags.FLAG_DREAM_OVERLAY_UPDATED_FONT, - Flags.FLAG_EDGE_BACK_GESTURE_HANDLER_THREAD, - Flags.FLAG_EDGEBACK_GESTURE_HANDLER_GET_RUNNING_TASKS_BACKGROUND, - Flags.FLAG_ENABLE_BACKGROUND_KEYGUARD_ONDRAWN_CALLBACK, - Flags.FLAG_ENABLE_CONTEXTUAL_TIP_FOR_MUTE_VOLUME, - Flags.FLAG_ENABLE_CONTEXTUAL_TIP_FOR_POWER_OFF, - Flags.FLAG_ENABLE_CONTEXTUAL_TIP_FOR_TAKE_SCREENSHOT, - Flags.FLAG_ENABLE_CONTEXTUAL_TIPS, - Flags.FLAG_ENABLE_EFFICIENT_DISPLAY_REPOSITORY, - Flags.FLAG_ENABLE_LAYOUT_TRACING, - Flags.FLAG_ENABLE_UNDERLAY, - Flags.FLAG_ENABLE_VIEW_CAPTURE_TRACING, - Flags.FLAG_ENFORCE_BRIGHTNESS_BASE_USER_RESTRICTION, - Flags.FLAG_EXAMPLE_FLAG, - Flags.FLAG_EXPAND_COLLAPSE_PRIVACY_DIALOG, - Flags.FLAG_EXPAND_HEADS_UP_ON_INLINE_REPLY, - Flags.FLAG_EXPANDED_PRIVACY_INDICATORS_ON_LARGE_SCREEN, - Flags.FLAG_EXTENDED_APPS_SHORTCUT_CATEGORY, - Flags.FLAG_FACE_MESSAGE_DEFER_UPDATE, - Flags.FLAG_FACE_SCANNING_ANIMATION_NPE_FIX, - Flags.FLAG_FASTER_UNLOCK_TRANSITION, - Flags.FLAG_FETCH_BOOKMARKS_XML_KEYBOARD_SHORTCUTS, - Flags.FLAG_FIX_IMAGE_WALLPAPER_CRASH_SURFACE_ALREADY_RELEASED, - Flags.FLAG_FIX_SCREENSHOT_ACTION_DISMISS_SYSTEM_WINDOWS, - Flags.FLAG_FLOATING_MENU_ANIMATED_TUCK, - Flags.FLAG_FLOATING_MENU_DISPLAY_CUTOUT_SUPPORT, - Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT, - Flags.FLAG_FLOATING_MENU_DRAG_TO_HIDE, - Flags.FLAG_FLOATING_MENU_HEARING_DEVICE_STATUS_ICON, - Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION, - Flags.FLAG_FLOATING_MENU_NARROW_TARGET_CONTENT_OBSERVER, - Flags.FLAG_FLOATING_MENU_NOTIFY_TARGETS_CHANGED_ON_STRICT_DIFF, - Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG, - Flags.FLAG_FLOATING_MENU_RADII_ANIMATION, - Flags.FLAG_GET_CONNECTED_DEVICE_NAME_UNSYNCHRONIZED, - Flags.FLAG_GLANCEABLE_HUB_ALLOW_KEYGUARD_WHEN_DREAMING, - Flags.FLAG_GLANCEABLE_HUB_BLURRED_BACKGROUND, - Flags.FLAG_GLANCEABLE_HUB_DIRECT_EDIT_MODE, - Flags.FLAG_GLANCEABLE_HUB_V2, - Flags.FLAG_GLANCEABLE_HUB_V2_RESOURCES, - Flags.FLAG_HAPTICS_FOR_COMPOSE_SLIDERS, - Flags.FLAG_HARDWARE_COLOR_STYLES, - Flags.FLAG_HEARING_AIDS_QS_TILE_DIALOG, - Flags.FLAG_HEARING_DEVICES_DIALOG_RELATED_TOOLS, - Flags.FLAG_HIDE_RINGER_BUTTON_IN_SINGLE_VOLUME_MODE, - Flags.FLAG_HOME_CONTROLS_DREAM_HSUM, - Flags.FLAG_HUB_EDIT_MODE_TOUCH_ADJUSTMENTS, - Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE, - Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX, - Flags.FLAG_ICON_REFRESH_2025, - Flags.FLAG_IGNORE_TOUCHES_NEXT_TO_NOTIFICATION_SHELF, - Flags.FLAG_INDICATION_TEXT_A11Y_FIX, - Flags.FLAG_KEYBOARD_DOCKING_INDICATOR, - Flags.FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE, - Flags.FLAG_KEYBOARD_SHORTCUT_HELPER_SHORTCUT_CUSTOMIZER, - Flags.FLAG_KEYBOARD_TOUCHPAD_CONTEXTUAL_EDUCATION, - Flags.FLAG_KEYGUARD_TRANSITION_FORCE_FINISH_ON_SCREEN_OFF, - Flags.FLAG_KEYGUARD_WM_REORDER_ATMS_CALLS, - Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR, - Flags.FLAG_LOCKSCREEN_FONT, - Flags.FLAG_LOW_LIGHT_CLOCK_DREAM, - Flags.FLAG_MAGNETIC_NOTIFICATION_SWIPES, - Flags.FLAG_MEDIA_CONTROLS_A11Y_COLORS, - Flags.FLAG_MEDIA_CONTROLS_BUTTON_MEDIA3, - Flags.FLAG_MEDIA_CONTROLS_BUTTON_MEDIA3_PLACEMENT, - Flags.FLAG_MEDIA_CONTROLS_DEVICE_MANAGER_BACKGROUND_EXECUTION, - Flags.FLAG_MEDIA_CONTROLS_DRAWABLES_REUSE_BUGFIX, - Flags.FLAG_MEDIA_CONTROLS_LOCKSCREEN_SHADE_BUG_FIX, - Flags.FLAG_MEDIA_CONTROLS_UI_UPDATE, - Flags.FLAG_MEDIA_CONTROLS_UMO_INFLATION_IN_BACKGROUND, - Flags.FLAG_MEDIA_CONTROLS_USER_INITIATED_DELETEINTENT, - Flags.FLAG_MEDIA_LOAD_METADATA_VIA_MEDIA_DATA_LOADER, - Flags.FLAG_MEDIA_LOCKSCREEN_LAUNCH_ANIMATION, - Flags.FLAG_MEDIA_PROJECTION_DIALOG_BEHIND_LOCKSCREEN, - Flags.FLAG_MEDIA_PROJECTION_GREY_ERROR_TEXT, - Flags.FLAG_MEDIA_PROJECTION_REQUEST_ATTRIBUTION_FIX, - Flags.FLAG_MODES_UI_DIALOG_PAGING, - Flags.FLAG_MOVE_TRANSITION_ANIMATION_LAYER, - Flags.FLAG_MSDL_FEEDBACK, - Flags.FLAG_MULTIUSER_WIFI_PICKER_TRACKER_SUPPORT, - Flags.FLAG_NEW_AOD_TRANSITION, - Flags.FLAG_NEW_VOLUME_PANEL, - Flags.FLAG_NON_TOUCHSCREEN_DEVICES_BYPASS_FALSING, - Flags.FLAG_NOTES_ROLE_QS_TILE, - Flags.FLAG_NOTIFICATION_ADD_X_ON_HOVER_TO_DISMISS, - Flags.FLAG_NOTIFICATION_AMBIENT_SUPPRESSION_AFTER_INFLATION, - Flags.FLAG_NOTIFICATION_ANIMATED_ACTIONS_TREATMENT, - Flags.FLAG_NOTIFICATION_APPEAR_NONLINEAR, - Flags.FLAG_NOTIFICATION_ASYNC_GROUP_HEADER_INFLATION, - Flags.FLAG_NOTIFICATION_ASYNC_HYBRID_VIEW_INFLATION, - Flags.FLAG_NOTIFICATION_AVALANCHE_SUPPRESSION, - Flags.FLAG_NOTIFICATION_AVALANCHE_THROTTLE_HUN, - Flags.FLAG_NOTIFICATION_BACKGROUND_TINT_OPTIMIZATION, - Flags.FLAG_NOTIFICATION_BUNDLE_UI, - Flags.FLAG_NOTIFICATION_COLOR_UPDATE_LOGGER, - Flags.FLAG_NOTIFICATION_CONTENT_ALPHA_OPTIMIZATION, - Flags.FLAG_NOTIFICATION_FOOTER_BACKGROUND_TINT_OPTIMIZATION, - Flags.FLAG_NOTIFICATION_OVER_EXPANSION_CLIPPING_FIX, - Flags.FLAG_NOTIFICATION_REENTRANT_DISMISS, - Flags.FLAG_NOTIFICATION_ROW_ACCESSIBILITY_EXPANDED, - Flags.FLAG_NOTIFICATION_ROW_CONTENT_BINDER_REFACTOR, - Flags.FLAG_NOTIFICATION_ROW_TRANSPARENCY, - Flags.FLAG_NOTIFICATION_ROW_USER_CONTEXT, - Flags.FLAG_NOTIFICATION_SHADE_BLUR, - Flags.FLAG_NOTIFICATION_SHADE_UI_THREAD, - Flags.FLAG_NOTIFICATION_SKIP_SILENT_UPDATES, - Flags.FLAG_NOTIFICATION_TRANSPARENT_HEADER_FIX, - Flags.FLAG_NOTIFICATION_VIEW_FLIPPER_PAUSING_V2, - Flags.FLAG_NOTIFICATIONS_BACKGROUND_ICONS, - Flags.FLAG_NOTIFICATIONS_FOOTER_VISIBILITY_FIX, - Flags.FLAG_NOTIFICATIONS_HIDE_ON_DISPLAY_SWITCH, - Flags.FLAG_NOTIFICATIONS_HUN_SHARED_ANIMATION_VALUES, - Flags.FLAG_NOTIFICATIONS_ICON_CONTAINER_REFACTOR, - Flags.FLAG_NOTIFICATIONS_LAUNCH_RADIUS, - Flags.FLAG_NOTIFICATIONS_LIVE_DATA_STORE_REFACTOR, - Flags.FLAG_NOTIFICATIONS_PINNED_HUN_IN_SHADE, - Flags.FLAG_NOTIFICATIONS_REDESIGN_FOOTER_VIEW, - Flags.FLAG_NOTIFICATIONS_REDESIGN_GUTS, - Flags.FLAG_NOTIFY_PASSWORD_TEXT_VIEW_USER_ACTIVITY_IN_BACKGROUND, - Flags.FLAG_NOTIFY_POWER_MANAGER_USER_ACTIVITY_BACKGROUND, - Flags.FLAG_ONLY_SHOW_MEDIA_STREAM_SLIDER_IN_SINGLE_VOLUME_MODE, - Flags.FLAG_OUTPUT_SWITCHER_REDESIGN, - Flags.FLAG_OVERRIDE_SUPPRESS_OVERLAY_CONDITION, - Flags.FLAG_PERMISSION_HELPER_INLINE_UI_RICH_ONGOING, - Flags.FLAG_PERMISSION_HELPER_UI_RICH_ONGOING, - Flags.FLAG_PHYSICAL_NOTIFICATION_MOVEMENT, - Flags.FLAG_PIN_INPUT_FIELD_STYLED_FOCUS_STATE, - Flags.FLAG_PREDICTIVE_BACK_ANIMATE_SHADE, - Flags.FLAG_PREDICTIVE_BACK_DELAY_WM_TRANSITION, - Flags.FLAG_PRIORITY_PEOPLE_SECTION, - Flags.FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY, - Flags.FLAG_PSS_APP_SELECTOR_RECENTS_SPLIT_SCREEN, - Flags.FLAG_PSS_TASK_SWITCHER, - Flags.FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX, - Flags.FLAG_QS_NEW_TILES, - Flags.FLAG_QS_NEW_TILES_FUTURE, - Flags.FLAG_QS_QUICK_REBIND_ACTIVE_TILES, - Flags.FLAG_QS_REGISTER_SETTING_OBSERVER_ON_BG_THREAD, - Flags.FLAG_QS_TILE_DETAILED_VIEW, - Flags.FLAG_QS_TILE_FOCUS_STATE, - Flags.FLAG_QS_UI_REFACTOR, - Flags.FLAG_QS_UI_REFACTOR_COMPOSE_FRAGMENT, - Flags.FLAG_RECORD_ISSUE_QS_TILE, - Flags.FLAG_REDESIGN_MAGNIFICATION_WINDOW_SIZE, - Flags.FLAG_REFACTOR_GET_CURRENT_USER, - Flags.FLAG_REGISTER_BATTERY_CONTROLLER_RECEIVERS_IN_CORESTARTABLE, - Flags.FLAG_REGISTER_CONTENT_OBSERVERS_ASYNC, - Flags.FLAG_REGISTER_NEW_WALLET_CARD_IN_BACKGROUND, - Flags.FLAG_REGISTER_WALLPAPER_NOTIFIER_BACKGROUND, - Flags.FLAG_RELOCK_WITH_POWER_BUTTON_IMMEDIATELY, - Flags.FLAG_REMOVE_DREAM_OVERLAY_HIDE_ON_TOUCH, - Flags.FLAG_REMOVE_UPDATE_LISTENER_IN_QS_ICON_VIEW_IMPL, - Flags.FLAG_REST_TO_UNLOCK, - Flags.FLAG_RESTART_DREAM_ON_UNOCCLUDE, - Flags.FLAG_REVAMPED_BOUNCER_MESSAGES, - Flags.FLAG_RUN_FINGERPRINT_DETECT_ON_DISMISSIBLE_KEYGUARD, - Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS, - Flags.FLAG_SCENE_CONTAINER, - Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX, - Flags.FLAG_SCREENSHOT_ACTION_DISMISS_SYSTEM_WINDOWS, - Flags.FLAG_SCREENSHOT_MULTIDISPLAY_FOCUS_CHANGE, - Flags.FLAG_SCREENSHOT_POLICY_SPLIT_AND_DESKTOP_MODE, - Flags.FLAG_SCREENSHOT_SCROLL_CROP_VIEW_CRASH_FIX, - Flags.FLAG_SCREENSHOT_UI_CONTROLLER_REFACTOR, - Flags.FLAG_SECONDARY_USER_WIDGET_HOST, - Flags.FLAG_SETTINGS_EXT_REGISTER_CONTENT_OBSERVER_ON_BG_THREAD, - Flags.FLAG_SHADE_EXPANDS_ON_STATUS_BAR_LONG_PRESS, - Flags.FLAG_SHADE_HEADER_FONT_UPDATE, - Flags.FLAG_SHADE_LAUNCH_ACCESSIBILITY, - Flags.FLAG_SHADE_WINDOW_GOES_AROUND, - Flags.FLAG_SHADERLIB_LOADING_EFFECT_REFACTOR, - Flags.FLAG_SHORTCUT_HELPER_KEY_GLYPH, - Flags.FLAG_SHOW_AUDIO_SHARING_SLIDER_IN_VOLUME_PANEL, - Flags.FLAG_SHOW_CLIPBOARD_INDICATION, - Flags.FLAG_SHOW_LOCKED_BY_YOUR_WATCH_KEYGUARD_INDICATOR, - Flags.FLAG_SHOW_TOAST_WHEN_APP_CONTROL_BRIGHTNESS, - Flags.FLAG_SIM_PIN_BOUNCER_RESET, - Flags.FLAG_SIM_PIN_RACE_CONDITION_ON_RESTART, - Flags.FLAG_SIM_PIN_USE_SLOT_ID, - Flags.FLAG_SKIP_HIDE_SENSITIVE_NOTIF_ANIMATION, - Flags.FLAG_SLICE_BROADCAST_RELAY_IN_BACKGROUND, - Flags.FLAG_SLICE_MANAGER_BINDER_CALL_BACKGROUND, - Flags.FLAG_SMARTSPACE_LOCKSCREEN_VIEWMODEL, - Flags.FLAG_SMARTSPACE_RELOCATE_TO_BOTTOM, - Flags.FLAG_SMARTSPACE_REMOTEVIEWS_RENDERING_FIX, - Flags.FLAG_SMARTSPACE_SWIPE_EVENT_LOGGING_FIX, - Flags.FLAG_SMARTSPACE_VIEWPAGER2, - Flags.FLAG_SOUNDDOSE_CUSTOMIZATION, - Flags.FLAG_SPATIAL_MODEL_APP_PUSHBACK, - Flags.FLAG_STABILIZE_HEADS_UP_GROUP_V2, - Flags.FLAG_STATUS_BAR_ALWAYS_CHECK_UNDERLYING_NETWORKS, - Flags.FLAG_STATUS_BAR_AUTO_START_SCREEN_RECORD_CHIP, - Flags.FLAG_STATUS_BAR_CHIPS_MODERNIZATION, - Flags.FLAG_STATUS_BAR_CHIPS_RETURN_ANIMATIONS, - Flags.FLAG_STATUS_BAR_FONT_UPDATES, - Flags.FLAG_STATUS_BAR_MOBILE_ICON_KAIROS, - Flags.FLAG_STATUS_BAR_MONOCHROME_ICONS_FIX, - Flags.FLAG_STATUS_BAR_NO_HUN_BEHAVIOR, - Flags.FLAG_STATUS_BAR_POPUP_CHIPS, - Flags.FLAG_STATUS_BAR_ROOT_MODERNIZATION, - Flags.FLAG_STATUS_BAR_SHOW_AUDIO_ONLY_PROJECTION_CHIP, - Flags.FLAG_STATUS_BAR_SIGNAL_POLICY_REFACTOR, - Flags.FLAG_STATUS_BAR_SIGNAL_POLICY_REFACTOR_ETHERNET, - Flags.FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS, - Flags.FLAG_STATUS_BAR_STOP_UPDATING_WINDOW_HEIGHT, - Flags.FLAG_STATUS_BAR_SWIPE_OVER_CHIP, - Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN, - Flags.FLAG_STATUS_BAR_UI_THREAD, - Flags.FLAG_STATUS_BAR_WINDOW_NO_CUSTOM_TOUCH, - Flags.FLAG_STOPPABLE_FGS_SYSTEM_APP, - Flags.FLAG_SWITCH_USER_ON_BG, - Flags.FLAG_SYSUI_TEAMFOOD, - Flags.FLAG_THEME_OVERLAY_CONTROLLER_WAKEFULNESS_DEPRECATION, - Flags.FLAG_TRANSITION_RACE_CONDITION, - Flags.FLAG_TRANSLUCENT_OCCLUDING_ACTIVITY_FIX, - Flags.FLAG_TV_GLOBAL_ACTIONS_FOCUS, - Flags.FLAG_UDFPS_VIEW_PERFORMANCE, - Flags.FLAG_UNFOLD_ANIMATION_BACKGROUND_PROGRESS, - Flags.FLAG_UNFOLD_LATENCY_TRACKING_FIX, - Flags.FLAG_UPDATE_CORNER_RADIUS_ON_DISPLAY_CHANGED, - Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND, - Flags.FLAG_UPDATE_WINDOW_MAGNIFIER_BOTTOM_BOUNDARY, - Flags.FLAG_USE_AAD_PROX_SENSOR, - Flags.FLAG_USE_NOTIF_INFLATION_THREAD_FOR_FOOTER, - Flags.FLAG_USE_NOTIF_INFLATION_THREAD_FOR_ROW, - Flags.FLAG_USE_TRANSITIONS_FOR_KEYGUARD_OCCLUDED, - Flags.FLAG_USE_VOLUME_CONTROLLER, - Flags.FLAG_USER_AWARE_SETTINGS_REPOSITORIES, - Flags.FLAG_USER_ENCRYPTED_SOURCE, - Flags.FLAG_USER_SWITCHER_ADD_SIGN_OUT_OPTION, - Flags.FLAG_VISUAL_INTERRUPTIONS_REFACTOR, - Flags.FLAG_VOLUME_REDESIGN, - "" - ) + Arrays.asList( + "" + ) ); } diff --git a/flags/src/com/android/systemui/FakeFeatureFlagsImpl.java b/flags/src/com/android/systemui/FakeFeatureFlagsImpl.java index 49523621c0..0054d4f3a5 100644 --- a/flags/src/com/android/systemui/FakeFeatureFlagsImpl.java +++ b/flags/src/com/android/systemui/FakeFeatureFlagsImpl.java @@ -3,6 +3,7 @@ package com.android.systemui; import java.util.HashMap; import java.util.Map; import java.util.function.Predicate; + /** @hide */ public class FakeFeatureFlagsImpl extends CustomFeatureFlags { private final Map mFlagMap = new HashMap<>(); diff --git a/flags/src/com/android/systemui/FeatureFlags.java b/flags/src/com/android/systemui/FeatureFlags.java index 681e59c0eb..6ce51d50c3 100644 --- a/flags/src/com/android/systemui/FeatureFlags.java +++ b/flags/src/com/android/systemui/FeatureFlags.java @@ -3,1124 +3,308 @@ package com.android.systemui; /** @hide */ public interface FeatureFlags { - - - + boolean activityTransitionUseLargestWindow(); - - - - boolean addBlackBackgroundForWindowMagnifier(); - - - - boolean alwaysComposeQsUiFragment(); - - - + boolean ambientTouchMonitorListenToDisplayChanges(); - - - + boolean appClipsBacklinks(); - - - - boolean appShortcutRemovalFix(); - - - - boolean avalancheReplaceHunWhenCritical(); - - - + boolean bindKeyguardMediaVisibility(); - - - - boolean bouncerUiRevamp(); - - - - boolean bouncerUiRevamp2(); - - - - boolean bpColors(); - - - + + boolean bpTalkback(); + boolean brightnessSliderFocusState(); - - - - boolean checkLockscreenGoneTransition(); - - - - boolean classicFlagsMultiUser(); - - - - boolean clipboardImageTimeout(); - - - + + boolean centralizedStatusBarHeightFix(); + boolean clipboardNoninteractiveOnLockscreen(); - - - - boolean clipboardOverlayMultiuser(); - - - - boolean clipboardSharedTransitions(); - - - - boolean clipboardUseDescriptionMimetype(); - - - - boolean clockFidgetAnimation(); - - - + + boolean clockReactiveVariants(); + boolean communalBouncerDoNotModifyPluginOpen(); - - - - boolean communalEditWidgetsActivityFinishFix(); - - - + boolean communalHub(); - - - - boolean communalHubUseThreadPoolForWidgets(); - - - - boolean communalResponsiveGrid(); - - - - boolean communalSceneKtfRefactor(); - - - - boolean communalStandaloneSupport(); - - - - boolean communalTimerFlickerFix(); - - - - boolean communalWidgetResizing(); - - - - boolean communalWidgetTrampolineFix(); - - - + boolean composeBouncer(); - - - + + boolean composeLockscreen(); + boolean confineNotificationTouchToViewWidth(); - - - - boolean contAuthPlugin(); - - - + + boolean constraintBp(); + boolean contextualTipsAssistantDismissFix(); - - - + boolean coroutineTracing(); - - - + boolean createWindowlessWindowMagnifier(); - - - - boolean debugLiveUpdatesPromoteAll(); - - - - boolean decoupleViewControllerInAnimlib(); - - - + + boolean dedicatedNotifInflationThread(); + boolean delayShowMagnificationButton(); - - - - boolean desktopEffectsQsTile(); - - - + + boolean delayedWakelockReleaseOnBackgroundThread(); + boolean deviceEntryUdfpsRefactor(); - - - - boolean disableBlurredShadeVisible(); - - - + boolean disableContextualTipsFrequencyCheck(); - - - + boolean disableContextualTipsIosSwitcherCheck(); - - - - boolean disableShadeTrackpadTwoFingerSwipe(); - - - - boolean doubleTapToSleep(); - - - + + boolean dozeuiSchedulingAlarmsBackgroundExecution(); + boolean dreamInputSessionPilferOnce(); - - - + boolean dreamOverlayBouncerSwipeDirectionFiltering(); - - - - boolean dreamOverlayUpdatedFont(); - - - + + boolean dualShade(); + boolean edgeBackGestureHandlerThread(); - - - + boolean edgebackGestureHandlerGetRunningTasksBackground(); - - - + boolean enableBackgroundKeyguardOndrawnCallback(); - - - + boolean enableContextualTipForMuteVolume(); - - - + boolean enableContextualTipForPowerOff(); - - - + boolean enableContextualTipForTakeScreenshot(); - - - + boolean enableContextualTips(); - - - + boolean enableEfficientDisplayRepository(); - - - + boolean enableLayoutTracing(); - - - - boolean enableUnderlay(); - - - + boolean enableViewCaptureTracing(); - - - + + boolean enableWidgetPickerSizeFilter(); + boolean enforceBrightnessBaseUserRestriction(); - - - + boolean exampleFlag(); - - - - boolean expandCollapsePrivacyDialog(); - - - - boolean expandHeadsUpOnInlineReply(); - - - - boolean expandedPrivacyIndicatorsOnLargeScreen(); - - - - boolean extendedAppsShortcutCategory(); - - - - boolean faceMessageDeferUpdate(); - - - - boolean faceScanningAnimationNpeFix(); - - - - boolean fasterUnlockTransition(); - - - - boolean fetchBookmarksXmlKeyboardShortcuts(); - - - + + boolean fastUnlockTransition(); + boolean fixImageWallpaperCrashSurfaceAlreadyReleased(); - - - + boolean fixScreenshotActionDismissSystemWindows(); - - - + boolean floatingMenuAnimatedTuck(); - - - - boolean floatingMenuDisplayCutoutSupport(); - - - + boolean floatingMenuDragToEdit(); - - - + boolean floatingMenuDragToHide(); - - - - boolean floatingMenuHearingDeviceStatusIcon(); - - - + boolean floatingMenuImeDisplacementAnimation(); - - - + boolean floatingMenuNarrowTargetContentObserver(); - - - - boolean floatingMenuNotifyTargetsChangedOnStrictDiff(); - - - + boolean floatingMenuOverlapsNavBarsFlag(); - - - + boolean floatingMenuRadiiAnimation(); - - - + + boolean generatedPreviews(); + boolean getConnectedDeviceNameUnsynchronized(); - - - + boolean glanceableHubAllowKeyguardWhenDreaming(); - - - - boolean glanceableHubBlurredBackground(); - - - - boolean glanceableHubDirectEditMode(); - - - - boolean glanceableHubV2(); - - - - boolean glanceableHubV2Resources(); - - - - boolean hapticsForComposeSliders(); - - - - boolean hardwareColorStyles(); - - - + + boolean glanceableHubFullscreenSwipe(); + + boolean glanceableHubGestureHandle(); + + boolean glanceableHubShortcutButton(); + + boolean hapticBrightnessSlider(); + + boolean hapticVolumeSlider(); + boolean hearingAidsQsTileDialog(); - - - + boolean hearingDevicesDialogRelatedTools(); - - - - boolean hideRingerButtonInSingleVolumeMode(); - - - - boolean homeControlsDreamHsum(); - - - - boolean hubEditModeTouchAdjustments(); - - - - boolean hubmodeFullscreenVerticalSwipe(); - - - - boolean hubmodeFullscreenVerticalSwipeFix(); - - - - boolean iconRefresh2025(); - - - - boolean ignoreTouchesNextToNotificationShelf(); - - - - boolean indicationTextA11yFix(); - - - + boolean keyboardDockingIndicator(); - - - + boolean keyboardShortcutHelperRewrite(); - - - - boolean keyboardShortcutHelperShortcutCustomizer(); - - - - boolean keyboardTouchpadContextualEducation(); - - - - boolean keyguardTransitionForceFinishOnScreenOff(); - - - - boolean keyguardWmReorderAtmsCalls(); - - - + + boolean keyguardBottomAreaRefactor(); + boolean keyguardWmStateRefactor(); - - - - boolean lockscreenFont(); - - - - boolean lowLightClockDream(); - - - - boolean magneticNotificationSwipes(); - - - - boolean mediaControlsA11yColors(); - - - - boolean mediaControlsButtonMedia3(); - - - - boolean mediaControlsButtonMedia3Placement(); - - - - boolean mediaControlsDeviceManagerBackgroundExecution(); - - - - boolean mediaControlsDrawablesReuseBugfix(); - - - + + boolean lightRevealMigration(); + boolean mediaControlsLockscreenShadeBugFix(); - - - - boolean mediaControlsUiUpdate(); - - - - boolean mediaControlsUmoInflationInBackground(); - - - + + boolean mediaControlsRefactor(); + boolean mediaControlsUserInitiatedDeleteintent(); - - - - boolean mediaLoadMetadataViaMediaDataLoader(); - - - - boolean mediaLockscreenLaunchAnimation(); - - - - boolean mediaProjectionDialogBehindLockscreen(); - - - - boolean mediaProjectionGreyErrorText(); - - - - boolean mediaProjectionRequestAttributionFix(); - - - - boolean modesUiDialogPaging(); - - - - boolean moveTransitionAnimationLayer(); - - - - boolean msdlFeedback(); - - - - boolean multiuserWifiPickerTrackerSupport(); - - - + + boolean migrateClocksToBlueprint(); + boolean newAodTransition(); - - - + + boolean newTouchpadGesturesTutorial(); + boolean newVolumePanel(); - - - - boolean nonTouchscreenDevicesBypassFalsing(); - - - - boolean notesRoleQsTile(); - - - - boolean notificationAddXOnHoverToDismiss(); - - - - boolean notificationAmbientSuppressionAfterInflation(); - - - - boolean notificationAnimatedActionsTreatment(); - - - - boolean notificationAppearNonlinear(); - - - + boolean notificationAsyncGroupHeaderInflation(); - - - + boolean notificationAsyncHybridViewInflation(); - - - + boolean notificationAvalancheSuppression(); - - - + boolean notificationAvalancheThrottleHun(); - - - + boolean notificationBackgroundTintOptimization(); - - - - boolean notificationBundleUi(); - - - + boolean notificationColorUpdateLogger(); - - - + boolean notificationContentAlphaOptimization(); - - - + boolean notificationFooterBackgroundTintOptimization(); - - - + + boolean notificationMediaManagerBackgroundExecution(); + + boolean notificationMinimalismPrototype(); + boolean notificationOverExpansionClippingFix(); - - - - boolean notificationReentrantDismiss(); - - - - boolean notificationRowAccessibilityExpanded(); - - - + + boolean notificationPulsingFix(); + boolean notificationRowContentBinderRefactor(); - - - - boolean notificationRowTransparency(); - - - + boolean notificationRowUserContext(); - - - - boolean notificationShadeBlur(); - - - - boolean notificationShadeUiThread(); - - - - boolean notificationSkipSilentUpdates(); - - - - boolean notificationTransparentHeaderFix(); - - - + boolean notificationViewFlipperPausingV2(); - - - + boolean notificationsBackgroundIcons(); - - - - boolean notificationsFooterVisibilityFix(); - - - + + boolean notificationsFooterViewRefactor(); + + boolean notificationsHeadsUpRefactor(); + boolean notificationsHideOnDisplaySwitch(); - - - - boolean notificationsHunSharedAnimationValues(); - - - + boolean notificationsIconContainerRefactor(); - - - - boolean notificationsLaunchRadius(); - - - + + boolean notificationsImprovedHunAnimation(); + boolean notificationsLiveDataStoreRefactor(); - - - - boolean notificationsPinnedHunInShade(); - - - - boolean notificationsRedesignFooterView(); - - - - boolean notificationsRedesignGuts(); - - - - boolean notifyPasswordTextViewUserActivityInBackground(); - - - + boolean notifyPowerManagerUserActivityBackground(); - - - - boolean onlyShowMediaStreamSliderInSingleVolumeMode(); - - - - boolean outputSwitcherRedesign(); - - - - boolean overrideSuppressOverlayCondition(); - - - - boolean permissionHelperInlineUiRichOngoing(); - - - - boolean permissionHelperUiRichOngoing(); - - - - boolean physicalNotificationMovement(); - - - + boolean pinInputFieldStyledFocusState(); - - - + + boolean predictiveBackAnimateBouncer(); + + boolean predictiveBackAnimateDialogs(); + boolean predictiveBackAnimateShade(); - - - - boolean predictiveBackDelayWmTransition(); - - - + + boolean predictiveBackSysui(); + boolean priorityPeopleSection(); - - - - boolean promoteNotificationsAutomatically(); - - - + + boolean privacyDotUnfoldWrongCornerFix(); + + boolean pssAppSelectorAbruptExitFix(); + boolean pssAppSelectorRecentsSplitScreen(); - - - + boolean pssTaskSwitcher(); - - - + boolean qsCustomTileClickGuaranteedBugFix(); - - - + + boolean qsNewPipeline(); + boolean qsNewTiles(); - - - + boolean qsNewTilesFuture(); - - - - boolean qsQuickRebindActiveTiles(); - - - - boolean qsRegisterSettingObserverOnBgThread(); - - - - boolean qsTileDetailedView(); - - - + boolean qsTileFocusState(); - - - + boolean qsUiRefactor(); - - - - boolean qsUiRefactorComposeFragment(); - - - + + boolean quickSettingsVisualHapticsLongpress(); + boolean recordIssueQsTile(); - - - - boolean redesignMagnificationWindowSize(); - - - + boolean refactorGetCurrentUser(); - - - + boolean registerBatteryControllerReceiversInCorestartable(); - - - - boolean registerContentObserversAsync(); - - - + boolean registerNewWalletCardInBackground(); - - - + boolean registerWallpaperNotifierBackground(); - - - - boolean relockWithPowerButtonImmediately(); - - - + + boolean registerZenModeContentObserverBackground(); + boolean removeDreamOverlayHideOnTouch(); - - - - boolean removeUpdateListenerInQsIconViewImpl(); - - - + boolean restToUnlock(); - - - + boolean restartDreamOnUnocclude(); - - - + boolean revampedBouncerMessages(); - - - + boolean runFingerprintDetectOnDismissibleKeyguard(); - - - + boolean saveAndRestoreMagnificationSettingsButtons(); - - - + boolean sceneContainer(); - - - + boolean screenshareNotificationHidingBugFix(); - - - + boolean screenshotActionDismissSystemWindows(); - - - - boolean screenshotMultidisplayFocusChange(); - - - - boolean screenshotPolicySplitAndDesktopMode(); - - - + + boolean screenshotPrivateProfileAccessibilityAnnouncementFix(); + + boolean screenshotPrivateProfileBehaviorFix(); + boolean screenshotScrollCropViewCrashFix(); - - - - boolean screenshotUiControllerRefactor(); - - - - boolean secondaryUserWidgetHost(); - - - - boolean settingsExtRegisterContentObserverOnBgThread(); - - - - boolean shadeExpandsOnStatusBarLongPress(); - - - - boolean shadeHeaderFontUpdate(); - - - - boolean shadeLaunchAccessibility(); - - - - boolean shadeWindowGoesAround(); - - - + + boolean screenshotShelfUi2(); + + boolean shadeCollapseActivityLaunchFix(); + boolean shaderlibLoadingEffectRefactor(); - - - - boolean shortcutHelperKeyGlyph(); - - - - boolean showAudioSharingSliderInVolumePanel(); - - - - boolean showClipboardIndication(); - - - - boolean showLockedByYourWatchKeyguardIndicator(); - - - - boolean showToastWhenAppControlBrightness(); - - - - boolean simPinBouncerReset(); - - - - boolean simPinRaceConditionOnRestart(); - - - - boolean simPinUseSlotId(); - - - - boolean skipHideSensitiveNotifAnimation(); - - - + boolean sliceBroadcastRelayInBackground(); - - - + boolean sliceManagerBinderCallBackground(); - - - + boolean smartspaceLockscreenViewmodel(); - - - + boolean smartspaceRelocateToBottom(); - - - - boolean smartspaceRemoteviewsRenderingFix(); - - - - boolean smartspaceSwipeEventLoggingFix(); - - - - boolean smartspaceViewpager2(); - - - - boolean sounddoseCustomization(); - - - - boolean spatialModelAppPushback(); - - - - boolean stabilizeHeadsUpGroupV2(); - - - - boolean statusBarAlwaysCheckUnderlyingNetworks(); - - - - boolean statusBarAutoStartScreenRecordChip(); - - - - boolean statusBarChipsModernization(); - - - - boolean statusBarChipsReturnAnimations(); - - - - boolean statusBarFontUpdates(); - - - - boolean statusBarMobileIconKairos(); - - - + + boolean smartspaceRemoteviewsRendering(); + boolean statusBarMonochromeIconsFix(); - - - - boolean statusBarNoHunBehavior(); - - - - boolean statusBarPopupChips(); - - - - boolean statusBarRootModernization(); - - - - boolean statusBarShowAudioOnlyProjectionChip(); - - - - boolean statusBarSignalPolicyRefactor(); - - - - boolean statusBarSignalPolicyRefactorEthernet(); - - - + + boolean statusBarScreenSharingChips(); + boolean statusBarStaticInoutIndicators(); - - - - boolean statusBarStopUpdatingWindowHeight(); - - - - boolean statusBarSwipeOverChip(); - - - - boolean statusBarSwitchToSpnFromDataSpn(); - - - - boolean statusBarUiThread(); - - - - boolean statusBarWindowNoCustomTouch(); - - - - boolean stoppableFgsSystemApp(); - - - + boolean switchUserOnBg(); - - - + boolean sysuiTeamfood(); - - - + boolean themeOverlayControllerWakefulnessDeprecation(); - - - - boolean transitionRaceCondition(); - - - + boolean translucentOccludingActivityFix(); - - - - boolean tvGlobalActionsFocus(); - - - + + boolean truncatedStatusBarIconsFix(); + boolean udfpsViewPerformance(); - - - + boolean unfoldAnimationBackgroundProgress(); - - - - boolean unfoldLatencyTrackingFix(); - - - - boolean updateCornerRadiusOnDisplayChanged(); - - - + boolean updateUserSwitcherBackground(); - - - - boolean updateWindowMagnifierBottomBoundary(); - - - - boolean useAadProxSensor(); - - - - boolean useNotifInflationThreadForFooter(); - - - - boolean useNotifInflationThreadForRow(); - - - - boolean useTransitionsForKeyguardOccluded(); - - - - boolean useVolumeController(); - - - - boolean userAwareSettingsRepositories(); - - - - boolean userEncryptedSource(); - - - - boolean userSwitcherAddSignOutOption(); - - - + + boolean validateKeyboardShortcutHelperIconUri(); + boolean visualInterruptionsRefactor(); - - - - boolean volumeRedesign(); } diff --git a/flags/src/com/android/systemui/FeatureFlagsImpl.java b/flags/src/com/android/systemui/FeatureFlagsImpl.java index 7dbc6b650e..32049600d5 100644 --- a/flags/src/com/android/systemui/FeatureFlagsImpl.java +++ b/flags/src/com/android/systemui/FeatureFlagsImpl.java @@ -1,1965 +1,3178 @@ package com.android.systemui; // TODO(b/303773055): Remove the annotation after access issue is resolved. + +import com.android.quickstep.util.DeviceConfigHelper; + +import java.nio.file.Files; +import java.nio.file.Paths; /** @hide */ public final class FeatureFlagsImpl implements FeatureFlags { + private static final boolean isReadFromNew = Files.exists(Paths.get("/metadata/aconfig/boot/enable_only_new_storage")); + private static volatile boolean isCached = false; + private static volatile boolean accessibility_is_cached = false; + private static volatile boolean biometrics_framework_is_cached = false; + private static volatile boolean communal_is_cached = false; + private static volatile boolean systemui_is_cached = false; + private static boolean activityTransitionUseLargestWindow = true; + private static boolean ambientTouchMonitorListenToDisplayChanges = false; + private static boolean appClipsBacklinks = false; + private static boolean bindKeyguardMediaVisibility = true; + private static boolean bpTalkback = true; + private static boolean brightnessSliderFocusState = false; + private static boolean centralizedStatusBarHeightFix = true; + private static boolean clipboardNoninteractiveOnLockscreen = false; + private static boolean clockReactiveVariants = false; + private static boolean communalBouncerDoNotModifyPluginOpen = false; + private static boolean communalHub = true; + private static boolean composeBouncer = false; + private static boolean composeLockscreen = false; + private static boolean confineNotificationTouchToViewWidth = true; + private static boolean constraintBp = true; + private static boolean contextualTipsAssistantDismissFix = true; + private static boolean coroutineTracing = true; + private static boolean createWindowlessWindowMagnifier = true; + private static boolean dedicatedNotifInflationThread = true; + private static boolean delayShowMagnificationButton = true; + private static boolean delayedWakelockReleaseOnBackgroundThread = true; + private static boolean deviceEntryUdfpsRefactor = true; + private static boolean disableContextualTipsFrequencyCheck = true; + private static boolean disableContextualTipsIosSwitcherCheck = true; + private static boolean dozeuiSchedulingAlarmsBackgroundExecution = false; + private static boolean dreamInputSessionPilferOnce = false; + private static boolean dreamOverlayBouncerSwipeDirectionFiltering = true; + private static boolean dualShade = false; + private static boolean edgeBackGestureHandlerThread = false; + private static boolean edgebackGestureHandlerGetRunningTasksBackground = true; + private static boolean enableBackgroundKeyguardOndrawnCallback = true; + private static boolean enableContextualTipForMuteVolume = false; + private static boolean enableContextualTipForPowerOff = true; + private static boolean enableContextualTipForTakeScreenshot = true; + private static boolean enableContextualTips = true; + private static boolean enableEfficientDisplayRepository = false; + private static boolean enableLayoutTracing = false; + private static boolean enableViewCaptureTracing = false; + private static boolean enableWidgetPickerSizeFilter = false; + private static boolean enforceBrightnessBaseUserRestriction = true; + private static boolean exampleFlag = false; + private static boolean fastUnlockTransition = false; + private static boolean fixImageWallpaperCrashSurfaceAlreadyReleased = true; + private static boolean fixScreenshotActionDismissSystemWindows = true; + private static boolean floatingMenuAnimatedTuck = true; + private static boolean floatingMenuDragToEdit = true; + private static boolean floatingMenuDragToHide = false; + private static boolean floatingMenuImeDisplacementAnimation = true; + private static boolean floatingMenuNarrowTargetContentObserver = true; + private static boolean floatingMenuOverlapsNavBarsFlag = true; + private static boolean floatingMenuRadiiAnimation = true; + private static boolean generatedPreviews = true; + private static boolean getConnectedDeviceNameUnsynchronized = true; + private static boolean glanceableHubAllowKeyguardWhenDreaming = false; + private static boolean glanceableHubFullscreenSwipe = false; + private static boolean glanceableHubGestureHandle = false; + private static boolean glanceableHubShortcutButton = false; + private static boolean hapticBrightnessSlider = true; + private static boolean hapticVolumeSlider = true; + private static boolean hearingAidsQsTileDialog = true; + private static boolean hearingDevicesDialogRelatedTools = true; + private static boolean keyboardDockingIndicator = true; + private static boolean keyboardShortcutHelperRewrite = false; + private static boolean keyguardBottomAreaRefactor = true; + private static boolean keyguardWmStateRefactor = false; + private static boolean lightRevealMigration = true; + private static boolean mediaControlsLockscreenShadeBugFix = true; + private static boolean mediaControlsRefactor = true; + private static boolean mediaControlsUserInitiatedDeleteintent = true; + private static boolean migrateClocksToBlueprint = true; + private static boolean newAodTransition = true; + private static boolean newTouchpadGesturesTutorial = false; + private static boolean newVolumePanel = true; + private static boolean notificationAsyncGroupHeaderInflation = true; + private static boolean notificationAsyncHybridViewInflation = true; + private static boolean notificationAvalancheSuppression = true; + private static boolean notificationAvalancheThrottleHun = true; + private static boolean notificationBackgroundTintOptimization = true; + private static boolean notificationColorUpdateLogger = false; + private static boolean notificationContentAlphaOptimization = true; + private static boolean notificationFooterBackgroundTintOptimization = false; + private static boolean notificationMediaManagerBackgroundExecution = true; + private static boolean notificationMinimalismPrototype = false; + private static boolean notificationOverExpansionClippingFix = true; + private static boolean notificationPulsingFix = true; + private static boolean notificationRowContentBinderRefactor = false; + private static boolean notificationRowUserContext = true; + private static boolean notificationViewFlipperPausingV2 = true; + private static boolean notificationsBackgroundIcons = false; + private static boolean notificationsFooterViewRefactor = true; + private static boolean notificationsHeadsUpRefactor = true; + private static boolean notificationsHideOnDisplaySwitch = false; + private static boolean notificationsIconContainerRefactor = true; + private static boolean notificationsImprovedHunAnimation = true; + private static boolean notificationsLiveDataStoreRefactor = true; + private static boolean notifyPowerManagerUserActivityBackground = true; + private static boolean pinInputFieldStyledFocusState = true; + private static boolean predictiveBackAnimateBouncer = true; + private static boolean predictiveBackAnimateDialogs = true; + private static boolean predictiveBackAnimateShade = false; + private static boolean predictiveBackSysui = true; + private static boolean priorityPeopleSection = true; + private static boolean privacyDotUnfoldWrongCornerFix = true; + private static boolean pssAppSelectorAbruptExitFix = true; + private static boolean pssAppSelectorRecentsSplitScreen = true; + private static boolean pssTaskSwitcher = false; + private static boolean qsCustomTileClickGuaranteedBugFix = true; + private static boolean qsNewPipeline = true; + private static boolean qsNewTiles = false; + private static boolean qsNewTilesFuture = false; + private static boolean qsTileFocusState = true; + private static boolean qsUiRefactor = false; + private static boolean quickSettingsVisualHapticsLongpress = true; + private static boolean recordIssueQsTile = true; + private static boolean refactorGetCurrentUser = true; + private static boolean registerBatteryControllerReceiversInCorestartable = false; + private static boolean registerNewWalletCardInBackground = true; + private static boolean registerWallpaperNotifierBackground = true; + private static boolean registerZenModeContentObserverBackground = true; + private static boolean removeDreamOverlayHideOnTouch = true; + private static boolean restToUnlock = false; + private static boolean restartDreamOnUnocclude = false; + private static boolean revampedBouncerMessages = true; + private static boolean runFingerprintDetectOnDismissibleKeyguard = true; + private static boolean saveAndRestoreMagnificationSettingsButtons = false; + private static boolean sceneContainer = false; + private static boolean screenshareNotificationHidingBugFix = true; + private static boolean screenshotActionDismissSystemWindows = true; + private static boolean screenshotPrivateProfileAccessibilityAnnouncementFix = true; + private static boolean screenshotPrivateProfileBehaviorFix = true; + private static boolean screenshotScrollCropViewCrashFix = true; + private static boolean screenshotShelfUi2 = true; + private static boolean shadeCollapseActivityLaunchFix = false; + private static boolean shaderlibLoadingEffectRefactor = true; + private static boolean sliceBroadcastRelayInBackground = true; + private static boolean sliceManagerBinderCallBackground = true; + private static boolean smartspaceLockscreenViewmodel = true; + private static boolean smartspaceRelocateToBottom = false; + private static boolean smartspaceRemoteviewsRendering = false; + private static boolean statusBarMonochromeIconsFix = true; + private static boolean statusBarScreenSharingChips = true; + private static boolean statusBarStaticInoutIndicators = false; + private static boolean switchUserOnBg = true; + private static boolean sysuiTeamfood = true; + private static boolean themeOverlayControllerWakefulnessDeprecation = false; + private static boolean translucentOccludingActivityFix = false; + private static boolean truncatedStatusBarIconsFix = true; + private static boolean udfpsViewPerformance = true; + private static boolean unfoldAnimationBackgroundProgress = true; + private static boolean updateUserSwitcherBackground = true; + private static boolean validateKeyboardShortcutHelperIconUri = true; + private static boolean visualInterruptionsRefactor = true; + + + private void init() { + boolean foundPackage = true; + + createWindowlessWindowMagnifier = foundPackage; + + + delayShowMagnificationButton = foundPackage; + + + floatingMenuAnimatedTuck = foundPackage; + + + floatingMenuDragToEdit = foundPackage; + + + floatingMenuDragToHide = foundPackage; + + + floatingMenuImeDisplacementAnimation = foundPackage; + + + floatingMenuNarrowTargetContentObserver = foundPackage; + + + floatingMenuOverlapsNavBarsFlag = foundPackage; + + + floatingMenuRadiiAnimation = foundPackage; + + + hearingDevicesDialogRelatedTools = foundPackage; + + saveAndRestoreMagnificationSettingsButtons = foundPackage; + bpTalkback = foundPackage; + constraintBp = foundPackage; + communalHub = foundPackage; + enableWidgetPickerSizeFilter = foundPackage; + activityTransitionUseLargestWindow = foundPackage; + ambientTouchMonitorListenToDisplayChanges = foundPackage; + appClipsBacklinks = foundPackage; + bindKeyguardMediaVisibility = foundPackage; + brightnessSliderFocusState = foundPackage; + centralizedStatusBarHeightFix = foundPackage; + clipboardNoninteractiveOnLockscreen = foundPackage; + clockReactiveVariants = foundPackage; + communalBouncerDoNotModifyPluginOpen = foundPackage; + composeBouncer = foundPackage; + composeLockscreen = foundPackage; + confineNotificationTouchToViewWidth = foundPackage; + contextualTipsAssistantDismissFix = foundPackage; + coroutineTracing = foundPackage; + dedicatedNotifInflationThread = foundPackage; + delayedWakelockReleaseOnBackgroundThread = foundPackage; + deviceEntryUdfpsRefactor = foundPackage; + disableContextualTipsFrequencyCheck = foundPackage; + disableContextualTipsIosSwitcherCheck = foundPackage; + dozeuiSchedulingAlarmsBackgroundExecution = foundPackage; + dreamInputSessionPilferOnce = foundPackage; + dreamOverlayBouncerSwipeDirectionFiltering = foundPackage; + dualShade = foundPackage; + edgeBackGestureHandlerThread = foundPackage; + edgebackGestureHandlerGetRunningTasksBackground = foundPackage; + enableBackgroundKeyguardOndrawnCallback = foundPackage; + enableContextualTipForMuteVolume = foundPackage; + enableContextualTipForPowerOff = foundPackage; + enableContextualTipForTakeScreenshot = foundPackage; + enableContextualTips = foundPackage; + enableEfficientDisplayRepository = foundPackage; + enableLayoutTracing = foundPackage; + enableViewCaptureTracing = foundPackage; + enforceBrightnessBaseUserRestriction = foundPackage; + exampleFlag = foundPackage; + fastUnlockTransition = foundPackage; + fixImageWallpaperCrashSurfaceAlreadyReleased = foundPackage; + fixScreenshotActionDismissSystemWindows = foundPackage; + generatedPreviews = foundPackage; + getConnectedDeviceNameUnsynchronized = foundPackage; + glanceableHubAllowKeyguardWhenDreaming = foundPackage; + glanceableHubFullscreenSwipe = foundPackage; + glanceableHubGestureHandle = foundPackage; + glanceableHubShortcutButton = foundPackage; + hapticBrightnessSlider = foundPackage; + hapticVolumeSlider = foundPackage; + hearingAidsQsTileDialog = foundPackage; + keyboardDockingIndicator = foundPackage; + keyboardShortcutHelperRewrite = foundPackage; + keyguardBottomAreaRefactor = foundPackage; + keyguardWmStateRefactor = foundPackage; + lightRevealMigration = foundPackage; + mediaControlsLockscreenShadeBugFix = foundPackage; + mediaControlsRefactor = foundPackage; + mediaControlsUserInitiatedDeleteintent = foundPackage; + migrateClocksToBlueprint = foundPackage; + newAodTransition = foundPackage; + newTouchpadGesturesTutorial = foundPackage; + newVolumePanel = foundPackage; + notificationAsyncGroupHeaderInflation = foundPackage; + notificationAsyncHybridViewInflation = foundPackage; + notificationAvalancheSuppression = foundPackage; + notificationAvalancheThrottleHun = foundPackage; + notificationBackgroundTintOptimization = foundPackage; + notificationColorUpdateLogger = foundPackage; + notificationContentAlphaOptimization = foundPackage; + notificationFooterBackgroundTintOptimization = foundPackage; + notificationMediaManagerBackgroundExecution = foundPackage; + notificationMinimalismPrototype = foundPackage; + notificationOverExpansionClippingFix = foundPackage; + notificationPulsingFix = foundPackage; + notificationRowContentBinderRefactor = foundPackage; + notificationRowUserContext = foundPackage; + notificationViewFlipperPausingV2 = foundPackage; + notificationsBackgroundIcons = foundPackage; + notificationsFooterViewRefactor = foundPackage; + notificationsHeadsUpRefactor = foundPackage; + notificationsHideOnDisplaySwitch = foundPackage; + notificationsIconContainerRefactor = foundPackage; + notificationsImprovedHunAnimation = foundPackage; + notificationsLiveDataStoreRefactor = foundPackage; + notifyPowerManagerUserActivityBackground = foundPackage; + pinInputFieldStyledFocusState = foundPackage; + predictiveBackAnimateBouncer = foundPackage; + predictiveBackAnimateDialogs = foundPackage; + predictiveBackAnimateShade = foundPackage; + predictiveBackSysui = foundPackage; + priorityPeopleSection = foundPackage; + privacyDotUnfoldWrongCornerFix = foundPackage; + pssAppSelectorAbruptExitFix = foundPackage; + pssAppSelectorRecentsSplitScreen = foundPackage; + pssTaskSwitcher = foundPackage; + qsCustomTileClickGuaranteedBugFix = foundPackage; + qsNewPipeline = foundPackage; + qsNewTiles = foundPackage; + qsNewTilesFuture = foundPackage; + qsTileFocusState = foundPackage; + qsUiRefactor = foundPackage; + quickSettingsVisualHapticsLongpress = foundPackage; + recordIssueQsTile = foundPackage; + refactorGetCurrentUser = foundPackage; + registerBatteryControllerReceiversInCorestartable = foundPackage; + registerNewWalletCardInBackground = foundPackage; + registerWallpaperNotifierBackground = foundPackage; + registerZenModeContentObserverBackground = foundPackage; + removeDreamOverlayHideOnTouch = foundPackage; + restToUnlock = foundPackage; + restartDreamOnUnocclude = foundPackage; + revampedBouncerMessages = foundPackage; + runFingerprintDetectOnDismissibleKeyguard = foundPackage; + sceneContainer = foundPackage; + screenshareNotificationHidingBugFix = foundPackage; + screenshotActionDismissSystemWindows = foundPackage; + screenshotPrivateProfileAccessibilityAnnouncementFix = foundPackage; + screenshotPrivateProfileBehaviorFix = foundPackage; + screenshotScrollCropViewCrashFix = foundPackage; + screenshotShelfUi2 = foundPackage; + shadeCollapseActivityLaunchFix = foundPackage; + shaderlibLoadingEffectRefactor = foundPackage; + sliceBroadcastRelayInBackground = foundPackage; + sliceManagerBinderCallBackground = foundPackage; + smartspaceLockscreenViewmodel = foundPackage; + smartspaceRelocateToBottom = foundPackage; + smartspaceRemoteviewsRendering = foundPackage; + + + statusBarMonochromeIconsFix = foundPackage; + + + statusBarScreenSharingChips = foundPackage; + + + statusBarStaticInoutIndicators = foundPackage; + + + switchUserOnBg = foundPackage; + + + sysuiTeamfood = foundPackage; + + + themeOverlayControllerWakefulnessDeprecation = foundPackage; + + + translucentOccludingActivityFix = foundPackage; + + + truncatedStatusBarIconsFix = foundPackage; + + + udfpsViewPerformance = foundPackage; + + + unfoldAnimationBackgroundProgress = foundPackage; + + + updateUserSwitcherBackground = foundPackage; + + + validateKeyboardShortcutHelperIconUri = foundPackage; + + + visualInterruptionsRefactor = foundPackage; + + isCached = true; + } + + + + + private void load_overrides_accessibility() { + try { + var properties = DeviceConfigHelper.Companion.getPrefs(); + createWindowlessWindowMagnifier = + properties.getBoolean(Flags.FLAG_CREATE_WINDOWLESS_WINDOW_MAGNIFIER, true); + delayShowMagnificationButton = + properties.getBoolean(Flags.FLAG_DELAY_SHOW_MAGNIFICATION_BUTTON, true); + floatingMenuAnimatedTuck = + properties.getBoolean(Flags.FLAG_FLOATING_MENU_ANIMATED_TUCK, true); + floatingMenuDragToEdit = + properties.getBoolean(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT, true); + floatingMenuDragToHide = + properties.getBoolean(Flags.FLAG_FLOATING_MENU_DRAG_TO_HIDE, false); + floatingMenuImeDisplacementAnimation = + properties.getBoolean(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION, true); + floatingMenuNarrowTargetContentObserver = + properties.getBoolean(Flags.FLAG_FLOATING_MENU_NARROW_TARGET_CONTENT_OBSERVER, true); + floatingMenuOverlapsNavBarsFlag = + properties.getBoolean(Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG, true); + floatingMenuRadiiAnimation = + properties.getBoolean(Flags.FLAG_FLOATING_MENU_RADII_ANIMATION, true); + hearingDevicesDialogRelatedTools = + properties.getBoolean(Flags.FLAG_HEARING_DEVICES_DIALOG_RELATED_TOOLS, true); + saveAndRestoreMagnificationSettingsButtons = + properties.getBoolean(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS, false); + } catch (NullPointerException e) { + throw new RuntimeException( + "Cannot read value from namespace accessibility " + + "from DeviceConfig. It could be that the code using flag " + + "executed before SettingsProvider initialization. Please use " + + "fixed read-only flag by adding is_fixed_read_only: true in " + + "flag declaration.", + e + ); + } + accessibility_is_cached = true; + } + + private void load_overrides_biometrics_framework() { + try { + var properties = DeviceConfigHelper.Companion.getPrefs(); + bpTalkback = + properties.getBoolean(Flags.FLAG_BP_TALKBACK, true); + constraintBp = + properties.getBoolean(Flags.FLAG_CONSTRAINT_BP, true); + } catch (NullPointerException e) { + throw new RuntimeException( + "Cannot read value from namespace biometrics_framework " + + "from DeviceConfig. It could be that the code using flag " + + "executed before SettingsProvider initialization. Please use " + + "fixed read-only flag by adding is_fixed_read_only: true in " + + "flag declaration.", + e + ); + } + biometrics_framework_is_cached = true; + } + + private void load_overrides_communal() { + try { + var properties = DeviceConfigHelper.Companion.getPrefs(); + communalHub = + properties.getBoolean(Flags.FLAG_COMMUNAL_HUB, true); + enableWidgetPickerSizeFilter = + properties.getBoolean(Flags.FLAG_ENABLE_WIDGET_PICKER_SIZE_FILTER, false); + } catch (NullPointerException e) { + throw new RuntimeException( + "Cannot read value from namespace communal " + + "from DeviceConfig. It could be that the code using flag " + + "executed before SettingsProvider initialization. Please use " + + "fixed read-only flag by adding is_fixed_read_only: true in " + + "flag declaration.", + e + ); + } + communal_is_cached = true; + } + + private void load_overrides_systemui() { + try { + var properties = DeviceConfigHelper.Companion.getPrefs(); + activityTransitionUseLargestWindow = + properties.getBoolean(Flags.FLAG_ACTIVITY_TRANSITION_USE_LARGEST_WINDOW, true); + ambientTouchMonitorListenToDisplayChanges = + properties.getBoolean(Flags.FLAG_AMBIENT_TOUCH_MONITOR_LISTEN_TO_DISPLAY_CHANGES, false); + appClipsBacklinks = + properties.getBoolean(Flags.FLAG_APP_CLIPS_BACKLINKS, false); + bindKeyguardMediaVisibility = + properties.getBoolean(Flags.FLAG_BIND_KEYGUARD_MEDIA_VISIBILITY, true); + brightnessSliderFocusState = + properties.getBoolean(Flags.FLAG_BRIGHTNESS_SLIDER_FOCUS_STATE, false); + centralizedStatusBarHeightFix = + properties.getBoolean(Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX, true); + clipboardNoninteractiveOnLockscreen = + properties.getBoolean(Flags.FLAG_CLIPBOARD_NONINTERACTIVE_ON_LOCKSCREEN, false); + clockReactiveVariants = + properties.getBoolean(Flags.FLAG_CLOCK_REACTIVE_VARIANTS, false); + communalBouncerDoNotModifyPluginOpen = + properties.getBoolean(Flags.FLAG_COMMUNAL_BOUNCER_DO_NOT_MODIFY_PLUGIN_OPEN, false); + composeBouncer = + properties.getBoolean(Flags.FLAG_COMPOSE_BOUNCER, false); + composeLockscreen = + properties.getBoolean(Flags.FLAG_COMPOSE_LOCKSCREEN, false); + confineNotificationTouchToViewWidth = + properties.getBoolean(Flags.FLAG_CONFINE_NOTIFICATION_TOUCH_TO_VIEW_WIDTH, true); + contextualTipsAssistantDismissFix = + properties.getBoolean(Flags.FLAG_CONTEXTUAL_TIPS_ASSISTANT_DISMISS_FIX, true); + coroutineTracing = + properties.getBoolean(Flags.FLAG_COROUTINE_TRACING, true); + dedicatedNotifInflationThread = + properties.getBoolean(Flags.FLAG_DEDICATED_NOTIF_INFLATION_THREAD, true); + delayedWakelockReleaseOnBackgroundThread = + properties.getBoolean(Flags.FLAG_DELAYED_WAKELOCK_RELEASE_ON_BACKGROUND_THREAD, true); + deviceEntryUdfpsRefactor = + properties.getBoolean(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR, true); + disableContextualTipsFrequencyCheck = + properties.getBoolean(Flags.FLAG_DISABLE_CONTEXTUAL_TIPS_FREQUENCY_CHECK, true); + disableContextualTipsIosSwitcherCheck = + properties.getBoolean(Flags.FLAG_DISABLE_CONTEXTUAL_TIPS_IOS_SWITCHER_CHECK, true); + dozeuiSchedulingAlarmsBackgroundExecution = + properties.getBoolean(Flags.FLAG_DOZEUI_SCHEDULING_ALARMS_BACKGROUND_EXECUTION, false); + dreamInputSessionPilferOnce = + properties.getBoolean(Flags.FLAG_DREAM_INPUT_SESSION_PILFER_ONCE, false); + dreamOverlayBouncerSwipeDirectionFiltering = + properties.getBoolean(Flags.FLAG_DREAM_OVERLAY_BOUNCER_SWIPE_DIRECTION_FILTERING, true); + dualShade = + properties.getBoolean(Flags.FLAG_DUAL_SHADE, false); + edgeBackGestureHandlerThread = + properties.getBoolean(Flags.FLAG_EDGE_BACK_GESTURE_HANDLER_THREAD, false); + edgebackGestureHandlerGetRunningTasksBackground = + properties.getBoolean(Flags.FLAG_EDGEBACK_GESTURE_HANDLER_GET_RUNNING_TASKS_BACKGROUND, true); + enableBackgroundKeyguardOndrawnCallback = + properties.getBoolean(Flags.FLAG_ENABLE_BACKGROUND_KEYGUARD_ONDRAWN_CALLBACK, true); + enableContextualTipForMuteVolume = + properties.getBoolean(Flags.FLAG_ENABLE_CONTEXTUAL_TIP_FOR_MUTE_VOLUME, false); + enableContextualTipForPowerOff = + properties.getBoolean(Flags.FLAG_ENABLE_CONTEXTUAL_TIP_FOR_POWER_OFF, true); + enableContextualTipForTakeScreenshot = + properties.getBoolean(Flags.FLAG_ENABLE_CONTEXTUAL_TIP_FOR_TAKE_SCREENSHOT, true); + enableContextualTips = + properties.getBoolean(Flags.FLAG_ENABLE_CONTEXTUAL_TIPS, true); + enableEfficientDisplayRepository = + properties.getBoolean(Flags.FLAG_ENABLE_EFFICIENT_DISPLAY_REPOSITORY, false); + enableLayoutTracing = + properties.getBoolean(Flags.FLAG_ENABLE_LAYOUT_TRACING, false); + enableViewCaptureTracing = + properties.getBoolean(Flags.FLAG_ENABLE_VIEW_CAPTURE_TRACING, false); + enforceBrightnessBaseUserRestriction = + properties.getBoolean(Flags.FLAG_ENFORCE_BRIGHTNESS_BASE_USER_RESTRICTION, true); + exampleFlag = + properties.getBoolean(Flags.FLAG_EXAMPLE_FLAG, false); + fastUnlockTransition = + properties.getBoolean(Flags.FLAG_FAST_UNLOCK_TRANSITION, false); + fixImageWallpaperCrashSurfaceAlreadyReleased = + properties.getBoolean(Flags.FLAG_FIX_IMAGE_WALLPAPER_CRASH_SURFACE_ALREADY_RELEASED, true); + fixScreenshotActionDismissSystemWindows = + properties.getBoolean(Flags.FLAG_FIX_SCREENSHOT_ACTION_DISMISS_SYSTEM_WINDOWS, true); + generatedPreviews = + properties.getBoolean(Flags.FLAG_GENERATED_PREVIEWS, true); + getConnectedDeviceNameUnsynchronized = + properties.getBoolean(Flags.FLAG_GET_CONNECTED_DEVICE_NAME_UNSYNCHRONIZED, true); + glanceableHubAllowKeyguardWhenDreaming = + properties.getBoolean(Flags.FLAG_GLANCEABLE_HUB_ALLOW_KEYGUARD_WHEN_DREAMING, false); + glanceableHubFullscreenSwipe = + properties.getBoolean(Flags.FLAG_GLANCEABLE_HUB_FULLSCREEN_SWIPE, false); + glanceableHubGestureHandle = + properties.getBoolean(Flags.FLAG_GLANCEABLE_HUB_GESTURE_HANDLE, false); + glanceableHubShortcutButton = + properties.getBoolean(Flags.FLAG_GLANCEABLE_HUB_SHORTCUT_BUTTON, false); + hapticBrightnessSlider = + properties.getBoolean(Flags.FLAG_HAPTIC_BRIGHTNESS_SLIDER, true); + hapticVolumeSlider = + properties.getBoolean(Flags.FLAG_HAPTIC_VOLUME_SLIDER, true); + hearingAidsQsTileDialog = + properties.getBoolean(Flags.FLAG_HEARING_AIDS_QS_TILE_DIALOG, true); + keyboardDockingIndicator = + properties.getBoolean(Flags.FLAG_KEYBOARD_DOCKING_INDICATOR, true); + keyboardShortcutHelperRewrite = + properties.getBoolean(Flags.FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE, false); + keyguardBottomAreaRefactor = + properties.getBoolean(Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR, true); + keyguardWmStateRefactor = + properties.getBoolean(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR, false); + lightRevealMigration = + properties.getBoolean(Flags.FLAG_LIGHT_REVEAL_MIGRATION, true); + mediaControlsLockscreenShadeBugFix = + properties.getBoolean(Flags.FLAG_MEDIA_CONTROLS_LOCKSCREEN_SHADE_BUG_FIX, true); + mediaControlsRefactor = + properties.getBoolean(Flags.FLAG_MEDIA_CONTROLS_REFACTOR, true); + mediaControlsUserInitiatedDeleteintent = + properties.getBoolean(Flags.FLAG_MEDIA_CONTROLS_USER_INITIATED_DELETEINTENT, true); + migrateClocksToBlueprint = + properties.getBoolean(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, true); + newAodTransition = + properties.getBoolean(Flags.FLAG_NEW_AOD_TRANSITION, true); + newTouchpadGesturesTutorial = + properties.getBoolean(Flags.FLAG_NEW_TOUCHPAD_GESTURES_TUTORIAL, false); + newVolumePanel = + properties.getBoolean(Flags.FLAG_NEW_VOLUME_PANEL, true); + notificationAsyncGroupHeaderInflation = + properties.getBoolean(Flags.FLAG_NOTIFICATION_ASYNC_GROUP_HEADER_INFLATION, true); + notificationAsyncHybridViewInflation = + properties.getBoolean(Flags.FLAG_NOTIFICATION_ASYNC_HYBRID_VIEW_INFLATION, true); + notificationAvalancheSuppression = + properties.getBoolean(Flags.FLAG_NOTIFICATION_AVALANCHE_SUPPRESSION, true); + notificationAvalancheThrottleHun = + properties.getBoolean(Flags.FLAG_NOTIFICATION_AVALANCHE_THROTTLE_HUN, true); + notificationBackgroundTintOptimization = + properties.getBoolean(Flags.FLAG_NOTIFICATION_BACKGROUND_TINT_OPTIMIZATION, true); + notificationColorUpdateLogger = + properties.getBoolean(Flags.FLAG_NOTIFICATION_COLOR_UPDATE_LOGGER, false); + notificationContentAlphaOptimization = + properties.getBoolean(Flags.FLAG_NOTIFICATION_CONTENT_ALPHA_OPTIMIZATION, true); + notificationFooterBackgroundTintOptimization = + properties.getBoolean(Flags.FLAG_NOTIFICATION_FOOTER_BACKGROUND_TINT_OPTIMIZATION, false); + notificationMediaManagerBackgroundExecution = + properties.getBoolean(Flags.FLAG_NOTIFICATION_MEDIA_MANAGER_BACKGROUND_EXECUTION, true); + notificationMinimalismPrototype = + properties.getBoolean(Flags.FLAG_NOTIFICATION_MINIMALISM_PROTOTYPE, false); + notificationOverExpansionClippingFix = + properties.getBoolean(Flags.FLAG_NOTIFICATION_OVER_EXPANSION_CLIPPING_FIX, true); + notificationPulsingFix = + properties.getBoolean(Flags.FLAG_NOTIFICATION_PULSING_FIX, true); + notificationRowContentBinderRefactor = + properties.getBoolean(Flags.FLAG_NOTIFICATION_ROW_CONTENT_BINDER_REFACTOR, false); + notificationRowUserContext = + properties.getBoolean(Flags.FLAG_NOTIFICATION_ROW_USER_CONTEXT, true); + notificationViewFlipperPausingV2 = + properties.getBoolean(Flags.FLAG_NOTIFICATION_VIEW_FLIPPER_PAUSING_V2, true); + notificationsBackgroundIcons = + properties.getBoolean(Flags.FLAG_NOTIFICATIONS_BACKGROUND_ICONS, false); + notificationsFooterViewRefactor = + properties.getBoolean(Flags.FLAG_NOTIFICATIONS_FOOTER_VIEW_REFACTOR, true); + notificationsHeadsUpRefactor = + properties.getBoolean(Flags.FLAG_NOTIFICATIONS_HEADS_UP_REFACTOR, true); + notificationsHideOnDisplaySwitch = + properties.getBoolean(Flags.FLAG_NOTIFICATIONS_HIDE_ON_DISPLAY_SWITCH, false); + notificationsIconContainerRefactor = + properties.getBoolean(Flags.FLAG_NOTIFICATIONS_ICON_CONTAINER_REFACTOR, true); + notificationsImprovedHunAnimation = + properties.getBoolean(Flags.FLAG_NOTIFICATIONS_IMPROVED_HUN_ANIMATION, true); + notificationsLiveDataStoreRefactor = + properties.getBoolean(Flags.FLAG_NOTIFICATIONS_LIVE_DATA_STORE_REFACTOR, true); + notifyPowerManagerUserActivityBackground = + properties.getBoolean(Flags.FLAG_NOTIFY_POWER_MANAGER_USER_ACTIVITY_BACKGROUND, true); + pinInputFieldStyledFocusState = + properties.getBoolean(Flags.FLAG_PIN_INPUT_FIELD_STYLED_FOCUS_STATE, true); + predictiveBackAnimateBouncer = + properties.getBoolean(Flags.FLAG_PREDICTIVE_BACK_ANIMATE_BOUNCER, true); + predictiveBackAnimateDialogs = + properties.getBoolean(Flags.FLAG_PREDICTIVE_BACK_ANIMATE_DIALOGS, true); + predictiveBackAnimateShade = + properties.getBoolean(Flags.FLAG_PREDICTIVE_BACK_ANIMATE_SHADE, false); + predictiveBackSysui = + properties.getBoolean(Flags.FLAG_PREDICTIVE_BACK_SYSUI, true); + priorityPeopleSection = + properties.getBoolean(Flags.FLAG_PRIORITY_PEOPLE_SECTION, true); + privacyDotUnfoldWrongCornerFix = + properties.getBoolean(Flags.FLAG_PRIVACY_DOT_UNFOLD_WRONG_CORNER_FIX, true); + pssAppSelectorAbruptExitFix = + properties.getBoolean(Flags.FLAG_PSS_APP_SELECTOR_ABRUPT_EXIT_FIX, true); + pssAppSelectorRecentsSplitScreen = + properties.getBoolean(Flags.FLAG_PSS_APP_SELECTOR_RECENTS_SPLIT_SCREEN, true); + pssTaskSwitcher = + properties.getBoolean(Flags.FLAG_PSS_TASK_SWITCHER, false); + qsCustomTileClickGuaranteedBugFix = + properties.getBoolean(Flags.FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX, true); + qsNewPipeline = + properties.getBoolean(Flags.FLAG_QS_NEW_PIPELINE, true); + qsNewTiles = + properties.getBoolean(Flags.FLAG_QS_NEW_TILES, false); + qsNewTilesFuture = + properties.getBoolean(Flags.FLAG_QS_NEW_TILES_FUTURE, false); + qsTileFocusState = + properties.getBoolean(Flags.FLAG_QS_TILE_FOCUS_STATE, true); + qsUiRefactor = + properties.getBoolean(Flags.FLAG_QS_UI_REFACTOR, false); + quickSettingsVisualHapticsLongpress = + properties.getBoolean(Flags.FLAG_QUICK_SETTINGS_VISUAL_HAPTICS_LONGPRESS, true); + recordIssueQsTile = + properties.getBoolean(Flags.FLAG_RECORD_ISSUE_QS_TILE, true); + refactorGetCurrentUser = + properties.getBoolean(Flags.FLAG_REFACTOR_GET_CURRENT_USER, true); + registerBatteryControllerReceiversInCorestartable = + properties.getBoolean(Flags.FLAG_REGISTER_BATTERY_CONTROLLER_RECEIVERS_IN_CORESTARTABLE, false); + registerNewWalletCardInBackground = + properties.getBoolean(Flags.FLAG_REGISTER_NEW_WALLET_CARD_IN_BACKGROUND, true); + registerWallpaperNotifierBackground = + properties.getBoolean(Flags.FLAG_REGISTER_WALLPAPER_NOTIFIER_BACKGROUND, true); + registerZenModeContentObserverBackground = + properties.getBoolean(Flags.FLAG_REGISTER_ZEN_MODE_CONTENT_OBSERVER_BACKGROUND, true); + removeDreamOverlayHideOnTouch = + properties.getBoolean(Flags.FLAG_REMOVE_DREAM_OVERLAY_HIDE_ON_TOUCH, true); + restToUnlock = + properties.getBoolean(Flags.FLAG_REST_TO_UNLOCK, false); + restartDreamOnUnocclude = + properties.getBoolean(Flags.FLAG_RESTART_DREAM_ON_UNOCCLUDE, false); + revampedBouncerMessages = + properties.getBoolean(Flags.FLAG_REVAMPED_BOUNCER_MESSAGES, true); + runFingerprintDetectOnDismissibleKeyguard = + properties.getBoolean(Flags.FLAG_RUN_FINGERPRINT_DETECT_ON_DISMISSIBLE_KEYGUARD, true); + sceneContainer = + properties.getBoolean(Flags.FLAG_SCENE_CONTAINER, false); + screenshareNotificationHidingBugFix = + properties.getBoolean(Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX, true); + screenshotActionDismissSystemWindows = + properties.getBoolean(Flags.FLAG_SCREENSHOT_ACTION_DISMISS_SYSTEM_WINDOWS, true); + screenshotPrivateProfileAccessibilityAnnouncementFix = + properties.getBoolean(Flags.FLAG_SCREENSHOT_PRIVATE_PROFILE_ACCESSIBILITY_ANNOUNCEMENT_FIX, true); + screenshotPrivateProfileBehaviorFix = + properties.getBoolean(Flags.FLAG_SCREENSHOT_PRIVATE_PROFILE_BEHAVIOR_FIX, true); + screenshotScrollCropViewCrashFix = + properties.getBoolean(Flags.FLAG_SCREENSHOT_SCROLL_CROP_VIEW_CRASH_FIX, true); + screenshotShelfUi2 = + properties.getBoolean(Flags.FLAG_SCREENSHOT_SHELF_UI2, true); + shadeCollapseActivityLaunchFix = + properties.getBoolean(Flags.FLAG_SHADE_COLLAPSE_ACTIVITY_LAUNCH_FIX, false); + shaderlibLoadingEffectRefactor = + properties.getBoolean(Flags.FLAG_SHADERLIB_LOADING_EFFECT_REFACTOR, true); + sliceBroadcastRelayInBackground = + properties.getBoolean(Flags.FLAG_SLICE_BROADCAST_RELAY_IN_BACKGROUND, true); + sliceManagerBinderCallBackground = + properties.getBoolean(Flags.FLAG_SLICE_MANAGER_BINDER_CALL_BACKGROUND, true); + smartspaceLockscreenViewmodel = + properties.getBoolean(Flags.FLAG_SMARTSPACE_LOCKSCREEN_VIEWMODEL, true); + smartspaceRelocateToBottom = + properties.getBoolean(Flags.FLAG_SMARTSPACE_RELOCATE_TO_BOTTOM, false); + smartspaceRemoteviewsRendering = + properties.getBoolean(Flags.FLAG_SMARTSPACE_REMOTEVIEWS_RENDERING, false); + statusBarMonochromeIconsFix = + properties.getBoolean(Flags.FLAG_STATUS_BAR_MONOCHROME_ICONS_FIX, true); + statusBarScreenSharingChips = + properties.getBoolean(Flags.FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, true); + statusBarStaticInoutIndicators = + properties.getBoolean(Flags.FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS, false); + switchUserOnBg = + properties.getBoolean(Flags.FLAG_SWITCH_USER_ON_BG, true); + sysuiTeamfood = + properties.getBoolean(Flags.FLAG_SYSUI_TEAMFOOD, true); + themeOverlayControllerWakefulnessDeprecation = + properties.getBoolean(Flags.FLAG_THEME_OVERLAY_CONTROLLER_WAKEFULNESS_DEPRECATION, false); + translucentOccludingActivityFix = + properties.getBoolean(Flags.FLAG_TRANSLUCENT_OCCLUDING_ACTIVITY_FIX, false); + truncatedStatusBarIconsFix = + properties.getBoolean(Flags.FLAG_TRUNCATED_STATUS_BAR_ICONS_FIX, true); + udfpsViewPerformance = + properties.getBoolean(Flags.FLAG_UDFPS_VIEW_PERFORMANCE, true); + unfoldAnimationBackgroundProgress = + properties.getBoolean(Flags.FLAG_UNFOLD_ANIMATION_BACKGROUND_PROGRESS, true); + updateUserSwitcherBackground = + properties.getBoolean(Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND, true); + validateKeyboardShortcutHelperIconUri = + properties.getBoolean(Flags.FLAG_VALIDATE_KEYBOARD_SHORTCUT_HELPER_ICON_URI, true); + visualInterruptionsRefactor = + properties.getBoolean(Flags.FLAG_VISUAL_INTERRUPTIONS_REFACTOR, true); + } catch (NullPointerException e) { + throw new RuntimeException( + "Cannot read value from namespace systemui " + + "from DeviceConfig. It could be that the code using flag " + + "executed before SettingsProvider initialization. Please use " + + "fixed read-only flag by adding is_fixed_read_only: true in " + + "flag declaration.", + e + ); + } + systemui_is_cached = true; + } + @Override - - + public boolean activityTransitionUseLargestWindow() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return activityTransitionUseLargestWindow; + } @Override - - - public boolean addBlackBackgroundForWindowMagnifier() { - return true; - } - - @Override - - - public boolean alwaysComposeQsUiFragment() { - return false; - } - - @Override - - + public boolean ambientTouchMonitorListenToDisplayChanges() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return ambientTouchMonitorListenToDisplayChanges; + } @Override - - + public boolean appClipsBacklinks() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return appClipsBacklinks; + } @Override - - - public boolean appShortcutRemovalFix() { - return true; - } - - @Override - - - public boolean avalancheReplaceHunWhenCritical() { - return false; - } - - @Override - - + public boolean bindKeyguardMediaVisibility() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return bindKeyguardMediaVisibility; + } @Override + + public boolean bpTalkback() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!biometrics_framework_is_cached) { + load_overrides_biometrics_framework(); + } + } + return bpTalkback; - - public boolean bouncerUiRevamp() { - return false; } @Override - - - public boolean bouncerUiRevamp2() { - return false; - } - - @Override - - - public boolean bpColors() { - return false; - } - - @Override - - + public boolean brightnessSliderFocusState() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return brightnessSliderFocusState; + } @Override + + public boolean centralizedStatusBarHeightFix() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return centralizedStatusBarHeightFix; - - public boolean checkLockscreenGoneTransition() { - return true; } @Override - - - public boolean classicFlagsMultiUser() { - return true; - } - - @Override - - - public boolean clipboardImageTimeout() { - return true; - } - - @Override - - + public boolean clipboardNoninteractiveOnLockscreen() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return clipboardNoninteractiveOnLockscreen; + } @Override + + public boolean clockReactiveVariants() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return clockReactiveVariants; - - public boolean clipboardOverlayMultiuser() { - return false; } @Override - - - public boolean clipboardSharedTransitions() { - return true; - } - - @Override - - - public boolean clipboardUseDescriptionMimetype() { - return true; - } - - @Override - - - public boolean clockFidgetAnimation() { - return false; - } - - @Override - - + public boolean communalBouncerDoNotModifyPluginOpen() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return communalBouncerDoNotModifyPluginOpen; + } @Override - - - public boolean communalEditWidgetsActivityFinishFix() { - return true; - } - - @Override - - + public boolean communalHub() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!communal_is_cached) { + load_overrides_communal(); + } + } + return communalHub; + } @Override - - - public boolean communalHubUseThreadPoolForWidgets() { - return true; - } - - @Override - - - public boolean communalResponsiveGrid() { - return false; - } - - @Override - - - public boolean communalSceneKtfRefactor() { - return true; - } - - @Override - - - public boolean communalStandaloneSupport() { - return false; - } - - @Override - - - public boolean communalTimerFlickerFix() { - return true; - } - - @Override - - - public boolean communalWidgetResizing() { - return true; - } - - @Override - - - public boolean communalWidgetTrampolineFix() { - return true; - } - - @Override - - + public boolean composeBouncer() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return composeBouncer; + } @Override + + public boolean composeLockscreen() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return composeLockscreen; + } + @Override + public boolean confineNotificationTouchToViewWidth() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return confineNotificationTouchToViewWidth; + } @Override + + public boolean constraintBp() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!biometrics_framework_is_cached) { + load_overrides_biometrics_framework(); + } + } + return constraintBp; - - public boolean contAuthPlugin() { - return false; } @Override - - + public boolean contextualTipsAssistantDismissFix() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return contextualTipsAssistantDismissFix; + } @Override - - + public boolean coroutineTracing() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return coroutineTracing; + } @Override - - + public boolean createWindowlessWindowMagnifier() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!accessibility_is_cached) { + load_overrides_accessibility(); + } + } + return createWindowlessWindowMagnifier; + } @Override + + public boolean dedicatedNotifInflationThread() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return dedicatedNotifInflationThread; - - public boolean debugLiveUpdatesPromoteAll() { - return false; } @Override - - - public boolean decoupleViewControllerInAnimlib() { - return false; - } - - @Override - - + public boolean delayShowMagnificationButton() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!accessibility_is_cached) { + load_overrides_accessibility(); + } + } + return delayShowMagnificationButton; + } @Override + + public boolean delayedWakelockReleaseOnBackgroundThread() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return delayedWakelockReleaseOnBackgroundThread; - - public boolean desktopEffectsQsTile() { - return false; } @Override - - + public boolean deviceEntryUdfpsRefactor() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return deviceEntryUdfpsRefactor; + } @Override - - - public boolean disableBlurredShadeVisible() { - return false; - } - - @Override - - + public boolean disableContextualTipsFrequencyCheck() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return disableContextualTipsFrequencyCheck; + } @Override - - + public boolean disableContextualTipsIosSwitcherCheck() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return disableContextualTipsIosSwitcherCheck; + } @Override + + public boolean dozeuiSchedulingAlarmsBackgroundExecution() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return dozeuiSchedulingAlarmsBackgroundExecution; - - public boolean disableShadeTrackpadTwoFingerSwipe() { - return false; } @Override - - - public boolean doubleTapToSleep() { - return false; - } - - @Override - - + public boolean dreamInputSessionPilferOnce() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return dreamInputSessionPilferOnce; + } @Override - - + public boolean dreamOverlayBouncerSwipeDirectionFiltering() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return dreamOverlayBouncerSwipeDirectionFiltering; + } @Override + + public boolean dualShade() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return dualShade; - - public boolean dreamOverlayUpdatedFont() { - return false; } @Override - - + public boolean edgeBackGestureHandlerThread() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return edgeBackGestureHandlerThread; + } @Override - - + public boolean edgebackGestureHandlerGetRunningTasksBackground() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return edgebackGestureHandlerGetRunningTasksBackground; + } @Override - - + public boolean enableBackgroundKeyguardOndrawnCallback() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return enableBackgroundKeyguardOndrawnCallback; + } @Override - - + public boolean enableContextualTipForMuteVolume() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return enableContextualTipForMuteVolume; + } @Override - - + public boolean enableContextualTipForPowerOff() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return enableContextualTipForPowerOff; + } @Override - - + public boolean enableContextualTipForTakeScreenshot() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return enableContextualTipForTakeScreenshot; + } @Override - - + public boolean enableContextualTips() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return enableContextualTips; + } @Override - - + public boolean enableEfficientDisplayRepository() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return enableEfficientDisplayRepository; + } @Override - - + public boolean enableLayoutTracing() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return enableLayoutTracing; + } @Override - - - public boolean enableUnderlay() { - return false; - } - - @Override - - + public boolean enableViewCaptureTracing() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return enableViewCaptureTracing; + } @Override + + public boolean enableWidgetPickerSizeFilter() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!communal_is_cached) { + load_overrides_communal(); + } + } + return enableWidgetPickerSizeFilter; + } + @Override + public boolean enforceBrightnessBaseUserRestriction() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return enforceBrightnessBaseUserRestriction; + } @Override - - + public boolean exampleFlag() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return exampleFlag; + } @Override + + public boolean fastUnlockTransition() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return fastUnlockTransition; - - public boolean expandCollapsePrivacyDialog() { - return true; } @Override - - - public boolean expandHeadsUpOnInlineReply() { - return true; - } - - @Override - - - public boolean expandedPrivacyIndicatorsOnLargeScreen() { - return false; - } - - @Override - - - public boolean extendedAppsShortcutCategory() { - return false; - } - - @Override - - - public boolean faceMessageDeferUpdate() { - return true; - } - - @Override - - - public boolean faceScanningAnimationNpeFix() { - return true; - } - - @Override - - - public boolean fasterUnlockTransition() { - return true; - } - - @Override - - - public boolean fetchBookmarksXmlKeyboardShortcuts() { - return true; - } - - @Override - - + public boolean fixImageWallpaperCrashSurfaceAlreadyReleased() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return fixImageWallpaperCrashSurfaceAlreadyReleased; + } @Override - - + public boolean fixScreenshotActionDismissSystemWindows() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return fixScreenshotActionDismissSystemWindows; + } @Override - - + public boolean floatingMenuAnimatedTuck() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!accessibility_is_cached) { + load_overrides_accessibility(); + } + } + return floatingMenuAnimatedTuck; + } @Override - - - public boolean floatingMenuDisplayCutoutSupport() { - return true; - } - - @Override - - + public boolean floatingMenuDragToEdit() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!accessibility_is_cached) { + load_overrides_accessibility(); + } + } + return floatingMenuDragToEdit; + } @Override - - + public boolean floatingMenuDragToHide() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!accessibility_is_cached) { + load_overrides_accessibility(); + } + } + return floatingMenuDragToHide; + } @Override - - - public boolean floatingMenuHearingDeviceStatusIcon() { - return false; - } - - @Override - - + public boolean floatingMenuImeDisplacementAnimation() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!accessibility_is_cached) { + load_overrides_accessibility(); + } + } + return floatingMenuImeDisplacementAnimation; + } @Override - - + public boolean floatingMenuNarrowTargetContentObserver() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!accessibility_is_cached) { + load_overrides_accessibility(); + } + } + return floatingMenuNarrowTargetContentObserver; + } @Override - - - public boolean floatingMenuNotifyTargetsChangedOnStrictDiff() { - return true; - } - - @Override - - + public boolean floatingMenuOverlapsNavBarsFlag() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!accessibility_is_cached) { + load_overrides_accessibility(); + } + } + return floatingMenuOverlapsNavBarsFlag; + } @Override - - + public boolean floatingMenuRadiiAnimation() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!accessibility_is_cached) { + load_overrides_accessibility(); + } + } + return floatingMenuRadiiAnimation; + + } + + @Override + + public boolean generatedPreviews() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return generatedPreviews; + } @Override - - + public boolean getConnectedDeviceNameUnsynchronized() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return getConnectedDeviceNameUnsynchronized; + } @Override - - + public boolean glanceableHubAllowKeyguardWhenDreaming() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return glanceableHubAllowKeyguardWhenDreaming; + } @Override + + public boolean glanceableHubFullscreenSwipe() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return glanceableHubFullscreenSwipe; - - public boolean glanceableHubBlurredBackground() { - return false; } @Override + + public boolean glanceableHubGestureHandle() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return glanceableHubGestureHandle; - - public boolean glanceableHubDirectEditMode() { - return false; } @Override + + public boolean glanceableHubShortcutButton() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return glanceableHubShortcutButton; - - public boolean glanceableHubV2() { - return false; } @Override + + public boolean hapticBrightnessSlider() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return hapticBrightnessSlider; - - public boolean glanceableHubV2Resources() { - return false; } @Override + + public boolean hapticVolumeSlider() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return hapticVolumeSlider; - - public boolean hapticsForComposeSliders() { - return true; } @Override - - - public boolean hardwareColorStyles() { - return false; - } - - @Override - - + public boolean hearingAidsQsTileDialog() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return hearingAidsQsTileDialog; + } @Override - - + public boolean hearingDevicesDialogRelatedTools() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!accessibility_is_cached) { + load_overrides_accessibility(); + } + } + return hearingDevicesDialogRelatedTools; + } @Override - - - public boolean hideRingerButtonInSingleVolumeMode() { - return false; - } - - @Override - - - public boolean homeControlsDreamHsum() { - return true; - } - - @Override - - - public boolean hubEditModeTouchAdjustments() { - return false; - } - - @Override - - - public boolean hubmodeFullscreenVerticalSwipe() { - return false; - } - - @Override - - - public boolean hubmodeFullscreenVerticalSwipeFix() { - return true; - } - - @Override - - - public boolean iconRefresh2025() { - return false; - } - - @Override - - - public boolean ignoreTouchesNextToNotificationShelf() { - return true; - } - - @Override - - - public boolean indicationTextA11yFix() { - return true; - } - - @Override - - + public boolean keyboardDockingIndicator() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return keyboardDockingIndicator; + } @Override - - + public boolean keyboardShortcutHelperRewrite() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return keyboardShortcutHelperRewrite; + } @Override + + public boolean keyguardBottomAreaRefactor() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return keyguardBottomAreaRefactor; - - public boolean keyboardShortcutHelperShortcutCustomizer() { - return true; } @Override - - - public boolean keyboardTouchpadContextualEducation() { - return true; - } - - @Override - - - public boolean keyguardTransitionForceFinishOnScreenOff() { - return false; - } - - @Override - - - public boolean keyguardWmReorderAtmsCalls() { - return true; - } - - @Override - - + public boolean keyguardWmStateRefactor() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return keyguardWmStateRefactor; + } @Override + + public boolean lightRevealMigration() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return lightRevealMigration; - - public boolean lockscreenFont() { - return false; } @Override - - - public boolean lowLightClockDream() { - return false; - } - - @Override - - - public boolean magneticNotificationSwipes() { - return false; - } - - @Override - - - public boolean mediaControlsA11yColors() { - return true; - } - - @Override - - - public boolean mediaControlsButtonMedia3() { - return false; - } - - @Override - - - public boolean mediaControlsButtonMedia3Placement() { - return false; - } - - @Override - - - public boolean mediaControlsDeviceManagerBackgroundExecution() { - return false; - } - - @Override - - - public boolean mediaControlsDrawablesReuseBugfix() { - return true; - } - - @Override - - + public boolean mediaControlsLockscreenShadeBugFix() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return mediaControlsLockscreenShadeBugFix; + } @Override + + public boolean mediaControlsRefactor() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return mediaControlsRefactor; - - public boolean mediaControlsUiUpdate() { - return false; } @Override - - - public boolean mediaControlsUmoInflationInBackground() { - return true; - } - - @Override - - + public boolean mediaControlsUserInitiatedDeleteintent() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return mediaControlsUserInitiatedDeleteintent; + } @Override + + public boolean migrateClocksToBlueprint() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return migrateClocksToBlueprint; - - public boolean mediaLoadMetadataViaMediaDataLoader() { - return true; } @Override - - - public boolean mediaLockscreenLaunchAnimation() { - return true; - } - - @Override - - - public boolean mediaProjectionDialogBehindLockscreen() { - return true; - } - - @Override - - - public boolean mediaProjectionGreyErrorText() { - return true; - } - - @Override - - - public boolean mediaProjectionRequestAttributionFix() { - return false; - } - - @Override - - - public boolean modesUiDialogPaging() { - return false; - } - - @Override - - - public boolean moveTransitionAnimationLayer() { - return false; - } - - @Override - - - public boolean msdlFeedback() { - return false; - } - - @Override - - - public boolean multiuserWifiPickerTrackerSupport() { - return false; - } - - @Override - - + public boolean newAodTransition() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return newAodTransition; + } @Override + + public boolean newTouchpadGesturesTutorial() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return newTouchpadGesturesTutorial; + } + @Override + public boolean newVolumePanel() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return newVolumePanel; + } @Override - - - public boolean nonTouchscreenDevicesBypassFalsing() { - return false; - } - - @Override - - - public boolean notesRoleQsTile() { - return false; - } - - @Override - - - public boolean notificationAddXOnHoverToDismiss() { - return false; - } - - @Override - - - public boolean notificationAmbientSuppressionAfterInflation() { - return false; - } - - @Override - - - public boolean notificationAnimatedActionsTreatment() { - return false; - } - - @Override - - - public boolean notificationAppearNonlinear() { - return true; - } - - @Override - - + public boolean notificationAsyncGroupHeaderInflation() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return notificationAsyncGroupHeaderInflation; + } @Override - - + public boolean notificationAsyncHybridViewInflation() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return notificationAsyncHybridViewInflation; + } @Override - - + public boolean notificationAvalancheSuppression() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return notificationAvalancheSuppression; + } @Override - - + public boolean notificationAvalancheThrottleHun() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return notificationAvalancheThrottleHun; + } @Override - - + public boolean notificationBackgroundTintOptimization() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return notificationBackgroundTintOptimization; + } @Override - - - public boolean notificationBundleUi() { - return false; - } - - @Override - - + public boolean notificationColorUpdateLogger() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return notificationColorUpdateLogger; + } @Override - - + public boolean notificationContentAlphaOptimization() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return notificationContentAlphaOptimization; + } @Override - - + public boolean notificationFooterBackgroundTintOptimization() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return notificationFooterBackgroundTintOptimization; + } @Override + + public boolean notificationMediaManagerBackgroundExecution() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return notificationMediaManagerBackgroundExecution; + } + @Override + + public boolean notificationMinimalismPrototype() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return notificationMinimalismPrototype; + + } + + @Override + public boolean notificationOverExpansionClippingFix() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return notificationOverExpansionClippingFix; + } @Override + + public boolean notificationPulsingFix() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return notificationPulsingFix; - - public boolean notificationReentrantDismiss() { - return true; } @Override - - - public boolean notificationRowAccessibilityExpanded() { - return true; - } - - @Override - - + public boolean notificationRowContentBinderRefactor() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return notificationRowContentBinderRefactor; + } @Override - - - public boolean notificationRowTransparency() { - return false; - } - - @Override - - + public boolean notificationRowUserContext() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return notificationRowUserContext; + } @Override - - - public boolean notificationShadeBlur() { - return false; - } - - @Override - - - public boolean notificationShadeUiThread() { - return false; - } - - @Override - - - public boolean notificationSkipSilentUpdates() { - return false; - } - - @Override - - - public boolean notificationTransparentHeaderFix() { - return true; - } - - @Override - - + public boolean notificationViewFlipperPausingV2() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return notificationViewFlipperPausingV2; + } @Override - - + public boolean notificationsBackgroundIcons() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return notificationsBackgroundIcons; + } @Override + + public boolean notificationsFooterViewRefactor() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return notificationsFooterViewRefactor; - - public boolean notificationsFooterVisibilityFix() { - return true; } @Override + + public boolean notificationsHeadsUpRefactor() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return notificationsHeadsUpRefactor; + } + @Override + public boolean notificationsHideOnDisplaySwitch() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return notificationsHideOnDisplaySwitch; + } @Override - - - public boolean notificationsHunSharedAnimationValues() { - return false; - } - - @Override - - + public boolean notificationsIconContainerRefactor() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return notificationsIconContainerRefactor; + } @Override + + public boolean notificationsImprovedHunAnimation() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return notificationsImprovedHunAnimation; - - public boolean notificationsLaunchRadius() { - return false; } @Override - - + public boolean notificationsLiveDataStoreRefactor() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return notificationsLiveDataStoreRefactor; + } @Override - - - public boolean notificationsPinnedHunInShade() { - return true; - } - - @Override - - - public boolean notificationsRedesignFooterView() { - return false; - } - - @Override - - - public boolean notificationsRedesignGuts() { - return false; - } - - @Override - - - public boolean notifyPasswordTextViewUserActivityInBackground() { - return true; - } - - @Override - - + public boolean notifyPowerManagerUserActivityBackground() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return notifyPowerManagerUserActivityBackground; + } @Override - - - public boolean onlyShowMediaStreamSliderInSingleVolumeMode() { - return true; - } - - @Override - - - public boolean outputSwitcherRedesign() { - return false; - } - - @Override - - - public boolean overrideSuppressOverlayCondition() { - return false; - } - - @Override - - - public boolean permissionHelperInlineUiRichOngoing() { - return false; - } - - @Override - - - public boolean permissionHelperUiRichOngoing() { - return false; - } - - @Override - - - public boolean physicalNotificationMovement() { - return false; - } - - @Override - - + public boolean pinInputFieldStyledFocusState() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return pinInputFieldStyledFocusState; + } @Override + + public boolean predictiveBackAnimateBouncer() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return predictiveBackAnimateBouncer; + } + @Override + + public boolean predictiveBackAnimateDialogs() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return predictiveBackAnimateDialogs; + + } + + @Override + public boolean predictiveBackAnimateShade() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return predictiveBackAnimateShade; + } @Override + + public boolean predictiveBackSysui() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return predictiveBackSysui; - - public boolean predictiveBackDelayWmTransition() { - return false; } @Override - - + public boolean priorityPeopleSection() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return priorityPeopleSection; + } @Override + + public boolean privacyDotUnfoldWrongCornerFix() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return privacyDotUnfoldWrongCornerFix; - - public boolean promoteNotificationsAutomatically() { - return false; } @Override + + public boolean pssAppSelectorAbruptExitFix() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return pssAppSelectorAbruptExitFix; + } + @Override + public boolean pssAppSelectorRecentsSplitScreen() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return pssAppSelectorRecentsSplitScreen; + } @Override - - + public boolean pssTaskSwitcher() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return pssTaskSwitcher; + } @Override - - + public boolean qsCustomTileClickGuaranteedBugFix() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return qsCustomTileClickGuaranteedBugFix; + } @Override + + public boolean qsNewPipeline() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return qsNewPipeline; + } + @Override + public boolean qsNewTiles() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return qsNewTiles; + } @Override - - + public boolean qsNewTilesFuture() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return qsNewTilesFuture; + } @Override - - - public boolean qsQuickRebindActiveTiles() { - return false; - } - - @Override - - - public boolean qsRegisterSettingObserverOnBgThread() { - return true; - } - - @Override - - - public boolean qsTileDetailedView() { - return false; - } - - @Override - - + public boolean qsTileFocusState() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return qsTileFocusState; + } @Override - - + public boolean qsUiRefactor() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return qsUiRefactor; + } @Override + + public boolean quickSettingsVisualHapticsLongpress() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return quickSettingsVisualHapticsLongpress; - - public boolean qsUiRefactorComposeFragment() { - return false; } @Override - - + public boolean recordIssueQsTile() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return recordIssueQsTile; + } @Override - - - public boolean redesignMagnificationWindowSize() { - return false; - } - - @Override - - + public boolean refactorGetCurrentUser() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return refactorGetCurrentUser; + } @Override - - + public boolean registerBatteryControllerReceiversInCorestartable() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return registerBatteryControllerReceiversInCorestartable; + } @Override - - - public boolean registerContentObserversAsync() { - return true; - } - - @Override - - + public boolean registerNewWalletCardInBackground() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return registerNewWalletCardInBackground; + } @Override - - + public boolean registerWallpaperNotifierBackground() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return registerWallpaperNotifierBackground; + } @Override + + public boolean registerZenModeContentObserverBackground() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return registerZenModeContentObserverBackground; - - public boolean relockWithPowerButtonImmediately() { - return true; } @Override - - + public boolean removeDreamOverlayHideOnTouch() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return removeDreamOverlayHideOnTouch; + } @Override - - - public boolean removeUpdateListenerInQsIconViewImpl() { - return true; - } - - @Override - - + public boolean restToUnlock() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return restToUnlock; + } @Override - - + public boolean restartDreamOnUnocclude() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return restartDreamOnUnocclude; + } @Override - - + public boolean revampedBouncerMessages() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return revampedBouncerMessages; + } @Override - - + public boolean runFingerprintDetectOnDismissibleKeyguard() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return runFingerprintDetectOnDismissibleKeyguard; + } @Override - - + public boolean saveAndRestoreMagnificationSettingsButtons() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!accessibility_is_cached) { + load_overrides_accessibility(); + } + } + return saveAndRestoreMagnificationSettingsButtons; + } @Override - - + public boolean sceneContainer() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return sceneContainer; + } @Override - - + public boolean screenshareNotificationHidingBugFix() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return screenshareNotificationHidingBugFix; + } @Override - - + public boolean screenshotActionDismissSystemWindows() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return screenshotActionDismissSystemWindows; + } @Override + + public boolean screenshotPrivateProfileAccessibilityAnnouncementFix() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return screenshotPrivateProfileAccessibilityAnnouncementFix; - - public boolean screenshotMultidisplayFocusChange() { - return false; } @Override + + public boolean screenshotPrivateProfileBehaviorFix() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return screenshotPrivateProfileBehaviorFix; - - public boolean screenshotPolicySplitAndDesktopMode() { - return true; } @Override - - + public boolean screenshotScrollCropViewCrashFix() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return screenshotScrollCropViewCrashFix; + } @Override + + public boolean screenshotShelfUi2() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return screenshotShelfUi2; - - public boolean screenshotUiControllerRefactor() { - return true; } @Override + + public boolean shadeCollapseActivityLaunchFix() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return shadeCollapseActivityLaunchFix; - - public boolean secondaryUserWidgetHost() { - return false; } @Override - - - public boolean settingsExtRegisterContentObserverOnBgThread() { - return true; - } - - @Override - - - public boolean shadeExpandsOnStatusBarLongPress() { - return true; - } - - @Override - - - public boolean shadeHeaderFontUpdate() { - return false; - } - - @Override - - - public boolean shadeLaunchAccessibility() { - return true; - } - - @Override - - - public boolean shadeWindowGoesAround() { - return false; - } - - @Override - - + public boolean shaderlibLoadingEffectRefactor() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return shaderlibLoadingEffectRefactor; + } @Override - - - public boolean shortcutHelperKeyGlyph() { - return true; - } - - @Override - - - public boolean showAudioSharingSliderInVolumePanel() { - return false; - } - - @Override - - - public boolean showClipboardIndication() { - return false; - } - - @Override - - - public boolean showLockedByYourWatchKeyguardIndicator() { - return false; - } - - @Override - - - public boolean showToastWhenAppControlBrightness() { - return true; - } - - @Override - - - public boolean simPinBouncerReset() { - return true; - } - - @Override - - - public boolean simPinRaceConditionOnRestart() { - return true; - } - - @Override - - - public boolean simPinUseSlotId() { - return true; - } - - @Override - - - public boolean skipHideSensitiveNotifAnimation() { - return true; - } - - @Override - - + public boolean sliceBroadcastRelayInBackground() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return sliceBroadcastRelayInBackground; + } @Override - - + public boolean sliceManagerBinderCallBackground() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return sliceManagerBinderCallBackground; + } @Override - - + public boolean smartspaceLockscreenViewmodel() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return smartspaceLockscreenViewmodel; + } @Override - - + public boolean smartspaceRelocateToBottom() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return smartspaceRelocateToBottom; + } @Override + + public boolean smartspaceRemoteviewsRendering() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return smartspaceRemoteviewsRendering; - - public boolean smartspaceRemoteviewsRenderingFix() { - return true; } @Override - - - public boolean smartspaceSwipeEventLoggingFix() { - return true; - } - - @Override - - - public boolean smartspaceViewpager2() { - return false; - } - - @Override - - - public boolean sounddoseCustomization() { - return true; - } - - @Override - - - public boolean spatialModelAppPushback() { - return false; - } - - @Override - - - public boolean stabilizeHeadsUpGroupV2() { - return true; - } - - @Override - - - public boolean statusBarAlwaysCheckUnderlyingNetworks() { - return true; - } - - @Override - - - public boolean statusBarAutoStartScreenRecordChip() { - return true; - } - - @Override - - - public boolean statusBarChipsModernization() { - return false; - } - - @Override - - - public boolean statusBarChipsReturnAnimations() { - return false; - } - - @Override - - - public boolean statusBarFontUpdates() { - return false; - } - - @Override - - - public boolean statusBarMobileIconKairos() { - return false; - } - - @Override - - + public boolean statusBarMonochromeIconsFix() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return statusBarMonochromeIconsFix; + } @Override + + public boolean statusBarScreenSharingChips() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return statusBarScreenSharingChips; - - public boolean statusBarNoHunBehavior() { - return false; } @Override - - - public boolean statusBarPopupChips() { - return false; - } - - @Override - - - public boolean statusBarRootModernization() { - return false; - } - - @Override - - - public boolean statusBarShowAudioOnlyProjectionChip() { - return true; - } - - @Override - - - public boolean statusBarSignalPolicyRefactor() { - return true; - } - - @Override - - - public boolean statusBarSignalPolicyRefactorEthernet() { - return true; - } - - @Override - - + public boolean statusBarStaticInoutIndicators() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return statusBarStaticInoutIndicators; + } @Override - - - public boolean statusBarStopUpdatingWindowHeight() { - return false; - } - - @Override - - - public boolean statusBarSwipeOverChip() { - return false; - } - - @Override - - - public boolean statusBarSwitchToSpnFromDataSpn() { - return true; - } - - @Override - - - public boolean statusBarUiThread() { - return false; - } - - @Override - - - public boolean statusBarWindowNoCustomTouch() { - return false; - } - - @Override - - - public boolean stoppableFgsSystemApp() { - return true; - } - - @Override - - + public boolean switchUserOnBg() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return switchUserOnBg; + } @Override - - + public boolean sysuiTeamfood() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return sysuiTeamfood; + } @Override - - + public boolean themeOverlayControllerWakefulnessDeprecation() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return themeOverlayControllerWakefulnessDeprecation; + } @Override - - - public boolean transitionRaceCondition() { - return true; - } - - @Override - - + public boolean translucentOccludingActivityFix() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return translucentOccludingActivityFix; + } @Override + + public boolean truncatedStatusBarIconsFix() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return truncatedStatusBarIconsFix; - - public boolean tvGlobalActionsFocus() { - return false; } @Override - - + public boolean udfpsViewPerformance() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return udfpsViewPerformance; + } @Override - - + public boolean unfoldAnimationBackgroundProgress() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return unfoldAnimationBackgroundProgress; + } @Override - - - public boolean unfoldLatencyTrackingFix() { - return false; - } - - @Override - - - public boolean updateCornerRadiusOnDisplayChanged() { - return true; - } - - @Override - - + public boolean updateUserSwitcherBackground() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return updateUserSwitcherBackground; + } @Override + + public boolean validateKeyboardShortcutHelperIconUri() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return validateKeyboardShortcutHelperIconUri; - - public boolean updateWindowMagnifierBottomBoundary() { - return false; } @Override - - - public boolean useAadProxSensor() { - return false; - } - - @Override - - - public boolean useNotifInflationThreadForFooter() { - return true; - } - - @Override - - - public boolean useNotifInflationThreadForRow() { - return true; - } - - @Override - - - public boolean useTransitionsForKeyguardOccluded() { - return true; - } - - @Override - - - public boolean useVolumeController() { - return true; - } - - @Override - - - public boolean userAwareSettingsRepositories() { - return true; - } - - @Override - - - public boolean userEncryptedSource() { - return true; - } - - @Override - - - public boolean userSwitcherAddSignOutOption() { - return false; - } - - @Override - - + public boolean visualInterruptionsRefactor() { - return true; - } + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return visualInterruptionsRefactor; - @Override - - - public boolean volumeRedesign() { - return false; } } + diff --git a/flags/src/com/android/systemui/Flags.java b/flags/src/com/android/systemui/Flags.java index ca51b086d1..dd9dc1fb73 100644 --- a/flags/src/com/android/systemui/Flags.java +++ b/flags/src/com/android/systemui/Flags.java @@ -1,74 +1,37 @@ package com.android.systemui; // TODO(b/303773055): Remove the annotation after access issue is resolved. - /** @hide */ public final class Flags { /** @hide */ public static final String FLAG_ACTIVITY_TRANSITION_USE_LARGEST_WINDOW = "com.android.systemui.activity_transition_use_largest_window"; /** @hide */ - public static final String FLAG_ADD_BLACK_BACKGROUND_FOR_WINDOW_MAGNIFIER = "com.android.systemui.add_black_background_for_window_magnifier"; - /** @hide */ - public static final String FLAG_ALWAYS_COMPOSE_QS_UI_FRAGMENT = "com.android.systemui.always_compose_qs_ui_fragment"; - /** @hide */ public static final String FLAG_AMBIENT_TOUCH_MONITOR_LISTEN_TO_DISPLAY_CHANGES = "com.android.systemui.ambient_touch_monitor_listen_to_display_changes"; /** @hide */ public static final String FLAG_APP_CLIPS_BACKLINKS = "com.android.systemui.app_clips_backlinks"; /** @hide */ - public static final String FLAG_APP_SHORTCUT_REMOVAL_FIX = "com.android.systemui.app_shortcut_removal_fix"; - /** @hide */ - public static final String FLAG_AVALANCHE_REPLACE_HUN_WHEN_CRITICAL = "com.android.systemui.avalanche_replace_hun_when_critical"; - /** @hide */ public static final String FLAG_BIND_KEYGUARD_MEDIA_VISIBILITY = "com.android.systemui.bind_keyguard_media_visibility"; /** @hide */ - public static final String FLAG_BOUNCER_UI_REVAMP = "com.android.systemui.bouncer_ui_revamp"; - /** @hide */ - public static final String FLAG_BOUNCER_UI_REVAMP_2 = "com.android.systemui.bouncer_ui_revamp_2"; - /** @hide */ - public static final String FLAG_BP_COLORS = "com.android.systemui.bp_colors"; + public static final String FLAG_BP_TALKBACK = "com.android.systemui.bp_talkback"; /** @hide */ public static final String FLAG_BRIGHTNESS_SLIDER_FOCUS_STATE = "com.android.systemui.brightness_slider_focus_state"; /** @hide */ - public static final String FLAG_CHECK_LOCKSCREEN_GONE_TRANSITION = "com.android.systemui.check_lockscreen_gone_transition"; - /** @hide */ - public static final String FLAG_CLASSIC_FLAGS_MULTI_USER = "com.android.systemui.classic_flags_multi_user"; - /** @hide */ - public static final String FLAG_CLIPBOARD_IMAGE_TIMEOUT = "com.android.systemui.clipboard_image_timeout"; + public static final String FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX = "com.android.systemui.centralized_status_bar_height_fix"; /** @hide */ public static final String FLAG_CLIPBOARD_NONINTERACTIVE_ON_LOCKSCREEN = "com.android.systemui.clipboard_noninteractive_on_lockscreen"; /** @hide */ - public static final String FLAG_CLIPBOARD_OVERLAY_MULTIUSER = "com.android.systemui.clipboard_overlay_multiuser"; - /** @hide */ - public static final String FLAG_CLIPBOARD_SHARED_TRANSITIONS = "com.android.systemui.clipboard_shared_transitions"; - /** @hide */ - public static final String FLAG_CLIPBOARD_USE_DESCRIPTION_MIMETYPE = "com.android.systemui.clipboard_use_description_mimetype"; - /** @hide */ - public static final String FLAG_CLOCK_FIDGET_ANIMATION = "com.android.systemui.clock_fidget_animation"; + public static final String FLAG_CLOCK_REACTIVE_VARIANTS = "com.android.systemui.clock_reactive_variants"; /** @hide */ public static final String FLAG_COMMUNAL_BOUNCER_DO_NOT_MODIFY_PLUGIN_OPEN = "com.android.systemui.communal_bouncer_do_not_modify_plugin_open"; /** @hide */ - public static final String FLAG_COMMUNAL_EDIT_WIDGETS_ACTIVITY_FINISH_FIX = "com.android.systemui.communal_edit_widgets_activity_finish_fix"; - /** @hide */ public static final String FLAG_COMMUNAL_HUB = "com.android.systemui.communal_hub"; /** @hide */ - public static final String FLAG_COMMUNAL_HUB_USE_THREAD_POOL_FOR_WIDGETS = "com.android.systemui.communal_hub_use_thread_pool_for_widgets"; - /** @hide */ - public static final String FLAG_COMMUNAL_RESPONSIVE_GRID = "com.android.systemui.communal_responsive_grid"; - /** @hide */ - public static final String FLAG_COMMUNAL_SCENE_KTF_REFACTOR = "com.android.systemui.communal_scene_ktf_refactor"; - /** @hide */ - public static final String FLAG_COMMUNAL_STANDALONE_SUPPORT = "com.android.systemui.communal_standalone_support"; - /** @hide */ - public static final String FLAG_COMMUNAL_TIMER_FLICKER_FIX = "com.android.systemui.communal_timer_flicker_fix"; - /** @hide */ - public static final String FLAG_COMMUNAL_WIDGET_RESIZING = "com.android.systemui.communal_widget_resizing"; - /** @hide */ - public static final String FLAG_COMMUNAL_WIDGET_TRAMPOLINE_FIX = "com.android.systemui.communal_widget_trampoline_fix"; - /** @hide */ public static final String FLAG_COMPOSE_BOUNCER = "com.android.systemui.compose_bouncer"; /** @hide */ + public static final String FLAG_COMPOSE_LOCKSCREEN = "com.android.systemui.compose_lockscreen"; + /** @hide */ public static final String FLAG_CONFINE_NOTIFICATION_TOUCH_TO_VIEW_WIDTH = "com.android.systemui.confine_notification_touch_to_view_width"; /** @hide */ - public static final String FLAG_CONT_AUTH_PLUGIN = "com.android.systemui.cont_auth_plugin"; + public static final String FLAG_CONSTRAINT_BP = "com.android.systemui.constraint_bp"; /** @hide */ public static final String FLAG_CONTEXTUAL_TIPS_ASSISTANT_DISMISS_FIX = "com.android.systemui.contextual_tips_assistant_dismiss_fix"; /** @hide */ @@ -76,31 +39,25 @@ public final class Flags { /** @hide */ public static final String FLAG_CREATE_WINDOWLESS_WINDOW_MAGNIFIER = "com.android.systemui.create_windowless_window_magnifier"; /** @hide */ - public static final String FLAG_DEBUG_LIVE_UPDATES_PROMOTE_ALL = "com.android.systemui.debug_live_updates_promote_all"; - /** @hide */ - public static final String FLAG_DECOUPLE_VIEW_CONTROLLER_IN_ANIMLIB = "com.android.systemui.decouple_view_controller_in_animlib"; + public static final String FLAG_DEDICATED_NOTIF_INFLATION_THREAD = "com.android.systemui.dedicated_notif_inflation_thread"; /** @hide */ public static final String FLAG_DELAY_SHOW_MAGNIFICATION_BUTTON = "com.android.systemui.delay_show_magnification_button"; /** @hide */ - public static final String FLAG_DESKTOP_EFFECTS_QS_TILE = "com.android.systemui.desktop_effects_qs_tile"; + public static final String FLAG_DELAYED_WAKELOCK_RELEASE_ON_BACKGROUND_THREAD = "com.android.systemui.delayed_wakelock_release_on_background_thread"; /** @hide */ public static final String FLAG_DEVICE_ENTRY_UDFPS_REFACTOR = "com.android.systemui.device_entry_udfps_refactor"; /** @hide */ - public static final String FLAG_DISABLE_BLURRED_SHADE_VISIBLE = "com.android.systemui.disable_blurred_shade_visible"; - /** @hide */ public static final String FLAG_DISABLE_CONTEXTUAL_TIPS_FREQUENCY_CHECK = "com.android.systemui.disable_contextual_tips_frequency_check"; /** @hide */ public static final String FLAG_DISABLE_CONTEXTUAL_TIPS_IOS_SWITCHER_CHECK = "com.android.systemui.disable_contextual_tips_ios_switcher_check"; /** @hide */ - public static final String FLAG_DISABLE_SHADE_TRACKPAD_TWO_FINGER_SWIPE = "com.android.systemui.disable_shade_trackpad_two_finger_swipe"; - /** @hide */ - public static final String FLAG_DOUBLE_TAP_TO_SLEEP = "com.android.systemui.double_tap_to_sleep"; + public static final String FLAG_DOZEUI_SCHEDULING_ALARMS_BACKGROUND_EXECUTION = "com.android.systemui.dozeui_scheduling_alarms_background_execution"; /** @hide */ public static final String FLAG_DREAM_INPUT_SESSION_PILFER_ONCE = "com.android.systemui.dream_input_session_pilfer_once"; /** @hide */ public static final String FLAG_DREAM_OVERLAY_BOUNCER_SWIPE_DIRECTION_FILTERING = "com.android.systemui.dream_overlay_bouncer_swipe_direction_filtering"; /** @hide */ - public static final String FLAG_DREAM_OVERLAY_UPDATED_FONT = "com.android.systemui.dream_overlay_updated_font"; + public static final String FLAG_DUAL_SHADE = "com.android.systemui.dual_shade"; /** @hide */ public static final String FLAG_EDGE_BACK_GESTURE_HANDLER_THREAD = "com.android.systemui.edge_back_gesture_handler_thread"; /** @hide */ @@ -120,29 +77,15 @@ public final class Flags { /** @hide */ public static final String FLAG_ENABLE_LAYOUT_TRACING = "com.android.systemui.enable_layout_tracing"; /** @hide */ - public static final String FLAG_ENABLE_UNDERLAY = "com.android.systemui.enable_underlay"; - /** @hide */ public static final String FLAG_ENABLE_VIEW_CAPTURE_TRACING = "com.android.systemui.enable_view_capture_tracing"; /** @hide */ + public static final String FLAG_ENABLE_WIDGET_PICKER_SIZE_FILTER = "com.android.systemui.enable_widget_picker_size_filter"; + /** @hide */ public static final String FLAG_ENFORCE_BRIGHTNESS_BASE_USER_RESTRICTION = "com.android.systemui.enforce_brightness_base_user_restriction"; /** @hide */ public static final String FLAG_EXAMPLE_FLAG = "com.android.systemui.example_flag"; /** @hide */ - public static final String FLAG_EXPAND_COLLAPSE_PRIVACY_DIALOG = "com.android.systemui.expand_collapse_privacy_dialog"; - /** @hide */ - public static final String FLAG_EXPAND_HEADS_UP_ON_INLINE_REPLY = "com.android.systemui.expand_heads_up_on_inline_reply"; - /** @hide */ - public static final String FLAG_EXPANDED_PRIVACY_INDICATORS_ON_LARGE_SCREEN = "com.android.systemui.expanded_privacy_indicators_on_large_screen"; - /** @hide */ - public static final String FLAG_EXTENDED_APPS_SHORTCUT_CATEGORY = "com.android.systemui.extended_apps_shortcut_category"; - /** @hide */ - public static final String FLAG_FACE_MESSAGE_DEFER_UPDATE = "com.android.systemui.face_message_defer_update"; - /** @hide */ - public static final String FLAG_FACE_SCANNING_ANIMATION_NPE_FIX = "com.android.systemui.face_scanning_animation_npe_fix"; - /** @hide */ - public static final String FLAG_FASTER_UNLOCK_TRANSITION = "com.android.systemui.faster_unlock_transition"; - /** @hide */ - public static final String FLAG_FETCH_BOOKMARKS_XML_KEYBOARD_SHORTCUTS = "com.android.systemui.fetch_bookmarks_xml_keyboard_shortcuts"; + public static final String FLAG_FAST_UNLOCK_TRANSITION = "com.android.systemui.fast_unlock_transition"; /** @hide */ public static final String FLAG_FIX_IMAGE_WALLPAPER_CRASH_SURFACE_ALREADY_RELEASED = "com.android.systemui.fix_image_wallpaper_crash_surface_already_released"; /** @hide */ @@ -150,132 +93,62 @@ public final class Flags { /** @hide */ public static final String FLAG_FLOATING_MENU_ANIMATED_TUCK = "com.android.systemui.floating_menu_animated_tuck"; /** @hide */ - public static final String FLAG_FLOATING_MENU_DISPLAY_CUTOUT_SUPPORT = "com.android.systemui.floating_menu_display_cutout_support"; - /** @hide */ public static final String FLAG_FLOATING_MENU_DRAG_TO_EDIT = "com.android.systemui.floating_menu_drag_to_edit"; /** @hide */ public static final String FLAG_FLOATING_MENU_DRAG_TO_HIDE = "com.android.systemui.floating_menu_drag_to_hide"; /** @hide */ - public static final String FLAG_FLOATING_MENU_HEARING_DEVICE_STATUS_ICON = "com.android.systemui.floating_menu_hearing_device_status_icon"; - /** @hide */ public static final String FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION = "com.android.systemui.floating_menu_ime_displacement_animation"; /** @hide */ public static final String FLAG_FLOATING_MENU_NARROW_TARGET_CONTENT_OBSERVER = "com.android.systemui.floating_menu_narrow_target_content_observer"; /** @hide */ - public static final String FLAG_FLOATING_MENU_NOTIFY_TARGETS_CHANGED_ON_STRICT_DIFF = "com.android.systemui.floating_menu_notify_targets_changed_on_strict_diff"; - /** @hide */ public static final String FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG = "com.android.systemui.floating_menu_overlaps_nav_bars_flag"; /** @hide */ public static final String FLAG_FLOATING_MENU_RADII_ANIMATION = "com.android.systemui.floating_menu_radii_animation"; /** @hide */ + public static final String FLAG_GENERATED_PREVIEWS = "android.appwidget.flags.generated_previews"; + /** @hide */ public static final String FLAG_GET_CONNECTED_DEVICE_NAME_UNSYNCHRONIZED = "com.android.systemui.get_connected_device_name_unsynchronized"; /** @hide */ public static final String FLAG_GLANCEABLE_HUB_ALLOW_KEYGUARD_WHEN_DREAMING = "com.android.systemui.glanceable_hub_allow_keyguard_when_dreaming"; /** @hide */ - public static final String FLAG_GLANCEABLE_HUB_BLURRED_BACKGROUND = "com.android.systemui.glanceable_hub_blurred_background"; + public static final String FLAG_GLANCEABLE_HUB_FULLSCREEN_SWIPE = "com.android.systemui.glanceable_hub_fullscreen_swipe"; /** @hide */ - public static final String FLAG_GLANCEABLE_HUB_DIRECT_EDIT_MODE = "com.android.systemui.glanceable_hub_direct_edit_mode"; + public static final String FLAG_GLANCEABLE_HUB_GESTURE_HANDLE = "com.android.systemui.glanceable_hub_gesture_handle"; /** @hide */ - public static final String FLAG_GLANCEABLE_HUB_V2 = "com.android.systemui.glanceable_hub_v2"; + public static final String FLAG_GLANCEABLE_HUB_SHORTCUT_BUTTON = "com.android.systemui.glanceable_hub_shortcut_button"; /** @hide */ - public static final String FLAG_GLANCEABLE_HUB_V2_RESOURCES = "com.android.systemui.glanceable_hub_v2_resources"; + public static final String FLAG_HAPTIC_BRIGHTNESS_SLIDER = "com.android.systemui.haptic_brightness_slider"; /** @hide */ - public static final String FLAG_HAPTICS_FOR_COMPOSE_SLIDERS = "com.android.systemui.haptics_for_compose_sliders"; - /** @hide */ - public static final String FLAG_HARDWARE_COLOR_STYLES = "com.android.systemui.hardware_color_styles"; + public static final String FLAG_HAPTIC_VOLUME_SLIDER = "com.android.systemui.haptic_volume_slider"; /** @hide */ public static final String FLAG_HEARING_AIDS_QS_TILE_DIALOG = "com.android.systemui.hearing_aids_qs_tile_dialog"; /** @hide */ public static final String FLAG_HEARING_DEVICES_DIALOG_RELATED_TOOLS = "com.android.systemui.hearing_devices_dialog_related_tools"; /** @hide */ - public static final String FLAG_HIDE_RINGER_BUTTON_IN_SINGLE_VOLUME_MODE = "com.android.systemui.hide_ringer_button_in_single_volume_mode"; - /** @hide */ - public static final String FLAG_HOME_CONTROLS_DREAM_HSUM = "com.android.systemui.home_controls_dream_hsum"; - /** @hide */ - public static final String FLAG_HUB_EDIT_MODE_TOUCH_ADJUSTMENTS = "com.android.systemui.hub_edit_mode_touch_adjustments"; - /** @hide */ - public static final String FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE = "com.android.systemui.hubmode_fullscreen_vertical_swipe"; - /** @hide */ - public static final String FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX = "com.android.systemui.hubmode_fullscreen_vertical_swipe_fix"; - /** @hide */ - public static final String FLAG_ICON_REFRESH_2025 = "com.android.systemui.icon_refresh_2025"; - /** @hide */ - public static final String FLAG_IGNORE_TOUCHES_NEXT_TO_NOTIFICATION_SHELF = "com.android.systemui.ignore_touches_next_to_notification_shelf"; - /** @hide */ - public static final String FLAG_INDICATION_TEXT_A11Y_FIX = "com.android.systemui.indication_text_a11y_fix"; - /** @hide */ public static final String FLAG_KEYBOARD_DOCKING_INDICATOR = "com.android.systemui.keyboard_docking_indicator"; /** @hide */ public static final String FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE = "com.android.systemui.keyboard_shortcut_helper_rewrite"; /** @hide */ - public static final String FLAG_KEYBOARD_SHORTCUT_HELPER_SHORTCUT_CUSTOMIZER = "com.android.systemui.keyboard_shortcut_helper_shortcut_customizer"; - /** @hide */ - public static final String FLAG_KEYBOARD_TOUCHPAD_CONTEXTUAL_EDUCATION = "com.android.systemui.keyboard_touchpad_contextual_education"; - /** @hide */ - public static final String FLAG_KEYGUARD_TRANSITION_FORCE_FINISH_ON_SCREEN_OFF = "com.android.systemui.keyguard_transition_force_finish_on_screen_off"; - /** @hide */ - public static final String FLAG_KEYGUARD_WM_REORDER_ATMS_CALLS = "com.android.systemui.keyguard_wm_reorder_atms_calls"; + public static final String FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR = "com.android.systemui.keyguard_bottom_area_refactor"; /** @hide */ public static final String FLAG_KEYGUARD_WM_STATE_REFACTOR = "com.android.systemui.keyguard_wm_state_refactor"; /** @hide */ - public static final String FLAG_LOCKSCREEN_FONT = "com.android.systemui.lockscreen_font"; - /** @hide */ - public static final String FLAG_LOW_LIGHT_CLOCK_DREAM = "com.android.systemui.low_light_clock_dream"; - /** @hide */ - public static final String FLAG_MAGNETIC_NOTIFICATION_SWIPES = "com.android.systemui.magnetic_notification_swipes"; - /** @hide */ - public static final String FLAG_MEDIA_CONTROLS_A11Y_COLORS = "com.android.systemui.media_controls_a11y_colors"; - /** @hide */ - public static final String FLAG_MEDIA_CONTROLS_BUTTON_MEDIA3 = "com.android.systemui.media_controls_button_media3"; - /** @hide */ - public static final String FLAG_MEDIA_CONTROLS_BUTTON_MEDIA3_PLACEMENT = "com.android.systemui.media_controls_button_media3_placement"; - /** @hide */ - public static final String FLAG_MEDIA_CONTROLS_DEVICE_MANAGER_BACKGROUND_EXECUTION = "com.android.systemui.media_controls_device_manager_background_execution"; - /** @hide */ - public static final String FLAG_MEDIA_CONTROLS_DRAWABLES_REUSE_BUGFIX = "com.android.systemui.media_controls_drawables_reuse_bugfix"; + public static final String FLAG_LIGHT_REVEAL_MIGRATION = "com.android.systemui.light_reveal_migration"; /** @hide */ public static final String FLAG_MEDIA_CONTROLS_LOCKSCREEN_SHADE_BUG_FIX = "com.android.systemui.media_controls_lockscreen_shade_bug_fix"; /** @hide */ - public static final String FLAG_MEDIA_CONTROLS_UI_UPDATE = "com.android.systemui.media_controls_ui_update"; - /** @hide */ - public static final String FLAG_MEDIA_CONTROLS_UMO_INFLATION_IN_BACKGROUND = "com.android.systemui.media_controls_umo_inflation_in_background"; + public static final String FLAG_MEDIA_CONTROLS_REFACTOR = "com.android.systemui.media_controls_refactor"; /** @hide */ public static final String FLAG_MEDIA_CONTROLS_USER_INITIATED_DELETEINTENT = "com.android.systemui.media_controls_user_initiated_deleteintent"; /** @hide */ - public static final String FLAG_MEDIA_LOAD_METADATA_VIA_MEDIA_DATA_LOADER = "com.android.systemui.media_load_metadata_via_media_data_loader"; - /** @hide */ - public static final String FLAG_MEDIA_LOCKSCREEN_LAUNCH_ANIMATION = "com.android.systemui.media_lockscreen_launch_animation"; - /** @hide */ - public static final String FLAG_MEDIA_PROJECTION_DIALOG_BEHIND_LOCKSCREEN = "com.android.systemui.media_projection_dialog_behind_lockscreen"; - /** @hide */ - public static final String FLAG_MEDIA_PROJECTION_GREY_ERROR_TEXT = "com.android.systemui.media_projection_grey_error_text"; - /** @hide */ - public static final String FLAG_MEDIA_PROJECTION_REQUEST_ATTRIBUTION_FIX = "com.android.systemui.media_projection_request_attribution_fix"; - /** @hide */ - public static final String FLAG_MODES_UI_DIALOG_PAGING = "com.android.systemui.modes_ui_dialog_paging"; - /** @hide */ - public static final String FLAG_MOVE_TRANSITION_ANIMATION_LAYER = "com.android.systemui.move_transition_animation_layer"; - /** @hide */ - public static final String FLAG_MSDL_FEEDBACK = "com.android.systemui.msdl_feedback"; - /** @hide */ - public static final String FLAG_MULTIUSER_WIFI_PICKER_TRACKER_SUPPORT = "com.android.systemui.multiuser_wifi_picker_tracker_support"; + public static final String FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT = "com.android.systemui.migrate_clocks_to_blueprint"; /** @hide */ public static final String FLAG_NEW_AOD_TRANSITION = "com.android.systemui.new_aod_transition"; /** @hide */ + public static final String FLAG_NEW_TOUCHPAD_GESTURES_TUTORIAL = "com.android.systemui.new_touchpad_gestures_tutorial"; + /** @hide */ public static final String FLAG_NEW_VOLUME_PANEL = "com.android.systemui.new_volume_panel"; /** @hide */ - public static final String FLAG_NON_TOUCHSCREEN_DEVICES_BYPASS_FALSING = "com.android.systemui.non_touchscreen_devices_bypass_falsing"; - /** @hide */ - public static final String FLAG_NOTES_ROLE_QS_TILE = "com.android.systemui.notes_role_qs_tile"; - /** @hide */ - public static final String FLAG_NOTIFICATION_ADD_X_ON_HOVER_TO_DISMISS = "com.android.systemui.notification_add_x_on_hover_to_dismiss"; - /** @hide */ - public static final String FLAG_NOTIFICATION_AMBIENT_SUPPRESSION_AFTER_INFLATION = "com.android.systemui.notification_ambient_suppression_after_inflation"; - /** @hide */ - public static final String FLAG_NOTIFICATION_ANIMATED_ACTIONS_TREATMENT = "com.android.systemui.notification_animated_actions_treatment"; - /** @hide */ - public static final String FLAG_NOTIFICATION_APPEAR_NONLINEAR = "com.android.systemui.notification_appear_nonlinear"; - /** @hide */ public static final String FLAG_NOTIFICATION_ASYNC_GROUP_HEADER_INFLATION = "com.android.systemui.notification_async_group_header_inflation"; /** @hide */ public static final String FLAG_NOTIFICATION_ASYNC_HYBRID_VIEW_INFLATION = "com.android.systemui.notification_async_hybrid_view_inflation"; @@ -286,81 +159,57 @@ public final class Flags { /** @hide */ public static final String FLAG_NOTIFICATION_BACKGROUND_TINT_OPTIMIZATION = "com.android.systemui.notification_background_tint_optimization"; /** @hide */ - public static final String FLAG_NOTIFICATION_BUNDLE_UI = "com.android.systemui.notification_bundle_ui"; - /** @hide */ public static final String FLAG_NOTIFICATION_COLOR_UPDATE_LOGGER = "com.android.systemui.notification_color_update_logger"; /** @hide */ public static final String FLAG_NOTIFICATION_CONTENT_ALPHA_OPTIMIZATION = "com.android.systemui.notification_content_alpha_optimization"; /** @hide */ public static final String FLAG_NOTIFICATION_FOOTER_BACKGROUND_TINT_OPTIMIZATION = "com.android.systemui.notification_footer_background_tint_optimization"; /** @hide */ + public static final String FLAG_NOTIFICATION_MEDIA_MANAGER_BACKGROUND_EXECUTION = "com.android.systemui.notification_media_manager_background_execution"; + /** @hide */ + public static final String FLAG_NOTIFICATION_MINIMALISM_PROTOTYPE = "com.android.systemui.notification_minimalism_prototype"; + /** @hide */ public static final String FLAG_NOTIFICATION_OVER_EXPANSION_CLIPPING_FIX = "com.android.systemui.notification_over_expansion_clipping_fix"; /** @hide */ - public static final String FLAG_NOTIFICATION_REENTRANT_DISMISS = "com.android.systemui.notification_reentrant_dismiss"; - /** @hide */ - public static final String FLAG_NOTIFICATION_ROW_ACCESSIBILITY_EXPANDED = "com.android.systemui.notification_row_accessibility_expanded"; + public static final String FLAG_NOTIFICATION_PULSING_FIX = "com.android.systemui.notification_pulsing_fix"; /** @hide */ public static final String FLAG_NOTIFICATION_ROW_CONTENT_BINDER_REFACTOR = "com.android.systemui.notification_row_content_binder_refactor"; /** @hide */ - public static final String FLAG_NOTIFICATION_ROW_TRANSPARENCY = "com.android.systemui.notification_row_transparency"; - /** @hide */ public static final String FLAG_NOTIFICATION_ROW_USER_CONTEXT = "com.android.systemui.notification_row_user_context"; /** @hide */ - public static final String FLAG_NOTIFICATION_SHADE_BLUR = "com.android.systemui.notification_shade_blur"; - /** @hide */ - public static final String FLAG_NOTIFICATION_SHADE_UI_THREAD = "com.android.systemui.notification_shade_ui_thread"; - /** @hide */ - public static final String FLAG_NOTIFICATION_SKIP_SILENT_UPDATES = "com.android.systemui.notification_skip_silent_updates"; - /** @hide */ - public static final String FLAG_NOTIFICATION_TRANSPARENT_HEADER_FIX = "com.android.systemui.notification_transparent_header_fix"; - /** @hide */ public static final String FLAG_NOTIFICATION_VIEW_FLIPPER_PAUSING_V2 = "com.android.systemui.notification_view_flipper_pausing_v2"; /** @hide */ public static final String FLAG_NOTIFICATIONS_BACKGROUND_ICONS = "com.android.systemui.notifications_background_icons"; /** @hide */ - public static final String FLAG_NOTIFICATIONS_FOOTER_VISIBILITY_FIX = "com.android.systemui.notifications_footer_visibility_fix"; + public static final String FLAG_NOTIFICATIONS_FOOTER_VIEW_REFACTOR = "com.android.systemui.notifications_footer_view_refactor"; + /** @hide */ + public static final String FLAG_NOTIFICATIONS_HEADS_UP_REFACTOR = "com.android.systemui.notifications_heads_up_refactor"; /** @hide */ public static final String FLAG_NOTIFICATIONS_HIDE_ON_DISPLAY_SWITCH = "com.android.systemui.notifications_hide_on_display_switch"; /** @hide */ - public static final String FLAG_NOTIFICATIONS_HUN_SHARED_ANIMATION_VALUES = "com.android.systemui.notifications_hun_shared_animation_values"; - /** @hide */ public static final String FLAG_NOTIFICATIONS_ICON_CONTAINER_REFACTOR = "com.android.systemui.notifications_icon_container_refactor"; /** @hide */ - public static final String FLAG_NOTIFICATIONS_LAUNCH_RADIUS = "com.android.systemui.notifications_launch_radius"; + public static final String FLAG_NOTIFICATIONS_IMPROVED_HUN_ANIMATION = "com.android.systemui.notifications_improved_hun_animation"; /** @hide */ public static final String FLAG_NOTIFICATIONS_LIVE_DATA_STORE_REFACTOR = "com.android.systemui.notifications_live_data_store_refactor"; /** @hide */ - public static final String FLAG_NOTIFICATIONS_PINNED_HUN_IN_SHADE = "com.android.systemui.notifications_pinned_hun_in_shade"; - /** @hide */ - public static final String FLAG_NOTIFICATIONS_REDESIGN_FOOTER_VIEW = "com.android.systemui.notifications_redesign_footer_view"; - /** @hide */ - public static final String FLAG_NOTIFICATIONS_REDESIGN_GUTS = "com.android.systemui.notifications_redesign_guts"; - /** @hide */ - public static final String FLAG_NOTIFY_PASSWORD_TEXT_VIEW_USER_ACTIVITY_IN_BACKGROUND = "com.android.systemui.notify_password_text_view_user_activity_in_background"; - /** @hide */ public static final String FLAG_NOTIFY_POWER_MANAGER_USER_ACTIVITY_BACKGROUND = "com.android.systemui.notify_power_manager_user_activity_background"; /** @hide */ - public static final String FLAG_ONLY_SHOW_MEDIA_STREAM_SLIDER_IN_SINGLE_VOLUME_MODE = "com.android.systemui.only_show_media_stream_slider_in_single_volume_mode"; - /** @hide */ - public static final String FLAG_OUTPUT_SWITCHER_REDESIGN = "com.android.systemui.output_switcher_redesign"; - /** @hide */ - public static final String FLAG_OVERRIDE_SUPPRESS_OVERLAY_CONDITION = "com.android.systemui.override_suppress_overlay_condition"; - /** @hide */ - public static final String FLAG_PERMISSION_HELPER_INLINE_UI_RICH_ONGOING = "com.android.systemui.permission_helper_inline_ui_rich_ongoing"; - /** @hide */ - public static final String FLAG_PERMISSION_HELPER_UI_RICH_ONGOING = "com.android.systemui.permission_helper_ui_rich_ongoing"; - /** @hide */ - public static final String FLAG_PHYSICAL_NOTIFICATION_MOVEMENT = "com.android.systemui.physical_notification_movement"; - /** @hide */ public static final String FLAG_PIN_INPUT_FIELD_STYLED_FOCUS_STATE = "com.android.systemui.pin_input_field_styled_focus_state"; /** @hide */ + public static final String FLAG_PREDICTIVE_BACK_ANIMATE_BOUNCER = "com.android.systemui.predictive_back_animate_bouncer"; + /** @hide */ + public static final String FLAG_PREDICTIVE_BACK_ANIMATE_DIALOGS = "com.android.systemui.predictive_back_animate_dialogs"; + /** @hide */ public static final String FLAG_PREDICTIVE_BACK_ANIMATE_SHADE = "com.android.systemui.predictive_back_animate_shade"; /** @hide */ - public static final String FLAG_PREDICTIVE_BACK_DELAY_WM_TRANSITION = "com.android.systemui.predictive_back_delay_wm_transition"; + public static final String FLAG_PREDICTIVE_BACK_SYSUI = "com.android.systemui.predictive_back_sysui"; /** @hide */ public static final String FLAG_PRIORITY_PEOPLE_SECTION = "com.android.systemui.priority_people_section"; /** @hide */ - public static final String FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY = "com.android.systemui.promote_notifications_automatically"; + public static final String FLAG_PRIVACY_DOT_UNFOLD_WRONG_CORNER_FIX = "com.android.systemui.privacy_dot_unfold_wrong_corner_fix"; + /** @hide */ + public static final String FLAG_PSS_APP_SELECTOR_ABRUPT_EXIT_FIX = "com.android.systemui.pss_app_selector_abrupt_exit_fix"; /** @hide */ public static final String FLAG_PSS_APP_SELECTOR_RECENTS_SPLIT_SCREEN = "com.android.systemui.pss_app_selector_recents_split_screen"; /** @hide */ @@ -368,42 +217,32 @@ public final class Flags { /** @hide */ public static final String FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX = "com.android.systemui.qs_custom_tile_click_guaranteed_bug_fix"; /** @hide */ + public static final String FLAG_QS_NEW_PIPELINE = "com.android.systemui.qs_new_pipeline"; + /** @hide */ public static final String FLAG_QS_NEW_TILES = "com.android.systemui.qs_new_tiles"; /** @hide */ public static final String FLAG_QS_NEW_TILES_FUTURE = "com.android.systemui.qs_new_tiles_future"; /** @hide */ - public static final String FLAG_QS_QUICK_REBIND_ACTIVE_TILES = "com.android.systemui.qs_quick_rebind_active_tiles"; - /** @hide */ - public static final String FLAG_QS_REGISTER_SETTING_OBSERVER_ON_BG_THREAD = "com.android.systemui.qs_register_setting_observer_on_bg_thread"; - /** @hide */ - public static final String FLAG_QS_TILE_DETAILED_VIEW = "com.android.systemui.qs_tile_detailed_view"; - /** @hide */ public static final String FLAG_QS_TILE_FOCUS_STATE = "com.android.systemui.qs_tile_focus_state"; /** @hide */ public static final String FLAG_QS_UI_REFACTOR = "com.android.systemui.qs_ui_refactor"; /** @hide */ - public static final String FLAG_QS_UI_REFACTOR_COMPOSE_FRAGMENT = "com.android.systemui.qs_ui_refactor_compose_fragment"; + public static final String FLAG_QUICK_SETTINGS_VISUAL_HAPTICS_LONGPRESS = "com.android.systemui.quick_settings_visual_haptics_longpress"; /** @hide */ public static final String FLAG_RECORD_ISSUE_QS_TILE = "com.android.systemui.record_issue_qs_tile"; /** @hide */ - public static final String FLAG_REDESIGN_MAGNIFICATION_WINDOW_SIZE = "com.android.systemui.redesign_magnification_window_size"; - /** @hide */ public static final String FLAG_REFACTOR_GET_CURRENT_USER = "com.android.systemui.refactor_get_current_user"; /** @hide */ public static final String FLAG_REGISTER_BATTERY_CONTROLLER_RECEIVERS_IN_CORESTARTABLE = "com.android.systemui.register_battery_controller_receivers_in_corestartable"; /** @hide */ - public static final String FLAG_REGISTER_CONTENT_OBSERVERS_ASYNC = "com.android.systemui.register_content_observers_async"; - /** @hide */ public static final String FLAG_REGISTER_NEW_WALLET_CARD_IN_BACKGROUND = "com.android.systemui.register_new_wallet_card_in_background"; /** @hide */ public static final String FLAG_REGISTER_WALLPAPER_NOTIFIER_BACKGROUND = "com.android.systemui.register_wallpaper_notifier_background"; /** @hide */ - public static final String FLAG_RELOCK_WITH_POWER_BUTTON_IMMEDIATELY = "com.android.systemui.relock_with_power_button_immediately"; + public static final String FLAG_REGISTER_ZEN_MODE_CONTENT_OBSERVER_BACKGROUND = "com.android.systemui.register_zen_mode_content_observer_background"; /** @hide */ public static final String FLAG_REMOVE_DREAM_OVERLAY_HIDE_ON_TOUCH = "com.android.systemui.remove_dream_overlay_hide_on_touch"; /** @hide */ - public static final String FLAG_REMOVE_UPDATE_LISTENER_IN_QS_ICON_VIEW_IMPL = "com.android.systemui.remove_update_listener_in_qs_icon_view_impl"; - /** @hide */ public static final String FLAG_REST_TO_UNLOCK = "com.android.systemui.rest_to_unlock"; /** @hide */ public static final String FLAG_RESTART_DREAM_ON_UNOCCLUDE = "com.android.systemui.restart_dream_on_unocclude"; @@ -420,46 +259,18 @@ public final class Flags { /** @hide */ public static final String FLAG_SCREENSHOT_ACTION_DISMISS_SYSTEM_WINDOWS = "com.android.systemui.screenshot_action_dismiss_system_windows"; /** @hide */ - public static final String FLAG_SCREENSHOT_MULTIDISPLAY_FOCUS_CHANGE = "com.android.systemui.screenshot_multidisplay_focus_change"; + public static final String FLAG_SCREENSHOT_PRIVATE_PROFILE_ACCESSIBILITY_ANNOUNCEMENT_FIX = "com.android.systemui.screenshot_private_profile_accessibility_announcement_fix"; /** @hide */ - public static final String FLAG_SCREENSHOT_POLICY_SPLIT_AND_DESKTOP_MODE = "com.android.systemui.screenshot_policy_split_and_desktop_mode"; + public static final String FLAG_SCREENSHOT_PRIVATE_PROFILE_BEHAVIOR_FIX = "com.android.systemui.screenshot_private_profile_behavior_fix"; /** @hide */ public static final String FLAG_SCREENSHOT_SCROLL_CROP_VIEW_CRASH_FIX = "com.android.systemui.screenshot_scroll_crop_view_crash_fix"; /** @hide */ - public static final String FLAG_SCREENSHOT_UI_CONTROLLER_REFACTOR = "com.android.systemui.screenshot_ui_controller_refactor"; + public static final String FLAG_SCREENSHOT_SHELF_UI2 = "com.android.systemui.screenshot_shelf_ui2"; /** @hide */ - public static final String FLAG_SECONDARY_USER_WIDGET_HOST = "com.android.systemui.secondary_user_widget_host"; - /** @hide */ - public static final String FLAG_SETTINGS_EXT_REGISTER_CONTENT_OBSERVER_ON_BG_THREAD = "com.android.systemui.settings_ext_register_content_observer_on_bg_thread"; - /** @hide */ - public static final String FLAG_SHADE_EXPANDS_ON_STATUS_BAR_LONG_PRESS = "com.android.systemui.shade_expands_on_status_bar_long_press"; - /** @hide */ - public static final String FLAG_SHADE_HEADER_FONT_UPDATE = "com.android.systemui.shade_header_font_update"; - /** @hide */ - public static final String FLAG_SHADE_LAUNCH_ACCESSIBILITY = "com.android.systemui.shade_launch_accessibility"; - /** @hide */ - public static final String FLAG_SHADE_WINDOW_GOES_AROUND = "com.android.systemui.shade_window_goes_around"; + public static final String FLAG_SHADE_COLLAPSE_ACTIVITY_LAUNCH_FIX = "com.android.systemui.shade_collapse_activity_launch_fix"; /** @hide */ public static final String FLAG_SHADERLIB_LOADING_EFFECT_REFACTOR = "com.android.systemui.shaderlib_loading_effect_refactor"; /** @hide */ - public static final String FLAG_SHORTCUT_HELPER_KEY_GLYPH = "com.android.systemui.shortcut_helper_key_glyph"; - /** @hide */ - public static final String FLAG_SHOW_AUDIO_SHARING_SLIDER_IN_VOLUME_PANEL = "com.android.systemui.show_audio_sharing_slider_in_volume_panel"; - /** @hide */ - public static final String FLAG_SHOW_CLIPBOARD_INDICATION = "com.android.systemui.show_clipboard_indication"; - /** @hide */ - public static final String FLAG_SHOW_LOCKED_BY_YOUR_WATCH_KEYGUARD_INDICATOR = "com.android.systemui.show_locked_by_your_watch_keyguard_indicator"; - /** @hide */ - public static final String FLAG_SHOW_TOAST_WHEN_APP_CONTROL_BRIGHTNESS = "com.android.systemui.show_toast_when_app_control_brightness"; - /** @hide */ - public static final String FLAG_SIM_PIN_BOUNCER_RESET = "com.android.systemui.sim_pin_bouncer_reset"; - /** @hide */ - public static final String FLAG_SIM_PIN_RACE_CONDITION_ON_RESTART = "com.android.systemui.sim_pin_race_condition_on_restart"; - /** @hide */ - public static final String FLAG_SIM_PIN_USE_SLOT_ID = "com.android.systemui.sim_pin_use_slot_id"; - /** @hide */ - public static final String FLAG_SKIP_HIDE_SENSITIVE_NOTIF_ANIMATION = "com.android.systemui.skip_hide_sensitive_notif_animation"; - /** @hide */ public static final String FLAG_SLICE_BROADCAST_RELAY_IN_BACKGROUND = "com.android.systemui.slice_broadcast_relay_in_background"; /** @hide */ public static final String FLAG_SLICE_MANAGER_BINDER_CALL_BACKGROUND = "com.android.systemui.slice_manager_binder_call_background"; @@ -468,2062 +279,642 @@ public final class Flags { /** @hide */ public static final String FLAG_SMARTSPACE_RELOCATE_TO_BOTTOM = "com.android.systemui.smartspace_relocate_to_bottom"; /** @hide */ - public static final String FLAG_SMARTSPACE_REMOTEVIEWS_RENDERING_FIX = "com.android.systemui.smartspace_remoteviews_rendering_fix"; - /** @hide */ - public static final String FLAG_SMARTSPACE_SWIPE_EVENT_LOGGING_FIX = "com.android.systemui.smartspace_swipe_event_logging_fix"; - /** @hide */ - public static final String FLAG_SMARTSPACE_VIEWPAGER2 = "com.android.systemui.smartspace_viewpager2"; - /** @hide */ - public static final String FLAG_SOUNDDOSE_CUSTOMIZATION = "com.android.systemui.sounddose_customization"; - /** @hide */ - public static final String FLAG_SPATIAL_MODEL_APP_PUSHBACK = "com.android.systemui.spatial_model_app_pushback"; - /** @hide */ - public static final String FLAG_STABILIZE_HEADS_UP_GROUP_V2 = "com.android.systemui.stabilize_heads_up_group_v2"; - /** @hide */ - public static final String FLAG_STATUS_BAR_ALWAYS_CHECK_UNDERLYING_NETWORKS = "com.android.systemui.status_bar_always_check_underlying_networks"; - /** @hide */ - public static final String FLAG_STATUS_BAR_AUTO_START_SCREEN_RECORD_CHIP = "com.android.systemui.status_bar_auto_start_screen_record_chip"; - /** @hide */ - public static final String FLAG_STATUS_BAR_CHIPS_MODERNIZATION = "com.android.systemui.status_bar_chips_modernization"; - /** @hide */ - public static final String FLAG_STATUS_BAR_CHIPS_RETURN_ANIMATIONS = "com.android.systemui.status_bar_chips_return_animations"; - /** @hide */ - public static final String FLAG_STATUS_BAR_FONT_UPDATES = "com.android.systemui.status_bar_font_updates"; - /** @hide */ - public static final String FLAG_STATUS_BAR_MOBILE_ICON_KAIROS = "com.android.systemui.status_bar_mobile_icon_kairos"; + public static final String FLAG_SMARTSPACE_REMOTEVIEWS_RENDERING = "com.android.systemui.smartspace_remoteviews_rendering"; /** @hide */ public static final String FLAG_STATUS_BAR_MONOCHROME_ICONS_FIX = "com.android.systemui.status_bar_monochrome_icons_fix"; /** @hide */ - public static final String FLAG_STATUS_BAR_NO_HUN_BEHAVIOR = "com.android.systemui.status_bar_no_hun_behavior"; - /** @hide */ - public static final String FLAG_STATUS_BAR_POPUP_CHIPS = "com.android.systemui.status_bar_popup_chips"; - /** @hide */ - public static final String FLAG_STATUS_BAR_ROOT_MODERNIZATION = "com.android.systemui.status_bar_root_modernization"; - /** @hide */ - public static final String FLAG_STATUS_BAR_SHOW_AUDIO_ONLY_PROJECTION_CHIP = "com.android.systemui.status_bar_show_audio_only_projection_chip"; - /** @hide */ - public static final String FLAG_STATUS_BAR_SIGNAL_POLICY_REFACTOR = "com.android.systemui.status_bar_signal_policy_refactor"; - /** @hide */ - public static final String FLAG_STATUS_BAR_SIGNAL_POLICY_REFACTOR_ETHERNET = "com.android.systemui.status_bar_signal_policy_refactor_ethernet"; + public static final String FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS = "com.android.systemui.status_bar_screen_sharing_chips"; /** @hide */ public static final String FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS = "com.android.systemui.status_bar_static_inout_indicators"; /** @hide */ - public static final String FLAG_STATUS_BAR_STOP_UPDATING_WINDOW_HEIGHT = "com.android.systemui.status_bar_stop_updating_window_height"; - /** @hide */ - public static final String FLAG_STATUS_BAR_SWIPE_OVER_CHIP = "com.android.systemui.status_bar_swipe_over_chip"; - /** @hide */ - public static final String FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN = "com.android.systemui.status_bar_switch_to_spn_from_data_spn"; - /** @hide */ - public static final String FLAG_STATUS_BAR_UI_THREAD = "com.android.systemui.status_bar_ui_thread"; - /** @hide */ - public static final String FLAG_STATUS_BAR_WINDOW_NO_CUSTOM_TOUCH = "com.android.systemui.status_bar_window_no_custom_touch"; - /** @hide */ - public static final String FLAG_STOPPABLE_FGS_SYSTEM_APP = "com.android.systemui.stoppable_fgs_system_app"; - /** @hide */ public static final String FLAG_SWITCH_USER_ON_BG = "com.android.systemui.switch_user_on_bg"; /** @hide */ public static final String FLAG_SYSUI_TEAMFOOD = "com.android.systemui.sysui_teamfood"; /** @hide */ public static final String FLAG_THEME_OVERLAY_CONTROLLER_WAKEFULNESS_DEPRECATION = "com.android.systemui.theme_overlay_controller_wakefulness_deprecation"; /** @hide */ - public static final String FLAG_TRANSITION_RACE_CONDITION = "com.android.systemui.transition_race_condition"; - /** @hide */ public static final String FLAG_TRANSLUCENT_OCCLUDING_ACTIVITY_FIX = "com.android.systemui.translucent_occluding_activity_fix"; /** @hide */ - public static final String FLAG_TV_GLOBAL_ACTIONS_FOCUS = "com.android.systemui.tv_global_actions_focus"; + public static final String FLAG_TRUNCATED_STATUS_BAR_ICONS_FIX = "com.android.systemui.truncated_status_bar_icons_fix"; /** @hide */ public static final String FLAG_UDFPS_VIEW_PERFORMANCE = "com.android.systemui.udfps_view_performance"; /** @hide */ public static final String FLAG_UNFOLD_ANIMATION_BACKGROUND_PROGRESS = "com.android.systemui.unfold_animation_background_progress"; /** @hide */ - public static final String FLAG_UNFOLD_LATENCY_TRACKING_FIX = "com.android.systemui.unfold_latency_tracking_fix"; - /** @hide */ - public static final String FLAG_UPDATE_CORNER_RADIUS_ON_DISPLAY_CHANGED = "com.android.systemui.update_corner_radius_on_display_changed"; - /** @hide */ public static final String FLAG_UPDATE_USER_SWITCHER_BACKGROUND = "com.android.systemui.update_user_switcher_background"; /** @hide */ - public static final String FLAG_UPDATE_WINDOW_MAGNIFIER_BOTTOM_BOUNDARY = "com.android.systemui.update_window_magnifier_bottom_boundary"; - /** @hide */ - public static final String FLAG_USE_AAD_PROX_SENSOR = "com.android.systemui.use_aad_prox_sensor"; - /** @hide */ - public static final String FLAG_USE_NOTIF_INFLATION_THREAD_FOR_FOOTER = "com.android.systemui.use_notif_inflation_thread_for_footer"; - /** @hide */ - public static final String FLAG_USE_NOTIF_INFLATION_THREAD_FOR_ROW = "com.android.systemui.use_notif_inflation_thread_for_row"; - /** @hide */ - public static final String FLAG_USE_TRANSITIONS_FOR_KEYGUARD_OCCLUDED = "com.android.systemui.use_transitions_for_keyguard_occluded"; - /** @hide */ - public static final String FLAG_USE_VOLUME_CONTROLLER = "com.android.systemui.use_volume_controller"; - /** @hide */ - public static final String FLAG_USER_AWARE_SETTINGS_REPOSITORIES = "com.android.systemui.user_aware_settings_repositories"; - /** @hide */ - public static final String FLAG_USER_ENCRYPTED_SOURCE = "com.android.systemui.user_encrypted_source"; - /** @hide */ - public static final String FLAG_USER_SWITCHER_ADD_SIGN_OUT_OPTION = "com.android.systemui.user_switcher_add_sign_out_option"; + public static final String FLAG_VALIDATE_KEYBOARD_SHORTCUT_HELPER_ICON_URI = "com.android.systemui.validate_keyboard_shortcut_helper_icon_uri"; /** @hide */ public static final String FLAG_VISUAL_INTERRUPTIONS_REFACTOR = "com.android.systemui.visual_interruptions_refactor"; - /** @hide */ - public static final String FLAG_VOLUME_REDESIGN = "com.android.systemui.volume_redesign"; - - - + public static boolean activityTransitionUseLargestWindow() { - return FEATURE_FLAGS.activityTransitionUseLargestWindow(); } - - - - public static boolean addBlackBackgroundForWindowMagnifier() { - - return FEATURE_FLAGS.addBlackBackgroundForWindowMagnifier(); - } - - - - public static boolean alwaysComposeQsUiFragment() { - - return FEATURE_FLAGS.alwaysComposeQsUiFragment(); - } - - - + public static boolean ambientTouchMonitorListenToDisplayChanges() { - return FEATURE_FLAGS.ambientTouchMonitorListenToDisplayChanges(); } - - - + public static boolean appClipsBacklinks() { - return FEATURE_FLAGS.appClipsBacklinks(); } - - - - public static boolean appShortcutRemovalFix() { - - return FEATURE_FLAGS.appShortcutRemovalFix(); - } - - - - public static boolean avalancheReplaceHunWhenCritical() { - - return FEATURE_FLAGS.avalancheReplaceHunWhenCritical(); - } - - - + public static boolean bindKeyguardMediaVisibility() { - return FEATURE_FLAGS.bindKeyguardMediaVisibility(); } - - - - public static boolean bouncerUiRevamp() { - - return FEATURE_FLAGS.bouncerUiRevamp(); + + public static boolean bpTalkback() { + return FEATURE_FLAGS.bpTalkback(); } - - - - public static boolean bouncerUiRevamp2() { - - return FEATURE_FLAGS.bouncerUiRevamp2(); - } - - - - public static boolean bpColors() { - - return FEATURE_FLAGS.bpColors(); - } - - - + public static boolean brightnessSliderFocusState() { - return FEATURE_FLAGS.brightnessSliderFocusState(); } - - - - public static boolean checkLockscreenGoneTransition() { - - return FEATURE_FLAGS.checkLockscreenGoneTransition(); + + public static boolean centralizedStatusBarHeightFix() { + return FEATURE_FLAGS.centralizedStatusBarHeightFix(); } - - - - public static boolean classicFlagsMultiUser() { - - return FEATURE_FLAGS.classicFlagsMultiUser(); - } - - - - public static boolean clipboardImageTimeout() { - - return FEATURE_FLAGS.clipboardImageTimeout(); - } - - - + public static boolean clipboardNoninteractiveOnLockscreen() { - return FEATURE_FLAGS.clipboardNoninteractiveOnLockscreen(); } - - - - public static boolean clipboardOverlayMultiuser() { - - return FEATURE_FLAGS.clipboardOverlayMultiuser(); + + public static boolean clockReactiveVariants() { + return FEATURE_FLAGS.clockReactiveVariants(); } - - - - public static boolean clipboardSharedTransitions() { - - return FEATURE_FLAGS.clipboardSharedTransitions(); - } - - - - public static boolean clipboardUseDescriptionMimetype() { - - return FEATURE_FLAGS.clipboardUseDescriptionMimetype(); - } - - - - public static boolean clockFidgetAnimation() { - - return FEATURE_FLAGS.clockFidgetAnimation(); - } - - - + public static boolean communalBouncerDoNotModifyPluginOpen() { - return FEATURE_FLAGS.communalBouncerDoNotModifyPluginOpen(); } - - - - public static boolean communalEditWidgetsActivityFinishFix() { - - return FEATURE_FLAGS.communalEditWidgetsActivityFinishFix(); - } - - - + public static boolean communalHub() { - return FEATURE_FLAGS.communalHub(); } - - - - public static boolean communalHubUseThreadPoolForWidgets() { - - return FEATURE_FLAGS.communalHubUseThreadPoolForWidgets(); - } - - - - public static boolean communalResponsiveGrid() { - - return FEATURE_FLAGS.communalResponsiveGrid(); - } - - - - public static boolean communalSceneKtfRefactor() { - - return FEATURE_FLAGS.communalSceneKtfRefactor(); - } - - - - public static boolean communalStandaloneSupport() { - - return FEATURE_FLAGS.communalStandaloneSupport(); - } - - - - public static boolean communalTimerFlickerFix() { - - return FEATURE_FLAGS.communalTimerFlickerFix(); - } - - - - public static boolean communalWidgetResizing() { - - return FEATURE_FLAGS.communalWidgetResizing(); - } - - - - public static boolean communalWidgetTrampolineFix() { - - return FEATURE_FLAGS.communalWidgetTrampolineFix(); - } - - - + public static boolean composeBouncer() { - return FEATURE_FLAGS.composeBouncer(); } - - - + + public static boolean composeLockscreen() { + return FEATURE_FLAGS.composeLockscreen(); + } + public static boolean confineNotificationTouchToViewWidth() { - return FEATURE_FLAGS.confineNotificationTouchToViewWidth(); } - - - - public static boolean contAuthPlugin() { - - return FEATURE_FLAGS.contAuthPlugin(); + + public static boolean constraintBp() { + return FEATURE_FLAGS.constraintBp(); } - - - + public static boolean contextualTipsAssistantDismissFix() { - return FEATURE_FLAGS.contextualTipsAssistantDismissFix(); } - - - + public static boolean coroutineTracing() { - return FEATURE_FLAGS.coroutineTracing(); } - - - + public static boolean createWindowlessWindowMagnifier() { - return FEATURE_FLAGS.createWindowlessWindowMagnifier(); } - - - - public static boolean debugLiveUpdatesPromoteAll() { - - return FEATURE_FLAGS.debugLiveUpdatesPromoteAll(); + + public static boolean dedicatedNotifInflationThread() { + return FEATURE_FLAGS.dedicatedNotifInflationThread(); } - - - - public static boolean decoupleViewControllerInAnimlib() { - - return FEATURE_FLAGS.decoupleViewControllerInAnimlib(); - } - - - + public static boolean delayShowMagnificationButton() { - return FEATURE_FLAGS.delayShowMagnificationButton(); } - - - - public static boolean desktopEffectsQsTile() { - - return FEATURE_FLAGS.desktopEffectsQsTile(); + + public static boolean delayedWakelockReleaseOnBackgroundThread() { + return FEATURE_FLAGS.delayedWakelockReleaseOnBackgroundThread(); } - - - + public static boolean deviceEntryUdfpsRefactor() { - return FEATURE_FLAGS.deviceEntryUdfpsRefactor(); } - - - - public static boolean disableBlurredShadeVisible() { - - return FEATURE_FLAGS.disableBlurredShadeVisible(); - } - - - + public static boolean disableContextualTipsFrequencyCheck() { - return FEATURE_FLAGS.disableContextualTipsFrequencyCheck(); } - - - + public static boolean disableContextualTipsIosSwitcherCheck() { - return FEATURE_FLAGS.disableContextualTipsIosSwitcherCheck(); } - - - - public static boolean disableShadeTrackpadTwoFingerSwipe() { - - return FEATURE_FLAGS.disableShadeTrackpadTwoFingerSwipe(); + + public static boolean dozeuiSchedulingAlarmsBackgroundExecution() { + return FEATURE_FLAGS.dozeuiSchedulingAlarmsBackgroundExecution(); } - - - - public static boolean doubleTapToSleep() { - - return FEATURE_FLAGS.doubleTapToSleep(); - } - - - + public static boolean dreamInputSessionPilferOnce() { - return FEATURE_FLAGS.dreamInputSessionPilferOnce(); } - - - + public static boolean dreamOverlayBouncerSwipeDirectionFiltering() { - return FEATURE_FLAGS.dreamOverlayBouncerSwipeDirectionFiltering(); } - - - - public static boolean dreamOverlayUpdatedFont() { - - return FEATURE_FLAGS.dreamOverlayUpdatedFont(); + + public static boolean dualShade() { + return FEATURE_FLAGS.dualShade(); } - - - + public static boolean edgeBackGestureHandlerThread() { - return FEATURE_FLAGS.edgeBackGestureHandlerThread(); } - - - + public static boolean edgebackGestureHandlerGetRunningTasksBackground() { - return FEATURE_FLAGS.edgebackGestureHandlerGetRunningTasksBackground(); } - - - + public static boolean enableBackgroundKeyguardOndrawnCallback() { - return FEATURE_FLAGS.enableBackgroundKeyguardOndrawnCallback(); } - - - + public static boolean enableContextualTipForMuteVolume() { - return FEATURE_FLAGS.enableContextualTipForMuteVolume(); } - - - + public static boolean enableContextualTipForPowerOff() { - return FEATURE_FLAGS.enableContextualTipForPowerOff(); } - - - + public static boolean enableContextualTipForTakeScreenshot() { - return FEATURE_FLAGS.enableContextualTipForTakeScreenshot(); } - - - + public static boolean enableContextualTips() { - return FEATURE_FLAGS.enableContextualTips(); } - - - + public static boolean enableEfficientDisplayRepository() { - return FEATURE_FLAGS.enableEfficientDisplayRepository(); } - - - + public static boolean enableLayoutTracing() { - return FEATURE_FLAGS.enableLayoutTracing(); } - - - - public static boolean enableUnderlay() { - - return FEATURE_FLAGS.enableUnderlay(); - } - - - + public static boolean enableViewCaptureTracing() { - return FEATURE_FLAGS.enableViewCaptureTracing(); } - - - + + public static boolean enableWidgetPickerSizeFilter() { + return FEATURE_FLAGS.enableWidgetPickerSizeFilter(); + } + public static boolean enforceBrightnessBaseUserRestriction() { - return FEATURE_FLAGS.enforceBrightnessBaseUserRestriction(); } - - - + public static boolean exampleFlag() { - return FEATURE_FLAGS.exampleFlag(); } - - - - public static boolean expandCollapsePrivacyDialog() { - - return FEATURE_FLAGS.expandCollapsePrivacyDialog(); + + public static boolean fastUnlockTransition() { + return FEATURE_FLAGS.fastUnlockTransition(); } - - - - public static boolean expandHeadsUpOnInlineReply() { - - return FEATURE_FLAGS.expandHeadsUpOnInlineReply(); - } - - - - public static boolean expandedPrivacyIndicatorsOnLargeScreen() { - - return FEATURE_FLAGS.expandedPrivacyIndicatorsOnLargeScreen(); - } - - - - public static boolean extendedAppsShortcutCategory() { - - return FEATURE_FLAGS.extendedAppsShortcutCategory(); - } - - - - public static boolean faceMessageDeferUpdate() { - - return FEATURE_FLAGS.faceMessageDeferUpdate(); - } - - - - public static boolean faceScanningAnimationNpeFix() { - - return FEATURE_FLAGS.faceScanningAnimationNpeFix(); - } - - - - public static boolean fasterUnlockTransition() { - - return FEATURE_FLAGS.fasterUnlockTransition(); - } - - - - public static boolean fetchBookmarksXmlKeyboardShortcuts() { - - return FEATURE_FLAGS.fetchBookmarksXmlKeyboardShortcuts(); - } - - - + public static boolean fixImageWallpaperCrashSurfaceAlreadyReleased() { - return FEATURE_FLAGS.fixImageWallpaperCrashSurfaceAlreadyReleased(); } - - - + public static boolean fixScreenshotActionDismissSystemWindows() { - return FEATURE_FLAGS.fixScreenshotActionDismissSystemWindows(); } - - - + public static boolean floatingMenuAnimatedTuck() { - return FEATURE_FLAGS.floatingMenuAnimatedTuck(); } - - - - public static boolean floatingMenuDisplayCutoutSupport() { - - return FEATURE_FLAGS.floatingMenuDisplayCutoutSupport(); - } - - - + public static boolean floatingMenuDragToEdit() { - return FEATURE_FLAGS.floatingMenuDragToEdit(); } - - - + public static boolean floatingMenuDragToHide() { - return FEATURE_FLAGS.floatingMenuDragToHide(); } - - - - public static boolean floatingMenuHearingDeviceStatusIcon() { - - return FEATURE_FLAGS.floatingMenuHearingDeviceStatusIcon(); - } - - - + public static boolean floatingMenuImeDisplacementAnimation() { - return FEATURE_FLAGS.floatingMenuImeDisplacementAnimation(); } - - - + public static boolean floatingMenuNarrowTargetContentObserver() { - return FEATURE_FLAGS.floatingMenuNarrowTargetContentObserver(); } - - - - public static boolean floatingMenuNotifyTargetsChangedOnStrictDiff() { - - return FEATURE_FLAGS.floatingMenuNotifyTargetsChangedOnStrictDiff(); - } - - - + public static boolean floatingMenuOverlapsNavBarsFlag() { - return FEATURE_FLAGS.floatingMenuOverlapsNavBarsFlag(); } - - - + public static boolean floatingMenuRadiiAnimation() { - return FEATURE_FLAGS.floatingMenuRadiiAnimation(); } - - + public static boolean generatedPreviews() { + return FEATURE_FLAGS.generatedPreviews(); + } + public static boolean getConnectedDeviceNameUnsynchronized() { - return FEATURE_FLAGS.getConnectedDeviceNameUnsynchronized(); } - - - + public static boolean glanceableHubAllowKeyguardWhenDreaming() { - return FEATURE_FLAGS.glanceableHubAllowKeyguardWhenDreaming(); } - - - - public static boolean glanceableHubBlurredBackground() { - - return FEATURE_FLAGS.glanceableHubBlurredBackground(); + + public static boolean glanceableHubFullscreenSwipe() { + return FEATURE_FLAGS.glanceableHubFullscreenSwipe(); } - - - - public static boolean glanceableHubDirectEditMode() { - - return FEATURE_FLAGS.glanceableHubDirectEditMode(); + + public static boolean glanceableHubGestureHandle() { + return FEATURE_FLAGS.glanceableHubGestureHandle(); } - - - - public static boolean glanceableHubV2() { - - return FEATURE_FLAGS.glanceableHubV2(); + + public static boolean glanceableHubShortcutButton() { + return FEATURE_FLAGS.glanceableHubShortcutButton(); } - - - - public static boolean glanceableHubV2Resources() { - - return FEATURE_FLAGS.glanceableHubV2Resources(); + + public static boolean hapticBrightnessSlider() { + return FEATURE_FLAGS.hapticBrightnessSlider(); } - - - - public static boolean hapticsForComposeSliders() { - - return FEATURE_FLAGS.hapticsForComposeSliders(); + + public static boolean hapticVolumeSlider() { + return FEATURE_FLAGS.hapticVolumeSlider(); } - - - - public static boolean hardwareColorStyles() { - - return FEATURE_FLAGS.hardwareColorStyles(); - } - - - + public static boolean hearingAidsQsTileDialog() { - return FEATURE_FLAGS.hearingAidsQsTileDialog(); } - - - + public static boolean hearingDevicesDialogRelatedTools() { - return FEATURE_FLAGS.hearingDevicesDialogRelatedTools(); } - - - - public static boolean hideRingerButtonInSingleVolumeMode() { - - return FEATURE_FLAGS.hideRingerButtonInSingleVolumeMode(); - } - - - - public static boolean homeControlsDreamHsum() { - - return FEATURE_FLAGS.homeControlsDreamHsum(); - } - - - - public static boolean hubEditModeTouchAdjustments() { - - return FEATURE_FLAGS.hubEditModeTouchAdjustments(); - } - - - - public static boolean hubmodeFullscreenVerticalSwipe() { - - return FEATURE_FLAGS.hubmodeFullscreenVerticalSwipe(); - } - - - - public static boolean hubmodeFullscreenVerticalSwipeFix() { - - return FEATURE_FLAGS.hubmodeFullscreenVerticalSwipeFix(); - } - - - - public static boolean iconRefresh2025() { - - return FEATURE_FLAGS.iconRefresh2025(); - } - - - - public static boolean ignoreTouchesNextToNotificationShelf() { - - return FEATURE_FLAGS.ignoreTouchesNextToNotificationShelf(); - } - - - - public static boolean indicationTextA11yFix() { - - return FEATURE_FLAGS.indicationTextA11yFix(); - } - - - + public static boolean keyboardDockingIndicator() { - return FEATURE_FLAGS.keyboardDockingIndicator(); } - - - + public static boolean keyboardShortcutHelperRewrite() { - return FEATURE_FLAGS.keyboardShortcutHelperRewrite(); } - - - - public static boolean keyboardShortcutHelperShortcutCustomizer() { - - return FEATURE_FLAGS.keyboardShortcutHelperShortcutCustomizer(); + + public static boolean keyguardBottomAreaRefactor() { + return FEATURE_FLAGS.keyguardBottomAreaRefactor(); } - - - - public static boolean keyboardTouchpadContextualEducation() { - - return FEATURE_FLAGS.keyboardTouchpadContextualEducation(); - } - - - - public static boolean keyguardTransitionForceFinishOnScreenOff() { - - return FEATURE_FLAGS.keyguardTransitionForceFinishOnScreenOff(); - } - - - - public static boolean keyguardWmReorderAtmsCalls() { - - return FEATURE_FLAGS.keyguardWmReorderAtmsCalls(); - } - - - + public static boolean keyguardWmStateRefactor() { - return FEATURE_FLAGS.keyguardWmStateRefactor(); } - - - - public static boolean lockscreenFont() { - - return FEATURE_FLAGS.lockscreenFont(); + + public static boolean lightRevealMigration() { + return FEATURE_FLAGS.lightRevealMigration(); } - - - - public static boolean lowLightClockDream() { - - return FEATURE_FLAGS.lowLightClockDream(); - } - - - - public static boolean magneticNotificationSwipes() { - - return FEATURE_FLAGS.magneticNotificationSwipes(); - } - - - - public static boolean mediaControlsA11yColors() { - - return FEATURE_FLAGS.mediaControlsA11yColors(); - } - - - - public static boolean mediaControlsButtonMedia3() { - - return FEATURE_FLAGS.mediaControlsButtonMedia3(); - } - - - - public static boolean mediaControlsButtonMedia3Placement() { - - return FEATURE_FLAGS.mediaControlsButtonMedia3Placement(); - } - - - - public static boolean mediaControlsDeviceManagerBackgroundExecution() { - - return FEATURE_FLAGS.mediaControlsDeviceManagerBackgroundExecution(); - } - - - - public static boolean mediaControlsDrawablesReuseBugfix() { - - return FEATURE_FLAGS.mediaControlsDrawablesReuseBugfix(); - } - - - + public static boolean mediaControlsLockscreenShadeBugFix() { - return FEATURE_FLAGS.mediaControlsLockscreenShadeBugFix(); } - - - - public static boolean mediaControlsUiUpdate() { - - return FEATURE_FLAGS.mediaControlsUiUpdate(); + + public static boolean mediaControlsRefactor() { + return FEATURE_FLAGS.mediaControlsRefactor(); } - - - - public static boolean mediaControlsUmoInflationInBackground() { - - return FEATURE_FLAGS.mediaControlsUmoInflationInBackground(); - } - - - + public static boolean mediaControlsUserInitiatedDeleteintent() { - return FEATURE_FLAGS.mediaControlsUserInitiatedDeleteintent(); } - - - - public static boolean mediaLoadMetadataViaMediaDataLoader() { - - return FEATURE_FLAGS.mediaLoadMetadataViaMediaDataLoader(); + + public static boolean migrateClocksToBlueprint() { + return FEATURE_FLAGS.migrateClocksToBlueprint(); } - - - - public static boolean mediaLockscreenLaunchAnimation() { - - return FEATURE_FLAGS.mediaLockscreenLaunchAnimation(); - } - - - - public static boolean mediaProjectionDialogBehindLockscreen() { - - return FEATURE_FLAGS.mediaProjectionDialogBehindLockscreen(); - } - - - - public static boolean mediaProjectionGreyErrorText() { - - return FEATURE_FLAGS.mediaProjectionGreyErrorText(); - } - - - - public static boolean mediaProjectionRequestAttributionFix() { - - return FEATURE_FLAGS.mediaProjectionRequestAttributionFix(); - } - - - - public static boolean modesUiDialogPaging() { - - return FEATURE_FLAGS.modesUiDialogPaging(); - } - - - - public static boolean moveTransitionAnimationLayer() { - - return FEATURE_FLAGS.moveTransitionAnimationLayer(); - } - - - - public static boolean msdlFeedback() { - - return FEATURE_FLAGS.msdlFeedback(); - } - - - - public static boolean multiuserWifiPickerTrackerSupport() { - - return FEATURE_FLAGS.multiuserWifiPickerTrackerSupport(); - } - - - + public static boolean newAodTransition() { - return FEATURE_FLAGS.newAodTransition(); } - - - + + public static boolean newTouchpadGesturesTutorial() { + return FEATURE_FLAGS.newTouchpadGesturesTutorial(); + } + public static boolean newVolumePanel() { - return FEATURE_FLAGS.newVolumePanel(); } - - - - public static boolean nonTouchscreenDevicesBypassFalsing() { - - return FEATURE_FLAGS.nonTouchscreenDevicesBypassFalsing(); - } - - - - public static boolean notesRoleQsTile() { - - return FEATURE_FLAGS.notesRoleQsTile(); - } - - - - public static boolean notificationAddXOnHoverToDismiss() { - - return FEATURE_FLAGS.notificationAddXOnHoverToDismiss(); - } - - - - public static boolean notificationAmbientSuppressionAfterInflation() { - - return FEATURE_FLAGS.notificationAmbientSuppressionAfterInflation(); - } - - - - public static boolean notificationAnimatedActionsTreatment() { - - return FEATURE_FLAGS.notificationAnimatedActionsTreatment(); - } - - - - public static boolean notificationAppearNonlinear() { - - return FEATURE_FLAGS.notificationAppearNonlinear(); - } - - - + public static boolean notificationAsyncGroupHeaderInflation() { - return FEATURE_FLAGS.notificationAsyncGroupHeaderInflation(); } - - - + public static boolean notificationAsyncHybridViewInflation() { - return FEATURE_FLAGS.notificationAsyncHybridViewInflation(); } - - - + public static boolean notificationAvalancheSuppression() { - return FEATURE_FLAGS.notificationAvalancheSuppression(); } - - - + public static boolean notificationAvalancheThrottleHun() { - return FEATURE_FLAGS.notificationAvalancheThrottleHun(); } - - - + public static boolean notificationBackgroundTintOptimization() { - return FEATURE_FLAGS.notificationBackgroundTintOptimization(); } - - - - public static boolean notificationBundleUi() { - - return FEATURE_FLAGS.notificationBundleUi(); - } - - - + public static boolean notificationColorUpdateLogger() { - return FEATURE_FLAGS.notificationColorUpdateLogger(); } - - - + public static boolean notificationContentAlphaOptimization() { - return FEATURE_FLAGS.notificationContentAlphaOptimization(); } - - - + public static boolean notificationFooterBackgroundTintOptimization() { - return FEATURE_FLAGS.notificationFooterBackgroundTintOptimization(); } - - - + + public static boolean notificationMediaManagerBackgroundExecution() { + return FEATURE_FLAGS.notificationMediaManagerBackgroundExecution(); + } + + public static boolean notificationMinimalismPrototype() { + return FEATURE_FLAGS.notificationMinimalismPrototype(); + } + public static boolean notificationOverExpansionClippingFix() { - return FEATURE_FLAGS.notificationOverExpansionClippingFix(); } - - - - public static boolean notificationReentrantDismiss() { - - return FEATURE_FLAGS.notificationReentrantDismiss(); + + public static boolean notificationPulsingFix() { + return FEATURE_FLAGS.notificationPulsingFix(); } - - - - public static boolean notificationRowAccessibilityExpanded() { - - return FEATURE_FLAGS.notificationRowAccessibilityExpanded(); - } - - - + public static boolean notificationRowContentBinderRefactor() { - return FEATURE_FLAGS.notificationRowContentBinderRefactor(); } - - - - public static boolean notificationRowTransparency() { - - return FEATURE_FLAGS.notificationRowTransparency(); - } - - - + public static boolean notificationRowUserContext() { - return FEATURE_FLAGS.notificationRowUserContext(); } - - - - public static boolean notificationShadeBlur() { - - return FEATURE_FLAGS.notificationShadeBlur(); - } - - - - public static boolean notificationShadeUiThread() { - - return FEATURE_FLAGS.notificationShadeUiThread(); - } - - - - public static boolean notificationSkipSilentUpdates() { - - return FEATURE_FLAGS.notificationSkipSilentUpdates(); - } - - - - public static boolean notificationTransparentHeaderFix() { - - return FEATURE_FLAGS.notificationTransparentHeaderFix(); - } - - - + public static boolean notificationViewFlipperPausingV2() { - return FEATURE_FLAGS.notificationViewFlipperPausingV2(); } - - - + public static boolean notificationsBackgroundIcons() { - return FEATURE_FLAGS.notificationsBackgroundIcons(); } - - - - public static boolean notificationsFooterVisibilityFix() { - - return FEATURE_FLAGS.notificationsFooterVisibilityFix(); + + public static boolean notificationsFooterViewRefactor() { + return FEATURE_FLAGS.notificationsFooterViewRefactor(); } - - - + + public static boolean notificationsHeadsUpRefactor() { + return FEATURE_FLAGS.notificationsHeadsUpRefactor(); + } + public static boolean notificationsHideOnDisplaySwitch() { - return FEATURE_FLAGS.notificationsHideOnDisplaySwitch(); } - - - - public static boolean notificationsHunSharedAnimationValues() { - - return FEATURE_FLAGS.notificationsHunSharedAnimationValues(); - } - - - + public static boolean notificationsIconContainerRefactor() { - return FEATURE_FLAGS.notificationsIconContainerRefactor(); } - - - - public static boolean notificationsLaunchRadius() { - - return FEATURE_FLAGS.notificationsLaunchRadius(); + + public static boolean notificationsImprovedHunAnimation() { + return FEATURE_FLAGS.notificationsImprovedHunAnimation(); } - - - + public static boolean notificationsLiveDataStoreRefactor() { - return FEATURE_FLAGS.notificationsLiveDataStoreRefactor(); } - - - - public static boolean notificationsPinnedHunInShade() { - - return FEATURE_FLAGS.notificationsPinnedHunInShade(); - } - - - - public static boolean notificationsRedesignFooterView() { - - return FEATURE_FLAGS.notificationsRedesignFooterView(); - } - - - - public static boolean notificationsRedesignGuts() { - - return FEATURE_FLAGS.notificationsRedesignGuts(); - } - - - - public static boolean notifyPasswordTextViewUserActivityInBackground() { - - return FEATURE_FLAGS.notifyPasswordTextViewUserActivityInBackground(); - } - - - + public static boolean notifyPowerManagerUserActivityBackground() { - return FEATURE_FLAGS.notifyPowerManagerUserActivityBackground(); } - - - - public static boolean onlyShowMediaStreamSliderInSingleVolumeMode() { - - return FEATURE_FLAGS.onlyShowMediaStreamSliderInSingleVolumeMode(); - } - - - - public static boolean outputSwitcherRedesign() { - - return FEATURE_FLAGS.outputSwitcherRedesign(); - } - - - - public static boolean overrideSuppressOverlayCondition() { - - return FEATURE_FLAGS.overrideSuppressOverlayCondition(); - } - - - - public static boolean permissionHelperInlineUiRichOngoing() { - - return FEATURE_FLAGS.permissionHelperInlineUiRichOngoing(); - } - - - - public static boolean permissionHelperUiRichOngoing() { - - return FEATURE_FLAGS.permissionHelperUiRichOngoing(); - } - - - - public static boolean physicalNotificationMovement() { - - return FEATURE_FLAGS.physicalNotificationMovement(); - } - - - + public static boolean pinInputFieldStyledFocusState() { - return FEATURE_FLAGS.pinInputFieldStyledFocusState(); } - - - + + public static boolean predictiveBackAnimateBouncer() { + return FEATURE_FLAGS.predictiveBackAnimateBouncer(); + } + + public static boolean predictiveBackAnimateDialogs() { + return FEATURE_FLAGS.predictiveBackAnimateDialogs(); + } + public static boolean predictiveBackAnimateShade() { - return FEATURE_FLAGS.predictiveBackAnimateShade(); } - - - - public static boolean predictiveBackDelayWmTransition() { - - return FEATURE_FLAGS.predictiveBackDelayWmTransition(); + + public static boolean predictiveBackSysui() { + return FEATURE_FLAGS.predictiveBackSysui(); } - - - + public static boolean priorityPeopleSection() { - return FEATURE_FLAGS.priorityPeopleSection(); } - - - - public static boolean promoteNotificationsAutomatically() { - - return FEATURE_FLAGS.promoteNotificationsAutomatically(); + + public static boolean privacyDotUnfoldWrongCornerFix() { + return FEATURE_FLAGS.privacyDotUnfoldWrongCornerFix(); } - - - + + public static boolean pssAppSelectorAbruptExitFix() { + return FEATURE_FLAGS.pssAppSelectorAbruptExitFix(); + } + public static boolean pssAppSelectorRecentsSplitScreen() { - return FEATURE_FLAGS.pssAppSelectorRecentsSplitScreen(); } - - - + public static boolean pssTaskSwitcher() { - return FEATURE_FLAGS.pssTaskSwitcher(); } - - - + public static boolean qsCustomTileClickGuaranteedBugFix() { - return FEATURE_FLAGS.qsCustomTileClickGuaranteedBugFix(); } - - - + + public static boolean qsNewPipeline() { + return FEATURE_FLAGS.qsNewPipeline(); + } + public static boolean qsNewTiles() { - return FEATURE_FLAGS.qsNewTiles(); } - - - + public static boolean qsNewTilesFuture() { - return FEATURE_FLAGS.qsNewTilesFuture(); } - - - - public static boolean qsQuickRebindActiveTiles() { - - return FEATURE_FLAGS.qsQuickRebindActiveTiles(); - } - - - - public static boolean qsRegisterSettingObserverOnBgThread() { - - return FEATURE_FLAGS.qsRegisterSettingObserverOnBgThread(); - } - - - - public static boolean qsTileDetailedView() { - - return FEATURE_FLAGS.qsTileDetailedView(); - } - - - + public static boolean qsTileFocusState() { - return FEATURE_FLAGS.qsTileFocusState(); } - - - + public static boolean qsUiRefactor() { - return FEATURE_FLAGS.qsUiRefactor(); } - - - - public static boolean qsUiRefactorComposeFragment() { - - return FEATURE_FLAGS.qsUiRefactorComposeFragment(); + + public static boolean quickSettingsVisualHapticsLongpress() { + return FEATURE_FLAGS.quickSettingsVisualHapticsLongpress(); } - - - + public static boolean recordIssueQsTile() { - return FEATURE_FLAGS.recordIssueQsTile(); } - - - - public static boolean redesignMagnificationWindowSize() { - - return FEATURE_FLAGS.redesignMagnificationWindowSize(); - } - - - + public static boolean refactorGetCurrentUser() { - return FEATURE_FLAGS.refactorGetCurrentUser(); } - - - + public static boolean registerBatteryControllerReceiversInCorestartable() { - return FEATURE_FLAGS.registerBatteryControllerReceiversInCorestartable(); } - - - - public static boolean registerContentObserversAsync() { - - return FEATURE_FLAGS.registerContentObserversAsync(); - } - - - + public static boolean registerNewWalletCardInBackground() { - return FEATURE_FLAGS.registerNewWalletCardInBackground(); } - - - + public static boolean registerWallpaperNotifierBackground() { - return FEATURE_FLAGS.registerWallpaperNotifierBackground(); } - - - - public static boolean relockWithPowerButtonImmediately() { - - return FEATURE_FLAGS.relockWithPowerButtonImmediately(); + + public static boolean registerZenModeContentObserverBackground() { + return FEATURE_FLAGS.registerZenModeContentObserverBackground(); } - - - + public static boolean removeDreamOverlayHideOnTouch() { - return FEATURE_FLAGS.removeDreamOverlayHideOnTouch(); } - - - - public static boolean removeUpdateListenerInQsIconViewImpl() { - - return FEATURE_FLAGS.removeUpdateListenerInQsIconViewImpl(); - } - - - + public static boolean restToUnlock() { - return FEATURE_FLAGS.restToUnlock(); } - - - + public static boolean restartDreamOnUnocclude() { - return FEATURE_FLAGS.restartDreamOnUnocclude(); } - - - + public static boolean revampedBouncerMessages() { - return FEATURE_FLAGS.revampedBouncerMessages(); } - - - + public static boolean runFingerprintDetectOnDismissibleKeyguard() { - return FEATURE_FLAGS.runFingerprintDetectOnDismissibleKeyguard(); } - - - + public static boolean saveAndRestoreMagnificationSettingsButtons() { - return FEATURE_FLAGS.saveAndRestoreMagnificationSettingsButtons(); } - - - + public static boolean sceneContainer() { - return FEATURE_FLAGS.sceneContainer(); } - - - + public static boolean screenshareNotificationHidingBugFix() { - return FEATURE_FLAGS.screenshareNotificationHidingBugFix(); } - - - + public static boolean screenshotActionDismissSystemWindows() { - return FEATURE_FLAGS.screenshotActionDismissSystemWindows(); } - - - - public static boolean screenshotMultidisplayFocusChange() { - - return FEATURE_FLAGS.screenshotMultidisplayFocusChange(); + + public static boolean screenshotPrivateProfileAccessibilityAnnouncementFix() { + return FEATURE_FLAGS.screenshotPrivateProfileAccessibilityAnnouncementFix(); } - - - - public static boolean screenshotPolicySplitAndDesktopMode() { - - return FEATURE_FLAGS.screenshotPolicySplitAndDesktopMode(); + + public static boolean screenshotPrivateProfileBehaviorFix() { + return FEATURE_FLAGS.screenshotPrivateProfileBehaviorFix(); } - - - + public static boolean screenshotScrollCropViewCrashFix() { - return FEATURE_FLAGS.screenshotScrollCropViewCrashFix(); } - - - - public static boolean screenshotUiControllerRefactor() { - - return FEATURE_FLAGS.screenshotUiControllerRefactor(); + + public static boolean screenshotShelfUi2() { + return FEATURE_FLAGS.screenshotShelfUi2(); } - - - - public static boolean secondaryUserWidgetHost() { - - return FEATURE_FLAGS.secondaryUserWidgetHost(); + + public static boolean shadeCollapseActivityLaunchFix() { + return FEATURE_FLAGS.shadeCollapseActivityLaunchFix(); } - - - - public static boolean settingsExtRegisterContentObserverOnBgThread() { - - return FEATURE_FLAGS.settingsExtRegisterContentObserverOnBgThread(); - } - - - - public static boolean shadeExpandsOnStatusBarLongPress() { - - return FEATURE_FLAGS.shadeExpandsOnStatusBarLongPress(); - } - - - - public static boolean shadeHeaderFontUpdate() { - - return FEATURE_FLAGS.shadeHeaderFontUpdate(); - } - - - - public static boolean shadeLaunchAccessibility() { - - return FEATURE_FLAGS.shadeLaunchAccessibility(); - } - - - - public static boolean shadeWindowGoesAround() { - - return FEATURE_FLAGS.shadeWindowGoesAround(); - } - - - + public static boolean shaderlibLoadingEffectRefactor() { - return FEATURE_FLAGS.shaderlibLoadingEffectRefactor(); } - - - - public static boolean shortcutHelperKeyGlyph() { - - return FEATURE_FLAGS.shortcutHelperKeyGlyph(); - } - - - - public static boolean showAudioSharingSliderInVolumePanel() { - - return FEATURE_FLAGS.showAudioSharingSliderInVolumePanel(); - } - - - - public static boolean showClipboardIndication() { - - return FEATURE_FLAGS.showClipboardIndication(); - } - - - - public static boolean showLockedByYourWatchKeyguardIndicator() { - - return FEATURE_FLAGS.showLockedByYourWatchKeyguardIndicator(); - } - - - - public static boolean showToastWhenAppControlBrightness() { - - return FEATURE_FLAGS.showToastWhenAppControlBrightness(); - } - - - - public static boolean simPinBouncerReset() { - - return FEATURE_FLAGS.simPinBouncerReset(); - } - - - - public static boolean simPinRaceConditionOnRestart() { - - return FEATURE_FLAGS.simPinRaceConditionOnRestart(); - } - - - - public static boolean simPinUseSlotId() { - - return FEATURE_FLAGS.simPinUseSlotId(); - } - - - - public static boolean skipHideSensitiveNotifAnimation() { - - return FEATURE_FLAGS.skipHideSensitiveNotifAnimation(); - } - - - + public static boolean sliceBroadcastRelayInBackground() { - return FEATURE_FLAGS.sliceBroadcastRelayInBackground(); } - - - + public static boolean sliceManagerBinderCallBackground() { - return FEATURE_FLAGS.sliceManagerBinderCallBackground(); } - - - + public static boolean smartspaceLockscreenViewmodel() { - return FEATURE_FLAGS.smartspaceLockscreenViewmodel(); } - - - + public static boolean smartspaceRelocateToBottom() { - return FEATURE_FLAGS.smartspaceRelocateToBottom(); } - - - - public static boolean smartspaceRemoteviewsRenderingFix() { - - return FEATURE_FLAGS.smartspaceRemoteviewsRenderingFix(); + + public static boolean smartspaceRemoteviewsRendering() { + return FEATURE_FLAGS.smartspaceRemoteviewsRendering(); } - - - - public static boolean smartspaceSwipeEventLoggingFix() { - - return FEATURE_FLAGS.smartspaceSwipeEventLoggingFix(); - } - - - - public static boolean smartspaceViewpager2() { - - return FEATURE_FLAGS.smartspaceViewpager2(); - } - - - - public static boolean sounddoseCustomization() { - - return FEATURE_FLAGS.sounddoseCustomization(); - } - - - - public static boolean spatialModelAppPushback() { - - return FEATURE_FLAGS.spatialModelAppPushback(); - } - - - - public static boolean stabilizeHeadsUpGroupV2() { - - return FEATURE_FLAGS.stabilizeHeadsUpGroupV2(); - } - - - - public static boolean statusBarAlwaysCheckUnderlyingNetworks() { - - return FEATURE_FLAGS.statusBarAlwaysCheckUnderlyingNetworks(); - } - - - - public static boolean statusBarAutoStartScreenRecordChip() { - - return FEATURE_FLAGS.statusBarAutoStartScreenRecordChip(); - } - - - - public static boolean statusBarChipsModernization() { - - return FEATURE_FLAGS.statusBarChipsModernization(); - } - - - - public static boolean statusBarChipsReturnAnimations() { - - return FEATURE_FLAGS.statusBarChipsReturnAnimations(); - } - - - - public static boolean statusBarFontUpdates() { - - return FEATURE_FLAGS.statusBarFontUpdates(); - } - - - - public static boolean statusBarMobileIconKairos() { - - return FEATURE_FLAGS.statusBarMobileIconKairos(); - } - - - + public static boolean statusBarMonochromeIconsFix() { - return FEATURE_FLAGS.statusBarMonochromeIconsFix(); } - - - - public static boolean statusBarNoHunBehavior() { - - return FEATURE_FLAGS.statusBarNoHunBehavior(); + + public static boolean statusBarScreenSharingChips() { + return FEATURE_FLAGS.statusBarScreenSharingChips(); } - - - - public static boolean statusBarPopupChips() { - - return FEATURE_FLAGS.statusBarPopupChips(); - } - - - - public static boolean statusBarRootModernization() { - - return FEATURE_FLAGS.statusBarRootModernization(); - } - - - - public static boolean statusBarShowAudioOnlyProjectionChip() { - - return FEATURE_FLAGS.statusBarShowAudioOnlyProjectionChip(); - } - - - - public static boolean statusBarSignalPolicyRefactor() { - - return FEATURE_FLAGS.statusBarSignalPolicyRefactor(); - } - - - - public static boolean statusBarSignalPolicyRefactorEthernet() { - - return FEATURE_FLAGS.statusBarSignalPolicyRefactorEthernet(); - } - - - + public static boolean statusBarStaticInoutIndicators() { - return FEATURE_FLAGS.statusBarStaticInoutIndicators(); } - - - - public static boolean statusBarStopUpdatingWindowHeight() { - - return FEATURE_FLAGS.statusBarStopUpdatingWindowHeight(); - } - - - - public static boolean statusBarSwipeOverChip() { - - return FEATURE_FLAGS.statusBarSwipeOverChip(); - } - - - - public static boolean statusBarSwitchToSpnFromDataSpn() { - - return FEATURE_FLAGS.statusBarSwitchToSpnFromDataSpn(); - } - - - - public static boolean statusBarUiThread() { - - return FEATURE_FLAGS.statusBarUiThread(); - } - - - - public static boolean statusBarWindowNoCustomTouch() { - - return FEATURE_FLAGS.statusBarWindowNoCustomTouch(); - } - - - - public static boolean stoppableFgsSystemApp() { - - return FEATURE_FLAGS.stoppableFgsSystemApp(); - } - - - + public static boolean switchUserOnBg() { - return FEATURE_FLAGS.switchUserOnBg(); } - - - + public static boolean sysuiTeamfood() { - return FEATURE_FLAGS.sysuiTeamfood(); } - - - + public static boolean themeOverlayControllerWakefulnessDeprecation() { - return FEATURE_FLAGS.themeOverlayControllerWakefulnessDeprecation(); } - - - - public static boolean transitionRaceCondition() { - - return FEATURE_FLAGS.transitionRaceCondition(); - } - - - + public static boolean translucentOccludingActivityFix() { - return FEATURE_FLAGS.translucentOccludingActivityFix(); } - - - - public static boolean tvGlobalActionsFocus() { - - return FEATURE_FLAGS.tvGlobalActionsFocus(); + + public static boolean truncatedStatusBarIconsFix() { + return FEATURE_FLAGS.truncatedStatusBarIconsFix(); } - - - + public static boolean udfpsViewPerformance() { - return FEATURE_FLAGS.udfpsViewPerformance(); } - - - + public static boolean unfoldAnimationBackgroundProgress() { - return FEATURE_FLAGS.unfoldAnimationBackgroundProgress(); } - - - - public static boolean unfoldLatencyTrackingFix() { - - return FEATURE_FLAGS.unfoldLatencyTrackingFix(); - } - - - - public static boolean updateCornerRadiusOnDisplayChanged() { - - return FEATURE_FLAGS.updateCornerRadiusOnDisplayChanged(); - } - - - + public static boolean updateUserSwitcherBackground() { - return FEATURE_FLAGS.updateUserSwitcherBackground(); } - - - - public static boolean updateWindowMagnifierBottomBoundary() { - - return FEATURE_FLAGS.updateWindowMagnifierBottomBoundary(); + + public static boolean validateKeyboardShortcutHelperIconUri() { + return FEATURE_FLAGS.validateKeyboardShortcutHelperIconUri(); } - - - - public static boolean useAadProxSensor() { - - return FEATURE_FLAGS.useAadProxSensor(); - } - - - - public static boolean useNotifInflationThreadForFooter() { - - return FEATURE_FLAGS.useNotifInflationThreadForFooter(); - } - - - - public static boolean useNotifInflationThreadForRow() { - - return FEATURE_FLAGS.useNotifInflationThreadForRow(); - } - - - - public static boolean useTransitionsForKeyguardOccluded() { - - return FEATURE_FLAGS.useTransitionsForKeyguardOccluded(); - } - - - - public static boolean useVolumeController() { - - return FEATURE_FLAGS.useVolumeController(); - } - - - - public static boolean userAwareSettingsRepositories() { - - return FEATURE_FLAGS.userAwareSettingsRepositories(); - } - - - - public static boolean userEncryptedSource() { - - return FEATURE_FLAGS.userEncryptedSource(); - } - - - - public static boolean userSwitcherAddSignOutOption() { - - return FEATURE_FLAGS.userSwitcherAddSignOutOption(); - } - - - + public static boolean visualInterruptionsRefactor() { - return FEATURE_FLAGS.visualInterruptionsRefactor(); } - - - public static boolean volumeRedesign() { - - return FEATURE_FLAGS.volumeRedesign(); - } - private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl(); } diff --git a/flags/src/com/android/systemui/shared/CustomFeatureFlags.java b/flags/src/com/android/systemui/shared/CustomFeatureFlags.java index 18f1f2b21d..b301340b4a 100644 --- a/flags/src/com/android/systemui/shared/CustomFeatureFlags.java +++ b/flags/src/com/android/systemui/shared/CustomFeatureFlags.java @@ -1,13 +1,13 @@ package com.android.systemui.shared; // TODO(b/303773055): Remove the annotation after access issue is resolved. - import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.function.BiPredicate; import java.util.function.Predicate; + /** @hide */ public class CustomFeatureFlags implements FeatureFlags { @@ -17,182 +17,56 @@ public class CustomFeatureFlags implements FeatureFlags { mGetValueImpl = getValueImpl; } @Override - - public boolean ambientAod() { - return getValue(Flags.FLAG_AMBIENT_AOD, - FeatureFlags::ambientAod); - } - - @Override - + public boolean bouncerAreaExclusion() { return getValue(Flags.FLAG_BOUNCER_AREA_EXCLUSION, - FeatureFlags::bouncerAreaExclusion); + FeatureFlags::bouncerAreaExclusion); } @Override - - public boolean clockReactiveSmartspaceLayout() { - return getValue(Flags.FLAG_CLOCK_REACTIVE_SMARTSPACE_LAYOUT, - FeatureFlags::clockReactiveSmartspaceLayout); - } - - @Override - - public boolean clockReactiveVariants() { - return getValue(Flags.FLAG_CLOCK_REACTIVE_VARIANTS, - FeatureFlags::clockReactiveVariants); - } - - @Override - - public boolean cursorHotCorner() { - return getValue(Flags.FLAG_CURSOR_HOT_CORNER, - FeatureFlags::cursorHotCorner); - } - - @Override - + public boolean enableHomeDelay() { return getValue(Flags.FLAG_ENABLE_HOME_DELAY, - FeatureFlags::enableHomeDelay); + FeatureFlags::enableHomeDelay); } @Override - - public boolean enableLppSqueezeEffect() { - return getValue(Flags.FLAG_ENABLE_LPP_SQUEEZE_EFFECT, - FeatureFlags::enableLppSqueezeEffect); - } - - @Override - + public boolean exampleSharedFlag() { return getValue(Flags.FLAG_EXAMPLE_SHARED_FLAG, - FeatureFlags::exampleSharedFlag); + FeatureFlags::exampleSharedFlag); } @Override - - public boolean extendedWallpaperEffects() { - return getValue(Flags.FLAG_EXTENDED_WALLPAPER_EFFECTS, - FeatureFlags::extendedWallpaperEffects); - } - - @Override - - public boolean lockscreenCustomClocks() { - return getValue(Flags.FLAG_LOCKSCREEN_CUSTOM_CLOCKS, - FeatureFlags::lockscreenCustomClocks); - } - - @Override - - public boolean newCustomizationPickerUi() { - return getValue(Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI, - FeatureFlags::newCustomizationPickerUi); - } - - @Override - - public boolean newTouchpadGesturesTutorial() { - return getValue(Flags.FLAG_NEW_TOUCHPAD_GESTURES_TUTORIAL, - FeatureFlags::newTouchpadGesturesTutorial); - } - - @Override - + public boolean returnAnimationFrameworkLibrary() { return getValue(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY, - FeatureFlags::returnAnimationFrameworkLibrary); + FeatureFlags::returnAnimationFrameworkLibrary); } @Override - - public boolean returnAnimationFrameworkLongLived() { - return getValue(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED, - FeatureFlags::returnAnimationFrameworkLongLived); - } - - @Override - - public boolean screenshotContextUrl() { - return getValue(Flags.FLAG_SCREENSHOT_CONTEXT_URL, - FeatureFlags::screenshotContextUrl); - } - - @Override - + public boolean shadeAllowBackGesture() { return getValue(Flags.FLAG_SHADE_ALLOW_BACK_GESTURE, - FeatureFlags::shadeAllowBackGesture); + FeatureFlags::shadeAllowBackGesture); } @Override - + public boolean sidefpsControllerRefactor() { return getValue(Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR, - FeatureFlags::sidefpsControllerRefactor); - } - - @Override - - public boolean smartspaceRemoteviewsIntentHandler() { - return getValue(Flags.FLAG_SMARTSPACE_REMOTEVIEWS_INTENT_HANDLER, - FeatureFlags::smartspaceRemoteviewsIntentHandler); - } - - @Override - - public boolean smartspaceSportsCardBackground() { - return getValue(Flags.FLAG_SMARTSPACE_SPORTS_CARD_BACKGROUND, - FeatureFlags::smartspaceSportsCardBackground); - } - - @Override - - public boolean smartspaceUiUpdate() { - return getValue(Flags.FLAG_SMARTSPACE_UI_UPDATE, - FeatureFlags::smartspaceUiUpdate); - } - - @Override - - public boolean smartspaceUiUpdateResources() { - return getValue(Flags.FLAG_SMARTSPACE_UI_UPDATE_RESOURCES, - FeatureFlags::smartspaceUiUpdateResources); - } - - @Override - - public boolean statusBarConnectedDisplays() { - return getValue(Flags.FLAG_STATUS_BAR_CONNECTED_DISPLAYS, - FeatureFlags::statusBarConnectedDisplays); - } - - @Override - - public boolean threeButtonCornerSwipe() { - return getValue(Flags.FLAG_THREE_BUTTON_CORNER_SWIPE, - FeatureFlags::threeButtonCornerSwipe); - } - - @Override - - public boolean usePreferredImageEditor() { - return getValue(Flags.FLAG_USE_PREFERRED_IMAGE_EDITOR, - FeatureFlags::usePreferredImageEditor); + FeatureFlags::sidefpsControllerRefactor); } public boolean isFlagReadOnlyOptimized(String flagName) { if (mReadOnlyFlagsSet.contains(flagName) && - isOptimizationEnabled()) { - return true; + isOptimizationEnabled()) { + return true; } return false; } - + private boolean isOptimizationEnabled() { return false; } @@ -203,60 +77,18 @@ public class CustomFeatureFlags implements FeatureFlags { public List getFlagNames() { return Arrays.asList( - Flags.FLAG_AMBIENT_AOD, - Flags.FLAG_BOUNCER_AREA_EXCLUSION, - Flags.FLAG_CLOCK_REACTIVE_SMARTSPACE_LAYOUT, - Flags.FLAG_CLOCK_REACTIVE_VARIANTS, - Flags.FLAG_CURSOR_HOT_CORNER, - Flags.FLAG_ENABLE_HOME_DELAY, - Flags.FLAG_ENABLE_LPP_SQUEEZE_EFFECT, - Flags.FLAG_EXAMPLE_SHARED_FLAG, - Flags.FLAG_EXTENDED_WALLPAPER_EFFECTS, - Flags.FLAG_LOCKSCREEN_CUSTOM_CLOCKS, - Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI, - Flags.FLAG_NEW_TOUCHPAD_GESTURES_TUTORIAL, - Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY, - Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED, - Flags.FLAG_SCREENSHOT_CONTEXT_URL, - Flags.FLAG_SHADE_ALLOW_BACK_GESTURE, - Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR, - Flags.FLAG_SMARTSPACE_REMOTEVIEWS_INTENT_HANDLER, - Flags.FLAG_SMARTSPACE_SPORTS_CARD_BACKGROUND, - Flags.FLAG_SMARTSPACE_UI_UPDATE, - Flags.FLAG_SMARTSPACE_UI_UPDATE_RESOURCES, - Flags.FLAG_STATUS_BAR_CONNECTED_DISPLAYS, - Flags.FLAG_THREE_BUTTON_CORNER_SWIPE, - Flags.FLAG_USE_PREFERRED_IMAGE_EDITOR + Flags.FLAG_BOUNCER_AREA_EXCLUSION, + Flags.FLAG_ENABLE_HOME_DELAY, + Flags.FLAG_EXAMPLE_SHARED_FLAG, + Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY, + Flags.FLAG_SHADE_ALLOW_BACK_GESTURE, + Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR ); } private Set mReadOnlyFlagsSet = new HashSet<>( - Arrays.asList( - Flags.FLAG_AMBIENT_AOD, - Flags.FLAG_BOUNCER_AREA_EXCLUSION, - Flags.FLAG_CLOCK_REACTIVE_SMARTSPACE_LAYOUT, - Flags.FLAG_CLOCK_REACTIVE_VARIANTS, - Flags.FLAG_CURSOR_HOT_CORNER, - Flags.FLAG_ENABLE_HOME_DELAY, - Flags.FLAG_ENABLE_LPP_SQUEEZE_EFFECT, - Flags.FLAG_EXAMPLE_SHARED_FLAG, - Flags.FLAG_EXTENDED_WALLPAPER_EFFECTS, - Flags.FLAG_LOCKSCREEN_CUSTOM_CLOCKS, - Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI, - Flags.FLAG_NEW_TOUCHPAD_GESTURES_TUTORIAL, - Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY, - Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED, - Flags.FLAG_SCREENSHOT_CONTEXT_URL, - Flags.FLAG_SHADE_ALLOW_BACK_GESTURE, - Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR, - Flags.FLAG_SMARTSPACE_REMOTEVIEWS_INTENT_HANDLER, - Flags.FLAG_SMARTSPACE_SPORTS_CARD_BACKGROUND, - Flags.FLAG_SMARTSPACE_UI_UPDATE, - Flags.FLAG_SMARTSPACE_UI_UPDATE_RESOURCES, - Flags.FLAG_STATUS_BAR_CONNECTED_DISPLAYS, - Flags.FLAG_THREE_BUTTON_CORNER_SWIPE, - Flags.FLAG_USE_PREFERRED_IMAGE_EDITOR, - "" - ) + Arrays.asList( + "" + ) ); } diff --git a/flags/src/com/android/systemui/shared/FakeFeatureFlagsImpl.java b/flags/src/com/android/systemui/shared/FakeFeatureFlagsImpl.java index 226db51996..c223248bbf 100644 --- a/flags/src/com/android/systemui/shared/FakeFeatureFlagsImpl.java +++ b/flags/src/com/android/systemui/shared/FakeFeatureFlagsImpl.java @@ -3,6 +3,7 @@ package com.android.systemui.shared; import java.util.HashMap; import java.util.Map; import java.util.function.Predicate; + /** @hide */ public class FakeFeatureFlagsImpl extends CustomFeatureFlags { private final Map mFlagMap = new HashMap<>(); diff --git a/flags/src/com/android/systemui/shared/FeatureFlags.java b/flags/src/com/android/systemui/shared/FeatureFlags.java index 7f14ce4149..e9fe9c7144 100644 --- a/flags/src/com/android/systemui/shared/FeatureFlags.java +++ b/flags/src/com/android/systemui/shared/FeatureFlags.java @@ -1,103 +1,24 @@ package com.android.systemui.shared; // TODO(b/303773055): Remove the annotation after access issue is resolved. - /** @hide */ public interface FeatureFlags { - - - - boolean ambientAod(); - - - + + boolean bouncerAreaExclusion(); - - - - boolean clockReactiveSmartspaceLayout(); - - - - boolean clockReactiveVariants(); - - - - boolean cursorHotCorner(); - - - + + boolean enableHomeDelay(); - - - - boolean enableLppSqueezeEffect(); - - - + + boolean exampleSharedFlag(); - - - - boolean extendedWallpaperEffects(); - - - - boolean lockscreenCustomClocks(); - - - - boolean newCustomizationPickerUi(); - - - - boolean newTouchpadGesturesTutorial(); - - - + + boolean returnAnimationFrameworkLibrary(); - - - - boolean returnAnimationFrameworkLongLived(); - - - - boolean screenshotContextUrl(); - - - + + boolean shadeAllowBackGesture(); - - - + + boolean sidefpsControllerRefactor(); - - - - boolean smartspaceRemoteviewsIntentHandler(); - - - - boolean smartspaceSportsCardBackground(); - - - - boolean smartspaceUiUpdate(); - - - - boolean smartspaceUiUpdateResources(); - - - - boolean statusBarConnectedDisplays(); - - - - boolean threeButtonCornerSwipe(); - - - - boolean usePreferredImageEditor(); } diff --git a/flags/src/com/android/systemui/shared/FeatureFlagsImpl.java b/flags/src/com/android/systemui/shared/FeatureFlagsImpl.java index 27cc82cf05..39a1f0ee3a 100644 --- a/flags/src/com/android/systemui/shared/FeatureFlagsImpl.java +++ b/flags/src/com/android/systemui/shared/FeatureFlagsImpl.java @@ -1,174 +1,195 @@ package com.android.systemui.shared; // TODO(b/303773055): Remove the annotation after access issue is resolved. +import com.android.quickstep.util.DeviceConfigHelper; + +import java.nio.file.Files; +import java.nio.file.Paths; /** @hide */ public final class FeatureFlagsImpl implements FeatureFlags { - @Override + private static final boolean isReadFromNew = Files.exists(Paths.get("/metadata/aconfig/boot/enable_only_new_storage")); + private static volatile boolean isCached = false; + private static volatile boolean biometrics_framework_is_cached = false; + private static volatile boolean systemui_is_cached = false; + private static boolean bouncerAreaExclusion = true; + private static boolean enableHomeDelay = false; + private static boolean exampleSharedFlag = false; + private static boolean returnAnimationFrameworkLibrary = false; + private static boolean shadeAllowBackGesture = false; + private static boolean sidefpsControllerRefactor = true; - public boolean ambientAod() { - return false; + private void init() { + boolean foundPackage = true; + + sidefpsControllerRefactor = foundPackage; + + + bouncerAreaExclusion = foundPackage; + + + enableHomeDelay = foundPackage; + + + exampleSharedFlag = foundPackage; + + + returnAnimationFrameworkLibrary = foundPackage ; + + + shadeAllowBackGesture = foundPackage; + + isCached = true; + } + + + + + private void load_overrides_biometrics_framework() { + try { + var properties = DeviceConfigHelper.Companion.getPrefs(); + sidefpsControllerRefactor = + properties.getBoolean(Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR, true); + } catch (NullPointerException e) { + throw new RuntimeException( + "Cannot read value from namespace biometrics_framework " + + "from DeviceConfig. It could be that the code using flag " + + "executed before SettingsProvider initialization. Please use " + + "fixed read-only flag by adding is_fixed_read_only: true in " + + "flag declaration.", + e + ); + } + biometrics_framework_is_cached = true; + } + + private void load_overrides_systemui() { + try { + var properties = DeviceConfigHelper.Companion.getPrefs(); + bouncerAreaExclusion = + properties.getBoolean(Flags.FLAG_BOUNCER_AREA_EXCLUSION, true); + enableHomeDelay = + properties.getBoolean(Flags.FLAG_ENABLE_HOME_DELAY, false); + exampleSharedFlag = + properties.getBoolean(Flags.FLAG_EXAMPLE_SHARED_FLAG, false); + returnAnimationFrameworkLibrary = + properties.getBoolean(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY, false); + shadeAllowBackGesture = + properties.getBoolean(Flags.FLAG_SHADE_ALLOW_BACK_GESTURE, false); + } catch (NullPointerException e) { + throw new RuntimeException( + "Cannot read value from namespace systemui " + + "from DeviceConfig. It could be that the code using flag " + + "executed before SettingsProvider initialization. Please use " + + "fixed read-only flag by adding is_fixed_read_only: true in " + + "flag declaration.", + e + ); + } + systemui_is_cached = true; } @Override - - + + public boolean bouncerAreaExclusion() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return bouncerAreaExclusion; + } @Override - - - public boolean clockReactiveSmartspaceLayout() { - return false; - } - - @Override - - - public boolean clockReactiveVariants() { - return false; - } - - @Override - - - public boolean cursorHotCorner() { - return false; - } - - @Override - - + + public boolean enableHomeDelay() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return enableHomeDelay; + } @Override - - - public boolean enableLppSqueezeEffect() { - return false; - } - - @Override - - + + public boolean exampleSharedFlag() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return exampleSharedFlag; + } @Override - - - public boolean extendedWallpaperEffects() { - return false; - } - - @Override - - - public boolean lockscreenCustomClocks() { - return false; - } - - @Override - - - public boolean newCustomizationPickerUi() { - return false; - } - - @Override - - - public boolean newTouchpadGesturesTutorial() { - return true; - } - - @Override - - + + public boolean returnAnimationFrameworkLibrary() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return returnAnimationFrameworkLibrary; + } @Override - - - public boolean returnAnimationFrameworkLongLived() { - return true; - } - - @Override - - - public boolean screenshotContextUrl() { - return false; - } - - @Override - - + + public boolean shadeAllowBackGesture() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return shadeAllowBackGesture; + } @Override - - + + public boolean sidefpsControllerRefactor() { - return true; - } + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!biometrics_framework_is_cached) { + load_overrides_biometrics_framework(); + } + } + return sidefpsControllerRefactor; - @Override - - - public boolean smartspaceRemoteviewsIntentHandler() { - return true; - } - - @Override - - - public boolean smartspaceSportsCardBackground() { - return false; - } - - @Override - - - public boolean smartspaceUiUpdate() { - return false; - } - - @Override - - - public boolean smartspaceUiUpdateResources() { - return false; - } - - @Override - - - public boolean statusBarConnectedDisplays() { - return false; - } - - @Override - - - public boolean threeButtonCornerSwipe() { - return false; - } - - @Override - - - public boolean usePreferredImageEditor() { - return false; } } + diff --git a/flags/src/com/android/systemui/shared/Flags.java b/flags/src/com/android/systemui/shared/Flags.java index 26a3ca3f47..4b817be4ef 100644 --- a/flags/src/com/android/systemui/shared/Flags.java +++ b/flags/src/com/android/systemui/shared/Flags.java @@ -1,226 +1,50 @@ package com.android.systemui.shared; // TODO(b/303773055): Remove the annotation after access issue is resolved. - - /** @hide */ public final class Flags { - /** @hide */ - public static final String FLAG_AMBIENT_AOD = "com.android.systemui.shared.ambient_aod"; /** @hide */ public static final String FLAG_BOUNCER_AREA_EXCLUSION = "com.android.systemui.shared.bouncer_area_exclusion"; /** @hide */ - public static final String FLAG_CLOCK_REACTIVE_SMARTSPACE_LAYOUT = "com.android.systemui.shared.clock_reactive_smartspace_layout"; - /** @hide */ - public static final String FLAG_CLOCK_REACTIVE_VARIANTS = "com.android.systemui.shared.clock_reactive_variants"; - /** @hide */ - public static final String FLAG_CURSOR_HOT_CORNER = "com.android.systemui.shared.cursor_hot_corner"; - /** @hide */ public static final String FLAG_ENABLE_HOME_DELAY = "com.android.systemui.shared.enable_home_delay"; /** @hide */ - public static final String FLAG_ENABLE_LPP_SQUEEZE_EFFECT = "com.android.systemui.shared.enable_lpp_squeeze_effect"; - /** @hide */ public static final String FLAG_EXAMPLE_SHARED_FLAG = "com.android.systemui.shared.example_shared_flag"; /** @hide */ - public static final String FLAG_EXTENDED_WALLPAPER_EFFECTS = "com.android.systemui.shared.extended_wallpaper_effects"; - /** @hide */ - public static final String FLAG_LOCKSCREEN_CUSTOM_CLOCKS = "com.android.systemui.shared.lockscreen_custom_clocks"; - /** @hide */ - public static final String FLAG_NEW_CUSTOMIZATION_PICKER_UI = "com.android.systemui.shared.new_customization_picker_ui"; - /** @hide */ - public static final String FLAG_NEW_TOUCHPAD_GESTURES_TUTORIAL = "com.android.systemui.shared.new_touchpad_gestures_tutorial"; - /** @hide */ public static final String FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY = "com.android.systemui.shared.return_animation_framework_library"; /** @hide */ - public static final String FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED = "com.android.systemui.shared.return_animation_framework_long_lived"; - /** @hide */ - public static final String FLAG_SCREENSHOT_CONTEXT_URL = "com.android.systemui.shared.screenshot_context_url"; - /** @hide */ public static final String FLAG_SHADE_ALLOW_BACK_GESTURE = "com.android.systemui.shared.shade_allow_back_gesture"; /** @hide */ public static final String FLAG_SIDEFPS_CONTROLLER_REFACTOR = "com.android.systemui.shared.sidefps_controller_refactor"; - /** @hide */ - public static final String FLAG_SMARTSPACE_REMOTEVIEWS_INTENT_HANDLER = "com.android.systemui.shared.smartspace_remoteviews_intent_handler"; - /** @hide */ - public static final String FLAG_SMARTSPACE_SPORTS_CARD_BACKGROUND = "com.android.systemui.shared.smartspace_sports_card_background"; - /** @hide */ - public static final String FLAG_SMARTSPACE_UI_UPDATE = "com.android.systemui.shared.smartspace_ui_update"; - /** @hide */ - public static final String FLAG_SMARTSPACE_UI_UPDATE_RESOURCES = "com.android.systemui.shared.smartspace_ui_update_resources"; - /** @hide */ - public static final String FLAG_STATUS_BAR_CONNECTED_DISPLAYS = "com.android.systemui.shared.status_bar_connected_displays"; - /** @hide */ - public static final String FLAG_THREE_BUTTON_CORNER_SWIPE = "com.android.systemui.shared.three_button_corner_swipe"; - /** @hide */ - public static final String FLAG_USE_PREFERRED_IMAGE_EDITOR = "com.android.systemui.shared.use_preferred_image_editor"; - - - - public static boolean ambientAod() { - - return FEATURE_FLAGS.ambientAod(); - } - - - + + public static boolean bouncerAreaExclusion() { - return FEATURE_FLAGS.bouncerAreaExclusion(); } - - - - public static boolean clockReactiveSmartspaceLayout() { - - return FEATURE_FLAGS.clockReactiveSmartspaceLayout(); - } - - - - public static boolean clockReactiveVariants() { - - return FEATURE_FLAGS.clockReactiveVariants(); - } - - - - public static boolean cursorHotCorner() { - - return FEATURE_FLAGS.cursorHotCorner(); - } - - - + + public static boolean enableHomeDelay() { - return FEATURE_FLAGS.enableHomeDelay(); } - - - - public static boolean enableLppSqueezeEffect() { - - return FEATURE_FLAGS.enableLppSqueezeEffect(); - } - - - + + public static boolean exampleSharedFlag() { - return FEATURE_FLAGS.exampleSharedFlag(); } - - - - public static boolean extendedWallpaperEffects() { - - return FEATURE_FLAGS.extendedWallpaperEffects(); - } - - - - public static boolean lockscreenCustomClocks() { - - return FEATURE_FLAGS.lockscreenCustomClocks(); - } - - - - public static boolean newCustomizationPickerUi() { - - return FEATURE_FLAGS.newCustomizationPickerUi(); - } - - - - public static boolean newTouchpadGesturesTutorial() { - - return FEATURE_FLAGS.newTouchpadGesturesTutorial(); - } - - - + + public static boolean returnAnimationFrameworkLibrary() { - return FEATURE_FLAGS.returnAnimationFrameworkLibrary(); } - - - - public static boolean returnAnimationFrameworkLongLived() { - - return FEATURE_FLAGS.returnAnimationFrameworkLongLived(); - } - - - - public static boolean screenshotContextUrl() { - - return FEATURE_FLAGS.screenshotContextUrl(); - } - - - + + public static boolean shadeAllowBackGesture() { - return FEATURE_FLAGS.shadeAllowBackGesture(); } - - - + + public static boolean sidefpsControllerRefactor() { - return FEATURE_FLAGS.sidefpsControllerRefactor(); } - - - public static boolean smartspaceRemoteviewsIntentHandler() { - - return FEATURE_FLAGS.smartspaceRemoteviewsIntentHandler(); - } - - - - public static boolean smartspaceSportsCardBackground() { - - return FEATURE_FLAGS.smartspaceSportsCardBackground(); - } - - - - public static boolean smartspaceUiUpdate() { - - return FEATURE_FLAGS.smartspaceUiUpdate(); - } - - - - public static boolean smartspaceUiUpdateResources() { - - return FEATURE_FLAGS.smartspaceUiUpdateResources(); - } - - - - public static boolean statusBarConnectedDisplays() { - - return FEATURE_FLAGS.statusBarConnectedDisplays(); - } - - - - public static boolean threeButtonCornerSwipe() { - - return FEATURE_FLAGS.threeButtonCornerSwipe(); - } - - - - public static boolean usePreferredImageEditor() { - - return FEATURE_FLAGS.usePreferredImageEditor(); - } - private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl(); } diff --git a/flags/src/com/android/window/flags2/CustomFeatureFlags.java b/flags/src/com/android/window/flags2/CustomFeatureFlags.java index 3591e0549a..c52b5a4bb9 100644 --- a/flags/src/com/android/window/flags2/CustomFeatureFlags.java +++ b/flags/src/com/android/window/flags2/CustomFeatureFlags.java @@ -7,6 +7,7 @@ import java.util.List; import java.util.Set; import java.util.function.BiPredicate; import java.util.function.Predicate; + /** @hide */ public class CustomFeatureFlags implements FeatureFlags { @@ -16,1870 +17,742 @@ public class CustomFeatureFlags implements FeatureFlags { mGetValueImpl = getValueImpl; } @Override - - public boolean actionModeEdgeToEdge() { - return getValue(Flags.FLAG_ACTION_MODE_EDGE_TO_EDGE, - FeatureFlags::actionModeEdgeToEdge); - } - - @Override - + public boolean activityEmbeddingAnimationCustomizationFlag() { return getValue(Flags.FLAG_ACTIVITY_EMBEDDING_ANIMATION_CUSTOMIZATION_FLAG, - FeatureFlags::activityEmbeddingAnimationCustomizationFlag); + FeatureFlags::activityEmbeddingAnimationCustomizationFlag); } @Override - - public boolean activityEmbeddingDelayTaskFragmentFinishForActivityLaunch() { - return getValue(Flags.FLAG_ACTIVITY_EMBEDDING_DELAY_TASK_FRAGMENT_FINISH_FOR_ACTIVITY_LAUNCH, - FeatureFlags::activityEmbeddingDelayTaskFragmentFinishForActivityLaunch); - } - - @Override - + public boolean activityEmbeddingInteractiveDividerFlag() { return getValue(Flags.FLAG_ACTIVITY_EMBEDDING_INTERACTIVE_DIVIDER_FLAG, - FeatureFlags::activityEmbeddingInteractiveDividerFlag); + FeatureFlags::activityEmbeddingInteractiveDividerFlag); } @Override - - public boolean activityEmbeddingMetrics() { - return getValue(Flags.FLAG_ACTIVITY_EMBEDDING_METRICS, - FeatureFlags::activityEmbeddingMetrics); + + public boolean activityEmbeddingOverlayPresentationFlag() { + return getValue(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG, + FeatureFlags::activityEmbeddingOverlayPresentationFlag); } @Override - - public boolean activityEmbeddingSupportForConnectedDisplays() { - return getValue(Flags.FLAG_ACTIVITY_EMBEDDING_SUPPORT_FOR_CONNECTED_DISPLAYS, - FeatureFlags::activityEmbeddingSupportForConnectedDisplays); + + public boolean activitySnapshotByDefault() { + return getValue(Flags.FLAG_ACTIVITY_SNAPSHOT_BY_DEFAULT, + FeatureFlags::activitySnapshotByDefault); } @Override + + public boolean activityWindowInfoFlag() { + return getValue(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG, + FeatureFlags::activityWindowInfoFlag); + } + @Override + public boolean allowDisableActivityRecordInputSink() { return getValue(Flags.FLAG_ALLOW_DISABLE_ACTIVITY_RECORD_INPUT_SINK, - FeatureFlags::allowDisableActivityRecordInputSink); + FeatureFlags::allowDisableActivityRecordInputSink); } @Override - + public boolean allowHideScmButton() { return getValue(Flags.FLAG_ALLOW_HIDE_SCM_BUTTON, - FeatureFlags::allowHideScmButton); + FeatureFlags::allowHideScmButton); } @Override - + public boolean allowsScreenSizeDecoupledFromStatusBarAndCutout() { return getValue(Flags.FLAG_ALLOWS_SCREEN_SIZE_DECOUPLED_FROM_STATUS_BAR_AND_CUTOUT, - FeatureFlags::allowsScreenSizeDecoupledFromStatusBarAndCutout); + FeatureFlags::allowsScreenSizeDecoupledFromStatusBarAndCutout); } @Override + + public boolean alwaysDeferTransitionWhenApplyWct() { + return getValue(Flags.FLAG_ALWAYS_DEFER_TRANSITION_WHEN_APPLY_WCT, + FeatureFlags::alwaysDeferTransitionWhenApplyWct); + } + @Override + public boolean alwaysDrawMagnificationFullscreenBorder() { return getValue(Flags.FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER, - FeatureFlags::alwaysDrawMagnificationFullscreenBorder); + FeatureFlags::alwaysDrawMagnificationFullscreenBorder); } @Override - + public boolean alwaysUpdateWallpaperPermission() { return getValue(Flags.FLAG_ALWAYS_UPDATE_WALLPAPER_PERMISSION, - FeatureFlags::alwaysUpdateWallpaperPermission); + FeatureFlags::alwaysUpdateWallpaperPermission); } @Override - - public boolean aodTransition() { - return getValue(Flags.FLAG_AOD_TRANSITION, - FeatureFlags::aodTransition); - } - - @Override - - public boolean appCompatAsyncRelayout() { - return getValue(Flags.FLAG_APP_COMPAT_ASYNC_RELAYOUT, - FeatureFlags::appCompatAsyncRelayout); - } - - @Override - + public boolean appCompatPropertiesApi() { return getValue(Flags.FLAG_APP_COMPAT_PROPERTIES_API, - FeatureFlags::appCompatPropertiesApi); + FeatureFlags::appCompatPropertiesApi); } @Override - + public boolean appCompatRefactoring() { return getValue(Flags.FLAG_APP_COMPAT_REFACTORING, - FeatureFlags::appCompatRefactoring); + FeatureFlags::appCompatRefactoring); } @Override - - public boolean appCompatUiFramework() { - return getValue(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK, - FeatureFlags::appCompatUiFramework); - } - - @Override - - public boolean appHandleNoRelayoutOnExclusionChange() { - return getValue(Flags.FLAG_APP_HANDLE_NO_RELAYOUT_ON_EXCLUSION_CHANGE, - FeatureFlags::appHandleNoRelayoutOnExclusionChange); - } - - @Override - - public boolean applyLifecycleOnPipChange() { - return getValue(Flags.FLAG_APPLY_LIFECYCLE_ON_PIP_CHANGE, - FeatureFlags::applyLifecycleOnPipChange); - } - - @Override - - public boolean avoidRebindingIntentionallyDisconnectedWallpaper() { - return getValue(Flags.FLAG_AVOID_REBINDING_INTENTIONALLY_DISCONNECTED_WALLPAPER, - FeatureFlags::avoidRebindingIntentionallyDisconnectedWallpaper); - } - - @Override - - public boolean backupAndRestoreForUserAspectRatioSettings() { - return getValue(Flags.FLAG_BACKUP_AND_RESTORE_FOR_USER_ASPECT_RATIO_SETTINGS, - FeatureFlags::backupAndRestoreForUserAspectRatioSettings); - } - - @Override - - public boolean balAdditionalLogging() { - return getValue(Flags.FLAG_BAL_ADDITIONAL_LOGGING, - FeatureFlags::balAdditionalLogging); - } - - @Override - - public boolean balAdditionalStartModes() { - return getValue(Flags.FLAG_BAL_ADDITIONAL_START_MODES, - FeatureFlags::balAdditionalStartModes); - } - - @Override - - public boolean balClearAllowlistDuration() { - return getValue(Flags.FLAG_BAL_CLEAR_ALLOWLIST_DURATION, - FeatureFlags::balClearAllowlistDuration); - } - - @Override - + public boolean balDontBringExistingBackgroundTaskStackToFg() { return getValue(Flags.FLAG_BAL_DONT_BRING_EXISTING_BACKGROUND_TASK_STACK_TO_FG, - FeatureFlags::balDontBringExistingBackgroundTaskStackToFg); + FeatureFlags::balDontBringExistingBackgroundTaskStackToFg); } @Override - + public boolean balImproveRealCallerVisibilityCheck() { return getValue(Flags.FLAG_BAL_IMPROVE_REAL_CALLER_VISIBILITY_CHECK, - FeatureFlags::balImproveRealCallerVisibilityCheck); + FeatureFlags::balImproveRealCallerVisibilityCheck); } @Override - + public boolean balImprovedMetrics() { return getValue(Flags.FLAG_BAL_IMPROVED_METRICS, - FeatureFlags::balImprovedMetrics); + FeatureFlags::balImprovedMetrics); } @Override - - public boolean balReduceGracePeriod() { - return getValue(Flags.FLAG_BAL_REDUCE_GRACE_PERIOD, - FeatureFlags::balReduceGracePeriod); - } - - @Override - + public boolean balRequireOptInByPendingIntentCreator() { return getValue(Flags.FLAG_BAL_REQUIRE_OPT_IN_BY_PENDING_INTENT_CREATOR, - FeatureFlags::balRequireOptInByPendingIntentCreator); + FeatureFlags::balRequireOptInByPendingIntentCreator); } @Override + + public boolean balRequireOptInSameUid() { + return getValue(Flags.FLAG_BAL_REQUIRE_OPT_IN_SAME_UID, + FeatureFlags::balRequireOptInSameUid); + } + @Override + public boolean balRespectAppSwitchStateWhenCheckBoundByForegroundUid() { return getValue(Flags.FLAG_BAL_RESPECT_APP_SWITCH_STATE_WHEN_CHECK_BOUND_BY_FOREGROUND_UID, - FeatureFlags::balRespectAppSwitchStateWhenCheckBoundByForegroundUid); + FeatureFlags::balRespectAppSwitchStateWhenCheckBoundByForegroundUid); } @Override - - public boolean balSendIntentWithOptions() { - return getValue(Flags.FLAG_BAL_SEND_INTENT_WITH_OPTIONS, - FeatureFlags::balSendIntentWithOptions); + + public boolean balShowToasts() { + return getValue(Flags.FLAG_BAL_SHOW_TOASTS, + FeatureFlags::balShowToasts); } @Override - + public boolean balShowToastsBlocked() { return getValue(Flags.FLAG_BAL_SHOW_TOASTS_BLOCKED, - FeatureFlags::balShowToastsBlocked); + FeatureFlags::balShowToastsBlocked); } @Override - - public boolean balStrictModeGracePeriod() { - return getValue(Flags.FLAG_BAL_STRICT_MODE_GRACE_PERIOD, - FeatureFlags::balStrictModeGracePeriod); + + public boolean blastSyncNotificationShadeOnDisplaySwitch() { + return getValue(Flags.FLAG_BLAST_SYNC_NOTIFICATION_SHADE_ON_DISPLAY_SWITCH, + FeatureFlags::blastSyncNotificationShadeOnDisplaySwitch); } @Override - - public boolean balStrictModeRo() { - return getValue(Flags.FLAG_BAL_STRICT_MODE_RO, - FeatureFlags::balStrictModeRo); + + public boolean bundleClientTransactionFlag() { + return getValue(Flags.FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG, + FeatureFlags::bundleClientTransactionFlag); } @Override - - public boolean betterSupportNonMatchParentActivity() { - return getValue(Flags.FLAG_BETTER_SUPPORT_NON_MATCH_PARENT_ACTIVITY, - FeatureFlags::betterSupportNonMatchParentActivity); - } - - @Override - - public boolean cacheWindowStyle() { - return getValue(Flags.FLAG_CACHE_WINDOW_STYLE, - FeatureFlags::cacheWindowStyle); - } - - @Override - + public boolean cameraCompatForFreeform() { return getValue(Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM, - FeatureFlags::cameraCompatForFreeform); + FeatureFlags::cameraCompatForFreeform); } @Override - - public boolean cameraCompatFullscreenPickSameTaskActivity() { - return getValue(Flags.FLAG_CAMERA_COMPAT_FULLSCREEN_PICK_SAME_TASK_ACTIVITY, - FeatureFlags::cameraCompatFullscreenPickSameTaskActivity); - } - - @Override - - public boolean checkDisabledSnapshotsInTaskPersister() { - return getValue(Flags.FLAG_CHECK_DISABLED_SNAPSHOTS_IN_TASK_PERSISTER, - FeatureFlags::checkDisabledSnapshotsInTaskPersister); - } - - @Override - - public boolean cleanupDispatchPendingTransactionsRemoteException() { - return getValue(Flags.FLAG_CLEANUP_DISPATCH_PENDING_TRANSACTIONS_REMOTE_EXCEPTION, - FeatureFlags::cleanupDispatchPendingTransactionsRemoteException); - } - - @Override - - public boolean clearSystemVibrator() { - return getValue(Flags.FLAG_CLEAR_SYSTEM_VIBRATOR, - FeatureFlags::clearSystemVibrator); - } - - @Override - + public boolean closeToSquareConfigIncludesStatusBar() { return getValue(Flags.FLAG_CLOSE_TO_SQUARE_CONFIG_INCLUDES_STATUS_BAR, - FeatureFlags::closeToSquareConfigIncludesStatusBar); + FeatureFlags::closeToSquareConfigIncludesStatusBar); } @Override - - public boolean condenseConfigurationChangeForSimpleMode() { - return getValue(Flags.FLAG_CONDENSE_CONFIGURATION_CHANGE_FOR_SIMPLE_MODE, - FeatureFlags::condenseConfigurationChangeForSimpleMode); - } - - @Override - + public boolean configurableFontScaleDefault() { return getValue(Flags.FLAG_CONFIGURABLE_FONT_SCALE_DEFAULT, - FeatureFlags::configurableFontScaleDefault); + FeatureFlags::configurableFontScaleDefault); } @Override - + public boolean coverDisplayOptIn() { return getValue(Flags.FLAG_COVER_DISPLAY_OPT_IN, - FeatureFlags::coverDisplayOptIn); + FeatureFlags::coverDisplayOptIn); } @Override + + public boolean deferDisplayUpdates() { + return getValue(Flags.FLAG_DEFER_DISPLAY_UPDATES, + FeatureFlags::deferDisplayUpdates); + } + @Override + public boolean delayNotificationToMagnificationWhenRecentsWindowToFrontTransition() { return getValue(Flags.FLAG_DELAY_NOTIFICATION_TO_MAGNIFICATION_WHEN_RECENTS_WINDOW_TO_FRONT_TRANSITION, - FeatureFlags::delayNotificationToMagnificationWhenRecentsWindowToFrontTransition); + FeatureFlags::delayNotificationToMagnificationWhenRecentsWindowToFrontTransition); } @Override - - public boolean delegateBackGestureToShell() { - return getValue(Flags.FLAG_DELEGATE_BACK_GESTURE_TO_SHELL, - FeatureFlags::delegateBackGestureToShell); - } - - @Override - + public boolean delegateUnhandledDrags() { return getValue(Flags.FLAG_DELEGATE_UNHANDLED_DRAGS, - FeatureFlags::delegateUnhandledDrags); + FeatureFlags::delegateUnhandledDrags); } @Override - + public boolean deleteCaptureDisplay() { return getValue(Flags.FLAG_DELETE_CAPTURE_DISPLAY, - FeatureFlags::deleteCaptureDisplay); + FeatureFlags::deleteCaptureDisplay); } @Override - + public boolean density390Api() { return getValue(Flags.FLAG_DENSITY_390_API, - FeatureFlags::density390Api); + FeatureFlags::density390Api); } @Override - - public boolean disableDesktopLaunchParamsOutsideDesktopBugFix() { - return getValue(Flags.FLAG_DISABLE_DESKTOP_LAUNCH_PARAMS_OUTSIDE_DESKTOP_BUG_FIX, - FeatureFlags::disableDesktopLaunchParamsOutsideDesktopBugFix); + + public boolean disableObjectPool() { + return getValue(Flags.FLAG_DISABLE_OBJECT_POOL, + FeatureFlags::disableObjectPool); } @Override - - public boolean disableNonResizableAppSnapResizing() { - return getValue(Flags.FLAG_DISABLE_NON_RESIZABLE_APP_SNAP_RESIZING, - FeatureFlags::disableNonResizableAppSnapResizing); + + public boolean disableThinLetterboxingPolicy() { + return getValue(Flags.FLAG_DISABLE_THIN_LETTERBOXING_POLICY, + FeatureFlags::disableThinLetterboxingPolicy); } @Override - - public boolean disableOptOutEdgeToEdge() { - return getValue(Flags.FLAG_DISABLE_OPT_OUT_EDGE_TO_EDGE, - FeatureFlags::disableOptOutEdgeToEdge); - } - - @Override - + public boolean doNotCheckIntersectionWhenNonMagnifiableWindowTransitions() { return getValue(Flags.FLAG_DO_NOT_CHECK_INTERSECTION_WHEN_NON_MAGNIFIABLE_WINDOW_TRANSITIONS, - FeatureFlags::doNotCheckIntersectionWhenNonMagnifiableWindowTransitions); + FeatureFlags::doNotCheckIntersectionWhenNonMagnifiableWindowTransitions); } @Override - - public boolean earlyLaunchHint() { - return getValue(Flags.FLAG_EARLY_LAUNCH_HINT, - FeatureFlags::earlyLaunchHint); + + public boolean drawSnapshotAspectRatioMatch() { + return getValue(Flags.FLAG_DRAW_SNAPSHOT_ASPECT_RATIO_MATCH, + FeatureFlags::drawSnapshotAspectRatioMatch); } @Override - + public boolean edgeToEdgeByDefault() { return getValue(Flags.FLAG_EDGE_TO_EDGE_BY_DEFAULT, - FeatureFlags::edgeToEdgeByDefault); + FeatureFlags::edgeToEdgeByDefault); } @Override - - public boolean enableAccessibleCustomHeaders() { - return getValue(Flags.FLAG_ENABLE_ACCESSIBLE_CUSTOM_HEADERS, - FeatureFlags::enableAccessibleCustomHeaders); + + public boolean embeddedActivityBackNavFlag() { + return getValue(Flags.FLAG_EMBEDDED_ACTIVITY_BACK_NAV_FLAG, + FeatureFlags::embeddedActivityBackNavFlag); } @Override - - public boolean enableActivityEmbeddingSupportForConnectedDisplays() { - return getValue(Flags.FLAG_ENABLE_ACTIVITY_EMBEDDING_SUPPORT_FOR_CONNECTED_DISPLAYS, - FeatureFlags::enableActivityEmbeddingSupportForConnectedDisplays); + + public boolean enableAdditionalWindowsAboveStatusBar() { + return getValue(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR, + FeatureFlags::enableAdditionalWindowsAboveStatusBar); } @Override - + public boolean enableAppHeaderWithTaskDensity() { return getValue(Flags.FLAG_ENABLE_APP_HEADER_WITH_TASK_DENSITY, - FeatureFlags::enableAppHeaderWithTaskDensity); + FeatureFlags::enableAppHeaderWithTaskDensity); } @Override - - public boolean enableBorderSettings() { - return getValue(Flags.FLAG_ENABLE_BORDER_SETTINGS, - FeatureFlags::enableBorderSettings); - } - - @Override - + public boolean enableBufferTransformHintFromDisplay() { return getValue(Flags.FLAG_ENABLE_BUFFER_TRANSFORM_HINT_FROM_DISPLAY, - FeatureFlags::enableBufferTransformHintFromDisplay); + FeatureFlags::enableBufferTransformHintFromDisplay); } @Override - - public boolean enableBugFixesForSecondaryDisplay() { - return getValue(Flags.FLAG_ENABLE_BUG_FIXES_FOR_SECONDARY_DISPLAY, - FeatureFlags::enableBugFixesForSecondaryDisplay); - } - - @Override - + public boolean enableCameraCompatForDesktopWindowing() { return getValue(Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING, - FeatureFlags::enableCameraCompatForDesktopWindowing); + FeatureFlags::enableCameraCompatForDesktopWindowing); } @Override - - public boolean enableCameraCompatForDesktopWindowingOptOut() { - return getValue(Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT, - FeatureFlags::enableCameraCompatForDesktopWindowingOptOut); - } - - @Override - - public boolean enableCameraCompatForDesktopWindowingOptOutApi() { - return getValue(Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT_API, - FeatureFlags::enableCameraCompatForDesktopWindowingOptOutApi); - } - - @Override - - public boolean enableCameraCompatTrackTaskAndAppBugfix() { - return getValue(Flags.FLAG_ENABLE_CAMERA_COMPAT_TRACK_TASK_AND_APP_BUGFIX, - FeatureFlags::enableCameraCompatTrackTaskAndAppBugfix); - } - - @Override - - public boolean enableCaptionCompatInsetConversion() { - return getValue(Flags.FLAG_ENABLE_CAPTION_COMPAT_INSET_CONVERSION, - FeatureFlags::enableCaptionCompatInsetConversion); - } - - @Override - - public boolean enableCaptionCompatInsetForceConsumption() { - return getValue(Flags.FLAG_ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION, - FeatureFlags::enableCaptionCompatInsetForceConsumption); - } - - @Override - - public boolean enableCaptionCompatInsetForceConsumptionAlways() { - return getValue(Flags.FLAG_ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS, - FeatureFlags::enableCaptionCompatInsetForceConsumptionAlways); - } - - @Override - - public boolean enableCascadingWindows() { - return getValue(Flags.FLAG_ENABLE_CASCADING_WINDOWS, - FeatureFlags::enableCascadingWindows); - } - - @Override - - public boolean enableCompatUiVisibilityStatus() { - return getValue(Flags.FLAG_ENABLE_COMPAT_UI_VISIBILITY_STATUS, - FeatureFlags::enableCompatUiVisibilityStatus); - } - - @Override - + public boolean enableCompatuiSysuiLauncher() { return getValue(Flags.FLAG_ENABLE_COMPATUI_SYSUI_LAUNCHER, - FeatureFlags::enableCompatuiSysuiLauncher); + FeatureFlags::enableCompatuiSysuiLauncher); } @Override - - public boolean enableConnectedDisplaysDnd() { - return getValue(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_DND, - FeatureFlags::enableConnectedDisplaysDnd); - } - - @Override - - public boolean enableConnectedDisplaysPip() { - return getValue(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_PIP, - FeatureFlags::enableConnectedDisplaysPip); - } - - @Override - - public boolean enableConnectedDisplaysWindowDrag() { - return getValue(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WINDOW_DRAG, - FeatureFlags::enableConnectedDisplaysWindowDrag); - } - - @Override - - public boolean enableDesktopAppHandleAnimation() { - return getValue(Flags.FLAG_ENABLE_DESKTOP_APP_HANDLE_ANIMATION, - FeatureFlags::enableDesktopAppHandleAnimation); - } - - @Override - - public boolean enableDesktopAppLaunchAlttabTransitions() { - return getValue(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_ALTTAB_TRANSITIONS, - FeatureFlags::enableDesktopAppLaunchAlttabTransitions); - } - - @Override - - public boolean enableDesktopAppLaunchAlttabTransitionsBugfix() { - return getValue(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_ALTTAB_TRANSITIONS_BUGFIX, - FeatureFlags::enableDesktopAppLaunchAlttabTransitionsBugfix); - } - - @Override - - public boolean enableDesktopAppLaunchTransitions() { - return getValue(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS, - FeatureFlags::enableDesktopAppLaunchTransitions); - } - - @Override - - public boolean enableDesktopAppLaunchTransitionsBugfix() { - return getValue(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX, - FeatureFlags::enableDesktopAppLaunchTransitionsBugfix); - } - - @Override - - public boolean enableDesktopCloseShortcutBugfix() { - return getValue(Flags.FLAG_ENABLE_DESKTOP_CLOSE_SHORTCUT_BUGFIX, - FeatureFlags::enableDesktopCloseShortcutBugfix); - } - - @Override - - public boolean enableDesktopCloseTaskAnimationInDtcBugfix() { - return getValue(Flags.FLAG_ENABLE_DESKTOP_CLOSE_TASK_ANIMATION_IN_DTC_BUGFIX, - FeatureFlags::enableDesktopCloseTaskAnimationInDtcBugfix); - } - - @Override - - public boolean enableDesktopImeBugfix() { - return getValue(Flags.FLAG_ENABLE_DESKTOP_IME_BUGFIX, - FeatureFlags::enableDesktopImeBugfix); - } - - @Override - - public boolean enableDesktopImmersiveDragBugfix() { - return getValue(Flags.FLAG_ENABLE_DESKTOP_IMMERSIVE_DRAG_BUGFIX, - FeatureFlags::enableDesktopImmersiveDragBugfix); - } - - @Override - - public boolean enableDesktopIndicatorInSeparateThreadBugfix() { - return getValue(Flags.FLAG_ENABLE_DESKTOP_INDICATOR_IN_SEPARATE_THREAD_BUGFIX, - FeatureFlags::enableDesktopIndicatorInSeparateThreadBugfix); - } - - @Override - - public boolean enableDesktopModeThroughDevOption() { - return getValue(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION, - FeatureFlags::enableDesktopModeThroughDevOption); - } - - @Override - - public boolean enableDesktopOpeningDeeplinkMinimizeAnimationBugfix() { - return getValue(Flags.FLAG_ENABLE_DESKTOP_OPENING_DEEPLINK_MINIMIZE_ANIMATION_BUGFIX, - FeatureFlags::enableDesktopOpeningDeeplinkMinimizeAnimationBugfix); - } - - @Override - - public boolean enableDesktopRecentsTransitionsCornersBugfix() { - return getValue(Flags.FLAG_ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX, - FeatureFlags::enableDesktopRecentsTransitionsCornersBugfix); - } - - @Override - - public boolean enableDesktopSwipeBackMinimizeAnimationBugfix() { - return getValue(Flags.FLAG_ENABLE_DESKTOP_SWIPE_BACK_MINIMIZE_ANIMATION_BUGFIX, - FeatureFlags::enableDesktopSwipeBackMinimizeAnimationBugfix); - } - - @Override - - public boolean enableDesktopSystemDialogsTransitions() { - return getValue(Flags.FLAG_ENABLE_DESKTOP_SYSTEM_DIALOGS_TRANSITIONS, - FeatureFlags::enableDesktopSystemDialogsTransitions); - } - - @Override - - public boolean enableDesktopTabTearingMinimizeAnimationBugfix() { - return getValue(Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX, - FeatureFlags::enableDesktopTabTearingMinimizeAnimationBugfix); - } - - @Override - - public boolean enableDesktopTaskbarOnFreeformDisplays() { - return getValue(Flags.FLAG_ENABLE_DESKTOP_TASKBAR_ON_FREEFORM_DISPLAYS, - FeatureFlags::enableDesktopTaskbarOnFreeformDisplays); - } - - @Override - - public boolean enableDesktopTrampolineCloseAnimationBugfix() { - return getValue(Flags.FLAG_ENABLE_DESKTOP_TRAMPOLINE_CLOSE_ANIMATION_BUGFIX, - FeatureFlags::enableDesktopTrampolineCloseAnimationBugfix); - } - - @Override - - public boolean enableDesktopWallpaperActivityForSystemUser() { - return getValue(Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER, - FeatureFlags::enableDesktopWallpaperActivityForSystemUser); - } - - @Override - - public boolean enableDesktopWindowingAppHandleEducation() { - return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION, - FeatureFlags::enableDesktopWindowingAppHandleEducation); - } - - @Override - - public boolean enableDesktopWindowingAppToWeb() { - return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB, - FeatureFlags::enableDesktopWindowingAppToWeb); - } - - @Override - - public boolean enableDesktopWindowingAppToWebEducation() { - return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB_EDUCATION, - FeatureFlags::enableDesktopWindowingAppToWebEducation); - } - - @Override - - public boolean enableDesktopWindowingAppToWebEducationIntegration() { - return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB_EDUCATION_INTEGRATION, - FeatureFlags::enableDesktopWindowingAppToWebEducationIntegration); - } - - @Override - - public boolean enableDesktopWindowingBackNavigation() { - return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION, - FeatureFlags::enableDesktopWindowingBackNavigation); - } - - @Override - - public boolean enableDesktopWindowingEnterTransitionBugfix() { - return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITION_BUGFIX, - FeatureFlags::enableDesktopWindowingEnterTransitionBugfix); - } - - @Override - - public boolean enableDesktopWindowingEnterTransitions() { - return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS, - FeatureFlags::enableDesktopWindowingEnterTransitions); - } - - @Override - - public boolean enableDesktopWindowingExitByMinimizeTransitionBugfix() { - return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_BY_MINIMIZE_TRANSITION_BUGFIX, - FeatureFlags::enableDesktopWindowingExitByMinimizeTransitionBugfix); - } - - @Override - - public boolean enableDesktopWindowingExitTransitions() { - return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS, - FeatureFlags::enableDesktopWindowingExitTransitions); - } - - @Override - - public boolean enableDesktopWindowingExitTransitionsBugfix() { - return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX, - FeatureFlags::enableDesktopWindowingExitTransitionsBugfix); - } - - @Override - - public boolean enableDesktopWindowingHsum() { - return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_HSUM, - FeatureFlags::enableDesktopWindowingHsum); - } - - @Override - + public boolean enableDesktopWindowingImmersiveHandleHiding() { return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_IMMERSIVE_HANDLE_HIDING, - FeatureFlags::enableDesktopWindowingImmersiveHandleHiding); + FeatureFlags::enableDesktopWindowingImmersiveHandleHiding); } @Override - + public boolean enableDesktopWindowingModalsPolicy() { return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY, - FeatureFlags::enableDesktopWindowingModalsPolicy); + FeatureFlags::enableDesktopWindowingModalsPolicy); } @Override - + public boolean enableDesktopWindowingMode() { return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE, - FeatureFlags::enableDesktopWindowingMode); + FeatureFlags::enableDesktopWindowingMode); } @Override - - public boolean enableDesktopWindowingMultiInstanceFeatures() { - return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES, - FeatureFlags::enableDesktopWindowingMultiInstanceFeatures); - } - - @Override - - public boolean enableDesktopWindowingPersistence() { - return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE, - FeatureFlags::enableDesktopWindowingPersistence); - } - - @Override - - public boolean enableDesktopWindowingPip() { - return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP, - FeatureFlags::enableDesktopWindowingPip); - } - - @Override - + public boolean enableDesktopWindowingQuickSwitch() { return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_QUICK_SWITCH, - FeatureFlags::enableDesktopWindowingQuickSwitch); + FeatureFlags::enableDesktopWindowingQuickSwitch); } @Override - - public boolean enableDesktopWindowingScvhCacheBugFix() { - return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_SCVH_CACHE_BUG_FIX, - FeatureFlags::enableDesktopWindowingScvhCacheBugFix); + + public boolean enableDesktopWindowingScvhCache() { + return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_SCVH_CACHE, + FeatureFlags::enableDesktopWindowingScvhCache); } @Override - + public boolean enableDesktopWindowingSizeConstraints() { return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_SIZE_CONSTRAINTS, - FeatureFlags::enableDesktopWindowingSizeConstraints); + FeatureFlags::enableDesktopWindowingSizeConstraints); } @Override - + public boolean enableDesktopWindowingTaskLimit() { return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TASK_LIMIT, - FeatureFlags::enableDesktopWindowingTaskLimit); + FeatureFlags::enableDesktopWindowingTaskLimit); } @Override - + public boolean enableDesktopWindowingTaskbarRunningApps() { return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS, - FeatureFlags::enableDesktopWindowingTaskbarRunningApps); + FeatureFlags::enableDesktopWindowingTaskbarRunningApps); } @Override - - public boolean enableDesktopWindowingTransitions() { - return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS, - FeatureFlags::enableDesktopWindowingTransitions); - } - - @Override - + public boolean enableDesktopWindowingWallpaperActivity() { return getValue(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, - FeatureFlags::enableDesktopWindowingWallpaperActivity); + FeatureFlags::enableDesktopWindowingWallpaperActivity); } @Override - - public boolean enableDeviceStateAutoRotateSettingLogging() { - return getValue(Flags.FLAG_ENABLE_DEVICE_STATE_AUTO_ROTATE_SETTING_LOGGING, - FeatureFlags::enableDeviceStateAutoRotateSettingLogging); + + public boolean enableScaledResizing() { + return getValue(Flags.FLAG_ENABLE_SCALED_RESIZING, + FeatureFlags::enableScaledResizing); } @Override - - public boolean enableDeviceStateAutoRotateSettingRefactor() { - return getValue(Flags.FLAG_ENABLE_DEVICE_STATE_AUTO_ROTATE_SETTING_REFACTOR, - FeatureFlags::enableDeviceStateAutoRotateSettingRefactor); - } - - @Override - - public boolean enableDisplayDisconnectInteraction() { - return getValue(Flags.FLAG_ENABLE_DISPLAY_DISCONNECT_INTERACTION, - FeatureFlags::enableDisplayDisconnectInteraction); - } - - @Override - - public boolean enableDisplayFocusInShellTransitions() { - return getValue(Flags.FLAG_ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS, - FeatureFlags::enableDisplayFocusInShellTransitions); - } - - @Override - - public boolean enableDisplayReconnectInteraction() { - return getValue(Flags.FLAG_ENABLE_DISPLAY_RECONNECT_INTERACTION, - FeatureFlags::enableDisplayReconnectInteraction); - } - - @Override - - public boolean enableDisplayWindowingModeSwitching() { - return getValue(Flags.FLAG_ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING, - FeatureFlags::enableDisplayWindowingModeSwitching); - } - - @Override - - public boolean enableDragResizeSetUpInBgThread() { - return getValue(Flags.FLAG_ENABLE_DRAG_RESIZE_SET_UP_IN_BG_THREAD, - FeatureFlags::enableDragResizeSetUpInBgThread); - } - - @Override - - public boolean enableDragToDesktopIncomingTransitionsBugfix() { - return getValue(Flags.FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX, - FeatureFlags::enableDragToDesktopIncomingTransitionsBugfix); - } - - @Override - - public boolean enableDragToMaximize() { - return getValue(Flags.FLAG_ENABLE_DRAG_TO_MAXIMIZE, - FeatureFlags::enableDragToMaximize); - } - - @Override - - public boolean enableDynamicRadiusComputationBugfix() { - return getValue(Flags.FLAG_ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX, - FeatureFlags::enableDynamicRadiusComputationBugfix); - } - - @Override - - public boolean enableFullScreenWindowOnRemovingSplitScreenStageBugfix() { - return getValue(Flags.FLAG_ENABLE_FULL_SCREEN_WINDOW_ON_REMOVING_SPLIT_SCREEN_STAGE_BUGFIX, - FeatureFlags::enableFullScreenWindowOnRemovingSplitScreenStageBugfix); - } - - @Override - - public boolean enableFullyImmersiveInDesktop() { - return getValue(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP, - FeatureFlags::enableFullyImmersiveInDesktop); - } - - @Override - - public boolean enableHandleInputFix() { - return getValue(Flags.FLAG_ENABLE_HANDLE_INPUT_FIX, - FeatureFlags::enableHandleInputFix); - } - - @Override - - public boolean enableHoldToDragAppHandle() { - return getValue(Flags.FLAG_ENABLE_HOLD_TO_DRAG_APP_HANDLE, - FeatureFlags::enableHoldToDragAppHandle); - } - - @Override - - public boolean enableInputLayerTransitionFix() { - return getValue(Flags.FLAG_ENABLE_INPUT_LAYER_TRANSITION_FIX, - FeatureFlags::enableInputLayerTransitionFix); - } - - @Override - - public boolean enableMinimizeButton() { - return getValue(Flags.FLAG_ENABLE_MINIMIZE_BUTTON, - FeatureFlags::enableMinimizeButton); - } - - @Override - - public boolean enableModalsFullscreenWithPermission() { - return getValue(Flags.FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PERMISSION, - FeatureFlags::enableModalsFullscreenWithPermission); - } - - @Override - - public boolean enableMoveToNextDisplayShortcut() { - return getValue(Flags.FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT, - FeatureFlags::enableMoveToNextDisplayShortcut); - } - - @Override - - public boolean enableMultiDisplaySplit() { - return getValue(Flags.FLAG_ENABLE_MULTI_DISPLAY_SPLIT, - FeatureFlags::enableMultiDisplaySplit); - } - - @Override - - public boolean enableMultidisplayTrackpadBackGesture() { - return getValue(Flags.FLAG_ENABLE_MULTIDISPLAY_TRACKPAD_BACK_GESTURE, - FeatureFlags::enableMultidisplayTrackpadBackGesture); - } - - @Override - - public boolean enableMultipleDesktopsBackend() { - return getValue(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, - FeatureFlags::enableMultipleDesktopsBackend); - } - - @Override - - public boolean enableMultipleDesktopsFrontend() { - return getValue(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_FRONTEND, - FeatureFlags::enableMultipleDesktopsFrontend); - } - - @Override - - public boolean enableNonDefaultDisplaySplit() { - return getValue(Flags.FLAG_ENABLE_NON_DEFAULT_DISPLAY_SPLIT, - FeatureFlags::enableNonDefaultDisplaySplit); - } - - @Override - - public boolean enableOpaqueBackgroundForTransparentWindows() { - return getValue(Flags.FLAG_ENABLE_OPAQUE_BACKGROUND_FOR_TRANSPARENT_WINDOWS, - FeatureFlags::enableOpaqueBackgroundForTransparentWindows); - } - - @Override - - public boolean enablePerDisplayDesktopWallpaperActivity() { - return getValue(Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY, - FeatureFlags::enablePerDisplayDesktopWallpaperActivity); - } - - @Override - - public boolean enablePerDisplayPackageContextCacheInStatusbarNotif() { - return getValue(Flags.FLAG_ENABLE_PER_DISPLAY_PACKAGE_CONTEXT_CACHE_IN_STATUSBAR_NOTIF, - FeatureFlags::enablePerDisplayPackageContextCacheInStatusbarNotif); - } - - @Override - - public boolean enablePersistingDisplaySizeForConnectedDisplays() { - return getValue(Flags.FLAG_ENABLE_PERSISTING_DISPLAY_SIZE_FOR_CONNECTED_DISPLAYS, - FeatureFlags::enablePersistingDisplaySizeForConnectedDisplays); - } - - @Override - - public boolean enablePresentationForConnectedDisplays() { - return getValue(Flags.FLAG_ENABLE_PRESENTATION_FOR_CONNECTED_DISPLAYS, - FeatureFlags::enablePresentationForConnectedDisplays); - } - - @Override - - public boolean enableProjectedDisplayDesktopMode() { - return getValue(Flags.FLAG_ENABLE_PROJECTED_DISPLAY_DESKTOP_MODE, - FeatureFlags::enableProjectedDisplayDesktopMode); - } - - @Override - - public boolean enableQuickswitchDesktopSplitBugfix() { - return getValue(Flags.FLAG_ENABLE_QUICKSWITCH_DESKTOP_SPLIT_BUGFIX, - FeatureFlags::enableQuickswitchDesktopSplitBugfix); - } - - @Override - - public boolean enableRequestFullscreenBugfix() { - return getValue(Flags.FLAG_ENABLE_REQUEST_FULLSCREEN_BUGFIX, - FeatureFlags::enableRequestFullscreenBugfix); - } - - @Override - - public boolean enableResizingMetrics() { - return getValue(Flags.FLAG_ENABLE_RESIZING_METRICS, - FeatureFlags::enableResizingMetrics); - } - - @Override - - public boolean enableRestartMenuForConnectedDisplays() { - return getValue(Flags.FLAG_ENABLE_RESTART_MENU_FOR_CONNECTED_DISPLAYS, - FeatureFlags::enableRestartMenuForConnectedDisplays); - } - - @Override - - public boolean enableRestoreToPreviousSizeFromDesktopImmersive() { - return getValue(Flags.FLAG_ENABLE_RESTORE_TO_PREVIOUS_SIZE_FROM_DESKTOP_IMMERSIVE, - FeatureFlags::enableRestoreToPreviousSizeFromDesktopImmersive); - } - - @Override - - public boolean enableShellInitialBoundsRegressionBugFix() { - return getValue(Flags.FLAG_ENABLE_SHELL_INITIAL_BOUNDS_REGRESSION_BUG_FIX, - FeatureFlags::enableShellInitialBoundsRegressionBugFix); - } - - @Override - - public boolean enableSizeCompatModeImprovementsForConnectedDisplays() { - return getValue(Flags.FLAG_ENABLE_SIZE_COMPAT_MODE_IMPROVEMENTS_FOR_CONNECTED_DISPLAYS, - FeatureFlags::enableSizeCompatModeImprovementsForConnectedDisplays); - } - - @Override - - public boolean enableStartLaunchTransitionFromTaskbarBugfix() { - return getValue(Flags.FLAG_ENABLE_START_LAUNCH_TRANSITION_FROM_TASKBAR_BUGFIX, - FeatureFlags::enableStartLaunchTransitionFromTaskbarBugfix); - } - - @Override - - public boolean enableTaskResizingKeyboardShortcuts() { - return getValue(Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS, - FeatureFlags::enableTaskResizingKeyboardShortcuts); - } - - @Override - + public boolean enableTaskStackObserverInShell() { return getValue(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL, - FeatureFlags::enableTaskStackObserverInShell); + FeatureFlags::enableTaskStackObserverInShell); } @Override - - public boolean enableTaskbarConnectedDisplays() { - return getValue(Flags.FLAG_ENABLE_TASKBAR_CONNECTED_DISPLAYS, - FeatureFlags::enableTaskbarConnectedDisplays); - } - - @Override - - public boolean enableTaskbarOverflow() { - return getValue(Flags.FLAG_ENABLE_TASKBAR_OVERFLOW, - FeatureFlags::enableTaskbarOverflow); - } - - @Override - - public boolean enableTaskbarRecentsLayoutTransition() { - return getValue(Flags.FLAG_ENABLE_TASKBAR_RECENTS_LAYOUT_TRANSITION, - FeatureFlags::enableTaskbarRecentsLayoutTransition); - } - - @Override - + public boolean enableThemedAppHeaders() { return getValue(Flags.FLAG_ENABLE_THEMED_APP_HEADERS, - FeatureFlags::enableThemedAppHeaders); + FeatureFlags::enableThemedAppHeaders); } @Override - - public boolean enableTileResizing() { - return getValue(Flags.FLAG_ENABLE_TILE_RESIZING, - FeatureFlags::enableTileResizing); - } - - @Override - - public boolean enableTopVisibleRootTaskPerUserTracking() { - return getValue(Flags.FLAG_ENABLE_TOP_VISIBLE_ROOT_TASK_PER_USER_TRACKING, - FeatureFlags::enableTopVisibleRootTaskPerUserTracking); - } - - @Override - - public boolean enableVisualIndicatorInTransitionBugfix() { - return getValue(Flags.FLAG_ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX, - FeatureFlags::enableVisualIndicatorInTransitionBugfix); - } - - @Override - - public boolean enableWindowContextResourcesUpdateOnConfigChange() { - return getValue(Flags.FLAG_ENABLE_WINDOW_CONTEXT_RESOURCES_UPDATE_ON_CONFIG_CHANGE, - FeatureFlags::enableWindowContextResourcesUpdateOnConfigChange); - } - - @Override - + public boolean enableWindowingDynamicInitialBounds() { return getValue(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS, - FeatureFlags::enableWindowingDynamicInitialBounds); + FeatureFlags::enableWindowingDynamicInitialBounds); } @Override - + public boolean enableWindowingEdgeDragResize() { return getValue(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE, - FeatureFlags::enableWindowingEdgeDragResize); + FeatureFlags::enableWindowingEdgeDragResize); } @Override - - public boolean enableWindowingScaledResizing() { - return getValue(Flags.FLAG_ENABLE_WINDOWING_SCALED_RESIZING, - FeatureFlags::enableWindowingScaledResizing); + + public boolean enableWmExtensionsForAllFlag() { + return getValue(Flags.FLAG_ENABLE_WM_EXTENSIONS_FOR_ALL_FLAG, + FeatureFlags::enableWmExtensionsForAllFlag); } @Override - - public boolean enableWindowingTransitionHandlersObservers() { - return getValue(Flags.FLAG_ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS, - FeatureFlags::enableWindowingTransitionHandlersObservers); - } - - @Override - + public boolean enforceEdgeToEdge() { return getValue(Flags.FLAG_ENFORCE_EDGE_TO_EDGE, - FeatureFlags::enforceEdgeToEdge); + FeatureFlags::enforceEdgeToEdge); } @Override - - public boolean ensureKeyguardDoesTransitionStarting() { - return getValue(Flags.FLAG_ENSURE_KEYGUARD_DOES_TRANSITION_STARTING, - FeatureFlags::ensureKeyguardDoesTransitionStarting); - } - - @Override - + public boolean ensureWallpaperInTransitions() { return getValue(Flags.FLAG_ENSURE_WALLPAPER_IN_TRANSITIONS, - FeatureFlags::ensureWallpaperInTransitions); + FeatureFlags::ensureWallpaperInTransitions); } @Override - - public boolean ensureWallpaperInWearTransitions() { - return getValue(Flags.FLAG_ENSURE_WALLPAPER_IN_WEAR_TRANSITIONS, - FeatureFlags::ensureWallpaperInWearTransitions); + + public boolean explicitRefreshRateHints() { + return getValue(Flags.FLAG_EXPLICIT_REFRESH_RATE_HINTS, + FeatureFlags::explicitRefreshRateHints); } @Override - - public boolean enterDesktopByDefaultOnFreeformDisplays() { - return getValue(Flags.FLAG_ENTER_DESKTOP_BY_DEFAULT_ON_FREEFORM_DISPLAYS, - FeatureFlags::enterDesktopByDefaultOnFreeformDisplays); - } - - @Override - - public boolean excludeCaptionFromAppBounds() { - return getValue(Flags.FLAG_EXCLUDE_CAPTION_FROM_APP_BOUNDS, - FeatureFlags::excludeCaptionFromAppBounds); - } - - @Override - - public boolean excludeDrawingAppThemeSnapshotFromLock() { - return getValue(Flags.FLAG_EXCLUDE_DRAWING_APP_THEME_SNAPSHOT_FROM_LOCK, - FeatureFlags::excludeDrawingAppThemeSnapshotFromLock); - } - - @Override - - public boolean excludeTaskFromRecents() { - return getValue(Flags.FLAG_EXCLUDE_TASK_FROM_RECENTS, - FeatureFlags::excludeTaskFromRecents); - } - - @Override - + public boolean fifoPriorityForMajorUiProcesses() { return getValue(Flags.FLAG_FIFO_PRIORITY_FOR_MAJOR_UI_PROCESSES, - FeatureFlags::fifoPriorityForMajorUiProcesses); + FeatureFlags::fifoPriorityForMajorUiProcesses); } @Override - - public boolean fixHideOverlayApi() { - return getValue(Flags.FLAG_FIX_HIDE_OVERLAY_API, - FeatureFlags::fixHideOverlayApi); + + public boolean fixNoContainerUpdateWithoutResize() { + return getValue(Flags.FLAG_FIX_NO_CONTAINER_UPDATE_WITHOUT_RESIZE, + FeatureFlags::fixNoContainerUpdateWithoutResize); } @Override - - public boolean fixLayoutExistingTask() { - return getValue(Flags.FLAG_FIX_LAYOUT_EXISTING_TASK, - FeatureFlags::fixLayoutExistingTask); + + public boolean fixPipRestoreToOverlay() { + return getValue(Flags.FLAG_FIX_PIP_RESTORE_TO_OVERLAY, + FeatureFlags::fixPipRestoreToOverlay); } @Override - - public boolean fixViewRootCallTrace() { - return getValue(Flags.FLAG_FIX_VIEW_ROOT_CALL_TRACE, - FeatureFlags::fixViewRootCallTrace); + + public boolean fullscreenDimFlag() { + return getValue(Flags.FLAG_FULLSCREEN_DIM_FLAG, + FeatureFlags::fullscreenDimFlag); } @Override - - public boolean forceCloseTopTransparentFullscreenTask() { - return getValue(Flags.FLAG_FORCE_CLOSE_TOP_TRANSPARENT_FULLSCREEN_TASK, - FeatureFlags::forceCloseTopTransparentFullscreenTask); - } - - @Override - - public boolean formFactorBasedDesktopFirstSwitch() { - return getValue(Flags.FLAG_FORM_FACTOR_BASED_DESKTOP_FIRST_SWITCH, - FeatureFlags::formFactorBasedDesktopFirstSwitch); - } - - @Override - + public boolean getDimmerOnClosing() { return getValue(Flags.FLAG_GET_DIMMER_ON_CLOSING, - FeatureFlags::getDimmerOnClosing); + FeatureFlags::getDimmerOnClosing); } @Override - - public boolean ignoreAspectRatioRestrictionsForResizeableFreeformActivities() { - return getValue(Flags.FLAG_IGNORE_ASPECT_RATIO_RESTRICTIONS_FOR_RESIZEABLE_FREEFORM_ACTIVITIES, - FeatureFlags::ignoreAspectRatioRestrictionsForResizeableFreeformActivities); + + public boolean immersiveAppRepositioning() { + return getValue(Flags.FLAG_IMMERSIVE_APP_REPOSITIONING, + FeatureFlags::immersiveAppRepositioning); } @Override - - public boolean ignoreCornerRadiusAndShadows() { - return getValue(Flags.FLAG_IGNORE_CORNER_RADIUS_AND_SHADOWS, - FeatureFlags::ignoreCornerRadiusAndShadows); + + public boolean insetsControlChangedItem() { + return getValue(Flags.FLAG_INSETS_CONTROL_CHANGED_ITEM, + FeatureFlags::insetsControlChangedItem); } @Override - - public boolean includeTopTransparentFullscreenTaskInDesktopHeuristic() { - return getValue(Flags.FLAG_INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC, - FeatureFlags::includeTopTransparentFullscreenTaskInDesktopHeuristic); + + public boolean insetsControlSeq() { + return getValue(Flags.FLAG_INSETS_CONTROL_SEQ, + FeatureFlags::insetsControlSeq); } @Override - - public boolean inheritTaskBoundsForTrampolineTaskLaunches() { - return getValue(Flags.FLAG_INHERIT_TASK_BOUNDS_FOR_TRAMPOLINE_TASK_LAUNCHES, - FeatureFlags::inheritTaskBoundsForTrampolineTaskLaunches); - } - - @Override - + public boolean insetsDecoupledConfiguration() { return getValue(Flags.FLAG_INSETS_DECOUPLED_CONFIGURATION, - FeatureFlags::insetsDecoupledConfiguration); + FeatureFlags::insetsDecoupledConfiguration); } @Override - - public boolean jankApi() { - return getValue(Flags.FLAG_JANK_API, - FeatureFlags::jankApi); + + public boolean introduceSmootherDimmer() { + return getValue(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER, + FeatureFlags::introduceSmootherDimmer); } @Override - - public boolean keepAppWindowHideWhileLocked() { - return getValue(Flags.FLAG_KEEP_APP_WINDOW_HIDE_WHILE_LOCKED, - FeatureFlags::keepAppWindowHideWhileLocked); + + public boolean keyguardAppearTransition() { + return getValue(Flags.FLAG_KEYGUARD_APPEAR_TRANSITION, + FeatureFlags::keyguardAppearTransition); } @Override - - public boolean keyboardShortcutsToSwitchDesks() { - return getValue(Flags.FLAG_KEYBOARD_SHORTCUTS_TO_SWITCH_DESKS, - FeatureFlags::keyboardShortcutsToSwitchDesks); - } - - @Override - - public boolean keyguardGoingAwayTimeout() { - return getValue(Flags.FLAG_KEYGUARD_GOING_AWAY_TIMEOUT, - FeatureFlags::keyguardGoingAwayTimeout); - } - - @Override - + public boolean letterboxBackgroundWallpaper() { return getValue(Flags.FLAG_LETTERBOX_BACKGROUND_WALLPAPER, - FeatureFlags::letterboxBackgroundWallpaper); + FeatureFlags::letterboxBackgroundWallpaper); } @Override - + public boolean movableCutoutConfiguration() { return getValue(Flags.FLAG_MOVABLE_CUTOUT_CONFIGURATION, - FeatureFlags::movableCutoutConfiguration); + FeatureFlags::movableCutoutConfiguration); } @Override - - public boolean moveToExternalDisplayShortcut() { - return getValue(Flags.FLAG_MOVE_TO_EXTERNAL_DISPLAY_SHORTCUT, - FeatureFlags::moveToExternalDisplayShortcut); + + public boolean moveAnimationOptionsToChange() { + return getValue(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE, + FeatureFlags::moveAnimationOptionsToChange); } @Override - + public boolean multiCrop() { return getValue(Flags.FLAG_MULTI_CROP, - FeatureFlags::multiCrop); + FeatureFlags::multiCrop); } @Override - + public boolean navBarTransparentByDefault() { return getValue(Flags.FLAG_NAV_BAR_TRANSPARENT_BY_DEFAULT, - FeatureFlags::navBarTransparentByDefault); + FeatureFlags::navBarTransparentByDefault); } @Override - - public boolean nestedTasksWithIndependentBounds() { - return getValue(Flags.FLAG_NESTED_TASKS_WITH_INDEPENDENT_BOUNDS, - FeatureFlags::nestedTasksWithIndependentBounds); - } - - @Override - + public boolean noConsecutiveVisibilityEvents() { return getValue(Flags.FLAG_NO_CONSECUTIVE_VISIBILITY_EVENTS, - FeatureFlags::noConsecutiveVisibilityEvents); + FeatureFlags::noConsecutiveVisibilityEvents); } @Override - - public boolean noDuplicateSurfaceDestroyedEvents() { - return getValue(Flags.FLAG_NO_DUPLICATE_SURFACE_DESTROYED_EVENTS, - FeatureFlags::noDuplicateSurfaceDestroyedEvents); - } - - @Override - + public boolean noVisibilityEventOnDisplayStateChange() { return getValue(Flags.FLAG_NO_VISIBILITY_EVENT_ON_DISPLAY_STATE_CHANGE, - FeatureFlags::noVisibilityEventOnDisplayStateChange); + FeatureFlags::noVisibilityEventOnDisplayStateChange); } @Override - + public boolean offloadColorExtraction() { return getValue(Flags.FLAG_OFFLOAD_COLOR_EXTRACTION, - FeatureFlags::offloadColorExtraction); + FeatureFlags::offloadColorExtraction); } @Override - - public boolean portWindowSizeAnimation() { - return getValue(Flags.FLAG_PORT_WINDOW_SIZE_ANIMATION, - FeatureFlags::portWindowSizeAnimation); + + public boolean predictiveBackSystemAnims() { + return getValue(Flags.FLAG_PREDICTIVE_BACK_SYSTEM_ANIMS, + FeatureFlags::predictiveBackSystemAnims); } @Override - - public boolean predictiveBackDefaultEnableSdk36() { - return getValue(Flags.FLAG_PREDICTIVE_BACK_DEFAULT_ENABLE_SDK_36, - FeatureFlags::predictiveBackDefaultEnableSdk36); - } - - @Override - - public boolean predictiveBackPrioritySystemNavigationObserver() { - return getValue(Flags.FLAG_PREDICTIVE_BACK_PRIORITY_SYSTEM_NAVIGATION_OBSERVER, - FeatureFlags::predictiveBackPrioritySystemNavigationObserver); - } - - @Override - - public boolean predictiveBackSwipeEdgeNoneApi() { - return getValue(Flags.FLAG_PREDICTIVE_BACK_SWIPE_EDGE_NONE_API, - FeatureFlags::predictiveBackSwipeEdgeNoneApi); - } - - @Override - - public boolean predictiveBackSystemOverrideCallback() { - return getValue(Flags.FLAG_PREDICTIVE_BACK_SYSTEM_OVERRIDE_CALLBACK, - FeatureFlags::predictiveBackSystemOverrideCallback); - } - - @Override - - public boolean predictiveBackThreeButtonNav() { - return getValue(Flags.FLAG_PREDICTIVE_BACK_THREE_BUTTON_NAV, - FeatureFlags::predictiveBackThreeButtonNav); - } - - @Override - - public boolean predictiveBackTimestampApi() { - return getValue(Flags.FLAG_PREDICTIVE_BACK_TIMESTAMP_API, - FeatureFlags::predictiveBackTimestampApi); - } - - @Override - - public boolean processPriorityPolicyForMultiWindowMode() { - return getValue(Flags.FLAG_PROCESS_PRIORITY_POLICY_FOR_MULTI_WINDOW_MODE, - FeatureFlags::processPriorityPolicyForMultiWindowMode); - } - - @Override - + public boolean rearDisplayDisableForceDesktopSystemDecorations() { return getValue(Flags.FLAG_REAR_DISPLAY_DISABLE_FORCE_DESKTOP_SYSTEM_DECORATIONS, - FeatureFlags::rearDisplayDisableForceDesktopSystemDecorations); + FeatureFlags::rearDisplayDisableForceDesktopSystemDecorations); } @Override - - public boolean recordTaskSnapshotsBeforeShutdown() { - return getValue(Flags.FLAG_RECORD_TASK_SNAPSHOTS_BEFORE_SHUTDOWN, - FeatureFlags::recordTaskSnapshotsBeforeShutdown); - } - - @Override - - public boolean reduceChangedExclusionRectsMsgs() { - return getValue(Flags.FLAG_REDUCE_CHANGED_EXCLUSION_RECTS_MSGS, - FeatureFlags::reduceChangedExclusionRectsMsgs); - } - - @Override - - public boolean reduceKeyguardTransitions() { - return getValue(Flags.FLAG_REDUCE_KEYGUARD_TRANSITIONS, - FeatureFlags::reduceKeyguardTransitions); - } - - @Override - - public boolean reduceTaskSnapshotMemoryUsage() { - return getValue(Flags.FLAG_REDUCE_TASK_SNAPSHOT_MEMORY_USAGE, - FeatureFlags::reduceTaskSnapshotMemoryUsage); - } - - @Override - - public boolean reduceUnnecessaryMeasure() { - return getValue(Flags.FLAG_REDUCE_UNNECESSARY_MEASURE, - FeatureFlags::reduceUnnecessaryMeasure); - } - - @Override - - public boolean relativeInsets() { - return getValue(Flags.FLAG_RELATIVE_INSETS, - FeatureFlags::relativeInsets); - } - - @Override - + public boolean releaseSnapshotAggressively() { return getValue(Flags.FLAG_RELEASE_SNAPSHOT_AGGRESSIVELY, - FeatureFlags::releaseSnapshotAggressively); + FeatureFlags::releaseSnapshotAggressively); } @Override - - public boolean releaseUserAspectRatioWm() { - return getValue(Flags.FLAG_RELEASE_USER_ASPECT_RATIO_WM, - FeatureFlags::releaseUserAspectRatioWm); + + public boolean removePrepareSurfaceInPlacement() { + return getValue(Flags.FLAG_REMOVE_PREPARE_SURFACE_IN_PLACEMENT, + FeatureFlags::removePrepareSurfaceInPlacement); } @Override - - public boolean removeActivityStarterDreamCallback() { - return getValue(Flags.FLAG_REMOVE_ACTIVITY_STARTER_DREAM_CALLBACK, - FeatureFlags::removeActivityStarterDreamCallback); - } - - @Override - - public boolean removeDeferHidingClient() { - return getValue(Flags.FLAG_REMOVE_DEFER_HIDING_CLIENT, - FeatureFlags::removeDeferHidingClient); - } - - @Override - - public boolean removeDepartTargetFromMotion() { - return getValue(Flags.FLAG_REMOVE_DEPART_TARGET_FROM_MOTION, - FeatureFlags::removeDepartTargetFromMotion); - } - - @Override - - public boolean reparentWindowTokenApi() { - return getValue(Flags.FLAG_REPARENT_WINDOW_TOKEN_API, - FeatureFlags::reparentWindowTokenApi); - } - - @Override - - public boolean respectNonTopVisibleFixedOrientation() { - return getValue(Flags.FLAG_RESPECT_NON_TOP_VISIBLE_FIXED_ORIENTATION, - FeatureFlags::respectNonTopVisibleFixedOrientation); - } - - @Override - - public boolean respectOrientationChangeForUnresizeable() { - return getValue(Flags.FLAG_RESPECT_ORIENTATION_CHANGE_FOR_UNRESIZEABLE, - FeatureFlags::respectOrientationChangeForUnresizeable); - } - - @Override - - public boolean safeRegionLetterboxing() { - return getValue(Flags.FLAG_SAFE_REGION_LETTERBOXING, - FeatureFlags::safeRegionLetterboxing); - } - - @Override - - public boolean safeReleaseSnapshotAggressively() { - return getValue(Flags.FLAG_SAFE_RELEASE_SNAPSHOT_AGGRESSIVELY, - FeatureFlags::safeReleaseSnapshotAggressively); - } - - @Override - - public boolean schedulingForNotificationShade() { - return getValue(Flags.FLAG_SCHEDULING_FOR_NOTIFICATION_SHADE, - FeatureFlags::schedulingForNotificationShade); - } - - @Override - - public boolean scrambleSnapshotFileName() { - return getValue(Flags.FLAG_SCRAMBLE_SNAPSHOT_FILE_NAME, - FeatureFlags::scrambleSnapshotFileName); - } - - @Override - + public boolean screenRecordingCallbacks() { return getValue(Flags.FLAG_SCREEN_RECORDING_CALLBACKS, - FeatureFlags::screenRecordingCallbacks); + FeatureFlags::screenRecordingCallbacks); } @Override - - public boolean scrollingFromLetterbox() { - return getValue(Flags.FLAG_SCROLLING_FROM_LETTERBOX, - FeatureFlags::scrollingFromLetterbox); - } - - @Override - + public boolean sdkDesiredPresentTime() { return getValue(Flags.FLAG_SDK_DESIRED_PRESENT_TIME, - FeatureFlags::sdkDesiredPresentTime); + FeatureFlags::sdkDesiredPresentTime); } @Override + + public boolean secureWindowState() { + return getValue(Flags.FLAG_SECURE_WINDOW_STATE, + FeatureFlags::secureWindowState); + } + @Override + public boolean setScPropertiesInClient() { return getValue(Flags.FLAG_SET_SC_PROPERTIES_IN_CLIENT, - FeatureFlags::setScPropertiesInClient); + FeatureFlags::setScPropertiesInClient); } @Override - - public boolean showAppHandleLargeScreens() { - return getValue(Flags.FLAG_SHOW_APP_HANDLE_LARGE_SCREENS, - FeatureFlags::showAppHandleLargeScreens); + + public boolean skipSleepingWhenSwitchingDisplay() { + return getValue(Flags.FLAG_SKIP_SLEEPING_WHEN_SWITCHING_DISPLAY, + FeatureFlags::skipSleepingWhenSwitchingDisplay); } @Override - - public boolean showDesktopExperienceDevOption() { - return getValue(Flags.FLAG_SHOW_DESKTOP_EXPERIENCE_DEV_OPTION, - FeatureFlags::showDesktopExperienceDevOption); - } - - @Override - - public boolean showDesktopWindowingDevOption() { - return getValue(Flags.FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, - FeatureFlags::showDesktopWindowingDevOption); - } - - @Override - - public boolean showHomeBehindDesktop() { - return getValue(Flags.FLAG_SHOW_HOME_BEHIND_DESKTOP, - FeatureFlags::showHomeBehindDesktop); - } - - @Override - - public boolean skipCompatUiEducationInDesktopMode() { - return getValue(Flags.FLAG_SKIP_COMPAT_UI_EDUCATION_IN_DESKTOP_MODE, - FeatureFlags::skipCompatUiEducationInDesktopMode); - } - - @Override - - public boolean skipDecorViewRelayoutWhenClosingBugfix() { - return getValue(Flags.FLAG_SKIP_DECOR_VIEW_RELAYOUT_WHEN_CLOSING_BUGFIX, - FeatureFlags::skipDecorViewRelayoutWhenClosingBugfix); - } - - @Override - - public boolean supportWidgetIntentsOnConnectedDisplay() { - return getValue(Flags.FLAG_SUPPORT_WIDGET_INTENTS_ON_CONNECTED_DISPLAY, - FeatureFlags::supportWidgetIntentsOnConnectedDisplay); - } - - @Override - - public boolean supportsDragAssistantToMultiwindow() { - return getValue(Flags.FLAG_SUPPORTS_DRAG_ASSISTANT_TO_MULTIWINDOW, - FeatureFlags::supportsDragAssistantToMultiwindow); - } - - @Override - + public boolean supportsMultiInstanceSystemUi() { return getValue(Flags.FLAG_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, - FeatureFlags::supportsMultiInstanceSystemUi); + FeatureFlags::supportsMultiInstanceSystemUi); } @Override - + public boolean surfaceControlInputReceiver() { return getValue(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER, - FeatureFlags::surfaceControlInputReceiver); + FeatureFlags::surfaceControlInputReceiver); } @Override - + public boolean surfaceTrustedOverlay() { return getValue(Flags.FLAG_SURFACE_TRUSTED_OVERLAY, - FeatureFlags::surfaceTrustedOverlay); + FeatureFlags::surfaceTrustedOverlay); } @Override - + public boolean syncScreenCapture() { return getValue(Flags.FLAG_SYNC_SCREEN_CAPTURE, - FeatureFlags::syncScreenCapture); + FeatureFlags::syncScreenCapture); } @Override - - public boolean systemUiPostAnimationEnd() { - return getValue(Flags.FLAG_SYSTEM_UI_POST_ANIMATION_END, - FeatureFlags::systemUiPostAnimationEnd); - } - - @Override - + public boolean taskFragmentSystemOrganizerFlag() { return getValue(Flags.FLAG_TASK_FRAGMENT_SYSTEM_ORGANIZER_FLAG, - FeatureFlags::taskFragmentSystemOrganizerFlag); + FeatureFlags::taskFragmentSystemOrganizerFlag); } @Override - - public boolean touchPassThroughOptIn() { - return getValue(Flags.FLAG_TOUCH_PASS_THROUGH_OPT_IN, - FeatureFlags::touchPassThroughOptIn); - } - - @Override - - public boolean trackSystemUiContextBeforeWms() { - return getValue(Flags.FLAG_TRACK_SYSTEM_UI_CONTEXT_BEFORE_WMS, - FeatureFlags::trackSystemUiContextBeforeWms); - } - - @Override - + public boolean transitReadyTracking() { return getValue(Flags.FLAG_TRANSIT_READY_TRACKING, - FeatureFlags::transitReadyTracking); + FeatureFlags::transitReadyTracking); } @Override - - public boolean transitTrackerPlumbing() { - return getValue(Flags.FLAG_TRANSIT_TRACKER_PLUMBING, - FeatureFlags::transitTrackerPlumbing); - } - - @Override - + public boolean trustedPresentationListenerForWindow() { return getValue(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW, - FeatureFlags::trustedPresentationListenerForWindow); + FeatureFlags::trustedPresentationListenerForWindow); } @Override - - public boolean unifyBackNavigationTransition() { - return getValue(Flags.FLAG_UNIFY_BACK_NAVIGATION_TRANSITION, - FeatureFlags::unifyBackNavigationTransition); - } - - @Override - - public boolean universalResizableByDefault() { - return getValue(Flags.FLAG_UNIVERSAL_RESIZABLE_BY_DEFAULT, - FeatureFlags::universalResizableByDefault); - } - - @Override - + public boolean untrustedEmbeddingAnyAppPermission() { return getValue(Flags.FLAG_UNTRUSTED_EMBEDDING_ANY_APP_PERMISSION, - FeatureFlags::untrustedEmbeddingAnyAppPermission); + FeatureFlags::untrustedEmbeddingAnyAppPermission); } @Override - + public boolean untrustedEmbeddingStateSharing() { return getValue(Flags.FLAG_UNTRUSTED_EMBEDDING_STATE_SHARING, - FeatureFlags::untrustedEmbeddingStateSharing); + FeatureFlags::untrustedEmbeddingStateSharing); } @Override - - public boolean updateDimsWhenWindowShown() { - return getValue(Flags.FLAG_UPDATE_DIMS_WHEN_WINDOW_SHOWN, - FeatureFlags::updateDimsWhenWindowShown); - } - - @Override - - public boolean useCachedInsetsForDisplaySwitch() { - return getValue(Flags.FLAG_USE_CACHED_INSETS_FOR_DISPLAY_SWITCH, - FeatureFlags::useCachedInsetsForDisplaySwitch); - } - - @Override - - public boolean useRtFrameCallbackForSplashScreenTransfer() { - return getValue(Flags.FLAG_USE_RT_FRAME_CALLBACK_FOR_SPLASH_SCREEN_TRANSFER, - FeatureFlags::useRtFrameCallbackForSplashScreenTransfer); - } - - @Override - - public boolean useTasksDimOnly() { - return getValue(Flags.FLAG_USE_TASKS_DIM_ONLY, - FeatureFlags::useTasksDimOnly); - } - - @Override - - public boolean useVisibleRequestedForProcessTracker() { - return getValue(Flags.FLAG_USE_VISIBLE_REQUESTED_FOR_PROCESS_TRACKER, - FeatureFlags::useVisibleRequestedForProcessTracker); - } - - @Override - + public boolean useWindowOriginalTouchableRegionWhenMagnificationRecomputeBounds() { return getValue(Flags.FLAG_USE_WINDOW_ORIGINAL_TOUCHABLE_REGION_WHEN_MAGNIFICATION_RECOMPUTE_BOUNDS, - FeatureFlags::useWindowOriginalTouchableRegionWhenMagnificationRecomputeBounds); + FeatureFlags::useWindowOriginalTouchableRegionWhenMagnificationRecomputeBounds); } @Override - - public boolean vdmForceAppUniversalResizableApi() { - return getValue(Flags.FLAG_VDM_FORCE_APP_UNIVERSAL_RESIZABLE_API, - FeatureFlags::vdmForceAppUniversalResizableApi); + + public boolean userMinAspectRatioAppDefault() { + return getValue(Flags.FLAG_USER_MIN_ASPECT_RATIO_APP_DEFAULT, + FeatureFlags::userMinAspectRatioAppDefault); } @Override + + public boolean waitForTransitionOnDisplaySwitch() { + return getValue(Flags.FLAG_WAIT_FOR_TRANSITION_ON_DISPLAY_SWITCH, + FeatureFlags::waitForTransitionOnDisplaySwitch); + } + @Override + public boolean wallpaperOffsetAsync() { return getValue(Flags.FLAG_WALLPAPER_OFFSET_ASYNC, - FeatureFlags::wallpaperOffsetAsync); + FeatureFlags::wallpaperOffsetAsync); } @Override + + public boolean windowSessionRelayoutInfo() { + return getValue(Flags.FLAG_WINDOW_SESSION_RELAYOUT_INFO, + FeatureFlags::windowSessionRelayoutInfo); + } - public boolean wlinfoOncreate() { - return getValue(Flags.FLAG_WLINFO_ONCREATE, - FeatureFlags::wlinfoOncreate); + @Override + + public boolean windowTokenConfigThreadSafe() { + return getValue(Flags.FLAG_WINDOW_TOKEN_CONFIG_THREAD_SAFE, + FeatureFlags::windowTokenConfigThreadSafe); } public boolean isFlagReadOnlyOptimized(String flagName) { if (mReadOnlyFlagsSet.contains(flagName) && - isOptimizationEnabled()) { - return true; + isOptimizationEnabled()) { + return true; } return false; } - - private boolean isOptimizationEnabled() { + private boolean isOptimizationEnabled() { return false; } @@ -1889,542 +762,162 @@ public class CustomFeatureFlags implements FeatureFlags { public List getFlagNames() { return Arrays.asList( - Flags.FLAG_ACTION_MODE_EDGE_TO_EDGE, - Flags.FLAG_ACTIVITY_EMBEDDING_ANIMATION_CUSTOMIZATION_FLAG, - Flags.FLAG_ACTIVITY_EMBEDDING_DELAY_TASK_FRAGMENT_FINISH_FOR_ACTIVITY_LAUNCH, - Flags.FLAG_ACTIVITY_EMBEDDING_INTERACTIVE_DIVIDER_FLAG, - Flags.FLAG_ACTIVITY_EMBEDDING_METRICS, - Flags.FLAG_ACTIVITY_EMBEDDING_SUPPORT_FOR_CONNECTED_DISPLAYS, - Flags.FLAG_ALLOW_DISABLE_ACTIVITY_RECORD_INPUT_SINK, - Flags.FLAG_ALLOW_HIDE_SCM_BUTTON, - Flags.FLAG_ALLOWS_SCREEN_SIZE_DECOUPLED_FROM_STATUS_BAR_AND_CUTOUT, - Flags.FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER, - Flags.FLAG_ALWAYS_UPDATE_WALLPAPER_PERMISSION, - Flags.FLAG_AOD_TRANSITION, - Flags.FLAG_APP_COMPAT_ASYNC_RELAYOUT, - Flags.FLAG_APP_COMPAT_PROPERTIES_API, - Flags.FLAG_APP_COMPAT_REFACTORING, - Flags.FLAG_APP_COMPAT_UI_FRAMEWORK, - Flags.FLAG_APP_HANDLE_NO_RELAYOUT_ON_EXCLUSION_CHANGE, - Flags.FLAG_APPLY_LIFECYCLE_ON_PIP_CHANGE, - Flags.FLAG_AVOID_REBINDING_INTENTIONALLY_DISCONNECTED_WALLPAPER, - Flags.FLAG_BACKUP_AND_RESTORE_FOR_USER_ASPECT_RATIO_SETTINGS, - Flags.FLAG_BAL_ADDITIONAL_LOGGING, - Flags.FLAG_BAL_ADDITIONAL_START_MODES, - Flags.FLAG_BAL_CLEAR_ALLOWLIST_DURATION, - Flags.FLAG_BAL_DONT_BRING_EXISTING_BACKGROUND_TASK_STACK_TO_FG, - Flags.FLAG_BAL_IMPROVE_REAL_CALLER_VISIBILITY_CHECK, - Flags.FLAG_BAL_IMPROVED_METRICS, - Flags.FLAG_BAL_REDUCE_GRACE_PERIOD, - Flags.FLAG_BAL_REQUIRE_OPT_IN_BY_PENDING_INTENT_CREATOR, - Flags.FLAG_BAL_RESPECT_APP_SWITCH_STATE_WHEN_CHECK_BOUND_BY_FOREGROUND_UID, - Flags.FLAG_BAL_SEND_INTENT_WITH_OPTIONS, - Flags.FLAG_BAL_SHOW_TOASTS_BLOCKED, - Flags.FLAG_BAL_STRICT_MODE_GRACE_PERIOD, - Flags.FLAG_BAL_STRICT_MODE_RO, - Flags.FLAG_BETTER_SUPPORT_NON_MATCH_PARENT_ACTIVITY, - Flags.FLAG_CACHE_WINDOW_STYLE, - Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM, - Flags.FLAG_CAMERA_COMPAT_FULLSCREEN_PICK_SAME_TASK_ACTIVITY, - Flags.FLAG_CHECK_DISABLED_SNAPSHOTS_IN_TASK_PERSISTER, - Flags.FLAG_CLEANUP_DISPATCH_PENDING_TRANSACTIONS_REMOTE_EXCEPTION, - Flags.FLAG_CLEAR_SYSTEM_VIBRATOR, - Flags.FLAG_CLOSE_TO_SQUARE_CONFIG_INCLUDES_STATUS_BAR, - Flags.FLAG_CONDENSE_CONFIGURATION_CHANGE_FOR_SIMPLE_MODE, - Flags.FLAG_CONFIGURABLE_FONT_SCALE_DEFAULT, - Flags.FLAG_COVER_DISPLAY_OPT_IN, - Flags.FLAG_DELAY_NOTIFICATION_TO_MAGNIFICATION_WHEN_RECENTS_WINDOW_TO_FRONT_TRANSITION, - Flags.FLAG_DELEGATE_BACK_GESTURE_TO_SHELL, - Flags.FLAG_DELEGATE_UNHANDLED_DRAGS, - Flags.FLAG_DELETE_CAPTURE_DISPLAY, - Flags.FLAG_DENSITY_390_API, - Flags.FLAG_DISABLE_DESKTOP_LAUNCH_PARAMS_OUTSIDE_DESKTOP_BUG_FIX, - Flags.FLAG_DISABLE_NON_RESIZABLE_APP_SNAP_RESIZING, - Flags.FLAG_DISABLE_OPT_OUT_EDGE_TO_EDGE, - Flags.FLAG_DO_NOT_CHECK_INTERSECTION_WHEN_NON_MAGNIFIABLE_WINDOW_TRANSITIONS, - Flags.FLAG_EARLY_LAUNCH_HINT, - Flags.FLAG_EDGE_TO_EDGE_BY_DEFAULT, - Flags.FLAG_ENABLE_ACCESSIBLE_CUSTOM_HEADERS, - Flags.FLAG_ENABLE_ACTIVITY_EMBEDDING_SUPPORT_FOR_CONNECTED_DISPLAYS, - Flags.FLAG_ENABLE_APP_HEADER_WITH_TASK_DENSITY, - Flags.FLAG_ENABLE_BORDER_SETTINGS, - Flags.FLAG_ENABLE_BUFFER_TRANSFORM_HINT_FROM_DISPLAY, - Flags.FLAG_ENABLE_BUG_FIXES_FOR_SECONDARY_DISPLAY, - Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING, - Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT, - Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT_API, - Flags.FLAG_ENABLE_CAMERA_COMPAT_TRACK_TASK_AND_APP_BUGFIX, - Flags.FLAG_ENABLE_CAPTION_COMPAT_INSET_CONVERSION, - Flags.FLAG_ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION, - Flags.FLAG_ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS, - Flags.FLAG_ENABLE_CASCADING_WINDOWS, - Flags.FLAG_ENABLE_COMPAT_UI_VISIBILITY_STATUS, - Flags.FLAG_ENABLE_COMPATUI_SYSUI_LAUNCHER, - Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_DND, - Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_PIP, - Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WINDOW_DRAG, - Flags.FLAG_ENABLE_DESKTOP_APP_HANDLE_ANIMATION, - Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_ALTTAB_TRANSITIONS, - Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_ALTTAB_TRANSITIONS_BUGFIX, - Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS, - Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX, - Flags.FLAG_ENABLE_DESKTOP_CLOSE_SHORTCUT_BUGFIX, - Flags.FLAG_ENABLE_DESKTOP_CLOSE_TASK_ANIMATION_IN_DTC_BUGFIX, - Flags.FLAG_ENABLE_DESKTOP_IME_BUGFIX, - Flags.FLAG_ENABLE_DESKTOP_IMMERSIVE_DRAG_BUGFIX, - Flags.FLAG_ENABLE_DESKTOP_INDICATOR_IN_SEPARATE_THREAD_BUGFIX, - Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION, - Flags.FLAG_ENABLE_DESKTOP_OPENING_DEEPLINK_MINIMIZE_ANIMATION_BUGFIX, - Flags.FLAG_ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX, - Flags.FLAG_ENABLE_DESKTOP_SWIPE_BACK_MINIMIZE_ANIMATION_BUGFIX, - Flags.FLAG_ENABLE_DESKTOP_SYSTEM_DIALOGS_TRANSITIONS, - Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX, - Flags.FLAG_ENABLE_DESKTOP_TASKBAR_ON_FREEFORM_DISPLAYS, - Flags.FLAG_ENABLE_DESKTOP_TRAMPOLINE_CLOSE_ANIMATION_BUGFIX, - Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB_EDUCATION, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB_EDUCATION_INTEGRATION, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITION_BUGFIX, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_BY_MINIMIZE_TRANSITION_BUGFIX, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_HSUM, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_IMMERSIVE_HANDLE_HIDING, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_QUICK_SWITCH, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_SCVH_CACHE_BUG_FIX, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_SIZE_CONSTRAINTS, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TASK_LIMIT, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, - Flags.FLAG_ENABLE_DEVICE_STATE_AUTO_ROTATE_SETTING_LOGGING, - Flags.FLAG_ENABLE_DEVICE_STATE_AUTO_ROTATE_SETTING_REFACTOR, - Flags.FLAG_ENABLE_DISPLAY_DISCONNECT_INTERACTION, - Flags.FLAG_ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS, - Flags.FLAG_ENABLE_DISPLAY_RECONNECT_INTERACTION, - Flags.FLAG_ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING, - Flags.FLAG_ENABLE_DRAG_RESIZE_SET_UP_IN_BG_THREAD, - Flags.FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX, - Flags.FLAG_ENABLE_DRAG_TO_MAXIMIZE, - Flags.FLAG_ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX, - Flags.FLAG_ENABLE_FULL_SCREEN_WINDOW_ON_REMOVING_SPLIT_SCREEN_STAGE_BUGFIX, - Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP, - Flags.FLAG_ENABLE_HANDLE_INPUT_FIX, - Flags.FLAG_ENABLE_HOLD_TO_DRAG_APP_HANDLE, - Flags.FLAG_ENABLE_INPUT_LAYER_TRANSITION_FIX, - Flags.FLAG_ENABLE_MINIMIZE_BUTTON, - Flags.FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PERMISSION, - Flags.FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT, - Flags.FLAG_ENABLE_MULTI_DISPLAY_SPLIT, - Flags.FLAG_ENABLE_MULTIDISPLAY_TRACKPAD_BACK_GESTURE, - Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, - Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_FRONTEND, - Flags.FLAG_ENABLE_NON_DEFAULT_DISPLAY_SPLIT, - Flags.FLAG_ENABLE_OPAQUE_BACKGROUND_FOR_TRANSPARENT_WINDOWS, - Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY, - Flags.FLAG_ENABLE_PER_DISPLAY_PACKAGE_CONTEXT_CACHE_IN_STATUSBAR_NOTIF, - Flags.FLAG_ENABLE_PERSISTING_DISPLAY_SIZE_FOR_CONNECTED_DISPLAYS, - Flags.FLAG_ENABLE_PRESENTATION_FOR_CONNECTED_DISPLAYS, - Flags.FLAG_ENABLE_PROJECTED_DISPLAY_DESKTOP_MODE, - Flags.FLAG_ENABLE_QUICKSWITCH_DESKTOP_SPLIT_BUGFIX, - Flags.FLAG_ENABLE_REQUEST_FULLSCREEN_BUGFIX, - Flags.FLAG_ENABLE_RESIZING_METRICS, - Flags.FLAG_ENABLE_RESTART_MENU_FOR_CONNECTED_DISPLAYS, - Flags.FLAG_ENABLE_RESTORE_TO_PREVIOUS_SIZE_FROM_DESKTOP_IMMERSIVE, - Flags.FLAG_ENABLE_SHELL_INITIAL_BOUNDS_REGRESSION_BUG_FIX, - Flags.FLAG_ENABLE_SIZE_COMPAT_MODE_IMPROVEMENTS_FOR_CONNECTED_DISPLAYS, - Flags.FLAG_ENABLE_START_LAUNCH_TRANSITION_FROM_TASKBAR_BUGFIX, - Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS, - Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL, - Flags.FLAG_ENABLE_TASKBAR_CONNECTED_DISPLAYS, - Flags.FLAG_ENABLE_TASKBAR_OVERFLOW, - Flags.FLAG_ENABLE_TASKBAR_RECENTS_LAYOUT_TRANSITION, - Flags.FLAG_ENABLE_THEMED_APP_HEADERS, - Flags.FLAG_ENABLE_TILE_RESIZING, - Flags.FLAG_ENABLE_TOP_VISIBLE_ROOT_TASK_PER_USER_TRACKING, - Flags.FLAG_ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX, - Flags.FLAG_ENABLE_WINDOW_CONTEXT_RESOURCES_UPDATE_ON_CONFIG_CHANGE, - Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS, - Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE, - Flags.FLAG_ENABLE_WINDOWING_SCALED_RESIZING, - Flags.FLAG_ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS, - Flags.FLAG_ENFORCE_EDGE_TO_EDGE, - Flags.FLAG_ENSURE_KEYGUARD_DOES_TRANSITION_STARTING, - Flags.FLAG_ENSURE_WALLPAPER_IN_TRANSITIONS, - Flags.FLAG_ENSURE_WALLPAPER_IN_WEAR_TRANSITIONS, - Flags.FLAG_ENTER_DESKTOP_BY_DEFAULT_ON_FREEFORM_DISPLAYS, - Flags.FLAG_EXCLUDE_CAPTION_FROM_APP_BOUNDS, - Flags.FLAG_EXCLUDE_DRAWING_APP_THEME_SNAPSHOT_FROM_LOCK, - Flags.FLAG_EXCLUDE_TASK_FROM_RECENTS, - Flags.FLAG_FIFO_PRIORITY_FOR_MAJOR_UI_PROCESSES, - Flags.FLAG_FIX_HIDE_OVERLAY_API, - Flags.FLAG_FIX_LAYOUT_EXISTING_TASK, - Flags.FLAG_FIX_VIEW_ROOT_CALL_TRACE, - Flags.FLAG_FORCE_CLOSE_TOP_TRANSPARENT_FULLSCREEN_TASK, - Flags.FLAG_FORM_FACTOR_BASED_DESKTOP_FIRST_SWITCH, - Flags.FLAG_GET_DIMMER_ON_CLOSING, - Flags.FLAG_IGNORE_ASPECT_RATIO_RESTRICTIONS_FOR_RESIZEABLE_FREEFORM_ACTIVITIES, - Flags.FLAG_IGNORE_CORNER_RADIUS_AND_SHADOWS, - Flags.FLAG_INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC, - Flags.FLAG_INHERIT_TASK_BOUNDS_FOR_TRAMPOLINE_TASK_LAUNCHES, - Flags.FLAG_INSETS_DECOUPLED_CONFIGURATION, - Flags.FLAG_JANK_API, - Flags.FLAG_KEEP_APP_WINDOW_HIDE_WHILE_LOCKED, - Flags.FLAG_KEYBOARD_SHORTCUTS_TO_SWITCH_DESKS, - Flags.FLAG_KEYGUARD_GOING_AWAY_TIMEOUT, - Flags.FLAG_LETTERBOX_BACKGROUND_WALLPAPER, - Flags.FLAG_MOVABLE_CUTOUT_CONFIGURATION, - Flags.FLAG_MOVE_TO_EXTERNAL_DISPLAY_SHORTCUT, - Flags.FLAG_MULTI_CROP, - Flags.FLAG_NAV_BAR_TRANSPARENT_BY_DEFAULT, - Flags.FLAG_NESTED_TASKS_WITH_INDEPENDENT_BOUNDS, - Flags.FLAG_NO_CONSECUTIVE_VISIBILITY_EVENTS, - Flags.FLAG_NO_DUPLICATE_SURFACE_DESTROYED_EVENTS, - Flags.FLAG_NO_VISIBILITY_EVENT_ON_DISPLAY_STATE_CHANGE, - Flags.FLAG_OFFLOAD_COLOR_EXTRACTION, - Flags.FLAG_PORT_WINDOW_SIZE_ANIMATION, - Flags.FLAG_PREDICTIVE_BACK_DEFAULT_ENABLE_SDK_36, - Flags.FLAG_PREDICTIVE_BACK_PRIORITY_SYSTEM_NAVIGATION_OBSERVER, - Flags.FLAG_PREDICTIVE_BACK_SWIPE_EDGE_NONE_API, - Flags.FLAG_PREDICTIVE_BACK_SYSTEM_OVERRIDE_CALLBACK, - Flags.FLAG_PREDICTIVE_BACK_THREE_BUTTON_NAV, - Flags.FLAG_PREDICTIVE_BACK_TIMESTAMP_API, - Flags.FLAG_PROCESS_PRIORITY_POLICY_FOR_MULTI_WINDOW_MODE, - Flags.FLAG_REAR_DISPLAY_DISABLE_FORCE_DESKTOP_SYSTEM_DECORATIONS, - Flags.FLAG_RECORD_TASK_SNAPSHOTS_BEFORE_SHUTDOWN, - Flags.FLAG_REDUCE_CHANGED_EXCLUSION_RECTS_MSGS, - Flags.FLAG_REDUCE_KEYGUARD_TRANSITIONS, - Flags.FLAG_REDUCE_TASK_SNAPSHOT_MEMORY_USAGE, - Flags.FLAG_REDUCE_UNNECESSARY_MEASURE, - Flags.FLAG_RELATIVE_INSETS, - Flags.FLAG_RELEASE_SNAPSHOT_AGGRESSIVELY, - Flags.FLAG_RELEASE_USER_ASPECT_RATIO_WM, - Flags.FLAG_REMOVE_ACTIVITY_STARTER_DREAM_CALLBACK, - Flags.FLAG_REMOVE_DEFER_HIDING_CLIENT, - Flags.FLAG_REMOVE_DEPART_TARGET_FROM_MOTION, - Flags.FLAG_REPARENT_WINDOW_TOKEN_API, - Flags.FLAG_RESPECT_NON_TOP_VISIBLE_FIXED_ORIENTATION, - Flags.FLAG_RESPECT_ORIENTATION_CHANGE_FOR_UNRESIZEABLE, - Flags.FLAG_SAFE_REGION_LETTERBOXING, - Flags.FLAG_SAFE_RELEASE_SNAPSHOT_AGGRESSIVELY, - Flags.FLAG_SCHEDULING_FOR_NOTIFICATION_SHADE, - Flags.FLAG_SCRAMBLE_SNAPSHOT_FILE_NAME, - Flags.FLAG_SCREEN_RECORDING_CALLBACKS, - Flags.FLAG_SCROLLING_FROM_LETTERBOX, - Flags.FLAG_SDK_DESIRED_PRESENT_TIME, - Flags.FLAG_SET_SC_PROPERTIES_IN_CLIENT, - Flags.FLAG_SHOW_APP_HANDLE_LARGE_SCREENS, - Flags.FLAG_SHOW_DESKTOP_EXPERIENCE_DEV_OPTION, - Flags.FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, - Flags.FLAG_SHOW_HOME_BEHIND_DESKTOP, - Flags.FLAG_SKIP_COMPAT_UI_EDUCATION_IN_DESKTOP_MODE, - Flags.FLAG_SKIP_DECOR_VIEW_RELAYOUT_WHEN_CLOSING_BUGFIX, - Flags.FLAG_SUPPORT_WIDGET_INTENTS_ON_CONNECTED_DISPLAY, - Flags.FLAG_SUPPORTS_DRAG_ASSISTANT_TO_MULTIWINDOW, - Flags.FLAG_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, - Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER, - Flags.FLAG_SURFACE_TRUSTED_OVERLAY, - Flags.FLAG_SYNC_SCREEN_CAPTURE, - Flags.FLAG_SYSTEM_UI_POST_ANIMATION_END, - Flags.FLAG_TASK_FRAGMENT_SYSTEM_ORGANIZER_FLAG, - Flags.FLAG_TOUCH_PASS_THROUGH_OPT_IN, - Flags.FLAG_TRACK_SYSTEM_UI_CONTEXT_BEFORE_WMS, - Flags.FLAG_TRANSIT_READY_TRACKING, - Flags.FLAG_TRANSIT_TRACKER_PLUMBING, - Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW, - Flags.FLAG_UNIFY_BACK_NAVIGATION_TRANSITION, - Flags.FLAG_UNIVERSAL_RESIZABLE_BY_DEFAULT, - Flags.FLAG_UNTRUSTED_EMBEDDING_ANY_APP_PERMISSION, - Flags.FLAG_UNTRUSTED_EMBEDDING_STATE_SHARING, - Flags.FLAG_UPDATE_DIMS_WHEN_WINDOW_SHOWN, - Flags.FLAG_USE_CACHED_INSETS_FOR_DISPLAY_SWITCH, - Flags.FLAG_USE_RT_FRAME_CALLBACK_FOR_SPLASH_SCREEN_TRANSFER, - Flags.FLAG_USE_TASKS_DIM_ONLY, - Flags.FLAG_USE_VISIBLE_REQUESTED_FOR_PROCESS_TRACKER, - Flags.FLAG_USE_WINDOW_ORIGINAL_TOUCHABLE_REGION_WHEN_MAGNIFICATION_RECOMPUTE_BOUNDS, - Flags.FLAG_VDM_FORCE_APP_UNIVERSAL_RESIZABLE_API, - Flags.FLAG_WALLPAPER_OFFSET_ASYNC, - Flags.FLAG_WLINFO_ONCREATE + Flags.FLAG_ACTIVITY_EMBEDDING_ANIMATION_CUSTOMIZATION_FLAG, + Flags.FLAG_ACTIVITY_EMBEDDING_INTERACTIVE_DIVIDER_FLAG, + Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG, + Flags.FLAG_ACTIVITY_SNAPSHOT_BY_DEFAULT, + Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG, + Flags.FLAG_ALLOW_DISABLE_ACTIVITY_RECORD_INPUT_SINK, + Flags.FLAG_ALLOW_HIDE_SCM_BUTTON, + Flags.FLAG_ALLOWS_SCREEN_SIZE_DECOUPLED_FROM_STATUS_BAR_AND_CUTOUT, + Flags.FLAG_ALWAYS_DEFER_TRANSITION_WHEN_APPLY_WCT, + Flags.FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER, + Flags.FLAG_ALWAYS_UPDATE_WALLPAPER_PERMISSION, + Flags.FLAG_APP_COMPAT_PROPERTIES_API, + Flags.FLAG_APP_COMPAT_REFACTORING, + Flags.FLAG_BAL_DONT_BRING_EXISTING_BACKGROUND_TASK_STACK_TO_FG, + Flags.FLAG_BAL_IMPROVE_REAL_CALLER_VISIBILITY_CHECK, + Flags.FLAG_BAL_IMPROVED_METRICS, + Flags.FLAG_BAL_REQUIRE_OPT_IN_BY_PENDING_INTENT_CREATOR, + Flags.FLAG_BAL_REQUIRE_OPT_IN_SAME_UID, + Flags.FLAG_BAL_RESPECT_APP_SWITCH_STATE_WHEN_CHECK_BOUND_BY_FOREGROUND_UID, + Flags.FLAG_BAL_SHOW_TOASTS, + Flags.FLAG_BAL_SHOW_TOASTS_BLOCKED, + Flags.FLAG_BLAST_SYNC_NOTIFICATION_SHADE_ON_DISPLAY_SWITCH, + Flags.FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG, + Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM, + Flags.FLAG_CLOSE_TO_SQUARE_CONFIG_INCLUDES_STATUS_BAR, + Flags.FLAG_CONFIGURABLE_FONT_SCALE_DEFAULT, + Flags.FLAG_COVER_DISPLAY_OPT_IN, + Flags.FLAG_DEFER_DISPLAY_UPDATES, + Flags.FLAG_DELAY_NOTIFICATION_TO_MAGNIFICATION_WHEN_RECENTS_WINDOW_TO_FRONT_TRANSITION, + Flags.FLAG_DELEGATE_UNHANDLED_DRAGS, + Flags.FLAG_DELETE_CAPTURE_DISPLAY, + Flags.FLAG_DENSITY_390_API, + Flags.FLAG_DISABLE_OBJECT_POOL, + Flags.FLAG_DISABLE_THIN_LETTERBOXING_POLICY, + Flags.FLAG_DO_NOT_CHECK_INTERSECTION_WHEN_NON_MAGNIFIABLE_WINDOW_TRANSITIONS, + Flags.FLAG_DRAW_SNAPSHOT_ASPECT_RATIO_MATCH, + Flags.FLAG_EDGE_TO_EDGE_BY_DEFAULT, + Flags.FLAG_EMBEDDED_ACTIVITY_BACK_NAV_FLAG, + Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR, + Flags.FLAG_ENABLE_APP_HEADER_WITH_TASK_DENSITY, + Flags.FLAG_ENABLE_BUFFER_TRANSFORM_HINT_FROM_DISPLAY, + Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING, + Flags.FLAG_ENABLE_COMPATUI_SYSUI_LAUNCHER, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_IMMERSIVE_HANDLE_HIDING, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_QUICK_SWITCH, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_SCVH_CACHE, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_SIZE_CONSTRAINTS, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TASK_LIMIT, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, + Flags.FLAG_ENABLE_SCALED_RESIZING, + Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL, + Flags.FLAG_ENABLE_THEMED_APP_HEADERS, + Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS, + Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE, + Flags.FLAG_ENABLE_WM_EXTENSIONS_FOR_ALL_FLAG, + Flags.FLAG_ENFORCE_EDGE_TO_EDGE, + Flags.FLAG_ENSURE_WALLPAPER_IN_TRANSITIONS, + Flags.FLAG_EXPLICIT_REFRESH_RATE_HINTS, + Flags.FLAG_FIFO_PRIORITY_FOR_MAJOR_UI_PROCESSES, + Flags.FLAG_FIX_NO_CONTAINER_UPDATE_WITHOUT_RESIZE, + Flags.FLAG_FIX_PIP_RESTORE_TO_OVERLAY, + Flags.FLAG_FULLSCREEN_DIM_FLAG, + Flags.FLAG_GET_DIMMER_ON_CLOSING, + Flags.FLAG_IMMERSIVE_APP_REPOSITIONING, + Flags.FLAG_INSETS_CONTROL_CHANGED_ITEM, + Flags.FLAG_INSETS_CONTROL_SEQ, + Flags.FLAG_INSETS_DECOUPLED_CONFIGURATION, + Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER, + Flags.FLAG_KEYGUARD_APPEAR_TRANSITION, + Flags.FLAG_LETTERBOX_BACKGROUND_WALLPAPER, + Flags.FLAG_MOVABLE_CUTOUT_CONFIGURATION, + Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE, + Flags.FLAG_MULTI_CROP, + Flags.FLAG_NAV_BAR_TRANSPARENT_BY_DEFAULT, + Flags.FLAG_NO_CONSECUTIVE_VISIBILITY_EVENTS, + Flags.FLAG_NO_VISIBILITY_EVENT_ON_DISPLAY_STATE_CHANGE, + Flags.FLAG_OFFLOAD_COLOR_EXTRACTION, + Flags.FLAG_PREDICTIVE_BACK_SYSTEM_ANIMS, + Flags.FLAG_REAR_DISPLAY_DISABLE_FORCE_DESKTOP_SYSTEM_DECORATIONS, + Flags.FLAG_RELEASE_SNAPSHOT_AGGRESSIVELY, + Flags.FLAG_REMOVE_PREPARE_SURFACE_IN_PLACEMENT, + Flags.FLAG_SCREEN_RECORDING_CALLBACKS, + Flags.FLAG_SDK_DESIRED_PRESENT_TIME, + Flags.FLAG_SECURE_WINDOW_STATE, + Flags.FLAG_SET_SC_PROPERTIES_IN_CLIENT, + Flags.FLAG_SKIP_SLEEPING_WHEN_SWITCHING_DISPLAY, + Flags.FLAG_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, + Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER, + Flags.FLAG_SURFACE_TRUSTED_OVERLAY, + Flags.FLAG_SYNC_SCREEN_CAPTURE, + Flags.FLAG_TASK_FRAGMENT_SYSTEM_ORGANIZER_FLAG, + Flags.FLAG_TRANSIT_READY_TRACKING, + Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW, + Flags.FLAG_UNTRUSTED_EMBEDDING_ANY_APP_PERMISSION, + Flags.FLAG_UNTRUSTED_EMBEDDING_STATE_SHARING, + Flags.FLAG_USE_WINDOW_ORIGINAL_TOUCHABLE_REGION_WHEN_MAGNIFICATION_RECOMPUTE_BOUNDS, + Flags.FLAG_USER_MIN_ASPECT_RATIO_APP_DEFAULT, + Flags.FLAG_WAIT_FOR_TRANSITION_ON_DISPLAY_SWITCH, + Flags.FLAG_WALLPAPER_OFFSET_ASYNC, + Flags.FLAG_WINDOW_SESSION_RELAYOUT_INFO, + Flags.FLAG_WINDOW_TOKEN_CONFIG_THREAD_SAFE ); } private Set mReadOnlyFlagsSet = new HashSet<>( - Arrays.asList( - Flags.FLAG_ACTION_MODE_EDGE_TO_EDGE, - Flags.FLAG_ACTIVITY_EMBEDDING_ANIMATION_CUSTOMIZATION_FLAG, - Flags.FLAG_ACTIVITY_EMBEDDING_DELAY_TASK_FRAGMENT_FINISH_FOR_ACTIVITY_LAUNCH, - Flags.FLAG_ACTIVITY_EMBEDDING_INTERACTIVE_DIVIDER_FLAG, - Flags.FLAG_ACTIVITY_EMBEDDING_METRICS, - Flags.FLAG_ACTIVITY_EMBEDDING_SUPPORT_FOR_CONNECTED_DISPLAYS, - Flags.FLAG_ALLOW_DISABLE_ACTIVITY_RECORD_INPUT_SINK, - Flags.FLAG_ALLOW_HIDE_SCM_BUTTON, - Flags.FLAG_ALLOWS_SCREEN_SIZE_DECOUPLED_FROM_STATUS_BAR_AND_CUTOUT, - Flags.FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER, - Flags.FLAG_ALWAYS_UPDATE_WALLPAPER_PERMISSION, - Flags.FLAG_AOD_TRANSITION, - Flags.FLAG_APP_COMPAT_ASYNC_RELAYOUT, - Flags.FLAG_APP_COMPAT_PROPERTIES_API, - Flags.FLAG_APP_COMPAT_REFACTORING, - Flags.FLAG_APP_COMPAT_UI_FRAMEWORK, - Flags.FLAG_APP_HANDLE_NO_RELAYOUT_ON_EXCLUSION_CHANGE, - Flags.FLAG_APPLY_LIFECYCLE_ON_PIP_CHANGE, - Flags.FLAG_AVOID_REBINDING_INTENTIONALLY_DISCONNECTED_WALLPAPER, - Flags.FLAG_BACKUP_AND_RESTORE_FOR_USER_ASPECT_RATIO_SETTINGS, - Flags.FLAG_BAL_ADDITIONAL_LOGGING, - Flags.FLAG_BAL_ADDITIONAL_START_MODES, - Flags.FLAG_BAL_CLEAR_ALLOWLIST_DURATION, - Flags.FLAG_BAL_DONT_BRING_EXISTING_BACKGROUND_TASK_STACK_TO_FG, - Flags.FLAG_BAL_IMPROVE_REAL_CALLER_VISIBILITY_CHECK, - Flags.FLAG_BAL_IMPROVED_METRICS, - Flags.FLAG_BAL_REDUCE_GRACE_PERIOD, - Flags.FLAG_BAL_REQUIRE_OPT_IN_BY_PENDING_INTENT_CREATOR, - Flags.FLAG_BAL_RESPECT_APP_SWITCH_STATE_WHEN_CHECK_BOUND_BY_FOREGROUND_UID, - Flags.FLAG_BAL_SEND_INTENT_WITH_OPTIONS, - Flags.FLAG_BAL_SHOW_TOASTS_BLOCKED, - Flags.FLAG_BAL_STRICT_MODE_GRACE_PERIOD, - Flags.FLAG_BAL_STRICT_MODE_RO, - Flags.FLAG_BETTER_SUPPORT_NON_MATCH_PARENT_ACTIVITY, - Flags.FLAG_CACHE_WINDOW_STYLE, - Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM, - Flags.FLAG_CAMERA_COMPAT_FULLSCREEN_PICK_SAME_TASK_ACTIVITY, - Flags.FLAG_CHECK_DISABLED_SNAPSHOTS_IN_TASK_PERSISTER, - Flags.FLAG_CLEANUP_DISPATCH_PENDING_TRANSACTIONS_REMOTE_EXCEPTION, - Flags.FLAG_CLEAR_SYSTEM_VIBRATOR, - Flags.FLAG_CLOSE_TO_SQUARE_CONFIG_INCLUDES_STATUS_BAR, - Flags.FLAG_CONDENSE_CONFIGURATION_CHANGE_FOR_SIMPLE_MODE, - Flags.FLAG_CONFIGURABLE_FONT_SCALE_DEFAULT, - Flags.FLAG_COVER_DISPLAY_OPT_IN, - Flags.FLAG_DELAY_NOTIFICATION_TO_MAGNIFICATION_WHEN_RECENTS_WINDOW_TO_FRONT_TRANSITION, - Flags.FLAG_DELEGATE_BACK_GESTURE_TO_SHELL, - Flags.FLAG_DELEGATE_UNHANDLED_DRAGS, - Flags.FLAG_DELETE_CAPTURE_DISPLAY, - Flags.FLAG_DENSITY_390_API, - Flags.FLAG_DISABLE_DESKTOP_LAUNCH_PARAMS_OUTSIDE_DESKTOP_BUG_FIX, - Flags.FLAG_DISABLE_NON_RESIZABLE_APP_SNAP_RESIZING, - Flags.FLAG_DISABLE_OPT_OUT_EDGE_TO_EDGE, - Flags.FLAG_DO_NOT_CHECK_INTERSECTION_WHEN_NON_MAGNIFIABLE_WINDOW_TRANSITIONS, - Flags.FLAG_EARLY_LAUNCH_HINT, - Flags.FLAG_EDGE_TO_EDGE_BY_DEFAULT, - Flags.FLAG_ENABLE_ACCESSIBLE_CUSTOM_HEADERS, - Flags.FLAG_ENABLE_ACTIVITY_EMBEDDING_SUPPORT_FOR_CONNECTED_DISPLAYS, - Flags.FLAG_ENABLE_APP_HEADER_WITH_TASK_DENSITY, - Flags.FLAG_ENABLE_BORDER_SETTINGS, - Flags.FLAG_ENABLE_BUFFER_TRANSFORM_HINT_FROM_DISPLAY, - Flags.FLAG_ENABLE_BUG_FIXES_FOR_SECONDARY_DISPLAY, - Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING, - Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT, - Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT_API, - Flags.FLAG_ENABLE_CAMERA_COMPAT_TRACK_TASK_AND_APP_BUGFIX, - Flags.FLAG_ENABLE_CAPTION_COMPAT_INSET_CONVERSION, - Flags.FLAG_ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION, - Flags.FLAG_ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS, - Flags.FLAG_ENABLE_CASCADING_WINDOWS, - Flags.FLAG_ENABLE_COMPAT_UI_VISIBILITY_STATUS, - Flags.FLAG_ENABLE_COMPATUI_SYSUI_LAUNCHER, - Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_DND, - Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_PIP, - Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WINDOW_DRAG, - Flags.FLAG_ENABLE_DESKTOP_APP_HANDLE_ANIMATION, - Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_ALTTAB_TRANSITIONS, - Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_ALTTAB_TRANSITIONS_BUGFIX, - Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS, - Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX, - Flags.FLAG_ENABLE_DESKTOP_CLOSE_SHORTCUT_BUGFIX, - Flags.FLAG_ENABLE_DESKTOP_CLOSE_TASK_ANIMATION_IN_DTC_BUGFIX, - Flags.FLAG_ENABLE_DESKTOP_IME_BUGFIX, - Flags.FLAG_ENABLE_DESKTOP_IMMERSIVE_DRAG_BUGFIX, - Flags.FLAG_ENABLE_DESKTOP_INDICATOR_IN_SEPARATE_THREAD_BUGFIX, - Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION, - Flags.FLAG_ENABLE_DESKTOP_OPENING_DEEPLINK_MINIMIZE_ANIMATION_BUGFIX, - Flags.FLAG_ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX, - Flags.FLAG_ENABLE_DESKTOP_SWIPE_BACK_MINIMIZE_ANIMATION_BUGFIX, - Flags.FLAG_ENABLE_DESKTOP_SYSTEM_DIALOGS_TRANSITIONS, - Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX, - Flags.FLAG_ENABLE_DESKTOP_TASKBAR_ON_FREEFORM_DISPLAYS, - Flags.FLAG_ENABLE_DESKTOP_TRAMPOLINE_CLOSE_ANIMATION_BUGFIX, - Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB_EDUCATION, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB_EDUCATION_INTEGRATION, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITION_BUGFIX, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_BY_MINIMIZE_TRANSITION_BUGFIX, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_HSUM, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_IMMERSIVE_HANDLE_HIDING, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_QUICK_SWITCH, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_SCVH_CACHE_BUG_FIX, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_SIZE_CONSTRAINTS, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TASK_LIMIT, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, - Flags.FLAG_ENABLE_DEVICE_STATE_AUTO_ROTATE_SETTING_LOGGING, - Flags.FLAG_ENABLE_DEVICE_STATE_AUTO_ROTATE_SETTING_REFACTOR, - Flags.FLAG_ENABLE_DISPLAY_DISCONNECT_INTERACTION, - Flags.FLAG_ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS, - Flags.FLAG_ENABLE_DISPLAY_RECONNECT_INTERACTION, - Flags.FLAG_ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING, - Flags.FLAG_ENABLE_DRAG_RESIZE_SET_UP_IN_BG_THREAD, - Flags.FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX, - Flags.FLAG_ENABLE_DRAG_TO_MAXIMIZE, - Flags.FLAG_ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX, - Flags.FLAG_ENABLE_FULL_SCREEN_WINDOW_ON_REMOVING_SPLIT_SCREEN_STAGE_BUGFIX, - Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP, - Flags.FLAG_ENABLE_HANDLE_INPUT_FIX, - Flags.FLAG_ENABLE_HOLD_TO_DRAG_APP_HANDLE, - Flags.FLAG_ENABLE_INPUT_LAYER_TRANSITION_FIX, - Flags.FLAG_ENABLE_MINIMIZE_BUTTON, - Flags.FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PERMISSION, - Flags.FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT, - Flags.FLAG_ENABLE_MULTI_DISPLAY_SPLIT, - Flags.FLAG_ENABLE_MULTIDISPLAY_TRACKPAD_BACK_GESTURE, - Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, - Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_FRONTEND, - Flags.FLAG_ENABLE_NON_DEFAULT_DISPLAY_SPLIT, - Flags.FLAG_ENABLE_OPAQUE_BACKGROUND_FOR_TRANSPARENT_WINDOWS, - Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY, - Flags.FLAG_ENABLE_PER_DISPLAY_PACKAGE_CONTEXT_CACHE_IN_STATUSBAR_NOTIF, - Flags.FLAG_ENABLE_PERSISTING_DISPLAY_SIZE_FOR_CONNECTED_DISPLAYS, - Flags.FLAG_ENABLE_PRESENTATION_FOR_CONNECTED_DISPLAYS, - Flags.FLAG_ENABLE_PROJECTED_DISPLAY_DESKTOP_MODE, - Flags.FLAG_ENABLE_QUICKSWITCH_DESKTOP_SPLIT_BUGFIX, - Flags.FLAG_ENABLE_REQUEST_FULLSCREEN_BUGFIX, - Flags.FLAG_ENABLE_RESIZING_METRICS, - Flags.FLAG_ENABLE_RESTART_MENU_FOR_CONNECTED_DISPLAYS, - Flags.FLAG_ENABLE_RESTORE_TO_PREVIOUS_SIZE_FROM_DESKTOP_IMMERSIVE, - Flags.FLAG_ENABLE_SHELL_INITIAL_BOUNDS_REGRESSION_BUG_FIX, - Flags.FLAG_ENABLE_SIZE_COMPAT_MODE_IMPROVEMENTS_FOR_CONNECTED_DISPLAYS, - Flags.FLAG_ENABLE_START_LAUNCH_TRANSITION_FROM_TASKBAR_BUGFIX, - Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS, - Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL, - Flags.FLAG_ENABLE_TASKBAR_CONNECTED_DISPLAYS, - Flags.FLAG_ENABLE_TASKBAR_OVERFLOW, - Flags.FLAG_ENABLE_TASKBAR_RECENTS_LAYOUT_TRANSITION, - Flags.FLAG_ENABLE_THEMED_APP_HEADERS, - Flags.FLAG_ENABLE_TILE_RESIZING, - Flags.FLAG_ENABLE_TOP_VISIBLE_ROOT_TASK_PER_USER_TRACKING, - Flags.FLAG_ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX, - Flags.FLAG_ENABLE_WINDOW_CONTEXT_RESOURCES_UPDATE_ON_CONFIG_CHANGE, - Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS, - Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE, - Flags.FLAG_ENABLE_WINDOWING_SCALED_RESIZING, - Flags.FLAG_ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS, - Flags.FLAG_ENFORCE_EDGE_TO_EDGE, - Flags.FLAG_ENSURE_KEYGUARD_DOES_TRANSITION_STARTING, - Flags.FLAG_ENSURE_WALLPAPER_IN_TRANSITIONS, - Flags.FLAG_ENSURE_WALLPAPER_IN_WEAR_TRANSITIONS, - Flags.FLAG_ENTER_DESKTOP_BY_DEFAULT_ON_FREEFORM_DISPLAYS, - Flags.FLAG_EXCLUDE_CAPTION_FROM_APP_BOUNDS, - Flags.FLAG_EXCLUDE_DRAWING_APP_THEME_SNAPSHOT_FROM_LOCK, - Flags.FLAG_EXCLUDE_TASK_FROM_RECENTS, - Flags.FLAG_FIFO_PRIORITY_FOR_MAJOR_UI_PROCESSES, - Flags.FLAG_FIX_HIDE_OVERLAY_API, - Flags.FLAG_FIX_LAYOUT_EXISTING_TASK, - Flags.FLAG_FIX_VIEW_ROOT_CALL_TRACE, - Flags.FLAG_FORCE_CLOSE_TOP_TRANSPARENT_FULLSCREEN_TASK, - Flags.FLAG_FORM_FACTOR_BASED_DESKTOP_FIRST_SWITCH, - Flags.FLAG_GET_DIMMER_ON_CLOSING, - Flags.FLAG_IGNORE_ASPECT_RATIO_RESTRICTIONS_FOR_RESIZEABLE_FREEFORM_ACTIVITIES, - Flags.FLAG_IGNORE_CORNER_RADIUS_AND_SHADOWS, - Flags.FLAG_INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC, - Flags.FLAG_INHERIT_TASK_BOUNDS_FOR_TRAMPOLINE_TASK_LAUNCHES, - Flags.FLAG_INSETS_DECOUPLED_CONFIGURATION, - Flags.FLAG_JANK_API, - Flags.FLAG_KEEP_APP_WINDOW_HIDE_WHILE_LOCKED, - Flags.FLAG_KEYBOARD_SHORTCUTS_TO_SWITCH_DESKS, - Flags.FLAG_KEYGUARD_GOING_AWAY_TIMEOUT, - Flags.FLAG_LETTERBOX_BACKGROUND_WALLPAPER, - Flags.FLAG_MOVABLE_CUTOUT_CONFIGURATION, - Flags.FLAG_MOVE_TO_EXTERNAL_DISPLAY_SHORTCUT, - Flags.FLAG_MULTI_CROP, - Flags.FLAG_NAV_BAR_TRANSPARENT_BY_DEFAULT, - Flags.FLAG_NESTED_TASKS_WITH_INDEPENDENT_BOUNDS, - Flags.FLAG_NO_CONSECUTIVE_VISIBILITY_EVENTS, - Flags.FLAG_NO_DUPLICATE_SURFACE_DESTROYED_EVENTS, - Flags.FLAG_NO_VISIBILITY_EVENT_ON_DISPLAY_STATE_CHANGE, - Flags.FLAG_OFFLOAD_COLOR_EXTRACTION, - Flags.FLAG_PORT_WINDOW_SIZE_ANIMATION, - Flags.FLAG_PREDICTIVE_BACK_DEFAULT_ENABLE_SDK_36, - Flags.FLAG_PREDICTIVE_BACK_PRIORITY_SYSTEM_NAVIGATION_OBSERVER, - Flags.FLAG_PREDICTIVE_BACK_SWIPE_EDGE_NONE_API, - Flags.FLAG_PREDICTIVE_BACK_SYSTEM_OVERRIDE_CALLBACK, - Flags.FLAG_PREDICTIVE_BACK_THREE_BUTTON_NAV, - Flags.FLAG_PREDICTIVE_BACK_TIMESTAMP_API, - Flags.FLAG_PROCESS_PRIORITY_POLICY_FOR_MULTI_WINDOW_MODE, - Flags.FLAG_REAR_DISPLAY_DISABLE_FORCE_DESKTOP_SYSTEM_DECORATIONS, - Flags.FLAG_RECORD_TASK_SNAPSHOTS_BEFORE_SHUTDOWN, - Flags.FLAG_REDUCE_CHANGED_EXCLUSION_RECTS_MSGS, - Flags.FLAG_REDUCE_KEYGUARD_TRANSITIONS, - Flags.FLAG_REDUCE_TASK_SNAPSHOT_MEMORY_USAGE, - Flags.FLAG_REDUCE_UNNECESSARY_MEASURE, - Flags.FLAG_RELATIVE_INSETS, - Flags.FLAG_RELEASE_SNAPSHOT_AGGRESSIVELY, - Flags.FLAG_RELEASE_USER_ASPECT_RATIO_WM, - Flags.FLAG_REMOVE_ACTIVITY_STARTER_DREAM_CALLBACK, - Flags.FLAG_REMOVE_DEFER_HIDING_CLIENT, - Flags.FLAG_REMOVE_DEPART_TARGET_FROM_MOTION, - Flags.FLAG_REPARENT_WINDOW_TOKEN_API, - Flags.FLAG_RESPECT_NON_TOP_VISIBLE_FIXED_ORIENTATION, - Flags.FLAG_RESPECT_ORIENTATION_CHANGE_FOR_UNRESIZEABLE, - Flags.FLAG_SAFE_REGION_LETTERBOXING, - Flags.FLAG_SAFE_RELEASE_SNAPSHOT_AGGRESSIVELY, - Flags.FLAG_SCHEDULING_FOR_NOTIFICATION_SHADE, - Flags.FLAG_SCRAMBLE_SNAPSHOT_FILE_NAME, - Flags.FLAG_SCREEN_RECORDING_CALLBACKS, - Flags.FLAG_SCROLLING_FROM_LETTERBOX, - Flags.FLAG_SDK_DESIRED_PRESENT_TIME, - Flags.FLAG_SET_SC_PROPERTIES_IN_CLIENT, - Flags.FLAG_SHOW_APP_HANDLE_LARGE_SCREENS, - Flags.FLAG_SHOW_DESKTOP_EXPERIENCE_DEV_OPTION, - Flags.FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, - Flags.FLAG_SHOW_HOME_BEHIND_DESKTOP, - Flags.FLAG_SKIP_COMPAT_UI_EDUCATION_IN_DESKTOP_MODE, - Flags.FLAG_SKIP_DECOR_VIEW_RELAYOUT_WHEN_CLOSING_BUGFIX, - Flags.FLAG_SUPPORT_WIDGET_INTENTS_ON_CONNECTED_DISPLAY, - Flags.FLAG_SUPPORTS_DRAG_ASSISTANT_TO_MULTIWINDOW, - Flags.FLAG_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, - Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER, - Flags.FLAG_SURFACE_TRUSTED_OVERLAY, - Flags.FLAG_SYNC_SCREEN_CAPTURE, - Flags.FLAG_SYSTEM_UI_POST_ANIMATION_END, - Flags.FLAG_TASK_FRAGMENT_SYSTEM_ORGANIZER_FLAG, - Flags.FLAG_TOUCH_PASS_THROUGH_OPT_IN, - Flags.FLAG_TRACK_SYSTEM_UI_CONTEXT_BEFORE_WMS, - Flags.FLAG_TRANSIT_READY_TRACKING, - Flags.FLAG_TRANSIT_TRACKER_PLUMBING, - Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW, - Flags.FLAG_UNIFY_BACK_NAVIGATION_TRANSITION, - Flags.FLAG_UNIVERSAL_RESIZABLE_BY_DEFAULT, - Flags.FLAG_UNTRUSTED_EMBEDDING_ANY_APP_PERMISSION, - Flags.FLAG_UNTRUSTED_EMBEDDING_STATE_SHARING, - Flags.FLAG_UPDATE_DIMS_WHEN_WINDOW_SHOWN, - Flags.FLAG_USE_CACHED_INSETS_FOR_DISPLAY_SWITCH, - Flags.FLAG_USE_RT_FRAME_CALLBACK_FOR_SPLASH_SCREEN_TRANSFER, - Flags.FLAG_USE_TASKS_DIM_ONLY, - Flags.FLAG_USE_VISIBLE_REQUESTED_FOR_PROCESS_TRACKER, - Flags.FLAG_USE_WINDOW_ORIGINAL_TOUCHABLE_REGION_WHEN_MAGNIFICATION_RECOMPUTE_BOUNDS, - Flags.FLAG_VDM_FORCE_APP_UNIVERSAL_RESIZABLE_API, - Flags.FLAG_WALLPAPER_OFFSET_ASYNC, - Flags.FLAG_WLINFO_ONCREATE, - "" - ) + Arrays.asList( + Flags.FLAG_ACTIVITY_SNAPSHOT_BY_DEFAULT, + Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG, + Flags.FLAG_ALLOW_DISABLE_ACTIVITY_RECORD_INPUT_SINK, + Flags.FLAG_ALLOWS_SCREEN_SIZE_DECOUPLED_FROM_STATUS_BAR_AND_CUTOUT, + Flags.FLAG_APP_COMPAT_PROPERTIES_API, + Flags.FLAG_APP_COMPAT_REFACTORING, + Flags.FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG, + Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM, + Flags.FLAG_CONFIGURABLE_FONT_SCALE_DEFAULT, + Flags.FLAG_COVER_DISPLAY_OPT_IN, + Flags.FLAG_DEFER_DISPLAY_UPDATES, + Flags.FLAG_DELEGATE_UNHANDLED_DRAGS, + Flags.FLAG_DELETE_CAPTURE_DISPLAY, + Flags.FLAG_DENSITY_390_API, + Flags.FLAG_DISABLE_OBJECT_POOL, + Flags.FLAG_DRAW_SNAPSHOT_ASPECT_RATIO_MATCH, + Flags.FLAG_ENABLE_BUFFER_TRANSFORM_HINT_FROM_DISPLAY, + Flags.FLAG_ENABLE_SCALED_RESIZING, + Flags.FLAG_ENABLE_WM_EXTENSIONS_FOR_ALL_FLAG, + Flags.FLAG_ENFORCE_EDGE_TO_EDGE, + Flags.FLAG_EXPLICIT_REFRESH_RATE_HINTS, + Flags.FLAG_FIFO_PRIORITY_FOR_MAJOR_UI_PROCESSES, + Flags.FLAG_FIX_NO_CONTAINER_UPDATE_WITHOUT_RESIZE, + Flags.FLAG_GET_DIMMER_ON_CLOSING, + Flags.FLAG_INSETS_CONTROL_SEQ, + Flags.FLAG_INSETS_DECOUPLED_CONFIGURATION, + Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER, + Flags.FLAG_KEYGUARD_APPEAR_TRANSITION, + Flags.FLAG_MOVABLE_CUTOUT_CONFIGURATION, + Flags.FLAG_REAR_DISPLAY_DISABLE_FORCE_DESKTOP_SYSTEM_DECORATIONS, + Flags.FLAG_RELEASE_SNAPSHOT_AGGRESSIVELY, + Flags.FLAG_REMOVE_PREPARE_SURFACE_IN_PLACEMENT, + Flags.FLAG_SCREEN_RECORDING_CALLBACKS, + Flags.FLAG_SDK_DESIRED_PRESENT_TIME, + Flags.FLAG_SECURE_WINDOW_STATE, + Flags.FLAG_SET_SC_PROPERTIES_IN_CLIENT, + Flags.FLAG_SKIP_SLEEPING_WHEN_SWITCHING_DISPLAY, + Flags.FLAG_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, + Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER, + Flags.FLAG_SURFACE_TRUSTED_OVERLAY, + Flags.FLAG_SYNC_SCREEN_CAPTURE, + Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW, + Flags.FLAG_UNTRUSTED_EMBEDDING_ANY_APP_PERMISSION, + Flags.FLAG_UNTRUSTED_EMBEDDING_STATE_SHARING, + Flags.FLAG_WALLPAPER_OFFSET_ASYNC, + Flags.FLAG_WINDOW_SESSION_RELAYOUT_INFO, + "" + ) ); } diff --git a/flags/src/com/android/window/flags2/FakeFeatureFlagsImpl.java b/flags/src/com/android/window/flags2/FakeFeatureFlagsImpl.java index 222ea4b0ec..3fd351436e 100644 --- a/flags/src/com/android/window/flags2/FakeFeatureFlagsImpl.java +++ b/flags/src/com/android/window/flags2/FakeFeatureFlagsImpl.java @@ -3,6 +3,7 @@ package com.android.window.flags2; import java.util.HashMap; import java.util.Map; import java.util.function.Predicate; + /** @hide */ public class FakeFeatureFlagsImpl extends CustomFeatureFlags { private final Map mFlagMap = new HashMap<>(); diff --git a/flags/src/com/android/window/flags2/FeatureFlags.java b/flags/src/com/android/window/flags2/FeatureFlags.java index d8c9510e77..bba4aabe12 100644 --- a/flags/src/com/android/window/flags2/FeatureFlags.java +++ b/flags/src/com/android/window/flags2/FeatureFlags.java @@ -3,1064 +3,212 @@ package com.android.window.flags2; /** @hide */ public interface FeatureFlags { - - - - boolean actionModeEdgeToEdge(); - - - + boolean activityEmbeddingAnimationCustomizationFlag(); - - - - boolean activityEmbeddingDelayTaskFragmentFinishForActivityLaunch(); - - - + boolean activityEmbeddingInteractiveDividerFlag(); - - - - boolean activityEmbeddingMetrics(); - - - - boolean activityEmbeddingSupportForConnectedDisplays(); - - - + + boolean activityEmbeddingOverlayPresentationFlag(); + + boolean activitySnapshotByDefault(); + + boolean activityWindowInfoFlag(); + boolean allowDisableActivityRecordInputSink(); - - - + boolean allowHideScmButton(); - - - + boolean allowsScreenSizeDecoupledFromStatusBarAndCutout(); - - - + + boolean alwaysDeferTransitionWhenApplyWct(); + boolean alwaysDrawMagnificationFullscreenBorder(); - - - + boolean alwaysUpdateWallpaperPermission(); - - - - boolean aodTransition(); - - - - boolean appCompatAsyncRelayout(); - - - + boolean appCompatPropertiesApi(); - - - + boolean appCompatRefactoring(); - - - - boolean appCompatUiFramework(); - - - - boolean appHandleNoRelayoutOnExclusionChange(); - - - - boolean applyLifecycleOnPipChange(); - - - - boolean avoidRebindingIntentionallyDisconnectedWallpaper(); - - - - boolean backupAndRestoreForUserAspectRatioSettings(); - - - - boolean balAdditionalLogging(); - - - - boolean balAdditionalStartModes(); - - - - boolean balClearAllowlistDuration(); - - - + boolean balDontBringExistingBackgroundTaskStackToFg(); - - - + boolean balImproveRealCallerVisibilityCheck(); - - - + boolean balImprovedMetrics(); - - - - boolean balReduceGracePeriod(); - - - + boolean balRequireOptInByPendingIntentCreator(); - - - + + boolean balRequireOptInSameUid(); + boolean balRespectAppSwitchStateWhenCheckBoundByForegroundUid(); - - - - boolean balSendIntentWithOptions(); - - - + + boolean balShowToasts(); + boolean balShowToastsBlocked(); - - - - boolean balStrictModeGracePeriod(); - - - - boolean balStrictModeRo(); - - - - boolean betterSupportNonMatchParentActivity(); - - - - boolean cacheWindowStyle(); - - - + + boolean blastSyncNotificationShadeOnDisplaySwitch(); + + boolean bundleClientTransactionFlag(); + boolean cameraCompatForFreeform(); - - - - boolean cameraCompatFullscreenPickSameTaskActivity(); - - - - boolean checkDisabledSnapshotsInTaskPersister(); - - - - boolean cleanupDispatchPendingTransactionsRemoteException(); - - - - boolean clearSystemVibrator(); - - - + boolean closeToSquareConfigIncludesStatusBar(); - - - - boolean condenseConfigurationChangeForSimpleMode(); - - - + boolean configurableFontScaleDefault(); - - - + boolean coverDisplayOptIn(); - - - + + boolean deferDisplayUpdates(); + boolean delayNotificationToMagnificationWhenRecentsWindowToFrontTransition(); - - - - boolean delegateBackGestureToShell(); - - - + boolean delegateUnhandledDrags(); - - - + boolean deleteCaptureDisplay(); - - - + boolean density390Api(); - - - - boolean disableDesktopLaunchParamsOutsideDesktopBugFix(); - - - - boolean disableNonResizableAppSnapResizing(); - - - - boolean disableOptOutEdgeToEdge(); - - - + + boolean disableObjectPool(); + + boolean disableThinLetterboxingPolicy(); + boolean doNotCheckIntersectionWhenNonMagnifiableWindowTransitions(); - - - - boolean earlyLaunchHint(); - - - + + boolean drawSnapshotAspectRatioMatch(); + boolean edgeToEdgeByDefault(); - - - - boolean enableAccessibleCustomHeaders(); - - - - boolean enableActivityEmbeddingSupportForConnectedDisplays(); - - - + + boolean embeddedActivityBackNavFlag(); + + boolean enableAdditionalWindowsAboveStatusBar(); + boolean enableAppHeaderWithTaskDensity(); - - - - boolean enableBorderSettings(); - - - + boolean enableBufferTransformHintFromDisplay(); - - - - boolean enableBugFixesForSecondaryDisplay(); - - - + boolean enableCameraCompatForDesktopWindowing(); - - - - boolean enableCameraCompatForDesktopWindowingOptOut(); - - - - boolean enableCameraCompatForDesktopWindowingOptOutApi(); - - - - boolean enableCameraCompatTrackTaskAndAppBugfix(); - - - - boolean enableCaptionCompatInsetConversion(); - - - - boolean enableCaptionCompatInsetForceConsumption(); - - - - boolean enableCaptionCompatInsetForceConsumptionAlways(); - - - - boolean enableCascadingWindows(); - - - - boolean enableCompatUiVisibilityStatus(); - - - + boolean enableCompatuiSysuiLauncher(); - - - - boolean enableConnectedDisplaysDnd(); - - - - boolean enableConnectedDisplaysPip(); - - - - boolean enableConnectedDisplaysWindowDrag(); - - - - boolean enableDesktopAppHandleAnimation(); - - - - boolean enableDesktopAppLaunchAlttabTransitions(); - - - - boolean enableDesktopAppLaunchAlttabTransitionsBugfix(); - - - - boolean enableDesktopAppLaunchTransitions(); - - - - boolean enableDesktopAppLaunchTransitionsBugfix(); - - - - boolean enableDesktopCloseShortcutBugfix(); - - - - boolean enableDesktopCloseTaskAnimationInDtcBugfix(); - - - - boolean enableDesktopImeBugfix(); - - - - boolean enableDesktopImmersiveDragBugfix(); - - - - boolean enableDesktopIndicatorInSeparateThreadBugfix(); - - - - boolean enableDesktopModeThroughDevOption(); - - - - boolean enableDesktopOpeningDeeplinkMinimizeAnimationBugfix(); - - - - boolean enableDesktopRecentsTransitionsCornersBugfix(); - - - - boolean enableDesktopSwipeBackMinimizeAnimationBugfix(); - - - - boolean enableDesktopSystemDialogsTransitions(); - - - - boolean enableDesktopTabTearingMinimizeAnimationBugfix(); - - - - boolean enableDesktopTaskbarOnFreeformDisplays(); - - - - boolean enableDesktopTrampolineCloseAnimationBugfix(); - - - - boolean enableDesktopWallpaperActivityForSystemUser(); - - - - boolean enableDesktopWindowingAppHandleEducation(); - - - - boolean enableDesktopWindowingAppToWeb(); - - - - boolean enableDesktopWindowingAppToWebEducation(); - - - - boolean enableDesktopWindowingAppToWebEducationIntegration(); - - - - boolean enableDesktopWindowingBackNavigation(); - - - - boolean enableDesktopWindowingEnterTransitionBugfix(); - - - - boolean enableDesktopWindowingEnterTransitions(); - - - - boolean enableDesktopWindowingExitByMinimizeTransitionBugfix(); - - - - boolean enableDesktopWindowingExitTransitions(); - - - - boolean enableDesktopWindowingExitTransitionsBugfix(); - - - - boolean enableDesktopWindowingHsum(); - - - + boolean enableDesktopWindowingImmersiveHandleHiding(); - - - + boolean enableDesktopWindowingModalsPolicy(); - - - + boolean enableDesktopWindowingMode(); - - - - boolean enableDesktopWindowingMultiInstanceFeatures(); - - - - boolean enableDesktopWindowingPersistence(); - - - - boolean enableDesktopWindowingPip(); - - - + boolean enableDesktopWindowingQuickSwitch(); - - - - boolean enableDesktopWindowingScvhCacheBugFix(); - - - + + boolean enableDesktopWindowingScvhCache(); + boolean enableDesktopWindowingSizeConstraints(); - - - + boolean enableDesktopWindowingTaskLimit(); - - - + boolean enableDesktopWindowingTaskbarRunningApps(); - - - - boolean enableDesktopWindowingTransitions(); - - - + boolean enableDesktopWindowingWallpaperActivity(); - - - - boolean enableDeviceStateAutoRotateSettingLogging(); - - - - boolean enableDeviceStateAutoRotateSettingRefactor(); - - - - boolean enableDisplayDisconnectInteraction(); - - - - boolean enableDisplayFocusInShellTransitions(); - - - - boolean enableDisplayReconnectInteraction(); - - - - boolean enableDisplayWindowingModeSwitching(); - - - - boolean enableDragResizeSetUpInBgThread(); - - - - boolean enableDragToDesktopIncomingTransitionsBugfix(); - - - - boolean enableDragToMaximize(); - - - - boolean enableDynamicRadiusComputationBugfix(); - - - - boolean enableFullScreenWindowOnRemovingSplitScreenStageBugfix(); - - - - boolean enableFullyImmersiveInDesktop(); - - - - boolean enableHandleInputFix(); - - - - boolean enableHoldToDragAppHandle(); - - - - boolean enableInputLayerTransitionFix(); - - - - boolean enableMinimizeButton(); - - - - boolean enableModalsFullscreenWithPermission(); - - - - boolean enableMoveToNextDisplayShortcut(); - - - - boolean enableMultiDisplaySplit(); - - - - boolean enableMultidisplayTrackpadBackGesture(); - - - - boolean enableMultipleDesktopsBackend(); - - - - boolean enableMultipleDesktopsFrontend(); - - - - boolean enableNonDefaultDisplaySplit(); - - - - boolean enableOpaqueBackgroundForTransparentWindows(); - - - - boolean enablePerDisplayDesktopWallpaperActivity(); - - - - boolean enablePerDisplayPackageContextCacheInStatusbarNotif(); - - - - boolean enablePersistingDisplaySizeForConnectedDisplays(); - - - - boolean enablePresentationForConnectedDisplays(); - - - - boolean enableProjectedDisplayDesktopMode(); - - - - boolean enableQuickswitchDesktopSplitBugfix(); - - - - boolean enableRequestFullscreenBugfix(); - - - - boolean enableResizingMetrics(); - - - - boolean enableRestartMenuForConnectedDisplays(); - - - - boolean enableRestoreToPreviousSizeFromDesktopImmersive(); - - - - boolean enableShellInitialBoundsRegressionBugFix(); - - - - boolean enableSizeCompatModeImprovementsForConnectedDisplays(); - - - - boolean enableStartLaunchTransitionFromTaskbarBugfix(); - - - - boolean enableTaskResizingKeyboardShortcuts(); - - - + + boolean enableScaledResizing(); + boolean enableTaskStackObserverInShell(); - - - - boolean enableTaskbarConnectedDisplays(); - - - - boolean enableTaskbarOverflow(); - - - - boolean enableTaskbarRecentsLayoutTransition(); - - - + boolean enableThemedAppHeaders(); - - - - boolean enableTileResizing(); - - - - boolean enableTopVisibleRootTaskPerUserTracking(); - - - - boolean enableVisualIndicatorInTransitionBugfix(); - - - - boolean enableWindowContextResourcesUpdateOnConfigChange(); - - - + boolean enableWindowingDynamicInitialBounds(); - - - + boolean enableWindowingEdgeDragResize(); - - - - boolean enableWindowingScaledResizing(); - - - - boolean enableWindowingTransitionHandlersObservers(); - - - + + boolean enableWmExtensionsForAllFlag(); + boolean enforceEdgeToEdge(); - - - - boolean ensureKeyguardDoesTransitionStarting(); - - - + boolean ensureWallpaperInTransitions(); - - - - boolean ensureWallpaperInWearTransitions(); - - - - boolean enterDesktopByDefaultOnFreeformDisplays(); - - - - boolean excludeCaptionFromAppBounds(); - - - - boolean excludeDrawingAppThemeSnapshotFromLock(); - - - - boolean excludeTaskFromRecents(); - - - + + boolean explicitRefreshRateHints(); + boolean fifoPriorityForMajorUiProcesses(); - - - - boolean fixHideOverlayApi(); - - - - boolean fixLayoutExistingTask(); - - - - boolean fixViewRootCallTrace(); - - - - boolean forceCloseTopTransparentFullscreenTask(); - - - - boolean formFactorBasedDesktopFirstSwitch(); - - - + + boolean fixNoContainerUpdateWithoutResize(); + + boolean fixPipRestoreToOverlay(); + + boolean fullscreenDimFlag(); + boolean getDimmerOnClosing(); - - - - boolean ignoreAspectRatioRestrictionsForResizeableFreeformActivities(); - - - - boolean ignoreCornerRadiusAndShadows(); - - - - boolean includeTopTransparentFullscreenTaskInDesktopHeuristic(); - - - - boolean inheritTaskBoundsForTrampolineTaskLaunches(); - - - + + boolean immersiveAppRepositioning(); + + boolean insetsControlChangedItem(); + + boolean insetsControlSeq(); + boolean insetsDecoupledConfiguration(); - - - - boolean jankApi(); - - - - boolean keepAppWindowHideWhileLocked(); - - - - boolean keyboardShortcutsToSwitchDesks(); - - - - boolean keyguardGoingAwayTimeout(); - - - + + boolean introduceSmootherDimmer(); + + boolean keyguardAppearTransition(); + boolean letterboxBackgroundWallpaper(); - - - + boolean movableCutoutConfiguration(); - - - - boolean moveToExternalDisplayShortcut(); - - - + + boolean moveAnimationOptionsToChange(); + boolean multiCrop(); - - - + boolean navBarTransparentByDefault(); - - - - boolean nestedTasksWithIndependentBounds(); - - - + boolean noConsecutiveVisibilityEvents(); - - - - boolean noDuplicateSurfaceDestroyedEvents(); - - - + boolean noVisibilityEventOnDisplayStateChange(); - - - + boolean offloadColorExtraction(); - - - - boolean portWindowSizeAnimation(); - - - - boolean predictiveBackDefaultEnableSdk36(); - - - - boolean predictiveBackPrioritySystemNavigationObserver(); - - - - boolean predictiveBackSwipeEdgeNoneApi(); - - - - boolean predictiveBackSystemOverrideCallback(); - - - - boolean predictiveBackThreeButtonNav(); - - - - boolean predictiveBackTimestampApi(); - - - - boolean processPriorityPolicyForMultiWindowMode(); - - - + + boolean predictiveBackSystemAnims(); + boolean rearDisplayDisableForceDesktopSystemDecorations(); - - - - boolean recordTaskSnapshotsBeforeShutdown(); - - - - boolean reduceChangedExclusionRectsMsgs(); - - - - boolean reduceKeyguardTransitions(); - - - - boolean reduceTaskSnapshotMemoryUsage(); - - - - boolean reduceUnnecessaryMeasure(); - - - - boolean relativeInsets(); - - - + boolean releaseSnapshotAggressively(); - - - - boolean releaseUserAspectRatioWm(); - - - - boolean removeActivityStarterDreamCallback(); - - - - boolean removeDeferHidingClient(); - - - - boolean removeDepartTargetFromMotion(); - - - - boolean reparentWindowTokenApi(); - - - - boolean respectNonTopVisibleFixedOrientation(); - - - - boolean respectOrientationChangeForUnresizeable(); - - - - boolean safeRegionLetterboxing(); - - - - boolean safeReleaseSnapshotAggressively(); - - - - boolean schedulingForNotificationShade(); - - - - boolean scrambleSnapshotFileName(); - - - + + boolean removePrepareSurfaceInPlacement(); + boolean screenRecordingCallbacks(); - - - - boolean scrollingFromLetterbox(); - - - + boolean sdkDesiredPresentTime(); - - - + + boolean secureWindowState(); + boolean setScPropertiesInClient(); - - - - boolean showAppHandleLargeScreens(); - - - - boolean showDesktopExperienceDevOption(); - - - - boolean showDesktopWindowingDevOption(); - - - - boolean showHomeBehindDesktop(); - - - - boolean skipCompatUiEducationInDesktopMode(); - - - - boolean skipDecorViewRelayoutWhenClosingBugfix(); - - - - boolean supportWidgetIntentsOnConnectedDisplay(); - - - - boolean supportsDragAssistantToMultiwindow(); - - - + + boolean skipSleepingWhenSwitchingDisplay(); + boolean supportsMultiInstanceSystemUi(); - - - + boolean surfaceControlInputReceiver(); - - - + boolean surfaceTrustedOverlay(); - - - + boolean syncScreenCapture(); - - - - boolean systemUiPostAnimationEnd(); - - - + boolean taskFragmentSystemOrganizerFlag(); - - - - boolean touchPassThroughOptIn(); - - - - boolean trackSystemUiContextBeforeWms(); - - - + boolean transitReadyTracking(); - - - - boolean transitTrackerPlumbing(); - - - + boolean trustedPresentationListenerForWindow(); - - - - boolean unifyBackNavigationTransition(); - - - - boolean universalResizableByDefault(); - - - + boolean untrustedEmbeddingAnyAppPermission(); - - - + boolean untrustedEmbeddingStateSharing(); - - - - boolean updateDimsWhenWindowShown(); - - - - boolean useCachedInsetsForDisplaySwitch(); - - - - boolean useRtFrameCallbackForSplashScreenTransfer(); - - - - boolean useTasksDimOnly(); - - - - boolean useVisibleRequestedForProcessTracker(); - - - + boolean useWindowOriginalTouchableRegionWhenMagnificationRecomputeBounds(); - - - - boolean vdmForceAppUniversalResizableApi(); - - - + + boolean userMinAspectRatioAppDefault(); + + boolean waitForTransitionOnDisplaySwitch(); + boolean wallpaperOffsetAsync(); - - - - boolean wlinfoOncreate(); + + boolean windowSessionRelayoutInfo(); + + boolean windowTokenConfigThreadSafe(); } diff --git a/flags/src/com/android/window/flags2/FeatureFlagsImpl.java b/flags/src/com/android/window/flags2/FeatureFlagsImpl.java index 142a639298..56df1ad9a1 100644 --- a/flags/src/com/android/window/flags2/FeatureFlagsImpl.java +++ b/flags/src/com/android/window/flags2/FeatureFlagsImpl.java @@ -1,1860 +1,1638 @@ package com.android.window.flags2; // TODO(b/303773055): Remove the annotation after access issue is resolved. + +import com.android.quickstep.util.DeviceConfigHelper; + +import java.nio.file.Files; +import java.nio.file.Paths; /** @hide */ public final class FeatureFlagsImpl implements FeatureFlags { - @Override + private static final boolean isReadFromNew = Files.exists(Paths.get("/metadata/aconfig/boot/enable_only_new_storage")); + private static volatile boolean isCached = false; + private static volatile boolean accessibility_is_cached = false; + private static volatile boolean large_screen_experiences_app_compat_is_cached = false; + private static volatile boolean lse_desktop_experience_is_cached = false; + private static volatile boolean multitasking_is_cached = false; + private static volatile boolean responsible_apis_is_cached = false; + private static volatile boolean systemui_is_cached = false; + private static volatile boolean wear_frameworks_is_cached = false; + private static volatile boolean window_surfaces_is_cached = false; + private static volatile boolean windowing_frontend_is_cached = false; + private static volatile boolean windowing_sdk_is_cached = false; + private static boolean activityEmbeddingAnimationCustomizationFlag = false; + private static boolean activityEmbeddingInteractiveDividerFlag = true; + private static boolean activityEmbeddingOverlayPresentationFlag = true; + private static boolean allowHideScmButton = true; + private static boolean alwaysDeferTransitionWhenApplyWct = true; + private static boolean alwaysDrawMagnificationFullscreenBorder = true; + private static boolean alwaysUpdateWallpaperPermission = true; + private static boolean balDontBringExistingBackgroundTaskStackToFg = true; + private static boolean balImproveRealCallerVisibilityCheck = true; + private static boolean balImprovedMetrics = true; + private static boolean balRequireOptInByPendingIntentCreator = true; + private static boolean balRequireOptInSameUid = false; + private static boolean balRespectAppSwitchStateWhenCheckBoundByForegroundUid = true; + private static boolean balShowToasts = false; + private static boolean balShowToastsBlocked = true; + private static boolean blastSyncNotificationShadeOnDisplaySwitch = true; + private static boolean closeToSquareConfigIncludesStatusBar = false; + private static boolean delayNotificationToMagnificationWhenRecentsWindowToFrontTransition = true; + private static boolean disableThinLetterboxingPolicy = true; + private static boolean doNotCheckIntersectionWhenNonMagnifiableWindowTransitions = true; + private static boolean edgeToEdgeByDefault = false; + private static boolean embeddedActivityBackNavFlag = true; + private static boolean enableAdditionalWindowsAboveStatusBar = false; + private static boolean enableAppHeaderWithTaskDensity = true; + private static boolean enableCameraCompatForDesktopWindowing = true; + private static boolean enableCompatuiSysuiLauncher = false; + private static boolean enableDesktopWindowingImmersiveHandleHiding = false; + private static boolean enableDesktopWindowingModalsPolicy = true; + private static boolean enableDesktopWindowingMode = false; + private static boolean enableDesktopWindowingQuickSwitch = false; + private static boolean enableDesktopWindowingScvhCache = false; + private static boolean enableDesktopWindowingSizeConstraints = false; + private static boolean enableDesktopWindowingTaskLimit = true; + private static boolean enableDesktopWindowingTaskbarRunningApps = true; + private static boolean enableDesktopWindowingWallpaperActivity = false; + private static boolean enableTaskStackObserverInShell = true; + private static boolean enableThemedAppHeaders = true; + private static boolean enableWindowingDynamicInitialBounds = false; + private static boolean enableWindowingEdgeDragResize = false; + private static boolean ensureWallpaperInTransitions = false; + private static boolean fixPipRestoreToOverlay = true; + private static boolean fullscreenDimFlag = true; + private static boolean immersiveAppRepositioning = true; + private static boolean insetsControlChangedItem = false; + private static boolean letterboxBackgroundWallpaper = false; + private static boolean moveAnimationOptionsToChange = true; + private static boolean multiCrop = true; + private static boolean navBarTransparentByDefault = true; + private static boolean noConsecutiveVisibilityEvents = true; + private static boolean noVisibilityEventOnDisplayStateChange = true; + private static boolean offloadColorExtraction = false; + private static boolean predictiveBackSystemAnims = true; + private static boolean taskFragmentSystemOrganizerFlag = true; + private static boolean transitReadyTracking = false; + private static boolean useWindowOriginalTouchableRegionWhenMagnificationRecomputeBounds = true; + private static boolean userMinAspectRatioAppDefault = true; + private static boolean waitForTransitionOnDisplaySwitch = true; + private static boolean windowTokenConfigThreadSafe = true; - public boolean actionModeEdgeToEdge() { - return false; + private void init() { + isCached = true; + } + + + + + private void load_overrides_accessibility() { + try { + var properties = DeviceConfigHelper.Companion.getPrefs(); + alwaysDrawMagnificationFullscreenBorder = + properties.getBoolean(Flags.FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER, true); + delayNotificationToMagnificationWhenRecentsWindowToFrontTransition = + properties.getBoolean(Flags.FLAG_DELAY_NOTIFICATION_TO_MAGNIFICATION_WHEN_RECENTS_WINDOW_TO_FRONT_TRANSITION, true); + doNotCheckIntersectionWhenNonMagnifiableWindowTransitions = + properties.getBoolean(Flags.FLAG_DO_NOT_CHECK_INTERSECTION_WHEN_NON_MAGNIFIABLE_WINDOW_TRANSITIONS, true); + useWindowOriginalTouchableRegionWhenMagnificationRecomputeBounds = + properties.getBoolean(Flags.FLAG_USE_WINDOW_ORIGINAL_TOUCHABLE_REGION_WHEN_MAGNIFICATION_RECOMPUTE_BOUNDS, true); + } catch (NullPointerException e) { + throw new RuntimeException( + "Cannot read value from namespace accessibility " + + "from DeviceConfig. It could be that the code using flag " + + "executed before SettingsProvider initialization. Please use " + + "fixed read-only flag by adding is_fixed_read_only: true in " + + "flag declaration.", + e + ); + } catch (SecurityException e) { + // for isolated process case, skip loading flag value from the storage, use the default + } + accessibility_is_cached = true; + } + + private void load_overrides_large_screen_experiences_app_compat() { + try { + var properties = DeviceConfigHelper.Companion.getPrefs(); + allowHideScmButton = + properties.getBoolean(Flags.FLAG_ALLOW_HIDE_SCM_BUTTON, true); + disableThinLetterboxingPolicy = + properties.getBoolean(Flags.FLAG_DISABLE_THIN_LETTERBOXING_POLICY, true); + enableCompatuiSysuiLauncher = + properties.getBoolean(Flags.FLAG_ENABLE_COMPATUI_SYSUI_LAUNCHER, false); + immersiveAppRepositioning = + properties.getBoolean(Flags.FLAG_IMMERSIVE_APP_REPOSITIONING, true); + letterboxBackgroundWallpaper = + properties.getBoolean(Flags.FLAG_LETTERBOX_BACKGROUND_WALLPAPER, false); + userMinAspectRatioAppDefault = + properties.getBoolean(Flags.FLAG_USER_MIN_ASPECT_RATIO_APP_DEFAULT, true); + } catch (NullPointerException e) { + throw new RuntimeException( + "Cannot read value from namespace large_screen_experiences_app_compat " + + "from DeviceConfig. It could be that the code using flag " + + "executed before SettingsProvider initialization. Please use " + + "fixed read-only flag by adding is_fixed_read_only: true in " + + "flag declaration.", + e + ); + } catch (SecurityException e) { + // for isolated process case, skip loading flag value from the storage, use the default + } + large_screen_experiences_app_compat_is_cached = true; + } + + private void load_overrides_lse_desktop_experience() { + try { + var properties = DeviceConfigHelper.Companion.getPrefs(); + enableAdditionalWindowsAboveStatusBar = + properties.getBoolean(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR, false); + enableAppHeaderWithTaskDensity = + properties.getBoolean(Flags.FLAG_ENABLE_APP_HEADER_WITH_TASK_DENSITY, true); + enableCameraCompatForDesktopWindowing = + properties.getBoolean(Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING, true); + enableDesktopWindowingImmersiveHandleHiding = + properties.getBoolean(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_IMMERSIVE_HANDLE_HIDING, false); + enableDesktopWindowingModalsPolicy = + properties.getBoolean(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY, true); + enableDesktopWindowingMode = + properties.getBoolean(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE, true); + enableDesktopWindowingQuickSwitch = + properties.getBoolean(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_QUICK_SWITCH, false); + enableDesktopWindowingScvhCache = + properties.getBoolean(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_SCVH_CACHE, false); + enableDesktopWindowingSizeConstraints = + properties.getBoolean(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_SIZE_CONSTRAINTS, false); + enableDesktopWindowingTaskLimit = + properties.getBoolean(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TASK_LIMIT, true); + enableDesktopWindowingTaskbarRunningApps = + properties.getBoolean(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS, true); + enableDesktopWindowingWallpaperActivity = + properties.getBoolean(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, false); + enableTaskStackObserverInShell = + properties.getBoolean(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL, true); + enableThemedAppHeaders = + properties.getBoolean(Flags.FLAG_ENABLE_THEMED_APP_HEADERS, true); + enableWindowingDynamicInitialBounds = + properties.getBoolean(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS, false); + enableWindowingEdgeDragResize = + properties.getBoolean(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE, false); + } catch (NullPointerException e) { + throw new RuntimeException( + "Cannot read value from namespace lse_desktop_experience " + + "from DeviceConfig. It could be that the code using flag " + + "executed before SettingsProvider initialization. Please use " + + "fixed read-only flag by adding is_fixed_read_only: true in " + + "flag declaration.", + e + ); + } catch (SecurityException e) { + // for isolated process case, skip loading flag value from the storage, use the default + } + lse_desktop_experience_is_cached = true; + } + + private void load_overrides_multitasking() { + try { + var properties = DeviceConfigHelper.Companion.getPrefs(); + } catch (NullPointerException e) { + throw new RuntimeException( + "Cannot read value from namespace multitasking " + + "from DeviceConfig. It could be that the code using flag " + + "executed before SettingsProvider initialization. Please use " + + "fixed read-only flag by adding is_fixed_read_only: true in " + + "flag declaration.", + e + ); + } catch (SecurityException e) { + // for isolated process case, skip loading flag value from the storage, use the default + } + multitasking_is_cached = true; + } + + private void load_overrides_responsible_apis() { + try { + var properties = DeviceConfigHelper.Companion.getPrefs(); + balDontBringExistingBackgroundTaskStackToFg = + properties.getBoolean(Flags.FLAG_BAL_DONT_BRING_EXISTING_BACKGROUND_TASK_STACK_TO_FG, true); + balImproveRealCallerVisibilityCheck = + properties.getBoolean(Flags.FLAG_BAL_IMPROVE_REAL_CALLER_VISIBILITY_CHECK, true); + balImprovedMetrics = + properties.getBoolean(Flags.FLAG_BAL_IMPROVED_METRICS, true); + balRequireOptInByPendingIntentCreator = + properties.getBoolean(Flags.FLAG_BAL_REQUIRE_OPT_IN_BY_PENDING_INTENT_CREATOR, true); + balRequireOptInSameUid = + properties.getBoolean(Flags.FLAG_BAL_REQUIRE_OPT_IN_SAME_UID, false); + balRespectAppSwitchStateWhenCheckBoundByForegroundUid = + properties.getBoolean(Flags.FLAG_BAL_RESPECT_APP_SWITCH_STATE_WHEN_CHECK_BOUND_BY_FOREGROUND_UID, true); + balShowToasts = + properties.getBoolean(Flags.FLAG_BAL_SHOW_TOASTS, false); + balShowToastsBlocked = + properties.getBoolean(Flags.FLAG_BAL_SHOW_TOASTS_BLOCKED, true); + } catch (NullPointerException e) { + throw new RuntimeException( + "Cannot read value from namespace responsible_apis " + + "from DeviceConfig. It could be that the code using flag " + + "executed before SettingsProvider initialization. Please use " + + "fixed read-only flag by adding is_fixed_read_only: true in " + + "flag declaration.", + e + ); + } catch (SecurityException e) { + // for isolated process case, skip loading flag value from the storage, use the default + } + responsible_apis_is_cached = true; + } + + private void load_overrides_systemui() { + try { + var properties = DeviceConfigHelper.Companion.getPrefs(); + multiCrop = + properties.getBoolean(Flags.FLAG_MULTI_CROP, true); + noConsecutiveVisibilityEvents = + properties.getBoolean(Flags.FLAG_NO_CONSECUTIVE_VISIBILITY_EVENTS, true); + offloadColorExtraction = + properties.getBoolean(Flags.FLAG_OFFLOAD_COLOR_EXTRACTION, false); + predictiveBackSystemAnims = + properties.getBoolean(Flags.FLAG_PREDICTIVE_BACK_SYSTEM_ANIMS, true); + } catch (NullPointerException e) { + throw new RuntimeException( + "Cannot read value from namespace systemui " + + "from DeviceConfig. It could be that the code using flag " + + "executed before SettingsProvider initialization. Please use " + + "fixed read-only flag by adding is_fixed_read_only: true in " + + "flag declaration.", + e + ); + } catch (SecurityException e) { + // for isolated process case, skip loading flag value from the storage, use the default + } + systemui_is_cached = true; + } + + private void load_overrides_wear_frameworks() { + try { + var properties = DeviceConfigHelper.Companion.getPrefs(); + alwaysUpdateWallpaperPermission = + properties.getBoolean(Flags.FLAG_ALWAYS_UPDATE_WALLPAPER_PERMISSION, true); + noVisibilityEventOnDisplayStateChange = + properties.getBoolean(Flags.FLAG_NO_VISIBILITY_EVENT_ON_DISPLAY_STATE_CHANGE, true); + } catch (NullPointerException e) { + throw new RuntimeException( + "Cannot read value from namespace wear_frameworks " + + "from DeviceConfig. It could be that the code using flag " + + "executed before SettingsProvider initialization. Please use " + + "fixed read-only flag by adding is_fixed_read_only: true in " + + "flag declaration.", + e + ); + } catch (SecurityException e) { + // for isolated process case, skip loading flag value from the storage, use the default + } + wear_frameworks_is_cached = true; + } + + private void load_overrides_window_surfaces() { + try { + var properties = DeviceConfigHelper.Companion.getPrefs(); + } catch (NullPointerException e) { + throw new RuntimeException( + "Cannot read value from namespace window_surfaces " + + "from DeviceConfig. It could be that the code using flag " + + "executed before SettingsProvider initialization. Please use " + + "fixed read-only flag by adding is_fixed_read_only: true in " + + "flag declaration.", + e + ); + } catch (SecurityException e) { + // for isolated process case, skip loading flag value from the storage, use the default + } + window_surfaces_is_cached = true; + } + + private void load_overrides_windowing_frontend() { + try { + var properties = DeviceConfigHelper.Companion.getPrefs(); + blastSyncNotificationShadeOnDisplaySwitch = + properties.getBoolean(Flags.FLAG_BLAST_SYNC_NOTIFICATION_SHADE_ON_DISPLAY_SWITCH, true); + closeToSquareConfigIncludesStatusBar = + properties.getBoolean(Flags.FLAG_CLOSE_TO_SQUARE_CONFIG_INCLUDES_STATUS_BAR, false); + edgeToEdgeByDefault = + properties.getBoolean(Flags.FLAG_EDGE_TO_EDGE_BY_DEFAULT, false); + ensureWallpaperInTransitions = + properties.getBoolean(Flags.FLAG_ENSURE_WALLPAPER_IN_TRANSITIONS, false); + navBarTransparentByDefault = + properties.getBoolean(Flags.FLAG_NAV_BAR_TRANSPARENT_BY_DEFAULT, true); + transitReadyTracking = + properties.getBoolean(Flags.FLAG_TRANSIT_READY_TRACKING, false); + waitForTransitionOnDisplaySwitch = + properties.getBoolean(Flags.FLAG_WAIT_FOR_TRANSITION_ON_DISPLAY_SWITCH, true); + } catch (NullPointerException e) { + throw new RuntimeException( + "Cannot read value from namespace windowing_frontend " + + "from DeviceConfig. It could be that the code using flag " + + "executed before SettingsProvider initialization. Please use " + + "fixed read-only flag by adding is_fixed_read_only: true in " + + "flag declaration.", + e + ); + } catch (SecurityException e) { + // for isolated process case, skip loading flag value from the storage, use the default + } + windowing_frontend_is_cached = true; + } + + private void load_overrides_windowing_sdk() { + try { + var properties = DeviceConfigHelper.Companion.getPrefs(); + activityEmbeddingAnimationCustomizationFlag = + properties.getBoolean(Flags.FLAG_ACTIVITY_EMBEDDING_ANIMATION_CUSTOMIZATION_FLAG, false); + activityEmbeddingInteractiveDividerFlag = + properties.getBoolean(Flags.FLAG_ACTIVITY_EMBEDDING_INTERACTIVE_DIVIDER_FLAG, true); + activityEmbeddingOverlayPresentationFlag = + properties.getBoolean(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG, true); + alwaysDeferTransitionWhenApplyWct = + properties.getBoolean(Flags.FLAG_ALWAYS_DEFER_TRANSITION_WHEN_APPLY_WCT, true); + embeddedActivityBackNavFlag = + properties.getBoolean(Flags.FLAG_EMBEDDED_ACTIVITY_BACK_NAV_FLAG, true); + fixPipRestoreToOverlay = + properties.getBoolean(Flags.FLAG_FIX_PIP_RESTORE_TO_OVERLAY, true); + fullscreenDimFlag = + properties.getBoolean(Flags.FLAG_FULLSCREEN_DIM_FLAG, true); + insetsControlChangedItem = + properties.getBoolean(Flags.FLAG_INSETS_CONTROL_CHANGED_ITEM, false); + moveAnimationOptionsToChange = + properties.getBoolean(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE, true); + taskFragmentSystemOrganizerFlag = + properties.getBoolean(Flags.FLAG_TASK_FRAGMENT_SYSTEM_ORGANIZER_FLAG, true); + windowTokenConfigThreadSafe = + properties.getBoolean(Flags.FLAG_WINDOW_TOKEN_CONFIG_THREAD_SAFE, true); + } catch (NullPointerException e) { + throw new RuntimeException( + "Cannot read value from namespace windowing_sdk " + + "from DeviceConfig. It could be that the code using flag " + + "executed before SettingsProvider initialization. Please use " + + "fixed read-only flag by adding is_fixed_read_only: true in " + + "flag declaration.", + e + ); + } catch (SecurityException e) { + // for isolated process case, skip loading flag value from the storage, use the default + } + windowing_sdk_is_cached = true; } @Override - - + public boolean activityEmbeddingAnimationCustomizationFlag() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!windowing_sdk_is_cached) { + load_overrides_windowing_sdk(); + } + } + return activityEmbeddingAnimationCustomizationFlag; + } @Override - - - public boolean activityEmbeddingDelayTaskFragmentFinishForActivityLaunch() { - return false; - } - - @Override - - + public boolean activityEmbeddingInteractiveDividerFlag() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!windowing_sdk_is_cached) { + load_overrides_windowing_sdk(); + } + } + return activityEmbeddingInteractiveDividerFlag; + + } + + @Override + + public boolean activityEmbeddingOverlayPresentationFlag() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!windowing_sdk_is_cached) { + load_overrides_windowing_sdk(); + } + } + return activityEmbeddingOverlayPresentationFlag; + + } + + @Override + + public boolean activitySnapshotByDefault() { return true; + } @Override + + public boolean activityWindowInfoFlag() { + return true; - - public boolean activityEmbeddingMetrics() { - return false; } @Override - - - public boolean activityEmbeddingSupportForConnectedDisplays() { - return false; - } - - @Override - - + public boolean allowDisableActivityRecordInputSink() { return true; + } @Override - - + public boolean allowHideScmButton() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!large_screen_experiences_app_compat_is_cached) { + load_overrides_large_screen_experiences_app_compat(); + } + } + return allowHideScmButton; + } @Override - - + public boolean allowsScreenSizeDecoupledFromStatusBarAndCutout() { return true; + } @Override + + public boolean alwaysDeferTransitionWhenApplyWct() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!windowing_sdk_is_cached) { + load_overrides_windowing_sdk(); + } + } + return alwaysDeferTransitionWhenApplyWct; + } + @Override + public boolean alwaysDrawMagnificationFullscreenBorder() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!accessibility_is_cached) { + load_overrides_accessibility(); + } + } + return alwaysDrawMagnificationFullscreenBorder; + } @Override - - + public boolean alwaysUpdateWallpaperPermission() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!wear_frameworks_is_cached) { + load_overrides_wear_frameworks(); + } + } + return alwaysUpdateWallpaperPermission; + } @Override - - - public boolean aodTransition() { - return false; - } - - @Override - - - public boolean appCompatAsyncRelayout() { - return false; - } - - @Override - - + public boolean appCompatPropertiesApi() { return true; + } @Override - - + public boolean appCompatRefactoring() { return false; + } @Override - - - public boolean appCompatUiFramework() { - return false; - } - - @Override - - - public boolean appHandleNoRelayoutOnExclusionChange() { - return false; - } - - @Override - - - public boolean applyLifecycleOnPipChange() { - return false; - } - - @Override - - - public boolean avoidRebindingIntentionallyDisconnectedWallpaper() { - return true; - } - - @Override - - - public boolean backupAndRestoreForUserAspectRatioSettings() { - return false; - } - - @Override - - - public boolean balAdditionalLogging() { - return false; - } - - @Override - - - public boolean balAdditionalStartModes() { - return true; - } - - @Override - - - public boolean balClearAllowlistDuration() { - return false; - } - - @Override - - + public boolean balDontBringExistingBackgroundTaskStackToFg() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!responsible_apis_is_cached) { + load_overrides_responsible_apis(); + } + } + return balDontBringExistingBackgroundTaskStackToFg; + } @Override - - + public boolean balImproveRealCallerVisibilityCheck() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!responsible_apis_is_cached) { + load_overrides_responsible_apis(); + } + } + return balImproveRealCallerVisibilityCheck; + } @Override - - + public boolean balImprovedMetrics() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!responsible_apis_is_cached) { + load_overrides_responsible_apis(); + } + } + return balImprovedMetrics; + } @Override - - - public boolean balReduceGracePeriod() { - return false; - } - - @Override - - + public boolean balRequireOptInByPendingIntentCreator() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!responsible_apis_is_cached) { + load_overrides_responsible_apis(); + } + } + return balRequireOptInByPendingIntentCreator; + } @Override + + public boolean balRequireOptInSameUid() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!responsible_apis_is_cached) { + load_overrides_responsible_apis(); + } + } + return balRequireOptInSameUid; + } + @Override + public boolean balRespectAppSwitchStateWhenCheckBoundByForegroundUid() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!responsible_apis_is_cached) { + load_overrides_responsible_apis(); + } + } + return balRespectAppSwitchStateWhenCheckBoundByForegroundUid; + } @Override + + public boolean balShowToasts() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!responsible_apis_is_cached) { + load_overrides_responsible_apis(); + } + } + return balShowToasts; - - public boolean balSendIntentWithOptions() { - return true; } @Override - - + public boolean balShowToastsBlocked() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!responsible_apis_is_cached) { + load_overrides_responsible_apis(); + } + } + return balShowToastsBlocked; + } @Override + + public boolean blastSyncNotificationShadeOnDisplaySwitch() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!windowing_frontend_is_cached) { + load_overrides_windowing_frontend(); + } + } + return blastSyncNotificationShadeOnDisplaySwitch; + } - public boolean balStrictModeGracePeriod() { + @Override + + public boolean bundleClientTransactionFlag() { return true; + } @Override - - - public boolean balStrictModeRo() { - return true; - } - - @Override - - - public boolean betterSupportNonMatchParentActivity() { - return true; - } - - @Override - - - public boolean cacheWindowStyle() { - return true; - } - - @Override - - + public boolean cameraCompatForFreeform() { - return false; - } - - @Override - - - public boolean cameraCompatFullscreenPickSameTaskActivity() { - return false; - } - - @Override - - - public boolean checkDisabledSnapshotsInTaskPersister() { return true; + } @Override - - - public boolean cleanupDispatchPendingTransactionsRemoteException() { - return false; - } - - @Override - - - public boolean clearSystemVibrator() { - return true; - } - - @Override - - + public boolean closeToSquareConfigIncludesStatusBar() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!windowing_frontend_is_cached) { + load_overrides_windowing_frontend(); + } + } + return closeToSquareConfigIncludesStatusBar; + } @Override - - - public boolean condenseConfigurationChangeForSimpleMode() { - return true; - } - - @Override - - + public boolean configurableFontScaleDefault() { return true; + } @Override - - + public boolean coverDisplayOptIn() { return true; + } @Override - - - public boolean delayNotificationToMagnificationWhenRecentsWindowToFrontTransition() { + + public boolean deferDisplayUpdates() { return true; + } @Override + + public boolean delayNotificationToMagnificationWhenRecentsWindowToFrontTransition() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!accessibility_is_cached) { + load_overrides_accessibility(); + } + } + return delayNotificationToMagnificationWhenRecentsWindowToFrontTransition; - - public boolean delegateBackGestureToShell() { - return false; } @Override - - + public boolean delegateUnhandledDrags() { return true; + } @Override - - + public boolean deleteCaptureDisplay() { return true; + } @Override - - + public boolean density390Api() { return true; + } @Override - - - public boolean disableDesktopLaunchParamsOutsideDesktopBugFix() { + + public boolean disableObjectPool() { return true; + } @Override + + public boolean disableThinLetterboxingPolicy() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!large_screen_experiences_app_compat_is_cached) { + load_overrides_large_screen_experiences_app_compat(); + } + } + return disableThinLetterboxingPolicy; - - public boolean disableNonResizableAppSnapResizing() { - return true; } @Override - - - public boolean disableOptOutEdgeToEdge() { - return true; - } - - @Override - - + public boolean doNotCheckIntersectionWhenNonMagnifiableWindowTransitions() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!accessibility_is_cached) { + load_overrides_accessibility(); + } + } + return doNotCheckIntersectionWhenNonMagnifiableWindowTransitions; + + } + + @Override + + public boolean drawSnapshotAspectRatioMatch() { return false; + } @Override - - - public boolean earlyLaunchHint() { - return true; - } - - @Override - - + public boolean edgeToEdgeByDefault() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!windowing_frontend_is_cached) { + load_overrides_windowing_frontend(); + } + } + return edgeToEdgeByDefault; + } @Override + + public boolean embeddedActivityBackNavFlag() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!windowing_sdk_is_cached) { + load_overrides_windowing_sdk(); + } + } + return embeddedActivityBackNavFlag; - - public boolean enableAccessibleCustomHeaders() { - return true; } @Override + + public boolean enableAdditionalWindowsAboveStatusBar() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!lse_desktop_experience_is_cached) { + load_overrides_lse_desktop_experience(); + } + } + return enableAdditionalWindowsAboveStatusBar; - - public boolean enableActivityEmbeddingSupportForConnectedDisplays() { - return false; } @Override - - + public boolean enableAppHeaderWithTaskDensity() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!lse_desktop_experience_is_cached) { + load_overrides_lse_desktop_experience(); + } + } + return enableAppHeaderWithTaskDensity; + } @Override - - - public boolean enableBorderSettings() { - return false; - } - - @Override - - + public boolean enableBufferTransformHintFromDisplay() { return true; + } @Override - - - public boolean enableBugFixesForSecondaryDisplay() { - return false; - } - - @Override - - + public boolean enableCameraCompatForDesktopWindowing() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!lse_desktop_experience_is_cached) { + load_overrides_lse_desktop_experience(); + } + } + return enableCameraCompatForDesktopWindowing; + } @Override - - - public boolean enableCameraCompatForDesktopWindowingOptOut() { - return true; - } - - @Override - - - public boolean enableCameraCompatForDesktopWindowingOptOutApi() { - return false; - } - - @Override - - - public boolean enableCameraCompatTrackTaskAndAppBugfix() { - return false; - } - - @Override - - - public boolean enableCaptionCompatInsetConversion() { - return false; - } - - @Override - - - public boolean enableCaptionCompatInsetForceConsumption() { - return true; - } - - @Override - - - public boolean enableCaptionCompatInsetForceConsumptionAlways() { - return true; - } - - @Override - - - public boolean enableCascadingWindows() { - return true; - } - - @Override - - - public boolean enableCompatUiVisibilityStatus() { - return true; - } - - @Override - - + public boolean enableCompatuiSysuiLauncher() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!large_screen_experiences_app_compat_is_cached) { + load_overrides_large_screen_experiences_app_compat(); + } + } + return enableCompatuiSysuiLauncher; + } @Override - - - public boolean enableConnectedDisplaysDnd() { - return false; - } - - @Override - - - public boolean enableConnectedDisplaysPip() { - return false; - } - - @Override - - - public boolean enableConnectedDisplaysWindowDrag() { - return false; - } - - @Override - - - public boolean enableDesktopAppHandleAnimation() { - return true; - } - - @Override - - - public boolean enableDesktopAppLaunchAlttabTransitions() { - return false; - } - - @Override - - - public boolean enableDesktopAppLaunchAlttabTransitionsBugfix() { - return true; - } - - @Override - - - public boolean enableDesktopAppLaunchTransitions() { - return false; - } - - @Override - - - public boolean enableDesktopAppLaunchTransitionsBugfix() { - return true; - } - - @Override - - - public boolean enableDesktopCloseShortcutBugfix() { - return false; - } - - @Override - - - public boolean enableDesktopCloseTaskAnimationInDtcBugfix() { - return false; - } - - @Override - - - public boolean enableDesktopImeBugfix() { - return false; - } - - @Override - - - public boolean enableDesktopImmersiveDragBugfix() { - return true; - } - - @Override - - - public boolean enableDesktopIndicatorInSeparateThreadBugfix() { - return true; - } - - @Override - - - public boolean enableDesktopModeThroughDevOption() { - return false; - } - - @Override - - - public boolean enableDesktopOpeningDeeplinkMinimizeAnimationBugfix() { - return true; - } - - @Override - - - public boolean enableDesktopRecentsTransitionsCornersBugfix() { - return true; - } - - @Override - - - public boolean enableDesktopSwipeBackMinimizeAnimationBugfix() { - return false; - } - - @Override - - - public boolean enableDesktopSystemDialogsTransitions() { - return true; - } - - @Override - - - public boolean enableDesktopTabTearingMinimizeAnimationBugfix() { - return true; - } - - @Override - - - public boolean enableDesktopTaskbarOnFreeformDisplays() { - return false; - } - - @Override - - - public boolean enableDesktopTrampolineCloseAnimationBugfix() { - return true; - } - - @Override - - - public boolean enableDesktopWallpaperActivityForSystemUser() { - return true; - } - - @Override - - - public boolean enableDesktopWindowingAppHandleEducation() { - return false; - } - - @Override - - - public boolean enableDesktopWindowingAppToWeb() { - return true; - } - - @Override - - - public boolean enableDesktopWindowingAppToWebEducation() { - return true; - } - - @Override - - - public boolean enableDesktopWindowingAppToWebEducationIntegration() { - return false; - } - - @Override - - - public boolean enableDesktopWindowingBackNavigation() { - return true; - } - - @Override - - - public boolean enableDesktopWindowingEnterTransitionBugfix() { - return true; - } - - @Override - - - public boolean enableDesktopWindowingEnterTransitions() { - return false; - } - - @Override - - - public boolean enableDesktopWindowingExitByMinimizeTransitionBugfix() { - return true; - } - - @Override - - - public boolean enableDesktopWindowingExitTransitions() { - return false; - } - - @Override - - - public boolean enableDesktopWindowingExitTransitionsBugfix() { - return true; - } - - @Override - - - public boolean enableDesktopWindowingHsum() { - return true; - } - - @Override - - + public boolean enableDesktopWindowingImmersiveHandleHiding() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!lse_desktop_experience_is_cached) { + load_overrides_lse_desktop_experience(); + } + } + return enableDesktopWindowingImmersiveHandleHiding; + } @Override - - + public boolean enableDesktopWindowingModalsPolicy() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!lse_desktop_experience_is_cached) { + load_overrides_lse_desktop_experience(); + } + } + return enableDesktopWindowingModalsPolicy; + } @Override - - + public boolean enableDesktopWindowingMode() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!lse_desktop_experience_is_cached) { + load_overrides_lse_desktop_experience(); + } + } + return enableDesktopWindowingMode; + } @Override - - - public boolean enableDesktopWindowingMultiInstanceFeatures() { - return true; - } - - @Override - - - public boolean enableDesktopWindowingPersistence() { - return true; - } - - @Override - - - public boolean enableDesktopWindowingPip() { - return false; - } - - @Override - - + public boolean enableDesktopWindowingQuickSwitch() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!lse_desktop_experience_is_cached) { + load_overrides_lse_desktop_experience(); + } + } + return enableDesktopWindowingQuickSwitch; + } @Override + + public boolean enableDesktopWindowingScvhCache() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!lse_desktop_experience_is_cached) { + load_overrides_lse_desktop_experience(); + } + } + return enableDesktopWindowingScvhCache; - - public boolean enableDesktopWindowingScvhCacheBugFix() { - return true; } @Override - - + public boolean enableDesktopWindowingSizeConstraints() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!lse_desktop_experience_is_cached) { + load_overrides_lse_desktop_experience(); + } + } + return enableDesktopWindowingSizeConstraints; + } @Override - - + public boolean enableDesktopWindowingTaskLimit() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!lse_desktop_experience_is_cached) { + load_overrides_lse_desktop_experience(); + } + } + return enableDesktopWindowingTaskLimit; + } @Override - - + public boolean enableDesktopWindowingTaskbarRunningApps() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!lse_desktop_experience_is_cached) { + load_overrides_lse_desktop_experience(); + } + } + return enableDesktopWindowingTaskbarRunningApps; + } @Override - - - public boolean enableDesktopWindowingTransitions() { - return false; - } - - @Override - - + public boolean enableDesktopWindowingWallpaperActivity() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!lse_desktop_experience_is_cached) { + load_overrides_lse_desktop_experience(); + } + } + return enableDesktopWindowingWallpaperActivity; + } @Override - - - public boolean enableDeviceStateAutoRotateSettingLogging() { + + public boolean enableScaledResizing() { return false; + } @Override - - - public boolean enableDeviceStateAutoRotateSettingRefactor() { - return false; - } - - @Override - - - public boolean enableDisplayDisconnectInteraction() { - return false; - } - - @Override - - - public boolean enableDisplayFocusInShellTransitions() { - return false; - } - - @Override - - - public boolean enableDisplayReconnectInteraction() { - return false; - } - - @Override - - - public boolean enableDisplayWindowingModeSwitching() { - return false; - } - - @Override - - - public boolean enableDragResizeSetUpInBgThread() { - return true; - } - - @Override - - - public boolean enableDragToDesktopIncomingTransitionsBugfix() { - return true; - } - - @Override - - - public boolean enableDragToMaximize() { - return false; - } - - @Override - - - public boolean enableDynamicRadiusComputationBugfix() { - return false; - } - - @Override - - - public boolean enableFullScreenWindowOnRemovingSplitScreenStageBugfix() { - return true; - } - - @Override - - - public boolean enableFullyImmersiveInDesktop() { - return true; - } - - @Override - - - public boolean enableHandleInputFix() { - return true; - } - - @Override - - - public boolean enableHoldToDragAppHandle() { - return true; - } - - @Override - - - public boolean enableInputLayerTransitionFix() { - return true; - } - - @Override - - - public boolean enableMinimizeButton() { - return true; - } - - @Override - - - public boolean enableModalsFullscreenWithPermission() { - return true; - } - - @Override - - - public boolean enableMoveToNextDisplayShortcut() { - return false; - } - - @Override - - - public boolean enableMultiDisplaySplit() { - return false; - } - - @Override - - - public boolean enableMultidisplayTrackpadBackGesture() { - return false; - } - - @Override - - - public boolean enableMultipleDesktopsBackend() { - return false; - } - - @Override - - - public boolean enableMultipleDesktopsFrontend() { - return false; - } - - @Override - - - public boolean enableNonDefaultDisplaySplit() { - return false; - } - - @Override - - - public boolean enableOpaqueBackgroundForTransparentWindows() { - return true; - } - - @Override - - - public boolean enablePerDisplayDesktopWallpaperActivity() { - return false; - } - - @Override - - - public boolean enablePerDisplayPackageContextCacheInStatusbarNotif() { - return false; - } - - @Override - - - public boolean enablePersistingDisplaySizeForConnectedDisplays() { - return false; - } - - @Override - - - public boolean enablePresentationForConnectedDisplays() { - return false; - } - - @Override - - - public boolean enableProjectedDisplayDesktopMode() { - return false; - } - - @Override - - - public boolean enableQuickswitchDesktopSplitBugfix() { - return true; - } - - @Override - - - public boolean enableRequestFullscreenBugfix() { - return true; - } - - @Override - - - public boolean enableResizingMetrics() { - return true; - } - - @Override - - - public boolean enableRestartMenuForConnectedDisplays() { - return false; - } - - @Override - - - public boolean enableRestoreToPreviousSizeFromDesktopImmersive() { - return true; - } - - @Override - - - public boolean enableShellInitialBoundsRegressionBugFix() { - return true; - } - - @Override - - - public boolean enableSizeCompatModeImprovementsForConnectedDisplays() { - return false; - } - - @Override - - - public boolean enableStartLaunchTransitionFromTaskbarBugfix() { - return true; - } - - @Override - - - public boolean enableTaskResizingKeyboardShortcuts() { - return true; - } - - @Override - - + public boolean enableTaskStackObserverInShell() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!lse_desktop_experience_is_cached) { + load_overrides_lse_desktop_experience(); + } + } + return enableTaskStackObserverInShell; + } @Override - - - public boolean enableTaskbarConnectedDisplays() { - return false; - } - - @Override - - - public boolean enableTaskbarOverflow() { - return false; - } - - @Override - - - public boolean enableTaskbarRecentsLayoutTransition() { - return true; - } - - @Override - - + public boolean enableThemedAppHeaders() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!lse_desktop_experience_is_cached) { + load_overrides_lse_desktop_experience(); + } + } + return enableThemedAppHeaders; + } @Override - - - public boolean enableTileResizing() { - return false; - } - - @Override - - - public boolean enableTopVisibleRootTaskPerUserTracking() { - return true; - } - - @Override - - - public boolean enableVisualIndicatorInTransitionBugfix() { - return true; - } - - @Override - - - public boolean enableWindowContextResourcesUpdateOnConfigChange() { - return true; - } - - @Override - - + public boolean enableWindowingDynamicInitialBounds() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!lse_desktop_experience_is_cached) { + load_overrides_lse_desktop_experience(); + } + } + return enableWindowingDynamicInitialBounds; + } @Override - - + public boolean enableWindowingEdgeDragResize() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!lse_desktop_experience_is_cached) { + load_overrides_lse_desktop_experience(); + } + } + return enableWindowingEdgeDragResize; + + } + + @Override + + public boolean enableWmExtensionsForAllFlag() { return true; + } @Override - - - public boolean enableWindowingScaledResizing() { - return true; - } - - @Override - - - public boolean enableWindowingTransitionHandlersObservers() { - return false; - } - - @Override - - + public boolean enforceEdgeToEdge() { return true; + } @Override - - - public boolean ensureKeyguardDoesTransitionStarting() { - return false; - } - - @Override - - + public boolean ensureWallpaperInTransitions() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!windowing_frontend_is_cached) { + load_overrides_windowing_frontend(); + } + } + return ensureWallpaperInTransitions; + + } + + @Override + + public boolean explicitRefreshRateHints() { return true; + } @Override - - - public boolean ensureWallpaperInWearTransitions() { - return true; - } - - @Override - - - public boolean enterDesktopByDefaultOnFreeformDisplays() { - return false; - } - - @Override - - - public boolean excludeCaptionFromAppBounds() { - return true; - } - - @Override - - - public boolean excludeDrawingAppThemeSnapshotFromLock() { - return true; - } - - @Override - - - public boolean excludeTaskFromRecents() { - return false; - } - - @Override - - + public boolean fifoPriorityForMajorUiProcesses() { return false; + } @Override - - - public boolean fixHideOverlayApi() { - return true; - } - - @Override - - - public boolean fixLayoutExistingTask() { - return true; - } - - @Override - - - public boolean fixViewRootCallTrace() { + + public boolean fixNoContainerUpdateWithoutResize() { return false; + } @Override + + public boolean fixPipRestoreToOverlay() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!windowing_sdk_is_cached) { + load_overrides_windowing_sdk(); + } + } + return fixPipRestoreToOverlay; - - public boolean forceCloseTopTransparentFullscreenTask() { - return false; } @Override + + public boolean fullscreenDimFlag() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!windowing_sdk_is_cached) { + load_overrides_windowing_sdk(); + } + } + return fullscreenDimFlag; - - public boolean formFactorBasedDesktopFirstSwitch() { - return false; } @Override - - + public boolean getDimmerOnClosing() { return true; + } @Override + + public boolean immersiveAppRepositioning() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!large_screen_experiences_app_compat_is_cached) { + load_overrides_large_screen_experiences_app_compat(); + } + } + return immersiveAppRepositioning; - - public boolean ignoreAspectRatioRestrictionsForResizeableFreeformActivities() { - return true; } @Override + + public boolean insetsControlChangedItem() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!windowing_sdk_is_cached) { + load_overrides_windowing_sdk(); + } + } + return insetsControlChangedItem; + } - public boolean ignoreCornerRadiusAndShadows() { + @Override + + public boolean insetsControlSeq() { return false; + } @Override - - - public boolean includeTopTransparentFullscreenTaskInDesktopHeuristic() { - return true; - } - - @Override - - - public boolean inheritTaskBoundsForTrampolineTaskLaunches() { - return true; - } - - @Override - - + public boolean insetsDecoupledConfiguration() { return true; + } @Override - - - public boolean jankApi() { + + public boolean introduceSmootherDimmer() { return true; + } @Override - - - public boolean keepAppWindowHideWhileLocked() { + + public boolean keyguardAppearTransition() { return true; + } @Override - - - public boolean keyboardShortcutsToSwitchDesks() { - return false; - } - - @Override - - - public boolean keyguardGoingAwayTimeout() { - return true; - } - - @Override - - + public boolean letterboxBackgroundWallpaper() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!large_screen_experiences_app_compat_is_cached) { + load_overrides_large_screen_experiences_app_compat(); + } + } + return letterboxBackgroundWallpaper; + } @Override - - + public boolean movableCutoutConfiguration() { return true; + } @Override + + public boolean moveAnimationOptionsToChange() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!windowing_sdk_is_cached) { + load_overrides_windowing_sdk(); + } + } + return moveAnimationOptionsToChange; - - public boolean moveToExternalDisplayShortcut() { - return false; } @Override - - + public boolean multiCrop() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return multiCrop; + } @Override - - + public boolean navBarTransparentByDefault() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!windowing_frontend_is_cached) { + load_overrides_windowing_frontend(); + } + } + return navBarTransparentByDefault; + } @Override - - - public boolean nestedTasksWithIndependentBounds() { - return false; - } - - @Override - - + public boolean noConsecutiveVisibilityEvents() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return noConsecutiveVisibilityEvents; + } @Override - - - public boolean noDuplicateSurfaceDestroyedEvents() { - return true; - } - - @Override - - + public boolean noVisibilityEventOnDisplayStateChange() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!wear_frameworks_is_cached) { + load_overrides_wear_frameworks(); + } + } + return noVisibilityEventOnDisplayStateChange; + } @Override - - + public boolean offloadColorExtraction() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return offloadColorExtraction; + } @Override + + public boolean predictiveBackSystemAnims() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!systemui_is_cached) { + load_overrides_systemui(); + } + } + return predictiveBackSystemAnims; - - public boolean portWindowSizeAnimation() { - return false; } @Override - - - public boolean predictiveBackDefaultEnableSdk36() { - return true; - } - - @Override - - - public boolean predictiveBackPrioritySystemNavigationObserver() { - return true; - } - - @Override - - - public boolean predictiveBackSwipeEdgeNoneApi() { - return true; - } - - @Override - - - public boolean predictiveBackSystemOverrideCallback() { - return true; - } - - @Override - - - public boolean predictiveBackThreeButtonNav() { - return true; - } - - @Override - - - public boolean predictiveBackTimestampApi() { - return true; - } - - @Override - - - public boolean processPriorityPolicyForMultiWindowMode() { - return true; - } - - @Override - - + public boolean rearDisplayDisableForceDesktopSystemDecorations() { return true; + } @Override - - - public boolean recordTaskSnapshotsBeforeShutdown() { - return true; - } - - @Override - - - public boolean reduceChangedExclusionRectsMsgs() { - return false; - } - - @Override - - - public boolean reduceKeyguardTransitions() { - return true; - } - - @Override - - - public boolean reduceTaskSnapshotMemoryUsage() { - return false; - } - - @Override - - - public boolean reduceUnnecessaryMeasure() { - return false; - } - - @Override - - - public boolean relativeInsets() { - return false; - } - - @Override - - + public boolean releaseSnapshotAggressively() { - return true; - } - - @Override - - - public boolean releaseUserAspectRatioWm() { - return true; - } - - @Override - - - public boolean removeActivityStarterDreamCallback() { return false; + } @Override - - - public boolean removeDeferHidingClient() { + + public boolean removePrepareSurfaceInPlacement() { return true; + } @Override - - - public boolean removeDepartTargetFromMotion() { - return false; - } - - @Override - - - public boolean reparentWindowTokenApi() { - return true; - } - - @Override - - - public boolean respectNonTopVisibleFixedOrientation() { - return true; - } - - @Override - - - public boolean respectOrientationChangeForUnresizeable() { - return true; - } - - @Override - - - public boolean safeRegionLetterboxing() { - return false; - } - - @Override - - - public boolean safeReleaseSnapshotAggressively() { - return false; - } - - @Override - - - public boolean schedulingForNotificationShade() { - return true; - } - - @Override - - - public boolean scrambleSnapshotFileName() { - return false; - } - - @Override - - + public boolean screenRecordingCallbacks() { return true; + } @Override - - - public boolean scrollingFromLetterbox() { - return false; - } - - @Override - - + public boolean sdkDesiredPresentTime() { return true; + } @Override + + public boolean secureWindowState() { + return true; + } + @Override + public boolean setScPropertiesInClient() { return false; + } @Override - - - public boolean showAppHandleLargeScreens() { - return false; - } - - @Override - - - public boolean showDesktopExperienceDevOption() { - return false; - } - - @Override - - - public boolean showDesktopWindowingDevOption() { + + public boolean skipSleepingWhenSwitchingDisplay() { return true; + } @Override - - - public boolean showHomeBehindDesktop() { - return false; - } - - @Override - - - public boolean skipCompatUiEducationInDesktopMode() { - return true; - } - - @Override - - - public boolean skipDecorViewRelayoutWhenClosingBugfix() { - return true; - } - - @Override - - - public boolean supportWidgetIntentsOnConnectedDisplay() { - return false; - } - - @Override - - - public boolean supportsDragAssistantToMultiwindow() { - return true; - } - - @Override - - + public boolean supportsMultiInstanceSystemUi() { return true; + } @Override - - + public boolean surfaceControlInputReceiver() { return true; + } @Override - - + public boolean surfaceTrustedOverlay() { return true; + } @Override - - + public boolean syncScreenCapture() { return true; + } @Override - - - public boolean systemUiPostAnimationEnd() { - return false; - } - - @Override - - + public boolean taskFragmentSystemOrganizerFlag() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!windowing_sdk_is_cached) { + load_overrides_windowing_sdk(); + } + } + return taskFragmentSystemOrganizerFlag; + } @Override - - - public boolean touchPassThroughOptIn() { - return true; - } - - @Override - - - public boolean trackSystemUiContextBeforeWms() { - return true; - } - - @Override - - + public boolean transitReadyTracking() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!windowing_frontend_is_cached) { + load_overrides_windowing_frontend(); + } + } + return transitReadyTracking; + } @Override - - - public boolean transitTrackerPlumbing() { - return false; - } - - @Override - - + public boolean trustedPresentationListenerForWindow() { return true; + } @Override - - - public boolean unifyBackNavigationTransition() { - return true; - } - - @Override - - - public boolean universalResizableByDefault() { - return true; - } - - @Override - - + public boolean untrustedEmbeddingAnyAppPermission() { - return false; + return true; + } @Override - - + public boolean untrustedEmbeddingStateSharing() { return true; + } @Override - - - public boolean updateDimsWhenWindowShown() { - return false; - } - - @Override - - - public boolean useCachedInsetsForDisplaySwitch() { - return false; - } - - @Override - - - public boolean useRtFrameCallbackForSplashScreenTransfer() { - return true; - } - - @Override - - - public boolean useTasksDimOnly() { - return true; - } - - @Override - - - public boolean useVisibleRequestedForProcessTracker() { - return false; - } - - @Override - - + public boolean useWindowOriginalTouchableRegionWhenMagnificationRecomputeBounds() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!accessibility_is_cached) { + load_overrides_accessibility(); + } + } + return useWindowOriginalTouchableRegionWhenMagnificationRecomputeBounds; + } @Override + + public boolean userMinAspectRatioAppDefault() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!large_screen_experiences_app_compat_is_cached) { + load_overrides_large_screen_experiences_app_compat(); + } + } + return userMinAspectRatioAppDefault; - - public boolean vdmForceAppUniversalResizableApi() { - return true; } @Override + + public boolean waitForTransitionOnDisplaySwitch() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!windowing_frontend_is_cached) { + load_overrides_windowing_frontend(); + } + } + return waitForTransitionOnDisplaySwitch; + } + @Override + public boolean wallpaperOffsetAsync() { return true; + } @Override - - - public boolean wlinfoOncreate() { + + public boolean windowSessionRelayoutInfo() { return true; + + } + + @Override + + public boolean windowTokenConfigThreadSafe() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!windowing_sdk_is_cached) { + load_overrides_windowing_sdk(); + } + } + return windowTokenConfigThreadSafe; + } } + + + diff --git a/flags/src/com/android/window/flags2/Flags.java b/flags/src/com/android/window/flags2/Flags.java index 5ad83f821d..8888b48ce7 100644 --- a/flags/src/com/android/window/flags2/Flags.java +++ b/flags/src/com/android/window/flags2/Flags.java @@ -1,20 +1,17 @@ package com.android.window.flags2; // TODO(b/303773055): Remove the annotation after access issue is resolved. - /** @hide */ public final class Flags { - /** @hide */ - public static final String FLAG_ACTION_MODE_EDGE_TO_EDGE = "com.android.window.flags.action_mode_edge_to_edge"; /** @hide */ public static final String FLAG_ACTIVITY_EMBEDDING_ANIMATION_CUSTOMIZATION_FLAG = "com.android.window.flags.activity_embedding_animation_customization_flag"; /** @hide */ - public static final String FLAG_ACTIVITY_EMBEDDING_DELAY_TASK_FRAGMENT_FINISH_FOR_ACTIVITY_LAUNCH = "com.android.window.flags.activity_embedding_delay_task_fragment_finish_for_activity_launch"; - /** @hide */ public static final String FLAG_ACTIVITY_EMBEDDING_INTERACTIVE_DIVIDER_FLAG = "com.android.window.flags.activity_embedding_interactive_divider_flag"; /** @hide */ - public static final String FLAG_ACTIVITY_EMBEDDING_METRICS = "com.android.window.flags.activity_embedding_metrics"; + public static final String FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG = "com.android.window.flags.activity_embedding_overlay_presentation_flag"; /** @hide */ - public static final String FLAG_ACTIVITY_EMBEDDING_SUPPORT_FOR_CONNECTED_DISPLAYS = "com.android.window.flags.activity_embedding_support_for_connected_displays"; + public static final String FLAG_ACTIVITY_SNAPSHOT_BY_DEFAULT = "com.android.window.flags.activity_snapshot_by_default"; + /** @hide */ + public static final String FLAG_ACTIVITY_WINDOW_INFO_FLAG = "com.android.window.flags.activity_window_info_flag"; /** @hide */ public static final String FLAG_ALLOW_DISABLE_ACTIVITY_RECORD_INPUT_SINK = "com.android.window.flags.allow_disable_activity_record_input_sink"; /** @hide */ @@ -22,79 +19,47 @@ public final class Flags { /** @hide */ public static final String FLAG_ALLOWS_SCREEN_SIZE_DECOUPLED_FROM_STATUS_BAR_AND_CUTOUT = "com.android.window.flags.allows_screen_size_decoupled_from_status_bar_and_cutout"; /** @hide */ + public static final String FLAG_ALWAYS_DEFER_TRANSITION_WHEN_APPLY_WCT = "com.android.window.flags.always_defer_transition_when_apply_wct"; + /** @hide */ public static final String FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER = "com.android.window.flags.always_draw_magnification_fullscreen_border"; /** @hide */ public static final String FLAG_ALWAYS_UPDATE_WALLPAPER_PERMISSION = "com.android.window.flags.always_update_wallpaper_permission"; /** @hide */ - public static final String FLAG_AOD_TRANSITION = "com.android.window.flags.aod_transition"; - /** @hide */ - public static final String FLAG_APP_COMPAT_ASYNC_RELAYOUT = "com.android.window.flags.app_compat_async_relayout"; - /** @hide */ public static final String FLAG_APP_COMPAT_PROPERTIES_API = "com.android.window.flags.app_compat_properties_api"; /** @hide */ public static final String FLAG_APP_COMPAT_REFACTORING = "com.android.window.flags.app_compat_refactoring"; /** @hide */ - public static final String FLAG_APP_COMPAT_UI_FRAMEWORK = "com.android.window.flags.app_compat_ui_framework"; - /** @hide */ - public static final String FLAG_APP_HANDLE_NO_RELAYOUT_ON_EXCLUSION_CHANGE = "com.android.window.flags.app_handle_no_relayout_on_exclusion_change"; - /** @hide */ - public static final String FLAG_APPLY_LIFECYCLE_ON_PIP_CHANGE = "com.android.window.flags.apply_lifecycle_on_pip_change"; - /** @hide */ - public static final String FLAG_AVOID_REBINDING_INTENTIONALLY_DISCONNECTED_WALLPAPER = "com.android.window.flags.avoid_rebinding_intentionally_disconnected_wallpaper"; - /** @hide */ - public static final String FLAG_BACKUP_AND_RESTORE_FOR_USER_ASPECT_RATIO_SETTINGS = "com.android.window.flags.backup_and_restore_for_user_aspect_ratio_settings"; - /** @hide */ - public static final String FLAG_BAL_ADDITIONAL_LOGGING = "com.android.window.flags.bal_additional_logging"; - /** @hide */ - public static final String FLAG_BAL_ADDITIONAL_START_MODES = "com.android.window.flags.bal_additional_start_modes"; - /** @hide */ - public static final String FLAG_BAL_CLEAR_ALLOWLIST_DURATION = "com.android.window.flags.bal_clear_allowlist_duration"; - /** @hide */ public static final String FLAG_BAL_DONT_BRING_EXISTING_BACKGROUND_TASK_STACK_TO_FG = "com.android.window.flags.bal_dont_bring_existing_background_task_stack_to_fg"; /** @hide */ public static final String FLAG_BAL_IMPROVE_REAL_CALLER_VISIBILITY_CHECK = "com.android.window.flags.bal_improve_real_caller_visibility_check"; /** @hide */ public static final String FLAG_BAL_IMPROVED_METRICS = "com.android.window.flags.bal_improved_metrics"; /** @hide */ - public static final String FLAG_BAL_REDUCE_GRACE_PERIOD = "com.android.window.flags.bal_reduce_grace_period"; - /** @hide */ public static final String FLAG_BAL_REQUIRE_OPT_IN_BY_PENDING_INTENT_CREATOR = "com.android.window.flags.bal_require_opt_in_by_pending_intent_creator"; /** @hide */ + public static final String FLAG_BAL_REQUIRE_OPT_IN_SAME_UID = "com.android.window.flags.bal_require_opt_in_same_uid"; + /** @hide */ public static final String FLAG_BAL_RESPECT_APP_SWITCH_STATE_WHEN_CHECK_BOUND_BY_FOREGROUND_UID = "com.android.window.flags.bal_respect_app_switch_state_when_check_bound_by_foreground_uid"; /** @hide */ - public static final String FLAG_BAL_SEND_INTENT_WITH_OPTIONS = "com.android.window.flags.bal_send_intent_with_options"; + public static final String FLAG_BAL_SHOW_TOASTS = "com.android.window.flags.bal_show_toasts"; /** @hide */ public static final String FLAG_BAL_SHOW_TOASTS_BLOCKED = "com.android.window.flags.bal_show_toasts_blocked"; /** @hide */ - public static final String FLAG_BAL_STRICT_MODE_GRACE_PERIOD = "com.android.window.flags.bal_strict_mode_grace_period"; + public static final String FLAG_BLAST_SYNC_NOTIFICATION_SHADE_ON_DISPLAY_SWITCH = "com.android.window.flags.blast_sync_notification_shade_on_display_switch"; /** @hide */ - public static final String FLAG_BAL_STRICT_MODE_RO = "com.android.window.flags.bal_strict_mode_ro"; - /** @hide */ - public static final String FLAG_BETTER_SUPPORT_NON_MATCH_PARENT_ACTIVITY = "com.android.window.flags.better_support_non_match_parent_activity"; - /** @hide */ - public static final String FLAG_CACHE_WINDOW_STYLE = "com.android.window.flags.cache_window_style"; + public static final String FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG = "com.android.window.flags.bundle_client_transaction_flag"; /** @hide */ public static final String FLAG_CAMERA_COMPAT_FOR_FREEFORM = "com.android.window.flags.camera_compat_for_freeform"; /** @hide */ - public static final String FLAG_CAMERA_COMPAT_FULLSCREEN_PICK_SAME_TASK_ACTIVITY = "com.android.window.flags.camera_compat_fullscreen_pick_same_task_activity"; - /** @hide */ - public static final String FLAG_CHECK_DISABLED_SNAPSHOTS_IN_TASK_PERSISTER = "com.android.window.flags.check_disabled_snapshots_in_task_persister"; - /** @hide */ - public static final String FLAG_CLEANUP_DISPATCH_PENDING_TRANSACTIONS_REMOTE_EXCEPTION = "com.android.window.flags.cleanup_dispatch_pending_transactions_remote_exception"; - /** @hide */ - public static final String FLAG_CLEAR_SYSTEM_VIBRATOR = "com.android.window.flags.clear_system_vibrator"; - /** @hide */ public static final String FLAG_CLOSE_TO_SQUARE_CONFIG_INCLUDES_STATUS_BAR = "com.android.window.flags.close_to_square_config_includes_status_bar"; /** @hide */ - public static final String FLAG_CONDENSE_CONFIGURATION_CHANGE_FOR_SIMPLE_MODE = "com.android.window.flags.condense_configuration_change_for_simple_mode"; - /** @hide */ public static final String FLAG_CONFIGURABLE_FONT_SCALE_DEFAULT = "com.android.window.flags.configurable_font_scale_default"; /** @hide */ public static final String FLAG_COVER_DISPLAY_OPT_IN = "com.android.window.flags.cover_display_opt_in"; /** @hide */ - public static final String FLAG_DELAY_NOTIFICATION_TO_MAGNIFICATION_WHEN_RECENTS_WINDOW_TO_FRONT_TRANSITION = "com.android.window.flags.delay_notification_to_magnification_when_recents_window_to_front_transition"; + public static final String FLAG_DEFER_DISPLAY_UPDATES = "com.android.window.flags.defer_display_updates"; /** @hide */ - public static final String FLAG_DELEGATE_BACK_GESTURE_TO_SHELL = "com.android.window.flags.delegate_back_gesture_to_shell"; + public static final String FLAG_DELAY_NOTIFICATION_TO_MAGNIFICATION_WHEN_RECENTS_WINDOW_TO_FRONT_TRANSITION = "com.android.window.flags.delay_notification_to_magnification_when_recents_window_to_front_transition"; /** @hide */ public static final String FLAG_DELEGATE_UNHANDLED_DRAGS = "com.android.window.flags.delegate_unhandled_drags"; /** @hide */ @@ -102,131 +67,37 @@ public final class Flags { /** @hide */ public static final String FLAG_DENSITY_390_API = "com.android.window.flags.density_390_api"; /** @hide */ - public static final String FLAG_DISABLE_DESKTOP_LAUNCH_PARAMS_OUTSIDE_DESKTOP_BUG_FIX = "com.android.window.flags.disable_desktop_launch_params_outside_desktop_bug_fix"; + public static final String FLAG_DISABLE_OBJECT_POOL = "com.android.window.flags.disable_object_pool"; /** @hide */ - public static final String FLAG_DISABLE_NON_RESIZABLE_APP_SNAP_RESIZING = "com.android.window.flags.disable_non_resizable_app_snap_resizing"; - /** @hide */ - public static final String FLAG_DISABLE_OPT_OUT_EDGE_TO_EDGE = "com.android.window.flags.disable_opt_out_edge_to_edge"; + public static final String FLAG_DISABLE_THIN_LETTERBOXING_POLICY = "com.android.window.flags.disable_thin_letterboxing_policy"; /** @hide */ public static final String FLAG_DO_NOT_CHECK_INTERSECTION_WHEN_NON_MAGNIFIABLE_WINDOW_TRANSITIONS = "com.android.window.flags.do_not_check_intersection_when_non_magnifiable_window_transitions"; /** @hide */ - public static final String FLAG_EARLY_LAUNCH_HINT = "com.android.window.flags.early_launch_hint"; + public static final String FLAG_DRAW_SNAPSHOT_ASPECT_RATIO_MATCH = "com.android.window.flags.draw_snapshot_aspect_ratio_match"; /** @hide */ public static final String FLAG_EDGE_TO_EDGE_BY_DEFAULT = "com.android.window.flags.edge_to_edge_by_default"; /** @hide */ - public static final String FLAG_ENABLE_ACCESSIBLE_CUSTOM_HEADERS = "com.android.window.flags.enable_accessible_custom_headers"; + public static final String FLAG_EMBEDDED_ACTIVITY_BACK_NAV_FLAG = "com.android.window.flags.embedded_activity_back_nav_flag"; /** @hide */ - public static final String FLAG_ENABLE_ACTIVITY_EMBEDDING_SUPPORT_FOR_CONNECTED_DISPLAYS = "com.android.window.flags.enable_activity_embedding_support_for_connected_displays"; + public static final String FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR = "com.android.window.flags.enable_additional_windows_above_status_bar"; /** @hide */ public static final String FLAG_ENABLE_APP_HEADER_WITH_TASK_DENSITY = "com.android.window.flags.enable_app_header_with_task_density"; /** @hide */ - public static final String FLAG_ENABLE_BORDER_SETTINGS = "com.android.window.flags.enable_border_settings"; - /** @hide */ public static final String FLAG_ENABLE_BUFFER_TRANSFORM_HINT_FROM_DISPLAY = "com.android.window.flags.enable_buffer_transform_hint_from_display"; /** @hide */ - public static final String FLAG_ENABLE_BUG_FIXES_FOR_SECONDARY_DISPLAY = "com.android.window.flags.enable_bug_fixes_for_secondary_display"; - /** @hide */ public static final String FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING = "com.android.window.flags.enable_camera_compat_for_desktop_windowing"; /** @hide */ - public static final String FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT = "com.android.window.flags.enable_camera_compat_for_desktop_windowing_opt_out"; - /** @hide */ - public static final String FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT_API = "com.android.window.flags.enable_camera_compat_for_desktop_windowing_opt_out_api"; - /** @hide */ - public static final String FLAG_ENABLE_CAMERA_COMPAT_TRACK_TASK_AND_APP_BUGFIX = "com.android.window.flags.enable_camera_compat_track_task_and_app_bugfix"; - /** @hide */ - public static final String FLAG_ENABLE_CAPTION_COMPAT_INSET_CONVERSION = "com.android.window.flags.enable_caption_compat_inset_conversion"; - /** @hide */ - public static final String FLAG_ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION = "com.android.window.flags.enable_caption_compat_inset_force_consumption"; - /** @hide */ - public static final String FLAG_ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS = "com.android.window.flags.enable_caption_compat_inset_force_consumption_always"; - /** @hide */ - public static final String FLAG_ENABLE_CASCADING_WINDOWS = "com.android.window.flags.enable_cascading_windows"; - /** @hide */ - public static final String FLAG_ENABLE_COMPAT_UI_VISIBILITY_STATUS = "com.android.window.flags.enable_compat_ui_visibility_status"; - /** @hide */ public static final String FLAG_ENABLE_COMPATUI_SYSUI_LAUNCHER = "com.android.window.flags.enable_compatui_sysui_launcher"; /** @hide */ - public static final String FLAG_ENABLE_CONNECTED_DISPLAYS_DND = "com.android.window.flags.enable_connected_displays_dnd"; - /** @hide */ - public static final String FLAG_ENABLE_CONNECTED_DISPLAYS_PIP = "com.android.window.flags.enable_connected_displays_pip"; - /** @hide */ - public static final String FLAG_ENABLE_CONNECTED_DISPLAYS_WINDOW_DRAG = "com.android.window.flags.enable_connected_displays_window_drag"; - /** @hide */ - public static final String FLAG_ENABLE_DESKTOP_APP_HANDLE_ANIMATION = "com.android.window.flags.enable_desktop_app_handle_animation"; - /** @hide */ - public static final String FLAG_ENABLE_DESKTOP_APP_LAUNCH_ALTTAB_TRANSITIONS = "com.android.window.flags.enable_desktop_app_launch_alttab_transitions"; - /** @hide */ - public static final String FLAG_ENABLE_DESKTOP_APP_LAUNCH_ALTTAB_TRANSITIONS_BUGFIX = "com.android.window.flags.enable_desktop_app_launch_alttab_transitions_bugfix"; - /** @hide */ - public static final String FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS = "com.android.window.flags.enable_desktop_app_launch_transitions"; - /** @hide */ - public static final String FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX = "com.android.window.flags.enable_desktop_app_launch_transitions_bugfix"; - /** @hide */ - public static final String FLAG_ENABLE_DESKTOP_CLOSE_SHORTCUT_BUGFIX = "com.android.window.flags.enable_desktop_close_shortcut_bugfix"; - /** @hide */ - public static final String FLAG_ENABLE_DESKTOP_CLOSE_TASK_ANIMATION_IN_DTC_BUGFIX = "com.android.window.flags.enable_desktop_close_task_animation_in_dtc_bugfix"; - /** @hide */ - public static final String FLAG_ENABLE_DESKTOP_IME_BUGFIX = "com.android.window.flags.enable_desktop_ime_bugfix"; - /** @hide */ - public static final String FLAG_ENABLE_DESKTOP_IMMERSIVE_DRAG_BUGFIX = "com.android.window.flags.enable_desktop_immersive_drag_bugfix"; - /** @hide */ - public static final String FLAG_ENABLE_DESKTOP_INDICATOR_IN_SEPARATE_THREAD_BUGFIX = "com.android.window.flags.enable_desktop_indicator_in_separate_thread_bugfix"; - /** @hide */ - public static final String FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION = "com.android.window.flags.enable_desktop_mode_through_dev_option"; - /** @hide */ - public static final String FLAG_ENABLE_DESKTOP_OPENING_DEEPLINK_MINIMIZE_ANIMATION_BUGFIX = "com.android.window.flags.enable_desktop_opening_deeplink_minimize_animation_bugfix"; - /** @hide */ - public static final String FLAG_ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX = "com.android.window.flags.enable_desktop_recents_transitions_corners_bugfix"; - /** @hide */ - public static final String FLAG_ENABLE_DESKTOP_SWIPE_BACK_MINIMIZE_ANIMATION_BUGFIX = "com.android.window.flags.enable_desktop_swipe_back_minimize_animation_bugfix"; - /** @hide */ - public static final String FLAG_ENABLE_DESKTOP_SYSTEM_DIALOGS_TRANSITIONS = "com.android.window.flags.enable_desktop_system_dialogs_transitions"; - /** @hide */ - public static final String FLAG_ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX = "com.android.window.flags.enable_desktop_tab_tearing_minimize_animation_bugfix"; - /** @hide */ - public static final String FLAG_ENABLE_DESKTOP_TASKBAR_ON_FREEFORM_DISPLAYS = "com.android.window.flags.enable_desktop_taskbar_on_freeform_displays"; - /** @hide */ - public static final String FLAG_ENABLE_DESKTOP_TRAMPOLINE_CLOSE_ANIMATION_BUGFIX = "com.android.window.flags.enable_desktop_trampoline_close_animation_bugfix"; - /** @hide */ - public static final String FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER = "com.android.window.flags.enable_desktop_wallpaper_activity_for_system_user"; - /** @hide */ - public static final String FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION = "com.android.window.flags.enable_desktop_windowing_app_handle_education"; - /** @hide */ - public static final String FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB = "com.android.window.flags.enable_desktop_windowing_app_to_web"; - /** @hide */ - public static final String FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB_EDUCATION = "com.android.window.flags.enable_desktop_windowing_app_to_web_education"; - /** @hide */ - public static final String FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB_EDUCATION_INTEGRATION = "com.android.window.flags.enable_desktop_windowing_app_to_web_education_integration"; - /** @hide */ - public static final String FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION = "com.android.window.flags.enable_desktop_windowing_back_navigation"; - /** @hide */ - public static final String FLAG_ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITION_BUGFIX = "com.android.window.flags.enable_desktop_windowing_enter_transition_bugfix"; - /** @hide */ - public static final String FLAG_ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS = "com.android.window.flags.enable_desktop_windowing_enter_transitions"; - /** @hide */ - public static final String FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_BY_MINIMIZE_TRANSITION_BUGFIX = "com.android.window.flags.enable_desktop_windowing_exit_by_minimize_transition_bugfix"; - /** @hide */ - public static final String FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS = "com.android.window.flags.enable_desktop_windowing_exit_transitions"; - /** @hide */ - public static final String FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX = "com.android.window.flags.enable_desktop_windowing_exit_transitions_bugfix"; - /** @hide */ - public static final String FLAG_ENABLE_DESKTOP_WINDOWING_HSUM = "com.android.window.flags.enable_desktop_windowing_hsum"; - /** @hide */ public static final String FLAG_ENABLE_DESKTOP_WINDOWING_IMMERSIVE_HANDLE_HIDING = "com.android.window.flags.enable_desktop_windowing_immersive_handle_hiding"; /** @hide */ public static final String FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY = "com.android.window.flags.enable_desktop_windowing_modals_policy"; /** @hide */ public static final String FLAG_ENABLE_DESKTOP_WINDOWING_MODE = "com.android.window.flags.enable_desktop_windowing_mode"; /** @hide */ - public static final String FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES = "com.android.window.flags.enable_desktop_windowing_multi_instance_features"; - /** @hide */ - public static final String FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE = "com.android.window.flags.enable_desktop_windowing_persistence"; - /** @hide */ - public static final String FLAG_ENABLE_DESKTOP_WINDOWING_PIP = "com.android.window.flags.enable_desktop_windowing_pip"; - /** @hide */ public static final String FLAG_ENABLE_DESKTOP_WINDOWING_QUICK_SWITCH = "com.android.window.flags.enable_desktop_windowing_quick_switch"; /** @hide */ - public static final String FLAG_ENABLE_DESKTOP_WINDOWING_SCVH_CACHE_BUG_FIX = "com.android.window.flags.enable_desktop_windowing_scvh_cache_bug_fix"; + public static final String FLAG_ENABLE_DESKTOP_WINDOWING_SCVH_CACHE = "com.android.window.flags.enable_desktop_windowing_scvh_cache"; /** @hide */ public static final String FLAG_ENABLE_DESKTOP_WINDOWING_SIZE_CONSTRAINTS = "com.android.window.flags.enable_desktop_windowing_size_constraints"; /** @hide */ @@ -234,257 +105,81 @@ public final class Flags { /** @hide */ public static final String FLAG_ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS = "com.android.window.flags.enable_desktop_windowing_taskbar_running_apps"; /** @hide */ - public static final String FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS = "com.android.window.flags.enable_desktop_windowing_transitions"; - /** @hide */ public static final String FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY = "com.android.window.flags.enable_desktop_windowing_wallpaper_activity"; /** @hide */ - public static final String FLAG_ENABLE_DEVICE_STATE_AUTO_ROTATE_SETTING_LOGGING = "com.android.window.flags.enable_device_state_auto_rotate_setting_logging"; - /** @hide */ - public static final String FLAG_ENABLE_DEVICE_STATE_AUTO_ROTATE_SETTING_REFACTOR = "com.android.window.flags.enable_device_state_auto_rotate_setting_refactor"; - /** @hide */ - public static final String FLAG_ENABLE_DISPLAY_DISCONNECT_INTERACTION = "com.android.window.flags.enable_display_disconnect_interaction"; - /** @hide */ - public static final String FLAG_ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS = "com.android.window.flags.enable_display_focus_in_shell_transitions"; - /** @hide */ - public static final String FLAG_ENABLE_DISPLAY_RECONNECT_INTERACTION = "com.android.window.flags.enable_display_reconnect_interaction"; - /** @hide */ - public static final String FLAG_ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING = "com.android.window.flags.enable_display_windowing_mode_switching"; - /** @hide */ - public static final String FLAG_ENABLE_DRAG_RESIZE_SET_UP_IN_BG_THREAD = "com.android.window.flags.enable_drag_resize_set_up_in_bg_thread"; - /** @hide */ - public static final String FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX = "com.android.window.flags.enable_drag_to_desktop_incoming_transitions_bugfix"; - /** @hide */ - public static final String FLAG_ENABLE_DRAG_TO_MAXIMIZE = "com.android.window.flags.enable_drag_to_maximize"; - /** @hide */ - public static final String FLAG_ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX = "com.android.window.flags.enable_dynamic_radius_computation_bugfix"; - /** @hide */ - public static final String FLAG_ENABLE_FULL_SCREEN_WINDOW_ON_REMOVING_SPLIT_SCREEN_STAGE_BUGFIX = "com.android.window.flags.enable_full_screen_window_on_removing_split_screen_stage_bugfix"; - /** @hide */ - public static final String FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP = "com.android.window.flags.enable_fully_immersive_in_desktop"; - /** @hide */ - public static final String FLAG_ENABLE_HANDLE_INPUT_FIX = "com.android.window.flags.enable_handle_input_fix"; - /** @hide */ - public static final String FLAG_ENABLE_HOLD_TO_DRAG_APP_HANDLE = "com.android.window.flags.enable_hold_to_drag_app_handle"; - /** @hide */ - public static final String FLAG_ENABLE_INPUT_LAYER_TRANSITION_FIX = "com.android.window.flags.enable_input_layer_transition_fix"; - /** @hide */ - public static final String FLAG_ENABLE_MINIMIZE_BUTTON = "com.android.window.flags.enable_minimize_button"; - /** @hide */ - public static final String FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PERMISSION = "com.android.window.flags.enable_modals_fullscreen_with_permission"; - /** @hide */ - public static final String FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT = "com.android.window.flags.enable_move_to_next_display_shortcut"; - /** @hide */ - public static final String FLAG_ENABLE_MULTI_DISPLAY_SPLIT = "com.android.window.flags.enable_multi_display_split"; - /** @hide */ - public static final String FLAG_ENABLE_MULTIDISPLAY_TRACKPAD_BACK_GESTURE = "com.android.window.flags.enable_multidisplay_trackpad_back_gesture"; - /** @hide */ - public static final String FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND = "com.android.window.flags.enable_multiple_desktops_backend"; - /** @hide */ - public static final String FLAG_ENABLE_MULTIPLE_DESKTOPS_FRONTEND = "com.android.window.flags.enable_multiple_desktops_frontend"; - /** @hide */ - public static final String FLAG_ENABLE_NON_DEFAULT_DISPLAY_SPLIT = "com.android.window.flags.enable_non_default_display_split"; - /** @hide */ - public static final String FLAG_ENABLE_OPAQUE_BACKGROUND_FOR_TRANSPARENT_WINDOWS = "com.android.window.flags.enable_opaque_background_for_transparent_windows"; - /** @hide */ - public static final String FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY = "com.android.window.flags.enable_per_display_desktop_wallpaper_activity"; - /** @hide */ - public static final String FLAG_ENABLE_PER_DISPLAY_PACKAGE_CONTEXT_CACHE_IN_STATUSBAR_NOTIF = "com.android.window.flags.enable_per_display_package_context_cache_in_statusbar_notif"; - /** @hide */ - public static final String FLAG_ENABLE_PERSISTING_DISPLAY_SIZE_FOR_CONNECTED_DISPLAYS = "com.android.window.flags.enable_persisting_display_size_for_connected_displays"; - /** @hide */ - public static final String FLAG_ENABLE_PRESENTATION_FOR_CONNECTED_DISPLAYS = "com.android.window.flags.enable_presentation_for_connected_displays"; - /** @hide */ - public static final String FLAG_ENABLE_PROJECTED_DISPLAY_DESKTOP_MODE = "com.android.window.flags.enable_projected_display_desktop_mode"; - /** @hide */ - public static final String FLAG_ENABLE_QUICKSWITCH_DESKTOP_SPLIT_BUGFIX = "com.android.window.flags.enable_quickswitch_desktop_split_bugfix"; - /** @hide */ - public static final String FLAG_ENABLE_REQUEST_FULLSCREEN_BUGFIX = "com.android.window.flags.enable_request_fullscreen_bugfix"; - /** @hide */ - public static final String FLAG_ENABLE_RESIZING_METRICS = "com.android.window.flags.enable_resizing_metrics"; - /** @hide */ - public static final String FLAG_ENABLE_RESTART_MENU_FOR_CONNECTED_DISPLAYS = "com.android.window.flags.enable_restart_menu_for_connected_displays"; - /** @hide */ - public static final String FLAG_ENABLE_RESTORE_TO_PREVIOUS_SIZE_FROM_DESKTOP_IMMERSIVE = "com.android.window.flags.enable_restore_to_previous_size_from_desktop_immersive"; - /** @hide */ - public static final String FLAG_ENABLE_SHELL_INITIAL_BOUNDS_REGRESSION_BUG_FIX = "com.android.window.flags.enable_shell_initial_bounds_regression_bug_fix"; - /** @hide */ - public static final String FLAG_ENABLE_SIZE_COMPAT_MODE_IMPROVEMENTS_FOR_CONNECTED_DISPLAYS = "com.android.window.flags.enable_size_compat_mode_improvements_for_connected_displays"; - /** @hide */ - public static final String FLAG_ENABLE_START_LAUNCH_TRANSITION_FROM_TASKBAR_BUGFIX = "com.android.window.flags.enable_start_launch_transition_from_taskbar_bugfix"; - /** @hide */ - public static final String FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS = "com.android.window.flags.enable_task_resizing_keyboard_shortcuts"; + public static final String FLAG_ENABLE_SCALED_RESIZING = "com.android.window.flags.enable_scaled_resizing"; /** @hide */ public static final String FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL = "com.android.window.flags.enable_task_stack_observer_in_shell"; /** @hide */ - public static final String FLAG_ENABLE_TASKBAR_CONNECTED_DISPLAYS = "com.android.window.flags.enable_taskbar_connected_displays"; - /** @hide */ - public static final String FLAG_ENABLE_TASKBAR_OVERFLOW = "com.android.window.flags.enable_taskbar_overflow"; - /** @hide */ - public static final String FLAG_ENABLE_TASKBAR_RECENTS_LAYOUT_TRANSITION = "com.android.window.flags.enable_taskbar_recents_layout_transition"; - /** @hide */ public static final String FLAG_ENABLE_THEMED_APP_HEADERS = "com.android.window.flags.enable_themed_app_headers"; /** @hide */ - public static final String FLAG_ENABLE_TILE_RESIZING = "com.android.window.flags.enable_tile_resizing"; - /** @hide */ - public static final String FLAG_ENABLE_TOP_VISIBLE_ROOT_TASK_PER_USER_TRACKING = "com.android.window.flags.enable_top_visible_root_task_per_user_tracking"; - /** @hide */ - public static final String FLAG_ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX = "com.android.window.flags.enable_visual_indicator_in_transition_bugfix"; - /** @hide */ - public static final String FLAG_ENABLE_WINDOW_CONTEXT_RESOURCES_UPDATE_ON_CONFIG_CHANGE = "com.android.window.flags.enable_window_context_resources_update_on_config_change"; - /** @hide */ public static final String FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS = "com.android.window.flags.enable_windowing_dynamic_initial_bounds"; /** @hide */ public static final String FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE = "com.android.window.flags.enable_windowing_edge_drag_resize"; /** @hide */ - public static final String FLAG_ENABLE_WINDOWING_SCALED_RESIZING = "com.android.window.flags.enable_windowing_scaled_resizing"; - /** @hide */ - public static final String FLAG_ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS = "com.android.window.flags.enable_windowing_transition_handlers_observers"; + public static final String FLAG_ENABLE_WM_EXTENSIONS_FOR_ALL_FLAG = "com.android.window.flags.enable_wm_extensions_for_all_flag"; /** @hide */ public static final String FLAG_ENFORCE_EDGE_TO_EDGE = "com.android.window.flags.enforce_edge_to_edge"; /** @hide */ - public static final String FLAG_ENSURE_KEYGUARD_DOES_TRANSITION_STARTING = "com.android.window.flags.ensure_keyguard_does_transition_starting"; - /** @hide */ public static final String FLAG_ENSURE_WALLPAPER_IN_TRANSITIONS = "com.android.window.flags.ensure_wallpaper_in_transitions"; /** @hide */ - public static final String FLAG_ENSURE_WALLPAPER_IN_WEAR_TRANSITIONS = "com.android.window.flags.ensure_wallpaper_in_wear_transitions"; - /** @hide */ - public static final String FLAG_ENTER_DESKTOP_BY_DEFAULT_ON_FREEFORM_DISPLAYS = "com.android.window.flags.enter_desktop_by_default_on_freeform_displays"; - /** @hide */ - public static final String FLAG_EXCLUDE_CAPTION_FROM_APP_BOUNDS = "com.android.window.flags.exclude_caption_from_app_bounds"; - /** @hide */ - public static final String FLAG_EXCLUDE_DRAWING_APP_THEME_SNAPSHOT_FROM_LOCK = "com.android.window.flags.exclude_drawing_app_theme_snapshot_from_lock"; - /** @hide */ - public static final String FLAG_EXCLUDE_TASK_FROM_RECENTS = "com.android.window.flags.exclude_task_from_recents"; + public static final String FLAG_EXPLICIT_REFRESH_RATE_HINTS = "com.android.window.flags.explicit_refresh_rate_hints"; /** @hide */ public static final String FLAG_FIFO_PRIORITY_FOR_MAJOR_UI_PROCESSES = "com.android.window.flags.fifo_priority_for_major_ui_processes"; /** @hide */ - public static final String FLAG_FIX_HIDE_OVERLAY_API = "com.android.window.flags.fix_hide_overlay_api"; + public static final String FLAG_FIX_NO_CONTAINER_UPDATE_WITHOUT_RESIZE = "com.android.window.flags.fix_no_container_update_without_resize"; /** @hide */ - public static final String FLAG_FIX_LAYOUT_EXISTING_TASK = "com.android.window.flags.fix_layout_existing_task"; + public static final String FLAG_FIX_PIP_RESTORE_TO_OVERLAY = "com.android.window.flags.fix_pip_restore_to_overlay"; /** @hide */ - public static final String FLAG_FIX_VIEW_ROOT_CALL_TRACE = "com.android.window.flags.fix_view_root_call_trace"; - /** @hide */ - public static final String FLAG_FORCE_CLOSE_TOP_TRANSPARENT_FULLSCREEN_TASK = "com.android.window.flags.force_close_top_transparent_fullscreen_task"; - /** @hide */ - public static final String FLAG_FORM_FACTOR_BASED_DESKTOP_FIRST_SWITCH = "com.android.window.flags.form_factor_based_desktop_first_switch"; + public static final String FLAG_FULLSCREEN_DIM_FLAG = "com.android.window.flags.fullscreen_dim_flag"; /** @hide */ public static final String FLAG_GET_DIMMER_ON_CLOSING = "com.android.window.flags.get_dimmer_on_closing"; /** @hide */ - public static final String FLAG_IGNORE_ASPECT_RATIO_RESTRICTIONS_FOR_RESIZEABLE_FREEFORM_ACTIVITIES = "com.android.window.flags.ignore_aspect_ratio_restrictions_for_resizeable_freeform_activities"; + public static final String FLAG_IMMERSIVE_APP_REPOSITIONING = "com.android.window.flags.immersive_app_repositioning"; /** @hide */ - public static final String FLAG_IGNORE_CORNER_RADIUS_AND_SHADOWS = "com.android.window.flags.ignore_corner_radius_and_shadows"; + public static final String FLAG_INSETS_CONTROL_CHANGED_ITEM = "com.android.window.flags.insets_control_changed_item"; /** @hide */ - public static final String FLAG_INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC = "com.android.window.flags.include_top_transparent_fullscreen_task_in_desktop_heuristic"; - /** @hide */ - public static final String FLAG_INHERIT_TASK_BOUNDS_FOR_TRAMPOLINE_TASK_LAUNCHES = "com.android.window.flags.inherit_task_bounds_for_trampoline_task_launches"; + public static final String FLAG_INSETS_CONTROL_SEQ = "com.android.window.flags.insets_control_seq"; /** @hide */ public static final String FLAG_INSETS_DECOUPLED_CONFIGURATION = "com.android.window.flags.insets_decoupled_configuration"; /** @hide */ - public static final String FLAG_JANK_API = "com.android.window.flags.jank_api"; + public static final String FLAG_INTRODUCE_SMOOTHER_DIMMER = "com.android.window.flags.introduce_smoother_dimmer"; /** @hide */ - public static final String FLAG_KEEP_APP_WINDOW_HIDE_WHILE_LOCKED = "com.android.window.flags.keep_app_window_hide_while_locked"; - /** @hide */ - public static final String FLAG_KEYBOARD_SHORTCUTS_TO_SWITCH_DESKS = "com.android.window.flags.keyboard_shortcuts_to_switch_desks"; - /** @hide */ - public static final String FLAG_KEYGUARD_GOING_AWAY_TIMEOUT = "com.android.window.flags.keyguard_going_away_timeout"; + public static final String FLAG_KEYGUARD_APPEAR_TRANSITION = "com.android.window.flags.keyguard_appear_transition"; /** @hide */ public static final String FLAG_LETTERBOX_BACKGROUND_WALLPAPER = "com.android.window.flags.letterbox_background_wallpaper"; /** @hide */ public static final String FLAG_MOVABLE_CUTOUT_CONFIGURATION = "com.android.window.flags.movable_cutout_configuration"; /** @hide */ - public static final String FLAG_MOVE_TO_EXTERNAL_DISPLAY_SHORTCUT = "com.android.window.flags.move_to_external_display_shortcut"; + public static final String FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE = "com.android.window.flags.move_animation_options_to_change"; /** @hide */ public static final String FLAG_MULTI_CROP = "com.android.window.flags.multi_crop"; /** @hide */ public static final String FLAG_NAV_BAR_TRANSPARENT_BY_DEFAULT = "com.android.window.flags.nav_bar_transparent_by_default"; /** @hide */ - public static final String FLAG_NESTED_TASKS_WITH_INDEPENDENT_BOUNDS = "com.android.window.flags.nested_tasks_with_independent_bounds"; - /** @hide */ public static final String FLAG_NO_CONSECUTIVE_VISIBILITY_EVENTS = "com.android.window.flags.no_consecutive_visibility_events"; /** @hide */ - public static final String FLAG_NO_DUPLICATE_SURFACE_DESTROYED_EVENTS = "com.android.window.flags.no_duplicate_surface_destroyed_events"; - /** @hide */ public static final String FLAG_NO_VISIBILITY_EVENT_ON_DISPLAY_STATE_CHANGE = "com.android.window.flags.no_visibility_event_on_display_state_change"; /** @hide */ public static final String FLAG_OFFLOAD_COLOR_EXTRACTION = "com.android.window.flags.offload_color_extraction"; /** @hide */ - public static final String FLAG_PORT_WINDOW_SIZE_ANIMATION = "com.android.window.flags.port_window_size_animation"; - /** @hide */ - public static final String FLAG_PREDICTIVE_BACK_DEFAULT_ENABLE_SDK_36 = "com.android.window.flags.predictive_back_default_enable_sdk_36"; - /** @hide */ - public static final String FLAG_PREDICTIVE_BACK_PRIORITY_SYSTEM_NAVIGATION_OBSERVER = "com.android.window.flags.predictive_back_priority_system_navigation_observer"; - /** @hide */ - public static final String FLAG_PREDICTIVE_BACK_SWIPE_EDGE_NONE_API = "com.android.window.flags.predictive_back_swipe_edge_none_api"; - /** @hide */ - public static final String FLAG_PREDICTIVE_BACK_SYSTEM_OVERRIDE_CALLBACK = "com.android.window.flags.predictive_back_system_override_callback"; - /** @hide */ - public static final String FLAG_PREDICTIVE_BACK_THREE_BUTTON_NAV = "com.android.window.flags.predictive_back_three_button_nav"; - /** @hide */ - public static final String FLAG_PREDICTIVE_BACK_TIMESTAMP_API = "com.android.window.flags.predictive_back_timestamp_api"; - /** @hide */ - public static final String FLAG_PROCESS_PRIORITY_POLICY_FOR_MULTI_WINDOW_MODE = "com.android.window.flags.process_priority_policy_for_multi_window_mode"; + public static final String FLAG_PREDICTIVE_BACK_SYSTEM_ANIMS = "com.android.window.flags.predictive_back_system_anims"; /** @hide */ public static final String FLAG_REAR_DISPLAY_DISABLE_FORCE_DESKTOP_SYSTEM_DECORATIONS = "com.android.window.flags.rear_display_disable_force_desktop_system_decorations"; /** @hide */ - public static final String FLAG_RECORD_TASK_SNAPSHOTS_BEFORE_SHUTDOWN = "com.android.window.flags.record_task_snapshots_before_shutdown"; - /** @hide */ - public static final String FLAG_REDUCE_CHANGED_EXCLUSION_RECTS_MSGS = "com.android.window.flags.reduce_changed_exclusion_rects_msgs"; - /** @hide */ - public static final String FLAG_REDUCE_KEYGUARD_TRANSITIONS = "com.android.window.flags.reduce_keyguard_transitions"; - /** @hide */ - public static final String FLAG_REDUCE_TASK_SNAPSHOT_MEMORY_USAGE = "com.android.window.flags.reduce_task_snapshot_memory_usage"; - /** @hide */ - public static final String FLAG_REDUCE_UNNECESSARY_MEASURE = "com.android.window.flags.reduce_unnecessary_measure"; - /** @hide */ - public static final String FLAG_RELATIVE_INSETS = "com.android.window.flags.relative_insets"; - /** @hide */ public static final String FLAG_RELEASE_SNAPSHOT_AGGRESSIVELY = "com.android.window.flags.release_snapshot_aggressively"; /** @hide */ - public static final String FLAG_RELEASE_USER_ASPECT_RATIO_WM = "com.android.window.flags.release_user_aspect_ratio_wm"; - /** @hide */ - public static final String FLAG_REMOVE_ACTIVITY_STARTER_DREAM_CALLBACK = "com.android.window.flags.remove_activity_starter_dream_callback"; - /** @hide */ - public static final String FLAG_REMOVE_DEFER_HIDING_CLIENT = "com.android.window.flags.remove_defer_hiding_client"; - /** @hide */ - public static final String FLAG_REMOVE_DEPART_TARGET_FROM_MOTION = "com.android.window.flags.remove_depart_target_from_motion"; - /** @hide */ - public static final String FLAG_REPARENT_WINDOW_TOKEN_API = "com.android.window.flags.reparent_window_token_api"; - /** @hide */ - public static final String FLAG_RESPECT_NON_TOP_VISIBLE_FIXED_ORIENTATION = "com.android.window.flags.respect_non_top_visible_fixed_orientation"; - /** @hide */ - public static final String FLAG_RESPECT_ORIENTATION_CHANGE_FOR_UNRESIZEABLE = "com.android.window.flags.respect_orientation_change_for_unresizeable"; - /** @hide */ - public static final String FLAG_SAFE_REGION_LETTERBOXING = "com.android.window.flags.safe_region_letterboxing"; - /** @hide */ - public static final String FLAG_SAFE_RELEASE_SNAPSHOT_AGGRESSIVELY = "com.android.window.flags.safe_release_snapshot_aggressively"; - /** @hide */ - public static final String FLAG_SCHEDULING_FOR_NOTIFICATION_SHADE = "com.android.window.flags.scheduling_for_notification_shade"; - /** @hide */ - public static final String FLAG_SCRAMBLE_SNAPSHOT_FILE_NAME = "com.android.window.flags.scramble_snapshot_file_name"; + public static final String FLAG_REMOVE_PREPARE_SURFACE_IN_PLACEMENT = "com.android.window.flags.remove_prepare_surface_in_placement"; /** @hide */ public static final String FLAG_SCREEN_RECORDING_CALLBACKS = "com.android.window.flags.screen_recording_callbacks"; /** @hide */ - public static final String FLAG_SCROLLING_FROM_LETTERBOX = "com.android.window.flags.scrolling_from_letterbox"; - /** @hide */ public static final String FLAG_SDK_DESIRED_PRESENT_TIME = "com.android.window.flags.sdk_desired_present_time"; /** @hide */ + public static final String FLAG_SECURE_WINDOW_STATE = "com.android.window.flags.secure_window_state"; + /** @hide */ public static final String FLAG_SET_SC_PROPERTIES_IN_CLIENT = "com.android.window.flags.set_sc_properties_in_client"; /** @hide */ - public static final String FLAG_SHOW_APP_HANDLE_LARGE_SCREENS = "com.android.window.flags.show_app_handle_large_screens"; - /** @hide */ - public static final String FLAG_SHOW_DESKTOP_EXPERIENCE_DEV_OPTION = "com.android.window.flags.show_desktop_experience_dev_option"; - /** @hide */ - public static final String FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION = "com.android.window.flags.show_desktop_windowing_dev_option"; - /** @hide */ - public static final String FLAG_SHOW_HOME_BEHIND_DESKTOP = "com.android.window.flags.show_home_behind_desktop"; - /** @hide */ - public static final String FLAG_SKIP_COMPAT_UI_EDUCATION_IN_DESKTOP_MODE = "com.android.window.flags.skip_compat_ui_education_in_desktop_mode"; - /** @hide */ - public static final String FLAG_SKIP_DECOR_VIEW_RELAYOUT_WHEN_CLOSING_BUGFIX = "com.android.window.flags.skip_decor_view_relayout_when_closing_bugfix"; - /** @hide */ - public static final String FLAG_SUPPORT_WIDGET_INTENTS_ON_CONNECTED_DISPLAY = "com.android.window.flags.support_widget_intents_on_connected_display"; - /** @hide */ - public static final String FLAG_SUPPORTS_DRAG_ASSISTANT_TO_MULTIWINDOW = "com.android.window.flags.supports_drag_assistant_to_multiwindow"; + public static final String FLAG_SKIP_SLEEPING_WHEN_SWITCHING_DISPLAY = "com.android.window.flags.skip_sleeping_when_switching_display"; /** @hide */ public static final String FLAG_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI = "com.android.window.flags.supports_multi_instance_system_ui"; /** @hide */ @@ -494,1899 +189,442 @@ public final class Flags { /** @hide */ public static final String FLAG_SYNC_SCREEN_CAPTURE = "com.android.window.flags.sync_screen_capture"; /** @hide */ - public static final String FLAG_SYSTEM_UI_POST_ANIMATION_END = "com.android.window.flags.system_ui_post_animation_end"; - /** @hide */ public static final String FLAG_TASK_FRAGMENT_SYSTEM_ORGANIZER_FLAG = "com.android.window.flags.task_fragment_system_organizer_flag"; /** @hide */ - public static final String FLAG_TOUCH_PASS_THROUGH_OPT_IN = "com.android.window.flags.touch_pass_through_opt_in"; - /** @hide */ - public static final String FLAG_TRACK_SYSTEM_UI_CONTEXT_BEFORE_WMS = "com.android.window.flags.track_system_ui_context_before_wms"; - /** @hide */ public static final String FLAG_TRANSIT_READY_TRACKING = "com.android.window.flags.transit_ready_tracking"; /** @hide */ - public static final String FLAG_TRANSIT_TRACKER_PLUMBING = "com.android.window.flags.transit_tracker_plumbing"; - /** @hide */ public static final String FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW = "com.android.window.flags.trusted_presentation_listener_for_window"; /** @hide */ - public static final String FLAG_UNIFY_BACK_NAVIGATION_TRANSITION = "com.android.window.flags.unify_back_navigation_transition"; - /** @hide */ - public static final String FLAG_UNIVERSAL_RESIZABLE_BY_DEFAULT = "com.android.window.flags.universal_resizable_by_default"; - /** @hide */ public static final String FLAG_UNTRUSTED_EMBEDDING_ANY_APP_PERMISSION = "com.android.window.flags.untrusted_embedding_any_app_permission"; /** @hide */ public static final String FLAG_UNTRUSTED_EMBEDDING_STATE_SHARING = "com.android.window.flags.untrusted_embedding_state_sharing"; /** @hide */ - public static final String FLAG_UPDATE_DIMS_WHEN_WINDOW_SHOWN = "com.android.window.flags.update_dims_when_window_shown"; - /** @hide */ - public static final String FLAG_USE_CACHED_INSETS_FOR_DISPLAY_SWITCH = "com.android.window.flags.use_cached_insets_for_display_switch"; - /** @hide */ - public static final String FLAG_USE_RT_FRAME_CALLBACK_FOR_SPLASH_SCREEN_TRANSFER = "com.android.window.flags.use_rt_frame_callback_for_splash_screen_transfer"; - /** @hide */ - public static final String FLAG_USE_TASKS_DIM_ONLY = "com.android.window.flags.use_tasks_dim_only"; - /** @hide */ - public static final String FLAG_USE_VISIBLE_REQUESTED_FOR_PROCESS_TRACKER = "com.android.window.flags.use_visible_requested_for_process_tracker"; - /** @hide */ public static final String FLAG_USE_WINDOW_ORIGINAL_TOUCHABLE_REGION_WHEN_MAGNIFICATION_RECOMPUTE_BOUNDS = "com.android.window.flags.use_window_original_touchable_region_when_magnification_recompute_bounds"; /** @hide */ - public static final String FLAG_VDM_FORCE_APP_UNIVERSAL_RESIZABLE_API = "com.android.window.flags.vdm_force_app_universal_resizable_api"; + public static final String FLAG_USER_MIN_ASPECT_RATIO_APP_DEFAULT = "com.android.window.flags.user_min_aspect_ratio_app_default"; + /** @hide */ + public static final String FLAG_WAIT_FOR_TRANSITION_ON_DISPLAY_SWITCH = "com.android.window.flags.wait_for_transition_on_display_switch"; /** @hide */ public static final String FLAG_WALLPAPER_OFFSET_ASYNC = "com.android.window.flags.wallpaper_offset_async"; /** @hide */ - public static final String FLAG_WLINFO_ONCREATE = "com.android.window.flags.wlinfo_oncreate"; - - - - public static boolean actionModeEdgeToEdge() { - - return FEATURE_FLAGS.actionModeEdgeToEdge(); - } - - - + public static final String FLAG_WINDOW_SESSION_RELAYOUT_INFO = "com.android.window.flags.window_session_relayout_info"; + /** @hide */ + public static final String FLAG_WINDOW_TOKEN_CONFIG_THREAD_SAFE = "com.android.window.flags.window_token_config_thread_safe"; + public static boolean activityEmbeddingAnimationCustomizationFlag() { - return FEATURE_FLAGS.activityEmbeddingAnimationCustomizationFlag(); } - - - - public static boolean activityEmbeddingDelayTaskFragmentFinishForActivityLaunch() { - - return FEATURE_FLAGS.activityEmbeddingDelayTaskFragmentFinishForActivityLaunch(); - } - - - + public static boolean activityEmbeddingInteractiveDividerFlag() { - return FEATURE_FLAGS.activityEmbeddingInteractiveDividerFlag(); } - - - - public static boolean activityEmbeddingMetrics() { - - return FEATURE_FLAGS.activityEmbeddingMetrics(); + + public static boolean activityEmbeddingOverlayPresentationFlag() { + return FEATURE_FLAGS.activityEmbeddingOverlayPresentationFlag(); } - - - - public static boolean activityEmbeddingSupportForConnectedDisplays() { - - return FEATURE_FLAGS.activityEmbeddingSupportForConnectedDisplays(); + + public static boolean activitySnapshotByDefault() { + return FEATURE_FLAGS.activitySnapshotByDefault(); } - - - + + public static boolean activityWindowInfoFlag() { + return FEATURE_FLAGS.activityWindowInfoFlag(); + } + public static boolean allowDisableActivityRecordInputSink() { - return FEATURE_FLAGS.allowDisableActivityRecordInputSink(); } - - - + public static boolean allowHideScmButton() { - return FEATURE_FLAGS.allowHideScmButton(); } - - - + public static boolean allowsScreenSizeDecoupledFromStatusBarAndCutout() { - return FEATURE_FLAGS.allowsScreenSizeDecoupledFromStatusBarAndCutout(); } - - - + + public static boolean alwaysDeferTransitionWhenApplyWct() { + return FEATURE_FLAGS.alwaysDeferTransitionWhenApplyWct(); + } + public static boolean alwaysDrawMagnificationFullscreenBorder() { - return FEATURE_FLAGS.alwaysDrawMagnificationFullscreenBorder(); } - - - + public static boolean alwaysUpdateWallpaperPermission() { - return FEATURE_FLAGS.alwaysUpdateWallpaperPermission(); } - - - - public static boolean aodTransition() { - - return FEATURE_FLAGS.aodTransition(); - } - - - - public static boolean appCompatAsyncRelayout() { - - return FEATURE_FLAGS.appCompatAsyncRelayout(); - } - - - + public static boolean appCompatPropertiesApi() { - return FEATURE_FLAGS.appCompatPropertiesApi(); } - - - + public static boolean appCompatRefactoring() { - return FEATURE_FLAGS.appCompatRefactoring(); } - - - - public static boolean appCompatUiFramework() { - - return FEATURE_FLAGS.appCompatUiFramework(); - } - - - - public static boolean appHandleNoRelayoutOnExclusionChange() { - - return FEATURE_FLAGS.appHandleNoRelayoutOnExclusionChange(); - } - - - - public static boolean applyLifecycleOnPipChange() { - - return FEATURE_FLAGS.applyLifecycleOnPipChange(); - } - - - - public static boolean avoidRebindingIntentionallyDisconnectedWallpaper() { - - return FEATURE_FLAGS.avoidRebindingIntentionallyDisconnectedWallpaper(); - } - - - - public static boolean backupAndRestoreForUserAspectRatioSettings() { - - return FEATURE_FLAGS.backupAndRestoreForUserAspectRatioSettings(); - } - - - - public static boolean balAdditionalLogging() { - - return FEATURE_FLAGS.balAdditionalLogging(); - } - - - - public static boolean balAdditionalStartModes() { - - return FEATURE_FLAGS.balAdditionalStartModes(); - } - - - - public static boolean balClearAllowlistDuration() { - - return FEATURE_FLAGS.balClearAllowlistDuration(); - } - - - + public static boolean balDontBringExistingBackgroundTaskStackToFg() { - return FEATURE_FLAGS.balDontBringExistingBackgroundTaskStackToFg(); } - - - + public static boolean balImproveRealCallerVisibilityCheck() { - return FEATURE_FLAGS.balImproveRealCallerVisibilityCheck(); } - - - + public static boolean balImprovedMetrics() { - return FEATURE_FLAGS.balImprovedMetrics(); } - - - - public static boolean balReduceGracePeriod() { - - return FEATURE_FLAGS.balReduceGracePeriod(); - } - - - + public static boolean balRequireOptInByPendingIntentCreator() { - return FEATURE_FLAGS.balRequireOptInByPendingIntentCreator(); } - - - + + public static boolean balRequireOptInSameUid() { + return FEATURE_FLAGS.balRequireOptInSameUid(); + } + public static boolean balRespectAppSwitchStateWhenCheckBoundByForegroundUid() { - return FEATURE_FLAGS.balRespectAppSwitchStateWhenCheckBoundByForegroundUid(); } - - - - public static boolean balSendIntentWithOptions() { - - return FEATURE_FLAGS.balSendIntentWithOptions(); + + public static boolean balShowToasts() { + return FEATURE_FLAGS.balShowToasts(); } - - - + public static boolean balShowToastsBlocked() { - return FEATURE_FLAGS.balShowToastsBlocked(); } - - - - public static boolean balStrictModeGracePeriod() { - - return FEATURE_FLAGS.balStrictModeGracePeriod(); + + public static boolean blastSyncNotificationShadeOnDisplaySwitch() { + return FEATURE_FLAGS.blastSyncNotificationShadeOnDisplaySwitch(); } - - - - public static boolean balStrictModeRo() { - - return FEATURE_FLAGS.balStrictModeRo(); + + public static boolean bundleClientTransactionFlag() { + return FEATURE_FLAGS.bundleClientTransactionFlag(); } - - - - public static boolean betterSupportNonMatchParentActivity() { - - return FEATURE_FLAGS.betterSupportNonMatchParentActivity(); - } - - - - public static boolean cacheWindowStyle() { - - return FEATURE_FLAGS.cacheWindowStyle(); - } - - - + public static boolean cameraCompatForFreeform() { - return FEATURE_FLAGS.cameraCompatForFreeform(); } - - - - public static boolean cameraCompatFullscreenPickSameTaskActivity() { - - return FEATURE_FLAGS.cameraCompatFullscreenPickSameTaskActivity(); - } - - - - public static boolean checkDisabledSnapshotsInTaskPersister() { - - return FEATURE_FLAGS.checkDisabledSnapshotsInTaskPersister(); - } - - - - public static boolean cleanupDispatchPendingTransactionsRemoteException() { - - return FEATURE_FLAGS.cleanupDispatchPendingTransactionsRemoteException(); - } - - - - public static boolean clearSystemVibrator() { - - return FEATURE_FLAGS.clearSystemVibrator(); - } - - - + public static boolean closeToSquareConfigIncludesStatusBar() { - return FEATURE_FLAGS.closeToSquareConfigIncludesStatusBar(); } - - - - public static boolean condenseConfigurationChangeForSimpleMode() { - - return FEATURE_FLAGS.condenseConfigurationChangeForSimpleMode(); - } - - - + public static boolean configurableFontScaleDefault() { - return FEATURE_FLAGS.configurableFontScaleDefault(); } - - - + public static boolean coverDisplayOptIn() { - return FEATURE_FLAGS.coverDisplayOptIn(); } - - - + + public static boolean deferDisplayUpdates() { + return FEATURE_FLAGS.deferDisplayUpdates(); + } + public static boolean delayNotificationToMagnificationWhenRecentsWindowToFrontTransition() { - return FEATURE_FLAGS.delayNotificationToMagnificationWhenRecentsWindowToFrontTransition(); } - - - - public static boolean delegateBackGestureToShell() { - - return FEATURE_FLAGS.delegateBackGestureToShell(); - } - - - + public static boolean delegateUnhandledDrags() { - return FEATURE_FLAGS.delegateUnhandledDrags(); } - - - + public static boolean deleteCaptureDisplay() { - return FEATURE_FLAGS.deleteCaptureDisplay(); } - - - + public static boolean density390Api() { - return FEATURE_FLAGS.density390Api(); } - - - - public static boolean disableDesktopLaunchParamsOutsideDesktopBugFix() { - - return FEATURE_FLAGS.disableDesktopLaunchParamsOutsideDesktopBugFix(); + + public static boolean disableObjectPool() { + return FEATURE_FLAGS.disableObjectPool(); } - - - - public static boolean disableNonResizableAppSnapResizing() { - - return FEATURE_FLAGS.disableNonResizableAppSnapResizing(); + + public static boolean disableThinLetterboxingPolicy() { + return FEATURE_FLAGS.disableThinLetterboxingPolicy(); } - - - - public static boolean disableOptOutEdgeToEdge() { - - return FEATURE_FLAGS.disableOptOutEdgeToEdge(); - } - - - + public static boolean doNotCheckIntersectionWhenNonMagnifiableWindowTransitions() { - return FEATURE_FLAGS.doNotCheckIntersectionWhenNonMagnifiableWindowTransitions(); } - - - - public static boolean earlyLaunchHint() { - - return FEATURE_FLAGS.earlyLaunchHint(); + + public static boolean drawSnapshotAspectRatioMatch() { + return FEATURE_FLAGS.drawSnapshotAspectRatioMatch(); } - - - + public static boolean edgeToEdgeByDefault() { - return FEATURE_FLAGS.edgeToEdgeByDefault(); } - - - - public static boolean enableAccessibleCustomHeaders() { - - return FEATURE_FLAGS.enableAccessibleCustomHeaders(); + + public static boolean embeddedActivityBackNavFlag() { + return FEATURE_FLAGS.embeddedActivityBackNavFlag(); } - - - - public static boolean enableActivityEmbeddingSupportForConnectedDisplays() { - - return FEATURE_FLAGS.enableActivityEmbeddingSupportForConnectedDisplays(); + + public static boolean enableAdditionalWindowsAboveStatusBar() { + return FEATURE_FLAGS.enableAdditionalWindowsAboveStatusBar(); } - - - + public static boolean enableAppHeaderWithTaskDensity() { - return FEATURE_FLAGS.enableAppHeaderWithTaskDensity(); } - - - - public static boolean enableBorderSettings() { - - return FEATURE_FLAGS.enableBorderSettings(); - } - - - + public static boolean enableBufferTransformHintFromDisplay() { - return FEATURE_FLAGS.enableBufferTransformHintFromDisplay(); } - - - - public static boolean enableBugFixesForSecondaryDisplay() { - - return FEATURE_FLAGS.enableBugFixesForSecondaryDisplay(); - } - - - + public static boolean enableCameraCompatForDesktopWindowing() { - return FEATURE_FLAGS.enableCameraCompatForDesktopWindowing(); } - - - - public static boolean enableCameraCompatForDesktopWindowingOptOut() { - - return FEATURE_FLAGS.enableCameraCompatForDesktopWindowingOptOut(); - } - - - - public static boolean enableCameraCompatForDesktopWindowingOptOutApi() { - - return FEATURE_FLAGS.enableCameraCompatForDesktopWindowingOptOutApi(); - } - - - - public static boolean enableCameraCompatTrackTaskAndAppBugfix() { - - return FEATURE_FLAGS.enableCameraCompatTrackTaskAndAppBugfix(); - } - - - - public static boolean enableCaptionCompatInsetConversion() { - - return FEATURE_FLAGS.enableCaptionCompatInsetConversion(); - } - - - - public static boolean enableCaptionCompatInsetForceConsumption() { - - return FEATURE_FLAGS.enableCaptionCompatInsetForceConsumption(); - } - - - - public static boolean enableCaptionCompatInsetForceConsumptionAlways() { - - return FEATURE_FLAGS.enableCaptionCompatInsetForceConsumptionAlways(); - } - - - - public static boolean enableCascadingWindows() { - - return FEATURE_FLAGS.enableCascadingWindows(); - } - - - - public static boolean enableCompatUiVisibilityStatus() { - - return FEATURE_FLAGS.enableCompatUiVisibilityStatus(); - } - - - + public static boolean enableCompatuiSysuiLauncher() { - return FEATURE_FLAGS.enableCompatuiSysuiLauncher(); } - - - - public static boolean enableConnectedDisplaysDnd() { - - return FEATURE_FLAGS.enableConnectedDisplaysDnd(); - } - - - - public static boolean enableConnectedDisplaysPip() { - - return FEATURE_FLAGS.enableConnectedDisplaysPip(); - } - - - - public static boolean enableConnectedDisplaysWindowDrag() { - - return FEATURE_FLAGS.enableConnectedDisplaysWindowDrag(); - } - - - - public static boolean enableDesktopAppHandleAnimation() { - - return FEATURE_FLAGS.enableDesktopAppHandleAnimation(); - } - - - - public static boolean enableDesktopAppLaunchAlttabTransitions() { - - return FEATURE_FLAGS.enableDesktopAppLaunchAlttabTransitions(); - } - - - - public static boolean enableDesktopAppLaunchAlttabTransitionsBugfix() { - - return FEATURE_FLAGS.enableDesktopAppLaunchAlttabTransitionsBugfix(); - } - - - - public static boolean enableDesktopAppLaunchTransitions() { - - return FEATURE_FLAGS.enableDesktopAppLaunchTransitions(); - } - - - - public static boolean enableDesktopAppLaunchTransitionsBugfix() { - - return FEATURE_FLAGS.enableDesktopAppLaunchTransitionsBugfix(); - } - - - - public static boolean enableDesktopCloseShortcutBugfix() { - - return FEATURE_FLAGS.enableDesktopCloseShortcutBugfix(); - } - - - - public static boolean enableDesktopCloseTaskAnimationInDtcBugfix() { - - return FEATURE_FLAGS.enableDesktopCloseTaskAnimationInDtcBugfix(); - } - - - - public static boolean enableDesktopImeBugfix() { - - return FEATURE_FLAGS.enableDesktopImeBugfix(); - } - - - - public static boolean enableDesktopImmersiveDragBugfix() { - - return FEATURE_FLAGS.enableDesktopImmersiveDragBugfix(); - } - - - - public static boolean enableDesktopIndicatorInSeparateThreadBugfix() { - - return FEATURE_FLAGS.enableDesktopIndicatorInSeparateThreadBugfix(); - } - - - - public static boolean enableDesktopModeThroughDevOption() { - - return FEATURE_FLAGS.enableDesktopModeThroughDevOption(); - } - - - - public static boolean enableDesktopOpeningDeeplinkMinimizeAnimationBugfix() { - - return FEATURE_FLAGS.enableDesktopOpeningDeeplinkMinimizeAnimationBugfix(); - } - - - - public static boolean enableDesktopRecentsTransitionsCornersBugfix() { - - return FEATURE_FLAGS.enableDesktopRecentsTransitionsCornersBugfix(); - } - - - - public static boolean enableDesktopSwipeBackMinimizeAnimationBugfix() { - - return FEATURE_FLAGS.enableDesktopSwipeBackMinimizeAnimationBugfix(); - } - - - - public static boolean enableDesktopSystemDialogsTransitions() { - - return FEATURE_FLAGS.enableDesktopSystemDialogsTransitions(); - } - - - - public static boolean enableDesktopTabTearingMinimizeAnimationBugfix() { - - return FEATURE_FLAGS.enableDesktopTabTearingMinimizeAnimationBugfix(); - } - - - - public static boolean enableDesktopTaskbarOnFreeformDisplays() { - - return FEATURE_FLAGS.enableDesktopTaskbarOnFreeformDisplays(); - } - - - - public static boolean enableDesktopTrampolineCloseAnimationBugfix() { - - return FEATURE_FLAGS.enableDesktopTrampolineCloseAnimationBugfix(); - } - - - - public static boolean enableDesktopWallpaperActivityForSystemUser() { - - return FEATURE_FLAGS.enableDesktopWallpaperActivityForSystemUser(); - } - - - - public static boolean enableDesktopWindowingAppHandleEducation() { - - return FEATURE_FLAGS.enableDesktopWindowingAppHandleEducation(); - } - - - - public static boolean enableDesktopWindowingAppToWeb() { - - return FEATURE_FLAGS.enableDesktopWindowingAppToWeb(); - } - - - - public static boolean enableDesktopWindowingAppToWebEducation() { - - return FEATURE_FLAGS.enableDesktopWindowingAppToWebEducation(); - } - - - - public static boolean enableDesktopWindowingAppToWebEducationIntegration() { - - return FEATURE_FLAGS.enableDesktopWindowingAppToWebEducationIntegration(); - } - - - - public static boolean enableDesktopWindowingBackNavigation() { - - return FEATURE_FLAGS.enableDesktopWindowingBackNavigation(); - } - - - - public static boolean enableDesktopWindowingEnterTransitionBugfix() { - - return FEATURE_FLAGS.enableDesktopWindowingEnterTransitionBugfix(); - } - - - - public static boolean enableDesktopWindowingEnterTransitions() { - - return FEATURE_FLAGS.enableDesktopWindowingEnterTransitions(); - } - - - - public static boolean enableDesktopWindowingExitByMinimizeTransitionBugfix() { - - return FEATURE_FLAGS.enableDesktopWindowingExitByMinimizeTransitionBugfix(); - } - - - - public static boolean enableDesktopWindowingExitTransitions() { - - return FEATURE_FLAGS.enableDesktopWindowingExitTransitions(); - } - - - - public static boolean enableDesktopWindowingExitTransitionsBugfix() { - - return FEATURE_FLAGS.enableDesktopWindowingExitTransitionsBugfix(); - } - - - - public static boolean enableDesktopWindowingHsum() { - - return FEATURE_FLAGS.enableDesktopWindowingHsum(); - } - - - + public static boolean enableDesktopWindowingImmersiveHandleHiding() { - return FEATURE_FLAGS.enableDesktopWindowingImmersiveHandleHiding(); } - - - + public static boolean enableDesktopWindowingModalsPolicy() { - return FEATURE_FLAGS.enableDesktopWindowingModalsPolicy(); } - - - + public static boolean enableDesktopWindowingMode() { - return FEATURE_FLAGS.enableDesktopWindowingMode(); } - - - - public static boolean enableDesktopWindowingMultiInstanceFeatures() { - - return FEATURE_FLAGS.enableDesktopWindowingMultiInstanceFeatures(); - } - - - - public static boolean enableDesktopWindowingPersistence() { - - return FEATURE_FLAGS.enableDesktopWindowingPersistence(); - } - - - - public static boolean enableDesktopWindowingPip() { - - return FEATURE_FLAGS.enableDesktopWindowingPip(); - } - - - + public static boolean enableDesktopWindowingQuickSwitch() { - return FEATURE_FLAGS.enableDesktopWindowingQuickSwitch(); } - - - - public static boolean enableDesktopWindowingScvhCacheBugFix() { - - return FEATURE_FLAGS.enableDesktopWindowingScvhCacheBugFix(); + + public static boolean enableDesktopWindowingScvhCache() { + return FEATURE_FLAGS.enableDesktopWindowingScvhCache(); } - - - + public static boolean enableDesktopWindowingSizeConstraints() { - return FEATURE_FLAGS.enableDesktopWindowingSizeConstraints(); } - - - + public static boolean enableDesktopWindowingTaskLimit() { - return FEATURE_FLAGS.enableDesktopWindowingTaskLimit(); } - - - + public static boolean enableDesktopWindowingTaskbarRunningApps() { - return FEATURE_FLAGS.enableDesktopWindowingTaskbarRunningApps(); } - - - - public static boolean enableDesktopWindowingTransitions() { - - return FEATURE_FLAGS.enableDesktopWindowingTransitions(); - } - - - + public static boolean enableDesktopWindowingWallpaperActivity() { - return FEATURE_FLAGS.enableDesktopWindowingWallpaperActivity(); } - - - - public static boolean enableDeviceStateAutoRotateSettingLogging() { - - return FEATURE_FLAGS.enableDeviceStateAutoRotateSettingLogging(); + + public static boolean enableScaledResizing() { + return FEATURE_FLAGS.enableScaledResizing(); } - - - - public static boolean enableDeviceStateAutoRotateSettingRefactor() { - - return FEATURE_FLAGS.enableDeviceStateAutoRotateSettingRefactor(); - } - - - - public static boolean enableDisplayDisconnectInteraction() { - - return FEATURE_FLAGS.enableDisplayDisconnectInteraction(); - } - - - - public static boolean enableDisplayFocusInShellTransitions() { - - return FEATURE_FLAGS.enableDisplayFocusInShellTransitions(); - } - - - - public static boolean enableDisplayReconnectInteraction() { - - return FEATURE_FLAGS.enableDisplayReconnectInteraction(); - } - - - - public static boolean enableDisplayWindowingModeSwitching() { - - return FEATURE_FLAGS.enableDisplayWindowingModeSwitching(); - } - - - - public static boolean enableDragResizeSetUpInBgThread() { - - return FEATURE_FLAGS.enableDragResizeSetUpInBgThread(); - } - - - - public static boolean enableDragToDesktopIncomingTransitionsBugfix() { - - return FEATURE_FLAGS.enableDragToDesktopIncomingTransitionsBugfix(); - } - - - - public static boolean enableDragToMaximize() { - - return FEATURE_FLAGS.enableDragToMaximize(); - } - - - - public static boolean enableDynamicRadiusComputationBugfix() { - - return FEATURE_FLAGS.enableDynamicRadiusComputationBugfix(); - } - - - - public static boolean enableFullScreenWindowOnRemovingSplitScreenStageBugfix() { - - return FEATURE_FLAGS.enableFullScreenWindowOnRemovingSplitScreenStageBugfix(); - } - - - - public static boolean enableFullyImmersiveInDesktop() { - - return FEATURE_FLAGS.enableFullyImmersiveInDesktop(); - } - - - - public static boolean enableHandleInputFix() { - - return FEATURE_FLAGS.enableHandleInputFix(); - } - - - - public static boolean enableHoldToDragAppHandle() { - - return FEATURE_FLAGS.enableHoldToDragAppHandle(); - } - - - - public static boolean enableInputLayerTransitionFix() { - - return FEATURE_FLAGS.enableInputLayerTransitionFix(); - } - - - - public static boolean enableMinimizeButton() { - - return FEATURE_FLAGS.enableMinimizeButton(); - } - - - - public static boolean enableModalsFullscreenWithPermission() { - - return FEATURE_FLAGS.enableModalsFullscreenWithPermission(); - } - - - - public static boolean enableMoveToNextDisplayShortcut() { - - return FEATURE_FLAGS.enableMoveToNextDisplayShortcut(); - } - - - - public static boolean enableMultiDisplaySplit() { - - return FEATURE_FLAGS.enableMultiDisplaySplit(); - } - - - - public static boolean enableMultidisplayTrackpadBackGesture() { - - return FEATURE_FLAGS.enableMultidisplayTrackpadBackGesture(); - } - - - - public static boolean enableMultipleDesktopsBackend() { - - return FEATURE_FLAGS.enableMultipleDesktopsBackend(); - } - - - - public static boolean enableMultipleDesktopsFrontend() { - - return FEATURE_FLAGS.enableMultipleDesktopsFrontend(); - } - - - - public static boolean enableNonDefaultDisplaySplit() { - - return FEATURE_FLAGS.enableNonDefaultDisplaySplit(); - } - - - - public static boolean enableOpaqueBackgroundForTransparentWindows() { - - return FEATURE_FLAGS.enableOpaqueBackgroundForTransparentWindows(); - } - - - - public static boolean enablePerDisplayDesktopWallpaperActivity() { - - return FEATURE_FLAGS.enablePerDisplayDesktopWallpaperActivity(); - } - - - - public static boolean enablePerDisplayPackageContextCacheInStatusbarNotif() { - - return FEATURE_FLAGS.enablePerDisplayPackageContextCacheInStatusbarNotif(); - } - - - - public static boolean enablePersistingDisplaySizeForConnectedDisplays() { - - return FEATURE_FLAGS.enablePersistingDisplaySizeForConnectedDisplays(); - } - - - - public static boolean enablePresentationForConnectedDisplays() { - - return FEATURE_FLAGS.enablePresentationForConnectedDisplays(); - } - - - - public static boolean enableProjectedDisplayDesktopMode() { - - return FEATURE_FLAGS.enableProjectedDisplayDesktopMode(); - } - - - - public static boolean enableQuickswitchDesktopSplitBugfix() { - - return FEATURE_FLAGS.enableQuickswitchDesktopSplitBugfix(); - } - - - - public static boolean enableRequestFullscreenBugfix() { - - return FEATURE_FLAGS.enableRequestFullscreenBugfix(); - } - - - - public static boolean enableResizingMetrics() { - - return FEATURE_FLAGS.enableResizingMetrics(); - } - - - - public static boolean enableRestartMenuForConnectedDisplays() { - - return FEATURE_FLAGS.enableRestartMenuForConnectedDisplays(); - } - - - - public static boolean enableRestoreToPreviousSizeFromDesktopImmersive() { - - return FEATURE_FLAGS.enableRestoreToPreviousSizeFromDesktopImmersive(); - } - - - - public static boolean enableShellInitialBoundsRegressionBugFix() { - - return FEATURE_FLAGS.enableShellInitialBoundsRegressionBugFix(); - } - - - - public static boolean enableSizeCompatModeImprovementsForConnectedDisplays() { - - return FEATURE_FLAGS.enableSizeCompatModeImprovementsForConnectedDisplays(); - } - - - - public static boolean enableStartLaunchTransitionFromTaskbarBugfix() { - - return FEATURE_FLAGS.enableStartLaunchTransitionFromTaskbarBugfix(); - } - - - - public static boolean enableTaskResizingKeyboardShortcuts() { - - return FEATURE_FLAGS.enableTaskResizingKeyboardShortcuts(); - } - - - + public static boolean enableTaskStackObserverInShell() { - return FEATURE_FLAGS.enableTaskStackObserverInShell(); } - - - - public static boolean enableTaskbarConnectedDisplays() { - - return FEATURE_FLAGS.enableTaskbarConnectedDisplays(); - } - - - - public static boolean enableTaskbarOverflow() { - - return FEATURE_FLAGS.enableTaskbarOverflow(); - } - - - - public static boolean enableTaskbarRecentsLayoutTransition() { - - return FEATURE_FLAGS.enableTaskbarRecentsLayoutTransition(); - } - - - + public static boolean enableThemedAppHeaders() { - return FEATURE_FLAGS.enableThemedAppHeaders(); } - - - - public static boolean enableTileResizing() { - - return FEATURE_FLAGS.enableTileResizing(); - } - - - - public static boolean enableTopVisibleRootTaskPerUserTracking() { - - return FEATURE_FLAGS.enableTopVisibleRootTaskPerUserTracking(); - } - - - - public static boolean enableVisualIndicatorInTransitionBugfix() { - - return FEATURE_FLAGS.enableVisualIndicatorInTransitionBugfix(); - } - - - - public static boolean enableWindowContextResourcesUpdateOnConfigChange() { - - return FEATURE_FLAGS.enableWindowContextResourcesUpdateOnConfigChange(); - } - - - + public static boolean enableWindowingDynamicInitialBounds() { - return FEATURE_FLAGS.enableWindowingDynamicInitialBounds(); } - - - + public static boolean enableWindowingEdgeDragResize() { - return FEATURE_FLAGS.enableWindowingEdgeDragResize(); } - - - - public static boolean enableWindowingScaledResizing() { - - return FEATURE_FLAGS.enableWindowingScaledResizing(); + + public static boolean enableWmExtensionsForAllFlag() { + return FEATURE_FLAGS.enableWmExtensionsForAllFlag(); } - - - - public static boolean enableWindowingTransitionHandlersObservers() { - - return FEATURE_FLAGS.enableWindowingTransitionHandlersObservers(); - } - - - + public static boolean enforceEdgeToEdge() { - return FEATURE_FLAGS.enforceEdgeToEdge(); } - - - - public static boolean ensureKeyguardDoesTransitionStarting() { - - return FEATURE_FLAGS.ensureKeyguardDoesTransitionStarting(); - } - - - + public static boolean ensureWallpaperInTransitions() { - return FEATURE_FLAGS.ensureWallpaperInTransitions(); } - - - - public static boolean ensureWallpaperInWearTransitions() { - - return FEATURE_FLAGS.ensureWallpaperInWearTransitions(); + + public static boolean explicitRefreshRateHints() { + return FEATURE_FLAGS.explicitRefreshRateHints(); } - - - - public static boolean enterDesktopByDefaultOnFreeformDisplays() { - - return FEATURE_FLAGS.enterDesktopByDefaultOnFreeformDisplays(); - } - - - - public static boolean excludeCaptionFromAppBounds() { - - return FEATURE_FLAGS.excludeCaptionFromAppBounds(); - } - - - - public static boolean excludeDrawingAppThemeSnapshotFromLock() { - - return FEATURE_FLAGS.excludeDrawingAppThemeSnapshotFromLock(); - } - - - - public static boolean excludeTaskFromRecents() { - - return FEATURE_FLAGS.excludeTaskFromRecents(); - } - - - + public static boolean fifoPriorityForMajorUiProcesses() { - return FEATURE_FLAGS.fifoPriorityForMajorUiProcesses(); } - - - - public static boolean fixHideOverlayApi() { - - return FEATURE_FLAGS.fixHideOverlayApi(); + + public static boolean fixNoContainerUpdateWithoutResize() { + return FEATURE_FLAGS.fixNoContainerUpdateWithoutResize(); } - - - - public static boolean fixLayoutExistingTask() { - - return FEATURE_FLAGS.fixLayoutExistingTask(); + + public static boolean fixPipRestoreToOverlay() { + return FEATURE_FLAGS.fixPipRestoreToOverlay(); } - - - - public static boolean fixViewRootCallTrace() { - - return FEATURE_FLAGS.fixViewRootCallTrace(); + + public static boolean fullscreenDimFlag() { + return FEATURE_FLAGS.fullscreenDimFlag(); } - - - - public static boolean forceCloseTopTransparentFullscreenTask() { - - return FEATURE_FLAGS.forceCloseTopTransparentFullscreenTask(); - } - - - - public static boolean formFactorBasedDesktopFirstSwitch() { - - return FEATURE_FLAGS.formFactorBasedDesktopFirstSwitch(); - } - - - + public static boolean getDimmerOnClosing() { - return FEATURE_FLAGS.getDimmerOnClosing(); } - - - - public static boolean ignoreAspectRatioRestrictionsForResizeableFreeformActivities() { - - return FEATURE_FLAGS.ignoreAspectRatioRestrictionsForResizeableFreeformActivities(); + + public static boolean immersiveAppRepositioning() { + return FEATURE_FLAGS.immersiveAppRepositioning(); } - - - - public static boolean ignoreCornerRadiusAndShadows() { - - return FEATURE_FLAGS.ignoreCornerRadiusAndShadows(); + + public static boolean insetsControlChangedItem() { + return FEATURE_FLAGS.insetsControlChangedItem(); } - - - - public static boolean includeTopTransparentFullscreenTaskInDesktopHeuristic() { - - return FEATURE_FLAGS.includeTopTransparentFullscreenTaskInDesktopHeuristic(); + + public static boolean insetsControlSeq() { + return FEATURE_FLAGS.insetsControlSeq(); } - - - - public static boolean inheritTaskBoundsForTrampolineTaskLaunches() { - - return FEATURE_FLAGS.inheritTaskBoundsForTrampolineTaskLaunches(); - } - - - + public static boolean insetsDecoupledConfiguration() { - return FEATURE_FLAGS.insetsDecoupledConfiguration(); } - - - - public static boolean jankApi() { - - return FEATURE_FLAGS.jankApi(); + + public static boolean introduceSmootherDimmer() { + return FEATURE_FLAGS.introduceSmootherDimmer(); } - - - - public static boolean keepAppWindowHideWhileLocked() { - - return FEATURE_FLAGS.keepAppWindowHideWhileLocked(); + + public static boolean keyguardAppearTransition() { + return FEATURE_FLAGS.keyguardAppearTransition(); } - - - - public static boolean keyboardShortcutsToSwitchDesks() { - - return FEATURE_FLAGS.keyboardShortcutsToSwitchDesks(); - } - - - - public static boolean keyguardGoingAwayTimeout() { - - return FEATURE_FLAGS.keyguardGoingAwayTimeout(); - } - - - + public static boolean letterboxBackgroundWallpaper() { - return FEATURE_FLAGS.letterboxBackgroundWallpaper(); } - - - + public static boolean movableCutoutConfiguration() { - return FEATURE_FLAGS.movableCutoutConfiguration(); } - - - - public static boolean moveToExternalDisplayShortcut() { - - return FEATURE_FLAGS.moveToExternalDisplayShortcut(); + + public static boolean moveAnimationOptionsToChange() { + return FEATURE_FLAGS.moveAnimationOptionsToChange(); } - - - + public static boolean multiCrop() { - return FEATURE_FLAGS.multiCrop(); } - - - + public static boolean navBarTransparentByDefault() { - return FEATURE_FLAGS.navBarTransparentByDefault(); } - - - - public static boolean nestedTasksWithIndependentBounds() { - - return FEATURE_FLAGS.nestedTasksWithIndependentBounds(); - } - - - + public static boolean noConsecutiveVisibilityEvents() { - return FEATURE_FLAGS.noConsecutiveVisibilityEvents(); } - - - - public static boolean noDuplicateSurfaceDestroyedEvents() { - - return FEATURE_FLAGS.noDuplicateSurfaceDestroyedEvents(); - } - - - + public static boolean noVisibilityEventOnDisplayStateChange() { - return FEATURE_FLAGS.noVisibilityEventOnDisplayStateChange(); } - - - + public static boolean offloadColorExtraction() { - return FEATURE_FLAGS.offloadColorExtraction(); } - - - - public static boolean portWindowSizeAnimation() { - - return FEATURE_FLAGS.portWindowSizeAnimation(); + + public static boolean predictiveBackSystemAnims() { + return FEATURE_FLAGS.predictiveBackSystemAnims(); } - - - - public static boolean predictiveBackDefaultEnableSdk36() { - - return FEATURE_FLAGS.predictiveBackDefaultEnableSdk36(); - } - - - - public static boolean predictiveBackPrioritySystemNavigationObserver() { - - return FEATURE_FLAGS.predictiveBackPrioritySystemNavigationObserver(); - } - - - - public static boolean predictiveBackSwipeEdgeNoneApi() { - - return FEATURE_FLAGS.predictiveBackSwipeEdgeNoneApi(); - } - - - - public static boolean predictiveBackSystemOverrideCallback() { - - return FEATURE_FLAGS.predictiveBackSystemOverrideCallback(); - } - - - - public static boolean predictiveBackThreeButtonNav() { - - return FEATURE_FLAGS.predictiveBackThreeButtonNav(); - } - - - - public static boolean predictiveBackTimestampApi() { - - return FEATURE_FLAGS.predictiveBackTimestampApi(); - } - - - - public static boolean processPriorityPolicyForMultiWindowMode() { - - return FEATURE_FLAGS.processPriorityPolicyForMultiWindowMode(); - } - - - + public static boolean rearDisplayDisableForceDesktopSystemDecorations() { - return FEATURE_FLAGS.rearDisplayDisableForceDesktopSystemDecorations(); } - - - - public static boolean recordTaskSnapshotsBeforeShutdown() { - - return FEATURE_FLAGS.recordTaskSnapshotsBeforeShutdown(); - } - - - - public static boolean reduceChangedExclusionRectsMsgs() { - - return FEATURE_FLAGS.reduceChangedExclusionRectsMsgs(); - } - - - - public static boolean reduceKeyguardTransitions() { - - return FEATURE_FLAGS.reduceKeyguardTransitions(); - } - - - - public static boolean reduceTaskSnapshotMemoryUsage() { - - return FEATURE_FLAGS.reduceTaskSnapshotMemoryUsage(); - } - - - - public static boolean reduceUnnecessaryMeasure() { - - return FEATURE_FLAGS.reduceUnnecessaryMeasure(); - } - - - - public static boolean relativeInsets() { - - return FEATURE_FLAGS.relativeInsets(); - } - - - + public static boolean releaseSnapshotAggressively() { - return FEATURE_FLAGS.releaseSnapshotAggressively(); } - - - - public static boolean releaseUserAspectRatioWm() { - - return FEATURE_FLAGS.releaseUserAspectRatioWm(); + + public static boolean removePrepareSurfaceInPlacement() { + return FEATURE_FLAGS.removePrepareSurfaceInPlacement(); } - - - - public static boolean removeActivityStarterDreamCallback() { - - return FEATURE_FLAGS.removeActivityStarterDreamCallback(); - } - - - - public static boolean removeDeferHidingClient() { - - return FEATURE_FLAGS.removeDeferHidingClient(); - } - - - - public static boolean removeDepartTargetFromMotion() { - - return FEATURE_FLAGS.removeDepartTargetFromMotion(); - } - - - - public static boolean reparentWindowTokenApi() { - - return FEATURE_FLAGS.reparentWindowTokenApi(); - } - - - - public static boolean respectNonTopVisibleFixedOrientation() { - - return FEATURE_FLAGS.respectNonTopVisibleFixedOrientation(); - } - - - - public static boolean respectOrientationChangeForUnresizeable() { - - return FEATURE_FLAGS.respectOrientationChangeForUnresizeable(); - } - - - - public static boolean safeRegionLetterboxing() { - - return FEATURE_FLAGS.safeRegionLetterboxing(); - } - - - - public static boolean safeReleaseSnapshotAggressively() { - - return FEATURE_FLAGS.safeReleaseSnapshotAggressively(); - } - - - - public static boolean schedulingForNotificationShade() { - - return FEATURE_FLAGS.schedulingForNotificationShade(); - } - - - - public static boolean scrambleSnapshotFileName() { - - return FEATURE_FLAGS.scrambleSnapshotFileName(); - } - - - + public static boolean screenRecordingCallbacks() { - return FEATURE_FLAGS.screenRecordingCallbacks(); } - - - - public static boolean scrollingFromLetterbox() { - - return FEATURE_FLAGS.scrollingFromLetterbox(); - } - - - + public static boolean sdkDesiredPresentTime() { - return FEATURE_FLAGS.sdkDesiredPresentTime(); } - - - + + public static boolean secureWindowState() { + return FEATURE_FLAGS.secureWindowState(); + } + public static boolean setScPropertiesInClient() { - return FEATURE_FLAGS.setScPropertiesInClient(); } - - - - public static boolean showAppHandleLargeScreens() { - - return FEATURE_FLAGS.showAppHandleLargeScreens(); + + public static boolean skipSleepingWhenSwitchingDisplay() { + return FEATURE_FLAGS.skipSleepingWhenSwitchingDisplay(); } - - - - public static boolean showDesktopExperienceDevOption() { - - return FEATURE_FLAGS.showDesktopExperienceDevOption(); - } - - - - public static boolean showDesktopWindowingDevOption() { - - return FEATURE_FLAGS.showDesktopWindowingDevOption(); - } - - - - public static boolean showHomeBehindDesktop() { - - return FEATURE_FLAGS.showHomeBehindDesktop(); - } - - - - public static boolean skipCompatUiEducationInDesktopMode() { - - return FEATURE_FLAGS.skipCompatUiEducationInDesktopMode(); - } - - - - public static boolean skipDecorViewRelayoutWhenClosingBugfix() { - - return FEATURE_FLAGS.skipDecorViewRelayoutWhenClosingBugfix(); - } - - - - public static boolean supportWidgetIntentsOnConnectedDisplay() { - - return FEATURE_FLAGS.supportWidgetIntentsOnConnectedDisplay(); - } - - - - public static boolean supportsDragAssistantToMultiwindow() { - - return FEATURE_FLAGS.supportsDragAssistantToMultiwindow(); - } - - - + public static boolean supportsMultiInstanceSystemUi() { - return FEATURE_FLAGS.supportsMultiInstanceSystemUi(); } - - - + public static boolean surfaceControlInputReceiver() { - return FEATURE_FLAGS.surfaceControlInputReceiver(); } - - - + public static boolean surfaceTrustedOverlay() { - return FEATURE_FLAGS.surfaceTrustedOverlay(); } - - - + public static boolean syncScreenCapture() { - return FEATURE_FLAGS.syncScreenCapture(); } - - - - public static boolean systemUiPostAnimationEnd() { - - return FEATURE_FLAGS.systemUiPostAnimationEnd(); - } - - - + public static boolean taskFragmentSystemOrganizerFlag() { - return FEATURE_FLAGS.taskFragmentSystemOrganizerFlag(); } - - - - public static boolean touchPassThroughOptIn() { - - return FEATURE_FLAGS.touchPassThroughOptIn(); - } - - - - public static boolean trackSystemUiContextBeforeWms() { - - return FEATURE_FLAGS.trackSystemUiContextBeforeWms(); - } - - - + public static boolean transitReadyTracking() { - return FEATURE_FLAGS.transitReadyTracking(); } - - - - public static boolean transitTrackerPlumbing() { - - return FEATURE_FLAGS.transitTrackerPlumbing(); - } - - - + public static boolean trustedPresentationListenerForWindow() { - return FEATURE_FLAGS.trustedPresentationListenerForWindow(); } - - - - public static boolean unifyBackNavigationTransition() { - - return FEATURE_FLAGS.unifyBackNavigationTransition(); - } - - - - public static boolean universalResizableByDefault() { - - return FEATURE_FLAGS.universalResizableByDefault(); - } - - - + public static boolean untrustedEmbeddingAnyAppPermission() { - return FEATURE_FLAGS.untrustedEmbeddingAnyAppPermission(); } - - - + public static boolean untrustedEmbeddingStateSharing() { - return FEATURE_FLAGS.untrustedEmbeddingStateSharing(); } - - - - public static boolean updateDimsWhenWindowShown() { - - return FEATURE_FLAGS.updateDimsWhenWindowShown(); - } - - - - public static boolean useCachedInsetsForDisplaySwitch() { - - return FEATURE_FLAGS.useCachedInsetsForDisplaySwitch(); - } - - - - public static boolean useRtFrameCallbackForSplashScreenTransfer() { - - return FEATURE_FLAGS.useRtFrameCallbackForSplashScreenTransfer(); - } - - - - public static boolean useTasksDimOnly() { - - return FEATURE_FLAGS.useTasksDimOnly(); - } - - - - public static boolean useVisibleRequestedForProcessTracker() { - - return FEATURE_FLAGS.useVisibleRequestedForProcessTracker(); - } - - - + public static boolean useWindowOriginalTouchableRegionWhenMagnificationRecomputeBounds() { - return FEATURE_FLAGS.useWindowOriginalTouchableRegionWhenMagnificationRecomputeBounds(); } - - - - public static boolean vdmForceAppUniversalResizableApi() { - - return FEATURE_FLAGS.vdmForceAppUniversalResizableApi(); + + public static boolean userMinAspectRatioAppDefault() { + return FEATURE_FLAGS.userMinAspectRatioAppDefault(); } - - - + + public static boolean waitForTransitionOnDisplaySwitch() { + return FEATURE_FLAGS.waitForTransitionOnDisplaySwitch(); + } + public static boolean wallpaperOffsetAsync() { - return FEATURE_FLAGS.wallpaperOffsetAsync(); } - - - - public static boolean wlinfoOncreate() { - - return FEATURE_FLAGS.wlinfoOncreate(); + + public static boolean windowSessionRelayoutInfo() { + return FEATURE_FLAGS.windowSessionRelayoutInfo(); + } + + public static boolean windowTokenConfigThreadSafe() { + return FEATURE_FLAGS.windowTokenConfigThreadSafe(); } private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl(); diff --git a/flags/src/com/android/wm/shell/CustomFeatureFlags.java b/flags/src/com/android/wm/shell/CustomFeatureFlags.java index ce1e21d693..e8a55c4591 100644 --- a/flags/src/com/android/wm/shell/CustomFeatureFlags.java +++ b/flags/src/com/android/wm/shell/CustomFeatureFlags.java @@ -1,13 +1,13 @@ package com.android.wm.shell; // TODO(b/303773055): Remove the annotation after access issue is resolved. - import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.function.BiPredicate; import java.util.function.Predicate; + /** @hide */ public class CustomFeatureFlags implements FeatureFlags { @@ -18,216 +18,124 @@ public class CustomFeatureFlags implements FeatureFlags { } @Override - public boolean bubbleViewInfoExecutors() { - return getValue(Flags.FLAG_BUBBLE_VIEW_INFO_EXECUTORS, - FeatureFlags::bubbleViewInfoExecutors); + public boolean animateBubbleSizeChange() { + return getValue(Flags.FLAG_ANIMATE_BUBBLE_SIZE_CHANGE, + FeatureFlags::animateBubbleSizeChange); } @Override - - public boolean enableAutoTaskStackController() { - return getValue(Flags.FLAG_ENABLE_AUTO_TASK_STACK_CONTROLLER, - FeatureFlags::enableAutoTaskStackController); + + public boolean enableAppPairs() { + return getValue(Flags.FLAG_ENABLE_APP_PAIRS, + FeatureFlags::enableAppPairs); } @Override - + public boolean enableBubbleAnything() { return getValue(Flags.FLAG_ENABLE_BUBBLE_ANYTHING, - FeatureFlags::enableBubbleAnything); + FeatureFlags::enableBubbleAnything); } @Override - + public boolean enableBubbleBar() { return getValue(Flags.FLAG_ENABLE_BUBBLE_BAR, - FeatureFlags::enableBubbleBar); + FeatureFlags::enableBubbleBar); } @Override - - public boolean enableBubbleBarOnPhones() { - return getValue(Flags.FLAG_ENABLE_BUBBLE_BAR_ON_PHONES, - FeatureFlags::enableBubbleBarOnPhones); - } - - @Override - + public boolean enableBubbleStashing() { return getValue(Flags.FLAG_ENABLE_BUBBLE_STASHING, - FeatureFlags::enableBubbleStashing); + FeatureFlags::enableBubbleStashing); } @Override - - public boolean enableBubbleTaskViewListener() { - return getValue(Flags.FLAG_ENABLE_BUBBLE_TASK_VIEW_LISTENER, - FeatureFlags::enableBubbleTaskViewListener); - } - - @Override - - public boolean enableBubbleToFullscreen() { - return getValue(Flags.FLAG_ENABLE_BUBBLE_TO_FULLSCREEN, - FeatureFlags::enableBubbleToFullscreen); - } - - @Override - + public boolean enableBubblesLongPressNavHandle() { return getValue(Flags.FLAG_ENABLE_BUBBLES_LONG_PRESS_NAV_HANDLE, - FeatureFlags::enableBubblesLongPressNavHandle); + FeatureFlags::enableBubblesLongPressNavHandle); } @Override - - public boolean enableCreateAnyBubble() { - return getValue(Flags.FLAG_ENABLE_CREATE_ANY_BUBBLE, - FeatureFlags::enableCreateAnyBubble); + + public boolean enableLeftRightSplitInPortrait() { + return getValue(Flags.FLAG_ENABLE_LEFT_RIGHT_SPLIT_IN_PORTRAIT, + FeatureFlags::enableLeftRightSplitInPortrait); } @Override - - public boolean enableDynamicInsetsForAppLaunch() { - return getValue(Flags.FLAG_ENABLE_DYNAMIC_INSETS_FOR_APP_LAUNCH, - FeatureFlags::enableDynamicInsetsForAppLaunch); - } - - @Override - - public boolean enableFlexibleSplit() { - return getValue(Flags.FLAG_ENABLE_FLEXIBLE_SPLIT, - FeatureFlags::enableFlexibleSplit); - } - - @Override - - public boolean enableFlexibleTwoAppSplit() { - return getValue(Flags.FLAG_ENABLE_FLEXIBLE_TWO_APP_SPLIT, - FeatureFlags::enableFlexibleTwoAppSplit); - } - - @Override - - public boolean enableGsf() { - return getValue(Flags.FLAG_ENABLE_GSF, - FeatureFlags::enableGsf); - } - - @Override - - public boolean enableMagneticSplitDivider() { - return getValue(Flags.FLAG_ENABLE_MAGNETIC_SPLIT_DIVIDER, - FeatureFlags::enableMagneticSplitDivider); - } - - @Override - + public boolean enableNewBubbleAnimations() { return getValue(Flags.FLAG_ENABLE_NEW_BUBBLE_ANIMATIONS, - FeatureFlags::enableNewBubbleAnimations); + FeatureFlags::enableNewBubbleAnimations); } @Override - + public boolean enableOptionalBubbleOverflow() { return getValue(Flags.FLAG_ENABLE_OPTIONAL_BUBBLE_OVERFLOW, - FeatureFlags::enableOptionalBubbleOverflow); + FeatureFlags::enableOptionalBubbleOverflow); } @Override - - public boolean enablePip2() { - return getValue(Flags.FLAG_ENABLE_PIP2, - FeatureFlags::enablePip2); + + public boolean enablePip2Implementation() { + return getValue(Flags.FLAG_ENABLE_PIP2_IMPLEMENTATION, + FeatureFlags::enablePip2Implementation); } @Override - + public boolean enablePipUmoExperience() { return getValue(Flags.FLAG_ENABLE_PIP_UMO_EXPERIENCE, - FeatureFlags::enablePipUmoExperience); + FeatureFlags::enablePipUmoExperience); } @Override - - public boolean enableRecentsBookendTransition() { - return getValue(Flags.FLAG_ENABLE_RECENTS_BOOKEND_TRANSITION, - FeatureFlags::enableRecentsBookendTransition); - } - - @Override - + public boolean enableRetrievableBubbles() { return getValue(Flags.FLAG_ENABLE_RETRIEVABLE_BUBBLES, - FeatureFlags::enableRetrievableBubbles); + FeatureFlags::enableRetrievableBubbles); } @Override - - public boolean enableShellTopTaskTracking() { - return getValue(Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING, - FeatureFlags::enableShellTopTaskTracking); + + public boolean enableSplitContextual() { + return getValue(Flags.FLAG_ENABLE_SPLIT_CONTEXTUAL, + FeatureFlags::enableSplitContextual); } @Override - - public boolean enableTaskViewControllerCleanup() { - return getValue(Flags.FLAG_ENABLE_TASK_VIEW_CONTROLLER_CLEANUP, - FeatureFlags::enableTaskViewControllerCleanup); - } - - @Override - + public boolean enableTaskbarNavbarUnification() { return getValue(Flags.FLAG_ENABLE_TASKBAR_NAVBAR_UNIFICATION, - FeatureFlags::enableTaskbarNavbarUnification); + FeatureFlags::enableTaskbarNavbarUnification); } @Override - - public boolean enableTaskbarOnPhones() { - return getValue(Flags.FLAG_ENABLE_TASKBAR_ON_PHONES, - FeatureFlags::enableTaskbarOnPhones); - } - - @Override - + public boolean enableTinyTaskbar() { return getValue(Flags.FLAG_ENABLE_TINY_TASKBAR, - FeatureFlags::enableTinyTaskbar); + FeatureFlags::enableTinyTaskbar); } @Override - - public boolean fixMissingUserChangeCallbacks() { - return getValue(Flags.FLAG_FIX_MISSING_USER_CHANGE_CALLBACKS, - FeatureFlags::fixMissingUserChangeCallbacks); - } - - @Override - + public boolean onlyReuseBubbledTaskWhenLaunchedFromBubble() { return getValue(Flags.FLAG_ONLY_REUSE_BUBBLED_TASK_WHEN_LAUNCHED_FROM_BUBBLE, - FeatureFlags::onlyReuseBubbledTaskWhenLaunchedFromBubble); - } - - @Override - - public boolean taskViewRepository() { - return getValue(Flags.FLAG_TASK_VIEW_REPOSITORY, - FeatureFlags::taskViewRepository); + FeatureFlags::onlyReuseBubbledTaskWhenLaunchedFromBubble); } public boolean isFlagReadOnlyOptimized(String flagName) { if (mReadOnlyFlagsSet.contains(flagName) && - isOptimizationEnabled()) { - return true; + isOptimizationEnabled()) { + return true; } return false; } - private boolean isOptimizationEnabled() { return false; } @@ -238,70 +146,29 @@ public class CustomFeatureFlags implements FeatureFlags { public List getFlagNames() { return Arrays.asList( - Flags.FLAG_BUBBLE_VIEW_INFO_EXECUTORS, - Flags.FLAG_ENABLE_AUTO_TASK_STACK_CONTROLLER, - Flags.FLAG_ENABLE_BUBBLE_ANYTHING, - Flags.FLAG_ENABLE_BUBBLE_BAR, - Flags.FLAG_ENABLE_BUBBLE_BAR_ON_PHONES, - Flags.FLAG_ENABLE_BUBBLE_STASHING, - Flags.FLAG_ENABLE_BUBBLE_TASK_VIEW_LISTENER, - Flags.FLAG_ENABLE_BUBBLE_TO_FULLSCREEN, - Flags.FLAG_ENABLE_BUBBLES_LONG_PRESS_NAV_HANDLE, - Flags.FLAG_ENABLE_CREATE_ANY_BUBBLE, - Flags.FLAG_ENABLE_DYNAMIC_INSETS_FOR_APP_LAUNCH, - Flags.FLAG_ENABLE_FLEXIBLE_SPLIT, - Flags.FLAG_ENABLE_FLEXIBLE_TWO_APP_SPLIT, - Flags.FLAG_ENABLE_GSF, - Flags.FLAG_ENABLE_MAGNETIC_SPLIT_DIVIDER, - Flags.FLAG_ENABLE_NEW_BUBBLE_ANIMATIONS, - Flags.FLAG_ENABLE_OPTIONAL_BUBBLE_OVERFLOW, - Flags.FLAG_ENABLE_PIP2, - Flags.FLAG_ENABLE_PIP_UMO_EXPERIENCE, - Flags.FLAG_ENABLE_RECENTS_BOOKEND_TRANSITION, - Flags.FLAG_ENABLE_RETRIEVABLE_BUBBLES, - Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING, - Flags.FLAG_ENABLE_TASK_VIEW_CONTROLLER_CLEANUP, - Flags.FLAG_ENABLE_TASKBAR_NAVBAR_UNIFICATION, - Flags.FLAG_ENABLE_TASKBAR_ON_PHONES, - Flags.FLAG_ENABLE_TINY_TASKBAR, - Flags.FLAG_FIX_MISSING_USER_CHANGE_CALLBACKS, - Flags.FLAG_ONLY_REUSE_BUBBLED_TASK_WHEN_LAUNCHED_FROM_BUBBLE, - Flags.FLAG_TASK_VIEW_REPOSITORY + Flags.FLAG_ANIMATE_BUBBLE_SIZE_CHANGE, + Flags.FLAG_ENABLE_APP_PAIRS, + Flags.FLAG_ENABLE_BUBBLE_ANYTHING, + Flags.FLAG_ENABLE_BUBBLE_BAR, + Flags.FLAG_ENABLE_BUBBLE_STASHING, + Flags.FLAG_ENABLE_BUBBLES_LONG_PRESS_NAV_HANDLE, + Flags.FLAG_ENABLE_LEFT_RIGHT_SPLIT_IN_PORTRAIT, + Flags.FLAG_ENABLE_NEW_BUBBLE_ANIMATIONS, + Flags.FLAG_ENABLE_OPTIONAL_BUBBLE_OVERFLOW, + Flags.FLAG_ENABLE_PIP2_IMPLEMENTATION, + Flags.FLAG_ENABLE_PIP_UMO_EXPERIENCE, + Flags.FLAG_ENABLE_RETRIEVABLE_BUBBLES, + Flags.FLAG_ENABLE_SPLIT_CONTEXTUAL, + Flags.FLAG_ENABLE_TASKBAR_NAVBAR_UNIFICATION, + Flags.FLAG_ENABLE_TINY_TASKBAR, + Flags.FLAG_ONLY_REUSE_BUBBLED_TASK_WHEN_LAUNCHED_FROM_BUBBLE ); } private Set mReadOnlyFlagsSet = new HashSet<>( - Arrays.asList( - Flags.FLAG_BUBBLE_VIEW_INFO_EXECUTORS, - Flags.FLAG_ENABLE_AUTO_TASK_STACK_CONTROLLER, - Flags.FLAG_ENABLE_BUBBLE_ANYTHING, - Flags.FLAG_ENABLE_BUBBLE_BAR, - Flags.FLAG_ENABLE_BUBBLE_BAR_ON_PHONES, - Flags.FLAG_ENABLE_BUBBLE_STASHING, - Flags.FLAG_ENABLE_BUBBLE_TASK_VIEW_LISTENER, - Flags.FLAG_ENABLE_BUBBLE_TO_FULLSCREEN, - Flags.FLAG_ENABLE_BUBBLES_LONG_PRESS_NAV_HANDLE, - Flags.FLAG_ENABLE_CREATE_ANY_BUBBLE, - Flags.FLAG_ENABLE_DYNAMIC_INSETS_FOR_APP_LAUNCH, - Flags.FLAG_ENABLE_FLEXIBLE_SPLIT, - Flags.FLAG_ENABLE_FLEXIBLE_TWO_APP_SPLIT, - Flags.FLAG_ENABLE_GSF, - Flags.FLAG_ENABLE_MAGNETIC_SPLIT_DIVIDER, - Flags.FLAG_ENABLE_NEW_BUBBLE_ANIMATIONS, - Flags.FLAG_ENABLE_OPTIONAL_BUBBLE_OVERFLOW, - Flags.FLAG_ENABLE_PIP2, - Flags.FLAG_ENABLE_PIP_UMO_EXPERIENCE, - Flags.FLAG_ENABLE_RECENTS_BOOKEND_TRANSITION, - Flags.FLAG_ENABLE_RETRIEVABLE_BUBBLES, - Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING, - Flags.FLAG_ENABLE_TASK_VIEW_CONTROLLER_CLEANUP, - Flags.FLAG_ENABLE_TASKBAR_NAVBAR_UNIFICATION, - Flags.FLAG_ENABLE_TASKBAR_ON_PHONES, - Flags.FLAG_ENABLE_TINY_TASKBAR, - Flags.FLAG_FIX_MISSING_USER_CHANGE_CALLBACKS, - Flags.FLAG_ONLY_REUSE_BUBBLED_TASK_WHEN_LAUNCHED_FROM_BUBBLE, - Flags.FLAG_TASK_VIEW_REPOSITORY, - "" - ) + Arrays.asList( + Flags.FLAG_ENABLE_PIP2_IMPLEMENTATION, + "" + ) ); } diff --git a/flags/src/com/android/wm/shell/FakeFeatureFlagsImpl.java b/flags/src/com/android/wm/shell/FakeFeatureFlagsImpl.java index ade311576c..c4594f283f 100644 --- a/flags/src/com/android/wm/shell/FakeFeatureFlagsImpl.java +++ b/flags/src/com/android/wm/shell/FakeFeatureFlagsImpl.java @@ -3,6 +3,7 @@ package com.android.wm.shell; import java.util.HashMap; import java.util.Map; import java.util.function.Predicate; + /** @hide */ public class FakeFeatureFlagsImpl extends CustomFeatureFlags { private final Map mFlagMap = new HashMap<>(); diff --git a/flags/src/com/android/wm/shell/FeatureFlags.java b/flags/src/com/android/wm/shell/FeatureFlags.java index 5441eaed38..5c5ba5bcc0 100644 --- a/flags/src/com/android/wm/shell/FeatureFlags.java +++ b/flags/src/com/android/wm/shell/FeatureFlags.java @@ -1,123 +1,53 @@ package com.android.wm.shell; // TODO(b/303773055): Remove the annotation after access issue is resolved. - /** @hide */ public interface FeatureFlags { - - - boolean bubbleViewInfoExecutors(); - - - - boolean enableAutoTaskStackController(); - - - + + boolean animateBubbleSizeChange(); + + + boolean enableAppPairs(); + + boolean enableBubbleAnything(); - - - + + boolean enableBubbleBar(); - - - - boolean enableBubbleBarOnPhones(); - - - + + boolean enableBubbleStashing(); - - - - boolean enableBubbleTaskViewListener(); - - - - boolean enableBubbleToFullscreen(); - - - + + boolean enableBubblesLongPressNavHandle(); - - - - boolean enableCreateAnyBubble(); - - - - boolean enableDynamicInsetsForAppLaunch(); - - - - boolean enableFlexibleSplit(); - - - - boolean enableFlexibleTwoAppSplit(); - - - - boolean enableGsf(); - - - - boolean enableMagneticSplitDivider(); - - - + + + boolean enableLeftRightSplitInPortrait(); + + boolean enableNewBubbleAnimations(); - - - + + boolean enableOptionalBubbleOverflow(); - - - - boolean enablePip2(); - - - + + boolean enablePip2Implementation(); + + boolean enablePipUmoExperience(); - - - - boolean enableRecentsBookendTransition(); - - - + + boolean enableRetrievableBubbles(); - - - - boolean enableShellTopTaskTracking(); - - - - boolean enableTaskViewControllerCleanup(); - - - + + + boolean enableSplitContextual(); + + boolean enableTaskbarNavbarUnification(); - - - - boolean enableTaskbarOnPhones(); - - - + + boolean enableTinyTaskbar(); - - - - boolean fixMissingUserChangeCallbacks(); - - - + + boolean onlyReuseBubbledTaskWhenLaunchedFromBubble(); - - - - boolean taskViewRepository(); } diff --git a/flags/src/com/android/wm/shell/FeatureFlagsImpl.java b/flags/src/com/android/wm/shell/FeatureFlagsImpl.java index 8620fce733..a5bba4b5ee 100644 --- a/flags/src/com/android/wm/shell/FeatureFlagsImpl.java +++ b/flags/src/com/android/wm/shell/FeatureFlagsImpl.java @@ -1,209 +1,394 @@ package com.android.wm.shell; // TODO(b/303773055): Remove the annotation after access issue is resolved. +import com.android.quickstep.util.DeviceConfigHelper; + +import java.nio.file.Files; +import java.nio.file.Paths; /** @hide */ public final class FeatureFlagsImpl implements FeatureFlags { - @Override + private static final boolean isReadFromNew = Files.exists(Paths.get("/metadata/aconfig/boot/enable_only_new_storage")); + private static volatile boolean isCached = false; + private static volatile boolean multitasking_is_cached = false; + private static boolean animateBubbleSizeChange = false; + private static boolean enableAppPairs = true; + private static boolean enableBubbleAnything = false; + private static boolean enableBubbleBar = false; + private static boolean enableBubbleStashing = false; + private static boolean enableBubblesLongPressNavHandle = false; + private static boolean enableLeftRightSplitInPortrait = true; + private static boolean enableNewBubbleAnimations = false; + private static boolean enableOptionalBubbleOverflow = true; + private static boolean enablePipUmoExperience = false; + private static boolean enableRetrievableBubbles = false; + private static boolean enableSplitContextual = true; + private static boolean enableTaskbarNavbarUnification = true; + private static boolean enableTinyTaskbar = false; + private static boolean onlyReuseBubbledTaskWhenLaunchedFromBubble = false; - public boolean bubbleViewInfoExecutors() { - return true; + private void init() { + boolean foundPackage = true; + + animateBubbleSizeChange = foundPackage; + + + enableAppPairs = foundPackage; + + + enableBubbleAnything = foundPackage; + + + enableBubbleBar = foundPackage; + + + enableBubbleStashing = foundPackage; + + + enableBubblesLongPressNavHandle = foundPackage ; + + + enableLeftRightSplitInPortrait = foundPackage; + + + enableNewBubbleAnimations = foundPackage; + + + enableOptionalBubbleOverflow = foundPackage; + + + + enablePipUmoExperience = foundPackage; + + + enableRetrievableBubbles = foundPackage; + + + enableSplitContextual = foundPackage; + + + enableTaskbarNavbarUnification = foundPackage; + + + enableTinyTaskbar = foundPackage; + + + onlyReuseBubbledTaskWhenLaunchedFromBubble = foundPackage ; + + isCached = true; + } + + + + + private void load_overrides_multitasking() { + try { + var properties = DeviceConfigHelper.Companion.getPrefs(); + animateBubbleSizeChange = + properties.getBoolean(Flags.FLAG_ANIMATE_BUBBLE_SIZE_CHANGE, false); + enableAppPairs = + properties.getBoolean(Flags.FLAG_ENABLE_APP_PAIRS, true); + enableBubbleAnything = + properties.getBoolean(Flags.FLAG_ENABLE_BUBBLE_ANYTHING, false); + enableBubbleBar = + properties.getBoolean(Flags.FLAG_ENABLE_BUBBLE_BAR, false); + enableBubbleStashing = + properties.getBoolean(Flags.FLAG_ENABLE_BUBBLE_STASHING, false); + enableBubblesLongPressNavHandle = + properties.getBoolean(Flags.FLAG_ENABLE_BUBBLES_LONG_PRESS_NAV_HANDLE, false); + enableLeftRightSplitInPortrait = + properties.getBoolean(Flags.FLAG_ENABLE_LEFT_RIGHT_SPLIT_IN_PORTRAIT, true); + enableNewBubbleAnimations = + properties.getBoolean(Flags.FLAG_ENABLE_NEW_BUBBLE_ANIMATIONS, false); + enableOptionalBubbleOverflow = + properties.getBoolean(Flags.FLAG_ENABLE_OPTIONAL_BUBBLE_OVERFLOW, true); + enablePipUmoExperience = + properties.getBoolean(Flags.FLAG_ENABLE_PIP_UMO_EXPERIENCE, false); + enableRetrievableBubbles = + properties.getBoolean(Flags.FLAG_ENABLE_RETRIEVABLE_BUBBLES, false); + enableSplitContextual = + properties.getBoolean(Flags.FLAG_ENABLE_SPLIT_CONTEXTUAL, true); + enableTaskbarNavbarUnification = + properties.getBoolean(Flags.FLAG_ENABLE_TASKBAR_NAVBAR_UNIFICATION, true); + enableTinyTaskbar = + properties.getBoolean(Flags.FLAG_ENABLE_TINY_TASKBAR, false); + onlyReuseBubbledTaskWhenLaunchedFromBubble = + properties.getBoolean(Flags.FLAG_ONLY_REUSE_BUBBLED_TASK_WHEN_LAUNCHED_FROM_BUBBLE, false); + } catch (NullPointerException e) { + throw new RuntimeException( + "Cannot read value from namespace multitasking " + + "from DeviceConfig. It could be that the code using flag " + + "executed before SettingsProvider initialization. Please use " + + "fixed read-only flag by adding is_fixed_read_only: true in " + + "flag declaration.", + e + ); + } + multitasking_is_cached = true; } @Override + + + public boolean animateBubbleSizeChange() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!multitasking_is_cached) { + load_overrides_multitasking(); + } + } + return animateBubbleSizeChange; - - public boolean enableAutoTaskStackController() { - return false; } @Override + + + public boolean enableAppPairs() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!multitasking_is_cached) { + load_overrides_multitasking(); + } + } + return enableAppPairs; + } + @Override + + public boolean enableBubbleAnything() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!multitasking_is_cached) { + load_overrides_multitasking(); + } + } + return enableBubbleAnything; + } @Override - - + + public boolean enableBubbleBar() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!multitasking_is_cached) { + load_overrides_multitasking(); + } + } + return enableBubbleBar; + } @Override - - - public boolean enableBubbleBarOnPhones() { - return false; - } - - @Override - - + + public boolean enableBubbleStashing() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!multitasking_is_cached) { + load_overrides_multitasking(); + } + } + return enableBubbleStashing; + } @Override - - - public boolean enableBubbleTaskViewListener() { - return false; - } - - @Override - - - public boolean enableBubbleToFullscreen() { - return false; - } - - @Override - - + + public boolean enableBubblesLongPressNavHandle() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!multitasking_is_cached) { + load_overrides_multitasking(); + } + } + return enableBubblesLongPressNavHandle; + } @Override + + + public boolean enableLeftRightSplitInPortrait() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!multitasking_is_cached) { + load_overrides_multitasking(); + } + } + return enableLeftRightSplitInPortrait; - - public boolean enableCreateAnyBubble() { - return false; } @Override - - - public boolean enableDynamicInsetsForAppLaunch() { - return false; - } - - @Override - - - public boolean enableFlexibleSplit() { - return false; - } - - @Override - - - public boolean enableFlexibleTwoAppSplit() { - return false; - } - - @Override - - - public boolean enableGsf() { - return true; - } - - @Override - - - public boolean enableMagneticSplitDivider() { - return false; - } - - @Override - - + + public boolean enableNewBubbleAnimations() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!multitasking_is_cached) { + load_overrides_multitasking(); + } + } + return enableNewBubbleAnimations; + } @Override - - + + public boolean enableOptionalBubbleOverflow() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!multitasking_is_cached) { + load_overrides_multitasking(); + } + } + return enableOptionalBubbleOverflow; + } @Override - - - public boolean enablePip2() { + + + public boolean enablePip2Implementation() { return false; + } @Override - - + + public boolean enablePipUmoExperience() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!multitasking_is_cached) { + load_overrides_multitasking(); + } + } + return enablePipUmoExperience; + } @Override - - - public boolean enableRecentsBookendTransition() { - return false; - } - - @Override - - + + public boolean enableRetrievableBubbles() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!multitasking_is_cached) { + load_overrides_multitasking(); + } + } + return enableRetrievableBubbles; + } @Override + + + public boolean enableSplitContextual() { + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!multitasking_is_cached) { + load_overrides_multitasking(); + } + } + return enableSplitContextual; - - public boolean enableShellTopTaskTracking() { - return false; } @Override - - - public boolean enableTaskViewControllerCleanup() { - return true; - } - - @Override - - + + public boolean enableTaskbarNavbarUnification() { - return true; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!multitasking_is_cached) { + load_overrides_multitasking(); + } + } + return enableTaskbarNavbarUnification; + } @Override - - - public boolean enableTaskbarOnPhones() { - return true; - } - - @Override - - + + public boolean enableTinyTaskbar() { - return false; + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!multitasking_is_cached) { + load_overrides_multitasking(); + } + } + return enableTinyTaskbar; + } @Override - - - public boolean fixMissingUserChangeCallbacks() { - return false; - } - - @Override - - + + public boolean onlyReuseBubbledTaskWhenLaunchedFromBubble() { - return true; - } + if (isReadFromNew) { + if (!isCached) { + init(); + } + } else { + if (!multitasking_is_cached) { + load_overrides_multitasking(); + } + } + return onlyReuseBubbledTaskWhenLaunchedFromBubble; - @Override - - - public boolean taskViewRepository() { - return false; } } + diff --git a/flags/src/com/android/wm/shell/Flags.java b/flags/src/com/android/wm/shell/Flags.java index d5dbf382c4..5dd4ab7470 100644 --- a/flags/src/com/android/wm/shell/Flags.java +++ b/flags/src/com/android/wm/shell/Flags.java @@ -1,271 +1,119 @@ package com.android.wm.shell; // TODO(b/303773055): Remove the annotation after access issue is resolved. - - /** @hide */ public final class Flags { /** @hide */ - public static final String FLAG_BUBBLE_VIEW_INFO_EXECUTORS = "com.android.wm.shell.bubble_view_info_executors"; + public static final String FLAG_ANIMATE_BUBBLE_SIZE_CHANGE = "com.android.wm.shell.animate_bubble_size_change"; /** @hide */ - public static final String FLAG_ENABLE_AUTO_TASK_STACK_CONTROLLER = "com.android.wm.shell.enable_auto_task_stack_controller"; + public static final String FLAG_ENABLE_APP_PAIRS = "com.android.wm.shell.enable_app_pairs"; /** @hide */ public static final String FLAG_ENABLE_BUBBLE_ANYTHING = "com.android.wm.shell.enable_bubble_anything"; /** @hide */ public static final String FLAG_ENABLE_BUBBLE_BAR = "com.android.wm.shell.enable_bubble_bar"; /** @hide */ - public static final String FLAG_ENABLE_BUBBLE_BAR_ON_PHONES = "com.android.wm.shell.enable_bubble_bar_on_phones"; - /** @hide */ public static final String FLAG_ENABLE_BUBBLE_STASHING = "com.android.wm.shell.enable_bubble_stashing"; /** @hide */ - public static final String FLAG_ENABLE_BUBBLE_TASK_VIEW_LISTENER = "com.android.wm.shell.enable_bubble_task_view_listener"; - /** @hide */ - public static final String FLAG_ENABLE_BUBBLE_TO_FULLSCREEN = "com.android.wm.shell.enable_bubble_to_fullscreen"; - /** @hide */ public static final String FLAG_ENABLE_BUBBLES_LONG_PRESS_NAV_HANDLE = "com.android.wm.shell.enable_bubbles_long_press_nav_handle"; /** @hide */ - public static final String FLAG_ENABLE_CREATE_ANY_BUBBLE = "com.android.wm.shell.enable_create_any_bubble"; - /** @hide */ - public static final String FLAG_ENABLE_DYNAMIC_INSETS_FOR_APP_LAUNCH = "com.android.wm.shell.enable_dynamic_insets_for_app_launch"; - /** @hide */ - public static final String FLAG_ENABLE_FLEXIBLE_SPLIT = "com.android.wm.shell.enable_flexible_split"; - /** @hide */ - public static final String FLAG_ENABLE_FLEXIBLE_TWO_APP_SPLIT = "com.android.wm.shell.enable_flexible_two_app_split"; - /** @hide */ - public static final String FLAG_ENABLE_GSF = "com.android.wm.shell.enable_gsf"; - /** @hide */ - public static final String FLAG_ENABLE_MAGNETIC_SPLIT_DIVIDER = "com.android.wm.shell.enable_magnetic_split_divider"; + public static final String FLAG_ENABLE_LEFT_RIGHT_SPLIT_IN_PORTRAIT = "com.android.wm.shell.enable_left_right_split_in_portrait"; /** @hide */ public static final String FLAG_ENABLE_NEW_BUBBLE_ANIMATIONS = "com.android.wm.shell.enable_new_bubble_animations"; /** @hide */ public static final String FLAG_ENABLE_OPTIONAL_BUBBLE_OVERFLOW = "com.android.wm.shell.enable_optional_bubble_overflow"; /** @hide */ - public static final String FLAG_ENABLE_PIP2 = "com.android.wm.shell.enable_pip2"; + public static final String FLAG_ENABLE_PIP2_IMPLEMENTATION = "com.android.wm.shell.enable_pip2_implementation"; /** @hide */ public static final String FLAG_ENABLE_PIP_UMO_EXPERIENCE = "com.android.wm.shell.enable_pip_umo_experience"; /** @hide */ - public static final String FLAG_ENABLE_RECENTS_BOOKEND_TRANSITION = "com.android.wm.shell.enable_recents_bookend_transition"; - /** @hide */ public static final String FLAG_ENABLE_RETRIEVABLE_BUBBLES = "com.android.wm.shell.enable_retrievable_bubbles"; /** @hide */ - public static final String FLAG_ENABLE_SHELL_TOP_TASK_TRACKING = "com.android.wm.shell.enable_shell_top_task_tracking"; - /** @hide */ - public static final String FLAG_ENABLE_TASK_VIEW_CONTROLLER_CLEANUP = "com.android.wm.shell.enable_task_view_controller_cleanup"; + public static final String FLAG_ENABLE_SPLIT_CONTEXTUAL = "com.android.wm.shell.enable_split_contextual"; /** @hide */ public static final String FLAG_ENABLE_TASKBAR_NAVBAR_UNIFICATION = "com.android.wm.shell.enable_taskbar_navbar_unification"; /** @hide */ - public static final String FLAG_ENABLE_TASKBAR_ON_PHONES = "com.android.wm.shell.enable_taskbar_on_phones"; - /** @hide */ public static final String FLAG_ENABLE_TINY_TASKBAR = "com.android.wm.shell.enable_tiny_taskbar"; /** @hide */ - public static final String FLAG_FIX_MISSING_USER_CHANGE_CALLBACKS = "com.android.wm.shell.fix_missing_user_change_callbacks"; - /** @hide */ public static final String FLAG_ONLY_REUSE_BUBBLED_TASK_WHEN_LAUNCHED_FROM_BUBBLE = "com.android.wm.shell.only_reuse_bubbled_task_when_launched_from_bubble"; - /** @hide */ - public static final String FLAG_TASK_VIEW_REPOSITORY = "com.android.wm.shell.task_view_repository"; - - - - public static boolean bubbleViewInfoExecutors() { - - return FEATURE_FLAGS.bubbleViewInfoExecutors(); + + + public static boolean animateBubbleSizeChange() { + return FEATURE_FLAGS.animateBubbleSizeChange(); } - - - - public static boolean enableAutoTaskStackController() { - - return FEATURE_FLAGS.enableAutoTaskStackController(); + + + public static boolean enableAppPairs() { + return FEATURE_FLAGS.enableAppPairs(); } - - - + + public static boolean enableBubbleAnything() { - return FEATURE_FLAGS.enableBubbleAnything(); } - - - + + public static boolean enableBubbleBar() { - return FEATURE_FLAGS.enableBubbleBar(); } - - - - public static boolean enableBubbleBarOnPhones() { - - return FEATURE_FLAGS.enableBubbleBarOnPhones(); - } - - - + + public static boolean enableBubbleStashing() { - return FEATURE_FLAGS.enableBubbleStashing(); } - - - - public static boolean enableBubbleTaskViewListener() { - - return FEATURE_FLAGS.enableBubbleTaskViewListener(); - } - - - - public static boolean enableBubbleToFullscreen() { - - return FEATURE_FLAGS.enableBubbleToFullscreen(); - } - - - + + public static boolean enableBubblesLongPressNavHandle() { - return FEATURE_FLAGS.enableBubblesLongPressNavHandle(); } - - - - public static boolean enableCreateAnyBubble() { - - return FEATURE_FLAGS.enableCreateAnyBubble(); + + + public static boolean enableLeftRightSplitInPortrait() { + return FEATURE_FLAGS.enableLeftRightSplitInPortrait(); } - - - - public static boolean enableDynamicInsetsForAppLaunch() { - - return FEATURE_FLAGS.enableDynamicInsetsForAppLaunch(); - } - - - - public static boolean enableFlexibleSplit() { - - return FEATURE_FLAGS.enableFlexibleSplit(); - } - - - - public static boolean enableFlexibleTwoAppSplit() { - - return FEATURE_FLAGS.enableFlexibleTwoAppSplit(); - } - - - - public static boolean enableGsf() { - - return FEATURE_FLAGS.enableGsf(); - } - - - - public static boolean enableMagneticSplitDivider() { - - return FEATURE_FLAGS.enableMagneticSplitDivider(); - } - - - + + public static boolean enableNewBubbleAnimations() { - return FEATURE_FLAGS.enableNewBubbleAnimations(); } - - - + + public static boolean enableOptionalBubbleOverflow() { - return FEATURE_FLAGS.enableOptionalBubbleOverflow(); } - - - - public static boolean enablePip2() { - - return FEATURE_FLAGS.enablePip2(); + + public static boolean enablePip2Implementation() { + return FEATURE_FLAGS.enablePip2Implementation(); } - - - + + public static boolean enablePipUmoExperience() { - return FEATURE_FLAGS.enablePipUmoExperience(); } - - - - public static boolean enableRecentsBookendTransition() { - - return FEATURE_FLAGS.enableRecentsBookendTransition(); - } - - - + + public static boolean enableRetrievableBubbles() { - return FEATURE_FLAGS.enableRetrievableBubbles(); } - - - - public static boolean enableShellTopTaskTracking() { - - return FEATURE_FLAGS.enableShellTopTaskTracking(); + + + public static boolean enableSplitContextual() { + return FEATURE_FLAGS.enableSplitContextual(); } - - - - public static boolean enableTaskViewControllerCleanup() { - - return FEATURE_FLAGS.enableTaskViewControllerCleanup(); - } - - - + + public static boolean enableTaskbarNavbarUnification() { - return FEATURE_FLAGS.enableTaskbarNavbarUnification(); } - - - - public static boolean enableTaskbarOnPhones() { - - return FEATURE_FLAGS.enableTaskbarOnPhones(); - } - - - + + public static boolean enableTinyTaskbar() { - return FEATURE_FLAGS.enableTinyTaskbar(); } - - - - public static boolean fixMissingUserChangeCallbacks() { - - return FEATURE_FLAGS.fixMissingUserChangeCallbacks(); - } - - - + + public static boolean onlyReuseBubbledTaskWhenLaunchedFromBubble() { - return FEATURE_FLAGS.onlyReuseBubbledTaskWhenLaunchedFromBubble(); } - - - public static boolean taskViewRepository() { - - return FEATURE_FLAGS.taskViewRepository(); - } - private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl(); } diff --git a/go/AndroidManifest-launcher.xml b/go/AndroidManifest-launcher.xml index cc4072e262..8e427d4c31 100644 --- a/go/AndroidManifest-launcher.xml +++ b/go/AndroidManifest-launcher.xml @@ -56,7 +56,6 @@ android:enabled="true"> - diff --git a/go/quickstep/res/values-af/strings.xml b/go/quickstep/res/values-af/strings.xml index f4ce476582..501d297a8d 100644 --- a/go/quickstep/res/values-af/strings.xml +++ b/go/quickstep/res/values-af/strings.xml @@ -1,7 +1,7 @@ - "Deel app" + "Deel program" "Luister" "Vertaal" "Lens" @@ -9,12 +9,12 @@ "KANSELLEER" "INSTELLINGS" "Vertaal of luister na teks op skerm" - "Inligting soos teks op jou skerm, webadresse en skermskote kan met Google gedeel word.\n\nGaan na ""Instellings > Apps > Verstekapps > Digitale Assistent-app"" om te verander watter inligting jy deel." + "Inligting soos teks op jou skerm, webadresse en skermskote kan met Google gedeel word.\n\nGaan na ""Instellings > Programme > Verstekprogramme > Digitale Assistent-program"" om te verander watter inligting jy deel." "Kies \'n assistent om hierdie kenmerk te gebruik" "Kies \'n digitalebystandprogram in Instellings om na teks op jou skerm te luister of dit te vertaal" "Verander jou assistent om hierdie kenmerk te gebruik" "Verander jou digitalebystandprogram in Instellings om na teks op jou skerm te luister of dit te vertaal" "Tik hier om na teks op hierdie skerm te luister" "Tik hier om teks op hierdie skerm te vertaal" - "Hierdie app kan nie gedeel word nie" + "Hierdie program kan nie gedeel word nie" diff --git a/go/quickstep/res/values-be/strings.xml b/go/quickstep/res/values-be/strings.xml index de8766f708..83374bb397 100644 --- a/go/quickstep/res/values-be/strings.xml +++ b/go/quickstep/res/values-be/strings.xml @@ -4,7 +4,7 @@ "Абагуліць праграму" "Праслухаць" "Перакласці" - "Аб’ектыў" + "Аб\'ектыў" "ЗРАЗУМЕЛА" "СКАСАВАЦЬ" "НАЛАДЫ" diff --git a/go/quickstep/res/values-fa/strings.xml b/go/quickstep/res/values-fa/strings.xml index f0e4a57712..8453d4e3e2 100644 --- a/go/quickstep/res/values-fa/strings.xml +++ b/go/quickstep/res/values-fa/strings.xml @@ -5,7 +5,7 @@ "گوش دادن" "ترجمه" "لنز" - "متوجهم" + "متوجه‌ام" "لغو" "تنظیمات" "ترجمه نوشتار روی صفحه‌نمایش یا گوش دادن به آن" diff --git a/go/quickstep/res/values-iw/strings.xml b/go/quickstep/res/values-iw/strings.xml index db661066e0..ddb8ddd9a9 100644 --- a/go/quickstep/res/values-iw/strings.xml +++ b/go/quickstep/res/values-iw/strings.xml @@ -14,7 +14,7 @@ "כדי להאזין לטקסט שבמסך או לתרגם אותו, צריך לבחור אפליקציית עוזר דיגיטלי ב\'הגדרות\'" "צריך לשנות את העוזר הדיגיטלי כדי להשתמש בתכונה הזו" "כדי להאזין לטקסט שבמסך או לתרגם אותו, צריך לשנות את אפליקציית העוזר הדיגיטלי ב\'הגדרות\'" - "צריך ללחוץ כאן כדי להאזין לטקסט שבמסך הזה" - "צריך ללחוץ כאן כדי לתרגם את הטקסט שבמסך הזה" + "צריך להקיש כאן כדי להאזין לטקסט שבמסך הזה" + "צריך להקיש כאן כדי לתרגם את הטקסט שבמסך הזה" "אי אפשר לשתף את האפליקציה הזו" diff --git a/go/quickstep/res/values-ne/strings.xml b/go/quickstep/res/values-ne/strings.xml index 4f771c3ec4..e66f06373d 100644 --- a/go/quickstep/res/values-ne/strings.xml +++ b/go/quickstep/res/values-ne/strings.xml @@ -9,11 +9,11 @@ "रद्द गर्नुहोस्" "सेटिङ" "स्क्रिनमा देखिने पाठ अनुवाद गर्नुहोस् वा पढेर सुनाउनुहोस्" - "तपाईंको स्क्रिनमा देखिने पाठ, वेब ठेगाना र स्क्रिनसटलगायतका जानकारी Google सँग सेयर गर्न सकिन्छ।\n\nकुन कुन जानकारी सेयर गर्न दिने भन्ने सेटिङ बदल्न ""सेटिङ > एप > डिफल्ट एप > डिजिटल एसिस्टेन्ट एप"" मा जानुहोस्।" + "तपाईंको स्क्रिनमा देखिने पाठ, वेब ठेगाना र स्क्रिनसटलगायतका जानकारी Google सँग सेयर गर्न सकिन्छ।\n\nकुन कुन जानकारी सेयर गर्न दिने भन्ने सेटिङ बदल्न ""सेटिङ > एप > डिफल्ट एप > डिजिटल सहायक एप"" मा जानुहोस्।" "तपाईं यो सुविधा चलाउन चाहनुहुन्छ भने कुनै सहायक छनौट गर्नुहोस्" - "तपाईं आफ्नो स्क्रिनमा देखिने पाठ सुन्न वा अनुवाद गर्न चाहनुहुन्छ भने सेटिङमा गई कुनै डिजिटल एसिस्टेन्ट एप छनौट गर्नुहोस्" + "तपाईं आफ्नो स्क्रिनमा देखिने पाठ सुन्न वा अनुवाद गर्न चाहनुहुन्छ भने सेटिङमा गई कुनै डिजिटल सहायक एप छनौट गर्नुहोस्" "तपाईं यो सुविधा चलाउन चाहनुहुन्छ भने आफ्नो सहायक परिवर्तन गर्नुहोस्" - "तपाईं आफ्नो स्क्रिनमा देखिने पाठ सुन्न वा अनुवाद गर्न चाहनुहुन्छ भने सेटिङमा गई कुनै डिजिटल एसिस्टेन्ट एप परिर्वर्तन गर्नुहोस्" + "तपाईं आफ्नो स्क्रिनमा देखिने पाठ सुन्न वा अनुवाद गर्न चाहनुहुन्छ भने सेटिङमा गई कुनै डिजिटल सहायक एप परिर्वर्तन गर्नुहोस्" "तपाईं यो स्क्रिनमा देखिने पाठ सुन्न चाहनुहुन्छ यहाँ ट्याप गर्नुहोस्" "तपाईं यो स्क्रिनमा देखिने पाठ अनुवाद गर्न चाहनुहुन्छ यहाँ ट्याप गर्नुहोस्" "यो एप अरूलाई चलाउन दिन मिल्दैन" diff --git a/go/quickstep/res/values/styles.xml b/go/quickstep/res/values/styles.xml index 2524c760f4..c659331bde 100644 --- a/go/quickstep/res/values/styles.xml +++ b/go/quickstep/res/values/styles.xml @@ -16,7 +16,7 @@ --> - \ No newline at end of file diff --git a/quickstep/res/values-nl/strings.xml b/quickstep/res/values-nl/strings.xml index ea6f39c1e1..ca44a6914c 100644 --- a/quickstep/res/values-nl/strings.xml +++ b/quickstep/res/values-nl/strings.xml @@ -22,13 +22,11 @@ "Vastzetten" "Vrije vorm" "Desktop" - "Verplaatsen naar extern scherm" - "Sluiten" - "Desktop" "Geen recente items" "Instellingen voor app-gebruik" "Alles wissen" "Recente apps" + "Taak gesloten" "%1$s, %2$s" "< 1 minuut" "Nog %1$s vandaag" @@ -47,7 +45,6 @@ "App-suggesties staan aan" "App-suggesties staan uit" "Voorspelde app: %1$s" - "Tutorial voor navigatie met gebaren" "Het apparaat draaien" "Draai het apparaat om de tutorial voor navigatie met gebaren af te ronden" "Swipe vanaf de rechter- of linkerrand" @@ -59,15 +56,17 @@ "Open Instellingen om de gevoeligheid van Terug te wijzigen" "Swipe om terug te gaan" "Swipe vanaf de linker- of rechterrand naar het midden om terug te gaan naar het vorige scherm." + "Als je wilt teruggaan naar het laatste scherm, swipe je met 2 vingers vanaf de linker- of rechterrand naar het midden van het scherm." "Terug" "Swipe vanaf de linker- of rechterrand naar het midden van het scherm" "Swipe vanaf de onderrand van het scherm omhoog" "Pauzeer niet voordat je loslaat" "Swipe recht omhoog" "Je weet nu hoe je het gebaar Naar startscherm maakt. Ontdek als volgende hoe je kunt teruggaan." - "Je weet nu hoe je het gebaar Naar het startscherm maakt" + "Je weet nu hoe je teruggaat naar het startscherm" "Swipe om naar het startscherm te gaan" "Swipe omhoog vanaf de onderkant van het scherm. Met dit gebaar ga je altijd naar het startscherm." + "Swipe met 2 vingers omhoog vanaf de onderkant van het scherm. Met dit gebaar ga je altijd naar het startscherm." "Naar het startscherm" "Swipe omhoog vanaf de onderkant van het scherm" "Goed gedaan" @@ -78,6 +77,7 @@ "Je weet nu hoe je het gebaar Schakelen tussen apps maakt" "Swipe om tussen apps te schakelen" "Swipe omhoog vanaf de onderkant van het scherm, houd vast en laat los om tussen apps te schakelen." + "Swipe met 2 vingers omhoog vanaf de onderkant van het scherm, houd vast en laat los om tussen apps te schakelen." "Schakelen tussen apps" "Swipe omhoog vanaf de onderkant van het scherm, houd je vinger op het scherm en laat dan los" "Goed bezig" @@ -99,7 +99,7 @@ "App-paar opslaan" "Tik op nog een app om je scherm te splitsen" "Kies een andere app om gesplitst scherm te gebruiken" - "Annuleren" + "Annuleren" "Sluit de selectie voor gesplitst scherm" "Kies andere app om gesplitst scherm te gebruiken" "Deze actie wordt niet toegestaan door de app of je organisatie" @@ -130,37 +130,18 @@ "Snelle instellingen" "Taakbalk" "Taakbalk wordt getoond" - "Taakbalk en bubbels links getoond" - "Taakbalk en bubbels rechts getoond" + "Taakbalk is verborgen" "Navigatiebalk" "Taakbalk altijd tonen" "Navigatiemodus wijzigen" "Scheiding voor Taakbalk" - "Taakbalkoverloop" "Naar boven/links verplaatsen" "Naar beneden/rechts verplaatsen" - "App openen als ballon" - "Recente apps" - "Lijst met recente apps" - "{count,plural, =1{extra app}other{extra apps}}" - "Desktop" + "{count,plural, =1{Nog # app tonen.}other{Nog # apps tonen.}}" + "{count,plural, =1{# desktop-app tonen.}other{# desktop-apps tonen.}}" "%1$s en %2$s" - "%1$s, item %2$d van %3$d" - "Naar links scrollen" - "Naar rechts scrollen" "Bubbel" "Overloop" "%1$s van %2$s" "%1$s en nog %2$d" - "Naar links verplaatsen" - "Naar rechts verplaatsen" - "Alles sluiten" - "%1$s uitvouwen" - "%1$s samenvouwen" - "Circle to Search" - "Icoon van app" - "Titel van app" - "Knop Sluiten" - "Vastzetten op taakbalk" - "Losmaken van taakbalk" diff --git a/quickstep/res/values-or/strings.xml b/quickstep/res/values-or/strings.xml index e3664bc00f..bf0bdc8323 100644 --- a/quickstep/res/values-or/strings.xml +++ b/quickstep/res/values-or/strings.xml @@ -22,13 +22,11 @@ "ପିନ୍‍" "ଫ୍ରିଫର୍ମ" "ଡେସ୍କଟପ" - "ଏକ୍ସଟର୍ନଲ ଡିସପ୍ଲେକୁ ମୁଭ କରନ୍ତୁ" - "ବନ୍ଦ କରନ୍ତୁ" - "ଡେସ୍କଟପ" "ବର୍ତ୍ତମାନର କୌଣସି ଆଇଟମ ନାହିଁ" "ଆପ ବ୍ୟବହାର ସେଟିଂସ" "ସବୁ ଖାଲି କରନ୍ତୁ" "ବର୍ତ୍ତମାନର ଆପ୍‌" + "ଟାସ୍କ ବନ୍ଦ ହୋଇଯାଇଛି" "%1$s %2$s" "< 1 ମିନିଟ୍" "ଆଜି %1$s ବାକି ଅଛି" @@ -47,18 +45,18 @@ "ଆପ୍ ପରାମର୍ଶଗୁଡ଼ିକୁ ସକ୍ଷମ କରାଯାଇଛି" "ଆପ୍ ପରାମର୍ଶଗୁଡ଼ିକୁ ଅକ୍ଷମ କରାଯାଇଛି" "ପୂର୍ବାନୁମାନ କରାଯାଇଥିବା ଆପ୍: %1$s" - "ଜେଶ୍ଚର ନାଭିଗେସନ ଟ୍ୟୁଟୋରିଆଲ" "ଆପଣଙ୍କ ଡିଭାଇସକୁ ରୋଟେଟ କରନ୍ତୁ" "ଜେଶ୍ଚର ନାଭିଗେସନ ଟ୍ୟୁଟୋରିଆଲ ସମ୍ପୂର୍ଣ୍ଣ କରିବାକୁ ଦୟାକରି ଆପଣଙ୍କ ଡିଭାଇସ ରୋଟେଟ କରନ୍ତୁ" - "ଆପଣ ସ୍କ୍ରିନର ଏକଦମ-ଡାହାଣ ବା ବାମ ଧାରରୁ ସ୍ୱାଇପ କରୁଥିବା ସୁନିଶ୍ଚିତ କରନ୍ତୁ।" + "ଆପଣ ସ୍କ୍ରିନର ଏକଦମ୍-ଡାହାଣ ବା ବାମ ଧାରରୁ ସ୍ୱାଇପ୍ କରୁଥିବା ସୁନିଶ୍ଚିତ କରନ୍ତୁ।" "ଆପଣ ସ୍କ୍ରିନର ଡାହାଣ ବା ବାମ ଧାରରୁ ମଝିକୁ ସ୍ୱାଇପ କରି ଛାଡ଼ି ଦେଉଥିବା ସୁନିଶ୍ଚିତ କରନ୍ତୁ" "ଆପଣ ଡାହାଣରୁ ସ୍ୱାଇପ୍ କରି ପଛକୁ କିପରି ଫେରିବେ ତାହା ଜାଣିଲେ। ତା\'ପରେ, ଆପକୁ କିପରି ସ୍ୱିଚ୍ କରିବେ ତାହା ଜାଣନ୍ତୁ।" "ଆପଣ \'ପଛକୁ ଫେରନ୍ତୁ\' ଜେଶ୍ଚର୍ ସମ୍ପୂର୍ଣ୍ଣ କରିଛନ୍ତି। ତା\'ପରେ, ଆପଗୁଡ଼ିକୁ କିପରି ସ୍ୱିଚ୍ କରିବେ ତାହା ଜାଣନ୍ତୁ।" - "ଆପଣ ପଛକୁ ଫେରନ୍ତୁ ଜେଶ୍ଚର ସମ୍ପୂର୍ଣ୍ଣ କରିଛନ୍ତି" + "ଆପଣ \'ପଛକୁ ଫେରନ୍ତୁ\' ଜେଶ୍ଚର ସମ୍ପୂର୍ଣ୍ଣ କରିଛନ୍ତି" "ଆପଣ ସ୍କ୍ରିନର ତଳଭାଗର ଅତି ନିକଟରୁ ସ୍ୱାଇପ କରୁନଥିବା ସୁନିଶ୍ଚିତ କରନ୍ତୁ" "ପଛକୁ ଫେରିବା ଜେଶ୍ଚରର ସମ୍ବେଦନଶୀଳତା ବଦଳାଇବାକୁ ସେଟିଂସକୁ ଯାଆନ୍ତୁ" "ପଛକୁ ଫେରିବା ପାଇଁ ସ୍ୱାଇପ୍ କରନ୍ତୁ" "ପୂର୍ବ ସ୍କ୍ରିନକୁ ଫେରିବା ପାଇଁ, ସ୍କ୍ରିନର ବାମ କିମ୍ବା ଡାହାଣ ଧାରରୁ ମଝିକୁ ସ୍ୱାଇପ କରନ୍ତୁ।" + "ପୂର୍ବ ସ୍କ୍ରିନକୁ ଫେରିବା ପାଇଁ, ସ୍କ୍ରିନର ବାମ କିମ୍ବା ଡାହାଣ ଧାରରୁ ମଝିକୁ 2ଟି ଆଙ୍ଗୁଠିରେ ସ୍ୱାଇପ କରନ୍ତୁ।" "ପଛକୁ ଫେରନ୍ତୁ" "ସ୍କ୍ରିନର ବାମ କିମ୍ବା ଡାହାଣ ଧାରରୁ ମଝିକୁ ସ୍ୱାଇପ କରନ୍ତୁ" "ଆପଣ ସ୍କ୍ରିନର ତଳ ଧାରରୁ ଉପରକୁ ସ୍ୱାଇପ କରୁଥିବା ସୁନିଶ୍ଚିତ କରନ୍ତୁ" @@ -68,18 +66,20 @@ "ଆପଣ \'ମୂଳପୃଷ୍ଠାକୁ ଯାଆନ୍ତୁ\' ଜେଶ୍ଚର୍ ସମ୍ପୂର୍ଣ୍ଣ କରିଛନ୍ତି।" "ହୋମକୁ ଯିବା ପାଇଁ ସ୍ୱାଇପ କରନ୍ତୁ" "ଆପଣଙ୍କ ସ୍କ୍ରିନର ତଳୁ ଉପରକୁ ସ୍ୱାଇପ କରନ୍ତୁ। ଏହି ଜେଶ୍ଚର ସର୍ବଦା ଆପଣଙ୍କୁ ହୋମ ସ୍କ୍ରିନକୁ ନେଇଥାଏ।" + "2ଟି ଆଙ୍ଗୁଠିରେ ସ୍କ୍ରିନର ତଳୁ ଉପରକୁ ସ୍ୱାଇପ କରନ୍ତୁ। ଏହି ଜେଶ୍ଚର ସର୍ବଦା ଆପଣଙ୍କୁ ହୋମ ସ୍କ୍ରିନକୁ ନେଇଥାଏ।" "ହୋମକୁ ଯାଆନ୍ତୁ" "ଆପଣଙ୍କ ସ୍କ୍ରିନର ତଳୁ ଉପରକୁ ସ୍ୱାଇପ କରନ୍ତୁ" "ବଢ଼ିଆ କାମ!" "ଆପଣ ସ୍କ୍ରିନର ତଳ ଧାରରୁ ଉପରକୁ ସ୍ୱାଇପ କରୁଥିବା ସୁନିଶ୍ଚିତ କରନ୍ତୁ" "ୱିଣ୍ଡୋକୁ ରିଲିଜ୍ କରିବା ପୂର୍ବରୁ ଅଧିକ ସମୟ ଧରି ରଖିବାକୁ ଚେଷ୍ଟା କରନ୍ତୁ।" - "ଆପଣ ସିଧା ଉପରକୁ ସ୍ୱାଇପ କରି ତା\'ପରେ ବିରତ କରୁଥିବା ସୁନିଶ୍ଚିତ କରନ୍ତୁ।" + "ଆପଣ ସିଧା ଉପରକୁ ସ୍ୱାଇପ୍ କରି ତା\'ପରେ ବିରତ କରୁଥିବା ସୁନିଶ୍ଚିତ କରନ୍ତୁ।" "ଜେଶ୍ଚରଗୁଡ଼ିକୁ କିପରି ବ୍ୟବହାର କରାଯିବ ଆପଣ ତାହା ଶିଖିଛନ୍ତି। ଜେଶ୍ଚରଗୁଡ଼ିକୁ ବନ୍ଦ କରିବାକୁ, ସେଟିଂସକୁ ଯାଆନ୍ତୁ।" - "ଆପଣ ଆପ୍ସ ସୁଇଚ କରନ୍ତୁ ଜେଶ୍ଚର ସମ୍ପୂର୍ଣ୍ଣ କରିଛନ୍ତି।" + "ଆପଣ \'ଆପଗୁଡ଼ିକୁ ସ୍ୱିଚ୍ କରନ୍ତୁ\' ଜେଶ୍ଚର୍ ସମ୍ପୂର୍ଣ୍ଣ କରିଛନ୍ତି।" "ଆପଗୁଡ଼ିକୁ ସ୍ୱିଚ୍ କରିବା ପାଇଁ ସ୍ୱାଇପ୍ କରନ୍ତୁ" "ଆପ୍ସ ମଧ୍ୟରେ ସୁଇଚ କରିବାକୁ, ସ୍କ୍ରିନର ତଳୁ ଉପରକୁ ସ୍ୱାଇପ କରନ୍ତୁ, ଧରି ରଖନ୍ତୁ, ତା\'ପରେ ରିଲିଜ କରନ୍ତୁ।" + "ଆପ୍ସ ମଧ୍ୟରେ ସ୍ୱିଚ କରିବାକୁ, 2ଟି ଆଙ୍ଗୁଠିରେ ସ୍କ୍ରିନର ତଳୁ ଉପରକୁ ସ୍ୱାଇପ କରି ଧରି ରଖନ୍ତୁ, ତା\'ପରେ ରିଲିଜ କର।" "ଆପ୍ସ ସୁଇଚ କରନ୍ତୁ" - "ଆପଣଙ୍କ ସ୍କ୍ରିନର ତଳୁ ଉପରକୁ ସ୍ୱାଇପ କରି ଧରି ରଖନ୍ତୁ, ତା\'ପରେ ରିଲିଜ କରନ୍ତୁ" + "ଆପଣଙ୍କ ସ୍କ୍ରିନ୍‌ର ତଳୁ ଉପରକୁ ସ୍ୱାଇପ୍ କରି ଧରି ରଖନ୍ତୁ, ତା\'ପରେ ରିଲିଜ୍ କରନ୍ତୁ" "ବହୁତ ବଢ଼ିଆ!" "ସବୁ ପ୍ରସ୍ତୁତ" "ହୋଇଗଲା" @@ -99,7 +99,7 @@ "ଆପ ପେୟାର ସେଭ କରନ୍ତୁ" "ସ୍ପ୍ଲିଟସ୍କ୍ରିନ ବ୍ୟବହାର କରିବାକୁ ଅନ୍ୟ ଏକ ଆପରେ ଟାପ କର" "ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନ ବ୍ୟବହାର କରିବାକୁ ଅନ୍ୟ ଏକ ଆପ ବାଛନ୍ତୁ" - "ବାତିଲ କରନ୍ତୁ" + "ବାତିଲ କରନ୍ତୁ" "ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନ ଚୟନରୁ ବାହାରି ଯାଆନ୍ତୁ" "ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନ ବ୍ୟବହାର କରିବାକୁ ଅନ୍ୟ ଏକ ଆପ ବାଛନ୍ତୁ" "ଆପ୍ କିମ୍ବା ଆପଣଙ୍କ ସଂସ୍ଥା ଦ୍ୱାରା ଏହି କାର୍ଯ୍ୟକୁ ଅନୁମତି ଦିଆଯାଇ ନାହିଁ" @@ -130,37 +130,18 @@ "କୁଇକ ସେଟିଂସ" "ଟାସ୍କବାର" "ଟାସ୍କବାର ଦେଖାଯାଇଛି" - "ଟାସ୍କବାର ଓ ବବଲ ବାମରେ ଦେଖାଯାଇଛି" - "ଟାସ୍କବାର ଓ ବବଲ ଡାହାଣରେ ଦେଖାଯାଇଛି" + "ଟାସ୍କବାର ଲୁଚାଯାଇଛି" "ନାଭିଗେସନ ବାର" "ସର୍ବଦା ଟାସ୍କବାର ଦେଖାନ୍ତୁ" "ନାଭିଗେସନ ମୋଡ ପରିବର୍ତ୍ତନ କରନ୍ତୁ" "ଟାସ୍କବାର ଡିଭାଇଡର" - "ଟାସ୍କବାର ଓଭରଫ୍ଲୋ" "ଶୀର୍ଷ/ବାମକୁ ମୁଭ କରନ୍ତୁ" "ନିମ୍ନ/ଡାହାଣକୁ ମୁଭ କରନ୍ତୁ" - "ଏକ ବବଲ ଭାବେ ଆପ ଖୋଲନ୍ତୁ" - "ବର୍ତ୍ତମାନର ଆପ" - "ବର୍ତ୍ତମାନର ଆପ ତାଲିକା" - "{count,plural, =1{ଅଧିକ ଆପ}other{ଅଧିକ ଆପ୍ସ}}" - "ଡେସ୍କଟପ" + "{count,plural, =1{ଅଧିକ #ଟି ଆପ ଦେଖାନ୍ତୁ।}other{ଅଧିକ #ଟି ଆପ୍ସ ଦେଖାନ୍ତୁ।}}" + "{count,plural, =1{# ଡେସ୍କଟପ ଆପ ଦେଖାନ୍ତୁ।}other{# ଡେସ୍କଟପ ଆପ୍ସ ଦେଖାନ୍ତୁ।}}" "%1$s ଏବଂ %2$s" - "%1$s, %3$d%2$d ଆଇଟମ" - "ବାମକୁ ସ୍କ୍ରୋଲ କରନ୍ତୁ" - "ଡାହାଣକୁ ସ୍କ୍ରୋଲ କରନ୍ତୁ" "ବବଲ" "ଓଭରଫ୍ଲୋ" "%2$sରୁ %1$s" "%1$s ଏବଂ ଅଧିକ %2$d" - "ବାମକୁ ମୁଭ କରନ୍ତୁ" - "ଡାହାଣକୁ ମୁଭ କରନ୍ତୁ" - "ସବୁ ଖାରଜ କରନ୍ତୁ" - "%1$s ବିସ୍ତାର କରନ୍ତୁ" - "%1$s ସଙ୍କୁଚିତ କରନ୍ତୁ" - "ସର୍ଚ୍ଚ କରିବାକୁ ସର୍କଲ କରନ୍ତୁ" - "ଆପ ଆଇକନ" - "ଆପ ଟାଇଟେଲ" - "\"ବନ୍ଦ କରନ୍ତୁ\" ବଟନ" - "ଟାସ୍କବାରରେ ପିନ କର" - "ଟାସ୍କବାରରୁ ଅନପିନ କର" diff --git a/quickstep/res/values-pa/strings.xml b/quickstep/res/values-pa/strings.xml index bcf1683d77..fc603969a7 100644 --- a/quickstep/res/values-pa/strings.xml +++ b/quickstep/res/values-pa/strings.xml @@ -22,13 +22,11 @@ "ਪਿੰਨ ਕਰੋ" "ਫ੍ਰੀਫਾਰਮ" "ਡੈਸਕਟਾਪ" - "ਬਾਹਰੀ ਡਿਸਪਲੇ \'ਤੇ ਜਾਓ" - "ਬੰਦ ਕਰੋ" - "ਡੈਸਕਟਾਪ" "ਕੋਈ ਹਾਲੀਆ ਆਈਟਮ ਨਹੀਂ" "ਐਪ ਵਰਤੋਂ ਦੀਆਂ ਸੈਟਿੰਗਾਂ" "ਸਭ ਕਲੀਅਰ ਕਰੋ" "ਹਾਲੀਆ ਐਪਾਂ" + "ਕਾਰਜ ਬੰਦ" "%1$s, %2$s" "< 1 ਮਿੰਟ" "ਅੱਜ %1$s ਬਾਕੀ" @@ -47,7 +45,6 @@ "ਐਪ ਸੁਝਾਵਾਂ ਨੂੰ ਚਾਲੂ ਕੀਤਾ ਗਿਆ" "ਐਪ ਸੁਝਾਵਾਂ ਨੂੰ ਬੰਦ ਕੀਤਾ ਗਿਆ" "ਪੂਰਵ ਅਨੁਮਾਨਿਤ ਐਪ: %1$s" - "ਇਸ਼ਾਰਾ ਨੈਵੀਗੇਸ਼ਨ ਸੰਬੰਧੀ ਟਿਊਟੋਰੀਅਲ" "ਆਪਣੇ ਡੀਵਾਈਸ ਨੂੰ ਘੁੰਮਾਓ" "ਕਿਰਪਾ ਕਰਕੇ ਇਸ਼ਾਰਾ ਨੈਵੀਗੇਸ਼ਨ ਟਿਊਟੋਰੀਅਲ ਨੂੰ ਪੂਰਾ ਕਰਨ ਲਈ ਆਪਣੇ ਡੀਵਾਈਸ ਨੂੰ ਘੁੰਮਾਓ" "ਇਹ ਪੱਕਾ ਕਰੋ ਕਿ ਤੁਸੀਂ ਸੱਜੇ ਜਾਂ ਖੱਬੇ ਪਾਸੇ ਦੇ ਬਿਲਕੁਲ ਕਿਨਾਰੇ ਤੋਂ ਸਵਾਈਪ ਕਰਦੇ ਹੋ" @@ -59,6 +56,7 @@ "ਪਿੱਛੇ ਜਾਣ ਦੇ ਸੰਕੇਤ ਦੀ ਸੰਵੇਦਨਸ਼ੀਲਤਾ ਬਦਲਣ ਲਈ, ਸੈਟਿੰਗਾਂ \'ਤੇ ਜਾਓ" "ਪਿੱਛੇ ਜਾਣ ਲਈ ਸਵਾਈਪ ਕਰੋ" "ਪਿਛਲੀ ਸਕ੍ਰੀਨ \'ਤੇ ਵਾਪਸ ਜਾਣ ਲਈ, ਖੱਬੇ ਜਾਂ ਸੱਜੇ ਕਿਨਾਰੇ ਤੋਂ ਸਕ੍ਰੀਨ ਦੇ ਵਿਚਕਾਰ ਤੱਕ ਸਵਾਈਪ ਕਰੋ।" + "ਪਿਛਲੀ ਸਕ੍ਰੀਨ \'ਤੇ ਵਾਪਸ ਜਾਣ ਲਈ, ਖੱਬੇ ਜਾਂ ਸੱਜੇ ਕਿਨਾਰੇ ਤੋਂ ਸਕ੍ਰੀਨ ਦੇ ਵਿਚਕਾਰ ਤੱਕ 2 ਉਂਗਲਾਂ ਨਾਲ ਸਵਾਈਪ ਕਰੋ।" "ਵਾਪਸ ਜਾਓ" "ਖੱਬੇ ਜਾਂ ਸੱਜੇ ਕਿਨਾਰੇ ਤੋਂ ਸਕ੍ਰੀਨ ਦੇ ਵਿਚਕਾਰ ਤੱਕ ਸਵਾਈਪ ਕਰੋ" "ਇਹ ਪੱਕਾ ਕਰੋ ਕਿ ਤੁਸੀਂ ਸਕ੍ਰੀਨ ਦੇ ਹੇਠਲੇ ਕਿਨਾਰੇ ਤੋਂ ਉੱਪਰ ਵੱਲ ਸਵਾਈਪ ਕਰੋ" @@ -68,6 +66,7 @@ "ਤੁਸੀਂ \'ਹੋਮ \'ਤੇ ਜਾਓ\' ਦਾ ਇਸ਼ਾਰਾ ਪੂਰਾ ਕੀਤਾ" "ਹੋਮ \'ਤੇ ਜਾਣ ਲਈ ਸਵਾਈਪ ਕਰੋ" "ਆਪਣੀ ਸਕ੍ਰੀਨ ਦੇ ਹੇਠਾਂ ਤੋਂ ਉੱਪਰ ਵੱਲ ਸਵਾਈਪ ਕਰੋ। ਇਹ ਇਸ਼ਾਰਾ ਹਮੇਸ਼ਾਂ ਤੁਹਾਨੂੰ ਹੋਮ ਸਕ੍ਰੀਨ \'ਤੇ ਲੈ ਜਾਂਦਾ ਹੈ।" + "ਸਕ੍ਰੀਨ ਦੇ ਹੇਠਾਂ ਤੋਂ 2 ਉਂਗਲਾਂ ਨਾਲ ਉੱਪਰ ਵੱਲ ਸਵਾਈਪ ਕਰੋ। ਇਹ ਇਸ਼ਾਰਾ ਹਮੇਸ਼ਾਂ ਤੁਹਾਨੂੰ ਹੋਮ ਸਕ੍ਰੀਨ \'ਤੇ ਲੈ ਜਾਂਦਾ ਹੈ।" "ਹੋਮ ਸਕ੍ਰੀਨ \'ਤੇ ਜਾਓ" "ਆਪਣੀ ਸਕ੍ਰੀਨ ਦੇ ਹੇਠਾਂ ਤੋਂ ਉੱਪਰ ਵੱਲ ਸਵਾਈਪ ਕਰੋ" "ਬਹੁਤ ਵਧੀਆ!" @@ -75,9 +74,10 @@ "ਛੱਡਣ ਤੋਂ ਪਹਿਲਾਂ ਵਿੰਡੋ ਨੂੰ ਕੁਝ ਸਮੇਂ ਲਈ ਦਬਾ ਕੇ ਰੱਖੋ" "ਇਹ ਪੱਕਾ ਕਰੋ ਕਿ ਤੁਸੀਂ ਸਿੱਧਾ ਉੱਪਰ ਵੱਲ ਸਵਾਈਪ ਕਰੋ, ਫਿਰ ਰੋਕੋ" "ਤੁਸੀਂ ਇਸ਼ਾਰੇ ਵਰਤਣ ਬਾਰੇ ਜਾਣਿਆ। ਇਸ਼ਾਰੇ ਬੰਦ ਕਰਨ ਲਈ, ਸੈਟਿੰਗਾਂ \'ਤੇ ਜਾਓ।" - "ਤੁਸੀਂ \'ਐਪਾਂ ਵਿਚਾਲੇ ਸਵਿੱਚ ਕਰੋ\' ਦਾ ਇਸ਼ਾਰਾ ਪੂਰਾ ਕੀਤਾ" + "ਤੁਸੀਂ \'ਐਪਾਂ ਵਿਚਾਲੇ ਅਦਲਾ-ਬਦਲੀ ਕਰੋ\' ਦਾ ਇਸ਼ਾਰਾ ਪੂਰਾ ਕੀਤਾ" "ਐਪਾਂ ਵਿਚਾਲੇ ਅਦਲਾ-ਬਦਲੀ ਕਰਨ ਲਈ ਸਵਾਈਪ ਕਰੋ" "ਐਪਾਂ ਵਿਚਾਲੇ ਸਵਿੱਚ ਕਰਨ ਲਈ, ਸਕ੍ਰੀਨ ਦੇ ਹੇਠਾਂ ਤੋਂ ਉੱਤੇ ਸਵਾਈਪ ਕਰ ਕੇ ਦਬਾਈ ਰੱਖੋ ਅਤੇ ਫਿਰ ਛੱਡੋ।" + "ਐਪਾਂ ਵਿਚਾਲੇ ਸਵਿੱਚ ਕਰਨ ਲਈ, ਸਕ੍ਰੀਨ ਦੇ ਹੇਠਾਂ ਤੋਂ 2 ਉਂਗਲਾਂ ਨਾਲ ਉੱਤੇ ਸਵਾਈਪ ਕਰ ਕੇ ਦਬਾਈ ਰੱਖੋ ਅਤੇ ਫਿਰ ਛੱਡੋ।" "ਐਪਾਂ ਸਵਿੱਚ ਕਰੋ" "ਆਪਣੀ ਸਕ੍ਰੀਨ ਦੇ ਹੇਠਾਂ ਤੋਂ ਉੱਪਰ ਵੱਲ ਸਵਾਈਪ ਕਰ ਕੇ ਦਬਾਈ ਰੱਖੋ, ਅਤੇ ਫਿਰ ਛੱਡੋ" "ਬਹੁਤ ਵਧੀਆ!" @@ -90,7 +90,7 @@ "ਪੂਰੀ ਤਰ੍ਹਾਂ ਤਿਆਰ!" "ਹੋਮ \'ਤੇ ਜਾਣ ਲਈ ਉੱਪਰ ਵੱਲ ਸਵਾਈਪ ਕਰੋ" "ਆਪਣੀ ਹੋਮ ਸਕ੍ਰੀਨ \'ਤੇ ਜਾਣ ਲਈ ਹੋਮ ਬਟਨ \'ਤੇ ਟੈਪ ਕਰੋ" - "ਹੁਣ ਤੁਹਾਡਾ %1$s ਵਰਤੋਂ ਲਈ ਤਿਆਰ ਹੈ" + "ਤੁਸੀਂ ਆਪਣਾ %1$s ਵਰਤਣ ਲਈ ਤਿਆਰ ਹੋ" "ਡੀਵਾਈਸ" "ਸਿਸਟਮ ਨੈਵੀਗੇਸ਼ਨ ਸੈਟਿੰਗਾਂ" "ਸਾਂਝਾ ਕਰੋ" @@ -99,7 +99,7 @@ "ਐਪ ਜੋੜਾਬੱਧ ਰੱਖਿਅਤ ਕਰੋ" "ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਨੂੰ ਵਰਤਣ ਲਈ ਕਿਸੇ ਹੋਰ ਐਪ \'ਤੇ ਟੈਪ ਕਰੋ" "ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਵਰਤਣ ਲਈ ਕਿਸੇ ਹੋਰ ਐਪ ਨੂੰ ਚੁਣੋ" - "ਰੱਦ ਕਰੋ" + "ਰੱਦ ਕਰੋ" "ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਦੀ ਚੋਣ ਤੋਂ ਬਾਹਰ ਜਾਓ" "ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਵਰਤਣ ਲਈ ਕਿਸੇ ਹੋਰ ਐਪ ਨੂੰ ਚੁਣੋ" "ਐਪ ਜਾਂ ਤੁਹਾਡੀ ਸੰਸਥਾ ਵੱਲੋਂ ਇਸ ਕਾਰਵਾਈ ਦੀ ਇਜਾਜ਼ਤ ਨਹੀਂ ਹੈ" @@ -130,37 +130,18 @@ "ਤਤਕਾਲ ਸੈਟਿੰਗਾਂ" "ਟਾਸਕਬਾਰ" "ਟਾਸਕਬਾਰ ਨੂੰ ਦਿਖਾਇਆ ਗਿਆ" - "ਟਾਸਕਬਾਰ ਤੇ ਬਬਲ ਨੂੰ ਖੱਬੇ ਦਿਖਾਇਆ" - "ਟਾਸਕਬਾਰ ਤੇ ਬਬਲ ਨੂੰ ਸੱਜੇ ਦਿਖਾਇਆ" + "ਟਾਸਕਬਾਰ ਨੂੰ ਲੁਕਾਇਆ ਗਿਆ" "ਨੈਵੀਗੇਸ਼ਨ ਵਾਲੀ ਪੱਟੀ" "ਹਮੇਸ਼ਾਂ ਟਾਸਕਬਾਰ ਦਿਖਾਓ" "ਨੈਵੀਗੇਸ਼ਨ ਮੋਡ ਬਦਲੋ" "ਟਾਸਕਬਾਰ ਵਿਭਾਜਕ" - "ਟਾਸਕਬਾਰ ਓਵਰਫ਼ਲੋ" "ਸਿਖਰਲੇ/ਖੱਬੇ ਪਾਸੇ ਲੈ ਕੇ ਜਾਓ" "ਹੇਠਾਂ/ਸੱਜੇ ਪਾਸੇ ਲੈ ਕੇ ਜਾਓ" - "ਐਪ ਨੂੰ ਬਬਲ ਵਜੋਂ ਖੋਲ੍ਹੋ" - "ਹਾਲੀਆ ਐਪਾਂ" - "ਹਾਲੀਆ ਐਪ ਸੂਚੀ" - "{count,plural, =1{ਹੋਰ ਐਪ}one{ਹੋਰ ਐਪ}other{ਹੋਰ ਐਪਾਂ}}" - "ਡੈਸਕਟਾਪ" + "{count,plural, =1{# ਹੋਰ ਐਪ ਦਿਖਾਓ।}one{# ਹੋਰ ਐਪ ਦਿਖਾਓ।}other{# ਹੋਰ ਐਪਾਂ ਦਿਖਾਓ।}}" + "{count,plural, =1{# ਡੈਸਕਟਾਪ ਐਪ ਦਿਖਾਓ।}one{# ਡੈਸਕਟਾਪ ਐਪ ਦਿਖਾਓ।}other{# ਡੈਸਕਟਾਪ ਐਪਾਂ ਦਿਖਾਓ।}}" "%1$s ਅਤੇ %2$s" - "%1$s, %3$d ਵਿੱਚੋਂ %2$d ਆਈਟਮ" - "ਖੱਬੇ ਪਾਸੇ ਵੱਲ ਸਕ੍ਰੋਲ ਕਰੋ" - "ਸੱਜੇ ਪਾਸੇ ਵੱਲ ਸਕ੍ਰੋਲ ਕਰੋ" "ਬਬਲ" "ਓਵਰਫ਼ਲੋ" "%2$s ਤੋਂ %1$s" "%1$s ਅਤੇ %2$d ਹੋਰ" - "ਖੱਬੇ ਲਿਜਾਓ" - "ਸੱਜੇ ਲਿਜਾਓ" - "ਸਭ ਖਾਰਜ ਕਰੋ" - "%1$s ਦਾ ਵਿਸਤਾਰ ਕਰੋ" - "%1$s ਨੂੰ ਸਮੇਟੋ" - "ਖੋਜਣ ਲਈ ਚੱਕਰ ਬਣਾਓ" - "ਐਪ ਪ੍ਰਤੀਕ" - "ਐਪ ਸਿਰਲੇਖ" - "\'ਬੰਦ ਕਰੋ\' ਬਟਨ" - "ਟਾਸਕਬਾਰ \'ਤੇ ਪਿੰਨ ਕਰੋ" - "ਟਾਸਕਬਾਰ ਤੋਂ ਅਣਪਿੰਨ ਕਰੋ" diff --git a/quickstep/res/values-pl/strings.xml b/quickstep/res/values-pl/strings.xml index 14b60c49d7..13c8c1c48b 100644 --- a/quickstep/res/values-pl/strings.xml +++ b/quickstep/res/values-pl/strings.xml @@ -22,13 +22,11 @@ "Przypnij" "Tryb dowolny" "Pulpit" - "Przenieś na wyświetlacz zewnętrzny" - "Zamknij" - "Pulpit" "Brak ostatnich elementów" "Ustawienia użycia aplikacji" "Wyczyść wszystko" "Ostatnie aplikacje" + "Zadanie zamknięte" "%1$s, %2$s" "> 1 min" "Na dziś zostało %1$s" @@ -47,7 +45,6 @@ "Włączono sugestie aplikacji" "Sugestie aplikacji są wyłączone" "Przewidywana aplikacja: %1$s" - "Samouczek dotyczący nawigacji przy użyciu gestów" "Obróć urządzenie" "Obróć urządzenie, aby ukończyć samouczek nawigacji przy użyciu gestów" "Pamiętaj, aby przesuwać palcem od samej krawędzi (prawej lub lewej)" @@ -59,6 +56,7 @@ "Czułość gestu cofania możesz zmienić w Ustawieniach" "Przesuń palcem, aby przejść wstecz" "Aby wrócić do poprzedniego ekranu, przesuń palcem od lewej lub prawej krawędzi do środka ekranu." + "Aby wrócić do poprzedniego ekranu, przesuń 2 palcami od lewej lub prawej krawędzi do środka ekranu." "Przejście wstecz" "Przesuń palcem od lewej lub prawej krawędzi do środka ekranu" "Pamiętaj, aby przesuwać palcem od dolnej krawędzi ekranu" @@ -68,6 +66,7 @@ "Gest przechodzenia na ekran główny został opanowany" "Przesuń palcem, aby przejść na ekran główny" "Przesuń palcem od dołu ekranu. Ten gest zawsze powoduje przejście na ekran główny." + "Przesuń 2 palcami od dołu ekranu. Ten gest zawsze powoduje przejście do ekranu głównego." "Przejście na ekran główny" "Przesuń palcem z dołu ekranu w górę" "Brawo!" @@ -78,7 +77,8 @@ "Gest przełączania aplikacji został opanowany" "Przesuń palcem, aby przełączać aplikacje" "Aby przełączać się między aplikacjami, przesuń palcem od dołu do góry ekranu, przytrzymaj i puść." - "Przełączanie aplikacji" + "Aby przełączać się między aplikacjami, przesuń 2 palcami od dołu ekranu, przytrzymaj i puść." + "Przełącz aplikację" "Przesuń palcem z dołu ekranu w górę, przytrzymaj i puść" "Brawo!" "Wszystko gotowe" @@ -99,7 +99,7 @@ "Zapisz parę" "Aby podzielić ekran, kliknij drugą aplikację" "Aby podzielić ekran, wybierz drugą aplikację" - "Anuluj" + "Anuluj" "Wyjdź z wyboru podzielonego ekranu" "Wybierz drugą aplikację, aby podzielić ekran" "Nie możesz wykonać tego działania, bo nie zezwala na to aplikacja lub Twoja organizacja" @@ -130,37 +130,18 @@ "Szybkie ustawienia" "Pasek aplikacji" "Pasek aplikacji widoczny" - "Pasek i dymki po lewej" - "Pasek i dymki po prawej" + "Pasek aplikacji ukryty" "Pasek nawigacyjny" "Zawsze pokazuj pasek aplikacji" "Zmień tryb nawigacji" "Linia dzielenia paska aplikacji" - "Rozwijany pasek aplikacji" "Przesuń w górny lewy róg" "Przesuń w dolny prawy róg" - "Otwórz aplikację jako dymek" - "Ostatnie aplikacje" - "Lista ostatnich aplikacji" - "{count,plural, =1{inna aplikacja}few{inne aplikacje}many{innych aplikacji}other{innej aplikacji}}" - "Pulpit" + "{count,plural, =1{Pokaż jeszcze # aplikację.}few{Pokaż jeszcze # aplikacje.}many{Pokaż jeszcze # aplikacji.}other{Pokaż jeszcze # aplikacji.}}" + "{count,plural, =1{Pokaż # aplikację komputerową.}few{Pokaż # aplikacje komputerowe.}many{Pokaż # aplikacji komputerowych.}other{Pokaż # aplikacji komputerowej.}}" "%1$s%2$s" - "%1$s, element %2$d%3$d" - "Przewiń w lewo" - "Przewiń w prawo" "Dymek" "Rozwijany" "%1$s z aplikacji %2$s" "%1$s i jeszcze %2$d" - "Przenieś w lewo" - "Przenieś w prawo" - "Zamknij wszystkie" - "rozwiń dymek: %1$s" - "zwiń dymek: %1$s" - "Zaznacz, aby wyszukać" - "Ikona aplikacji" - "Tytuł aplikacji" - "Przycisk Zamknij" - "Przypnij do paska" - "Odepnij od paska" diff --git a/quickstep/res/values-pt-rPT/strings.xml b/quickstep/res/values-pt-rPT/strings.xml index 2225496c18..e4d07bd97a 100644 --- a/quickstep/res/values-pt-rPT/strings.xml +++ b/quickstep/res/values-pt-rPT/strings.xml @@ -22,13 +22,11 @@ "Fixar" "Forma livre" "Computador" - "Mover para o ecrã externo" - "Fechar" - "Computador" "Nenhum item recente" "Definições de utilização de aplicações" "Limpar tudo" "Apps recentes" + "Tarefa fechada" "%1$s, %2$s" "< 1 minuto" "Resta(m) %1$s hoje." @@ -47,10 +45,9 @@ "Sugestões de apps ativadas" "As sugestões de apps estão desativadas" "App prevista: %1$s" - "Tutorial da navegação por gestos" "Rode o dispositivo" "Rode o seu dispositivo para concluir o tutorial de navegação por gestos" - "Deslize a partir da extremidade mais à direita ou mais à esquerda" + "Deslize rapidamente a partir da extremidade mais à direita ou mais à esquerda" "Deslize rapidamente a partir da extremidade esquerda ou direita até ao centro do ecrã e solte" "Aprendeu a deslizar a partir da direita para retroceder. A seguir, saiba como alternar entre apps." "Concluiu o gesto para retroceder. A seguir, saiba como alternar entre apps." @@ -59,27 +56,30 @@ "Altere a sensibilidade do gesto para voltar nas Definições." "Deslize rapidamente com o dedo para retroceder" "Para voltar ao último ecrã, deslize rapidamente do limite esquerdo ou direito até ao centro do ecrã." + "Para voltar ao último ecrã, deslize rapidamente com 2 dedos a partir da extremidade esquerda ou direita até ao centro do ecrã." "Retroceder" - "Deslize a partir da extremidade esquerda ou direita até ao centro do ecrã" - "Deslize a partir do limite inferior do ecrã" + "Deslize rapidamente a partir da extremidade esquerda ou direita para o meio do ecrã" + "Deslize rapidamente com o dedo a partir do limite inferior do ecrã" "Não faça uma pausa antes de soltar" "Deslize rapidamente com o dedo para cima" "Concluiu o gesto para aceder ao ecrã principal. A seguir, saiba como retroceder." "Concluiu o gesto para aceder ao ecrã principal" "Deslize rapidamente com o dedo para aceder ao ecrã principal" "Deslize rapidamente para cima a partir da parte inferior. Este gesto abre sempre o ecrã principal." + "Deslize rapidamente para cima com 2 dedos no fundo do ecrã. Este gesto abre sempre o ecrã principal." "Aceda ao ecrã principal" - "Deslize para cima a partir da parte inferior do ecrã" + "Deslize rapidamente para cima a partir da parte inferior do ecrã" "Muito bem!" - "Deslize a partir do limite inferior do ecrã" + "Deslize rapidamente com o dedo a partir do limite inferior do ecrã" "Experimente premir a janela durante mais tempo antes de soltar" - "Deslize para cima e pause" + "Garanta que desliza rapidamente com o dedo para cima e, em seguida, faz uma pausa" "Aprendeu a utilizar gestos. Para desativar os gestos, aceda às Definições." "Concluiu o gesto para alternar entre apps" "Deslize rapidamente com o dedo para alternar entre apps" "Para alternar entre apps, deslize para cima sem soltar a partir da parte inferior do ecrã e solte." + "Para mudar de app, deslize rapidamente para cima com 2 dedos sem soltar no fundo do ecrã e solte." "Mude de app" - "Deslize para cima a partir da parte inferior do ecrã, detenha o gesto e solte" + "Deslize rapidamente para cima a partir da parte inferior do ecrã sem soltar e, em seguida, solte" "Muito bem!" "Está tudo pronto" "Concluído" @@ -99,7 +99,7 @@ "Guardar par de apps" "Toque noutra app para usar o ecrã dividido" "Escolha outra app para usar o ecrã dividido" - "Cancelar" + "Cancelar" "Saia da seleção de ecrã dividido" "Escolher outra app para usar o ecrã dividido" "Esta ação não é permitida pela app ou a sua entidade." @@ -130,37 +130,18 @@ "Definiç. rápidas" "Barra de tarefas" "Barra de tarefas apresentada" - "Barra de tarefas/balões à esq." - "Barra de tarefas/balões à dir." + "Barra de tarefas ocultada" "Barra de navegação" "Ver sempre Barra de tarefas" "Alterar modo de navegação" "Divisor da Barra de tarefas" - "Menu adicional da Barra de tarefas" "Mover para a parte superior esquerda" "Mover para a part superior direita" - "Abrir app como um balão" - "Apps recentes" - "Lista de apps recentes" - "{count,plural, =1{outra app}other{outras apps}}" - "Computador" + "{count,plural, =1{Mostrar mais # app.}other{Mostrar mais # apps.}}" + "{count,plural, =1{Mostrar # app para computador.}other{Mostrar # apps para computador.}}" "%1$s e %2$s" - "%1$s, item %2$d de %3$d" - "Deslocar para a esquerda" - "Deslocar para a direita" "Balão" "Menu adicional" "%1$s da app %2$s" "%1$s e mais %2$d pessoas" - "Mover para a esquerda" - "Mover para a direita" - "Ignorar tudo" - "expandir %1$s" - "reduzir %1$s" - "Circundar para Pesquisar" - "Ícone da app" - "Título da app" - "Botão Fechar" - "Afixar na barra tar." - "Desaf. da barra tar." diff --git a/quickstep/res/values-pt/strings.xml b/quickstep/res/values-pt/strings.xml index e4c3cf1996..4fec4f8511 100644 --- a/quickstep/res/values-pt/strings.xml +++ b/quickstep/res/values-pt/strings.xml @@ -21,14 +21,12 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Fixar" "Forma livre" - "Modo área de trabalho" - "Mover para a tela externa" - "Fechar" - "Computador" + "Computador" "Nenhum item recente" "Configurações de uso do app" "Remover tudo" "Apps recentes" + "Tarefa encerrada" "%1$s, %2$s" "< 1 min" "%1$s restante(s) hoje" @@ -47,7 +45,6 @@ "O recurso \"sugestões de apps\" está ativado" "O recurso \"sugestões de apps\" está desativado" "App previsto: %1$s" - "Tutorial da navegação por gestos" "Gire o dispositivo" "Gire o dispositivo para concluir o tutorial da navegação por gestos" "Deslize da borda direita ou esquerda" @@ -59,6 +56,7 @@ "Mude a sensibilidade do gesto de voltar nas configurações" "Deslize para voltar" "Para voltar à tela anterior, deslize da borda esquerda ou direita até o meio da tela." + "Para voltar à tela anterior, deslize da borda esquerda ou direita até o meio da tela com dois dedos." "Volte" "Deslize da borda esquerda ou direita até o meio da tela" "Deslize da borda inferior da tela para cima" @@ -68,16 +66,18 @@ "Você concluiu o gesto para acessar a tela inicial" "Deslizar para voltar à tela inicial" "Deslize de baixo para cima na tela. Esse gesto sempre leva você para a tela inicial." + "Deslize de baixo para cima na tela com dois dedos. Esse gesto sempre leva você para a tela inicial." "Vá para a página inicial" "Deslize de baixo para cima na tela" "Muito bem!" "Deslize da borda inferior da tela para cima" "Mantenha a janela pressionada por mais tempo antes de soltar" - "Deslize para cima em linha reta e pare" + "Deslize para cima e pare" "Você aprendeu. Para desativar os gestos, acesse as Configurações." "Você concluiu o gesto para mudar de app" "Deslizar para trocar de app" "Para mudar de app, deslize de baixo para cima, mantenha a tela pressionada por um tempo e solte." + "Para mudar de app, deslize de baixo para cima na tela com dois dedos, segure por um tempo e solte." "Mude de app" "Deslize de baixo para cima na tela, segure e depois solte" "Muito bem!" @@ -89,8 +89,8 @@ "Tutorial %1$d/%2$d" "Tudo pronto!" "Deslize para cima para acessar a tela inicial" - "Toque no botão home para acessar a tela inicial" - "O %1$s já pode ser usado" + "Toque no botão home para ir para a tela inicial" + "Você já pode começar a usar seu %1$s" "dispositivo" "Configurações de navegação do sistema" "Compartilhar" @@ -99,7 +99,7 @@ "Salvar par de apps" "Toque em outro app para usar a tela dividida" "Escolha outro app para usar na tela dividida" - "Cancelar" + "Cancelar" "Sair da seleção de tela dividida" "Escolha outro app para usar na tela dividida" "Essa ação não é permitida pelo app ou pela organização" @@ -118,7 +118,7 @@ "Sempre mostrar a Barra de tarefas" "Toque e pressione o divisor para sempre mostrar a Barra de tarefas na parte de baixo da tela" "Toque na tecla de ação e pressione para pesquisar o que está na tela" - "Este produto usa a parte selecionada da tela para pesquisar. O uso desses dados está sujeito à <a href="%1$s">Política de Privacidade</a> e aos <a href="%2$s">Termos de Serviço</a> do Google." + "O produto usa a parte selecionada da tela para pesquisar. O uso desses dados está sujeito à <a href="%1$s">Política de Privacidade</a> e aos <a href="%2$s">Termos de Serviço</a> do Google." "Fechar" "Concluído" "Início" @@ -130,37 +130,18 @@ "Config. rápidas" "Barra de tarefas" "Barra de tarefas visível" - "Barra de tar. e balões à esq." - "Barra de tar. e balões à dir." + "Barra de tarefas oculta" "Barra de navegação" "Sempre mostrar a Barra de tarefas" "Mudar o modo de navegação" "Separador da Barra de tarefas" - "Barra de tarefas flutuante" "Mover para cima/para a esquerda" "Mover para baixo/para a direita" - "Abrir o app como um balão" - "Apps recentes" - "Lista de apps recentes" - "{count,plural, =1{outro app}one{outro app}other{outros apps}}" - "Computador" + "{count,plural, =1{Mostrar mais # app.}one{Mostrar mais # app.}other{Mostrar mais # apps.}}" + "{count,plural, =1{Mostrar # app para computador.}one{Mostrar # app para computador.}other{Mostrar # apps para computador.}}" "%1$s e %2$s" - "%1$s, item %2$d de %3$d" - "Rolar para a esquerda" - "Rolar para a direita" "Balão" "Balão flutuante" "%1$s do app %2$s" "%1$s e mais %2$d" - "Mover para esquerda" - "Mover para direita" - "Dispensar todos" - "abrir %1$s" - "fechar %1$s" - "Circule para pesquisar" - "Ícone do app" - "Título do app" - "Botão \"Fechar\"" - "Fixar na barra de tarefas" - "Liberar da barra de tarefas" diff --git a/quickstep/res/values-ro/strings.xml b/quickstep/res/values-ro/strings.xml index 27f80fbb5c..c839602e89 100644 --- a/quickstep/res/values-ro/strings.xml +++ b/quickstep/res/values-ro/strings.xml @@ -21,14 +21,12 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Fixează" "Formă liberă" - "Desktop" - "Mută pe ecranul extern" - "Închide" - "Computer" + "Computer" "Niciun element recent" "Setări de utilizare a aplicației" "Șterge tot" "Aplicații recente" + "Activitatea s-a încheiat" "%1$s, %2$s" "< 1 minut" "Au mai rămas %1$s astăzi" @@ -47,7 +45,6 @@ "Sugestiile de aplicații au fost activate" "Sugestiile de aplicații au fost dezactivate" "Aplicația estimată: %1$s" - "Tutorial de navigare prin gesturi" "Rotește dispozitivul" "Rotește dispozitivul pentru a încheia tutorialul de navigare prin gesturi" "Glisează dinspre marginea dreaptă îndepărtată sau dinspre marginea stângă îndepărtată" @@ -59,6 +56,7 @@ "Schimbă sensibilitatea gestului „Înapoi” accesând Setările" "Glisează pentru a reveni" "Pentru a reveni la ultimul ecran, glisează de la marginea stângă sau dreaptă spre mijlocul ecranului." + "Pentru a reveni la ultimul ecran, glisează cu două degete dinspre marginea stângă sau dreaptă spre mijlocul ecranului." "Înapoi" "Glisează dinspre marginea stângă sau dreaptă până la jumătatea ecranului" "Glisează în sus dinspre marginea de jos a ecranului" @@ -68,6 +66,7 @@ "Ai finalizat gestul „accesează ecranul de pornire”" "Glisează pentru a accesa ecranul de pornire" "Glisează în sus din partea de jos a ecranului. Cu acest gest accesezi mereu ecranul de pornire." + "Glisează în sus cu două degete din partea de jos. Cu acest gest accesezi mereu ecranul de pornire." "Înapoi la ecranul de pornire" "Glisează în sus din partea de jos a ecranului" "Excelent!" @@ -78,6 +77,7 @@ "Ai finalizat gestul „comută între aplicații”" "Glisează pentru a comuta între aplicații" "Ca să comuți între aplicații, glisează de jos în sus, ține degetul pe ecran, apoi ridică-l." + "Ca să comuți între aplicații, glisează cu 2 degete de jos în sus, ține-le pe ecran, apoi ridică-le." "Comută între aplicații" "Glisează în sus din partea de jos a ecranului, ține apăsat, apoi eliberează" "Felicitări!" @@ -99,7 +99,7 @@ "Salvează perechea de aplicații" "Atinge altă aplicație pentru ecranul împărțit" "Alege altă aplicație pentru a folosi ecranul împărțit" - "Anulează" + "Anulează" "Ieși din selecția cu ecran împărțit" "Alege altă aplicație pentru ecranul împărțit" "Această acțiune nu este permisă de aplicație sau de organizația ta" @@ -130,37 +130,18 @@ "Setări rapide" "Bară de activități" "Bara de activități este afișată" - "Bară și baloane stânga afișate" - "Bară & baloane dreapta afișate" + "Bara de activități este ascunsă" "Bară de navigare" "Afișează mereu bara" "Schimbă modul de navigare" "Separator pentru bara de activități" - "Meniu suplimentar pentru bara de activități" "Mută în stânga sus" "Mută în dreapta jos" - "Deschide aplicația ca balon" - "Aplicații recente" - "Lista de aplicații recente" - "{count,plural, =1{aplicație suplimentară}few{mai multe aplicații}other{mai multe aplicații}}" - "Computer" + "{count,plural, =1{Afișează încă # aplicație}few{Afișează încă # aplicații}other{Afișează încă # de aplicații}}" + "{count,plural, =1{Afișează # aplicație pentru computer.}few{Afișează # aplicații pentru computer.}other{Afișează # de aplicații pentru computer.}}" "%1$s și %2$s" - "%1$s, articolul %2$d din %3$d" - "Derulează la stânga" - "Derulează la dreapta" "Balon" "Suplimentar" "%1$s de la %2$s" "%1$s și încă %2$d" - "Deplasează spre stânga" - "Deplasează spre dreapta" - "Închide-le pe toate" - "extinde %1$s" - "restrânge %1$s" - "Încercuiește și caută" - "Pictograma aplicației" - "Titlul aplicației" - "Buton de închidere" - "Fixează pe bara de activități" - "Anulează fixarea din bara de activități" diff --git a/quickstep/res/values-ru/strings.xml b/quickstep/res/values-ru/strings.xml index 605c5654ef..da49ad32b8 100644 --- a/quickstep/res/values-ru/strings.xml +++ b/quickstep/res/values-ru/strings.xml @@ -21,14 +21,12 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Закрепить" "Произвольная форма" - "Мультиоконный режим" - "Перенести на внешний дисплей" - "Закрыть" - "Мультиоконный режим" + "Включить режим для ПК" "Здесь пока ничего нет." "Настройки использования приложения" "Очистить все" "Недавние приложения" + "Задача закрыта" "%1$s: %2$s" "< 1 мин." "Осталось сегодня: %1$s" @@ -47,7 +45,6 @@ "Функция \"Рекомендуемые приложения\" включена." "Функция \"Рекомендуемые приложения\" отключена." "Рекомендуемое приложение: %1$s" - "Руководство: навигация с помощью жестов" "Поверните устройство" "Чтобы перейти к руководству по жестам, нужно повернуть устройство." "Проведите справа налево или слева направо от самого края экрана." @@ -59,6 +56,7 @@ "Уровень чувствительности можно изменить в настройках." "Возврат к предыдущему экрану" "Чтобы вернуться к предыдущему экрану, проведите от левого или правого края дисплея к центру." + "Чтобы вернуться на предыдущий экран, проведите двумя пальцами от левого или правого края экрана к центру." "Как вернуться к предыдущему экрану" "Проведите от левого или правого края экрана к центру." "Проведите снизу вверх от самого края экрана." @@ -68,16 +66,18 @@ "Вы выполнили жест для перехода на главный экран." "Переход на главный экран" "Проведите вверх от нижнего края дисплея. Этот жест открывает главный экран." + "Проведите двумя пальцами вверх от нижнего края экрана. Этот жест открывает главный экран." "Как перейти на главный экран" "Проведите вверх от нижнего края экрана." "У вас получилось!" "Проведите снизу вверх от самого края экрана." "Прежде чем отпустить палец, задержите его на экране немного дольше." - "Проведите по экрану вверх и задержите палец." + "Проведите по экрану ровно вверх и задержите палец в конце." "Теперь вы знаете, как использовать жесты. Чтобы отключить их, перейдите в настройки." "Вы выполнили жест для переключения между приложениями." "Переключение между приложениями" "Чтобы переключиться между приложениями‚ проведите по экрану снизу вверх, задержите палец, а затем отпустите." + "Чтобы сменить приложение, проведите двумя пальцами снизу вверх, задержите пальцы, а затем отпустите." "Как переключаться между приложениями" "Проведите вверх от нижнего края экрана, задержите палец в одной точке и отпустите." "Отлично!" @@ -92,14 +92,14 @@ "Нажмите кнопку главного экрана, чтобы открыть его." "Теперь вы можете использовать %1$s." "устройство" - "Настройки навигации в системе" + "Системные настройки навигации" "Поделиться" "Скриншот" "Разделить" "Сохранить приложения" "Для разделения экрана выберите другое приложение." "Чтобы использовать разделенный экран, выберите другое приложение." - "Отмена" + "Отмена" "Выйдите из режима разделения экрана." "Выберите другое приложение для разделения экрана." "Это действие заблокировано приложением или организацией." @@ -130,37 +130,18 @@ "Быстрые настройки" "Панель задач" "Панель задач показана" - "Слева панель задач, подсказки" - "Справа панель задач, подсказки" + "Панель задач скрыта" "Панель навигации" "Всегда показывать панель задач" "Изменить режим навигации" "Разделитель панели задач" - "Дополнительное меню панели задач" "Переместить вверх или влево" "Переместить вниз или вправо" - "Открыть приложение во всплывающем окне" - "Недавние приложения" - "Список недавних приложений" - "{count,plural, =1{дополнительное приложение}one{дополнительное приложение}few{дополнительных приложения}many{дополнительных приложений}other{дополнительного приложения}}" - "Режим компьютера" + "{count,plural, =1{Показать ещё # приложение}one{Показать ещё # приложение}few{Показать ещё # приложения}many{Показать ещё # приложений}other{Показать ещё # приложения}}" + "{count,plural, =1{Показать # компьютерное приложение.}one{Показать # компьютерное приложение.}few{Показать # компьютерных приложения.}many{Показать # компьютерных приложений.}other{Показать # компьютерного приложения.}}" "%1$s и %2$s" - "%1$s, элемент %2$d из %3$d" - "Прокрутить влево" - "Прокрутить вправо" "Всплывающая подсказка" "Дополнительное меню" "\"%1$s\" из приложения \"%2$s\"" "%1$s и ещё %2$d" - "Переместить влево" - "Переместить вправо" - "Закрыть все" - "Развернуто: %1$s" - "Свернуто: %1$s" - "Обвести и найти" - "Значок приложения" - "Название приложения" - "Кнопка \"Закрыть\"" - "Закрепить на панели" - "Открепить от панели" diff --git a/quickstep/res/values-si/strings.xml b/quickstep/res/values-si/strings.xml index 2030252b29..9cbe837f94 100644 --- a/quickstep/res/values-si/strings.xml +++ b/quickstep/res/values-si/strings.xml @@ -22,13 +22,11 @@ "අමුණන්න" "Freeform" "ඩෙස්ක්ටොපය" - "බාහිර සංදර්ශකය වෙත ගෙන යන්න" - "වසන්න" - "ඩෙස්ක්ටොපය" "මෑත අයිතම නැත" "යෙදුම් භාවිත සැකසීම්" "සියල්ල හිස් කරන්න" "මෑත යෙදුම්" + "කාර්යය අවසන් කරන ලදි" "%1$s, %2$s" "< 1 විනාඩියක්" "අද %1$sක් ඉතුරුයි" @@ -47,7 +45,6 @@ "යෙදුම් යෝජනා සබලිතයි" "යෙදුම් යෝජනා අබල කර ඇත" "පුරෝකථනය කළ යෙදුම: %1$s" - "අභින සංචාලන නිබන්ධනය" "ඔබේ උපාංගය කරකවන්න" "අභින සංචාලන නිබන්ධනය සම්පූර්ණ කිරීම සඳහා ඔබේ උපාංගය කරකවන්න" "ඔබ ඈත දකුණු හෝ ඈත වම් දාරයේ සිට ස්වයිප් කරන බව සහතික කර ගන්න" @@ -59,6 +56,7 @@ "ආපසු ඉංගිතයෙහි සංවේදීතාව වෙනස් කිරීමට, සැකසීම් වෙත යන්න" "ආපසු යාමට ස්වයිප් කරන්න" "අවසාන තිරයට ආපසු යාමට, වම් හෝ දකුණු දාරයෙන් තිරයේ මැදට ස්වයිප් කරන්න." + "අවසාන තිරයට ආපසු යාමට, වම් හෝ දකුණු දාරයෙන් තිරයේ මැදට ඇඟිලි 2කින් ස්වයිප් කරන්න." "ආපසු යන්න" "වම් හෝ දකුණු කෙළවරේ සිට තිරයේ මැදට ස්වයිප් කරන්න" "ඔබ තිරයේ පහළ දාරයේ සිට ඉහළට ස්වයිප් කරන බව සහතික කර ගන්න" @@ -68,6 +66,7 @@ "ඔබ මුල් පිටුවට යාමේ ඉංගිතය සම්පූර්ණ කළා" "මුල් පිටුවට යාමට ස්වයිප් කරන්න" "ඔබගේ තිරයේ පහළින් උඩට ස්වයිප් කරන්න.මෙම ඉංගිතය සැම විටම ඔබව මුල් තිරයට ගෙන යයි." + "තිරයේ පහළම සිට ඇඟිලි 2කින් ඉහළට ස්වයිප් කරන්න. මෙම ඉංගිතය සැම විටම ඔබව මුල් තිරයට ගෙන යයි." "මුල් පිටුවට යන්න" "ඔබේ තිරයේ පහළ සිට උඩට ස්වයිප් කරන්න" "අනර්ඝ වැඩක්!" @@ -78,6 +77,7 @@ "ඔබ යෙදුම් මාරු කිරීමේ ඉංගිතය සම්පූර්ණ කළා" "යෙදුම් මාරු කිරීමට ස්වයිප් කරන්න" "යෙදුම් අතර මාරු වීමට, ඔබගේ තිරයේ පහළම සිට උඩට ස්වයිප් කර, අල්ලාගෙන සිට, අනතුරුව මුදා හරින්න." + "යෙදුම් අතර මාරු වීමට, ඔබගේ තිරයේ පහළම සිට උඩට ඇඟිලි 2කින් ස්වයිප් කර, අල්ලාගෙන සිට, අනතුරුව මුදා හරින්න." "යෙදුම් මාරු කරන්න" "ඔබේ තිරයේ පහළ සිට ඉහළට ස්වයිප් කරන්න, රඳවා ගෙන සිට, පසුව මුදා හරින්න" "හොඳින් කළා!" @@ -99,7 +99,7 @@ "යෙදුම් යුගල සුරකින්න" "බෙදුම් තිරය භාවිතා කිරීමට තවත් යෙදුමක් තට්ටු කරන්න" "බෙදුම් තිරය භාවිත කිරීමට වෙනත් යෙදුමක් තෝරා ගන්න" - "අවලංගු කරන්න" + "අවලංගු කරන්න" "බෙදීම් තිර තේරීමෙන් පිටවන්න" "බෙදීම් තිරය භාවිතා කිරීමට වෙනත් යෙදුමක් තෝරා ගන්න" "මෙම ක්‍රියාව යෙදුම හෝ ඔබේ සංවිධානය මගින් ඉඩ නොදේ" @@ -130,37 +130,18 @@ "ඉක්මන් සැකසීම්" "කාර්ය තීරුව" "කාර්ය තීරුව පෙන්වා ඇත" - "කාර්ය තීරුව සහ බුබුළු පෙන්වා ඇත" - "කාර්ය තීරුව සහ බුබුළු දකුණට පෙන්වා ඇත" + "කාර්ය තීරුව සඟවා ඇත" "සංචලන තීරුව" "සෑම විටම කාර්ය තීරුව පෙන්වන්න" "සංචාලන ප්‍රකාරය වෙනස් කරන්න" "කාර්ය තීරු බෙදනය" - "කාර්ය තීරුව පිටාර යාම" "ඉහළ/වම වෙත ගෙන යන්න" "පහළ/දකුණ වෙත ගෙන යන්න" - "යෙදුම බුබුලක් ලෙස විවෘත කරන්න" - "මෑත යෙදුම්" - "මෑත යෙදුම් ලැයිස්තුව" - "{count,plural, =1{තව යෙදුම}one{තවත් යෙදුම්}other{තවත් යෙදුම්}}" - "ඩෙස්ක්ටොපය" + "{count,plural, =1{තවත් # යෙදුමක් පෙන්වන්න.}one{තවත් යෙදුම් #ක් පෙන්වන්න.}other{තවත් යෙදුම් #ක් පෙන්වන්න.}}" + "{count,plural, =1{# ඩෙස්ක්ටොප් යෙදුමක් පෙන්වන්න.}one{ඩෙස්ක්ටොප් යෙදුම් # ක් පෙන්වන්න.}other{ඩෙස්ක්ටොප් යෙදුම් # ක් පෙන්වන්න.}}" "%1$s සහ %2$s" - "%1$s, අයිතම %3$dන් %2$d" - "වමට අනුචලනය කරන්න" - "දකුණට අනුචලනය කරන්න" "බුබුළු" "පිටාර යාම" "%2$s සිට %1$s" "%1$s හා තව %2$dක්" - "වමට ගෙන යන්න" - "දකුණට ගෙන යන්න" - "සියල්ල ඉවතලන්න" - "%1$s දිග හරින්න" - "%1$s හකුළන්න" - "සෙවීමට කවයසෙවීමට කවය අදින්න" - "යෙදුම් නිරූපකය" - "යෙදුම් මාතෘකාව" - "වැසීමේ බොත්තම" - "කාර්ය තීරුවට අමුණන්න" - "කාර්ය තීරුවෙන් ඉවත් කරන්න" diff --git a/quickstep/res/values-sk/strings.xml b/quickstep/res/values-sk/strings.xml index 7e071167a3..3eca787fed 100644 --- a/quickstep/res/values-sk/strings.xml +++ b/quickstep/res/values-sk/strings.xml @@ -22,13 +22,11 @@ "Pripnúť" "Voľný režim" "Počítač" - "Presunúť na externú obrazovku" - "Zavrieť" - "Počítač" "Žiadne nedávne položky" "Nastavenia využívania aplikácie" "Vymazať všetko" "Nedávne aplikácie" + "Úloha bola zavretá" "%1$s, %2$s" "Menej ako 1 minúta" "Dnes ešte zostáva: %1$s" @@ -47,39 +45,41 @@ "Návrhy aplikácií zapnuté" "Návrhy aplikácií vypnuté" "Predpovedaná aplikácia: %1$s" - "Návod na navigáciu gestami" "Otočte zariadenie" "Otočte zariadenie a dokončite tak návod, ako navigovať gestami" - "Musíte potiahnuť úplne z pravého alebo ľavého okraja." + "Musíte potiahnuť úplne z pravého alebo ľavého okraja" "Musíte potiahnuť z pravého alebo ľavého okraja do stredu obrazovky a potom uvoľniť" "Naučili ste sa prejsť späť potiahnutím sprava. V ďalšom kroku sa naučíte prepínať aplikácie." "Dokončili ste gesto na prechod späť. V ďalšom kroku sa naučíte, ako prepínať aplikácie." - "Použili ste gesto na prechod späť." + "Dokončili ste gesto na prechod späť" "Nesmiete potiahnuť príliš blízko dolnej časti obrazovky" "Ak chcete zmeniť citlivosť gesta Späť, prejdite do Nastavení" "Prechod späť potiahnutím" "Na poslednú obrazovku prejdete potiahnutím z ľavého alebo pravého okraja do stredu obrazovky." + "Na poslednú obrazovku sa vrátite potiahnutím dvoma prstami z ľavého alebo pravého okraja do stredu obrazovky." "Prechod späť" - "Potiahnite z ľavého alebo pravého okraja do stredu obrazovky." - "Musíte potiahnuť nahor z dolného okraja obrazovky." + "Potiahnite z ľavého alebo pravého okraja do stredu obrazovky" + "Musíte potiahnuť nahor z dolného okraja obrazovky" "Pred uvoľnením nesmiete zastať" "Musíte potiahnuť priamo nahor" "Dokončili ste gesto prechodu na plochu. Teraz sa naučíte, ako sa vrátiť späť." "Dokončili ste gesto prechodu na plochu" "Prechod na plochu potiahnutím" "Potiahnite nahor zdola obrazovky. Týmto gestom sa vždy vrátite na plochu." + "Postiahnite dvoma prstami z dolnej časti obrazovky. Týmto gestom sa vždy vrátite na plochu." "Prechod na plochu" - "Potiahnite z dolnej časti obrazovky nahor." + "Potiahnite z dolnej časti obrazovky nahor" "Skvelé!" "Musíte potiahnuť nahor z dolného okraja obrazovky" "Skúste okno pred uvoľnením podržať dlhšie" - "Musite potiahnuť priamo nahor a potom zastať." - "Naučili ste sa používať gestá. Gestá môžete vypnúť v Nastaveniach." - "Použili ste gesto na prepnutie aplikácií." + "Musite potiahnuť priamo nahor a potom zastať" + "Naučili ste sa používať gestá. Gestá môžete vypnúť v nastaveniach." + "Dokončili ste gesto na prepnutie aplikácií" "Prepínanie aplikácií potiahnutím" "Aplikácie môžete prepínať potiahnutím obrazovky zdola nahor, pridržaním a následným uvoľnením." + "Aplikácie prepnete potiahnutím dvoma prstami z dolnej časti obrazovky, ich pridržaním a uvoľnením." "Prepnutie aplikácií" - "Potiahnite nahor z dolného okraja obrazovky, pridržte a uvoľnite." + "Potiahnite nahor z dolného okraja obrazovky, pridržte a uvoľnite" "Výborne" "Hotovo" "Hotovo" @@ -90,7 +90,7 @@ "Hotovo" "Potiahnutím nahor prejdete na plochu" "Na plochu prejdete klepnutím na tlačidlo plochy" - "Môžete %1$s začať používať" + "%1$s môžete začať používať" "zariadenie" "Nastavenia navigácie systémom" "Zdieľať" @@ -99,7 +99,7 @@ "Uložiť pár aplikácií" "Obrazovku rozdelíte klepnutím na inú aplikáciu" "Na použitie rozdelenej obrazovky vyberte ďalšiu aplikáciu" - "Zrušiť" + "Zrušiť" "Ukončite výber rozdelenej obrazovky" "Na použitie rozd. obrazovky vyberte inú aplikáciu" "Aplikácia alebo vaša organizácia túto akciu nepovoľuje" @@ -130,37 +130,18 @@ "Rýchle nastavenia" "Panel aplikácií" "Panel aplikácií je zobrazený" - "Panel aplik. a bubl. sú vľavo" - "Panel aplik. a bubl. sú vpravo" + "Panel aplikácií je skrytý" "Navigačný panel" "Zobrazovať panel aplikácií" "Zmeniť režim navigácie" "Rozdeľovač panela aplikácií" - "Rozšírená ponuka panela aplikácií" "Presunúť hore alebo doľava" "Presunúť dole alebo doprava" - "Otvoriť aplikáciu ako bublinu" - "Nedávne aplikácie" - "Zoznam nedávnych aplikácií" - "{count,plural, =1{ďalšia aplikácia}few{ďalšie aplikácie}many{ďalšie aplikácie}other{ďalšie aplikácie}}" - "Počítač" + "{count,plural, =1{Zobraziť # ďalšiu aplikáciu.}few{Zobraziť # ďalšie aplikácie.}many{Show # more apps.}other{Zobraziť # ďalších aplikácií.}}" + "{count,plural, =1{Zobraziť # aplikáciu pre počítač.}few{Zobraziť # aplikácie pre počítač.}many{Show # desktop apps.}other{Zobraziť # aplikácií pre počítač.}}" "%1$s%2$s" - "%1$s, %2$d. položka z %3$d" - "Posunúť doľava" - "Posunúť doprava" "Bublina" "Rozbaľovacia ponuka" "%1$s z aplikácie %2$s" "%1$s a ešte %2$d" - "Posunúť doľava" - "Posunúť doprava" - "Zavrieť všetko" - "rozbaliť %1$s" - "zbaliť %1$s" - "Vyhľadávanie krúžením" - "Ikona aplikácie" - "Názov aplikácie" - "Tlačidlo Zavrieť" - "Pripnúť na panel" - "Odopnúť z panela" diff --git a/quickstep/res/values-sl/strings.xml b/quickstep/res/values-sl/strings.xml index fd7dc69d44..52faeb7a9e 100644 --- a/quickstep/res/values-sl/strings.xml +++ b/quickstep/res/values-sl/strings.xml @@ -22,13 +22,11 @@ "Pripni" "Prosta oblika" "Namizni računalnik" - "Premik v zunanji zaslon" - "Zapri" - "Namizni način" "Ni nedavnih elementov" "Nastavitve uporabe aplikacij" "Počisti vse" "Nedavne aplikacije" + "Opravilo je zaprto" "%1$s, %2$s" "< 1 min" "Danes je ostalo še %1$s" @@ -47,7 +45,6 @@ "Predlogi aplikacij so omogočeni." "Predlogi aplikacij so onemogočeni." "Predvidena aplikacija: %1$s" - "Vadnica za krmarjenje s potezami" "Zasukajte napravo" "Zasukajte napravo, če si želite ogledati vadnico za krmarjenje s potezami" "Pazite, da povlečete s skrajno desnega ali skrajno levega roba." @@ -59,6 +56,7 @@ "Občutljivost poteze za nazaj lahko spremenite v nastavitvah." "Povlecite za vrnitev" "Če se želite vrniti na prejšnji zaslon, povlecite z levega ali desnega roba do sredine zaslona." + "Če se želite vrniti na zadnji prikazani zaslon, z dvema prstoma povlecite z levega ali desnega roba do sredine zaslona." "Pomik nazaj" "Povlecite z levega ali desnega roba do sredine zaslona." "Pazite, da povlečete s spodnjega roba zaslona navzgor." @@ -68,6 +66,7 @@ "Izvedli ste potezo za pomik na začetni zaslon." "Povlecite za pomik na začetni zaslon" "Z dna zaslona s prstom povlecite navzgor. S to potezo lahko vedno odprete začetni zaslon." + "Z dvema prstoma povlecite navzgor z dna zaslona. S to potezo lahko vedno odprete začetni zaslon." "Pomik na začetni zaslon" "Z dna zaslona s prstom povlecite navzgor." "Odlično!" @@ -78,6 +77,7 @@ "Izvedli ste potezo za preklapljanje med aplikacijami." "Povlecite za preklapljanje med aplikacijami" "Za preklapljanje med aplikacijami povlecite navzgor z dna zaslona, pridržite in nato izpustite." + "Za preklop med aplikacijami z dvema prstoma povlecite navzgor z dna zaslona, pridržite in spustite." "Preklop aplikacij" "Povlecite navzgor z dna zaslona, pridržite, nato izpustite." "Odlično!" @@ -99,7 +99,7 @@ "Shrani par aplikacij" "Za razdeljeni zaslon se dotaknite še 1 aplikacije" "Izberite drugo aplikacijo za uporabo razdeljenega zaslona." - "Prekliči" + "Prekliči" "Zapri izbiro razdeljenega zaslona" "Izberite drugo aplikacijo za uporabo razdeljenega zaslona." "Aplikacija ali vaša organizacija ne dovoljuje tega dejanja" @@ -130,37 +130,18 @@ "Hitre nastavitve" "Opravilna vrstica" "Opravilna vrstica je prikazana" - "Prikazani so opravilna vrstica in oblački na levi" - "Prikazani so opravilna vrstica in oblački na desni" + "Opravilna vrstica je skrita" "Vrstica za krmarjenje" "Stalen prikaz oprav. vrstice" "Spreminjanje načina navigacije" "Razdelilnik opravilne vrstice" - "Oblaček opravilne vrstice z dodatnimi elementi" "Premakni na vrh/levo" "Premakni na dno/desno" - "Odpri aplikacijo kot oblaček" - "Nedavne aplikacije" - "Seznam nedavnih aplikacij" - "{count,plural, =1{dodatna aplikacija}one{dodatna aplikacija}two{dodatni aplikaciji}few{dodatne aplikacije}other{dodatnih aplikacij}}" - "Namizni računalnik" + "{count,plural, =1{Pokaži še # aplikacijo.}one{Pokaži še # aplikacijo.}two{Pokaži še # aplikaciji.}few{Pokaži še # aplikacije.}other{Pokaži še # aplikacij.}}" + "{count,plural, =1{Prikaz # aplikacije za namizni računalnik.}one{Prikaz # aplikacije za namizni računalnik.}two{Prikaz # aplikacij za namizni računalnik.}few{Prikaz # aplikacij za namizni računalnik.}other{Prikaz # aplikacij za namizni računalnik.}}" "%1$s in %2$s" - "%1$s, element %2$d od %3$d" - "Pomik levo" - "Pomik desno" "Oblaček" "Oblaček z dodatnimi elementi" "%1$s iz aplikacije %2$s" "%1$s in še %2$d" - "Premik v levo" - "Premik v desno" - "Opusti vse" - "razširitev oblačka %1$s" - "strnitev oblačka %1$s" - "Iskanje z obkroževanjem" - "Ikona aplikacije" - "Ime aplikacije" - "Gumb za zapiranje" - "Pripni v opravilno vrstico" - "Odpni iz opravilne vrstice" diff --git a/quickstep/res/values-sq/strings.xml b/quickstep/res/values-sq/strings.xml index 3ebff3a74d..cdb9cf9a7c 100644 --- a/quickstep/res/values-sq/strings.xml +++ b/quickstep/res/values-sq/strings.xml @@ -22,13 +22,11 @@ "Gozhdo" "Formë e lirë" "Desktopi" - "Zhvendose tek ekrani i jashtëm" - "Mbyll" - "Desktopi" "Nuk ka asnjë artikull të fundit" "Cilësimet e përdorimit të aplikacionit" "Pastroji të gjitha" "Aplikacionet e fundit" + "Detyra u mbyll" "%1$s, %2$s" "< 1 minutë" "%1$s të mbetura sot" @@ -47,7 +45,6 @@ "Aplikacionet e sugjeruara janë aktivizuar" "Sugjerimet e aplikacioneve janë çaktivizuar" "Aplikacioni i parashikuar: %1$s" - "Udhëzuesi për navigimin me gjeste" "Rrotullo pajisjen" "Rrotullo pajisjen për të përfunduar udhëzuesin e navigimit me gjeste" "Sigurohu që të rrëshqasësh shpejt nga skaji më i djathtë ose më i majtë" @@ -59,6 +56,7 @@ "Për të ndryshuar ndjeshmërinë e gjestit të kthimit prapa, shko te \"Cilësimet\"" "Rrëshqit shpejt për t\'u kthyer prapa" "Për t\'u kthyer prapa tek ekrani i fundit, rrëshqit shpejt nga skaji i majtë ose i djathtë drejt mesit të ekranit" + "Për t\'u kthyer prapa tek ekrani i fundit, rrëshqit shpejt me 2 gishta nga skaji i majtë ose i djathtë drejt mesit të ekranit." "Kthehu prapa" "Rrëshqit shpejt nga skaji i majtë ose i djathtë drejt mesit të ekranit" "Sigurohu që të rrëshqasësh shpejt lart nga skaji i poshtëm i ekranit" @@ -68,6 +66,7 @@ "E ke përfunduar gjestin e kalimit tek ekrani bazë" "Rrëshqit shpejt për të kaluar tek ekrani bazë" "Rrëshqit shpejt lart nga fundi i ekranit tënd. Ky gjest të dërgon gjithmonë tek ekrani bazë." + "Rrëshqit shpejt lart me 2 gishta nga fundi i ekranit. Ky gjest të dërgon gjithmonë tek ekrani bazë." "Shko tek ekrani bazë" "Rrëshqit shpejt lart nga pjesa e poshtme e ekranit" "Punë e shkëlqyer!" @@ -78,6 +77,7 @@ "E ke përfunduar gjestin e ndërrimit të aplikacioneve" "Rrëshqit shpejt për të ndërruar aplikacionet" "Për të ndërruar mes aplikacioneve, rrëshqit shpejt lart nga fundi i ekranit tënd, mbaj dhe pastaj lësho." + "Për të ndërruar mes aplikacioneve, rrëshqit lart me 2 gishta nga fundi i ekranit, mbaje dhe lëshoje." "Ndërro aplikacionet" "Rrëshqit shpejt lart nga fundi i ekranit, mbaje të shtypur dhe më pas lëshoje" "Shumë mirë!" @@ -99,7 +99,7 @@ "Ruaj çiftin e aplikacioneve" "Trokit një apl. tjetër; përdor ekranin e ndarë" "Zgjidh një aplikacion tjetër për të përdorur ekranin e ndarë" - "Anulo" + "Anulo" "Dil nga zgjedhja e ekranit të ndarë" "Zgjidh një aplikacion tjetër për të përdorur ekranin e ndarë" "Ky veprim nuk lejohet nga aplikacioni ose organizata jote" @@ -130,37 +130,18 @@ "Cilësimet shpejt" "Shiriti i detyrave" "Shiriti i detyrave i shfaqur" - "Shiriti i detyrave dhe flluskat majtas janë shfaqur" - "Shiriti i detyrave dhe flluskat djathtas janë shfaqur" + "Shiriti i detyrave i fshehur" "Shiriti i navigimit" "Shfaq gjithmonë shiritin e detyrave" "Ndrysho modalitetin e navigimit" "Ndarësi i shiritit të detyrave" - "Tejkalimi i shiritit të detyrave" "Lëviz në krye/majtas" "Lëviz në fund/djathtas" - "Hap aplikacionin si një flluskë" - "Aplikacionet e fundit" - "Lista e aplikacioneve të fundit" - "{count,plural, =1{aplikacion tjetër}other{aplikacione të tjera}}" - "Desktop" + "{count,plural, =1{Shfaq # aplikacion tjetër.}other{Shfaq # aplikacione të tjera.}}" + "{count,plural, =1{Shfaq # aplikacion për desktop.}other{Shfaq # aplikacione për desktop.}}" "%1$s dhe %2$s" - "%1$s, artikulli %2$d nga %3$d" - "Lëviz majtas" - "Lëviz djathtas" "Flluska" "Tejkalimi" "\"%1$s\" nga %2$s" "\"%1$s\" dhe %2$d të tjera" - "Lëviz majtas" - "Lëviz djathtas" - "Hiqi të gjitha" - "zgjero %1$s" - "palos %1$s" - "Qarko për të kërkuar" - "Ikona e aplikacionit" - "Titulli i aplikacionit" - "Butoni i mbylljes" - "Gozhdo te shiriti" - "Zhgozhdo nga shiriti" diff --git a/quickstep/res/values-sr/strings.xml b/quickstep/res/values-sr/strings.xml index 6742da9967..7456a36b7e 100644 --- a/quickstep/res/values-sr/strings.xml +++ b/quickstep/res/values-sr/strings.xml @@ -22,13 +22,11 @@ "Закачи" "Слободни облик" "Рачунар" - "Преместите на спољни екран" - "Затвори" - "Рачунари" "Нема недавних ставки" "Подешавања коришћења апликације" "Обриши све" "Недавне апликације" + "Задатак је затворен" "%1$s, %2$s" "< 1 мин" "Још %1$s данас" @@ -47,18 +45,18 @@ "Предлози апликација су омогућени" "Предлози апликација су онемогућени" "Предвиђамо апликацију: %1$s" - "Водич за навигацију помоћу покрета" "Ротирајте уређај" "Ротирајте уређај да бисте довршили водич за навигацију помоћу покрета" "Обавезно превуците од саме десне или леве ивице" "Обавезно превуците од десне или леве ивице до средине екрана и отпустите" "Научили сте како да превлачите здесна да бисте се вратили уназад. Сада научите да замените апликације." "Довршили сте покрет за повратак. Сада сазнајте како да промените апликације." - "Довршили сте покрет за назад" + "Довршили сте покрет за повратак" "Никако не превлачите превише близу дна екрана" "Осетљивост пок. за назад можете да промените у Подешавањима" "Превуците да бисте се вратили уназад" "Да бисте се вратили на последњи екран, превуците од леве или десне ивице до средине екрана." + "Да бисте се вратили на последњи екран, превуците помоћу два прста од леве или десне ивице до средине екрана." "Назад" "Превуците од леве или десне ивице до средине екрана" "Обавезно превуците нагоре од доње ивице екрана" @@ -68,7 +66,8 @@ "Довршили сте покрет за повратак на почетну страницу." "Превуците да бисте отишли на почетну страницу" "Превуците нагоре од дна екрана. Овај покрет вас увек води на почетни екран." - "На почетни екран" + "Превуците помоћу два прста нагоре од дна екрана. Овим покретом увек отварате почетни екран." + "Идите на почетни екран" "Превуците нагоре са доњег дела екрана" "Одлично!" "Обавезно превуците нагоре од доње ивице екрана" @@ -78,7 +77,8 @@ "Довршили сте покрет за промену апликација" "Превуците да бисте заменили апликације" "За прелазак са једне апликације на другу превуците нагоре од дна екрана, задржите, па пустите." - "На другу апликацију" + "За прелазак између апликација превуците помоћу два прста нагоре од дна екрана, задржите, па пустите." + "Пређите на другу апликацију" "Превуците нагоре од дна екрана, задржите, па пустите" "Одлично!" "То је то" @@ -89,7 +89,7 @@ "Водич %1$d/%2$d" "Готово!" "Превуците нагоре да бисте отворили почетни екран" - "Додирните дугме Почетак да бисте отишли на почетни екран" + "Додирните дугме Почетак да бисти ишли на почетни екран" "Спремни сте да почнете да користите %1$s" "уређај" "Подешавања кретања кроз систем" @@ -99,7 +99,7 @@ "Сачувај пар апликација" "Додирните другу апликацију за подељени екран" "Одаберите другу апликацију да бисте користили подељени екран" - "Откажи" + "Откажи" "Излазак из бирања подељеног екрана" "Одаберите другу апликацију за подељени екран" "Апликација или организација не дозвољавају ову радњу" @@ -130,37 +130,18 @@ "Брза подешавања" "Трака задатака" "Трака задатака је приказана" - "Приказ задатака/облачића лево" - "Приказ задатака/облачића десно" + "Трака задатака је скривена" "Трака за навигацију" "Увек приказуј траку задатака" "Промени режим навигације" "Разделник траке задатака" - "Преклопна трака задатака" "Премести горе лево" "Премести доле десно" - "Отвори апликацију као облачић" - "Недавне апликације" - "Листа недавних апликација" - "{count,plural, =1{додатна апликација}one{додатна апликација}few{додатне апликације}other{додатних апликација}}" - "Рачунар" + "{count,plural, =1{Прикажи још # апликацију.}one{Прикажи још # апликацију.}few{Прикажи још # апликације.}other{Прикажи још # апликација.}}" + "{count,plural, =1{Прикажи # апликацију за рачунаре.}one{Прикажи # апликацију за рачунаре.}few{Прикажи # апликације за рачунаре.}other{Прикажи # апликација за рачунаре.}}" "%1$s и %2$s" - "%1$s, ставка %2$d од %3$d" - "Скролујте улево" - "Скролујте удесно" "Облачић" "Преклопни" "%1$s%2$s" "%1$s и још %2$d" - "Помери налево" - "Помери надесно" - "Одбаци све" - "проширите облачић %1$s" - "скупите облачић %1$s" - "Претрага заокруживањем" - "Икона апликације" - "Назив апликације" - "Дугме Затвори" - "Закачи за траку зад." - "Откачи са траке зад." diff --git a/quickstep/res/values-sv/strings.xml b/quickstep/res/values-sv/strings.xml index c995fd0521..f369daeb33 100644 --- a/quickstep/res/values-sv/strings.xml +++ b/quickstep/res/values-sv/strings.xml @@ -21,14 +21,12 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Fäst" "Fritt format" - "Skrivbordsläge" - "Flytta till extern skärm" - "Stäng" - "Skrivbordsläge" + "Dator" "Listan är tom" "Inställningar för appanvändning" "Rensa alla" "Senaste apparna" + "Uppgiften har stängts" "%1$s, %2$s" "< 1 min" "%1$s kvar i dag" @@ -47,7 +45,6 @@ "Appförslag har aktiverats" "Appförslag har inaktiverats" "Appförslag: %1$s" - "Guide för navigering med rörelser" "Rotera enheten" "Rotera enheten för att slutföra guiden för navigering med rörelser" "Se till att du sveper ända från högerkanten eller vänsterkanten" @@ -59,6 +56,7 @@ "Öppna inställningarna om du vill ändra rörelsens känslighet" "Svep för att återgå" "Återgå till den senaste skärmen genom att svepa från skärmens vänster- eller högerkant till mitten." + "Gå tillbaka till den senaste skärmen genom att med två fingrar svepa mot mitten av skärmen från vänster eller höger kant." "Tillbaka" "Svep från den högra eller vänstra kanten till mitten av skärmen" "Se till att du sveper uppåt från nederkanten av skärmen" @@ -68,6 +66,7 @@ "Du är klar med rörelsen för att öppna startskärmen" "Svep för att öppna startskärmen" "Svep uppåt från skärmens nederkant. Du kan alltid återgå till startskärmen med den här rörelsen." + "Svep uppåt med två fingrar från skärmens nederkant. Så kommer du alltid tillbaka till startskärmen." "Öppna startskärmen" "Svep uppåt från skärmens nederkant" "Bra jobbat!" @@ -78,6 +77,7 @@ "Du är klar med rörelsen för att byta mellan appar" "Svep för att byta mellan appar" "Byt mellan appar genom att svepa uppåt från skärmens nederkant. Håll fingret nedtryckt och släpp." + "Byta mellan appar: Svep uppåt med två fingrar från skärmens nederkant, håll kvar och släpp sedan." "Byt app" "Svep uppåt från skärmens nederkant. Håll fingret nedtryckt och släpp sedan" "Bra gjort!" @@ -99,7 +99,7 @@ "Spara app-par" "Tryck på en annan app för att använda delad skärm" "Välj en annan app för att använda delad skärm" - "Avbryt" + "Avbryt" "Avsluta val av delad skärm" "Välj en annan app för att använda delad skärm" "Appen eller organisationen tillåter inte den här åtgärden" @@ -130,37 +130,18 @@ "Snabbinställn." "Aktivitetsfält" "Aktivitetsfältet visas" - "Vänster fält och bubblor visas" - "Höger fält och bubblor visas" + "Aktivitetsfältet är dolt" "Navigeringsfält" "Visa alltid aktivitetsfältet" "Ändra navigeringsläge" "Avdelare för aktivitetsfältet" - "Fler alternativ för aktivitetsfältet" "Flytta högst upp/till vänster" "Flytta längst ned/till höger" - "Öppna appen som en bubbla" - "Senaste apparna" - "Lista över senaste appar" - "{count,plural, =1{app till}other{appar till}}" - "Skrivbordsläge" + "{count,plural, =1{Visa # app till.}other{Visa # appar till.}}" + "{count,plural, =1{Visa # datorapp.}other{Visa # datorappar.}}" "%1$s och %2$s" - "%1$s, objekt %2$d av %3$d" - "Scrolla åt vänster" - "Scrolla åt höger" "Bubbla" "Fler alternativ" "%1$s från %2$s" "%1$s och %2$d till" - "Flytta åt vänster" - "Flytta åt höger" - "Stäng alla" - "utöka %1$s" - "komprimera %1$s" - "Circle to Search" - "Appikon" - "Apptitel" - "Knappen Stäng" - "Fäst i aktivitetsfält" - "Lossa från aktivitetsfält" diff --git a/quickstep/res/values-sw/strings.xml b/quickstep/res/values-sw/strings.xml index 046e37a01f..3d8277b448 100644 --- a/quickstep/res/values-sw/strings.xml +++ b/quickstep/res/values-sw/strings.xml @@ -22,13 +22,11 @@ "Bandika" "Muundo huru" "Kompyuta ya mezani" - "Hamishia programu kwenye skrini ya nje" - "Funga" - "Kompyuta ya Mezani" "Hakuna vipengee vya hivi karibuni" "Mipangilio ya matumizi ya programu" "Ondoa zote" "Programu za hivi karibuni" + "Jukumu Limefungwa" "%1$s, %2$s" "< dak 1" "Umebakisha %1$s leo" @@ -47,27 +45,28 @@ "Mapendekezo ya programu yamewashwa" "Umezima mapendekezo ya programu" "Programu iliyotabiriwa: %1$s" - "Mafunzo ya Usogezaji kwa Kutumia Miguso" "Zungusha kifaa chako" - "Tafadhali zungusha kifaa chako ili ukamilishe mafunzo ya usogezaji kwa kutumia miguso" + "Tafadhali zungusha kifaa chako ili ukamilishe mafunzo ya usogezaji kwa kutumia ishara" "Hakikisha unatelezesha kidole kutoka ukingo wa kulia au kushoto kabisa" "Hakikisha unatelezesha kidole kutoka ukingo wa kulia au kushoto hadi katikati ya skrini na uachilie" "Umejifunza jinsi ya kutelezesha kidole kuanzia kulia ili kurudi nyuma. Sasa jifunze jinsi ya kubadilisha programu." - "Umekamilisha mafunzo ya miguso ya kurudi nyuma. Hatua inayofuata, fahamu jinsi ya kubadilisha programu." - "Umekamilisha mafunzo ya miguso ya kurudi nyuma" + "Umekamilisha ishara ya kurudi nyuma. Hatua inayofuata, jifunze jinsi ya kubadilisha programu." + "Umeweka ishara ya kurudi nyuma" "Hakikisha hutelezeshi kidole karibu sana na sehemu ya chini ya skrini" "Kubadilisha hisi ya ishara ya nyuma, nenda kwenye Mipangilio" "Telezesha kidole ili urudi nyuma" "Ili urudi kwenye skrini iliyotangulia, telezesha kidole kuanzia ukingo wa kushoto au wa kulia kuelekea katikati ya skrini." + "Ili urudi kwenye skrini iliyopita, telezesha vidole viwili kuanzia ukingo wa kushoto au wa kulia kuelekea katikati ya skrini." "Rudi nyuma" "Telezesha kidole kutoka ukingo wa kushoto au kulia hadi katikati ya skrini" "Hakikisha unatelezesha kidole juu kuanzia ukingo wa chini wa skrini" "Hakikisha husitishi kabla ya kuachilia" "Hakikisha unatelezesha kidole juu" - "Umekamilisha mguso wa kwenda kwenye skrini ya kwanza. Inayofuata, fahamu jinsi ya kurudi nyuma." - "Umekamilisha mguso wa kwenda kwenye skrini ya kwanza" + "Umeweka ishara ya kwenda kwenye Skrini ya kwanza. Inayofuata, jifunze jinsi ya kurudi nyuma." + "Umeweka ishara ya kwenda kwenye skrini ya kwanza" "Telezesha kidole ili uende kwenye skrini ya kwanza" "Telezesha kidole juu kuanzia chini ya skrini yako. Ishara hii kila wakati hukupeleka kwenye Skrini ya kwanza." + "Telezesha vidole viwili kuelekea juu kuanzia sehemu ya chini ya skrini. Ishara hii kila wakati hukupeleka kwenye Skrini ya kwanza." "Nenda kwenye ukurasa wa mwanzo" "Telezesha kidole juu kutoka sehemu ya chini ya skrini yako" "Kazi nzuri!" @@ -75,9 +74,10 @@ "Jaribu kushikilia dirisha kwa muda mrefu kabla ya kuachilia" "Hakikisha unatelezesha kidole juu, kisha usitishe" "Umejifunza jinsi ya kutumia ishara. Ili uzime ishara, nenda kwenye Mipangilio." - "Umekamilisha mguso wa kubadilisha programu" + "Umeweka ishara ya kubadilisha programu" "Telezesha kidole ili ubadilishe programu" "Ili ubadili kati ya programu, telezesha kidole juu kuanzia sehemu ya chini ya skrini yako, ushikilie, kisha uachilie." + "Ili ubadilishe kati ya programu, telezesha vidole viwili kuelekea juu kuanzia sehemu ya chini ya skrini yako, ushikilie, kisha uachilie." "Badilisha programu" "Telezesha kidole juu kutoka sehemu ya chini ya skrini yako, shikilia kisha uachilie" "Hongera!" @@ -99,7 +99,7 @@ "Hifadhi jozi ya programu" "Gusa programu nyingine ili utumie kipengele cha kugawa skrini" "Chagua programu nyingine ili utumie hali ya kugawa skrini" - "Acha" + "Ghairi" "Ondoka kwenye hali ya skrini iliyogawanywa" "Chagua programu nyingine ili utumie hali ya kugawa skrini" "Kitendo hiki hakiruhusiwi na programu au shirika lako" @@ -130,37 +130,18 @@ "Mipangilio ya Haraka" "Upauzana" "Upauzana umeonyeshwa" - "Upauzana na viputo vinaonyeshwa kushoto" - "Upauzana na viputo vinaonyeshwa kulia" + "Upauzana umefichwa" "Sehemu ya viungo muhimu" "Onyesha Zana kila wakati" "Badilisha hali ya usogezaji" "Kitenganishi cha Upauzana" - "Upauzana wa Vipengele vya Ziada" "Sogeza juu/kushoto" "Sogeza chini/kulia" - "Fungua programu kama kiputo" - "Programu ulizofungua hivi majuzi" - "Orodha ya programu ulizofungua hivi majuzi" - "{count,plural, =1{programu nyingine}other{programu zingine}}" - "Kompyuta ya Mezani" + "{count,plural, =1{Onyesha programu # zaidi.}other{Onyesha programu # zaidi.}}" + "{count,plural, =1{Onyesha programu # ya kompyuta ya mezani.}other{Onyesha programu # za kompyuta ya mezani.}}" "%1$s na %2$s" - "%1$s, kipengee cha %2$d kati ya %3$d" - "Sogeza kushoto" - "Sogeza kulia" "Kiputo" "Kiputo cha vipengee vya ziada" "%1$s kutoka %2$s" "%1$s na vingine %2$d" - "Sogeza kushoto" - "Sogeza kulia" - "Ondoa vyote" - "panua %1$s" - "kunja %1$s" - "Chora Mviringo ili Kutafuta" - "Aikoni ya programu" - "Kichwa cha programu" - "Kitufe cha kufunga" - "Bandika kwa upauzana" - "Bandua kwa upauzana" diff --git a/quickstep/res/values-sw600dp-land/dimens.xml b/quickstep/res/values-sw600dp-land/dimens.xml index cf7ba0094b..5e9a177b8b 100644 --- a/quickstep/res/values-sw600dp-land/dimens.xml +++ b/quickstep/res/values-sw600dp-land/dimens.xml @@ -16,7 +16,7 @@ --> - 48dp + 48dp 48dp @@ -27,6 +27,10 @@ 40dp 49dp - 24dp + + + 24dp + + 40dp diff --git a/quickstep/res/values-sw600dp/dimens.xml b/quickstep/res/values-sw600dp/dimens.xml index 3e726519ac..e24d8fea79 100644 --- a/quickstep/res/values-sw600dp/dimens.xml +++ b/quickstep/res/values-sw600dp/dimens.xml @@ -33,11 +33,20 @@ 36dp 64dp + + 80dp + + 80dp 24dp - 120dp + 120dp 38sp 15sp + + + + 300dp + diff --git a/quickstep/res/values-ta/strings.xml b/quickstep/res/values-ta/strings.xml index 9074f6c9c3..47d8055e16 100644 --- a/quickstep/res/values-ta/strings.xml +++ b/quickstep/res/values-ta/strings.xml @@ -22,13 +22,11 @@ "பின் செய்தல்" "குறிப்பிட்ட வடிவமில்லாத பயன்முறை" "டெஸ்க்டாப்" - "வெளிப்புற டிஸ்ப்ளேவிற்கு நகர்த்துதல்" - "மூடு" - "டெஸ்க்டாப்" "சமீபத்தியவை எதுவுமில்லை" "ஆப்ஸ் உபயோக அமைப்புகள்" "எல்லாம் அழி" "சமீபத்திய ஆப்ஸ்" + "பணி முடிந்தது" "%1$s, %2$s" "< 1 நி" "இன்று %1$s மீதமுள்ளது" @@ -47,7 +45,6 @@ "ஆப்ஸ் பரிந்துரைகள் இயக்கப்பட்டுள்ளன" "ஆப்ஸ் பரிந்துரைகள் முடக்கப்பட்டுள்ளன" "கணித்த ஆப்ஸ்: %1$s" - "சைகை வழிசெலுத்தலுக்கான பயிற்சி" "உங்கள் சாதனத்தைச் சுழற்றுங்கள்" "சைகை வழிசெலுத்தல் பயிற்சியை நிறைவுசெய்ய உங்கள் சாதனத்தைச் சுழற்றுங்கள்" "வலது அல்லது இடது ஓரத்தின் விளிம்பிலிருந்து ஸ்வைப் செய்வதை உறுதிசெய்துகொள்ளுங்கள்" @@ -59,6 +56,7 @@ "பின்செல் சைகையின் உணர்திறனை மாற்ற அமைப்புகளுக்குச் செல்க" "பின்செல்ல ஸ்வைப் செய்யுங்கள்" "முந்தைய திரைக்கு மீண்டும் செல்ல, இடது/வலது ஓரத்திலிருந்து திரையின் மையப் பகுதிக்கு ஸ்வைப் செய்க." + "முந்தைய திரைக்கு மீண்டும் செல்ல, 2 விரல்களால் இடது அல்லது வலது ஓரத்திலிருந்து திரையின் மையப் பகுதிக்கு ஸ்வைப் செய்யுங்கள்." "பின்செல்லுதல்" "வலது அல்லது இடது ஓரத்திலிருந்து திரையின் மையப் பகுதிக்கு ஸ்வைப் செய்யுங்கள்" "திரையின் கீழ் ஓரத்திலிருந்து மேல்நோக்கி ஸ்வைப் செய்வதை உறுதிசெய்துகொள்ளுங்கள்" @@ -68,16 +66,18 @@ "முகப்புக்குச் செல் சைகைப் பயிற்சியை நிறைவுசெய்துவிட்டீர்கள்" "முகப்புக்குச் செல்ல ஸ்வைப் செய்யுங்கள்" "திரையின் கீழிருந்து மேலாக ஸ்வைப் செய்க. இந்தச் சைகை எப்போதும் முகப்புத் திரைக்கு அழைத்துச் செல்லும்." + "2 விரலால் திரையின் கீழிருந்து மேலாக ஸ்வைப் செய்க. இந்தச் சைகை முகப்புத் திரைக்கு அழைத்துச் செல்லும்." "முகப்புக்குச் செல்லுதல்" "திரையின் கீழிருந்து மேல்நோக்கி ஸ்வைப் செய்யுங்கள்" "அருமை!" "திரையின் கீழ் ஓரத்திலிருந்து மேல்நோக்கி ஸ்வைப் செய்வதை உறுதிசெய்துகொள்ளுங்கள்" "விடுவிப்பதற்கு முன்பாக நீண்டநேரம் சாளரத்தை அழுத்திப் பிடித்திருங்கள்" - "மேல்நோக்கி நேராக ஸ்வைப் செய்தபிறகு சற்றுநேரம் அழுத்திபடியே வைத்திருங்கள்" + "மேல்நோக்கி நேராக ஸ்வைப் செய்தபிறகு இடைநிறுத்துவதை உறுதிசெய்துகொள்ளுங்கள்" "சைகைகளை எப்படி உபயோகிப்பது என்று கற்றுக்கொண்டீர்கள். சைகைகளை முடக்க அமைப்புகளுக்குச் செல்லுங்கள்." "ஆப்ஸுக்கிடையே மாறும் சைகைப் பயிற்சியை நிறைவுசெய்துவிட்டீர்கள்" "ஆப்ஸுக்கிடையே மாற ஸ்வைப் செய்யுங்கள்" "ஆப்ஸுக்கு இடையே மாற, திரையின் கீழிலிருந்து மேலாக ஸ்வைப் செய்து, பிடித்திருந்து, பிறகு விடுவிக்கவும்." + "ஆப்ஸுக்கிடையே மாற, திரையின் கீழிருந்து மேலாக 2 விரலால் ஸ்வைப் செய்து, பிடித்து, பிறகு விடுவிக்கவும்." "ஆப்ஸுக்கிடையே மாறுதல்" "உங்கள் திரையின் கீழ்ப்பகுதியில் இருந்து மேலே ஸ்வைப் செய்து, பிடித்து, பிறகு விடுவியுங்கள்" "அருமை!" @@ -90,7 +90,7 @@ "அனைத்தையும் அமைத்துவிட்டீர்கள்!" "முகப்புக்குச் செல்ல மேல்நோக்கி ஸ்வைப் செய்யுங்கள்" "முகப்புத் திரைக்குச் செல்வதற்கு முகப்பு பட்டனைத் தட்டவும்" - "உங்கள் %1$s உங்களுக்காகத் தயாராக இருக்கிறது" + "உங்கள் %1$s சாதனத்தைப் பயன்படுத்தத் தயாராகிவிட்டீர்கள்" "சாதனம்" "சிஸ்டம் வழிசெலுத்தல் அமைப்புகள்" "பகிர்" @@ -99,7 +99,7 @@ "ஆப்ஸ் ஜோடியைச் சேமி" "திரைப் பிரிப்பைப் பயன்படுத்த வேறு ஆப்ஸைத் தட்டவும்" "திரைப் பிரிப்பைப் பயன்படுத்த வேறு ஆப்ஸைத் தேர்வுசெய்யுங்கள்" - "ரத்துசெய்" + "ரத்துசெய்" "திரைப் பிரிப்பு தேர்வில் இருந்து வெளியேறும்" "திரைப் பிரிப்பை பயன்படுத்த வேறு ஆப்ஸை தேர்வுசெய்க" "ஆப்ஸோ உங்கள் நிறுவனமோ இந்த செயலை அனுமதிப்பதில்லை" @@ -130,37 +130,18 @@ "விரைவு அமைப்புகள்" "செயல் பட்டி" "செயல் பட்டி காட்டப்படுகிறது" - "செயல் பட்டி & குமிழை இடதுபுறம் காட்டும்" - "செயல் பட்டி & குமிழை வலதுபுறம் காட்டும்" + "செயல் பட்டி மறைக்கப்பட்டுள்ளது" "வழிசெலுத்தல் பட்டி" "செயல் பட்டியை எப்போதும் காட்டு" "வழிசெலுத்தல் பயன்முறையை மாற்று" "செயல் பட்டிப் பிரிப்பான்" - "செயல் பட்டிக்கான கூடுதல் விருப்பங்கள்" "மேலே/இடதுபுறம் நகர்த்தும்" "கீழே/வலதுபுறம் நகர்த்தும்" - "ஆப்ஸைக் குமிழாகத் திற" - "சமீபத்திய ஆப்ஸ்" - "சமீபத்திய ஆப்ஸ் பட்டியல்" - "{count,plural, =1{கூடுதல் ஆப்ஸ்}other{கூடுதல் ஆப்ஸ்}}" - "டெஸ்க்டாப்" + "{count,plural, =1{மேலும் # ஆப்ஸைக் காட்டு.}other{மேலும் # ஆப்ஸைக் காட்டு.}}" + "{count,plural, =1{# டெஸ்க்டாப் ஆப்ஸைக் காட்டு.}other{# டெஸ்க்டாப் ஆப்ஸைக் காட்டு.}}" "%1$s மற்றும் %2$s" - "%1$s, %3$d இல் %2$d கட்டம்" - "இடதுபுறம் நகர்த்தும்" - "வலதுபுறம் நகர்த்தும்" "குமிழ்" "கூடுதல் விருப்பங்களைக் காட்டும்" "%2$s வழங்கும் %1$s" "%1$s மற்றும் %2$d" - "இடதுபுறம் நகர்த்தும்" - "வலதுபுறம் நகர்த்தும்" - "அனைத்தையும் மூடும்" - "%1$s ஐ விரிவாக்கும்" - "%1$s ஐச் சுருக்கும்" - "வட்டமிட்டுத் தேடல்" - "ஆப்ஸ் ஐகான்" - "ஆப்ஸ் தலைப்பு" - "மூடுவதற்கான பட்டன்" - "செயல்பட்டியில் பின் செய்" - "செயல்பட்டியிலிருந்து அகற்று" diff --git a/quickstep/res/values-te/strings.xml b/quickstep/res/values-te/strings.xml index 28852236c9..a4e1cbf09c 100644 --- a/quickstep/res/values-te/strings.xml +++ b/quickstep/res/values-te/strings.xml @@ -22,13 +22,11 @@ "పిన్ చేయండి" "సంప్రదాయేతర" "డెస్క్‌టాప్" - "ఎక్స్‌టర్నల్ డిస్‌ప్లేకు తరలించండి" - "మూసివేయండి" - "డెస్క్‌టాప్" "ఇటీవలి ఐటెమ్‌లు ఏవీ లేవు" "యాప్ వినియోగ సెట్టింగ్‌లు" "అన్నీ తీసివేయండి" "ఇటీవలి యాప్‌లు" + "టాస్క్ మూసివేయబడింది" "%1$s, %2$s" "< 1 నిమిషం" "నేటికి %1$s మిగిలి ఉంది" @@ -47,7 +45,6 @@ "యాప్ సలహాలు ఎనేబుల్ చేయబడ్డాయి" "యాప్ సూచ‌న‌లు డిజేబుల్‌ చేయబడ్డాయి" "సూచించబడిన యాప్: %1$s" - "సంజ్ఞ నావిగేషన్ ట్యుటోరియల్" "మీ పరికరాన్ని రొటేట్ చేయండి" "సంజ్ఞ నావిగేషన్ ట్యుటోరియల్‌ను పూర్తి చేయడానికి దయచేసి మీ పరికరాన్ని రొటేట్ చేయండి" "కుడి వైపు చిట్ట చివరి లేదా ఎడమ వైపు చిట్ట చివరి అంచు నుండి స్వైప్ చేస్తున్నారని నిర్ధారించుకోండి" @@ -59,6 +56,7 @@ "వెనుక సంజ్ఞ సున్నితత్వం మార్చడానికి, సెట్టింగ్‌లకు వెళ్లండి" "వెనుకకు వెళ్ళడం కోసం స్వైప్ చేయండి" "మునుపటి స్క్రీన్‌కు తిరిగి వెళ్లడానికి, ఎడమ లేదా కుడి అంచు నుండి స్క్రీన్ మధ్యలోకి స్వయిప్ చేయండి." + "గత స్క్రీన్‌కు తిరిగి వెళ్లడానికి, ఎడమ లేదా కుడి అంచు నుండి స్క్రీన్ మధ్యలోకి 2 వేళ్లతో స్వైప్ చేయండి." "వెనుకకు వెళ్లండి" "ఎడమ లేదా కుడి అంచు నుండి స్క్రీన్ మధ్యకు స్వైప్ చేయండి" "మీరు స్క్రీన్ దిగువ అంచు నుండి పైకి స్వైప్ చేశారని నిర్ధారించుకోండి" @@ -68,6 +66,7 @@ "మీరు మొదటి స్క్రీన్‌కు వెళ్లే సంజ్ఞను పూర్తి చేశారు" "మొదటి స్క్రీన్‌కు వెళ్లడానికి స్వైప్ చేయండి" "స్క్రీన్ కింది నుండి పైకి స్వైప్ చేయండి. ఈ సంజ్ఞ ఎప్పుడూ మిమ్మల్ని మొదటి స్క్రీన్‌కు తీసుకెళ్తుంది." + "స్క్రీన్ కింది నుండి 2 వేళ్లతో పైకి స్వైప్ చేయండి. సంజ్ఞ ఎల్లప్పుడూ మొదటి స్క్రీన్‌కు తీసుకెళ్తుంది." "మొదటి ట్యాబ్‌కు వెళ్లండి" "స్క్రీన్ కింది భాగం నుండి పైకి స్వైప్ చేయండి" "బాగా చేశారు!" @@ -75,9 +74,10 @@ "వేలిని రిలీజ్ చేయడానికి ముందు విండోను ఎక్కువసేపు నొక్కి, పట్టుకోవడానికి ట్రై చేయండి" "స్క్రీన్‌పై నేరుగా పైకి స్వైప్ చేసి, ఆపై పాజ్ చేయండి" "మీరు సంజ్ఞలను ఎలా ఉపయోగించాలో నేర్చుకున్నారు. సంజ్ఞలను ఆఫ్ చేయడానికి, సెట్టింగ్‌లకు వెళ్లండి." - "మీరు \'యాప్‌ల మధ్య మారేందుకు సంజ్ఞ\' ట్యుటోరియల్‌ను పూర్తి చేశారు" + "మీరు \'యాప్‌ల మధ్య మార్పు\' సంజ్ఞను పూర్తి చేశారు" "యాప్‌ల మధ్య మార్చడం కోసం స్వైప్ చేయండి" "యాప్‌ల మధ్య మారడానికి, మీ స్క్రీన్ కింది వైపు నుండి పైకి స్వైప్ చేసి, పట్టుకుని, తర్వాత వదలండి." + "యాప్‌ల మధ్య మారడానికి, మీ స్క్రీన్ కింది నుండి 2 వేళ్లతో పైకి స్వైప్ చేసి, నొక్కి పట్టి, వదలండి." "యాప్‌ల మధ్య స్విచ్ అవ్వండి" "మీ స్క్రీన్ కింది వైపు నుండి పైకి స్వైప్ చేసి, పట్టుకుని, తర్వాత వదలండి" "చాలా బాగా చేశారు!" @@ -99,7 +99,7 @@ "యాప్ పెయిర్‌ను సేవ్ చేయండి" "స్ప్లిట్ స్క్రీన్ కోసం మరొక యాప్‌ను ట్యాప్ చేయండి" "స్ప్లిట్ స్క్రీన్‌ను ఉపయోగించడానికి మరొక యాప్ ఎంచుకోండి" - "రద్దు చేయండి" + "రద్దు చేయండి" "స్ప్లిట్ స్క్రీన్ ఎంపిక నుండి ఎగ్జిట్ అవ్వండి" "స్ప్లిట్ స్క్రీన్ ఉపయోగానికి మరొక యాప్ ఎంచుకోండి" "ఈ చర్యను యాప్ గానీ, మీ సంస్థ గానీ అనుమతించవు" @@ -130,37 +130,18 @@ "క్విక్ సెట్టింగ్‌లు" "టాస్క్‌బార్" "టాస్క్‌బార్ చూపబడింది" - "టాస్క్‌బార్, బబుల్స్ ఎడమవైపున చూపబడ్డాయి" - "టాస్క్‌బార్, బబుల్స్ కుడివైపున చూపబడ్డాయి" + "టాస్క్‌బార్ దాచబడింది" "నావిగేషన్ బార్" "టాస్క్‌బార్‌ను నిరంతరం చూపండి" "నావిగేషన్ మోడ్‌ను మార్చండి" "టాస్క్‌బార్ డివైడర్" - "టాస్క్‌బార్ ఓవర్‌ఫ్లో" "ఎగువ/ఎడమ వైపునకు తరలించండి" "దిగువ/కుడి వైపునకు తరలించండి" - "యాప్‌ను బబుల్‌లాగా తెరవండి" - "ఇటీవలి యాప్‌లు" - "ఇటీవలి యాప్ లిస్ట్" - "{count,plural, =1{మరో యాప్‌}other{మరిన్ని యాప్‌లు}}" - "డెస్క్‌టాప్" + "{count,plural, =1{మరో # యాప్‌ను చూడండి.}other{మరో # యాప్‌లను చూడండి.}}" + "{count,plural, =1{# డెస్క్‌టాప్ యాప్‌ను చూపండి.}other{# డెస్క్‌టాప్ యాప్‌లను చూపండి.}}" "%1$s, %2$s" - "%1$s, %3$d‌లో %2$d‌వ ఐటెమ్" - "ఎడమవైపునకు స్క్రోల్ చేయండి" - "కుడివైపునకు స్క్రోల్ చేయండి" "బబుల్" "ఓవర్‌ఫ్లో" "%2$s నుండి %1$s" "%1$s, మరో %2$d" - "ఎడమ వైపుగా జరపండి" - "కుడి వైపుగా జరపండి" - "అన్నింటినీ విస్మరించండి" - "%1$sను విస్తరించండి" - "%1$sను కుదించండి" - "సెర్చ్ చేయడానికి సర్కిల్ గీయండి" - "యాప్ చిహ్నం" - "యాప్ టైటిల్" - "\'మూసివేయండి\' బటన్" - "టాస్క్‌బార్‌కు పిన్" - "టాస్క్‌బార్ అన్‌పిన్" diff --git a/quickstep/res/values-th/strings.xml b/quickstep/res/values-th/strings.xml index 09d0df14a8..1bbb137a1b 100644 --- a/quickstep/res/values-th/strings.xml +++ b/quickstep/res/values-th/strings.xml @@ -22,13 +22,11 @@ "ปักหมุด" "รูปแบบอิสระ" "เดสก์ท็อป" - "ย้ายไปยังจอแสดงผลภายนอก" - "ปิด" - "เดสก์ท็อป" "ไม่มีรายการล่าสุด" "การตั้งค่าการใช้แอป" "ล้างทั้งหมด" "แอปล่าสุด" + "ปิดงานแล้ว" "%1$s %2$s" "<1 นาที" "วันนี้เหลืออีก %1$s" @@ -47,37 +45,39 @@ "เปิดใช้แอปแนะนำแล้ว" "ปิดใช้คำแนะนำเกี่ยวกับแอปอยู่" "แอปที่คาดว่าจะใช้: %1$s" - "บทแนะนำการไปยังส่วนต่างๆ ด้วยท่าทางสัมผัส" "หมุนอุปกรณ์ของคุณ" - "โปรดหมุนอุปกรณ์เพื่อทำตามบทแนะนำการไปยังส่วนต่างๆ ด้วยท่าทางสัมผัสให้เสร็จสมบูรณ์" - "ปัดจากขอบด้านขวาสุดหรือซ้ายสุด" + "โปรดหมุนอุปกรณ์เพื่อทำตามบทแนะนำการนำทางด้วยท่าทางสัมผัสให้เสร็จสมบูรณ์" + "ตรวจสอบว่าปัดจากขอบด้านขวาสุดหรือซ้ายสุด" "ตรวจสอบว่าปัดจากขอบด้านขวาหรือซ้ายไปตรงกลางหน้าจอ แล้วยกนิ้วขึ้น" "คุณรู้วิธีปัดจากด้านขวาเพื่อย้อนกลับแล้ว ต่อไปดูวิธีสลับแอป" - "คุณทำท่าทางสัมผัสเพื่อย้อนกลับสำเร็จแล้ว ต่อไปดูวิธีสลับแอป" - "คุณทำท่าทางสัมผัสเพื่อย้อนกลับสำเร็จแล้ว" + "คุณทำท่าทางสัมผัสเพื่อย้อนกลับเสร็จแล้ว ต่อไปดูวิธีสลับแอป" + "คุณทำท่าทางสัมผัสเพื่อย้อนกลับเสร็จแล้ว" "ไม่ปัดใกล้กับด้านล่างของหน้าจอมากเกินไป" "เปลี่ยนความไวของท่าทางสัมผัสเพื่อย้อนกลับได้ที่การตั้งค่า" "ปัดเพื่อย้อนกลับ" "หากต้องการย้อนกลับไปที่หน้าจอล่าสุด ให้ปัดจากขอบด้านซ้ายหรือขวาไปตรงกลางหน้าจอ" + "หากต้องการย้อนกลับไปที่หน้าจอล่าสุด ให้ใช้ 2 นิ้วปัดจากขอบด้านซ้ายหรือขวาไปตรงกลางหน้าจอ" "ย้อนกลับ" "ปัดจากขอบด้านซ้ายหรือขวาไปตรงกลางหน้าจอ" "ปัดขึ้นจากขอบด้านล่างของหน้าจอ" "ไม่ต้องหยุดชั่วคราวก่อนยกนิ้วขึ้น" "ปัดขึ้นในแนวตรง" - "คุณทำท่าทางสัมผัสเพื่อไปที่หน้าจอหลักสำเร็จแล้ว ต่อไปดูวิธีย้อนกลับ" - "คุณทำท่าทางสัมผัสเพื่อไปที่หน้าจอหลักสำเร็จแล้ว" + "คุณทำท่าทางสัมผัสเพื่อไปที่หน้าแรกเสร็จแล้ว ต่อไปดูวิธีย้อนกลับ" + "คุณทำท่าทางสัมผัสเพื่อไปที่หน้าแรกเสร็จแล้ว" "ปัดเพื่อไปที่หน้าแรก" "ปัดขึ้นจากด้านล่างของหน้าจอ ท่าทางสัมผัสนี้จะนำคุณไปที่หน้าจอหลักเสมอ" + "ใช้ 2 นิ้วปัดขึ้นจากด้านล่างของหน้าจอ ท่าทางสัมผัสนี้จะนำคุณไปที่หน้าจอหลักเสมอ" "ไปที่หน้าจอหลัก" "ปัดขึ้นจากด้านล่างของหน้าจอ" "เก่งมาก" "ปัดขึ้นจากขอบด้านล่างของหน้าจอ" "ลองแตะหน้าต่างค้างไว้นานขึ้นก่อนปล่อยนิ้ว" - "ปัดขึ้นในแนวตรง แล้วหยุดชั่วคราว" + "ตรวจสอบว่าปัดขึ้นในแนวตรง แล้วหยุดชั่วคราว" "คุณรู้วิธีใช้ท่าทางสัมผัสแล้ว หากต้องการปิดท่าทางสัมผัส ให้ไปที่การตั้งค่า" - "คุณทำท่าทางสัมผัสเพื่อเปลี่ยนแอปสำเร็จแล้ว" + "คุณทำท่าทางสัมผัสเพื่อสลับแอปเสร็จแล้ว" "ปัดเพื่อสลับแอป" "หากต้องการสลับระหว่างแอปต่างๆ ให้ปัดขึ้นจากด้านล่างของหน้าจอ ค้างไว้ แล้วปล่อย" + "หากต้องการสลับระหว่างแอป ให้ใช้ 2 นิ้วปัดขึ้นจากด้านล่างของหน้าจอค้างไว้แล้วปล่อย" "เปลี่ยนแอป" "ปัดขึ้นจากด้านล่างของหน้าจอ ค้างไว้ แล้วปล่อย" "เยี่ยมมาก" @@ -99,7 +99,7 @@ "บันทึกคู่แอป" "แตะแอปอื่นเพื่อใช้การแยกหน้าจอ" "เลือกแอปอื่นเพื่อใช้การแยกหน้าจอ" - "ยกเลิก" + "ยกเลิก" "ออกจากการเลือกโหมดแยกหน้าจอ" "เลือกแอปอื่นเพื่อใช้การแยกหน้าจอ" "แอปหรือองค์กรของคุณไม่อนุญาตการดำเนินการนี้" @@ -130,37 +130,18 @@ "การตั้งค่าด่วน" "แถบงาน" "แถบงานแสดงอยู่" - "แถบงานและบับเบิลแสดงไว้ทางซ้าย" - "แถบงานและบับเบิลแสดงไว้ทางขวา" + "แถบงานซ่อนอยู่" "แถบนำทาง" "แสดงแถบงานเสมอ" "เปลี่ยนโหมดการนําทาง" "ตัวแบ่งแถบงาน" - "การดำเนินการเพิ่มเติมของแถบงาน" "ย้ายไปที่ด้านบนหรือด้านซ้าย" "ย้ายไปที่ด้านล่างหรือด้านขวา" - "เปิดแอปเป็นบับเบิล" - "แอปล่าสุด" - "รายการแอปล่าสุด" - "{count,plural, =1{แอปเพิ่มเติม}other{แอปเพิ่มเติม}}" - "เดสก์ท็อป" + "{count,plural, =1{แสดงเพิ่มเติมอีก # แอป}other{แสดงเพิ่มเติมอีก # แอป}}" + "{count,plural, =1{แสดงแอปบนเดสก์ท็อป # รายการ}other{แสดงแอปบนเดสก์ท็อป # รายการ}}" "%1$s และ %2$s" - "%1$s, รายการที่ %2$d จาก %3$d" - "เลื่อนไปทางซ้าย" - "เลื่อนไปทางขวา" "บับเบิล" "การดำเนินการเพิ่มเติม" "%1$s จาก %2$s" "%1$s และอีก %2$d รายการ" - "ย้ายไปทางซ้าย" - "ย้ายไปทางขวา" - "ปิดทั้งหมด" - "ขยาย %1$s" - "ยุบ %1$s" - "วงเพื่อค้นหา" - "ไอคอนแอป" - "ชื่อแอป" - "ปุ่มปิด" - "ปักหมุดไปยังแถบงาน" - "เลิกปักหมุดจากแถบงาน" diff --git a/quickstep/res/values-tl/strings.xml b/quickstep/res/values-tl/strings.xml index 35515789cb..978a5a37f8 100644 --- a/quickstep/res/values-tl/strings.xml +++ b/quickstep/res/values-tl/strings.xml @@ -22,13 +22,11 @@ "I-pin" "Freeform" "Desktop" - "Ilipat sa external na display" - "Isara" - "Desktop" "Walang kamakailang item" "Mga setting ng paggamit ng app" "I-clear lahat" "Mga kamakailang app" + "Isinara ang Gawain" "%1$s, %2$s" "< 1 min" "%1$s na lang ngayon" @@ -47,7 +45,6 @@ "Naka-enable ang mga iminumungkahing app" "Naka-disable ang mga iminumungkahing app" "Hinulaang app: %1$s" - "Tutorial sa Navigation gamit ang Galaw" "I-rotate ang iyong device" "Paki-rotate ang iyong device para tapusin ang tutorial sa navigation gamit ang galaw" "Tiyaking magsa-swipe ka mula sa dulong kanan o dulong kaliwang gilid" @@ -59,6 +56,7 @@ "Pumunta sa Settings para baguhin ang sensitivity ng pagbalik" "Mag-swipe para bumalik" "Para bumalik sa nakaraang screen, mag-swipe mula sa kaliwa o kanang gilid patungo sa gitna ng screen." + "Para bumalik sa huling screen, mag-swipe gamit ang 2 daliri mula sa kaliwa o kanang gilid hanggang sa gitna ng screen." "Bumalik" "Mag-swipe mula sa kaliwa o kanang gilid papunta sa gitna ng screen" "Tiyaking magsa-swipe ka pataas mula sa pinakaibaba ng screen" @@ -68,6 +66,7 @@ "Nakumpleto mo na ang galaw para pumunta sa home" "Mag-swipe para pumunta sa home" "Mag-swipe pataas mula sa ibaba ng iyong screen. Dadalhin ka palagi ng galaw na ito sa Home screen." + "Mag-swipe pataas gamit ang 2 daliri mula sa ibaba ng screen. Dadalhin ka palagi nito sa Home screen." "Pumunta sa home" "Mag-swipe pataas mula sa ibabang bahagi ng iyong screen" "Magaling!" @@ -78,6 +77,7 @@ "Nakumpleto mo na ang galaw para magpalipat-lipat sa mga app" "Mag-swipe para lumipat ng app" "Para lumipat ng app, mag-swipe pataas mula sa ibaba ng iyong screen, mag-hold, at iangat ang daliri." + "Para lumipat ng app, mag-swipe pataas gamit ang 2 daliri mula sa ibaba, mag-hold, at bumitaw." "Lumipat ng app" "Mag-swipe pataas mula sa ibaba ng iyong screen, i-hold ito saka bitawan" "Magaling!" @@ -99,7 +99,7 @@ "I-save ang app pair" "Mag-tap ng ibang app para gamitin ang split screen" "Pumili ng ibang app para gamitin ang split screen" - "Kanselahin" + "Kanselahin" "Lumabas sa pagpili ng split screen" "Pumili ng ibang app para gamitin ang split screen" "Hindi pinapayagan ng app o ng iyong organisasyon ang pagkilos na ito" @@ -130,37 +130,18 @@ "Quick Settings" "Taskbar" "Ipinapakita ang taskbar" - "Taskbar at bubble sa kaliwa" - "Taskbar at bubble sa kanan" + "Nakatago ang taskbar" "Navigation bar" "Ipakita lagi ang Taskbar" "Magpalit ng navigation mode" "Divider ng Taskbar" - "Taskbar Overflow" "Ilipat sa itaas/kaliwa" "Ilipat sa ibaba/kanan" - "Buksan ang app bilang bubble" - "Mga kamakailang app" - "Kamakailang listahan ng app" - "{count,plural, =1{pang app}one{pang app}other{pang app}}" - "Desktop" + "{count,plural, =1{Magpakita ng # pang app.}one{Magpakita ng # pang app.}other{Magpakita ng # pang app.}}" + "{count,plural, =1{Ipakita ang # desktop app.}one{Ipakita ang # desktop app.}other{Ipakita ang # na desktop app.}}" "%1$s at %2$s" - "%1$s, item %2$d ng %3$d" - "Mag-scroll pakaliwa" - "Mag-scroll pakanan" "Bubble" "Overflow" "%1$s mula sa %2$s" "%1$s at %2$d pa" - "Ilipat pakaliwa" - "Ilipat pakanan" - "I-dismiss lahat" - "i-expand ang %1$s" - "i-collapse ang %1$s" - "Circle to Search" - "Icon ng app" - "Pamagat ng app" - "Button na isara" - "I-pin sa taskbar" - "I-unpin sa taskbar" diff --git a/quickstep/res/values-tr/strings.xml b/quickstep/res/values-tr/strings.xml index 5c5fba656d..0cc5d7f856 100644 --- a/quickstep/res/values-tr/strings.xml +++ b/quickstep/res/values-tr/strings.xml @@ -22,13 +22,11 @@ "Sabitle" "Serbest çalışma" "Masaüstü" - "Harici ekrana taşı" - "Kapat" - "Masaüstü" "Yeni öğe yok" "Uygulama kullanım ayarları" "Tümünü temizle" "Son uygulamalar" + "Görev Kapatıldı" "%1$s, %2$s" "< 1 dk." "Bugün %1$s kaldı" @@ -47,7 +45,6 @@ "Uygulama önerileri etkinleştirildi" "Uygulama önerileri devre dışı bırakıldı" "Tahmin edilen uygulama: %1$s" - "Hareketle Gezinme Eğitimi" "Cihazınızı döndürün" "Hareketle gezinme eğitimini tamamlamak için lütfen cihazınızı döndürün" "En sağ veya en sol kenardan kaydırdığınızdan emin olun" @@ -59,6 +56,7 @@ "Geri hareketinin hassasiyetini değiştirmek için Ayarlar\'a gidin" "Geri dönmek için kaydırma" "Son ekrana geri gitmek için sol veya sağ kenardan ekranın ortasına doğru kaydırın." + "Son ekrana geri gitmek için sol veya sağ kenardan ekranın ortasına doğru 2 parmağınızla kaydırın." "Geri dönme" "Sol veya sağ kenardan ekranın ortasına doğru kaydırın" "Ekranın alt kenarından yukarı kaydırdığınızdan emin olun" @@ -68,6 +66,7 @@ "Ana ekrana git hareketini tamamladınız" "Ana ekrana gitmek için kaydırma" "Ekranın alt kısmından yukarıya doğru kaydırın. Bu hareket sizi her zaman Ana ekrana götürür." + "Ekranın alt kısmından 2 parmağınızla yukarı kaydırın. Bu hareket sizi her zaman Ana ekrana götürür." "Ana sayfaya gitme" "Parmağınızı ekranın alt kısmından yukarıya doğru kaydırın" "Tebrikler!" @@ -78,6 +77,7 @@ "Uygulamalar arasında geçiş yapma hareketini tamamladınız" "Uygulamalar arasında geçiş yapmak için kaydırma" "Uygulamalar arasında geçiş yapmak için ekranınızın altından yukarı kaydırıp basılı tutun ve sonra bırakın." + "Uygulamalara geçiş yapmak için ekranın altından 2 parmakla yukarı kaydırıp basılı tutun ve bırakın." "Uygulamalar arasında geçiş yapma" "Ekranınızın alt tarafından yukarı doğru kaydırın, tutun ve sonra bırakın" "Tebrikler!" @@ -87,10 +87,10 @@ "Tekrar deneyin" "Güzel!" "Eğitim %1$d/%2$d" - "Kurulum tamamlandı" + "İşlem tamam!" "Ana ekrana gitmek için yukarı kaydırın" "Ana ekranınıza gitmek için ana sayfa düğmesine dokunun" - "Artık %1$s kullanılmak için hazır" + "%1$s cihazınızı kullanmaya hazırsınız" "cihaz" "Sistem gezinme ayarları" "Paylaş" @@ -99,7 +99,7 @@ "Uygulama çiftini kaydet" "Bölünmüş ekran için başka bir uygulamaya dokunun" "Bölünmüş ekran kullanmak için başka bir uygulama seçin" - "İptal" + "İptal" "Bölünmüş ekran seçiminden çıkın" "Bölünmüş ekran kullanmak için başka bir uygulama seçin" "Uygulamanız veya kuruluşunuz bu işleme izin vermiyor" @@ -130,37 +130,18 @@ "Hızlı Ayarlar" "Görev çubuğu." "Görev çubuğu gösteriliyor" - "Görev çubuğu ve baloncuklar solda gösteriliyor" - "Görev çubuğu ve baloncuklar sağda gösteriliyor" + "Görev çubuğu gizlendi" "Gezinme çubuğu" "Görev çubuğunu daima göster" "Gezinme modunu değiştir" "Görev Çubuğu Ayırıcısı" - "Görev Çubuğu Taşması" "Sol üste taşı" "Sağ alta taşı" - "Uygulamayı balon olarak aç" - "Son uygulamalar" - "Son uygulama listesi" - "{count,plural, =1{uygulama daha}other{uygulama daha}}" - "Masaüstü" + "{count,plural, =1{# uygulama daha göster.}other{# uygulama daha göster}}" + "{count,plural, =1{# masaüstü uygulamasını göster.}other{# masaüstü uygulamasını göster.}}" "%1$s ve %2$s" - "%1$s, %2$d/%3$d öğe" - "Sola kaydır" - "Sağa kaydır" "Balon" "Taşma" "%2$s uygulamasından %1$s" "%1$s ve %2$d tane daha" - "Sola taşı" - "Sağa taşı" - "Tümünü kapat" - "genişlet: %1$s" - "daralt: %1$s" - "Seçerek Arat" - "Uygulama simgesi" - "Uygulama başlığı" - "Kapat düğmesi" - "Çubuğa sabitle" - "Çubuktan kaldır" diff --git a/quickstep/res/values-uk/strings.xml b/quickstep/res/values-uk/strings.xml index 1d1f040acc..9c706a8403 100644 --- a/quickstep/res/values-uk/strings.xml +++ b/quickstep/res/values-uk/strings.xml @@ -21,14 +21,12 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Закріпити" "Довільна форма" - "Робочий стіл" - "Перемістити на зовнішній екран" - "Закрити" - "Комп’ютер" + "Комп’ютер" "Немає нещодавніх додатків" "Налаштування використання додатка" "Очистити все" "Нещодавні додатки" + "Завдання закрито" "%1$s, %2$s" "< 1 хв" "Сьогодні залишилося %1$s" @@ -47,7 +45,6 @@ "Рекомендовані додатки ввімкнено" "Рекомендовані додатки вимкнено" "Передбачений додаток: %1$s" - "Посібник із навігації за допомогою жестів" "Оберніть пристрій" "Обертайте пристрій, щоб ознайомитися з посібником із навігації за допомогою жестів" "Проведіть пальцем від самого краю екрана (правого або лівого)" @@ -59,6 +56,7 @@ "Щоб змінити чутливість жесту \"Назад\", відкрийте налаштування" "Щоб повернутися, проведіть пальцем по екрану" "Щоб перейти на попередній екран, проведіть пальцем від лівого чи правого краю до середини екрана." + "Щоб перейти на попередній екран, проведіть двома пальцями від лівого чи правого краю до середини екрана." "Повернення на попередній екран" "Проведіть пальцем від лівого чи правого краю до середини екрана" "Проведіть пальцем угору від нижнього краю екрана" @@ -68,6 +66,7 @@ "Ви виконали жест переходу на головний екран" "Проведіть пальцем, щоб перейти на головний екран" "Проведіть пальцем по екрану знизу вгору. Цей жест завжди повертатиме вас на головний екран." + "Проведіть двома пальцями вгору від низу екрана. Цей жест завжди спрямовує вас на головний екран." "Перехід на головний екран" "Проведіть пальцем угору від низу екрана" "Чудово!" @@ -78,6 +77,7 @@ "Ви виконали жест переходу в інший додаток" "Проведіть пальцем, щоб перейти в інший додаток" "Щоб переключатися між додатками, проведіть знизу вгору по екрану, утримуйте палець, а потім відпустіть." + "Щоб перейти в інший додаток, проведіть 2 пальцями від низу екрана, потримайте й відпустіть палець." "Перемикання між додатками" "Проведіть пальцем знизу вгору, утримуйте палець на екрані, а потім відпустіть" "Чудово!" @@ -92,14 +92,14 @@ "Натисніть кнопку головного екрана, щоб відкрити його" "Тепер ви можете використовувати %1$s" "пристрій" - "Налаштування навігації в системі" + "Системні налаштування навігації" "Поділитися" "Знімок екрана" "Розділити" "Зберегти пару" "Щоб розділити екран, виберіть ще один додаток." "Щоб розділити екран, виберіть ще один додаток." - "Скасувати" + "Скасувати" "Вийти з режиму розділення екрана" "Щоб розділити екран, виберіть ще один додаток." "Ця дія заборонена додатком або адміністратором організації" @@ -130,37 +130,18 @@ "Швидкі налаштув." "Панель завдань" "Панель завдань показано" - "Панель завдань і чати – зліва" - "Панель завдань і чати – справа" + "Панель завдань приховано" "Панель навігації" "Завжди показув. панель завдань" "Змінити режим навігації" "Розділювач панелі завдань" - "Додаткове меню панелі завдань" "Перемістити вгору або вліво" "Перемістити вниз або вправо" - "Відкрити додаток у спливаючому вікні" - "Нещодавні додатки" - "Список нещодавніх додатків" - "{count,plural, =1{інший додаток}one{інший додаток}few{інші додатки}many{інших додатків}other{іншого додатка}}" - "Комп’ютер" + "{count,plural, =1{Показати ще # додаток.}one{Показати ще # додаток.}few{Показати ще # додатки.}many{Показати ще # додатків.}other{Показати ще # додатка.}}" + "{count,plural, =1{Показати # комп’ютерну програму.}one{Показати # комп’ютерну програму.}few{Показати # комп’ютерні програми.}many{Показати # комп’ютерних програм.}other{Показати # комп’ютерної програми.}}" "%1$s та %2$s" - "%1$s, об’єкт %2$d з %3$d" - "Прокрутити вліво" - "Прокрутити вправо" "Повідомлення" "Додаткове повідомлення" "%1$s з додатка %2$s" "%1$s і ще %2$d" - "Перемістити вліво" - "Перемістити вправо" - "Закрити все" - "розгорнути \"%1$s\"" - "згорнути \"%1$s\"" - "Обвести й знайти" - "Значок додатка" - "Назва додатка" - "Кнопка \"Закрити\"" - "На панель завдань" - "З панелі завдань" diff --git a/quickstep/res/values-ur/strings.xml b/quickstep/res/values-ur/strings.xml index 8c44ef71a4..e12524868f 100644 --- a/quickstep/res/values-ur/strings.xml +++ b/quickstep/res/values-ur/strings.xml @@ -22,13 +22,11 @@ "پن کریں" "فری فارم" "ڈیسک ٹاپ" - "بیرونی ڈسپلے پر متقل کریں" - "بند کریں" - "ڈیسک ٹاپ" "کوئی حالیہ آئٹم نہیں" "ایپ کے استعمال کی ترتیبات" "سبھی کو صاف کریں" "حالیہ ایپس" + "ٹاسک بند ہے" "%1$s،%2$s" "‏< 1 منٹ" "آج %1$s بچا ہے" @@ -47,7 +45,6 @@ "ایپ کی تجاویز فعال ہیں" "ایپ کی تجاویز غیر فعال ہیں" "پیشن گوئی کردہ ایپ: %1$s" - "اشاروں والی نیویگیشن ٹیوٹوریل" "اپنا آلہ گھمائیں" "براہ کرم اشاروں والی نیویگیشن کا ٹیوٹوریل مکمل کرنے کے لیے اپنا آلہ گھمائیں" "یقینی بنائیں کہ آپ دائیں یا بائیں کنارے سے دور سے سوائپ کریں" @@ -59,6 +56,7 @@ "پچھلے اشارے کی حساسیت تبدیل کرنے کے لیے ترتیبات پر جائیں" "واپس جانے کے لیے سوائپ کریں" "پچھلی اسکرین پر واپس جانے کے لیے بائیں یا دائیں کنارے سے اسکرین کے وسط تک سوائپ کریں۔" + "آخری اسکرین پر واپس جانے کے لیے، 2 انگلیوں سے بائیں یا دائیں کنارے سے اسکرین کے وسط تک سوائپ کریں۔" "واپس جائیں" "دائیں یا بائیں کنارے سے اسکرین کے وسط تک سوائپ کریں" "اس بات کو یقینی بنائیں کہ آپ اسکرین کے نچلے کنارے سے اوپر کی طرف سوائپ کریں" @@ -68,6 +66,7 @@ "آپ نے ہوم پر جانے کا اشارہ مکمل کر لیا" "ہوم پر جانے کے لیے سوائپ کریں" "اپنی اسکرین کے نیچے سے اوپر کی طرف سوائپ کریں۔ یہ اشارہ آپ کو ہمیشہ ہوم اسکرین پر لے جاتا ہے۔" + "اسکرین کے نیچے سے 2 انگلیوں سے اوپر سوائپ کریں۔ یہ اشارہ آپ کو ہمیشہ ہوم اسکرین پر لے جاتا ہے۔" "ہوم پر جائیں" "اپنی اسکرین کے نچلے حصے سے اوپر کی طرف سوائپ کریں" "بہترین!" @@ -78,6 +77,7 @@ "آپ نے ایپس کو سوئچ کرنے کا اشارہ مکمل کر لیا" "ایپس سوئچ کرنے کے لیے سوائپ کریں" "ایپس کے مابین سوئچ کرنے کے لیے، اپنی اسکرین کے نچلے حصے سے اوپر کی جانب سوائپ کریں، پکڑے رکھیں، پھر چھوڑ دیں۔" + "ایپس کے مابین سوئچ کرنے کیلئے، اپنی اسکرین کے نیچے سے 2 انگلیوں سے اوپر سوائپ کریں، دبائے رکھیں پھر چھوڑ دیں۔" "ایپس سوئچ کریں" "اپنی اسکرین کے نچلے حصے سے اوپر کی جانب سوائپ کریں، دبائے رکھیں، پھر چھوڑ دیں" "بہت خوب!" @@ -99,7 +99,7 @@ "ایپس کے جوڑے کو محفوظ کریں" "اسپلٹ اسکرین کا استعمال کرنے کیلئے دوسری ایپ پر تھپتھپائیں" "اسپلٹ اسکرین کے استعمال کیلئے دوسری ایپ منتخب کریں" - "منسوخ کریں" + "منسوخ کریں" "اسپلٹ اسکرین کے انتخاب سے باہر نکلیں" "اسپلٹ اسکرین کے استعمال کیلئے دوسری ایپ منتخب کریں" "ایپ یا آپ کی تنظیم کی جانب سے اس کارروائی کی اجازت نہیں ہے" @@ -130,37 +130,18 @@ "فوری ترتیبات" "ٹاسک بار" "ٹاشک بار دکھایا گیا" - "ٹاسک بار و بلبلے بائیں طرف ہیں" - "ٹاسک بار و بلبلے دائیں طرف ہیں" + "ٹاسک بار چھپایا گیا" "نیویگیشن بار" "ہمیشہ ٹاسک بار دکھائیں" "نیویگیشن موڈ تبدیل کریں" "ٹاسک بار ڈیوائیڈر" - "ٹاسک بار اوورفلو" "اوپر/بائیں طرف منتقل کریں" "نیچے/دائیں طرف منتقل کریں" - "ایپ کو بطور ببل کھولیں" - "حالیہ ایپس" - "حالیہ ایپ کی فہرست" - "{count,plural, =1{مزید ایپ}other{مزید ایپس}}" - "ڈیسک ٹاپ" + "{count,plural, =1{# مزید ایپ دکھائیں۔}other{# مزید ایپس دکھائیں۔}}" + "{count,plural, =1{# ڈیسک ٹاپ ایپ دکھائیں۔}other{# ڈیسک ٹاپ ایپس دکھائیں۔}}" "%1$s اور %2$s" - "‫%1$s، آئٹم %2$d از %3$d" - "بائیں طرف اسکرول کریں" - "دائیں طرف اسکرول کریں" "ببل" "اوورفلو" "%2$s سے %1$s" "%1$s اور %2$d مزید" - "بائیں منتقل کریں" - "دائیں منتقل کریں" - "سبھی کو برخاست کریں" - "%1$s کو پھیلائیں" - "%1$s کو سکیڑیں" - "تلاش کرنے کیلئے دائرہ بنائیں" - "ایپ آئیکن" - "ایپ کا عنوان" - "\'بند کریں\' بٹن" - "ٹاسک بار میں پن کریں" - "ٹاسک بار سے پن ہٹائیں" diff --git a/quickstep/res/values-uz/strings.xml b/quickstep/res/values-uz/strings.xml index 86fdd5614c..3f4f981095 100644 --- a/quickstep/res/values-uz/strings.xml +++ b/quickstep/res/values-uz/strings.xml @@ -22,13 +22,11 @@ "Qadash" "Erkin shakl" "Desktop" - "Tashqi displeyga olish" - "Yopish" - "Desktop" "Yaqinda ishlatilgan ilovalar yo‘q" "Ilovadan foydalanish sozlamalari" "Hammasini tozalash" "Oxirgi ilovalar" + "Vazifalar yopildi" "%1$s, %2$s" "< 1 daqiqa" "Bugun %1$s qoldi" @@ -47,7 +45,6 @@ "Ilova tavsiyalari yoqildi" "Endi ilova takliflari chiqmaydi" "Taklif etilgan ilova: %1$s" - "Ishorali navigatsiya darsligi" "Qurilmangizni buring" "Ishorali navigatsiya darsligini tugatish uchun qurilmani buring" "Ekran chetidan boshlab oʻngdan yoki chapdan suring" @@ -59,6 +56,7 @@ "Orqaga ishorasi sezuvchanligi Sozlamalardan oʻzgartiriladi" "Orqaga qaytish" "Ortga qaytish uchun barmoqni ekranning yon chekkalaridan oʻrtasigacha suring." + "Oxirgi ekranga qaytish uchun 2 barmoq bilan ekranning chap yoki oʻng chekkasidan oʻrtasigacha suring." "Orqaga" "Chap yoki oʻng chetidan ekranning oʻrtasiga suring" "Barmoqni ekranning pastki chetidan yuqoriga suring." @@ -68,6 +66,7 @@ "Bosh ekranni ochish ishorasi darsini tamomladingiz" "Svayp bilan bosh ekranni ochish" "Ekranning pastidan tepaga qarab suring. Bu ishora doim Bosh ekranni ochadi." + "2 barmoq bilan ekranning quyidan tepasiga suring. Bu ishora har doim Bosh ekranni ochadi." "Boshiga" "Ekranning quyi qismidan tepaga torting" "Barakalla!" @@ -78,6 +77,7 @@ "Ilovalarni almashtirish darsini tamomladingiz" "Ilovalar orasida almashish" "Ilovalarni ochish uchun ekranning pastidan tepaga qarab suring, biroz ushlab turing va qoʻyib yuboring" + "Ilovalarni almashtirish uchun 2 barmoq bilan ekranning quyidan tepasiga surib turib, qoʻyib yuboring" "Ilovalarni almashtirish" "Ekranning pastidan tepaga qarab suring, biroz ushlab turing va qoʻyib yuboring" "Juda soz!" @@ -90,7 +90,7 @@ "Hammasi tayyor!" "Boshiga qaytish uchun tepaga suring" "Bosh ekranga oʻtish uchun bosh ekran tugmasini bosing" - "Sizning %1$s xizmatga tayyor" + "%1$s xizmatga tayyor" "qurilma" "Tizim navigatsiya sozlamalari" "Ulashish" @@ -99,7 +99,7 @@ "Ilova juftini saqlash" "Ekranni ikkiga ajratish uchun boshqa ilovani bosing" "Ekranni ikkiga ajratish uchun boshqa ilovani tanlang" - "Bekor qilish" + "Bekor qilish" "Ekranni ikkiga ajratish tanlovidan chiqish" "Ekranni ikkiga ajratish uchun boshqa ilovani tanlang" "Bu amal ilova yoki tashkilotingiz tomonidan taqiqlangan" @@ -130,37 +130,18 @@ "Tezkor sozlamalar" "Vazifalar paneli" "Vazifalar paneli ochiq" - "Panel va bulutchalar chapda" - "Panel va bulutchalar oʻngda" + "Vazifalar paneli yopiq" "Navigatsiya paneli" "Vazifalar paneli doim chiqarilsin" "Navigatsiya rejimini oʻzgartirish" "Vazifalar panelini ajratkich" - "Vazifalar panelini kengaytirish" "Yuqoriga yoki chapga oʻtkazish" "Pastga yoki oʻngga oʻtkazish" - "Ilovani qalqib chiquvchi oynada ochish" - "Oxirgi ilovalar" - "Oxirgi ilovalar roʻyxati" - "{count,plural, =1{boshqa ilova}other{boshqa ilovalar}}" - "Kompyuter" + "{count,plural, =1{Yana # ta ilovani chiqarish}other{Yana # ta ilovani chiqarish}}" + "{count,plural, =1{# ta desktop ilovani chiqarish.}other{# ta desktop ilovani chiqarish.}}" "%1$s va %2$s" - "%1$s, %2$d-element, jami: %3$d ta" - "Chapga varaqlash" - "Oʻngga varaqlash" "Pufak" "Kengaytirish" "%1$s (%2$s)" "%1$s va yana %2$d kishi" - "Chapga siljitish" - "Oʻngga siljitish" - "Hammasini yopish" - "%1$sni yoyish" - "%1$sni yigʻish" - "Chizib qidirish" - "Ilova belgisi" - "Ilova nomi" - "Yopish tugmasi" - "Panelga qadash" - "Vazifalar panelidan yechib olish" diff --git a/quickstep/res/values-vi/strings.xml b/quickstep/res/values-vi/strings.xml index 26b37702ea..9bc526faac 100644 --- a/quickstep/res/values-vi/strings.xml +++ b/quickstep/res/values-vi/strings.xml @@ -22,13 +22,11 @@ "Ghim" "Dạng tự do" "Máy tính" - "Chuyển sang màn hình ngoài" - "Đóng" - "Máy tính" "Không có mục gần đây nào" "Cài đặt mức sử dụng ứng dụng" "Xóa tất cả" "Ứng dụng gần đây" + "Đã đóng tác vụ" "%1$s, %2$s" "< 1 phút" "Hôm nay còn %1$s" @@ -47,20 +45,20 @@ "Đã bật tính năng Ứng dụng đề xuất" "Tính năng Ứng dụng đề xuất bị tắt" "Ứng dụng dự đoán: %1$s" - "Hướng dẫn thực hiện thao tác bằng cử chỉ" "Xoay thiết bị của bạn" "Vui lòng xoay thiết bị của bạn để hoàn tất hướng dẫn thao tác bằng cử chỉ" "Hãy vuốt từ mép ngoài cùng bên phải hoặc ngoài cùng bên trái" "Hãy vuốt từ mép phải hoặc mép trái tới giữa màn hình rồi nhấc ngón tay ra" "Bạn đã học được cách vuốt từ mép phải để quay lại. Tiếp theo, hãy tìm hiểu cách chuyển đổi ứng dụng." "Bạn đã thực hiện xong cử chỉ quay lại. Tiếp theo, hãy tìm hiểu cách chuyển đổi ứng dụng." - "Bạn đã hoàn tất cử chỉ quay lại" + "Bạn đã thực hiện xong cử chỉ quay lại" "Hãy nhớ không được vuốt quá gần phần dưới cùng của màn hình" "Để thay đổi độ nhạy của cử chỉ quay lại, hãy vào mục Cài đặt" "Vuốt để quay lại" "Để quay lại màn hình gần đây nhất, hãy vuốt từ mép trái hoặc mép phải tới chính giữa màn hình." + "Để quay lại màn hình trước đó, hãy vuốt 2 ngón tay từ cạnh trái hoặc phải vào giữa màn hình." "Quay lại" - "Vuốt từ mép trái hoặc mép phải tới giữa màn hình" + "Hãy vuốt từ mép trái hoặc mép phải tới giữa màn hình" "Hãy vuốt lên từ mép dưới cùng của màn hình" "Hãy nhớ không được dừng trước khi nhấc ngón tay" "Hãy vuốt thẳng lên" @@ -68,6 +66,7 @@ "Bạn đã thực hiện xong cử chỉ chuyển đến Màn hình chính" "Vuốt để chuyển đến Màn hình chính" "Vuốt lên từ cuối màn hình. Cử chỉ này luôn đưa bạn đến Màn hình chính." + "Vuốt 2 ngón tay lên từ cuối màn hình. Cử chỉ này luôn đưa bạn về Màn hình chính." "Chuyển đến màn hình chính" "Vuốt lên từ mép dưới cùng của màn hình" "Tuyệt vời!" @@ -78,6 +77,7 @@ "Bạn đã thực hiện xong cử chỉ chuyển đổi ứng dụng" "Vuốt để chuyển đổi ứng dụng" "Để chuyển đổi ứng dụng, hãy vuốt lên từ cuối màn hình, giữ rồi thả ra." + "Để chuyển đổi giữa các ứng dụng, hãy vuốt 2 ngón tay lên từ cuối màn hình, giữ rồi thả ra." "Chuyển đổi ứng dụng" "Vuốt lên từ mép dưới cùng của màn hình, giữ rồi nhấc ngón tay ra" "Rất tốt!" @@ -99,7 +99,7 @@ "Lưu cặp ứng dụng" "Nhấn vào ứng dụng khác để chia đôi màn hình" "Chọn một ứng dụng khác để dùng chế độ chia đôi màn hình" - "Huỷ" + "Huỷ" "Thoát khỏi lựa chọn chia đôi màn hình" "Chọn một ứng dụng khác để dùng chế độ chia đôi màn hình" "Ứng dụng hoặc tổ chức của bạn không cho phép thực hiện hành động này" @@ -130,37 +130,18 @@ "Cài đặt nhanh" "Thanh tác vụ" "Đã hiện thanh thao tác" - "Hiện thanh tác vụ, b.bóng trái" - "Hiện thanh tác vụ, b.bóng phải" + "Đã ẩn thanh thao tác" "Thanh điều hướng" "Luôn hiện Thanh tác vụ" "Thay đổi chế độ điều hướng" "Đường phân chia Taskbar" - "Trình đơn mục bổ sung trên thanh tác vụ" "Chuyển lên trên cùng/sang bên trái" "Chuyển xuống dưới cùng/sang bên phải" - "Mở ứng dụng dưới dạng bong bóng" - "Các ứng dụng gần đây" - "Danh sách ứng dụng gần đây" - "{count,plural, =1{ứng dụng khác}other{ứng dụng khác}}" - "Máy tính" + "{count,plural, =1{Hiện thêm # ứng dụng.}other{Hiện thêm # ứng dụng.}}" + "{count,plural, =1{Hiện # ứng dụng dành cho máy tính.}other{Hiện # ứng dụng dành cho máy tính.}}" "%1$s%2$s" - "%1$s, mục %2$d trong số %3$d" - "Cuộn sang trái" - "Cuộn sang phải" "Bong bóng" "Bong bóng bổ sung" "%1$s từ %2$s" "%1$s%2$d bong bóng khác" - "Di chuyển sang trái" - "Di chuyển sang phải" - "Đóng tất cả" - "mở rộng %1$s" - "thu gọn %1$s" - "Khoanh tròn để tìm kiếm" - "Biểu tượng ứng dụng" - "Tên ứng dụng" - "Nút đóng" - "Ghim vào thanh tác vụ" - "Bỏ ghim khỏi thanh tác vụ" diff --git a/quickstep/res/values-zh-rCN/strings.xml b/quickstep/res/values-zh-rCN/strings.xml index 21c212f88c..f6e446e803 100644 --- a/quickstep/res/values-zh-rCN/strings.xml +++ b/quickstep/res/values-zh-rCN/strings.xml @@ -22,13 +22,11 @@ "固定" "自由窗口" "桌面" - "移至外接显示屏" - "关闭" - "桌面设备" "近期没有任何内容" "应用使用设置" "全部清除" "最近用过的应用" + "任务已关闭" "%1$s%2$s)" "不到 1 分钟" "今天还可使用 %1$s" @@ -47,27 +45,28 @@ "已启用应用建议" "已停用应用建议" "预测的应用:%1$s" - "手势导航教程" "请旋转设备" "请旋转设备,完成手势导航教程" "确保从最右侧或最左侧边缘开始滑动" "确保从右侧或左侧边缘滑动到屏幕中间位置后再松开手指" "您已了解如何使用“从右侧向左滑动”手势返回。接下来学习切换应用吧!" "您完成了“返回”手势教程。接下来了解如何切换应用。" - "您已完成“返回”手势教程" + "您完成了“返回”手势" "确保滑动时手的位置不要太靠近屏幕底部" "如要调节“返回”手势的灵敏度,请转到“设置”" "滑动即可返回" "如要返回上一个屏幕,请从屏幕左侧或右侧边缘往屏幕中间滑动。" + "若要返回上一个屏幕,请用两根手指从屏幕左侧或右侧边缘向中间滑动。" "返回" "从屏幕左侧或右侧边缘滑动到中间" "确保从屏幕底部边缘向上滑动" "松开手指前,请勿中途停顿" "确保笔直向上滑动" - "您已完成“前往主屏幕”手势教程。接下来学习如何返回。" - "您已完成“前往主屏幕”手势教程" + "您完成了“转到主屏幕”手势。接下来了解如何返回。" + "您完成了“转到主屏幕”手势" "上滑可转到主屏幕" "从屏幕底部向上滑动,即可随时回到主屏幕。" + "用两根手指从屏幕底部向上滑动,这个手势会一律使您回到主屏幕。" "前往主屏幕" "从屏幕底部向上滑动" "太棒了!" @@ -75,9 +74,10 @@ "尝试按住窗口较长时间,然后再松开手指" "确保笔直向上滑动,然后停住" "您已了解如何使用手势了。如要关闭手势,请前往“设置”。" - "您已完成“切换应用”手势教程" + "您完成了应用切换手势" "滑动即可切换应用" "如需在应用之间切换,请从屏幕底部向上滑动,按住,然后松开。" + "如需在应用之间切换,请从屏幕底部向上滑动,按住,然后松开。" "切换应用" "从屏幕底部向上滑动后按住,然后松开" "恭喜!" @@ -99,7 +99,7 @@ "保存应用组合" "点按另一个应用即可使用分屏" "另外选择一个应用才可使用分屏模式" - "取消" + "取消" "退出分屏选择模式" "另外选择一个应用才可使用分屏模式" "该应用或您所在的单位不允许执行此操作" @@ -130,37 +130,18 @@ "快捷设置" "任务栏" "任务栏已显示" - "已显示任务栏和左侧消息气泡" - "已显示任务栏和右侧消息气泡" + "任务栏已隐藏" "导航栏" "始终显示任务栏" "更改导航模式" "任务栏分隔线" - "任务栏溢出图标" "移到顶部/左侧" "移到底部/右侧" - "以气泡的形式打开应用" - "最近打开过的应用" - "“最近打开过的应用”列表" - "{count,plural, =1{多个应用}other{多个应用}}" - "桌面模式" + "{count,plural, =1{显示另外 # 个应用。}other{显示另外 # 个应用。}}" + "{count,plural, =1{显示 # 款桌面应用。}other{显示 # 款桌面应用。}}" "%1$s%2$s" - "%1$s,第 %2$d 项(共 %3$d 项)" - "向左滚动" - "向右滚动" "气泡框" "溢出式气泡框" "来自“%2$s”的%1$s" "%1$s以及另外 %2$d 个" - "左移" - "右移" - "全部关闭" - "展开“%1$s”" - "收起“%1$s”" - "圈定即搜" - "应用图标" - "应用名称" - "“关闭”按钮" - "固定到任务栏" - "从任务栏取消固定" diff --git a/quickstep/res/values-zh-rHK/strings.xml b/quickstep/res/values-zh-rHK/strings.xml index 34f8aacb49..b9d8eb765a 100644 --- a/quickstep/res/values-zh-rHK/strings.xml +++ b/quickstep/res/values-zh-rHK/strings.xml @@ -22,13 +22,11 @@ "固定" "自由形式" "桌面" - "移至外部顯示屏" - "關閉" - "桌面" "最近沒有任何項目" "應用程式使用情況設定" "全部清除" "最近使用的應用程式" + "閂咗工作" "%1$s%2$s" "少於 1 分鐘" "今天剩餘時間:%1$s" @@ -47,7 +45,6 @@ "已啟用應用程式建議" "已停用應用程式建議" "預測應用程式:%1$s" - "手勢導覽教學課程" "旋轉裝置方向" "請旋轉裝置方向以完成手勢導覽教學課程" "請確保從螢幕最右側或最左側邊緣滑動" @@ -59,6 +56,7 @@ "如要變更「返回」手勢的敏感度,請前往「設定」" "滑動即可返回" "如要返回上一個畫面,請從螢幕左側或右側邊緣往中央滑動。" + "如要返回上一個畫面,請用兩指從螢幕左側或右側邊緣往中央滑動。" "返回" "從螢幕左側或右側邊緣往中央滑動" "請確保從螢幕底部邊緣向上滑動" @@ -68,6 +66,7 @@ "你已完成「返回主畫面」手勢的教學課程" "向上滑動即可返回主畫面" "從螢幕底部向上滑動。這個手勢在所有畫面下都可讓你返回主畫面。" + "請用兩指從螢幕底部向上滑動。這個手勢在所有畫面下都可讓你返回主畫面。" "返回主畫面" "從螢幕底部向上滑動" "太好了!" @@ -78,6 +77,7 @@ "你已完成「切換應用程式」手勢的教學課程" "滑動即可切換應用程式" "如要切換應用程式,請從螢幕底部向上滑動並按住,然後放開。" + "如要切換應用程式,請用兩指從螢幕底部向上滑動並按住,然後放開手指。" "切換應用程式" "從螢幕底部向上滑動並按住,然後放開" "做得好!" @@ -99,7 +99,7 @@ "儲存應用程式組合" "輕按其他應用程式以使用分割螢幕" "選擇其他應用程式才能使用分割螢幕" - "取消" + "取消" "退出分割螢幕選取頁面" "選擇其他應用程式才能使用分割螢幕" "應用程式或你的機構不允許此操作" @@ -130,37 +130,18 @@ "快速設定" "工作列" "顯示咗工作列" - "工作列和對話氣泡在左邊顯示" - "工作列和對話氣泡在右邊顯示" + "隱藏咗工作列" "導覽列" "一律顯示工作列" "變更導覽模式" "工作列分隔線" - "工作列溢位" "移至上方/左側" "移至底部/右側" - "在小視窗開啟應用程式" - "最近開啟的應用程式" - "「最近開啟的應用程式」清單" - "{count,plural, =1{個其他應用程式}other{個其他應用程式}}" - "桌面" + "{count,plural, =1{顯示另外 # 個應用程式。}other{顯示另外 # 個應用程式。}}" + "{count,plural, =1{顯示 # 個桌面應用程式。}other{顯示 # 個桌面應用程式。}}" "「%1$s」和「%2$s」" - "%1$s,第 %2$d 個項目,總共有 %3$d 項" - "向左捲動" - "向右捲動" "對話氣泡" "展開式" "%2$s 的「%1$s」通知" "%1$s和其他 %2$d 則通知" - "向左移" - "向右移" - "全部關閉" - "打開%1$s" - "收埋%1$s" - "一圈即搜" - "應用程式圖示" - "應用程式名稱" - "關閉按鈕" - "固定至工作列" - "取消固定至工作列" diff --git a/quickstep/res/values-zh-rTW/strings.xml b/quickstep/res/values-zh-rTW/strings.xml index 5244b05d48..90140cb587 100644 --- a/quickstep/res/values-zh-rTW/strings.xml +++ b/quickstep/res/values-zh-rTW/strings.xml @@ -21,14 +21,12 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "固定" "自由形式" - "電腦模式" - "移至外接螢幕" - "關閉" - "電腦模式" + "電腦" "最近沒有任何項目" "應用程式使用情況設定" "全部清除" "最近使用的應用程式" + "工作已關閉" "%1$s (%2$s)" "< 1 分鐘" "今天還能使用 %1$s" @@ -47,7 +45,6 @@ "應用程式建議功能已啟用" "應用程式建議功能已停用" "預測的應用程式:%1$s" - "手勢操作教學課程" "旋轉裝置螢幕方向" "如要完成手勢操作教學課程,請旋轉裝置螢幕方向" "請務必從螢幕最右側或最左側滑動" @@ -59,6 +56,7 @@ "如要變更「返回」手勢的敏感度,請前往「設定」" "滑動即可返回" "如要返回上一個畫面,請從螢幕左側或右側邊緣往中央滑動。" + "如要返回上一個畫面,請用 2 指從螢幕左側或右側邊緣往中央滑動。" "返回" "從螢幕右側或左側往中央滑動" "請務必從螢幕底部向上滑動" @@ -68,6 +66,7 @@ "你已完成「返回主畫面」手勢的教學課程" "使用滑動手勢返回主畫面" "從螢幕底部向上滑動,即可返回主畫面。" + "用 2 指從螢幕底部向上滑動,即可回到主畫面。" "返回主畫面" "從螢幕底部向上滑動" "太棒了!" @@ -78,6 +77,7 @@ "你已完成「切換應用程式」手勢的教學課程" "使用滑動手勢切換應用程式" "如要切換不同的應用程式,請從螢幕底部向上滑動並按住,然後放開手指。" + "如要切換應用程式,請用 2 指從螢幕底部向上滑動並按住,然後放開手指。" "切換應用程式" "從螢幕底部向上滑動並按住,然後放開手指" "非常好!" @@ -99,7 +99,7 @@ "儲存應用程式配對" "輕觸另一個應用程式即可使用分割畫面" "選擇要在分割畫面中使用的另一個應用程式" - "取消" + "取消" "退出分割畫面選擇器" "必須選擇另一個應用程式才能使用分割畫面" "這個應用程式或貴機構不允許執行這個動作" @@ -130,37 +130,18 @@ "快速設定" "工作列" "已顯示工作列" - "工作列和對話框顯示在左側" - "工作列和對話框顯示在右側" + "已隱藏工作列" "導覽列" "一律顯示工作列" "變更操作模式" "工作列分隔線" - "工作列溢位" "移到上方/左側" "移到底部/右側" - "以泡泡形式開啟應用程式" - "最近開啟的應用程式" - "「最近開啟的應用程式」清單" - "{count,plural, =1{個其他應用程式}other{個其他應用程式}}" - "電腦模式" + "{count,plural, =1{再多顯示 # 個應用程式。}other{再多顯示 # 個應用程式。}}" + "{count,plural, =1{顯示 # 個電腦版應用程式。}other{顯示 # 個電腦版應用程式。}}" "「%1$s」和「%2$s」" - "%1$s,第 %2$d 個項目,共 %3$d 項" - "向左捲動" - "向右捲動" "泡泡" "溢位" "「%2$s」的「%1$s」通知" "%1$s和另外 %2$d 則通知" - "向左移" - "向右移" - "全部關閉" - "展開「%1$s」" - "收合「%1$s」" - "畫圈搜尋" - "應用程式圖示" - "應用程式標題" - "關閉按鈕" - "固定到工作列" - "從工作列中取消固定" diff --git a/quickstep/res/values-zu/strings.xml b/quickstep/res/values-zu/strings.xml index a81fe25676..73be445a55 100644 --- a/quickstep/res/values-zu/strings.xml +++ b/quickstep/res/values-zu/strings.xml @@ -22,13 +22,11 @@ "Phina" "I-Freeform" "Ideskithophu" - "Hambisa esibonisini sangaphandle" - "Vala" - "Ideskithophu" "Azikho izinto zakamuva" "Izilungiselelo zokusetshenziswa kohlelo lokusebenza" "Sula konke" "Izinhlelo zokusebenza zakamuva" + "Umsebenzi Uvaliwe" "%1$s, %2$s" "< 1 iminithi" "%1$s esele namhlanje" @@ -47,7 +45,6 @@ "Iziphakamiso zohlelo lokusebenza zinikwe amandla" "Iziphakamiso zohlelo lokusebenza zikhutshaziwe" "Uhlelo lokusebenza olubikezelwe: %1$s" - "Okokufundisa Kokuzulazula Kokuthinta" "Zungezisa idivayisi yakho" "Sicela uzungezise idivayisi yakho ukuze uqedele okokufundisa kokufuna ngokuthinta" "Qinisekisa ukuthi uswayipha ukusuka onqenqemeni olukude ngakwesokudla noma olukude ngakwesokunxele" @@ -59,6 +56,7 @@ "Ukuze ushintshe ukuzwela kokuthinta emuva, iya Kumasethingi" "Swayipha ukuze uye emuva" "Ukuze ubuyele emuva esikrinini sokugcina, swapha kusuka emngceleni wesobunxele noma wesokudla kuya phakathi kwesikrini." + "Ukuze ubuyele esikrinini sokugcina, swayipha ngeminwe emi-2 ukusuka kwesokunxele noma kwesokudla emphethweni uye phakathi kwesikrini." "Iya emuva" "Swayipha ukusuka kwesokunxele noma kwesokudla ukuya phakathi kwesikrini" "Qiniseka ukuthi uswayiphela phezulu ukusuka emngceleni ophansi wesikrini" @@ -68,6 +66,7 @@ "Ukuqedile ukuthinta kokuya ekhaya" "Swayipha ukuze uye ekhaya" "Swayiphela phezulu kusuka phansi kwesikrini sakho.Lokhu kuthinta kuhlala kukusa esikrinini sasekhaya." + "Swayiphela phezulu ngeminwe emi-2 kusukela phansi esikrinini. Lesi senzo sihlala sikuyisa esikrinini Sasekhaya." "Iya ekhasini lokuqala" "Swayiphela phezulu ukusuka phansi esikrinini sakho" "Umsebenzi omuhle!" @@ -78,6 +77,7 @@ "Ukuqedile ukuthinta kokushintsha ama-app" "Swayipha ukuze ushintshe ama-app" "Ukuze ushintshe phakathi kwama-app, swayiphela phezulu kusuka ngezansi kwesikrini sakho, bese uyadedela." + "Ukuze ushintshe phakathi kwama-app, swayiphela phezulu ngeminwe emi-2 kusukela phansi esikrinini sakho, ubambe, bese uyakhulula." "Shintsha ama-app" "Swayiphela phezulu ukusuka phansi esikrinini sakho, ubambe, bese uyadedela" "Wenze kahle!" @@ -99,7 +99,7 @@ "Londoloza ukubhangqa i-app" "Thepha enye i-app ukuze usebenzise isikrini sokuhlukanisa" "Khetha enye i-app ukuze usebenzise ukuhlukanisa isikrini" - "Khansela" + "Khansela" "Phuma ekukhetheni ukuhlukaniswa kwesikrini" "Khetha enye i-app ukuze usebenzise ukuhlukanisa isikrini" "Lesi senzo asivunyelwanga uhlelo lokusebenza noma inhlangano yakho" @@ -130,37 +130,18 @@ "Amasethingi Asheshayo" "I-Taskbar" "Ibha yomsebenzi ibonisiwe" - "ITaskbar namabhamuza aboniswe kwesokunxele" - "ITaskbar namabhamuza aboniswe kwesokudla" + "Ibha yomsebenzi ifihliwe" "Ibha yokufuna" "Bonisa i-Taskbar njalo." "Shintsha imodi yokufuna" "Isihlukanisi se-Taskbar" - "Ukuphuphuma Kwetaskbar" "Hamba phezulu/kwesokunxele" "Hamba phansi/kwesokudla" - "Vula i-app njengebhamuza" - "Ama-app wakamuva" - "Uhlu lwe-app lwakamuva" - "{count,plural, =1{i-app eyengeziwe}one{ama-app engeziwe}other{ama-app engeziwe}}" - "Ideskithophu" + "{count,plural, =1{Bonisa i-app e-# ngaphezulu.}one{Bonisa ama-app angu-# ngaphezulu.}other{Bonisa ama-app angu-# ngaphezulu.}}" + "{count,plural, =1{Bonisa i-app engu-# yedeskithophu.}one{Bonisa ama-app angu-# wedeskithophu.}other{Bonisa ama-app angu-# wedeskithophu.}}" "I-%1$s ne-%2$s" - "I-%1$s, into engu-%2$d kwezingu-%3$d" - "Skrolela ngakwesokunxele" - "Skrolela ngakwesokudla" "Ibhamuza" "Ukugcwala kakhulu" "%1$s kusuka ku-%2$s" "%1$s nokunye okungu-%2$d" - "Iya kwesokunxele" - "Iya kwesokudla" - "Chitha konke" - "nweba %1$s" - "goqa %1$s" - "Khethela Ukusesha" - "Isithonjana se-app" - "Isihloko se-app" - "Inkinobho yokuvala" - "Phina kutaskbar" - "Susa ukuphina i-taskbar" diff --git a/quickstep/res/values/attrs.xml b/quickstep/res/values/attrs.xml index 28c0d5c9e8..ccc7f180dd 100644 --- a/quickstep/res/values/attrs.xml +++ b/quickstep/res/values/attrs.xml @@ -28,7 +28,6 @@ - @@ -36,11 +35,6 @@ - - - - - - + #fff #39000000 @@ -31,6 +31,7 @@ #99000000 #EBffffff #99000000 + #646464 #ffffff @@ -75,7 +76,7 @@ #80868b #bdc1c6 - @android:color/system_neutral1_50 + #FFFFFFFF #333333 @@ -93,5 +94,7 @@ #f9ab00 - @color/materialColorPrimary - \ No newline at end of file + ?attr/colorAccentPrimary + ?attr/materialColorPrimaryFixedDim + @color/material_color_on_primary_fixed + diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml index 49cee0fa86..fd122103e1 100644 --- a/quickstep/res/values/config.xml +++ b/quickstep/res/values/config.xml @@ -23,15 +23,23 @@ com.android.quickstep.logging.StatsLogCompatManager com.android.quickstep.QuickstepTestInformationHandler + com.android.quickstep.util.SystemWindowManagerProxy + com.android.launcher3.uioverrides.QuickstepWidgetHolder$QuickstepHolderFactory com.android.quickstep.InstantAppResolverImpl com.android.launcher3.appprediction.PredictionAppTracker com.android.quickstep.QuickstepProcessInitializer + com.android.launcher3.model.QuickstepModelDelegate com.android.launcher3.secondarydisplay.SecondaryDisplayPredictionsImpl com.android.launcher3.taskbar.TaskbarModelCallbacksFactory com.android.launcher3.taskbar.TaskbarViewCallbacksFactory com.android.quickstep.LauncherRestoreEventLoggerImpl + com.android.launcher3.uioverrides.plugins.PluginManagerWrapperImpl com.android.launcher3.taskbar.TaskbarEduTooltipController + + + + com.android.launcher3.uioverrides.SystemApiWrapper @@ -46,17 +54,10 @@ 23 - 34dp - - - 6 - diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml index 86d44c978a..5c82c991c7 100644 --- a/quickstep/res/values/dimens.xml +++ b/quickstep/res/values/dimens.xml @@ -34,12 +34,13 @@ 0.7 + + 0.46 48dp 44dp - - 52dp 4dp @@ -81,15 +82,6 @@ 44dp 4dp - - 30dp - 18dp - 9dp - 18dp - 16dp - - - 25dp 72dp 1.1 @@ -109,10 +101,6 @@ 24dp 2dp - - 56dp - 2dp - 600dp @@ -121,14 +109,12 @@ 0.0285dp 0.15dp 0.285dp - 0.7dp 1.4dp 36dp 0.5dp 115dp - 36dp 100dp @@ -277,7 +263,7 @@ 24dp - 40dp + 40dp 36sp 14sp @@ -362,16 +348,19 @@ 48dp 48dp 32dp + 6dp 6dp - 5.5dp 64dp 64dp 48dp 1dp 72dp - 2dp - 12dp - 4dp + 4dp + 14dp + 2dp + 2dp + 12dp + 2dp 12dp @@ -382,11 +371,9 @@ 10dp 32dp 8dp - 8dp 400dp 8dp 16dp - 4dp 16dp @@ -435,10 +422,6 @@ 300dp 16dp - 16dp - - - 8dp 60dp @@ -451,7 +434,6 @@ 55dp @dimen/transient_taskbar_stashed_height @dimen/taskbar_stashed_handle_height - @dimen/transient_taskbar_stash_spring_velocity_dp_per_s 9dp @@ -461,28 +443,23 @@ 80dp 1dp - - 3dp + 2dp 90dp - 20dp 32dp 36dp - 28dp 24dp 12dp 16dp 6dp 8dp - @dimen/bubblebar_icon_spacing 12dp 1dp - 12dp - @dimen/floating_dismiss_background_size + 96dp 60dp - @dimen/floating_dismiss_icon_size + 24dp 50dp 548dp 192dp @@ -496,49 +473,31 @@ 24dp 16dp - - 16dp - 4dp - 14dp - 238dp - 276dp - 12dp - 10dp - 1dp - 2dp - 20dp - 5dp - 3dp + 4dp 104dp - 136dp + 134dp 52dp 20dp - 32dp 56dp - 24dp 16dp 16dp - 4dp + 2dp 28dp 16dp 24dp 8dp - 104dp - 360dp - 16dp - 20dp - 36dp - 56dp - 6dp - 16dp - 18dp 48dp + + + + 220dp + diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml index 77a6bf9b89..f3066389d5 100644 --- a/quickstep/res/values/strings.xml +++ b/quickstep/res/values/strings.xml @@ -23,15 +23,8 @@ Pin Freeform - + Desktop - - Move to external display - - Close - - - Desktop No recent items @@ -48,6 +41,9 @@ Recent apps + + Task Closed + %1$s, %2$s @@ -97,9 +93,6 @@ Predicted app: %1$s - - Gesture Navigation Tutorial - Rotate your device @@ -124,6 +117,8 @@ Swipe to go back To go back to the last screen, swipe from the left or right edge to the middle of the screen. + + To go back to the last screen, swipe with 2 fingers from the left or right edge to the middle of the screen. Go back @@ -142,6 +137,8 @@ Swipe to go home Swipe up from the bottom of your screen. This gesture always takes you to the Home screen. + + Swipe up with 2 fingers from the bottom of the screen. This gesture always takes you to the Home screen. Go home @@ -163,6 +160,8 @@ Swipe to switch apps To switch between apps, swipe up from the bottom of your screen, hold, then release. + + To switch between apps, swipe up with 2 fingers from the bottom of your screen, hold, then release. Switch apps @@ -235,7 +234,7 @@ Tap another app to use split screen Choose another app to use split screen - Cancel + Cancel Exit split screen selection Choose another app to use split screen @@ -297,12 +296,10 @@ Quick Settings Taskbar - + Taskbar shown - - Taskbar & bubbles left shown - - Taskbar & bubbles right shown + + Taskbar hidden Navigation bar @@ -311,41 +308,27 @@ Change navigation mode Taskbar Divider - - Taskbar Overflow Move to top/left Move to bottom/right - - Open app as a bubble - - Recent apps - - - Recent app list - - + {count, plural, - =1{more app} - other{more apps} + =1{Show # more app.} + other{Show # more apps.} } - - Desktop + + {count, plural, + =1{Show # desktop app.} + other{Show # desktop apps.} + } %1$s and %2$s - - %1$s, item %2$d of %3$d - - - Scroll left - - Scroll right @@ -356,31 +339,4 @@ %1$s from %2$s %1$s and %2$d more - - Move left - - Move right - - Dismiss all - - expand %1$s - - collapse %1$s - - - Circle to Search - - - - App icon - - App title - - Close button - - - Pin to taskbar - - Unpin from taskbar diff --git a/quickstep/res/values/styles.xml b/quickstep/res/values/styles.xml index f8ca8d9adf..1da1166631 100644 --- a/quickstep/res/values/styles.xml +++ b/quickstep/res/values/styles.xml @@ -124,7 +124,7 @@ - (1) Split Root 1 (left/top side parent) (WINDOWING_MODE_MULTI_WINDOW) + * |--> Split Root 1 (left/top side parent) (WINDOWING_MODE_MULTI_WINDOW) * | | - * | --> (1a) App 1 (left/top side child) (WINDOWING_MODE_MULTI_WINDOW) + * | --> App 1 (left/top side child) (WINDOWING_MODE_MULTI_WINDOW) * |--> Divider - * |--> (2) Split Root 2 (right/bottom side parent) (WINDOWING_MODE_MULTI_WINDOW) + * |--> Split Root 2 (right/bottom side parent) (WINDOWING_MODE_MULTI_WINDOW) * | - * --> (2a) App 2 (right/bottom side child) (WINDOWING_MODE_MULTI_WINDOW) + * --> App 2 (right/bottom side child) (WINDOWING_MODE_MULTI_WINDOW) * * We want to animate the Root (grandparent) so that it affects both apps and the divider. To do * this, we find one of the nodes with WINDOWING_MODE_MULTI_WINDOW (one of the left-side ones, @@ -707,8 +682,7 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC launchingIconView: AppPairIcon, transitionInfo: TransitionInfo, t: Transaction, - finishCallback: Runnable, - windowRadius: Float, + finishCallback: Runnable ) { // If launching an app pair from Taskbar inside of an app context (no access to Launcher), // use the scale-up animation @@ -717,7 +691,7 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC transitionInfo, t, finishCallback, - WINDOWING_MODE_MULTI_WINDOW, + WINDOWING_MODE_MULTI_WINDOW ) return } @@ -728,28 +702,48 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC // Create an AnimatorSet that will run both shell and launcher transitions together val launchAnimation = AnimatorSet() + var rootCandidate: Change? = null - val splitRoots: Pair>? = - SplitScreenUtils.extractTopParentAndChildren(transitionInfo) - check(splitRoots != null) { "Could not find split roots" } + for (change in transitionInfo.changes) { + val taskInfo: RunningTaskInfo = change.taskInfo ?: continue - // Will point to change (0) in diagram above - val mainRootCandidate = splitRoots.first - // Will contain changes (1) and (2) in diagram above - val leafRoots: List = splitRoots.second - // Don't rely on DP.isLeftRightSplit because if launcher is portrait apps could still - // launch in landscape if system auto-rotate is enabled and phone is held horizontally - val isLeftRightSplit = leafRoots.all { it.endAbsBounds.top == 0 } + // TODO (b/316490565): Replace this logic when SplitBounds is available to + // startAnimation() and we can know the precise taskIds of launching tasks. + // Find a change that has WINDOWING_MODE_MULTI_WINDOW. + if ( + taskInfo.windowingMode == WINDOWING_MODE_MULTI_WINDOW && + (change.mode == TRANSIT_OPEN || change.mode == TRANSIT_TO_FRONT) + ) { + // Check if it is a left/top app. + val isLeftTopApp = + (dp.isLeftRightSplit && change.endAbsBounds.left == 0) || + (!dp.isLeftRightSplit && change.endAbsBounds.top == 0) + if (isLeftTopApp) { + // Found one! + rootCandidate = change + break + } + } + } + + // If we could not find a proper root candidate, something went wrong. + check(rootCandidate != null) { "Could not find a split root candidate" } // Find the place where our left/top app window meets the divider (used for the // launcher side animation) - val leftTopApp = - leafRoots.single { change -> - (isLeftRightSplit && change.endAbsBounds.left <= 0) || - (!isLeftRightSplit && change.endAbsBounds.top <= 0) - } val dividerPos = - if (isLeftRightSplit) leftTopApp.endAbsBounds.right else leftTopApp.endAbsBounds.bottom + if (dp.isLeftRightSplit) rootCandidate.endAbsBounds.right + else rootCandidate.endAbsBounds.bottom + + // Recurse up the tree until parent is null, then we've found our root. + var parentToken: WindowContainerToken? = rootCandidate.parent + while (parentToken != null) { + rootCandidate = transitionInfo.getChange(parentToken) ?: break + parentToken = rootCandidate.parent + } + + // Make sure nothing weird happened, like getChange() returning null. + check(rootCandidate != null) { "Failed to find a root leash" } // Create a new floating view in Launcher, positioned above the launching icon val drawableArea = launchingIconView.iconDrawableArea @@ -764,30 +758,13 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC drawableArea, appIcon1, appIcon2, - dividerPos, + dividerPos ) floatingView.bringToFront() - val iconLaunchValueAnimator = - getIconLaunchValueAnimator( - t, - dp, - finishCallback, - launcher, - floatingView, - mainRootCandidate, - ) - iconLaunchValueAnimator.addListener( - object : AnimatorListenerAdapter() { - override fun onAnimationStart(animation: Animator, isReverse: Boolean) { - for (c in leafRoots) { - t.setCornerRadius(c.leash, windowRadius) - t.apply() - } - } - } + launchAnimation.play( + getIconLaunchValueAnimator(t, dp, finishCallback, launcher, floatingView, rootCandidate) ) - launchAnimation.play(iconLaunchValueAnimator) launchAnimation.start() } @@ -801,7 +778,7 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC transitionInfo: TransitionInfo, t: Transaction, finishCallback: Runnable, - launchFullscreenIndex: Int, + launchFullscreenIndex: Int ) { // If launching an app pair from Taskbar inside of an app context (no access to Launcher), // use the scale-up animation @@ -810,7 +787,7 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC transitionInfo, t, finishCallback, - WINDOWING_MODE_FULLSCREEN, + WINDOWING_MODE_FULLSCREEN ) return } @@ -831,8 +808,8 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC val baseIntent = taskInfo.baseIntent.component?.packageName if ( TransitionUtil.isOpeningType(change.mode) && - taskInfo.windowingMode == WINDOWING_MODE_FULLSCREEN && - baseIntent == intentToLaunch + taskInfo.windowingMode == WINDOWING_MODE_FULLSCREEN && + baseIntent == intentToLaunch ) { rootCandidate = change } @@ -862,7 +839,7 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC drawableArea, appIcon, null /*appIcon2*/, - 0, /*dividerPos*/ + 0 /*dividerPos*/ ) floatingView.bringToFront() launchAnimation.play( @@ -877,7 +854,7 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC finishCallback: Runnable, launcher: QuickstepLauncher, floatingView: FloatingAppPairView, - rootCandidate: Change, + rootCandidate: Change ): ValueAnimator { val progressUpdater = ValueAnimator.ofFloat(0f, 1f) val timings = AnimUtils.getDeviceAppPairLaunchTimings(dp.isTablet) @@ -891,7 +868,7 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC Interpolators.LINEAR, valueAnimator.animatedFraction, timings.appRevealStartOffset, - timings.appRevealEndOffset, + timings.appRevealEndOffset ) // Set the alpha of the shell layer (2 apps + divider) @@ -908,8 +885,8 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC Interpolators.clampToProgress( timings.getStagedRectXInterpolator(), timings.stagedRectSlideStartOffset, - timings.stagedRectSlideEndOffset, - ), + timings.stagedRectSlideEndOffset + ) ) var mDy = FloatProp( @@ -918,8 +895,8 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC Interpolators.clampToProgress( Interpolators.EMPHASIZED, timings.stagedRectSlideStartOffset, - timings.stagedRectSlideEndOffset, - ), + timings.stagedRectSlideEndOffset + ) ) var mScaleX = FloatProp( @@ -928,8 +905,8 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC Interpolators.clampToProgress( Interpolators.EMPHASIZED, timings.stagedRectSlideStartOffset, - timings.stagedRectSlideEndOffset, - ), + timings.stagedRectSlideEndOffset + ) ) var mScaleY = FloatProp( @@ -938,8 +915,8 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC Interpolators.clampToProgress( Interpolators.EMPHASIZED, timings.stagedRectSlideStartOffset, - timings.stagedRectSlideEndOffset, - ), + timings.stagedRectSlideEndOffset + ) ) override fun onUpdate(percent: Float, initOnly: Boolean) { @@ -974,7 +951,7 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC transitionInfo: TransitionInfo, t: Transaction, finishCallback: Runnable, - windowingMode: Int, + windowingMode: Int ) { val launchAnimation = AnimatorSet() val progressUpdater = ValueAnimator.ofFloat(0f, 1f) @@ -990,7 +967,7 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC // startAnimation() and we can know the precise taskIds of launching tasks. if ( taskInfo.windowingMode == windowingMode && - (change.mode == TRANSIT_OPEN || change.mode == TRANSIT_TO_FRONT) + (change.mode == TRANSIT_OPEN || change.mode == TRANSIT_TO_FRONT) ) { // Found one! rootCandidate = change @@ -1017,10 +994,10 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC val startingScale = 0.34f val startX = screenBounds.left + - ((screenBounds.right - screenBounds.left) * ((1 - startingScale) / 2f)) + ((screenBounds.right - screenBounds.left) * ((1 - startingScale) / 2f)) val startY = screenBounds.top + - ((screenBounds.bottom - screenBounds.top) * ((1 - startingScale) / 2f)) + ((screenBounds.bottom - screenBounds.top) * ((1 - startingScale) / 2f)) val endX = screenBounds.left val endY = screenBounds.top @@ -1060,8 +1037,7 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC secondTaskId: Int, transitionInfo: TransitionInfo, t: Transaction, - finishCallback: Runnable, - cornerRadius: Float, + finishCallback: Runnable ) { var splitRoot1: Change? = null var splitRoot2: Change? = null @@ -1126,7 +1102,7 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC Interpolators.LINEAR, valueAnimator.animatedFraction, 0.8f, - 1f, + 1f ) for (leash in openingTargets) { animTransaction.setAlpha(leash, progress) @@ -1139,7 +1115,6 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC override fun onAnimationStart(animation: Animator) { for (leash in openingTargets) { animTransaction.show(leash).setAlpha(leash, 0.0f) - animTransaction.setCornerRadius(leash, cornerRadius) } animTransaction.apply() } @@ -1159,4 +1134,4 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC container.dragLayer.removeView(view) } } -} +} \ No newline at end of file diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationTimings.java b/quickstep/src/com/android/quickstep/util/SplitAnimationTimings.java index 3a6d9b027b..b618546576 100644 --- a/quickstep/src/com/android/quickstep/util/SplitAnimationTimings.java +++ b/quickstep/src/com/android/quickstep/util/SplitAnimationTimings.java @@ -17,7 +17,6 @@ package com.android.quickstep.util; import static com.android.app.animation.Interpolators.LINEAR; -import static com.android.app.animation.Interpolators.STANDARD; import android.view.animation.Interpolator; @@ -39,8 +38,6 @@ public interface SplitAnimationTimings { int TABLET_APP_PAIR_LAUNCH_DURATION = 998; /** Total duration (ms) for launching an app pair from its icon on phones. */ int PHONE_APP_PAIR_LAUNCH_DURATION = 915; - /** Total duration (ms) for fading out desktop tasks in split mode. */ - int DESKTOP_FADE_OUT_DURATION = 200; // Initialize timing classes so they can be accessed statically SplitAnimationTimings TABLET_OVERVIEW_TO_SPLIT = new TabletOverviewToSplitTimings(); @@ -86,10 +83,6 @@ public interface SplitAnimationTimings { return (float) getStagedRectSlideEnd() / getDuration(); } - default float getDesktopFadeSplitAnimationEndOffset() { - return (float) DESKTOP_FADE_OUT_DURATION / getDuration(); - } - // DEFAULT VALUES: We define default values here so that SplitAnimationTimings can be used // flexibly in animation-running functions, e.g. a single function that handles 2 types of split // animations. The values are not intended to be used, and can safely be removed if refactoring @@ -112,10 +105,6 @@ public interface SplitAnimationTimings { default Interpolator getGridSlidePrimaryInterpolator() { return LINEAR; } default Interpolator getGridSlideSecondaryInterpolator() { return LINEAR; } - default Interpolator getDesktopTaskFadeInterpolator() { - return LINEAR; - } - // Defaults for HomeToSplit default float getScrimFadeInStartOffset() { return 0; } default float getScrimFadeInEndOffset() { return 0; } @@ -131,9 +120,5 @@ public interface SplitAnimationTimings { default float getAppRevealEndOffset() { return 0; } default Interpolator getCellSplitInterpolator() { return LINEAR; } default Interpolator getIconFadeInterpolator() { return LINEAR; } - - default Interpolator getDesktopTaskScaleInterpolator() { - return STANDARD; - } } diff --git a/quickstep/src/com/android/quickstep/util/SplitScreenUtils.kt b/quickstep/src/com/android/quickstep/util/SplitScreenUtils.kt index 4005c5a813..38bbe601b6 100644 --- a/quickstep/src/com/android/quickstep/util/SplitScreenUtils.kt +++ b/quickstep/src/com/android/quickstep/util/SplitScreenUtils.kt @@ -16,78 +16,43 @@ package com.android.quickstep.util -import android.util.Log -import android.view.WindowManager.TRANSIT_OPEN -import android.view.WindowManager.TRANSIT_TO_FRONT -import android.window.TransitionInfo -import android.window.TransitionInfo.Change -import android.window.TransitionInfo.FLAG_FIRST_CUSTOM import com.android.launcher3.util.SplitConfigurationOptions -import com.android.wm.shell.shared.split.SplitBounds -import java.lang.IllegalStateException +import com.android.wm.shell.util.SplitBounds class SplitScreenUtils { companion object { - private const val TAG = "SplitScreenUtils" - // TODO(b/254378592): Remove these methods when the two classes are reunited /** Converts the shell version of SplitBounds to the launcher version */ @JvmStatic - fun convertShellSplitBoundsToLauncher(shellSplitBounds: SplitBounds) = - SplitConfigurationOptions.SplitBounds( - shellSplitBounds.leftTopBounds, - shellSplitBounds.rightBottomBounds, - shellSplitBounds.leftTopTaskId, - shellSplitBounds.rightBottomTaskId, - shellSplitBounds.snapPosition, - ) - - /** - * Given a TransitionInfo, generates the tree structure for those changes and extracts out - * the top most root and it's two immediate children. Changes can be provided in any order. - * - * @return a [Pair] where first -> top most split root, second -> [List] of 2, - * leftTop/bottomRight stage roots - */ - fun extractTopParentAndChildren( - transitionInfo: TransitionInfo - ): Pair>? { - val parentToChildren = mutableMapOf>() - val hasParent = mutableSetOf() - // filter out anything that isn't opening and the divider - val taskChanges: List = - transitionInfo.changes - .filter { change -> - (change.mode == TRANSIT_OPEN || change.mode == TRANSIT_TO_FRONT) && - change.flags < FLAG_FIRST_CUSTOM - } - .toList() - - // 1. Build Parent-Child Relationships - for (change in taskChanges) { - // TODO (b/316490565): Replace this logic when SplitBounds is available to - // startAnimation() and we can know the precise taskIds of launching tasks. - change.parent?.let { parent -> - parentToChildren - .getOrPut(transitionInfo.getChange(parent)!!) { mutableListOf() } - .add(change) - hasParent.add(change) - } - } - - // 2. Find Top Parent - val topParent = taskChanges.firstOrNull { it !in hasParent } - - // 3. Extract Immediate Children - return if (topParent != null) { - val immediateChildren = parentToChildren.getOrDefault(topParent, emptyList()) - if (immediateChildren.size != 2) { - throw IllegalStateException("incorrect split stage root size") - } - Pair(topParent, immediateChildren) - } else { - Log.w(TAG, "No top parent found") + fun convertShellSplitBoundsToLauncher( + shellSplitBounds: SplitBounds? + ): SplitConfigurationOptions.SplitBounds? { + return if (shellSplitBounds == null) { null + } else { + SplitConfigurationOptions.SplitBounds( + shellSplitBounds.leftTopBounds, shellSplitBounds.rightBottomBounds, + shellSplitBounds.leftTopTaskId, shellSplitBounds.rightBottomTaskId, + shellSplitBounds.snapPosition + ) + } + } + + /** Converts the launcher version of SplitBounds to the shell version */ + @JvmStatic + fun convertLauncherSplitBoundsToShell( + launcherSplitBounds: SplitConfigurationOptions.SplitBounds? + ): SplitBounds? { + return if (launcherSplitBounds == null) { + null + } else { + SplitBounds( + launcherSplitBounds.leftTopBounds, + launcherSplitBounds.rightBottomBounds, + launcherSplitBounds.leftTopTaskId, + launcherSplitBounds.rightBottomTaskId, + launcherSplitBounds.snapPosition + ) } } } diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java index 08f2552157..bcfcd8eb26 100644 --- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java +++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java @@ -16,6 +16,7 @@ package com.android.quickstep.util; +import static com.android.launcher3.Utilities.postAsyncCallback; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_DESKTOP_MODE_SPLIT_LEFT_TOP; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_DESKTOP_MODE_SPLIT_RIGHT_BOTTOM; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTED_SECOND_APP; @@ -34,8 +35,8 @@ import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_SINGLE_TASK import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_TASK_PENDINGINTENT; import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_TASK_SHORTCUT; import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_TASK_TASK; -import static com.android.wm.shell.shared.split.SplitScreenConstants.KEY_EXTRA_WIDGET_INTENT; -import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50; +import static com.android.wm.shell.common.split.SplitScreenConstants.KEY_EXTRA_WIDGET_INTENT; +import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -52,13 +53,16 @@ import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.os.Bundle; +import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; +import android.os.SystemClock; import android.os.UserHandle; import android.util.Log; import android.util.Pair; +import android.view.RemoteAnimationAdapter; +import android.view.RemoteAnimationTarget; import android.view.SurfaceControl; -import android.view.View; import android.window.IRemoteTransitionFinishedCallback; import android.window.RemoteTransition; import android.window.RemoteTransitionStub; @@ -71,12 +75,12 @@ import com.android.internal.logging.InstanceId; import com.android.launcher3.R; import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.apppairs.AppPairIcon; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.icons.IconProvider; import com.android.launcher3.logging.StatsLogManager; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.statehandlers.DepthController; import com.android.launcher3.statemanager.StateManager; -import com.android.launcher3.taskbar.LauncherTaskbarUIController; import com.android.launcher3.testing.TestLogging; import com.android.launcher3.testing.shared.TestProtocol; import com.android.launcher3.uioverrides.QuickstepLauncher; @@ -90,22 +94,23 @@ import com.android.quickstep.RecentsAnimationTargets; import com.android.quickstep.RecentsModel; import com.android.quickstep.SplitSelectionListener; import com.android.quickstep.SystemUiProxy; +import com.android.quickstep.TaskAnimationManager; import com.android.quickstep.views.FloatingTaskView; import com.android.quickstep.views.GroupedTaskView; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.RecentsViewContainer; import com.android.quickstep.views.SplitInstructionsView; +import com.android.systemui.animation.RemoteAnimationRunnerCompat; import com.android.systemui.shared.recents.model.Task; +import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.InteractionJankMonitorWrapper; -import com.android.systemui.shared.system.QuickStepContract; -import com.android.wm.shell.shared.split.SplitScreenConstants.PersistentSnapPosition; +import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition; import com.android.wm.shell.splitscreen.ISplitSelectListener; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.Optional; import java.util.function.Consumer; /** @@ -116,6 +121,7 @@ public class SplitSelectStateController { private static final String TAG = "SplitSelectStateCtor"; private RecentsViewContainer mContainer; + private final Handler mHandler; private final RecentsModel mRecentTasksModel; @Nullable private Runnable mActivityBackCallback; @@ -148,10 +154,9 @@ public class SplitSelectStateController { /** * Should be a constant from {@link com.android.internal.jank.Cuj} or -1, does not need to be - * set for all launches. Used in conjunction with {@link #mLaunchingViewCuj} below. + * set for all launches. */ private int mLaunchCuj = -1; - private View mLaunchingViewCuj; private FloatingTaskView mFirstFloatingTaskView; private SplitInstructionsView mSplitInstructionsView; @@ -163,12 +168,10 @@ public class SplitSelectStateController { */ private Pair mSessionInstanceIds; - private boolean mIsDestroyed = false; - private final BackPressHandler mSplitBackHandler = new BackPressHandler() { @Override public boolean canHandleBack() { - return isSplitSelectActive(); + return FeatureFlags.enableSplitContextually() && isSplitSelectActive(); } @Override @@ -183,11 +186,12 @@ public class SplitSelectStateController { } }; - public SplitSelectStateController(RecentsViewContainer container, - StateManager stateManager, DepthController depthController, - StatsLogManager statsLogManager, SystemUiProxy systemUiProxy, RecentsModel recentsModel, - Runnable activityBackCallback) { + public SplitSelectStateController(RecentsViewContainer container, Handler handler, + StateManager stateManager, DepthController depthController, + StatsLogManager statsLogManager, SystemUiProxy systemUiProxy, RecentsModel recentsModel, + Runnable activityBackCallback) { mContainer = container; + mHandler = handler; mStatsLogManager = statsLogManager; mSystemUiProxy = systemUiProxy; mStateManager = stateManager; @@ -195,13 +199,12 @@ public class SplitSelectStateController { mRecentTasksModel = recentsModel; mActivityBackCallback = activityBackCallback; mSplitAnimationController = new SplitAnimationController(this); - mAppPairsController = new AppPairsController(mContainer, this, statsLogManager); + mAppPairsController = new AppPairsController(mContainer.asContext(), this, statsLogManager); mSplitSelectDataHolder = new SplitSelectDataHolder(mContainer.asContext()); } public void onDestroy() { mContainer = null; - mIsDestroyed = true; mActivityBackCallback = null; mAppPairsController.onDestroy(); mSplitSelectDataHolder.onDestroy(); @@ -216,8 +219,8 @@ public class SplitSelectStateController { * @param intent will be ignored if @param alreadyRunningTask is set */ public void setInitialTaskSelect(@Nullable Intent intent, @StagePosition int stagePosition, - @NonNull ItemInfo itemInfo, StatsLogManager.EventEnum splitEvent, - int alreadyRunningTask) { + @NonNull ItemInfo itemInfo, StatsLogManager.EventEnum splitEvent, + int alreadyRunningTask) { mSplitSelectDataHolder.setInitialTaskSelect(intent, stagePosition, itemInfo, splitEvent, alreadyRunningTask); createAndLogInstanceIdsForSession(); @@ -228,8 +231,8 @@ public class SplitSelectStateController { * running app. */ public void setInitialTaskSelect(ActivityManager.RunningTaskInfo info, - @StagePosition int stagePosition, @NonNull ItemInfo itemInfo, - StatsLogManager.EventEnum splitEvent) { + @StagePosition int stagePosition, @NonNull ItemInfo itemInfo, + StatsLogManager.EventEnum splitEvent) { mSplitSelectDataHolder.setInitialTaskSelect(info, stagePosition, itemInfo, splitEvent); createAndLogInstanceIdsForSession(); } @@ -244,7 +247,7 @@ public class SplitSelectStateController { * tasks (i.e. searching for a running pair of tasks.) */ public void findLastActiveTasksAndRunCallback(@Nullable List componentKeys, - boolean findExactPairMatch, Consumer callback) { + boolean findExactPairMatch, Consumer callback) { mRecentTasksModel.getTasks(taskGroups -> { if (componentKeys == null || componentKeys.isEmpty()) { callback.accept(new Task[]{}); @@ -259,7 +262,7 @@ public class SplitSelectStateController { GroupTask groupTask = taskGroups.get(i); if (isInstanceOfAppPair( groupTask, componentKeys.get(0), componentKeys.get(1))) { - lastActiveTasks[0] = ((SplitTask) groupTask).getTopLeftTask(); + lastActiveTasks[0] = groupTask.task1; break; } } @@ -272,15 +275,17 @@ public class SplitSelectStateController { // Loop through tasks in reverse, since they are ordered with recent tasks last for (int j = taskGroups.size() - 1; j >= 0; j--) { GroupTask groupTask = taskGroups.get(j); - // Account for desktop cases where there can be N tasks in the group - for (Task task : groupTask.getTasks()) { - if (isInstanceOfComponent(task, key) - && !Arrays.asList(lastActiveTasks).contains(task)) { - lastActiveTask = task; - break; - } + Task task1 = groupTask.task1; + // Don't add duplicate Tasks + if (isInstanceOfComponent(task1, key) + && !Arrays.asList(lastActiveTasks).contains(task1)) { + lastActiveTask = task1; + break; } - if (lastActiveTask != null) { + Task task2 = groupTask.task2; + if (isInstanceOfComponent(task2, key) + && !Arrays.asList(lastActiveTasks).contains(task2)) { + lastActiveTask = task2; break; } } @@ -312,16 +317,12 @@ public class SplitSelectStateController { * both permutations because task order is not guaranteed in GroupTasks. */ public boolean isInstanceOfAppPair(GroupTask groupTask, @NonNull ComponentKey componentKey1, - @NonNull ComponentKey componentKey2) { - if (groupTask instanceof SplitTask splitTask) { - return ((isInstanceOfComponent(splitTask.getTopLeftTask(), componentKey1) - && isInstanceOfComponent(splitTask.getBottomRightTask(), componentKey2)) - || - (isInstanceOfComponent(splitTask.getTopLeftTask(), componentKey2) - && isInstanceOfComponent(splitTask.getBottomRightTask(), - componentKey1))); - } - return false; + @NonNull ComponentKey componentKey2) { + return ((isInstanceOfComponent(groupTask.task1, componentKey1) + && isInstanceOfComponent(groupTask.task2, componentKey2)) + || + (isInstanceOfComponent(groupTask.task1, componentKey2) + && isInstanceOfComponent(groupTask.task2, componentKey1))); } /** @@ -350,7 +351,7 @@ public class SplitSelectStateController { * animations are complete. */ public void launchSplitTasks(@PersistentSnapPosition int snapPosition, - @Nullable Consumer callback) { + @Nullable Consumer callback) { launchTasks(callback, false /* freezeTaskList */, snapPosition, mSessionInstanceIds.first); mStatsLogManager.logger() @@ -371,7 +372,7 @@ public class SplitSelectStateController { * A version of {@link #launchSplitTasks(int, Consumer)} that launches with default split ratio. */ public void launchSplitTasks(@Nullable Consumer callback) { - launchSplitTasks(SNAP_TO_2_50_50, callback); + launchSplitTasks(SNAP_TO_50_50, callback); } /** @@ -379,7 +380,7 @@ public class SplitSelectStateController { * ratio and no callback. */ public void launchSplitTasks() { - launchSplitTasks(SNAP_TO_2_50_50, null); + launchSplitTasks(SNAP_TO_50_50, null); } /** @@ -435,7 +436,7 @@ public class SplitSelectStateController { * foreground (quickswitch, launching previous pairs from overview) */ public void launchTasks(@Nullable Consumer callback, boolean freezeTaskList, - @PersistentSnapPosition int snapPosition, @Nullable InstanceId shellInstanceId) { + @PersistentSnapPosition int snapPosition, @Nullable InstanceId shellInstanceId) { TestLogging.recordEvent( TestProtocol.SEQUENCE_MAIN, "launchSplitTasks"); final ActivityOptions options1 = ActivityOptions.makeBasic(); @@ -458,41 +459,77 @@ public class SplitSelectStateController { Bundle optionsBundle = options1.toBundle(); Bundle extrasBundle = new Bundle(1); extrasBundle.putParcelable(KEY_EXTRA_WIDGET_INTENT, widgetIntent); - final RemoteTransition remoteTransition = getRemoteTransition(firstTaskId, - secondTaskId, callback, "LaunchSplitPair"); - switch (launchData.getSplitLaunchType()) { - case SPLIT_TASK_TASK -> - mSystemUiProxy.startTasks(firstTaskId, optionsBundle, secondTaskId, - null /* options2 */, initialStagePosition, snapPosition, - remoteTransition, shellInstanceId); + if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) { + final RemoteTransition remoteTransition = getShellRemoteTransition(firstTaskId, + secondTaskId, callback, "LaunchSplitPair"); + switch (launchData.getSplitLaunchType()) { + case SPLIT_TASK_TASK -> + mSystemUiProxy.startTasks(firstTaskId, optionsBundle, secondTaskId, + null /* options2 */, initialStagePosition, snapPosition, + remoteTransition, shellInstanceId); - case SPLIT_TASK_PENDINGINTENT -> - mSystemUiProxy.startIntentAndTask(secondPI, secondUserId, optionsBundle, - firstTaskId, extrasBundle, initialStagePosition, snapPosition, - remoteTransition, shellInstanceId); + case SPLIT_TASK_PENDINGINTENT -> + mSystemUiProxy.startIntentAndTask(secondPI, secondUserId, optionsBundle, + firstTaskId, extrasBundle, initialStagePosition, snapPosition, + remoteTransition, shellInstanceId); - case SPLIT_TASK_SHORTCUT -> - mSystemUiProxy.startShortcutAndTask(secondShortcut, optionsBundle, - firstTaskId, null /*options2*/, initialStagePosition, snapPosition, - remoteTransition, shellInstanceId); + case SPLIT_TASK_SHORTCUT -> + mSystemUiProxy.startShortcutAndTask(secondShortcut, optionsBundle, + firstTaskId, null /*options2*/, initialStagePosition, snapPosition, + remoteTransition, shellInstanceId); - case SPLIT_PENDINGINTENT_TASK -> - mSystemUiProxy.startIntentAndTask(firstPI, firstUserId, optionsBundle, - secondTaskId, null /*options2*/, initialStagePosition, snapPosition, - remoteTransition, shellInstanceId); + case SPLIT_PENDINGINTENT_TASK -> + mSystemUiProxy.startIntentAndTask(firstPI, firstUserId, optionsBundle, + secondTaskId, null /*options2*/, initialStagePosition, snapPosition, + remoteTransition, shellInstanceId); - case SPLIT_PENDINGINTENT_PENDINGINTENT -> - mSystemUiProxy.startIntents(firstPI, firstUserId, firstShortcut, - optionsBundle, secondPI, secondUserId, secondShortcut, extrasBundle, - initialStagePosition, snapPosition, remoteTransition, - shellInstanceId); + case SPLIT_PENDINGINTENT_PENDINGINTENT -> + mSystemUiProxy.startIntents(firstPI, firstUserId, firstShortcut, + optionsBundle, secondPI, secondUserId, secondShortcut, extrasBundle, + initialStagePosition, snapPosition, remoteTransition, + shellInstanceId); - case SPLIT_SHORTCUT_TASK -> - mSystemUiProxy.startShortcutAndTask(firstShortcut, optionsBundle, - secondTaskId, null /*options2*/, initialStagePosition, snapPosition, - remoteTransition, shellInstanceId); + case SPLIT_SHORTCUT_TASK -> + mSystemUiProxy.startShortcutAndTask(firstShortcut, optionsBundle, + secondTaskId, null /*options2*/, initialStagePosition, snapPosition, + remoteTransition, shellInstanceId); + } + } else { + final RemoteAnimationAdapter adapter = getLegacyRemoteAdapter(firstTaskId, secondTaskId, + callback); + switch (launchData.getSplitLaunchType()) { + case SPLIT_TASK_TASK -> + mSystemUiProxy.startTasksWithLegacyTransition(firstTaskId, optionsBundle, + secondTaskId, null /* options2 */, initialStagePosition, + snapPosition, adapter, shellInstanceId); + + case SPLIT_TASK_PENDINGINTENT -> + mSystemUiProxy.startIntentAndTaskWithLegacyTransition(secondPI, + secondUserId, optionsBundle, firstTaskId, null /*options2*/, + initialStagePosition, snapPosition, adapter, shellInstanceId); + + case SPLIT_TASK_SHORTCUT -> + mSystemUiProxy.startShortcutAndTaskWithLegacyTransition(secondShortcut, + optionsBundle, firstTaskId, null /*options2*/, initialStagePosition, + snapPosition, adapter, shellInstanceId); + + case SPLIT_PENDINGINTENT_TASK -> + mSystemUiProxy.startIntentAndTaskWithLegacyTransition(firstPI, firstUserId, + optionsBundle, secondTaskId, null /*options2*/, + initialStagePosition, snapPosition, adapter, shellInstanceId); + + case SPLIT_PENDINGINTENT_PENDINGINTENT -> + mSystemUiProxy.startIntentsWithLegacyTransition(firstPI, firstUserId, + firstShortcut, optionsBundle, secondPI, secondUserId, + secondShortcut, null /*options2*/, initialStagePosition, + snapPosition, adapter, shellInstanceId); + + case SPLIT_SHORTCUT_TASK -> + mSystemUiProxy.startShortcutAndTaskWithLegacyTransition(firstShortcut, + optionsBundle, secondTaskId, null /*options2*/, + initialStagePosition, snapPosition, adapter, shellInstanceId); + } } - } /** @@ -503,9 +540,9 @@ public class SplitSelectStateController { * GroupedTaskView, int, int, int, Consumer, boolean, int, RemoteTransition)} */ public void launchExistingSplitPair(@Nullable GroupedTaskView groupedTaskView, - int firstTaskId, int secondTaskId, @StagePosition int stagePosition, - Consumer callback, boolean freezeTaskList, - @PersistentSnapPosition int snapPosition) { + int firstTaskId, int secondTaskId, @StagePosition int stagePosition, + Consumer callback, boolean freezeTaskList, + @PersistentSnapPosition int snapPosition) { launchExistingSplitPair( groupedTaskView, firstTaskId, @@ -528,9 +565,9 @@ public class SplitSelectStateController { * NOTE: This is not to be used to launch AppPairs. */ public void launchExistingSplitPair(@Nullable GroupedTaskView groupedTaskView, - int firstTaskId, int secondTaskId, @StagePosition int stagePosition, - Consumer callback, boolean freezeTaskList, - @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition) { + int firstTaskId, int secondTaskId, @StagePosition int stagePosition, + Consumer callback, boolean freezeTaskList, + @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition) { mLaunchingTaskView = groupedTaskView; final ActivityOptions options1 = ActivityOptions.makeBasic(); if (freezeTaskList) { @@ -538,13 +575,20 @@ public class SplitSelectStateController { } Bundle optionsBundle = options1.toBundle(); - final RemoteTransition transition = remoteTransition == null - ? getRemoteTransition( - firstTaskId, secondTaskId, callback, "LaunchExistingPair") - : remoteTransition; - mSystemUiProxy.startTasks(firstTaskId, optionsBundle, secondTaskId, null /* options2 */, - stagePosition, snapPosition, transition, null /*shellInstanceId*/); - + if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) { + final RemoteTransition transition = remoteTransition == null + ? getShellRemoteTransition( + firstTaskId, secondTaskId, callback, "LaunchExistingPair") + : remoteTransition; + mSystemUiProxy.startTasks(firstTaskId, optionsBundle, secondTaskId, null /* options2 */, + stagePosition, snapPosition, transition, null /*shellInstanceId*/); + } else { + final RemoteAnimationAdapter adapter = getLegacyRemoteAdapter(firstTaskId, + secondTaskId, callback); + mSystemUiProxy.startTasksWithLegacyTransition(firstTaskId, optionsBundle, secondTaskId, + null /* options2 */, stagePosition, snapPosition, adapter, + null /*shellInstanceId*/); + } } /** @@ -570,24 +614,44 @@ public class SplitSelectStateController { ActivityThread.currentActivityThread().getApplicationThread(), "LaunchAppFullscreen"); InstanceId instanceId = mSessionInstanceIds.first; - switch (launchData.getSplitLaunchType()) { - case SPLIT_SINGLE_TASK_FULLSCREEN -> mSystemUiProxy.startTasks(firstTaskId, - optionsBundle, secondTaskId, null /* options2 */, initialStagePosition, - SNAP_TO_2_50_50, remoteTransition, instanceId); - case SPLIT_SINGLE_INTENT_FULLSCREEN -> mSystemUiProxy.startIntentAndTask(firstPI, - firstUserId, optionsBundle, secondTaskId, null /*options2*/, - initialStagePosition, SNAP_TO_2_50_50, remoteTransition, instanceId); - case SPLIT_SINGLE_SHORTCUT_FULLSCREEN -> mSystemUiProxy.startShortcutAndTask( - initialShortcut, optionsBundle, firstTaskId, null /* options2 */, - initialStagePosition, SNAP_TO_2_50_50, remoteTransition, instanceId); + if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) { + switch (launchData.getSplitLaunchType()) { + case SPLIT_SINGLE_TASK_FULLSCREEN -> mSystemUiProxy.startTasks(firstTaskId, + optionsBundle, secondTaskId, null /* options2 */, initialStagePosition, + SNAP_TO_50_50, remoteTransition, instanceId); + case SPLIT_SINGLE_INTENT_FULLSCREEN -> mSystemUiProxy.startIntentAndTask(firstPI, + firstUserId, optionsBundle, secondTaskId, null /*options2*/, + initialStagePosition, SNAP_TO_50_50, remoteTransition, instanceId); + case SPLIT_SINGLE_SHORTCUT_FULLSCREEN -> mSystemUiProxy.startShortcutAndTask( + initialShortcut, optionsBundle, firstTaskId, null /* options2 */, + initialStagePosition, SNAP_TO_50_50, remoteTransition, instanceId); + } + } else { + final RemoteAnimationAdapter adapter = getLegacyRemoteAdapter(firstTaskId, + secondTaskId, callback); + switch (launchData.getSplitLaunchType()) { + case SPLIT_SINGLE_TASK_FULLSCREEN -> mSystemUiProxy.startTasksWithLegacyTransition( + firstTaskId, optionsBundle, secondTaskId, null /* options2 */, + initialStagePosition, SNAP_TO_50_50, adapter, instanceId); + case SPLIT_SINGLE_INTENT_FULLSCREEN -> + mSystemUiProxy.startIntentAndTaskWithLegacyTransition(firstPI, firstUserId, + optionsBundle, secondTaskId, null /*options2*/, + initialStagePosition, SNAP_TO_50_50, adapter, instanceId); + case SPLIT_SINGLE_SHORTCUT_FULLSCREEN -> + mSystemUiProxy.startShortcutAndTaskWithLegacyTransition( + initialShortcut, optionsBundle, firstTaskId, null /* options2 */, + initialStagePosition, SNAP_TO_50_50, adapter, instanceId); + } } } /** * Init {@code SplitFromDesktopController} */ - public void initSplitFromDesktopController(QuickstepLauncher launcher) { - initSplitFromDesktopController(new SplitFromDesktopController(launcher)); + public void initSplitFromDesktopController(QuickstepLauncher launcher, + OverviewComponentObserver overviewComponentObserver) { + initSplitFromDesktopController( + new SplitFromDesktopController(launcher, overviewComponentObserver)); } @VisibleForTesting @@ -595,14 +659,22 @@ public class SplitSelectStateController { mSplitFromDesktopController = controller; } - private RemoteTransition getRemoteTransition(int firstTaskId, int secondTaskId, - @Nullable Consumer callback, String transitionName) { + private RemoteTransition getShellRemoteTransition(int firstTaskId, int secondTaskId, + @Nullable Consumer callback, String transitionName) { final RemoteSplitLaunchTransitionRunner animationRunner = new RemoteSplitLaunchTransitionRunner(firstTaskId, secondTaskId, callback); return new RemoteTransition(animationRunner, ActivityThread.currentActivityThread().getApplicationThread(), transitionName); } + private RemoteAnimationAdapter getLegacyRemoteAdapter(int firstTaskId, int secondTaskId, + @Nullable Consumer callback) { + final RemoteSplitLaunchAnimationRunner animationRunner = + new RemoteSplitLaunchAnimationRunner(firstTaskId, secondTaskId, callback); + return new RemoteAnimationAdapter(animationRunner, 300, 150, + ActivityThread.currentActivityThread().getApplicationThread()); + } + /** * Will initialize {@link #mSessionInstanceIds} if null and log the first split event from * {@link #mSplitSelectDataHolder} @@ -655,12 +727,7 @@ public class SplitSelectStateController { return mSplitAnimationController; } - /** - * Set params to invoke a trace session for the given view and CUJ when we begin animating the - * split launch AFTER we get a response from Shell. - */ - public void setLaunchingCuj(View launchingView, int launchCuj) { - mLaunchingViewCuj = launchingView; + public void setLaunchingCuj(int launchCuj) { mLaunchCuj = launchCuj; } @@ -674,16 +741,16 @@ public class SplitSelectStateController { private Consumer mFinishCallback; RemoteSplitLaunchTransitionRunner(int initialTaskId, int secondTaskId, - @Nullable Consumer callback) { + @Nullable Consumer callback) { mInitialTaskId = initialTaskId; mSecondTaskId = secondTaskId; mFinishCallback = callback; } @Override - public void startAnimation(IBinder transition, TransitionInfo transitionInfo, - SurfaceControl.Transaction t, - IRemoteTransitionFinishedCallback finishedCallback) { + public void startAnimation(IBinder transition, TransitionInfo info, + SurfaceControl.Transaction t, + IRemoteTransitionFinishedCallback finishedCallback) { final Runnable finishAdapter = () -> { try { finishedCallback.onTransitionFinished(null /* wct */, null /* sct */); @@ -698,9 +765,6 @@ public class SplitSelectStateController { && mLaunchingTaskView.getRecentsView() != null && mLaunchingTaskView.getRecentsView().isTaskViewVisible( mLaunchingTaskView); - if (mLaunchingViewCuj != null && mLaunchCuj != -1) { - InteractionJankMonitorWrapper.begin(mLaunchingViewCuj, mLaunchCuj); - } mSplitAnimationController.playSplitLaunchAnimation( shouldLaunchFromTaskView ? mLaunchingTaskView : null, mLaunchingIconView, @@ -711,11 +775,10 @@ public class SplitSelectStateController { null /* nonApps */, mStateManager, mDepthController, - transitionInfo, t, () -> { + info, t, () -> { finishAdapter.run(); cleanup(true /*success*/); - }, - QuickStepContract.getWindowCornerRadius(mContainer.asContext())); + }); }); } @@ -742,15 +805,60 @@ public class SplitSelectStateController { } /** - * To be called whenever we exit split selection state. This should be the + * LEGACY + * Remote animation runner for animation to launch an app. + */ + private class RemoteSplitLaunchAnimationRunner extends RemoteAnimationRunnerCompat { + + private final int mInitialTaskId; + private final int mSecondTaskId; + private final Consumer mSuccessCallback; + + RemoteSplitLaunchAnimationRunner(int initialTaskId, int secondTaskId, + @Nullable Consumer successCallback) { + mInitialTaskId = initialTaskId; + mSecondTaskId = secondTaskId; + mSuccessCallback = successCallback; + } + + @Override + public void onAnimationStart(int transit, RemoteAnimationTarget[] apps, + RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps, + Runnable finishedCallback) { + postAsyncCallback(mHandler, + () -> mSplitAnimationController.playSplitLaunchAnimation(mLaunchingTaskView, + mLaunchingIconView, mInitialTaskId, mSecondTaskId, apps, wallpapers, + nonApps, mStateManager, mDepthController, null /* info */, null /* t */, + () -> { + finishedCallback.run(); + if (mSuccessCallback != null) { + mSuccessCallback.accept(true); + } + resetState(); + })); + } + + @Override + public void onAnimationCancelled() { + postAsyncCallback(mHandler, () -> { + if (mSuccessCallback != null) { + // Launching legacy tasks while recents animation is running will always cause + // onAnimationCancelled to be called (should be fixed w/ shell transitions?) + mSuccessCallback.accept(mRecentsAnimationRunning); + } + resetState(); + }); + } + } + + /** + * To be called whenever we exit split selection state. If + * {@link FeatureFlags#enableSplitContextually()} is set, this should be the * central way split is getting reset, which should then go through the callbacks to reset * other state. */ public void resetState() { mSplitSelectDataHolder.resetState(); - if (!mIsDestroyed) { - mContainer.getOverviewPanel().resetDesktopTaskFromSplitSelectState(); - } dispatchOnSplitSelectionExit(); mRecentsAnimationRunning = false; mLaunchingTaskView = null; @@ -765,7 +873,6 @@ public class SplitSelectStateController { InteractionJankMonitorWrapper.end(mLaunchCuj); } mLaunchCuj = -1; - mLaunchingViewCuj = null; if (mSessionInstanceIds != null) { mStatsLogManager.logger() @@ -850,24 +957,32 @@ public class SplitSelectStateController { private final int mSplitPlaceholderSize; private final int mSplitPlaceholderInset; private ActivityManager.RunningTaskInfo mTaskInfo; - private DesktopSplitSelectListenerImpl mSplitSelectListener; + private ISplitSelectListener mSplitSelectListener; private Drawable mAppIcon; - public SplitFromDesktopController(QuickstepLauncher launcher) { + public SplitFromDesktopController(QuickstepLauncher launcher, + OverviewComponentObserver overviewComponentObserver) { mLauncher = launcher; - mOverviewComponentObserver = OverviewComponentObserver.INSTANCE.get(launcher); + mOverviewComponentObserver = overviewComponentObserver; mSplitPlaceholderSize = mLauncher.getResources().getDimensionPixelSize( R.dimen.split_placeholder_size); mSplitPlaceholderInset = mLauncher.getResources().getDimensionPixelSize( R.dimen.split_placeholder_inset); - mSplitSelectListener = new DesktopSplitSelectListenerImpl(this); + mSplitSelectListener = new ISplitSelectListener.Stub() { + @Override + public boolean onRequestSplitSelect(ActivityManager.RunningTaskInfo taskInfo, + int splitPosition, Rect taskBounds) { + MAIN_EXECUTOR.execute(() -> enterSplitSelect(taskInfo, splitPosition, + taskBounds)); + return true; + } + }; SystemUiProxy.INSTANCE.get(mLauncher).registerSplitSelectListener(mSplitSelectListener); } void onDestroy() { SystemUiProxy.INSTANCE.get(mLauncher).unregisterSplitSelectListener( mSplitSelectListener); - mSplitSelectListener.release(); mSplitSelectListener = null; } @@ -878,34 +993,32 @@ public class SplitSelectStateController { * @param taskBounds the bounds of the task, used for {@link FloatingTaskView} animation */ public void enterSplitSelect(ActivityManager.RunningTaskInfo taskInfo, - int splitPosition, Rect taskBounds) { + int splitPosition, Rect taskBounds) { mTaskInfo = taskInfo; String packageName = mTaskInfo.realActivity.getPackageName(); PackageManager pm = mLauncher.getApplicationContext().getPackageManager(); IconProvider provider = new IconProvider(mLauncher.getApplicationContext()); try { mAppIcon = provider.getIcon(pm.getActivityInfo(mTaskInfo.baseActivity, - PackageManager.ComponentInfoFlags.of(0))); + PackageManager.ComponentInfoFlags.of(0))); } catch (PackageManager.NameNotFoundException e) { Log.w(TAG, "Package not found: " + packageName, e); } RecentsAnimationCallbacks callbacks = new RecentsAnimationCallbacks( - SystemUiProxy.INSTANCE.get(mLauncher.getApplicationContext())); + SystemUiProxy.INSTANCE.get(mLauncher.getApplicationContext()), + false /* allowMinimizeSplitScreen */); DesktopSplitRecentsAnimationListener listener = new DesktopSplitRecentsAnimationListener(splitPosition, taskBounds); - callbacks.addListener(listener); - UI_HELPER_EXECUTOR.execute(() -> { - // Transition from app to enter stage split in launcher with recents animation - final ActivityOptions options = ActivityOptions.makeBasic(); - options.setPendingIntentBackgroundActivityStartMode( - ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS); - options.setTransientLaunch(); - SystemUiProxy.INSTANCE.get(mLauncher.getApplicationContext()) - .startRecentsActivity( - mOverviewComponentObserver.getOverviewIntent(), options, - callbacks, false /* useSyntheticRecentsTransition */); + MAIN_EXECUTOR.execute(() -> { + callbacks.addListener(listener); + UI_HELPER_EXECUTOR.execute( + // Transition from app to enter stage split in launcher with + // recents animation. + () -> ActivityManagerWrapper.getInstance().startRecentsActivity( + mOverviewComponentObserver.getOverviewIntent(), + SystemClock.uptimeMillis(), callbacks, null, null)); }); } @@ -922,11 +1035,11 @@ public class SplitSelectStateController { @Override public void onRecentsAnimationStart(RecentsAnimationController controller, - RecentsAnimationTargets targets, TransitionInfo transitionInfo) { + RecentsAnimationTargets targets) { StatsLogManager.LauncherEvent launcherDesktopSplitEvent = mSplitPosition == STAGE_POSITION_BOTTOM_OR_RIGHT ? - LAUNCHER_DESKTOP_MODE_SPLIT_RIGHT_BOTTOM : - LAUNCHER_DESKTOP_MODE_SPLIT_LEFT_TOP; + LAUNCHER_DESKTOP_MODE_SPLIT_RIGHT_BOTTOM : + LAUNCHER_DESKTOP_MODE_SPLIT_LEFT_TOP; setInitialTaskSelect(mTaskInfo, mSplitPosition, null, launcherDesktopSplitEvent); @@ -941,10 +1054,6 @@ public class SplitSelectStateController { mLauncher, mLauncher.getDragLayer(), null /* thumbnail */, mAppIcon, new RectF()); - floatingTaskView.setOnClickListener(view -> - getSplitAnimationController() - .playAnimPlaceholderToFullscreen(mContainer, view, - Optional.of(() -> resetState()))); floatingTaskView.setAlpha(1); floatingTaskView.addStagingAnimation(anim, mTaskBounds, mTempRect, false /* fadeWithThumbnail */, true /* isStagedTask */); @@ -953,16 +1062,7 @@ public class SplitSelectStateController { anim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { - controller.finish( - true /* toRecents */, - () -> { - LauncherTaskbarUIController controller = - mLauncher.getTaskbarUIController(); - if (controller != null) { - controller.updateTaskbarLauncherStateGoingHome(); - } - - }, + controller.finish(true /* toRecents */, null /* onFinishComplete */, false /* sendUserLeaveHint */); } @Override @@ -970,49 +1070,9 @@ public class SplitSelectStateController { SystemUiProxy.INSTANCE.get(mLauncher.getApplicationContext()) .onDesktopSplitSelectAnimComplete(mTaskInfo); } - @Override - public void onAnimationCancel(Animator animation) { - mLauncher.getDragLayer().removeView(floatingTaskView); - getSplitAnimationController() - .removeSplitInstructionsView(mLauncher); - resetState(); - } }); - anim.add(getSplitAnimationController() - .getShowSplitInstructionsAnim(mLauncher).buildAnim()); anim.buildAnim().start(); } } } - - /** - * Wrapper for the ISplitSelectListener stub to prevent lingering references to the launcher - * activity via the controller. - */ - private static class DesktopSplitSelectListenerImpl extends ISplitSelectListener.Stub { - - private SplitFromDesktopController mController; - - DesktopSplitSelectListenerImpl(@NonNull SplitFromDesktopController controller) { - mController = controller; - } - - /** - * Clears any references to the controller. - */ - void release() { - mController = null; - } - - @Override - public boolean onRequestSplitSelect(ActivityManager.RunningTaskInfo taskInfo, - int splitPosition, Rect taskBounds) { - MAIN_EXECUTOR.execute(() -> { - if (mController != null) { - mController.enterSplitSelect(taskInfo, splitPosition, taskBounds); - } - }); - return true; - } - } -} +} \ No newline at end of file diff --git a/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java b/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java index d3390b4ed9..4962367bd2 100644 --- a/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java +++ b/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java @@ -16,7 +16,6 @@ package com.android.quickstep.util; -import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; import android.animation.Animator; @@ -49,11 +48,8 @@ import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.uioverrides.QuickstepLauncher; import com.android.quickstep.views.FloatingTaskView; import com.android.quickstep.views.RecentsView; -import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.system.InteractionJankMonitorWrapper; -import java.util.Collections; - /** Handles when the stage split lands on the home screen. */ public class SplitToWorkspaceController { @@ -90,7 +86,7 @@ public class SplitToWorkspaceController { MODEL_EXECUTOR.execute(() -> { PackageItemInfo infoInOut = new PackageItemInfo(pendingIntent.getCreatorPackage(), pendingIntent.getCreatorUserHandle()); - mIconCache.getTitleAndIconForApp(infoInOut, DEFAULT_LOOKUP_FLAG); + mIconCache.getTitleAndIconForApp(infoInOut, false); Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); view.post(() -> { @@ -137,20 +133,10 @@ public class SplitToWorkspaceController { // Use Launcher's default click handler return false; } - // Check for background task matching this tag; if we find one, set second task - // via task instead of intent so the bounds and windowing mode will be corrected. - mController.findLastActiveTasksAndRunCallback( - Collections.singletonList(((ItemInfo) tag).getComponentKey()), - false /* findExactPairMatch */, - foundTasks -> { - Task foundTask = foundTasks[0]; - if (foundTask != null) { - mController.setSecondTask(foundTask, (ItemInfo) tag); - } else { - mController.setSecondTask(intent, user, (ItemInfo) tag); - } - startWorkspaceAnimation(view, null /*bitmap*/, bitmapInfo.newIcon(mLauncher)); - }); + + mController.setSecondTask(intent, user, (ItemInfo) tag); + + startWorkspaceAnimation(view, null /*bitmap*/, bitmapInfo.newIcon(mLauncher)); return true; } @@ -219,6 +205,6 @@ public class SplitToWorkspaceController { } private boolean shouldIgnoreSecondSplitLaunch() { - return !mController.isSplitSelectActive(); + return !FeatureFlags.enableSplitContextually() || !mController.isSplitSelectActive(); } } diff --git a/quickstep/src/com/android/quickstep/util/SplitWithKeyboardShortcutController.java b/quickstep/src/com/android/quickstep/util/SplitWithKeyboardShortcutController.java index 0e2713981d..5e42b9001b 100644 --- a/quickstep/src/com/android/quickstep/util/SplitWithKeyboardShortcutController.java +++ b/quickstep/src/com/android/quickstep/util/SplitWithKeyboardShortcutController.java @@ -16,6 +16,7 @@ package com.android.quickstep.util; +import static com.android.launcher3.config.FeatureFlags.enableSplitContextually; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_KEYBOARD_SHORTCUT_SPLIT_LEFT_TOP; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_KEYBOARD_SHORTCUT_SPLIT_RIGHT_BOTTOM; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; @@ -26,10 +27,9 @@ import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITIO import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.app.ActivityManager; -import android.app.ActivityOptions; import android.graphics.Rect; import android.graphics.RectF; -import android.window.TransitionInfo; +import android.os.SystemClock; import androidx.annotation.BinderThread; @@ -40,6 +40,7 @@ import com.android.launcher3.uioverrides.QuickstepLauncher; import com.android.quickstep.OverviewComponentObserver; import com.android.quickstep.RecentsAnimationCallbacks; import com.android.quickstep.RecentsAnimationController; +import com.android.quickstep.RecentsAnimationDeviceState; import com.android.quickstep.RecentsAnimationTargets; import com.android.quickstep.RecentsModel; import com.android.quickstep.SystemUiProxy; @@ -54,16 +55,20 @@ public class SplitWithKeyboardShortcutController { private final QuickstepLauncher mLauncher; private final SplitSelectStateController mController; + private final RecentsAnimationDeviceState mDeviceState; private final OverviewComponentObserver mOverviewComponentObserver; private final int mSplitPlaceholderSize; private final int mSplitPlaceholderInset; - public SplitWithKeyboardShortcutController( - QuickstepLauncher launcher, SplitSelectStateController controller) { + public SplitWithKeyboardShortcutController(QuickstepLauncher launcher, + SplitSelectStateController controller, + OverviewComponentObserver overviewComponentObserver, + RecentsAnimationDeviceState deviceState) { mLauncher = launcher; mController = controller; - mOverviewComponentObserver = OverviewComponentObserver.INSTANCE.get(launcher); + mDeviceState = deviceState; + mOverviewComponentObserver = overviewComponentObserver; mSplitPlaceholderSize = mLauncher.getResources().getDimensionPixelSize( R.dimen.split_placeholder_size); @@ -73,48 +78,49 @@ public class SplitWithKeyboardShortcutController { @BinderThread public void enterStageSplit(boolean leftOrTop) { - if (TopTaskTracker.INSTANCE.get(mLauncher).getRunningSplitTaskIds().length == 2) { - // Do not enter stage split from keyboard shortcuts if the user is already in split + if (!enableSplitContextually() || + // Do not enter stage split from keyboard shortcuts if the user is already in split + TopTaskTracker.INSTANCE.get(mLauncher).getRunningSplitTaskIds().length == 2) { return; } RecentsAnimationCallbacks callbacks = new RecentsAnimationCallbacks( - SystemUiProxy.INSTANCE.get(mLauncher.getApplicationContext())); + SystemUiProxy.INSTANCE.get(mLauncher.getApplicationContext()), + false /* allowMinimizeSplitScreen */); SplitWithKeyboardShortcutRecentsAnimationListener listener = new SplitWithKeyboardShortcutRecentsAnimationListener(leftOrTop); MAIN_EXECUTOR.execute(() -> { callbacks.addListener(listener); - UI_HELPER_EXECUTOR.execute(() -> { - // Transition from fullscreen app to enter stage split in launcher with - // recents animation - final ActivityOptions options = ActivityOptions.makeBasic(); - options.setPendingIntentBackgroundActivityStartMode( - ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS); - options.setTransientLaunch(); - SystemUiProxy.INSTANCE.get(mLauncher.getApplicationContext()) - .startRecentsActivity(mOverviewComponentObserver.getOverviewIntent(), - ActivityOptions.makeBasic(), callbacks, - false /* useSyntheticRecentsTransition */); - }); + UI_HELPER_EXECUTOR.execute( + // Transition from fullscreen app to enter stage split in launcher with + // recents animation. + () -> ActivityManagerWrapper.getInstance().startRecentsActivity( + mOverviewComponentObserver.getOverviewIntent(), + SystemClock.uptimeMillis(), callbacks, null, null)); }); } + public void onDestroy() { + mOverviewComponentObserver.onDestroy(); + mDeviceState.destroy(); + } + private class SplitWithKeyboardShortcutRecentsAnimationListener implements RecentsAnimationCallbacks.RecentsAnimationListener { private final boolean mLeftOrTop; private final Rect mTempRect = new Rect(); - private final ActivityManager.RunningTaskInfo mRunningTaskInfo; private SplitWithKeyboardShortcutRecentsAnimationListener(boolean leftOrTop) { mLeftOrTop = leftOrTop; - mRunningTaskInfo = ActivityManagerWrapper.getInstance().getRunningTask(); } @Override public void onRecentsAnimationStart(RecentsAnimationController controller, - RecentsAnimationTargets targets, TransitionInfo transitionInfo) { - mController.setInitialTaskSelect(mRunningTaskInfo, + RecentsAnimationTargets targets) { + ActivityManager.RunningTaskInfo runningTaskInfo = + ActivityManagerWrapper.getInstance().getRunningTask(); + mController.setInitialTaskSelect(runningTaskInfo, mLeftOrTop ? STAGE_POSITION_TOP_OR_LEFT : STAGE_POSITION_BOTTOM_OR_RIGHT, null /* itemInfo */, mLeftOrTop ? LAUNCHER_KEYBOARD_SHORTCUT_SPLIT_LEFT_TOP @@ -130,15 +136,14 @@ public class SplitWithKeyboardShortcutController { RectF startingTaskRect = new RectF(); final FloatingTaskView floatingTaskView = FloatingTaskView.getFloatingTaskView( mLauncher, mLauncher.getDragLayer(), - controller.screenshotTask(mRunningTaskInfo.taskId).getThumbnail(), + controller.screenshotTask(runningTaskInfo.taskId).getThumbnail(), null /* icon */, startingTaskRect); - Task task = Task.from(new Task.TaskKey(mRunningTaskInfo), mRunningTaskInfo, - false /* isLocked */); RecentsModel.INSTANCE.get(mLauncher.getApplicationContext()) .getIconCache() - .getIconInBackground( - task, - (icon, contentDescription, title) -> floatingTaskView.setIcon(icon)); + .updateIconInBackground( + Task.from(new Task.TaskKey(runningTaskInfo), runningTaskInfo, + false /* isLocked */), + (task) -> floatingTaskView.setIcon(task.icon)); floatingTaskView.setAlpha(1); floatingTaskView.addStagingAnimation(anim, startingTaskRect, mTempRect, false /* fadeWithThumbnail */, true /* isStagedTask */); diff --git a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java index a2856a6012..997a842dc2 100644 --- a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java +++ b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java @@ -49,7 +49,6 @@ import com.android.launcher3.celllayout.CellLayoutLayoutParams; import com.android.launcher3.statehandlers.DepthController; import com.android.launcher3.states.StateAnimationConfig; import com.android.launcher3.uioverrides.QuickstepLauncher; -import com.android.launcher3.util.DisplayController; import com.android.launcher3.util.DynamicResource; import com.android.quickstep.views.RecentsView; import com.android.systemui.plugins.ResourceProvider; @@ -64,7 +63,8 @@ public class StaggeredWorkspaceAnim { private static final int APP_CLOSE_ROW_START_DELAY_MS = 10; // Should be used for animations running alongside this StaggeredWorkspaceAnim. public static final int DURATION_MS = 250; - private final int mTaskbarDurationInMs; + public static final int DURATION_TASKBAR_MS = + QuickstepTransitionManager.getTaskbarToHomeDuration(); private static final float MAX_VELOCITY_PX_PER_S = 22f; @@ -81,10 +81,6 @@ public class StaggeredWorkspaceAnim { public StaggeredWorkspaceAnim(QuickstepLauncher launcher, float velocity, boolean animateOverviewScrim, @Nullable View ignoredView, boolean staggerWorkspace) { - boolean isPinnedTaskbarAndNotInDesktopMode = DisplayController.isPinnedTaskbar(launcher) - && !DisplayController.isInDesktopMode(launcher); - mTaskbarDurationInMs = QuickstepTransitionManager.getTaskbarToHomeDuration( - isPinnedTaskbarAndNotInDesktopMode); prepareToAnimate(launcher, animateOverviewScrim); mIgnoredView = ignoredView; @@ -97,7 +93,7 @@ public class StaggeredWorkspaceAnim { .getDimensionPixelSize(R.dimen.swipe_up_max_workspace_trans_y); DeviceProfile grid = launcher.getDeviceProfile(); - long duration = grid.isTaskbarPresent ? mTaskbarDurationInMs : DURATION_MS; + long duration = grid.isTaskbarPresent ? DURATION_TASKBAR_MS : DURATION_MS; if (staggerWorkspace) { Workspace workspace = launcher.getWorkspace(); Hotseat hotseat = launcher.getHotseat(); diff --git a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java index 49c69e248c..c3a7bfeac5 100644 --- a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java +++ b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java @@ -18,7 +18,6 @@ package com.android.quickstep.util; import android.animation.Animator; import android.animation.RectEvaluator; -import android.app.PictureInPictureParams; import android.content.ComponentName; import android.content.Context; import android.content.pm.ActivityInfo; @@ -26,7 +25,6 @@ import android.graphics.Matrix; import android.graphics.Rect; import android.graphics.RectF; import android.util.Log; -import android.util.Rational; import android.view.Surface; import android.view.SurfaceControl; import android.view.View; @@ -41,7 +39,7 @@ import com.android.launcher3.icons.IconProvider; import com.android.quickstep.TaskAnimationManager; import com.android.systemui.shared.pip.PipSurfaceTransactionHelper; import com.android.systemui.shared.system.InteractionJankMonitorWrapper; -import com.android.wm.shell.shared.pip.PipContentOverlay; +import com.android.wm.shell.pip.PipContentOverlay; /** * Subclass of {@link RectFSpringAnim} that animates an Activity to PiP (picture-in-picture) window @@ -52,6 +50,8 @@ public class SwipePipToHomeAnimator extends RectFSpringAnim { private static final float END_PROGRESS = 1.0f; + private static final float PIP_ASPECT_RATIO_MISMATCH_THRESHOLD = 0.01f; + private final int mTaskId; private final ActivityInfo mActivityInfo; private final SurfaceControl mLeash; @@ -137,13 +137,8 @@ public class SwipePipToHomeAnimator extends RectFSpringAnim { mDestinationBoundsTransformed.set(destinationBoundsTransformed); mSurfaceTransactionHelper = new PipSurfaceTransactionHelper(cornerRadius, shadowRadius); - final Rational aspectRatio = new Rational( - destinationBounds.width(), destinationBounds.height()); + final float aspectRatio = destinationBounds.width() / (float) destinationBounds.height(); String reasonForCreateOverlay = null; // For debugging purpose. - - // Slightly larger app bounds to allow for off by 1 pixel source-rect-hint errors. - Rect overflowAppBounds = new Rect(appBounds.left - 1, appBounds.top - 1, - appBounds.right + 1, appBounds.bottom + 1); if (sourceRectHint.isEmpty()) { reasonForCreateOverlay = "Source rect hint is empty"; } else if (sourceRectHint.width() < destinationBounds.width() @@ -154,27 +149,40 @@ public class SwipePipToHomeAnimator extends RectFSpringAnim { // animation in this case. reasonForCreateOverlay = "Source rect hint is too small " + sourceRectHint; sourceRectHint.setEmpty(); - } else if (!overflowAppBounds.contains(sourceRectHint)) { + } else if (!appBounds.contains(sourceRectHint)) { // This is a situation in which the source hint rect is outside the app bounds, so it is // not a valid rectangle to use for cropping app surface reasonForCreateOverlay = "Source rect hint exceeds display bounds " + sourceRectHint; sourceRectHint.setEmpty(); - } else { - final Rational srcAspectRatio = new Rational( - sourceRectHint.width(), sourceRectHint.height()); - if (!PictureInPictureParams.isSameAspectRatio(destinationBounds, srcAspectRatio)) { - // The aspect ratio of destination bounds does not match source rect hint. - // We use the aspect ratio of source rect hint to check against destination bounds - // here to avoid upscaling error. - reasonForCreateOverlay = "Source rect hint:" + sourceRectHint - + " does not match destination bounds:" + destinationBounds; - sourceRectHint.setEmpty(); - } + } else if (Math.abs( + aspectRatio - (sourceRectHint.width() / (float) sourceRectHint.height())) + > PIP_ASPECT_RATIO_MISMATCH_THRESHOLD) { + // The source rect hint does not aspect ratio + reasonForCreateOverlay = "Source rect hint does not match aspect ratio " + + sourceRectHint + " aspect ratio " + aspectRatio; + sourceRectHint.setEmpty(); } if (sourceRectHint.isEmpty()) { - mSourceRectHint.set( - getEnterPipWithOverlaySrcRectHint(appBounds, aspectRatio.floatValue())); + // Crop a Rect matches the aspect ratio and pivots at the center point. + // To make the animation path simplified. + if ((appBounds.width() / (float) appBounds.height()) > aspectRatio) { + // use the full height. + mSourceRectHint.set(0, 0, + (int) (appBounds.height() * aspectRatio), appBounds.height()); + mSourceRectHint.offset( + (appBounds.width() - mSourceRectHint.width()) / 2, 0); + } else { + // use the full width. + mSourceRectHint.set(0, 0, + appBounds.width(), (int) (appBounds.width() / aspectRatio)); + mSourceRectHint.offset( + 0, (appBounds.height() - mSourceRectHint.height()) / 2); + } + + // Create a new overlay layer. We do not call detach on this instance, it's propagated + // to other classes like PipTaskOrganizer / RecentsAnimationController to complete + // the cleanup. mPipContentOverlay = new PipContentOverlay.PipAppIconOverlay(view.getContext(), mAppBounds, mDestinationBounds, new IconProvider(context).getIcon(mActivityInfo), appIconSizePx); @@ -217,26 +225,6 @@ public class SwipePipToHomeAnimator extends RectFSpringAnim { addOnUpdateListener(this::onAnimationUpdate); } - /** - * Crop a Rect matches the aspect ratio and pivots at the center point. - */ - private Rect getEnterPipWithOverlaySrcRectHint(Rect appBounds, float aspectRatio) { - final float appBoundsAspectRatio = appBounds.width() / (float) appBounds.height(); - final int width, height; - int left = appBounds.left; - int top = appBounds.top; - if (appBoundsAspectRatio < aspectRatio) { - width = appBounds.width(); - height = (int) (width / aspectRatio); - top = appBounds.top + (appBounds.height() - height) / 2; - } else { - height = appBounds.height(); - width = (int) (height * aspectRatio); - left = appBounds.left + (appBounds.width() - width) / 2; - } - return new Rect(left, top, left + width, top + height); - } - private void onAnimationUpdate(RectF currentRect, float progress) { if (mHasAnimationEnded) return; final SurfaceControl.Transaction tx = @@ -453,22 +441,13 @@ public class SwipePipToHomeAnimator extends RectFSpringAnim { return this; } - public Builder setDisplayCutoutInsets(@NonNull Rect displayCutoutInsets) { - mDisplayCutoutInsets = new Rect(displayCutoutInsets); - return this; - } - public SwipePipToHomeAnimator build() { if (mDestinationBoundsTransformed.isEmpty()) { mDestinationBoundsTransformed.set(mDestinationBounds); } // adjust the mSourceRectHint / mAppBounds by display cutout if applicable. if (mSourceRectHint != null && mDisplayCutoutInsets != null) { - if (mFromRotation == Surface.ROTATION_0) { - // TODO: this is to special case the issues on Foldable device - // with display cutout. - mSourceRectHint.offset(mDisplayCutoutInsets.left, mDisplayCutoutInsets.top); - } else if (mFromRotation == Surface.ROTATION_90) { + if (mFromRotation == Surface.ROTATION_90) { mSourceRectHint.offset(mDisplayCutoutInsets.left, mDisplayCutoutInsets.top); } else if (mFromRotation == Surface.ROTATION_270) { mAppBounds.inset(mDisplayCutoutInsets); @@ -482,6 +461,15 @@ public class SwipePipToHomeAnimator extends RectFSpringAnim { } } - private record RotatedPosition(float degree, float positionX, float positionY) { + private static class RotatedPosition { + private final float degree; + private final float positionX; + private final float positionY; + + private RotatedPosition(float degree, float positionX, float positionY) { + this.degree = degree; + this.positionX = positionX; + this.positionY = positionY; + } } } diff --git a/quickstep/src/com/android/quickstep/util/SystemUiFlagUtils.kt b/quickstep/src/com/android/quickstep/util/SystemUiFlagUtils.kt index 1ff05dae56..5f4388c918 100644 --- a/quickstep/src/com/android/quickstep/util/SystemUiFlagUtils.kt +++ b/quickstep/src/com/android/quickstep/util/SystemUiFlagUtils.kt @@ -47,22 +47,6 @@ object SystemUiFlagUtils { !hasAnyFlag(flags, QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY) } - /** - * Taskbar is hidden whenever the device is dreaming. The dreaming state includes the - * interactive dreams, AoD, screen off. Since the SYSUI_STATE_DEVICE_DREAMING only kicks in when - * the device is asleep, the second condition extends ensures that the transition from and to - * the WAKEFULNESS_ASLEEP state also hide the taskbar, and improves the taskbar hide/reveal - * animation timings. The Taskbar can show when dreaming if the glanceable hub is showing on - * top. - */ - @JvmStatic - fun isTaskbarHidden(@SystemUiStateFlags flags: Long): Boolean { - return ((hasAnyFlag(flags, QuickStepContract.SYSUI_STATE_DEVICE_DREAMING) && - !hasAnyFlag(flags, QuickStepContract.SYSUI_STATE_COMMUNAL_HUB_SHOWING)) || - (flags and QuickStepContract.SYSUI_STATE_WAKEFULNESS_MASK) != - QuickStepContract.WAKEFULNESS_AWAKE) - } - private fun hasAnyFlag(@SystemUiStateFlags flags: Long, flagMask: Long): Boolean { return (flags and flagMask) != 0L } diff --git a/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java b/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java index b3ab4df1b6..5653d932cd 100644 --- a/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java +++ b/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java @@ -15,7 +15,6 @@ */ package com.android.quickstep.util; -import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.view.Display.DEFAULT_DISPLAY; import android.content.Context; @@ -27,36 +26,24 @@ import android.view.WindowManager; import android.view.WindowMetrics; import com.android.internal.policy.SystemBarUtils; -import com.android.launcher3.dagger.LauncherAppSingleton; import com.android.launcher3.logging.FileLog; import com.android.launcher3.statehandlers.DesktopVisibilityController; import com.android.launcher3.util.WindowBounds; import com.android.launcher3.util.window.CachedDisplayInfo; import com.android.launcher3.util.window.WindowManagerProxy; -import com.android.quickstep.SystemUiProxy; -import com.android.window.flags.Flags; -import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; +import com.android.quickstep.LauncherActivityInterface; -import java.util.Collections; import java.util.List; import java.util.Set; -import javax.inject.Inject; - /** - * Extension of {@link WindowManagerProxy} with some assumption for the default system Launcher + * Extension of {@link WindowManagerProxy} with some assumption for the default + * system Launcher */ -@LauncherAppSingleton public class SystemWindowManagerProxy extends WindowManagerProxy { - // LC-Note: This is pretty much unused by Launcher3, see [LawnchairWindowManagerProxy] - private final DesktopVisibilityController mDesktopVisibilityController; - - - @Inject - public SystemWindowManagerProxy(DesktopVisibilityController desktopVisibilityController) { + public SystemWindowManagerProxy(Context context) { super(true); - mDesktopVisibilityController = desktopVisibilityController; } @Override @@ -66,55 +53,10 @@ public class SystemWindowManagerProxy extends WindowManagerProxy { } @Override - public void registerDesktopVisibilityListener(DesktopVisibilityListener listener) { - mDesktopVisibilityController.registerDesktopVisibilityListener(listener); - } - - @Override - public void unregisterDesktopVisibilityListener(DesktopVisibilityListener listener) { - mDesktopVisibilityController.unregisterDesktopVisibilityListener(listener); - } - - @Override - public boolean isInDesktopMode(int displayId) { - return mDesktopVisibilityController.isInDesktopMode(displayId); - } - - @Override - public boolean showLockedTaskbarOnHome(Context displayInfoContext) { - if (!DesktopModeStatus.canEnterDesktopMode(displayInfoContext)) { - return false; - } - if (!DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(displayInfoContext)) { - return false; - } - final boolean isFreeformDisplay = displayInfoContext.getResources().getConfiguration() - .windowConfiguration.getWindowingMode() == WINDOWING_MODE_FREEFORM; - return isFreeformDisplay; - } - - @Override - public boolean showDesktopTaskbarForFreeformDisplay(Context displayInfoContext) { - if (!DesktopModeStatus.canEnterDesktopMode(displayInfoContext)) { - return false; - } - - if (!DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(displayInfoContext)) { - return false; - } - - if (!Flags.enableDesktopTaskbarOnFreeformDisplays()) { - return false; - } - - final boolean isFreeformDisplay = displayInfoContext.getResources().getConfiguration() - .windowConfiguration.getWindowingMode() == WINDOWING_MODE_FREEFORM; - return isFreeformDisplay; - } - - @Override - public boolean isHomeVisible(Context context) { - return SystemUiProxy.INSTANCE.get(context).getHomeVisibilityState().isHomeVisible(); + public boolean isInDesktopMode() { + DesktopVisibilityController desktopController = LauncherActivityInterface.INSTANCE + .getDesktopVisibilityController(); + return desktopController != null && desktopController.areDesktopTasksVisible(); } @Override @@ -125,8 +67,10 @@ public class SystemWindowManagerProxy extends WindowManagerProxy { @Override protected int getStatusBarHeight(Context context, boolean isPortrait, int statusBarInset) { - // See b/264656380, calculate the status bar height manually as the inset in the system - // server might not be updated by this point yet causing extra DeviceProfile updates + // See b/264656380, calculate the status bar height manually as the inset in the + // system + // server might not be updated by this point yet causing extra DeviceProfile + // updates return SystemBarUtils.getStatusBarHeight(context); } @@ -135,14 +79,9 @@ public class SystemWindowManagerProxy extends WindowManagerProxy { Context displayInfoContext) { ArrayMap> result = new ArrayMap<>(); WindowManager windowManager = displayInfoContext.getSystemService(WindowManager.class); - Set possibleMaximumWindowMetrics = - null; - try { - possibleMaximumWindowMetrics = windowManager.getPossibleMaximumWindowMetrics(DEFAULT_DISPLAY); - } catch (Throwable t) { - possibleMaximumWindowMetrics = Collections.singleton( - windowManager.getMaximumWindowMetrics()); - } + Set possibleMaximumWindowMetrics = windowManager + .getPossibleMaximumWindowMetrics(DEFAULT_DISPLAY); + FileLog.d("b/283944974", "possibleMaximumWindowMetrics: " + possibleMaximumWindowMetrics); for (WindowMetrics windowMetrics : possibleMaximumWindowMetrics) { CachedDisplayInfo info = getDisplayInfo(windowMetrics, Surface.ROTATION_0); List bounds = estimateWindowBounds(displayInfoContext, info); diff --git a/quickstep/src/com/android/quickstep/util/TISBindHelper.java b/quickstep/src/com/android/quickstep/util/TISBindHelper.java index 027dc083cc..9a010429d3 100644 --- a/quickstep/src/com/android/quickstep/util/TISBindHelper.java +++ b/quickstep/src/com/android/quickstep/util/TISBindHelper.java @@ -21,7 +21,6 @@ import android.content.Intent; import android.content.ServiceConnection; import android.os.Handler; import android.os.IBinder; -import android.os.Looper; import android.util.Log; import androidx.annotation.Nullable; @@ -46,7 +45,7 @@ public class TISBindHelper implements ServiceConnection { // Max backoff caps at 5 mins private static final long MAX_BACKOFF_MILLIS = 10 * 60 * 1000; - private final Handler mHandler = new Handler(Looper.getMainLooper()); + private final Handler mHandler = new Handler(); private final Runnable mConnectionRunnable = this::internalBindToTIS; private final Context mContext; private final Consumer mConnectionCallback; diff --git a/quickstep/src/com/android/quickstep/util/TaskGridNavHelper.java b/quickstep/src/com/android/quickstep/util/TaskGridNavHelper.java new file mode 100644 index 0000000000..98d363ef5c --- /dev/null +++ b/quickstep/src/com/android/quickstep/util/TaskGridNavHelper.java @@ -0,0 +1,136 @@ +/* + * 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.quickstep.util; + +import static java.lang.annotation.RetentionPolicy.SOURCE; + +import androidx.annotation.IntDef; + +import com.android.launcher3.util.IntArray; + +import java.lang.annotation.Retention; + +/** + * Helper class for navigating RecentsView grid tasks via arrow keys and tab. + */ +public class TaskGridNavHelper { + public static final int CLEAR_ALL_PLACEHOLDER_ID = -1; + public static final int INVALID_FOCUSED_TASK_ID = -1; + + public static final int DIRECTION_UP = 0; + public static final int DIRECTION_DOWN = 1; + public static final int DIRECTION_LEFT = 2; + public static final int DIRECTION_RIGHT = 3; + public static final int DIRECTION_TAB = 4; + + @Retention(SOURCE) + @IntDef({DIRECTION_UP, DIRECTION_DOWN, DIRECTION_LEFT, DIRECTION_RIGHT, DIRECTION_TAB}) + public @interface TASK_NAV_DIRECTION {} + + private final IntArray mOriginalTopRowIds; + private IntArray mTopRowIds; + private IntArray mBottomRowIds; + private final int mFocusedTaskId; + + public TaskGridNavHelper(IntArray topIds, IntArray bottomIds, int focusedTaskId) { + mFocusedTaskId = focusedTaskId; + mOriginalTopRowIds = topIds.clone(); + generateTaskViewIdGrid(topIds, bottomIds); + } + + private void generateTaskViewIdGrid(IntArray topRowIdArray, IntArray bottomRowIdArray) { + boolean hasFocusedTask = mFocusedTaskId != INVALID_FOCUSED_TASK_ID; + int maxSize = + Math.max(topRowIdArray.size(), bottomRowIdArray.size()) + (hasFocusedTask ? 1 : 0); + int minSize = + Math.min(topRowIdArray.size(), bottomRowIdArray.size()) + (hasFocusedTask ? 1 : 0); + + // Add the focused task to the beginning of both arrays if it exists. + if (hasFocusedTask) { + topRowIdArray.add(0, mFocusedTaskId); + bottomRowIdArray.add(0, mFocusedTaskId); + } + + // Fill in the shorter array with the ids from the longer one. + for (int i = minSize; i < maxSize; i++) { + if (i >= topRowIdArray.size()) { + topRowIdArray.add(bottomRowIdArray.get(i)); + } else { + bottomRowIdArray.add(topRowIdArray.get(i)); + } + } + + // Add the clear all button to the end of both arrays + topRowIdArray.add(CLEAR_ALL_PLACEHOLDER_ID); + bottomRowIdArray.add(CLEAR_ALL_PLACEHOLDER_ID); + + mTopRowIds = topRowIdArray; + mBottomRowIds = bottomRowIdArray; + } + + /** + * Returns the id of the next page in the grid or -1 for the clear all button. + */ + public int getNextGridPage(int currentPageTaskViewId, int delta, + @TASK_NAV_DIRECTION int direction, boolean cycle) { + boolean inTop = mTopRowIds.contains(currentPageTaskViewId); + int index = inTop ? mTopRowIds.indexOf(currentPageTaskViewId) + : mBottomRowIds.indexOf(currentPageTaskViewId); + int maxSize = Math.max(mTopRowIds.size(), mBottomRowIds.size()); + int nextIndex = index + delta; + + switch (direction) { + case DIRECTION_UP: + case DIRECTION_DOWN: { + return inTop ? mBottomRowIds.get(index) : mTopRowIds.get(index); + } + case DIRECTION_LEFT: { + int boundedIndex = cycle ? nextIndex % maxSize : Math.min(nextIndex, maxSize - 1); + return inTop ? mTopRowIds.get(boundedIndex) + : mBottomRowIds.get(boundedIndex); + } + case DIRECTION_RIGHT: { + int boundedIndex = + cycle ? (nextIndex < 0 ? maxSize - 1 : nextIndex) : Math.max( + nextIndex, 0); + boolean inOriginalTop = mOriginalTopRowIds.contains(currentPageTaskViewId); + return inOriginalTop ? mTopRowIds.get(boundedIndex) + : mBottomRowIds.get(boundedIndex); + } + case DIRECTION_TAB: { + int boundedIndex = + cycle ? nextIndex < 0 ? maxSize - 1 : nextIndex % maxSize : Math.min( + nextIndex, maxSize - 1); + if (delta >= 0) { + return inTop && mTopRowIds.get(index) != mBottomRowIds.get(index) + ? mBottomRowIds.get(index) + : mTopRowIds.get(boundedIndex); + } else { + if (mTopRowIds.contains(currentPageTaskViewId)) { + return mBottomRowIds.get(boundedIndex); + } else { + // Go up to top if there is task above + return mTopRowIds.get(index) != mBottomRowIds.get(index) + ? mTopRowIds.get(index) + : mBottomRowIds.get(boundedIndex); + } + } + } + default: + return currentPageTaskViewId; + } + } +} diff --git a/quickstep/src/com/android/quickstep/util/TaskKeyByLastActiveTimeCache.java b/quickstep/src/com/android/quickstep/util/TaskKeyByLastActiveTimeCache.java index 43ef39c256..69137cc1b6 100644 --- a/quickstep/src/com/android/quickstep/util/TaskKeyByLastActiveTimeCache.java +++ b/quickstep/src/com/android/quickstep/util/TaskKeyByLastActiveTimeCache.java @@ -17,7 +17,6 @@ package com.android.quickstep.util; import android.util.Log; -import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.android.systemui.shared.recents.model.Task; @@ -95,7 +94,6 @@ public class TaskKeyByLastActiveTimeCache implements TaskKeyCache { * Gets the entry if it is still valid */ @Override - @Nullable public synchronized V getAndInvalidateIfModified(Task.TaskKey key) { Entry entry = mMap.get(key.id); if (entry != null && entry.mKey.windowingMode == key.windowingMode diff --git a/quickstep/src/com/android/quickstep/util/TaskKeyCache.java b/quickstep/src/com/android/quickstep/util/TaskKeyCache.java index 9df0993a7b..8ee78ab0ba 100644 --- a/quickstep/src/com/android/quickstep/util/TaskKeyCache.java +++ b/quickstep/src/com/android/quickstep/util/TaskKeyCache.java @@ -15,8 +15,6 @@ */ package com.android.quickstep.util; -import androidx.annotation.Nullable; - import com.android.systemui.shared.recents.model.Task; import java.util.function.Predicate; @@ -46,7 +44,6 @@ public interface TaskKeyCache { /** * Gets the entry if it is still valid. */ - @Nullable V getAndInvalidateIfModified(Task.TaskKey key); /** diff --git a/quickstep/src/com/android/quickstep/util/TaskKeyLruCache.java b/quickstep/src/com/android/quickstep/util/TaskKeyLruCache.java index 9fe8cc954d..89f5d41dad 100644 --- a/quickstep/src/com/android/quickstep/util/TaskKeyLruCache.java +++ b/quickstep/src/com/android/quickstep/util/TaskKeyLruCache.java @@ -17,8 +17,6 @@ package com.android.quickstep.util; import android.util.Log; -import androidx.annotation.Nullable; - import com.android.systemui.shared.recents.model.Task.TaskKey; import java.util.LinkedHashMap; @@ -61,7 +59,6 @@ public class TaskKeyLruCache implements TaskKeyCache { /** * Gets the entry if it is still valid */ - @Nullable public synchronized V getAndInvalidateIfModified(TaskKey key) { Entry entry = mMap.get(key.id); diff --git a/quickstep/src/com/android/quickstep/util/TaskRemovedDuringLaunchListener.java b/quickstep/src/com/android/quickstep/util/TaskRemovedDuringLaunchListener.java index 40a328ccfc..e80d2a6d3f 100644 --- a/quickstep/src/com/android/quickstep/util/TaskRemovedDuringLaunchListener.java +++ b/quickstep/src/com/android/quickstep/util/TaskRemovedDuringLaunchListener.java @@ -98,7 +98,10 @@ public class TaskRemovedDuringLaunchListener { final Runnable taskLaunchFailedCallback = mTaskLaunchFailedCallback; RecentsModel.INSTANCE.get(mContext).isTaskRemoved(mLaunchedTaskId, (taskRemoved) -> { if (taskRemoved) { - ActiveGestureProtoLogProxy.logTaskLaunchFailed(launchedTaskId); + ActiveGestureLog.INSTANCE.addLog( + new ActiveGestureLog.CompoundString("Launch failed, task (id=") + .append(launchedTaskId) + .append(") finished mid transition")); taskLaunchFailedCallback.run(); } }, (task) -> true /* filter */); diff --git a/quickstep/src/com/android/quickstep/util/TaskRestartedDuringLaunchListener.java b/quickstep/src/com/android/quickstep/util/TaskRestartedDuringLaunchListener.java index 6e2d469d18..91e8376990 100644 --- a/quickstep/src/com/android/quickstep/util/TaskRestartedDuringLaunchListener.java +++ b/quickstep/src/com/android/quickstep/util/TaskRestartedDuringLaunchListener.java @@ -16,11 +16,16 @@ package com.android.quickstep.util; +import static android.app.ActivityTaskManager.INVALID_TASK_ID; + +import android.app.Activity; import android.app.ActivityManager; import android.util.Log; import androidx.annotation.NonNull; +import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter; +import com.android.quickstep.RecentsModel; import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.shared.system.TaskStackChangeListeners; diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java index 58d8b78962..fcf303fdd9 100644 --- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java +++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java @@ -24,8 +24,10 @@ import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITIO import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT; import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED; import static com.android.launcher3.util.SplitConfigurationOptions.StagePosition; +import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS; import static com.android.quickstep.util.RecentsOrientedState.postDisplayRotation; import static com.android.quickstep.util.RecentsOrientedState.preDisplayRotation; +import static com.android.quickstep.util.SplitScreenUtils.convertLauncherSplitBoundsToShell; import android.animation.TimeInterpolator; import android.content.Context; @@ -41,6 +43,7 @@ import android.view.animation.Interpolator; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.app.animation.Interpolators; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Utilities; import com.android.launcher3.anim.AnimatedFloat; @@ -49,10 +52,9 @@ import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds; import com.android.launcher3.util.TraceHelper; import com.android.quickstep.BaseActivityInterface; import com.android.quickstep.BaseContainerInterface; -import com.android.quickstep.DesktopFullscreenDrawParams; -import com.android.quickstep.FullscreenDrawParams; import com.android.quickstep.TaskAnimationManager; import com.android.quickstep.util.SurfaceTransaction.SurfaceProperties; +import com.android.quickstep.views.TaskView.FullscreenDrawParams; import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.recents.utilities.PreviewPositionHelper; @@ -97,11 +99,11 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { private final FullscreenDrawParams mCurrentFullscreenParams; public final AnimatedFloat taskPrimaryTranslation = new AnimatedFloat(); public final AnimatedFloat taskSecondaryTranslation = new AnimatedFloat(); - public final AnimatedFloat taskGridTranslationX = new AnimatedFloat(); - public final AnimatedFloat taskGridTranslationY = new AnimatedFloat(); // Carousel properties public final AnimatedFloat carouselScale = new AnimatedFloat(); + public final AnimatedFloat carouselPrimaryTranslation = new AnimatedFloat(); + public final AnimatedFloat carouselSecondaryTranslation = new AnimatedFloat(); // RecentsView properties public final AnimatedFloat recentsViewScale = new AnimatedFloat(); @@ -116,28 +118,19 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { private SplitBounds mSplitBounds; private Boolean mDrawsBelowRecents = null; private boolean mIsGridTask; - private final boolean mIsDesktopTask; - private boolean mIsAnimatingToCarousel = false; + private boolean mIsDesktopTask; + private boolean mScaleToCarouselTaskSize = false; private int mTaskRectTranslationX; private int mTaskRectTranslationY; - private int mDesktopTaskIndex = 0; - @Nullable - private Matrix mTaskRectTransform = null; - - public TaskViewSimulator(Context context, BaseContainerInterface sizeStrategy, - boolean isDesktop, int desktopTaskIndex) { + public TaskViewSimulator(Context context, BaseContainerInterface sizeStrategy) { mContext = context; mSizeStrategy = sizeStrategy; - mIsDesktopTask = isDesktop; - mDesktopTaskIndex = desktopTaskIndex; mOrientationState = TraceHelper.allowIpcs("TaskViewSimulator.init", () -> new RecentsOrientedState(context, sizeStrategy, i -> { })); mOrientationState.setGestureActive(true); - mCurrentFullscreenParams = mIsDesktopTask - ? new DesktopFullscreenDrawParams(context) - : new FullscreenDrawParams(context); + mCurrentFullscreenParams = new FullscreenDrawParams(context); mOrientationStateId = mOrientationState.getStateId(); Resources resources = context.getResources(); mIsRecentsRtl = mOrientationState.getOrientationHandler().getRecentsRtlSetting(resources); @@ -151,9 +144,6 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { mDp = dp; mLayoutValid = false; mOrientationState.setDeviceProfile(dp); - if (enableGridOnlyOverview()) { - mIsGridTask = dp.isTablet && !mIsDesktopTask; - } calculateTaskSize(); } @@ -165,16 +155,14 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { if (mIsGridTask) { mSizeStrategy.calculateGridTaskSize(mContext, mDp, mFullTaskSize, mOrientationState.getOrientationHandler()); - if (enableGridOnlyOverview()) { - mSizeStrategy.calculateTaskSize(mContext, mDp, mCarouselTaskSize, - mOrientationState.getOrientationHandler()); - } } else { mSizeStrategy.calculateTaskSize(mContext, mDp, mFullTaskSize, mOrientationState.getOrientationHandler()); - if (enableGridOnlyOverview()) { - mCarouselTaskSize.set(mFullTaskSize); - } + } + + if (enableGridOnlyOverview()) { + mSizeStrategy.calculateCarouselTaskSize(mContext, mDp, mCarouselTaskSize, + mOrientationState.getOrientationHandler()); } if (mSplitBounds != null) { @@ -184,6 +172,7 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { mTaskRect.set(mFullTaskSize); mOrientationState.getOrientationHandler() .setSplitTaskSwipeRect(mDp, mTaskRect, mSplitBounds, mStagePosition); + mTaskRect.offset(mTaskRectTranslationX, mTaskRectTranslationY); } else if (mIsDesktopTask) { // For desktop, tasks can take up only part of the screen size. // Full task size represents the whole screen size, but scaled down to fit in recents. @@ -197,19 +186,10 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { mTaskRect.scale(scale); // Ensure the task rect is inside the full task rect mTaskRect.offset(mFullTaskSize.left, mFullTaskSize.top); - - Rect taskDimension = new Rect(0, 0, (int) fullscreenTaskDimension.x, - (int) fullscreenTaskDimension.y); - mTmpCropRect.set(mThumbnailPosition); - if (mTmpCropRect.setIntersect(taskDimension, mThumbnailPosition)) { - mTmpCropRect.offset(-mThumbnailPosition.left, -mThumbnailPosition.top); - } else { - mTmpCropRect.setEmpty(); - } } else { mTaskRect.set(mFullTaskSize); + mTaskRect.offset(mTaskRectTranslationX, mTaskRectTranslationY); } - mTaskRect.offset(mTaskRectTranslationX, mTaskRectTranslationY); } /** @@ -229,7 +209,12 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { } // Copy mFullTaskSize instead of updating it directly so it could be reused next time // without recalculating - Rect scaleRect = new Rect(mIsAnimatingToCarousel ? mCarouselTaskSize : mFullTaskSize); + Rect scaleRect = new Rect(); + if (mScaleToCarouselTaskSize) { + scaleRect.set(mCarouselTaskSize); + } else { + scaleRect.set(mFullTaskSize); + } scaleRect.offset(mTaskRectTranslationX, mTaskRectTranslationY); float scale = mOrientationState.getFullScreenScaleAndPivot(scaleRect, mDp, mPivot); if (mPivotOverride != null) { @@ -263,6 +248,8 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { } else { mStagePosition = runningTarget.taskId == splitInfo.leftTopTaskId ? STAGE_POSITION_TOP_OR_LEFT : STAGE_POSITION_BOTTOM_OR_RIGHT; + mPositionHelper.setSplitBounds(convertLauncherSplitBoundsToShell(mSplitBounds), + mStagePosition); } calculateTaskSize(); } @@ -297,6 +284,13 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { mIsGridTask = isGridTask; } + /** + * Sets whether this task is part of desktop tasks in overview. + */ + public void setIsDesktopTask(boolean desktop) { + mIsDesktopTask = desktop; + } + /** * Apply translations on TaskRect's starting location. */ @@ -308,24 +302,66 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { } /** - * Override the pivot used to apply scale changes. + * Adds animation for all the components corresponding to transition from an app to overview. */ - public void setPivotOverride(PointF pivotOverride) { - mPivotOverride = pivotOverride; - getFullScreenScale(); - } - - /** - * Adds animation for all the components corresponding to transition from an app to carousel. - */ - public void addAppToCarouselAnim(PendingAnimation pa, Interpolator interpolator) { + public void addAppToOverviewAnim(PendingAnimation pa, Interpolator interpolator) { pa.addFloat(fullScreenProgress, AnimatedFloat.VALUE, 1, 0, interpolator); + float fullScreenScale; if (enableGridOnlyOverview() && mDp.isTablet && mDp.isGestureMode) { - mIsAnimatingToCarousel = true; + // Move pivot to top right edge of the screen, to avoid task scaling down in opposite + // direction of app window movement, otherwise the animation will wiggle left and right. + // Also translate the app window to top right edge of the screen to simplify + // calculations. + taskPrimaryTranslation.value = mIsRecentsRtl + ? mDp.widthPx - mFullTaskSize.right + : -mFullTaskSize.left; + taskSecondaryTranslation.value = -mFullTaskSize.top; + mPivotOverride = new PointF(mIsRecentsRtl ? mDp.widthPx : 0, 0); + + // Scale down to the carousel and use the carousel Rect to calculate fullScreenScale. + mScaleToCarouselTaskSize = true; carouselScale.value = mCarouselTaskSize.width() / (float) mFullTaskSize.width(); + fullScreenScale = getFullScreenScale(); + + float carouselPrimaryTranslationTarget = mIsRecentsRtl + ? mCarouselTaskSize.right - mDp.widthPx + : mCarouselTaskSize.left; + float carouselSecondaryTranslationTarget = mCarouselTaskSize.top; + + // Expected carousel position's center is in the middle, and invariant of + // recentsViewScale. + float exceptedCarouselCenterX = mCarouselTaskSize.centerX(); + // Animating carousel translations linearly will result in a curved path, therefore + // we'll need to calculate the expected translation at each recentsView scale. Luckily + // primary and secondary follow the same translation, and primary is used here due to + // it being simpler. + Interpolator carouselTranslationInterpolator = t -> { + // recentsViewScale is calculated rather than using recentsViewScale.value, so that + // this interpolator works independently even if recentsViewScale don't animate. + float recentsViewScale = + Utilities.mapToRange(t, 0, 1, fullScreenScale, 1, Interpolators.LINEAR); + // Without the translation, the app window will animate from fullscreen into top + // right corner. + float expectedTaskCenterX = mIsRecentsRtl + ? mDp.widthPx - mCarouselTaskSize.width() * recentsViewScale / 2f + : mCarouselTaskSize.width() * recentsViewScale / 2f; + // Calculate the expected translation, then work back the animatedFraction that + // results in this value. + float carouselPrimaryTranslation = + (exceptedCarouselCenterX - expectedTaskCenterX) / recentsViewScale; + return carouselPrimaryTranslation / carouselPrimaryTranslationTarget; + }; + + // Use addAnimatedFloat so this animation can later be canceled and animate to a + // different value in RecentsView.onPrepareGestureEndAnimation. + pa.addAnimatedFloat(carouselPrimaryTranslation, 0, carouselPrimaryTranslationTarget, + carouselTranslationInterpolator); + pa.addAnimatedFloat(carouselSecondaryTranslation, 0, carouselSecondaryTranslationTarget, + carouselTranslationInterpolator); + } else { + fullScreenScale = getFullScreenScale(); } - pa.addFloat(recentsViewScale, AnimatedFloat.VALUE, getFullScreenScale(), 1, - interpolator); + pa.addFloat(recentsViewScale, AnimatedFloat.VALUE, fullScreenScale, 1, interpolator); } /** @@ -369,38 +405,6 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { return mMatrix; } - /** - * Sets a matrix used to transform the position of tasks. If set, this matrix is applied to - * the task rect after the task has been scaled and positioned inside the fulltask, but - * before scaling and translation of the whole recents view is performed. - */ - public void setTaskRectTransform(@Nullable Matrix taskRectTransform) { - mTaskRectTransform = taskRectTransform; - } - - /** - * Calculates the crop rect for desktop tasks given the current matrix. - */ - private void calculateDesktopTaskCropRect() { - // The approach here is to map a rect that represents the untransformed thumbnail position - // using the current matrix. This will give us a rect that can be intersected with - // [mFullTaskSize]. Using the intersection, we then compute how much of the task window that - // needs to be cropped (which will be nothing if the window is entirely within the desktop). - mTempRectF.set(0, 0, mThumbnailPosition.width(), mThumbnailPosition.height()); - mMatrix.mapRect(mTempRectF); - - float offsetX = mTempRectF.left; - float offsetY = mTempRectF.top; - float scale = mThumbnailPosition.width() / mTempRectF.width(); - - if (mTempRectF.intersect(mFullTaskSize.left, mFullTaskSize.top, mFullTaskSize.right, - mFullTaskSize.bottom)) { - mTempRectF.offset(-offsetX, -offsetY); - mTempRectF.scale(scale); - mTempRectF.round(mTmpCropRect); - } - } - /** * Applies the rotation on the matrix to so that it maps from launcher coordinate space to * window coordinate space. @@ -462,29 +466,18 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { mMatrix.set(mPositionHelper.getMatrix()); - // Apply TaskView matrix: taskRect, optional transform, translate + // Apply TaskView matrix: taskRect, translate mMatrix.postTranslate(mTaskRect.left, mTaskRect.top); - if (mTaskRectTransform != null) { - mMatrix.postConcat(mTaskRectTransform); - - // Calculate cropping for desktop tasks. The order is important since it uses the - // current matrix. Therefore we calculate it here, after applying the task rect - // transform, but before applying scaling/translation that affects the whole - // recentsview. - if (mIsDesktopTask) { - calculateDesktopTaskCropRect(); - } - } - mOrientationState.getOrientationHandler().setPrimary(mMatrix, MATRIX_POST_TRANSLATE, taskPrimaryTranslation.value); mOrientationState.getOrientationHandler().setSecondary(mMatrix, MATRIX_POST_TRANSLATE, taskSecondaryTranslation.value); - mMatrix.postTranslate(taskGridTranslationX.value, taskGridTranslationY.value); - mMatrix.postScale(carouselScale.value, carouselScale.value, - mIsRecentsRtl ? mCarouselTaskSize.right : mCarouselTaskSize.left, - mCarouselTaskSize.top); + mMatrix.postScale(carouselScale.value, carouselScale.value, mPivot.x, mPivot.y); + mOrientationState.getOrientationHandler().setPrimary(mMatrix, MATRIX_POST_TRANSLATE, + carouselPrimaryTranslation.value); + mOrientationState.getOrientationHandler().setSecondary(mMatrix, MATRIX_POST_TRANSLATE, + carouselSecondaryTranslation.value); mOrientationState.getOrientationHandler().setPrimary( mMatrix, MATRIX_POST_TRANSLATE, recentsViewScroll.value); @@ -497,12 +490,10 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { recentsViewPrimaryTranslation.value); applyWindowToHomeRotation(mMatrix); - if (!mIsDesktopTask) { - // Crop rect is the inverse of thumbnail matrix - mTempRectF.set(0, 0, taskWidth, taskHeight); - mInversePositionMatrix.mapRect(mTempRectF); - mTempRectF.roundOut(mTmpCropRect); - } + // Crop rect is the inverse of thumbnail matrix + mTempRectF.set(0, 0, taskWidth, taskHeight); + mInversePositionMatrix.mapRect(mTempRectF); + mTempRectF.roundOut(mTmpCropRect); params.setProgress(1f - fullScreenProgress); params.applySurfaceParams(surfaceTransaction == null @@ -520,8 +511,8 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { + " taskRect: " + mTaskRect + " taskPrimaryT: " + taskPrimaryTranslation.value + " taskSecondaryT: " + taskSecondaryTranslation.value - + " taskGridTranslationX: " + taskGridTranslationX.value - + " taskGridTranslationY: " + taskGridTranslationY.value + + " carouselPrimaryT: " + carouselPrimaryTranslation.value + + " carouselSecondaryT: " + carouselSecondaryTranslation.value + " recentsPrimaryT: " + recentsViewPrimaryTranslation.value + " recentsSecondaryT: " + recentsViewSecondaryTranslation.value + " recentsScroll: " + recentsViewScroll.value @@ -538,12 +529,21 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { // If mDrawsBelowRecents is unset, no reordering will be enforced. if (mDrawsBelowRecents != null) { - // In shell transitions, the animation leashes are reparented to an animation container - // so we can bump layers as needed. - builder.setLayer(mDrawsBelowRecents - // 1000 is an arbitrary number to give room for multiple layers. - ? Integer.MIN_VALUE + 1000 + app.prefixOrderIndex - mDesktopTaskIndex - : Integer.MAX_VALUE - 1000 + app.prefixOrderIndex - mDesktopTaskIndex); + // In legacy transitions, the animation leashes remain in same hierarchy in the + // TaskDisplayArea, so we don't want to bump the layer too high otherwise it will + // conflict with layers that WM core positions (ie. the input consumers). For shell + // transitions, the animation leashes are reparented to an animation container so we + // can bump layers as needed. + if (ENABLE_SHELL_TRANSITIONS) { + builder.setLayer(mDrawsBelowRecents + ? Integer.MIN_VALUE + app.prefixOrderIndex + // 1000 is an arbitrary number to give room for multiple layers. + : Integer.MAX_VALUE - 1000 + app.prefixOrderIndex); + } else { + builder.setLayer(mDrawsBelowRecents + ? Integer.MIN_VALUE + app.prefixOrderIndex + : 0); + } } } @@ -552,7 +552,7 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { * TaskView */ public float getCurrentCornerRadius() { - float visibleRadius = mCurrentFullscreenParams.getCurrentCornerRadius(); + float visibleRadius = mCurrentFullscreenParams.getCurrentDrawnCornerRadius(); mTempPoint[0] = visibleRadius; mTempPoint[1] = 0; mInversePositionMatrix.mapVectors(mTempPoint); diff --git a/quickstep/src/com/android/quickstep/util/TaskVisualsChangeListener.java b/quickstep/src/com/android/quickstep/util/TaskVisualsChangeListener.java index 519ef60eca..66bff730bf 100644 --- a/quickstep/src/com/android/quickstep/util/TaskVisualsChangeListener.java +++ b/quickstep/src/com/android/quickstep/util/TaskVisualsChangeListener.java @@ -16,7 +16,6 @@ package com.android.quickstep.util; -import android.annotation.NonNull; import android.os.UserHandle; import com.android.systemui.shared.recents.model.Task; @@ -37,7 +36,7 @@ public interface TaskVisualsChangeListener { /** * Called when the icon for a task changes */ - default void onTaskIconChanged(@NonNull String pkg, @NonNull UserHandle user) {} + default void onTaskIconChanged(String pkg, UserHandle user) {} /** * Called when the icon for a task changes diff --git a/quickstep/src/com/android/quickstep/util/TransformParams.java b/quickstep/src/com/android/quickstep/util/TransformParams.java index cb591ed035..9bad1108bb 100644 --- a/quickstep/src/com/android/quickstep/util/TransformParams.java +++ b/quickstep/src/com/android/quickstep/util/TransformParams.java @@ -19,16 +19,9 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import android.util.FloatProperty; import android.view.RemoteAnimationTarget; -import android.view.SurfaceControl; -import android.window.TransitionInfo; - -import androidx.annotation.VisibleForTesting; import com.android.quickstep.RemoteAnimationTargets; import com.android.quickstep.util.SurfaceTransaction.SurfaceProperties; -import com.android.window.flags.Flags; - -import java.util.function.Supplier; public class TransformParams { @@ -63,24 +56,15 @@ public class TransformParams { private float mTargetAlpha; private float mCornerRadius; private RemoteAnimationTargets mTargetSet; - private TransitionInfo mTransitionInfo; - private boolean mCornerRadiusIsOverridden; private SurfaceTransactionApplier mSyncTransactionApplier; - private Supplier mSurfaceTransactionSupplier; private BuilderProxy mHomeBuilderProxy = BuilderProxy.ALWAYS_VISIBLE; private BuilderProxy mBaseBuilderProxy = BuilderProxy.ALWAYS_VISIBLE; public TransformParams() { - this(SurfaceTransaction::new); - } - - @VisibleForTesting - public TransformParams(Supplier surfaceTransactionSupplier) { mProgress = 0; mTargetAlpha = 1; mCornerRadius = -1; - mSurfaceTransactionSupplier = surfaceTransactionSupplier; } /** @@ -122,15 +106,6 @@ public class TransformParams { return this; } - /** - * Provides the {@code TransitionInfo} of the transition that this transformation stems from. - */ - public TransformParams setTransitionInfo(TransitionInfo transitionInfo) { - mTransitionInfo = transitionInfo; - mCornerRadiusIsOverridden = false; - return this; - } - /** * Sets the SyncRtSurfaceTransactionApplierCompat that will apply the SurfaceParams that * are computed based on these TransformParams. @@ -161,31 +136,26 @@ public class TransformParams { /** Builds the SurfaceTransaction from the given BuilderProxy params. */ public SurfaceTransaction createSurfaceParams(BuilderProxy proxy) { RemoteAnimationTargets targets = mTargetSet; - SurfaceTransaction transaction = mSurfaceTransactionSupplier.get(); + SurfaceTransaction transaction = new SurfaceTransaction(); if (targets == null) { return transaction; } for (int i = 0; i < targets.unfilteredApps.length; i++) { RemoteAnimationTarget app = targets.unfilteredApps[i]; SurfaceProperties builder = transaction.forSurface(app.leash); - BuilderProxy targetProxy = - app.windowConfiguration.getActivityType() == ACTIVITY_TYPE_HOME - ? mHomeBuilderProxy - : (app.mode == targets.targetMode ? proxy : mBaseBuilderProxy); if (app.mode == targets.targetMode) { - builder.setAlpha(getTargetAlpha()); - } - targetProxy.onBuildTargetParams(builder, app, this); - // Override the corner radius for {@code app} with the leash used by Shell, so that it - // doesn't interfere with the window clip and corner radius applied here. - // Only override the corner radius once - so that we don't accidentally override at the - // end of transition after WM Shell has reset the corner radius of the task. - if (!mCornerRadiusIsOverridden) { - overrideFreeformChangeLeashCornerRadiusToZero(app, transaction.getTransaction()); + int activityType = app.windowConfiguration.getActivityType(); + if (activityType == ACTIVITY_TYPE_HOME) { + mHomeBuilderProxy.onBuildTargetParams(builder, app, this); + } else { + builder.setAlpha(getTargetAlpha()); + proxy.onBuildTargetParams(builder, app, this); + } + } else { + mBaseBuilderProxy.onBuildTargetParams(builder, app, this); } } - mCornerRadiusIsOverridden = true; // always put wallpaper layer to bottom. final int wallpaperLength = targets.wallpapers != null ? targets.wallpapers.length : 0; @@ -196,33 +166,7 @@ public class TransformParams { return transaction; } - private void overrideFreeformChangeLeashCornerRadiusToZero( - RemoteAnimationTarget app, SurfaceControl.Transaction transaction) { - if (!Flags.enableDesktopRecentsTransitionsCornersBugfix()) { - return; - } - if (app.taskInfo == null || !app.taskInfo.isFreeform()) { - return; - } - - SurfaceControl changeLeash = getChangeLeashForApp(app); - if (changeLeash != null) { - transaction.setCornerRadius(changeLeash, 0); - } - } - - private SurfaceControl getChangeLeashForApp(RemoteAnimationTarget app) { - if (mTransitionInfo == null) return null; - for (TransitionInfo.Change change : mTransitionInfo.getChanges()) { - if (change.getTaskInfo() == null) continue; - if (change.getTaskInfo().taskId == app.taskId) { - return change.getLeash(); - } - } - return null; - } - - // Public getters so outside packages can read the values. + // Pubic getters so outside packages can read the values. public float getProgress() { return mProgress; diff --git a/quickstep/src/com/android/quickstep/util/WorkspaceRevealAnim.java b/quickstep/src/com/android/quickstep/util/WorkspaceRevealAnim.java index 32e0e132e0..0a97793b28 100644 --- a/quickstep/src/com/android/quickstep/util/WorkspaceRevealAnim.java +++ b/quickstep/src/com/android/quickstep/util/WorkspaceRevealAnim.java @@ -30,7 +30,6 @@ import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.util.FloatProperty; -import android.util.Log; import android.view.View; import com.android.app.animation.Interpolators; @@ -52,8 +51,6 @@ import com.android.systemui.plugins.ResourceProvider; */ public class WorkspaceRevealAnim { - private static final String TAG = "WorkspaceRevealAnim"; - // Should be used for animations running alongside this WorkspaceRevealAnim. public static final int DURATION_MS = 350; private static final FloatProperty> WORKSPACE_SCALE_PROPERTY = @@ -100,19 +97,6 @@ public class WorkspaceRevealAnim { mAnimators.setDuration(DURATION_MS); mAnimators.setInterpolator(Interpolators.DECELERATED_EASE); - mAnimators.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationCancel(Animator animation) { - super.onAnimationCancel(animation); - Log.d(TAG, "onAnimationCancel"); - } - - @Override - public void onAnimationEnd(Animator animation) { - super.onAnimationEnd(animation); - Log.d(TAG, "onAnimationEnd: workspace alpha = " + workspace.getAlpha()); - } - }); } private void addRevealAnimatorsForView(T v, FloatProperty scaleProperty) { diff --git a/quickstep/src/com/android/quickstep/util/unfold/LauncherUnfoldTransitionController.kt b/quickstep/src/com/android/quickstep/util/unfold/LauncherUnfoldTransitionController.kt index 915c9e5305..09563f5527 100644 --- a/quickstep/src/com/android/quickstep/util/unfold/LauncherUnfoldTransitionController.kt +++ b/quickstep/src/com/android/quickstep/util/unfold/LauncherUnfoldTransitionController.kt @@ -22,6 +22,7 @@ import com.android.launcher3.Alarm import com.android.launcher3.DeviceProfile import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener import com.android.launcher3.anim.PendingAnimation +import com.android.launcher3.config.FeatureFlags import com.android.launcher3.uioverrides.QuickstepLauncher import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener @@ -29,7 +30,7 @@ import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionPr /** Controls animations that are happening during unfolding foldable devices */ class LauncherUnfoldTransitionController( private val launcher: QuickstepLauncher, - private val progressProvider: ProxyUnfoldTransitionProvider, + private val progressProvider: ProxyUnfoldTransitionProvider ) : OnDeviceProfileChangeListener, ActivityLifecycleCallbacksAdapter, TransitionProgressListener { private var isTablet: Boolean? = null @@ -56,6 +57,10 @@ class LauncherUnfoldTransitionController( } override fun onDeviceProfileChanged(dp: DeviceProfile) { + if (!FeatureFlags.PREEMPTIVE_UNFOLD_ANIMATION_START.get()) { + return + } + if (isTablet != null && dp.isTablet != isTablet) { // We should preemptively start the animation only if: // - We changed to the unfolded screen @@ -88,7 +93,7 @@ class LauncherUnfoldTransitionController( provider = this, factory = this::onPrepareUnfoldAnimation, duration = - 1000L, // The expected duration for the animation. Then only comes to play if we have + 1000L // The expected duration for the animation. Then only comes to play if we have // to run the animation ourselves in case sysui misses the end signal ) timeoutAlarm.cancelAlarm() @@ -114,7 +119,7 @@ class LauncherUnfoldTransitionController( launcher, isVertical, dp.displayInfo.currentSize, - anim, + anim ) } diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt index bbd06be365..c56d7db5c6 100644 --- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt +++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt @@ -15,517 +15,271 @@ */ package com.android.quickstep.views -import android.annotation.SuppressLint import android.content.Context -import android.graphics.Matrix +import android.graphics.Point import android.graphics.PointF import android.graphics.Rect -import android.graphics.Rect.intersects -import android.graphics.RectF +import android.graphics.drawable.LayerDrawable +import android.graphics.drawable.ShapeDrawable +import android.graphics.drawable.shapes.RoundRectShape import android.util.AttributeSet import android.util.Log -import android.util.Size -import android.view.Display.INVALID_DISPLAY -import android.view.Gravity import android.view.View -import android.view.ViewStub -import androidx.core.content.res.ResourcesCompat +import android.view.ViewGroup import androidx.core.view.updateLayoutParams -import com.android.internal.hidden_from_bootclasspath.com.android.window.flags.Flags.enableDesktopRecentsTransitionsCornersBugfix -import com.android.launcher3.Flags.enableDesktopExplodedView -import com.android.launcher3.Flags.enableOverviewIconMenu -import com.android.launcher3.Flags.enableRefactorTaskThumbnail +import app.lawnchair.theme.color.tokens.ColorTokens import com.android.launcher3.R -import com.android.launcher3.statehandlers.DesktopVisibilityController -import com.android.launcher3.testing.TestLogging -import com.android.launcher3.testing.shared.TestProtocol import com.android.launcher3.Utilities import com.android.launcher3.util.RunnableList import com.android.launcher3.util.SplitConfigurationOptions import com.android.launcher3.util.TransformingTouchDelegate import com.android.launcher3.util.ViewPool -import com.android.launcher3.util.rects.lerpRect import com.android.launcher3.util.rects.set import com.android.quickstep.BaseContainerInterface -import com.android.quickstep.DesktopFullscreenDrawParams -import com.android.quickstep.FullscreenDrawParams -import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle import com.android.quickstep.TaskOverlayFactory -import com.android.quickstep.ViewUtils -import com.android.quickstep.recents.di.RecentsDependencies -import com.android.quickstep.recents.di.get -import com.android.quickstep.recents.domain.model.DesktopTaskBoundsData -import com.android.quickstep.recents.ui.viewmodel.DesktopTaskViewModel -import com.android.quickstep.recents.ui.viewmodel.TaskData -import com.android.quickstep.task.thumbnail.TaskThumbnailView -import com.android.quickstep.util.DesktopTask import com.android.quickstep.util.RecentsOrientedState -import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.enableMultipleDesktops -import kotlin.math.roundToInt - -import app.lawnchair.theme.color.tokens.ColorTokens +import com.android.systemui.shared.recents.model.Task /** TaskView that contains all tasks that are part of the desktop. */ class DesktopTaskView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : - TaskView( - context, - attrs, - type = TaskViewType.DESKTOP, - thumbnailFullscreenParams = DesktopFullscreenDrawParams(context), - ) { - val deskId - get() = desktopTask?.deskId ?: DesktopVisibilityController.INACTIVE_DESK_ID - - private var desktopTask: DesktopTask? = null - - private val contentViewFullscreenParams = FullscreenDrawParams(context) - - private val taskThumbnailViewDeprecatedPool = - if (!enableRefactorTaskThumbnail()) { - ViewPool( - context, - this, - R.layout.task_thumbnail_deprecated, - VIEW_POOL_MAX_SIZE, - VIEW_POOL_INITIAL_SIZE, - ) - } else null + TaskView(context, attrs) { + private val snapshotDrawParams = + object : FullscreenDrawParams(context) { + // DesktopTaskView thumbnail's corner radius is independent of fullscreenProgress. + override fun computeTaskCornerRadius(context: Context) = + computeWindowCornerRadius(context) + } private val taskThumbnailViewPool = - if (enableRefactorTaskThumbnail()) { - ViewPool( - context, - this, - R.layout.task_thumbnail, - VIEW_POOL_MAX_SIZE, - VIEW_POOL_INITIAL_SIZE, - ) - } else null - + ViewPool( + context, + this, + R.layout.task_thumbnail, + VIEW_POOL_MAX_SIZE, + VIEW_POOL_INITIAL_SIZE + ) private val tempPointF = PointF() - private val lastComputedTaskSize = Rect() - private lateinit var iconView: TaskViewIcon - private lateinit var contentView: DesktopTaskContentView + private val tempRect = Rect() private lateinit var backgroundView: View - - private var viewModel: DesktopTaskViewModel? = null - - /** - * Holds the default (user placed) positions of task windows. This can be moved into the - * viewModel once RefactorTaskThumbnail has been launched. - */ - private var fullscreenTaskPositions: List = emptyList() - - /** - * When enableDesktopExplodedView is enabled, this controls the gradual transition from the - * default positions to the organized non-overlapping positions. - */ - var explodeProgress = 0.0f - set(value) { - field = value - positionTaskWindows() - } - - var remoteTargetHandles: Array? = null - set(value) { - field = value - positionTaskWindows() - } - - override val displayId: Int - get() = - if (enableMultipleDesktops(context)) { - desktopTask?.displayId ?: INVALID_DISPLAY - } else { - super.displayId - } - - private fun getRemoteTargetHandle(taskId: Int): RemoteTargetHandle? = - remoteTargetHandles?.firstOrNull { - it.transformParams.targetSet.firstAppTargetTaskId == taskId - } + private lateinit var iconView: TaskViewIcon + private var childCountAtInflation = 0 override fun onFinishInflate() { super.onFinishInflate() - iconView = - (findViewById(R.id.icon) as TaskViewIcon).apply { - setIcon( - this, - ResourcesCompat.getDrawable( - context.resources, - R.drawable.ic_desktop_with_bg, - context.theme, - ), - ) - setText(resources.getText(R.string.recent_task_desktop)) - } - contentView = - findViewById(R.id.desktop_content).apply { + backgroundView = + findViewById(R.id.background)!!.apply { updateLayoutParams { topMargin = container.deviceProfile.overviewTaskThumbnailTopMarginPx } - cornerRadius = contentViewFullscreenParams.currentCornerRadius - backgroundView = findViewById(R.id.background) - backgroundView.setBackgroundColor( - resources.getColor(ColorTokens.Neutral2_300.resolveColor(context), context.theme) - ) + background = + ShapeDrawable(RoundRectShape(FloatArray(8) { taskCornerRadius }, null, null)) + .apply { + setTint( + ColorTokens.Neutral2_300.resolveColor(context) + ) + } } + iconView = + getOrInflateIconView(R.id.icon).apply { + val iconBackground = resources.getDrawable(R.drawable.bg_circle, context.theme) + val icon = resources.getDrawable(R.drawable.ic_desktop, context.theme) + setIcon(this, LayerDrawable(arrayOf(iconBackground, icon))) + } + childCountAtInflation = childCount } - override fun inflateViewStubs() { - findViewById(R.id.icon) - ?.apply { - layoutResource = - if (enableOverviewIconMenu()) R.layout.icon_app_chip_view - else R.layout.icon_view - } - ?.inflate() - } + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + val containerWidth = MeasureSpec.getSize(widthMeasureSpec) + var containerHeight = MeasureSpec.getSize(heightMeasureSpec) + setMeasuredDimension(containerWidth, containerHeight) - private fun positionTaskWindows() { if (taskContainers.isEmpty()) { return } val thumbnailTopMarginPx = container.deviceProfile.overviewTaskThumbnailTopMarginPx + containerHeight -= thumbnailTopMarginPx - val taskViewWidth = layoutParams.width - val taskViewHeight = layoutParams.height - thumbnailTopMarginPx - - BaseContainerInterface.getTaskDimension(mContext, container.deviceProfile, tempPointF) - - val screenWidth = tempPointF.x.toInt() - val screenHeight = tempPointF.y.toInt() - val screenRect = Rect(0, 0, screenWidth, screenHeight) - val scaleWidth = taskViewWidth / screenWidth.toFloat() - val scaleHeight = taskViewHeight / screenHeight.toFloat() + BaseContainerInterface.getTaskDimension(context, container.deviceProfile, tempPointF) + val windowWidth = tempPointF.x.toInt() + val windowHeight = tempPointF.y.toInt() + val scaleWidth = containerWidth / windowWidth.toFloat() + val scaleHeight = containerHeight / windowHeight.toFloat() + if (DEBUG) { + Log.d( + TAG, + "onMeasure: container=[$containerWidth,$containerHeight] " + + "window=[$windowWidth,$windowHeight] scale=[$scaleWidth,$scaleHeight]" + ) + } + // Desktop tile is a shrunk down version of launcher and freeform task thumbnails. taskContainers.forEach { - val taskId = it.task.key.id - val fullscreenTaskPosition = - fullscreenTaskPositions.firstOrNull { it.taskId == taskId } ?: return - val overviewTaskPosition = - if (enableDesktopExplodedView()) { - viewModel!! - .organizedDesktopTaskPositions - .firstOrNull { it.taskId == taskId } - ?.let { organizedPosition -> - TEMP_OVERVIEW_TASK_POSITION.apply { - lerpRect( - fullscreenTaskPosition.bounds, - organizedPosition.bounds, - explodeProgress, - ) - } - } ?: fullscreenTaskPosition.bounds - } else { - fullscreenTaskPosition.bounds - } + // Default to quarter of the desktop if we did not get app bounds. + val taskSize = + it.task.appBounds + ?: tempRect.apply { + left = 0 + top = 0 + right = windowWidth / 4 + bottom = windowHeight / 4 + } + val thumbWidth = (taskSize.width() * scaleWidth).toInt() + val thumbHeight = (taskSize.height() * scaleHeight).toInt() + it.thumbnailViewDeprecated.measure( + MeasureSpec.makeMeasureSpec(thumbWidth, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(thumbHeight, MeasureSpec.EXACTLY) + ) - if (enableDesktopExplodedView()) { - getRemoteTargetHandle(taskId)?.let { remoteTargetHandle -> - val fromRect = - TEMP_FROM_RECTF.apply { - set(fullscreenTaskPosition.bounds) - scale(scaleWidth) - offset( - lastComputedTaskSize.left.toFloat(), - lastComputedTaskSize.top.toFloat(), - ) - } - val toRect = - TEMP_TO_RECTF.apply { - set(overviewTaskPosition) - scale(scaleWidth) - offset( - lastComputedTaskSize.left.toFloat(), - lastComputedTaskSize.top.toFloat(), - ) - } - val transform = Matrix() - transform.setRectToRect(fromRect, toRect, Matrix.ScaleToFit.FILL) - remoteTargetHandle.taskViewSimulator.setTaskRectTransform(transform) - remoteTargetHandle.taskViewSimulator.apply(remoteTargetHandle.transformParams) - } - } - - val taskLeft = overviewTaskPosition.left * scaleWidth - val taskTop = overviewTaskPosition.top * scaleHeight - val taskWidth = overviewTaskPosition.width() * scaleWidth - val taskHeight = overviewTaskPosition.height() * scaleHeight - // TODO(b/394660950): Revisit the choice to update the layout when explodeProgress == 1. - // To run the explode animation in reverse, it may be simpler to use translation/scale - // for all cases where the progress is non-zero. - if (explodeProgress == 0.0f || explodeProgress == 1.0f) { - // Reset scaling and translation that may have been applied during animation. - it.snapshotView.apply { - scaleX = 1.0f - scaleY = 1.0f - translationX = 0.0f - translationY = 0.0f - } - - // Position the task to the same position as it would be on the desktop - it.snapshotView.updateLayoutParams { - gravity = Gravity.LEFT or Gravity.TOP - width = taskWidth.toInt() - height = taskHeight.toInt() - leftMargin = taskLeft.toInt() - topMargin = taskTop.toInt() - } - - if ( - enableDesktopRecentsTransitionsCornersBugfix() && enableRefactorTaskThumbnail() - ) { - it.thumbnailView.outlineBounds = - if (intersects(overviewTaskPosition, screenRect)) - Rect(overviewTaskPosition).apply { - intersectUnchecked(screenRect) - // Offset to 0,0 to transform into TaskThumbnailView's coordinate - // system. - offset(-overviewTaskPosition.left, -overviewTaskPosition.top) - left = (left * scaleWidth).roundToInt() - top = (top * scaleHeight).roundToInt() - right = (right * scaleWidth).roundToInt() - bottom = (bottom * scaleHeight).roundToInt() - } - else null - } - } else { - // During the animation, apply translation and scale such that the view is - // transformed to where we want, without triggering layout. - it.snapshotView.apply { - pivotX = 0.0f - pivotY = 0.0f - translationX = taskLeft - left - translationY = taskTop - top - scaleX = taskWidth / width.toFloat() - scaleY = taskHeight / height.toFloat() - } + // Position the task to the same position as it would be on the desktop + val positionInParent = it.task.positionInParent ?: ORIGIN + val taskX = (positionInParent.x * scaleWidth).toInt() + var taskY = (positionInParent.y * scaleHeight).toInt() + // move task down by margin size + taskY += thumbnailTopMarginPx + it.thumbnailViewDeprecated.x = taskX.toFloat() + it.thumbnailViewDeprecated.y = taskY.toFloat() + if (DEBUG) { + Log.d( + TAG, + "onMeasure: task=${it.task.key} thumb=[$thumbWidth,$thumbHeight]" + + " pos=[$taskX,$taskY]" + ) } } } + override fun onRecycle() { + super.onRecycle() + visibility = VISIBLE + } + /** Updates this desktop task to the gives task list defined in `tasks` */ fun bind( - desktopTask: DesktopTask, + tasks: List, orientedState: RecentsOrientedState, - taskOverlayFactory: TaskOverlayFactory, + taskOverlayFactory: TaskOverlayFactory ) { - this.desktopTask = desktopTask - // TODO(b/370495260): Minimized tasks should not be filtered with desktop exploded view - // support. - // Minimized tasks should not be shown in Overview. - val tasks = desktopTask.tasks.filterNot { it.isMinimized } if (DEBUG) { val sb = StringBuilder() sb.append("bind tasks=").append(tasks.size).append("\n") tasks.forEach { sb.append(" key=${it.key}\n") } Log.d(TAG, sb.toString()) } - cancelPendingLoadTasks() - val backgroundViewIndex = contentView.indexOfChild(backgroundView) - taskContainers = - tasks.map { task -> - val snapshotView = - if (enableRefactorTaskThumbnail()) { - taskThumbnailViewPool!!.view - } else { - taskThumbnailViewDeprecatedPool!!.view - } - contentView.addView(snapshotView, backgroundViewIndex + 1) - TaskContainer( - this, - task, - snapshotView, - iconView, - TransformingTouchDelegate(iconView.asView()), - SplitConfigurationOptions.STAGE_POSITION_UNDEFINED, - digitalWellBeingToast = null, - showWindowsView = null, - taskOverlayFactory, + if (!isTaskContainersInitialized()) { + taskContainers = arrayListOf() + } + val taskContainers = taskContainers as ArrayList + taskContainers.ensureCapacity(tasks.size) + tasks.forEachIndexed { index, task -> + val thumbnailViewDeprecated: TaskThumbnailViewDeprecated + if (index >= taskContainers.size) { + thumbnailViewDeprecated = taskThumbnailViewPool.view + // Add thumbnailView from to position after the initial child views. + addView( + thumbnailViewDeprecated, + childCountAtInflation, + LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) ) + } else { + thumbnailViewDeprecated = taskContainers[index].thumbnailViewDeprecated + } + val taskContainer = + TaskContainer( + task, + // TODO(b/338360089): Support new TTV for DesktopTaskView + thumbnailView = null, + thumbnailViewDeprecated, + iconView, + TransformingTouchDelegate(iconView.asView()), + SplitConfigurationOptions.STAGE_POSITION_UNDEFINED, + digitalWellBeingToast = null, + showWindowsView = null, + taskOverlayFactory + ) + .apply { thumbnailViewDeprecated.bind(task, overlay) } + if (index >= taskContainers.size) { + taskContainers.add(taskContainer) + } else { + taskContainers[index] = taskContainer } - onBind(orientedState) - } - - override fun onBind(orientedState: RecentsOrientedState) { - super.onBind(orientedState) - - if (enableRefactorTaskThumbnail()) { - viewModel = - DesktopTaskViewModel(organizeDesktopTasksUseCase = RecentsDependencies.get(context)) } - } - - override fun onRecycle() { - super.onRecycle() - desktopTask = null - explodeProgress = 0.0f - viewModel = null - visibility = VISIBLE - taskContainers.forEach { removeAndRecycleThumbnailView(it) } - } - - override fun setOrientationState(orientationState: RecentsOrientedState) { - super.setOrientationState(orientationState) - iconView.setIconOrientation(orientationState, isGridTask) - } - - @SuppressLint("RtlHardcoded") - override fun updateTaskSize(lastComputedTaskSize: Rect, lastComputedGridTaskSize: Rect) { - super.updateTaskSize(lastComputedTaskSize, lastComputedGridTaskSize) - this.lastComputedTaskSize.set(lastComputedTaskSize) - - updateTaskPositions() - } - - override fun onTaskListVisibilityChanged(visible: Boolean, changes: Int) { - super.onTaskListVisibilityChanged(visible, changes) - if (needsUpdate(changes, FLAG_UPDATE_CORNER_RADIUS)) { - contentViewFullscreenParams.updateCornerRadius(context) + repeat(taskContainers.size - tasks.size) { + if (Utilities.ATLEAST_T) { + with(taskContainers.removeLast()) { + removeView(thumbnailViewDeprecated) + taskThumbnailViewPool.recycle(thumbnailViewDeprecated) + } + } else { + taskContainers.removeAt(taskContainers.lastIndex) + } } + + setOrientationState(orientedState) } - override fun onIconLoaded(taskContainer: TaskContainer) { - // Update contentDescription of snapshotView only, individual task icon is unused. - taskContainer.snapshotView.contentDescription = taskContainer.task.titleDescription - } - - override fun setIconState(container: TaskContainer, state: TaskData?) { - container.snapshotView.contentDescription = (state as? TaskData.Data)?.titleDescription - } - - // Ignoring [onIconUnloaded] as all tasks shares the same Desktop icon - override fun onIconUnloaded(taskContainer: TaskContainer) {} + override fun needsUpdate(dataChange: Int, flag: Int) = + if (flag == FLAG_UPDATE_THUMBNAIL) super.needsUpdate(dataChange, flag) else false // thumbnailView is laid out differently and is handled in onMeasure override fun updateThumbnailSize() {} override fun getThumbnailBounds(bounds: Rect, relativeToDragLayer: Boolean) { if (relativeToDragLayer) { - container.dragLayer.getDescendantRectRelativeToSelf(contentView, bounds) + container.dragLayer.getDescendantRectRelativeToSelf(backgroundView, bounds) } else { - bounds.set(contentView) + bounds.set(backgroundView) } } - private fun launchTaskWithDesktopController(animated: Boolean): RunnableList? { + override fun launchTaskAnimated(): RunnableList? { val recentsView = recentsView ?: return null - TestLogging.recordEvent( - TestProtocol.SEQUENCE_MAIN, - "launchDesktopFromRecents", - taskIds.contentToString(), - ) val endCallback = RunnableList() val desktopController = recentsView.desktopRecentsController checkNotNull(desktopController) { "recentsController is null" } - desktopController.launchDesktopFromRecents(this, animated) { - endCallback.executeAllAndDestroy() - } - Log.d( - TAG, - "launchTaskWithDesktopController: ${taskIds.contentToString()}, withRemoteTransition: $animated", - ) + desktopController.launchDesktopFromRecents(this) { endCallback.executeAllAndDestroy() } + Log.d(TAG, "launchTaskAnimated - launchDesktopFromRecents: ${taskIds.contentToString()}") // Callbacks get run from recentsView for case when recents animation already running recentsView.addSideTaskLaunchCallback(endCallback) return endCallback } - override fun launchAsStaticTile() = launchTaskWithDesktopController(animated = true) + override fun launchTask(callback: (launched: Boolean) -> Unit, isQuickSwitch: Boolean) { + launchTasks() + callback(true) + } - override fun launchWithoutAnimation( - isQuickSwitch: Boolean, - callback: (launched: Boolean) -> Unit, - ) = launchTaskWithDesktopController(animated = false)?.add { callback(true) } ?: callback(false) - - // Return true when Task cannot be launched as fullscreen (i.e. in split select state) to skip - // putting DesktopTaskView to split as it's not supported. - override fun confirmSecondSplitSelectApp(): Boolean = - recentsView?.canLaunchFullscreenTask() != true + // Desktop tile can't be in split screen + override fun confirmSecondSplitSelectApp(): Boolean = false // TODO(b/330685808) support overlay for Screenshot action override fun setOverlayEnabled(overlayEnabled: Boolean) {} override fun onFullscreenProgressChanged(fullscreenProgress: Float) { - backgroundView.alpha = 1 - fullscreenProgress + // Don't show background while we are transitioning to/from fullscreen + backgroundView.visibility = if (fullscreenProgress > 0) INVISIBLE else VISIBLE } - override fun updateFullscreenParams() { - super.updateFullscreenParams() - updateFullscreenParams(contentViewFullscreenParams) - contentView.cornerRadius = contentViewFullscreenParams.currentCornerRadius + override fun updateCurrentFullscreenParams() { + super.updateCurrentFullscreenParams() + updateFullscreenParams(snapshotDrawParams) } - override fun addChildrenForAccessibility(outChildren: ArrayList) { - super.addChildrenForAccessibility(outChildren) - ViewUtils.addAccessibleChildToList(backgroundView, outChildren) - } - - fun removeTaskFromExplodedView(taskId: Int, animate: Boolean) { - if (!enableDesktopExplodedView()) { - Log.e( - TAG, - "removeTaskFromExplodedView called when enableDesktopExplodedView flag is false", - ) - return - } - - // Remove the task's [taskContainer] and its associated Views. - val taskContainer = getTaskContainerById(taskId) ?: return - removeAndRecycleThumbnailView(taskContainer) - taskContainer.destroy() - taskContainers = taskContainers.filterNot { it == taskContainer } - - // Dismiss the current DesktopTaskView if all its windows are closed. - if (taskContainers.isEmpty()) { - recentsView?.dismissTaskView(this, animate, /* removeTask= */ true) - } else { - // Otherwise, re-position the remaining task windows. - // TODO(b/353949276): Implement the re-layout animations. - updateTaskPositions() - } - } - - private fun removeAndRecycleThumbnailView(taskContainer: TaskContainer) { - contentView.removeView(taskContainer.snapshotView) - if (enableRefactorTaskThumbnail()) { - taskThumbnailViewPool!!.recycle(taskContainer.thumbnailView) - } else { - taskThumbnailViewDeprecatedPool!!.recycle(taskContainer.thumbnailViewDeprecated) - } - } - - private fun updateTaskPositions() { - BaseContainerInterface.getTaskDimension(mContext, container.deviceProfile, tempPointF) - val desktopSize = Size(tempPointF.x.toInt(), tempPointF.y.toInt()) - DEFAULT_BOUNDS.set(0, 0, desktopSize.width / 4, desktopSize.height / 4) - - fullscreenTaskPositions = - taskContainers.map { - DesktopTaskBoundsData(it.task.key.id, it.task.appBounds ?: DEFAULT_BOUNDS) - } - - if (enableDesktopExplodedView()) { - viewModel?.organizeDesktopTasks(desktopSize, fullscreenTaskPositions) - } - positionTaskWindows() - } + override fun getThumbnailFullscreenParams() = snapshotDrawParams companion object { private const val TAG = "DesktopTaskView" private const val DEBUG = false - private const val VIEW_POOL_MAX_SIZE = 5 - + private const val VIEW_POOL_MAX_SIZE = 10 // As DesktopTaskView is inflated in background, use initialSize=0 to avoid initPool. private const val VIEW_POOL_INITIAL_SIZE = 0 - private val DEFAULT_BOUNDS = Rect() - // Temporaries used for various purposes to avoid allocations. - private val TEMP_OVERVIEW_TASK_POSITION = Rect() - private val TEMP_FROM_RECTF = RectF() - private val TEMP_TO_RECTF = RectF() + private val ORIGIN = Point(0, 0) } } diff --git a/quickstep/src/com/android/quickstep/views/FloatingAppPairBackground.kt b/quickstep/src/com/android/quickstep/views/FloatingAppPairBackground.kt index 6bbd6b2c15..e024995aa3 100644 --- a/quickstep/src/com/android/quickstep/views/FloatingAppPairBackground.kt +++ b/quickstep/src/com/android/quickstep/views/FloatingAppPairBackground.kt @@ -57,7 +57,6 @@ open class FloatingAppPairBackground( private val container: RecentsViewContainer private val backgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG) - private val dividerPaint = Paint(Paint.ANTI_ALIAS_FLAG) // Animation interpolators protected val expandXInterpolator: Interpolator @@ -106,15 +105,13 @@ open class FloatingAppPairBackground( ) // Find device-specific measurements - val resources = context.resources - deviceCornerRadius = QuickStepContract.getWindowCornerRadius(context) + deviceCornerRadius = QuickStepContract.getWindowCornerRadius(container.asContext()) deviceHalfDividerSize = - resources.getDimensionPixelSize(R.dimen.multi_window_task_divider_size) / 2f + container.asContext().resources.getDimensionPixelSize(R.dimen.multi_window_task_divider_size) / 2f val dividerCenterPos = dividerPos + deviceHalfDividerSize desiredSplitRatio = if (dp.isLeftRightSplit) dividerCenterPos / dp.widthPx else dividerCenterPos / dp.heightPx - dividerPaint.color = resources.getColor(R.color.taskbar_background_dark, null /*theme*/) } override fun draw(canvas: Canvas) { @@ -156,12 +153,8 @@ open class FloatingAppPairBackground( val leftSide = RectF(0f, 0f, dividerCenterPos - changingDividerSize, height) // The right half of the background image val rightSide = RectF(dividerCenterPos + changingDividerSize, 0f, width, height) - // Middle part is for divider background - val middleRect = RectF(leftSide.right - deviceHalfDividerSize, 0f, - rightSide.left + deviceHalfDividerSize, height) // Draw background - canvas.drawRect(middleRect, dividerPaint) drawCustomRoundedRect( canvas, leftSide, @@ -258,12 +251,8 @@ open class FloatingAppPairBackground( val topSide = RectF(0f, 0f, width, dividerCenterPos - changingDividerSize) // The bottom half of the background image val bottomSide = RectF(0f, dividerCenterPos + changingDividerSize, width, height) - // Middle part is for divider background - val middleRect = RectF(0f, topSide.bottom - deviceHalfDividerSize, - width, bottomSide.top + deviceHalfDividerSize) // Draw background - canvas.drawRect(middleRect, dividerPaint) drawCustomRoundedRect( canvas, topSide, diff --git a/quickstep/src/com/android/quickstep/views/FloatingWidgetBackgroundView.java b/quickstep/src/com/android/quickstep/views/FloatingWidgetBackgroundView.java index b060168b1b..e5f241fdbe 100644 --- a/quickstep/src/com/android/quickstep/views/FloatingWidgetBackgroundView.java +++ b/quickstep/src/com/android/quickstep/views/FloatingWidgetBackgroundView.java @@ -33,6 +33,7 @@ import androidx.annotation.Nullable; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.widget.LauncherAppWidgetHostView; +import com.android.launcher3.widget.RoundedCornerEnforcement; import java.util.stream.IntStream; @@ -179,7 +180,8 @@ final class FloatingWidgetBackgroundView extends View { /** Corner radius from source view's outline, or enforced view. */ private static float getOutlineRadius(LauncherAppWidgetHostView hostView, View v) { - if (hostView.hasEnforcedCornerRadius()) { + if (RoundedCornerEnforcement.isRoundedCornerEnabled() + && hostView.hasEnforcedCornerRadius()) { return hostView.getEnforcedCornerRadius(); } else if (Utilities.ATLEAST_S && v.getOutlineProvider() instanceof RemoteViewOutlineProvider diff --git a/quickstep/src/com/android/quickstep/views/FloatingWidgetView.java b/quickstep/src/com/android/quickstep/views/FloatingWidgetView.java index b719ee5b73..fc52b8ee93 100644 --- a/quickstep/src/com/android/quickstep/views/FloatingWidgetView.java +++ b/quickstep/src/com/android/quickstep/views/FloatingWidgetView.java @@ -18,7 +18,6 @@ package com.android.quickstep.views; import android.animation.Animator; import android.animation.Animator.AnimatorListener; import android.annotation.TargetApi; -import android.app.TaskInfo; import android.content.Context; import android.graphics.Matrix; import android.graphics.RectF; @@ -123,9 +122,10 @@ public class FloatingWidgetView extends FrameLayout implements AnimatorListener, @Override public void onGlobalLayout() { - if (isUninitialized()) return; - boolean positionsChanged = positionViews(); - if (mOnTargetChangeRunnable != null && positionsChanged) { + if (isUninitialized()) + return; + positionViews(); + if (mOnTargetChangeRunnable != null) { mOnTargetChangeRunnable.run(); } } @@ -143,7 +143,8 @@ public class FloatingWidgetView extends FrameLayout implements AnimatorListener, /** Callback at the end or early exit of the animation. */ @Override public void fastFinish() { - if (isUninitialized()) return; + if (isUninitialized()) + return; Runnable fastFinishRunnable = mFastFinishRunnable; if (fastFinishRunnable != null) { fastFinishRunnable.run(); @@ -188,18 +189,23 @@ public class FloatingWidgetView extends FrameLayout implements AnimatorListener, /** * Updates the position and opacity of the floating widget's components. * - * @param backgroundPosition the new position of the widget's background relative to the + * @param backgroundPosition the new position of the widget's background + * relative to the * {@link FloatingWidgetView}'s parent - * @param floatingWidgetAlpha the overall opacity of the {@link FloatingWidgetView} + * @param floatingWidgetAlpha the overall opacity of the + * {@link FloatingWidgetView} * @param foregroundAlpha the opacity of the foreground layer - * @param fallbackBackgroundAlpha the opacity of the fallback background used when the App + * @param fallbackBackgroundAlpha the opacity of the fallback background used + * when the App * Widget doesn't have a background - * @param cornerRadiusProgress progress of the corner radius animation, where 0 is the + * @param cornerRadiusProgress progress of the corner radius animation, where + * 0 is the * original radius and 1 is the window radius */ public void update(RectF backgroundPosition, float floatingWidgetAlpha, float foregroundAlpha, float fallbackBackgroundAlpha, float cornerRadiusProgress) { - if (isUninitialized() || mAppTargetIsTranslucent) return; + if (isUninitialized() || mAppTargetIsTranslucent) + return; setAlpha(floatingWidgetAlpha); mBackgroundView.update(cornerRadiusProgress, fallbackBackgroundAlpha); mAppWidgetView.setAlpha(foregroundAlpha); @@ -214,61 +220,34 @@ public class FloatingWidgetView extends FrameLayout implements AnimatorListener, } /** - * Sets the layout parameters of the floating view and its background view child. - * @return true if any of the views positions change due to this call. + * Sets the layout parameters of the floating view and its background view + * child. */ - private boolean positionViews() { - boolean positionsChanged = false; - + private void positionViews() { LayoutParams layoutParams = (LayoutParams) getLayoutParams(); - - if (layoutParams.topMargin != 0 || layoutParams.bottomMargin != 0 - || layoutParams.rightMargin != 0 || layoutParams.leftMargin != 0) { - positionsChanged = true; - layoutParams.setMargins(0, 0, 0, 0); - setLayoutParams(layoutParams); - } + layoutParams.setMargins(0, 0, 0, 0); + setLayoutParams(layoutParams); // FloatingWidgetView layout is forced LTR - float targetY = mBackgroundPosition.top + mIconOffsetY; - if (mBackgroundView.getTranslationX() != mBackgroundPosition.left - || mBackgroundView.getTranslationY() != targetY) { - positionsChanged = true; - mBackgroundView.setTranslationX(mBackgroundPosition.left); - mBackgroundView.setTranslationY(targetY); - } - + mBackgroundView.setTranslationX(mBackgroundPosition.left); + mBackgroundView.setTranslationY(mBackgroundPosition.top + mIconOffsetY); LayoutParams backgroundParams = (LayoutParams) mBackgroundView.getLayoutParams(); - if (backgroundParams.leftMargin != 0 || backgroundParams.topMargin != 0 - || backgroundParams.width != Math.round(mBackgroundPosition.width()) - || backgroundParams.height != Math.round(mBackgroundPosition.height())) { - positionsChanged = true; - - backgroundParams.leftMargin = 0; - backgroundParams.topMargin = 0; - backgroundParams.width = Math.round(mBackgroundPosition.width()); - backgroundParams.height = Math.round(mBackgroundPosition.height()); - mBackgroundView.setLayoutParams(backgroundParams); - } + backgroundParams.leftMargin = 0; + backgroundParams.topMargin = 0; + backgroundParams.width = (int) mBackgroundPosition.width(); + backgroundParams.height = (int) mBackgroundPosition.height(); + mBackgroundView.setLayoutParams(backgroundParams); if (mForegroundOverlayView != null) { sTmpMatrix.reset(); - float foregroundScale = - mBackgroundPosition.width() / mAppWidgetBackgroundView.getWidth(); + float foregroundScale = mBackgroundPosition.width() / mAppWidgetBackgroundView.getWidth(); sTmpMatrix.setTranslate(-mBackgroundOffset.left - mAppWidgetView.getLeft(), -mBackgroundOffset.top - mAppWidgetView.getTop()); sTmpMatrix.postScale(foregroundScale, foregroundScale); sTmpMatrix.postTranslate(mBackgroundPosition.left, mBackgroundPosition.top + mIconOffsetY); - - // We use the animation matrix here, because calling setMatrix on the GhostView - // actually sets the animation matrix, not the regular one. - if (!sTmpMatrix.equals(mForegroundOverlayView.getAnimationMatrix())) { - positionsChanged = true; - mForegroundOverlayView.setMatrix(sTmpMatrix); - } + mForegroundOverlayView.setMatrix(sTmpMatrix); } - return positionsChanged; } private void finish(DragLayer dragLayer) { @@ -305,10 +284,12 @@ public class FloatingWidgetView extends FrameLayout implements AnimatorListener, } /** - * Configures and returns a an instance of {@link FloatingWidgetView} matching the appearance of + * Configures and returns a an instance of {@link FloatingWidgetView} matching + * the appearance of * {@param originalView}. * - * @param widgetBackgroundPosition a {@link RectF} that will be updated with the widget's + * @param widgetBackgroundPosition a {@link RectF} that will be updated with the + * widget's * background bounds * @param windowSize the size of the window when launched * @param windowCornerRadius the corner radius of the window @@ -319,8 +300,8 @@ public class FloatingWidgetView extends FrameLayout implements AnimatorListener, int fallbackBackgroundColor) { final DragLayer dragLayer = launcher.getDragLayer(); ViewGroup parent = (ViewGroup) dragLayer.getParent(); - FloatingWidgetView floatingView = - launcher.getViewCache().getView(R.layout.floating_widget_view, launcher, parent); + FloatingWidgetView floatingView = launcher.getViewCache().getView(R.layout.floating_widget_view, launcher, + parent); floatingView.recycle(); floatingView.init(dragLayer, originalView, widgetBackgroundPosition, windowSize, @@ -330,24 +311,20 @@ public class FloatingWidgetView extends FrameLayout implements AnimatorListener, } /** - * Extract a background color from a target's task description, or fall back to the given + * Extract a background color from a target's task description, or fall back to + * the given * context's theme background color. */ public static int getDefaultBackgroundColor( - Context context, @Nullable RemoteAnimationTarget target) { - final int fallbackColor = Themes.getColorBackground(context); - if (target == null) { - return fallbackColor; - } - final TaskInfo taskInfo = target.taskInfo; - if (taskInfo == null) { - return fallbackColor; - } - return taskInfo.taskDescription.getBackgroundColor(); + Context context, RemoteAnimationTarget target) { + return (target != null && target.taskInfo != null + && target.taskInfo.taskDescription != null) + ? target.taskInfo.taskDescription.getBackgroundColor() + : Themes.getColorBackground(context); } private static void getRelativePosition(View descendant, View ancestor, RectF position) { - float[] points = new float[]{0, 0, descendant.getWidth(), descendant.getHeight()}; + float[] points = new float[] { 0, 0, descendant.getWidth(), descendant.getHeight() }; Utilities.getDescendantCoordRelativeToAncestor(descendant, ancestor, points, false /* includeRootScroll */, true /* ignoreTransform */); position.set( diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt index faa9e2893b..d6a3376c53 100644 --- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt +++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt @@ -21,12 +21,11 @@ import android.graphics.PointF import android.util.AttributeSet import android.util.Log import android.view.View -import android.view.ViewStub import com.android.internal.jank.Cuj import com.android.launcher3.Flags.enableOverviewIconMenu -import com.android.launcher3.Flags.enableRefactorTaskThumbnail import com.android.launcher3.R import com.android.launcher3.Utilities +import com.android.launcher3.config.FeatureFlags import com.android.launcher3.util.RunnableList import com.android.launcher3.util.SplitConfigurationOptions import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT @@ -34,11 +33,12 @@ import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_O import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED import com.android.quickstep.TaskOverlayFactory import com.android.quickstep.util.RecentsOrientedState +import com.android.quickstep.util.SplitScreenUtils.Companion.convertLauncherSplitBoundsToShell import com.android.quickstep.util.SplitSelectStateController import com.android.systemui.shared.recents.model.Task +import com.android.systemui.shared.recents.utilities.PreviewPositionHelper import com.android.systemui.shared.system.InteractionJankMonitorWrapper -import com.android.wm.shell.Flags.enableFlexibleTwoAppSplit -import com.android.wm.shell.shared.split.SplitScreenConstants.PersistentSnapPosition +import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition /** * TaskView that contains and shows thumbnails for not one, BUT TWO(!!) tasks @@ -51,16 +51,7 @@ import com.android.wm.shell.shared.split.SplitScreenConstants.PersistentSnapPosi * (Icon loading sold separately, fees may apply. Shipping & Handling for Overlays not included). */ class GroupedTaskView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : - TaskView(context, attrs, type = TaskViewType.GROUPED) { - - private val MINIMUM_RATIO_TO_SHOW_ICON = 0.2f - - val leftTopTaskContainer: TaskContainer - get() = taskContainers[0] - - val rightBottomTaskContainer: TaskContainer - get() = taskContainers[1] - + TaskView(context, attrs) { // TODO(b/336612373): Support new TTV for GroupedTaskView var splitBoundsConfig: SplitConfigurationOptions.SplitBounds? = null private set @@ -76,41 +67,52 @@ class GroupedTaskView @JvmOverloads constructor(context: Context, attrs: Attribu val heightSize = MeasureSpec.getSize(heightMeasureSpec) setMeasuredDimension(widthSize, heightSize) val splitBoundsConfig = splitBoundsConfig ?: return - val inSplitSelection = getThisTaskCurrentlyInSplitSelection() != INVALID_TASK_ID - pagedOrientationHandler.measureGroupedTaskViewThumbnailBounds( - leftTopTaskContainer.snapshotView, - rightBottomTaskContainer.snapshotView, - widthSize, - heightSize, - splitBoundsConfig, - container.deviceProfile, - layoutDirection == LAYOUT_DIRECTION_RTL, - inSplitSelection, - ) - + val initSplitTaskId = getThisTaskCurrentlyInSplitSelection() + if (initSplitTaskId == INVALID_TASK_ID) { + pagedOrientationHandler.measureGroupedTaskViewThumbnailBounds( + taskContainers[0].thumbnailViewDeprecated, + taskContainers[1].thumbnailViewDeprecated, + widthSize, + heightSize, + splitBoundsConfig, + container.deviceProfile, + layoutDirection == LAYOUT_DIRECTION_RTL + ) + // Should we be having a separate translation step apart from the measuring above? + // The following only applies to large screen for now, but for future reference + // we'd want to abstract this out in PagedViewHandlers to get the primary/secondary + // translation directions + taskContainers[0] + .thumbnailViewDeprecated + .applySplitSelectTranslateX(taskContainers[0].thumbnailViewDeprecated.translationX) + taskContainers[0] + .thumbnailViewDeprecated + .applySplitSelectTranslateY(taskContainers[0].thumbnailViewDeprecated.translationY) + taskContainers[1] + .thumbnailViewDeprecated + .applySplitSelectTranslateX(taskContainers[1].thumbnailViewDeprecated.translationX) + taskContainers[1] + .thumbnailViewDeprecated + .applySplitSelectTranslateY(taskContainers[1].thumbnailViewDeprecated.translationY) + } else { + // Currently being split with this taskView, let the non-split selected thumbnail + // take up full thumbnail area + taskContainers + .firstOrNull { it.task.key.id != initSplitTaskId } + ?.thumbnailViewDeprecated + ?.measure( + widthMeasureSpec, + MeasureSpec.makeMeasureSpec( + heightSize - container.deviceProfile.overviewTaskThumbnailTopMarginPx, + MeasureSpec.EXACTLY + ) + ) + } if (!enableOverviewIconMenu()) { updateIconPlacement() } } - override fun inflateViewStubs() { - super.inflateViewStubs() - findViewById(R.id.bottomright_snapshot) - ?.apply { - layoutResource = - if (enableRefactorTaskThumbnail()) R.layout.task_thumbnail - else R.layout.task_thumbnail_deprecated - } - ?.inflate() - findViewById(R.id.bottomRight_icon) - ?.apply { - layoutResource = - if (enableOverviewIconMenu()) R.layout.icon_app_chip_view - else R.layout.icon_view - } - ?.inflate() - } - override fun onRecycle() { super.onRecycle() splitBoundsConfig = null @@ -131,23 +133,37 @@ class GroupedTaskView @JvmOverloads constructor(context: Context, attrs: Attribu R.id.snapshot, R.id.icon, R.id.show_windows, - R.id.digital_wellbeing_toast, STAGE_POSITION_TOP_OR_LEFT, - taskOverlayFactory, + taskOverlayFactory ), createTaskContainer( secondaryTask, R.id.bottomright_snapshot, R.id.bottomRight_icon, R.id.show_windows_right, - R.id.bottomRight_digital_wellbeing_toast, STAGE_POSITION_BOTTOM_OR_RIGHT, - taskOverlayFactory, - ), + taskOverlayFactory + ) ) - this.splitBoundsConfig = splitBoundsConfig - taskContainers.forEach { it.digitalWellBeingToast?.splitBounds = splitBoundsConfig } - onBind(orientedState) + this.splitBoundsConfig = + splitBoundsConfig?.also { + taskContainers[0] + .thumbnailViewDeprecated + .previewPositionHelper + .setSplitBounds( + convertLauncherSplitBoundsToShell(it), + PreviewPositionHelper.STAGE_POSITION_TOP_OR_LEFT + ) + taskContainers[1] + .thumbnailViewDeprecated + .previewPositionHelper + .setSplitBounds( + convertLauncherSplitBoundsToShell(it), + PreviewPositionHelper.STAGE_POSITION_BOTTOM_OR_RIGHT + ) + } + taskContainers.forEach { it.digitalWellBeingToast?.setSplitBounds(splitBoundsConfig) } + setOrientationState(orientedState) } override fun setOrientationState(orientationState: RecentsOrientedState) { @@ -158,7 +174,7 @@ class GroupedTaskView @JvmOverloads constructor(context: Context, attrs: Attribu container.deviceProfile, it, layoutParams.width, - layoutParams.height, + layoutParams.height ) val iconViewMarginStart = resources.getDimensionPixelSize( @@ -171,10 +187,12 @@ class GroupedTaskView @JvmOverloads constructor(context: Context, attrs: Attribu val iconMargins = (iconViewMarginStart + iconViewBackgroundMarginStart) * 2 // setMaxWidth() needs to be called before mIconView.setIconOrientation which is // called in the super below. - (leftTopTaskContainer.iconView as IconAppChipView).maxWidth = + (taskContainers[0].iconView as IconAppChipView).setMaxWidth( groupedTaskViewSizes.first.x - iconMargins - (rightBottomTaskContainer.iconView as IconAppChipView).maxWidth = + ) + (taskContainers[1].iconView as IconAppChipView).setMaxWidth( groupedTaskViewSizes.second.x - iconMargins + ) } } super.setOrientationState(orientationState) @@ -183,72 +201,40 @@ class GroupedTaskView @JvmOverloads constructor(context: Context, attrs: Attribu private fun updateIconPlacement() { val splitBoundsConfig = splitBoundsConfig ?: return - val deviceProfile = container.deviceProfile - val taskIconHeight = deviceProfile.overviewTaskIconSizePx - val inSplitSelection = getThisTaskCurrentlyInSplitSelection() != INVALID_TASK_ID - var oneIconHiddenDueToSmallWidth = false - - if (enableFlexibleTwoAppSplit()) { - // Update values for both icons' setFlexSplitAlpha. Mainly, we want to hide an icon if - // its app tile is too small. But we also have to set the alphas back if we go to - // split selection. - val hideLeftTopIcon: Boolean - val hideRightBottomIcon: Boolean - if (inSplitSelection) { - hideLeftTopIcon = - getThisTaskCurrentlyInSplitSelection() == splitBoundsConfig.leftTopTaskId - hideRightBottomIcon = - getThisTaskCurrentlyInSplitSelection() == splitBoundsConfig.rightBottomTaskId - } else { - hideLeftTopIcon = splitBoundsConfig.leftTopTaskPercent < MINIMUM_RATIO_TO_SHOW_ICON - hideRightBottomIcon = - splitBoundsConfig.rightBottomTaskPercent < MINIMUM_RATIO_TO_SHOW_ICON - if (hideLeftTopIcon || hideRightBottomIcon) { - oneIconHiddenDueToSmallWidth = true - } - } - - leftTopTaskContainer.iconView.setFlexSplitAlpha(if (hideLeftTopIcon) 0f else 1f) - rightBottomTaskContainer.iconView.setFlexSplitAlpha(if (hideRightBottomIcon) 0f else 1f) - } - + val taskIconHeight = container.deviceProfile.overviewTaskIconSizePx + val isRtl = layoutDirection == LAYOUT_DIRECTION_RTL if (enableOverviewIconMenu()) { - val isDeviceRtl = Utilities.isRtl(resources) val groupedTaskViewSizes = pagedOrientationHandler.getGroupedTaskViewSizes( - deviceProfile, + container.deviceProfile, splitBoundsConfig, layoutParams.width, - layoutParams.height, + layoutParams.height ) pagedOrientationHandler.setSplitIconParams( - leftTopTaskContainer.iconView.asView(), - rightBottomTaskContainer.iconView.asView(), + taskContainers[0].iconView.asView(), + taskContainers[1].iconView.asView(), taskIconHeight, groupedTaskViewSizes.first.x, groupedTaskViewSizes.first.y, layoutParams.height, layoutParams.width, - isDeviceRtl, - deviceProfile, - splitBoundsConfig, - inSplitSelection, - oneIconHiddenDueToSmallWidth, + isRtl, + container.deviceProfile, + splitBoundsConfig ) } else { pagedOrientationHandler.setSplitIconParams( - leftTopTaskContainer.iconView.asView(), - rightBottomTaskContainer.iconView.asView(), + taskContainers[0].iconView.asView(), + taskContainers[1].iconView.asView(), taskIconHeight, - leftTopTaskContainer.snapshotView.measuredWidth, - leftTopTaskContainer.snapshotView.measuredHeight, + taskContainers[0].thumbnailViewDeprecated.measuredWidth, + taskContainers[0].thumbnailViewDeprecated.measuredHeight, measuredHeight, measuredWidth, - isLayoutRtl, - deviceProfile, - splitBoundsConfig, - inSplitSelection, - oneIconHiddenDueToSmallWidth, + isRtl, + container.deviceProfile, + splitBoundsConfig ) } } @@ -256,20 +242,24 @@ class GroupedTaskView @JvmOverloads constructor(context: Context, attrs: Attribu fun updateSplitBoundsConfig(splitBounds: SplitConfigurationOptions.SplitBounds?) { splitBoundsConfig = splitBounds taskContainers.forEach { - it.digitalWellBeingToast?.splitBounds = splitBoundsConfig - it.digitalWellBeingToast?.initialize() + it.digitalWellBeingToast?.setSplitBounds(splitBoundsConfig) + it.digitalWellBeingToast?.initialize(it.task) } invalidate() } - override fun launchAsStaticTile(): RunnableList? { + override fun launchTaskAnimated(): RunnableList? { + if (taskContainers.isEmpty()) { + Log.d(TAG, "launchTaskAnimated - task is not bound") + return null + } val recentsView = recentsView ?: return null val endCallback = RunnableList() // Callbacks run from remote animation when recents animation not currently running InteractionJankMonitorWrapper.begin( this, Cuj.CUJ_SPLIT_SCREEN_ENTER, - "Enter form GroupedTaskView", + "Enter form GroupedTaskView" ) launchTaskInternal(isQuickSwitch = false, launchingExistingTaskView = true) { endCallback.executeAllAndDestroy() @@ -281,11 +271,8 @@ class GroupedTaskView @JvmOverloads constructor(context: Context, attrs: Attribu return endCallback } - override fun launchWithoutAnimation( - isQuickSwitch: Boolean, - callback: (launched: Boolean) -> Unit, - ) { - launchTaskInternal(isQuickSwitch, launchingExistingTaskView = false, callback) + override fun launchTask(callback: (launched: Boolean) -> Unit, isQuickSwitch: Boolean) { + launchTaskInternal(isQuickSwitch, false, callback /*launchingExistingTaskview*/) } /** @@ -297,22 +284,19 @@ class GroupedTaskView @JvmOverloads constructor(context: Context, attrs: Attribu private fun launchTaskInternal( isQuickSwitch: Boolean, launchingExistingTaskView: Boolean, - callback: (launched: Boolean) -> Unit, + callback: (launched: Boolean) -> Unit ) { recentsView?.let { it.splitSelectController.launchExistingSplitPair( if (launchingExistingTaskView) this else null, - leftTopTaskContainer.task.key.id, - rightBottomTaskContainer.task.key.id, + taskContainers[0].task.key.id, + taskContainers[1].task.key.id, STAGE_POSITION_TOP_OR_LEFT, callback, isQuickSwitch, - snapPosition, - ) - Log.d( - TAG, - "launchTaskInternal - launchExistingSplitPair: ${taskIds.contentToString()}, launchingExistingTaskView: $launchingExistingTaskView", + snapPosition ) + Log.d(TAG, "launchTaskInternal - launchExistingSplitPair: ${taskIds.contentToString()}") } } @@ -333,14 +317,14 @@ class GroupedTaskView @JvmOverloads constructor(context: Context, attrs: Attribu // checks below aren't reliable since both of those views may be gone/transformed val initSplitTaskId = getThisTaskCurrentlyInSplitSelection() if (initSplitTaskId != INVALID_TASK_ID) { - return if (initSplitTaskId == leftTopTaskContainer.task.key.id) 1 else 0 + return if (initSplitTaskId == taskContainers[0].task.key.id) 1 else 0 } } // Check which of the two apps was selected if ( - rightBottomTaskContainer.iconView.asView().containsPoint(lastTouchDownPosition) || - rightBottomTaskContainer.snapshotView.containsPoint(lastTouchDownPosition) + taskContainers[1].iconView.asView().containsPoint(lastTouchDownPosition) || + taskContainers[1].thumbnailViewDeprecated.containsPoint(lastTouchDownPosition) ) { return 1 } @@ -353,6 +337,14 @@ class GroupedTaskView @JvmOverloads constructor(context: Context, attrs: Attribu return Utilities.pointInView(this, localPos[0], localPos[1], 0f /* slop */) } + override fun setOverlayEnabled(overlayEnabled: Boolean) { + if (FeatureFlags.enableAppPairs()) { + super.setOverlayEnabled(overlayEnabled) + } else { + // Intentional no-op to prevent setting smart actions overlay on thumbnails + } + } + companion object { private const val TAG = "GroupedTaskView" } diff --git a/quickstep/src/com/android/quickstep/views/IconAppChipView.java b/quickstep/src/com/android/quickstep/views/IconAppChipView.java new file mode 100644 index 0000000000..ba42594e99 --- /dev/null +++ b/quickstep/src/com/android/quickstep/views/IconAppChipView.java @@ -0,0 +1,464 @@ +/* + * 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.quickstep.views; + +import static com.android.app.animation.Interpolators.EMPHASIZED; +import static com.android.app.animation.Interpolators.LINEAR; +import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; +import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X; +import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y; + +import android.animation.Animator; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.animation.RectEvaluator; +import android.animation.ValueAnimator; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Outline; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewAnimationUtils; +import android.view.ViewOutlineProvider; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.Nullable; + +import com.android.launcher3.R; +import com.android.launcher3.Utilities; +import com.android.launcher3.util.MultiPropertyFactory; +import com.android.launcher3.util.MultiValueAlpha; +import com.android.quickstep.orientation.RecentsPagedOrientationHandler; +import com.android.quickstep.util.RecentsOrientedState; + +/** + * An icon app menu view which can be used in place of an IconView in overview TaskViews. + */ +public class IconAppChipView extends FrameLayout implements TaskViewIcon { + + private static final int MENU_BACKGROUND_REVEAL_DURATION = 417; + private static final int MENU_BACKGROUND_HIDE_DURATION = 333; + + private static final int NUM_ALPHA_CHANNELS = 3; + private static final int INDEX_CONTENT_ALPHA = 0; + private static final int INDEX_COLOR_FILTER_ALPHA = 1; + private static final int INDEX_MODAL_ALPHA = 2; + + private final MultiValueAlpha mMultiValueAlpha; + + private View mMenuAnchorView; + private IconView mIconView; + // Two textview so we can ellipsize the collapsed view and crossfade on expand to the full name. + private TextView mIconTextCollapsedView; + private TextView mIconTextExpandedView; + private ImageView mIconArrowView; + private final Rect mBackgroundRelativeLtrLocation = new Rect(); + final RectEvaluator mBackgroundAnimationRectEvaluator = + new RectEvaluator(mBackgroundRelativeLtrLocation); + private final int mCollapsedMenuDefaultWidth; + private final int mExpandedMenuDefaultWidth; + private final int mCollapsedMenuDefaultHeight; + private final int mExpandedMenuDefaultHeight; + private final int mIconMenuMarginTopStart; + private final int mMenuToChipGap; + private final int mBackgroundMarginTopStart; + private final int mAppNameHorizontalMargin; + private final int mIconViewMarginStart; + private final int mAppIconSize; + private final int mArrowSize; + private final int mIconViewDrawableExpandedSize; + private final int mArrowMarginEnd; + private AnimatorSet mAnimator; + + private int mMaxWidth = Integer.MAX_VALUE; + + private static final int INDEX_SPLIT_TRANSLATION = 0; + private static final int INDEX_MENU_TRANSLATION = 1; + private static final int INDEX_COUNT_TRANSLATION = 2; + + private final MultiPropertyFactory mViewTranslationX; + private final MultiPropertyFactory mViewTranslationY; + + /** + * Gets the view split x-axis translation + */ + public MultiPropertyFactory.MultiProperty getSplitTranslationX() { + return mViewTranslationX.get(INDEX_SPLIT_TRANSLATION); + } + + /** + * Sets the view split x-axis translation + * @param translationX x-axis translation + */ + public void setSplitTranslationX(float translationX) { + getSplitTranslationX().setValue(translationX); + } + + /** + * Gets the view split y-axis translation + */ + public MultiPropertyFactory.MultiProperty getSplitTranslationY() { + return mViewTranslationY.get(INDEX_SPLIT_TRANSLATION); + } + + /** + * Sets the view split y-axis translation + * @param translationY y-axis translation + */ + public void setSplitTranslationY(float translationY) { + getSplitTranslationY().setValue(translationY); + } + + /** + * Gets the menu x-axis translation for split task + */ + public MultiPropertyFactory.MultiProperty getMenuTranslationX() { + return mViewTranslationX.get(INDEX_MENU_TRANSLATION); + } + + /** + * Gets the menu y-axis translation for split task + */ + public MultiPropertyFactory.MultiProperty getMenuTranslationY() { + return mViewTranslationY.get(INDEX_MENU_TRANSLATION); + } + + public IconAppChipView(Context context) { + this(context, null); + } + + public IconAppChipView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public IconAppChipView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public IconAppChipView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + Resources res = getResources(); + mMultiValueAlpha = new MultiValueAlpha(this, NUM_ALPHA_CHANNELS); + mMultiValueAlpha.setUpdateVisibility(/* updateVisibility= */ true); + + // Menu dimensions + mCollapsedMenuDefaultWidth = + res.getDimensionPixelSize(R.dimen.task_thumbnail_icon_menu_collapsed_width); + mExpandedMenuDefaultWidth = + res.getDimensionPixelSize(R.dimen.task_thumbnail_icon_menu_expanded_width); + mCollapsedMenuDefaultHeight = + res.getDimensionPixelSize(R.dimen.task_thumbnail_icon_menu_collapsed_height); + mExpandedMenuDefaultHeight = + res.getDimensionPixelSize(R.dimen.task_thumbnail_icon_menu_expanded_height); + mIconMenuMarginTopStart = res.getDimensionPixelSize( + R.dimen.task_thumbnail_icon_menu_expanded_top_start_margin); + mMenuToChipGap = res.getDimensionPixelSize( + R.dimen.task_thumbnail_icon_menu_expanded_gap); + + // Background dimensions + mBackgroundMarginTopStart = res.getDimensionPixelSize( + R.dimen.task_thumbnail_icon_menu_background_margin_top_start); + + // Contents dimensions + mAppNameHorizontalMargin = res.getDimensionPixelSize( + R.dimen.task_thumbnail_icon_menu_app_name_margin_horizontal_collapsed); + mArrowMarginEnd = res.getDimensionPixelSize(R.dimen.task_thumbnail_icon_menu_arrow_margin); + mIconViewMarginStart = res.getDimensionPixelSize( + R.dimen.task_thumbnail_icon_view_start_margin); + mAppIconSize = res.getDimensionPixelSize( + R.dimen.task_thumbnail_icon_menu_app_icon_collapsed_size); + mArrowSize = res.getDimensionPixelSize( + R.dimen.task_thumbnail_icon_menu_arrow_size); + mIconViewDrawableExpandedSize = res.getDimensionPixelSize( + R.dimen.task_thumbnail_icon_menu_app_icon_expanded_size); + + mViewTranslationX = new MultiPropertyFactory<>(this, VIEW_TRANSLATE_X, + INDEX_COUNT_TRANSLATION, + Float::sum); + mViewTranslationY = new MultiPropertyFactory<>(this, VIEW_TRANSLATE_Y, + INDEX_COUNT_TRANSLATION, + Float::sum); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mIconView = findViewById(R.id.icon_view); + mIconTextCollapsedView = findViewById(R.id.icon_text_collapsed); + mIconTextExpandedView = findViewById(R.id.icon_text_expanded); + mIconArrowView = findViewById(R.id.icon_arrow); + mMenuAnchorView = findViewById(R.id.icon_view_menu_anchor); + } + + protected IconView getIconView() { + return mIconView; + } + + @Override + public void setText(CharSequence text) { + if (mIconTextCollapsedView != null) { + mIconTextCollapsedView.setText(text); + } + if (mIconTextExpandedView != null) { + mIconTextExpandedView.setText(text); + } + } + + @Override + public Drawable getDrawable() { + return mIconView == null ? null : mIconView.getDrawable(); + } + + @Override + public void setDrawable(Drawable icon) { + if (mIconView != null) { + mIconView.setDrawable(icon); + } + } + + @Override + public void setDrawableSize(int iconWidth, int iconHeight) { + if (mIconView != null) { + mIconView.setDrawableSize(iconWidth, iconHeight); + } + } + + /** + * Sets the maximum width of this Icon Menu. This is usually used when space is limited for + * split screen. + */ + public void setMaxWidth(int maxWidth) { + // Width showing only the app icon and arrow. Max width should not be set to less than this. + int minimumMaxWidth = mIconViewMarginStart + mAppIconSize + mArrowSize + mArrowMarginEnd; + mMaxWidth = Math.max(maxWidth, minimumMaxWidth); + } + + @Override + public void setIconOrientation(RecentsOrientedState orientationState, boolean isGridTask) { + RecentsPagedOrientationHandler orientationHandler = + orientationState.getOrientationHandler(); + // Layout params for anchor view + LayoutParams anchorLayoutParams = (LayoutParams) mMenuAnchorView.getLayoutParams(); + anchorLayoutParams.topMargin = mExpandedMenuDefaultHeight + mMenuToChipGap; + mMenuAnchorView.setLayoutParams(anchorLayoutParams); + + // Layout Params for the Menu View (this) + LayoutParams iconMenuParams = (LayoutParams) getLayoutParams(); + iconMenuParams.width = mExpandedMenuDefaultWidth; + iconMenuParams.height = mExpandedMenuDefaultHeight; + orientationHandler.setIconAppChipMenuParams(this, iconMenuParams, mIconMenuMarginTopStart, + mIconMenuMarginTopStart); + setLayoutParams(iconMenuParams); + + // Layout params for the background + Rect collapsedBackgroundBounds = getCollapsedBackgroundLtrBounds(); + mBackgroundRelativeLtrLocation.set(collapsedBackgroundBounds); + setOutlineProvider(new ViewOutlineProvider() { + final Rect mRtlAppliedOutlineBounds = new Rect(); + @Override + public void getOutline(View view, Outline outline) { + mRtlAppliedOutlineBounds.set(mBackgroundRelativeLtrLocation); + if (isLayoutRtl()) { + int width = getWidth(); + mRtlAppliedOutlineBounds.left = width - mBackgroundRelativeLtrLocation.right; + mRtlAppliedOutlineBounds.right = width - mBackgroundRelativeLtrLocation.left; + } + outline.setRoundRect( + mRtlAppliedOutlineBounds, mRtlAppliedOutlineBounds.height() / 2f); + } + }); + + // Layout Params for the Icon View + LayoutParams iconParams = (LayoutParams) mIconView.getLayoutParams(); + int iconMarginStartRelativeToParent = mIconViewMarginStart + mBackgroundMarginTopStart; + orientationHandler.setIconAppChipChildrenParams( + iconParams, iconMarginStartRelativeToParent); + + mIconView.setLayoutParams(iconParams); + mIconView.setDrawableSize(mAppIconSize, mAppIconSize); + + // Layout Params for the collapsed Icon Text View + int textMarginStart = + iconMarginStartRelativeToParent + mAppIconSize + mAppNameHorizontalMargin; + LayoutParams iconTextCollapsedParams = + (LayoutParams) mIconTextCollapsedView.getLayoutParams(); + orientationHandler.setIconAppChipChildrenParams(iconTextCollapsedParams, textMarginStart); + int collapsedTextWidth = collapsedBackgroundBounds.width() - mIconViewMarginStart + - mAppIconSize - mArrowSize - mAppNameHorizontalMargin - mArrowMarginEnd; + iconTextCollapsedParams.width = collapsedTextWidth; + mIconTextCollapsedView.setLayoutParams(iconTextCollapsedParams); + mIconTextCollapsedView.setAlpha(1f); + + // Layout Params for the expanded Icon Text View + LayoutParams iconTextExpandedParams = + (LayoutParams) mIconTextExpandedView.getLayoutParams(); + orientationHandler.setIconAppChipChildrenParams(iconTextExpandedParams, textMarginStart); + mIconTextExpandedView.setLayoutParams(iconTextExpandedParams); + mIconTextExpandedView.setAlpha(0f); + mIconTextExpandedView.setRevealClip(true, 0, mAppIconSize / 2f, collapsedTextWidth); + + // Layout Params for the Icon Arrow View + LayoutParams iconArrowParams = (LayoutParams) mIconArrowView.getLayoutParams(); + int arrowMarginStart = collapsedBackgroundBounds.right - mArrowMarginEnd - mArrowSize; + orientationHandler.setIconAppChipChildrenParams(iconArrowParams, arrowMarginStart); + mIconArrowView.setPivotY(iconArrowParams.height / 2f); + mIconArrowView.setLayoutParams(iconArrowParams); + + // This method is called twice sometimes (like when rotating split tasks). It is called + // once before onMeasure and onLayout, and again after onMeasure but before onLayout with + // a new width. This happens because we update widths on rotation and on measure of + // grouped task views. Calling requestLayout() does not guarantee a call to onMeasure if + // it has just measured, so we explicitly call it here. + measure(MeasureSpec.makeMeasureSpec(getLayoutParams().width, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(getLayoutParams().height, MeasureSpec.EXACTLY)); + } + + @Override + public void setIconColorTint(int color, float amount) { + // RecentsView's COLOR_TINT animates between 0 and 0.5f, we want to hide the app chip menu. + float colorTintAlpha = Utilities.mapToRange(amount, 0f, 0.5f, 1f, 0f, LINEAR); + mMultiValueAlpha.get(INDEX_COLOR_FILTER_ALPHA).setValue(colorTintAlpha); + } + + @Override + public void setContentAlpha(float alpha) { + mMultiValueAlpha.get(INDEX_CONTENT_ALPHA).setValue(alpha); + } + + @Override + public void setModalAlpha(float alpha) { + mMultiValueAlpha.get(INDEX_MODAL_ALPHA).setValue(alpha); + } + + @Override + public int getDrawableWidth() { + return mIconView == null ? 0 : mIconView.getDrawableWidth(); + } + + @Override + public int getDrawableHeight() { + return mIconView == null ? 0 : mIconView.getDrawableHeight(); + } + + protected void revealAnim(boolean isRevealing) { + cancelInProgressAnimations(); + final Rect collapsedBackgroundBounds = getCollapsedBackgroundLtrBounds(); + final Rect expandedBackgroundBounds = getExpandedBackgroundLtrBounds(); + final Rect initialBackground = new Rect(mBackgroundRelativeLtrLocation); + mAnimator = new AnimatorSet(); + + if (isRevealing) { + boolean isRtl = isLayoutRtl(); + bringToFront(); + // Clip expanded text with reveal animation so it doesn't go beyond the edge of the menu + Animator expandedTextRevealAnim = ViewAnimationUtils.createCircularReveal( + mIconTextExpandedView, 0, mIconTextExpandedView.getHeight() / 2, + mIconTextCollapsedView.getWidth(), mIconTextExpandedView.getWidth()); + // Animate background clipping + ValueAnimator backgroundAnimator = ValueAnimator.ofObject( + mBackgroundAnimationRectEvaluator, + initialBackground, + expandedBackgroundBounds); + backgroundAnimator.addUpdateListener(valueAnimator -> invalidateOutline()); + + float iconViewScaling = mIconViewDrawableExpandedSize / (float) mAppIconSize; + float arrowTranslationX = + expandedBackgroundBounds.right - collapsedBackgroundBounds.right; + float iconCenterToTextCollapsed = mAppIconSize / 2f + mAppNameHorizontalMargin; + float iconCenterToTextExpanded = + mIconViewDrawableExpandedSize / 2f + mAppNameHorizontalMargin; + float textTranslationX = iconCenterToTextExpanded - iconCenterToTextCollapsed; + + float textTranslationXWithRtl = isRtl ? -textTranslationX : textTranslationX; + float arrowTranslationWithRtl = isRtl ? -arrowTranslationX : arrowTranslationX; + + mAnimator.playTogether( + expandedTextRevealAnim, + backgroundAnimator, + ObjectAnimator.ofFloat(mIconView, SCALE_X, iconViewScaling), + ObjectAnimator.ofFloat(mIconView, SCALE_Y, iconViewScaling), + ObjectAnimator.ofFloat(mIconTextCollapsedView, TRANSLATION_X, + textTranslationXWithRtl), + ObjectAnimator.ofFloat(mIconTextExpandedView, TRANSLATION_X, + textTranslationXWithRtl), + ObjectAnimator.ofFloat(mIconTextCollapsedView, ALPHA, 0), + ObjectAnimator.ofFloat(mIconTextExpandedView, ALPHA, 1), + ObjectAnimator.ofFloat(mIconArrowView, TRANSLATION_X, arrowTranslationWithRtl), + ObjectAnimator.ofFloat(mIconArrowView, SCALE_Y, -1)); + mAnimator.setDuration(MENU_BACKGROUND_REVEAL_DURATION); + } else { + // Clip expanded text with reveal animation so it doesn't go beyond the edge of the menu + Animator expandedTextClipAnim = ViewAnimationUtils.createCircularReveal( + mIconTextExpandedView, 0, mIconTextExpandedView.getHeight() / 2, + mIconTextExpandedView.getWidth(), mIconTextCollapsedView.getWidth()); + + // Animate background clipping + ValueAnimator backgroundAnimator = ValueAnimator.ofObject( + mBackgroundAnimationRectEvaluator, + initialBackground, + collapsedBackgroundBounds); + backgroundAnimator.addUpdateListener(valueAnimator -> invalidateOutline()); + + mAnimator.playTogether( + expandedTextClipAnim, + backgroundAnimator, + ObjectAnimator.ofFloat(mIconView, SCALE_PROPERTY, 1), + ObjectAnimator.ofFloat(mIconTextCollapsedView, TRANSLATION_X, 0), + ObjectAnimator.ofFloat(mIconTextExpandedView, TRANSLATION_X, 0), + ObjectAnimator.ofFloat(mIconTextCollapsedView, ALPHA, 1), + ObjectAnimator.ofFloat(mIconTextExpandedView, ALPHA, 0), + ObjectAnimator.ofFloat(mIconArrowView, TRANSLATION_X, 0), + ObjectAnimator.ofFloat(mIconArrowView, SCALE_Y, 1)); + mAnimator.setDuration(MENU_BACKGROUND_HIDE_DURATION); + } + + mAnimator.setInterpolator(EMPHASIZED); + mAnimator.start(); + } + + private Rect getCollapsedBackgroundLtrBounds() { + Rect bounds = new Rect( + 0, + 0, + Math.min(mMaxWidth, mCollapsedMenuDefaultWidth), + mCollapsedMenuDefaultHeight); + bounds.offset(mBackgroundMarginTopStart, mBackgroundMarginTopStart); + return bounds; + } + + private Rect getExpandedBackgroundLtrBounds() { + return new Rect(0, 0, mExpandedMenuDefaultWidth, mExpandedMenuDefaultHeight); + } + + private void cancelInProgressAnimations() { + // We null the `AnimatorSet` because it holds references to the `Animators` which aren't + // expecting to be mutable and will cause a crash if they are re-used. + if (mAnimator != null && mAnimator.isStarted()) { + mAnimator.cancel(); + mAnimator = null; + } + } + + @Override + public View asView() { + return this; + } +} diff --git a/quickstep/src/com/android/quickstep/views/IconView.java b/quickstep/src/com/android/quickstep/views/IconView.java new file mode 100644 index 0000000000..bb4a7ecda6 --- /dev/null +++ b/quickstep/src/com/android/quickstep/views/IconView.java @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.quickstep.views; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.View; +import android.widget.FrameLayout; + +import androidx.annotation.Nullable; + +import com.android.launcher3.DeviceProfile; +import com.android.launcher3.Utilities; +import com.android.launcher3.util.MultiValueAlpha; +import com.android.launcher3.views.ActivityContext; +import com.android.quickstep.orientation.RecentsPagedOrientationHandler; +import com.android.quickstep.util.RecentsOrientedState; + +/** + * A view which draws a drawable stretched to fit its size. Unlike ImageView, it avoids relayout + * when the drawable changes. + */ +public class IconView extends View implements TaskViewIcon { + private static final int NUM_ALPHA_CHANNELS = 2; + private static final int INDEX_CONTENT_ALPHA = 0; + private static final int INDEX_MODAL_ALPHA = 1; + + private final MultiValueAlpha mMultiValueAlpha; + + @Nullable + private Drawable mDrawable; + private int mDrawableWidth, mDrawableHeight; + + public IconView(Context context) { + this(context, null); + } + + public IconView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public IconView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public IconView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + mMultiValueAlpha = new MultiValueAlpha(this, NUM_ALPHA_CHANNELS); + mMultiValueAlpha.setUpdateVisibility(/* updateVisibility= */ true); + } + + /** + * Sets a {@link Drawable} to be displayed. + */ + @Override + public void setDrawable(@Nullable Drawable d) { + if (mDrawable != null) { + mDrawable.setCallback(null); + } + mDrawable = d; + if (mDrawable != null) { + mDrawable.setCallback(this); + setDrawableSizeInternal(getWidth(), getHeight()); + } + invalidate(); + } + + /** + * Sets the size of the icon drawable. + */ + @Override + public void setDrawableSize(int iconWidth, int iconHeight) { + mDrawableWidth = iconWidth; + mDrawableHeight = iconHeight; + if (mDrawable != null) { + setDrawableSizeInternal(getWidth(), getHeight()); + } + } + + private void setDrawableSizeInternal(int selfWidth, int selfHeight) { + Rect selfRect = new Rect(0, 0, selfWidth, selfHeight); + Rect drawableRect = new Rect(); + Gravity.apply(Gravity.CENTER, mDrawableWidth, mDrawableHeight, selfRect, drawableRect); + mDrawable.setBounds(drawableRect); + } + + @Override + @Nullable + public Drawable getDrawable() { + return mDrawable; + } + + @Override + public int getDrawableWidth() { + return mDrawableWidth; + } + + @Override + public int getDrawableHeight() { + return mDrawableHeight; + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + if (mDrawable != null) { + setDrawableSizeInternal(w, h); + } + } + + @Override + protected boolean verifyDrawable(Drawable who) { + return super.verifyDrawable(who) || who == mDrawable; + } + + @Override + protected void drawableStateChanged() { + super.drawableStateChanged(); + + final Drawable drawable = mDrawable; + if (drawable != null && drawable.isStateful() + && drawable.setState(getDrawableState())) { + invalidateDrawable(drawable); + } + } + + @Override + protected void onDraw(Canvas canvas) { + if (mDrawable != null) { + mDrawable.draw(canvas); + } + } + + @Override + public boolean hasOverlappingRendering() { + return false; + } + + @Override + public void setContentAlpha(float alpha) { + mMultiValueAlpha.get(INDEX_CONTENT_ALPHA).setValue(alpha); + } + + @Override + public void setModalAlpha(float alpha) { + mMultiValueAlpha.get(INDEX_MODAL_ALPHA).setValue(alpha); + } + + /** + * Set the tint color of the icon, useful for scrimming or dimming. + * + * @param color to blend in. + * @param amount [0,1] 0 no tint, 1 full tint + */ + @Override + public void setIconColorTint(int color, float amount) { + if (mDrawable != null) { + mDrawable.setColorFilter(Utilities.makeColorTintingColorFilter(color, amount)); + } + } + + @Override + public void setIconOrientation(RecentsOrientedState orientationState, boolean isGridTask) { + RecentsPagedOrientationHandler orientationHandler = + orientationState.getOrientationHandler(); + boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL; + DeviceProfile deviceProfile = + ActivityContext.lookupContext(getContext()).getDeviceProfile(); + + FrameLayout.LayoutParams iconParams = (FrameLayout.LayoutParams) getLayoutParams(); + + int thumbnailTopMargin = deviceProfile.overviewTaskThumbnailTopMarginPx; + int taskIconHeight = deviceProfile.overviewTaskIconSizePx; + int taskMargin = deviceProfile.overviewTaskMarginPx; + + orientationHandler.setTaskIconParams(iconParams, taskMargin, taskIconHeight, + thumbnailTopMargin, isRtl); + iconParams.width = iconParams.height = taskIconHeight; + setLayoutParams(iconParams); + + setRotation(orientationHandler.getDegreesRotated()); + int iconDrawableSize = isGridTask ? deviceProfile.overviewTaskIconDrawableSizeGridPx + : deviceProfile.overviewTaskIconDrawableSizePx; + setDrawableSize(iconDrawableSize, iconDrawableSize); + } + + @Override + public View asView() { + return this; + } +} diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java index a6be3f789a..b4bca1ced8 100644 --- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java +++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java @@ -16,16 +16,17 @@ package com.android.quickstep.views; import static android.app.ActivityTaskManager.INVALID_TASK_ID; -import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY; -import static com.android.launcher3.Flags.enableGridOnlyOverview; +import static com.android.launcher3.LauncherState.ALL_APPS; import static com.android.launcher3.LauncherState.CLEAR_ALL_BUTTON; -import static com.android.launcher3.LauncherState.ADD_DESK_BUTTON; +import static com.android.launcher3.LauncherState.EDIT_MODE; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK; import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT; +import static com.android.launcher3.LauncherState.SPRING_LOADED; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTION_EXIT_HOME; +import static com.android.window.flags2.Flags.enableDesktopWindowingWallpaperActivity; import android.annotation.TargetApi; import android.content.Context; @@ -39,6 +40,7 @@ import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; import com.android.launcher3.Utilities; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.desktop.DesktopRecentsTransitionController; import com.android.launcher3.logging.StatsLogManager; import com.android.launcher3.statehandlers.DepthController; @@ -49,13 +51,12 @@ import com.android.launcher3.uioverrides.QuickstepLauncher; import com.android.launcher3.util.PendingSplitSelectInfo; import com.android.launcher3.util.SplitConfigurationOptions; import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource; -import com.android.quickstep.BaseContainerInterface; import com.android.quickstep.GestureState; import com.android.quickstep.LauncherActivityInterface; +import com.android.quickstep.RotationTouchHelper; import com.android.quickstep.SystemUiProxy; -import com.android.quickstep.util.AnimUtils; import com.android.quickstep.util.SplitSelectStateController; -import com.android.wm.shell.shared.GroupedTaskInfo; +import com.android.systemui.shared.recents.model.Task; import kotlin.Unit; @@ -75,7 +76,7 @@ public class LauncherRecentsView extends RecentsView remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(true)); } } @@ -211,10 +200,7 @@ public class LauncherRecentsView extends RecentsView getContainerInterface(int displayId) { - return LauncherActivityInterface.INSTANCE; - } - @Override protected void onDismissAnimationEnds() { super.onDismissAnimationEnds(); @@ -257,10 +238,10 @@ public class LauncherRecentsView extends RecentsView extends FrameLayo HIDDEN_DESKTOP }) @Retention(RetentionPolicy.SOURCE) - public @interface ActionsHiddenFlags { } + public @interface ActionsHiddenFlags { + } public static final int HIDDEN_NON_ZERO_ROTATION = 1 << 0; public static final int HIDDEN_NO_TASKS = 1 << 1; @@ -80,9 +79,10 @@ public class OverviewActionsView extends FrameLayo @IntDef(flag = true, value = { DISABLED_SCROLLING, DISABLED_ROTATED, - DISABLED_NO_THUMBNAIL}) + DISABLED_NO_THUMBNAIL }) @Retention(RetentionPolicy.SOURCE) - public @interface ActionsDisabledFlags { } + public @interface ActionsDisabledFlags { + } public static final int DISABLED_SCROLLING = 1 << 0; public static final int DISABLED_ROTATED = 1 << 1; @@ -98,11 +98,14 @@ public class OverviewActionsView extends FrameLayo private static final int INDEX_3P_LAUNCHER = 7; private static final int NUM_ALPHAS = 8; - public @interface SplitButtonHiddenFlags { } + public @interface SplitButtonHiddenFlags { + } + public static final int FLAG_SMALL_SCREEN_HIDE_SPLIT = 1 << 0; /** - * Holds an AnimatedFloat for each alpha property, used to set or animate alpha values in + * Holds an AnimatedFloat for each alpha property, used to set or animate alpha + * values in * {@link #mMultiValueAlphas}. */ private final AnimatedFloat[] mAlphaProperties = new AnimatedFloat[NUM_ALPHAS]; @@ -114,11 +117,14 @@ public class OverviewActionsView extends FrameLayo /** Index used for grouped-task actions in the mMultiValueAlphas array */ private static final int GROUP_ACTIONS_ALPHAS = 1; - /** Container for the action buttons below a focused, non-split Overview tile. */ + /** + * Container for the action buttons below a focused, non-split Overview tile. + */ protected LinearLayout mActionButtons; private Button mSplitButton; /** - * The "save app pair" button. Currently this is the only button that is not contained in + * The "save app pair" button. Currently this is the only button that is not + * contained in * mActionButtons, since it is the sole button that appears for a grouped task. */ private Button mSaveAppPairButton; @@ -157,17 +163,18 @@ public class OverviewActionsView extends FrameLayo protected void onFinishInflate() { super.onFinishInflate(); // Initialize 2 view containers: one for single tasks, one for grouped tasks. - // These will take up the same space on the screen and alternate visibility as needed. + // These will take up the same space on the screen and alternate visibility as + // needed. // Currently, the only grouped task action is "save app pairs". mActionButtons = findViewById(R.id.action_buttons); mSaveAppPairButton = findViewById(R.id.action_save_app_pair); - TypefaceUtils.setTypeface(mSaveAppPairButton, FontFamily.GSF_LABEL_LARGE); - // Initialize a list to hold alphas for mActionButtons and any group action buttons. + // Initialize a list to hold alphas for mActionButtons and any group action + // buttons. mMultiValueAlphas[ACTIONS_ALPHAS] = new MultiValueAlpha(mActionButtons, NUM_ALPHAS); - mMultiValueAlphas[GROUP_ACTIONS_ALPHAS] = - new MultiValueAlpha(mSaveAppPairButton, NUM_ALPHAS); + mMultiValueAlphas[GROUP_ACTIONS_ALPHAS] = new MultiValueAlpha(mSaveAppPairButton, NUM_ALPHAS); Arrays.stream(mMultiValueAlphas).forEach(a -> a.setUpdateVisibility(true)); - // To control alpha simultaneously on mActionButtons and any group action buttons, we set up + // To control alpha simultaneously on mActionButtons and any group action + // buttons, we set up // an AnimatedFloat for each alpha property. for (int i = 0; i < NUM_ALPHAS; i++) { final int index = i; @@ -178,8 +185,10 @@ public class OverviewActionsView extends FrameLayo }, 1f /* initialValue */); } - // The screenshot button is implemented as a Button in launcher3 and NexusLauncher, but is - // an ImageButton in go launcher (does not share a common class with Button). Take care when + // The screenshot button is implemented as a Button in launcher3 and + // NexusLauncher, but is + // an ImageButton in go launcher (does not share a common class with Button). + // Take care when // casting this. View screenshotButton = findViewById(R.id.action_screenshot); screenshotButton.setOnClickListener(this); @@ -236,12 +245,15 @@ public class OverviewActionsView extends FrameLayo } /** - * Updates the proper disabled flag to indicate whether OverviewActionsView should be enabled. - * Ignores DISABLED_ROTATED flag for determining enabled. Flag is used to enable/disable + * Updates the proper disabled flag to indicate whether OverviewActionsView + * should be enabled. + * Ignores DISABLED_ROTATED flag for determining enabled. Flag is used to + * enable/disable * buttons individually, currently done for select button in subclass. * * @param disabledFlags The flag to update. - * @param enable Whether to enable the disable flag: True will cause view to be disabled. + * @param enable Whether to enable the disable flag: True will cause view + * to be disabled. */ public void updateDisabledFlags(@ActionsDisabledFlags int disabledFlags, boolean enable) { if (enable) { @@ -254,11 +266,14 @@ public class OverviewActionsView extends FrameLayo } /** - * Updates a batch of flags to hide and show actions buttons when a grouped task (split screen) + * Updates a batch of flags to hide and show actions buttons when a grouped task + * (split screen) * is focused. - * @param isGroupedTask True if the focused task is a grouped task. - * @param canSaveAppPair True if the focused task is a grouped task and can be saved as an app - * pair. + * + * @param isGroupedTask True if the focused task is a grouped task. + * @param canSaveAppPair True if the focused task is a grouped task and can be + * saved as an app + * pair. */ public void updateForGroupedTask(boolean isGroupedTask, boolean canSaveAppPair) { Log.d(TAG, "updateForGroupedTask() called with: isGroupedTask = [" + isGroupedTask @@ -269,7 +284,8 @@ public class OverviewActionsView extends FrameLayo } /** - * Updates a batch of flags to hide and show actions buttons for tablet/non tablet case. + * Updates a batch of flags to hide and show actions buttons for tablet/non + * tablet case. */ private void updateForIsTablet() { assert mDp != null; @@ -279,9 +295,7 @@ public class OverviewActionsView extends FrameLayo } private void updateActionButtonsVisibility() { - if (mDp == null) { - return; - } + assert mDp != null; boolean showSingleTaskActions = !mIsGroupedTask; boolean showGroupActions = mIsGroupedTask && mDp.isTablet && mCanSaveAppPair; Log.d(TAG, "updateActionButtonsVisibility() called: showSingleTaskActions = [" @@ -306,14 +320,17 @@ public class OverviewActionsView extends FrameLayo } /** - * Updates the proper flags to indicate whether the "Split screen" button should be hidden. + * Updates the proper flags to indicate whether the "Split screen" button should + * be hidden. * * @param flag The flag to update. - * @param enable Whether to enable the hidden flag: True will cause view to be hidden. + * @param enable Whether to enable the hidden flag: True will cause view to be + * hidden. */ void updateSplitButtonHiddenFlags(@SplitButtonHiddenFlags int flag, boolean enable) { - if (mSplitButton == null) return; + if (mSplitButton == null) + return; if (enable) { mSplitButtonHiddenFlags |= flag; } else { @@ -355,14 +372,19 @@ public class OverviewActionsView extends FrameLayo } /** - * Offsets OverviewActionsView horizontal position based on 3 button nav container in taskbar. + * Offsets OverviewActionsView horizontal position based on 3 button nav + * container in taskbar. */ private void updatePadding() { - // If taskbar is in overview, overview action has dedicated space above nav buttons + // If taskbar is in overview, overview action has dedicated space above nav + // buttons setPadding(mInsets.left, 0, mInsets.right, 0); } - /** Updates vertical margins for different navigation mode or configuration changes. */ + /** + * Updates vertical margins for different navigation mode or configuration + * changes. + */ public void updateVerticalMargin(NavigationMode mode) { updateActionBarPosition(mActionButtons); updateActionBarPosition(mSaveAppPairButton); diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java index 63bb799c97..163732ff07 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -17,8 +17,6 @@ package com.android.quickstep.views; import static android.app.ActivityTaskManager.INVALID_TASK_ID; -import static android.os.Trace.traceBegin; -import static android.os.Trace.traceEnd; import static android.view.Surface.ROTATION_0; import static android.view.View.MeasureSpec.EXACTLY; import static android.view.View.MeasureSpec.makeMeasureSpec; @@ -27,25 +25,21 @@ import static com.android.app.animation.Interpolators.ACCELERATE; import static com.android.app.animation.Interpolators.ACCELERATE_0_75; import static com.android.app.animation.Interpolators.ACCELERATE_DECELERATE; import static com.android.app.animation.Interpolators.DECELERATE_2; -import static com.android.app.animation.Interpolators.EMPHASIZED; import static com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE; import static com.android.app.animation.Interpolators.FAST_OUT_SLOW_IN; import static com.android.app.animation.Interpolators.FINAL_FRAME; import static com.android.app.animation.Interpolators.LINEAR; +import static com.android.app.animation.Interpolators.OVERSHOOT_0_75; import static com.android.app.animation.Interpolators.clampToProgress; import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE; +import static com.android.launcher3.AbstractFloatingView.TYPE_TASK_MENU; +import static com.android.launcher3.AbstractFloatingView.getTopOpenViewWithType; import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS; import static com.android.launcher3.Flags.enableAdditionalHomeAnimations; -import static com.android.launcher3.Flags.enableDesktopExplodedView; -import static com.android.launcher3.Flags.enableDesktopTaskAlphaAnimation; import static com.android.launcher3.Flags.enableGridOnlyOverview; -import static com.android.launcher3.Flags.enableLargeDesktopWindowingTile; -import static com.android.launcher3.Flags.enableOverviewBackgroundWallpaperBlur; import static com.android.launcher3.Flags.enableRefactorTaskThumbnail; -import static com.android.launcher3.Flags.enableSeparateExternalDisplayTasks; import static com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS; import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA; -import static com.android.launcher3.LauncherAnimUtils.VIEW_BACKGROUND_COLOR; import static com.android.launcher3.LauncherState.BACKGROUND_APP; import static com.android.launcher3.QuickstepTransitionManager.RECENTS_LAUNCH_DURATION; import static com.android.launcher3.Utilities.EDGE_NAV_BAR; @@ -57,26 +51,27 @@ import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCH import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_CLEAR_ALL; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_DISMISS_SWIPE_UP; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_SWIPE_DOWN; -import static com.android.launcher3.statehandlers.DesktopVisibilityController.INACTIVE_DESK_ID; import static com.android.launcher3.testing.shared.TestProtocol.DISMISS_ANIMATION_ENDS_MESSAGE; import static com.android.launcher3.touch.PagedOrientationHandler.CANVAS_TRANSLATE; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE; import static com.android.launcher3.util.SystemUiController.UI_STATE_FULLSCREEN_TASK; -import static com.android.quickstep.BaseContainerInterface.getTaskDimension; import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId; -import static com.android.quickstep.util.DesksUtils.areMultiDesksFlagsEnabled; import static com.android.quickstep.util.LogUtils.splitFailureMessage; +import static com.android.quickstep.util.TaskGridNavHelper.DIRECTION_DOWN; +import static com.android.quickstep.util.TaskGridNavHelper.DIRECTION_LEFT; +import static com.android.quickstep.util.TaskGridNavHelper.DIRECTION_RIGHT; +import static com.android.quickstep.util.TaskGridNavHelper.DIRECTION_TAB; +import static com.android.quickstep.util.TaskGridNavHelper.DIRECTION_UP; import static com.android.quickstep.views.ClearAllButton.DISMISS_ALPHA; import static com.android.quickstep.views.OverviewActionsView.HIDDEN_ACTIONS_IN_MENU; import static com.android.quickstep.views.OverviewActionsView.HIDDEN_DESKTOP; import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NON_ZERO_ROTATION; import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_RECENTS; import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_TASKS; +import static com.android.quickstep.views.OverviewActionsView.HIDDEN_SPLIT_SCREEN; import static com.android.quickstep.views.OverviewActionsView.HIDDEN_SPLIT_SELECT_ACTIVE; -import static com.android.quickstep.views.RecentsViewUtils.DESK_EXPLODE_PROGRESS; -import static com.android.quickstep.views.TaskView.SPLIT_ALPHA; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -95,7 +90,6 @@ import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.BlendMode; import android.graphics.Canvas; -import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Point; import android.graphics.PointF; @@ -106,7 +100,6 @@ import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.SystemClock; -import android.os.Trace; import android.os.UserHandle; import android.os.VibrationEffect; import android.text.Layout; @@ -132,15 +125,12 @@ import android.view.animation.Interpolator; import android.widget.ListView; import android.widget.OverScroller; import android.widget.Toast; -import android.window.DesktopModeFlags; import android.window.PictureInPictureSurfaceTransaction; -import android.window.TransitionInfo; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.UiThread; import androidx.core.graphics.ColorUtils; -import androidx.dynamicanimation.animation.SpringAnimation; import com.android.internal.jank.Cuj; import com.android.launcher3.AbstractFloatingView; @@ -148,6 +138,7 @@ import com.android.launcher3.BaseActivity.MultiWindowModeChangedListener; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Flags; import com.android.launcher3.Insettable; +import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.PagedView; import com.android.launcher3.R; import com.android.launcher3.Utilities; @@ -163,7 +154,6 @@ import com.android.launcher3.logger.LauncherAtom; import com.android.launcher3.logging.StatsLogManager; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.statehandlers.DepthController; -import com.android.launcher3.statehandlers.DesktopVisibilityController; import com.android.launcher3.statemanager.BaseState; import com.android.launcher3.statemanager.StateManager; import com.android.launcher3.statemanager.StatefulContainer; @@ -184,11 +174,8 @@ import com.android.launcher3.util.TraceHelper; import com.android.launcher3.util.TranslateEdgeEffect; import com.android.launcher3.util.VibratorWrapper; import com.android.launcher3.util.ViewPool; -import com.android.launcher3.util.coroutines.DispatcherProvider; -import com.android.launcher3.util.window.WindowManagerProxy.DesktopVisibilityListener; import com.android.quickstep.BaseContainerInterface; import com.android.quickstep.GestureState; -import com.android.quickstep.HighResLoadingState; import com.android.quickstep.OverviewCommandHelper; import com.android.quickstep.RecentsAnimationController; import com.android.quickstep.RecentsAnimationTargets; @@ -201,31 +188,24 @@ import com.android.quickstep.RotationTouchHelper; import com.android.quickstep.SplitSelectionListener; import com.android.quickstep.SystemUiProxy; import com.android.quickstep.TaskOverlayFactory; +import com.android.quickstep.TaskThumbnailCache; import com.android.quickstep.TaskViewUtils; import com.android.quickstep.TopTaskTracker; import com.android.quickstep.ViewUtils; -import com.android.quickstep.fallback.window.RecentsWindowFlags; import com.android.quickstep.orientation.RecentsPagedOrientationHandler; -import com.android.quickstep.recents.data.RecentTasksRepository; -import com.android.quickstep.recents.data.RecentsDeviceProfileRepository; -import com.android.quickstep.recents.data.RecentsDeviceProfileRepositoryImpl; -import com.android.quickstep.recents.data.RecentsRotationStateRepository; -import com.android.quickstep.recents.data.RecentsRotationStateRepositoryImpl; -import com.android.quickstep.recents.di.RecentsDependencies; +import com.android.quickstep.recents.data.TasksRepository; import com.android.quickstep.recents.viewmodel.RecentsViewData; -import com.android.quickstep.recents.viewmodel.RecentsViewModel; -import com.android.quickstep.util.ActiveGestureProtoLogProxy; +import com.android.quickstep.util.ActiveGestureErrorDetector; +import com.android.quickstep.util.ActiveGestureLog; import com.android.quickstep.util.AnimUtils; import com.android.quickstep.util.DesktopTask; import com.android.quickstep.util.GroupTask; import com.android.quickstep.util.LayoutUtils; import com.android.quickstep.util.RecentsAtomicAnimationFactory; import com.android.quickstep.util.RecentsOrientedState; -import com.android.quickstep.util.SingleTask; import com.android.quickstep.util.SplitAnimationController.Companion.SplitAnimInitProps; import com.android.quickstep.util.SplitAnimationTimings; import com.android.quickstep.util.SplitSelectStateController; -import com.android.quickstep.util.SplitTask; import com.android.quickstep.util.SurfaceTransaction; import com.android.quickstep.util.SurfaceTransactionApplier; import com.android.quickstep.util.TaskGridNavHelper; @@ -233,55 +213,47 @@ import com.android.quickstep.util.TaskViewSimulator; import com.android.quickstep.util.TaskVisualsChangeListener; import com.android.quickstep.util.TransformParams; import com.android.quickstep.util.VibrationConstants; +import com.android.quickstep.views.TaskView.TaskContainer; import com.android.systemui.plugins.ResourceProvider; import com.android.systemui.shared.recents.model.Task; -import com.android.systemui.shared.recents.model.Task.TaskKey; import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.InteractionJankMonitorWrapper; import com.android.systemui.shared.system.PackageManagerWrapper; import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.shared.system.TaskStackChangeListeners; +import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource; import com.android.wm.shell.common.pip.IPipAnimationListener; -import com.android.wm.shell.shared.GroupedTaskInfo; -import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; -import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource; +import com.android.wm.shell.shared.DesktopModeStatus; import kotlin.Unit; -import kotlin.collections.CollectionsKt; -import kotlin.jvm.functions.Function0; - -import kotlinx.coroutines.CoroutineScope; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; -import java.util.Set; import java.util.function.Consumer; import java.util.stream.Collectors; + /** * A list of recent tasks. - * * @param : the container that should host recents view - * @param : the type of base state that will be used + * @param : the type of base state that will be used */ -public abstract class RecentsView< - CONTAINER_TYPE extends Context & RecentsViewContainer & StatefulContainer, - STATE_TYPE extends BaseState> extends PagedView implements Insettable, - HighResLoadingState.HighResLoadingStateChangedCallback, - TaskVisualsChangeListener, DesktopVisibilityListener { - protected static final String TAG = "RecentsView"; +public abstract class RecentsView> extends PagedView implements Insettable, + TaskThumbnailCache.HighResLoadingState.HighResLoadingStateChangedCallback, + TaskVisualsChangeListener { + + private static final String TAG = "RecentsView"; private static final boolean DEBUG = false; - public static final FloatProperty> CONTENT_ALPHA = - new FloatProperty<>("contentAlpha") { + public static final FloatProperty CONTENT_ALPHA = + new FloatProperty("contentAlpha") { @Override public void setValue(RecentsView view, float v) { view.setContentAlpha(v); @@ -293,8 +265,8 @@ public abstract class RecentsView< } }; - public static final FloatProperty> FULLSCREEN_PROGRESS = - new FloatProperty<>("fullscreenProgress") { + public static final FloatProperty FULLSCREEN_PROGRESS = + new FloatProperty("fullscreenProgress") { @Override public void setValue(RecentsView recentsView, float v) { recentsView.setFullscreenProgress(v); @@ -306,8 +278,8 @@ public abstract class RecentsView< } }; - public static final FloatProperty> TASK_MODALNESS = - new FloatProperty<>("taskModalness") { + public static final FloatProperty TASK_MODALNESS = + new FloatProperty("taskModalness") { @Override public void setValue(RecentsView recentsView, float v) { recentsView.setTaskModalness(v); @@ -319,8 +291,8 @@ public abstract class RecentsView< } }; - public static final FloatProperty> ADJACENT_PAGE_HORIZONTAL_OFFSET = - new FloatProperty<>("adjacentPageHorizontalOffset") { + public static final FloatProperty ADJACENT_PAGE_HORIZONTAL_OFFSET = + new FloatProperty("adjacentPageHorizontalOffset") { @Override public void setValue(RecentsView recentsView, float v) { if (recentsView.mAdjacentPageHorizontalOffset != v) { @@ -335,20 +307,6 @@ public abstract class RecentsView< } }; - public static final FloatProperty> RUNNING_TASK_ATTACH_ALPHA = - new FloatProperty<>("runningTaskAttachAlpha") { - @Override - public void setValue(RecentsView recentsView, float v) { - recentsView.mRunningTaskAttachAlpha = v; - recentsView.applyAttachAlpha(); - } - - @Override - public Float get(RecentsView recentsView) { - return recentsView.mRunningTaskAttachAlpha; - } - }; - public static final int SCROLL_VIBRATION_PRIMITIVE = Utilities.ATLEAST_S ? VibrationEffect.Composition.PRIMITIVE_LOW_TICK : -1; public static final float SCROLL_VIBRATION_PRIMITIVE_SCALE = 0.6f; @@ -359,9 +317,10 @@ public abstract class RecentsView< /** * Can be used to tint the color of the RecentsView to simulate a scrim that can views * excluded from. Really should be a proper scrim. + * TODO(b/187528071): Remove this and replace with a real scrim. */ - private static final FloatProperty> COLOR_TINT = - new FloatProperty<>("colorTint") { + private static final FloatProperty COLOR_TINT = + new FloatProperty("colorTint") { @Override public void setValue(RecentsView recentsView, float v) { recentsView.setColorTint(v); @@ -379,8 +338,8 @@ public abstract class RecentsView< * more specific, we'd want to create a similar FloatProperty just for a TaskView's * offsetX/Y property */ - public static final FloatProperty> TASK_SECONDARY_TRANSLATION = - new FloatProperty<>("taskSecondaryTranslation") { + public static final FloatProperty TASK_SECONDARY_TRANSLATION = + new FloatProperty("taskSecondaryTranslation") { @Override public void setValue(RecentsView recentsView, float v) { recentsView.setTaskViewsResistanceTranslation(v); @@ -398,8 +357,8 @@ public abstract class RecentsView< * more specific, we'd want to create a similar FloatProperty just for a TaskView's * offsetX/Y property */ - public static final FloatProperty> TASK_PRIMARY_SPLIT_TRANSLATION = - new FloatProperty<>("taskPrimarySplitTranslation") { + public static final FloatProperty TASK_PRIMARY_SPLIT_TRANSLATION = + new FloatProperty("taskPrimarySplitTranslation") { @Override public void setValue(RecentsView recentsView, float v) { recentsView.setTaskViewsPrimarySplitTranslation(v); @@ -411,8 +370,8 @@ public abstract class RecentsView< } }; - public static final FloatProperty> TASK_SECONDARY_SPLIT_TRANSLATION = - new FloatProperty<>("taskSecondarySplitTranslation") { + public static final FloatProperty TASK_SECONDARY_SPLIT_TRANSLATION = + new FloatProperty("taskSecondarySplitTranslation") { @Override public void setValue(RecentsView recentsView, float v) { recentsView.setTaskViewsSecondarySplitTranslation(v); @@ -425,12 +384,15 @@ public abstract class RecentsView< }; /** Same as normal SCALE_PROPERTY, but also updates page offsets that depend on this scale. */ - public static final FloatProperty> RECENTS_SCALE_PROPERTY = - new FloatProperty<>("recentsScale") { + public static final FloatProperty RECENTS_SCALE_PROPERTY = + new FloatProperty("recentsScale") { @Override public void setValue(RecentsView view, float scale) { view.setScaleX(scale); view.setScaleY(scale); + if (enableRefactorTaskThumbnail()) { + view.mRecentsViewData.getScale().setValue(scale); + } view.mLastComputedTaskStartPushOutDistance = null; view.mLastComputedTaskEndPushOutDistance = null; view.runActionOnRemoteHandles(new Consumer() { @@ -455,8 +417,8 @@ public abstract class RecentsView< * Progress of Recents view from carousel layout to grid layout. If Recents is not shown as a * grid, then the value remains 0. */ - public static final FloatProperty> RECENTS_GRID_PROGRESS = - new FloatProperty<>("recentsGrid") { + public static final FloatProperty RECENTS_GRID_PROGRESS = + new FloatProperty("recentsGrid") { @Override public void setValue(RecentsView view, float gridProgress) { view.setGridProgress(gridProgress); @@ -468,27 +430,12 @@ public abstract class RecentsView< } }; - public static final FloatProperty> DESKTOP_CAROUSEL_DETACH_PROGRESS = - new FloatProperty<>("desktopCarouselDetachProgress") { - @Override - public void setValue(RecentsView view, float offset) { - view.mDesktopCarouselDetachProgress = offset; - view.applyAttachAlpha(); - view.updatePageOffsets(); - } - - @Override - public Float get(RecentsView view) { - return view.mDesktopCarouselDetachProgress; - } - }; - /** * Alpha of the task thumbnail splash, where being in BackgroundAppState has a value of 1, and * being in any other state has a value of 0. */ - public static final FloatProperty> TASK_THUMBNAIL_SPLASH_ALPHA = - new FloatProperty<>("taskThumbnailSplashAlpha") { + public static final FloatProperty TASK_THUMBNAIL_SPLASH_ALPHA = + new FloatProperty("taskThumbnailSplashAlpha") { @Override public void setValue(RecentsView view, float taskThumbnailSplashAlpha) { view.setTaskThumbnailSplashAlpha(taskThumbnailSplashAlpha); @@ -516,8 +463,11 @@ public abstract class RecentsView< private static final float FOREGROUND_SCRIM_TINT = 0.32f; + public final RecentsViewData mRecentsViewData = new RecentsViewData(); + public final TasksRepository mTasksRepository; + protected final RecentsOrientedState mOrientationState; - protected final BaseContainerInterface mSizeStrategy; + protected final BaseContainerInterface mSizeStrategy; @Nullable protected RecentsAnimationController mRecentsAnimationController; @Nullable @@ -533,9 +483,11 @@ public abstract class RecentsView< @Nullable protected RemoteTargetHandle[] mRemoteTargetHandles; + protected final Rect mLastComputedCarouselTaskSize = new Rect(); protected final Rect mLastComputedTaskSize = new Rect(); protected final Rect mLastComputedGridSize = new Rect(); protected final Rect mLastComputedGridTaskSize = new Rect(); + private TaskView mSelectedTask = null; // How much a task that is directly offscreen will be pushed out due to RecentsView scale/pivot. @Nullable protected Float mLastComputedTaskStartPushOutDistance = null; @@ -559,25 +511,19 @@ public abstract class RecentsView< private final int mSplitPlaceholderSize; private final int mSplitPlaceholderInset; private final ClearAllButton mClearAllButton; - @Nullable - private AddDesktopButton mAddDesktopButton = null; private final Rect mClearAllButtonDeadZoneRect = new Rect(); private final Rect mTaskViewDeadZoneRect = new Rect(); - private final Rect mTopRowDeadZoneRect = new Rect(); - private final Rect mBottomRowDeadZoneRect = new Rect(); - - @Nullable - private DesktopVisibilityController mDesktopVisibilityController = null; - /** - * Reflects if Recents is currently in the middle of a gesture, and if so, which related - * [GroupedTaskInfo] is running. If a gesture is not in progress, this will be null. + * Reflects if Recents is currently in the middle of a gesture, and if so, which tasks are + * running. If a gesture is not in progress, this will be null. */ - private @Nullable GroupedTaskInfo mActiveGestureGroupedTaskInfo; + private @Nullable Task[] mActiveGestureRunningTasks; // Keeps track of the previously known visible tasks for purposes of loading/unloading task data private final SparseBooleanArray mHasVisibleTaskData = new SparseBooleanArray(); + private final InvariantDeviceProfile mIdp; + /** * Getting views should be done via {@link #getTaskViewFromPool(int)} */ @@ -585,11 +531,9 @@ public abstract class RecentsView< private final ViewPool mGroupedTaskViewPool; private final ViewPool mDesktopTaskViewPool; - protected final TaskOverlayFactory mTaskOverlayFactory; + private final TaskOverlayFactory mTaskOverlayFactory; protected boolean mDisallowScrollToClearAll; - // True if it is not allowed to scroll to [AddDesktopButton]. - protected boolean mDisallowScrollToAddDesk; private boolean mOverlayEnabled; protected boolean mFreezeViewVisibility; private boolean mOverviewGridEnabled; @@ -600,22 +544,21 @@ public abstract class RecentsView< private int mClampedScrollOffsetBound; private float mAdjacentPageHorizontalOffset = 0; - private float mDesktopCarouselDetachProgress = 0; protected float mTaskViewsSecondaryTranslation = 0; protected float mTaskViewsPrimarySplitTranslation = 0; protected float mTaskViewsSecondarySplitTranslation = 0; // Progress from 0 to 1 where 0 is a carousel and 1 is a 2 row grid. private float mGridProgress = 0; private float mTaskThumbnailSplashAlpha = 0; - private boolean mBorderEnabled = false; private boolean mShowAsGridLastOnLayout = false; - protected final IntSet mTopRowIdSet = new IntSet(); + private final IntSet mTopRowIdSet = new IntSet(); private int mClearAllShortTotalWidthTranslation = 0; // The GestureEndTarget that is still in progress. @Nullable protected GestureState.GestureEndTarget mCurrentGestureEndTarget; + // TODO(b/187528071): Remove these and replace with a real scrim. private float mColorTint; private final int mTintingColor; @Nullable @@ -627,9 +570,6 @@ public abstract class RecentsView< private int mKeyboardTaskFocusSnapAnimationDuration; private int mKeyboardTaskFocusIndex = INVALID_PAGE; - private Map mTaskViewsDismissPrimaryTranslations = - new HashMap(); - /** * TODO: Call reloadIdNeeded in onTaskStackChanged. */ @@ -664,28 +604,25 @@ public abstract class RecentsView< @Override public void onTaskRemoved(int taskId) { if (!mHandleTaskStackChanges) { - Log.d(TAG, "onTaskRemoved: " + taskId + ", not handling task stack changes"); return; } - TaskContainer taskContainer = mUtils.getTaskContainerById(taskId); - if (taskContainer == null) { - Log.d(TAG, "onTaskRemoved: " + taskId + ", no associated Task"); + TaskView taskView = getTaskViewByTaskId(taskId); + if (taskView == null) { return; } - Log.d(TAG, "onTaskRemoved: " + taskId); - TaskKey taskKey = taskContainer.getTask().key; + Task.TaskKey taskKey = taskView.getFirstTask().key; UI_HELPER_EXECUTOR.execute(new CancellableTask<>( () -> PackageManagerWrapper.getInstance() .getActivityInfo(taskKey.getComponent(), taskKey.userId) == null, MAIN_EXECUTOR, apkRemoved -> { if (apkRemoved) { - dismissTask(taskId, /*animate=*/true, /*removeTask=*/false); + dismissTask(taskId); } else { mModel.isTaskRemoved(taskKey.id, taskRemoved -> { if (taskRemoved) { - dismissTask(taskId, /*animate=*/true, /*removeTask=*/false); + dismissTask(taskId); } }, RecentsFilterState.getFilter(mFilterState.getPackageNameToFilter())); } @@ -710,11 +647,12 @@ public abstract class RecentsView< protected int mRunningTaskViewId = -1; private int mTaskViewIdCount; protected boolean mRunningTaskTileHidden; - protected int mFocusedTaskViewId = INVALID_TASK_ID; + @Nullable + private Task[] mTmpRunningTasks; + protected int mFocusedTaskViewId = -1; - private boolean mTaskIconVisible = true; + private boolean mTaskIconScaledDown = false; private boolean mRunningTaskShowScreenshot = false; - private float mRunningTaskAttachAlpha; private boolean mOverviewStateEnabled; private boolean mHandleTaskStackChanges; @@ -783,12 +721,10 @@ public abstract class RecentsView< private final SplitSelectionListener mSplitSelectionListener = new SplitSelectionListener() { @Override - public void onSplitSelectionConfirmed() { - } + public void onSplitSelectionConfirmed() { } @Override - public void onSplitSelectionActive() { - } + public void onSplitSelectionActive() { } @Override public void onSplitSelectionExit(boolean launchedSplit) { @@ -826,6 +762,12 @@ public abstract class RecentsView< @Nullable private DesktopRecentsTransitionController mDesktopRecentsTransitionController; + /** + * Keeps track of the desktop task. Optional and only present when the feature flag is enabled. + */ + @Nullable + private DesktopTaskView mDesktopTaskView; + private MultiWindowModeChangedListener mMultiWindowModeChangedListener = new MultiWindowModeChangedListener() { @Override @@ -833,7 +775,7 @@ public abstract class RecentsView< mOrientationState.setMultiWindowMode(inMultiWindowMode); setLayoutRotation(mOrientationState.getTouchRotation(), mOrientationState.getDisplayRotation()); - mUtils.updateChildTaskOrientations(); + updateChildTaskOrientations(); if (!inMultiWindowMode && mOverviewStateEnabled) { // TODO: Re-enable layout transitions for addition of the unpinned task reloadIfNeeded(); @@ -854,93 +796,40 @@ public abstract class RecentsView< private int mOffsetMidpointIndexOverride = INVALID_PAGE; - /** - * Whether or not any task has been dismissed i.e. swiped away by the user, in the lifetime of - * RecentsView being open and displayed to the user. It is reset in the {@link #reset()} method - * i.e. when RecentsView closes. - */ - private boolean mAnyTaskHasBeenDismissed; - - protected final RecentsViewModel mRecentsViewModel; - private final RecentsViewModelHelper mHelper; - protected final RecentsViewUtils mUtils = new RecentsViewUtils(this); - protected final RecentsDismissUtils mDismissUtils = new RecentsDismissUtils(this); - - private final Matrix mTmpMatrix = new Matrix(); - - private int mTaskViewCount = 0; - - protected final BlurUtils mBlurUtils = new BlurUtils(this); - - @Nullable - public TaskView getFirstTaskView() { - return mUtils.getFirstTaskView(); - } - - public RecentsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + public RecentsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, + BaseContainerInterface sizeStrategy) { super(context, attrs, defStyleAttr); setEnableFreeScroll(true); - + mSizeStrategy = sizeStrategy; mContainer = RecentsViewContainer.containerFromContext(context); - mSizeStrategy = getContainerInterface(mContainer.getDisplayId()); - mOrientationState = new RecentsOrientedState( context, mSizeStrategy, this::animateRecentsRotationInPlace); final int rotation; rotation = Utilities.ATLEAST_R ? mContainer.getDisplay().getRotation() : WindowConfiguration.ROTATION_UNDEFINED; mOrientationState.setRecentsRotation(rotation); - // Start Recents Dependency graph - if (enableRefactorTaskThumbnail()) { - RecentsDependencies recentsDependencies = RecentsDependencies.Companion.maybeInitialize( - context); - String scopeId = recentsDependencies.createRecentsViewScope(context); - mRecentsViewModel = new RecentsViewModel( - recentsDependencies.inject(RecentTasksRepository.class, scopeId), - recentsDependencies.inject(RecentsViewData.class, scopeId) - ); - mHelper = new RecentsViewModelHelper( - mRecentsViewModel, - recentsDependencies.inject(CoroutineScope.class, scopeId), - recentsDependencies.inject(DispatcherProvider.class, scopeId) - ); - - recentsDependencies.provide(RecentsRotationStateRepository.class, scopeId, - () -> new RecentsRotationStateRepositoryImpl(mOrientationState)); - - recentsDependencies.provide(RecentsDeviceProfileRepository.class, scopeId, - () -> new RecentsDeviceProfileRepositoryImpl(mContainer)); - } else { - mRecentsViewModel = null; - mHelper = null; - } - mScrollHapticMinGapMillis = getResources() .getInteger(R.integer.recentsScrollHapticMinGapMillis); mFastFlingVelocity = getResources() .getDimensionPixelSize(R.dimen.recents_fast_fling_velocity); mModel = RecentsModel.INSTANCE.get(context); + mIdp = InvariantDeviceProfile.INSTANCE.get(context); + if (enableRefactorTaskThumbnail()) { + mTasksRepository = new TasksRepository( + mModel, mModel.getThumbnailCache(), mModel.getIconCache()); + } else { + mTasksRepository = null; + } mClearAllButton = (ClearAllButton) LayoutInflater.from(context) .inflate(R.layout.overview_clear_all_button, this, false); mClearAllButton.setOnClickListener(this::dismissAllTasks); - - if (DesktopModeStatus.enableMultipleDesktops(context)) { - mAddDesktopButton = (AddDesktopButton) LayoutInflater.from(context).inflate( - R.layout.overview_add_desktop_button, this, false); - mAddDesktopButton.setOnClickListener(this::createDesk); - - mDesktopVisibilityController = DesktopVisibilityController.INSTANCE.get(context); - } - mTaskViewPool = new ViewPool<>(context, this, R.layout.task, 20 /* max size */, 10 /* initial size */); - int groupedViewPoolInitialSize = enableRefactorTaskThumbnail() ? 2 : 10; mGroupedTaskViewPool = new ViewPool<>(context, this, - R.layout.task_grouped, 20 /* max size */, groupedViewPoolInitialSize); - int desktopViewPoolInitialSize = DesktopModeStatus.canEnterDesktopMode(context) ? 1 : 0; + R.layout.task_grouped, 20 /* max size */, 10 /* initial size */); mDesktopTaskViewPool = new ViewPool<>(context, this, R.layout.task_desktop, - 5 /* max size */, desktopViewPoolInitialSize); + 5 /* max size */, 1 /* initial size */); setOrientationHandler(mOrientationState.getOrientationHandler()); mIsRtl = getPagedOrientationHandler().getRecentsRtlSetting(getResources()); @@ -960,11 +849,8 @@ public abstract class RecentsView< mEmptyMessagePaint.setColor(Themes.getAttrColor(context, android.R.attr.textColorPrimary)); mEmptyMessagePaint.setTextSize(getResources() .getDimension(R.dimen.recents_empty_message_text_size)); - Typeface typeface = Typeface.create( - Typeface.create(Themes.getDefaultBodyFont(context), Typeface.NORMAL), - getFontWeight(), - false); - mEmptyMessagePaint.setTypeface(typeface); + mEmptyMessagePaint.setTypeface(Typeface.create(Themes.getDefaultBodyFont(context), + Typeface.NORMAL)); mEmptyMessagePaint.setAntiAlias(true); mEmptyMessagePadding = getResources() .getDimensionPixelSize(R.dimen.recents_empty_message_text_padding); @@ -1133,20 +1019,15 @@ public abstract class RecentsView< @Override @Nullable public Task onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData) { - if (enableRefactorTaskThumbnail()) { - return null; - } if (mHandleTaskStackChanges) { - if (!enableRefactorTaskThumbnail()) { - TaskView taskView = getTaskViewByTaskId(taskId); - if (taskView != null) { - for (TaskContainer container : taskView.getTaskContainers()) { - if (taskId != container.getTask().key.id) { - continue; - } - container.getThumbnailViewDeprecated().setThumbnail(container.getTask(), - thumbnailData); + TaskView taskView = getTaskViewByTaskId(taskId); + if (taskView != null) { + for (TaskContainer container : taskView.getTaskContainers()) { + if (container == null || taskId != container.getTask().key.id) { + continue; } + container.getThumbnailViewDeprecated().setThumbnail(container.getTask(), + thumbnailData); } } } @@ -1154,15 +1035,15 @@ public abstract class RecentsView< } @Override - public void onTaskIconChanged(@NonNull String pkg, @NonNull UserHandle user) { - for (TaskView taskView : getTaskViews()) { - Task firstTask = taskView.getFirstTask(); - if (firstTask != null && pkg.equals(firstTask.key.getPackageName()) - && firstTask.key.userId == user.getIdentifier()) { - firstTask.icon = null; - if (taskView.getTaskContainers().stream().anyMatch( + public void onTaskIconChanged(String pkg, UserHandle user) { + for (int i = 0; i < getTaskViewCount(); i++) { + TaskView tv = requireTaskViewAt(i); + Task task = tv.getFirstTask(); + if (pkg.equals(task.key.getPackageName()) && task.key.userId == user.getIdentifier()) { + task.icon = null; + if (tv.getTaskContainers().stream().anyMatch( container -> container.getIconView().getDrawable() != null)) { - taskView.onTaskListVisibilityChanged(true /* visible */); + tv.onTaskListVisibilityChanged(true /* visible */); } } } @@ -1170,36 +1051,42 @@ public abstract class RecentsView< @Override public void onTaskIconChanged(int taskId) { - if (enableRefactorTaskThumbnail()) { - return; - } TaskView taskView = getTaskViewByTaskId(taskId); if (taskView != null) { taskView.refreshTaskThumbnailSplash(); } } - /** Updates the thumbnail(s) of the relevant TaskView. */ - public void updateThumbnail(Map thumbnailData) { - if (!enableRefactorTaskThumbnail()) { - for (Map.Entry entry : thumbnailData.entrySet()) { - Integer id = entry.getKey(); - ThumbnailData thumbnail = entry.getValue(); - TaskView taskView = getTaskViewByTaskId(id); - if (taskView == null) { - continue; - } - // taskView could be a GroupedTaskView, so select the relevant task by ID - TaskContainer taskContainer = taskView.getTaskContainerById(id); - if (taskContainer == null) { - continue; - } - Task task = taskContainer.getTask(); - TaskThumbnailViewDeprecated taskThumbnailViewDeprecated = - taskContainer.getThumbnailViewDeprecated(); - taskThumbnailViewDeprecated.setThumbnail(task, thumbnail, /*refreshNow=*/false); + /** + * Update the thumbnail(s) of the relevant TaskView. + * @param refreshNow Refresh immediately if it's true. + */ + @Nullable + public TaskView updateThumbnail( + HashMap thumbnailData, boolean refreshNow) { + TaskView updatedTaskView = null; + for (Map.Entry entry : thumbnailData.entrySet()) { + Integer id = entry.getKey(); + ThumbnailData thumbnail = entry.getValue(); + TaskView taskView = getTaskViewByTaskId(id); + if (taskView == null) { + continue; } + // taskView could be a GroupedTaskView, so select the relevant task by ID + TaskContainer taskAttributes = taskView.getTaskContainerById(id); + if (taskAttributes == null) { + continue; + } + Task task = taskAttributes.getTask(); + TaskThumbnailViewDeprecated taskThumbnailViewDeprecated = + taskAttributes.getThumbnailViewDeprecated(); + taskThumbnailViewDeprecated.setThumbnail(task, thumbnail, refreshNow); + // thumbnailData can contain 1-2 ids, but they should correspond to the same + // TaskView, so overwriting is ok + updatedTaskView = taskView; } + + return updatedTaskView; } @Override @@ -1211,7 +1098,7 @@ public abstract class RecentsView< public void init(OverviewActionsView actionsView, SplitSelectStateController splitController, @Nullable DesktopRecentsTransitionController desktopRecentsTransitionController) { mActionsView = actionsView; - mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, !hasTaskViews()); + mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, getTaskViewCount() == 0); // Update flags for 1p/3p launchers mActionsView.updateFor3pLauncher(!supportsAppPairs()); mSplitSelectStateController = splitController; @@ -1229,10 +1116,9 @@ public abstract class RecentsView< /** * See overridden implementations - * * @return {@code true} if child TaskViews can be launched when user taps on them */ - public boolean canLaunchFullscreenTask() { + protected boolean canLaunchFullscreenTask() { return true; } @@ -1246,22 +1132,20 @@ public abstract class RecentsView< mSyncTransactionApplier = new SurfaceTransactionApplier(this); runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.getTransformParams() .setSyncTransactionApplier(mSyncTransactionApplier)); - RecentsModel.INSTANCE.get(mContext).addThumbnailChangeListener(this); + RecentsModel.INSTANCE.get(getContext()).addThumbnailChangeListener(this); mIPipAnimationListener.setActivityAndRecentsView(mContainer, this); - SystemUiProxy.INSTANCE.get(mContext).setPipAnimationListener( + SystemUiProxy.INSTANCE.get(getContext()).setPipAnimationListener( mIPipAnimationListener); mOrientationState.initListeners(); mTaskOverlayFactory.initListeners(); - mSplitSelectStateController.registerSplitListener(mSplitSelectionListener); - if (mDesktopVisibilityController != null) { - mDesktopVisibilityController.registerDesktopVisibilityListener(this); + if (FeatureFlags.enableSplitContextually()) { + mSplitSelectStateController.registerSplitListener(mSplitSelectionListener); } } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); - updateTaskStackListenerState(); mModel.getThumbnailCache().getHighResLoadingState().removeCallback(this); mContainer.removeMultiWindowModeChangedListener(mMultiWindowModeChangedListener); @@ -1270,81 +1154,51 @@ public abstract class RecentsView< runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.getTransformParams() .setSyncTransactionApplier(null)); executeSideTaskLaunchCallback(); - RecentsModel.INSTANCE.get(mContext).removeThumbnailChangeListener(this); - SystemUiProxy.INSTANCE.get(mContext).setPipAnimationListener(null); + RecentsModel.INSTANCE.get(getContext()).removeThumbnailChangeListener(this); + SystemUiProxy.INSTANCE.get(getContext()).setPipAnimationListener(null); mIPipAnimationListener.setActivityAndRecentsView(null, null); mOrientationState.destroyListeners(); mTaskOverlayFactory.removeListeners(); - mSplitSelectStateController.unregisterSplitListener(mSplitSelectionListener); - if (mDesktopVisibilityController != null) { - mDesktopVisibilityController.unregisterDesktopVisibilityListener(this); + if (FeatureFlags.enableSplitContextually()) { + mSplitSelectStateController.unregisterSplitListener(mSplitSelectionListener); } reset(); } - /** - * Execute clean-up logic needed when the view is destroyed. - */ - public void destroy() { - Log.d(TAG, "destroy"); - if (enableRefactorTaskThumbnail()) { - try { - mTaskViewPool.killOngoingInitializations(); - mGroupedTaskViewPool.killOngoingInitializations(); - mDesktopTaskViewPool.killOngoingInitializations(); - } catch (InterruptedException e) { - Log.e(TAG, "Ongoing initializations could not be killed", e); - } - mHelper.onDestroy(); - RecentsDependencies.destroy(getContext()); - } - } - @Override public void onViewRemoved(View child) { - traceBegin(Trace.TRACE_TAG_APP, "RecentsView.onViewRemoved"); super.onViewRemoved(child); + // Clear the task data for the removed child if it was visible unless: // - It's the initial taskview for entering split screen, we only pretend to dismiss the // task // - It's the focused task to be moved to the front, we immediately re-add the task - if (child instanceof TaskView) { - mTaskViewCount = Math.max(0, --mTaskViewCount); - if (child != mSplitHiddenTaskView && child != mMovingTaskView) { - clearAndRecycleTaskView((TaskView) child); + if (child instanceof TaskView && child != mSplitHiddenTaskView + && child != mMovingTaskView) { + TaskView taskView = (TaskView) child; + for (int i : taskView.getTaskIds()) { + mHasVisibleTaskData.delete(i); } + if (child instanceof GroupedTaskView) { + mGroupedTaskViewPool.recycle((GroupedTaskView) taskView); + } else if (child instanceof DesktopTaskView) { + mDesktopTaskViewPool.recycle((DesktopTaskView) taskView); + } else { + mTaskViewPool.recycle(taskView); + } + mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, getTaskViewCount() == 0); } - traceEnd(Trace.TRACE_TAG_APP); - } - - private void clearAndRecycleTaskView(TaskView taskView) { - for (int i : taskView.getTaskIds()) { - mHasVisibleTaskData.delete(i); - } - if (taskView instanceof GroupedTaskView) { - mGroupedTaskViewPool.recycle((GroupedTaskView) taskView); - } else if (taskView instanceof DesktopTaskView) { - mDesktopTaskViewPool.recycle((DesktopTaskView) taskView); - } else { - mTaskViewPool.recycle(taskView); - } - mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, !hasTaskViews()); } @Override public void onViewAdded(View child) { - traceBegin(Trace.TRACE_TAG_APP, "RecentsView.onViewAdded"); super.onViewAdded(child); - if (child instanceof TaskView) { - mTaskViewCount++; - } child.setAlpha(mContentAlpha); // RecentsView is set to RTL in the constructor when system is using LTR. Here we set the // child direction back to match system settings. child.setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_LTR : View.LAYOUT_DIRECTION_RTL); mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, false); updateEmptyMessage(); - traceEnd(Trace.TRACE_TAG_APP); } @Override @@ -1423,13 +1277,12 @@ public abstract class RecentsView< RemoteAnimationTargets targets = params.getTargetSet(); if (targets != null && targets.findTask(taskId) != null) { launchSideTaskInLiveTileMode(taskId, targets.apps, targets.wallpapers, - targets.nonApps, /* transitionInfo= */ null); + targets.nonApps); } } public void launchSideTaskInLiveTileMode(int taskId, RemoteAnimationTarget[] apps, - RemoteAnimationTarget[] wallpaper, RemoteAnimationTarget[] nonApps, - @Nullable TransitionInfo transitionInfo) { + RemoteAnimationTarget[] wallpaper, RemoteAnimationTarget[] nonApps) { AnimatorSet anim = new AnimatorSet(); TaskView taskView = getTaskViewByTaskId(taskId); if (taskView == null || !isTaskViewVisible(taskView)) { @@ -1475,35 +1328,22 @@ public abstract class RecentsView< anim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { - finishRecentsAnimation(false /* toRecents */, true /*shouldPip*/, - allAppsAreTranslucent(apps), null); + finishRecentsAnimation(false /* toRecents */, null); } }); } else { TaskViewUtils.composeRecentsLaunchAnimator(anim, taskView, apps, wallpaper, nonApps, true /* launcherClosing */, getStateManager(), this, - getDepthController(), transitionInfo); + getDepthController()); } anim.start(); } - private boolean allAppsAreTranslucent(RemoteAnimationTarget[] apps) { - if (apps == null) { - return false; - } - for (int i = apps.length - 1; i >= 0; --i) { - if (!apps[i].isTranslucent) { - return false; - } - } - return true; - } - public boolean isTaskViewVisible(TaskView tv) { if (showAsGrid()) { int screenStart = getPagedOrientationHandler().getPrimaryScroll(this); int screenEnd = screenStart + getPagedOrientationHandler().getMeasuredSize(this); - return isTaskViewWithinBounds(tv, screenStart, screenEnd, /*taskViewTranslation=*/ 0); + return isTaskViewWithinBounds(tv, screenStart, screenEnd); } else { // For now, just check if it's the active task or an adjacent task return Math.abs(indexOfChild(tv) - getNextPage()) <= 1; @@ -1523,7 +1363,7 @@ public abstract class RecentsView< @Nullable private TaskView getLastGridTaskView() { - return getLastGridTaskView(mUtils.getTopRowIdArray(), mUtils.getBottomRowIdArray()); + return getLastGridTaskView(getTopRowIdArray(), getBottomRowIdArray()); } @Nullable @@ -1550,41 +1390,14 @@ public abstract class RecentsView< return clearAllScroll + (mIsRtl ? distance : -distance); } - /** - * Launch running task view if it is instance of DesktopTaskView. - * @return provides runnable list to attach runnable at end of Desktop Mode launch - */ - @Nullable - public RunnableList launchRunningDesktopTaskView() { - TaskView taskView = getRunningTaskView(); - if (taskView instanceof DesktopTaskView) { - return taskView.launchWithAnimation(); - } - return null; - } - - /* - * Returns if TaskView is within screen bounds defined in [screenStart, screenEnd]. - * - * @param taskViewTranslation taskView is considered within bounds if either translated or - * original position of taskView is within screen bounds. - */ - protected boolean isTaskViewWithinBounds(TaskView taskView, int screenStart, int screenEnd, - int taskViewTranslation) { - int taskStart = getPagedOrientationHandler().getChildStart(taskView) - + (int) taskView.getOffsetAdjustment(showAsGrid()); - int taskSize = (int) (getPagedOrientationHandler().getMeasuredSize(taskView) - * taskView.getSizeAdjustment(showAsFullscreen())); + private boolean isTaskViewWithinBounds(TaskView tv, int start, int end) { + int taskStart = getPagedOrientationHandler().getChildStart(tv) + + (int) tv.getOffsetAdjustment(showAsGrid()); + int taskSize = (int) (getPagedOrientationHandler().getMeasuredSize(tv) + * tv.getSizeAdjustment(showAsFullscreen())); int taskEnd = taskStart + taskSize; - - int translatedTaskStart = taskStart + taskViewTranslation; - int translatedTaskEnd = taskEnd + taskViewTranslation; - - taskStart = Math.min(taskStart, translatedTaskStart); - taskEnd = Math.max(taskEnd, translatedTaskEnd); - - return (taskStart >= screenStart && taskStart <= screenEnd) || (taskEnd >= screenStart - && taskEnd <= screenEnd); + return (taskStart >= start && taskStart <= end) || (taskEnd >= start + && taskEnd <= end); } private boolean isTaskViewFullyWithinBounds(TaskView tv, int start, int end) { @@ -1597,19 +1410,17 @@ public abstract class RecentsView< } /** - * Returns true if the given TaskView is in expected scroll position. + * Returns true if the task is in expected scroll position. + * + * @param taskIndex the index of the task */ - public boolean isTaskInExpectedScrollPosition(@NonNull TaskView taskView) { - return getScrollForPage(indexOfChild(taskView)) - == getPagedOrientationHandler().getPrimaryScroll(this); + public boolean isTaskInExpectedScrollPosition(int taskIndex) { + return getScrollForPage(taskIndex) == getPagedOrientationHandler().getPrimaryScroll(this); } - /** - * Returns true if the focused TaskView is in expected scroll position. - */ - public boolean isFocusedTaskInExpectedScrollPosition() { + private boolean isFocusedTaskInExpectedScrollPosition() { TaskView focusedTask = getFocusedTaskView(); - return focusedTask != null && isTaskInExpectedScrollPosition(focusedTask); + return focusedTask != null && isTaskInExpectedScrollPosition(indexOfChild(focusedTask)); } /** @@ -1621,7 +1432,8 @@ public abstract class RecentsView< return null; } - for (TaskView taskView : getTaskViews()) { + for (int i = 0; i < getTaskViewCount(); i++) { + TaskView taskView = requireTaskViewAt(i); if (taskView.containsTaskId(taskId)) { return taskView; } @@ -1642,7 +1454,8 @@ public abstract class RecentsView< int[] taskIdsCopy = Arrays.copyOf(taskIds, taskIds.length); Arrays.sort(taskIdsCopy); - for (TaskView taskView : getTaskViews()) { + for (int i = 0; i < getTaskViewCount(); i++) { + TaskView taskView = requireTaskViewAt(i); int[] taskViewIdsCopy = taskView.getTaskIds(); Arrays.sort(taskViewIdsCopy); if (Arrays.equals(taskIdsCopy, taskViewIdsCopy)) { @@ -1664,6 +1477,9 @@ public abstract class RecentsView< updateTaskStackListenerState(); mOrientationState.setRotationWatcherEnabled(enabled); if (!enabled) { + // Reset the running task when leaving overview since it can still have a reference to + // its thumbnail + mTmpRunningTasks = null; mSplitBoundsConfig = null; mTaskOverlayFactory.clearAllActiveState(); } @@ -1674,14 +1490,12 @@ public abstract class RecentsView< * Enable or disable showing border on hover and focus change on task views */ public void setTaskBorderEnabled(boolean enabled) { - mBorderEnabled = enabled; - for (TaskView taskView : getTaskViews()) { + int taskCount = getTaskViewCount(); + for (int i = 0; i < taskCount; i++) { + TaskView taskView = requireTaskViewAt(i); taskView.setBorderEnabled(enabled); } mClearAllButton.setBorderEnabled(enabled); - if (mAddDesktopButton != null) { - mAddDesktopButton.setBorderEnabled(enabled); - } } /** @@ -1709,7 +1523,8 @@ public abstract class RecentsView< @Override protected void onPageEndTransition() { super.onPageEndTransition(); - ActiveGestureProtoLogProxy.logOnPageEndTransition(getNextPage()); + ActiveGestureLog.INSTANCE.addLog( + "onPageEndTransition: current page index updated", getNextPage()); if (isClearAllHidden() && !mContainer.getDeviceProfile().isTablet) { mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING, false); } @@ -1744,7 +1559,9 @@ public abstract class RecentsView< super.onTouchEvent(ev); if (showAsGrid()) { - for (TaskView taskView : getTaskViews()) { + int taskCount = getTaskViewCount(); + for (int i = 0; i < taskCount; i++) { + TaskView taskView = requireTaskViewAt(i); if (isTaskViewVisible(taskView) && taskView.offerTouchToChildren(ev)) { // Keep consuming events to pass to delegate return true; @@ -1790,11 +1607,8 @@ public abstract class RecentsView< mClearAllButton.getAlpha() == 1 && mClearAllButtonDeadZoneRect.contains(x, y); final boolean cameFromNavBar = (ev.getEdgeFlags() & EDGE_NAV_BAR) != 0; - int adjustedX = x + getScrollX(); if (!clearAllButtonDeadZoneConsumed && !cameFromNavBar - && !mTaskViewDeadZoneRect.contains(adjustedX, y) - && !mTopRowDeadZoneRect.contains(adjustedX, y) - && !mBottomRowDeadZoneRect.contains(adjustedX, y)) { + && !mTaskViewDeadZoneRect.contains(x + getScrollX(), y)) { mTouchDownToStartHome = true; } } @@ -1829,11 +1643,11 @@ public abstract class RecentsView< return; } TaskView taskView = getTaskViewAt(mNextPage); - boolean shouldSnapToLargeTask = taskView != null && taskView.isLargeTile() - && !mUtils.isAnySmallTaskFullyVisible(); + // Snap to fully visible focused task and clear all button. + boolean shouldSnapToFocusedTask = taskView != null && taskView.isFocusedTask() + && isTaskViewFullyVisible(taskView); boolean shouldSnapToClearAll = mNextPage == indexOfChild(mClearAllButton); - // Snap to large tile when grid tasks aren't fully visible or the clear all button. - if (!shouldSnapToLargeTask && !shouldSnapToClearAll) { + if (!shouldSnapToFocusedTask && !shouldSnapToClearAll) { return; } } @@ -1876,17 +1690,24 @@ public abstract class RecentsView< } /** - * Moves the running task to the expected position in the carousel. In tablets, this minimize - * animation required to move the running task into focused task position. + * Moves the running task to the front of the carousel in tablets, to minimize animation + * required to move the running task in grid. */ - public void moveRunningTaskToExpectedPosition() { - TaskView runningTaskView = getRunningTaskView(); - if (runningTaskView == null || mCurrentPage != indexOfChild(runningTaskView)) { + public void moveRunningTaskToFront() { + if (!mContainer.getDeviceProfile().isTablet) { return; } - int runningTaskExpectedIndex = mUtils.getRunningTaskExpectedIndex(runningTaskView); - if (mCurrentPage == runningTaskExpectedIndex) { + TaskView runningTaskView = getRunningTaskView(); + if (runningTaskView == null) { + return; + } + + if (indexOfChild(runningTaskView) != mCurrentPage) { + return; + } + + if (mCurrentPage == 0) { return; } @@ -1898,16 +1719,16 @@ public abstract class RecentsView< removeView(runningTaskView); mMovingTaskView = null; runningTaskView.resetPersistentViewTransforms(); - - addView(runningTaskView, runningTaskExpectedIndex); - setCurrentPage(runningTaskExpectedIndex); + addView(runningTaskView, 0); + setCurrentPage(0); updateTaskSize(); } @Override protected void onScrollerAnimationAborted() { - ActiveGestureProtoLogProxy.logOnScrollerAnimationAborted(); + ActiveGestureLog.INSTANCE.addLog("scroller animation aborted", + ActiveGestureErrorDetector.GestureEvent.SCROLLER_ANIMATION_ABORTED); } @Override @@ -1917,8 +1738,7 @@ public abstract class RecentsView< protected void applyLoadPlan(List taskGroups) { if (mPendingAnimation != null) { - final List finalTaskGroups = taskGroups; - mPendingAnimation.addEndListener(success -> applyLoadPlan(finalTaskGroups)); + mPendingAnimation.addEndListener(success -> applyLoadPlan(taskGroups)); return; } @@ -1926,11 +1746,11 @@ public abstract class RecentsView< Log.d(TAG, "applyLoadPlan - taskGroups is null"); } else { Log.d(TAG, "applyLoadPlan - taskGroups: " + taskGroups.stream().map( - GroupTask::toString).toList()); + GroupTask::toString).collect(Collectors.toList())); } mLoadPlanEverApplied = true; if (taskGroups == null || taskGroups.isEmpty()) { - removeAllTaskViews(); + removeTasksViewsAndClearAllButton(); onTaskStackUpdated(); // With all tasks removed, touch handling in PagedView is disabled and we need to reset // touch state or otherwise values will be obsolete. @@ -1941,19 +1761,12 @@ public abstract class RecentsView< return; } - // Start here to avoid early returns and empty cases which have special logic - traceBegin(Trace.TRACE_TAG_APP, "RecentsView.applyLoadPlan"); - + int[] currentTaskIds; TaskView currentTaskView = getTaskViewAt(mCurrentPage); - int[] currentTaskIds = null; - // Track the current DesktopTaskView through [deskId] as a desk can be empty without any - // tasks. - int currentTaskViewDeskId = INACTIVE_DESK_ID; - if (areMultiDesksFlagsEnabled() - && currentTaskView instanceof DesktopTaskView desktopTaskView) { - currentTaskViewDeskId = desktopTaskView.getDeskId(); - } else if (currentTaskView != null) { + if (currentTaskView != null) { currentTaskIds = currentTaskView.getTaskIds(); + } else { + currentTaskIds = new int[0]; } // Unload existing visible task data @@ -1965,33 +1778,19 @@ public abstract class RecentsView< // Save running task ID if it exists before rebinding all taskViews, otherwise the task from // the runningTaskView currently bound could get assigned to another TaskView - TaskView runningTaskView = getRunningTaskView(); - int[] runningTaskIds = null; - - // Track the running TaskView through [deskId] as a desk can be empty without any tasks. - int runningTaskViewDeskId = INACTIVE_DESK_ID; - if (areMultiDesksFlagsEnabled() - && runningTaskView instanceof DesktopTaskView desktopTaskView) { - runningTaskViewDeskId = desktopTaskView.getDeskId(); - } else if (runningTaskView != null) { - runningTaskIds = runningTaskView.getTaskIds(); - } - + int[] runningTaskIds = getTaskIdsForTaskViewId(mRunningTaskViewId); int[] focusedTaskIds = getTaskIdsForTaskViewId(mFocusedTaskViewId); + // Reset the focused task to avoiding initializing TaskViews layout as focused task during // binding. The focused task view will be updated after all the TaskViews are bound. - setFocusedTaskViewId(INVALID_TASK_ID); + mFocusedTaskViewId = INVALID_TASK_ID; // Removing views sets the currentPage to 0, so we save this and restore it after // the new set of views are added int previousCurrentPage = mCurrentPage; int previousFocusedPage = indexOfChild(getFocusedChild()); - // TaskIds will no longer be valid after remove and re-add, clearing mTopRowIdSet. - mAnyTaskHasBeenDismissed = false; - mTopRowIdSet.clear(); - traceBegin(Trace.TRACE_TAG_APP, "RecentsView.applyLoadPlan.removeAllViews"); removeAllViews(); - traceEnd(Trace.TRACE_TAG_APP); + // If we are entering Overview as a result of initiating a split from somewhere else // (e.g. split from Home), we need to make sure the staged app is not drawn as a thumbnail. int stagedTaskIdToBeRemoved; @@ -2005,115 +1804,81 @@ public abstract class RecentsView< mFilterState.updateInstanceCountMap(taskGroups); // Clear out desktop view if it is set - - // Move Desktop Tasks to the end of the list - if (enableLargeDesktopWindowingTile()) { - taskGroups = mUtils.sortDesktopTasksToFront(taskGroups); - } - if (enableSeparateExternalDisplayTasks()) { - taskGroups = mUtils.sortExternalDisplayTasksToFront(taskGroups); - } - - if (mAddDesktopButton != null) { - // Add `mAddDesktopButton` as the first child. - addView(mAddDesktopButton); - } - traceBegin(Trace.TRACE_TAG_APP, "RecentsView.applyLoadPlan.forLoop"); + mDesktopTaskView = null; // Add views as children based on whether it's grouped or single task. Looping through // taskGroups backwards populates the thumbnail grid from least recent to most recent. for (int i = taskGroups.size() - 1; i >= 0; i--) { GroupTask groupTask = taskGroups.get(i); - boolean containsStagedTask = stagedTaskIdToBeRemoved != INVALID_TASK_ID + boolean isRemovalNeeded = stagedTaskIdToBeRemoved != INVALID_TASK_ID && groupTask.containsTask(stagedTaskIdToBeRemoved); - boolean shouldSkipGroupTask = containsStagedTask && groupTask instanceof SingleTask; - if ((isSplitSelectionActive() && groupTask.taskViewType == TaskViewType.DESKTOP) - || shouldSkipGroupTask) { - // To avoid these tasks from being chosen as the app pair, the creation of a - // TaskView is bypassed. The staged task is already selected for the app pair, - // and the Desktop task should be hidden when selecting a pair. + if (isRemovalNeeded && !groupTask.hasMultipleTasks()) { + // If the task we need to remove is not part of a pair, avoiding creating the + // TaskView. continue; } // If we need to remove half of a pair of tasks, force a TaskView with Type.SINGLE // to be a temporary container for the remaining task. - traceBegin(Trace.TRACE_TAG_APP, "RecentsView.applyLoadPlan.forLoop.createTaskView"); TaskView taskView = getTaskViewFromPool( - containsStagedTask ? TaskViewType.SINGLE : groupTask.taskViewType); - traceEnd(Trace.TRACE_TAG_APP); - traceBegin(Trace.TRACE_TAG_APP, "RecentsView.applyLoadPlan.forLoop.bind"); - if (taskView instanceof GroupedTaskView groupedTaskView) { - var splitTask = (SplitTask) groupTask; - groupedTaskView.bind(splitTask.getTopLeftTask(), - splitTask.getBottomRightTask(), mOrientationState, - mTaskOverlayFactory, splitTask.getSplitBounds()); - } else if (taskView instanceof DesktopTaskView desktopTaskView) { - desktopTaskView.bind((DesktopTask) groupTask, mOrientationState, - mTaskOverlayFactory); - } else if (groupTask instanceof SplitTask splitTask) { - Task task = splitTask.getTopLeftTask().key.id == stagedTaskIdToBeRemoved - ? splitTask.getBottomRightTask() - : splitTask.getTopLeftTask(); - taskView.bind(task, mOrientationState, mTaskOverlayFactory); + isRemovalNeeded ? TaskView.Type.SINGLE : groupTask.taskViewType); + if (taskView instanceof GroupedTaskView) { + boolean firstTaskIsLeftTopTask = + groupTask.mSplitBounds.leftTopTaskId == groupTask.task1.key.id; + Task leftTopTask = firstTaskIsLeftTopTask ? groupTask.task1 : groupTask.task2; + Task rightBottomTask = firstTaskIsLeftTopTask ? groupTask.task2 : groupTask.task1; + ((GroupedTaskView) taskView).bind(leftTopTask, rightBottomTask, mOrientationState, + mTaskOverlayFactory, groupTask.mSplitBounds); + } else if (taskView instanceof DesktopTaskView) { + ((DesktopTaskView) taskView).bind(((DesktopTask) groupTask).tasks, + mOrientationState, mTaskOverlayFactory); + mDesktopTaskView = (DesktopTaskView) taskView; } else { - taskView.bind(((SingleTask) groupTask).getTask(), mOrientationState, - mTaskOverlayFactory); + Task task = groupTask.task1.key.id == stagedTaskIdToBeRemoved ? groupTask.task2 + : groupTask.task1; + taskView.bind(task, mOrientationState, mTaskOverlayFactory); } - traceEnd(Trace.TRACE_TAG_APP); - traceBegin(Trace.TRACE_TAG_APP, "RecentsView.applyLoadPlan.forLoop.addTaskView"); addView(taskView); - traceEnd(Trace.TRACE_TAG_APP); // enables instance filtering if the feature flag for it is on if (FeatureFlags.ENABLE_MULTI_INSTANCE.get()) { taskView.setUpShowAllInstancesListener(); } } - // For loop end trace - traceEnd(Trace.TRACE_TAG_APP); - addView(mClearAllButton); + if (!taskGroups.isEmpty()) { + addView(mClearAllButton); + } // Keep same previous focused task - TaskView newFocusedTaskView = null; - if (!enableGridOnlyOverview()) { - newFocusedTaskView = getTaskViewByTaskIds(focusedTaskIds); - if (enableLargeDesktopWindowingTile() - && newFocusedTaskView instanceof DesktopTaskView) { - newFocusedTaskView = null; - } - // If the list changed, maybe the focused task doesn't exist anymore. - if (newFocusedTaskView == null) { - newFocusedTaskView = mUtils.getFirstNonDesktopTaskView(); - } + TaskView newFocusedTaskView = getTaskViewByTaskIds(focusedTaskIds); + // If the list changed, maybe the focused task doesn't exist anymore + if (newFocusedTaskView == null && getTaskViewCount() > 0) { + newFocusedTaskView = getTaskViewAt(0); } - setFocusedTaskViewId( - newFocusedTaskView != null ? newFocusedTaskView.getTaskViewId() : INVALID_TASK_ID); - - traceBegin(Trace.TRACE_TAG_APP, "RecentsView.applyLoadPlan.layouts"); + mFocusedTaskViewId = newFocusedTaskView != null && !enableGridOnlyOverview() + ? newFocusedTaskView.getTaskViewId() : INVALID_TASK_ID; updateTaskSize(); - mUtils.updateChildTaskOrientations(); - traceEnd(Trace.TRACE_TAG_APP); + updateChildTaskOrientations(); - TaskView newRunningTaskView = mUtils.getDesktopTaskViewForDeskId(runningTaskViewDeskId); - if (newRunningTaskView == null) { + TaskView newRunningTaskView = null; + if (hasAllValidTaskIds(runningTaskIds)) { // Update mRunningTaskViewId to be the new TaskView that was assigned by binding // the full list of tasks to taskViews newRunningTaskView = getTaskViewByTaskIds(runningTaskIds); - } - if (newRunningTaskView != null) { - setRunningTaskViewId(newRunningTaskView.getTaskViewId()); - } else { - if (mActiveGestureGroupedTaskInfo != null) { - // This will update mRunningTaskViewId and create a stub view if necessary. - // We try to avoid this because it can cause a scroll jump, but it is needed - // for cases where the running task isn't included in this load plan (e.g. if - // the current running task is excludedFromRecents.) - showCurrentTask(mActiveGestureGroupedTaskInfo, "applyLoadPlan"); - newRunningTaskView = getRunningTaskView(); + if (newRunningTaskView != null) { + setRunningTaskViewId(newRunningTaskView.getTaskViewId()); } else { - setRunningTaskViewId(INVALID_TASK_ID); + if (mActiveGestureRunningTasks != null) { + // This will update mRunningTaskViewId and create a stub view if necessary. + // We try to avoid this because it can cause a scroll jump, but it is needed + // for cases where the running task isn't included in this load plan (e.g. if + // the current running task is excludedFromRecents.) + showCurrentTask(mActiveGestureRunningTasks); + } else { + setRunningTaskViewId(INVALID_TASK_ID); + } } } @@ -2121,18 +1886,21 @@ public abstract class RecentsView< if (mNextPage != INVALID_PAGE) { // Restore mCurrentPage but don't call setCurrentPage() as that clobbers the scroll. mCurrentPage = previousCurrentPage; - currentTaskView = mUtils.getDesktopTaskViewForDeskId(currentTaskViewDeskId); - if (currentTaskView == null) { + if (hasAllValidTaskIds(currentTaskIds)) { currentTaskView = getTaskViewByTaskIds(currentTaskIds); - } - if (currentTaskView != null) { - targetPage = indexOfChild(currentTaskView); + if (currentTaskView != null) { + targetPage = indexOfChild(currentTaskView); + } } } else if (previousFocusedPage != INVALID_PAGE) { targetPage = previousFocusedPage; } else { - targetPage = indexOfChild( - mUtils.getExpectedCurrentTask(newRunningTaskView, newFocusedTaskView)); + // Set the current page to the running task, but not if settling on new task. + if (hasAllValidTaskIds(runningTaskIds)) { + targetPage = indexOfChild(newRunningTaskView); + } else if (getTaskViewCount() > 0) { + targetPage = indexOfChild(requireTaskViewAt(0)); + } } if (targetPage != -1 && mCurrentPage != targetPage) { int finalTargetPage = targetPage; @@ -2149,7 +1917,6 @@ public abstract class RecentsView< }); } - traceBegin(Trace.TRACE_TAG_APP, "RecentsView.applyLoadPlan.cleanupStates"); if (mIgnoreResetTaskId != INVALID_TASK_ID && getTaskViewByTaskId(mIgnoreResetTaskId) != ignoreResetTaskView) { // If the taskView mapping is changing, do not preserve the visuals. Since we are @@ -2157,17 +1924,12 @@ public abstract class RecentsView< // generally map to the same task. mIgnoreResetTaskId = INVALID_TASK_ID; } - resetTaskVisuals(); onTaskStackUpdated(); updateEnabledOverlays(); if (isPageScrollsInitialized()) { onPageScrollsInitialized(); } - traceEnd(Trace.TRACE_TAG_APP); - - // applyLoadPlan end trace - traceEnd(Trace.TRACE_TAG_APP); } private boolean isModal() { @@ -2178,30 +1940,38 @@ public abstract class RecentsView< return mModel.isLoadingTasksInBackground(); } - private void removeAllTaskViews() { - // This handles an edge case where applyLoadPlan happens during a gesture when the only - // Task is one with excludeFromRecents, in which case we should not remove it. - CollectionsKt - .filter(getTaskViews(), taskView -> !isGestureActive() || !taskView.isRunningTask()) - .forEach(this::removeView); - if (!hasTaskViews()) { - removeView(mAddDesktopButton); + private void removeTasksViewsAndClearAllButton() { + // This handles an edge case where applyLoadPlan happens during a gesture when the + // only Task is one with excludeFromRecents, in which case we should not remove it. + final int stubRunningTaskIndex = isGestureActive() ? getRunningTaskIndex() : -1; + + for (int i = getTaskViewCount() - 1; i >= 0; i--) { + if (i == stubRunningTaskIndex) { + continue; + } + removeView(requireTaskViewAt(i)); + } + if (getTaskViewCount() == 0 && indexOfChild(mClearAllButton) != -1) { removeView(mClearAllButton); } } - /** Returns true if there are at least one TaskView has been added to the RecentsView. */ - public boolean hasTaskViews() { - return mUtils.hasTaskViews(); - } - public int getTaskViewCount() { - return mTaskViewCount; + int taskViewCount = getChildCount(); + if (indexOfChild(mClearAllButton) != -1) { + taskViewCount--; + } + return taskViewCount; } - /** Counts {@link TaskView}s that are not {@link DesktopTaskView} instances. */ - public int getNonDesktopTaskViewCount() { - return mUtils.getNonDesktopTaskViewCount(); + public int getGroupedTaskViewCount() { + int groupViewCount = 0; + for (int i = 0; i < getChildCount(); i++) { + if (getChildAt(i) instanceof GroupedTaskView) { + groupViewCount++; + } + } + return groupViewCount; } /** @@ -2224,16 +1994,16 @@ public abstract class RecentsView< } public void resetTaskVisuals() { - for (TaskView taskView : getTaskViews()) { + for (int i = getTaskViewCount() - 1; i >= 0; i--) { + TaskView taskView = requireTaskViewAt(i); if (Arrays.stream(taskView.getTaskIds()).noneMatch( taskId -> taskId == mIgnoreResetTaskId)) { taskView.resetViewTransforms(); - taskView.setIconVisibleForGesture(mTaskIconVisible); + taskView.setIconScaleAndDim(mTaskIconScaledDown ? 0 : 1); taskView.setStableAlpha(mContentAlpha); taskView.setFullscreenProgress(mFullscreenProgress); taskView.setModalness(mTaskModalness); taskView.setTaskThumbnailSplashAlpha(mTaskThumbnailSplashAlpha); - taskView.setBorderEnabled(mBorderEnabled); } } // resetTaskVisuals is called at the end of dismiss animation which could update @@ -2246,10 +2016,14 @@ public abstract class RecentsView< simulator.fullScreenProgress.value = 0; simulator.recentsViewScale.value = 1; }); - // Reapply runningTask related attributes as they might have been reset by - // resetViewTransforms(). - setRunningTaskViewShowScreenshot(mRunningTaskShowScreenshot); - applyAttachAlpha(); + // Similar to setRunningTaskHidden below, reapply the state before runningTaskView is + // null. + if (!mRunningTaskShowScreenshot) { + setRunningTaskViewShowScreenshot(mRunningTaskShowScreenshot); + } + if (mRunningTaskTileHidden) { + setRunningTaskHidden(mRunningTaskTileHidden); + } updateCurveProperties(); // Update the set of visible task's data @@ -2260,8 +2034,12 @@ public abstract class RecentsView< public void setFullscreenProgress(float fullscreenProgress) { mFullscreenProgress = fullscreenProgress; - for (TaskView taskView : getTaskViews()) { - taskView.setFullscreenProgress(mFullscreenProgress); + if (enableRefactorTaskThumbnail()) { + mRecentsViewData.getFullscreenProgress().setValue(mFullscreenProgress); + } + int taskCount = getTaskViewCount(); + for (int i = 0; i < taskCount; i++) { + requireTaskViewAt(i).setFullscreenProgress(mFullscreenProgress); } mClearAllButton.setFullscreenProgress(fullscreenProgress); @@ -2274,7 +2052,6 @@ public abstract class RecentsView< boolean handleTaskStackChanges = mOverviewStateEnabled && isAttachedToWindow() && getWindowVisibility() == VISIBLE; if (handleTaskStackChanges != mHandleTaskStackChanges) { - Log.d(TAG, "updateTaskStackListenerState: " + handleTaskStackChanges); mHandleTaskStackChanges = handleTaskStackChanges; if (handleTaskStackChanges) { reloadIfNeeded(); @@ -2345,7 +2122,7 @@ public abstract class RecentsView< updateSizeAndPadding(); // Update TaskView's DeviceProfile dependent layout. - mUtils.updateChildTaskOrientations(); + updateChildTaskOrientations(); requestLayout(); // Reapply the current page to update page scrolls. @@ -2376,6 +2153,11 @@ public abstract class RecentsView< mSizeStrategy.calculateGridTaskSize(mContainer, dp, mLastComputedGridTaskSize, getPagedOrientationHandler()); + if (enableGridOnlyOverview()) { + mSizeStrategy.calculateCarouselTaskSize(mContainer, dp, mLastComputedCarouselTaskSize, + getPagedOrientationHandler()); + } + mTaskGridVerticalDiff = mLastComputedGridTaskSize.top - mLastComputedTaskSize.top; mTopBottomRowHeightDiff = mLastComputedGridTaskSize.height() + dp.overviewTaskThumbnailTopMarginPx @@ -2390,14 +2172,33 @@ public abstract class RecentsView< * Updates TaskView scaling and translation required to support variable width. */ private void updateTaskSize() { - if (!hasTaskViews()) { + updateTaskSize(false); + } + + /** + * Updates TaskView scaling and translation required to support variable width. + * + * @param isTaskDismissal indicates if update was called due to task dismissal + */ + private void updateTaskSize(boolean isTaskDismissal) { + final int taskCount = getTaskViewCount(); + if (taskCount == 0) { return; } float accumulatedTranslationX = 0; - for (TaskView taskView : getTaskViews()) { - taskView.updateTaskSize(mLastComputedTaskSize, mLastComputedGridTaskSize); + float translateXToMiddle = 0; + if (enableGridOnlyOverview() && mContainer.getDeviceProfile().isTablet) { + translateXToMiddle = mIsRtl + ? mLastComputedCarouselTaskSize.right - mLastComputedTaskSize.right + : mLastComputedCarouselTaskSize.left - mLastComputedTaskSize.left; + } + for (int i = 0; i < taskCount; i++) { + TaskView taskView = requireTaskViewAt(i); + taskView.updateTaskSize(mLastComputedTaskSize, mLastComputedGridTaskSize, + mLastComputedCarouselTaskSize); taskView.setNonGridTranslationX(accumulatedTranslationX); + taskView.setNonGridPivotTranslationX(translateXToMiddle); // Compensate space caused by TaskView scaling. float widthDiff = taskView.getLayoutParams().width * (1 - taskView.getNonGridScale()); @@ -2406,13 +2207,7 @@ public abstract class RecentsView< mClearAllButton.setFullscreenTranslationPrimary(accumulatedTranslationX); - float taskAlignmentTranslationY = getTaskAlignmentTranslationY(); - mClearAllButton.setTaskAlignmentTranslationY(taskAlignmentTranslationY); - if (mAddDesktopButton != null) { - mAddDesktopButton.setTranslationY(taskAlignmentTranslationY); - } - - updateGridProperties(); + updateGridProperties(isTaskDismissal); } public void getTaskSize(Rect outRect) { @@ -2421,50 +2216,28 @@ public abstract class RecentsView< } /** - * Returns the currently selected TaskView in Select mode. - */ - @Nullable - public TaskView getSelectedTaskView() { - return mUtils.getSelectedTaskView(); - } - - /** - * Sets the selected TaskView in Select mode. + * Sets the last TaskView selected. */ public void setSelectedTask(int lastSelectedTaskId) { - mUtils.setSelectedTaskView(getTaskViewByTaskId(lastSelectedTaskId)); + mSelectedTask = getTaskViewByTaskId(lastSelectedTaskId); } /** * Returns the bounds of the task selected to enter modal state. */ public Rect getSelectedTaskBounds() { - if (getSelectedTaskView() == null) { + if (mSelectedTask == null) { return mLastComputedTaskSize; } - return getTaskBounds(getSelectedTaskView()); + return getTaskBounds(mSelectedTask); } - /** - * Get the Y translation that should be applied to the non-TaskView item inside the RecentsView - * (ClearAllButton and AddDesktopButton) in the original layout position, before scrolling. This - * is done to make sure the button is aligned to the middle of Task thumbnail in y coordinate. - */ - private float getTaskAlignmentTranslationY() { - DeviceProfile deviceProfile = mContainer.getDeviceProfile(); - if (deviceProfile.isTablet) { - return deviceProfile.overviewRowSpacing; - } - return deviceProfile.overviewTaskThumbnailTopMarginPx / 2.0f; - } - - protected Rect getTaskBounds(TaskView taskView) { + private Rect getTaskBounds(TaskView taskView) { int selectedPage = indexOfChild(taskView); int primaryScroll = getPagedOrientationHandler().getPrimaryScroll(this); int selectedPageScroll = getScrollForPage(selectedPage); - boolean isTopRow = mTopRowIdSet.contains(taskView.getTaskViewId()); - Rect outRect = new Rect( - taskView.isGridTask() ? mLastComputedGridTaskSize : mLastComputedTaskSize); + boolean isTopRow = taskView != null && mTopRowIdSet.contains(taskView.getTaskViewId()); + Rect outRect = new Rect(mLastComputedTaskSize); outRect.offset( -(primaryScroll - (selectedPageScroll + getOffsetFromScrollPosition(selectedPage))), (int) (showAsGrid() && enableGridOnlyOverview() && !isTopRow @@ -2481,6 +2254,10 @@ public abstract class RecentsView< return mLastComputedGridTaskSize; } + public Rect getLastComputedCarouselTaskSize() { + return mLastComputedCarouselTaskSize; + } + /** Gets the task size for modal state. */ public void getModalTaskSize(Rect outRect) { mSizeStrategy.calculateModalTaskSize(mContainer, mContainer.getDeviceProfile(), outRect, @@ -2564,11 +2341,6 @@ public abstract class RecentsView< int minDistanceFromScreenStart = Integer.MAX_VALUE; int minDistanceFromScreenStartIndex = INVALID_PAGE; for (int i = 0; i < getChildCount(); ++i) { - // Do not set the destination page to the AddDesktopButton, which has the same page - // scrolls as the first [TaskView] and shouldn't be scrolled to. - if (getChildAt(i) instanceof AddDesktopButton) { - continue; - } int distanceFromScreenStart = Math.abs(mPageScrolls[i] - scaledScroll); if (distanceFromScreenStart < minDistanceFromScreenStart) { minDistanceFromScreenStart = distanceFromScreenStart; @@ -2590,39 +2362,41 @@ public abstract class RecentsView< return; } - int lowerIndex, upperIndex, visibleStart, visibleEnd; + int lower = 0; + int upper = 0; + int visibleStart = 0; + int visibleEnd = 0; if (showAsGrid()) { int screenStart = getPagedOrientationHandler().getPrimaryScroll(this); int pageOrientedSize = getPagedOrientationHandler().getMeasuredSize(this); // For GRID_ONLY_OVERVIEW, use +/- 1 task column as visible area for preloading // adjacent thumbnails, otherwise use +/-50% screen width - int extraWidth = - enableGridOnlyOverview() ? getLastComputedTaskSize().width() + getPageSpacing() - : pageOrientedSize / 2; - lowerIndex = upperIndex = 0; + int extraWidth = enableGridOnlyOverview() ? getLastComputedTaskSize().width() + + getPageSpacing() : pageOrientedSize / 2; visibleStart = screenStart - extraWidth; visibleEnd = screenStart + pageOrientedSize + extraWidth; } else { int centerPageIndex = getPageNearestToCenterOfScreen(); int numChildren = getChildCount(); - lowerIndex = Math.max(0, centerPageIndex - 2); - upperIndex = Math.min(centerPageIndex + 2, numChildren - 1); - visibleStart = visibleEnd = 0; + lower = Math.max(0, centerPageIndex - 2); + upper = Math.min(centerPageIndex + 2, numChildren - 1); } List visibleTaskIds = new ArrayList<>(); + // Update the task data for the in/visible children - getTaskViews().forEachWithIndexInParent((index, taskView) -> { + for (int i = 0; i < getTaskViewCount(); i++) { + TaskView taskView = requireTaskViewAt(i); List containers = taskView.getTaskContainers(); if (containers.isEmpty()) { - return; + continue; } + int index = indexOfChild(taskView); boolean visible; if (showAsGrid()) { - visible = isTaskViewWithinBounds(taskView, visibleStart, visibleEnd, - mTaskViewsDismissPrimaryTranslations.getOrDefault(taskView, 0)); + visible = isTaskViewWithinBounds(taskView, visibleStart, visibleEnd); } else { - visible = index >= lowerIndex && index <= upperIndex; + visible = lower <= index && index <= upper; } if (visible) { // Default update all non-null tasks, then remove running ones @@ -2631,12 +2405,18 @@ public abstract class RecentsView< .collect(Collectors.toCollection(ArrayList::new)); if (enableRefactorTaskThumbnail()) { visibleTaskIds.addAll( - tasksToUpdate.stream().map((task) -> task.key.id).toList()); + tasksToUpdate.stream().map((task) -> task.key.id).collect(Collectors.toList())); + } + if (mTmpRunningTasks != null) { + for (Task t : mTmpRunningTasks) { + // Skip loading if this is the task that we are animating into + // TODO(b/280812109) change this equality check to use A.equals(B) + tasksToUpdate.removeIf(task -> task == t); + } } if (tasksToUpdate.isEmpty()) { - return; + continue; } - int visibilityChanges = 0; for (Task task : tasksToUpdate) { if (!mHasVisibleTaskData.get(task.key.id)) { // Ignore thumbnail update if it's current running task during the gesture @@ -2645,32 +2425,25 @@ public abstract class RecentsView< if (taskView == getRunningTaskView() && isGestureActive()) { changes &= ~TaskView.FLAG_UPDATE_THUMBNAIL; } - visibilityChanges |= changes; + taskView.onTaskListVisibilityChanged(true /* visible */, changes); } - mHasVisibleTaskData.put(task.key.id, true); - } - if (visibilityChanges != 0) { - taskView.onTaskListVisibilityChanged(true /* visible */, visibilityChanges); + mHasVisibleTaskData.put(task.key.id, visible); } } else { - int visibilityChanges = 0; for (TaskContainer container : containers) { if (container == null) { continue; } if (mHasVisibleTaskData.get(container.getTask().key.id)) { - visibilityChanges = dataChanges; + taskView.onTaskListVisibilityChanged(false /* visible */, dataChanges); } mHasVisibleTaskData.delete(container.getTask().key.id); } - if (visibilityChanges != 0) { - taskView.onTaskListVisibilityChanged(false /* visible */, visibilityChanges); - } } - }); + } if (enableRefactorTaskThumbnail()) { - mRecentsViewModel.updateVisibleTasks(visibleTaskIds); + mTasksRepository.setVisibleTasks(visibleTaskIds); } } @@ -2697,10 +2470,6 @@ public abstract class RecentsView< mModel.preloadCacheIfNeeded(); } - if (enableRefactorTaskThumbnail()) { - return; - } - // Whenever the high res loading state changes, poke each of the visible tasks to see if // they want to updated their thumbnail state for (int i = 0; i < mHasVisibleTaskData.size(); i++) { @@ -2738,14 +2507,7 @@ public abstract class RecentsView< mCurrentPageScrollDiff = 0; mIgnoreResetTaskId = -1; mTaskListChangeId = -1; - setFocusedTaskViewId(INVALID_TASK_ID); - mAnyTaskHasBeenDismissed = false; - - if (enableRefactorTaskThumbnail()) { - // TODO(b/353917593): RecentsView is never destroyed, so its dependencies need to - // be cleaned up during the reset, but re-created when RecentsView is "resumed". - // RecentsDependencies.Companion.destroy(); - } + mFocusedTaskViewId = -1; Log.d(TAG, "reset - mEnableDrawingLiveTile: " + mEnableDrawingLiveTile + ", mRecentsAnimationController: " + mRecentsAnimationController); @@ -2760,22 +2522,22 @@ public abstract class RecentsView< } setEnableDrawingLiveTile(false); } - mBlurUtils.setDrawLiveTileBelowRecents(false); + runActionOnRemoteHandles(remoteTargetHandle -> + remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(false)); + if (!FeatureFlags.enableSplitContextually()) { + resetFromSplitSelectionState(); + } + // These are relatively expensive and don't need to be done this frame (RecentsView isn't // visible anyway), so defer by a frame to get off the critical path, e.g. app to home. - post(this::onReset); - } - - private void onReset() { - unloadVisibleTaskData(TaskView.FLAG_UPDATE_ALL); - setCurrentPage(0); - LayoutUtils.setViewEnabled(mActionsView, true); - if (mOrientationState.setGestureActive(false)) { - updateOrientationHandler(/* forceRecreateDragLayerControllers = */ false); - } - if (enableRefactorTaskThumbnail()) { - mRecentsViewModel.onReset(); - } + post(() -> { + unloadVisibleTaskData(TaskView.FLAG_UPDATE_ALL); + setCurrentPage(0); + LayoutUtils.setViewEnabled(mActionsView, true); + if (mOrientationState.setGestureActive(false)) { + updateOrientationHandler(/* forceRecreateDragLayerControllers = */ false); + } + }); } public int getRunningTaskViewId() { @@ -2805,12 +2567,13 @@ public abstract class RecentsView< } @Nullable - TaskView getTaskViewFromTaskViewId(int taskViewId) { + private TaskView getTaskViewFromTaskViewId(int taskViewId) { if (taskViewId == -1) { return null; } - for (TaskView taskView : getTaskViews()) { + for (int i = 0; i < getTaskViewCount(); i++) { + TaskView taskView = requireTaskViewAt(i); if (taskView.getTaskViewId() == taskViewId) { return taskView; } @@ -2831,16 +2594,16 @@ public abstract class RecentsView< * Handle the edge case where Recents could increment task count very high over long * period of device usage. Probably will never happen, but meh. */ - protected TaskView getTaskViewFromPool(TaskViewType type) { + private TaskView getTaskViewFromPool(@TaskView.Type int type) { TaskView taskView; switch (type) { - case GROUPED: + case TaskView.Type.GROUPED: taskView = mGroupedTaskViewPool.getView(); break; - case DESKTOP: + case TaskView.Type.DESKTOP: taskView = mDesktopTaskViewPool.getView(); break; - case SINGLE: + case TaskView.Type.SINGLE: default: taskView = mTaskViewPool.getView(); } @@ -2856,7 +2619,6 @@ public abstract class RecentsView< /** * Get the index of the task view whose id matches {@param taskId}. - * * @return -1 if there is no task view for the task id, else the index of the task view. */ public int getTaskIndexForId(int taskId) { @@ -2871,48 +2633,37 @@ public abstract class RecentsView< if (!mModel.isTaskListValid(mTaskListChangeId)) { mTaskListChangeId = mModel.getTasks(this::applyLoadPlan, RecentsFilterState .getFilter(mFilterState.getPackageNameToFilter())); - Log.d(TAG, "reloadIfNeeded - getTasks: " + mTaskListChangeId); if (enableRefactorTaskThumbnail()) { - mRecentsViewModel.refreshAllTaskData(); + mTasksRepository.getAllTaskData(/* forceRefresh = */ true); } - } else { - Log.d(TAG, "reloadIfNeeded - task list still valid: " + mTaskListChangeId); } } /** * Called when a gesture from an app is starting. */ - // TODO: b/401582344 - Implement a way to exclude the `DesktopWallpaperActivity` from being - // considered in Overview. - public void onGestureAnimationStart(GroupedTaskInfo groupedTaskInfo) { - Log.d(TAG, "onGestureAnimationStart - groupedTaskInfo: " + groupedTaskInfo); - mActiveGestureGroupedTaskInfo = groupedTaskInfo; - + public void onGestureAnimationStart( + Task[] runningTasks, RotationTouchHelper rotationTouchHelper) { + Log.d(TAG, "onGestureAnimationStart - runningTasks: " + Arrays.toString(runningTasks)); + mActiveGestureRunningTasks = runningTasks; // This needs to be called before the other states are set since it can create the task view if (mOrientationState.setGestureActive(true)) { - reapplyActiveRotation(); + setLayoutRotation(rotationTouchHelper.getCurrentActiveRotation(), + rotationTouchHelper.getDisplayRotation()); // Force update to ensure the initial task size is computed even if the orientation has // not changed. updateSizeAndPadding(); } - showCurrentTask(groupedTaskInfo, "onGestureAnimationStart"); + showCurrentTask(mActiveGestureRunningTasks); setEnableFreeScroll(false); setEnableDrawingLiveTile(false); setRunningTaskHidden(true); - setTaskIconVisible(false); - } - - /** - * Returns whether the running task's attach alpha should be updated during the attach animation - */ - public boolean shouldUpdateRunningTaskAlpha() { - return enableDesktopTaskAlphaAnimation() && getRunningTaskView() instanceof DesktopTaskView; + setTaskIconScaledDown(true); } private boolean isGestureActive() { - return mActiveGestureGroupedTaskInfo != null; + return mActiveGestureRunningTasks != null; } /** @@ -2920,7 +2671,7 @@ public abstract class RecentsView< * {@link #onGestureAnimationStart} and {@link #onGestureAnimationEnd()}. */ public void onSwipeUpAnimationSuccess() { - startIconFadeInOnGestureComplete(); + animateUpTaskIconScale(); setSwipeDownShouldLaunchApp(true); } @@ -2959,12 +2710,28 @@ public abstract class RecentsView< return as; } + private void updateChildTaskOrientations() { + for (int i = 0; i < getTaskViewCount(); i++) { + requireTaskViewAt(i).setOrientationState(mOrientationState); + } + boolean shouldRotateMenuForFakeRotation = + !mOrientationState.isRecentsActivityRotationAllowed(); + if (!shouldRotateMenuForFakeRotation) { + return; + } + TaskMenuView tv = (TaskMenuView) getTopOpenViewWithType(mContainer, TYPE_TASK_MENU); + if (tv != null) { + // Rotation is supported on phone (details at b/254198019#comment4) + tv.onRotationChanged(); + } + } + /** * Called when a gesture from an app has finished, and an end target has been determined. */ public void onPrepareGestureEndAnimation( @Nullable AnimatorSet animatorSet, GestureState.GestureEndTarget endTarget, - RemoteTargetHandle[] remoteTargetHandles) { + TaskViewSimulator[] taskViewSimulators) { Log.d(TAG, "onPrepareGestureEndAnimation - endTarget: " + endTarget); mCurrentGestureEndTarget = endTarget; boolean isOverviewEndTarget = endTarget == GestureState.GestureEndTarget.RECENTS; @@ -2973,59 +2740,31 @@ public abstract class RecentsView< } BaseState endState = mSizeStrategy.stateFromGestureEndTarget(endTarget); - // Starting the desk exploded animation when the gesture from an app is released. - if (enableDesktopExplodedView()) { - if (animatorSet == null) { - mUtils.setDeskExplodeProgress(endState.showExplodedDesktopView() ? 1f : 0f); - } else { - animatorSet.play( - ObjectAnimator.ofFloat(this, DESK_EXPLODE_PROGRESS, - endState.showExplodedDesktopView() ? 1f : 0f)); - } - - for (TaskView taskView : getTaskViews()) { - if (taskView instanceof DesktopTaskView desktopTaskView) { - desktopTaskView.setRemoteTargetHandles(remoteTargetHandles); - } - } - } - if (endState.displayOverviewTasksAsGrid(mContainer.getDeviceProfile())) { TaskView runningTaskView = getRunningTaskView(); - float runningTaskGridTranslationX = 0; - float runningTaskGridTranslationY = 0; + float runningTaskPrimaryGridTranslation = 0; + float runningTaskSecondaryGridTranslation = 0; if (runningTaskView != null) { // Apply the grid translation to running task unless it's being snapped to // and removes the current translation applied to the running task. - runningTaskGridTranslationX = runningTaskView.getGridTranslationX() + runningTaskPrimaryGridTranslation = runningTaskView.getGridTranslationX() - runningTaskView.getNonGridTranslationX(); - runningTaskGridTranslationY = runningTaskView.getGridTranslationY(); + runningTaskSecondaryGridTranslation = runningTaskView.getGridTranslationY(); } - for (RemoteTargetHandle remoteTargetHandle : remoteTargetHandles) { - TaskViewSimulator tvs = remoteTargetHandle.getTaskViewSimulator(); + for (TaskViewSimulator tvs : taskViewSimulators) { if (animatorSet == null) { setGridProgress(1); - if (enableGridOnlyOverview()) { - tvs.taskGridTranslationX.value = runningTaskGridTranslationX; - tvs.taskGridTranslationY.value = runningTaskGridTranslationY; - } else { - tvs.taskPrimaryTranslation.value = runningTaskGridTranslationX; - tvs.taskSecondaryTranslation.value = runningTaskGridTranslationY; - } + tvs.taskPrimaryTranslation.value = runningTaskPrimaryGridTranslation; + tvs.taskSecondaryTranslation.value = runningTaskSecondaryGridTranslation; } else { animatorSet.play(ObjectAnimator.ofFloat(this, RECENTS_GRID_PROGRESS, 1)); - if (enableGridOnlyOverview()) { - animatorSet.play(tvs.carouselScale.animateToValue(1)); - animatorSet.play(tvs.taskGridTranslationX.animateToValue( - runningTaskGridTranslationX)); - animatorSet.play(tvs.taskGridTranslationY.animateToValue( - runningTaskGridTranslationY)); - } else { - animatorSet.play(tvs.taskPrimaryTranslation.animateToValue( - runningTaskGridTranslationX)); - animatorSet.play(tvs.taskSecondaryTranslation.animateToValue( - runningTaskGridTranslationY)); - } + animatorSet.play(tvs.carouselScale.animateToValue(1)); + animatorSet.play(tvs.carouselPrimaryTranslation.animateToValue(0)); + animatorSet.play(tvs.carouselSecondaryTranslation.animateToValue(0)); + animatorSet.play(tvs.taskPrimaryTranslation.animateToValue( + runningTaskPrimaryGridTranslation)); + animatorSet.play(tvs.taskSecondaryTranslation.animateToValue( + runningTaskSecondaryGridTranslation)); } } } @@ -3036,21 +2775,13 @@ public abstract class RecentsView< animatorSet.play( ObjectAnimator.ofFloat(this, TASK_THUMBNAIL_SPLASH_ALPHA, splashAlpha)); } - if (enableLargeDesktopWindowingTile()) { - if (animatorSet != null) { - animatorSet.play( - ObjectAnimator.ofFloat(this, DESKTOP_CAROUSEL_DETACH_PROGRESS, 0f)); - } else { - DESKTOP_CAROUSEL_DETACH_PROGRESS.set(this, 0f); - } - } } /** * Called when a gesture from an app has finished, and the animation to the target has ended. */ public void onGestureAnimationEnd() { - mActiveGestureGroupedTaskInfo = null; + mActiveGestureRunningTasks = null; if (mOrientationState.setGestureActive(false)) { updateOrientationHandler(/* forceRecreateDragLayerControllers = */ false); } @@ -3059,48 +2790,20 @@ public abstract class RecentsView< setEnableDrawingLiveTile(mCurrentGestureEndTarget == GestureState.GestureEndTarget.RECENTS); Log.d(TAG, "onGestureAnimationEnd - mEnableDrawingLiveTile: " + mEnableDrawingLiveTile); setRunningTaskHidden(false); - startIconFadeInOnGestureComplete(); + animateUpTaskIconScale(); animateActionsViewIn(); - if (mEnableDrawingLiveTile) { - if (enableDesktopExplodedView()) { - for (TaskView taskView : getTaskViews()) { - if (taskView instanceof DesktopTaskView desktopTaskView) { - desktopTaskView.setRemoteTargetHandles(mRemoteTargetHandles); - } - } - } - TaskView runningTaskView = getRunningTaskView(); - if (showAsGrid() && enableGridOnlyOverview() && runningTaskView != null) { - runActionOnRemoteHandles(remoteTargetHandle -> { - TaskViewSimulator taskViewSimulator = remoteTargetHandle.getTaskViewSimulator(); - // After settling in Overview, recentsScroll will be used to adjust horizontally - // location and taskGridTranslationX doesn't needs to be applied. - taskViewSimulator.taskGridTranslationX.value = 0; - taskViewSimulator.taskGridTranslationY.value = - runningTaskView.getGridTranslationY(); - }); - } - } - mCurrentGestureEndTarget = null; } /** * Returns true if we should add a stub taskView for the running task id */ - protected boolean shouldAddStubTaskView(GroupedTaskInfo groupedTaskInfo) { - int[] runningTaskIds; - if (groupedTaskInfo != null) { - runningTaskIds = groupedTaskInfo.getTaskInfoList().stream().mapToInt( - taskInfo -> taskInfo.taskId).toArray(); - } else { - runningTaskIds = new int[0]; - } + protected boolean shouldAddStubTaskView(Task[] runningTasks) { + int[] runningTaskIds = Arrays.stream(runningTasks).mapToInt(task -> task.key.id).toArray(); TaskView matchingTaskView = null; - if (groupedTaskInfo != null && groupedTaskInfo.isBaseType(GroupedTaskInfo.TYPE_DESK) - && runningTaskIds.length == 1) { - // TODO(b/342635213): Unsure if it's expected, desktop runningTasks only have a single + if (hasDesktopTask(runningTasks) && runningTaskIds.length == 1) { + // TODO(b/249371338): Unsure if it's expected, desktop runningTasks only have a single // taskId, therefore we match any DesktopTaskView that contains the runningTaskId. TaskView taskview = getTaskViewByTaskId(runningTaskIds[0]); if (taskview instanceof DesktopTaskView) { @@ -3113,41 +2816,44 @@ public abstract class RecentsView< } /** - * Creates a task view (if necessary) to represent the tasks with the {@param groupedTaskInfo}. + * Creates a task view (if necessary) to represent the task with the {@param runningTaskId}. * * All subsequent calls to reload will keep the task as the first item until {@link #reset()} * is called. Also scrolls the view to this task. */ - private void showCurrentTask(GroupedTaskInfo groupedTaskInfo, String caller) { - Log.d(TAG, "showCurrentTask(" + caller + ") - groupedTaskInfo: " + groupedTaskInfo); - if (groupedTaskInfo == null) { + private void showCurrentTask(Task[] runningTasks) { + Log.d(TAG, "showCurrentTask - runningTasks: " + Arrays.toString(runningTasks)); + if (runningTasks.length == 0) { return; } - int runningTaskViewId = -1; - if (shouldAddStubTaskView(groupedTaskInfo)) { + boolean needGroupTaskView = runningTasks.length > 1; + boolean needDesktopTask = hasDesktopTask(runningTasks); + if (shouldAddStubTaskView(runningTasks)) { boolean wasEmpty = getChildCount() == 0; // Add an empty view for now until the task plan is loaded and applied final TaskView taskView; - if (groupedTaskInfo.isBaseType(GroupedTaskInfo.TYPE_DESK)) { - taskView = mUtils.createDesktopTaskViewForActiveDesk(groupedTaskInfo); - } else if (groupedTaskInfo.isBaseType(GroupedTaskInfo.TYPE_SPLIT)) { - taskView = getTaskViewFromPool(TaskViewType.GROUPED); + if (needDesktopTask) { + taskView = getTaskViewFromPool(TaskView.Type.DESKTOP); + mTmpRunningTasks = Arrays.copyOf(runningTasks, runningTasks.length); + ((DesktopTaskView) taskView).bind(Arrays.asList(mTmpRunningTasks), + mOrientationState, mTaskOverlayFactory); + } else if (needGroupTaskView) { + taskView = getTaskViewFromPool(TaskView.Type.GROUPED); + mTmpRunningTasks = new Task[]{runningTasks[0], runningTasks[1]}; // When we create a placeholder task view mSplitBoundsConfig will be null, but with // the actual app running we won't need to show the thumbnail until all the tasks // load later anyways - ((GroupedTaskView) taskView).bind(Task.from(groupedTaskInfo.getTaskInfo1()), - Task.from(groupedTaskInfo.getTaskInfo2()), mOrientationState, - mTaskOverlayFactory, mSplitBoundsConfig); + ((GroupedTaskView)taskView).bind(mTmpRunningTasks[0], mTmpRunningTasks[1], + mOrientationState, mTaskOverlayFactory, mSplitBoundsConfig); } else { - taskView = getTaskViewFromPool(TaskViewType.SINGLE); - taskView.bind(Task.from(groupedTaskInfo.getTaskInfo1()), mOrientationState, - mTaskOverlayFactory); + taskView = getTaskViewFromPool(TaskView.Type.SINGLE); + // The temporary running task is only used for the duration between the start of the + // gesture and the task list is loaded and applied + mTmpRunningTasks = new Task[]{runningTasks[0]}; + taskView.bind(mTmpRunningTasks[0], mOrientationState, mTaskOverlayFactory); } - if (mAddDesktopButton != null && wasEmpty) { - addView(mAddDesktopButton); - } - addView(taskView, mUtils.getRunningTaskExpectedIndex(taskView)); + addView(taskView, 0); runningTaskViewId = taskView.getTaskViewId(); if (wasEmpty) { addView(mClearAllButton); @@ -3158,40 +2864,41 @@ public abstract class RecentsView< measure(makeMeasureSpec(getMeasuredWidth(), EXACTLY), makeMeasureSpec(getMeasuredHeight(), EXACTLY)); layout(getLeft(), getTop(), getRight(), getBottom()); - } else { - var runningTaskView = getTaskViewByTaskId(groupedTaskInfo.getTaskInfo1().taskId); - if (runningTaskView != null) { - runningTaskViewId = runningTaskView.getTaskViewId(); - } + } else if (getTaskViewByTaskId(runningTasks[0].key.id) != null) { + runningTaskViewId = getTaskViewByTaskId(runningTasks[0].key.id).getTaskViewId(); } boolean runningTaskTileHidden = mRunningTaskTileHidden; setCurrentTask(runningTaskViewId); - - int focusedTaskViewId; - if (enableGridOnlyOverview()) { - focusedTaskViewId = INVALID_TASK_ID; - } else if (enableLargeDesktopWindowingTile() - && getRunningTaskView() instanceof DesktopTaskView) { - TaskView focusedTaskView = mUtils.getFirstNonDesktopTaskView(); - focusedTaskViewId = - focusedTaskView != null ? focusedTaskView.getTaskViewId() : INVALID_TASK_ID; - } else { - focusedTaskViewId = runningTaskViewId; - } - setFocusedTaskViewId(focusedTaskViewId); - + mFocusedTaskViewId = enableGridOnlyOverview() ? INVALID_TASK_ID : runningTaskViewId; runOnPageScrollsInitialized(() -> setCurrentPage(getRunningTaskIndex())); setRunningTaskViewShowScreenshot(false); setRunningTaskHidden(runningTaskTileHidden); // Update task size after setting current task. updateTaskSize(); - mUtils.updateChildTaskOrientations(); + updateChildTaskOrientations(); // Reload the task list reloadIfNeeded(); } + private boolean hasDesktopTask(Task[] runningTasks) { + try { + if (!DesktopModeStatus.canEnterDesktopMode(getContext())) { + return false; + } + } catch (NoClassDefFoundError e) { + // Desktop mode is not supported on this device + return false; + } + for (Task task : runningTasks) { + if (task.key.windowingMode == WindowConfiguration.WINDOWING_MODE_FREEFORM) { + return true; + } + } + return false; + } + /** * Sets the running task id, cleaning up the old running task if necessary. */ @@ -3202,7 +2909,7 @@ public abstract class RecentsView< if (mRunningTaskViewId != -1) { // Reset the state on the old running task view - setTaskIconVisible(true); + setTaskIconScaledDown(false); setRunningTaskViewShowScreenshot(true); setRunningTaskHidden(false); } @@ -3210,20 +2917,21 @@ public abstract class RecentsView< } private void setRunningTaskViewId(int runningTaskViewId) { + int prevRunningTaskViewId = mRunningTaskViewId; mRunningTaskViewId = runningTaskViewId; - if (enableRefactorTaskThumbnail()) { - TaskView runningTaskView = getTaskViewFromTaskViewId(runningTaskViewId); - mRecentsViewModel.updateRunningTask( - runningTaskView != null ? runningTaskView.getTaskIdSet() - : Collections.emptySet()); + if (Flags.enableRefactorTaskThumbnail()) { + TaskView previousRunningTaskView = getTaskViewFromTaskViewId(prevRunningTaskViewId); + if (previousRunningTaskView != null) { + previousRunningTaskView.notifyIsRunningTaskUpdated(); + } + TaskView newRunningTaskView = getTaskViewFromTaskViewId(runningTaskViewId); + if (newRunningTaskView != null) { + newRunningTaskView.notifyIsRunningTaskUpdated(); + } } } - private void setFocusedTaskViewId(int viewId) { - mFocusedTaskViewId = viewId; - } - private int getTaskViewIdFromTaskId(int taskId) { TaskView taskView = getTaskViewByTaskId(taskId); return taskView != null ? taskView.getTaskViewId() : -1; @@ -3234,79 +2942,68 @@ public abstract class RecentsView< */ public void setRunningTaskHidden(boolean isHidden) { mRunningTaskTileHidden = isHidden; - // mRunningTaskAttachAlpha can be changed by RUNNING_TASK_ATTACH_ALPHA animation without - // changing mRunningTaskTileHidden. - mRunningTaskAttachAlpha = isHidden ? 0f : 1f; TaskView runningTask = getRunningTaskView(); - if (runningTask == null) { - return; + if (runningTask != null) { + runningTask.setStableAlpha(isHidden ? 0 : mContentAlpha); + if (!isHidden) { + AccessibilityManagerCompat.sendCustomAccessibilityEvent(runningTask, + AccessibilityEvent.TYPE_VIEW_FOCUSED, null); + } } - applyAttachAlpha(); - if (!isHidden) { - AccessibilityManagerCompat.sendCustomAccessibilityEvent( - runningTask, AccessibilityEvent.TYPE_VIEW_FOCUSED, null); - } - } - - private void applyAttachAlpha() { - // Only hide non running task carousel when it's fully off screen, otherwise it needs to - // be visible to move to on screen. - mUtils.applyAttachAlpha( - /*nonRunningTaskCarouselHidden=*/mDesktopCarouselDetachProgress == 1f); } private void setRunningTaskViewShowScreenshot(boolean showScreenshot) { - setRunningTaskViewShowScreenshot(showScreenshot, /*updatedThumbnails=*/null); - } - - private void setRunningTaskViewShowScreenshot(boolean showScreenshot, - @Nullable Map updatedThumbnails) { mRunningTaskShowScreenshot = showScreenshot; TaskView runningTaskView = getRunningTaskView(); if (runningTaskView != null) { - runningTaskView.setShouldShowScreenshot(mRunningTaskShowScreenshot, updatedThumbnails); - } - if (enableRefactorTaskThumbnail()) { - mRecentsViewModel.setRunningTaskShowScreenshot(showScreenshot); + runningTaskView.setShouldShowScreenshot(mRunningTaskShowScreenshot); } } - /** - * Updates icon visibility when going in or out of overview. - */ - public void setTaskIconVisible(boolean isVisible) { - if (mTaskIconVisible != isVisible) { - mTaskIconVisible = isVisible; - for (TaskView taskView : getTaskViews()) { - taskView.setIconVisibleForGesture(mTaskIconVisible); + public void setTaskIconScaledDown(boolean isScaledDown) { + if (mTaskIconScaledDown != isScaledDown) { + mTaskIconScaledDown = isScaledDown; + int taskCount = getTaskViewCount(); + for (int i = 0; i < taskCount; i++) { + requireTaskViewAt(i).setIconScaleAndDim(mTaskIconScaledDown ? 0 : 1); } } } private void animateActionsViewIn() { if (!showAsGrid() || isFocusedTaskInExpectedScrollPosition()) { - animateActionsViewAlpha(1, TaskView.FADE_IN_ICON_DURATION); + animateActionsViewAlpha(1, TaskView.SCALE_ICON_DURATION); } } - /** - * Updates icon visibility when gesture is settled. - */ - public void startIconFadeInOnGestureComplete() { - mTaskIconVisible = true; - for (TaskView taskView : getTaskViews()) { - taskView.startIconFadeInOnGestureComplete(); + public void animateUpTaskIconScale() { + mTaskIconScaledDown = false; + int taskCount = getTaskViewCount(); + for (int i = 0; i < taskCount; i++) { + TaskView taskView = requireTaskViewAt(i); + taskView.animateIconScaleAndDimIntoView(); } } /** * Updates TaskView and ClearAllButtion scaling and translation required to turn into grid * layout. - * - * Skips rebalance. + * This method is used when no task dismissal has occurred. */ private void updateGridProperties() { - updateGridProperties(null); + updateGridProperties(false, Integer.MAX_VALUE); + } + + /** + * Updates TaskView and ClearAllButtion scaling and translation required to turn into grid + * layout. + * + * This method is used when task dismissal has occurred, but rebalance is not needed. + * + * @param isTaskDismissal indicates if update was called due to task dismissal + */ + private void updateGridProperties(boolean isTaskDismissal) { + updateGridProperties(isTaskDismissal, Integer.MAX_VALUE); } /** @@ -3316,11 +3013,13 @@ public abstract class RecentsView< * This method only calculates the potential position and depends on {@link #setGridProgress} to * apply the actual scaling and translation. * - * @param lastVisibleTaskViewDuringDismiss which TaskView to start rebalancing from. Use - * `null` to skip rebalance. + * @param isTaskDismissal indicates if update was called due to task dismissal + * @param startRebalanceAfter which view index to start rebalancing from. Use Integer.MAX_VALUE + * to skip rebalance */ - private void updateGridProperties(TaskView lastVisibleTaskViewDuringDismiss) { - if (!hasTaskViews()) { + private void updateGridProperties(boolean isTaskDismissal, int startRebalanceAfter) { + int taskCount = getTaskViewCount(); + if (taskCount == 0) { return; } @@ -3329,97 +3028,68 @@ public abstract class RecentsView< int topRowWidth = 0; int bottomRowWidth = 0; - int largeTileRowWidth = 0; float topAccumulatedTranslationX = 0; float bottomAccumulatedTranslationX = 0; - // Horizontal grid translation for each task. - Map gridTranslations = new HashMap<>(); + // Contains whether the child index is in top or bottom of grid (for non-focused task) + // Different from mTopRowIdSet, which contains the taskViewId of what task is in top row + IntSet topSet = new IntSet(); + IntSet bottomSet = new IntSet(); - TaskView lastLargeTaskView = mUtils.getLastLargeTaskView(); - int focusedTaskViewShift = 0; - int largeTaskWidthAndSpacing = 0; + // Horizontal grid translation for each task + float[] gridTranslations = new float[taskCount]; + + int focusedTaskIndex = Integer.MAX_VALUE; + int focusedTaskShift = 0; + int focusedTaskWidthAndSpacing = 0; int snappedTaskRowWidth = 0; - int expectedCurrentTaskRowWidth = 0; int snappedPage = isKeyboardTaskFocusPending() ? mKeyboardTaskFocusIndex : getNextPage(); TaskView snappedTaskView = getTaskViewAt(snappedPage); TaskView homeTaskView = getHomeTaskView(); - TaskView expectedCurrentTaskView = mUtils.getExpectedCurrentTask(getRunningTaskView(), - getFocusedTaskView()); TaskView nextFocusedTaskView = null; - // Don't clear the top row, if the user has dismissed a task, to maintain the task order. - if (!mAnyTaskHasBeenDismissed) { + if (!isTaskDismissal) { mTopRowIdSet.clear(); } - - // Consecutive task views in the top row or bottom row, which means another one set will - // be cleared up while starting to add TaskViews to one of them. Also means only one of - // them can be non-empty at most. - Set lastTopTaskViews = new HashSet<>(); - Set lastBottomTaskViews = new HashSet<>(); - - int largeTasksCount = 0; - // True if the last large TaskView has been visited during the TaskViews iteration. - boolean encounteredLastLargeTaskView = false; - // True if the highest index visible TaskView has been visited during the TaskViews - // iteration. - boolean encounteredLastVisibleTaskView = false; - for (TaskView taskView : getTaskViews()) { - if (taskView == lastLargeTaskView) { - encounteredLastLargeTaskView = true; - } - if (taskView == lastVisibleTaskViewDuringDismiss) { - encounteredLastVisibleTaskView = true; - } - float gridTranslation = 0f; + for (int i = 0; i < taskCount; i++) { + TaskView taskView = requireTaskViewAt(i); int taskWidthAndSpacing = taskView.getLayoutParams().width + mPageSpacing; // Evenly distribute tasks between rows unless rearranging due to task dismissal, in // which case keep tasks in their respective rows. For the running task, don't join // the grid. - if (taskView.isLargeTile()) { - largeTasksCount++; - // DesktopTaskView`s are hidden during split select state, so we shouldn't count - // them when calculating row width. - if (!(taskView instanceof DesktopTaskView && isSplitSelectionActive())) { - topRowWidth += taskWidthAndSpacing; - bottomRowWidth += taskWidthAndSpacing; - largeTileRowWidth += taskWidthAndSpacing; - } - gridTranslation += focusedTaskViewShift; - gridTranslation += mIsRtl ? taskWidthAndSpacing : -taskWidthAndSpacing; + if (taskView.isFocusedTask()) { + topRowWidth += taskWidthAndSpacing; + bottomRowWidth += taskWidthAndSpacing; + + focusedTaskIndex = i; + focusedTaskWidthAndSpacing = taskWidthAndSpacing; + gridTranslations[i] += focusedTaskShift; + gridTranslations[i] += mIsRtl ? taskWidthAndSpacing : -taskWidthAndSpacing; // Center view vertically in case it's from different orientation. taskView.setGridTranslationY((mLastComputedTaskSize.height() + taskTopMargin - taskView.getLayoutParams().height) / 2f); - largeTaskWidthAndSpacing = taskWidthAndSpacing; - if (taskView == snappedTaskView) { - snappedTaskRowWidth = largeTileRowWidth; - } - if (taskView == expectedCurrentTaskView) { - expectedCurrentTaskRowWidth = largeTileRowWidth; + // If focused task is snapped, the row width is just task width and spacing. + snappedTaskRowWidth = taskWidthAndSpacing; } } else { - if (encounteredLastLargeTaskView) { - // For tasks after the last large task, shift by large task's width and spacing. - gridTranslation += - mIsRtl ? largeTaskWidthAndSpacing : -largeTaskWidthAndSpacing; + if (i > focusedTaskIndex) { + // For tasks after the focused task, shift by focused task's width and spacing. + gridTranslations[i] += + mIsRtl ? focusedTaskWidthAndSpacing : -focusedTaskWidthAndSpacing; } else { - // For TaskViews before the new focused TaskView, accumulate the width and - // spacing to calculate the distance the new focused TaskView needs to shift. - // This could happen for example after multiple times of dismissing the - // focused TaskView, the triggered rebalance might set a non-first TaskView - // inside `mChildren` as the new focused TaskView. - focusedTaskViewShift += mIsRtl ? taskWidthAndSpacing : -taskWidthAndSpacing; + // For task before the focused task, accumulate the width and spacing to + // calculate the distance focused task need to shift. + focusedTaskShift += mIsRtl ? taskWidthAndSpacing : -taskWidthAndSpacing; } int taskViewId = taskView.getTaskViewId(); + // Rebalance the grid starting after a certain index boolean isTopRow; - if (mAnyTaskHasBeenDismissed) { - // Rebalance the grid starting after a certain index. - if (encounteredLastVisibleTaskView) { + if (isTaskDismissal) { + if (i > startRebalanceAfter) { mTopRowIdSet.remove(taskViewId); isTopRow = topRowWidth <= bottomRowWidth; } else { @@ -3436,47 +3106,47 @@ public abstract class RecentsView< } else { topRowWidth += taskWidthAndSpacing; } + topSet.add(i); mTopRowIdSet.add(taskViewId); + taskView.setGridTranslationY(mTaskGridVerticalDiff); // Move horizontally into empty space. float widthOffset = 0; - for (TaskView bottomTaskView : lastBottomTaskViews) { - widthOffset += bottomTaskView.getLayoutParams().width + mPageSpacing; + for (int j = i - 1; !topSet.contains(j) && j >= 0; j--) { + if (j == focusedTaskIndex) { + continue; + } + widthOffset += requireTaskViewAt(j).getLayoutParams().width + mPageSpacing; } float currentTaskTranslationX = mIsRtl ? widthOffset : -widthOffset; - gridTranslation += topAccumulatedTranslationX + currentTaskTranslationX; + gridTranslations[i] += topAccumulatedTranslationX + currentTaskTranslationX; topAccumulatedTranslationX += currentTaskTranslationX; - lastTopTaskViews.add(taskView); - lastBottomTaskViews.clear(); } else { bottomRowWidth += taskWidthAndSpacing; + bottomSet.add(i); // Move into bottom row. taskView.setGridTranslationY(mTopBottomRowHeightDiff + mTaskGridVerticalDiff); // Move horizontally into empty space. float widthOffset = 0; - for (TaskView topTaskView : lastTopTaskViews) { - widthOffset += topTaskView.getLayoutParams().width + mPageSpacing; + for (int j = i - 1; !bottomSet.contains(j) && j >= 0; j--) { + if (j == focusedTaskIndex) { + continue; + } + widthOffset += requireTaskViewAt(j).getLayoutParams().width + mPageSpacing; } float currentTaskTranslationX = mIsRtl ? widthOffset : -widthOffset; - gridTranslation += bottomAccumulatedTranslationX + currentTaskTranslationX; + gridTranslations[i] += bottomAccumulatedTranslationX + currentTaskTranslationX; bottomAccumulatedTranslationX += currentTaskTranslationX; - lastBottomTaskViews.add(taskView); - lastTopTaskViews.clear(); } - int taskViewRowWidth = isTopRow ? topRowWidth : bottomRowWidth; if (taskView == snappedTaskView) { - snappedTaskRowWidth = taskViewRowWidth; - } - if (taskView == expectedCurrentTaskView) { - expectedCurrentTaskRowWidth = taskViewRowWidth; + snappedTaskRowWidth = isTopRow ? topRowWidth : bottomRowWidth; } } - gridTranslations.put(taskView, gridTranslation); } // We need to maintain snapped task's page scroll invariant between quick switch and @@ -3487,22 +3157,22 @@ public abstract class RecentsView< if (snappedTaskView != null) { snappedTaskNonGridScrollAdjustment = snappedTaskView.getScrollAdjustment( /*gridEnabled=*/false); - snappedTaskGridTranslationX = gridTranslations.getOrDefault(snappedTaskView, 0f); + snappedTaskGridTranslationX = gridTranslations[snappedPage]; } // Use the accumulated translation of the row containing the last task. - float clearAllAccumulatedTranslation = !lastTopTaskViews.isEmpty() + float clearAllAccumulatedTranslation = topSet.contains(taskCount - 1) ? topAccumulatedTranslationX : bottomAccumulatedTranslationX; // If the last task is on the shorter row, ClearAllButton will embed into the shorter row // which is not what we want. Compensate the width difference of the 2 rows in that case. float shorterRowCompensation = 0; if (topRowWidth <= bottomRowWidth) { - if (!lastTopTaskViews.isEmpty()) { + if (topSet.contains(taskCount - 1)) { shorterRowCompensation = bottomRowWidth - topRowWidth; } } else { - if (!lastBottomTaskViews.isEmpty()) { + if (bottomSet.contains(taskCount - 1)) { shorterRowCompensation = topRowWidth - bottomRowWidth; } } @@ -3513,20 +3183,12 @@ public abstract class RecentsView< // accordingly. Update longRowWidth if ClearAllButton has been moved. float clearAllShortTotalWidthTranslation = 0; int longRowWidth = Math.max(topRowWidth, bottomRowWidth); - - // If first task is not in the expected position (mLastComputedTaskSize) and being too close - // to ClearAllButton, then apply extra translation to ClearAllButton. - int rowWidthAfterExpectedCurrentTask = longRowWidth - expectedCurrentTaskRowWidth; - int expectedCurrentTaskWidthAndSpacing = - (expectedCurrentTaskView != null - ? expectedCurrentTaskView.getLayoutParams().width - : 0 - ) + mPageSpacing; - int firstTaskStart = mLastComputedGridSize.left + rowWidthAfterExpectedCurrentTask - + expectedCurrentTaskWidthAndSpacing; - int expectedFirstTaskStart = mLastComputedTaskSize.right; - if (firstTaskStart < expectedFirstTaskStart) { - mClearAllShortTotalWidthTranslation = expectedFirstTaskStart - firstTaskStart; + if (longRowWidth < mLastComputedGridSize.width()) { + mClearAllShortTotalWidthTranslation = + (mIsRtl + ? mLastComputedTaskSize.right + : deviceProfile.widthPx - mLastComputedTaskSize.left) + - longRowWidth - deviceProfile.overviewGridSideMargin; clearAllShortTotalWidthTranslation = mIsRtl ? -mClearAllShortTotalWidthTranslation : mClearAllShortTotalWidthTranslation; if (snappedTaskRowWidth == longRowWidth) { @@ -3541,10 +3203,10 @@ public abstract class RecentsView< float clearAllTotalTranslationX = clearAllAccumulatedTranslation + clearAllShorterRowCompensation + clearAllShortTotalWidthTranslation + snappedTaskNonGridScrollAdjustment; - if (largeTasksCount > 0) { + if (focusedTaskIndex < taskCount) { // Shift by focused task's width and spacing if a task is focused. clearAllTotalTranslationX += - mIsRtl ? largeTaskWidthAndSpacing : -largeTaskWidthAndSpacing; + mIsRtl ? focusedTaskWidthAndSpacing : -focusedTaskWidthAndSpacing; } // Make sure there are enough space between snapped page and ClearAllButton, for the case @@ -3556,33 +3218,27 @@ public abstract class RecentsView< (mIsRtl ? mLastComputedTaskSize.left : deviceProfile.widthPx - mLastComputedTaskSize.right) - - deviceProfile.overviewGridSideMargin - mPageSpacing - + (mTaskWidth - snappedTaskView.getLayoutParams().width) - - mClearAllShortTotalWidthTranslation; + - deviceProfile.overviewGridSideMargin - mPageSpacing + + (mTaskWidth - snappedTaskView.getLayoutParams().width) + - mClearAllShortTotalWidthTranslation; if (distanceFromClearAll < minimumDistance) { int distanceDifference = minimumDistance - distanceFromClearAll; snappedTaskGridTranslationX += mIsRtl ? distanceDifference : -distanceDifference; } } - for (TaskView taskView : getTaskViews()) { - taskView.setGridTranslationX( - gridTranslations.getOrDefault(taskView, 0f) - snappedTaskGridTranslationX - + snappedTaskNonGridScrollAdjustment); + for (int i = 0; i < taskCount; i++) { + TaskView taskView = requireTaskViewAt(i); + taskView.setGridTranslationX(gridTranslations[i] - snappedTaskGridTranslationX + + snappedTaskNonGridScrollAdjustment); } - if (mAddDesktopButton != null) { - TaskView firstTaskView = getFirstTaskView(); - float translationX = 0f; - if (firstTaskView != null) { - translationX += firstTaskView.getGridTranslationX(); - } - if (focusedTaskViewShift != 0) { - // If the focused task is inserted between `firstTaskView` and - // `mAddDesktopButton`, shift `mAddDesktopButton` to accommodate. - translationX += largeTaskWidthAndSpacing; - } - mAddDesktopButton.setGridTranslationX(translationX); + final TaskView runningTask = getRunningTaskView(); + if (showAsGrid() && enableGridOnlyOverview() && runningTask != null) { + runActionOnRemoteHandles( + remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator() + .taskSecondaryTranslation.value = runningTask.getGridTranslationY() + ); } mClearAllButton.setGridTranslationPrimary( @@ -3590,18 +3246,19 @@ public abstract class RecentsView< mClearAllButton.setGridScrollOffset( mIsRtl ? mLastComputedTaskSize.left - mLastComputedGridSize.left : mLastComputedTaskSize.right - mLastComputedGridSize.right); + setGridProgress(mGridProgress); } - protected boolean isSameGridRow(TaskView taskView1, TaskView taskView2) { + private boolean isSameGridRow(TaskView taskView1, TaskView taskView2) { if (taskView1 == null || taskView2 == null) { return false; } - if (taskView1.isLargeTile() || taskView2.isLargeTile()) { - return false; - } int taskViewId1 = taskView1.getTaskViewId(); int taskViewId2 = taskView2.getTaskViewId(); + if (taskViewId1 == mFocusedTaskViewId || taskViewId2 == mFocusedTaskViewId) { + return false; + } return (mTopRowIdSet.contains(taskViewId1) && mTopRowIdSet.contains(taskViewId2)) || ( !mTopRowIdSet.contains(taskViewId1) && !mTopRowIdSet.contains(taskViewId2)); } @@ -3614,16 +3271,22 @@ public abstract class RecentsView< private void setGridProgress(float gridProgress) { mGridProgress = gridProgress; - for (TaskView taskView : getTaskViews()) { - taskView.setGridProgress(gridProgress); + int taskCount = getTaskViewCount(); + for (int i = 0; i < taskCount; i++) { + requireTaskViewAt(i).setGridProgress(gridProgress); } mClearAllButton.setGridProgress(gridProgress); } private void setTaskThumbnailSplashAlpha(float taskThumbnailSplashAlpha) { + int taskCount = getTaskViewCount(); + if (taskCount == 0) { + return; + } + mTaskThumbnailSplashAlpha = taskThumbnailSplashAlpha; - for (TaskView taskView : getTaskViews()) { - taskView.setTaskThumbnailSplashAlpha(taskThumbnailSplashAlpha); + for (int i = 0; i < taskCount; i++) { + requireTaskViewAt(i).setTaskThumbnailSplashAlpha(taskThumbnailSplashAlpha); } } @@ -3637,12 +3300,12 @@ public abstract class RecentsView< mLayoutTransition.addTransitionListener(new TransitionListener() { @Override public void startTransition(LayoutTransition transition, ViewGroup viewGroup, - View view, int i) { + View view, int i) { } @Override public void endTransition(LayoutTransition transition, ViewGroup viewGroup, - View view, int i) { + View view, int i) { // When the unpinned task is added, snap to first page and disable transitions if (view instanceof TaskView) { snapToPage(0); @@ -3695,9 +3358,12 @@ public abstract class RecentsView< if (taskView.isRunningTask()) { anim.addOnFrameCallback(() -> { if (!mEnableDrawingLiveTile) return; - runActionOnRemoteHandles(remoteTargetHandle -> - remoteTargetHandle.getTaskViewSimulator().taskSecondaryTranslation.value = - taskView.getSecondaryDismissTranslationProperty().get(taskView)); + runActionOnRemoteHandles( + remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator() + .taskSecondaryTranslation.value = getPagedOrientationHandler() + .getSecondaryValue(taskView.getTranslationX(), + taskView.getTranslationY() + )); redrawLiveTile(); }); } @@ -3742,7 +3408,6 @@ public abstract class RecentsView< mSplitSelectStateController.getSplitAnimationController(). playAnimPlaceholderToFullscreen(mContainer, view, Optional.of(() -> resetFromSplitSelectionState()))); - firstFloatingTaskView.setContentDescription(splitAnimInitProps.getContentDescription()); // SplitInstructionsView: animate in safeRemoveDragLayerView(mSplitSelectStateController.getSplitInstructionsView()); @@ -3778,7 +3443,11 @@ public abstract class RecentsView< InteractionJankMonitorWrapper.end(Cuj.CUJ_SPLIT_SCREEN_ENTER); } else { // If transition to split select was interrupted, clean up to prevent glitches - mSplitSelectStateController.resetState(); + if (FeatureFlags.enableSplitContextually()) { + mSplitSelectStateController.resetState(); + } else { + resetFromSplitSelectionState(); + } InteractionJankMonitorWrapper.cancel(Cuj.CUJ_SPLIT_SCREEN_ENTER); } @@ -3788,22 +3457,17 @@ public abstract class RecentsView< /** * Creates a {@link PendingAnimation} for dismissing the specified {@link TaskView}. - * - * @param dismissedTaskView the {@link TaskView} to be dismissed - * @param animateTaskView whether the {@link TaskView} to be dismissed should be - * animated - * @param shouldRemoveTask whether the associated {@link Task} should be removed from - * ActivityManager after dismissal - * @param duration duration of the animation + * @param dismissedTaskView the {@link TaskView} to be dismissed + * @param animateTaskView whether the {@link TaskView} to be dismissed should be animated + * @param shouldRemoveTask whether the associated {@link Task} should be removed from + * ActivityManager after dismissal + * @param duration duration of the animation * @param dismissingForSplitSelection task dismiss animation is used for entering split * selection state from app icon - * @param isExpressiveDismiss runs expressive animations controlled via - * {@link RecentsDismissUtils} */ - public void createTaskDismissAnimation(PendingAnimation anim, - @Nullable TaskView dismissedTaskView, + public void createTaskDismissAnimation(PendingAnimation anim, TaskView dismissedTaskView, boolean animateTaskView, boolean shouldRemoveTask, long duration, - boolean dismissingForSplitSelection, boolean isExpressiveDismiss) { + boolean dismissingForSplitSelection) { if (mPendingAnimation != null) { mPendingAnimation.createPlaybackController().dispatchOnCancel().dispatchOnEnd(); } @@ -3816,44 +3480,35 @@ public abstract class RecentsView< boolean showAsGrid = showAsGrid(); int taskCount = getTaskViewCount(); int dismissedIndex = indexOfChild(dismissedTaskView); - int dismissedTaskViewId = - dismissedTaskView != null ? dismissedTaskView.getTaskViewId() : INVALID_TASK_ID; + int dismissedTaskViewId = dismissedTaskView.getTaskViewId(); // Grid specific properties. boolean isFocusedTaskDismissed = false; boolean isStagingFocusedTask = false; - boolean isSlidingTasks = false; TaskView nextFocusedTaskView = null; boolean nextFocusedTaskFromTop = false; float dismissedTaskWidth = 0; float nextFocusedTaskWidth = 0; + // Non-grid specific properties. int[] oldScroll = new int[count]; int[] newScroll = new int[count]; int scrollDiffPerPage = 0; - // Non-grid specific properties. boolean needsCurveUpdates = false; - boolean areAllDesktopTasksDismissed = false; if (showAsGrid) { - if (dismissedTaskView != null) { - dismissedTaskWidth = dismissedTaskView.getLayoutParams().width + mPageSpacing; - } - isFocusedTaskDismissed = dismissedTaskViewId != INVALID_TASK_ID - && dismissedTaskViewId == mFocusedTaskViewId; - if (dismissingForSplitSelection && getTaskViewAt( - mCurrentPage) instanceof DesktopTaskView) { - areAllDesktopTasksDismissed = true; - } + dismissedTaskWidth = dismissedTaskView.getLayoutParams().width + mPageSpacing; + isFocusedTaskDismissed = dismissedTaskViewId == mFocusedTaskViewId; if (isFocusedTaskDismissed) { if (isSplitSelectionActive()) { isStagingFocusedTask = true; } else { nextFocusedTaskFromTop = - !mTopRowIdSet.isEmpty() && mTopRowIdSet.size() >= (taskCount - 1) / 2f; + mTopRowIdSet.size() > 0 && mTopRowIdSet.size() >= (taskCount - 1) / 2f; // Pick the next focused task from the preferred row. - for (TaskView taskView : getTaskViews()) { - if (taskView == dismissedTaskView || taskView.isLargeTile()) { + for (int i = 0; i < taskCount; i++) { + TaskView taskView = requireTaskViewAt(i); + if (taskView == dismissedTaskView) { continue; } boolean isTopRow = mTopRowIdSet.contains(taskView.getTaskViewId()); @@ -3869,16 +3524,15 @@ public abstract class RecentsView< } } } + } else { + getPageScrolls(oldScroll, false, SIMPLE_SCROLL_LOGIC); + getPageScrolls(newScroll, false, + v -> v.getVisibility() != GONE && v != dismissedTaskView); + if (count > 1) { + scrollDiffPerPage = Math.abs(oldScroll[1] - oldScroll[0]); + } } - getPageScrolls(oldScroll, false, SIMPLE_SCROLL_LOGIC); - getPageScrolls(newScroll, false, - v -> v.getVisibility() != GONE && v != dismissedTaskView); - if (count > 1) { - scrollDiffPerPage = Math.abs(oldScroll[1] - oldScroll[0]); - } - - isSlidingTasks = isStagingFocusedTask || areAllDesktopTasksDismissed; float dismissTranslationInterpolationEnd = 1; boolean closeGapBetweenClearAll = false; boolean isClearAllHidden = isClearAllHidden(); @@ -3889,46 +3543,31 @@ public abstract class RecentsView< int currentPageScroll = getScrollForPage(mCurrentPage); int lastGridTaskScroll = getScrollForPage(indexOfChild(lastGridTaskView)); boolean currentPageSnapsToEndOfGrid = currentPageScroll == lastGridTaskScroll; - - int topGridRowSize = mTopRowIdSet.size(); - int numLargeTiles = mUtils.getLargeTileCount(); - int bottomGridRowSize = taskCount - mTopRowIdSet.size() - numLargeTiles; - boolean topRowLonger = topGridRowSize > bottomGridRowSize; - boolean bottomRowLonger = bottomGridRowSize > topGridRowSize; - boolean dismissedTaskFromTop = mTopRowIdSet.contains(dismissedTaskViewId); - boolean dismissedTaskFromBottom = !dismissedTaskFromTop && !isFocusedTaskDismissed; - if (dismissedTaskFromTop || (isFocusedTaskDismissed && nextFocusedTaskFromTop)) { - topGridRowSize--; - } - if (dismissedTaskFromBottom || (isFocusedTaskDismissed && !nextFocusedTaskFromTop)) { - bottomGridRowSize--; - } - int longRowWidth = Math.max(topGridRowSize, bottomGridRowSize) - * (mLastComputedGridTaskSize.width() + mPageSpacing); - if (!enableGridOnlyOverview() && !isStagingFocusedTask) { - longRowWidth += mLastComputedTaskSize.width() + mPageSpacing; - } - // Compensate the removed gap if we don't already have shortTotalCompensation, - // and adjust accordingly to the new shortTotalCompensation after dismiss. - int newClearAllShortTotalWidthTranslation = 0; - if (mClearAllShortTotalWidthTranslation == 0) { - // If first task is not in the expected position (mLastComputedTaskSize) and being too - // close to ClearAllButton, then apply extra translation to ClearAllButton. - int firstTaskStart = mLastComputedGridSize.left + longRowWidth; - int expectedFirstTaskStart = mLastComputedTaskSize.right; - if (firstTaskStart < expectedFirstTaskStart) { - newClearAllShortTotalWidthTranslation = expectedFirstTaskStart - firstTaskStart; - } - } - if (lastGridTaskView != null && ( - (!isExpressiveDismiss && lastGridTaskView.isVisibleToUser()) || (isExpressiveDismiss - && (isTaskViewVisible(lastGridTaskView) - || lastGridTaskView == dismissedTaskView)))) { + if (lastGridTaskView != null && lastGridTaskView.isVisibleToUser()) { // After dismissal, animate translation of the remaining tasks to fill any gap left // between the end of the grid and the clear all button. Only animate if the clear // all button is visible or would become visible after dismissal. float longGridRowWidthDiff = 0; + int topGridRowSize = mTopRowIdSet.size(); + int bottomGridRowSize = taskCount - mTopRowIdSet.size() + - (enableGridOnlyOverview() ? 0 : 1); + boolean topRowLonger = topGridRowSize > bottomGridRowSize; + boolean bottomRowLonger = bottomGridRowSize > topGridRowSize; + boolean dismissedTaskFromTop = mTopRowIdSet.contains(dismissedTaskViewId); + boolean dismissedTaskFromBottom = !dismissedTaskFromTop && !isFocusedTaskDismissed; + if (dismissedTaskFromTop || (isFocusedTaskDismissed && nextFocusedTaskFromTop)) { + topGridRowSize--; + } + if (dismissedTaskFromBottom || (isFocusedTaskDismissed && !nextFocusedTaskFromTop)) { + bottomGridRowSize--; + } + int longRowWidth = Math.max(topGridRowSize, bottomGridRowSize) + * (mLastComputedGridTaskSize.width() + mPageSpacing); + if (!enableGridOnlyOverview() && !isStagingFocusedTask) { + longRowWidth += mLastComputedTaskSize.width() + mPageSpacing; + } + float gapWidth = 0; if ((topRowLonger && dismissedTaskFromTop) || (bottomRowLonger && dismissedTaskFromBottom)) { @@ -3940,6 +3579,17 @@ public abstract class RecentsView< } if (gapWidth > 0) { if (mClearAllShortTotalWidthTranslation == 0) { + // Compensate the removed gap if we don't already have shortTotalCompensation, + // and adjust accordingly to the new shortTotalCompensation after dismiss. + int newClearAllShortTotalWidthTranslation = 0; + if (longRowWidth < mLastComputedGridSize.width()) { + DeviceProfile deviceProfile = mContainer.getDeviceProfile(); + newClearAllShortTotalWidthTranslation = + (mIsRtl + ? mLastComputedTaskSize.right + : deviceProfile.widthPx - mLastComputedTaskSize.left) + - longRowWidth - deviceProfile.overviewGridSideMargin; + } float gapCompensation = gapWidth - newClearAllShortTotalWidthTranslation; longGridRowWidthDiff += mIsRtl ? -gapCompensation : gapCompensation; } @@ -3968,22 +3618,6 @@ public abstract class RecentsView< // the only invariant point in landscape split screen. snapToLastTask = true; } - if (mUtils.getGridTaskCount() == 1 && dismissedTaskView.isGridTask()) { - TaskView lastLargeTile = mUtils.getLastLargeTaskView(); - if (lastLargeTile != null) { - // Calculate the distance to put last large tile back to middle of the screen. - int primaryScroll = getPagedOrientationHandler().getPrimaryScroll(this); - int lastLargeTileScroll = getScrollForPage(indexOfChild(lastLargeTile)); - longGridRowWidthDiff = primaryScroll - lastLargeTileScroll; - - if (!isClearAllHidden) { - // If ClearAllButton is visible, reduce the distance by scroll difference - // between ClearAllButton and the last task. - longGridRowWidthDiff += getLastTaskScroll(/*clearAllScroll=*/0, - getPagedOrientationHandler().getPrimarySize(mClearAllButton)); - } - } - } // If we need to animate the grid to compensate the clear all gap, we split the second // half of the dismiss pending animation (in which the non-dismissed tasks slide into @@ -4001,7 +3635,8 @@ public abstract class RecentsView< END_DISMISS_TRANSLATION_INTERPOLATION_OFFSET + (taskCount - 1) * halfAdditionalDismissTranslationOffset, END_DISMISS_TRANSLATION_INTERPOLATION_OFFSET, 1); - for (TaskView taskView : getTaskViews()) { + for (int i = 0; i < taskCount; i++) { + TaskView taskView = requireTaskViewAt(i); anim.setFloat(taskView, TaskView.GRID_END_TRANSLATION_X, longGridRowWidthDiff, clampToProgress(LINEAR, dismissTranslationInterpolationEnd, 1)); dismissTranslationInterpolationEnd = Utilities.boundToRange( @@ -4037,99 +3672,140 @@ public abstract class RecentsView< SplitAnimationTimings splitTimings = AnimUtils.getDeviceOverviewToSplitTimings(mContainer.getDeviceProfile().isTablet); - int distanceFromDismissedTask = 1; - int slidingTranslation = 0; - if (isSlidingTasks) { - int nextSnappedPage = indexOfChild(isStagingFocusedTask - ? mUtils.getFirstSmallTaskView() - : mUtils.getFirstNonDesktopTaskView()); - slidingTranslation = getPagedOrientationHandler().getPrimaryScroll(this) - - getScrollForPage(nextSnappedPage); - slidingTranslation += mIsRtl ? newClearAllShortTotalWidthTranslation - : -newClearAllShortTotalWidthTranslation; - } - mTaskViewsDismissPrimaryTranslations.clear(); - int lastTaskViewIndex = indexOfChild(mUtils.getLastTaskView()); + int distanceFromDismissedTask = 0; for (int i = 0; i < count; i++) { View child = getChildAt(i); if (child == dismissedTaskView) { - if (animateTaskView && !dismissingForSplitSelection) { - addDismissedTaskAnimations(dismissedTaskView, duration, anim); + if (animateTaskView) { + if (dismissingForSplitSelection) { + createInitialSplitSelectAnimation(anim); + } else { + addDismissedTaskAnimations(dismissedTaskView, duration, anim); + } } - } else if (!showAsGrid || (enableLargeDesktopWindowingTile() - && dismissedTaskView != null && dismissedTaskView.isLargeTile() - && nextFocusedTaskView == null && !dismissingForSplitSelection)) { - int offset = getOffsetToDismissedTask(scrollDiffPerPage, dismissedIndex, - lastTaskViewIndex); + } else if (!showAsGrid) { + // Compute scroll offsets from task dismissal for animation. + // If we just take newScroll - oldScroll, everything to the right of dragged task + // translates to the left. We need to offset this in some cases: + // - In RTL, add page offset to all pages, since we want pages to move to the right + // Additionally, add a page offset if: + // - Current page is rightmost page (leftmost for RTL) + // - Dragging an adjacent page on the left side (right side for RTL) + int offset = mIsRtl ? scrollDiffPerPage : 0; + if (mCurrentPage == dismissedIndex) { + int lastPage = taskCount - 1; + if (mCurrentPage == lastPage) { + offset += mIsRtl ? -scrollDiffPerPage : scrollDiffPerPage; + } + } else { + // Dismissing an adjacent page. + int negativeAdjacent = mCurrentPage - 1; // (Right in RTL, left in LTR) + if (dismissedIndex == negativeAdjacent) { + offset += mIsRtl ? -scrollDiffPerPage : scrollDiffPerPage; + } + } + int scrollDiff = newScroll[i] - oldScroll[i] + offset; if (scrollDiff != 0) { - if (!isExpressiveDismiss) { - translateTaskWhenDismissed( - child, - Math.abs(i - dismissedIndex), - scrollDiff, - anim, - splitTimings); - } - if (child instanceof TaskView taskView) { - mTaskViewsDismissPrimaryTranslations.put(taskView, scrollDiffPerPage); + FloatProperty translationProperty = child instanceof TaskView + ? ((TaskView) child).getPrimaryDismissTranslationProperty() + : getPagedOrientationHandler().getPrimaryViewTranslate(); + + float additionalDismissDuration = + ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET * Math.abs( + i - dismissedIndex); + + // We are in non-grid layout. + // If dismissing for split select, use split timings. + // If not, use dismiss timings. + float animationStartProgress = isSplitSelectionActive() + ? Utilities.boundToRange(splitTimings.getGridSlideStartOffset(), 0f, 1f) + : Utilities.boundToRange( + INITIAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET + + additionalDismissDuration, 0f, 1f); + + float animationEndProgress = isSplitSelectionActive() + ? Utilities.boundToRange(splitTimings.getGridSlideStartOffset() + + splitTimings.getGridSlideDurationOffset(), 0f, 1f) + : 1f; + + // Slide tiles in horizontally to fill dismissed area + anim.setFloat(child, translationProperty, scrollDiff, + clampToProgress( + splitTimings.getGridSlidePrimaryInterpolator(), + animationStartProgress, + animationEndProgress + ) + ); + + if (mEnableDrawingLiveTile && child instanceof TaskView + && ((TaskView) child).isRunningTask()) { + anim.addOnFrameCallback(() -> { + runActionOnRemoteHandles( + remoteTargetHandle -> + remoteTargetHandle.getTaskViewSimulator() + .taskPrimaryTranslation.value = + getPagedOrientationHandler().getPrimaryValue( + child.getTranslationX(), + child.getTranslationY() + )); + redrawLiveTile(); + }); } needsCurveUpdates = true; } - } else if (child instanceof TaskView taskView) { + } else if (child instanceof TaskView) { + TaskView taskView = (TaskView) child; + if (isFocusedTaskDismissed) { + if (nextFocusedTaskView != null && + !isSameGridRow(taskView, nextFocusedTaskView)) { + continue; + } + } else { + if (i < dismissedIndex || !isSameGridRow(taskView, dismissedTaskView)) { + continue; + } + } // Animate task with index >= dismissed index and in the same row as the // dismissed index or next focused index. Offset successive task dismissal // durations for a staggered effect. - int staggerColumn = isSlidingTasks + distanceFromDismissedTask++; + int staggerColumn = isStagingFocusedTask ? (int) Math.ceil(distanceFromDismissedTask / 2f) : distanceFromDismissedTask; // Set timings based on if user is initiating splitscreen on the focused task, // or splitting/dismissing some other task. - final float animationStartProgress; - if (isSlidingTasks) { - float slidingStartOffset = splitTimings.getGridSlideStartOffset() - + (splitTimings.getGridSlideStaggerOffset() * staggerColumn); - if (areAllDesktopTasksDismissed) { - animationStartProgress = Utilities.boundToRange( - slidingStartOffset - + splitTimings.getDesktopFadeSplitAnimationEndOffset(), - 0f, - dismissTranslationInterpolationEnd); - } else { - animationStartProgress = Utilities.boundToRange( - slidingStartOffset, - 0f, - dismissTranslationInterpolationEnd); - } - } else { - animationStartProgress = Utilities.boundToRange( - INITIAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET - + ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET - * staggerColumn, 0f, dismissTranslationInterpolationEnd); - } + float animationStartProgress = isStagingFocusedTask + ? Utilities.boundToRange( + splitTimings.getGridSlideStartOffset() + + (splitTimings.getGridSlideStaggerOffset() + * staggerColumn), + 0f, + dismissTranslationInterpolationEnd) + : Utilities.boundToRange( + INITIAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET + + ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET + * staggerColumn, 0f, dismissTranslationInterpolationEnd); + float animationEndProgress = isStagingFocusedTask + ? Utilities.boundToRange( + splitTimings.getGridSlideStartOffset() + + (splitTimings.getGridSlideStaggerOffset() * staggerColumn) + + splitTimings.getGridSlideDurationOffset(), + 0f, + dismissTranslationInterpolationEnd) + : dismissTranslationInterpolationEnd; + Interpolator dismissInterpolator = isStagingFocusedTask ? OVERSHOOT_0_75 : LINEAR; - final float animationEndProgress; - if (isSlidingTasks && taskView != nextFocusedTaskView) { - animationEndProgress = Utilities.boundToRange( - splitTimings.getGridSlideStartOffset() - + (splitTimings.getGridSlideStaggerOffset() * staggerColumn) - + splitTimings.getGridSlideDurationOffset(), - 0f, - dismissTranslationInterpolationEnd); - } else { - animationEndProgress = dismissTranslationInterpolationEnd; - } - - Interpolator dismissInterpolator = isSlidingTasks ? EMPHASIZED : LINEAR; - - float primaryTranslation = 0; if (taskView == nextFocusedTaskView) { // Enlarge the task to be focused next, and translate into focus position. float scale = mTaskWidth / (float) mLastComputedGridTaskSize.width(); anim.setFloat(taskView, TaskView.DISMISS_SCALE, scale, clampToProgress(LINEAR, animationStartProgress, dismissTranslationInterpolationEnd)); - primaryTranslation += dismissedTaskWidth; + anim.setFloat(taskView, taskView.getPrimaryDismissTranslationProperty(), + mIsRtl ? dismissedTaskWidth : -dismissedTaskWidth, + clampToProgress(LINEAR, animationStartProgress, + dismissTranslationInterpolationEnd)); float secondaryTranslation = -mTaskGridVerticalDiff; if (!nextFocusedTaskFromTop) { secondaryTranslation -= mTopBottomRowHeightDiff; @@ -4137,45 +3813,27 @@ public abstract class RecentsView< anim.setFloat(taskView, taskView.getSecondaryDismissTranslationProperty(), secondaryTranslation, clampToProgress(LINEAR, animationStartProgress, dismissTranslationInterpolationEnd)); - anim.add(taskView.getDismissIconFadeOutAnimator(), + anim.add(taskView.getFocusTransitionScaleAndDimOutAnimator(), clampToProgress(LINEAR, 0f, ANIMATION_DISMISS_PROGRESS_MIDPOINT)); - } else if ((isFocusedTaskDismissed && nextFocusedTaskView != null && isSameGridRow( - taskView, nextFocusedTaskView)) - || (!isFocusedTaskDismissed && i >= dismissedIndex && isSameGridRow( - taskView, dismissedTaskView))) { - primaryTranslation += + } else { + float primaryTranslation = nextFocusedTaskView != null ? nextFocusedTaskWidth : dismissedTaskWidth; - } - if (!(taskView instanceof DesktopTaskView)) { - primaryTranslation += mIsRtl ? slidingTranslation : -slidingTranslation; - } + if (isStagingFocusedTask) { + // Moves less if focused task is not in scroll position. + int focusedTaskScroll = getScrollForPage(dismissedIndex); + int primaryScroll = getPagedOrientationHandler().getPrimaryScroll(this); + int focusedTaskScrollDiff = primaryScroll - focusedTaskScroll; + primaryTranslation += + mIsRtl ? focusedTaskScrollDiff : -focusedTaskScrollDiff; + } - if (primaryTranslation != 0) { - float finalTranslation = mIsRtl ? primaryTranslation : -primaryTranslation; - float startTranslation = 0; - if (!(taskView instanceof DesktopTaskView) && slidingTranslation != 0) { - startTranslation = isTaskViewVisible(taskView) ? 0 - : finalTranslation + (mIsRtl ? -mLastComputedTaskSize.right - : mLastComputedTaskSize.right); - } - // Expressive dismiss will animate the translations of taskViews itself. - if (!isExpressiveDismiss) { - Animator dismissAnimator = ObjectAnimator.ofFloat(taskView, - taskView.getPrimaryDismissTranslationProperty(), - startTranslation, finalTranslation); - dismissAnimator.setInterpolator( - clampToProgress(dismissInterpolator, animationStartProgress, - animationEndProgress)); - anim.add(dismissAnimator); - } - mTaskViewsDismissPrimaryTranslations.put(taskView, (int) finalTranslation); - distanceFromDismissedTask++; + anim.setFloat(taskView, taskView.getPrimaryDismissTranslationProperty(), + mIsRtl ? primaryTranslation : -primaryTranslation, + clampToProgress(dismissInterpolator, animationStartProgress, + animationEndProgress)); } } } - if (dismissingForSplitSelection) { - createInitialSplitSelectAnimation(anim); - } if (needsCurveUpdates) { anim.addOnFrameCallback(this::updateCurveProperties); @@ -4184,26 +3842,21 @@ public abstract class RecentsView< // Add a tiny bit of translation Z, so that it draws on top of other views. This is relevant // (e.g.) when we dismiss a task by sliding it upward: if there is a row of icons above, we // want the dragged task to stay above all other views. - if (animateTaskView && dismissedTaskView != null) { + if (animateTaskView) { dismissedTaskView.setTranslationZ(0.1f); } - loadVisibleTaskData(TaskView.FLAG_UPDATE_ALL); - if (!dismissingForSplitSelection) { - anim.addStartListener(() -> InteractionJankMonitorWrapper.begin(this, - Cuj.CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS)); - } + mPendingAnimation = anim; final TaskView finalNextFocusedTaskView = nextFocusedTaskView; final boolean finalCloseGapBetweenClearAll = closeGapBetweenClearAll; final boolean finalSnapToLastTask = snapToLastTask; final boolean finalIsFocusedTaskDismissed = isFocusedTaskDismissed; - mPendingAnimation.addEndListener(new Consumer<>() { + mPendingAnimation.addEndListener(new Consumer() { @Override public void accept(Boolean success) { - if (mEnableDrawingLiveTile && dismissedTaskView != null - && dismissedTaskView.isRunningTask() && success) { + if (mEnableDrawingLiveTile && dismissedTaskView.isRunningTask() && success) { finishRecentsAnimation(true /* toRecents */, false /* shouldPip */, - () -> onEnd(true)); + () -> onEnd(success)); } else { onEnd(success); } @@ -4216,16 +3869,17 @@ public abstract class RecentsView< resetTaskVisuals(); if (success) { - mAnyTaskHasBeenDismissed = true; - if (shouldRemoveTask && dismissedTaskView != null) { + if (shouldRemoveTask) { if (dismissedTaskView.isRunningTask()) { finishRecentsAnimation(true /* toRecents */, false /* shouldPip */, - () -> removeTaskInternal(dismissedTaskView)); + () -> removeTaskInternal(dismissedTaskViewId)); } else { - removeTaskInternal(dismissedTaskView); + removeTaskInternal(dismissedTaskViewId); } + announceForAccessibility( + getResources().getString(R.string.task_view_closed)); mContainer.getStatsLogManager().logger() - .withItemInfo(dismissedTaskView.getItemInfo()) + .withItemInfo(dismissedTaskView.getFirstItemInfo()) .log(LAUNCHER_TASK_DISMISS_SWIPE_UP); } @@ -4241,7 +3895,7 @@ public abstract class RecentsView< pageToSnapTo = indexOfChild(mClearAllButton); } else if (isClearAllHidden) { // Snap to focused task if clear all is hidden. - pageToSnapTo = indexOfChild(getFirstTaskView()); + pageToSnapTo = 0; } } else { // Get the id of the task view we will snap to based on the current @@ -4259,7 +3913,7 @@ public abstract class RecentsView< } else { // Won't focus next task in split select, so snap to the // first task. - pageToSnapTo = indexOfChild(getFirstTaskView()); + pageToSnapTo = 0; calculateScrollDiff = false; } } else { @@ -4267,8 +3921,8 @@ public abstract class RecentsView< boolean isSnappedTaskInTopRow = mTopRowIdSet.contains( snappedTaskViewId); IntArray taskViewIdArray = - isSnappedTaskInTopRow ? mUtils.getTopRowIdArray() - : mUtils.getBottomRowIdArray(); + isSnappedTaskInTopRow ? getTopRowIdArray() + : getBottomRowIdArray(); int snappedIndex = taskViewIdArray.indexOf(snappedTaskViewId); taskViewIdArray.removeValue(dismissedTaskViewId); if (finalNextFocusedTaskView != null) { @@ -4283,8 +3937,8 @@ public abstract class RecentsView< // dismissed row, // snap to the same column in the other grid row IntArray inverseRowTaskViewIdArray = - isSnappedTaskInTopRow ? mUtils.getBottomRowIdArray() - : mUtils.getTopRowIdArray(); + isSnappedTaskInTopRow ? getBottomRowIdArray() + : getTopRowIdArray(); if (snappedIndex < inverseRowTaskViewIdArray.size()) { taskViewIdToSnapTo = inverseRowTaskViewIdArray.get( snappedIndex); @@ -4300,7 +3954,7 @@ public abstract class RecentsView< mCurrentPageScrollDiff = primaryScroll - currentPageScroll; } } - } else if (dismissedIndex < pageToSnapTo || pageToSnapTo == lastTaskViewIndex) { + } else if (dismissedIndex < pageToSnapTo || pageToSnapTo == taskCount - 1) { pageToSnapTo--; } boolean isHomeTaskDismissed = dismissedTaskView == getHomeTaskView(); @@ -4309,7 +3963,6 @@ public abstract class RecentsView< if (taskCount == 1) { removeViewInLayout(mClearAllButton); - removeViewInLayout(mAddDesktopButton); if (isHomeTaskDismissed) { updateEmptyMessage(); } else if (!mSplitSelectStateController.isSplitSelectActive()) { @@ -4318,28 +3971,28 @@ public abstract class RecentsView< } else { // Update focus task and its size. if (finalIsFocusedTaskDismissed && finalNextFocusedTaskView != null) { - setFocusedTaskViewId(enableGridOnlyOverview() + mFocusedTaskViewId = enableGridOnlyOverview() ? INVALID_TASK_ID - : finalNextFocusedTaskView.getTaskViewId()); + : finalNextFocusedTaskView.getTaskViewId(); mTopRowIdSet.remove(mFocusedTaskViewId); - finalNextFocusedTaskView.getDismissIconFadeInAnimator().start(); + finalNextFocusedTaskView.animateIconScaleAndDimIntoView(); } - updateTaskSize(); - mUtils.updateChildTaskOrientations(); + updateTaskSize(/*isTaskDismissal=*/ true); + updateChildTaskOrientations(); // Update scroll and snap to page. updateScrollSynchronously(); if (showAsGrid) { // Rebalance tasks in the grid - TaskView highestVisibleTaskView = getHighestVisibleTaskView(); - if (highestVisibleTaskView != null) { + int highestVisibleTaskIndex = getHighestVisibleTaskIndex(); + if (highestVisibleTaskIndex < Integer.MAX_VALUE) { + TaskView taskView = requireTaskViewAt(highestVisibleTaskIndex); + boolean shouldRebalance; int screenStart = getPagedOrientationHandler().getPrimaryScroll( RecentsView.this); - int taskStart = getPagedOrientationHandler().getChildStart( - highestVisibleTaskView) - + (int) highestVisibleTaskView.getOffsetAdjustment( - /*gridEnabled=*/true); + int taskStart = getPagedOrientationHandler().getChildStart(taskView) + + (int) taskView.getOffsetAdjustment(/*gridEnabled=*/ true); // Rebalance only if there is a maximum gap between the task and the // screen's edge; this ensures that rebalanced tasks are outside the @@ -4352,33 +4005,26 @@ public abstract class RecentsView< RecentsView.this); int taskSize = (int) ( getPagedOrientationHandler().getMeasuredSize( - highestVisibleTaskView) * highestVisibleTaskView - .getSizeAdjustment(/*fullscreenEnabled=*/ - false)); + taskView) * taskView + .getSizeAdjustment(/*fullscreenEnabled=*/false)); int taskEnd = taskStart + taskSize; shouldRebalance = taskEnd >= screenEnd - mPageSpacing; } if (shouldRebalance) { - updateGridProperties(highestVisibleTaskView); + updateGridProperties(/*isTaskDismissal=*/ true, + highestVisibleTaskIndex); updateScrollSynchronously(); } } - IntArray topRowIdArray = mUtils.getTopRowIdArray(); - IntArray bottomRowIdArray = mUtils.getBottomRowIdArray(); + IntArray topRowIdArray = getTopRowIdArray(); + IntArray bottomRowIdArray = getBottomRowIdArray(); if (finalSnapToLastTask) { // If snapping to last task, find the last task after dismissal. pageToSnapTo = indexOfChild( getLastGridTaskView(topRowIdArray, bottomRowIdArray)); - - if (pageToSnapTo == INVALID_PAGE) { - // Snap to latest large tile page after dismissing the - // last grid task. This will prevent snapping to page 0 when - // desktop task is visible as large tile. - pageToSnapTo = indexOfChild(mUtils.getLastLargeTaskView()); - } } else if (taskViewIdToSnapTo != -1) { // If snapping to another page due to indices rearranging, find // the new index after dismissal & rearrange using the task view id. @@ -4407,105 +4053,10 @@ public abstract class RecentsView< updateCurrentTaskActionsVisibility(); onDismissAnimationEnds(); mPendingAnimation = null; - mTaskViewsDismissPrimaryTranslations.clear(); - - if (!dismissingForSplitSelection && success) { - InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS); - } else if (!dismissingForSplitSelection) { - InteractionJankMonitorWrapper.cancel(Cuj.CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS); - } } }); } - /** - * Compute scroll offsets from task dismissal for animation. - * If we just take newScroll - oldScroll, everything to the right of dragged task - * translates to the left. We need to offset this in some cases: - * - In RTL, add page offset to all pages, since we want pages to move to the right - * Additionally, add a page offset if: - * - Current page is rightmost page (leftmost for RTL) - * - Dragging an adjacent page on the left side (right side for RTL) - */ - private int getOffsetToDismissedTask(int scrollDiffPerPage, int dismissedIndex, - int lastTaskViewIndex) { - // If `mCurrentPage` is beyond `lastTaskViewIndex`, use the last TaskView instead to - // calculate offset. - int currentPage = Math.min(mCurrentPage, lastTaskViewIndex); - int offset = mIsRtl ? scrollDiffPerPage : 0; - if (currentPage == dismissedIndex) { - if (currentPage == lastTaskViewIndex) { - offset += mIsRtl ? -scrollDiffPerPage : scrollDiffPerPage; - } - } else { - // Dismissing an adjacent page. - int negativeAdjacent = currentPage - 1; // (Right in RTL, left in LTR) - if (dismissedIndex == negativeAdjacent) { - offset += mIsRtl ? -scrollDiffPerPage : scrollDiffPerPage; - } - } - return offset; - } - - private void translateTaskWhenDismissed( - View view, - int indexDiff, - int scrollDiffPerPage, - PendingAnimation pendingAnimation, - SplitAnimationTimings splitTimings) { - // No need to translate the AddDesktopButton on dismissing a TaskView, which should be - // always at the right most position, even when dismissing the last TaskView. - if (view instanceof AddDesktopButton) { - return; - } - FloatProperty translationProperty = view instanceof TaskView - ? ((TaskView) view).getPrimaryDismissTranslationProperty() - : getPagedOrientationHandler().getPrimaryViewTranslate(); - - float additionalDismissDuration = - ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET * indexDiff; - - // We are in non-grid layout. - // If dismissing for split select, use split timings. - // If not, use dismiss timings. - float animationStartProgress = isSplitSelectionActive() - ? Utilities.boundToRange(splitTimings.getGridSlideStartOffset(), 0f, 1f) - : Utilities.boundToRange( - INITIAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET - + additionalDismissDuration, 0f, 1f); - - float animationEndProgress = isSplitSelectionActive() - ? Utilities.boundToRange(splitTimings.getGridSlideStartOffset() - + splitTimings.getGridSlideDurationOffset(), 0f, 1f) - : 1f; - - // Slide tiles in horizontally to fill dismissed area - pendingAnimation.setFloat( - view, - translationProperty, - scrollDiffPerPage, - clampToProgress( - splitTimings.getGridSlidePrimaryInterpolator(), - animationStartProgress, - animationEndProgress - ) - ); - if (mEnableDrawingLiveTile && view instanceof TaskView - && ((TaskView) view).isRunningTask()) { - pendingAnimation.addOnFrameCallback(() -> { - runActionOnRemoteHandles( - remoteTargetHandle -> - remoteTargetHandle.getTaskViewSimulator() - .taskPrimaryTranslation.value = - getPagedOrientationHandler().getPrimaryValue( - view.getTranslationX(), - view.getTranslationY() - )); - redrawLiveTile(); - }); - } - } - /** * Hides all overview actions if user is halfway through split selection, shows otherwise. * We only show split option if: @@ -4517,6 +4068,9 @@ public abstract class RecentsView< boolean isCurrentSplit = taskView instanceof GroupedTaskView; GroupedTaskView groupedTaskView = isCurrentSplit ? (GroupedTaskView) taskView : null; // Update flags to see if entire actions bar should be hidden. + if (!FeatureFlags.enableAppPairs()) { + mActionsView.updateHiddenFlags(HIDDEN_SPLIT_SCREEN, isCurrentSplit); + } mActionsView.updateHiddenFlags(HIDDEN_SPLIT_SELECT_ACTIVE, isSplitSelectionActive()); // Update flags to see if actions bar should show buttons for a single task or a pair of // tasks. @@ -4533,18 +4087,55 @@ public abstract class RecentsView< return true; } + /** + * Returns all the tasks in the top row, without the focused task + */ + private IntArray getTopRowIdArray() { + if (mTopRowIdSet.isEmpty()) { + return new IntArray(0); + } + IntArray topArray = new IntArray(mTopRowIdSet.size()); + int taskViewCount = getTaskViewCount(); + for (int i = 0; i < taskViewCount; i++) { + int taskViewId = requireTaskViewAt(i).getTaskViewId(); + if (mTopRowIdSet.contains(taskViewId)) { + topArray.add(taskViewId); + } + } + return topArray; + } + + /** + * Returns all the tasks in the bottom row, without the focused task + */ + private IntArray getBottomRowIdArray() { + int bottomRowIdArraySize = getBottomRowTaskCountForTablet(); + if (bottomRowIdArraySize <= 0) { + return new IntArray(0); + } + IntArray bottomArray = new IntArray(bottomRowIdArraySize); + int taskViewCount = getTaskViewCount(); + for (int i = 0; i < taskViewCount; i++) { + int taskViewId = requireTaskViewAt(i).getTaskViewId(); + if (!mTopRowIdSet.contains(taskViewId) && taskViewId != mFocusedTaskViewId) { + bottomArray.add(taskViewId); + } + } + return bottomArray; + } + /** * Iterate the grid by columns instead of by TaskView index, starting after the focused task and * up to the last balanced column. * - * @return the highest visible TaskView between both rows + * @return the highest visible TaskView index between both rows */ - private TaskView getHighestVisibleTaskView() { - if (mTopRowIdSet.isEmpty()) return null; // return earlier + private int getHighestVisibleTaskIndex() { + if (mTopRowIdSet.isEmpty()) return Integer.MAX_VALUE; // return earlier - TaskView lastVisibleTaskView = null; - IntArray topRowIdArray = mUtils.getTopRowIdArray(); - IntArray bottomRowIdArray = mUtils.getBottomRowIdArray(); + int lastVisibleIndex = Integer.MAX_VALUE; + IntArray topRowIdArray = getTopRowIdArray(); + IntArray bottomRowIdArray = getBottomRowIdArray(); int balancedColumns = Math.min(bottomRowIdArray.size(), topRowIdArray.size()); for (int i = 0; i < balancedColumns; i++) { @@ -4552,42 +4143,25 @@ public abstract class RecentsView< if (isTaskViewVisible(topTask)) { TaskView bottomTask = getTaskViewFromTaskViewId(bottomRowIdArray.get(i)); - lastVisibleTaskView = - indexOfChild(topTask) > indexOfChild(bottomTask) ? topTask : bottomTask; - } else if (lastVisibleTaskView != null) { + lastVisibleIndex = Math.max(indexOfChild(topTask), indexOfChild(bottomTask)); + } else if (lastVisibleIndex < Integer.MAX_VALUE) { break; } } - return lastVisibleTaskView; + return lastVisibleIndex; } - private void removeTaskInternal(@NonNull TaskView dismissedTaskView) { - UI_HELPER_EXECUTOR - .getHandler() - .post( - () -> { - if (dismissedTaskView instanceof DesktopTaskView desktopTaskView) { - removeDesktopTaskView(desktopTaskView); - } else { - for (int taskId : dismissedTaskView.getTaskIds()) { - ActivityManagerWrapper.getInstance().removeTask(taskId); - } - } - }); - } - - private void removeDesktopTaskView(DesktopTaskView desktopTaskView) { - if (areMultiDesksFlagsEnabled()) { - SystemUiProxy.INSTANCE - .get(getContext()) - .removeDesk(desktopTaskView.getDeskId()); - } else if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) { - SystemUiProxy.INSTANCE - .get(getContext()) - .removeDefaultDeskInDisplay( - mContainer.getDisplay().getDisplayId()); - } + private void removeTaskInternal(int dismissedTaskViewId) { + int[] taskIds = getTaskIdsForTaskViewId(dismissedTaskViewId); + UI_HELPER_EXECUTOR.getHandler().post( + () -> { + for (int taskId : taskIds) { + if (taskId != -1) { + ActivityManagerWrapper.getInstance().removeTask(taskId); + } + } + }); } protected void onDismissAnimationEnds() { @@ -4601,24 +4175,19 @@ public abstract class RecentsView< } PendingAnimation anim = new PendingAnimation(duration); - for (TaskView taskView : getTaskViews()) { - addDismissedTaskAnimations(taskView, duration, anim); + int count = getTaskViewCount(); + for (int i = 0; i < count; i++) { + addDismissedTaskAnimations(requireTaskViewAt(i), duration, anim); } mPendingAnimation = anim; mPendingAnimation.addEndListener(isSuccess -> { if (isSuccess) { - // Remove desktops first, since desks can be empty (so they have no recent tasks), - // and closing all tasks on a desk doesn't always necessarily mean that the desk - // will be removed. So, there are no guarantees that the below call to - // `ActivityManagerWrapper::removeAllRecentTasks()` will be enough. - SystemUiProxy.INSTANCE.get(getContext()).removeAllDesks(); - // Remove all the task views now finishRecentsAnimation(true /* toRecents */, false /* shouldPip */, () -> { UI_HELPER_EXECUTOR.getHandler().post( ActivityManagerWrapper.getInstance()::removeAllRecentTasks); - removeAllTaskViews(); + removeTasksViewsAndClearAllButton(); startHome(); }); } @@ -4628,7 +4197,7 @@ public abstract class RecentsView< } private boolean snapToPageRelative(int delta, boolean cycle, - TaskGridNavHelper.TaskNavDirection direction) { + @TaskGridNavHelper.TASK_NAV_DIRECTION int direction) { // Set next page if scroll animation is still running, otherwise cannot snap to the // next page on successive key presses. Setting the current page aborts the scroll. if (!mScroller.isFinished()) { @@ -4647,41 +4216,32 @@ public abstract class RecentsView< return true; } - private int getNextPageInternal(int delta, TaskGridNavHelper.TaskNavDirection direction, + private int getNextPageInternal(int delta, @TaskGridNavHelper.TASK_NAV_DIRECTION int direction, boolean cycle) { if (!showAsGrid()) { return getNextPage() + delta; } // Init task grid nav helper with top/bottom id arrays. - TaskGridNavHelper taskGridNavHelper = new TaskGridNavHelper(mUtils.getTopRowIdArray(), - mUtils.getBottomRowIdArray(), mUtils.getLargeTaskViewIds(), - mAddDesktopButton != null); + TaskGridNavHelper taskGridNavHelper = new TaskGridNavHelper(getTopRowIdArray(), + getBottomRowIdArray(), mFocusedTaskViewId); // Get current page's task view ID. TaskView currentPageTaskView = getCurrentPageTaskView(); int currentPageTaskViewId; - final int clearAllButtonIndex = indexOfChild(mClearAllButton); - final int addDesktopButtonIndex = indexOfChild(mAddDesktopButton); if (currentPageTaskView != null) { currentPageTaskViewId = currentPageTaskView.getTaskViewId(); - } else if (mCurrentPage == clearAllButtonIndex) { + } else if (mCurrentPage == indexOfChild(mClearAllButton)) { currentPageTaskViewId = TaskGridNavHelper.CLEAR_ALL_PLACEHOLDER_ID; - } else if (mCurrentPage == addDesktopButtonIndex) { - currentPageTaskViewId = TaskGridNavHelper.ADD_DESK_PLACEHOLDER_ID; } else { return INVALID_PAGE; } - final int nextGridPage = + int nextGridPage = taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); - if (nextGridPage == TaskGridNavHelper.CLEAR_ALL_PLACEHOLDER_ID) { - return clearAllButtonIndex; - } - if (nextGridPage == TaskGridNavHelper.ADD_DESK_PLACEHOLDER_ID) { - return addDesktopButtonIndex; - } - return indexOfChild(getTaskViewFromTaskViewId(nextGridPage)); + return nextGridPage == TaskGridNavHelper.CLEAR_ALL_PLACEHOLDER_ID + ? indexOfChild(mClearAllButton) + : indexOfChild(getTaskViewFromTaskViewId(nextGridPage)); } private void runDismissAnimation(PendingAnimation pendingAnim) { @@ -4692,39 +4252,18 @@ public abstract class RecentsView< } @UiThread - public void dismissTask(int taskId, boolean animate, boolean removeTask) { + private void dismissTask(int taskId) { TaskView taskView = getTaskViewByTaskId(taskId); if (taskView == null) { - Log.d(TAG, "dismissTask: " + taskId + ", no associated TaskView"); return; } - Log.d(TAG, "dismissTask: " + taskId); - - if (enableDesktopExplodedView() && taskView instanceof DesktopTaskView desktopTaskView) { - desktopTaskView.removeTaskFromExplodedView(taskId, animate); - - if (removeTask) { - ActivityManagerWrapper.getInstance().removeTask(taskId); - } - } else { - dismissTaskView(taskView, animate, removeTask); - } + dismissTask(taskView, true /* animate */, false /* removeTask */); } - /** Dismisses the entire [taskView]. */ - public void dismissTaskView(TaskView taskView, boolean animateTaskView, boolean removeTask) { + public void dismissTask(TaskView taskView, boolean animateTaskView, boolean removeTask) { PendingAnimation pa = new PendingAnimation(DISMISS_TASK_DURATION); createTaskDismissAnimation(pa, taskView, animateTaskView, removeTask, DISMISS_TASK_DURATION, - false /* dismissingForSplitSelection*/, false /* isExpressiveDismiss */); - runDismissAnimation(pa); - } - - protected void expressiveDismissTaskView(TaskView taskView, Function0 onEndRunnable) { - PendingAnimation pa = new PendingAnimation(DISMISS_TASK_DURATION); - createTaskDismissAnimation(pa, taskView, false /* animateTaskView */, true /* removeTask */, - DISMISS_TASK_DURATION, false /* dismissingForSplitSelection*/, - true /* isExpressiveDismiss */); - pa.addEndListener((success) -> onEndRunnable.invoke()); + false /* dismissingForSplitSelection*/); runDismissAnimation(pa); } @@ -4737,42 +4276,27 @@ public abstract class RecentsView< private void dismissCurrentTask() { TaskView taskView = getNextPageTaskView(); if (taskView != null) { - dismissTaskView(taskView, true /*animateTaskView*/, true /*removeTask*/); + dismissTask(taskView, true /*animateTaskView*/, true /*removeTask*/); } } - private void createDesk(View view) { - SystemUiProxy.INSTANCE - .get(getContext()) - .createDesk(mContainer.getDisplay().getDisplayId()); - } - @Override public boolean dispatchKeyEvent(KeyEvent event) { if (isHandlingTouch() || event.getAction() != KeyEvent.ACTION_DOWN) { return super.dispatchKeyEvent(event); } - - if (mUtils.shouldInterceptKeyEvent(event)) { - return super.dispatchKeyEvent(event); - } - switch (event.getKeyCode()) { case KeyEvent.KEYCODE_TAB: return snapToPageRelative(event.isShiftPressed() ? -1 : 1, true /* cycle */, - TaskGridNavHelper.TaskNavDirection.TAB); + DIRECTION_TAB); case KeyEvent.KEYCODE_DPAD_RIGHT: - return snapToPageRelative(mIsRtl ? -1 : 1, true /* cycle */, - TaskGridNavHelper.TaskNavDirection.RIGHT); + return snapToPageRelative(mIsRtl ? -1 : 1, true /* cycle */, DIRECTION_RIGHT); case KeyEvent.KEYCODE_DPAD_LEFT: - return snapToPageRelative(mIsRtl ? 1 : -1, true /* cycle */, - TaskGridNavHelper.TaskNavDirection.LEFT); + return snapToPageRelative(mIsRtl ? 1 : -1, true /* cycle */, DIRECTION_LEFT); case KeyEvent.KEYCODE_DPAD_UP: - return snapToPageRelative(1, false /* cycle */, - TaskGridNavHelper.TaskNavDirection.UP); + return snapToPageRelative(1, false /* cycle */, DIRECTION_UP); case KeyEvent.KEYCODE_DPAD_DOWN: - return snapToPageRelative(1, false /* cycle */, - TaskGridNavHelper.TaskNavDirection.DOWN); + return snapToPageRelative(1, false /* cycle */, DIRECTION_DOWN); case KeyEvent.KEYCODE_DEL: case KeyEvent.KEYCODE_FORWARD_DEL: dismissCurrentTask(); @@ -4816,14 +4340,15 @@ public abstract class RecentsView< alpha = Utilities.boundToRange(alpha, 0, 1); mContentAlpha = alpha; - for (TaskView taskView : getTaskViews()) { - taskView.setStableAlpha(alpha); + TaskView runningTaskView = getRunningTaskView(); + for (int i = getTaskViewCount() - 1; i >= 0; i--) { + TaskView child = requireTaskViewAt(i); + if (runningTaskView != null && mRunningTaskTileHidden && child == runningTaskView) { + continue; + } + child.setStableAlpha(alpha); } mClearAllButton.setContentAlpha(mContentAlpha); - - if (mAddDesktopButton != null) { - mAddDesktopButton.setContentAlpha(mContentAlpha); - } int alphaInt = Math.round(alpha * 255); mEmptyMessagePaint.setAlpha(alphaInt); mEmptyIcon.setAlpha(alphaInt); @@ -4879,12 +4404,6 @@ public abstract class RecentsView< } } - public void reapplyActiveRotation() { - RotationTouchHelper rotationTouchHelper = RotationTouchHelper.INSTANCE.get(getContext()); - setLayoutRotation(rotationTouchHelper.getCurrentActiveRotation(), - rotationTouchHelper.getDisplayRotation()); - } - public void setLayoutRotation(int touchRotation, int displayRotation) { if (mOrientationState.update(touchRotation, displayRotation)) { updateOrientationHandler(); @@ -4904,20 +4423,6 @@ public abstract class RecentsView< return getTaskViewAt(getRunningTaskIndex() + 1); } - @Nullable - public TaskView getPreviousTaskView() { - return getTaskViewAt(getRunningTaskIndex() - 1); - } - - @Nullable - public TaskView getLastLargeTaskView() { - return mUtils.getLastLargeTaskView(); - } - - public int getLargeTilesCount() { - return mUtils.getLargeTileCount(); - } - @Nullable public TaskView getCurrentPageTaskView() { return getTaskViewAt(getCurrentPage()); @@ -4943,10 +4448,11 @@ public abstract class RecentsView< } /** - * Returns iterable [TaskView] children. + * A version of {@link #getTaskViewAt} when the caller is sure about the input index. */ - public RecentsViewUtils.TaskViewsIterable getTaskViews() { - return mUtils.getTaskViews(); + @NonNull + private TaskView requireTaskViewAt(int index) { + return Objects.requireNonNull(getTaskViewAt(index)); } public void setOnEmptyMessageUpdatedListener(OnEmptyMessageUpdatedListener listener) { @@ -4954,14 +4460,13 @@ public abstract class RecentsView< } public void updateEmptyMessage() { - boolean isEmpty = !hasTaskViews(); + boolean isEmpty = getTaskViewCount() == 0; boolean hasSizeChanged = mLastMeasureSize.x != getWidth() || mLastMeasureSize.y != getHeight(); if (isEmpty == mShowEmptyMessage && !hasSizeChanged) { return; } setContentDescription(isEmpty ? mEmptyMessage : ""); - setFocusable(isEmpty); mShowEmptyMessage = isEmpty; updateEmptyStateUi(hasSizeChanged); invalidate(); @@ -4997,19 +4502,29 @@ public abstract class RecentsView< } private void updatePivots() { - if (mOverviewSelectEnabled && !enableGridOnlyOverview()) { - mTempPointF.set(mLastComputedTaskSize.centerX(), mLastComputedTaskSize.bottom); + if (mOverviewSelectEnabled) { + if (enableGridOnlyOverview()) { + getModalTaskSize(mTempRect); + Rect selectedTaskPosition = getSelectedTaskBounds(); + Utilities.getPivotsForScalingRectToRect(mTempRect, selectedTaskPosition, + mTempPointF); + } else { + mTempPointF.set(mLastComputedTaskSize.centerX(), mLastComputedTaskSize.bottom); + } } else { - mTempRect.set(mLastComputedTaskSize); + // Only update pivot when it is tablet and not in grid yet, so the pivot is correct + // for non-current tasks when swiping up to overview + if (enableGridOnlyOverview() && mContainer.getDeviceProfile().isTablet + && !mOverviewGridEnabled) { + mTempRect.set(mLastComputedCarouselTaskSize); + } else { + mTempRect.set(mLastComputedTaskSize); + } getPagedViewOrientedState().getFullScreenScaleAndPivot(mTempRect, mContainer.getDeviceProfile(), mTempPointF); } setPivotX(mTempPointF.x); setPivotY(mTempPointF.y); - if (enableGridOnlyOverview()) { - runActionOnRemoteHandles(remoteTargetHandle -> - remoteTargetHandle.getTaskViewSimulator().setPivotOverride(mTempPointF)); - } } /** @@ -5036,15 +4551,10 @@ public abstract class RecentsView< ? (runningTask == null ? INVALID_PAGE : indexOfChild(runningTask)) : mOffsetMidpointIndexOverride; int modalMidpoint = getCurrentPage(); - TaskView carouselHiddenMidpointTask = runningTask != null ? runningTask - : mUtils.getFirstTaskViewInCarousel(/*nonRunningTaskCarouselHidden=*/true, - /*runningTaskView=*/null); - int carouselHiddenMidpoint = indexOfChild(carouselHiddenMidpointTask); - boolean shouldCalculateOffsetForAllTasks = showAsGrid - && (enableGridOnlyOverview() || enableLargeDesktopWindowingTile()) - && mTaskModalness > 0; - if (shouldCalculateOffsetForAllTasks) { - modalMidpoint = indexOfChild(getSelectedTaskView()); + boolean isModalGridWithoutFocusedTask = + showAsGrid && enableGridOnlyOverview() && mTaskModalness > 0; + if (isModalGridWithoutFocusedTask) { + modalMidpoint = indexOfChild(mSelectedTask); } float midpointOffsetSize = 0; @@ -5059,7 +4569,6 @@ public abstract class RecentsView< float modalLeftOffsetSize = 0; float modalRightOffsetSize = 0; float gridOffsetSize = 0; - float carouselHiddenOffsetSize = 0; if (showAsGrid) { // In grid, we only focus the task on the side. The reference index used for offset @@ -5077,52 +4586,27 @@ public abstract class RecentsView< : 0; } - int primarySize = getPagedOrientationHandler().getPrimaryValue(getWidth(), getHeight()); - float maxOverscroll = primarySize * OverScroll.OVERSCROLL_DAMP_FACTOR; for (int i = 0; i < count; i++) { - View child = getChildAt(i); float translation = i == midpoint ? midpointOffsetSize : i < midpoint ? leftOffsetSize : rightOffsetSize; - if (shouldCalculateOffsetForAllTasks) { + if (isModalGridWithoutFocusedTask) { gridOffsetSize = getHorizontalOffsetSize(i, modalMidpoint, modalOffset); gridOffsetSize = Math.abs(gridOffsetSize) * (i <= modalMidpoint ? 1 : -1); } - if (enableLargeDesktopWindowingTile()) { - if (child instanceof TaskView - && !mUtils.isVisibleInCarousel((TaskView) child, - runningTask, /*nonRunningTaskCarouselHidden=*/true)) { - // Increment carouselHiddenOffsetSize by maxOverscroll so it won't be on screen - // even when user overscroll. - carouselHiddenOffsetSize = (Math.abs(getMaxHorizontalOffsetSize(i, - carouselHiddenMidpoint)) + maxOverscroll) - * mDesktopCarouselDetachProgress; - carouselHiddenOffsetSize = carouselHiddenOffsetSize * ( - i <= carouselHiddenMidpoint ? 1 : -1); - } else { - carouselHiddenOffsetSize = 0; - } - } float modalTranslation = i == modalMidpoint ? modalMidpointOffsetSize : showAsGrid ? gridOffsetSize : i < modalMidpoint ? modalLeftOffsetSize : modalRightOffsetSize; - boolean skipTranslationOffset = enableDesktopTaskAlphaAnimation() - && i == getRunningTaskIndex() - && child instanceof DesktopTaskView; - float totalTranslationX = (skipTranslationOffset ? 0f : translation) + modalTranslation - + carouselHiddenOffsetSize; - if (child instanceof TaskView taskView) { - taskView.getPrimaryTaskOffsetTranslationProperty().set(taskView, totalTranslationX); - } else if (child instanceof ClearAllButton) { - getPagedOrientationHandler().getPrimaryViewTranslate().set(child, - totalTranslationX); - } else if (child instanceof AddDesktopButton addDesktopButton) { - addDesktopButton.setOffsetTranslationX(totalTranslationX); - } + float totalTranslationX = translation + modalTranslation; + View child = getChildAt(i); + FloatProperty translationPropertyX = child instanceof TaskView + ? ((TaskView) child).getPrimaryTaskOffsetTranslationProperty() + : getPagedOrientationHandler().getPrimaryViewTranslate(); + translationPropertyX.set(child, totalTranslationX); if (mEnableDrawingLiveTile && i == getRunningTaskIndex()) { runActionOnRemoteHandles( remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator() @@ -5130,11 +4614,11 @@ public abstract class RecentsView< redrawLiveTile(); } - if (showAsGrid && enableGridOnlyOverview() && child instanceof TaskView taskView) { - float totalTranslationY = getVerticalOffsetSize(taskView, modalOffset); - FloatProperty translationPropertyY = - taskView.getSecondaryTaskOffsetTranslationProperty(); - translationPropertyY.set(taskView, totalTranslationY); + if (showAsGrid && enableGridOnlyOverview() && child instanceof TaskView) { + float totalTranslationY = getVerticalOffsetSize(i, modalOffset); + FloatProperty translationPropertyY = + ((TaskView) child).getSecondaryTaskOffsetTranslationProperty(); + translationPropertyY.set(child, totalTranslationY); } } updateCurveProperties(); @@ -5166,7 +4650,6 @@ public abstract class RecentsView< /** * Computes the distance to offset the given child such that it is completely offscreen when * translating away from the given midpoint. - * * @param offsetProgress From 0 to 1 where 0 means no offset and 1 means offset offscreen. */ private float getHorizontalOffsetSize(int childIndex, int midpointIndex, float offsetProgress) { @@ -5175,14 +4658,6 @@ public abstract class RecentsView< return 0; } - return getMaxHorizontalOffsetSize(childIndex, midpointIndex) * offsetProgress; - } - - /** - * Computes the distance to offset the given child such that it is completely offscreen when - * translating away from the given midpoint. - */ - private float getMaxHorizontalOffsetSize(int childIndex, int midpointIndex) { // First, get the position of the task relative to the midpoint. If there is no midpoint // then we just use the normal (centered) task position. RectF taskPosition = mTempRectF; @@ -5242,7 +4717,7 @@ public abstract class RecentsView< } distanceToOffscreen -= mLastComputedTaskEndPushOutDistance; } - return distanceToOffscreen; + return distanceToOffscreen * offsetProgress; } /** @@ -5250,18 +4725,19 @@ public abstract class RecentsView< * * @param offsetProgress From 0 to 1 where 0 means no offset and 1 means offset offscreen. */ - private float getVerticalOffsetSize(TaskView taskView, float offsetProgress) { + private float getVerticalOffsetSize(int childIndex, float offsetProgress) { if (offsetProgress == 0 || !(showAsGrid() && enableGridOnlyOverview()) - || getSelectedTaskView() == null) { + || mSelectedTask == null) { // Don't bother calculating everything below if we won't offset vertically. return 0; } // First, get the position of the task relative to the top row. - Rect taskPosition = getTaskBounds(taskView); + TaskView child = getTaskViewAt(childIndex); + Rect taskPosition = getTaskBounds(child); - boolean isSelectedTaskTopRow = mTopRowIdSet.contains(getSelectedTaskView().getTaskViewId()); - boolean isChildTopRow = mTopRowIdSet.contains(taskView.getTaskViewId()); + boolean isSelectedTaskTopRow = mTopRowIdSet.contains(mSelectedTask.getTaskViewId()); + boolean isChildTopRow = mTopRowIdSet.contains(child.getTaskViewId()); // Whether the task should be shifted to the top. boolean isTopShift = !isSelectedTaskTopRow && isChildTopRow; boolean isBottomShift = isSelectedTaskTopRow && !isChildTopRow; @@ -5278,9 +4754,9 @@ public abstract class RecentsView< protected void setTaskViewsResistanceTranslation(float translation) { mTaskViewsSecondaryTranslation = translation; - for (TaskView taskView : getTaskViews()) { - taskView.getTaskResistanceTranslationProperty().set(taskView, - translation / getScaleY()); + for (int i = 0; i < getTaskViewCount(); i++) { + TaskView task = requireTaskViewAt(i); + task.getTaskResistanceTranslationProperty().set(task, translation / getScaleY()); } runActionOnRemoteHandles( remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator() @@ -5288,21 +4764,23 @@ public abstract class RecentsView< } private void updateTaskViewsSnapshotRadius() { - for (TaskView taskView : getTaskViews()) { - taskView.updateFullscreenParams(); + for (int i = 0; i < getTaskViewCount(); i++) { + requireTaskViewAt(i).updateSnapshotRadius(); } } protected void setTaskViewsPrimarySplitTranslation(float translation) { mTaskViewsPrimarySplitTranslation = translation; - for (TaskView taskView : getTaskViews()) { - taskView.getPrimarySplitTranslationProperty().set(taskView, translation); + for (int i = 0; i < getTaskViewCount(); i++) { + TaskView task = requireTaskViewAt(i); + task.getPrimarySplitTranslationProperty().set(task, translation); } } protected void setTaskViewsSecondarySplitTranslation(float translation) { mTaskViewsSecondarySplitTranslation = translation; - for (TaskView taskView : getTaskViews()) { + for (int i = 0; i < getTaskViewCount(); i++) { + TaskView taskView = requireTaskViewAt(i); if (taskView == mSplitHiddenTaskView && !taskView.containsMultipleTasks()) { continue; } @@ -5314,8 +4792,8 @@ public abstract class RecentsView< * Resets the visuals when exit modal state. */ public void resetModalVisuals() { - if (getSelectedTaskView() != null) { - getSelectedTaskView().taskContainers.forEach( + if (mSelectedTask != null) { + mSelectedTask.taskContainers.forEach( taskContainer -> taskContainer.getOverlay().resetModalVisuals()); } } @@ -5324,23 +4802,22 @@ public abstract class RecentsView< * Primarily used by overview actions to initiate split from focused task, logs the source * of split invocation as such. */ - public void initiateSplitSelect(TaskContainer taskContainer) { + public void initiateSplitSelect(TaskView taskView) { int defaultSplitPosition = getPagedOrientationHandler() .getDefaultSplitPosition(mContainer.getDeviceProfile()); - initiateSplitSelect(taskContainer, defaultSplitPosition, LAUNCHER_OVERVIEW_ACTIONS_SPLIT); + initiateSplitSelect(taskView, defaultSplitPosition, LAUNCHER_OVERVIEW_ACTIONS_SPLIT); } /** TODO(b/266477929): Consolidate this call w/ the one below */ - public void initiateSplitSelect(TaskContainer taskContainer, - @StagePosition int stagePosition, + public void initiateSplitSelect(TaskView taskView, @StagePosition int stagePosition, StatsLogManager.EventEnum splitEvent) { - TaskView taskView = taskContainer.getTaskView(); mSplitHiddenTaskView = taskView; mSplitSelectStateController.setInitialTaskSelect(null /*intent*/, stagePosition, - taskContainer.getItemInfo(), splitEvent, taskContainer.getTask().key.id); + taskView.getFirstItemInfo(), splitEvent, taskView.getFirstTask().key.id); mSplitSelectStateController.setAnimateCurrentTaskDismissal( true /*animateCurrentTaskDismissal*/); mSplitHiddenTaskViewIndex = indexOfChild(taskView); + updateDesktopTaskVisibility(false /* visible */); } /** @@ -5354,67 +4831,20 @@ public abstract class RecentsView< mSplitHiddenTaskView = getTaskViewByTaskId(splitSelectSource.alreadyRunningTaskId); mSplitHiddenTaskViewIndex = indexOfChild(mSplitHiddenTaskView); mSplitSelectStateController - .setAnimateCurrentTaskDismissal(splitSelectSource.animateCurrentTaskDismissal - && mSplitHiddenTaskView != null - && !(mSplitHiddenTaskView instanceof DesktopTaskView)); + .setAnimateCurrentTaskDismissal(splitSelectSource.animateCurrentTaskDismissal); // Prevent dismissing whole task if we're only initiating from one of 2 tasks in split pair mSplitSelectStateController.setDismissingFromSplitPair(mSplitHiddenTaskView != null && mSplitHiddenTaskView instanceof GroupedTaskView); mSplitSelectStateController.setInitialTaskSelect(splitSelectSource.intent, - splitSelectSource.position.stagePosition, splitSelectSource.getItemInfo(), + splitSelectSource.position.stagePosition, splitSelectSource.itemInfo, splitSelectSource.splitEvent, splitSelectSource.alreadyRunningTaskId); + updateDesktopTaskVisibility(false /* visible */); } - /** - * Animate DesktopTaskView(s) to hide in split select - */ - public void handleDesktopTaskInSplitSelectState(PendingAnimation builder, - Interpolator deskTopFadeInterPolator) { - SplitAnimationTimings timings = AnimUtils.getDeviceOverviewToSplitTimings( - mContainer.getDeviceProfile().isTablet); - if (enableLargeDesktopWindowingTile()) { - getTaskViews().forEachWithIndexInParent((index, taskView) -> { - if (taskView instanceof DesktopTaskView) { - // Setting pivot to scale down from screen centre. - if (isTaskViewVisible(taskView)) { - float pivotX = 0f; - if (index < mCurrentPage) { - pivotX = mIsRtl ? taskView.getWidth() / 2f - mPageSpacing - - taskView.getWidth() - : taskView.getWidth() / 2f + mPageSpacing + taskView.getWidth(); - } else if (index == mCurrentPage) { - pivotX = taskView.getWidth() / 2f; - } else { - pivotX = mIsRtl ? taskView.getWidth() + mPageSpacing - + taskView.getWidth() - : taskView.getWidth() - mPageSpacing - taskView.getWidth(); - } - taskView.setPivotX(pivotX); - taskView.setPivotY(taskView.getHeight() / 2f); - builder.add(ObjectAnimator - .ofFloat(taskView, TaskView.DISMISS_SCALE, 0.95f), - clampToProgress(timings.getDesktopTaskScaleInterpolator(), 0f, - timings.getDesktopFadeSplitAnimationEndOffset())); - } - builder.addFloat(taskView, SPLIT_ALPHA, 1f, 0f, - clampToProgress(deskTopFadeInterPolator, 0f, - timings.getDesktopFadeSplitAnimationEndOffset())); - } - }); - } - } - - /** - * While exiting from split mode, show all existing DesktopTaskViews. - */ - public void resetDesktopTaskFromSplitSelectState() { - if (enableLargeDesktopWindowingTile()) { - for (TaskView taskView : getTaskViews()) { - if (taskView instanceof DesktopTaskView) { - taskView.setSplitAlpha(1f); - } - } + private void updateDesktopTaskVisibility(boolean visible) { + if (mDesktopTaskView != null) { + mDesktopTaskView.setVisibility(visible ? VISIBLE : GONE); } } @@ -5427,46 +4857,32 @@ public abstract class RecentsView< boolean isInitiatingTaskViewSplitPair = mSplitSelectStateController.isDismissingFromSplitPair(); if (isInitiatingSplitFromTaskView && isInitiatingTaskViewSplitPair - && mSplitHiddenTaskView instanceof GroupedTaskView groupedTaskView) { + && mSplitHiddenTaskView instanceof GroupedTaskView) { // Splitting from Overview for split pair task createInitialSplitSelectAnimation(builder); // Animate pair thumbnail into full thumbnail - boolean primaryTaskSelected = groupedTaskView.getLeftTopTaskContainer().getTask().key.id + boolean primaryTaskSelected = mSplitHiddenTaskView.getTaskIds()[0] == mSplitSelectStateController.getInitialTaskId(); - TaskContainer taskContainer = - primaryTaskSelected ? groupedTaskView.getRightBottomTaskContainer() - : groupedTaskView.getLeftTopTaskContainer(); + TaskContainer taskContainer = mSplitHiddenTaskView + .getTaskContainers().get(primaryTaskSelected ? 1 : 0); + TaskThumbnailViewDeprecated thumbnail = taskContainer.getThumbnailViewDeprecated(); mSplitSelectStateController.getSplitAnimationController() .addInitialSplitFromPair(taskContainer, builder, mContainer.getDeviceProfile(), - mSplitHiddenTaskView.getLayoutParams().width, - mSplitHiddenTaskView.getLayoutParams().height, + mSplitHiddenTaskView.getWidth(), mSplitHiddenTaskView.getHeight(), primaryTaskSelected); - builder.addOnFrameCallback(() -> { - if (!enableRefactorTaskThumbnail()) { - taskContainer.getThumbnailViewDeprecated().refreshSplashView(); - } - mSplitHiddenTaskView.updateFullscreenParams(); + builder.addOnFrameCallback(() ->{ + thumbnail.refreshSplashView(); + mSplitHiddenTaskView.updateSnapshotRadius(); }); } else if (isInitiatingSplitFromTaskView) { - if (Flags.enableHoverOfChildElementsInTaskview()) { - mSplitHiddenTaskView.setBorderEnabled(false); - } // Splitting from Overview for fullscreen task createTaskDismissAnimation(builder, mSplitHiddenTaskView, true, false, duration, - true /* dismissingForSplitSelection*/, false /* isExpressiveDismiss */); + true /* dismissingForSplitSelection*/); } else { // Splitting from Home - TaskView currentPageTaskView = getTaskViewAt(mCurrentPage); - // When current page is a Desktop task it needs special handling to - // display correct animation in split mode - if (currentPageTaskView instanceof DesktopTaskView) { - createTaskDismissAnimation(builder, null, true, false, duration, - true /* dismissingForSplitSelection*/, false /* isExpressiveDismiss */); - } else { - createInitialSplitSelectAnimation(builder); - } + createInitialSplitSelectAnimation(builder); } } @@ -5477,21 +4893,17 @@ public abstract class RecentsView< * @param containerTaskView If our second selected app is currently running in Recents, this is * the "container" TaskView from Recents. If we are starting a fresh * instance of the app from an Intent, this will be null. - * @param task The Task corresponding to our second selected app. If we are - * starting a fresh - * instance of the app from an Intent, this will be null. - * @param drawable The Drawable corresponding to our second selected app's icon. - * @param secondView The View representing the current space on the screen where the - * second app - * is (either the ThumbnailView or the tapped icon). - * @param intent If we are launching a fresh instance of the app, this is the Intent - * for it. If - * the second app is already running in Recents, this will be null. - * @param user If we are launching a fresh instance of the app, this is the - * UserHandle for it. - * If the second app is already running in Recents, this will be null. + * @param task The Task corresponding to our second selected app. If we are starting a fresh + * instance of the app from an Intent, this will be null. + * @param drawable The Drawable corresponding to our second selected app's icon. + * @param secondView The View representing the current space on the screen where the second app + * is (either the ThumbnailView or the tapped icon). + * @param intent If we are launching a fresh instance of the app, this is the Intent for it. If + * the second app is already running in Recents, this will be null. + * @param user If we are launching a fresh instance of the app, this is the UserHandle for it. + * If the second app is already running in Recents, this will be null. * @return true if waiting for confirmation of second app or if split animations are running, - * false otherwise + * false otherwise */ public boolean confirmSplitSelect(TaskView containerTaskView, Task task, Drawable drawable, View secondView, @Nullable Bitmap thumbnail, Intent intent, UserHandle user, @@ -5560,8 +4972,12 @@ public abstract class RecentsView< pendingAnimation.addEndListener(aBoolean -> { mSplitSelectStateController.launchSplitTasks( aBoolean1 -> { + if (FeatureFlags.enableSplitContextually()) { + mSplitSelectStateController.resetState(); + } else { + resetFromSplitSelectionState(); + } InteractionJankMonitorWrapper.end(Cuj.CUJ_SPLIT_SCREEN_ENTER); - mSplitSelectStateController.resetState(); }); }); @@ -5575,7 +4991,7 @@ public abstract class RecentsView< "Second tile selected"); // Fade out all other views underneath placeholders - ObjectAnimator tvFade = ObjectAnimator.ofFloat(this, RecentsView.CONTENT_ALPHA, 1, 0); + ObjectAnimator tvFade = ObjectAnimator.ofFloat(this, RecentsView.CONTENT_ALPHA,1, 0); pendingAnimation.add(tvFade, DECELERATE_2, SpringProperty.DEFAULT); pendingAnimation.buildAnim().start(); return true; @@ -5583,14 +4999,17 @@ public abstract class RecentsView< @SuppressLint("WrongCall") protected void resetFromSplitSelectionState() { - safeRemoveDragLayerView(mSplitSelectStateController.getFirstFloatingTaskView()); - safeRemoveDragLayerView(mSecondFloatingTaskView); - safeRemoveDragLayerView(mSplitSelectStateController.getSplitInstructionsView()); - safeRemoveDragLayerView(mSplitScrim); - mSecondFloatingTaskView = null; - mSplitSelectSource = null; - mSplitSelectStateController.getSplitAnimationController() - .removeSplitInstructionsView(mContainer); + if (mSplitSelectSource != null || mSplitHiddenTaskViewIndex != -1 || + FeatureFlags.enableSplitContextually()) { + safeRemoveDragLayerView(mSplitSelectStateController.getFirstFloatingTaskView()); + safeRemoveDragLayerView(mSecondFloatingTaskView); + safeRemoveDragLayerView(mSplitSelectStateController.getSplitInstructionsView()); + safeRemoveDragLayerView(mSplitScrim); + mSecondFloatingTaskView = null; + mSplitSelectSource = null; + mSplitSelectStateController.getSplitAnimationController() + .removeSplitInstructionsView(mContainer); + } if (mSecondSplitHiddenView != null) { mSecondSplitHiddenView.setThumbnailVisibility(VISIBLE, INVALID_TASK_ID); @@ -5602,6 +5021,11 @@ public abstract class RecentsView< setTaskViewsPrimarySplitTranslation(0); setTaskViewsSecondarySplitTranslation(0); + if (!FeatureFlags.enableSplitContextually()) { + // When flag is on, this method gets called from resetState() call below, let's avoid + // infinite recursion today + mSplitSelectStateController.resetState(); + } if (mSplitHiddenTaskViewIndex == -1) { return; } @@ -5620,15 +5044,9 @@ public abstract class RecentsView< mSplitHiddenTaskViewIndex = -1; if (mSplitHiddenTaskView != null) { mSplitHiddenTaskView.setThumbnailVisibility(VISIBLE, INVALID_TASK_ID); - // mSplitHiddenTaskView is set when split select animation starts. The TaskView is only - // removed when when the animation finishes. So in the case of overview being dismissed - // during the animation, we should not call clearAndRecycleTaskView() because it has - // not been removed yet. - if (mSplitHiddenTaskView.getParent() == null) { - clearAndRecycleTaskView(mSplitHiddenTaskView); - } mSplitHiddenTaskView = null; } + updateDesktopTaskVisibility(true /* visible */); } private void safeRemoveDragLayerView(@Nullable View viewToRemove) { @@ -5678,7 +5096,7 @@ public abstract class RecentsView< firstFloatingTaskView.update(mTempRectF, /*progress=*/1f); RecentsPagedOrientationHandler orientationHandler = getPagedOrientationHandler(); - Pair>, FloatProperty>> taskViewsFloat = + Pair, FloatProperty> taskViewsFloat = orientationHandler.getSplitSelectTaskOffset( TASK_PRIMARY_SPLIT_TRANSLATION, TASK_SECONDARY_SPLIT_TRANSLATION, mContainer.getDeviceProfile()); @@ -5700,8 +5118,15 @@ public abstract class RecentsView< mClearAllButtonDeadZoneRect.inset(-getPaddingRight() / 2, -verticalMargin); } - mUtils.updateTaskViewDeadZoneRect(mTaskViewDeadZoneRect, mTopRowDeadZoneRect, - mBottomRowDeadZoneRect); + // Get the deadzone rect between the task views + mTaskViewDeadZoneRect.setEmpty(); + int count = getTaskViewCount(); + if (count > 0) { + final View taskView = requireTaskViewAt(0); + requireTaskViewAt(count - 1).getHitRect(mTaskViewDeadZoneRect); + mTaskViewDeadZoneRect.union(taskView.getLeft(), taskView.getTop(), taskView.getRight(), + taskView.getBottom()); + } } private void updateEmptyStateUi(boolean sizeChanged) { @@ -5714,7 +5139,7 @@ public abstract class RecentsView< if (mShowEmptyMessage && hasValidSize && mEmptyTextLayout == null) { int availableWidth = mLastMeasureSize.x - mEmptyMessagePadding - mEmptyMessagePadding; mEmptyTextLayout = StaticLayout.Builder.obtain(mEmptyMessage, 0, mEmptyMessage.length(), - mEmptyMessagePaint, availableWidth) + mEmptyMessagePaint, availableWidth) .setAlignment(Layout.Alignment.ALIGN_CENTER) .build(); int totalHeight = mEmptyTextLayout.getHeight() @@ -5756,56 +5181,23 @@ public abstract class RecentsView< * to the right. */ @SuppressLint("Recycle") - public AnimatorSet createAdjacentPageAnimForTaskLaunch(TaskView taskView) { + public AnimatorSet createAdjacentPageAnimForTaskLaunch(TaskView tv) { AnimatorSet anim = new AnimatorSet(); - int taskIndex = indexOfChild(taskView); + int taskIndex = indexOfChild(tv); int centerTaskIndex = getCurrentPage(); float toScale = getMaxScaleForFullScreen(); boolean showAsGrid = showAsGrid(); - boolean zoomInTaskView = showAsGrid ? taskView.isLargeTile() : taskIndex == centerTaskIndex; - if (zoomInTaskView) { + boolean launchingCenterTask = showAsGrid + ? tv.isFocusedTask() && isTaskViewFullyVisible(tv) + : taskIndex == centerTaskIndex; + if (launchingCenterTask) { anim.play(ObjectAnimator.ofFloat(this, RECENTS_SCALE_PROPERTY, toScale)); anim.play(ObjectAnimator.ofFloat(this, FULLSCREEN_PROGRESS, 1)); - anim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(@NonNull Animator animation) { - taskView.getThumbnailBounds(mTempRect, /*relativeToDragLayer=*/true); - getTaskDimension(mContext, mContainer.getDeviceProfile(), mTempPointF); - Rect fullscreenBounds = new Rect(0, 0, (int) mTempPointF.x, - (int) mTempPointF.y); - Utilities.getPivotsForScalingRectToRect(mTempRect, fullscreenBounds, - mTempPointF); - setPivotX(mTempPointF.x); - setPivotY(mTempPointF.y); - - // If live tile is not launching, apply pivot to live tile as well and bring it - // above RecentsView to avoid wallpaper blur from being applied to it. - if (!taskView.isRunningTask()) { - runActionOnRemoteHandles( - remoteTargetHandle -> - remoteTargetHandle.getTaskViewSimulator() - .setPivotOverride(mTempPointF)); - mBlurUtils.setDrawLiveTileBelowRecents(false); - } - } - - @Override - public void onAnimationEnd(Animator animation) { - // If live tile is not launching, reset the pivot applied above. - if (!taskView.isRunningTask()) { - runActionOnRemoteHandles( - remoteTargetHandle -> { - remoteTargetHandle.getTaskViewSimulator().setPivotOverride( - null); - }); - } - } - }); } else if (!showAsGrid) { // We are launching an adjacent task, so parallax the center and other adjacent task. - float displacementX = taskView.getWidth() * (toScale - 1f); + float displacementX = tv.getWidth() * (toScale - 1f); float primaryTranslation = mIsRtl ? -displacementX : displacementX; anim.play(ObjectAnimator.ofFloat(getPageAt(centerTaskIndex), getPagedOrientationHandler().getPrimaryViewTranslate(), primaryTranslation)); @@ -5833,20 +5225,6 @@ public abstract class RecentsView< } } anim.play(ObjectAnimator.ofFloat(this, TASK_THUMBNAIL_SPLASH_ALPHA, 0, 1)); - if (taskView instanceof DesktopTaskView) { - anim.play(ObjectAnimator.ofArgb(mContainer.getScrimView(), VIEW_BACKGROUND_COLOR, - Color.TRANSPARENT)); - if (enableDesktopExplodedView()) { - anim.play(ObjectAnimator.ofFloat(this, DESK_EXPLODE_PROGRESS, 1f, 0f)); - } - } - DepthController depthController = getDepthController(); - if (depthController != null) { - float targetDepth = taskView instanceof DesktopTaskView ? 0 : BACKGROUND_APP.getDepth( - mContainer); - anim.play(ObjectAnimator.ofFloat(depthController.stateDepth, MULTI_PROPERTY_VALUE, - targetDepth)); - } return anim; } @@ -5854,28 +5232,31 @@ public abstract class RecentsView< * Returns the scale up required on the view, so that it coves the screen completely */ public float getMaxScaleForFullScreen() { - if (mLastComputedTaskSize.isEmpty()) { - getTaskSize(mLastComputedTaskSize); + if (enableGridOnlyOverview() && mContainer.getDeviceProfile().isTablet + && !mOverviewGridEnabled) { + if (mLastComputedCarouselTaskSize.isEmpty()) { + mSizeStrategy.calculateCarouselTaskSize(mContainer, mContainer.getDeviceProfile(), + mLastComputedCarouselTaskSize, getPagedOrientationHandler()); + } + mTempRect.set(mLastComputedCarouselTaskSize); + } else { + if (mLastComputedTaskSize.isEmpty()) { + getTaskSize(mLastComputedTaskSize); + } + mTempRect.set(mLastComputedTaskSize); } - mTempRect.set(mLastComputedTaskSize); return getPagedViewOrientedState().getFullScreenScaleAndPivot( mTempRect, mContainer.getDeviceProfile(), mTempPointF); } - /** - * Clears the existing PendingAnimation. - */ - public void clearPendingAnimation() { - mPendingAnimation = null; - } - public PendingAnimation createTaskLaunchAnimation( - TaskView taskView, long duration, Interpolator interpolator) { + TaskView tv, long duration, Interpolator interpolator) { if (FeatureFlags.IS_STUDIO_BUILD && mPendingAnimation != null) { throw new IllegalStateException("Another pending animation is still running"); } - if (!hasTaskViews()) { + int count = getTaskViewCount(); + if (count == 0) { return new PendingAnimation(duration); } @@ -5884,13 +5265,13 @@ public abstract class RecentsView< updateGridProperties(); updateScrollSynchronously(); - int targetSysUiFlags = taskView.getSysUiStatusNavFlags(); - final boolean[] passedOverviewThreshold = new boolean[]{false}; - AnimatorSet anim = createAdjacentPageAnimForTaskLaunch(taskView); - anim.play(new AnimatedFloat(v -> { + int targetSysUiFlags = tv.getFirstThumbnailViewDeprecated().getSysUiStatusNavFlags(); + final boolean[] passedOverviewThreshold = new boolean[] {false}; + ValueAnimator progressAnim = ValueAnimator.ofFloat(0, 1); + progressAnim.addUpdateListener(animator -> { // Once we pass a certain threshold, update the sysui flags to match the target // tasks' flags - if (v > UPDATE_SYSUI_FLAGS_THRESHOLD) { + if (animator.getAnimatedFraction() > UPDATE_SYSUI_FLAGS_THRESHOLD) { mContainer.getSystemUiController().updateUiState( UI_STATE_FULLSCREEN_TASK, targetSysUiFlags); } else { @@ -5898,7 +5279,8 @@ public abstract class RecentsView< } // Passing the threshold from taskview to fullscreen app will vibrate - final boolean passed = v >= SUCCESS_TRANSITION_PROGRESS; + final boolean passed = animator.getAnimatedFraction() >= + SUCCESS_TRANSITION_PROGRESS; if (passed != passedOverviewThreshold[0]) { passedOverviewThreshold[0] = passed; performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, @@ -5908,26 +5290,30 @@ public abstract class RecentsView< mRecentsAnimationController.setWillFinishToHome(!passed); } } - }).animateToValue(0f, 1f)); + }); + + AnimatorSet anim = createAdjacentPageAnimForTaskLaunch(tv); + + DepthController depthController = getDepthController(); + if (depthController != null) { + ObjectAnimator depthAnimator = ObjectAnimator.ofFloat(depthController.stateDepth, + MULTI_PROPERTY_VALUE, BACKGROUND_APP.getDepth(mContainer)); + anim.play(depthAnimator); + } + anim.play(ObjectAnimator.ofFloat(this, TASK_THUMBNAIL_SPLASH_ALPHA, 0f, 1f)); + + anim.play(progressAnim); anim.setInterpolator(interpolator); mPendingAnimation = new PendingAnimation(duration); mPendingAnimation.add(anim); - if (taskView.isRunningTask()) { - runActionOnRemoteHandles( - remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator() - .addOverviewToAppAnim(mPendingAnimation, interpolator)); - mPendingAnimation.addOnFrameCallback(this::redrawLiveTile); - } - mPendingAnimation.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animation) { - mBlurUtils.setDrawLiveTileBelowRecents(false); - } - }); + runActionOnRemoteHandles( + remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator() + .addOverviewToAppAnim(mPendingAnimation, interpolator)); + mPendingAnimation.addOnFrameCallback(this::redrawLiveTile); mPendingAnimation.addEndListener(isSuccess -> { if (isSuccess) { - if (taskView instanceof GroupedTaskView && hasAllValidTaskIds(taskView.getTaskIds()) + if (tv instanceof GroupedTaskView && hasAllValidTaskIds(tv.getTaskIds()) && mRemoteTargetHandles != null) { // TODO(b/194414938): make this part of the animations instead. TaskViewUtils.createSplitAuxiliarySurfacesAnimator( @@ -5937,13 +5323,13 @@ public abstract class RecentsView< dividerAnimator.end(); }); } - if (taskView.isRunningTask()) { + if (tv.isRunningTask()) { finishRecentsAnimation(false /* toRecents */, null); onTaskLaunchAnimationEnd(true /* success */); } else { - taskView.launchWithoutAnimation(this::onTaskLaunchAnimationEnd); + tv.launchTask(this::onTaskLaunchAnimationEnd); } - mContainer.getStatsLogManager().logger().withItemInfo(taskView.getItemInfo()) + mContainer.getStatsLogManager().logger().withItemInfo(tv.getFirstItemInfo()) .log(LAUNCHER_TASK_LAUNCH_SWIPE_DOWN); } else { onTaskLaunchAnimationEnd(false); @@ -5956,12 +5342,6 @@ public abstract class RecentsView< protected Unit onTaskLaunchAnimationEnd(boolean success) { if (success) { resetTaskVisuals(); - } else { - // If launch animation didn't complete i.e. user dragged live tile down and then - // back up and returned to Overview, then we need to ensure we reset the - // view to draw below recents so that it can't be interacted with. - mBlurUtils.setDrawLiveTileBelowRecents(true); - redrawLiveTile(); } return Unit.INSTANCE; } @@ -5972,9 +5352,6 @@ public abstract class RecentsView< updateCurrentTaskActionsVisibility(); loadVisibleTaskData(TaskView.FLAG_UPDATE_ALL); updateEnabledOverlays(); - if (enableRefactorTaskThumbnail()) { - mUtils.updateCentralTask(); - } } @Override @@ -5984,33 +5361,34 @@ public abstract class RecentsView< @Override public void addChildrenForAccessibility(ArrayList outChildren) { - outChildren.addAll(getAccessibilityChildren()); - } - - public List getAccessibilityChildren() { - return mUtils.getAccessibilityChildren(); + // Add children in reverse order + for (int i = getChildCount() - 1; i >= 0; --i) { + outChildren.add(getChildAt(i)); + } } @Override public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); final AccessibilityNodeInfo.CollectionInfo - collectionInfo = new AccessibilityNodeInfo.CollectionInfo( - 1, getAccessibilityChildren().size(), false); + collectionInfo = AccessibilityNodeInfo.CollectionInfo.obtain( + 1, getTaskViewCount(), false, + AccessibilityNodeInfo.CollectionInfo.SELECTION_MODE_NONE); info.setCollectionInfo(collectionInfo); } @Override public void onInitializeAccessibilityEvent(AccessibilityEvent event) { super.onInitializeAccessibilityEvent(event); - event.setScrollable(hasTaskViews()); + + final int taskViewCount = getTaskViewCount(); + event.setScrollable(taskViewCount > 0); if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) { - final List accessibilityChildren = getAccessibilityChildren(); final int[] visibleTasks = getVisibleChildrenRange(); - event.setFromIndex(accessibilityChildren.indexOf(getChildAt(visibleTasks[1]))); - event.setToIndex(accessibilityChildren.indexOf(getChildAt(visibleTasks[0]))); - event.setItemCount(accessibilityChildren.size()); + event.setFromIndex(taskViewCount - visibleTasks[1]); + event.setToIndex(taskViewCount - visibleTasks[0]); + event.setItemCount(taskViewCount); } } @@ -6029,10 +5407,6 @@ public abstract class RecentsView< mEnableDrawingLiveTile = enableDrawingLiveTile; } - public boolean getEnableDrawingLiveTile() { - return mEnableDrawingLiveTile; - } - public void redrawLiveTile() { runActionOnRemoteHandles(remoteTargetHandle -> { TransformParams params = remoteTargetHandle.getTransformParams(); @@ -6042,7 +5416,6 @@ public abstract class RecentsView< }); } - @Nullable public RemoteTargetHandle[] getRemoteTargetHandles() { return mRemoteTargetHandles; } @@ -6060,11 +5433,10 @@ public abstract class RecentsView< } RemoteTargetGluer gluer; - if (recentsAnimationTargets.hasDesktopTasks(mContext)) { + if (recentsAnimationTargets.hasDesktopTasks()) { gluer = new RemoteTargetGluer(getContext(), getSizeStrategy(), recentsAnimationTargets, true /* forDesktop */); - mRemoteTargetHandles = gluer.assignTargetsForDesktop( - recentsAnimationTargets, /* transitionInfo= */ null); + mRemoteTargetHandles = gluer.assignTargetsForDesktop(recentsAnimationTargets); } else { gluer = new RemoteTargetGluer(getContext(), getSizeStrategy(), recentsAnimationTargets, false); @@ -6077,14 +5449,6 @@ public abstract class RecentsView< // mSyncTransactionApplier doesn't get transferred over runActionOnRemoteHandles(remoteTargetHandle -> { final TransformParams params = remoteTargetHandle.getTransformParams(); - if (RecentsWindowFlags.Companion.getEnableOverviewInWindow()) { - params.setHomeBuilderProxy((builder, app, transformParams) -> { - mTmpMatrix.setScale( - 1f, 1f, app.localBounds.exactCenterX(), app.localBounds.exactCenterY()); - builder.setMatrix(mTmpMatrix).setAlpha(1f).setShow(); - }); - } - if (mSyncTransactionApplier != null) { params.setSyncTransactionApplier(mSyncTransactionApplier); params.getTargetSet().addReleaseCheck(mSyncTransactionApplier); @@ -6124,19 +5488,12 @@ public abstract class RecentsView< finishRecentsAnimation(toRecents, true /* shouldPip */, onFinishComplete); } - /** - * Finish recents animation. - */ - public void finishRecentsAnimation(boolean toRecents, boolean shouldPip, - @Nullable Runnable onFinishComplete) { - finishRecentsAnimation(toRecents, shouldPip, false, onFinishComplete); - } /** * NOTE: Whatever value gets passed through to the toRecents param may need to also be set on * {@link #mRecentsAnimationController#setWillFinishToHome}. */ public void finishRecentsAnimation(boolean toRecents, boolean shouldPip, - boolean allAppTargetsAreTranslucent, @Nullable Runnable onFinishComplete) { + @Nullable Runnable onFinishComplete) { Log.d(TAG, "finishRecentsAnimation - mRecentsAnimationController: " + mRecentsAnimationController); // TODO(b/197232424#comment#10) Move this back into onRecentsAnimationComplete(). Maybe? @@ -6150,9 +5507,8 @@ public abstract class RecentsView< } final boolean sendUserLeaveHint = toRecents && shouldPip; - if (sendUserLeaveHint && !com.android.wm.shell.Flags.enablePip2()) { + if (sendUserLeaveHint) { // Notify the SysUI to use fade-in animation when entering PiP from live tile. - // Note: PiP2 handles entering differently, so skip if enable_pip2=true. final SystemUiProxy systemUiProxy = SystemUiProxy.INSTANCE.get(getContext()); systemUiProxy.setPipAnimationTypeToAlpha(); systemUiProxy.setShelfHeight(true, mContainer.getDeviceProfile().hotseatBarSizePx); @@ -6169,7 +5525,7 @@ public abstract class RecentsView< tx, null /* overlay */); } } - mRecentsAnimationController.finish(toRecents, allAppTargetsAreTranslucent, () -> { + mRecentsAnimationController.finish(toRecents, () -> { if (onFinishComplete != null) { onFinishComplete.run(); } @@ -6196,9 +5552,6 @@ public abstract class RecentsView< mRecentsAnimationController = null; mSplitSelectStateController.setRecentsAnimationRunning(false); executeSideTaskLaunchCallback(); - if (enableOverviewBackgroundWallpaperBlur()) { - mBlurUtils.setDrawLiveTileBelowRecents(false); - } } public void setDisallowScrollToClearAll(boolean disallowScrollToClearAll) { @@ -6207,17 +5560,6 @@ public abstract class RecentsView< updateMinAndMaxScrollX(); } } - /** - * Update the value of [mDisallowScrollToAddDesk] - */ - public void setDisallowScrollToAddDesk(boolean disallowScrollToAddDesk) { - if (mDisallowScrollToAddDesk != disallowScrollToAddDesk) { - mDisallowScrollToAddDesk = disallowScrollToAddDesk; - updateMinAndMaxScrollX(); - } - } - - /** * Updates page scroll synchronously after measure and layout child views. @@ -6256,7 +5598,7 @@ public abstract class RecentsView< @Override protected int computeMinScroll() { - if (!hasTaskViews()) { + if (getTaskViewCount() <= 0) { return super.computeMinScroll(); } @@ -6265,7 +5607,7 @@ public abstract class RecentsView< @Override protected int computeMaxScroll() { - if (!hasTaskViews()) { + if (getTaskViewCount() <= 0) { return super.computeMaxScroll(); } @@ -6273,42 +5615,26 @@ public abstract class RecentsView< } private int getFirstViewIndex() { - final View firstView; - if (mShowAsGridLastOnLayout) { - // For grid Overview, it always start if a large tile (focused task or desktop task) if - // they exist, otherwise it start with the first task. - TaskView firstLargeTaskView = mUtils.getFirstLargeTaskView(); - if (firstLargeTaskView != null) { - firstView = firstLargeTaskView; - } else { - firstView = mUtils.getFirstSmallTaskView(); - } - } else { - firstView = mUtils.getFirstTaskViewInCarousel( - /*nonRunningTaskCarouselHidden=*/mDesktopCarouselDetachProgress > 0); - } - return indexOfChild(firstView); + TaskView focusedTaskView = mShowAsGridLastOnLayout ? getFocusedTaskView() : null; + return focusedTaskView != null ? indexOfChild(focusedTaskView) : 0; } private int getLastViewIndex() { - final View lastView; if (!mDisallowScrollToClearAll) { - // When ClearAllButton is present, it always end with ClearAllButton. - lastView = mClearAllButton; - } else if (mShowAsGridLastOnLayout) { - // When ClearAllButton is absent, for the grid Overview, it always end with a grid task - // if they exist, otherwise it ends with a large tile (focused task or desktop task). - TaskView lastGridTaskView = getLastGridTaskView(); - if (lastGridTaskView != null) { - lastView = lastGridTaskView; - } else { - lastView = mUtils.getLastLargeTaskView(); - } - } else { - lastView = mUtils.getLastTaskViewInCarousel( - /*nonRunningTaskCarouselHidden=*/mDesktopCarouselDetachProgress > 0); + return indexOfChild(mClearAllButton); } - return indexOfChild(lastView); + + if (!mShowAsGridLastOnLayout) { + return getTaskViewCount() - 1; + } + + TaskView lastGridTaskView = getLastGridTaskView(); + if (lastGridTaskView != null) { + return indexOfChild(lastGridTaskView); + } + + // Returns focus task if there are no grid tasks. + return indexOfChild(getFocusedTaskView()); } /** @@ -6334,55 +5660,42 @@ public abstract class RecentsView< mClearAllButton.setScrollOffsetPrimary(mIsRtl ? clearAllWidthDiff : -clearAllWidthDiff); } - int[] oldPageScrolls = Arrays.copyOf(outPageScrolls, outPageScrolls.length); + boolean pageScrollChanged = false; + int clearAllIndex = indexOfChild(mClearAllButton); int clearAllScroll = 0; int clearAllWidth = getPagedOrientationHandler().getPrimarySize(mClearAllButton); if (clearAllIndex != -1 && clearAllIndex < outPageScrolls.length) { float scrollDiff = mClearAllButton.getScrollAdjustment(showAsFullscreen, showAsGrid); - clearAllScroll = newPageScrolls[clearAllIndex] + Math.round(scrollDiff); - outPageScrolls[clearAllIndex] = clearAllScroll; + clearAllScroll = newPageScrolls[clearAllIndex] + (int) scrollDiff; + if (outPageScrolls[clearAllIndex] != clearAllScroll) { + pageScrollChanged = true; + outPageScrolls[clearAllIndex] = clearAllScroll; + } } + final int taskCount = getTaskViewCount(); int lastTaskScroll = getLastTaskScroll(clearAllScroll, clearAllWidth); - getTaskViews().forEachWithIndexInParent((index, taskView) -> { + for (int i = 0; i < taskCount; i++) { + TaskView taskView = requireTaskViewAt(i); float scrollDiff = taskView.getScrollAdjustment(showAsGrid); - int pageScroll = newPageScrolls[index] + Math.round(scrollDiff); + int pageScroll = newPageScrolls[i] + Math.round(scrollDiff); if ((mIsRtl && pageScroll < lastTaskScroll) || (!mIsRtl && pageScroll > lastTaskScroll)) { pageScroll = lastTaskScroll; } - outPageScrolls[index] = pageScroll; - if (DEBUG) { - Log.d(TAG, - "getPageScrolls - outPageScrolls[" + index + "]: " + outPageScrolls[index]); + if (outPageScrolls[i] != pageScroll) { + pageScrollChanged = true; + outPageScrolls[i] = pageScroll; } - }); - - int addDesktopButtonIndex = indexOfChild(mAddDesktopButton); - if (addDesktopButtonIndex >= 0 && addDesktopButtonIndex < outPageScrolls.length) { - int firstViewIndex = getFirstViewIndex(); - if (firstViewIndex >= 0 && firstViewIndex < outPageScrolls.length) { - // If we can scroll to [AddDesktopButton], make its page scroll equal to - // the first [TaskView]. Otherwise, make its page scroll out of range of - // [minScroll, maxScroll]. - if (!mDisallowScrollToAddDesk) { - outPageScrolls[addDesktopButtonIndex] = outPageScrolls[firstViewIndex]; - } else { - outPageScrolls[addDesktopButtonIndex] = - outPageScrolls[firstViewIndex] + (mIsRtl ? 1 : -1); - } - } - if (DEBUG) { - Log.d(TAG, "getPageScrolls - addDesktopButtonScroll: " - + outPageScrolls[addDesktopButtonIndex]); + Log.d(TAG, "getPageScrolls - outPageScrolls[" + i + "]: " + outPageScrolls[i]); } } if (DEBUG) { Log.d(TAG, "getPageScrolls - clearAllScroll: " + clearAllScroll); } - return !Arrays.equals(oldPageScrolls, outPageScrolls); + return pageScrollChanged; } @Override @@ -6399,12 +5712,12 @@ public abstract class RecentsView< } @Override - protected int getChildVisibleSize(int childIndex) { - final TaskView taskView = getTaskViewAt(childIndex); + protected int getChildVisibleSize(int index) { + final TaskView taskView = getTaskViewAt(index); if (taskView == null) { - return super.getChildVisibleSize(childIndex); + return super.getChildVisibleSize(index); } - return (int) (super.getChildVisibleSize(childIndex) * taskView.getSizeAdjustment( + return (int) (super.getChildVisibleSize(index) * taskView.getSizeAdjustment( showAsFullscreen())); } @@ -6412,11 +5725,6 @@ public abstract class RecentsView< return mClearAllButton; } - @Nullable - public AddDesktopButton getAddDeskButton() { - return mAddDesktopButton; - } - /** * @return How many pixels the running task is offset on the currently laid out dominant axis. */ @@ -6441,7 +5749,6 @@ public abstract class RecentsView< * Sets whether or not we should clamp the scroll offset. * This is used to avoid x-axis movement when swiping up transient taskbar. * Should only be set at the beginning and end of the gesture, otherwise a jump may occur. - * * @param clampScrollOffset When true, we clamp the scroll to 0 before the clamp threshold is * met. */ @@ -6468,17 +5775,17 @@ public abstract class RecentsView< * Returns how many pixels the page is offset on the currently laid out dominant axis. */ private int getUnclampedScrollOffset(int pageIndex) { - if (pageIndex == INVALID_PAGE) { + if (pageIndex == -1) { return 0; } // Don't dampen the scroll (due to overscroll) if the adjacent tasks are offscreen, so that // the page can move freely given there's no visual indication why it shouldn't. int overScrollShift = mAdjacentPageHorizontalOffset > 0 - ? (int) Utilities.mapRange( - mAdjacentPageHorizontalOffset, - getOverScrollShift(), - getUndampedOverScrollShift()) - : getOverScrollShift(); + ? (int) Utilities.mapRange( + mAdjacentPageHorizontalOffset, + getOverScrollShift(), + getUndampedOverScrollShift()) + : getOverScrollShift(); return getScrollForPage(pageIndex) - getPagedOrientationHandler().getPrimaryScroll(this) + overScrollShift + getOffsetFromScrollPosition(pageIndex); } @@ -6487,8 +5794,7 @@ public abstract class RecentsView< * Returns how many pixels the page is offset from its scroll position. */ private int getOffsetFromScrollPosition(int pageIndex) { - return getOffsetFromScrollPosition(pageIndex, mUtils.getTopRowIdArray(), - mUtils.getBottomRowIdArray()); + return getOffsetFromScrollPosition(pageIndex, getTopRowIdArray(), getBottomRowIdArray()); } private int getOffsetFromScrollPosition( @@ -6538,12 +5844,12 @@ public abstract class RecentsView< } /** - * @return true if the task in on the bottom of the grid + * @return true if the task in on the top of the grid */ public boolean isOnGridBottomRow(TaskView taskView) { return showAsGrid() && !mTopRowIdSet.contains(taskView.getTaskViewId()) - && !taskView.isLargeTile(); + && taskView.getTaskViewId() != mFocusedTaskViewId; } public Consumer getEventDispatcher(float navbarRotation) { @@ -6576,27 +5882,19 @@ public abstract class RecentsView< } private void updateEnabledOverlays() { - if (enableRefactorTaskThumbnail()) { - Set fullyVisibleTaskIds = new HashSet<>(); - for (TaskView taskView : getTaskViews()) { - if (isTaskViewFullyVisible(taskView)) { - fullyVisibleTaskIds.addAll(taskView.getTaskIdSet()); - } - } - mRecentsViewModel.updateTasksFullyVisible(fullyVisibleTaskIds); - } else { - TaskView focusedTaskView = getFocusedTaskView(); - for (TaskView taskView : getTaskViews()) { - if (taskView == focusedTaskView) { - continue; - } - taskView.setOverlayEnabled(mOverlayEnabled && isTaskViewFullyVisible(taskView)); - } - // Focus task overlay should be enabled and refreshed at last - if (focusedTaskView != null) { - focusedTaskView.setOverlayEnabled( - mOverlayEnabled && isTaskViewFullyVisible(focusedTaskView)); + TaskView focusedTaskView = getFocusedTaskView(); + int taskCount = getTaskViewCount(); + for (int i = 0; i < taskCount; i++) { + TaskView taskView = requireTaskViewAt(i); + if (taskView == focusedTaskView) { + continue; } + taskView.setOverlayEnabled(mOverlayEnabled && isTaskViewFullyVisible(taskView)); + } + // Focus task overlay should be enabled and refreshed at last + if (focusedTaskView != null) { + focusedTaskView.setOverlayEnabled( + mOverlayEnabled && isTaskViewFullyVisible(focusedTaskView)); } } @@ -6604,10 +5902,6 @@ public abstract class RecentsView< if (mOverlayEnabled != overlayEnabled) { mOverlayEnabled = overlayEnabled; updateEnabledOverlays(); - - if (enableRefactorTaskThumbnail()) { - mRecentsViewModel.setOverlayEnabled(overlayEnabled); - } } } @@ -6655,19 +5949,32 @@ public abstract class RecentsView< return; } + switchToScreenshotInternal(onFinishRunnable); + } + + private void switchToScreenshotInternal(Runnable onFinishRunnable) { TaskView taskView = getRunningTaskView(); if (taskView == null) { onFinishRunnable.run(); return; } - Map updatedThumbnails = mUtils.screenshotTasks(taskView); - if (enableRefactorTaskThumbnail()) { - mHelper.switchToScreenshot(taskView, updatedThumbnails, onFinishRunnable); - } else { - setRunningTaskViewShowScreenshot(true, updatedThumbnails); - ViewUtils.postFrameDrawn(taskView, onFinishRunnable); + setRunningTaskViewShowScreenshot(true); + for (TaskContainer container : taskView.getTaskContainers()) { + if (container == null) { + continue; + } + + ThumbnailData td = + mRecentsAnimationController.screenshotTask(container.getTask().key.id); + TaskThumbnailViewDeprecated thumbnailView = container.getThumbnailViewDeprecated(); + if (td != null) { + thumbnailView.setThumbnail(container.getTask(), td); + } else { + thumbnailView.refresh(); + } } + ViewUtils.postFrameDrawn(taskView, onFinishRunnable); } /** @@ -6681,12 +5988,9 @@ public abstract class RecentsView< Runnable onFinishRunnable) { final TaskView taskView = getRunningTaskView(); if (taskView != null) { - if (enableRefactorTaskThumbnail()) { - mHelper.switchToScreenshot(taskView, thumbnailDatas, onFinishRunnable); - } else { - taskView.setShouldShowScreenshot(true, thumbnailDatas); - ViewUtils.postFrameDrawn(taskView, onFinishRunnable); - } + taskView.setShouldShowScreenshot(true); + taskView.refreshThumbnails(thumbnailDatas); + ViewUtils.postFrameDrawn(taskView, onFinishRunnable); } else { onFinishRunnable.run(); } @@ -6699,14 +6003,8 @@ public abstract class RecentsView< private void setTaskModalness(float modalness) { mTaskModalness = modalness; updatePageOffsets(); - if (getSelectedTaskView() != null) { - if (enableGridOnlyOverview()) { - for (TaskView taskView : getTaskViews()) { - taskView.setModalness(modalness); - } - } else { - getSelectedTaskView().setModalness(modalness); - } + if (mSelectedTask != null) { + mSelectedTask.setModalness(modalness); } else if (getCurrentPageTaskView() != null) { getCurrentPageTaskView().setModalness(modalness); } @@ -6737,12 +6035,6 @@ public abstract class RecentsView< return mSizeStrategy; } - - /** - * Returns the container interface - */ - protected abstract BaseContainerInterface getContainerInterface(int displayId); - /** * Set all the task views to color tint scrim mode, dimming or tinting them all. Allows the * tasks to be dimmed while other elements in the recents view are left alone. @@ -6763,11 +6055,12 @@ public abstract class RecentsView< } /** Tint the RecentsView and TaskViews in to simulate a scrim. */ + // TODO(b/187528071): Replace this tinting with a scrim on top of RecentsView private void setColorTint(float tintAmount) { mColorTint = tintAmount; - for (TaskView taskView : getTaskViews()) { - taskView.setColorTint(mColorTint, mTintingColor); + for (int i = 0; i < getTaskViewCount(); i++) { + requireTaskViewAt(i).setColorTint(mColorTint, mTintingColor); } Drawable scrimBg = mContainer.getScrimView().getBackground(); @@ -6794,10 +6087,10 @@ public abstract class RecentsView< public boolean showAsGrid() { return mOverviewGridEnabled || (mCurrentGestureEndTarget != null && mSizeStrategy.stateFromGestureEndTarget(mCurrentGestureEndTarget) - .displayOverviewTasksAsGrid(mContainer.getDeviceProfile())); + .displayOverviewTasksAsGrid(mContainer.getDeviceProfile())); } - protected boolean showAsFullscreen() { + private boolean showAsFullscreen() { return mOverviewFullscreenEnabled && mCurrentGestureEndTarget != GestureState.GestureEndTarget.RECENTS; } @@ -6835,7 +6128,7 @@ public abstract class RecentsView< /** * @return Corner radius in pixel value for PiP window, which is updated via - * {@link #mIPipAnimationListener} + * {@link #mIPipAnimationListener} */ public int getPipCornerRadius() { return mPipCornerRadius; @@ -6843,7 +6136,7 @@ public abstract class RecentsView< /** * @return Shadow radius in pixel value for PiP window, which is updated via - * {@link #mIPipAnimationListener} + * {@link #mIPipAnimationListener} */ public int getPipShadowRadius() { return mPipShadowRadius; @@ -7007,68 +6300,6 @@ public abstract class RecentsView< } } - @Override - public void onCanCreateDesksChanged(boolean canCreateDesks) { - // TODO: b/389209338 - update the AddDesktopButton's visibility on this. - } - - @Override - public void onDeskAdded(int displayId, int deskId) { - // Ignore desk changes that don't belong to this display. - if (displayId != mContainer.getDisplay().getDisplayId()) { - return; - } - - if (mUtils.getDesktopTaskViewForDeskId(deskId) != null) { - Log.e(TAG, "A task view for this desk has already been added."); - return; - } - - TaskView currentTaskView = getTaskViewAt(mCurrentPage); - - // We assume that a newly added desk is always empty and gets added to the left of the - // `AddNewDesktopButton`. - DesktopTaskView desktopTaskView = - (DesktopTaskView) getTaskViewFromPool(TaskViewType.DESKTOP); - desktopTaskView.bind(new DesktopTask(deskId, displayId, new ArrayList<>()), - mOrientationState, mTaskOverlayFactory); - - Objects.requireNonNull(mAddDesktopButton); - final int insertionIndex = 1 + indexOfChild(mAddDesktopButton); - addView(desktopTaskView, insertionIndex); - - updateTaskSize(); - mUtils.updateChildTaskOrientations(); - updateScrollSynchronously(); - - // Set Current Page based on the stored TaskView. - if (currentTaskView != null) { - setCurrentPage(indexOfChild(currentTaskView)); - } - } - - @Override - public void onDeskRemoved(int displayId, int deskId) { - // Ignore desk changes that don't belong to this display. - if (displayId != mContainer.getDisplay().getDisplayId()) { - return; - } - - // We need to distinguish between desk removals that are triggered from outside of overview - // vs. the ones that were initiated from overview by dismissing the corresponding desktop - // task view. - var taskView = mUtils.getDesktopTaskViewForDeskId(deskId); - if (taskView != null) { - dismissTaskView(taskView, true, true); - } - } - - @Override - public void onActiveDeskChanged(int displayId, int newActiveDesk, int oldActiveDesk) { - // TODO: b/400870600 - We may need to add code here to special case when an empty desk gets - // activated, since `RemoteDesktopLaunchTransitionRunner` doesn't always get triggered. - } - /** Get the color used for foreground scrimming the RecentsView for sharing. */ public static int getForegroundScrimDimColor(Context context) { return context.getColor(R.color.overview_foreground_scrim_color); @@ -7119,31 +6350,11 @@ public abstract class RecentsView< if (mDesktopRecentsTransitionController == null) { return; } - - mDesktopRecentsTransitionController.moveToDesktop(taskContainer, transitionSource, - successCallback); - } - - /** - * Move the provided task into external display and invoke {@code successCallback} if succeeded. - */ - public void moveTaskToExternalDisplay(TaskContainer taskContainer, Runnable successCallback) { - if (!DesktopModeStatus.canEnterDesktopMode(mContext)) { - return; - } - switchToScreenshot(() -> finishRecentsAnimation(/* toRecents= */true, /* shouldPip= */false, - () -> moveTaskToDesktopInternal(taskContainer, successCallback))); - } - - private void moveTaskToDesktopInternal(TaskContainer taskContainer, Runnable successCallback) { - if (mDesktopRecentsTransitionController == null) { - return; - } - mDesktopRecentsTransitionController.moveToExternalDisplay(taskContainer.getTask().key.id); + mDesktopRecentsTransitionController.moveToDesktop(taskContainer.getTask().key.id, + transitionSource); successCallback.run(); } - // Logs when the orientation of Overview changes. We log both real and fake orientation changes. private void logOrientationChanged() { // Only log when Overview is showing. @@ -7162,47 +6373,7 @@ public abstract class RecentsView< } } - private int getFontWeight() { - int fontWeightAdjustment = getResources().getConfiguration().fontWeightAdjustment; - if (fontWeightAdjustment != Configuration.FONT_WEIGHT_ADJUSTMENT_UNDEFINED) { - return Typeface.Builder.NORMAL_WEIGHT + fontWeightAdjustment; - } - return Typeface.Builder.NORMAL_WEIGHT; - } - - /** - * Creates the spring animations which run as a task settles back into its place in overview. - * - *

When a task dismiss is cancelled, the task will return to its original position via a - * spring animation. As it passes the threshold of its settling state, its neighbors will - * spring in response to the perceived impact of the settling task. - */ - public SpringAnimation createTaskDismissSettlingSpringAnimation(TaskView draggedTaskView, - float velocity, boolean isDismissing, int dismissLength, - Function0 onEndRunnable) { - return mDismissUtils.createTaskDismissSettlingSpringAnimation(draggedTaskView, velocity, - isDismissing, dismissLength, onEndRunnable); - } - - /** - * Animates RecentsView's scale to the provided value, using spring animations. - */ - public SpringAnimation animateRecentsScale(float scale) { - return mDismissUtils.animateRecentsScale(scale); - } - public interface TaskLaunchListener { void onTaskLaunched(); } - - /** - * Sets whether the remote animation targets should draw below the recents view. - * - * @param drawBelowRecents whether the surface should draw below Recents. - * @param remoteTargetHandles collection of remoteTargetHandles in Recents. - */ - public void setDrawBelowRecents(boolean drawBelowRecents, - RemoteTargetHandle[] remoteTargetHandles) { - mBlurUtils.setDrawBelowRecents(drawBelowRecents, remoteTargetHandles); - } } diff --git a/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java b/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java index e61d402698..060c71e446 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java +++ b/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java @@ -16,6 +16,7 @@ package com.android.quickstep.views; +import android.app.Activity; import android.content.Context; import android.content.ContextWrapper; import android.content.LocusId; @@ -25,11 +26,9 @@ import android.view.MotionEvent; import android.view.View; import android.view.Window; -import androidx.annotation.Nullable; - import com.android.launcher3.BaseActivity; import com.android.launcher3.logger.LauncherAtom; -import com.android.launcher3.taskbar.TaskbarUIController; +import com.android.launcher3.util.SystemUiController; import com.android.launcher3.views.ActivityContext; import com.android.launcher3.views.ScrimView; @@ -42,7 +41,7 @@ public interface RecentsViewContainer extends ActivityContext { * Returns an instance of an implementation of RecentsViewContainer * @param context will find instance of recentsViewContainer from given context. */ - static T containerFromContext(Context context) { + static T containerFromContext(Context context) { if (context instanceof RecentsViewContainer) { return (T) context; } else if (context instanceof ContextWrapper) { @@ -52,6 +51,11 @@ public interface RecentsViewContainer extends ActivityContext { } } + /** + * Returns {@link SystemUiController} to manage various window flags to control system UI. + */ + SystemUiController getSystemUiController(); + /** * Returns {@link ScrimView} */ @@ -62,6 +66,19 @@ public interface RecentsViewContainer extends ActivityContext { */ T getOverviewPanel(); + /** + * Returns the RootView + */ + View getRootView(); + + /** + * Dispatches a generic motion event to the view hierarchy. + * Returns the current RecentsViewContainer as context + */ + default Context asContext() { + return (Context) this; + } + /** * @see Window.Callback#dispatchGenericMotionEvent(MotionEvent) */ @@ -75,7 +92,7 @@ public interface RecentsViewContainer extends ActivityContext { /** * Returns overview actions view as a view */ - OverviewActionsView getActionsView(); + View getActionsView(); /** * @see BaseActivity#addForceInvisibleFlag(int) @@ -122,6 +139,12 @@ public interface RecentsViewContainer extends ActivityContext { */ void runOnBindToTouchInteractionService(Runnable r); + /** + * @see Activity#getWindow() + * @return Window + */ + Window getWindow(); + /** * @see * BaseActivity#addMultiWindowModeChangedListener(BaseActivity.MultiWindowModeChangedListener) @@ -150,25 +173,6 @@ public interface RecentsViewContainer extends ActivityContext { */ boolean isRecentsViewVisible(); - /** - * Begins transition to start home through container - */ - default void startHome(){ - // no op - } - - /** - * Checks container to see if we can start home transition safely - */ - boolean canStartHomeSafely(); - - - /** - * Enter staged split directly from the current running app. - * @param leftOrTop if the staged split will be positioned left or top. - */ - default void enterStageSplitFromRunningApp(boolean leftOrTop){} - /** * Overwrites any logged item in Launcher that doesn't have a container with the * {@link com.android.launcher3.touch.PagedOrientationHandler} in use for Overview. @@ -194,8 +198,4 @@ public interface RecentsViewContainer extends ActivityContext { .setOrientationHandler(orientationForLogging)) .build()); } - - void setTaskbarUIController(@Nullable TaskbarUIController taskbarUIController); - - @Nullable TaskbarUIController getTaskbarUIController(); } diff --git a/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java b/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java index fb23039c62..e86b5a0fd9 100644 --- a/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java +++ b/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java @@ -16,10 +16,12 @@ package com.android.quickstep.views; +import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTION_EXIT_CANCEL_BUTTON; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; import android.content.Context; import android.graphics.Rect; import android.util.AttributeSet; @@ -38,11 +40,11 @@ import com.android.app.animation.Interpolators; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.anim.PendingAnimation; +import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.statemanager.BaseState; import com.android.launcher3.statemanager.StateManager; -import com.android.quickstep.util.AnimUtils; +import com.android.launcher3.states.StateAnimationConfig; import com.android.quickstep.util.SplitSelectStateController; -import com.android.wm.shell.shared.TypefaceUtils; -import com.android.wm.shell.shared.TypefaceUtils.FontFamily; /** * A rounded rectangular component containing a single TextView. @@ -54,6 +56,7 @@ import com.android.wm.shell.shared.TypefaceUtils.FontFamily; public class SplitInstructionsView extends LinearLayout { private static final int BOUNCE_DURATION = 250; private static final float BOUNCE_HEIGHT = 20; + private static final int DURATION_DEFAULT_SPLIT_DISMISS = 350; private final RecentsViewContainer mContainer; public boolean mIsCurrentlyAnimating = false; @@ -123,35 +126,36 @@ public class SplitInstructionsView extends LinearLayout { } private void init() { - TextView cancelTextView = findViewById(R.id.split_instructions_text_cancel); + TextView cancelTextView = findViewById(R.id.split_instructions_text); TextView instructionTextView = findViewById(R.id.split_instructions_text); - cancelTextView.setVisibility(VISIBLE); - cancelTextView.setOnClickListener((v) -> exitSplitSelection()); - instructionTextView.setText(R.string.toast_contextual_split_select_app); - TypefaceUtils.setTypeface(instructionTextView, FontFamily.GSF_BODY_MEDIUM); + if (FeatureFlags.enableSplitContextually()) { + cancelTextView.setVisibility(VISIBLE); + cancelTextView.setOnClickListener((v) -> exitSplitSelection()); + instructionTextView.setText(R.string.toast_contextual_split_select_app); - // After layout, expand touch target of cancel button to meet minimum a11y measurements. - post(() -> { - int minTouchSize = getResources() - .getDimensionPixelSize(R.dimen.settingslib_preferred_minimum_touch_target); - Rect r = new Rect(); - cancelTextView.getHitRect(r); + // After layout, expand touch target of cancel button to meet minimum a11y measurements. + post(() -> { + int minTouchSize = getResources() + .getDimensionPixelSize(R.dimen.settingslib_preferred_minimum_touch_target); + Rect r = new Rect(); + cancelTextView.getHitRect(r); - if (r.width() < minTouchSize) { - // add 1 to ensure ceiling on int division - int expandAmount = (minTouchSize + 1 - r.width()) / 2; - r.left -= expandAmount; - r.right += expandAmount; - } - if (r.height() < minTouchSize) { - int expandAmount = (minTouchSize + 1 - r.height()) / 2; - r.top -= expandAmount; - r.bottom += expandAmount; - } + if (r.width() < minTouchSize) { + // add 1 to ensure ceiling on int division + int expandAmount = (minTouchSize + 1 - r.width()) / 2; + r.left -= expandAmount; + r.right += expandAmount; + } + if (r.height() < minTouchSize) { + int expandAmount = (minTouchSize + 1 - r.height()) / 2; + r.top -= expandAmount; + r.bottom += expandAmount; + } - setTouchDelegate(new TouchDelegate(r, cancelTextView)); - }); + setTouchDelegate(new TouchDelegate(r, cancelTextView)); + }); + } // Set accessibility title, will be announced by a11y tools. if (Utilities.ATLEAST_P) { @@ -162,11 +166,25 @@ public class SplitInstructionsView extends LinearLayout { private void exitSplitSelection() { RecentsView recentsView = mContainer.getOverviewPanel(); SplitSelectStateController splitSelectController = recentsView.getSplitSelectController(); - StateManager stateManager = recentsView.getStateManager(); - AnimUtils.goToNormalStateWithSplitDismissal(stateManager, mContainer, - LAUNCHER_SPLIT_SELECTION_EXIT_CANCEL_BUTTON, - splitSelectController.getSplitAnimationController()); + StateManager stateManager = recentsView.getStateManager(); + BaseState startState = stateManager.getState(); + long duration = startState.getTransitionDuration(mContainer.asContext(), false); + if (duration == 0) { + // Case where we're in contextual on workspace (NORMAL), which by default has 0 + // transition duration + duration = DURATION_DEFAULT_SPLIT_DISMISS; + } + StateAnimationConfig config = new StateAnimationConfig(); + config.duration = duration; + AnimatorSet stateAnim = stateManager.createAtomicAnimation( + startState, NORMAL, config); + AnimatorSet dismissAnim = splitSelectController.getSplitAnimationController() + .createPlaceholderDismissAnim(mContainer, + LAUNCHER_SPLIT_SELECTION_EXIT_CANCEL_BUTTON, duration); + stateAnim.play(dismissAnim); + stateManager.setCurrentAnimation(stateAnim, NORMAL); + stateAnim.start(); } void ensureProperRotation() { diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt b/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt index 833683b59e..346eb2ff65 100644 --- a/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt +++ b/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt @@ -30,6 +30,7 @@ import android.view.ViewGroup import android.widget.FrameLayout import android.widget.LinearLayout import app.lawnchair.theme.color.tokens.ColorTokens +import com.android.launcher3.BaseDraggingActivity import com.android.launcher3.DeviceProfile import com.android.launcher3.InsettableFrameLayout import com.android.launcher3.R @@ -38,16 +39,13 @@ import com.android.launcher3.popup.RoundedArrowDrawable import com.android.launcher3.popup.SystemShortcut import com.android.launcher3.util.Themes import com.android.quickstep.TaskOverlayFactory +import com.android.quickstep.views.TaskView.TaskContainer class TaskMenuViewWithArrow : ArrowPopup where T : RecentsViewContainer, T : Context { companion object { const val TAG = "TaskMenuViewWithArrow" - fun showForTask( - taskContainer: TaskContainer, - alignedOptionIndex: Int = 0, - onClosedCallback: Runnable? = null - ): Boolean { + fun showForTask(taskContainer: TaskContainer, alignedOptionIndex: Int = 0): Boolean { val container: RecentsViewContainer = RecentsViewContainer.containerFromContext(taskContainer.taskView.context) val taskMenuViewWithArrow = @@ -57,18 +55,12 @@ class TaskMenuViewWithArrow : ArrowPopup where T : RecentsViewContainer, T false ) as TaskMenuViewWithArrow<*> - return taskMenuViewWithArrow.populateAndShowForTask( - taskContainer, - alignedOptionIndex, - onClosedCallback - ) + return taskMenuViewWithArrow.populateAndShowForTask(taskContainer, alignedOptionIndex) } } constructor(context: Context) : super(context) - constructor(context: Context, attrs: AttributeSet) : super(context, attrs) - constructor( context: Context, attrs: AttributeSet, @@ -90,7 +82,6 @@ class TaskMenuViewWithArrow : ArrowPopup where T : RecentsViewContainer, T private var alignedOptionIndex: Int = 0 private val extraSpaceForRowAlignment: Int get() = optionMeasuredHeight * alignedOptionIndex - private val menuPaddingEnd = context.resources.getDimensionPixelSize(R.dimen.task_card_margin) private lateinit var taskView: TaskView @@ -100,14 +91,13 @@ class TaskMenuViewWithArrow : ArrowPopup where T : RecentsViewContainer, T private var optionMeasuredHeight = 0 private val arrowHorizontalPadding: Int get() = - if (taskView.isLargeTile) + if (taskView.isFocusedTask) resources.getDimensionPixelSize(R.dimen.task_menu_horizontal_padding) else 0 private var iconView: IconView? = null private var scrim: View? = null private val scrimAlpha = 0.8f - private var onClosedCallback: Runnable? = null override fun isOfType(type: Int): Boolean = type and TYPE_TASK_MENU != 0 @@ -151,8 +141,7 @@ class TaskMenuViewWithArrow : ArrowPopup where T : RecentsViewContainer, T private fun populateAndShowForTask( taskContainer: TaskContainer, - alignedOptionIndex: Int, - onClosedCallback: Runnable? + alignedOptionIndex: Int ): Boolean { if (isAttachedToWindow) { return false @@ -161,7 +150,6 @@ class TaskMenuViewWithArrow : ArrowPopup where T : RecentsViewContainer, T taskView = taskContainer.taskView this.taskContainer = taskContainer this.alignedOptionIndex = alignedOptionIndex - this.onClosedCallback = onClosedCallback if (!populateMenu()) return false addScrim() show() @@ -264,7 +252,6 @@ class TaskMenuViewWithArrow : ArrowPopup where T : RecentsViewContainer, T super.closeComplete() popupContainer.removeView(scrim) popupContainer.removeView(iconView) - onClosedCallback?.run() } /** diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java index 74d76e6432..4283d0e4cf 100644 --- a/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java +++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java @@ -28,13 +28,16 @@ import android.graphics.BitmapShader; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorFilter; +import android.graphics.Insets; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; +import android.graphics.RectF; import android.graphics.Shader; import android.graphics.drawable.Drawable; +import android.os.Build; import android.util.AttributeSet; import android.util.FloatProperty; import android.util.Property; @@ -42,16 +45,18 @@ import android.view.View; import android.widget.ImageView; import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; import androidx.core.graphics.ColorUtils; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Utilities; +import com.android.launcher3.util.MainThreadInitializedObject; import com.android.launcher3.util.SystemUiController; import com.android.launcher3.util.SystemUiController.SystemUiControllerFlags; import com.android.launcher3.util.ViewPool; -import com.android.quickstep.FullscreenDrawParams; import com.android.quickstep.TaskOverlayFactory.TaskOverlay; import com.android.quickstep.orientation.RecentsPagedOrientationHandler; +import com.android.quickstep.views.TaskView.FullscreenDrawParams; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.recents.utilities.PreviewPositionHelper; @@ -65,6 +70,8 @@ import java.util.Objects; */ @Deprecated public class TaskThumbnailViewDeprecated extends View implements ViewPool.Reusable { + private static final MainThreadInitializedObject TEMP_PARAMS = + new MainThreadInitializedObject<>(FullscreenDrawParams::new); public static final Property DIM_ALPHA = new FloatProperty("dimAlpha") { @@ -92,6 +99,36 @@ public class TaskThumbnailViewDeprecated extends View implements ViewPool.Reusab } }; + /** Use to animate thumbnail translationX while first app in split selection is initiated */ + public static final Property SPLIT_SELECT_TRANSLATE_X = + new FloatProperty("splitSelectTranslateX") { + @Override + public void setValue(TaskThumbnailViewDeprecated thumbnail, + float splitSelectTranslateX) { + thumbnail.applySplitSelectTranslateX(splitSelectTranslateX); + } + + @Override + public Float get(TaskThumbnailViewDeprecated thumbnailView) { + return thumbnailView.mSplitSelectTranslateX; + } + }; + + /** Use to animate thumbnail translationY while first app in split selection is initiated */ + public static final Property SPLIT_SELECT_TRANSLATE_Y = + new FloatProperty("splitSelectTranslateY") { + @Override + public void setValue(TaskThumbnailViewDeprecated thumbnail, + float splitSelectTranslateY) { + thumbnail.applySplitSelectTranslateY(splitSelectTranslateY); + } + + @Override + public Float get(TaskThumbnailViewDeprecated thumbnailView) { + return thumbnailView.mSplitSelectTranslateY; + } + }; + private final RecentsViewContainer mContainer; private TaskOverlay mOverlay; private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); @@ -104,10 +141,9 @@ public class TaskThumbnailViewDeprecated extends View implements ViewPool.Reusab // Contains the portion of the thumbnail that is clipped when fullscreen progress = 0. private final Rect mPreviewRect = new Rect(); private final PreviewPositionHelper mPreviewPositionHelper = new PreviewPositionHelper(); - private FullscreenDrawParams mFullscreenParams; + private TaskView.FullscreenDrawParams mFullscreenParams; private ImageView mSplashView; private Drawable mSplashViewDrawable; - private TaskView mTaskView; @Nullable private Task mTask; @@ -124,6 +160,8 @@ public class TaskThumbnailViewDeprecated extends View implements ViewPool.Reusab private boolean mOverlayEnabled; /** Used as a placeholder when the original thumbnail animates out to. */ private boolean mShowSplashForSplitSelection; + private float mSplitSelectTranslateX; + private float mSplitSelectTranslateY; public TaskThumbnailViewDeprecated(Context context) { this(context, null); @@ -142,7 +180,8 @@ public class TaskThumbnailViewDeprecated extends View implements ViewPool.Reusab mClearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); mContainer = RecentsViewContainer.containerFromContext(context); // Initialize with placeholder value. It is overridden later by TaskView - mFullscreenParams = new FullscreenDrawParams(context, __ -> 0f, __ -> 0f); + mFullscreenParams = TEMP_PARAMS.get(context); + mDimColor = RecentsView.getForegroundScrimDimColor(context); mDimmingPaintAfterClearing.setColor(mDimColor); } @@ -150,11 +189,10 @@ public class TaskThumbnailViewDeprecated extends View implements ViewPool.Reusab /** * Updates the thumbnail to draw the provided task */ - public void bind(Task task, TaskOverlay overlay, TaskView taskView) { + public void bind(Task task, TaskOverlay overlay) { mOverlay = overlay; mOverlay.reset(); mTask = task; - mTaskView = taskView; int color = task == null ? Color.BLACK : task.colorBackground | 0xFF000000; mPaint.setColor(color); mBackgroundPaint.setColor(color); @@ -162,6 +200,17 @@ public class TaskThumbnailViewDeprecated extends View implements ViewPool.Reusab updateSplashView(mTask.icon); } + /** + * Sets TaskOverlay without binding a task. + * + * @deprecated Should only be used when using new + * {@link com.android.quickstep.task.thumbnail.TaskThumbnailView}. + */ + @Deprecated + public void setTaskOverlay(TaskOverlay overlay) { + mOverlay = overlay; + } + /** * Updates the thumbnail. * @@ -249,6 +298,40 @@ public class TaskThumbnailViewDeprecated extends View implements ViewPool.Reusab return mDimAlpha; } + /** + * Get the scaled insets that are being used to draw the task view. This is a subsection of + * the full snapshot. + * + * @return the insets in snapshot bitmap coordinates. + */ + @RequiresApi(api = Build.VERSION_CODES.Q) + public Insets getScaledInsets() { + if (mThumbnailData == null) { + return Insets.NONE; + } + + RectF bitmapRect = new RectF( + 0, + 0, + mThumbnailData.getThumbnail().getWidth(), + mThumbnailData.getThumbnail().getHeight()); + RectF viewRect = new RectF(0, 0, getMeasuredWidth(), getMeasuredHeight()); + + // The position helper matrix tells us how to transform the bitmap to fit the view, the + // inverse tells us where the view would be in the bitmaps coordinates. The insets are the + // difference between the bitmap bounds and the projected view bounds. + Matrix boundsToBitmapSpace = new Matrix(); + mPreviewPositionHelper.getMatrix().invert(boundsToBitmapSpace); + RectF boundsInBitmapSpace = new RectF(); + boundsToBitmapSpace.mapRect(boundsInBitmapSpace, viewRect); + + DeviceProfile dp = mContainer.getDeviceProfile(); + int bottomInset = dp.isTablet + ? Math.round(bitmapRect.bottom - boundsInBitmapSpace.bottom) : 0; + return Insets.of(0, 0, 0, bottomInset); + } + + @SystemUiControllerFlags public int getSysUiStatusNavFlags() { if (mThumbnailData != null) { @@ -275,7 +358,7 @@ public class TaskThumbnailViewDeprecated extends View implements ViewPool.Reusab canvas.save(); // Draw the insets if we're being drawn fullscreen (we do this for quick switch). drawOnCanvas(canvas, 0, 0, getMeasuredWidth(), getMeasuredHeight(), - mFullscreenParams.getCurrentCornerRadius()); + mFullscreenParams.getCurrentDrawnCornerRadius()); canvas.restore(); } @@ -283,15 +366,15 @@ public class TaskThumbnailViewDeprecated extends View implements ViewPool.Reusab return mPreviewPositionHelper; } - public void setFullscreenParams(FullscreenDrawParams fullscreenParams) { + public void setFullscreenParams(TaskView.FullscreenDrawParams fullscreenParams) { mFullscreenParams = fullscreenParams; invalidate(); } public void drawOnCanvas(Canvas canvas, float x, float y, float width, float height, float cornerRadius) { - if (mTask != null && mTaskView.isRunningTask() - && !mTaskView.getShouldShowScreenshot()) { + if (mTask != null && getTaskView().isRunningTask() + && !getTaskView().getShouldShowScreenshot()) { canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mClearPaint); canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mDimmingPaintAfterClearing); @@ -332,6 +415,35 @@ public class TaskThumbnailViewDeprecated extends View implements ViewPool.Reusab } } + /** See {@link #SPLIT_SELECT_TRANSLATE_X} */ + protected void applySplitSelectTranslateX(float splitSelectTranslateX) { + mSplitSelectTranslateX = splitSelectTranslateX; + applyTranslateX(); + } + + /** See {@link #SPLIT_SELECT_TRANSLATE_Y} */ + protected void applySplitSelectTranslateY(float splitSelectTranslateY) { + mSplitSelectTranslateY = splitSelectTranslateY; + applyTranslateY(); + } + + private void applyTranslateX() { + setTranslationX(mSplitSelectTranslateX); + } + + private void applyTranslateY() { + setTranslationY(mSplitSelectTranslateY); + } + + protected void resetViewTransforms() { + mSplitSelectTranslateX = 0; + mSplitSelectTranslateY = 0; + } + + public TaskView getTaskView() { + return (TaskView) getParent(); + } + public void setOverlayEnabled(boolean overlayEnabled) { if (mOverlayEnabled != overlayEnabled) { mOverlayEnabled = overlayEnabled; @@ -384,9 +496,9 @@ public class TaskThumbnailViewDeprecated extends View implements ViewPool.Reusab float viewCenterY = viewHeight / 2f; float centeredDrawableLeft = (viewWidth - drawableWidth) / 2f; float centeredDrawableTop = (viewHeight - drawableHeight) / 2f; - float nonGridScale = mTaskView == null ? 1 : 1 / mTaskView.getNonGridScale(); - float recentsMaxScale = mTaskView == null || mTaskView.getRecentsView() == null - ? 1 : 1 / mTaskView.getRecentsView().getMaxScaleForFullScreen(); + float nonGridScale = getTaskView() == null ? 1 : 1 / getTaskView().getNonGridScale(); + float recentsMaxScale = getTaskView() == null || getTaskView().getRecentsView() == null + ? 1 : 1 / getTaskView().getRecentsView().getMaxScaleForFullScreen(); float scaleX = nonGridScale * recentsMaxScale * (1 / getScaleX()); float scaleY = nonGridScale * recentsMaxScale * (1 / getScaleY()); @@ -412,13 +524,8 @@ public class TaskThumbnailViewDeprecated extends View implements ViewPool.Reusab thumbnailDataAspect, MAX_PCT_BEFORE_ASPECT_RATIOS_CONSIDERED_DIFFERENT); } - /** - * Returns whether or not the current thumbnail is a different orientation to the task. - *

- * Used to disable modal state when screenshot doesn't match the device orientation. - */ - public boolean isThumbnailRotationDifferentFromTask() { - RecentsView recents = mTaskView.getRecentsView(); + private boolean isThumbnailRotationDifferentFromTask() { + RecentsView recents = getTaskView().getRecentsView(); if (recents == null || mThumbnailData == null) { return false; } @@ -437,9 +544,7 @@ public class TaskThumbnailViewDeprecated extends View implements ViewPool.Reusab */ private void refreshOverlay() { if (mOverlayEnabled) { - mOverlay.initOverlay(mTask, - mThumbnailData != null ? mThumbnailData.getThumbnail() : null, - mPreviewPositionHelper.getMatrix(), + mOverlay.initOverlay(mTask, mThumbnailData, mPreviewPositionHelper.getMatrix(), mPreviewPositionHelper.isOrientationChanged()); } else { mOverlay.reset(); @@ -466,7 +571,7 @@ public class TaskThumbnailViewDeprecated extends View implements ViewPool.Reusab if (mBitmapShader != null && mThumbnailData != null) { mPreviewRect.set(0, 0, mThumbnailData.getThumbnail().getWidth(), mThumbnailData.getThumbnail().getHeight()); - int currentRotation = mTaskView.getOrientedState().getRecentsActivityRotation(); + int currentRotation = getTaskView().getOrientedState().getRecentsActivityRotation(); boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL; mPreviewPositionHelper.updateThumbnailMatrix(mPreviewRect, mThumbnailData, getMeasuredWidth(), getMeasuredHeight(), dp.isTablet, currentRotation, isRtl); @@ -474,7 +579,7 @@ public class TaskThumbnailViewDeprecated extends View implements ViewPool.Reusab mBitmapShader.setLocalMatrix(mPreviewPositionHelper.getMatrix()); mPaint.setShader(mBitmapShader); } - mTaskView.updateFullscreenParams(); + getTaskView().updateCurrentFullscreenParams(); invalidate(); } @@ -512,10 +617,6 @@ public class TaskThumbnailViewDeprecated extends View implements ViewPool.Reusab return mThumbnailData.isRealSnapshot && !mTask.isLocked; } - public Matrix getThumbnailMatrix() { - return mPreviewPositionHelper.getMatrix(); - } - @Override public void onRecycle() { // Do nothing diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt index 97bbe87d45..3b2be643ec 100644 --- a/quickstep/src/com/android/quickstep/views/TaskView.kt +++ b/quickstep/src/com/android/quickstep/views/TaskView.kt @@ -22,6 +22,7 @@ import android.animation.ObjectAnimator import android.annotation.IdRes import android.app.ActivityOptions import android.content.Context +import android.content.Intent import android.graphics.Canvas import android.graphics.PointF import android.graphics.Rect @@ -38,6 +39,7 @@ import android.view.View.OnClickListener import android.view.ViewGroup import android.view.ViewStub import android.view.accessibility.AccessibilityNodeInfo +import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction import android.widget.FrameLayout import android.widget.Toast import androidx.annotation.IntDef @@ -45,51 +47,51 @@ import androidx.annotation.RequiresApi import androidx.annotation.VisibleForTesting import androidx.core.view.updateLayoutParams import com.android.app.animation.Interpolators -import com.android.launcher3.AbstractFloatingView import com.android.launcher3.Flags.enableCursorHoverStates -import com.android.launcher3.Flags.enableDesktopExplodedView +import com.android.launcher3.Flags.enableFocusOutline import com.android.launcher3.Flags.enableGridOnlyOverview -import com.android.launcher3.Flags.enableHoverOfChildElementsInTaskview -import com.android.launcher3.Flags.enableLargeDesktopWindowingTile import com.android.launcher3.Flags.enableOverviewIconMenu import com.android.launcher3.Flags.enableRefactorTaskThumbnail -import com.android.launcher3.Flags.enableSeparateExternalDisplayTasks +import com.android.launcher3.Flags.privateSpaceRestrictAccessibilityDrag +import com.android.launcher3.LauncherSettings import com.android.launcher3.R import com.android.launcher3.Utilities import com.android.launcher3.anim.AnimatedFloat +import com.android.launcher3.config.FeatureFlags.ENABLE_KEYBOARD_QUICK_SWITCH import com.android.launcher3.logging.StatsLogManager.LauncherEvent import com.android.launcher3.model.data.ItemInfo -import com.android.launcher3.model.data.TaskViewItemInfo +import com.android.launcher3.model.data.ItemInfoWithIcon +import com.android.launcher3.model.data.WorkspaceItemInfo +import com.android.launcher3.pm.UserCache import com.android.launcher3.testing.TestLogging import com.android.launcher3.testing.shared.TestProtocol import com.android.launcher3.util.CancellableTask +import com.android.launcher3.util.DisplayController import com.android.launcher3.util.Executors -import com.android.launcher3.util.KFloatProperty -import com.android.launcher3.util.MultiPropertyDelegate import com.android.launcher3.util.MultiPropertyFactory -import com.android.launcher3.util.MultiValueAlpha +import com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE import com.android.launcher3.util.RunnableList +import com.android.launcher3.util.SafeCloseable +import com.android.launcher3.util.SplitConfigurationOptions import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED +import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption import com.android.launcher3.util.SplitConfigurationOptions.StagePosition import com.android.launcher3.util.TraceHelper import com.android.launcher3.util.TransformingTouchDelegate import com.android.launcher3.util.ViewPool -import com.android.launcher3.util.coroutines.DispatcherProvider import com.android.launcher3.util.rects.set -import com.android.quickstep.FullscreenDrawParams +import com.android.launcher3.views.ActivityContext import com.android.quickstep.RecentsModel import com.android.quickstep.RemoteAnimationTargets -import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle +import com.android.quickstep.TaskAnimationManager import com.android.quickstep.TaskOverlayFactory +import com.android.quickstep.TaskOverlayFactory.TaskOverlay +import com.android.quickstep.TaskUtils import com.android.quickstep.TaskViewUtils import com.android.quickstep.orientation.RecentsPagedOrientationHandler -import com.android.quickstep.recents.di.RecentsDependencies -import com.android.quickstep.recents.di.get -import com.android.quickstep.recents.di.inject -import com.android.quickstep.recents.domain.usecase.ThumbnailPosition -import com.android.quickstep.recents.ui.viewmodel.TaskData -import com.android.quickstep.recents.ui.viewmodel.TaskTileUiState -import com.android.quickstep.recents.ui.viewmodel.TaskViewModel +import com.android.quickstep.task.thumbnail.TaskThumbnail +import com.android.quickstep.task.thumbnail.TaskThumbnailView +import com.android.quickstep.task.viewmodel.TaskViewData import com.android.quickstep.util.ActiveGestureErrorDetector import com.android.quickstep.util.ActiveGestureLog import com.android.quickstep.util.BorderAnimator @@ -97,20 +99,11 @@ import com.android.quickstep.util.BorderAnimator.Companion.createSimpleBorderAni import com.android.quickstep.util.RecentsOrientedState import com.android.quickstep.util.TaskCornerRadius import com.android.quickstep.util.TaskRemovedDuringLaunchListener -import com.android.quickstep.util.isExternalDisplay -import com.android.quickstep.util.safeDisplayId -import com.android.quickstep.views.IconAppChipView.AppChipStatus -import com.android.quickstep.views.OverviewActionsView.DISABLED_NO_THUMBNAIL -import com.android.quickstep.views.OverviewActionsView.DISABLED_ROTATED import com.android.quickstep.views.RecentsView.UNBOUND_TASK_VIEW_ID import com.android.systemui.shared.recents.model.Task import com.android.systemui.shared.recents.model.ThumbnailData import com.android.systemui.shared.system.ActivityManagerWrapper -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job -import kotlinx.coroutines.cancel -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.launch +import com.android.systemui.shared.system.QuickStepContract /** A task in the Recents view. */ open class TaskView @@ -121,9 +114,7 @@ constructor( defStyleAttr: Int = 0, defStyleRes: Int = 0, focusBorderAnimator: BorderAnimator? = null, - hoverBorderAnimator: BorderAnimator? = null, - val type: TaskViewType = TaskViewType.SINGLE, - protected val thumbnailFullscreenParams: FullscreenDrawParams = FullscreenDrawParams(context), + hoverBorderAnimator: BorderAnimator? = null ) : FrameLayout(context, attrs), ViewPool.Reusable { /** * Used in conjunction with [onTaskListVisibilityChanged], providing more granularity on which @@ -133,38 +124,37 @@ constructor( @IntDef(FLAG_UPDATE_ALL, FLAG_UPDATE_ICON, FLAG_UPDATE_THUMBNAIL, FLAG_UPDATE_CORNER_RADIUS) annotation class TaskDataChanges + /** Type of task view */ + @Retention(AnnotationRetention.SOURCE) + @IntDef(Type.SINGLE, Type.GROUPED, Type.DESKTOP) + annotation class Type { + companion object { + const val SINGLE = 1 + const val GROUPED = 2 + const val DESKTOP = 3 + } + } + + val taskViewData = TaskViewData() val taskIds: IntArray /** Returns a copy of integer array containing taskIds of all tasks in the TaskView. */ get() = taskContainers.map { it.task.key.id }.toIntArray() - val taskIdSet: Set - /** Returns a copy of integer array containing taskIds of all tasks in the TaskView. */ - get() = taskContainers.map { it.task.key.id }.toSet() - - val snapshotViews: Array - get() = taskContainers.map { it.snapshotView }.toTypedArray() + val thumbnailViews: Array + get() = taskContainers.map { it.thumbnailViewDeprecated }.toTypedArray() val isGridTask: Boolean /** Returns whether the task is part of overview grid and not being focused. */ - get() = container.deviceProfile.isTablet && !isLargeTile + get() = container.deviceProfile.isTablet && !isFocusedTask val isRunningTask: Boolean get() = this === recentsView?.runningTaskView - private val isSelectedTask: Boolean - get() = this === recentsView?.selectedTaskView + val isFocusedTask: Boolean + get() = this === recentsView?.focusedTaskView - open val displayId: Int - get() = taskContainers.firstOrNull()?.task.safeDisplayId - - val isExternalDisplay: Boolean - get() = displayId.isExternalDisplay - - val isLargeTile: Boolean - get() = - this == recentsView?.focusedTaskView || - (enableLargeDesktopWindowingTile() && type == TaskViewType.DESKTOP) || - (enableSeparateExternalDisplayTasks() && isExternalDisplay) + val taskCornerRadius: Float + get() = currentFullscreenParams.cornerRadius val recentsView: RecentsView<*, *>? get() = parent as? RecentsView<*, *> @@ -172,25 +162,21 @@ constructor( val pagedOrientationHandler: RecentsPagedOrientationHandler get() = orientedState.orientationHandler - val firstTaskContainer: TaskContainer? - get() = taskContainers.firstOrNull() - - val firstTask: Task? + @get:Deprecated("Use [taskContainers] instead.") + val firstTask: Task /** Returns the first task bound to this TaskView. */ - get() = firstTaskContainer?.task + get() = taskContainers[0].task - val firstItemInfo: ItemInfo? - get() = firstTaskContainer?.itemInfo + @get:Deprecated("Use [taskContainers] instead.") + val firstThumbnailViewDeprecated: TaskThumbnailViewDeprecated + /** Returns the first thumbnailView of the TaskView. */ + get() = taskContainers[0].thumbnailViewDeprecated - /** - * A [TaskViewItemInfo] of this TaskView. The [firstTaskContainer] will be used to get some - * specific information like user, title etc of the Task. However, these task specific - * information will be skipped if the TaskView has no [taskContainers]. Note, please use - * [TaskContainer.itemInfo] for [TaskViewItemInfo] on a specific [TaskContainer]. - */ - val itemInfo: TaskViewItemInfo - get() = TaskViewItemInfo(this, firstTaskContainer) + @get:Deprecated("Use [taskContainers] instead.") + val firstItemInfo: ItemInfo + get() = taskContainers[0].itemInfo + private val currentFullscreenParams = FullscreenDrawParams(context) protected val container: RecentsViewContainer = RecentsViewContainer.containerFromContext(context) protected val lastTouchDownPosition = PointF() @@ -208,9 +194,12 @@ constructor( * Returns addition of translationX that is persistent (e.g. fullscreen and grid), and does * not change according to a temporary state (e.g. task offset). */ - get() = (getNonGridTrans(nonGridTranslationX) + getGridTrans(this.gridTranslationX)) + get() = + (getNonGridTrans(nonGridTranslationX) + + getGridTrans(this.gridTranslationX) + + getNonGridTrans(nonGridPivotTranslationX)) - val persistentTranslationY: Float + protected val persistentTranslationY: Float /** * Returns addition of translationY that is persistent (e.g. fullscreen and grid), and does * not change according to a temporary state (e.g. task offset). @@ -221,21 +210,21 @@ constructor( get() = pagedOrientationHandler.getPrimaryValue( SPLIT_SELECT_TRANSLATION_X, - SPLIT_SELECT_TRANSLATION_Y, + SPLIT_SELECT_TRANSLATION_Y ) protected val secondarySplitTranslationProperty: FloatProperty get() = pagedOrientationHandler.getSecondaryValue( SPLIT_SELECT_TRANSLATION_X, - SPLIT_SELECT_TRANSLATION_Y, + SPLIT_SELECT_TRANSLATION_Y ) - val primaryDismissTranslationProperty: FloatProperty + protected val primaryDismissTranslationProperty: FloatProperty get() = pagedOrientationHandler.getPrimaryValue(DISMISS_TRANSLATION_X, DISMISS_TRANSLATION_Y) - val secondaryDismissTranslationProperty: FloatProperty + protected val secondaryDismissTranslationProperty: FloatProperty get() = pagedOrientationHandler.getSecondaryValue(DISMISS_TRANSLATION_X, DISMISS_TRANSLATION_Y) @@ -243,61 +232,26 @@ constructor( get() = pagedOrientationHandler.getPrimaryValue( TASK_OFFSET_TRANSLATION_X, - TASK_OFFSET_TRANSLATION_Y, + TASK_OFFSET_TRANSLATION_Y ) protected val secondaryTaskOffsetTranslationProperty: FloatProperty get() = pagedOrientationHandler.getSecondaryValue( TASK_OFFSET_TRANSLATION_X, - TASK_OFFSET_TRANSLATION_Y, + TASK_OFFSET_TRANSLATION_Y ) protected val taskResistanceTranslationProperty: FloatProperty get() = pagedOrientationHandler.getSecondaryValue( TASK_RESISTANCE_TRANSLATION_X, - TASK_RESISTANCE_TRANSLATION_Y, + TASK_RESISTANCE_TRANSLATION_Y ) private val tempCoordinates = FloatArray(2) - private val focusBorderAnimator: BorderAnimator? = - focusBorderAnimator - ?: createSimpleBorderAnimator( - TaskCornerRadius.get(context).toInt(), - context.resources.getDimensionPixelSize(R.dimen.keyboard_quick_switch_border_width), - this::getThumbnailBounds, - this, - context - .obtainStyledAttributes(attrs, R.styleable.TaskView, defStyleAttr, defStyleRes) - .getColor( - R.styleable.TaskView_focusBorderColor, - BorderAnimator.DEFAULT_BORDER_COLOR, - ), - ) - - private val hoverBorderAnimator: BorderAnimator? = - hoverBorderAnimator - ?: if (enableCursorHoverStates()) - createSimpleBorderAnimator( - TaskCornerRadius.get(context).toInt(), - context.resources.getDimensionPixelSize(R.dimen.task_hover_border_width), - this::getThumbnailBounds, - this, - context - .obtainStyledAttributes( - attrs, - R.styleable.TaskView, - defStyleAttr, - defStyleRes, - ) - .getColor( - R.styleable.TaskView_hoverBorderColor, - BorderAnimator.DEFAULT_BORDER_COLOR, - ), - ) - else null - + private val focusBorderAnimator: BorderAnimator? + private val hoverBorderAnimator: BorderAnimator? private val rootViewDisplayId: Int get() = rootView.display?.displayId ?: Display.DEFAULT_DISPLAY @@ -309,17 +263,11 @@ constructor( var taskViewId = UNBOUND_TASK_VIEW_ID var isEndQuickSwitchCuj = false - var sysUiStatusNavFlags: Int = 0 - get() = - if (enableRefactorTaskThumbnail()) field - else firstTaskContainer?.thumbnailViewDeprecated?.sysUiStatusNavFlags ?: 0 - private set // Various animation progress variables. // progress: 0 = show icon and no insets; 1 = don't show icon and show full insets. protected var fullscreenProgress = 0f set(value) { - if (value == field && enableOverviewIconMenu()) return field = Utilities.boundToRange(value, 0f, 1f) onFullscreenProgressChanged(field) } @@ -344,18 +292,6 @@ constructor( onModalnessUpdated(field) } - var modalPivot: PointF? = null - set(value) { - field = value - updatePivots() - } - - var splitSplashAlpha = 0f - set(value) { - field = value - applyThumbnailSplashAlpha() - } - protected var taskThumbnailSplashAlpha = 0f set(value) { field = value @@ -374,12 +310,6 @@ constructor( applyScale() } - var modalScale = 1f - set(value) { - field = value - applyScale() - } - private var dismissTranslationX = 0f set(value) { field = value @@ -451,6 +381,12 @@ constructor( applyTranslationX() } + protected var nonGridPivotTranslationX = 0f + set(value) { + field = value + applyTranslationX() + } + // Used when in SplitScreenSelectState private var splitSelectTranslationY = 0f set(value) { @@ -464,15 +400,14 @@ constructor( applyTranslationX() } - private val taskViewAlpha = MultiValueAlpha(this, Alpha.entries.size) - protected var stableAlpha by MultiPropertyDelegate(taskViewAlpha, Alpha.Stable) - var attachAlpha by MultiPropertyDelegate(taskViewAlpha, Alpha.Attach) - var splitAlpha by MultiPropertyDelegate(taskViewAlpha, Alpha.Split) - private var modalAlpha by MultiPropertyDelegate(taskViewAlpha, Alpha.Modal) + protected var stableAlpha = 1f + set(value) { + field = value + alpha = stableAlpha + } protected var shouldShowScreenshot = false get() = !isRunningTask || field - private set /** Enable or disable showing border on hover and focus change */ @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) @@ -488,79 +423,37 @@ constructor( focusBorderAnimator?.setBorderVisibility(visible = field && isFocused, animated = true) } - /** - * Used to cache the hover border state so we don't repeatedly call the border animator with - * every hover event when the user hasn't crossed the threshold of the [thumbnailBounds]. - */ - private var hoverBorderVisible = false + private var focusTransitionProgress = 1f set(value) { - if (field == value) { - return - } field = value - Log.d( - TAG, - "${taskIds.contentToString()} - setting border animator visibility to: $field", - ) - hoverBorderAnimator?.setBorderVisibility(visible = field, animated = true) + onFocusTransitionProgressUpdated(field) } - // Used to cache thumbnail bounds to avoid recalculating on every hover move. - private var thumbnailBounds = Rect() - - // Progress variable indicating if the TaskView is in a settled state: - // 0 = The TaskView is in a transitioning state e.g. during gesture, in quickswitch carousel, - // becoming focus task etc. - // 1 = The TaskView is settled and no longer transitioning - private var settledProgress = 1f - set(value) { - if (value == field && enableOverviewIconMenu()) return - field = value - onSettledProgressUpdated(field) - } - - private val settledProgressPropertyFactory = + private val focusTransitionPropertyFactory = MultiPropertyFactory( this, - SETTLED_PROGRESS, - SettledProgress.entries.size, + FOCUS_TRANSITION, + FOCUS_TRANSITION_INDEX_COUNT, { x: Float, y: Float -> x * y }, - 1f, + 1f ) - private var settledProgressFullscreen by - MultiPropertyDelegate(settledProgressPropertyFactory, SettledProgress.Fullscreen) - private var settledProgressGesture by - MultiPropertyDelegate(settledProgressPropertyFactory, SettledProgress.Gesture) - private var settledProgressDismiss by - MultiPropertyDelegate(settledProgressPropertyFactory, SettledProgress.Dismiss) - - private var viewModel: TaskViewModel? = null - private val dispatcherProvider: DispatcherProvider by RecentsDependencies.inject() - private val coroutineScope: CoroutineScope by RecentsDependencies.inject() - private val coroutineJobs = mutableListOf() + private val focusTransitionFullscreen = + focusTransitionPropertyFactory.get(FOCUS_TRANSITION_INDEX_FULLSCREEN) + private val focusTransitionScaleAndDim = + focusTransitionPropertyFactory.get(FOCUS_TRANSITION_INDEX_SCALE_AND_DIM) /** - * Returns an animator of [settledProgressDismiss] that transition in with a built-in + * Returns an animator of [focusTransitionScaleAndDim] that transition out with a built-in * interpolator. */ - fun getDismissIconFadeInAnimator(): ObjectAnimator = - ObjectAnimator.ofFloat(this, SETTLED_PROGRESS_DISMISS, 1f).apply { - duration = FADE_IN_ICON_DURATION - interpolator = FADE_IN_ICON_INTERPOLATOR - } - - /** - * Returns an animator of [settledProgressDismiss] that transition out with a built-in - * interpolator. [AnimatedFloat] is used to apply another level of interpolation, on top of - * interpolator set to the [Animator] by the caller. - */ - fun getDismissIconFadeOutAnimator(): ObjectAnimator = + fun getFocusTransitionScaleAndDimOutAnimator(): ObjectAnimator = AnimatedFloat { v -> - settledProgressDismiss = SETTLED_PROGRESS_FAST_OUT_INTERPOLATOR.getInterpolation(v) + focusTransitionScaleAndDim.value = + FOCUS_TRANSITION_FAST_OUT_INTERPOLATOR.getInterpolation(v) } .animateToValue(1f, 0f) - private var iconFadeInOnGestureCompleteAnimator: ObjectAnimator? = null + private var iconAndDimAnimator: ObjectAnimator? = null // The current background requests to load the task thumbnail and icon private val pendingThumbnailLoadRequests = mutableListOf>() private val pendingIconLoadRequests = mutableListOf>() @@ -568,15 +461,56 @@ constructor( init { setOnClickListener { _ -> onClick() } - - setWillNotDraw(!enableCursorHoverStates()) + val keyboardFocusHighlightEnabled = + (ENABLE_KEYBOARD_QUICK_SWITCH.get() || enableFocusOutline()) + val cursorHoverStatesEnabled = enableCursorHoverStates() + setWillNotDraw(!keyboardFocusHighlightEnabled && !cursorHoverStatesEnabled) + val attrs = + context.obtainStyledAttributes(attrs, R.styleable.TaskView, defStyleAttr, defStyleRes) + try { + this.focusBorderAnimator = + focusBorderAnimator + ?: if (keyboardFocusHighlightEnabled) + createSimpleBorderAnimator( + currentFullscreenParams.cornerRadius.toInt(), + context.resources.getDimensionPixelSize( + R.dimen.keyboard_quick_switch_border_width + ), + { bounds: Rect -> getThumbnailBounds(bounds) }, + this, + attrs.getColor( + R.styleable.TaskView_focusBorderColor, + BorderAnimator.DEFAULT_BORDER_COLOR + ) + ) + else null + this.hoverBorderAnimator = + hoverBorderAnimator + ?: if (cursorHoverStatesEnabled) + createSimpleBorderAnimator( + currentFullscreenParams.cornerRadius.toInt(), + context.resources.getDimensionPixelSize( + R.dimen.task_hover_border_width + ), + { bounds: Rect -> getThumbnailBounds(bounds) }, + this, + attrs.getColor( + R.styleable.TaskView_hoverBorderColor, + BorderAnimator.DEFAULT_BORDER_COLOR + ) + ) + else null + } finally { + attrs.recycle() + } } + @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) public override fun onFocusChanged( gainFocus: Boolean, direction: Int, - previouslyFocusedRect: Rect?, + previouslyFocusedRect: Rect? ) { super.onFocusChanged(gainFocus, direction, previouslyFocusedRect) if (borderEnabled) { @@ -587,28 +521,20 @@ constructor( override fun onHoverEvent(event: MotionEvent): Boolean { if (borderEnabled) { when (event.action) { - MotionEvent.ACTION_HOVER_ENTER -> { - hoverBorderVisible = - if (enableHoverOfChildElementsInTaskview()) { - getThumbnailBounds(thumbnailBounds) - event.isWithinThumbnailBounds() - } else { - true - } - } - MotionEvent.ACTION_HOVER_MOVE -> - if (enableHoverOfChildElementsInTaskview()) - hoverBorderVisible = event.isWithinThumbnailBounds() - MotionEvent.ACTION_HOVER_EXIT -> hoverBorderVisible = false + MotionEvent.ACTION_HOVER_ENTER -> + hoverBorderAnimator?.setBorderVisibility(visible = true, animated = true) + MotionEvent.ACTION_HOVER_EXIT -> + hoverBorderAnimator?.setBorderVisibility(visible = false, animated = true) else -> {} } } return super.onHoverEvent(event) } - override fun onInterceptHoverEvent(event: MotionEvent): Boolean = - if (enableHoverOfChildElementsInTaskview()) super.onInterceptHoverEvent(event) - else if (enableCursorHoverStates()) true else super.onInterceptHoverEvent(event) + // avoid triggering hover event on child elements which would cause HOVER_EXIT for this + // task view + override fun onInterceptHoverEvent(event: MotionEvent) = + if (enableCursorHoverStates()) true else super.onInterceptHoverEvent(event) override fun dispatchTouchEvent(ev: MotionEvent): Boolean { val recentsView = recentsView ?: return false @@ -636,70 +562,37 @@ constructor( super.draw(canvas) } - override fun setLayoutDirection(layoutDirection: Int) { - super.setLayoutDirection(layoutDirection) - if (enableOverviewIconMenu()) { - val deviceLayoutDirection = resources.configuration.layoutDirection - taskContainers.forEach { - (it.iconView as IconAppChipView).layoutDirection = deviceLayoutDirection - } - } - } - @RequiresApi(Build.VERSION_CODES.Q) override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { super.onLayout(changed, left, top, right, bottom) - updatePivots() + val thumbnailTopMargin = container.deviceProfile.overviewTaskThumbnailTopMarginPx + if (container.deviceProfile.isTablet) { + pivotX = (if (layoutDirection == LAYOUT_DIRECTION_RTL) 0 else right - left).toFloat() + pivotY = thumbnailTopMargin.toFloat() + } else { + pivotX = (right - left) * 0.5f + pivotY = thumbnailTopMargin + (height - thumbnailTopMargin) * 0.5f + } systemGestureExclusionRects = SYSTEM_GESTURE_EXCLUSION_RECT.onEach { it.right = width it.bottom = height } - if (enableHoverOfChildElementsInTaskview()) { - getThumbnailBounds(thumbnailBounds) - } - } - - private fun updatePivots() { - val modalPivot = modalPivot - if (modalPivot != null) { - pivotX = modalPivot.x - pivotY = modalPivot.y - } else { - val thumbnailTopMargin = container.deviceProfile.overviewTaskThumbnailTopMarginPx - if (container.deviceProfile.isTablet) { - pivotX = - (if (layoutDirection == LAYOUT_DIRECTION_RTL) 0 else right - left).toFloat() - pivotY = thumbnailTopMargin.toFloat() - } else { - pivotX = (right - left) * 0.5f - pivotY = thumbnailTopMargin + (height - thumbnailTopMargin) * 0.5f - } - } } override fun onRecycle() { resetPersistentViewTransforms() - - viewModel = null - attachAlpha = 1f - splitAlpha = 1f - splitSplashAlpha = 0f - modalAlpha = 1f - modalScale = 1f - modalPivot = null - taskThumbnailSplashAlpha = 0f // Clear any references to the thumbnail (it will be re-read either from the cache or the // system on next bind) - if (!enableRefactorTaskThumbnail()) { + if (enableRefactorTaskThumbnail()) { + notifyIsRunningTaskUpdated() + } else { taskContainers.forEach { it.thumbnailViewDeprecated.setThumbnail(it.task, null) } } setOverlayEnabled(false) onTaskListVisibilityChanged(false) borderEnabled = false - hoverBorderVisible = false taskViewId = UNBOUND_TASK_VIEW_ID - // TODO(b/390583187): Clean the components UI State when TaskView is recycled. taskContainers.forEach { it.destroy() } } @@ -709,36 +602,34 @@ constructor( override fun onInitializeAccessibilityNodeInfo(info: AccessibilityNodeInfo) { super.onInitializeAccessibilityNodeInfo(info) with(info) { - // Only make actions available if the app icon menu is visible to the user. - // When modalness is >0, the user is in select mode and the icon menu is hidden. - // When split selection is active, they should only be able to select the app and not - // take any other action. - val shouldPopulateAccessibilityMenu = - modalness == 0f && recentsView?.isSplitSelectionActive == false - if (shouldPopulateAccessibilityMenu) { - taskContainers.forEach { - TraceHelper.allowIpcs("TV.a11yInfo") { - TaskOverlayFactory.getEnabledShortcuts(this@TaskView, it).forEach { shortcut - -> - addAction(shortcut.createAccessibilityAction(context)) - } + addAction( + AccessibilityAction( + R.id.action_close, + context.getText(R.string.accessibility_close) + ) + ) + + taskContainers.forEach { + TraceHelper.allowIpcs("TV.a11yInfo") { + TaskOverlayFactory.getEnabledShortcuts(this@TaskView, it).forEach { shortcut -> + addAction(shortcut.createAccessibilityAction(context)) } } + } - // Add DWB accessibility action at the end of the list - taskContainers.forEach { - it.digitalWellBeingToast?.getDWBAccessibilityAction()?.let(::addAction) - } + // Add DWB accessibility action at the end of the list + taskContainers.forEach { + it.digitalWellBeingToast?.getDWBAccessibilityAction()?.let(::addAction) } recentsView?.let { collectionItemInfo = - AccessibilityNodeInfo.CollectionItemInfo( + AccessibilityNodeInfo.CollectionItemInfo.obtain( 0, 1, - it.getAccessibilityChildren().indexOf(this@TaskView), + it.taskViewCount - it.indexOfChild(this@TaskView) - 1, 1, - false, + false ) } } @@ -746,6 +637,11 @@ constructor( override fun performAccessibilityAction(action: Int, arguments: Bundle?): Boolean { // TODO(b/343708271): Add support for multiple tasks per action. + if (action == R.id.action_close) { + recentsView?.dismissTask(this, true /*animateTaskView*/, true /*removeTask*/) + return true + } + taskContainers.forEach { if (it.digitalWellBeingToast?.handleAccessibilityAction(action) == true) { return true @@ -762,155 +658,13 @@ constructor( return super.performAccessibilityAction(action, arguments) } - override fun onFinishInflate() { - super.onFinishInflate() - inflateViewStubs() - } - - protected open fun inflateViewStubs() { - findViewById(R.id.snapshot) - ?.apply { - layoutResource = - if (enableRefactorTaskThumbnail()) R.layout.task_thumbnail - else R.layout.task_thumbnail_deprecated - } - ?.inflate() - findViewById(R.id.icon) - ?.apply { - layoutResource = - if (enableOverviewIconMenu()) R.layout.icon_app_chip_view - else R.layout.icon_view - } - ?.inflate() - } - - override fun onAttachedToWindow() { - super.onAttachedToWindow() - if (enableRefactorTaskThumbnail()) { - // The TaskView lifecycle is starts the ViewModel during onBind, and cleans it in - // onRecycle. So it should be initialized at this point. TaskView Lifecycle: - // `bind` -> `onBind` -> onAttachedToWindow() -> onDetachFromWindow -> onRecycle - coroutineJobs += - coroutineScope.launch(dispatcherProvider.main) { - viewModel!!.state.collectLatest(::updateTaskViewState) - } - } - } - - private fun updateTaskViewState(state: TaskTileUiState) { - sysUiStatusNavFlags = state.sysUiStatusNavFlags - - // Updating containers - val mapOfTasks = state.tasks.associateBy { it.taskId } - taskContainers.forEach { container -> - val taskId = container.task.key.id - val containerState = mapOfTasks[taskId] - val shouldHaveHeader = (type == TaskViewType.DESKTOP) && enableDesktopExplodedView() - container.setState( - state = containerState, - liveTile = state.isLiveTile, - hasHeader = shouldHaveHeader, - clickCloseListener = - if (shouldHaveHeader) { - { - // Update the layout UI to remove this task from the layout grid, - // and remove the task from ActivityManager afterwards. - recentsView?.dismissTask( - taskId, - /* animate= */ true, - /* removeTask= */ true, - ) - } - } else { - null - }, - ) - updateThumbnailValidity(container) - val thumbnailPosition = - updateThumbnailMatrix( - container = container, - width = container.thumbnailView.width, - height = container.thumbnailView.height, - ) - container.setOverlayEnabled(state.taskOverlayEnabled, thumbnailPosition) - if (state.isCentralTask) { - this.container.actionsView.let { - it.updateDisabledFlags( - DISABLED_ROTATED, - thumbnailPosition?.isRotated ?: false, - ) - it.updateDisabledFlags( - DISABLED_NO_THUMBNAIL, - state.tasks.any { taskData -> - (taskData as? TaskData.Data)?.thumbnailData?.thumbnail == null - }, - ) - } - } - - if (enableOverviewIconMenu()) { - setIconState(container, containerState) - } - } - } - - private fun updateThumbnailValidity(container: TaskContainer) { - container.isThumbnailValid = - viewModel?.isThumbnailValid( - thumbnail = container.thumbnailData, - width = container.thumbnailView.width, - height = container.thumbnailView.height, - ) ?: return - applyThumbnailSplashAlpha() - } - - /** - * Updates the thumbnail's transformation matrix and rotation state within a TaskContainer. - * - * This function is called to reposition the thumbnail in the following scenarios: - * - When the TTV's size changes (onSizeChanged), and it's displaying a SnapshotSplash. - * - When drawing a snapshot (drawSnapshot). - * - * @param container The TaskContainer holding the thumbnail to be updated. - * @param width The desired width of the thumbnail's container. - * @param height The desired height of the thumbnail's container. - */ - private fun updateThumbnailMatrix( - container: TaskContainer, - width: Int, - height: Int, - ): ThumbnailPosition? { - val thumbnailPosition = - viewModel?.getThumbnailPosition(container.thumbnailData, width, height, isLayoutRtl) - ?: return null - container.updateThumbnailMatrix(thumbnailPosition.matrix) - return thumbnailPosition - } - - override fun onDetachedFromWindow() { - super.onDetachedFromWindow() - if (enableRefactorTaskThumbnail()) { - // The jobs are being cancelled in the background thread. So we make a copy of the - // list to prevent cleaning a new job that might be added to this list during - // onAttach or another moment in the lifecycle. - val coroutineJobsToCancel = coroutineJobs.toList() - coroutineJobs.clear() - coroutineScope.launch(dispatcherProvider.background) { - coroutineJobsToCancel.forEach { - it.cancel("TaskView detaching from window") - } - } - } - } - /** Updates this task view to the given {@param task}. */ open fun bind( task: Task, orientedState: RecentsOrientedState, - taskOverlayFactory: TaskOverlayFactory, + taskOverlayFactory: TaskOverlayFactory ) { cancelPendingLoadTasks() - this.orientedState = orientedState // Needed for dependencies taskContainers = listOf( createTaskContainer( @@ -918,83 +672,69 @@ constructor( R.id.snapshot, R.id.icon, R.id.show_windows, - R.id.digital_wellbeing_toast, STAGE_POSITION_UNDEFINED, - taskOverlayFactory, + taskOverlayFactory ) ) - onBind(orientedState) + setOrientationState(orientedState) } - protected open fun onBind(orientedState: RecentsOrientedState) { - if (enableRefactorTaskThumbnail()) { - val scopeId = context - Log.d(TAG, "onBind $scopeId ${orientedState.containerInterface}") - viewModel = - TaskViewModel( - taskViewType = type, - recentsViewData = RecentsDependencies.get(scopeId), - getTaskUseCase = RecentsDependencies.get(scopeId), - getSysUiStatusNavFlagsUseCase = RecentsDependencies.get(scopeId), - isThumbnailValidUseCase = RecentsDependencies.get(scopeId), - getThumbnailPositionUseCase = RecentsDependencies.get(scopeId), - dispatcherProvider = RecentsDependencies.get(scopeId), - ) - .apply { bind(*taskIds) } - } - - taskContainers.forEach { container -> - container.bind() - if (enableRefactorTaskThumbnail()) { - container.thumbnailView.cornerRadius = - thumbnailFullscreenParams.currentCornerRadius - container.thumbnailView.doOnSizeChange { width, height -> - updateThumbnailValidity(container) - val thumbnailPosition = updateThumbnailMatrix(container, width, height) - container.refreshOverlay(thumbnailPosition) - } - } - } - setOrientationState(orientedState) - } - - private fun applyThumbnailSplashAlpha() { - val alpha = getSplashAlphaProgress() - taskContainers.forEach { it.updateThumbnailSplashProgress(alpha) } - } - - private fun getSplashAlphaProgress(): Float = - when { - !enableRefactorTaskThumbnail() -> taskThumbnailSplashAlpha - splitSplashAlpha > 0f -> splitSplashAlpha - shouldShowSplash() -> taskThumbnailSplashAlpha - else -> 0f - } - - internal fun shouldShowSplash(): Boolean = taskContainers.any { !it.isThumbnailValid } - protected fun createTaskContainer( task: Task, @IdRes thumbnailViewId: Int, @IdRes iconViewId: Int, @IdRes showWindowViewId: Int, - @IdRes digitalWellbeingBannerId: Int, @StagePosition stagePosition: Int, - taskOverlayFactory: TaskOverlayFactory, + taskOverlayFactory: TaskOverlayFactory ): TaskContainer { - val iconView = findViewById(iconViewId) as TaskViewIcon - return TaskContainer( - this, + val thumbnailViewDeprecated: TaskThumbnailViewDeprecated = findViewById(thumbnailViewId)!! + val thumbnailView: TaskThumbnailView? + if (enableRefactorTaskThumbnail()) { + val indexOfSnapshotView = indexOfChild(thumbnailViewDeprecated) + thumbnailView = + TaskThumbnailView(context).apply { + layoutParams = thumbnailViewDeprecated.layoutParams + addView(this, indexOfSnapshotView) + } + thumbnailViewDeprecated.visibility = GONE + } else { + thumbnailView = null + } + val iconView = getOrInflateIconView(iconViewId) + return TaskContainer( task, - findViewById(thumbnailViewId), + thumbnailView, + thumbnailViewDeprecated, iconView, TransformingTouchDelegate(iconView.asView()), stagePosition, - findViewById(digitalWellbeingBannerId)!!, + DigitalWellBeingToast(container, this), findViewById(showWindowViewId)!!, - taskOverlayFactory, + taskOverlayFactory ) - } + .apply { + if (enableRefactorTaskThumbnail()) { + thumbnailViewDeprecated.setTaskOverlay(overlay) + bindThumbnailView() + } else { + thumbnailViewDeprecated.bind(task, overlay) + } + } + } + + protected fun getOrInflateIconView(@IdRes iconViewId: Int): TaskViewIcon { + val iconView = findViewById(iconViewId)!! + return iconView as? TaskViewIcon + ?: (iconView as ViewStub) + .apply { + layoutResource = + if (enableOverviewIconMenu()) R.layout.icon_app_chip_view + else R.layout.icon_view + } + .inflate() as TaskViewIcon + } + + protected fun isTaskContainersInitialized() = this::taskContainers.isInitialized fun containsMultipleTasks() = taskContainers.size > 1 @@ -1008,15 +748,15 @@ constructor( fun containsTaskId(taskId: Int) = getTaskContainerById(taskId) != null open fun setOrientationState(orientationState: RecentsOrientedState) { - this.orientedState = orientationState - taskContainers.forEach { it.iconView.setIconOrientation(orientationState, isGridTask) } - setThumbnailOrientation(orientationState) - } + this.orientedState = orientationState + taskContainers.forEach { it.iconView.setIconOrientation(orientationState, isGridTask) } + setThumbnailOrientation(orientationState) + } protected open fun setThumbnailOrientation(orientationState: RecentsOrientedState) { taskContainers.forEach { it.overlay.updateOrientationState(orientationState) - it.digitalWellBeingToast?.initialize() + it.digitalWellBeingToast?.initialize(it.task) } } @@ -1024,7 +764,11 @@ constructor( * Updates TaskView scaling and translation required to support variable width if enabled, while * ensuring TaskView fits into screen in fullscreen. */ - open fun updateTaskSize(lastComputedTaskSize: Rect, lastComputedGridTaskSize: Rect) { + fun updateTaskSize( + lastComputedTaskSize: Rect, + lastComputedGridTaskSize: Rect, + lastComputedCarouselTaskSize: Rect + ) { val thumbnailPadding = container.deviceProfile.overviewTaskThumbnailTopMarginPx val taskWidth = lastComputedTaskSize.width() val taskHeight = lastComputedTaskSize.height() @@ -1035,10 +779,9 @@ constructor( if (container.deviceProfile.isTablet) { val boxWidth: Int val boxHeight: Int - - // Focused task and Desktop tasks should use focusTaskRatio that is associated - // with the original orientation of the focused task. - if (isLargeTile) { + if (isFocusedTask) { + // Task will be focused and should use focused task size. Use focusTaskRatio + // that is associated with the original orientation of the focused task. boxWidth = taskWidth boxHeight = taskHeight } else { @@ -1052,15 +795,22 @@ constructor( expectedHeight = boxHeight + thumbnailPadding // Scale to to fit task Rect. - nonGridScale = taskWidth / boxWidth.toFloat() + nonGridScale = + if (enableGridOnlyOverview()) { + lastComputedCarouselTaskSize.width() / taskWidth.toFloat() + } else { + taskWidth / boxWidth.toFloat() + } // Align to top of task Rect. boxTranslationY = (expectedHeight - thumbnailPadding - taskHeight) / 2.0f } else { nonGridScale = 1f boxTranslationY = 0f - expectedWidth = taskWidth - expectedHeight = taskHeight + thumbnailPadding + expectedWidth = if (enableOverviewIconMenu()) taskWidth else LayoutParams.MATCH_PARENT + expectedHeight = + if (enableOverviewIconMenu()) taskHeight + thumbnailPadding + else LayoutParams.MATCH_PARENT } this.nonGridScale = nonGridScale this.boxTranslationY = boxTranslationY @@ -1074,10 +824,9 @@ constructor( protected open fun updateThumbnailSize() { // TODO(b/271468547), we should default to setting translations only on the snapshot instead // of a hybrid of both margins and translations - firstTaskContainer?.snapshotView?.updateLayoutParams { + taskContainers[0].snapshotView.updateLayoutParams { topMargin = container.deviceProfile.overviewTaskThumbnailTopMarginPx } - taskContainers.forEach { it.digitalWellBeingToast?.setupLayout() } } /** Returns the thumbnail's bounds, optionally relative to the screen. */ @@ -1089,7 +838,7 @@ constructor( if (relativeToDragLayer) { container.dragLayer.getDescendantRectRelativeToSelf( it.snapshotView, - thumbnailBounds, + thumbnailBounds ) } else { thumbnailBounds.set(it.snapshotView) @@ -1121,8 +870,7 @@ constructor( taskContainers.forEach { if (visible) { recentsModel.thumbnailCache - .getThumbnailInBackground(it.task) { thumbnailData -> - it.task.thumbnail = thumbnailData + .updateThumbnailInBackground(it.task) { thumbnailData -> it.thumbnailViewDeprecated.setThumbnail(it.task, thumbnailData) } ?.also { request -> pendingThumbnailLoadRequests.add(request) } @@ -1134,24 +882,28 @@ constructor( } } } - if (needsUpdate(changes, FLAG_UPDATE_ICON) && !enableOverviewIconMenu()) { + if (needsUpdate(changes, FLAG_UPDATE_ICON)) { taskContainers.forEach { if (visible) { recentsModel.iconCache - .getIconInBackground(it.task) { icon, contentDescription, title -> - it.task.icon = icon - it.task.titleDescription = contentDescription - it.task.title = title - onIconLoaded(it) + .updateIconInBackground(it.task) { task -> + setIcon(it.iconView, task.icon) + if (enableOverviewIconMenu()) { + setText(it.iconView, task.title) + } + it.digitalWellBeingToast?.initialize(task) } ?.also { request -> pendingIconLoadRequests.add(request) } } else { - onIconUnloaded(it) + setIcon(it.iconView, null) + if (enableOverviewIconMenu()) { + setText(it.iconView, null) + } } } } if (needsUpdate(changes, FLAG_UPDATE_CORNER_RADIUS)) { - thumbnailFullscreenParams.updateCornerRadius(context) + currentFullscreenParams.updateCornerRadius(context) } } @@ -1159,38 +911,10 @@ constructor( (dataChange and flag) == flag protected open fun cancelPendingLoadTasks() { - pendingThumbnailLoadRequests.forEach { it.cancel() } - pendingThumbnailLoadRequests.clear() - pendingIconLoadRequests.forEach { it.cancel() } - pendingIconLoadRequests.clear() - } - - protected open fun setIconState(container: TaskContainer, state: TaskData?) { - if (enableOverviewIconMenu()) { - if (state is TaskData.Data) { - setIcon(container.iconView, state.icon) - container.iconView.setText(state.title) - container.digitalWellBeingToast?.initialize() - } else { - setIcon(container.iconView, null) - container.iconView.setText(null) - } - } - } - - protected open fun onIconLoaded(taskContainer: TaskContainer) { - setIcon(taskContainer.iconView, taskContainer.task.icon) - if (enableOverviewIconMenu()) { - taskContainer.iconView.setText(taskContainer.task.title) - } - taskContainer.digitalWellBeingToast?.initialize() - } - - protected open fun onIconUnloaded(taskContainer: TaskContainer) { - setIcon(taskContainer.iconView, null) - if (enableOverviewIconMenu()) { - taskContainer.iconView.setText(null) - } + pendingThumbnailLoadRequests.forEach { it.cancel() } + pendingThumbnailLoadRequests.clear() + pendingIconLoadRequests.forEach { it.cancel() } + pendingIconLoadRequests.clear() } protected fun setIcon(iconView: TaskViewIcon, icon: Drawable?) { @@ -1214,14 +938,13 @@ constructor( } } - @JvmOverloads - open fun setShouldShowScreenshot( - shouldShowScreenshot: Boolean, - thumbnailDatas: Map? = null, - ) { - if (this.shouldShowScreenshot == shouldShowScreenshot) return - this.shouldShowScreenshot = shouldShowScreenshot + protected fun setText(iconView: TaskViewIcon, text: CharSequence?) { + iconView.setText(text) + } + + open fun refreshThumbnails(thumbnailDatas: HashMap?) { if (enableRefactorTaskThumbnail()) { + // TODO(b/342560598) add thumbnail logic return } @@ -1241,7 +964,7 @@ constructor( return } val callbackList = - launchWithAnimation()?.apply { + launchTasks()?.apply { add { Log.d("b/310064698", "${taskIds.contentToString()} - onClick - launchCompleted") } @@ -1249,180 +972,79 @@ constructor( Log.d("b/310064698", "${taskIds.contentToString()} - onClick - callbackList: $callbackList") container.statsLogManager .logger() - .withItemInfo(itemInfo) + .withItemInfo(firstItemInfo) .log(LauncherEvent.LAUNCHER_TASK_LAUNCH_TAP) } - /** Launch of the current task (both live and inactive tasks) with an animation. */ - fun launchWithAnimation(): RunnableList? { - return if (isRunningTask && recentsView?.remoteTargetHandles != null) { - launchAsLiveTile(recentsView?.remoteTargetHandles!!) - } else { - launchAsStaticTile() - } - } - - private fun launchAsLiveTile(remoteTargetHandles: Array): RunnableList? { - val recentsView = recentsView ?: return null - if (!isClickableAsLiveTile) { - Log.e( - TAG, - "launchAsLiveTile - TaskView is not clickable as a live tile; returning to home: ${taskIds.contentToString()}", - ) - return null - } - isClickableAsLiveTile = false - val targets = - if (remoteTargetHandles.isNotEmpty()) { - if (remoteTargetHandles.size == 1) { - remoteTargetHandles[0].transformParams.targetSet - } else { - val apps = - remoteTargetHandles.flatMap { - it.transformParams.targetSet.apps.asIterable() - } - val wallpapers = - remoteTargetHandles.flatMap { - it.transformParams.targetSet.wallpapers.asIterable() - } - RemoteAnimationTargets( - apps.toTypedArray(), - wallpapers.toTypedArray(), - remoteTargetHandles[0].transformParams.targetSet.nonApps, - remoteTargetHandles[0].transformParams.targetSet.targetMode, - ) - } - } else { - null - } - if (targets == null) { - // If the recents animation is cancelled somehow between the parent if block and - // here, try to launch the task as a non live tile task. - val runnableList = launchAsStaticTile() - if (runnableList == null) { - Log.e( - TAG, - "launchAsLiveTile - Recents animation cancelled and cannot launch task as non-live tile; returning to home: ${taskIds.contentToString()}", - ) - } - isClickableAsLiveTile = true - return runnableList - } - TestLogging.recordEvent( - TestProtocol.SEQUENCE_MAIN, - "composeRecentsLaunchAnimator", - taskIds.contentToString(), - ) - val runnableList = RunnableList() - with(AnimatorSet()) { - TaskViewUtils.composeRecentsLaunchAnimator( - this, - this@TaskView, - targets.apps, - targets.wallpapers, - targets.nonApps, - true, /* launcherClosing */ - recentsView.stateManager, - recentsView, - recentsView.depthController, - /* transitionInfo= */ null, - ) - addListener( - object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animator: Animator) { - if (taskContainers.any { it.task.key.displayId != rootViewDisplayId }) { - launchAsStaticTile() - } - isClickableAsLiveTile = true - runEndCallback() - } - - override fun onAnimationCancel(animation: Animator) { - runEndCallback() - } - - private fun runEndCallback() { - runnableList.executeAllAndDestroy() - } - } - ) - start() - } - Log.d(TAG, "launchAsLiveTile - composeRecentsLaunchAnimator: ${taskIds.contentToString()}") - recentsView.onTaskLaunchedInLiveTileMode() - return runnableList - } - /** * Starts the task associated with this view and animates the startup. * * @return CompletionStage to indicate the animation completion or null if the launch failed. */ - open fun launchAsStaticTile(): RunnableList? { - val firstTaskContainer = firstTaskContainer ?: return null + open fun launchTaskAnimated(): RunnableList? { TestLogging.recordEvent( TestProtocol.SEQUENCE_MAIN, "startActivityFromRecentsAsync", - taskIds.contentToString(), + taskIds.contentToString() ) val opts = container.getActivityLaunchOptions(this, null).apply { - options.launchDisplayId = displayId + options.launchDisplayId = display?.displayId ?: Display.DEFAULT_DISPLAY } if ( ActivityManagerWrapper.getInstance() - .startActivityFromRecents(firstTaskContainer.task.key, opts.options) + .startActivityFromRecents(taskContainers[0].task.key, opts.options) ) { Log.d( TAG, - "launchAsStaticTile - startActivityFromRecents: ${taskIds.contentToString()}", + "launchTaskAnimated - startActivityFromRecents: ${taskIds.contentToString()}" ) ActiveGestureLog.INSTANCE.trackEvent( ActiveGestureErrorDetector.GestureEvent.EXPECTING_TASK_APPEARED ) val recentsView = recentsView ?: return null - if ( - recentsView.runningTaskViewId != -1 && - recentsView.mRecentsAnimationController != null - ) { + if (recentsView.runningTaskViewId != -1) { recentsView.onTaskLaunchedInLiveTileMode() // Return a fresh callback in the live tile case, so that it's not accidentally // triggered by QuickstepTransitionManager.AppLaunchAnimationRunner. return RunnableList().also { recentsView.addSideTaskLaunchCallback(it) } } - // If the recents transition is running (ie. in live tile mode), then the start - // of a new task will merge into the existing transition and it currently will - // not be run independently, so we need to rely on the onTaskAppeared() call - // for the new task to trigger the side launch callback to flush this runnable - // list (which is usually flushed when the app launch animation finishes) - recentsView.addSideTaskLaunchCallback(opts.onEndCallback) + if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) { + // If the recents transition is running (ie. in live tile mode), then the start + // of a new task will merge into the existing transition and it currently will + // not be run independently, so we need to rely on the onTaskAppeared() call + // for the new task to trigger the side launch callback to flush this runnable + // list (which is usually flushed when the app launch animation finishes) + recentsView.addSideTaskLaunchCallback(opts.onEndCallback) + } return opts.onEndCallback } else { - notifyTaskLaunchFailed("launchAsStaticTile") + notifyTaskLaunchFailed() return null } } /** Starts the task associated with this view without any animation */ - @JvmOverloads - open fun launchWithoutAnimation( - isQuickSwitch: Boolean = false, - callback: (launched: Boolean) -> Unit, - ) { - val firstTaskContainer = firstTaskContainer ?: return + fun launchTask(callback: (launched: Boolean) -> Unit) { + launchTask(callback, isQuickSwitch = false) + } + + /** Starts the task associated with this view without any animation */ + open fun launchTask(callback: (launched: Boolean) -> Unit, isQuickSwitch: Boolean) { TestLogging.recordEvent( TestProtocol.SEQUENCE_MAIN, "startActivityFromRecentsAsync", - taskIds.contentToString(), + taskIds.contentToString() ) + val firstContainer = taskContainers[0] val failureListener = TaskRemovedDuringLaunchListener(context.applicationContext) if (isQuickSwitch) { // We only listen for failures to launch in quickswitch because the during this // gesture launcher is in the background state, vs other launches which are in // the actual overview state - failureListener.register(container, firstTaskContainer.task.key.id) { - notifyTaskLaunchFailed("launchWithoutAnimation") + failureListener.register(container, firstContainer.task.key.id) { + notifyTaskLaunchFailed() recentsView?.let { // Disable animations for now, as it is an edge case and the app usually // covers launcher and also any state transition animation also gets @@ -1444,7 +1066,7 @@ constructor( 0, 0, Executors.MAIN_EXECUTOR.handler, - { callback(true) }, + { callback(true) } ) { failureListener.onTransitionFinished() } @@ -1453,32 +1075,113 @@ constructor( if (isQuickSwitch) { setFreezeRecentTasksReordering() } - // TODO(b/331754864): Update this to use TV.shouldShowSplash - disableStartingWindow = firstTaskContainer.shouldShowSplashView + // TODO(b/334826842) add splash functionality to new TTV + if (!enableRefactorTaskThumbnail()) { + disableStartingWindow = + firstContainer.thumbnailViewDeprecated.shouldShowSplashView() + } } Executors.UI_HELPER_EXECUTOR.execute { if ( !ActivityManagerWrapper.getInstance() - .startActivityFromRecents(firstTaskContainer.task.key, if (Utilities.ATLEAST_Q) opts else null) + .startActivityFromRecents(firstContainer.task.key, if (Utilities.ATLEAST_Q) opts else null) ) { // If the call to start activity failed, then post the result immediately, // otherwise, wait for the animation start callback from the activity options // above Executors.MAIN_EXECUTOR.post { - notifyTaskLaunchFailed("launchTask") + notifyTaskLaunchFailed() callback(false) } } - Log.d( - TAG, - "launchWithoutAnimation - startActivityFromRecents: ${taskIds.contentToString()}", - ) + Log.d(TAG, "launchTask - startActivityFromRecents: ${taskIds.contentToString()}") } } - private fun notifyTaskLaunchFailed(launchMethod: String) { - val sb = - StringBuilder("$launchMethod - Failed to launch task: ${taskIds.contentToString()}\n") + /** Launch of the current task (both live and inactive tasks) with an animation. */ + fun launchTasks(): RunnableList? { + val recentsView = recentsView ?: return null + val remoteTargetHandles = recentsView.mRemoteTargetHandles + if (!isRunningTask || remoteTargetHandles == null) { + return launchTaskAnimated() + } + if (!isClickableAsLiveTile) { + Log.e(TAG, "TaskView is not clickable as a live tile; returning to home.") + return null + } + isClickableAsLiveTile = false + val targets = + if (remoteTargetHandles.size == 1) { + remoteTargetHandles[0].transformParams.targetSet + } else { + val apps = + remoteTargetHandles.flatMap { it.transformParams.targetSet.apps.asIterable() } + val wallpapers = + remoteTargetHandles.flatMap { + it.transformParams.targetSet.wallpapers.asIterable() + } + RemoteAnimationTargets( + apps.toTypedArray(), + wallpapers.toTypedArray(), + remoteTargetHandles[0].transformParams.targetSet.nonApps, + remoteTargetHandles[0].transformParams.targetSet.targetMode + ) + } + if (targets == null) { + // If the recents animation is cancelled somehow between the parent if block and + // here, try to launch the task as a non live tile task. + val runnableList = launchTaskAnimated() + if (runnableList == null) { + Log.e( + TAG, + "Recents animation cancelled and cannot launch task as non-live tile" + + "; returning to home" + ) + } + isClickableAsLiveTile = true + return runnableList + } + val runnableList = RunnableList() + with(AnimatorSet()) { + TaskViewUtils.composeRecentsLaunchAnimator( + this, + this@TaskView, + targets.apps, + targets.wallpapers, + targets.nonApps, + true /* launcherClosing */, + recentsView.stateManager, + recentsView, + recentsView.depthController + ) + addListener( + object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animator: Animator) { + if (taskContainers.any { it.task.key.displayId != rootViewDisplayId }) { + launchTaskAnimated() + } + isClickableAsLiveTile = true + runEndCallback() + } + + override fun onAnimationCancel(animation: Animator) { + runEndCallback() + } + + private fun runEndCallback() { + runnableList.executeAllAndDestroy() + } + } + ) + start() + } + Log.d(TAG, "launchTasks - composeRecentsLaunchAnimator: ${taskIds.contentToString()}") + recentsView.onTaskLaunchedInLiveTileMode() + return runnableList + } + + private fun notifyTaskLaunchFailed() { + val sb = StringBuilder("Failed to launch task \n") taskContainers.forEach { sb.append("(task=${it.task.key.baseIntent} userId=${it.task.key.userId})\n") } @@ -1486,6 +1189,14 @@ constructor( Toast.makeText(context, R.string.activity_not_available, Toast.LENGTH_SHORT).show() } + fun initiateSplitSelect(splitPositionOption: SplitPositionOption) { + recentsView?.initiateSplitSelect( + this, + splitPositionOption.stagePosition, + SplitConfigurationOptions.getLogEventForPosition(splitPositionOption.stagePosition) + ) + } + /** * Returns `true` if user is already in split select mode and this tap was to choose the second * app. `false` otherwise @@ -1501,11 +1212,11 @@ constructor( this, container.task, container.iconView.drawable, - container.snapshotView, - container.thumbnail, - /* intent */ null, - /* user */ null, - container.itemInfo, + container.thumbnailViewDeprecated, + container.thumbnailViewDeprecated.thumbnail, /* intent */ + null, /* user */ + null, + container.itemInfo ) } @@ -1534,38 +1245,12 @@ constructor( return showTaskMenuWithContainer(menuContainer) } - private fun closeTaskMenu(): Boolean { - val floatingView: AbstractFloatingView? = - AbstractFloatingView.getTopOpenViewWithType( - container, - AbstractFloatingView.TYPE_TASK_MENU, - ) - if (floatingView?.isOpen == true) { - floatingView.close(true) - return true - } else { - return false - } - } - private fun showTaskMenuWithContainer(menuContainer: TaskContainer): Boolean { val recentsView = recentsView ?: return false - if (enableHoverOfChildElementsInTaskview()) { - // Disable hover on all TaskView's whilst menu is showing. - recentsView.setTaskBorderEnabled(false) - } return if (enableOverviewIconMenu() && menuContainer.iconView is IconAppChipView) { - if (menuContainer.iconView.status == AppChipStatus.Expanded) { - closeTaskMenu() - } else { - menuContainer.iconView.revealAnim(/* isRevealing= */ true) - TaskMenuView.showForTask(menuContainer) { - val isAnimated = !recentsView.isSplitSelectionActive - menuContainer.iconView.revealAnim(/* isRevealing= */ false, isAnimated) - if (enableHoverOfChildElementsInTaskview()) { - recentsView.setTaskBorderEnabled(true) - } - } + menuContainer.iconView.revealAnim(/* isRevealing= */ true) + TaskMenuView.showForTask(menuContainer) { + menuContainer.iconView.revealAnim(/* isRevealing= */ false) } } else if (container.deviceProfile.isTablet) { val alignedOptionIndex = @@ -1585,17 +1270,9 @@ constructor( } else { 0 } - TaskMenuViewWithArrow.showForTask(menuContainer, alignedOptionIndex) { - if (enableHoverOfChildElementsInTaskview()) { - recentsView.setTaskBorderEnabled(true) - } - } + TaskMenuViewWithArrow.showForTask(menuContainer, alignedOptionIndex) } else { - TaskMenuView.showForTask(menuContainer) { - if (enableHoverOfChildElementsInTaskview()) { - recentsView.setTaskBorderEnabled(true) - } - } + TaskMenuView.showForTask(menuContainer) } } @@ -1618,7 +1295,7 @@ constructor( private fun computeAndSetIconTouchDelegate( view: TaskViewIcon, tempCenterCoordinates: FloatArray, - transformingTouchDelegate: TransformingTouchDelegate, + transformingTouchDelegate: TransformingTouchDelegate ) { val viewHalfWidth = view.width / 2f val viewHalfHeight = view.height / 2f @@ -1629,13 +1306,13 @@ constructor( this[0] = viewHalfWidth this[1] = viewHalfHeight }, - false, + false ) transformingTouchDelegate.setBounds( (tempCenterCoordinates[0] - viewHalfWidth).toInt(), (tempCenterCoordinates[1] - viewHalfHeight).toInt(), (tempCenterCoordinates[0] + viewHalfWidth).toInt(), - (tempCenterCoordinates[1] + viewHalfHeight).toInt(), + (tempCenterCoordinates[1] + viewHalfHeight).toInt() ) } @@ -1645,7 +1322,7 @@ constructor( it.showWindowsView?.let { showWindowsView -> updateFilterCallback( showWindowsView, - getFilterUpdateCallback(it.task.key.packageName), + getFilterUpdateCallback(it.task.key.packageName) ) } } @@ -1678,23 +1355,23 @@ constructor( * Called to animate a smooth transition when going directly from an app into Overview (and vice * versa). Icons fade in, and DWB banners slide in with a "shift up" animation. */ - private fun onSettledProgressUpdated(settledProgress: Float) { + private fun onFocusTransitionProgressUpdated(focusTransitionProgress: Float) { taskContainers.forEach { - it.iconView.setContentAlpha(settledProgress) - it.digitalWellBeingToast?.bannerOffsetPercentage = 1f - settledProgress + it.iconView.setContentAlpha(focusTransitionProgress) + it.digitalWellBeingToast?.updateBannerOffset(1f - focusTransitionProgress) } } - fun startIconFadeInOnGestureComplete() { - iconFadeInOnGestureCompleteAnimator?.cancel() - iconFadeInOnGestureCompleteAnimator = - ObjectAnimator.ofFloat(this, SETTLED_PROGRESS_GESTURE, 1f).apply { - duration = FADE_IN_ICON_DURATION + fun animateIconScaleAndDimIntoView() { + iconAndDimAnimator?.cancel() + iconAndDimAnimator = + ObjectAnimator.ofFloat(focusTransitionScaleAndDim, MULTI_PROPERTY_VALUE, 0f, 1f).apply { + duration = SCALE_ICON_DURATION interpolator = Interpolators.LINEAR addListener( object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator) { - iconFadeInOnGestureCompleteAnimator = null + iconAndDimAnimator = null } } ) @@ -1702,21 +1379,20 @@ constructor( } } - fun setIconVisibleForGesture(isVisible: Boolean) { - iconFadeInOnGestureCompleteAnimator?.cancel() - settledProgressGesture = if (isVisible) 1f else 0f + fun setIconScaleAndDim(iconScale: Float) { + iconAndDimAnimator?.cancel() + focusTransitionScaleAndDim.value = iconScale } /** Set a color tint on the snapshot and supporting views. */ open fun setColorTint(amount: Float, tintColor: Int) { taskContainers.forEach { - if (enableRefactorTaskThumbnail()) { - it.updateTintAmount(amount) - } else { + if (!enableRefactorTaskThumbnail()) { + // TODO(b/334832108) Add scrim to new TTV it.thumbnailViewDeprecated.dimAlpha = amount } it.iconView.setIconColorTint(tintColor, amount) - it.digitalWellBeingToast?.setColorTint(tintColor, amount) + it.digitalWellBeingToast?.setBannerColorTint(tintColor, amount) } } @@ -1730,7 +1406,7 @@ constructor( taskContainers.forEach { if (visibility == VISIBLE || it.task.key.id == taskId) { it.snapshotView.visibility = visibility - it.digitalWellBeingToast?.visibility = visibility + it.digitalWellBeingToast?.setBannerVisibility(visibility) it.showWindowsView?.visibility = visibility it.overlay.setVisibility(visibility) } @@ -1738,13 +1414,16 @@ constructor( } open fun setOverlayEnabled(overlayEnabled: Boolean) { + // TODO(b/335606129) Investigate the usage of [TaskOverlay] in the new TaskThumbnailView. + // and if it's still necessary we should support that in the new TTV class. if (!enableRefactorTaskThumbnail()) { - taskContainers.forEach { it.setOverlayEnabled(overlayEnabled) } + taskContainers.forEach { it.thumbnailViewDeprecated.setOverlayEnabled(overlayEnabled) } } } protected open fun refreshTaskThumbnailSplash() { if (!enableRefactorTaskThumbnail()) { + // TODO(b/334826842) add splash functionality to new TTV taskContainers.forEach { it.thumbnailViewDeprecated.refreshSplashView() } } } @@ -1752,15 +1431,27 @@ constructor( protected fun getScrollAdjustment(gridEnabled: Boolean) = if (gridEnabled) gridTranslationX else nonGridTranslationX - fun getOffsetAdjustment(gridEnabled: Boolean) = getScrollAdjustment(gridEnabled) + protected fun getOffsetAdjustment(gridEnabled: Boolean) = getScrollAdjustment(gridEnabled) fun getSizeAdjustment(fullscreenEnabled: Boolean) = if (fullscreenEnabled) nonGridScale else 1f private fun applyScale() { - val scale = persistentScale * dismissScale * Utilities.mapRange(modalness, 1f, modalScale) + val scale = persistentScale * dismissScale scaleX = scale scaleY = scale - updateFullscreenParams() + if (enableRefactorTaskThumbnail()) { + taskViewData.scale.value = scale + } + updateSnapshotRadius() + } + + protected open fun applyThumbnailSplashAlpha() { + if (!enableRefactorTaskThumbnail()) { + // TODO(b/334826842) add splash functionality to new TTV + taskContainers.forEach { + it.thumbnailViewDeprecated.setSplashAlpha(taskThumbnailSplashAlpha) + } + } } private fun applyTranslationX() { @@ -1790,57 +1481,57 @@ constructor( protected open fun onFullscreenProgressChanged(fullscreenProgress: Float) { taskContainers.forEach { - if (!enableOverviewIconMenu()) { - it.iconView.setVisibility(if (fullscreenProgress < 1) VISIBLE else INVISIBLE) - } + it.iconView.setVisibility(if (fullscreenProgress < 1) VISIBLE else INVISIBLE) it.overlay.setFullscreenProgress(fullscreenProgress) } - settledProgressFullscreen = - SETTLED_PROGRESS_FAST_OUT_INTERPOLATOR.getInterpolation(1 - fullscreenProgress) - updateFullscreenParams() + focusTransitionFullscreen.value = + FOCUS_TRANSITION_FAST_OUT_INTERPOLATOR.getInterpolation(1 - fullscreenProgress) + updateSnapshotRadius() } - protected open fun updateFullscreenParams() { - updateFullscreenParams(thumbnailFullscreenParams) + protected open fun updateSnapshotRadius() { + updateCurrentFullscreenParams() taskContainers.forEach { - if (enableRefactorTaskThumbnail()) { - it.thumbnailView.cornerRadius = thumbnailFullscreenParams.currentCornerRadius - } else { - it.thumbnailViewDeprecated.setFullscreenParams(thumbnailFullscreenParams) - } - it.overlay.setFullscreenParams(thumbnailFullscreenParams) + it.thumbnailViewDeprecated.setFullscreenParams(getThumbnailFullscreenParams()) + it.overlay.setFullscreenParams(getThumbnailFullscreenParams()) } } + protected open fun updateCurrentFullscreenParams() { + updateFullscreenParams(currentFullscreenParams) + } + protected fun updateFullscreenParams(fullscreenParams: FullscreenDrawParams) { recentsView?.let { fullscreenParams.setProgress(fullscreenProgress, it.scaleX, scaleX) } } + protected open fun getThumbnailFullscreenParams(): FullscreenDrawParams = + currentFullscreenParams + private fun onModalnessUpdated(modalness: Float) { - isClickable = modalness == 0f taskContainers.forEach { - it.iconView.setModalAlpha(1f - modalness) - it.digitalWellBeingToast?.bannerOffsetPercentage = modalness - } - if (enableGridOnlyOverview()) { - modalAlpha = if (isSelectedTask) 1f else (1f - modalness) - applyScale() + it.iconView.setModalAlpha(1 - modalness) + it.digitalWellBeingToast?.updateBannerOffset(modalness) } } + /** Updates [TaskThumbnailView] to reflect the latest [Task] state (i.e., task isRunning). */ + fun notifyIsRunningTaskUpdated() { + // TODO(b/335649589): TaskView's VM will already have access to TaskThumbnailView's VM + // so there will be no need to access TaskThumbnailView's VM through the TaskThumbnailView + taskContainers.forEach { it.bindThumbnailView() } + } + fun resetPersistentViewTransforms() { nonGridTranslationX = 0f gridTranslationX = 0f gridTranslationY = 0f boxTranslationY = 0f - taskContainers.forEach { - it.snapshotView.translationX = 0f - it.snapshotView.translationY = 0f - } + nonGridPivotTranslationX = 0f resetViewTransforms() } - fun resetViewTransforms() { + open fun resetViewTransforms() { // fullscreenTranslation and accumulatedTranslation should not be reset, as // resetViewTransforms is called during QuickSwitch scrolling. dismissTranslationX = 0f @@ -1856,9 +1547,13 @@ constructor( } dismissScale = 1f translationZ = 0f - setIconVisibleForGesture(true) - settledProgressDismiss = 1f + alpha = stableAlpha + setIconScaleAndDim(1f) setColorTint(0f, 0) + if (!enableRefactorTaskThumbnail()) { + // TODO(b/335399428) add split select functionality to new TTV + taskContainers.forEach { it.thumbnailViewDeprecated.resetViewTransforms() } + } } private fun getGridTrans(endTranslation: Float) = @@ -1867,93 +1562,243 @@ constructor( private fun getNonGridTrans(endTranslation: Float) = endTranslation - getGridTrans(endTranslation) - private fun MotionEvent.isWithinThumbnailBounds(): Boolean { - return thumbnailBounds.contains(x.toInt(), y.toInt()) + /** We update and subsequently draw these in [fullscreenProgress]. */ + open class FullscreenDrawParams(context: Context) : SafeCloseable { + var cornerRadius = 0f + private var windowCornerRadius = 0f + var currentDrawnCornerRadius = 0f + + init { + updateCornerRadius(context) + } + + /** Recomputes the start and end corner radius for the given Context. */ + fun updateCornerRadius(context: Context) { + cornerRadius = computeTaskCornerRadius(context) + windowCornerRadius = computeWindowCornerRadius(context) + } + + @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) + open fun computeTaskCornerRadius(context: Context): Float { + return TaskCornerRadius.get(context) + } + + @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) + open fun computeWindowCornerRadius(context: Context): Float { + val activityContext: ActivityContext? = ActivityContext.lookupContextNoThrow(context) + + // The corner radius is fixed to match when Taskbar is persistent mode + return if ( + activityContext != null && + activityContext.deviceProfile?.isTaskbarPresent == true && + DisplayController.isTransientTaskbar(context) + ) { + context.resources + .getDimensionPixelSize(R.dimen.persistent_taskbar_corner_radius) + .toFloat() + } else { + QuickStepContract.getWindowCornerRadius(context) + } + } + + /** Sets the progress in range [0, 1] */ + fun setProgress(fullscreenProgress: Float, parentScale: Float, taskViewScale: Float) { + currentDrawnCornerRadius = + Utilities.mapRange(fullscreenProgress, cornerRadius, windowCornerRadius) / + parentScale / + taskViewScale + } + + override fun close() {} } - override fun addChildrenForAccessibility(outChildren: ArrayList) { - (if (isLayoutRtl) taskContainers.reversed() else taskContainers).forEach { - it.addChildForAccessibility(outChildren) + /** Holder for all Task dependent information. */ + inner class TaskContainer( + val task: Task, + val thumbnailView: TaskThumbnailView?, + val thumbnailViewDeprecated: TaskThumbnailViewDeprecated, + val iconView: TaskViewIcon, + /** + * This technically can be a vanilla [android.view.TouchDelegate] class, however that class + * requires setting the touch bounds at construction, so we'd repeatedly be created many + * instances unnecessarily as scrolling occurs, whereas [TransformingTouchDelegate] allows + * touch delegated bounds only to be updated. + */ + val iconTouchDelegate: TransformingTouchDelegate, + /** Defaults to STAGE_POSITION_UNDEFINED if in not a split screen task view */ + @StagePosition val stagePosition: Int, + val digitalWellBeingToast: DigitalWellBeingToast?, + val showWindowsView: View?, + taskOverlayFactory: TaskOverlayFactory + ) { + val overlay: TaskOverlay<*> = taskOverlayFactory.createOverlay(this) + + val snapshotView: View + get() = thumbnailView ?: thumbnailViewDeprecated + + /** Builds proto for logging */ + val itemInfo: WorkspaceItemInfo + get() = + WorkspaceItemInfo().apply { + itemType = LauncherSettings.Favorites.ITEM_TYPE_TASK + container = LauncherSettings.Favorites.CONTAINER_TASKSWITCHER + val componentKey = TaskUtils.getLaunchComponentKeyForTask(task.key) + user = componentKey.user + intent = Intent().setComponent(componentKey.componentName) + title = task.title + recentsView?.let { screenId = it.indexOfChild(this@TaskView) } + if (privateSpaceRestrictAccessibilityDrag()) { + if ( + UserCache.getInstance(context).getUserInfo(componentKey.user).isPrivate + ) { + runtimeStatusFlags = + runtimeStatusFlags or ItemInfoWithIcon.FLAG_NOT_PINNABLE + } + } + } + + val taskView: TaskView + get() = this@TaskView + + fun destroy() { + digitalWellBeingToast?.destroy() + thumbnailView?.let { taskView.removeView(it) } + } + + // TODO(b/335649589): TaskView's VM will already have access to TaskThumbnailView's VM + // so there will be no need to access TaskThumbnailView's VM through the TaskThumbnailView + fun bindThumbnailView() { + // TODO(b/343364498): Existing view has shouldShowScreenshot as an override as well but + // this should be decided inside TaskThumbnailViewModel. + thumbnailView?.viewModel?.bind(TaskThumbnail(task.key.id, isRunningTask)) } } companion object { private const val TAG = "TaskView" - - private enum class Alpha { - Stable, - Attach, - Split, - Modal, - } - - private enum class SettledProgress { - Fullscreen, - Gesture, - Dismiss, - } - const val FLAG_UPDATE_ICON = 1 const val FLAG_UPDATE_THUMBNAIL = FLAG_UPDATE_ICON shl 1 const val FLAG_UPDATE_CORNER_RADIUS = FLAG_UPDATE_THUMBNAIL shl 1 const val FLAG_UPDATE_ALL = (FLAG_UPDATE_ICON or FLAG_UPDATE_THUMBNAIL or FLAG_UPDATE_CORNER_RADIUS) + const val FOCUS_TRANSITION_INDEX_FULLSCREEN = 0 + const val FOCUS_TRANSITION_INDEX_SCALE_AND_DIM = 1 + const val FOCUS_TRANSITION_INDEX_COUNT = 2 + /** The maximum amount that a task view can be scrimmed, dimmed or tinted. */ const val MAX_PAGE_SCRIM_ALPHA = 0.4f - const val FADE_IN_ICON_DURATION: Long = 120 + const val SCALE_ICON_DURATION: Long = 120 private const val DIM_ANIM_DURATION: Long = 700 - private const val SETTLE_TRANSITION_THRESHOLD = - FADE_IN_ICON_DURATION.toFloat() / DIM_ANIM_DURATION - val SETTLED_PROGRESS_FAST_OUT_INTERPOLATOR = + private const val FOCUS_TRANSITION_THRESHOLD = + SCALE_ICON_DURATION.toFloat() / DIM_ANIM_DURATION + val FOCUS_TRANSITION_FAST_OUT_INTERPOLATOR = Interpolators.clampToProgress( Interpolators.FAST_OUT_SLOW_IN, - 1f - SETTLE_TRANSITION_THRESHOLD, - 1f, + 1f - FOCUS_TRANSITION_THRESHOLD, + 1f )!! - private val FADE_IN_ICON_INTERPOLATOR = Interpolators.LINEAR private val SYSTEM_GESTURE_EXCLUSION_RECT = listOf(Rect()) - private val SETTLED_PROGRESS: FloatProperty = - KFloatProperty(TaskView::settledProgress) + private val FOCUS_TRANSITION: FloatProperty = + object : FloatProperty("focusTransition") { + override fun setValue(taskView: TaskView, v: Float) { + taskView.focusTransitionProgress = v + } - private val SETTLED_PROGRESS_GESTURE: FloatProperty = - KFloatProperty(TaskView::settledProgressGesture) - - private val SETTLED_PROGRESS_DISMISS: FloatProperty = - KFloatProperty(TaskView::settledProgressDismiss) + override fun get(taskView: TaskView) = taskView.focusTransitionProgress + } private val SPLIT_SELECT_TRANSLATION_X: FloatProperty = - KFloatProperty(TaskView::splitSelectTranslationX) + object : FloatProperty("splitSelectTranslationX") { + override fun setValue(taskView: TaskView, v: Float) { + taskView.splitSelectTranslationX = v + } + + override fun get(taskView: TaskView) = taskView.splitSelectTranslationX + } private val SPLIT_SELECT_TRANSLATION_Y: FloatProperty = - KFloatProperty(TaskView::splitSelectTranslationY) + object : FloatProperty("splitSelectTranslationY") { + override fun setValue(taskView: TaskView, v: Float) { + taskView.splitSelectTranslationY = v + } + + override fun get(taskView: TaskView) = taskView.splitSelectTranslationY + } private val DISMISS_TRANSLATION_X: FloatProperty = - KFloatProperty(TaskView::dismissTranslationX) + object : FloatProperty("dismissTranslationX") { + override fun setValue(taskView: TaskView, v: Float) { + taskView.dismissTranslationX = v + } + + override fun get(taskView: TaskView) = taskView.dismissTranslationX + } private val DISMISS_TRANSLATION_Y: FloatProperty = - KFloatProperty(TaskView::dismissTranslationY) + object : FloatProperty("dismissTranslationY") { + override fun setValue(taskView: TaskView, v: Float) { + taskView.dismissTranslationY = v + } + + override fun get(taskView: TaskView) = taskView.dismissTranslationY + } private val TASK_OFFSET_TRANSLATION_X: FloatProperty = - KFloatProperty(TaskView::taskOffsetTranslationX) + object : FloatProperty("taskOffsetTranslationX") { + override fun setValue(taskView: TaskView, v: Float) { + taskView.taskOffsetTranslationX = v + } + + override fun get(taskView: TaskView) = taskView.taskOffsetTranslationX + } private val TASK_OFFSET_TRANSLATION_Y: FloatProperty = - KFloatProperty(TaskView::taskOffsetTranslationY) + object : FloatProperty("taskOffsetTranslationY") { + override fun setValue(taskView: TaskView, v: Float) { + taskView.taskOffsetTranslationY = v + } + + override fun get(taskView: TaskView) = taskView.taskOffsetTranslationY + } private val TASK_RESISTANCE_TRANSLATION_X: FloatProperty = - KFloatProperty(TaskView::taskResistanceTranslationX) + object : FloatProperty("taskResistanceTranslationX") { + override fun setValue(taskView: TaskView, v: Float) { + taskView.taskResistanceTranslationX = v + } + + override fun get(taskView: TaskView) = taskView.taskResistanceTranslationX + } private val TASK_RESISTANCE_TRANSLATION_Y: FloatProperty = - KFloatProperty(TaskView::taskResistanceTranslationY) + object : FloatProperty("taskResistanceTranslationY") { + override fun setValue(taskView: TaskView, v: Float) { + taskView.taskResistanceTranslationY = v + } + + override fun get(taskView: TaskView) = taskView.taskResistanceTranslationY + } @JvmField val GRID_END_TRANSLATION_X: FloatProperty = - KFloatProperty(TaskView::gridEndTranslationX) + object : FloatProperty("gridEndTranslationX") { + override fun setValue(taskView: TaskView, v: Float) { + taskView.gridEndTranslationX = v + } + + override fun get(taskView: TaskView) = taskView.gridEndTranslationX + } @JvmField - val DISMISS_SCALE: FloatProperty = KFloatProperty(TaskView::dismissScale) + val DISMISS_SCALE: FloatProperty = + object : FloatProperty("dismissScale") { + override fun setValue(taskView: TaskView, v: Float) { + taskView.dismissScale = v + } - @JvmField val SPLIT_ALPHA: FloatProperty = KFloatProperty(TaskView::splitAlpha) + override fun get(taskView: TaskView) = taskView.dismissScale + } } } diff --git a/quickstep/src/com/android/quickstep/views/TaskViewIcon.java b/quickstep/src/com/android/quickstep/views/TaskViewIcon.java index 80e3a2b932..94739cbc90 100644 --- a/quickstep/src/com/android/quickstep/views/TaskViewIcon.java +++ b/quickstep/src/com/android/quickstep/views/TaskViewIcon.java @@ -47,11 +47,6 @@ public interface TaskViewIcon { */ void setModalAlpha(float alpha); - /** - * Sets the opacity of the view for flex split state. - */ - void setFlexSplitAlpha(float alpha); - /** * Returns this icon view's drawable. */ diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/model/AppEventProducerTest.java b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/AppEventProducerTest.java index 91f9e53eaa..d4dd58040a 100644 --- a/quickstep/tests/multivalentTests/src/com/android/launcher3/model/AppEventProducerTest.java +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/AppEventProducerTest.java @@ -35,13 +35,10 @@ import android.os.UserHandle; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import com.android.launcher3.dagger.LauncherAppComponent; -import com.android.launcher3.dagger.LauncherAppSingleton; import com.android.launcher3.logger.LauncherAtom; import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.pm.UserCache; -import com.android.launcher3.util.AllModulesForTest; -import com.android.launcher3.util.SandboxContext; +import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext; import com.android.launcher3.util.UserIconInfo; import com.android.systemui.shared.system.SysUiStatsLog; @@ -54,9 +51,6 @@ import org.mockito.MockitoAnnotations; import java.util.Arrays; -import dagger.BindsInstance; -import dagger.Component; - @SmallTest @RunWith(AndroidJUnit4.class) public class AppEventProducerTest { @@ -78,9 +72,7 @@ public class AppEventProducerTest { public void setUp() { MockitoAnnotations.initMocks(this); mContext = new SandboxContext(getApplicationContext()); - mContext.initDaggerComponent( - DaggerAppEventProducerTest_TestComponent.builder().bindUserCache(mUserCache) - ); + mContext.putObject(UserCache.INSTANCE, mUserCache); mAppEventProducer = new AppEventProducer(mContext, null); } @@ -137,15 +129,4 @@ public class AppEventProducerTest { .build()); return itemBuilder.build(); } - - @LauncherAppSingleton - @Component(modules = { AllModulesForTest.class }) - interface TestComponent extends LauncherAppComponent { - @Component.Builder - interface Builder extends LauncherAppComponent.Builder { - @BindsInstance - AppEventProducerTest.TestComponent.Builder bindUserCache(UserCache userCache); - @Override LauncherAppComponent build(); - } - } } diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java index a8f3500a72..0f06d98740 100644 --- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java @@ -4,8 +4,6 @@ import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCH import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_BACK_BUTTON_TAP; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_HOME_BUTTON_LONGPRESS; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_HOME_BUTTON_TAP; -import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_LONGPRESS; -import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_TAP; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_OVERVIEW_BUTTON_LONGPRESS; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_OVERVIEW_BUTTON_TAP; import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_A11Y; @@ -15,14 +13,11 @@ import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_IM import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_RECENTS; import static com.android.launcher3.taskbar.TaskbarNavButtonController.SCREEN_PIN_LONG_PRESS_THRESHOLD; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING; -import static com.android.window.flags.Flags.FLAG_PREDICTIVE_BACK_THREE_BUTTON_NAV; import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; @@ -30,12 +25,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.os.Handler; -import android.platform.test.annotations.RequiresFlagsEnabled; -import android.platform.test.flag.junit.CheckFlagsRule; -import android.platform.test.flag.junit.DeviceFlagsValueProvider; -import android.view.KeyEvent; import android.view.View; -import android.view.inputmethod.Flags; import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; @@ -44,14 +34,11 @@ import com.android.launcher3.logging.StatsLogManager; import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarNavButtonCallbacks; import com.android.quickstep.SystemUiProxy; import com.android.quickstep.TouchInteractionService; -import com.android.quickstep.util.ContextualSearchInvoker; -import com.android.systemui.contextualeducation.GestureType; +import com.android.quickstep.util.AssistUtils; 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.MockitoAnnotations; @@ -62,13 +49,12 @@ public class TaskbarNavButtonControllerTest { @Mock SystemUiProxy mockSystemUiProxy; - @Mock TouchInteractionService mockService; @Mock Handler mockHandler; @Mock - ContextualSearchInvoker mockContextualSearchInvoker; + AssistUtils mockAssistUtils; @Mock StatsLogManager mockStatsLogManager; @Mock @@ -80,9 +66,6 @@ public class TaskbarNavButtonControllerTest { @Mock View mockView; - @Rule - public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); - private int mHomePressCount; private int mOverviewToggleCount; private final TaskbarNavButtonCallbacks mCallbacks = new TaskbarNavButtonCallbacks() { @@ -115,45 +98,19 @@ public class TaskbarNavButtonControllerTest { mCallbacks, mockSystemUiProxy, mockHandler, - mockContextualSearchInvoker); + mockAssistUtils); } @Test public void testPressBack() { mNavButtonController.onButtonClick(BUTTON_BACK, mockView); - verify(mockSystemUiProxy, times(1)).onBackEvent(null); - } - - @Test - public void testPressBack_updateContextualEduData() { - mNavButtonController.onButtonClick(BUTTON_BACK, mockView); - verify(mockSystemUiProxy, times(1)) - .updateContextualEduStats(/* isTrackpad= */ eq(false), eq(GestureType.BACK)); + verify(mockSystemUiProxy, times(1)).onBackPressed(); } @Test public void testPressImeSwitcher() { - mNavButtonController.init(mockTaskbarControllers); mNavButtonController.onButtonClick(BUTTON_IME_SWITCH, mockView); - verify(mockStatsLogger, times(1)).log(LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_TAP); - verify(mockStatsLogger, never()).log(LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_LONGPRESS); verify(mockSystemUiProxy, times(1)).onImeSwitcherPressed(); - verify(mockSystemUiProxy, never()).onImeSwitcherLongPress(); - } - - @Test - public void testLongPressImeSwitcher() { - mNavButtonController.init(mockTaskbarControllers); - mNavButtonController.onButtonLongClick(BUTTON_IME_SWITCH, mockView); - verify(mockStatsLogger, never()).log(LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_TAP); - verify(mockSystemUiProxy, never()).onImeSwitcherPressed(); - if (Flags.imeSwitcherRevamp()) { - verify(mockStatsLogger, times(1)).log(LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_LONGPRESS); - verify(mockSystemUiProxy, times(1)).onImeSwitcherLongPress(); - } else { - verify(mockStatsLogger, never()).log(LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_LONGPRESS); - verify(mockSystemUiProxy, never()).onImeSwitcherLongPress(); - } } @Test @@ -172,40 +129,40 @@ public class TaskbarNavButtonControllerTest { @Test public void testLongPressHome_enabled_withoutOverride() { mNavButtonController.setAssistantLongPressEnabled(true /*assistantLongPressEnabled*/); - when(mockContextualSearchInvoker.tryStartAssistOverride(anyInt())).thenReturn(false); + when(mockAssistUtils.tryStartAssistOverride(anyInt())).thenReturn(false); mNavButtonController.onButtonLongClick(BUTTON_HOME, mockView); - verify(mockContextualSearchInvoker, times(1)).tryStartAssistOverride(anyInt()); + verify(mockAssistUtils, times(1)).tryStartAssistOverride(anyInt()); verify(mockSystemUiProxy, times(1)).startAssistant(any()); } @Test public void testLongPressHome_enabled_withOverride() { mNavButtonController.setAssistantLongPressEnabled(true /*assistantLongPressEnabled*/); - when(mockContextualSearchInvoker.tryStartAssistOverride(anyInt())).thenReturn(true); + when(mockAssistUtils.tryStartAssistOverride(anyInt())).thenReturn(true); mNavButtonController.onButtonLongClick(BUTTON_HOME, mockView); - verify(mockContextualSearchInvoker, times(1)).tryStartAssistOverride(anyInt()); + verify(mockAssistUtils, times(1)).tryStartAssistOverride(anyInt()); verify(mockSystemUiProxy, never()).startAssistant(any()); } @Test public void testLongPressHome_disabled_withoutOverride() { mNavButtonController.setAssistantLongPressEnabled(false /*assistantLongPressEnabled*/); - when(mockContextualSearchInvoker.tryStartAssistOverride(anyInt())).thenReturn(false); + when(mockAssistUtils.tryStartAssistOverride(anyInt())).thenReturn(false); mNavButtonController.onButtonLongClick(BUTTON_HOME, mockView); - verify(mockContextualSearchInvoker, never()).tryStartAssistOverride(anyInt()); + verify(mockAssistUtils, never()).tryStartAssistOverride(anyInt()); verify(mockSystemUiProxy, never()).startAssistant(any()); } @Test public void testLongPressHome_disabled_withOverride() { mNavButtonController.setAssistantLongPressEnabled(false /*assistantLongPressEnabled*/); - when(mockContextualSearchInvoker.tryStartAssistOverride(anyInt())).thenReturn(true); + when(mockAssistUtils.tryStartAssistOverride(anyInt())).thenReturn(true); mNavButtonController.onButtonLongClick(BUTTON_HOME, mockView); - verify(mockContextualSearchInvoker, never()).tryStartAssistOverride(anyInt()); + verify(mockAssistUtils, never()).tryStartAssistOverride(anyInt()); verify(mockSystemUiProxy, never()).startAssistant(any()); } @@ -215,26 +172,12 @@ public class TaskbarNavButtonControllerTest { assertThat(mHomePressCount).isEqualTo(1); } - @Test - public void testPressHome_updateContextualEduData() { - mNavButtonController.onButtonClick(BUTTON_HOME, mockView); - verify(mockSystemUiProxy, times(1)) - .updateContextualEduStats(/* isTrackpad= */ eq(false), eq(GestureType.HOME)); - } - @Test public void testPressRecents() { mNavButtonController.onButtonClick(BUTTON_RECENTS, mockView); assertThat(mOverviewToggleCount).isEqualTo(1); } - @Test - public void testPressRecents_updateContextualEduData() { - mNavButtonController.onButtonClick(BUTTON_RECENTS, mockView); - verify(mockSystemUiProxy, times(1)) - .updateContextualEduStats(/* isTrackpad= */ eq(false), eq(GestureType.OVERVIEW)); - } - @Test public void testPressRecentsWithScreenPinned_noNavigationToOverview() { mNavButtonController.updateSysuiFlags(SYSUI_STATE_SCREEN_PINNING); @@ -339,46 +282,4 @@ public class TaskbarNavButtonControllerTest { verify(mockStatsLogger, times(1)).log(LAUNCHER_TASKBAR_BACK_BUTTON_LONGPRESS); verify(mockStatsLogger, times(0)).log(LAUNCHER_TASKBAR_BACK_BUTTON_TAP); } - - @Test - @RequiresFlagsEnabled(FLAG_PREDICTIVE_BACK_THREE_BUTTON_NAV) - public void testPredictiveBackInvoked() { - ArgumentCaptor keyEventCaptor = ArgumentCaptor.forClass(KeyEvent.class); - mNavButtonController.sendBackKeyEvent(KeyEvent.ACTION_DOWN, false); - mNavButtonController.sendBackKeyEvent(KeyEvent.ACTION_UP, false); - verify(mockSystemUiProxy, times(2)).onBackEvent(keyEventCaptor.capture()); - verifyKeyEvent(keyEventCaptor.getAllValues().getFirst(), KeyEvent.ACTION_DOWN, false); - verifyKeyEvent(keyEventCaptor.getAllValues().getLast(), KeyEvent.ACTION_UP, false); - } - - @Test - @RequiresFlagsEnabled(FLAG_PREDICTIVE_BACK_THREE_BUTTON_NAV) - public void testPredictiveBackCancelled() { - ArgumentCaptor keyEventCaptor = ArgumentCaptor.forClass(KeyEvent.class); - mNavButtonController.sendBackKeyEvent(KeyEvent.ACTION_DOWN, false); - mNavButtonController.sendBackKeyEvent(KeyEvent.ACTION_UP, true); - verify(mockSystemUiProxy, times(2)).onBackEvent(keyEventCaptor.capture()); - verifyKeyEvent(keyEventCaptor.getAllValues().getFirst(), KeyEvent.ACTION_DOWN, false); - verifyKeyEvent(keyEventCaptor.getAllValues().getLast(), KeyEvent.ACTION_UP, true); - } - - @Test - @RequiresFlagsEnabled(FLAG_PREDICTIVE_BACK_THREE_BUTTON_NAV) - public void testButtonsDisabledWhileBackPressed() { - mNavButtonController.sendBackKeyEvent(KeyEvent.ACTION_DOWN, false); - mNavButtonController.onButtonClick(BUTTON_HOME, mockView); - mNavButtonController.onButtonClick(BUTTON_RECENTS, mockView); - mNavButtonController.onButtonLongClick(BUTTON_A11Y, mockView); - mNavButtonController.onButtonClick(BUTTON_IME_SWITCH, mockView); - mNavButtonController.sendBackKeyEvent(KeyEvent.ACTION_UP, false); - assertThat(mHomePressCount).isEqualTo(0); - verify(mockSystemUiProxy, never()).notifyAccessibilityButtonLongClicked(); - assertThat(mOverviewToggleCount).isEqualTo(0); - verify(mockSystemUiProxy, never()).onImeSwitcherPressed(); - } - - private void verifyKeyEvent(KeyEvent keyEvent, int action, boolean isCancelled) { - assertEquals(isCancelled, keyEvent.isCanceled()); - assertEquals(action, KeyEvent.ACTION_DOWN, keyEvent.getAction()); - } } diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarUnitTestRule.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarUnitTestRule.kt new file mode 100644 index 0000000000..a999e7f7de --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarUnitTestRule.kt @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.taskbar + +import android.app.Instrumentation +import android.app.PendingIntent +import android.content.IIntentSender +import android.content.Intent +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.rule.ServiceTestRule +import com.android.launcher3.LauncherAppState +import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarNavButtonCallbacks +import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR +import com.android.launcher3.util.LauncherMultivalentJUnit.Companion.isRunningInRobolectric +import com.android.quickstep.AllAppsActionManager +import com.android.quickstep.TouchInteractionService +import com.android.quickstep.TouchInteractionService.TISBinder +import org.junit.Assume.assumeTrue +import org.junit.rules.MethodRule +import org.junit.runners.model.FrameworkMethod +import org.junit.runners.model.Statement + +/** + * Manages the Taskbar lifecycle for unit tests. + * + * See [InjectController] for grabbing controller(s) under test with minimal boilerplate. + * + * The rule interacts with [TaskbarManager] on the main thread. A good rule of thumb for tests is + * that code that is executed on the main thread in production should also happen on that thread + * when tested. + * + * `@UiThreadTest` is a simple way to run an entire test body on the main thread. But if a test + * executes code that appends message(s) to the main thread's `MessageQueue`, the annotation will + * prevent those messages from being processed until after the test body finishes. + * + * To test pending messages, instead use something like [Instrumentation.runOnMainSync] to perform + * only sections of the test body on the main thread synchronously: + * ``` + * @Test + * fun example() { + * instrumentation.runOnMainSync { doWorkThatPostsMessage() } + * // Second lambda will not execute until message is processed. + * instrumentation.runOnMainSync { verifyMessageResults() } + * } + * ``` + */ +class TaskbarUnitTestRule : MethodRule { + private val instrumentation = InstrumentationRegistry.getInstrumentation() + private val serviceTestRule = ServiceTestRule() + + private lateinit var taskbarManager: TaskbarManager + private lateinit var target: Any + + val activityContext: TaskbarActivityContext + get() { + return taskbarManager.currentActivityContext + ?: throw RuntimeException("Failed to obtain TaskbarActivityContext.") + } + + override fun apply(base: Statement, method: FrameworkMethod, target: Any): Statement { + return object : Statement() { + override fun evaluate() { + this@TaskbarUnitTestRule.target = target + + val context = instrumentation.targetContext + instrumentation.runOnMainSync { + assumeTrue( + LauncherAppState.getIDP(context).getDeviceProfile(context).isTaskbarPresent + ) + } + + // Check for existing Taskbar instance from Launcher process. + val launcherTaskbarManager: TaskbarManager? = + if (!isRunningInRobolectric) { + try { + val tisBinder = + serviceTestRule.bindService( + Intent(context, TouchInteractionService::class.java) + ) as? TISBinder + tisBinder?.taskbarManager + } catch (_: Exception) { + null + } + } else { + null + } + + instrumentation.runOnMainSync { + taskbarManager = + TaskbarManager( + context, + AllAppsActionManager(context, UI_HELPER_EXECUTOR) { + PendingIntent(IIntentSender.Default()) + }, + object : TaskbarNavButtonCallbacks {}, + ) + } + + try { + // Replace Launcher Taskbar window with test instance. + instrumentation.runOnMainSync { + launcherTaskbarManager?.removeTaskbarRootViewFromWindow() + taskbarManager.onUserUnlocked() // Required to complete initialization. + } + + injectControllers() + base.evaluate() + } finally { + // Revert Taskbar window. + instrumentation.runOnMainSync { + taskbarManager.destroy() + launcherTaskbarManager?.addTaskbarRootViewToWindow() + } + } + } + } + } + + /** Simulates Taskbar recreation lifecycle. */ + fun recreateTaskbar() { + taskbarManager.recreateTaskbar() + injectControllers() + } + + private fun injectControllers() { + val controllers = activityContext.controllers + val controllerFieldsByType = controllers.javaClass.fields.associateBy { it.type } + target.javaClass.fields + .filter { it.isAnnotationPresent(InjectController::class.java) } + .forEach { + it.set( + target, + controllerFieldsByType[it.type]?.get(controllers) + ?: throw NoSuchElementException("Failed to find controller for ${it.type}"), + ) + } + } + + /** + * Annotates test controller fields to inject the corresponding controllers from the current + * [TaskbarControllers] instance. + * + * Controllers are injected during test setup and upon calling [recreateTaskbar]. + * + * Multiple controllers can be injected if needed. + */ + @Retention(AnnotationRetention.RUNTIME) + @Target(AnnotationTarget.FIELD) + annotation class InjectController +} diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsControllerTest.kt index 60c94a8317..c09dcf27de 100644 --- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsControllerTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsControllerTest.kt @@ -16,25 +16,22 @@ package com.android.launcher3.taskbar.allapps -import android.animation.AnimatorTestRule import android.content.ComponentName import android.content.Intent import android.os.Process +import androidx.test.annotation.UiThreadTest import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import com.android.launcher3.BubbleTextView import com.android.launcher3.appprediction.PredictionRowView import com.android.launcher3.model.data.AppInfo import com.android.launcher3.model.data.WorkspaceItemInfo import com.android.launcher3.notification.NotificationKeyData -import com.android.launcher3.taskbar.TaskbarControllerTestUtil.runOnMainSync +import com.android.launcher3.taskbar.TaskbarUnitTestRule +import com.android.launcher3.taskbar.TaskbarUnitTestRule.InjectController import com.android.launcher3.taskbar.overlay.TaskbarOverlayController -import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule -import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.InjectController -import com.android.launcher3.taskbar.rules.TaskbarWindowSandboxContext import com.android.launcher3.util.LauncherMultivalentJUnit import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices import com.android.launcher3.util.PackageUserKey -import com.android.launcher3.util.TestUtil import com.google.common.truth.Truth.assertThat import org.junit.Rule import org.junit.Test @@ -44,98 +41,89 @@ import org.junit.runner.RunWith @EmulatedDevices(["pixelFoldable2023", "pixelTablet2023"]) class TaskbarAllAppsControllerTest { - @get:Rule(order = 0) val context = TaskbarWindowSandboxContext.create() - @get:Rule(order = 1) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context) - @get:Rule(order = 2) val animatorTestRule = AnimatorTestRule(this) + @get:Rule val taskbarUnitTestRule = TaskbarUnitTestRule() @InjectController lateinit var allAppsController: TaskbarAllAppsController @InjectController lateinit var overlayController: TaskbarOverlayController @Test + @UiThreadTest fun testToggle_once_showsAllApps() { - runOnMainSync { allAppsController.toggle() } + allAppsController.toggle() assertThat(allAppsController.isOpen).isTrue() } @Test + @UiThreadTest fun testToggle_twice_closesAllApps() { - runOnMainSync { - allAppsController.toggle() - allAppsController.toggle() - } + allAppsController.toggle() + allAppsController.toggle() assertThat(allAppsController.isOpen).isFalse() } @Test + @UiThreadTest fun testToggle_taskbarRecreated_allAppsReopened() { - runOnMainSync { allAppsController.toggle() } + allAppsController.toggle() taskbarUnitTestRule.recreateTaskbar() assertThat(allAppsController.isOpen).isTrue() } @Test + @UiThreadTest fun testSetApps_beforeOpened_cachesInfo() { - val overlayContext = - TestUtil.getOnUiThread { - allAppsController.setApps(TEST_APPS, 0, emptyMap()) - allAppsController.toggle() - overlayController.requestWindow() - } + allAppsController.setApps(TEST_APPS, 0, emptyMap()) + allAppsController.toggle() + val overlayContext = overlayController.requestWindow() assertThat(overlayContext.appsView.appsStore.apps).isEqualTo(TEST_APPS) } @Test + @UiThreadTest fun testSetApps_afterOpened_updatesStore() { - val overlayContext = - TestUtil.getOnUiThread { - allAppsController.toggle() - allAppsController.setApps(TEST_APPS, 0, emptyMap()) - overlayController.requestWindow() - } + allAppsController.toggle() + allAppsController.setApps(TEST_APPS, 0, emptyMap()) + val overlayContext = overlayController.requestWindow() assertThat(overlayContext.appsView.appsStore.apps).isEqualTo(TEST_APPS) } @Test + @UiThreadTest fun testSetPredictedApps_beforeOpened_cachesInfo() { + allAppsController.setPredictedApps(TEST_PREDICTED_APPS) + allAppsController.toggle() + val predictedApps = - TestUtil.getOnUiThread { - allAppsController.setPredictedApps(TEST_PREDICTED_APPS) - allAppsController.toggle() - - overlayController - .requestWindow() - .appsView - .floatingHeaderView - .findFixedRowByType(PredictionRowView::class.java) - .predictedApps - } - + overlayController + .requestWindow() + .appsView + .floatingHeaderView + .findFixedRowByType(PredictionRowView::class.java) + .predictedApps assertThat(predictedApps).isEqualTo(TEST_PREDICTED_APPS) } @Test + @UiThreadTest fun testSetPredictedApps_afterOpened_cachesInfo() { + allAppsController.toggle() + allAppsController.setPredictedApps(TEST_PREDICTED_APPS) + val predictedApps = - TestUtil.getOnUiThread { - allAppsController.toggle() - allAppsController.setPredictedApps(TEST_PREDICTED_APPS) - - overlayController - .requestWindow() - .appsView - .floatingHeaderView - .findFixedRowByType(PredictionRowView::class.java) - .predictedApps - } - + overlayController + .requestWindow() + .appsView + .floatingHeaderView + .findFixedRowByType(PredictionRowView::class.java) + .predictedApps assertThat(predictedApps).isEqualTo(TEST_PREDICTED_APPS) } @Test fun testUpdateNotificationDots_appInfo_hasDot() { - runOnMainSync { + getInstrumentation().runOnMainSync { allAppsController.setApps(TEST_APPS, 0, emptyMap()) allAppsController.toggle() taskbarUnitTestRule.activityContext.popupDataProvider.onNotificationPosted( @@ -145,58 +133,41 @@ class TaskbarAllAppsControllerTest { } // Ensure the recycler view fully inflates before trying to grab an icon. - val btv = - TestUtil.getOnUiThread { + getInstrumentation().runOnMainSync { + val btv = overlayController .requestWindow() .appsView .activeRecyclerView .findViewHolderForAdapterPosition(0) ?.itemView as? BubbleTextView - } - assertThat(btv?.hasDot()).isTrue() + assertThat(btv?.hasDot()).isTrue() + } } @Test + @UiThreadTest fun testUpdateNotificationDots_predictedApp_hasDot() { - runOnMainSync { - allAppsController.setPredictedApps(TEST_PREDICTED_APPS) - allAppsController.toggle() - taskbarUnitTestRule.activityContext.popupDataProvider.onNotificationPosted( - PackageUserKey.fromItemInfo(TEST_PREDICTED_APPS[0]), - NotificationKeyData("key"), - ) - } + allAppsController.setPredictedApps(TEST_PREDICTED_APPS) + allAppsController.toggle() - val btv = - TestUtil.getOnUiThread { - overlayController - .requestWindow() - .appsView - .floatingHeaderView - .findFixedRowByType(PredictionRowView::class.java) - .getChildAt(0) as BubbleTextView - } + taskbarUnitTestRule.activityContext.popupDataProvider.onNotificationPosted( + PackageUserKey.fromItemInfo(TEST_PREDICTED_APPS[0]), + NotificationKeyData("key"), + ) + + val predictionRowView = + overlayController + .requestWindow() + .appsView + .floatingHeaderView + .findFixedRowByType(PredictionRowView::class.java) + val btv = predictionRowView.getChildAt(0) as BubbleTextView assertThat(btv.hasDot()).isTrue() } - @Test - fun testToggleSearch_searchEditTextFocused() { - runOnMainSync { allAppsController.toggleSearch() } - runOnMainSync { - // All Apps is now attached to window. Open animation is posted but not started. - } - - runOnMainSync { - // Animation has started. Advance to end of animation. - animatorTestRule.advanceTimeBy(overlayController.openDuration.toLong()) - } - val editText = overlayController.requestWindow().appsView.searchUiManager.editText - assertThat(editText?.hasFocus()).isTrue() - } - - companion object { - val TEST_APPS = + private companion object { + private val TEST_APPS = Array(16) { AppInfo( ComponentName( @@ -209,6 +180,6 @@ class TaskbarAllAppsControllerTest { ) } - val TEST_PREDICTED_APPS = TEST_APPS.take(4).map { WorkspaceItemInfo(it) } + private val TEST_PREDICTED_APPS = TEST_APPS.take(4).map { WorkspaceItemInfo(it) } } } diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt new file mode 100644 index 0000000000..cc579abc9e --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt @@ -0,0 +1,454 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.taskbar.bubbles.animation + +import android.content.Context +import android.graphics.Color +import android.graphics.Path +import android.graphics.drawable.ColorDrawable +import android.view.LayoutInflater +import android.view.View +import android.view.View.VISIBLE +import android.widget.FrameLayout +import androidx.core.graphics.drawable.toBitmap +import androidx.dynamicanimation.animation.DynamicAnimation +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import androidx.test.platform.app.InstrumentationRegistry +import com.android.launcher3.R +import com.android.launcher3.taskbar.bubbles.BubbleBarBubble +import com.android.launcher3.taskbar.bubbles.BubbleBarOverflow +import com.android.launcher3.taskbar.bubbles.BubbleBarView +import com.android.launcher3.taskbar.bubbles.BubbleStashController +import com.android.launcher3.taskbar.bubbles.BubbleView +import com.android.wm.shell.common.bubbles.BubbleInfo +import com.android.wm.shell.shared.animation.PhysicsAnimator +import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.any +import org.mockito.kotlin.atLeastOnce +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +@SmallTest +@RunWith(AndroidJUnit4::class) +class BubbleBarViewAnimatorTest { + + private val context = ApplicationProvider.getApplicationContext() + private lateinit var animatorScheduler: TestBubbleBarViewAnimatorScheduler + private lateinit var overflowView: BubbleView + private lateinit var bubbleView: BubbleView + private lateinit var bubble: BubbleBarBubble + private lateinit var bubbleBarView: BubbleBarView + private lateinit var bubbleStashController: BubbleStashController + + @Before + fun setUp() { + animatorScheduler = TestBubbleBarViewAnimatorScheduler() + PhysicsAnimatorTestUtils.prepareForTest() + } + + @Test + fun animateBubbleInForStashed() { + setUpBubbleBar() + setUpBubbleStashController() + + val handle = View(context) + val handleAnimator = PhysicsAnimator.getInstance(handle) + whenever(bubbleStashController.stashedHandlePhysicsAnimator).thenReturn(handleAnimator) + + val animator = + BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animator.animateBubbleInForStashed(bubble) + } + + // let the animation start and wait for it to complete + InstrumentationRegistry.getInstrumentation().runOnMainSync {} + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y) + + assertThat(handle.alpha).isEqualTo(0) + assertThat(handle.translationY) + .isEqualTo(DIFF_BETWEEN_HANDLE_AND_BAR_CENTERS + BAR_TRANSLATION_Y_FOR_TASKBAR) + assertThat(bubbleBarView.visibility).isEqualTo(VISIBLE) + assertThat(bubbleBarView.scaleX).isEqualTo(1) + assertThat(bubbleBarView.scaleY).isEqualTo(1) + assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_TASKBAR) + assertThat(bubbleBarView.isAnimatingNewBubble).isTrue() + + // execute the hide bubble animation + assertThat(animatorScheduler.delayedBlock).isNotNull() + InstrumentationRegistry.getInstrumentation().runOnMainSync(animatorScheduler.delayedBlock!!) + + // let the animation start and wait for it to complete + InstrumentationRegistry.getInstrumentation().runOnMainSync {} + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y) + + assertThat(handle.alpha).isEqualTo(1) + assertThat(handle.translationY).isEqualTo(0) + assertThat(bubbleBarView.alpha).isEqualTo(0) + assertThat(bubbleBarView.isAnimatingNewBubble).isFalse() + verify(bubbleStashController).stashBubbleBarImmediate() + } + + @Test + fun animateBubbleInForStashed_tapAnimatingBubble() { + setUpBubbleBar() + setUpBubbleStashController() + + val handle = View(context) + val handleAnimator = PhysicsAnimator.getInstance(handle) + whenever(bubbleStashController.stashedHandlePhysicsAnimator).thenReturn(handleAnimator) + + val animator = + BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animator.animateBubbleInForStashed(bubble) + } + + // let the animation start and wait for it to complete + InstrumentationRegistry.getInstrumentation().runOnMainSync {} + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y) + + assertThat(handle.alpha).isEqualTo(0) + assertThat(handle.translationY) + .isEqualTo(DIFF_BETWEEN_HANDLE_AND_BAR_CENTERS + BAR_TRANSLATION_Y_FOR_TASKBAR) + assertThat(bubbleBarView.visibility).isEqualTo(VISIBLE) + assertThat(bubbleBarView.scaleX).isEqualTo(1) + assertThat(bubbleBarView.scaleY).isEqualTo(1) + assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_TASKBAR) + assertThat(bubbleBarView.isAnimatingNewBubble).isTrue() + + verify(bubbleStashController, atLeastOnce()).updateTaskbarTouchRegion() + + // verify the hide bubble animation is pending + assertThat(animatorScheduler.delayedBlock).isNotNull() + + animator.onBubbleBarTouchedWhileAnimating() + + assertThat(animatorScheduler.delayedBlock).isNull() + assertThat(bubbleBarView.alpha).isEqualTo(1) + assertThat(bubbleBarView.visibility).isEqualTo(VISIBLE) + assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_TASKBAR) + assertThat(bubbleBarView.isAnimatingNewBubble).isFalse() + } + + @Test + fun animateBubbleInForStashed_touchTaskbarArea_whileShowing() { + setUpBubbleBar() + setUpBubbleStashController() + + val handle = View(context) + val handleAnimator = PhysicsAnimator.getInstance(handle) + whenever(bubbleStashController.stashedHandlePhysicsAnimator).thenReturn(handleAnimator) + + val animator = + BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animator.animateBubbleInForStashed(bubble) + } + + // wait for the animation to start + InstrumentationRegistry.getInstrumentation().runOnMainSync {} + PhysicsAnimatorTestUtils.blockUntilFirstAnimationFrameWhereTrue(handleAnimator) { true } + + handleAnimator.assertIsRunning() + assertThat(bubbleBarView.isAnimatingNewBubble).isTrue() + // verify the hide bubble animation is pending + assertThat(animatorScheduler.delayedBlock).isNotNull() + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animator.onStashStateChangingWhileAnimating() + } + + // verify that the hide animation was canceled + assertThat(animatorScheduler.delayedBlock).isNull() + assertThat(bubbleBarView.isAnimatingNewBubble).isFalse() + verify(bubbleStashController).onNewBubbleAnimationInterrupted(any(), any()) + + // PhysicsAnimatorTestUtils posts the cancellation to the main thread so we need to wait + // again + InstrumentationRegistry.getInstrumentation().waitForIdleSync() + handleAnimator.assertIsNotRunning() + } + + @Test + fun animateBubbleInForStashed_touchTaskbarArea_whileHiding() { + setUpBubbleBar() + setUpBubbleStashController() + + val handle = View(context) + val handleAnimator = PhysicsAnimator.getInstance(handle) + whenever(bubbleStashController.stashedHandlePhysicsAnimator).thenReturn(handleAnimator) + + val animator = + BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animator.animateBubbleInForStashed(bubble) + } + + // let the animation start and wait for it to complete + InstrumentationRegistry.getInstrumentation().runOnMainSync {} + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y) + + // execute the hide bubble animation + assertThat(animatorScheduler.delayedBlock).isNotNull() + InstrumentationRegistry.getInstrumentation().runOnMainSync(animatorScheduler.delayedBlock!!) + + // wait for the hide animation to start + InstrumentationRegistry.getInstrumentation().runOnMainSync {} + handleAnimator.assertIsRunning() + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animator.onStashStateChangingWhileAnimating() + } + + assertThat(bubbleBarView.isAnimatingNewBubble).isFalse() + verify(bubbleStashController).onNewBubbleAnimationInterrupted(any(), any()) + + // PhysicsAnimatorTestUtils posts the cancellation to the main thread so we need to wait + // again + InstrumentationRegistry.getInstrumentation().waitForIdleSync() + handleAnimator.assertIsNotRunning() + } + + @Test + fun animateBubbleInForStashed_showAnimationCanceled() { + setUpBubbleBar() + setUpBubbleStashController() + + val handle = View(context) + val handleAnimator = PhysicsAnimator.getInstance(handle) + whenever(bubbleStashController.stashedHandlePhysicsAnimator).thenReturn(handleAnimator) + + val animator = + BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animator.animateBubbleInForStashed(bubble) + } + + // wait for the animation to start + InstrumentationRegistry.getInstrumentation().runOnMainSync {} + PhysicsAnimatorTestUtils.blockUntilFirstAnimationFrameWhereTrue(handleAnimator) { true } + + handleAnimator.assertIsRunning() + assertThat(bubbleBarView.isAnimatingNewBubble).isTrue() + assertThat(animatorScheduler.delayedBlock).isNotNull() + + handleAnimator.cancel() + handleAnimator.assertIsNotRunning() + assertThat(bubbleBarView.isAnimatingNewBubble).isFalse() + assertThat(animatorScheduler.delayedBlock).isNull() + } + + @Test + fun animateToInitialState_inApp() { + setUpBubbleBar() + setUpBubbleStashController() + whenever(bubbleStashController.bubbleBarTranslationY) + .thenReturn(BAR_TRANSLATION_Y_FOR_TASKBAR) + + val handle = View(context) + val handleAnimator = PhysicsAnimator.getInstance(handle) + whenever(bubbleStashController.stashedHandlePhysicsAnimator).thenReturn(handleAnimator) + + val barAnimator = PhysicsAnimator.getInstance(bubbleBarView) + + val animator = + BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animator.animateToInitialState(bubble, isInApp = true, isExpanding = false) + } + + InstrumentationRegistry.getInstrumentation().runOnMainSync {} + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y) + + barAnimator.assertIsNotRunning() + assertThat(bubbleBarView.isAnimatingNewBubble).isTrue() + assertThat(bubbleBarView.alpha).isEqualTo(1) + assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_TASKBAR) + + assertThat(animatorScheduler.delayedBlock).isNotNull() + InstrumentationRegistry.getInstrumentation().runOnMainSync(animatorScheduler.delayedBlock!!) + + InstrumentationRegistry.getInstrumentation().runOnMainSync {} + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y) + + InstrumentationRegistry.getInstrumentation().waitForIdleSync() + assertThat(bubbleBarView.isAnimatingNewBubble).isFalse() + assertThat(bubbleBarView.alpha).isEqualTo(0) + assertThat(handle.translationY).isEqualTo(0) + assertThat(handle.alpha).isEqualTo(1) + + verify(bubbleStashController).stashBubbleBarImmediate() + } + + @Test + fun animateToInitialState_inApp_autoExpanding() { + setUpBubbleBar() + setUpBubbleStashController() + whenever(bubbleStashController.bubbleBarTranslationY) + .thenReturn(BAR_TRANSLATION_Y_FOR_TASKBAR) + + val handle = View(context) + val handleAnimator = PhysicsAnimator.getInstance(handle) + whenever(bubbleStashController.stashedHandlePhysicsAnimator).thenReturn(handleAnimator) + + val barAnimator = PhysicsAnimator.getInstance(bubbleBarView) + + val animator = + BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animator.animateToInitialState(bubble, isInApp = true, isExpanding = true) + } + + InstrumentationRegistry.getInstrumentation().runOnMainSync {} + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y) + + barAnimator.assertIsNotRunning() + assertThat(bubbleBarView.isAnimatingNewBubble).isTrue() + assertThat(bubbleBarView.alpha).isEqualTo(1) + assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_TASKBAR) + + assertThat(animatorScheduler.delayedBlock).isNotNull() + InstrumentationRegistry.getInstrumentation().runOnMainSync(animatorScheduler.delayedBlock!!) + + assertThat(bubbleBarView.isAnimatingNewBubble).isFalse() + assertThat(bubbleBarView.alpha).isEqualTo(1) + assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_TASKBAR) + + verify(bubbleStashController).showBubbleBarImmediate() + } + + @Test + fun animateToInitialState_inHome() { + setUpBubbleBar() + setUpBubbleStashController() + whenever(bubbleStashController.bubbleBarTranslationY) + .thenReturn(BAR_TRANSLATION_Y_FOR_HOTSEAT) + + val barAnimator = PhysicsAnimator.getInstance(bubbleBarView) + + val animator = + BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animator.animateToInitialState(bubble, isInApp = false, isExpanding = false) + } + + InstrumentationRegistry.getInstrumentation().runOnMainSync {} + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y) + + barAnimator.assertIsNotRunning() + assertThat(bubbleBarView.isAnimatingNewBubble).isTrue() + assertThat(bubbleBarView.alpha).isEqualTo(1) + assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_HOTSEAT) + + assertThat(animatorScheduler.delayedBlock).isNotNull() + InstrumentationRegistry.getInstrumentation().runOnMainSync(animatorScheduler.delayedBlock!!) + + assertThat(bubbleBarView.isAnimatingNewBubble).isFalse() + assertThat(bubbleBarView.alpha).isEqualTo(1) + assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_HOTSEAT) + + verify(bubbleStashController).showBubbleBarImmediate() + } + + private fun setUpBubbleBar() { + bubbleBarView = BubbleBarView(context) + InstrumentationRegistry.getInstrumentation().runOnMainSync { + bubbleBarView.layoutParams = FrameLayout.LayoutParams(0, 0) + val inflater = LayoutInflater.from(context) + + val bitmap = ColorDrawable(Color.WHITE).toBitmap(width = 20, height = 20) + overflowView = + inflater.inflate(R.layout.bubblebar_item_view, bubbleBarView, false) as BubbleView + overflowView.setOverflow(BubbleBarOverflow(overflowView), bitmap) + bubbleBarView.addView(overflowView) + + val bubbleInfo = + BubbleInfo("key", 0, null, null, 0, context.packageName, null, null, false) + bubbleView = + inflater.inflate(R.layout.bubblebar_item_view, bubbleBarView, false) as BubbleView + bubble = + BubbleBarBubble(bubbleInfo, bubbleView, bitmap, bitmap, Color.WHITE, Path(), "") + bubbleView.setBubble(bubble) + bubbleBarView.addView(bubbleView) + } + InstrumentationRegistry.getInstrumentation().waitForIdleSync() + } + + private fun setUpBubbleStashController() { + bubbleStashController = mock() + whenever(bubbleStashController.isStashed).thenReturn(true) + whenever(bubbleStashController.diffBetweenHandleAndBarCenters) + .thenReturn(DIFF_BETWEEN_HANDLE_AND_BAR_CENTERS) + whenever(bubbleStashController.stashedHandleTranslationForNewBubbleAnimation) + .thenReturn(HANDLE_TRANSLATION) + whenever(bubbleStashController.bubbleBarTranslationYForTaskbar) + .thenReturn(BAR_TRANSLATION_Y_FOR_TASKBAR) + } + + private fun PhysicsAnimator.assertIsRunning() { + InstrumentationRegistry.getInstrumentation().runOnMainSync { + assertThat(isRunning()).isTrue() + } + } + + private fun PhysicsAnimator.assertIsNotRunning() { + InstrumentationRegistry.getInstrumentation().runOnMainSync { + assertThat(isRunning()).isFalse() + } + } + + private class TestBubbleBarViewAnimatorScheduler : BubbleBarViewAnimator.Scheduler { + + var delayedBlock: Runnable? = null + private set + + override fun post(block: Runnable) { + block.run() + } + + override fun postDelayed(delayMillis: Long, block: Runnable) { + check(delayedBlock == null) { "there is already a pending block waiting to run" } + delayedBlock = block + } + + override fun cancel(block: Runnable) { + check(delayedBlock == block) { "the pending block does not match the canceled block" } + delayedBlock = null + } + } +} + +private const val DIFF_BETWEEN_HANDLE_AND_BAR_CENTERS = -20f +private const val HANDLE_TRANSLATION = -30f +private const val BAR_TRANSLATION_Y_FOR_TASKBAR = -50f +private const val BAR_TRANSLATION_Y_FOR_HOTSEAT = -40f diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayControllerTest.kt index 1113129ba2..eebd8f9f16 100644 --- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayControllerTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayControllerTest.kt @@ -18,19 +18,19 @@ package com.android.launcher3.taskbar.overlay import android.app.ActivityManager.RunningTaskInfo import android.view.MotionEvent +import androidx.test.annotation.UiThreadTest +import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import com.android.launcher3.AbstractFloatingView import com.android.launcher3.AbstractFloatingView.TYPE_OPTIONS_POPUP import com.android.launcher3.AbstractFloatingView.TYPE_TASKBAR_ALL_APPS import com.android.launcher3.AbstractFloatingView.TYPE_TASKBAR_OVERLAY_PROXY import com.android.launcher3.AbstractFloatingView.hasOpenView import com.android.launcher3.taskbar.TaskbarActivityContext -import com.android.launcher3.taskbar.TaskbarControllerTestUtil.runOnMainSync -import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule -import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.InjectController -import com.android.launcher3.taskbar.rules.TaskbarWindowSandboxContext +import com.android.launcher3.taskbar.TaskbarUnitTestRule +import com.android.launcher3.taskbar.TaskbarUnitTestRule.InjectController import com.android.launcher3.util.LauncherMultivalentJUnit import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices -import com.android.launcher3.util.TestUtil.getOnUiThread +import com.android.launcher3.views.BaseDragLayer import com.android.systemui.shared.system.TaskStackChangeListeners import com.google.common.truth.Truth.assertThat import org.junit.Rule @@ -41,185 +41,193 @@ import org.junit.runner.RunWith @EmulatedDevices(["pixelFoldable2023"]) class TaskbarOverlayControllerTest { - @get:Rule(order = 0) val context = TaskbarWindowSandboxContext.create() - @get:Rule(order = 1) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context) + @get:Rule val taskbarUnitTestRule = TaskbarUnitTestRule() @InjectController lateinit var overlayController: TaskbarOverlayController private val taskbarContext: TaskbarActivityContext get() = taskbarUnitTestRule.activityContext @Test + @UiThreadTest fun testRequestWindow_twice_reusesWindow() { - val (context1, context2) = - getOnUiThread { - Pair(overlayController.requestWindow(), overlayController.requestWindow()) - } + val context1 = overlayController.requestWindow() + val context2 = overlayController.requestWindow() assertThat(context1).isSameInstanceAs(context2) } @Test + @UiThreadTest fun testRequestWindow_afterHidingExistingWindow_createsNewWindow() { - val context1 = getOnUiThread { overlayController.requestWindow() } - runOnMainSync { overlayController.hideWindow() } + val context1 = overlayController.requestWindow() + overlayController.hideWindow() - val context2 = getOnUiThread { overlayController.requestWindow() } + val context2 = overlayController.requestWindow() assertThat(context1).isNotSameInstanceAs(context2) } @Test + @UiThreadTest fun testRequestWindow_afterHidingOverlay_createsNewWindow() { - val context1 = getOnUiThread { overlayController.requestWindow() } - runOnMainSync { - TestOverlayView.show(context1) - overlayController.hideWindow() - } + val context1 = overlayController.requestWindow() + TestOverlayView.show(context1) + overlayController.hideWindow() - val context2 = getOnUiThread { overlayController.requestWindow() } + val context2 = overlayController.requestWindow() assertThat(context1).isNotSameInstanceAs(context2) } @Test + @UiThreadTest fun testRequestWindow_addsProxyView() { - runOnMainSync { TestOverlayView.show(overlayController.requestWindow()) } + TestOverlayView.show(overlayController.requestWindow()) assertThat(hasOpenView(taskbarContext, TYPE_TASKBAR_OVERLAY_PROXY)).isTrue() } @Test + @UiThreadTest fun testRequestWindow_closeProxyView_closesOverlay() { - val overlay = getOnUiThread { TestOverlayView.show(overlayController.requestWindow()) } - runOnMainSync { - AbstractFloatingView.closeOpenContainer(taskbarContext, TYPE_TASKBAR_OVERLAY_PROXY) - } + val overlay = TestOverlayView.show(overlayController.requestWindow()) + AbstractFloatingView.closeOpenContainer(taskbarContext, TYPE_TASKBAR_OVERLAY_PROXY) assertThat(overlay.isOpen).isFalse() } @Test fun testRequestWindow_attachesDragLayer() { - val dragLayer = getOnUiThread { overlayController.requestWindow().dragLayer } + lateinit var dragLayer: BaseDragLayer<*> + getInstrumentation().runOnMainSync { + dragLayer = overlayController.requestWindow().dragLayer + } + // Allow drag layer to attach before checking. - runOnMainSync { assertThat(dragLayer.isAttachedToWindow).isTrue() } + getInstrumentation().runOnMainSync { assertThat(dragLayer.isAttachedToWindow).isTrue() } } @Test + @UiThreadTest fun testHideWindow_closesOverlay() { - val overlay = getOnUiThread { TestOverlayView.show(overlayController.requestWindow()) } - runOnMainSync { overlayController.hideWindow() } + val overlay = TestOverlayView.show(overlayController.requestWindow()) + overlayController.hideWindow() assertThat(overlay.isOpen).isFalse() } @Test fun testHideWindow_detachesDragLayer() { - val dragLayer = getOnUiThread { overlayController.requestWindow().dragLayer } + lateinit var dragLayer: BaseDragLayer<*> + getInstrumentation().runOnMainSync { + dragLayer = overlayController.requestWindow().dragLayer + } // Wait for drag layer to be attached to window before hiding. - runOnMainSync { + getInstrumentation().runOnMainSync { overlayController.hideWindow() assertThat(dragLayer.isAttachedToWindow).isFalse() } } @Test + @UiThreadTest fun testTwoOverlays_closeOne_windowStaysOpen() { - val (overlay1, overlay2) = - getOnUiThread { - val context = overlayController.requestWindow() - Pair(TestOverlayView.show(context), TestOverlayView.show(context)) - } + val context = overlayController.requestWindow() + val overlay1 = TestOverlayView.show(context) + val overlay2 = TestOverlayView.show(context) - runOnMainSync { overlay1.close(false) } + overlay1.close(false) assertThat(overlay2.isOpen).isTrue() assertThat(hasOpenView(taskbarContext, TYPE_TASKBAR_OVERLAY_PROXY)).isTrue() } @Test + @UiThreadTest fun testTwoOverlays_closeAll_closesWindow() { - val (overlay1, overlay2) = - getOnUiThread { - val context = overlayController.requestWindow() - Pair(TestOverlayView.show(context), TestOverlayView.show(context)) - } + val context = overlayController.requestWindow() + val overlay1 = TestOverlayView.show(context) + val overlay2 = TestOverlayView.show(context) - runOnMainSync { - overlay1.close(false) - overlay2.close(false) - } + overlay1.close(false) + overlay2.close(false) assertThat(hasOpenView(taskbarContext, TYPE_TASKBAR_OVERLAY_PROXY)).isFalse() } @Test + @UiThreadTest fun testRecreateTaskbar_closesWindow() { - runOnMainSync { TestOverlayView.show(overlayController.requestWindow()) } + TestOverlayView.show(overlayController.requestWindow()) taskbarUnitTestRule.recreateTaskbar() assertThat(hasOpenView(taskbarContext, TYPE_TASKBAR_OVERLAY_PROXY)).isFalse() } @Test fun testTaskMovedToFront_closesOverlay() { - val overlay = getOnUiThread { TestOverlayView.show(overlayController.requestWindow()) } + lateinit var overlay: TestOverlayView + getInstrumentation().runOnMainSync { + overlay = TestOverlayView.show(overlayController.requestWindow()) + } + TaskStackChangeListeners.getInstance().listenerImpl.onTaskMovedToFront(RunningTaskInfo()) // Make sure TaskStackChangeListeners' Handler posts the callback before checking state. - runOnMainSync { assertThat(overlay.isOpen).isFalse() } + getInstrumentation().runOnMainSync { assertThat(overlay.isOpen).isFalse() } } @Test fun testTaskStackChanged_allAppsClosed_overlayStaysOpen() { - val overlay = getOnUiThread { TestOverlayView.show(overlayController.requestWindow()) } - runOnMainSync { taskbarContext.controllers.sharedState?.allAppsVisible = false } + lateinit var overlay: TestOverlayView + getInstrumentation().runOnMainSync { + overlay = TestOverlayView.show(overlayController.requestWindow()) + taskbarContext.controllers.sharedState?.allAppsVisible = false + } TaskStackChangeListeners.getInstance().listenerImpl.onTaskStackChanged() - runOnMainSync { assertThat(overlay.isOpen).isTrue() } + getInstrumentation().runOnMainSync { assertThat(overlay.isOpen).isTrue() } } @Test fun testTaskStackChanged_allAppsOpen_closesOverlay() { - val overlay = getOnUiThread { TestOverlayView.show(overlayController.requestWindow()) } - runOnMainSync { taskbarContext.controllers.sharedState?.allAppsVisible = true } + lateinit var overlay: TestOverlayView + getInstrumentation().runOnMainSync { + overlay = TestOverlayView.show(overlayController.requestWindow()) + taskbarContext.controllers.sharedState?.allAppsVisible = true + } TaskStackChangeListeners.getInstance().listenerImpl.onTaskStackChanged() - runOnMainSync { assertThat(overlay.isOpen).isFalse() } + getInstrumentation().runOnMainSync { assertThat(overlay.isOpen).isFalse() } } @Test + @UiThreadTest fun testUpdateLauncherDeviceProfile_overlayNotRebindSafe_closesOverlay() { - val context = getOnUiThread { overlayController.requestWindow() } - val overlay = getOnUiThread { - TestOverlayView.show(context).apply { type = TYPE_OPTIONS_POPUP } - } + val overlayContext = overlayController.requestWindow() + val overlay = TestOverlayView.show(overlayContext).apply { type = TYPE_OPTIONS_POPUP } - runOnMainSync { - overlayController.updateLauncherDeviceProfile( - overlayController.launcherDeviceProfile - .toBuilder(context) - .setGestureMode(false) - .build() - ) - } + overlayController.updateLauncherDeviceProfile( + overlayController.launcherDeviceProfile + .toBuilder(overlayContext) + .setGestureMode(false) + .build() + ) assertThat(overlay.isOpen).isFalse() } @Test + @UiThreadTest fun testUpdateLauncherDeviceProfile_overlayRebindSafe_overlayStaysOpen() { - val context = getOnUiThread { overlayController.requestWindow() } - val overlay = getOnUiThread { - TestOverlayView.show(context).apply { type = TYPE_TASKBAR_ALL_APPS } - } + val overlayContext = overlayController.requestWindow() + val overlay = TestOverlayView.show(overlayContext).apply { type = TYPE_TASKBAR_ALL_APPS } - runOnMainSync { - overlayController.updateLauncherDeviceProfile( - overlayController.launcherDeviceProfile - .toBuilder(context) - .setGestureMode(false) - .build() - ) - } + overlayController.updateLauncherDeviceProfile( + overlayController.launcherDeviceProfile + .toBuilder(overlayContext) + .setGestureMode(false) + .build() + ) assertThat(overlay.isOpen).isTrue() } private class TestOverlayView - private constructor(private val overlayContext: TaskbarOverlayContext) : - AbstractFloatingView(overlayContext, null) { + private constructor( + private val overlayContext: TaskbarOverlayContext, + ) : AbstractFloatingView(overlayContext, null) { var type = TYPE_OPTIONS_POPUP diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/AllAppsActionManagerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/AllAppsActionManagerTest.kt index a1bd107caf..73b35e8a5c 100644 --- a/quickstep/tests/multivalentTests/src/com/android/quickstep/AllAppsActionManagerTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/AllAppsActionManagerTest.kt @@ -18,59 +18,32 @@ package com.android.quickstep import android.app.PendingIntent import android.content.IIntentSender -import android.provider.Settings -import android.provider.Settings.Secure.USER_SETUP_COMPLETE import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.android.launcher3.dagger.LauncherAppComponent -import com.android.launcher3.dagger.LauncherAppSingleton -import com.android.launcher3.util.AllModulesForTest +import androidx.test.platform.app.InstrumentationRegistry import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR -import com.android.launcher3.util.SandboxApplication -import com.android.launcher3.util.SettingsCache -import com.android.launcher3.util.SettingsCacheSandbox import com.android.launcher3.util.TestUtil import com.google.common.truth.Truth.assertThat -import dagger.BindsInstance -import dagger.Component import java.util.concurrent.Semaphore import java.util.concurrent.TimeUnit.SECONDS -import org.junit.After -import org.junit.Before -import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith private const val TIMEOUT = 5L -private val USER_SETUP_COMPLETE_URI = Settings.Secure.getUriFor(USER_SETUP_COMPLETE) @RunWith(AndroidJUnit4::class) class AllAppsActionManagerTest { private val callbackSemaphore = Semaphore(0) private val bgExecutor = UI_HELPER_EXECUTOR - @get:Rule val context = SandboxApplication() - - private val settingsCacheSandbox = - SettingsCacheSandbox().also { it[USER_SETUP_COMPLETE_URI] = 1 } - - private val allAppsActionManager by - lazy(LazyThreadSafetyMode.NONE) { - AllAppsActionManager(context, bgExecutor) { - callbackSemaphore.release() - PendingIntent(IIntentSender.Default()) - } + private val allAppsActionManager = + AllAppsActionManager( + InstrumentationRegistry.getInstrumentation().targetContext, + bgExecutor, + ) { + callbackSemaphore.release() + PendingIntent(IIntentSender.Default()) } - @Before - fun initDaggerComponent() { - context.initDaggerComponent( - DaggerAllAppsActionManagerTestComponent.builder() - .bindSettingsCache(settingsCacheSandbox.cache) - ) - } - - @After fun destroyManager() = allAppsActionManager.onDestroy() - @Test fun taskbarPresent_actionRegistered() { allAppsActionManager.isTaskbarPresent = true @@ -115,50 +88,4 @@ class AllAppsActionManagerTest { assertThat(callbackSemaphore.tryAcquire(TIMEOUT, SECONDS)).isTrue() assertThat(allAppsActionManager.isActionRegistered).isTrue() } - - @Test - fun taskbarPresent_userSetupIncomplete_actionUnregistered() { - settingsCacheSandbox[USER_SETUP_COMPLETE_URI] = 0 - allAppsActionManager.isTaskbarPresent = true - assertThat(allAppsActionManager.isActionRegistered).isFalse() - } - - @Test - fun taskbarPresent_setupUiVisible_actionUnregistered() { - allAppsActionManager.isSetupUiVisible = true - allAppsActionManager.isTaskbarPresent = true - assertThat(allAppsActionManager.isActionRegistered).isFalse() - } - - @Test - fun taskbarPresent_userSetupCompleted_actionRegistered() { - settingsCacheSandbox[USER_SETUP_COMPLETE_URI] = 0 - allAppsActionManager.isTaskbarPresent = true - - settingsCacheSandbox[USER_SETUP_COMPLETE_URI] = 1 - assertThat(callbackSemaphore.tryAcquire(TIMEOUT, SECONDS)).isTrue() - assertThat(allAppsActionManager.isActionRegistered).isTrue() - } - - @Test - fun taskbarPresent_setupUiDismissed_actionRegistered() { - allAppsActionManager.isSetupUiVisible = true - allAppsActionManager.isTaskbarPresent = true - - allAppsActionManager.isSetupUiVisible = false - assertThat(callbackSemaphore.tryAcquire(TIMEOUT, SECONDS)).isTrue() - assertThat(allAppsActionManager.isActionRegistered).isTrue() - } -} - -@LauncherAppSingleton -@Component(modules = [AllModulesForTest::class]) -interface AllAppsActionManagerTestComponent : LauncherAppComponent { - - @Component.Builder - interface Builder : LauncherAppComponent.Builder { - @BindsInstance fun bindSettingsCache(settingsCache: SettingsCache): Builder - - override fun build(): AllAppsActionManagerTestComponent - } } diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/FullscreenDrawParamsTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/FullscreenDrawParamsTest.kt index 99b81e05eb..5d62a4c749 100644 --- a/quickstep/tests/multivalentTests/src/com/android/quickstep/FullscreenDrawParamsTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/FullscreenDrawParamsTest.kt @@ -20,17 +20,21 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.launcher3.FakeInvariantDeviceProfileTest import com.android.quickstep.util.TaskCornerRadius +import com.android.quickstep.views.TaskView.FullscreenDrawParams import com.android.systemui.shared.system.QuickStepContract import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mockito.kotlin.mock +import org.mockito.Mockito.doReturn +import org.mockito.Mockito.mock +import org.mockito.Mockito.spy -/** Test for [FullscreenDrawParams] class. */ +/** Test for FullscreenDrawParams class. */ @SmallTest @RunWith(AndroidJUnit4::class) class FullscreenDrawParamsTest : FakeInvariantDeviceProfileTest() { + private lateinit var params: FullscreenDrawParams @Before @@ -42,108 +46,115 @@ class FullscreenDrawParamsTest : FakeInvariantDeviceProfileTest() { fun setStartProgress_correctCornerRadiusForTablet() { initializeVarsForTablet() - params.setProgress(fullscreenProgress = 0f, parentScale = 1.0f, taskViewScale = 1.0f) + params.setProgress( + /* fullscreenProgress= */ 0f, + /* parentScale= */ 1.0f, + /* taskViewScale= */ 1.0f + ) val expectedRadius = TaskCornerRadius.get(context) - assertThat(params.currentCornerRadius).isEqualTo(expectedRadius) + assertThat(params.currentDrawnCornerRadius).isEqualTo(expectedRadius) } @Test fun setFullProgress_correctCornerRadiusForTablet() { initializeVarsForTablet() - params.setProgress(fullscreenProgress = 1.0f, parentScale = 1f, taskViewScale = 1f) + params.setProgress( + /* fullscreenProgress= */ 1.0f, + /* parentScale= */ 1.0f, + /* taskViewScale= */ 1.0f + ) val expectedRadius = QuickStepContract.getWindowCornerRadius(context) - assertThat(params.currentCornerRadius).isEqualTo(expectedRadius) + assertThat(params.currentDrawnCornerRadius).isEqualTo(expectedRadius) } @Test fun setStartProgress_correctCornerRadiusForPhone() { initializeVarsForPhone() - params.setProgress(fullscreenProgress = 0f, parentScale = 1f, taskViewScale = 1f) + params.setProgress( + /* fullscreenProgress= */ 0f, + /* parentScale= */ 1.0f, + /* taskViewScale= */ 1.0f + ) val expectedRadius = TaskCornerRadius.get(context) - assertThat(params.currentCornerRadius).isEqualTo(expectedRadius) + assertThat(params.currentDrawnCornerRadius).isEqualTo(expectedRadius) } @Test fun setFullProgress_correctCornerRadiusForPhone() { initializeVarsForPhone() - params.setProgress(fullscreenProgress = 1.0f, parentScale = 1f, taskViewScale = 1f) + params.setProgress( + /* fullscreenProgress= */ 1.0f, + /* parentScale= */ 1.0f, + /* taskViewScale= */ 1.0f + ) val expectedRadius = QuickStepContract.getWindowCornerRadius(context) - assertThat(params.currentCornerRadius).isEqualTo(expectedRadius) + assertThat(params.currentDrawnCornerRadius).isEqualTo(expectedRadius) } @Test fun setStartProgress_correctCornerRadiusForMultiDisplay() { - val display1Context = mock() - val display2Context = mock() - val display1TaskRadius = TASK_CORNER_RADIUS + 1 - val display2TaskRadius = TASK_CORNER_RADIUS + 2 + val display1Context = context + val display2Context = mock(Context::class.java) + val spyParams = spy(params) - val params = - FullscreenDrawParams( - context, - taskCornerRadiusProvider = { context -> - when (context) { - display1Context -> display1TaskRadius - display2Context -> display2TaskRadius - else -> TASK_CORNER_RADIUS - } - }, - windowCornerRadiusProvider = { 0f }, - ) + val display1TaskRadius = TaskCornerRadius.get(display1Context) + val display1WindowRadius = QuickStepContract.getWindowCornerRadius(display1Context) + val display2TaskRadius = display1TaskRadius * 2 + 1 // Arbitrarily different. + val display2WindowRadius = display1WindowRadius * 2 + 1 // Arbitrarily different. + doReturn(display2TaskRadius).`when`(spyParams).computeTaskCornerRadius(display2Context) + doReturn(display2WindowRadius).`when`(spyParams).computeWindowCornerRadius(display2Context) - params.setProgress(fullscreenProgress = 0f, parentScale = 1f, taskViewScale = 1f) - assertThat(params.currentCornerRadius).isEqualTo(TASK_CORNER_RADIUS) + spyParams.updateCornerRadius(display1Context) + spyParams.setProgress( + /* fullscreenProgress= */ 0f, + /* parentScale= */ 1.0f, + /* taskViewScale= */ 1.0f + ) + assertThat(spyParams.currentDrawnCornerRadius).isEqualTo(display1TaskRadius) - params.updateCornerRadius(display1Context) - params.setProgress(fullscreenProgress = 0f, parentScale = 1f, taskViewScale = 1f) - assertThat(params.currentCornerRadius).isEqualTo(display1TaskRadius) - - params.updateCornerRadius(display2Context) - params.setProgress(fullscreenProgress = 0f, parentScale = 1f, taskViewScale = 1f) - assertThat(params.currentCornerRadius).isEqualTo(display2TaskRadius) + spyParams.updateCornerRadius(display2Context) + spyParams.setProgress( + /* fullscreenProgress= */ 0f, + /* parentScale= */ 1.0f, + /* taskViewScale= */ 1.0f + ) + assertThat(spyParams.currentDrawnCornerRadius).isEqualTo(display2TaskRadius) } @Test fun setFullProgress_correctCornerRadiusForMultiDisplay() { - val display1Context = mock() - val display2Context = mock() - val display1WindowRadius = WINDOW_CORNER_RADIUS + 1 - val display2WindowRadius = WINDOW_CORNER_RADIUS + 2 + val display1Context = context + val display2Context = mock(Context::class.java) + val spyParams = spy(params) - val params = - FullscreenDrawParams( - context, - taskCornerRadiusProvider = { 0f }, - windowCornerRadiusProvider = { context -> - when (context) { - display1Context -> display1WindowRadius - display2Context -> display2WindowRadius - else -> WINDOW_CORNER_RADIUS - } - }, - ) + val display1TaskRadius = TaskCornerRadius.get(display1Context) + val display1WindowRadius = QuickStepContract.getWindowCornerRadius(display1Context) + val display2TaskRadius = display1TaskRadius * 2 + 1 // Arbitrarily different. + val display2WindowRadius = display1WindowRadius * 2 + 1 // Arbitrarily different. + doReturn(display2TaskRadius).`when`(spyParams).computeTaskCornerRadius(display2Context) + doReturn(display2WindowRadius).`when`(spyParams).computeWindowCornerRadius(display2Context) - params.setProgress(fullscreenProgress = 1f, parentScale = 1f, taskViewScale = 1f) - assertThat(params.currentCornerRadius).isEqualTo(WINDOW_CORNER_RADIUS) + spyParams.updateCornerRadius(display1Context) + spyParams.setProgress( + /* fullscreenProgress= */ 1.0f, + /* parentScale= */ 1.0f, + /* taskViewScale= */ 1.0f + ) + assertThat(spyParams.currentDrawnCornerRadius).isEqualTo(display1WindowRadius) - params.updateCornerRadius(display1Context) - params.setProgress(fullscreenProgress = 1f, parentScale = 1f, taskViewScale = 1f) - assertThat(params.currentCornerRadius).isEqualTo(display1WindowRadius) - - params.updateCornerRadius(display2Context) - params.setProgress(fullscreenProgress = 1f, parentScale = 1f, taskViewScale = 1f) - assertThat(params.currentCornerRadius).isEqualTo(display2WindowRadius) - } - - companion object { - const val TASK_CORNER_RADIUS = 56f - const val WINDOW_CORNER_RADIUS = 32f + spyParams.updateCornerRadius(display2Context) + spyParams.setProgress( + /* fullscreenProgress= */ 1.0f, + /* parentScale= */ 1.0f, + /* taskViewScale= */ 1.0f, + ) + assertThat(spyParams.currentDrawnCornerRadius).isEqualTo(display2WindowRadius) } } diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/orientation/LandscapePagedViewHandlerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/orientation/LandscapePagedViewHandlerTest.kt index 66b3b047d7..ea52842e22 100644 --- a/quickstep/tests/multivalentTests/src/com/android/quickstep/orientation/LandscapePagedViewHandlerTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/orientation/LandscapePagedViewHandlerTest.kt @@ -43,12 +43,12 @@ class LandscapePagedViewHandlerTest { if (isEnabled) { setFlagsRule.enableFlags( Flags.FLAG_ENABLE_GRID_ONLY_OVERVIEW, - Flags.FLAG_ENABLE_OVERVIEW_ICON_MENU, + Flags.FLAG_ENABLE_OVERVIEW_ICON_MENU ) } else { setFlagsRule.disableFlags( Flags.FLAG_ENABLE_GRID_ONLY_OVERVIEW, - Flags.FLAG_ENABLE_OVERVIEW_ICON_MENU, + Flags.FLAG_ENABLE_OVERVIEW_ICON_MENU ) } } @@ -62,7 +62,6 @@ class LandscapePagedViewHandlerTest { isRTL, OVERVIEW_TASK_MARGIN_PX, DIVIDER_SIZE_PX, - oneIconHiddenDueToSmallWidth = false, ) } @@ -108,8 +107,14 @@ class LandscapePagedViewHandlerTest { val (topLeftY, bottomRightY) = getSplitIconsPosition(isRTL = true) - assertThat(topLeftY).isEqualTo(-316) - assertThat(bottomRightY).isEqualTo(0) + // TODO(b/326377497): When started in fake seascape and rotated to landscape, + // the icon chips are in RTL and wrongly positioned at the right side of the snapshot. + // Top-Left app chip should be placed at the top left of the first snapshot, but because + // this issue, it's displayed at the top-right of the second snapshot. + // The Bottom-Right app chip is displayed at the top-right of the first snapshot because + // of this issue. + assertThat(topLeftY).isEqualTo(0) + assertThat(bottomRightY).isEqualTo(-316) } /** Test updateSplitIconsPosition */ diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/orientation/SeascapePagedViewHandlerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/orientation/SeascapePagedViewHandlerTest.kt index d455b0d1e9..2bc182c02d 100644 --- a/quickstep/tests/multivalentTests/src/com/android/quickstep/orientation/SeascapePagedViewHandlerTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/orientation/SeascapePagedViewHandlerTest.kt @@ -43,12 +43,12 @@ class SeascapePagedViewHandlerTest { if (isEnabled) { setFlagsRule.enableFlags( Flags.FLAG_ENABLE_GRID_ONLY_OVERVIEW, - Flags.FLAG_ENABLE_OVERVIEW_ICON_MENU, + Flags.FLAG_ENABLE_OVERVIEW_ICON_MENU ) } else { setFlagsRule.disableFlags( Flags.FLAG_ENABLE_GRID_ONLY_OVERVIEW, - Flags.FLAG_ENABLE_OVERVIEW_ICON_MENU, + Flags.FLAG_ENABLE_OVERVIEW_ICON_MENU ) } } @@ -62,7 +62,6 @@ class SeascapePagedViewHandlerTest { isRTL, OVERVIEW_TASK_MARGIN_PX, DIVIDER_SIZE_PX, - oneIconHiddenDueToSmallWidth = false, ) } @@ -110,6 +109,12 @@ class SeascapePagedViewHandlerTest { val (topLeftY, bottomRightY) = getSplitIconsPosition(isRTL = true) + // TODO(b/326377497): When started in fake seascape and rotated to landscape, + // the icon chips are in RTL and wrongly positioned at the right side of the snapshot. + // Top-Left app chip should be placed at the top left of the first snapshot, but because + // this issue, it's displayed at the top-right of the second snapshot. + // The Bottom-Right app chip is displayed at the top-right of the first snapshot because + // of this issue. assertThat(topLeftY).isEqualTo(316) assertThat(bottomRightY).isEqualTo(0) } @@ -161,7 +166,7 @@ class SeascapePagedViewHandlerTest { `when`(iconView.layoutParams).thenReturn(frameLayout) sut.updateSplitIconsPosition(iconView, expectedTranslationY, false) - assertThat(frameLayout.gravity).isEqualTo(Gravity.BOTTOM or Gravity.END) + assertThat(frameLayout.gravity).isEqualTo(Gravity.BOTTOM or Gravity.START) verify(iconView).setSplitTranslationX(0f) verify(iconView).setSplitTranslationY(expectedTranslationY.toFloat()) } @@ -176,7 +181,7 @@ class SeascapePagedViewHandlerTest { `when`(iconView.layoutParams).thenReturn(frameLayout) sut.updateSplitIconsPosition(iconView, expectedTranslationY, true) - assertThat(frameLayout.gravity).isEqualTo(Gravity.TOP or Gravity.START) + assertThat(frameLayout.gravity).isEqualTo(Gravity.TOP or Gravity.END) verify(iconView).setSplitTranslationX(0f) verify(iconView).setSplitTranslationY(expectedTranslationY.toFloat()) } diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeRecentTasksDataSource.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeRecentTasksDataSource.kt index 9e99a0bd62..eaeb513ea5 100644 --- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeRecentTasksDataSource.kt +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeRecentTasksDataSource.kt @@ -20,12 +20,10 @@ import com.android.quickstep.util.GroupTask import java.util.function.Consumer class FakeRecentTasksDataSource : RecentTasksDataSource { - private var taskList: List = listOf() + var taskList: List = listOf() override fun getTasks(callback: Consumer>?): Int { - // Makes a copy of the GroupTask to create a new GroupTask instance and to simulate - // RecentsModel::getTasks behavior. - callback?.accept(taskList.map { it.copy() }) + callback?.accept(taskList) return 0 } diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskThumbnailDataSource.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskThumbnailDataSource.kt index 40d5e0264c..b66b7351bf 100644 --- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskThumbnailDataSource.kt +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskThumbnailDataSource.kt @@ -17,41 +17,36 @@ package com.android.quickstep.recents.data import android.graphics.Bitmap +import com.android.launcher3.util.CancellableTask import com.android.quickstep.task.thumbnail.data.TaskThumbnailDataSource import com.android.systemui.shared.recents.model.Task import com.android.systemui.shared.recents.model.ThumbnailData -import kotlinx.coroutines.yield +import java.util.function.Consumer import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever class FakeTaskThumbnailDataSource : TaskThumbnailDataSource { - val taskIdToBitmap: MutableMap = - (0..10).associateWith { mock() }.toMutableMap() - private val completionPrevented: MutableSet = mutableSetOf() - private val getThumbnailCalls = mutableMapOf() - - var highResEnabled = true + val taskIdToBitmap: Map = (0..10).associateWith { mock() } + val taskIdToUpdatingTask: MutableMap Unit> = mutableMapOf() + var shouldLoadSynchronously: Boolean = true /** Retrieves and sets a thumbnail on [task] from [taskIdToBitmap]. */ - override suspend fun getThumbnail(task: Task): ThumbnailData { - getThumbnailCalls[task.key.id] = (getThumbnailCalls[task.key.id] ?: 0) + 1 - - while (task.key.id in completionPrevented) { - yield() + override fun updateThumbnailInBackground( + task: Task, + callback: Consumer + ): CancellableTask? { + val thumbnailData = mock() + whenever(thumbnailData.thumbnail).thenReturn(taskIdToBitmap[task.key.id]) + val wrappedCallback = { + task.thumbnail = thumbnailData + callback.accept(thumbnailData) } - return ThumbnailData( - thumbnail = taskIdToBitmap[task.key.id], - reducedResolution = !highResEnabled, - ) - } - - fun getNumberOfGetThumbnailCalls(taskId: Int): Int = getThumbnailCalls[taskId] ?: 0 - - fun preventThumbnailLoad(taskId: Int) { - completionPrevented.add(taskId) - } - - fun completeLoadingForTask(taskId: Int) { - completionPrevented.remove(taskId) + if (shouldLoadSynchronously) { + wrappedCallback() + } else { + taskIdToUpdatingTask[task.key.id] = wrappedCallback + } + return null } } diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt index 35af29f2c6..e160627d75 100644 --- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt @@ -16,55 +16,25 @@ package com.android.quickstep.recents.data -import android.graphics.drawable.Drawable import com.android.systemui.shared.recents.model.Task import com.android.systemui.shared.recents.model.ThumbnailData import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map class FakeTasksRepository : RecentTasksRepository { private var thumbnailDataMap: Map = emptyMap() - private var taskIconDataMap: Map = emptyMap() private var tasks: MutableStateFlow> = MutableStateFlow(emptyList()) - private var visibleTasks: MutableStateFlow> = MutableStateFlow(emptySet()) + private var visibleTasks: MutableStateFlow> = MutableStateFlow(emptyList()) override fun getAllTaskData(forceRefresh: Boolean): Flow> = tasks override fun getTaskDataById(taskId: Int): Flow = - combine(getAllTaskData(), visibleTasks) { taskList, visibleTasks -> - taskList.filter { visibleTasks.contains(it.key.id) } - } - .map { taskList -> - val task = taskList.firstOrNull { it.key.id == taskId } ?: return@map null - Task(task).apply { - thumbnail = task.thumbnail - icon = task.icon - titleDescription = task.titleDescription - title = task.title - } - } + getAllTaskData().map { taskList -> taskList.firstOrNull { it.key.id == taskId } } - override fun getThumbnailById(taskId: Int): Flow = - getTaskDataById(taskId).map { it?.thumbnail } - - override fun getCurrentThumbnailById(taskId: Int): ThumbnailData? = - tasks.value.firstOrNull { it.key.id == taskId }?.thumbnail - - override fun setVisibleTasks(visibleTaskIdList: Set) { + override fun setVisibleTasks(visibleTaskIdList: List) { visibleTasks.value = visibleTaskIdList - tasks.value = - tasks.value.map { - it.apply { - thumbnail = thumbnailDataMap[it.key.id] - taskIconDataMap[it.key.id]?.let { data -> - title = data.title - titleDescription = data.titleDescription - icon = data.icon - } - } - } + tasks.value = tasks.value.map { it.apply { thumbnail = thumbnailDataMap[it.key.id] } } } fun seedTasks(tasks: List) { @@ -74,15 +44,4 @@ class FakeTasksRepository : RecentTasksRepository { fun seedThumbnailData(thumbnailDataMap: Map) { this.thumbnailDataMap = thumbnailDataMap } - - fun seedIconData(id: Int, title: String, contentDescription: String, icon: Drawable) { - val iconData = FakeIconData(icon, contentDescription, title) - this.taskIconDataMap = mapOf(id to iconData) - } - - private data class FakeIconData( - val icon: Drawable, - val titleDescription: String, - val title: String, - ) } diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt index e22892c9cc..c28a85a8f8 100644 --- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt @@ -18,455 +18,133 @@ package com.android.quickstep.recents.data import android.content.ComponentName import android.content.Intent -import android.graphics.Bitmap -import android.graphics.Rect -import android.graphics.drawable.Drawable -import android.view.Display.DEFAULT_DISPLAY -import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.android.launcher3.util.SplitConfigurationOptions -import com.android.launcher3.util.TestDispatcherProvider +import com.android.quickstep.TaskIconCache import com.android.quickstep.util.DesktopTask -import com.android.quickstep.util.SingleTask -import com.android.quickstep.util.SplitTask +import com.android.quickstep.util.GroupTask import com.android.systemui.shared.recents.model.Task -import com.android.systemui.shared.recents.model.ThumbnailData -import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50 import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.toList import kotlinx.coroutines.launch -import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.Mockito.spy import org.mockito.kotlin.mock -import org.mockito.kotlin.verify -import org.mockito.kotlin.whenever @OptIn(ExperimentalCoroutinesApi::class) -@RunWith(AndroidJUnit4::class) class TasksRepositoryTest { private val tasks = (0..5).map(::createTaskWithId) private val defaultTaskList = listOf( - SingleTask(tasks[0]), - SplitTask( - tasks[1], - tasks[2], - SplitConfigurationOptions.SplitBounds( - /* leftTopBounds = */ Rect(), - /* rightBottomBounds = */ Rect(), - /* leftTopTaskId = */ -1, - /* rightBottomTaskId = */ -1, - /* snapPosition = */ SNAP_TO_2_50_50, - ), - ), - DesktopTask(deskId = 0, DEFAULT_DISPLAY, tasks.subList(3, 6)), + GroupTask(tasks[0]), + GroupTask(tasks[1], tasks[2], null), + DesktopTask(tasks.subList(3, 6)) ) private val recentsModel = FakeRecentTasksDataSource() private val taskThumbnailDataSource = FakeTaskThumbnailDataSource() - private val taskIconDataSource = FakeTaskIconDataSource() - private val taskVisualsChangeNotifier = FakeTaskVisualsChangeNotifier() - private val highResLoadingStateNotifier = FakeHighResLoadingStateNotifier() - private val taskVisualsChangedDelegate = - spy(TaskVisualsChangedDelegateImpl(taskVisualsChangeNotifier, highResLoadingStateNotifier)) + private val taskIconCache = mock() - private val dispatcher = UnconfinedTestDispatcher() - private val testScope = TestScope(dispatcher) private val systemUnderTest = - TasksRepository( - recentsModel, - taskThumbnailDataSource, - taskIconDataSource, - taskVisualsChangedDelegate, - testScope.backgroundScope, - TestDispatcherProvider(dispatcher), - ) + TasksRepository(recentsModel, taskThumbnailDataSource, taskIconCache) @Test - fun getAllTaskDataReturnsFlattenedListOfTasks() = - testScope.runTest { - recentsModel.seedTasks(defaultTaskList) - assertThat(systemUnderTest.getAllTaskData(forceRefresh = true).first()).isEqualTo(tasks) - } + fun getAllTaskDataReturnsFlattenedListOfTasks() = runTest { + recentsModel.seedTasks(defaultTaskList) + + assertThat(systemUnderTest.getAllTaskData(forceRefresh = true).first()).isEqualTo(tasks) + } @Test - fun getTaskDataByIdReturnsSpecificTask() = - testScope.runTest { - recentsModel.seedTasks(defaultTaskList) - systemUnderTest.getAllTaskData(forceRefresh = true) + fun getTaskDataByIdReturnsSpecificTask() = runTest { + recentsModel.seedTasks(defaultTaskList) + systemUnderTest.getAllTaskData(forceRefresh = true) - assertThat(systemUnderTest.getTaskDataById(2).first()).isEqualTo(tasks[2]) - } + assertThat(systemUnderTest.getTaskDataById(2).first()).isEqualTo(tasks[2]) + } @Test - fun getThumbnailByIdReturnsNullWithNoLoadedThumbnails() = - testScope.runTest { - recentsModel.seedTasks(defaultTaskList) - systemUnderTest.getAllTaskData(forceRefresh = true) + fun setVisibleTasksPopulatesThumbnails() = runTest { + recentsModel.seedTasks(defaultTaskList) + val bitmap1 = taskThumbnailDataSource.taskIdToBitmap[1] + val bitmap2 = taskThumbnailDataSource.taskIdToBitmap[2] + systemUnderTest.getAllTaskData(forceRefresh = true) - assertThat(systemUnderTest.getThumbnailById(1).first()).isNull() - } + systemUnderTest.setVisibleTasks(listOf(1, 2)) + + // .drop(1) to ignore initial null content before from thumbnail was loaded. + assertThat(systemUnderTest.getTaskDataById(1).drop(1).first()!!.thumbnail!!.thumbnail) + .isEqualTo(bitmap1) + assertThat(systemUnderTest.getTaskDataById(2).first()!!.thumbnail!!.thumbnail) + .isEqualTo(bitmap2) + } @Test - fun getCurrentThumbnailByIdReturnsNullWithNoLoadedThumbnails() = - testScope.runTest { - recentsModel.seedTasks(defaultTaskList) - systemUnderTest.getAllTaskData(forceRefresh = true) + fun changingVisibleTasksContainsAlreadyPopulatedThumbnails() = runTest { + recentsModel.seedTasks(defaultTaskList) + val bitmap2 = taskThumbnailDataSource.taskIdToBitmap[2] + systemUnderTest.getAllTaskData(forceRefresh = true) - assertThat(systemUnderTest.getCurrentThumbnailById(1)).isNull() - } + systemUnderTest.setVisibleTasks(listOf(1, 2)) + + // .drop(1) to ignore initial null content before from thumbnail was loaded. + assertThat(systemUnderTest.getTaskDataById(2).drop(1).first()!!.thumbnail!!.thumbnail) + .isEqualTo(bitmap2) + + // Prevent new loading of Bitmaps + taskThumbnailDataSource.shouldLoadSynchronously = false + systemUnderTest.setVisibleTasks(listOf(2, 3)) + + assertThat(systemUnderTest.getTaskDataById(2).first()!!.thumbnail!!.thumbnail) + .isEqualTo(bitmap2) + } @Test - fun getThumbnailByIdReturnsThumbnailWithLoadedThumbnails() = - testScope.runTest { - recentsModel.seedTasks(defaultTaskList) - systemUnderTest.getAllTaskData(forceRefresh = true) - val bitmap1 = taskThumbnailDataSource.taskIdToBitmap[1] + fun retrievedThumbnailsAreDiscardedWhenTaskBecomesInvisible() = runTest { + recentsModel.seedTasks(defaultTaskList) + val bitmap2 = taskThumbnailDataSource.taskIdToBitmap[2] + systemUnderTest.getAllTaskData(forceRefresh = true) - systemUnderTest.setVisibleTasks(setOf(1)) + systemUnderTest.setVisibleTasks(listOf(1, 2)) - assertThat(systemUnderTest.getThumbnailById(1).first()!!.thumbnail).isEqualTo(bitmap1) - } + // .drop(1) to ignore initial null content before from thumbnail was loaded. + assertThat(systemUnderTest.getTaskDataById(2).drop(1).first()!!.thumbnail!!.thumbnail) + .isEqualTo(bitmap2) + + // Prevent new loading of Bitmaps + taskThumbnailDataSource.shouldLoadSynchronously = false + systemUnderTest.setVisibleTasks(listOf(0, 1)) + + assertThat(systemUnderTest.getTaskDataById(2).first()!!.thumbnail).isNull() + } @Test - fun whenThumbnailIsLoaded_getAllTaskData_usesPreviousLoadedThumbnailAndIcon() = - testScope.runTest { - recentsModel.seedTasks(defaultTaskList) - systemUnderTest.getAllTaskData(forceRefresh = true) - val bitmap1 = taskThumbnailDataSource.taskIdToBitmap[1] + fun retrievedThumbnailsCauseEmissionOnTaskDataFlow() = runTest { + // Setup fakes + recentsModel.seedTasks(defaultTaskList) + val bitmap2 = taskThumbnailDataSource.taskIdToBitmap[2] + taskThumbnailDataSource.shouldLoadSynchronously = false - systemUnderTest.setVisibleTasks(setOf(1)) - assertThat(systemUnderTest.getThumbnailById(1).first()!!.thumbnail).isEqualTo(bitmap1) + // Setup TasksRepository + systemUnderTest.getAllTaskData(forceRefresh = true) + systemUnderTest.setVisibleTasks(listOf(1, 2)) - systemUnderTest.getAllTaskData(forceRefresh = true) - assertThat(systemUnderTest.getThumbnailById(1).first()!!.thumbnail).isEqualTo(bitmap1) + // Assert there is no bitmap in first emission + val taskFlow = systemUnderTest.getTaskDataById(2) + val taskFlowValuesList = mutableListOf() + backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) { + taskFlow.toList(taskFlowValuesList) } + assertThat(taskFlowValuesList[0]!!.thumbnail).isNull() - @Test - fun getAllTaskData_clearsPreviouslyLoadedImagesForRemovedTasks() = - testScope.runTest { - // Setup data - recentsModel.seedTasks(defaultTaskList) - systemUnderTest.getAllTaskData(forceRefresh = true) - val bitmap1 = taskThumbnailDataSource.taskIdToBitmap[1] + // Simulate bitmap loading after first emission + taskThumbnailDataSource.taskIdToUpdatingTask.getValue(2).invoke() - // Load images for task 1 - systemUnderTest.setVisibleTasks(setOf(1)) - assertThat(systemUnderTest.getThumbnailById(1).first()!!.thumbnail).isEqualTo(bitmap1) - - // Remove task 1 from "all data" - recentsModel.seedTasks( - defaultTaskList.filterNot { groupTask -> groupTask.tasks.any { it.key.id == 1 } } - ) - systemUnderTest.getAllTaskData(forceRefresh = true) - - // Assert task 1 was fully removed - assertThat(systemUnderTest.getThumbnailById(1).first()?.thumbnail).isNull() - verify(taskVisualsChangedDelegate).unregisterTaskThumbnailChangedCallback(tasks[1].key) - } - - @Test - fun getCurrentThumbnailByIdReturnsThumbnailWithLoadedThumbnails() = - testScope.runTest { - recentsModel.seedTasks(defaultTaskList) - systemUnderTest.getAllTaskData(forceRefresh = true) - val bitmap1 = taskThumbnailDataSource.taskIdToBitmap[1] - - systemUnderTest.setVisibleTasks(setOf(1)) - - assertThat(systemUnderTest.getCurrentThumbnailById(1)?.thumbnail).isEqualTo(bitmap1) - } - - @Test - fun setVisibleTasksPopulatesThumbnails() = - testScope.runTest { - recentsModel.seedTasks(defaultTaskList) - val bitmap1 = taskThumbnailDataSource.taskIdToBitmap[1] - val bitmap2 = taskThumbnailDataSource.taskIdToBitmap[2] - systemUnderTest.getAllTaskData(forceRefresh = true) - - systemUnderTest.setVisibleTasks(setOf(1, 2)) - - assertThat(systemUnderTest.getTaskDataById(1).first()!!.thumbnail!!.thumbnail) - .isEqualTo(bitmap1) - assertThat(systemUnderTest.getTaskDataById(2).first()!!.thumbnail!!.thumbnail) - .isEqualTo(bitmap2) - } - - @Test - fun setVisibleTasksPopulatesIcons() = - testScope.runTest { - recentsModel.seedTasks(defaultTaskList) - systemUnderTest.getAllTaskData(forceRefresh = true) - - systemUnderTest.setVisibleTasks(setOf(1, 2)) - - systemUnderTest - .getTaskDataById(1) - .first()!! - .assertHasIconDataFromSource(taskIconDataSource) - systemUnderTest - .getTaskDataById(2) - .first()!! - .assertHasIconDataFromSource(taskIconDataSource) - } - - @Test - fun changingVisibleTasksContainsAlreadyPopulatedThumbnails() = - testScope.runTest { - recentsModel.seedTasks(defaultTaskList) - val bitmap2 = taskThumbnailDataSource.taskIdToBitmap[2] - systemUnderTest.getAllTaskData(forceRefresh = true) - - systemUnderTest.setVisibleTasks(setOf(1, 2)) - - assertThat(systemUnderTest.getTaskDataById(2).first()!!.thumbnail!!.thumbnail) - .isEqualTo(bitmap2) - - // Prevent new loading of Bitmaps - taskThumbnailDataSource.preventThumbnailLoad(2) - systemUnderTest.setVisibleTasks(setOf(2, 3)) - - assertThat(systemUnderTest.getTaskDataById(2).first()!!.thumbnail!!.thumbnail) - .isEqualTo(bitmap2) - } - - @Test - fun changingVisibleTasksContainsAlreadyPopulatedIcons() = - testScope.runTest { - recentsModel.seedTasks(defaultTaskList) - systemUnderTest.getAllTaskData(forceRefresh = true) - - systemUnderTest.setVisibleTasks(setOf(1, 2)) - - systemUnderTest - .getTaskDataById(2) - .first()!! - .assertHasIconDataFromSource(taskIconDataSource) - - // Prevent new loading of Drawables - taskIconDataSource.preventIconLoad(2) - systemUnderTest.setVisibleTasks(setOf(2, 3)) - - systemUnderTest - .getTaskDataById(2) - .first()!! - .assertHasIconDataFromSource(taskIconDataSource) - } - - @Test - fun retrievedImagesAreDiscardedWhenTaskBecomesInvisible() = - testScope.runTest { - recentsModel.seedTasks(defaultTaskList) - val bitmap2 = taskThumbnailDataSource.taskIdToBitmap[2] - systemUnderTest.getAllTaskData(forceRefresh = true) - - systemUnderTest.setVisibleTasks(setOf(1, 2)) - - val task2 = systemUnderTest.getTaskDataById(2).first()!! - assertThat(task2.thumbnail!!.thumbnail).isEqualTo(bitmap2) - task2.assertHasIconDataFromSource(taskIconDataSource) - - systemUnderTest.setVisibleTasks(setOf(0, 1)) - - val task2AfterVisibleTasksChanged = systemUnderTest.getTaskDataById(2).first()!! - assertThat(task2AfterVisibleTasksChanged.thumbnail).isNull() - assertThat(task2AfterVisibleTasksChanged.icon).isNull() - assertThat(task2AfterVisibleTasksChanged.titleDescription).isNull() - assertThat(task2AfterVisibleTasksChanged.title).isNull() - } - - @Test - fun retrievedThumbnailsCauseEmissionOnTaskDataFlow() = - testScope.runTest { - // Setup fakes - recentsModel.seedTasks(defaultTaskList) - val bitmap2 = taskThumbnailDataSource.taskIdToBitmap[2] - - // Setup TasksRepository - systemUnderTest.getAllTaskData(forceRefresh = true) - - val task2DataFlow = systemUnderTest.getTaskDataById(2) - val task2BitmapValues = mutableListOf() - testScope.backgroundScope.launch { - task2DataFlow.map { it?.thumbnail?.thumbnail }.toList(task2BitmapValues) - } - - // Check for first emission - assertThat(task2BitmapValues.single()).isNull() - - systemUnderTest.setVisibleTasks(setOf(2)) - // Check for second emission - assertThat(task2BitmapValues).isEqualTo(listOf(null, bitmap2)) - } - - @Test - fun onTaskThumbnailChanged_setsNewThumbnailDataOnTask() = - testScope.runTest { - recentsModel.seedTasks(defaultTaskList) - systemUnderTest.getAllTaskData(forceRefresh = true) - systemUnderTest.setVisibleTasks(setOf(1)) - - val expectedThumbnailData = createThumbnailData() - val expectedPreviousBitmap = taskThumbnailDataSource.taskIdToBitmap[1] - val taskDataFlow = systemUnderTest.getTaskDataById(1) - - val task1ThumbnailValues = mutableListOf() - testScope.backgroundScope.launch { - taskDataFlow.map { it?.thumbnail }.toList(task1ThumbnailValues) - } - taskVisualsChangedDelegate.onTaskThumbnailChanged(1, expectedThumbnailData) - - assertThat(task1ThumbnailValues.first()!!.thumbnail).isEqualTo(expectedPreviousBitmap) - assertThat(task1ThumbnailValues.last()).isEqualTo(expectedThumbnailData) - } - - @Test - fun onHighResLoadingStateChanged_highResReplacesLowResThumbnail() = - testScope.runTest { - taskThumbnailDataSource.highResEnabled = false - recentsModel.seedTasks(defaultTaskList) - systemUnderTest.getAllTaskData(forceRefresh = true) - - systemUnderTest.setVisibleTasks(setOf(1)) - - val expectedBitmap = mock() - val expectedPreviousBitmap = taskThumbnailDataSource.taskIdToBitmap[1] - val taskDataFlow = systemUnderTest.getTaskDataById(1) - - val task1ThumbnailValues = mutableListOf() - testScope.backgroundScope.launch { - taskDataFlow.map { it?.thumbnail }.toList(task1ThumbnailValues) - } - - taskThumbnailDataSource.taskIdToBitmap[1] = expectedBitmap - taskThumbnailDataSource.highResEnabled = true - taskVisualsChangedDelegate.onHighResLoadingStateChanged(true) - - val firstThumbnailValue = task1ThumbnailValues.first()!! - assertThat(firstThumbnailValue.thumbnail).isEqualTo(expectedPreviousBitmap) - assertThat(firstThumbnailValue.reducedResolution).isTrue() - - val lastThumbnailValue = task1ThumbnailValues.last()!! - assertThat(lastThumbnailValue.thumbnail).isEqualTo(expectedBitmap) - assertThat(lastThumbnailValue.reducedResolution).isFalse() - } - - @Test - fun onHighResLoadingStateChanged_invisibleTaskIgnored() = - testScope.runTest { - taskThumbnailDataSource.highResEnabled = false - recentsModel.seedTasks(defaultTaskList) - systemUnderTest.getAllTaskData(forceRefresh = true) - - systemUnderTest.setVisibleTasks(setOf(1)) - - val invisibleTaskId = 2 - val taskDataFlow = systemUnderTest.getTaskDataById(invisibleTaskId) - - val task2ThumbnailValues = mutableListOf() - testScope.backgroundScope.launch { - taskDataFlow.map { it?.thumbnail }.toList(task2ThumbnailValues) - } - - taskThumbnailDataSource.highResEnabled = true - taskVisualsChangedDelegate.onHighResLoadingStateChanged(true) - - assertThat(task2ThumbnailValues.filterNotNull()).isEmpty() - assertThat(taskThumbnailDataSource.getNumberOfGetThumbnailCalls(2)).isEqualTo(0) - } - - @Test - fun onHighResLoadingStateChanged_lowResDoesNotReplaceHighResThumbnail() = - testScope.runTest { - taskThumbnailDataSource.highResEnabled = true - recentsModel.seedTasks(defaultTaskList) - systemUnderTest.getAllTaskData(forceRefresh = true) - - systemUnderTest.setVisibleTasks(setOf(1)) - - val expectedBitmap = mock() - val expectedPreviousBitmap = taskThumbnailDataSource.taskIdToBitmap[1] - val taskDataFlow = systemUnderTest.getTaskDataById(1) - - val task1ThumbnailValues = mutableListOf() - testScope.backgroundScope.launch { - taskDataFlow.map { it?.thumbnail }.toList(task1ThumbnailValues) - } - - taskThumbnailDataSource.taskIdToBitmap[1] = expectedBitmap - taskThumbnailDataSource.highResEnabled = false - taskVisualsChangedDelegate.onHighResLoadingStateChanged(false) - - val firstThumbnailValue = task1ThumbnailValues.first()!! - assertThat(firstThumbnailValue.thumbnail).isEqualTo(expectedPreviousBitmap) - assertThat(firstThumbnailValue.reducedResolution).isFalse() - - val lastThumbnailValue = task1ThumbnailValues.last()!! - assertThat(lastThumbnailValue.thumbnail).isEqualTo(expectedPreviousBitmap) - assertThat(lastThumbnailValue.reducedResolution).isFalse() - } - - @Test - fun onTaskIconChanged_setsNewIconOnTask() = - testScope.runTest { - recentsModel.seedTasks(defaultTaskList) - systemUnderTest.getAllTaskData(forceRefresh = true) - - systemUnderTest.setVisibleTasks(setOf(1)) - - val expectedIcon = FakeTaskIconDataSource.mockCopyableDrawable() - val expectedPreviousIcon = taskIconDataSource.taskIdToDrawable[1] - val taskDataFlow = systemUnderTest.getTaskDataById(1) - - val task1IconValues = mutableListOf() - testScope.backgroundScope.launch { - taskDataFlow.map { it?.icon }.toList(task1IconValues) - } - taskIconDataSource.taskIdToDrawable[1] = expectedIcon - taskVisualsChangedDelegate.onTaskIconChanged(1) - - assertThat(task1IconValues.first()).isEqualTo(expectedPreviousIcon) - assertThat(task1IconValues.last()).isEqualTo(expectedIcon) - } - - @Test - fun setVisibleTasks_multipleTimesWithDifferentTasks_reusesThumbnailRequests() = - testScope.runTest { - recentsModel.seedTasks(defaultTaskList) - systemUnderTest.getAllTaskData(forceRefresh = true) - - val taskDataFlow = systemUnderTest.getTaskDataById(1) - val task1IconValues = mutableListOf() - testScope.backgroundScope.launch { - taskDataFlow.map { it?.icon }.toList(task1IconValues) - } - - systemUnderTest.setVisibleTasks(setOf(1)) - assertThat(taskThumbnailDataSource.getNumberOfGetThumbnailCalls(1)).isEqualTo(1) - - systemUnderTest.setVisibleTasks(setOf(1, 2)) - assertThat(taskThumbnailDataSource.getNumberOfGetThumbnailCalls(1)).isEqualTo(1) - } + // Check for second emission + assertThat(taskFlowValuesList[1]!!.thumbnail!!.thumbnail).isEqualTo(bitmap2) + } private fun createTaskWithId(taskId: Int) = Task(Task.TaskKey(taskId, 0, Intent(), ComponentName("", ""), 0, 2000)) - - private fun createThumbnailData(): ThumbnailData { - val bitmap = mock() - whenever(bitmap.width).thenReturn(THUMBNAIL_WIDTH) - whenever(bitmap.height).thenReturn(THUMBNAIL_HEIGHT) - - return ThumbnailData(thumbnail = bitmap) - } - - companion object { - const val THUMBNAIL_WIDTH = 100 - const val THUMBNAIL_HEIGHT = 200 - } } diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelTest.kt new file mode 100644 index 0000000000..3b8754c200 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelTest.kt @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.quickstep.task.thumbnail + +import android.content.ComponentName +import android.content.Intent +import android.graphics.Bitmap +import android.graphics.Color +import android.graphics.Rect +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.quickstep.recents.data.FakeTasksRepository +import com.android.quickstep.recents.viewmodel.RecentsViewData +import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.BackgroundOnly +import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.LiveTile +import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Snapshot +import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized +import com.android.quickstep.task.viewmodel.TaskViewData +import com.android.systemui.shared.recents.model.Task +import com.android.systemui.shared.recents.model.ThumbnailData +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever + +@RunWith(AndroidJUnit4::class) +class TaskThumbnailViewModelTest { + private val recentsViewData = RecentsViewData() + private val taskViewData = TaskViewData() + private val tasksRepository = FakeTasksRepository() + private val systemUnderTest = + TaskThumbnailViewModel(recentsViewData, taskViewData, tasksRepository) + + private val tasks = (0..5).map(::createTaskWithId) + + @Test + fun initialStateIsUninitialized() = runTest { + assertThat(systemUnderTest.uiState.first()).isEqualTo(Uninitialized) + } + + @Test + fun bindRunningTask_thenStateIs_LiveTile() = runTest { + tasksRepository.seedTasks(tasks) + val taskThumbnail = TaskThumbnail(taskId = 1, isRunning = true) + systemUnderTest.bind(taskThumbnail) + + assertThat(systemUnderTest.uiState.first()).isEqualTo(LiveTile) + } + + @Test + fun setRecentsFullscreenProgress_thenProgressIsPassedThrough() = runTest { + recentsViewData.fullscreenProgress.value = 0.5f + + assertThat(systemUnderTest.recentsFullscreenProgress.first()).isEqualTo(0.5f) + + recentsViewData.fullscreenProgress.value = 0.6f + + assertThat(systemUnderTest.recentsFullscreenProgress.first()).isEqualTo(0.6f) + } + + @Test + fun setAncestorScales_thenScaleIsCalculated() = runTest { + recentsViewData.scale.value = 0.5f + taskViewData.scale.value = 0.6f + + assertThat(systemUnderTest.inheritedScale.first()).isEqualTo(0.3f) + } + + @Test + fun bindRunningTaskThenStoppedTaskWithoutThumbnail_thenStateChangesToBackgroundOnly() = + runTest { + tasksRepository.seedTasks(tasks) + val runningTask = TaskThumbnail(taskId = 1, isRunning = true) + val stoppedTask = TaskThumbnail(taskId = 2, isRunning = false) + systemUnderTest.bind(runningTask) + assertThat(systemUnderTest.uiState.first()).isEqualTo(LiveTile) + + systemUnderTest.bind(stoppedTask) + assertThat(systemUnderTest.uiState.first()) + .isEqualTo(BackgroundOnly(backgroundColor = Color.rgb(2, 2, 2))) + } + + @Test + fun bindStoppedTaskWithoutThumbnail_thenStateIs_BackgroundOnly_withAlphaRemoved() = runTest { + tasksRepository.seedTasks(tasks) + val stoppedTask = TaskThumbnail(taskId = 2, isRunning = false) + + systemUnderTest.bind(stoppedTask) + assertThat(systemUnderTest.uiState.first()) + .isEqualTo(BackgroundOnly(backgroundColor = Color.rgb(2, 2, 2))) + } + + @Test + fun bindLockedTaskWithThumbnail_thenStateIs_BackgroundOnly() = runTest { + tasksRepository.seedThumbnailData(mapOf(2 to createThumbnailData())) + tasks[2].isLocked = true + tasksRepository.seedTasks(tasks) + val recentTask = TaskThumbnail(taskId = 2, isRunning = false) + + systemUnderTest.bind(recentTask) + assertThat(systemUnderTest.uiState.first()) + .isEqualTo(BackgroundOnly(backgroundColor = Color.rgb(2, 2, 2))) + } + + @Test + fun bindStoppedTaskWithThumbnail_thenStateIs_Snapshot_withAlphaRemoved() = runTest { + val expectedThumbnailData = createThumbnailData() + tasksRepository.seedThumbnailData(mapOf(2 to expectedThumbnailData)) + tasksRepository.seedTasks(tasks) + tasksRepository.setVisibleTasks(listOf(2)) + val recentTask = TaskThumbnail(taskId = 2, isRunning = false) + + systemUnderTest.bind(recentTask) + assertThat(systemUnderTest.uiState.first()) + .isEqualTo( + Snapshot( + backgroundColor = Color.rgb(2, 2, 2), + bitmap = expectedThumbnailData.thumbnail!!, + drawnRect = Rect(0, 0, THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT) + ) + ) + } + + @Test + fun bindNonVisibleStoppedTask_whenMadeVisible_thenStateIsSnapshot() = runTest { + val expectedThumbnailData = createThumbnailData() + tasksRepository.seedThumbnailData(mapOf(2 to expectedThumbnailData)) + tasksRepository.seedTasks(tasks) + val recentTask = TaskThumbnail(taskId = 2, isRunning = false) + + systemUnderTest.bind(recentTask) + assertThat(systemUnderTest.uiState.first()) + .isEqualTo(BackgroundOnly(backgroundColor = Color.rgb(2, 2, 2))) + tasksRepository.setVisibleTasks(listOf(2)) + assertThat(systemUnderTest.uiState.first()) + .isEqualTo( + Snapshot( + backgroundColor = Color.rgb(2, 2, 2), + bitmap = expectedThumbnailData.thumbnail!!, + drawnRect = Rect(0, 0, THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT) + ) + ) + } + + private fun createTaskWithId(taskId: Int) = + Task(Task.TaskKey(taskId, 0, Intent(), ComponentName("", ""), 0, 2000)).apply { + colorBackground = Color.argb(taskId, taskId, taskId, taskId) + } + + private fun createThumbnailData(): ThumbnailData { + val bitmap = mock() + whenever(bitmap.width).thenReturn(THUMBNAIL_WIDTH) + whenever(bitmap.height).thenReturn(THUMBNAIL_HEIGHT) + + return ThumbnailData(thumbnail = bitmap) + } + + companion object { + const val THUMBNAIL_WIDTH = 100 + const val THUMBNAIL_HEIGHT = 200 + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/AppPairsControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/AppPairsControllerTest.kt index c325af4d3e..ece67aff62 100644 --- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/AppPairsControllerTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/AppPairsControllerTest.kt @@ -16,10 +16,7 @@ package com.android.quickstep.util -import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM -import android.content.res.Resources -import android.view.Display -import android.view.Display.DEFAULT_DISPLAY +import android.content.Context import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.launcher3.apppairs.AppPairIcon import com.android.launcher3.logging.StatsLogManager @@ -27,15 +24,13 @@ import com.android.launcher3.model.data.ItemInfo import com.android.launcher3.taskbar.TaskbarActivityContext import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT -import com.android.launcher3.views.ActivityContext import com.android.quickstep.TopTaskTracker import com.android.quickstep.TopTaskTracker.CachedTaskInfo import com.android.systemui.shared.recents.model.Task import com.android.systemui.shared.recents.model.Task.TaskKey -import com.android.wm.shell.shared.desktopmode.DesktopModeStatus -import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_33_66 -import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50 -import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_66_33 +import com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_30_70 +import com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50 +import com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_70_30 import java.util.function.Consumer import org.junit.Assert.assertEquals import org.junit.Before @@ -58,34 +53,32 @@ import org.mockito.kotlin.whenever @RunWith(AndroidJUnit4::class) class AppPairsControllerTest { - @Mock lateinit var context: ActivityContext - @Mock lateinit var resources: Resources + @Mock lateinit var context: Context @Mock lateinit var splitSelectStateController: SplitSelectStateController @Mock lateinit var statsLogManager: StatsLogManager private lateinit var appPairsController: AppPairsController - private val left33: Int by lazy { - appPairsController.encodeRank(STAGE_POSITION_TOP_OR_LEFT, SNAP_TO_2_33_66) + private val left30: Int by lazy { + appPairsController.encodeRank(STAGE_POSITION_TOP_OR_LEFT, SNAP_TO_30_70) } private val left50: Int by lazy { - appPairsController.encodeRank(STAGE_POSITION_TOP_OR_LEFT, SNAP_TO_2_50_50) + appPairsController.encodeRank(STAGE_POSITION_TOP_OR_LEFT, SNAP_TO_50_50) } - private val left66: Int by lazy { - appPairsController.encodeRank(STAGE_POSITION_TOP_OR_LEFT, SNAP_TO_2_66_33) + private val left70: Int by lazy { + appPairsController.encodeRank(STAGE_POSITION_TOP_OR_LEFT, SNAP_TO_70_30) } - private val right33: Int by lazy { - appPairsController.encodeRank(STAGE_POSITION_BOTTOM_OR_RIGHT, SNAP_TO_2_33_66) + private val right30: Int by lazy { + appPairsController.encodeRank(STAGE_POSITION_BOTTOM_OR_RIGHT, SNAP_TO_30_70) } private val right50: Int by lazy { - appPairsController.encodeRank(STAGE_POSITION_BOTTOM_OR_RIGHT, SNAP_TO_2_50_50) + appPairsController.encodeRank(STAGE_POSITION_BOTTOM_OR_RIGHT, SNAP_TO_50_50) } - private val right66: Int by lazy { - appPairsController.encodeRank(STAGE_POSITION_BOTTOM_OR_RIGHT, SNAP_TO_2_66_33) + private val right70: Int by lazy { + appPairsController.encodeRank(STAGE_POSITION_BOTTOM_OR_RIGHT, SNAP_TO_70_30) } @Mock lateinit var mockAppPairIcon: AppPairIcon - @Mock lateinit var mockDisplay: Display @Mock lateinit var mockTaskbarActivityContext: TaskbarActivityContext @Mock lateinit var mockTopTaskTracker: TopTaskTracker @Mock lateinit var mockCachedTaskInfo: CachedTaskInfo @@ -108,42 +101,38 @@ class AppPairsControllerTest { // Stub methods on appPairsController so that they return mocks spyAppPairsController = spy(appPairsController) whenever(mockAppPairIcon.context).thenReturn(mockTaskbarActivityContext) - whenever(mockAppPairIcon.display).thenReturn(mockDisplay) - whenever(mockDisplay.displayId).thenReturn(DEFAULT_DISPLAY) doReturn(mockTopTaskTracker).whenever(spyAppPairsController).topTaskTracker - whenever(mockTopTaskTracker.getCachedTopTask(any(), any())).thenReturn(mockCachedTaskInfo) + whenever(mockTopTaskTracker.getCachedTopTask(any())).thenReturn(mockCachedTaskInfo) whenever(mockTask1.getKey()).thenReturn(mockTaskKey1) whenever(mockTask2.getKey()).thenReturn(mockTaskKey2) doNothing().whenever(spyAppPairsController).launchAppPair(any(), any()) doNothing() .whenever(spyAppPairsController) .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull()) - whenever(mockAppPairIcon.context.resources).thenReturn(resources) - whenever(DesktopModeStatus.canEnterDesktopMode(mockAppPairIcon.context)).thenReturn(false) } @Test fun shouldEncodeRankCorrectly() { - assertEquals("left + 33-66 should encode as 0 (0b0)", 0, left33) + assertEquals("left + 30-70 should encode as 0 (0b0)", 0, left30) assertEquals("left + 50-50 should encode as 1 (0b1)", 1, left50) - assertEquals("left + 66-33 should encode as 2 (0b10)", 2, left66) + assertEquals("left + 70-30 should encode as 2 (0b10)", 2, left70) // See AppPairsController#BITMASK_SIZE and BITMASK_FOR_SNAP_POSITION for context - assertEquals("right + 33-66 should encode as 1 followed by 16 0s", 1 shl 16, right33) + assertEquals("right + 30-70 should encode as 1 followed by 16 0s", 1 shl 16, right30) assertEquals("right + 50-50 should encode as the above value + 1", (1 shl 16) + 1, right50) - assertEquals("right + 66-33 should encode as the above value + 2", (1 shl 16) + 2, right66) + assertEquals("right + 70-30 should encode as the above value + 2", (1 shl 16) + 2, right70) } @Test fun shouldDecodeRankCorrectly() { assertEquals( - "left + 33-66 should decode to left", + "left + 30-70 should decode to left", STAGE_POSITION_TOP_OR_LEFT, - AppPairsController.convertRankToStagePosition(left33), + AppPairsController.convertRankToStagePosition(left30), ) assertEquals( - "left + 33-66 should decode to 33-66", - SNAP_TO_2_33_66, - AppPairsController.convertRankToSnapPosition(left33), + "left + 30-70 should decode to 30-70", + SNAP_TO_30_70, + AppPairsController.convertRankToSnapPosition(left30), ) assertEquals( @@ -153,30 +142,30 @@ class AppPairsControllerTest { ) assertEquals( "left + 50-50 should decode to 50-50", - SNAP_TO_2_50_50, + SNAP_TO_50_50, AppPairsController.convertRankToSnapPosition(left50), ) assertEquals( - "left + 66-33 should decode to left", + "left + 70-30 should decode to left", STAGE_POSITION_TOP_OR_LEFT, - AppPairsController.convertRankToStagePosition(left66), + AppPairsController.convertRankToStagePosition(left70), ) assertEquals( - "left + 66-33 should decode to 66-33", - SNAP_TO_2_66_33, - AppPairsController.convertRankToSnapPosition(left66), + "left + 70-30 should decode to 70-30", + SNAP_TO_70_30, + AppPairsController.convertRankToSnapPosition(left70), ) assertEquals( - "right + 33-66 should decode to right", + "right + 30-70 should decode to right", STAGE_POSITION_BOTTOM_OR_RIGHT, - AppPairsController.convertRankToStagePosition(right33), + AppPairsController.convertRankToStagePosition(right30), ) assertEquals( - "right + 33-66 should decode to 33-66", - SNAP_TO_2_33_66, - AppPairsController.convertRankToSnapPosition(right33), + "right + 30-70 should decode to 30-70", + SNAP_TO_30_70, + AppPairsController.convertRankToSnapPosition(right30), ) assertEquals( @@ -186,19 +175,19 @@ class AppPairsControllerTest { ) assertEquals( "right + 50-50 should decode to 50-50", - SNAP_TO_2_50_50, + SNAP_TO_50_50, AppPairsController.convertRankToSnapPosition(right50), ) assertEquals( - "right + 66-33 should decode to right", + "right + 70-30 should decode to right", STAGE_POSITION_BOTTOM_OR_RIGHT, - AppPairsController.convertRankToStagePosition(right66), + AppPairsController.convertRankToStagePosition(right70), ) assertEquals( - "right + 66-33 should decode to 66-33", - SNAP_TO_2_66_33, - AppPairsController.convertRankToSnapPosition(right66), + "right + 70-30 should decode to 70-30", + SNAP_TO_70_30, + AppPairsController.convertRankToSnapPosition(right70), ) } @@ -213,7 +202,7 @@ class AppPairsControllerTest { // Trigger app pair launch, capture and run callback from findLastActiveTasksAndRunCallback spyAppPairsController.handleAppPairLaunchInApp( mockAppPairIcon, - listOf(mockItemInfo1, mockItemInfo2), + listOf(mockItemInfo1, mockItemInfo2) ) verify(splitSelectStateController) .findLastActiveTasksAndRunCallback(any(), any(), callbackCaptor.capture()) @@ -237,7 +226,7 @@ class AppPairsControllerTest { // Trigger app pair launch, capture and run callback from findLastActiveTasksAndRunCallback spyAppPairsController.handleAppPairLaunchInApp( mockAppPairIcon, - listOf(mockItemInfo1, mockItemInfo2), + listOf(mockItemInfo1, mockItemInfo2) ) verify(splitSelectStateController) .findLastActiveTasksAndRunCallback(any(), any(), callbackCaptor.capture()) @@ -261,7 +250,7 @@ class AppPairsControllerTest { // Trigger app pair launch, capture and run callback from findLastActiveTasksAndRunCallback spyAppPairsController.handleAppPairLaunchInApp( mockAppPairIcon, - listOf(mockItemInfo1, mockItemInfo2), + listOf(mockItemInfo1, mockItemInfo2) ) verify(splitSelectStateController) .findLastActiveTasksAndRunCallback(any(), any(), callbackCaptor.capture()) @@ -285,7 +274,7 @@ class AppPairsControllerTest { // Trigger app pair launch, capture and run callback from findLastActiveTasksAndRunCallback spyAppPairsController.handleAppPairLaunchInApp( mockAppPairIcon, - listOf(mockItemInfo1, mockItemInfo2), + listOf(mockItemInfo1, mockItemInfo2) ) verify(splitSelectStateController) .findLastActiveTasksAndRunCallback(any(), any(), callbackCaptor.capture()) @@ -309,7 +298,7 @@ class AppPairsControllerTest { // Trigger app pair launch, capture and run callback from findLastActiveTasksAndRunCallback spyAppPairsController.handleAppPairLaunchInApp( mockAppPairIcon, - listOf(mockItemInfo1, mockItemInfo2), + listOf(mockItemInfo1, mockItemInfo2) ) verify(splitSelectStateController) .findLastActiveTasksAndRunCallback(any(), any(), callbackCaptor.capture()) @@ -333,7 +322,7 @@ class AppPairsControllerTest { // Trigger app pair launch, capture and run callback from findLastActiveTasksAndRunCallback spyAppPairsController.handleAppPairLaunchInApp( mockAppPairIcon, - listOf(mockItemInfo1, mockItemInfo2), + listOf(mockItemInfo1, mockItemInfo2) ) verify(splitSelectStateController) .findLastActiveTasksAndRunCallback(any(), any(), callbackCaptor.capture()) @@ -352,16 +341,12 @@ class AppPairsControllerTest { whenever(mockTaskKey1.getId()).thenReturn(1) whenever(mockTaskKey2.getId()).thenReturn(2) // ... with app 1 already on screen - if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) { - whenever(mockCachedTaskInfo.topGroupedTaskContainsTask(eq(1))).thenReturn(true) - } else { - whenever(mockCachedTaskInfo.taskId).thenReturn(1) - } + whenever(mockCachedTaskInfo.taskId).thenReturn(1) // Trigger app pair launch, capture and run callback from findLastActiveTasksAndRunCallback spyAppPairsController.handleAppPairLaunchInApp( mockAppPairIcon, - listOf(mockItemInfo1, mockItemInfo2), + listOf(mockItemInfo1, mockItemInfo2) ) verify(splitSelectStateController) .findLastActiveTasksAndRunCallback(any(), any(), callbackCaptor.capture()) @@ -380,16 +365,12 @@ class AppPairsControllerTest { whenever(mockTaskKey1.getId()).thenReturn(1) whenever(mockTaskKey2.getId()).thenReturn(2) // ... with app 2 already on screen - if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) { - whenever(mockCachedTaskInfo.topGroupedTaskContainsTask(eq(2))).thenReturn(true) - } else { - whenever(mockCachedTaskInfo.taskId).thenReturn(2) - } + whenever(mockCachedTaskInfo.taskId).thenReturn(2) // Trigger app pair launch, capture and run callback from findLastActiveTasksAndRunCallback spyAppPairsController.handleAppPairLaunchInApp( mockAppPairIcon, - listOf(mockItemInfo1, mockItemInfo2), + listOf(mockItemInfo1, mockItemInfo2) ) verify(splitSelectStateController) .findLastActiveTasksAndRunCallback(any(), any(), callbackCaptor.capture()) @@ -402,84 +383,18 @@ class AppPairsControllerTest { .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), eq(STAGE_POSITION_TOP_OR_LEFT)) } - @Test - fun handleAppPairLaunchInApp_freeformTask1IsOnScreen_shouldLaunchAppPair() { - whenever(DesktopModeStatus.canEnterDesktopMode(mockAppPairIcon.context)).thenReturn(true) - /// Test launching apps 1 and 2 from app pair - whenever(mockTaskKey1.getId()).thenReturn(1) - whenever(mockTaskKey2.getId()).thenReturn(2) - // Task 1 is in freeform windowing mode - mockTaskKey1.windowingMode = WINDOWING_MODE_FREEFORM - // ... and app 1 is already on screen - if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) { - whenever(mockCachedTaskInfo.topGroupedTaskContainsTask(eq(1))).thenReturn(true) - } else { - whenever(mockCachedTaskInfo.taskId).thenReturn(1) - } - - // Trigger app pair launch, capture and run callback from findLastActiveTasksAndRunCallback - spyAppPairsController.handleAppPairLaunchInApp( - mockAppPairIcon, - listOf(mockItemInfo1, mockItemInfo2), - ) - verify(splitSelectStateController) - .findLastActiveTasksAndRunCallback(any(), any(), callbackCaptor.capture()) - val callback: Consumer> = callbackCaptor.value - callback.accept(arrayOf(mockTask1, mockTask2)) - - // Verify that launchAppPair was called - verify(spyAppPairsController, times(1)).launchAppPair(any(), any()) - verify(spyAppPairsController, never()) - .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull()) - } - - @Test - fun handleAppPairLaunchInApp_freeformTask2IsOnScreen_shouldLaunchAppPair() { - whenever(DesktopModeStatus.canEnterDesktopMode(mockAppPairIcon.context)).thenReturn(true) - /// Test launching apps 1 and 2 from app pair - whenever(mockTaskKey1.getId()).thenReturn(1) - whenever(mockTaskKey2.getId()).thenReturn(2) - // Task 2 is in freeform windowing mode - mockTaskKey1.windowingMode = WINDOWING_MODE_FREEFORM - // ... and app 2 is already on screen - if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) { - whenever(mockCachedTaskInfo.topGroupedTaskContainsTask(eq(2))).thenReturn(true) - } else { - whenever(mockCachedTaskInfo.taskId).thenReturn(2) - } - - // Trigger app pair launch, capture and run callback from findLastActiveTasksAndRunCallback - spyAppPairsController.handleAppPairLaunchInApp( - mockAppPairIcon, - listOf(mockItemInfo1, mockItemInfo2), - ) - verify(splitSelectStateController) - .findLastActiveTasksAndRunCallback(any(), any(), callbackCaptor.capture()) - val callback: Consumer> = callbackCaptor.value - callback.accept(arrayOf(mockTask1, mockTask2)) - - // Verify that launchAppPair was called - verify(spyAppPairsController, times(1)).launchAppPair(any(), any()) - verify(spyAppPairsController, never()) - .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull()) - } - @Test fun handleAppPairLaunchInApp_shouldLaunchAppPairNormallyWhenUnrelatedSingleAppIsFullscreen() { // Test launching apps 1 and 2 from app pair whenever(mockTaskKey1.getId()).thenReturn(1) whenever(mockTaskKey2.getId()).thenReturn(2) // ... with app 3 already on screen - if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) { - whenever(mockCachedTaskInfo.topGroupedTaskContainsTask(eq(3))).thenReturn(true) - } else { - whenever(mockCachedTaskInfo.taskId).thenReturn(3) - } + whenever(mockCachedTaskInfo.taskId).thenReturn(3) // Trigger app pair launch, capture and run callback from findLastActiveTasksAndRunCallback spyAppPairsController.handleAppPairLaunchInApp( mockAppPairIcon, - listOf(mockItemInfo1, mockItemInfo2), + listOf(mockItemInfo1, mockItemInfo2) ) verify(splitSelectStateController) .findLastActiveTasksAndRunCallback(any(), any(), callbackCaptor.capture()) diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt index c9d7e1dee9..d40f8ab389 100644 --- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt @@ -27,15 +27,15 @@ import android.view.View import android.window.TransitionInfo import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.launcher3.apppairs.AppPairIcon -import com.android.launcher3.model.data.ItemInfo import com.android.launcher3.statehandlers.DepthController import com.android.launcher3.statemanager.StateManager import com.android.launcher3.taskbar.TaskbarActivityContext import com.android.launcher3.util.SplitConfigurationOptions import com.android.quickstep.views.GroupedTaskView import com.android.quickstep.views.IconView -import com.android.quickstep.views.TaskContainer +import com.android.quickstep.views.TaskThumbnailViewDeprecated import com.android.quickstep.views.TaskView +import com.android.quickstep.views.TaskView.TaskContainer import com.android.systemui.shared.recents.model.Task import org.junit.Assert.assertEquals import org.junit.Before @@ -59,7 +59,7 @@ class SplitAnimationControllerTest { private val mockSplitSelectStateController: SplitSelectStateController = mock() // TaskView private val mockTaskView: TaskView = mock() - private val mockSnapshotView: View = mock() + private val mockThumbnailView: TaskThumbnailViewDeprecated = mock() private val mockBitmap: Bitmap = mock() private val mockIconView: IconView = mock() private val mockTaskViewDrawable: Drawable = mock() @@ -77,7 +77,6 @@ class SplitAnimationControllerTest { private val splitSelectSource: SplitConfigurationOptions.SplitSelectSource = mock() private val mockSplitSourceDrawable: Drawable = mock() private val mockSplitSourceView: View = mock() - private val mockItemInfo: ItemInfo = mock() private val stateManager: StateManager<*, *> = mock() private val depthController: DepthController = mock() @@ -88,17 +87,14 @@ class SplitAnimationControllerTest { @Before fun setup() { - whenever(mockTaskContainer.snapshotView).thenReturn(mockSnapshotView) - whenever(mockTaskContainer.thumbnail).thenReturn(mockBitmap) + whenever(mockTaskContainer.thumbnailViewDeprecated).thenReturn(mockThumbnailView) + whenever(mockThumbnailView.thumbnail).thenReturn(mockBitmap) whenever(mockTaskContainer.iconView).thenReturn(mockIconView) - whenever(mockTaskContainer.task).thenReturn(mockTask) whenever(mockIconView.drawable).thenReturn(mockTaskViewDrawable) whenever(mockTaskView.taskContainers).thenReturn(List(1) { mockTaskContainer }) - whenever(mockTaskView.firstTaskContainer).thenReturn(mockTaskContainer) whenever(splitSelectSource.drawable).thenReturn(mockSplitSourceDrawable) whenever(splitSelectSource.view).thenReturn(mockSplitSourceView) - whenever(splitSelectSource.itemInfo).thenReturn(mockItemInfo) splitAnimationController = SplitAnimationController(mockSplitSelectStateController) } @@ -118,7 +114,7 @@ class SplitAnimationControllerTest { assertEquals( "Did not fallback to use splitSource icon drawable", mockSplitSourceDrawable, - splitAnimInitProps.iconDrawable, + splitAnimInitProps.iconDrawable ) } @@ -134,7 +130,7 @@ class SplitAnimationControllerTest { assertEquals( "Did not use taskView icon drawable", mockTaskViewDrawable, - splitAnimInitProps.iconDrawable, + splitAnimInitProps.iconDrawable ) } @@ -153,7 +149,7 @@ class SplitAnimationControllerTest { assertEquals( "Did not use taskView icon drawable", mockTaskViewDrawable, - splitAnimInitProps.iconDrawable, + splitAnimInitProps.iconDrawable ) } @@ -169,7 +165,7 @@ class SplitAnimationControllerTest { assertEquals( "Did not use splitSource icon drawable", mockSplitSourceDrawable, - splitAnimInitProps.iconDrawable, + splitAnimInitProps.iconDrawable ) } @@ -184,6 +180,7 @@ class SplitAnimationControllerTest { whenever(mockTaskContainer.task).thenReturn(mockTask) whenever(mockTaskContainer.iconView).thenReturn(mockIconView) + whenever(mockTaskContainer.thumbnailViewDeprecated).thenReturn(mockThumbnailView) whenever(mockTask.getKey()).thenReturn(mockTaskKey) whenever(mockTaskKey.getId()).thenReturn(taskId) whenever(mockSplitSelectStateController.initialTaskId).thenReturn(taskId) @@ -191,13 +188,13 @@ class SplitAnimationControllerTest { val splitAnimInitProps: SplitAnimationController.Companion.SplitAnimInitProps = splitAnimationController.getFirstAnimInitViews( { mockGroupedTaskView }, - { splitSelectSource }, + { splitSelectSource } ) assertEquals( "Did not use splitSource icon drawable", mockSplitSourceDrawable, - splitAnimInitProps.iconDrawable, + splitAnimInitProps.iconDrawable ) } @@ -215,7 +212,7 @@ class SplitAnimationControllerTest { any(), any(), any(), - any(), + any() ) spySplitAnimationController.playSplitLaunchAnimation( @@ -230,8 +227,7 @@ class SplitAnimationControllerTest { depthController, null /* info */, null /* t */, - {} /* finishCallback */, - 1f, /* cornerRadius */ + {} /* finishCallback */ ) verify(spySplitAnimationController) @@ -244,7 +240,7 @@ class SplitAnimationControllerTest { any(), any(), any(), - any(), + any() ) } @@ -267,8 +263,7 @@ class SplitAnimationControllerTest { depthController, transitionInfo, transaction, - {} /* finishCallback */, - 1f, /* cornerRadius */ + {} /* finishCallback */ ) verify(spySplitAnimationController) @@ -281,7 +276,7 @@ class SplitAnimationControllerTest { whenever(mockAppPairIcon.context).thenReturn(mockContextThemeWrapper) doNothing() .whenever(spySplitAnimationController) - .composeIconSplitLaunchAnimator(any(), any(), any(), any(), any()) + .composeIconSplitLaunchAnimator(any(), any(), any(), any()) doReturn(-1).whenever(spySplitAnimationController).hasChangesForBothAppPairs(any(), any()) spySplitAnimationController.playSplitLaunchAnimation( @@ -296,12 +291,11 @@ class SplitAnimationControllerTest { depthController, transitionInfo, transaction, - {} /* finishCallback */, - 1f, /* cornerRadius */ + {} /* finishCallback */ ) verify(spySplitAnimationController) - .composeIconSplitLaunchAnimator(any(), any(), any(), any(), any()) + .composeIconSplitLaunchAnimator(any(), any(), any(), any()) } @Test @@ -325,8 +319,7 @@ class SplitAnimationControllerTest { depthController, transitionInfo, transaction, - {} /* finishCallback */, - 1f, /* cornerRadius */ + {} /* finishCallback */ ) verify(spySplitAnimationController) @@ -353,8 +346,7 @@ class SplitAnimationControllerTest { depthController, transitionInfo, transaction, - {} /* finishCallback */, - 1f, /* cornerRadius */ + {} /* finishCallback */ ) verify(spySplitAnimationController) @@ -381,8 +373,7 @@ class SplitAnimationControllerTest { depthController, transitionInfo, transaction, - {} /* finishCallback */, - 1f, /* cornerRadius */ + {} /* finishCallback */ ) verify(spySplitAnimationController) @@ -394,7 +385,7 @@ class SplitAnimationControllerTest { val spySplitAnimationController = spy(splitAnimationController) doNothing() .whenever(spySplitAnimationController) - .composeFadeInSplitLaunchAnimator(any(), any(), any(), any(), any(), any()) + .composeFadeInSplitLaunchAnimator(any(), any(), any(), any(), any()) spySplitAnimationController.playSplitLaunchAnimation( null /* launchingTaskView */, @@ -408,11 +399,10 @@ class SplitAnimationControllerTest { depthController, transitionInfo, transaction, - {} /* finishCallback */, - 1f, /* cornerRadius */ + {} /* finishCallback */ ) verify(spySplitAnimationController) - .composeFadeInSplitLaunchAnimator(any(), any(), any(), any(), any(), any()) + .composeFadeInSplitLaunchAnimator(any(), any(), any(), any(), any()) } } diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt index e4bdba510f..bab84ef28b 100644 --- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt @@ -22,6 +22,7 @@ import android.app.PendingIntent import android.content.ComponentName import android.content.Intent import android.graphics.Rect +import android.os.Handler import android.os.UserHandle import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.launcher3.LauncherState @@ -36,10 +37,9 @@ import com.android.launcher3.util.SplitConfigurationOptions import com.android.quickstep.RecentsModel import com.android.quickstep.SystemUiProxy import com.android.quickstep.util.SplitSelectStateController.SplitFromDesktopController -import com.android.quickstep.views.RecentsView import com.android.quickstep.views.RecentsViewContainer import com.android.systemui.shared.recents.model.Task -import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50 +import com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50 import java.util.function.Consumer import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse @@ -52,7 +52,6 @@ import org.mockito.Mockito.any import org.mockito.Mockito.`when` import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.mock -import org.mockito.kotlin.times import org.mockito.kotlin.verify import org.mockito.kotlin.whenever @@ -64,11 +63,11 @@ class SplitSelectStateControllerTest { private val statsLogManager: StatsLogManager = mock() private val statsLogger: StatsLogger = mock() private val stateManager: StateManager> = mock() + private val handler: Handler = mock() private val context: RecentsViewContainer = mock() private val recentsModel: RecentsModel = mock() private val pendingIntent: PendingIntent = mock() private val splitFromDesktopController: SplitFromDesktopController = mock() - private val recentsView: RecentsView<*, *> = mock() private lateinit var splitSelectStateController: SplitSelectStateController @@ -76,7 +75,6 @@ class SplitSelectStateControllerTest { private val nonPrimaryUserHandle = UserHandle(ActivityManager.RunningTaskInfo().userId + 10) private var taskIdCounter = 0 - private fun getUniqueId(): Int { return ++taskIdCounter } @@ -89,12 +87,13 @@ class SplitSelectStateControllerTest { splitSelectStateController = SplitSelectStateController( context, + handler, stateManager, depthController, statsLogManager, systemUiProxy, recentsModel, - null, /*activityBackCallback*/ + null /*activityBackCallback*/ ) } @@ -102,14 +101,14 @@ class SplitSelectStateControllerTest { fun activeTasks_noMatchingTasks() { val nonMatchingComponent = ComponentKey(ComponentName("no", "match"), primaryUserHandle) val groupTask1 = - generateSplitTask( + generateGroupTask( ComponentName("pomegranate", "juice"), - ComponentName("pumpkin", "pie"), + ComponentName("pumpkin", "pie") ) val groupTask2 = - generateSplitTask( + generateGroupTask( ComponentName("hotdog", "juice"), - ComponentName("personal", "computer"), + ComponentName("personal", "computer") ) val tasks: ArrayList = ArrayList() tasks.add(groupTask1) @@ -126,7 +125,7 @@ class SplitSelectStateControllerTest { splitSelectStateController.findLastActiveTasksAndRunCallback( listOf(nonMatchingComponent), false /* findExactPairMatch */, - taskConsumer, + taskConsumer ) verify(recentsModel).getTasks(capture()) } @@ -143,14 +142,14 @@ class SplitSelectStateControllerTest { val matchingComponent = ComponentKey(ComponentName(matchingPackage, matchingClass), primaryUserHandle) val groupTask1 = - generateSplitTask( + generateGroupTask( ComponentName(matchingPackage, matchingClass), - ComponentName("pomegranate", "juice"), + ComponentName("pomegranate", "juice") ) val groupTask2 = - generateSplitTask( + generateGroupTask( ComponentName("pumpkin", "pie"), - ComponentName("personal", "computer"), + ComponentName("personal", "computer") ) val tasks: ArrayList = ArrayList() tasks.add(groupTask1) @@ -163,14 +162,14 @@ class SplitSelectStateControllerTest { assertEquals( "ComponentName package mismatched", it[0].key.baseIntent.component?.packageName, - matchingPackage, + matchingPackage ) assertEquals( "ComponentName class mismatched", it[0].key.baseIntent.component?.className, - matchingClass, + matchingClass ) - assertEquals(it[0], groupTask1.topLeftTask) + assertEquals(it[0], groupTask1.task1) } // Capture callback from recentsModel#getTasks() @@ -179,7 +178,7 @@ class SplitSelectStateControllerTest { splitSelectStateController.findLastActiveTasksAndRunCallback( listOf(matchingComponent), false /* findExactPairMatch */, - taskConsumer, + taskConsumer ) verify(recentsModel).getTasks(capture()) } @@ -196,14 +195,14 @@ class SplitSelectStateControllerTest { val nonPrimaryUserComponent = ComponentKey(ComponentName(matchingPackage, matchingClass), nonPrimaryUserHandle) val groupTask1 = - generateSplitTask( + generateGroupTask( ComponentName(matchingPackage, matchingClass), - ComponentName("pomegranate", "juice"), + ComponentName("pomegranate", "juice") ) val groupTask2 = - generateSplitTask( + generateGroupTask( ComponentName("pumpkin", "pie"), - ComponentName("personal", "computer"), + ComponentName("personal", "computer") ) val tasks: ArrayList = ArrayList() tasks.add(groupTask1) @@ -220,7 +219,7 @@ class SplitSelectStateControllerTest { splitSelectStateController.findLastActiveTasksAndRunCallback( listOf(nonPrimaryUserComponent), false /* findExactPairMatch */, - taskConsumer, + taskConsumer ) verify(recentsModel).getTasks(capture()) } @@ -237,16 +236,16 @@ class SplitSelectStateControllerTest { val nonPrimaryUserComponent = ComponentKey(ComponentName(matchingPackage, matchingClass), nonPrimaryUserHandle) val groupTask1 = - generateSplitTask( + generateGroupTask( ComponentName(matchingPackage, matchingClass), nonPrimaryUserHandle, ComponentName("pomegranate", "juice"), - nonPrimaryUserHandle, + nonPrimaryUserHandle ) val groupTask2 = - generateSplitTask( + generateGroupTask( ComponentName("pumpkin", "pie"), - ComponentName("personal", "computer"), + ComponentName("personal", "computer") ) val tasks: ArrayList = ArrayList() tasks.add(groupTask1) @@ -259,15 +258,15 @@ class SplitSelectStateControllerTest { assertEquals( "ComponentName package mismatched", it[0].key.baseIntent.component?.packageName, - matchingPackage, + matchingPackage ) assertEquals( "ComponentName class mismatched", it[0].key.baseIntent.component?.className, - matchingClass, + matchingClass ) assertEquals("userId mismatched", it[0].key.userId, nonPrimaryUserHandle.identifier) - assertEquals(it[0], groupTask1.topLeftTask) + assertEquals(it[0], groupTask1.task1) } // Capture callback from recentsModel#getTasks() @@ -276,7 +275,7 @@ class SplitSelectStateControllerTest { splitSelectStateController.findLastActiveTasksAndRunCallback( listOf(nonPrimaryUserComponent), false /* findExactPairMatch */, - taskConsumer, + taskConsumer ) verify(recentsModel).getTasks(capture()) } @@ -293,14 +292,14 @@ class SplitSelectStateControllerTest { val matchingComponent = ComponentKey(ComponentName(matchingPackage, matchingClass), primaryUserHandle) val groupTask1 = - generateSplitTask( + generateGroupTask( ComponentName(matchingPackage, matchingClass), - ComponentName("pumpkin", "pie"), + ComponentName("pumpkin", "pie") ) val groupTask2 = - generateSplitTask( + generateGroupTask( ComponentName("pomegranate", "juice"), - ComponentName(matchingPackage, matchingClass), + ComponentName(matchingPackage, matchingClass) ) val tasks: ArrayList = ArrayList() tasks.add(groupTask2) @@ -313,14 +312,14 @@ class SplitSelectStateControllerTest { assertEquals( "ComponentName package mismatched", it[0].key.baseIntent.component?.packageName, - matchingPackage, + matchingPackage ) assertEquals( "ComponentName class mismatched", it[0].key.baseIntent.component?.className, - matchingClass, + matchingClass ) - assertEquals(it[0], groupTask1.topLeftTask) + assertEquals(it[0], groupTask1.task1) } // Capture callback from recentsModel#getTasks() @@ -329,7 +328,7 @@ class SplitSelectStateControllerTest { splitSelectStateController.findLastActiveTasksAndRunCallback( listOf(matchingComponent), false /* findExactPairMatch */, - taskConsumer, + taskConsumer ) verify(recentsModel).getTasks(capture()) } @@ -348,11 +347,11 @@ class SplitSelectStateControllerTest { ComponentKey(ComponentName(matchingPackage, matchingClass), primaryUserHandle) val groupTask1 = - generateSplitTask(ComponentName("hotdog", "pie"), ComponentName("pumpkin", "pie")) + generateGroupTask(ComponentName("hotdog", "pie"), ComponentName("pumpkin", "pie")) val groupTask2 = - generateSplitTask( + generateGroupTask( ComponentName("pomegranate", "juice"), - ComponentName(matchingPackage, matchingClass), + ComponentName(matchingPackage, matchingClass) ) val tasks: ArrayList = ArrayList() tasks.add(groupTask2) @@ -367,14 +366,14 @@ class SplitSelectStateControllerTest { assertEquals( "ComponentName package mismatched", it[1].key.baseIntent.component?.packageName, - matchingPackage, + matchingPackage ) assertEquals( "ComponentName class mismatched", it[1].key.baseIntent.component?.className, - matchingClass, + matchingClass ) - assertEquals(it[1], groupTask2.bottomRightTask) + assertEquals(it[1], groupTask2.task2) } // Capture callback from recentsModel#getTasks() @@ -383,7 +382,7 @@ class SplitSelectStateControllerTest { splitSelectStateController.findLastActiveTasksAndRunCallback( listOf(nonMatchingComponent, matchingComponent), false /* findExactPairMatch */, - taskConsumer, + taskConsumer ) verify(recentsModel).getTasks(capture()) } @@ -401,11 +400,11 @@ class SplitSelectStateControllerTest { ComponentKey(ComponentName(matchingPackage, matchingClass), primaryUserHandle) val groupTask1 = - generateSplitTask(ComponentName("hotdog", "pie"), ComponentName("pumpkin", "pie")) + generateGroupTask(ComponentName("hotdog", "pie"), ComponentName("pumpkin", "pie")) val groupTask2 = - generateSplitTask( + generateGroupTask( ComponentName("pomegranate", "juice"), - ComponentName(matchingPackage, matchingClass), + ComponentName(matchingPackage, matchingClass) ) val tasks: ArrayList = ArrayList() tasks.add(groupTask2) @@ -419,14 +418,14 @@ class SplitSelectStateControllerTest { assertEquals( "ComponentName package mismatched", it[0].key.baseIntent.component?.packageName, - matchingPackage, + matchingPackage ) assertEquals( "ComponentName class mismatched", it[0].key.baseIntent.component?.className, - matchingClass, + matchingClass ) - assertEquals(it[0], groupTask2.bottomRightTask) + assertEquals(it[0], groupTask2.task2) assertNull("No tasks should have matched", it[1] /*task*/) } @@ -436,7 +435,7 @@ class SplitSelectStateControllerTest { splitSelectStateController.findLastActiveTasksAndRunCallback( listOf(matchingComponent, matchingComponent), false /* findExactPairMatch */, - taskConsumer, + taskConsumer ) verify(recentsModel).getTasks(capture()) } @@ -454,14 +453,14 @@ class SplitSelectStateControllerTest { ComponentKey(ComponentName(matchingPackage, matchingClass), primaryUserHandle) val groupTask1 = - generateSplitTask( + generateGroupTask( ComponentName(matchingPackage, matchingClass), - ComponentName("pumpkin", "pie"), + ComponentName("pumpkin", "pie") ) val groupTask2 = - generateSplitTask( + generateGroupTask( ComponentName("pomegranate", "juice"), - ComponentName(matchingPackage, matchingClass), + ComponentName(matchingPackage, matchingClass) ) val tasks: ArrayList = ArrayList() tasks.add(groupTask2) @@ -475,25 +474,25 @@ class SplitSelectStateControllerTest { assertEquals( "ComponentName package mismatched", it[0].key.baseIntent.component?.packageName, - matchingPackage, + matchingPackage ) assertEquals( "ComponentName class mismatched", it[0].key.baseIntent.component?.className, - matchingClass, + matchingClass ) - assertEquals(it[0], groupTask1.topLeftTask) + assertEquals(it[0], groupTask1.task1) assertEquals( "ComponentName package mismatched", it[1].key.baseIntent.component?.packageName, - matchingPackage, + matchingPackage ) assertEquals( "ComponentName class mismatched", it[1].key.baseIntent.component?.className, - matchingClass, + matchingClass ) - assertEquals(it[1], groupTask2.bottomRightTask) + assertEquals(it[1], groupTask2.task2) } // Capture callback from recentsModel#getTasks() @@ -502,7 +501,7 @@ class SplitSelectStateControllerTest { splitSelectStateController.findLastActiveTasksAndRunCallback( listOf(matchingComponent, matchingComponent), false /* findExactPairMatch */, - taskConsumer, + taskConsumer ) verify(recentsModel).getTasks(capture()) } @@ -524,16 +523,16 @@ class SplitSelectStateControllerTest { ComponentKey(ComponentName(matchingPackage2, matchingClass2), primaryUserHandle) val groupTask1 = - generateSplitTask(ComponentName("hotdog", "pie"), ComponentName("pumpkin", "pie")) + generateGroupTask(ComponentName("hotdog", "pie"), ComponentName("pumpkin", "pie")) val groupTask2 = - generateSplitTask( + generateGroupTask( ComponentName(matchingPackage2, matchingClass2), - ComponentName(matchingPackage, matchingClass), + ComponentName(matchingPackage, matchingClass) ) val groupTask3 = - generateSplitTask( + generateGroupTask( ComponentName("hotdog", "pie"), - ComponentName(matchingPackage, matchingClass), + ComponentName(matchingPackage, matchingClass) ) val tasks: ArrayList = ArrayList() tasks.add(groupTask3) @@ -545,7 +544,7 @@ class SplitSelectStateControllerTest { val taskConsumer = Consumer> { assertEquals("Expected array length 2", 2, it.size) - assertEquals("Found wrong task", it[0], groupTask2.topLeftTask) + assertEquals("Found wrong task", it[0], groupTask2.task1) } // Capture callback from recentsModel#getTasks() @@ -554,7 +553,7 @@ class SplitSelectStateControllerTest { splitSelectStateController.findLastActiveTasksAndRunCallback( listOf(matchingComponent2, matchingComponent), true /* findExactPairMatch */, - taskConsumer, + taskConsumer ) verify(recentsModel).getTasks(capture()) } @@ -571,7 +570,7 @@ class SplitSelectStateControllerTest { -1 /*stagePosition*/, ItemInfo(), null /*splitEvent*/, - 10, /*alreadyRunningTask*/ + 10 /*alreadyRunningTask*/ ) assertTrue(splitSelectStateController.isSplitSelectActive) } @@ -583,23 +582,21 @@ class SplitSelectStateControllerTest { -1 /*stagePosition*/, ItemInfo(), null /*splitEvent*/, - -1, /*alreadyRunningTask*/ + -1 /*alreadyRunningTask*/ ) assertTrue(splitSelectStateController.isSplitSelectActive) } @Test fun resetAfterInitial() { - whenever(context.getOverviewPanel>()).thenReturn(recentsView) splitSelectStateController.setInitialTaskSelect( Intent() /*intent*/, -1 /*stagePosition*/, ItemInfo(), null /*splitEvent*/, - -1, + -1 ) splitSelectStateController.resetState() - verify(recentsView, times(1)).resetDesktopTaskFromSplitSelectState() assertFalse(splitSelectStateController.isSplitSelectActive) } @@ -625,26 +622,11 @@ class SplitSelectStateControllerTest { verify(splitFromDesktopController).onDestroy() } - @Test - fun splitSelectStateControllerDestroyed_doNotResetDeskTopTasks() { - whenever(context.getOverviewPanel>()).thenReturn(recentsView) - splitSelectStateController.setInitialTaskSelect( - Intent(), /*intent*/ - -1, /*stagePosition*/ - ItemInfo(), - null, /*splitEvent*/ - -1, - ) - splitSelectStateController.onDestroy() - splitSelectStateController.resetState() - verify(recentsView, times(0)).resetDesktopTaskFromSplitSelectState() - } - - /** Generates a [SplitTask] with default userId. */ - private fun generateSplitTask( + // Generate GroupTask with default userId. + private fun generateGroupTask( task1ComponentName: ComponentName, - task2ComponentName: ComponentName, - ): SplitTask { + task2ComponentName: ComponentName + ): GroupTask { val task1 = Task() var taskInfo = ActivityManager.RunningTaskInfo() taskInfo.taskId = getUniqueId() @@ -660,26 +642,20 @@ class SplitSelectStateControllerTest { intent.component = task2ComponentName taskInfo.baseIntent = intent task2.key = Task.TaskKey(taskInfo) - return SplitTask( + return GroupTask( task1, task2, - SplitConfigurationOptions.SplitBounds( - /* leftTopBounds = */ Rect(), - /* rightBottomBounds = */ Rect(), - /* leftTopTaskId = */ task1.key.id, - /* rightBottomTaskId = */ task2.key.id, - /* snapPosition = */ SNAP_TO_2_50_50, - ), + SplitConfigurationOptions.SplitBounds(Rect(), Rect(), -1, -1, SNAP_TO_50_50) ) } - /** Generates a [SplitTask] with custom user handles. */ - private fun generateSplitTask( + // Generate GroupTask with custom user handles. + private fun generateGroupTask( task1ComponentName: ComponentName, userHandle1: UserHandle, task2ComponentName: ComponentName, - userHandle2: UserHandle, - ): SplitTask { + userHandle2: UserHandle + ): GroupTask { val task1 = Task() var taskInfo = ActivityManager.RunningTaskInfo() taskInfo.taskId = getUniqueId() @@ -698,16 +674,10 @@ class SplitSelectStateControllerTest { intent.component = task2ComponentName taskInfo.baseIntent = intent task2.key = Task.TaskKey(taskInfo) - return SplitTask( + return GroupTask( task1, task2, - SplitConfigurationOptions.SplitBounds( - /* leftTopBounds = */ Rect(), - /* rightBottomBounds = */ Rect(), - /* leftTopTaskId = */ task1.key.id, - /* rightBottomTaskId = */ task2.key.id, - /* snapPosition = */ SNAP_TO_2_50_50, - ), + SplitConfigurationOptions.SplitBounds(Rect(), Rect(), -1, -1, SNAP_TO_50_50) ) } } diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskGridNavHelperTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskGridNavHelperTest.java new file mode 100644 index 0000000000..7ef4910ce5 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskGridNavHelperTest.java @@ -0,0 +1,510 @@ +/* + * 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.quickstep.util; + +import static com.android.quickstep.util.TaskGridNavHelper.CLEAR_ALL_PLACEHOLDER_ID; +import static com.android.quickstep.util.TaskGridNavHelper.INVALID_FOCUSED_TASK_ID; + +import static org.junit.Assert.assertEquals; + +import com.android.launcher3.util.IntArray; + +import org.junit.Test; + +public class TaskGridNavHelperTest { + + @Test + public void equalLengthRows_noFocused_onTop_pressDown_goesToBottom() { + IntArray topIds = IntArray.wrap(1, 3, 5); + IntArray bottomIds = IntArray.wrap(2, 4, 6); + int currentPageTaskViewId = 1; + int delta = 1; + @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_DOWN; + boolean cycle = true; + TaskGridNavHelper taskGridNavHelper = + new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID); + + int nextGridPage = + taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); + + assertEquals("Wrong next page returned.", 2, nextGridPage); + } + + @Test + public void equalLengthRows_noFocused_onTop_pressUp_goesToBottom() { + IntArray topIds = IntArray.wrap(1, 3, 5); + IntArray bottomIds = IntArray.wrap(2, 4, 6); + int currentPageTaskViewId = 1; + int delta = 1; + @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_UP; + boolean cycle = true; + TaskGridNavHelper taskGridNavHelper = + new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID); + + int nextGridPage = + taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); + + assertEquals("Wrong next page returned.", 2, nextGridPage); + } + + @Test + public void equalLengthRows_noFocused_onBottom_pressDown_goesToTop() { + IntArray topIds = IntArray.wrap(1, 3, 5); + IntArray bottomIds = IntArray.wrap(2, 4, 6); + int currentPageTaskViewId = 2; + int delta = 1; + @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_DOWN; + boolean cycle = true; + TaskGridNavHelper taskGridNavHelper = + new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID); + + int nextGridPage = + taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); + + assertEquals("Wrong next page returned.", 1, nextGridPage); + } + + @Test + public void equalLengthRows_noFocused_onBottom_pressUp_goesToTop() { + IntArray topIds = IntArray.wrap(1, 3, 5); + IntArray bottomIds = IntArray.wrap(2, 4, 6); + int currentPageTaskViewId = 2; + int delta = 1; + @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_UP; + boolean cycle = true; + TaskGridNavHelper taskGridNavHelper = + new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID); + + int nextGridPage = + taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); + + assertEquals("Wrong next page returned.", 1, nextGridPage); + } + + @Test + public void equalLengthRows_noFocused_onTop_pressLeft_goesLeft() { + IntArray topIds = IntArray.wrap(1, 3, 5); + IntArray bottomIds = IntArray.wrap(2, 4, 6); + int currentPageTaskViewId = 1; + int delta = 1; + @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_LEFT; + boolean cycle = true; + TaskGridNavHelper taskGridNavHelper = + new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID); + + int nextGridPage = + taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); + + assertEquals("Wrong next page returned.", 3, nextGridPage); + } + + @Test + public void equalLengthRows_noFocused_onBottom_pressLeft_goesLeft() { + IntArray topIds = IntArray.wrap(1, 3, 5); + IntArray bottomIds = IntArray.wrap(2, 4, 6); + int currentPageTaskViewId = 2; + int delta = 1; + @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_LEFT; + boolean cycle = true; + TaskGridNavHelper taskGridNavHelper = + new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID); + + int nextGridPage = + taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); + + assertEquals("Wrong next page returned.", 4, nextGridPage); + } + + @Test + public void equalLengthRows_noFocused_onTop_secondItem_pressRight_goesRight() { + IntArray topIds = IntArray.wrap(1, 3, 5); + IntArray bottomIds = IntArray.wrap(2, 4, 6); + int currentPageTaskViewId = 3; + int delta = -1; + @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_RIGHT; + boolean cycle = true; + TaskGridNavHelper taskGridNavHelper = + new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID); + + int nextGridPage = + taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); + + assertEquals("Wrong next page returned.", 1, nextGridPage); + } + + @Test + public void equalLengthRows_noFocused_onBottom_secondItem_pressRight_goesRight() { + IntArray topIds = IntArray.wrap(1, 3, 5); + IntArray bottomIds = IntArray.wrap(2, 4, 6); + int currentPageTaskViewId = 4; + int delta = -1; + @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_RIGHT; + boolean cycle = true; + TaskGridNavHelper taskGridNavHelper = + new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID); + + int nextGridPage = + taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); + + assertEquals("Wrong next page returned.", 2, nextGridPage); + } + + @Test + public void equalLengthRows_noFocused_onTop_pressRight_cycleToClearAll() { + IntArray topIds = IntArray.wrap(1, 3, 5); + IntArray bottomIds = IntArray.wrap(2, 4, 6); + int currentPageTaskViewId = 1; + int delta = -1; + @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_RIGHT; + boolean cycle = true; + TaskGridNavHelper taskGridNavHelper = + new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID); + + int nextGridPage = + taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); + + assertEquals("Wrong next page returned.", CLEAR_ALL_PLACEHOLDER_ID, nextGridPage); + } + + @Test + public void equalLengthRows_noFocused_onBottom_pressRight_cycleToClearAll() { + IntArray topIds = IntArray.wrap(1, 3, 5); + IntArray bottomIds = IntArray.wrap(2, 4, 6); + int currentPageTaskViewId = 2; + int delta = -1; + @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_RIGHT; + boolean cycle = true; + TaskGridNavHelper taskGridNavHelper = + new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID); + + int nextGridPage = + taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); + + assertEquals("Wrong next page returned.", CLEAR_ALL_PLACEHOLDER_ID, nextGridPage); + } + + @Test + public void equalLengthRows_noFocused_onTop_lastItem_pressLeft_toClearAll() { + IntArray topIds = IntArray.wrap(1, 3, 5); + IntArray bottomIds = IntArray.wrap(2, 4, 6); + int currentPageTaskViewId = 5; + int delta = 1; + @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_LEFT; + boolean cycle = true; + TaskGridNavHelper taskGridNavHelper = + new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID); + + int nextGridPage = + taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); + + assertEquals("Wrong next page returned.", CLEAR_ALL_PLACEHOLDER_ID, nextGridPage); + } + + @Test + public void equalLengthRows_noFocused_onBottom_lastItem_pressLeft_toClearAll() { + IntArray topIds = IntArray.wrap(1, 3, 5); + IntArray bottomIds = IntArray.wrap(2, 4, 6); + int currentPageTaskViewId = 6; + int delta = 1; + @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_LEFT; + boolean cycle = true; + TaskGridNavHelper taskGridNavHelper = + new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID); + + int nextGridPage = + taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); + + assertEquals("Wrong next page returned.", CLEAR_ALL_PLACEHOLDER_ID, nextGridPage); + } + + @Test + public void equalLengthRows_noFocused_onClearAll_pressLeft_cycleToFirst() { + IntArray topIds = IntArray.wrap(1, 3, 5); + IntArray bottomIds = IntArray.wrap(2, 4, 6); + int currentPageTaskViewId = CLEAR_ALL_PLACEHOLDER_ID; + int delta = 1; + @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_LEFT; + boolean cycle = true; + TaskGridNavHelper taskGridNavHelper = + new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID); + + int nextGridPage = + taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); + + assertEquals("Wrong next page returned.", 1, nextGridPage); + } + + @Test + public void equalLengthRows_noFocused_onClearAll_pressRight_toLastInBottom() { + IntArray topIds = IntArray.wrap(1, 3, 5); + IntArray bottomIds = IntArray.wrap(2, 4, 6); + int currentPageTaskViewId = CLEAR_ALL_PLACEHOLDER_ID; + int delta = -1; + @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_RIGHT; + boolean cycle = true; + TaskGridNavHelper taskGridNavHelper = + new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID); + + int nextGridPage = + taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); + + assertEquals("Wrong next page returned.", 6, nextGridPage); + } + + @Test + public void equalLengthRows_withFocused_onFocused_pressLeft_toTop() { + IntArray topIds = IntArray.wrap(1, 3, 5); + IntArray bottomIds = IntArray.wrap(2, 4, 6); + int focusedTaskId = 99; + int currentPageTaskViewId = focusedTaskId; + int delta = 1; + @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_LEFT; + boolean cycle = true; + TaskGridNavHelper taskGridNavHelper = + new TaskGridNavHelper(topIds, bottomIds, focusedTaskId); + + int nextGridPage = + taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); + + assertEquals("Wrong next page returned.", 1, nextGridPage); + } + + @Test + public void equalLengthRows_withFocused_onFocused_pressUp_stayOnFocused() { + IntArray topIds = IntArray.wrap(1, 3, 5); + IntArray bottomIds = IntArray.wrap(2, 4, 6); + int focusedTaskId = 99; + int currentPageTaskViewId = focusedTaskId; + int delta = 1; + @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_UP; + boolean cycle = true; + TaskGridNavHelper taskGridNavHelper = + new TaskGridNavHelper(topIds, bottomIds, focusedTaskId); + + int nextGridPage = + taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); + + assertEquals("Wrong next page returned.", focusedTaskId, nextGridPage); + } + + @Test + public void equalLengthRows_withFocused_onFocused_pressDown_stayOnFocused() { + IntArray topIds = IntArray.wrap(1, 3, 5); + IntArray bottomIds = IntArray.wrap(2, 4, 6); + int focusedTaskId = 99; + int currentPageTaskViewId = focusedTaskId; + int delta = 1; + @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_DOWN; + boolean cycle = true; + TaskGridNavHelper taskGridNavHelper = + new TaskGridNavHelper(topIds, bottomIds, focusedTaskId); + + int nextGridPage = + taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); + + assertEquals("Wrong next page returned.", focusedTaskId, nextGridPage); + } + + @Test + public void equalLengthRows_withFocused_onFocused_pressRight_cycleToClearAll() { + IntArray topIds = IntArray.wrap(1, 3, 5); + IntArray bottomIds = IntArray.wrap(2, 4, 6); + int focusedTaskId = 99; + int currentPageTaskViewId = focusedTaskId; + int delta = -1; + @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_RIGHT; + boolean cycle = true; + TaskGridNavHelper taskGridNavHelper = + new TaskGridNavHelper(topIds, bottomIds, focusedTaskId); + + int nextGridPage = + taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); + + assertEquals("Wrong next page returned.", CLEAR_ALL_PLACEHOLDER_ID, nextGridPage); + } + + @Test + public void equalLengthRows_withFocused_onClearAll_pressLeft_cycleToFocusedTask() { + IntArray topIds = IntArray.wrap(1, 3, 5); + IntArray bottomIds = IntArray.wrap(2, 4, 6); + int focusedTaskId = 99; + int currentPageTaskViewId = CLEAR_ALL_PLACEHOLDER_ID; + int delta = 1; + @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_LEFT; + boolean cycle = true; + TaskGridNavHelper taskGridNavHelper = + new TaskGridNavHelper(topIds, bottomIds, focusedTaskId); + + int nextGridPage = + taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); + + assertEquals("Wrong next page returned.", focusedTaskId, nextGridPage); + } + + @Test + public void longerTopRow_noFocused_atEndTopBeyondBottom_pressDown_stayTop() { + IntArray topIds = IntArray.wrap(1, 3, 5, 7); + IntArray bottomIds = IntArray.wrap(2, 4, 6); + int currentPageTaskViewId = 7; + int delta = 1; + @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_DOWN; + boolean cycle = true; + TaskGridNavHelper taskGridNavHelper = + new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID); + + int nextGridPage = + taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); + + assertEquals("Wrong next page returned.", 7, nextGridPage); + } + + @Test + public void longerTopRow_noFocused_atEndTopBeyondBottom_pressUp_stayTop() { + IntArray topIds = IntArray.wrap(1, 3, 5, 7); + IntArray bottomIds = IntArray.wrap(2, 4, 6); + int currentPageTaskViewId = 7; + int delta = 1; + @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_UP; + boolean cycle = true; + TaskGridNavHelper taskGridNavHelper = + new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID); + + int nextGridPage = + taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); + + assertEquals("Wrong next page returned.", 7, nextGridPage); + } + + @Test + public void longerTopRow_noFocused_atEndBottom_pressLeft_goToTop() { + IntArray topIds = IntArray.wrap(1, 3, 5, 7); + IntArray bottomIds = IntArray.wrap(2, 4, 6); + int currentPageTaskViewId = 6; + int delta = 1; + @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_LEFT; + boolean cycle = true; + TaskGridNavHelper taskGridNavHelper = + new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID); + + int nextGridPage = + taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); + + assertEquals("Wrong next page returned.", 7, nextGridPage); + } + + @Test + public void longerTopRow_noFocused_atClearAll_pressRight_goToLonger() { + IntArray topIds = IntArray.wrap(1, 3, 5, 7); + IntArray bottomIds = IntArray.wrap(2, 4, 6); + int currentPageTaskViewId = CLEAR_ALL_PLACEHOLDER_ID; + int delta = -1; + @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_RIGHT; + boolean cycle = true; + TaskGridNavHelper taskGridNavHelper = + new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID); + + int nextGridPage = + taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); + + assertEquals("Wrong next page returned.", 7, nextGridPage); + } + + @Test + public void longerBottomRow_noFocused_atClearAll_pressRight_goToLonger() { + IntArray topIds = IntArray.wrap(1, 3, 5); + IntArray bottomIds = IntArray.wrap(2, 4, 6, 7); + int currentPageTaskViewId = CLEAR_ALL_PLACEHOLDER_ID; + int delta = -1; + @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_RIGHT; + boolean cycle = true; + TaskGridNavHelper taskGridNavHelper = + new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID); + + int nextGridPage = + taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); + + assertEquals("Wrong next page returned.", 7, nextGridPage); + } + + @Test + public void equalLengthRows_noFocused_onTop_pressTab_goesToBottom() { + IntArray topIds = IntArray.wrap(1, 3, 5); + IntArray bottomIds = IntArray.wrap(2, 4, 6); + int currentPageTaskViewId = 1; + int delta = 1; + @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_TAB; + boolean cycle = true; + TaskGridNavHelper taskGridNavHelper = + new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID); + + int nextGridPage = + taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); + + assertEquals("Wrong next page returned.", 2, nextGridPage); + } + + @Test + public void equalLengthRows_noFocused_onBottom_pressTab_goesToNextTop() { + IntArray topIds = IntArray.wrap(1, 3, 5); + IntArray bottomIds = IntArray.wrap(2, 4, 6); + int currentPageTaskViewId = 2; + int delta = 1; + @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_TAB; + boolean cycle = true; + TaskGridNavHelper taskGridNavHelper = + new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID); + + int nextGridPage = + taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); + + assertEquals("Wrong next page returned.", 3, nextGridPage); + } + + @Test + public void equalLengthRows_noFocused_onTop_pressTabWithShift_goesToPreviousBottom() { + IntArray topIds = IntArray.wrap(1, 3, 5); + IntArray bottomIds = IntArray.wrap(2, 4, 6); + int currentPageTaskViewId = 3; + int delta = -1; + @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_TAB; + boolean cycle = true; + TaskGridNavHelper taskGridNavHelper = + new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID); + + int nextGridPage = + taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); + + assertEquals("Wrong next page returned.", 2, nextGridPage); + } + + @Test + public void equalLengthRows_noFocused_onBottom_pressTabWithShift_goesToTop() { + IntArray topIds = IntArray.wrap(1, 3, 5); + IntArray bottomIds = IntArray.wrap(2, 4, 6); + int currentPageTaskViewId = 2; + int delta = -1; + @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_TAB; + boolean cycle = true; + TaskGridNavHelper taskGridNavHelper = + new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID); + + int nextGridPage = + taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); + + assertEquals("Wrong next page returned.", 1, nextGridPage); + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskViewSimulatorTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskViewSimulatorTest.java index be76f9e86d..72cfd92b86 100644 --- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskViewSimulatorTest.java +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskViewSimulatorTest.java @@ -35,9 +35,6 @@ import androidx.test.filters.SmallTest; import com.android.launcher3.DeviceProfile; import com.android.launcher3.InvariantDeviceProfile; -import com.android.launcher3.dagger.LauncherAppComponent; -import com.android.launcher3.dagger.LauncherAppSingleton; -import com.android.launcher3.util.AllModulesMinusWMProxy; import com.android.launcher3.util.DisplayController; import com.android.launcher3.util.DisplayController.Info; import com.android.launcher3.util.LauncherModelHelper; @@ -49,9 +46,6 @@ import com.android.launcher3.util.window.WindowManagerProxy; import com.android.quickstep.FallbackActivityInterface; import com.android.quickstep.util.SurfaceTransaction.MockProperties; -import dagger.BindsInstance; -import dagger.Component; - import org.hamcrest.Description; import org.hamcrest.TypeSafeMatcher; import org.junit.Assert; @@ -165,11 +159,6 @@ public class TaskViewSimulatorTest { void verifyNoTransforms() { LauncherModelHelper helper = new LauncherModelHelper(); try { - DisplayController mockController = mock(DisplayController.class); - - helper.sandboxContext.initDaggerComponent( - DaggerTaskViewSimulatorTest_TaskViewSimulatorTestComponent.builder() - .bindDisplayController(mockController)); int rotation = mDisplaySize.x > mDisplaySize.y ? Surface.ROTATION_90 : Surface.ROTATION_0; CachedDisplayInfo cdi = new CachedDisplayInfo(mDisplaySize, rotation); @@ -203,14 +192,17 @@ public class TaskViewSimulatorTest { DisplayController.Info info = new Info( configurationContext, wmProxy, perDisplayBoundsCache); + + DisplayController mockController = mock(DisplayController.class); when(mockController.getInfo()).thenReturn(info); + helper.sandboxContext.putObject(DisplayController.INSTANCE, mockController); mDeviceProfile = InvariantDeviceProfile.INSTANCE.get(helper.sandboxContext) .getBestMatch(mAppBounds.width(), mAppBounds.height(), rotation); mDeviceProfile.updateInsets(mLauncherInsets); TaskViewSimulator tvs = new TaskViewSimulator(helper.sandboxContext, - FallbackActivityInterface.INSTANCE, false, 0); + FallbackActivityInterface.INSTANCE); tvs.setDp(mDeviceProfile); int launcherRotation = info.rotation; @@ -279,18 +271,4 @@ public class TaskViewSimulatorTest { description.appendValue(mExpected); } } - - @LauncherAppSingleton - @Component(modules = {AllModulesMinusWMProxy.class}) - interface TaskViewSimulatorTestComponent extends LauncherAppComponent { - - @Component.Builder - interface Builder extends LauncherAppComponent.Builder { - - @BindsInstance - Builder bindDisplayController(DisplayController controller); - - TaskViewSimulatorTestComponent build(); - } - } } diff --git a/quickstep/tests/src/com/android/launcher3/model/QuickstepModelDelegateTest.kt b/quickstep/tests/src/com/android/launcher3/model/QuickstepModelDelegateTest.kt new file mode 100644 index 0000000000..a5327628d1 --- /dev/null +++ b/quickstep/tests/src/com/android/launcher3/model/QuickstepModelDelegateTest.kt @@ -0,0 +1,127 @@ +package com.android.launcher3.model + +import android.app.prediction.AppPredictor +import android.app.prediction.AppTarget +import android.app.prediction.AppTargetEvent +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.launcher3.LauncherAppState +import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION +import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PREDICTION +import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WALLPAPERS +import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION +import com.android.launcher3.util.LauncherModelHelper +import org.junit.After +import org.junit.Assert.assertNotSame +import org.junit.Assert.assertSame +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyZeroInteractions +import org.mockito.MockitoAnnotations + +/** Unit tests for [QuickstepModelDelegate]. */ +@RunWith(AndroidJUnit4::class) +class QuickstepModelDelegateTest { + + private lateinit var underTest: QuickstepModelDelegate + private lateinit var modelHelper: LauncherModelHelper + + @Mock private lateinit var target: AppTarget + @Mock private lateinit var mockedAppTargetEvent: AppTargetEvent + @Mock private lateinit var allAppsPredictor: AppPredictor + @Mock private lateinit var hotseatPredictor: AppPredictor + @Mock private lateinit var widgetRecommendationPredictor: AppPredictor + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + modelHelper = LauncherModelHelper() + underTest = QuickstepModelDelegate(modelHelper.sandboxContext) + underTest.mAllAppsState.predictor = allAppsPredictor + underTest.mHotseatState.predictor = hotseatPredictor + underTest.mWidgetsRecommendationState.predictor = widgetRecommendationPredictor + underTest.mApp = LauncherAppState.getInstance(modelHelper.sandboxContext) + underTest.mDataModel = BgDataModel() + } + + @After + fun tearDown() { + modelHelper.destroy() + } + + @Test + fun onAppTargetEvent_notifyTarget() { + underTest.onAppTargetEvent(mockedAppTargetEvent, CONTAINER_PREDICTION) + + verify(allAppsPredictor).notifyAppTargetEvent(mockedAppTargetEvent) + verifyZeroInteractions(hotseatPredictor) + verifyZeroInteractions(widgetRecommendationPredictor) + } + + @Test + fun onWidgetPrediction_notifyWidgetRecommendationPredictor() { + underTest.onAppTargetEvent(mockedAppTargetEvent, CONTAINER_WIDGETS_PREDICTION) + + verifyZeroInteractions(allAppsPredictor) + verify(widgetRecommendationPredictor).notifyAppTargetEvent(mockedAppTargetEvent) + verifyZeroInteractions(hotseatPredictor) + } + + @Test + fun onHotseatPrediction_notifyHotseatPredictor() { + underTest.onAppTargetEvent(mockedAppTargetEvent, CONTAINER_HOTSEAT_PREDICTION) + + verifyZeroInteractions(allAppsPredictor) + verifyZeroInteractions(widgetRecommendationPredictor) + verify(hotseatPredictor).notifyAppTargetEvent(mockedAppTargetEvent) + } + + @Test + fun onOtherClient_notifyHotseatPredictor() { + underTest.onAppTargetEvent(mockedAppTargetEvent, CONTAINER_WALLPAPERS) + + verifyZeroInteractions(allAppsPredictor) + verifyZeroInteractions(widgetRecommendationPredictor) + verify(hotseatPredictor).notifyAppTargetEvent(mockedAppTargetEvent) + } + + @Test + fun hotseatActionPin_recreateHotSeat() { + assertSame(underTest.mHotseatState.predictor, hotseatPredictor) + val appTargetEvent = AppTargetEvent.Builder(target, AppTargetEvent.ACTION_PIN).build() + underTest.markActive() + + underTest.onAppTargetEvent(appTargetEvent, CONTAINER_HOTSEAT_PREDICTION) + + verify(hotseatPredictor).destroy() + assertNotSame(underTest.mHotseatState.predictor, hotseatPredictor) + } + + @Test + fun hotseatActionUnpin_recreateHotSeat() { + assertSame(underTest.mHotseatState.predictor, hotseatPredictor) + underTest.markActive() + val appTargetEvent = AppTargetEvent.Builder(target, AppTargetEvent.ACTION_UNPIN).build() + + underTest.onAppTargetEvent(appTargetEvent, CONTAINER_HOTSEAT_PREDICTION) + + verify(hotseatPredictor).destroy() + assertNotSame(underTest.mHotseatState.predictor, hotseatPredictor) + } + + @Test + fun container_actionPin_notRecreateHotSeat() { + assertSame(underTest.mHotseatState.predictor, hotseatPredictor) + val appTargetEvent = AppTargetEvent.Builder(target, AppTargetEvent.ACTION_UNPIN).build() + underTest.markActive() + + underTest.onAppTargetEvent(appTargetEvent, CONTAINER_PREDICTION) + + verify(allAppsPredictor, never()).destroy() + verify(hotseatPredictor, never()).destroy() + assertSame(underTest.mHotseatState.predictor, hotseatPredictor) + } +} diff --git a/quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java b/quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java index 59ce6370ea..7b57c81b74 100644 --- a/quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java +++ b/quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java @@ -20,7 +20,6 @@ import static android.content.pm.ApplicationInfo.FLAG_INSTALLED; import static android.os.Process.myUserHandle; import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION; -import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; import static com.android.launcher3.util.TestUtil.runOnExecutorSync; @@ -42,12 +41,11 @@ import android.appwidget.AppWidgetProviderInfo; import android.content.ComponentName; import android.content.pm.ApplicationInfo; import android.content.pm.LauncherApps; -import android.os.Process; import android.os.UserHandle; -import android.platform.test.annotations.DisableFlags; import android.platform.test.flag.junit.SetFlagsRule; import android.text.TextUtils; +import androidx.test.core.content.pm.ApplicationInfoBuilder; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; @@ -64,8 +62,6 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.junit.MockitoJUnit; -import org.mockito.junit.MockitoRule; import java.util.Arrays; import java.util.List; @@ -75,9 +71,6 @@ import java.util.stream.Collectors; @RunWith(AndroidJUnit4.class) public final class WidgetsPredicationUpdateTaskTest { - @Rule - public final MockitoRule mocks = MockitoJUnit.rule(); - @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @@ -87,7 +80,6 @@ public final class WidgetsPredicationUpdateTaskTest { private AppWidgetProviderInfo mApp4Provider1; private AppWidgetProviderInfo mApp4Provider2; private AppWidgetProviderInfo mApp5Provider1; - private AppWidgetProviderInfo mApp6PinOnlyProvider1; private List allWidgets; private FakeBgDataModelCallback mCallback = new FakeBgDataModelCallback(); @@ -114,22 +106,16 @@ public final class WidgetsPredicationUpdateTaskTest { ComponentName.createRelative("app4", ".provider2")); mApp5Provider1 = createAppWidgetProviderInfo( ComponentName.createRelative("app5", "provider1")); - mApp6PinOnlyProvider1 = createAppWidgetProviderInfo( - ComponentName.createRelative("app6", "provider1"), - /*hideFromPicker=*/ true - ); - - allWidgets = Arrays.asList(mApp1Provider1, mApp1Provider2, mApp2Provider1, - mApp4Provider1, mApp4Provider2, mApp5Provider1, mApp6PinOnlyProvider1); + mApp4Provider1, mApp4Provider2, mApp5Provider1); mLauncherApps = mModelHelper.sandboxContext.spyService(LauncherApps.class); doAnswer(i -> { String pkg = i.getArgument(0); - ApplicationInfo applicationInfo = new ApplicationInfo(); - applicationInfo.packageName = pkg; - applicationInfo.name = "App " + pkg; - applicationInfo.uid = Process.myUid(); + ApplicationInfo applicationInfo = ApplicationInfoBuilder.newBuilder() + .setPackageName(pkg) + .setName("App " + pkg) + .build(); applicationInfo.category = CATEGORY_PRODUCTIVITY; applicationInfo.flags = FLAG_INSTALLED; return applicationInfo; @@ -159,7 +145,6 @@ public final class WidgetsPredicationUpdateTaskTest { } @Test - @DisableFlags(Flags.FLAG_ENABLE_TIERED_WIDGETS_BY_DEFAULT_IN_PICKER) // Flag off public void widgetsRecommendationRan_shouldOnlyReturnNotAddedWidgetsInAppPredictionOrder() { // Run on model executor so that no other task runs in the middle. runOnExecutorSync(MODEL_EXECUTOR, () -> { @@ -199,7 +184,6 @@ public final class WidgetsPredicationUpdateTaskTest { } @Test - @DisableFlags(Flags.FLAG_ENABLE_TIERED_WIDGETS_BY_DEFAULT_IN_PICKER) // Flag off public void widgetsRecommendationRan_shouldReturnEmptyWidgetsWhenEmpty() { runOnExecutorSync(MODEL_EXECUTOR, () -> { @@ -229,32 +213,6 @@ public final class WidgetsPredicationUpdateTaskTest { }); } - @Test - public void widgetsRecommendations_excludesWidgetsHiddenForPicker() { - runOnExecutorSync(MODEL_EXECUTOR, () -> { - - // Not installed widget - hence eligible - AppTarget widget1 = new AppTarget(new AppTargetId("app1"), "app1", "provider1", - mUserHandle); - // Provider marked as hidden from picker - hence not eligible - AppTarget widget6 = new AppTarget(new AppTargetId("app6"), "app6", "provider1", - mUserHandle); - - mCallback.mRecommendedWidgets = null; - mModelHelper.getModel().enqueueModelUpdateTask( - newWidgetsPredicationTask(List.of(widget1, widget6))); - runOnExecutorSync(MAIN_EXECUTOR, () -> { }); - - // Only widget 1 (and no widget 6 as its meant to be hidden from picker). - List recommendedWidgets = mCallback.mRecommendedWidgets.items - .stream() - .map(itemInfo -> (PendingAddWidgetInfo) itemInfo) - .collect(Collectors.toList()); - assertThat(recommendedWidgets).hasSize(1); - assertThat(recommendedWidgets.get(0).componentName.getPackageName()).isEqualTo("app1"); - }); - } - private void assertWidgetInfo( LauncherAppWidgetProviderInfo actual, AppWidgetProviderInfo expected) { assertThat(actual.provider).isEqualTo(expected.provider); @@ -263,8 +221,7 @@ public final class WidgetsPredicationUpdateTaskTest { private WidgetsPredictionUpdateTask newWidgetsPredicationTask(List appTargets) { return new WidgetsPredictionUpdateTask( - new PredictorState(CONTAINER_WIDGETS_PREDICTION, "test_widgets_prediction", - DEFAULT_LOOKUP_FLAG), + new PredictorState(CONTAINER_WIDGETS_PREDICTION, "test_widgets_prediction"), appTargets); } diff --git a/quickstep/tests/src/com/android/launcher3/model/WidgetsPredictionsRequesterTest.kt b/quickstep/tests/src/com/android/launcher3/model/WidgetsPredictionsRequesterTest.kt new file mode 100644 index 0000000000..5c7b4aba4d --- /dev/null +++ b/quickstep/tests/src/com/android/launcher3/model/WidgetsPredictionsRequesterTest.kt @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.model + +import android.app.prediction.AppTarget +import android.app.prediction.AppTargetEvent +import android.app.prediction.AppTargetId +import android.appwidget.AppWidgetProviderInfo +import android.content.ComponentName +import android.content.Context +import android.os.Process.myUserHandle +import android.os.UserHandle +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.launcher3.DeviceProfile +import com.android.launcher3.InvariantDeviceProfile +import com.android.launcher3.LauncherAppState +import com.android.launcher3.icons.IconCache +import com.android.launcher3.model.WidgetPredictionsRequester.buildBundleForPredictionSession +import com.android.launcher3.model.WidgetPredictionsRequester.filterPredictions +import com.android.launcher3.model.WidgetPredictionsRequester.notOnUiSurfaceFilter +import com.android.launcher3.util.ActivityContextWrapper +import com.android.launcher3.util.PackageUserKey +import com.android.launcher3.util.WidgetUtils.createAppWidgetProviderInfo +import com.android.launcher3.widget.LauncherAppWidgetProviderInfo +import com.google.common.truth.Truth.assertThat +import java.util.function.Predicate +import junit.framework.Assert.assertNotNull +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +@RunWith(AndroidJUnit4::class) +class WidgetsPredictionsRequesterTest { + + private lateinit var mUserHandle: UserHandle + private lateinit var context: Context + private lateinit var deviceProfile: DeviceProfile + private lateinit var testInvariantProfile: InvariantDeviceProfile + + private lateinit var widget1aInfo: AppWidgetProviderInfo + private lateinit var widget1bInfo: AppWidgetProviderInfo + private lateinit var widget2Info: AppWidgetProviderInfo + + private lateinit var widgetItem1a: WidgetItem + private lateinit var widgetItem1b: WidgetItem + private lateinit var widgetItem2: WidgetItem + + private lateinit var allWidgets: Map> + + @Mock private lateinit var iconCache: IconCache + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + mUserHandle = myUserHandle() + context = ActivityContextWrapper(ApplicationProvider.getApplicationContext()) + testInvariantProfile = LauncherAppState.getIDP(context) + deviceProfile = testInvariantProfile.getDeviceProfile(context).copy(context) + + widget1aInfo = + createAppWidgetProviderInfo( + ComponentName.createRelative(APP_1_PACKAGE_NAME, APP_1_PROVIDER_A_CLASS_NAME) + ) + widget1bInfo = + createAppWidgetProviderInfo( + ComponentName.createRelative(APP_1_PACKAGE_NAME, APP_1_PROVIDER_B_CLASS_NAME) + ) + widgetItem1a = createWidgetItem(widget1aInfo) + widgetItem1b = createWidgetItem(widget1bInfo) + + widget2Info = + createAppWidgetProviderInfo( + ComponentName.createRelative(APP_2_PACKAGE_NAME, APP_2_PROVIDER_1_CLASS_NAME) + ) + widgetItem2 = createWidgetItem(widget2Info) + + allWidgets = + mapOf( + PackageUserKey(APP_1_PACKAGE_NAME, mUserHandle) to + listOf(widgetItem1a, widgetItem1b), + PackageUserKey(APP_2_PACKAGE_NAME, mUserHandle) to listOf(widgetItem2), + ) + } + + @Test + fun buildBundleForPredictionSession_includesAddedAppWidgets() { + val existingWidgets = arrayListOf(widget1aInfo, widget1bInfo, widget2Info) + + val bundle = buildBundleForPredictionSession(existingWidgets, TEST_UI_SURFACE) + val addedWidgetsBundleExtra = + bundle.getParcelableArrayList(BUNDLE_KEY_ADDED_APP_WIDGETS, AppTarget::class.java) + + assertNotNull(addedWidgetsBundleExtra) + assertThat(addedWidgetsBundleExtra) + .containsExactly( + buildExpectedAppTargetEvent( + /*pkg=*/ APP_1_PACKAGE_NAME, + /*providerClassName=*/ APP_1_PROVIDER_A_CLASS_NAME, + /*user=*/ mUserHandle + ), + buildExpectedAppTargetEvent( + /*pkg=*/ APP_1_PACKAGE_NAME, + /*providerClassName=*/ APP_1_PROVIDER_B_CLASS_NAME, + /*user=*/ mUserHandle + ), + buildExpectedAppTargetEvent( + /*pkg=*/ APP_2_PACKAGE_NAME, + /*providerClassName=*/ APP_2_PROVIDER_1_CLASS_NAME, + /*user=*/ mUserHandle + ) + ) + } + + @Test + fun filterPredictions_notOnUiSurfaceFilter_returnsOnlyEligiblePredictions() { + val widgetsAlreadyOnSurface = arrayListOf(widget1bInfo) + val filter: Predicate = notOnUiSurfaceFilter(widgetsAlreadyOnSurface) + + val predictions = + listOf( + // already on surface + AppTarget( + AppTargetId(APP_1_PACKAGE_NAME), + APP_1_PACKAGE_NAME, + APP_1_PROVIDER_B_CLASS_NAME, + mUserHandle + ), + // eligible + AppTarget( + AppTargetId(APP_2_PACKAGE_NAME), + APP_2_PACKAGE_NAME, + APP_2_PROVIDER_1_CLASS_NAME, + mUserHandle + ) + ) + + // only 2 was eligible + assertThat(filterPredictions(predictions, allWidgets, filter)).containsExactly(widgetItem2) + } + + @Test + fun filterPredictions_appPredictions_returnsWidgetFromPackage() { + val widgetsAlreadyOnSurface = arrayListOf(widget1bInfo) + val filter: Predicate = notOnUiSurfaceFilter(widgetsAlreadyOnSurface) + + val predictions = + listOf( + AppTarget( + AppTargetId(APP_1_PACKAGE_NAME), + APP_1_PACKAGE_NAME, + "$APP_1_PACKAGE_NAME.SomeActivity", + mUserHandle + ), + AppTarget( + AppTargetId(APP_2_PACKAGE_NAME), + APP_2_PACKAGE_NAME, + "$APP_2_PACKAGE_NAME.SomeActivity2", + mUserHandle + ), + ) + + assertThat(filterPredictions(predictions, allWidgets, filter)) + .containsExactly(widgetItem1a, widgetItem2) + } + + private fun createWidgetItem( + providerInfo: AppWidgetProviderInfo, + ): WidgetItem { + val widgetInfo = LauncherAppWidgetProviderInfo.fromProviderInfo(context, providerInfo) + return WidgetItem(widgetInfo, testInvariantProfile, iconCache, context) + } + + companion object { + const val TEST_UI_SURFACE = "widgets_test" + const val BUNDLE_KEY_ADDED_APP_WIDGETS = "added_app_widgets" + + const val APP_1_PACKAGE_NAME = "com.example.app1" + const val APP_1_PROVIDER_A_CLASS_NAME = "app1Provider1" + const val APP_1_PROVIDER_B_CLASS_NAME = "app1Provider2" + + const val APP_2_PACKAGE_NAME = "com.example.app2" + const val APP_2_PROVIDER_1_CLASS_NAME = "app2Provider1" + + const val TEST_PACKAGE = "pkg" + + private fun buildExpectedAppTargetEvent( + pkg: String, + providerClassName: String, + userHandle: UserHandle + ): AppTargetEvent { + val appTarget = + AppTarget.Builder( + /*id=*/ AppTargetId("widget:$pkg"), + /*packageName=*/ pkg, + /*user=*/ userHandle + ) + .setClassName(providerClassName) + .build() + return AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_PIN) + .setLaunchLocation(TEST_UI_SURFACE) + .build() + } + } +} diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/FallbackTaskbarUIControllerTest.kt b/quickstep/tests/src/com/android/launcher3/taskbar/FallbackTaskbarUIControllerTest.kt new file mode 100644 index 0000000000..04012c027d --- /dev/null +++ b/quickstep/tests/src/com/android/launcher3/taskbar/FallbackTaskbarUIControllerTest.kt @@ -0,0 +1,80 @@ +/* + * 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.launcher3.taskbar + +import androidx.test.runner.AndroidJUnit4 +import com.android.launcher3.statemanager.StateManager +import com.android.quickstep.RecentsActivity +import com.android.quickstep.fallback.RecentsState +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.mock +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +@RunWith(AndroidJUnit4::class) +class FallbackTaskbarUIControllerTest : TaskbarBaseTestCase() { + + lateinit var fallbackTaskbarUIController: FallbackTaskbarUIController + lateinit var stateListener: StateManager.StateListener + + private val recentsActivity: RecentsActivity = mock() + private val stateManager: StateManager = mock() + + @Before + override fun setup() { + super.setup() + whenever(recentsActivity.stateManager).thenReturn(stateManager) + fallbackTaskbarUIController = FallbackTaskbarUIController(recentsActivity) + + // Capture registered state listener to send events to in our tests + val captor = argumentCaptor>() + fallbackTaskbarUIController.init(taskbarControllers) + verify(stateManager).addStateListener(captor.capture()) + stateListener = captor.lastValue + } + + @Test + fun stateTransitionComplete_stateDefault() { + stateListener.onStateTransitionComplete(RecentsState.DEFAULT) + // verify dragging disabled + verify(taskbarDragController, times(1)).setDisallowGlobalDrag(true) + verify(taskbarAllAppsController, times(1)).setDisallowGlobalDrag(true) + // verify long click enabled + verify(taskbarDragController, times(1)).setDisallowLongClick(false) + verify(taskbarAllAppsController, times(1)).setDisallowLongClick(false) + // verify split selection enabled + verify(taskbarPopupController, times(1)).setAllowInitialSplitSelection(true) + } + + @Test + fun stateTransitionComplete_stateSplitSelect() { + stateListener.onStateTransitionComplete(RecentsState.OVERVIEW_SPLIT_SELECT) + // verify dragging disabled + verify(taskbarDragController, times(1)).setDisallowGlobalDrag(false) + verify(taskbarAllAppsController, times(1)).setDisallowGlobalDrag(false) + // verify long click enabled + verify(taskbarDragController, times(1)).setDisallowLongClick(true) + verify(taskbarAllAppsController, times(1)).setDisallowLongClick(true) + // verify split selection enabled + verify(taskbarPopupController, times(1)).setAllowInitialSplitSelection(false) + } +} diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt new file mode 100644 index 0000000000..15b1e532bd --- /dev/null +++ b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt @@ -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.launcher3.taskbar + +import com.android.launcher3.taskbar.allapps.TaskbarAllAppsController +import com.android.launcher3.taskbar.bubbles.BubbleControllers +import com.android.launcher3.taskbar.overlay.TaskbarOverlayController +import com.android.systemui.shared.rotation.RotationButtonController +import java.util.Optional +import org.junit.Before +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +/** + * Helper class to extend to get access to all controllers. Gotta be careful of your relationship + * with this class though, it can be quite... controlling. + */ +abstract class TaskbarBaseTestCase { + + @Mock lateinit var taskbarActivityContext: TaskbarActivityContext + @Mock lateinit var taskbarDragController: TaskbarDragController + @Mock lateinit var navButtonController: TaskbarNavButtonController + @Mock lateinit var navbarButtonsViewController: NavbarButtonsViewController + @Mock lateinit var rotationButtonController: RotationButtonController + @Mock lateinit var taskbarDragLayerController: TaskbarDragLayerController + @Mock lateinit var taskbarScrimViewController: TaskbarScrimViewController + @Mock lateinit var taskbarViewController: TaskbarViewController + @Mock lateinit var taskbarUnfoldAnimationController: TaskbarUnfoldAnimationController + @Mock lateinit var taskbarKeyguardController: TaskbarKeyguardController + @Mock lateinit var stashedHandleViewController: StashedHandleViewController + @Mock lateinit var taskbarStashController: TaskbarStashController + @Mock lateinit var taskbarAutohideSuspendController: TaskbarAutohideSuspendController + @Mock lateinit var taskbarPopupController: TaskbarPopupController + @Mock + lateinit var taskbarForceVisibleImmersiveController: TaskbarForceVisibleImmersiveController + @Mock lateinit var taskbarAllAppsController: TaskbarAllAppsController + @Mock lateinit var taskbarInsetsController: TaskbarInsetsController + @Mock lateinit var voiceInteractionWindowController: VoiceInteractionWindowController + @Mock lateinit var taskbarRecentAppsController: TaskbarRecentAppsController + @Mock lateinit var taskbarTranslationController: TaskbarTranslationController + @Mock lateinit var taskbarSpringOnStashController: TaskbarSpringOnStashController + @Mock lateinit var taskbarOverlayController: TaskbarOverlayController + @Mock lateinit var taskbarEduTooltipController: TaskbarEduTooltipController + @Mock lateinit var keyboardQuickSwitchController: KeyboardQuickSwitchController + @Mock lateinit var taskbarPinningController: TaskbarPinningController + @Mock lateinit var optionalBubbleControllers: Optional + + lateinit var taskbarControllers: TaskbarControllers + + @Before + open fun setup() { + /* + * NOTE: Mocking of controllers that are written in Kotlin won't work since their methods + * are final by default (and should not be changed only for tests), meaning unmockable. + * Womp, womp woooommmmppp. + * If you want to mock one of those methods, you need to make a parent interface that + * includes that method to allow mocking it. + */ + MockitoAnnotations.initMocks(this) + taskbarControllers = + TaskbarControllers( + taskbarActivityContext, + taskbarDragController, + navButtonController, + navbarButtonsViewController, + rotationButtonController, + taskbarDragLayerController, + taskbarViewController, + taskbarScrimViewController, + taskbarUnfoldAnimationController, + taskbarKeyguardController, + stashedHandleViewController, + taskbarStashController, + taskbarAutohideSuspendController, + taskbarPopupController, + taskbarForceVisibleImmersiveController, + taskbarOverlayController, + taskbarAllAppsController, + taskbarInsetsController, + voiceInteractionWindowController, + taskbarTranslationController, + taskbarSpringOnStashController, + taskbarRecentAppsController, + taskbarEduTooltipController, + keyboardQuickSwitchController, + taskbarPinningController, + optionalBubbleControllers, + ) + } +} diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarHoverToolTipControllerTest.java b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarHoverToolTipControllerTest.java new file mode 100644 index 0000000000..9ed39066dd --- /dev/null +++ b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarHoverToolTipControllerTest.java @@ -0,0 +1,219 @@ +/* + * 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.launcher3.taskbar; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; + +import static com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.view.Display; +import android.view.MotionEvent; + +import androidx.test.filters.SmallTest; + +import com.android.launcher3.BubbleTextView; +import com.android.launcher3.folder.Folder; +import com.android.launcher3.folder.FolderIcon; +import com.android.launcher3.model.data.FolderInfo; +import com.android.launcher3.util.ActivityContextWrapper; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.stubbing.Answer; + +/** + * Tests for TaskbarHoverToolTipController. + */ +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +public class TaskbarHoverToolTipControllerTest extends TaskbarBaseTestCase { + + private TaskbarHoverToolTipController mTaskbarHoverToolTipController; + private TestableLooper mTestableLooper; + + @Mock private TaskbarView mTaskbarView; + @Mock private MotionEvent mMotionEvent; + @Mock private BubbleTextView mHoverBubbleTextView; + @Mock private FolderIcon mHoverFolderIcon; + @Mock private Display mDisplay; + @Mock private TaskbarDragLayer mTaskbarDragLayer; + private Folder mSpyFolderView; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + + Context context = getApplicationContext(); + + doAnswer((Answer) invocation -> context.getSystemService( + (String) invocation.getArgument(0))) + .when(taskbarActivityContext).getSystemService(anyString()); + when(taskbarActivityContext.getResources()).thenReturn(context.getResources()); + when(taskbarActivityContext.getApplicationInfo()).thenReturn( + context.getApplicationInfo()); + when(taskbarActivityContext.getDragLayer()).thenReturn(mTaskbarDragLayer); + when(taskbarActivityContext.getMainLooper()).thenReturn(context.getMainLooper()); + when(taskbarActivityContext.getDisplay()).thenReturn(mDisplay); + + when(mTaskbarDragLayer.getChildCount()).thenReturn(1); + mSpyFolderView = spy(new Folder(new ActivityContextWrapper(context), null)); + when(mTaskbarDragLayer.getChildAt(anyInt())).thenReturn(mSpyFolderView); + doReturn(false).when(mSpyFolderView).isOpen(); + + when(mHoverBubbleTextView.getText()).thenReturn("tooltip"); + doAnswer((Answer) invocation -> { + Object[] args = invocation.getArguments(); + ((int[]) args[0])[0] = 0; + ((int[]) args[0])[1] = 0; + return null; + }).when(mHoverBubbleTextView).getLocationOnScreen(any(int[].class)); + when(mHoverBubbleTextView.getWidth()).thenReturn(100); + when(mHoverBubbleTextView.getHeight()).thenReturn(100); + + mHoverFolderIcon.mInfo = new FolderInfo(); + mHoverFolderIcon.mInfo.title = "tooltip"; + doAnswer((Answer) invocation -> { + Object[] args = invocation.getArguments(); + ((int[]) args[0])[0] = 0; + ((int[]) args[0])[1] = 0; + return null; + }).when(mHoverFolderIcon).getLocationOnScreen(any(int[].class)); + when(mHoverFolderIcon.getWidth()).thenReturn(100); + when(mHoverFolderIcon.getHeight()).thenReturn(100); + + when(mTaskbarView.getTop()).thenReturn(200); + + mTaskbarHoverToolTipController = new TaskbarHoverToolTipController( + taskbarActivityContext, mTaskbarView, mHoverBubbleTextView); + mTestableLooper = TestableLooper.get(this); + } + + @Test + public void onHover_hoverEnterIcon_revealToolTip() { + when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_ENTER); + when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_ENTER); + + boolean hoverHandled = + mTaskbarHoverToolTipController.onHover(mHoverBubbleTextView, mMotionEvent); + waitForIdleSync(); + + assertThat(hoverHandled).isTrue(); + verify(taskbarActivityContext).setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS, + true); + } + + @Test + public void onHover_hoverExitIcon_closeToolTip() { + when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_EXIT); + when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_EXIT); + + boolean hoverHandled = + mTaskbarHoverToolTipController.onHover(mHoverBubbleTextView, mMotionEvent); + waitForIdleSync(); + + assertThat(hoverHandled).isTrue(); + verify(taskbarActivityContext).setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS, + false); + } + + @Test + public void onHover_hoverEnterFolderIcon_revealToolTip() { + when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_ENTER); + when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_ENTER); + + boolean hoverHandled = + mTaskbarHoverToolTipController.onHover(mHoverFolderIcon, mMotionEvent); + waitForIdleSync(); + + assertThat(hoverHandled).isTrue(); + verify(taskbarActivityContext).setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS, + true); + } + + @Test + public void onHover_hoverExitFolderIcon_closeToolTip() { + when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_EXIT); + when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_EXIT); + + boolean hoverHandled = + mTaskbarHoverToolTipController.onHover(mHoverFolderIcon, mMotionEvent); + waitForIdleSync(); + + assertThat(hoverHandled).isTrue(); + verify(taskbarActivityContext).setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS, + false); + } + + @Test + public void onHover_hoverExitFolderOpen_closeToolTip() { + when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_EXIT); + when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_EXIT); + doReturn(true).when(mSpyFolderView).isOpen(); + + boolean hoverHandled = + mTaskbarHoverToolTipController.onHover(mHoverFolderIcon, mMotionEvent); + waitForIdleSync(); + + assertThat(hoverHandled).isTrue(); + verify(taskbarActivityContext).setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS, + false); + } + + @Test + public void onHover_hoverEnterFolderOpen_noToolTip() { + when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_ENTER); + when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_ENTER); + doReturn(true).when(mSpyFolderView).isOpen(); + + boolean hoverHandled = + mTaskbarHoverToolTipController.onHover(mHoverFolderIcon, mMotionEvent); + + assertThat(hoverHandled).isFalse(); + } + + @Test + public void onHover_hoverMove_noUpdate() { + when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_MOVE); + when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_MOVE); + + boolean hoverHandled = + mTaskbarHoverToolTipController.onHover(mHoverFolderIcon, mMotionEvent); + + assertThat(hoverHandled).isFalse(); + } + + private void waitForIdleSync() { + mTestableLooper.processAllMessages(); + } +} diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarKeyguardControllerTest.kt b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarKeyguardControllerTest.kt new file mode 100644 index 0000000000..e619e7cd38 --- /dev/null +++ b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarKeyguardControllerTest.kt @@ -0,0 +1,111 @@ +/* + * 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.launcher3.taskbar + +import android.app.KeyguardManager +import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BACK_DISABLED +import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING +import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DOZING +import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING +import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED +import org.junit.Before +import org.junit.Test +import org.mockito.kotlin.any +import org.mockito.kotlin.mock +import org.mockito.kotlin.never +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +class TaskbarKeyguardControllerTest : TaskbarBaseTestCase() { + + private val baseDragLayer: TaskbarDragLayer = mock() + private val keyguardManager: KeyguardManager = mock() + + @Before + override fun setup() { + super.setup() + whenever(taskbarActivityContext.getSystemService(KeyguardManager::class.java)) + .thenReturn(keyguardManager) + whenever(baseDragLayer.childCount).thenReturn(0) + whenever(taskbarActivityContext.dragLayer).thenReturn(baseDragLayer) + + taskbarKeyguardController = TaskbarKeyguardController(taskbarActivityContext) + taskbarKeyguardController.init(navbarButtonsViewController) + } + + @Test + fun uninterestingFlags_noActions() { + setFlags(0) + verify(navbarButtonsViewController, never()).setKeyguardVisible(any(), any()) + } + + @Test + fun keyguardShowing() { + setFlags(SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING) + verify(navbarButtonsViewController, times(1)) + .setKeyguardVisible(true /*isKeyguardVisible*/, false /*isKeyguardOccluded*/) + } + + @Test + fun dozingShowing() { + setFlags(SYSUI_STATE_DEVICE_DOZING) + verify(navbarButtonsViewController, times(1)) + .setKeyguardVisible(true /*isKeyguardVisible*/, false /*isKeyguardOccluded*/) + } + + @Test + fun keyguardOccluded() { + setFlags(SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED) + verify(navbarButtonsViewController, times(1)) + .setKeyguardVisible(false /*isKeyguardVisible*/, true /*isKeyguardOccluded*/) + } + + @Test + fun keyguardOccludedAndDozing() { + setFlags(SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED.or(SYSUI_STATE_DEVICE_DOZING)) + verify(navbarButtonsViewController, times(1)) + .setKeyguardVisible(true /*isKeyguardVisible*/, true /*isKeyguardOccluded*/) + } + + @Test + fun deviceInsecure_hideBackForBouncer() { + whenever(keyguardManager.isDeviceSecure).thenReturn(false) + setFlags(SYSUI_STATE_BOUNCER_SHOWING) + + verify(navbarButtonsViewController, times(1)).setBackForBouncer(false) + } + + @Test + fun deviceSecure_showBackForBouncer() { + whenever(keyguardManager.isDeviceSecure).thenReturn(true) + setFlags(SYSUI_STATE_BOUNCER_SHOWING) + + verify(navbarButtonsViewController, times(1)).setBackForBouncer(true) + } + + @Test + fun backDisabled_hideBackForBouncer() { + whenever(keyguardManager.isDeviceSecure).thenReturn(true) + setFlags(SYSUI_STATE_BACK_DISABLED.or(SYSUI_STATE_BOUNCER_SHOWING)) + + verify(navbarButtonsViewController, times(1)).setBackForBouncer(false) + } + + private fun setFlags(flags: Long) { + taskbarKeyguardController.updateStateForSysuiFlags(flags) + } +} diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt new file mode 100644 index 0000000000..104263af5b --- /dev/null +++ b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt @@ -0,0 +1,242 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.taskbar + +import android.app.ActivityManager.RunningTaskInfo +import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM +import android.content.ComponentName +import android.content.Intent +import android.os.Process +import android.os.UserHandle +import android.testing.AndroidTestingRunner +import com.android.launcher3.model.data.AppInfo +import com.android.launcher3.model.data.ItemInfo +import com.android.launcher3.statehandlers.DesktopVisibilityController +import com.android.quickstep.RecentsModel +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.junit.MockitoJUnit +import org.mockito.kotlin.whenever + +@RunWith(AndroidTestingRunner::class) +class TaskbarRecentAppsControllerTest : TaskbarBaseTestCase() { + + @get:Rule val mockitoRule = MockitoJUnit.rule() + + @Mock private lateinit var mockRecentsModel: RecentsModel + @Mock private lateinit var mockDesktopVisibilityController: DesktopVisibilityController + + private var nextTaskId: Int = 500 + + private lateinit var recentAppsController: TaskbarRecentAppsController + private lateinit var userHandle: UserHandle + + @Before + fun setUp() { + super.setup() + userHandle = Process.myUserHandle() + recentAppsController = + TaskbarRecentAppsController(mockRecentsModel) { mockDesktopVisibilityController } + recentAppsController.init(taskbarControllers) + recentAppsController.isEnabled = true + recentAppsController.setApps( + ALL_APP_PACKAGES.map { createTestAppInfo(packageName = it) }.toTypedArray() + ) + } + + @Test + fun updateHotseatItemInfos_notInDesktopMode_returnsExistingHotseatItems() { + setInDesktopMode(false) + val hotseatItems = + createHotseatItemsFromPackageNames(listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2)) + + assertThat(recentAppsController.updateHotseatItemInfos(hotseatItems.toTypedArray())) + .isEqualTo(hotseatItems.toTypedArray()) + } + + @Test + fun updateHotseatItemInfos_notInDesktopMode_runningApps_returnsExistingHotseatItems() { + setInDesktopMode(false) + val hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2) + val hotseatItems = createHotseatItemsFromPackageNames(hotseatPackages) + val runningTasks = + createDesktopTasksFromPackageNames(listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2)) + whenever(mockRecentsModel.runningTasks).thenReturn(runningTasks) + recentAppsController.updateRunningApps() + + val newHotseatItems = + recentAppsController.updateHotseatItemInfos(hotseatItems.toTypedArray()) + + assertThat(newHotseatItems.map { it?.targetPackage }) + .containsExactlyElementsIn(hotseatPackages) + } + + @Test + fun updateHotseatItemInfos_noRunningApps_returnsExistingHotseatItems() { + setInDesktopMode(true) + val hotseatItems = + createHotseatItemsFromPackageNames(listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2)) + + assertThat(recentAppsController.updateHotseatItemInfos(hotseatItems.toTypedArray())) + .isEqualTo(hotseatItems.toTypedArray()) + } + + @Test + fun updateHotseatItemInfos_returnsExistingHotseatItemsAndRunningApps() { + setInDesktopMode(true) + val hotseatItems = + createHotseatItemsFromPackageNames(listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2)) + val runningTasks = + createDesktopTasksFromPackageNames(listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2)) + whenever(mockRecentsModel.runningTasks).thenReturn(runningTasks) + recentAppsController.updateRunningApps() + + val newHotseatItems = + recentAppsController.updateHotseatItemInfos(hotseatItems.toTypedArray()) + + val expectedPackages = + listOf( + HOTSEAT_PACKAGE_1, + HOTSEAT_PACKAGE_2, + RUNNING_APP_PACKAGE_1, + RUNNING_APP_PACKAGE_2, + ) + assertThat(newHotseatItems.map { it?.targetPackage }) + .containsExactlyElementsIn(expectedPackages) + } + + @Test + fun updateHotseatItemInfos_runningAppIsHotseatItem_returnsDistinctItems() { + setInDesktopMode(true) + val hotseatItems = + createHotseatItemsFromPackageNames(listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2)) + val runningTasks = + createDesktopTasksFromPackageNames( + listOf(HOTSEAT_PACKAGE_1, RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2) + ) + whenever(mockRecentsModel.runningTasks).thenReturn(runningTasks) + recentAppsController.updateRunningApps() + + val newHotseatItems = + recentAppsController.updateHotseatItemInfos(hotseatItems.toTypedArray()) + + val expectedPackages = + listOf( + HOTSEAT_PACKAGE_1, + HOTSEAT_PACKAGE_2, + RUNNING_APP_PACKAGE_1, + RUNNING_APP_PACKAGE_2, + ) + assertThat(newHotseatItems.map { it?.targetPackage }) + .containsExactlyElementsIn(expectedPackages) + } + + @Test + fun getRunningApps_notInDesktopMode_returnsEmptySet() { + setInDesktopMode(false) + val runningTasks = + createDesktopTasksFromPackageNames(listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2)) + whenever(mockRecentsModel.runningTasks).thenReturn(runningTasks) + recentAppsController.updateRunningApps() + + assertThat(recentAppsController.runningApps).isEmpty() + assertThat(recentAppsController.minimizedApps).isEmpty() + } + + @Test + fun getRunningApps_inDesktopMode_returnsRunningApps() { + setInDesktopMode(true) + val runningTasks = + createDesktopTasksFromPackageNames(listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2)) + whenever(mockRecentsModel.runningTasks).thenReturn(runningTasks) + recentAppsController.updateRunningApps() + + assertThat(recentAppsController.runningApps) + .containsExactly(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2) + assertThat(recentAppsController.minimizedApps).isEmpty() + } + + @Test + fun getMinimizedApps_inDesktopMode_returnsAllAppsRunningAndInvisibleAppsMinimized() { + setInDesktopMode(true) + val runningTasks = + ArrayList( + listOf( + createDesktopTaskInfo(RUNNING_APP_PACKAGE_1) { isVisible = true }, + createDesktopTaskInfo(RUNNING_APP_PACKAGE_2) { isVisible = true }, + createDesktopTaskInfo(RUNNING_APP_PACKAGE_3) { isVisible = false }, + ) + ) + whenever(mockRecentsModel.runningTasks).thenReturn(runningTasks) + recentAppsController.updateRunningApps() + + assertThat(recentAppsController.runningApps) + .containsExactly(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2, RUNNING_APP_PACKAGE_3) + assertThat(recentAppsController.minimizedApps).containsExactly(RUNNING_APP_PACKAGE_3) + } + + private fun createHotseatItemsFromPackageNames(packageNames: List): List { + return packageNames.map { createTestAppInfo(packageName = it) } + } + + private fun createDesktopTasksFromPackageNames( + packageNames: List + ): ArrayList { + return ArrayList(packageNames.map { createDesktopTaskInfo(packageName = it) }) + } + + private fun createDesktopTaskInfo( + packageName: String, + init: RunningTaskInfo.() -> Unit = { isVisible = true }, + ): RunningTaskInfo { + return RunningTaskInfo().apply { + taskId = nextTaskId++ + configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM + realActivity = ComponentName(packageName, "TestActivity") + init() + } + } + + private fun createTestAppInfo( + packageName: String = "testPackageName", + className: String = "testClassName" + ) = AppInfo(ComponentName(packageName, className), className /* title */, userHandle, Intent()) + + private fun setInDesktopMode(inDesktopMode: Boolean) { + whenever(mockDesktopVisibilityController.areDesktopTasksVisible()).thenReturn(inDesktopMode) + } + + private companion object { + const val HOTSEAT_PACKAGE_1 = "hotseat1" + const val HOTSEAT_PACKAGE_2 = "hotseat2" + const val RUNNING_APP_PACKAGE_1 = "running1" + const val RUNNING_APP_PACKAGE_2 = "running2" + const val RUNNING_APP_PACKAGE_3 = "running3" + val ALL_APP_PACKAGES = + listOf( + HOTSEAT_PACKAGE_1, + HOTSEAT_PACKAGE_2, + RUNNING_APP_PACKAGE_1, + RUNNING_APP_PACKAGE_2, + RUNNING_APP_PACKAGE_3, + ) + } +} diff --git a/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java b/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java index c215ff2227..44c23ba9a2 100644 --- a/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java +++ b/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java @@ -24,22 +24,13 @@ import androidx.test.uiautomator.By; import androidx.test.uiautomator.Until; import com.android.launcher3.tapl.LaunchedAppState; -import com.android.launcher3.tapl.TestHelpers; import com.android.launcher3.ui.AbstractLauncherUiTest; import com.android.launcher3.uioverrides.QuickstepLauncher; -import com.android.launcher3.util.TestUtil; -import com.android.launcher3.util.Wait; -import com.android.quickstep.fallback.RecentsState; -import com.android.quickstep.fallback.window.RecentsWindowManager; import com.android.quickstep.views.RecentsView; import org.junit.rules.RuleChain; import org.junit.rules.TestRule; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Supplier; - /** * Base class for all instrumentation tests that deal with Quickstep. */ @@ -62,48 +53,10 @@ public abstract class AbstractQuickStepTest extends AbstractLauncherUiTest state) { - waitForRecentsWindowCondition(message, recentsWindow -> - recentsWindow.getStateManager().getCurrentStableState() == state.get()); - } - - // Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide - // flakiness. - protected void waitForRecentsWindowCondition(String - message, Function condition) { - waitForRecentsWindowCondition(message, condition, TestUtil.DEFAULT_UI_TIMEOUT); - } - - protected T getFromRecentsWindow(Function f) { - if (!TestHelpers.isInLauncherProcess()) return null; - return getOnUiThread(() -> { - RecentsWindowManager recentsWindowManager = - RecentsWindowManager.getRecentsWindowTracker().getCreatedContext(); - return recentsWindowManager != null ? f.apply(recentsWindowManager) : null; - }); - } - - // Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide - // flakiness. - protected void waitForRecentsWindowCondition( - String message, Function condition, long timeout) { - verifyKeyguardInvisible(); - if (!TestHelpers.isInLauncherProcess()) return; - Wait.atMost(message, () -> getFromRecentsWindow(condition), mLauncher, timeout); - } - - protected boolean isInRecentsWindowState(Supplier state) { - if (!TestHelpers.isInLauncherProcess()) return true; - return getFromRecentsWindow( - recentsWindow -> recentsWindow.getStateManager().getState() == state.get()); - } - protected void assertTestActivityIsRunning(int activityNumber, String message) { assertTrue(message, mDevice.wait( Until.hasObject(By.pkg(getAppPackageName()).text("TestActivity" + activityNumber)), - TestUtil.DEFAULT_UI_TIMEOUT)); + DEFAULT_UI_TIMEOUT)); } protected LaunchedAppState getAndAssertLaunchedApp() { diff --git a/quickstep/tests/src/com/android/quickstep/AbstractTaplTestsTaskbar.java b/quickstep/tests/src/com/android/quickstep/AbstractTaplTestsTaskbar.java index 0ccc76b24e..fc757b44c9 100644 --- a/quickstep/tests/src/com/android/quickstep/AbstractTaplTestsTaskbar.java +++ b/quickstep/tests/src/com/android/quickstep/AbstractTaplTestsTaskbar.java @@ -55,9 +55,7 @@ public class AbstractTaplTestsTaskbar extends AbstractQuickStepTest { "com.android.launcher3.testcomponent.BaseTestingActivity"); mLauncherLayout = TestUtil.setLauncherDefaultLayout(mTargetContext, layoutBuilder); AbstractLauncherUiTest.initialize(this); - if (startCalendarAppDuringSetup()) { - startAppFast(CALCULATOR_APP_PACKAGE); - } + startAppFast(CALCULATOR_APP_PACKAGE); mLauncher.enableBlockTimeout(true); mLauncher.showTaskbarIfHidden(); } @@ -74,20 +72,8 @@ public class AbstractTaplTestsTaskbar extends AbstractQuickStepTest { return DisplayController.isTransientTaskbar(context); } - protected boolean startCalendarAppDuringSetup() { - return true; - } - - protected boolean expectTaskbarIconsMatchHotseat() { - return true; - } - protected Taskbar getTaskbar() { Taskbar taskbar = mLauncher.getLaunchedAppState().getTaskbar(); - if (!expectTaskbarIconsMatchHotseat()) { - return taskbar; - } - List taskbarIconNames = taskbar.getIconNames(); List hotseatIconNames = mLauncher.getHotseatIconNames(); diff --git a/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt b/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt index 8a2393d539..50b5df13f2 100644 --- a/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt +++ b/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt @@ -16,50 +16,35 @@ package com.android.quickstep -import android.Manifest.permission.SYSTEM_ALERT_WINDOW import android.content.ComponentName -import android.content.Context import android.content.Intent -import android.content.pm.PackageInfo -import android.content.pm.PackageManager -import android.platform.test.annotations.DisableFlags -import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.SetFlagsRule -import android.view.Display.DEFAULT_DISPLAY -import androidx.test.platform.app.InstrumentationRegistry +import com.android.dx.mockito.inline.extended.ExtendedMockito import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession import com.android.dx.mockito.inline.extended.StaticMockitoSession -import com.android.internal.R import com.android.launcher3.AbstractFloatingView import com.android.launcher3.AbstractFloatingViewHelper -import com.android.launcher3.Flags.enableRefactorTaskThumbnail import com.android.launcher3.logging.StatsLogManager import com.android.launcher3.logging.StatsLogManager.LauncherEvent -import com.android.launcher3.model.data.TaskViewItemInfo +import com.android.launcher3.model.data.WorkspaceItemInfo +import com.android.launcher3.uioverrides.QuickstepLauncher import com.android.launcher3.util.SplitConfigurationOptions import com.android.launcher3.util.TransformingTouchDelegate import com.android.quickstep.TaskOverlayFactory.TaskOverlay -import com.android.quickstep.task.thumbnail.TaskThumbnailView import com.android.quickstep.views.LauncherRecentsView -import com.android.quickstep.views.RecentsViewContainer -import com.android.quickstep.views.TaskContainer import com.android.quickstep.views.TaskThumbnailViewDeprecated import com.android.quickstep.views.TaskView import com.android.quickstep.views.TaskViewIcon -import com.android.quickstep.views.TaskViewType import com.android.systemui.shared.recents.model.Task import com.android.systemui.shared.recents.model.Task.TaskKey import com.android.window.flags.Flags -import com.android.wm.shell.shared.desktopmode.DesktopModeStatus -import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource +import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource +import com.android.wm.shell.shared.DesktopModeStatus import com.google.common.truth.Truth.assertThat import org.junit.After import org.junit.Before import org.junit.Rule import org.junit.Test -import org.mockito.ArgumentMatchers.anyInt -import org.mockito.ArgumentMatchers.anyString -import org.mockito.Mockito.`when` import org.mockito.kotlin.any import org.mockito.kotlin.doReturn import org.mockito.kotlin.eq @@ -69,22 +54,25 @@ import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import org.mockito.quality.Strictness -/** Test for [DesktopSystemShortcut] */ -// TODO(b/403558856): Improve test coverage for DesktopModeCompatPolicy integration. +/** Test for DesktopSystemShortcut */ class DesktopSystemShortcutTest { @get:Rule val setFlagsRule = SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT) - private val launcher: RecentsViewContainer = mock() + private val launcher: QuickstepLauncher = mock() private val statsLogManager: StatsLogManager = mock() private val statsLogger: StatsLogManager.StatsLogger = mock() private val recentsView: LauncherRecentsView = mock() + private val taskView: TaskView = mock() + private val workspaceItemInfo: WorkspaceItemInfo = mock() private val abstractFloatingViewHelper: AbstractFloatingViewHelper = mock() - private val overlayFactory: TaskOverlayFactory = mock() + private val thumbnailViewDeprecated: TaskThumbnailViewDeprecated = mock() + private val iconView: TaskViewIcon = mock() + private val transformingTouchDelegate: TransformingTouchDelegate = mock() private val factory: TaskShortcutFactory = DesktopSystemShortcut.createFactory(abstractFloatingViewHelper) - private val context: Context = spy(InstrumentationRegistry.getInstrumentation().targetContext) - private val taskView: TaskView = createTaskViewMock() + private val overlayFactory: TaskOverlayFactory = mock() + private val overlay: TaskOverlay<*> = mock() private lateinit var mockitoSession: StaticMockitoSession @@ -93,11 +81,11 @@ class DesktopSystemShortcutTest { mockitoSession = mockitoSession() .strictness(Strictness.LENIENT) - .mockStatic(DesktopModeStatus::class.java) + .spyStatic(DesktopModeStatus::class.java) .startMocking() - whenever(DesktopModeStatus.canEnterDesktopMode(any())).thenReturn(true) - whenever(overlayFactory.createOverlay(any())).thenReturn(mock>()) - whenever(launcher.asContext()).thenReturn(context) + ExtendedMockito.doReturn(true).`when` { DesktopModeStatus.enforceDeviceRestrictions() } + ExtendedMockito.doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } + whenever(overlayFactory.createOverlay(any())).thenReturn(overlay) } @After @@ -107,7 +95,22 @@ class DesktopSystemShortcutTest { @Test fun createDesktopTaskShortcutFactory_desktopModeDisabled() { - `when`(DesktopModeStatus.canEnterDesktopMode(any())).thenReturn(false) + setFlagsRule.disableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + + val task = + Task(TaskKey(1, 0, Intent(), ComponentName("", ""), 0, 2000)).apply { + isDockable = true + } + val taskContainer = createTaskContainer(task) + + val shortcuts = factory.getShortcuts(launcher, taskContainer) + assertThat(shortcuts).isNull() + } + + @Test + fun createDesktopTaskShortcutFactory_desktopModeEnabled_DeviceNotSupported() { + setFlagsRule.enableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + ExtendedMockito.doReturn(false).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } val taskContainer = createTaskContainer(createTask()) @@ -116,139 +119,22 @@ class DesktopSystemShortcutTest { } @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) - @DisableFlags(Flags.FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PERMISSION) - fun createDesktopTaskShortcutFactory_transparentTask() { - val baseComponent = ComponentName("", /* class */ "") - val taskKey = - TaskKey( - /* id */ 1, - /* windowingMode */ 0, - Intent(), - baseComponent, - /* userId */ 0, - /* lastActiveTime */ 2000, - DEFAULT_DISPLAY, - baseComponent, - /* numActivities */ 1, - /* isTopActivityNoDisplay */ false, - /* isActivityStackTransparent */ true, - ) - val taskContainer = createTaskContainer(Task(taskKey)) - val shortcuts = factory.getShortcuts(launcher, taskContainer) - assertThat(shortcuts).isNull() - } + fun createDesktopTaskShortcutFactory_desktopModeEnabled_DeviceNotSupported_OverrideEnabled() { + setFlagsRule.enableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + ExtendedMockito.doReturn(false).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } + ExtendedMockito.doReturn(false).`when` { DesktopModeStatus.enforceDeviceRestrictions() } - @Test - @EnableFlags( - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY, - Flags.FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PERMISSION, - ) - fun createDesktopTaskShortcutFactoryPermissionEnabledAllowed_transparentTask() { - val packageManager: PackageManager = mock() - setUpTransparentPermission(packageManager, isAllowed = true) - val baseComponent = ComponentName("", /* class */ "") - val taskKey = - TaskKey( - /* id */ 1, - /* windowingMode */ 0, - Intent(), - baseComponent, - /* userId */ 0, - /* lastActiveTime */ 2000, - DEFAULT_DISPLAY, - baseComponent, - /* numActivities */ 1, - /* isTopActivityNoDisplay */ false, - /* isActivityStackTransparent */ true, - ) - val taskContainer = createTaskContainer(Task(taskKey)) - val shortcuts = factory.getShortcuts(launcher, taskContainer) - assertThat(shortcuts).isNull() - } + val taskContainer = spy(createTaskContainer(createTask())) + doReturn(workspaceItemInfo).whenever(taskContainer).itemInfo - @Test - @EnableFlags( - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY, - Flags.FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PERMISSION, - ) - fun createDesktopTaskShortcutFactoryPermissionEnabledNotAllowed_transparentTask() { - val packageManager: PackageManager = mock() - setUpTransparentPermission(packageManager, isAllowed = false) - val baseComponent = ComponentName("", /* class */ "") - val homeActivities = ComponentName("defaultHomePackage", /* class */ "") - whenever(packageManager.getHomeActivities(any())).thenReturn(homeActivities) - val taskKey = - TaskKey( - /* id */ 1, - /* windowingMode */ 0, - Intent(), - baseComponent, - /* userId */ 0, - /* lastActiveTime */ 2000, - DEFAULT_DISPLAY, - baseComponent, - /* numActivities */ 1, - /* isTopActivityNoDisplay */ false, - /* isActivityStackTransparent */ true, - ) - val taskContainer = createTaskContainer(Task(taskKey).apply { isDockable = true }) val shortcuts = factory.getShortcuts(launcher, taskContainer) - assertThat(shortcuts).isNotEmpty() - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) - fun createDesktopTaskShortcutFactory_systemUiTask() { - val sysUiPackageName: String = context.resources.getString(R.string.config_systemUi) - val baseComponent = ComponentName(sysUiPackageName, /* class */ "") - val taskKey = - TaskKey( - /* id */ 1, - /* windowingMode */ 0, - Intent(), - baseComponent, - /* userId */ 0, - /* lastActiveTime */ 2000, - DEFAULT_DISPLAY, - baseComponent, - /* numActivities */ 1, - /* isTopActivityNoDisplay */ false, - /* isActivityStackTransparent */ false, - ) - val taskContainer = createTaskContainer(Task(taskKey)) - val shortcuts = factory.getShortcuts(launcher, taskContainer) - assertThat(shortcuts).isNull() - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) - fun createDesktopTaskShortcutFactory_defaultHomeTask() { - val packageManager: PackageManager = mock() - whenever(context.packageManager).thenReturn(packageManager) - val homeActivities = ComponentName("defaultHomePackage", /* class */ "") - whenever(packageManager.getHomeActivities(any())).thenReturn(homeActivities) - val taskKey = - TaskKey( - /* id */ 1, - /* windowingMode */ 0, - Intent(), - homeActivities, - /* userId */ 0, - /* lastActiveTime */ 2000, - DEFAULT_DISPLAY, - homeActivities, - /* numActivities */ 1, - /* isTopActivityNoDisplay */ false, - /* isActivityStackTransparent */ false, - ) - val taskContainer = createTaskContainer(Task(taskKey).apply { isDockable = true }) - val shortcuts = factory.getShortcuts(launcher, taskContainer) - assertThat(shortcuts).isNull() + assertThat(shortcuts).isNotNull() } @Test fun createDesktopTaskShortcutFactory_undockable() { + setFlagsRule.enableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + val unDockableTask = createTask().apply { isDockable = false } val taskContainer = createTaskContainer(unDockableTask) @@ -258,6 +144,8 @@ class DesktopSystemShortcutTest { @Test fun desktopSystemShortcutClicked() { + setFlagsRule.enableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + val task = createTask() val taskContainer = spy(createTaskContainer(task)) @@ -265,19 +153,17 @@ class DesktopSystemShortcutTest { whenever(launcher.statsLogManager).thenReturn(statsLogManager) whenever(statsLogManager.logger()).thenReturn(statsLogger) whenever(statsLogger.withItemInfo(any())).thenReturn(statsLogger) - whenever(taskView.context).thenReturn(context) whenever(recentsView.moveTaskToDesktop(any(), any(), any())).thenAnswer { val successCallback = it.getArgument(2) successCallback.run() } - val taskViewItemInfo = mock() - doReturn(taskViewItemInfo).whenever(taskContainer).itemInfo + doReturn(workspaceItemInfo).whenever(taskContainer).itemInfo val shortcuts = factory.getShortcuts(launcher, taskContainer) - assertThat(shortcuts).isNotNull() - assertThat(shortcuts!!.single()).isInstanceOf(DesktopSystemShortcut::class.java) + assertThat(shortcuts).hasSize(1) + assertThat(shortcuts!!.first()).isInstanceOf(DesktopSystemShortcut::class.java) - val desktopShortcut = shortcuts.single() as DesktopSystemShortcut + val desktopShortcut = shortcuts.first() as DesktopSystemShortcut desktopShortcut.onClick(taskView) @@ -288,64 +174,29 @@ class DesktopSystemShortcutTest { .moveTaskToDesktop( eq(taskContainer), eq(DesktopModeTransitionSource.APP_FROM_OVERVIEW), - any(), + any() ) - verify(statsLogger).withItemInfo(taskViewItemInfo) + verify(statsLogger).withItemInfo(workspaceItemInfo) verify(statsLogger).log(LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_DESKTOP_TAP) } - private fun createTask() = - Task( - TaskKey( - /* id */ 1, - /* windowingMode */ 0, - Intent(), - ComponentName("", ""), - /* userId */ 0, - /* lastActiveTime */ 2000, - DEFAULT_DISPLAY, - ComponentName("", ""), - /* numActivities */ 1, - /* isTopActivityNoDisplay */ false, - /* isActivityStackTransparent */ false, - ) - ) - .apply { isDockable = true } + private fun createTask(): Task { + return Task(TaskKey(1, 0, Intent(), ComponentName("", ""), 0, 2000)).apply { + isDockable = true + } + } - private fun createTaskContainer(task: Task) = - TaskContainer( - taskView, + private fun createTaskContainer(task: Task): TaskView.TaskContainer { + return taskView.TaskContainer( task, - if (enableRefactorTaskThumbnail()) mock() - else mock(), - mock(), - mock(), + thumbnailView = null, + thumbnailViewDeprecated, + iconView, + transformingTouchDelegate, SplitConfigurationOptions.STAGE_POSITION_UNDEFINED, digitalWellBeingToast = null, showWindowsView = null, - overlayFactory, + overlayFactory ) - - private fun setUpTransparentPermission(packageManager: PackageManager, isAllowed: Boolean) { - val packageInfo: PackageInfo = mock() - if (isAllowed) { - packageInfo.requestedPermissions = arrayOf(SYSTEM_ALERT_WINDOW) - } - whenever(context.packageManager).thenReturn(packageManager) - whenever( - packageManager.getPackageInfoAsUser( - anyString(), - eq(PackageManager.GET_PERMISSIONS), - anyInt(), - ) - ) - .thenReturn(packageInfo) - } - - private fun createTaskViewMock(): TaskView { - val taskView: TaskView = mock() - whenever(taskView.type).thenReturn(TaskViewType.SINGLE) - whenever(taskView.context).thenReturn(context) - return taskView } } diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java index a4c9ef2d5c..28589291fd 100644 --- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java +++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java @@ -19,11 +19,12 @@ import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS; import static androidx.test.InstrumentationRegistry.getInstrumentation; -import static com.android.launcher3.Flags.enableFallbackOverviewInWindow; import static com.android.launcher3.tapl.LauncherInstrumentation.WAIT_TIME_MS; import static com.android.launcher3.tapl.TestHelpers.getHomeIntentInPackage; import static com.android.launcher3.tapl.TestHelpers.getLauncherInMyProcess; +import static com.android.launcher3.ui.AbstractLauncherUiTest.DEFAULT_ACTIVITY_TIMEOUT; import static com.android.launcher3.ui.AbstractLauncherUiTest.DEFAULT_BROADCAST_TIMEOUT_SECS; +import static com.android.launcher3.ui.AbstractLauncherUiTest.DEFAULT_UI_TIMEOUT; import static com.android.launcher3.ui.AbstractLauncherUiTest.resolveSystemApp; import static com.android.launcher3.ui.AbstractLauncherUiTest.startAppFast; import static com.android.launcher3.ui.AbstractLauncherUiTest.startTestActivity; @@ -55,7 +56,6 @@ import com.android.launcher3.tapl.OverviewTask; import com.android.launcher3.tapl.TestHelpers; import com.android.launcher3.testcomponent.TestCommandReceiver; import com.android.launcher3.ui.AbstractLauncherUiTest; -import com.android.launcher3.util.TestUtil; import com.android.launcher3.util.Wait; import com.android.launcher3.util.rule.ExtendedLongPressTimeoutRule; import com.android.launcher3.util.rule.FailureWatcher; @@ -64,10 +64,7 @@ import com.android.launcher3.util.rule.ScreenRecordRule; import com.android.launcher3.util.rule.TestIsolationRule; import com.android.launcher3.util.rule.TestStabilityRule; import com.android.launcher3.util.rule.ViewCaptureRule; -import com.android.quickstep.OverviewComponentObserver.OverviewChangeListener; -import com.android.quickstep.fallback.window.RecentsWindowManager; import com.android.quickstep.views.RecentsView; -import com.android.quickstep.views.RecentsViewContainer; import org.junit.After; import org.junit.Before; @@ -145,7 +142,7 @@ public class FallbackRecentsTest { }; final ViewCaptureRule viewCaptureRule = new ViewCaptureRule( - RecentsActivity.ACTIVITY_TRACKER::getCreatedContext); + RecentsActivity.ACTIVITY_TRACKER::getCreatedActivity); mOrderSensitiveRules = RuleChain .outerRule(new SamplerRule()) .around(new TestStabilityRule()) @@ -195,31 +192,29 @@ public class FallbackRecentsTest { @Test public void goToOverviewFromApp() { startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR)); - waitForRecentsClosed(); + waitForRecentsActivityStop(); mLauncher.getLaunchedAppState().switchToOverview(); } - protected void executeOnRecents(Consumer f) { + protected void executeOnRecents(Consumer f) { getFromRecents(r -> { f.accept(r); return true; }); } - protected T getFromRecents(Function f) { + protected T getFromRecents(Function f) { if (!TestHelpers.isInLauncherProcess()) return null; Object[] result = new Object[1]; Wait.atMost("Failed to get from recents", () -> MAIN_EXECUTOR.submit(() -> { - RecentsViewContainer recentsViewContainer = enableFallbackOverviewInWindow() - ? RecentsWindowManager.getRecentsWindowTracker().getCreatedContext() - : RecentsActivity.ACTIVITY_TRACKER.getCreatedContext(); - if (recentsViewContainer == null) { + RecentsActivity activity = RecentsActivity.ACTIVITY_TRACKER.getCreatedActivity(); + if (activity == null) { return false; } - result[0] = f.apply(recentsViewContainer); + result[0] = f.apply(activity); return true; - }).get(), mLauncher); + }).get(), DEFAULT_UI_TIMEOUT, mLauncher); return (T) result[0]; } @@ -230,19 +225,14 @@ public class FallbackRecentsTest { private void pressHomeAndWaitForOverviewClose() { mDevice.pressHome(); - waitForRecentsClosed(); + waitForRecentsActivityStop(); } - private void waitForRecentsClosed() { + private void waitForRecentsActivityStop() { try { - final boolean isRecentsContainerNUll = MAIN_EXECUTOR.submit(() -> { - RecentsViewContainer recentsViewContainer = enableFallbackOverviewInWindow() - ? RecentsWindowManager.getRecentsWindowTracker().getCreatedContext() - : RecentsActivity.ACTIVITY_TRACKER.getCreatedContext(); - - return recentsViewContainer == null; - }).get(); - if (isRecentsContainerNUll) { + final boolean recentsActivityIsNull = MAIN_EXECUTOR.submit( + () -> RecentsActivity.ACTIVITY_TRACKER.getCreatedActivity() == null).get(); + if (recentsActivityIsNull) { // Null activity counts as a "stopped" one. return; } @@ -252,9 +242,9 @@ public class FallbackRecentsTest { throw new RuntimeException(e); } - Wait.atMost("Recents view container didn't close", + Wait.atMost("Recents activity didn't stop", () -> getFromRecents(recents -> !recents.isStarted()), - mLauncher); + DEFAULT_UI_TIMEOUT, mLauncher); } @Test @@ -262,10 +252,9 @@ public class FallbackRecentsTest { startAppFast(getAppPackageName()); startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR)); startTestActivity(2); - waitForRecentsClosed(); + waitForRecentsActivityStop(); Wait.atMost("Expected three apps in the task list", - () -> mLauncher.getRecentTasks().size() >= 3, - mLauncher); + () -> mLauncher.getRecentTasks().size() >= 3, DEFAULT_ACTIVITY_TIMEOUT, mLauncher); checkTestLauncher(); BaseOverview overview = mLauncher.getLaunchedAppState().switchToOverview(); @@ -293,7 +282,7 @@ public class FallbackRecentsTest { assertNotNull("OverviewTask.open returned null", task.open()); assertTrue("Test activity didn't open from Overview", TestHelpers.wait(Until.hasObject( By.pkg(getAppPackageName()).text("TestActivity2")), - TestUtil.DEFAULT_UI_TIMEOUT)); + DEFAULT_UI_TIMEOUT)); // Test dismissing a task. @@ -323,39 +312,37 @@ public class FallbackRecentsTest { ); } - private int getCurrentOverviewPage(RecentsViewContainer recentsViewContainer) { - return recentsViewContainer.getOverviewPanel().getCurrentPage(); + private int getCurrentOverviewPage(RecentsActivity recents) { + return recents.getOverviewPanel().getCurrentPage(); } - private int getTaskCount(RecentsViewContainer recentsViewContainer) { - return recentsViewContainer.getOverviewPanel().getTaskViewCount(); + private int getTaskCount(RecentsActivity recents) { + return recents.getOverviewPanel().getTaskViewCount(); } - private class OverviewUpdateHandler implements OverviewChangeListener { + private class OverviewUpdateHandler { + final RecentsAnimationDeviceState mRads; final OverviewComponentObserver mObserver; final CountDownLatch mChangeCounter; OverviewUpdateHandler() { Context ctx = getInstrumentation().getTargetContext(); - mObserver = OverviewComponentObserver.INSTANCE.get(ctx); + mRads = new RecentsAnimationDeviceState(ctx); + mObserver = new OverviewComponentObserver(ctx, mRads); mChangeCounter = new CountDownLatch(1); if (mObserver.getHomeIntent().getComponent() .getPackageName().equals(mOtherLauncherActivity.packageName)) { // Home already same mChangeCounter.countDown(); } else { - mObserver.addOverviewChangeListener(this); + mObserver.setOverviewChangeListener(b -> mChangeCounter.countDown()); } } - @Override - public void onOverviewTargetChange(boolean isHomeAndOverviewSame) { - mChangeCounter.countDown(); - } - void destroy() { - mObserver.removeOverviewChangeListener(this); + mObserver.onDestroy(); + mRads.destroy(); } } } diff --git a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java index c713c3dc9a..4459ed6944 100644 --- a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java +++ b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java @@ -16,7 +16,7 @@ package com.android.quickstep; -import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; +import static androidx.test.InstrumentationRegistry.getInstrumentation; import static com.android.quickstep.NavigationModeSwitchRule.Mode.ALL; import static com.android.quickstep.NavigationModeSwitchRule.Mode.THREE_BUTTON; @@ -26,7 +26,6 @@ import static com.android.systemui.shared.system.QuickStepContract.NAV_BAR_MODE_ import android.content.Context; import android.content.pm.PackageManager; -import android.os.Process; import android.util.Log; import androidx.test.uiautomator.UiDevice; @@ -58,6 +57,8 @@ public class NavigationModeSwitchRule implements TestRule { static final String TAG = "QuickStepOnOffRule"; + public static final int WAIT_TIME_MS = 10000; + public enum Mode { THREE_BUTTON, ZERO_BUTTON, ALL } @@ -154,9 +155,7 @@ public class NavigationModeSwitchRule implements TestRule { Log.d(TAG, "setActiveOverlay: " + overlayPackage + "..."); UiDevice.getInstance(getInstrumentation()).executeShellCommand( - String.format("cmd overlay enable-exclusive --user %d --category %s", - Process.myUserHandle().getIdentifier(), - overlayPackage)); + "cmd overlay enable-exclusive --category " + overlayPackage); if (currentSysUiNavigationMode() != expectedMode) { final CountDownLatch latch = new CountDownLatch(1); @@ -180,13 +179,12 @@ public class NavigationModeSwitchRule implements TestRule { } Wait.atMost("Couldn't switch to " + overlayPackage, - () -> launcher.getNavigationModel() == expectedMode, - launcher); + () -> launcher.getNavigationModel() == expectedMode, WAIT_TIME_MS, launcher); Wait.atMost(() -> "Switching nav mode: " + launcher.getNavigationModeMismatchError(false), () -> launcher.getNavigationModeMismatchError(false) == null, - launcher); + WAIT_TIME_MS, launcher); AbstractLauncherUiTest.checkDetectedLeaks(launcher, false); return true; } diff --git a/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java b/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java index 154d86d977..a738e76833 100644 --- a/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java +++ b/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java @@ -21,7 +21,6 @@ import static androidx.test.core.app.ApplicationProvider.getApplicationContext; import static com.android.launcher3.util.NavigationMode.NO_BUTTON; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -42,8 +41,6 @@ import android.view.Surface; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import com.android.launcher3.testing.shared.ResourceUtils; -import com.android.launcher3.util.DaggerSingletonTracker; import com.android.launcher3.util.DisplayController; import com.android.launcher3.util.RotationUtils; import com.android.launcher3.util.WindowBounds; @@ -91,7 +88,7 @@ public class OrientationTouchTransformerTest { float landscapeRegionY = generateTouchRegionHeight(NORMAL_SCREEN_SIZE, Surface.ROTATION_90) + 1; - mTouchTransformer.createOrAddTouchRegion(mInfo, "test"); + mTouchTransformer.createOrAddTouchRegion(mInfo); tapAndAssertTrue(100, portraitRegionY, event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY())); tapAndAssertFalse(100, landscapeRegionY, @@ -103,8 +100,7 @@ public class OrientationTouchTransformerTest { // Override region mTouchTransformer - .createOrAddTouchRegion(createDisplayInfo(NORMAL_SCREEN_SIZE, Surface.ROTATION_90), - "test"); + .createOrAddTouchRegion(createDisplayInfo(NORMAL_SCREEN_SIZE, Surface.ROTATION_90)); tapAndAssertFalse(100, portraitRegionY, event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY())); tapAndAssertTrue(100, landscapeRegionY, @@ -115,7 +111,7 @@ public class OrientationTouchTransformerTest { event -> mTouchTransformer.touchInAssistantRegion(event)); // Override region again - mTouchTransformer.createOrAddTouchRegion(mInfo, "test"); + mTouchTransformer.createOrAddTouchRegion(mInfo); tapAndAssertTrue(100, portraitRegionY, event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY())); tapAndAssertFalse(100, landscapeRegionY, @@ -134,8 +130,7 @@ public class OrientationTouchTransformerTest { generateTouchRegionHeight(NORMAL_SCREEN_SIZE, Surface.ROTATION_90) + 1; mTouchTransformer - .createOrAddTouchRegion(createDisplayInfo(NORMAL_SCREEN_SIZE, Surface.ROTATION_90), - "test"); + .createOrAddTouchRegion(createDisplayInfo(NORMAL_SCREEN_SIZE, Surface.ROTATION_90)); tapAndAssertFalse(100, portraitRegionY, event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY())); tapAndAssertTrue(100, landscapeRegionY, @@ -147,7 +142,7 @@ public class OrientationTouchTransformerTest { // We have to add 0 rotation second so that gets set as the current rotation, otherwise // matrix transform will fail (tests only work in Portrait at the moment) mTouchTransformer.enableMultipleRegions(true, mInfo); - mTouchTransformer.createOrAddTouchRegion(mInfo, "test"); + mTouchTransformer.createOrAddTouchRegion(mInfo); tapAndAssertTrue(100, portraitRegionY, event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY())); @@ -168,9 +163,8 @@ public class OrientationTouchTransformerTest { mTouchTransformer.enableMultipleRegions(true, mInfo); mTouchTransformer - .createOrAddTouchRegion(createDisplayInfo(NORMAL_SCREEN_SIZE, Surface.ROTATION_90), - "test"); - mTouchTransformer.createOrAddTouchRegion(mInfo, "test"); + .createOrAddTouchRegion(createDisplayInfo(NORMAL_SCREEN_SIZE, Surface.ROTATION_90)); + mTouchTransformer.createOrAddTouchRegion(mInfo); tapAndAssertTrue(0, portraitRegionY, event -> mTouchTransformer.touchInAssistantRegion(event)); tapAndAssertFalse(0, landscapeRegionY, @@ -185,10 +179,9 @@ public class OrientationTouchTransformerTest { generateTouchRegionHeight(NORMAL_SCREEN_SIZE, Surface.ROTATION_90) + 1; mTouchTransformer.enableMultipleRegions(true, mInfo); - mTouchTransformer.createOrAddTouchRegion(mInfo, "test"); + mTouchTransformer.createOrAddTouchRegion(mInfo); mTouchTransformer - .createOrAddTouchRegion(createDisplayInfo(NORMAL_SCREEN_SIZE, Surface.ROTATION_90), - "test"); + .createOrAddTouchRegion(createDisplayInfo(NORMAL_SCREEN_SIZE, Surface.ROTATION_90)); mTouchTransformer.enableMultipleRegions(false, mInfo); tapAndAssertTrue(0, portraitRegionY, event -> mTouchTransformer.touchInAssistantRegion(event)); @@ -218,14 +211,14 @@ public class OrientationTouchTransformerTest { @Test public void applyTransform_taskNotFrozen_notInRegion() { - mTouchTransformer.createOrAddTouchRegion(mInfo, "test"); + mTouchTransformer.createOrAddTouchRegion(mInfo); tapAndAssertFalse(100, 100, event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY())); } @Test public void applyTransform_taskFrozen_noRotate_outOfRegion() { - mTouchTransformer.createOrAddTouchRegion(mInfo, "test"); + mTouchTransformer.createOrAddTouchRegion(mInfo); mTouchTransformer.enableMultipleRegions(true, mInfo); tapAndAssertFalse(100, 100, event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY())); @@ -233,7 +226,7 @@ public class OrientationTouchTransformerTest { @Test public void applyTransform_taskFrozen_noRotate_inRegion() { - mTouchTransformer.createOrAddTouchRegion(mInfo, "test"); + mTouchTransformer.createOrAddTouchRegion(mInfo); mTouchTransformer.enableMultipleRegions(true, mInfo); float y = generateTouchRegionHeight(NORMAL_SCREEN_SIZE, Surface.ROTATION_0) + 1; tapAndAssertTrue(100, y, @@ -242,7 +235,7 @@ public class OrientationTouchTransformerTest { @Test public void applyTransform_taskNotFrozen_noRotate_inDefaultRegion() { - mTouchTransformer.createOrAddTouchRegion(mInfo, "test"); + mTouchTransformer.createOrAddTouchRegion(mInfo); float y = generateTouchRegionHeight(NORMAL_SCREEN_SIZE, Surface.ROTATION_0) + 1; tapAndAssertTrue(100, y, event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY())); @@ -251,8 +244,7 @@ public class OrientationTouchTransformerTest { @Test public void applyTransform_taskNotFrozen_90Rotate_inRegion() { mTouchTransformer - .createOrAddTouchRegion(createDisplayInfo(NORMAL_SCREEN_SIZE, Surface.ROTATION_90), - "test"); + .createOrAddTouchRegion(createDisplayInfo(NORMAL_SCREEN_SIZE, Surface.ROTATION_90)); float y = generateTouchRegionHeight(NORMAL_SCREEN_SIZE, Surface.ROTATION_90) + 1; tapAndAssertTrue(100, y, event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY())); @@ -260,11 +252,10 @@ public class OrientationTouchTransformerTest { @Test public void applyTransform_taskNotFrozen_90Rotate_withTwoRegions() { - mTouchTransformer.createOrAddTouchRegion(mInfo, "test"); + mTouchTransformer.createOrAddTouchRegion(mInfo); mTouchTransformer.enableMultipleRegions(true, mInfo); mTouchTransformer - .createOrAddTouchRegion(createDisplayInfo(NORMAL_SCREEN_SIZE, Surface.ROTATION_90), - "test"); + .createOrAddTouchRegion(createDisplayInfo(NORMAL_SCREEN_SIZE, Surface.ROTATION_90)); // Landscape point float y1 = generateTouchRegionHeight(NORMAL_SCREEN_SIZE, Surface.ROTATION_90) + 1; MotionEvent inRegion1_down = generateMotionEvent(MotionEvent.ACTION_DOWN, 10, y1); @@ -285,11 +276,10 @@ public class OrientationTouchTransformerTest { @Test public void applyTransform_90Rotate_inRotatedRegion() { // Create regions for both 0 Rotation and 90 Rotation - mTouchTransformer.createOrAddTouchRegion(mInfo, "test"); + mTouchTransformer.createOrAddTouchRegion(mInfo); mTouchTransformer.enableMultipleRegions(true, mInfo); mTouchTransformer - .createOrAddTouchRegion(createDisplayInfo(NORMAL_SCREEN_SIZE, Surface.ROTATION_90), - "test"); + .createOrAddTouchRegion(createDisplayInfo(NORMAL_SCREEN_SIZE, Surface.ROTATION_90)); // Portrait point in landscape orientation axis float x1 = generateTouchRegionHeight(NORMAL_SCREEN_SIZE, Surface.ROTATION_0); // bottom of screen, from landscape perspective right side of screen @@ -297,35 +287,6 @@ public class OrientationTouchTransformerTest { assertTrue(mTouchTransformer.touchInValidSwipeRegions(inRegion2.getX(), inRegion2.getY())); } - @Test - public void testSimpleOrientationTouchTransformer() { - final DisplayController displayController = mock(DisplayController.class); - doReturn(mInfo).when(displayController).getInfoForDisplay(anyInt()); - final SimpleOrientationTouchTransformer transformer = - new SimpleOrientationTouchTransformer(getApplicationContext(), displayController, - mock(DaggerSingletonTracker.class)); - final MotionEvent move1 = generateMotionEvent(MotionEvent.ACTION_MOVE, 100, 10); - transformer.transform(move1, Surface.ROTATION_90); - // The position is transformed to 90 degree. - assertEquals(10, move1.getX(), 0f /* delta */); - assertEquals(NORMAL_SCREEN_SIZE.getWidth() - 100, move1.getY(), 0f /* delta */); - - // If the touching state is specified, the position is still transformed to 90 degree even - // if the given rotation is changed. - final MotionEvent move2 = generateMotionEvent(MotionEvent.ACTION_MOVE, 100, 10); - transformer.updateTouchingOrientation(Surface.ROTATION_90); - transformer.transform(move2, Surface.ROTATION_0); - assertEquals(move1.getX(), move2.getX(), 0f /* delta */); - assertEquals(move1.getY(), move2.getY(), 0f /* delta */); - - // If the touching state is cleared, it restores to use the given rotation. - final MotionEvent move3 = generateMotionEvent(MotionEvent.ACTION_MOVE, 100, 10); - transformer.clearTouchingOrientation(); - transformer.transform(move3, Surface.ROTATION_0); - assertEquals(100, move3.getX(), 0f /* delta */); - assertEquals(10, move3.getY(), 0f /* delta */); - } - private DisplayController.Info createDisplayInfo(Size screenSize, int rotation) { Point displaySize = new Point(screenSize.getWidth(), screenSize.getHeight()); RotationUtils.rotateSize(displaySize, rotation); diff --git a/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java b/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java new file mode 100644 index 0000000000..03244eb0bf --- /dev/null +++ b/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.quickstep; + +import static junit.framework.TestCase.assertNull; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.ActivityManager; +import android.app.KeyguardManager; + +import androidx.test.filters.SmallTest; + +import com.android.launcher3.util.LooperExecutor; +import com.android.quickstep.util.GroupTask; +import com.android.wm.shell.util.GroupedRecentTaskInfo; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@SmallTest +public class RecentTasksListTest { + + @Mock + private SystemUiProxy mockSystemUiProxy; + @Mock + private TopTaskTracker mTopTaskTracker; + + // Class under test + private RecentTasksList mRecentTasksList; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + LooperExecutor mockMainThreadExecutor = mock(LooperExecutor.class); + KeyguardManager mockKeyguardManager = mock(KeyguardManager.class); + mRecentTasksList = new RecentTasksList(mockMainThreadExecutor, mockKeyguardManager, + mockSystemUiProxy, mTopTaskTracker); + } + + @Test + public void onRecentTasksChanged_doesNotFetchTasks() { + mRecentTasksList.onRecentTasksChanged(); + verify(mockSystemUiProxy, times(0)) + .getRecentTasks(anyInt(), anyInt()); + } + + @Test + public void loadTasksInBackground_onlyKeys_noValidTaskDescription() { + GroupedRecentTaskInfo recentTaskInfos = GroupedRecentTaskInfo.forSplitTasks( + new ActivityManager.RecentTaskInfo(), new ActivityManager.RecentTaskInfo(), null); + when(mockSystemUiProxy.getRecentTasks(anyInt(), anyInt())) + .thenReturn(new ArrayList<>(Collections.singletonList(recentTaskInfos))); + + List taskList = mRecentTasksList.loadTasksInBackground(Integer.MAX_VALUE, -1, + true); + + assertEquals(1, taskList.size()); + assertNull(taskList.get(0).task1.taskDescription.getLabel()); + assertNull(taskList.get(0).task2.taskDescription.getLabel()); + } + + @Test + public void loadTasksInBackground_moreThanKeys_hasValidTaskDescription() { + String taskDescription = "Wheeee!"; + ActivityManager.RecentTaskInfo task1 = new ActivityManager.RecentTaskInfo(); + task1.taskDescription = new ActivityManager.TaskDescription(taskDescription); + ActivityManager.RecentTaskInfo task2 = new ActivityManager.RecentTaskInfo(); + task2.taskDescription = new ActivityManager.TaskDescription(); + GroupedRecentTaskInfo recentTaskInfos = GroupedRecentTaskInfo.forSplitTasks(task1, task2, + null); + when(mockSystemUiProxy.getRecentTasks(anyInt(), anyInt())) + .thenReturn(new ArrayList<>(Collections.singletonList(recentTaskInfos))); + + List taskList = mRecentTasksList.loadTasksInBackground(Integer.MAX_VALUE, -1, + false); + + assertEquals(1, taskList.size()); + assertEquals(taskDescription, taskList.get(0).task1.taskDescription.getLabel()); + assertNull(taskList.get(0).task2.taskDescription.getLabel()); + } +} diff --git a/quickstep/tests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt b/quickstep/tests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt new file mode 100644 index 0000000000..80fbce7265 --- /dev/null +++ b/quickstep/tests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt @@ -0,0 +1,205 @@ +package com.android.quickstep + +import android.content.Context +import android.testing.AndroidTestingRunner +import androidx.test.core.app.ApplicationProvider +import androidx.test.filters.SmallTest +import com.android.launcher3.util.DisplayController.CHANGE_DENSITY +import com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE +import com.android.launcher3.util.DisplayController.CHANGE_ROTATION +import com.android.launcher3.util.DisplayController.Info +import com.android.launcher3.util.NavigationMode +import com.android.quickstep.util.GestureExclusionManager +import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY +import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DREAMING +import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION +import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED +import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_MAGNIFICATION_OVERLAP +import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN +import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED +import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED +import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED +import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING +import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.reset +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.verifyZeroInteractions +import org.mockito.kotlin.whenever + +/** Unit test for [RecentsAnimationDeviceState]. */ +@SmallTest +@RunWith(AndroidTestingRunner::class) +class RecentsAnimationDeviceStateTest { + + @Mock private lateinit var exclusionManager: GestureExclusionManager + @Mock private lateinit var info: Info + + private val context = ApplicationProvider.getApplicationContext() as Context + private lateinit var underTest: RecentsAnimationDeviceState + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + underTest = RecentsAnimationDeviceState(context, exclusionManager) + } + + @Test + fun registerExclusionListener_success() { + underTest.registerExclusionListener() + + verify(exclusionManager).addListener(underTest) + } + + @Test + fun registerExclusionListener_again_fail() { + underTest.registerExclusionListener() + reset(exclusionManager) + + underTest.registerExclusionListener() + + verifyZeroInteractions(exclusionManager) + } + + @Test + fun unregisterExclusionListener_success() { + underTest.registerExclusionListener() + reset(exclusionManager) + + underTest.unregisterExclusionListener() + + verify(exclusionManager).removeListener(underTest) + } + + @Test + fun unregisterExclusionListener_again_fail() { + underTest.registerExclusionListener() + underTest.unregisterExclusionListener() + reset(exclusionManager) + + underTest.unregisterExclusionListener() + + verifyZeroInteractions(exclusionManager) + } + + @Test + fun onDisplayInfoChanged_noButton_registerExclusionListener() { + doReturn(NavigationMode.NO_BUTTON).whenever(info).getNavigationMode() + + underTest.onDisplayInfoChanged(context, info, CHANGE_ROTATION or CHANGE_NAVIGATION_MODE) + + verify(exclusionManager).addListener(underTest) + } + + @Test + fun onDisplayInfoChanged_twoButton_unregisterExclusionListener() { + underTest.registerExclusionListener() + whenever(info.getNavigationMode()).thenReturn(NavigationMode.TWO_BUTTONS) + reset(exclusionManager) + + underTest.onDisplayInfoChanged(context, info, CHANGE_ROTATION or CHANGE_NAVIGATION_MODE) + + verify(exclusionManager).removeListener(underTest) + } + + @Test + fun onDisplayInfoChanged_changeDensity_noOp() { + underTest.registerExclusionListener() + whenever(info.getNavigationMode()).thenReturn(NavigationMode.NO_BUTTON) + reset(exclusionManager) + + underTest.onDisplayInfoChanged(context, info, CHANGE_DENSITY) + + verifyZeroInteractions(exclusionManager) + } + + @Test + fun trackpadGesturesNotAllowedForSelectedStates() { + val disablingStates = GESTURE_DISABLING_SYSUI_STATES + + SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED + + allSysUiStates().forEach { state -> + val canStartGesture = !disablingStates.contains(state) + underTest.setSystemUiFlags(state) + assertThat(underTest.canStartTrackpadGesture()).isEqualTo(canStartGesture) + } + } + + @Test + fun trackpadGesturesNotAllowedIfHomeAndOverviewIsDisabled() { + val stateToExpectedResult = mapOf( + SYSUI_STATE_HOME_DISABLED to true, + SYSUI_STATE_OVERVIEW_DISABLED to true, + DEFAULT_STATE + .enable(SYSUI_STATE_OVERVIEW_DISABLED) + .enable(SYSUI_STATE_HOME_DISABLED) to false + ) + + stateToExpectedResult.forEach { (state, allowed) -> + underTest.setSystemUiFlags(state) + assertThat(underTest.canStartTrackpadGesture()).isEqualTo(allowed) + } + } + + @Test + fun systemGesturesNotAllowedForSelectedStates() { + val disablingStates = GESTURE_DISABLING_SYSUI_STATES + SYSUI_STATE_NAV_BAR_HIDDEN + + allSysUiStates().forEach { state -> + val canStartGesture = !disablingStates.contains(state) + underTest.setSystemUiFlags(state) + assertThat(underTest.canStartSystemGesture()).isEqualTo(canStartGesture) + } + } + + @Test + fun systemGesturesNotAllowedWhenGestureStateDisabledAndNavBarVisible() { + val stateToExpectedResult = mapOf( + DEFAULT_STATE + .enable(SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY) + .disable(SYSUI_STATE_NAV_BAR_HIDDEN) to true, + DEFAULT_STATE + .enable(SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY) + .enable(SYSUI_STATE_NAV_BAR_HIDDEN) to true, + DEFAULT_STATE + .disable(SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY) + .disable(SYSUI_STATE_NAV_BAR_HIDDEN) to true, + DEFAULT_STATE + .disable(SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY) + .enable(SYSUI_STATE_NAV_BAR_HIDDEN) to false, + ) + + stateToExpectedResult.forEach {(state, gestureAllowed) -> + underTest.setSystemUiFlags(state) + assertThat(underTest.canStartSystemGesture()).isEqualTo(gestureAllowed) + } + } + + private fun allSysUiStates(): List { + // SYSUI_STATES_* are binary flags + return (0..SYSUI_STATES_COUNT).map { 1L shl it } + } + + companion object { + private val GESTURE_DISABLING_SYSUI_STATES = listOf( + SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED, + SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING, + SYSUI_STATE_QUICK_SETTINGS_EXPANDED, + SYSUI_STATE_MAGNIFICATION_OVERLAP, + SYSUI_STATE_DEVICE_DREAMING, + SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION, + ) + private const val SYSUI_STATES_COUNT = 33 + private const val DEFAULT_STATE = 0L + } + + private fun Long.enable(state: Long) = this or state + + private fun Long.disable(state: Long) = this and state.inv() +} diff --git a/quickstep/tests/src/com/android/quickstep/RecentsModelTest.java b/quickstep/tests/src/com/android/quickstep/RecentsModelTest.java new file mode 100644 index 0000000000..648fa932a6 --- /dev/null +++ b/quickstep/tests/src/com/android/quickstep/RecentsModelTest.java @@ -0,0 +1,173 @@ +/* + * 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.quickstep; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.ActivityManager; +import android.content.Context; +import android.content.res.Resources; +import android.platform.test.flag.junit.SetFlagsRule; + +import androidx.test.annotation.UiThreadTest; +import androidx.test.filters.SmallTest; + +import com.android.launcher3.Flags; +import com.android.launcher3.R; +import com.android.launcher3.icons.IconProvider; +import com.android.quickstep.util.GroupTask; +import com.android.systemui.shared.recents.model.Task; +import com.android.systemui.shared.system.TaskStackChangeListeners; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.function.Consumer; + +@SmallTest +public class RecentsModelTest { + @Mock + private Context mContext; + + @Mock + private TaskThumbnailCache mThumbnailCache; + + @Mock + private RecentTasksList mTasksList; + + @Mock + private TaskThumbnailCache.HighResLoadingState mHighResLoadingState; + + private RecentsModel mRecentsModel; + + private RecentTasksList.TaskLoadResult mTaskResult; + + private Resources mResource; + + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + + @Before + public void setup() throws NoSuchFieldException { + MockitoAnnotations.initMocks(this); + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_GRID_ONLY_OVERVIEW); + mTaskResult = getTaskResult(); + doAnswer(invocation-> { + Consumer> callback = invocation.getArgument(1); + callback.accept(mTaskResult); + return null; + }).when(mTasksList).getTaskKeys(anyInt(), any()); + + when(mHighResLoadingState.isEnabled()).thenReturn(true); + when(mThumbnailCache.getHighResLoadingState()).thenReturn(mHighResLoadingState); + when(mThumbnailCache.isPreloadingEnabled()).thenReturn(true); + + mRecentsModel = new RecentsModel(mContext, mTasksList, mock(TaskIconCache.class), + mThumbnailCache, mock(IconProvider.class), mock(TaskStackChangeListeners.class)); + + mResource = mock(Resources.class); + when(mResource.getInteger((R.integer.recentsThumbnailCacheSize))).thenReturn(3); + when(mContext.getResources()).thenReturn(mResource); + } + + @Test + @UiThreadTest + public void preloadOnHighResolutionEnabled() { + mRecentsModel.preloadCacheIfNeeded(); + + ArgumentCaptor taskArgs = ArgumentCaptor.forClass(Task.class); + verify(mRecentsModel.getThumbnailCache(), times(2)) + .updateThumbnailInCache(taskArgs.capture(), /* lowResolution= */ eq(false)); + + GroupTask expectedGroupTask = mTaskResult.get(0); + assertThat(taskArgs.getAllValues().get(0)).isEqualTo( + expectedGroupTask.task1); + assertThat(taskArgs.getAllValues().get(1)).isEqualTo( + expectedGroupTask.task2); + } + + @Test + public void notPreloadOnHighResolutionDisabled() { + when(mHighResLoadingState.isEnabled()).thenReturn(false); + when(mThumbnailCache.isPreloadingEnabled()).thenReturn(true); + mRecentsModel.preloadCacheIfNeeded(); + verify(mRecentsModel.getThumbnailCache(), never()) + .updateThumbnailInCache(any(), anyBoolean()); + } + + @Test + public void notPreloadOnPreloadDisabled() { + when(mThumbnailCache.isPreloadingEnabled()).thenReturn(false); + mRecentsModel.preloadCacheIfNeeded(); + verify(mRecentsModel.getThumbnailCache(), never()) + .updateThumbnailInCache(any(), anyBoolean()); + + } + + @Test + public void increaseCacheSizeAndPreload() { + // Mock to return preload is needed + when(mThumbnailCache.updateCacheSizeAndRemoveExcess()).thenReturn(true); + // Update cache size + mRecentsModel.updateCacheSizeAndPreloadIfNeeded(); + // Assert update cache is called + verify(mRecentsModel.getThumbnailCache(), times(2)) + .updateThumbnailInCache(any(), /* lowResolution= */ eq(false)); + } + + @Test + public void decreaseCacheSizeAndNotPreload() { + // Mock to return preload is not needed + when(mThumbnailCache.updateCacheSizeAndRemoveExcess()).thenReturn(false); + // Update cache size + mRecentsModel.updateCacheSizeAndPreloadIfNeeded(); + // Assert update cache is never called + verify(mRecentsModel.getThumbnailCache(), never()) + .updateThumbnailInCache(any(), anyBoolean()); + } + + private RecentTasksList.TaskLoadResult getTaskResult() { + RecentTasksList.TaskLoadResult allTasks = new RecentTasksList.TaskLoadResult(0, false, 1); + ActivityManager.RecentTaskInfo taskInfo1 = new ActivityManager.RecentTaskInfo(); + Task.TaskKey taskKey1 = new Task.TaskKey(taskInfo1); + Task task1 = Task.from(taskKey1, taskInfo1, false); + + ActivityManager.RecentTaskInfo taskInfo2 = new ActivityManager.RecentTaskInfo(); + Task.TaskKey taskKey2 = new Task.TaskKey(taskInfo2); + Task task2 = Task.from(taskKey2, taskInfo2, false); + + allTasks.add(new GroupTask(task1, task2, null)); + return allTasks; + } +} diff --git a/quickstep/tests/src/com/android/quickstep/TaplDigitalWellBeingToastTest.java b/quickstep/tests/src/com/android/quickstep/TaplDigitalWellBeingToastTest.java new file mode 100644 index 0000000000..07d8f61992 --- /dev/null +++ b/quickstep/tests/src/com/android/quickstep/TaplDigitalWellBeingToastTest.java @@ -0,0 +1,109 @@ +/* + * 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.quickstep; + +import static androidx.test.InstrumentationRegistry.getInstrumentation; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.app.PendingIntent; +import android.app.usage.UsageStatsManager; +import android.content.Intent; + +import androidx.test.filters.LargeTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.launcher3.Launcher; +import com.android.quickstep.views.DigitalWellBeingToast; +import com.android.quickstep.views.RecentsView; +import com.android.quickstep.views.TaskView; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.time.Duration; + +@LargeTest +@RunWith(AndroidJUnit4.class) +public class TaplDigitalWellBeingToastTest extends AbstractQuickStepTest { + private static final String CALCULATOR_PACKAGE = + resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR); + + @Test + public void testToast() throws Exception { + startAppFast(CALCULATOR_PACKAGE); + + final UsageStatsManager usageStatsManager = + mTargetContext.getSystemService(UsageStatsManager.class); + final int observerId = 0; + + try { + final String[] packages = new String[]{CALCULATOR_PACKAGE}; + + // Set time limit for app. + runWithShellPermission(() -> + usageStatsManager.registerAppUsageLimitObserver(observerId, packages, + Duration.ofSeconds(600), Duration.ofSeconds(300), + PendingIntent.getActivity(mTargetContext, -1, new Intent() + .setPackage(mTargetContext.getPackageName()), + PendingIntent.FLAG_MUTABLE))); + + mLauncher.goHome(); + final DigitalWellBeingToast toast = getToast(); + + waitForLauncherCondition("Toast is not visible", launcher -> toast.hasLimit()); + assertEquals("Toast text: ", "5 minutes left today", toast.getText()); + + // Unset time limit for app. + runWithShellPermission( + () -> usageStatsManager.unregisterAppUsageLimitObserver(observerId)); + + mLauncher.goHome(); + assertFalse("Toast is visible", getToast().hasLimit()); + } finally { + runWithShellPermission( + () -> usageStatsManager.unregisterAppUsageLimitObserver(observerId)); + } + } + + private DigitalWellBeingToast getToast() { + mLauncher.getWorkspace().switchToOverview(); + final TaskView task = getOnceNotNull("No latest task", launcher -> getLatestTask(launcher)); + + return getFromLauncher(launcher -> { + TaskView.TaskContainer taskContainer = task.getTaskContainers().get(0); + assertTrue("Latest task is not Calculator", CALCULATOR_PACKAGE.equals( + taskContainer.getTask().getTopComponent().getPackageName())); + return taskContainer.getDigitalWellBeingToast(); + }); + } + + private TaskView getLatestTask(Launcher launcher) { + return launcher.getOverviewPanel().getTaskViewAt(0); + } + + private void runWithShellPermission(Runnable action) { + getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(); + try { + action.run(); + } finally { + getInstrumentation().getUiAutomation().dropShellPermissionIdentity(); + } + + } +} diff --git a/quickstep/tests/src/com/android/quickstep/TaplOverviewIconTest.java b/quickstep/tests/src/com/android/quickstep/TaplOverviewIconTest.java index 2c275f45ee..b7fd8be311 100644 --- a/quickstep/tests/src/com/android/quickstep/TaplOverviewIconTest.java +++ b/quickstep/tests/src/com/android/quickstep/TaplOverviewIconTest.java @@ -15,18 +15,20 @@ */ package com.android.quickstep; +import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL; +import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT; + import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import android.content.Intent; import android.platform.test.annotations.PlatinumTest; -import com.android.launcher3.tapl.Overview; -import com.android.launcher3.tapl.OverviewTask.OverviewTaskContainer; +import com.android.launcher3.tapl.OverviewTask.OverviewSplitTask; import com.android.launcher3.tapl.OverviewTaskMenu; import com.android.launcher3.ui.AbstractLauncherUiTest; import com.android.launcher3.uioverrides.QuickstepLauncher; -import com.android.quickstep.util.SplitScreenTestUtils; +import com.android.launcher3.util.rule.TestStabilityRule; import org.junit.Test; @@ -67,18 +69,41 @@ public class TaplOverviewIconTest extends AbstractLauncherUiTest launcher.getAppsView().resetAndScrollToPrivateSpaceHeader()); HomeAllApps homeAllApps = mLauncher.getAllApps(); diff --git a/quickstep/tests/src/com/android/quickstep/TaplStartLauncherViaGestureTests.java b/quickstep/tests/src/com/android/quickstep/TaplStartLauncherViaGestureTests.java index 2fb08dd9a6..1886ce671a 100644 --- a/quickstep/tests/src/com/android/quickstep/TaplStartLauncherViaGestureTests.java +++ b/quickstep/tests/src/com/android/quickstep/TaplStartLauncherViaGestureTests.java @@ -16,8 +16,6 @@ package com.android.quickstep; -import android.util.Log; - import androidx.test.filters.LargeTest; import androidx.test.runner.AndroidJUnit4; @@ -31,14 +29,8 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public class TaplStartLauncherViaGestureTests extends AbstractQuickStepTest { - public static final String TAG = "TaplStartLauncherViaGestureTests"; - static final int STRESS_REPEAT_COUNT = 10; - private enum TestCase { - TO_HOME, TO_OVERVIEW, - } - @Override @Before public void setUp() throws Exception { @@ -49,60 +41,28 @@ public class TaplStartLauncherViaGestureTests extends AbstractQuickStepTest { } @Test - @NavigationModeSwitch(mode = NavigationModeSwitchRule.Mode.THREE_BUTTON) + @NavigationModeSwitch public void testStressPressHome() { - runTest(TestCase.TO_HOME); - } - - @Test - @NavigationModeSwitch(mode = NavigationModeSwitchRule.Mode.ZERO_BUTTON) - public void testStressSwipeHome() { - runTest(TestCase.TO_HOME); - } - - @Test - @NavigationModeSwitch(mode = NavigationModeSwitchRule.Mode.THREE_BUTTON) - public void testStressPressOverview() { - runTest(TestCase.TO_OVERVIEW); - } - - @Test - @NavigationModeSwitch(mode = NavigationModeSwitchRule.Mode.ZERO_BUTTON) - public void testStressSwipeToOverview() { - runTest(TestCase.TO_OVERVIEW); - } - - private void runTest(TestCase testCase) { - long testStartTime = System.currentTimeMillis(); for (int i = 0; i < STRESS_REPEAT_COUNT; ++i) { - long loopStartTime = System.currentTimeMillis(); // Destroy Launcher activity. closeLauncherActivity(); // The test action. - switch (testCase) { - case TO_OVERVIEW: - mLauncher.getLaunchedAppState().switchToOverview(); - break; - case TO_HOME: - mLauncher.goHome(); - break; - default: - throw new IllegalStateException("Cannot run test case: " + testCase); - } - Log.d(TAG, "Loop " + (i + 1) + " runtime=" - + (System.currentTimeMillis() - loopStartTime) + "ms"); - } - Log.d(TAG, "Test runtime=" + (System.currentTimeMillis() - testStartTime) + "ms"); - switch (testCase) { - case TO_OVERVIEW: - closeLauncherActivity(); - mLauncher.goHome(); - break; - case TO_HOME: - default: - // No-Op - break; + mLauncher.goHome(); } } + + @Test + @NavigationModeSwitch + public void testStressSwipeToOverview() { + for (int i = 0; i < STRESS_REPEAT_COUNT; ++i) { + // Destroy Launcher activity. + closeLauncherActivity(); + + // The test action. + mLauncher.getLaunchedAppState().switchToOverview(); + } + closeLauncherActivity(); + mLauncher.goHome(); + } } diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsKeyboardQuickSwitch.java b/quickstep/tests/src/com/android/quickstep/TaplTestsKeyboardQuickSwitch.java index 3c4f1d9c6c..43ebb1752a 100644 --- a/quickstep/tests/src/com/android/quickstep/TaplTestsKeyboardQuickSwitch.java +++ b/quickstep/tests/src/com/android/quickstep/TaplTestsKeyboardQuickSwitch.java @@ -49,7 +49,6 @@ public class TaplTestsKeyboardQuickSwitch extends AbstractQuickStepTest { DISMISS(0), LAUNCH_LAST_APP(0), LAUNCH_SELECTED_APP(1), - DISMISS_WHEN_GOING_HOME(1), LAUNCH_OVERVIEW(KeyboardQuickSwitchController.MAX_TASKS - 1); private final int mNumAdditionalRunningTasks; @@ -157,11 +156,6 @@ public class TaplTestsKeyboardQuickSwitch extends AbstractQuickStepTest { mLauncher.goHome().showQuickSwitchView().launchFocusedAppTask(CALCULATOR_APP_PACKAGE); } - @Test - public void testDismissedWhenGoingHome() { - runTest(TestSurface.LAUNCHED_APP, TestCase.DISMISS_WHEN_GOING_HOME); - } - private void runTest(@NonNull TestSurface testSurface, @NonNull TestCase testCase) { for (int i = 0; i < testCase.mNumAdditionalRunningTasks; i++) { startTestActivity(3 + i); @@ -203,9 +197,6 @@ public class TaplTestsKeyboardQuickSwitch extends AbstractQuickStepTest { } kqs.launchFocusedAppTask(CALCULATOR_APP_PACKAGE); break; - case DISMISS_WHEN_GOING_HOME: - kqs.dismissByGoingHome(); - break; case LAUNCH_OVERVIEW: kqs.moveFocusBackward(); if (!testSurface.mInitialFocusAtZero) { diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsPersistentTaskbar.java b/quickstep/tests/src/com/android/quickstep/TaplTestsPersistentTaskbar.java index a16811efaf..df73e0913e 100644 --- a/quickstep/tests/src/com/android/quickstep/TaplTestsPersistentTaskbar.java +++ b/quickstep/tests/src/com/android/quickstep/TaplTestsPersistentTaskbar.java @@ -15,12 +15,16 @@ */ package com.android.quickstep; +import static com.android.quickstep.TaskbarModeSwitchRule.Mode.PERSISTENT; + import android.graphics.Rect; import androidx.test.filters.LargeTest; import androidx.test.runner.AndroidJUnit4; +import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape; import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch; +import com.android.quickstep.TaskbarModeSwitchRule.TaskbarModeSwitch; import org.junit.Assert; import org.junit.Test; @@ -31,6 +35,8 @@ import org.junit.runner.RunWith; public class TaplTestsPersistentTaskbar extends AbstractTaplTestsTaskbar { @Test + @TaskbarModeSwitch(mode = PERSISTENT) + @PortraitLandscape @NavigationModeSwitch public void testTaskbarFillsWidth() { // Width check is performed inside TAPL whenever getTaskbar() is called. diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java index b207d4ac8f..7877e8ac22 100644 --- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java +++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java @@ -16,14 +16,12 @@ package com.android.quickstep; -import static com.android.launcher3.Flags.enableLauncherOverviewInWindow; import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL; import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT; import static com.android.quickstep.TaskbarModeSwitchRule.Mode.TRANSIENT; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeFalse; @@ -32,13 +30,13 @@ import static org.junit.Assume.assumeTrue; import android.content.Intent; import android.content.res.Configuration; -import androidx.annotation.NonNull; import androidx.test.filters.LargeTest; import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; import androidx.test.uiautomator.By; import androidx.test.uiautomator.Until; +import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; import com.android.launcher3.tapl.BaseOverview; import com.android.launcher3.tapl.LaunchedAppState; @@ -49,13 +47,11 @@ import com.android.launcher3.tapl.OverviewTask; import com.android.launcher3.tapl.SelectModeButtons; import com.android.launcher3.tapl.Workspace; import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape; -import com.android.launcher3.util.TestUtil; import com.android.launcher3.util.Wait; -import com.android.launcher3.util.rule.ScreenRecordRule; +import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord; import com.android.launcher3.util.rule.TestStabilityRule; import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch; import com.android.quickstep.TaskbarModeSwitchRule.TaskbarModeSwitch; -import com.android.quickstep.fallback.RecentsState; import com.android.quickstep.views.RecentsView; import org.junit.After; @@ -64,10 +60,6 @@ import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; -import java.util.Comparator; -import java.util.function.Consumer; -import java.util.function.Function; - @LargeTest @RunWith(AndroidJUnit4.class) public class TaplTestsQuickstep extends AbstractQuickStepTest { @@ -77,32 +69,21 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest { private static final String READ_DEVICE_CONFIG_PERMISSION = "android.permission.READ_DEVICE_CONFIG"; - private enum ExpectedState { - - HOME(LauncherState.NORMAL, RecentsState.HOME), - OVERVIEW(LauncherState.OVERVIEW, RecentsState.DEFAULT); - - private final LauncherState mLauncherState; - private final RecentsState mRecentsState; - - ExpectedState(LauncherState launcherState, RecentsState recentsState) { - this.mLauncherState = launcherState; - this.mRecentsState = recentsState; - } - } - @Before public void setUp() throws Exception { super.setUp(); - runOnRecentsView(recentsView -> - recentsView.getPagedViewOrientedState().forceAllowRotationForTesting(true)); + executeOnLauncher(launcher -> { + RecentsView recentsView = launcher.getOverviewPanel(); + recentsView.getPagedViewOrientedState().forceAllowRotationForTesting(true); + }); } @After public void tearDown() { - runOnRecentsView(recentsView -> - recentsView.getPagedViewOrientedState().forceAllowRotationForTesting(false), - /* forTearDown= */ true); + executeOnLauncherInTearDown(launcher -> { + RecentsView recentsView = launcher.getOverviewPanel(); + recentsView.getPagedViewOrientedState().forceAllowRotationForTesting(false); + }); } public static void startTestApps() throws Exception { @@ -111,6 +92,14 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest { startTestActivity(2); } + private void startTestAppsWithCheck() throws Exception { + startTestApps(); + executeOnLauncher(launcher -> assertTrue( + "Launcher activity is the top activity; expecting another activity to be the top " + + "one", + isInLaunchedApp(launcher))); + } + @Test @NavigationModeSwitch @PortraitLandscape @@ -127,26 +116,28 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest { startTestAppsWithCheck(); // mLauncher.pressHome() also tests an important case of pressing home while in background. Overview overview = mLauncher.goHome().switchToOverview(); - assertIsInState( - "Launcher internal state didn't switch to Overview", ExpectedState.OVERVIEW); - runOnRecentsView(recentsView -> assertTrue("Don't have at least 3 tasks", - recentsView.getTaskViewCount() >= 3)); + assertTrue("Launcher internal state didn't switch to Overview", + isInState(() -> LauncherState.OVERVIEW)); + executeOnLauncher( + launcher -> assertTrue("Don't have at least 3 tasks", getTaskCount(launcher) >= 3)); // Test flinging forward and backward. - runOnRecentsView(recentsView -> assertEquals("Current task in Overview is not 0", - 0, recentsView.getCurrentPage())); + executeOnLauncher(launcher -> assertEquals("Current task in Overview is not 0", + 0, getCurrentOverviewPage(launcher))); overview.flingForward(); - assertIsInState("Launcher internal state is not Overview", ExpectedState.OVERVIEW); - final Integer currentTaskAfterFlingForward = - getFromRecentsView(RecentsView::getCurrentPage); - runOnRecentsView(recentsView -> assertTrue("Current task in Overview is still 0", + assertTrue("Launcher internal state is not Overview", + isInState(() -> LauncherState.OVERVIEW)); + final Integer currentTaskAfterFlingForward = getFromLauncher( + launcher -> getCurrentOverviewPage(launcher)); + executeOnLauncher(launcher -> assertTrue("Current task in Overview is still 0", currentTaskAfterFlingForward > 0)); overview.flingBackward(); - assertIsInState("Launcher internal state is not Overview", ExpectedState.OVERVIEW); - runOnRecentsView(recentsView -> assertTrue("Flinging back in Overview did nothing", - recentsView.getCurrentPage() < currentTaskAfterFlingForward)); + assertTrue("Launcher internal state is not Overview", + isInState(() -> LauncherState.OVERVIEW)); + executeOnLauncher(launcher -> assertTrue("Flinging back in Overview did nothing", + getCurrentOverviewPage(launcher) < currentTaskAfterFlingForward)); // Test opening a task. OverviewTask task = mLauncher.goHome().switchToOverview().getCurrentTask(); @@ -154,26 +145,31 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest { assertNotNull("OverviewTask.open returned null", task.open()); assertTrue("Test activity didn't open from Overview", mDevice.wait(Until.hasObject( By.pkg(getAppPackageName()).text("TestActivity2")), - TestUtil.DEFAULT_UI_TIMEOUT)); - expectLaunchedAppState(); + DEFAULT_UI_TIMEOUT)); + executeOnLauncher(launcher -> assertTrue( + "Launcher activity is the top activity; expecting another activity to be the top " + + "one", + isInLaunchedApp(launcher))); // Test dismissing a task. overview = mLauncher.goHome().switchToOverview(); - assertIsInState("Launcher internal state didn't switch to Overview", - ExpectedState.OVERVIEW); - final Integer numTasks = getFromRecentsView(RecentsView::getTaskViewCount); + assertTrue("Launcher internal state didn't switch to Overview", + isInState(() -> LauncherState.OVERVIEW)); + final Integer numTasks = getFromLauncher(launcher -> getTaskCount(launcher)); task = overview.getCurrentTask(); assertNotNull("overview.getCurrentTask() returned null (2)", task); task.dismiss(); - runOnRecentsView(recentsView -> assertEquals( - "Dismissing a task didn't remove 1 task from Overview", - numTasks - 1, recentsView.getTaskViewCount())); + executeOnLauncher( + launcher -> assertEquals("Dismissing a task didn't remove 1 task from Overview", + numTasks - 1, getTaskCount(launcher))); // Test dismissing all tasks. mLauncher.goHome().switchToOverview().dismissAllTasks(); - assertIsInState("Launcher internal state is not Home", ExpectedState.HOME); - runOnRecentsView(recentsView -> assertEquals("Still have tasks after dismissing all", - 0, recentsView.getTaskViewCount())); + assertTrue("Launcher internal state is not Home", + isInState(() -> LauncherState.NORMAL)); + executeOnLauncher( + launcher -> assertEquals("Still have tasks after dismissing all", + 0, getTaskCount(launcher))); } /** @@ -195,10 +191,12 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest { public void testDismissOverviewWithEscKey() throws Exception { startTestAppsWithCheck(); final Overview overview = mLauncher.goHome().switchToOverview(); - assertIsInState("Launcher internal state is not Overview", ExpectedState.OVERVIEW); + assertTrue("Launcher internal state is not Overview", + isInState(() -> LauncherState.OVERVIEW)); overview.dismissByEscKey(); - assertIsInState("Launcher internal state is not Home", ExpectedState.HOME); + assertTrue("Launcher internal state is not Home", + isInState(() -> LauncherState.NORMAL)); } @Test @@ -219,9 +217,11 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest { selectModeButtons.dismissByEscKey(); - assertIsInState("Launcher internal state is not Overview", ExpectedState.OVERVIEW); + assertTrue("Launcher internal state is not Overview", + isInState(() -> LauncherState.OVERVIEW)); overview.dismissByEscKey(); - assertIsInState("Launcher internal state is not Home", ExpectedState.HOME); + assertTrue("Launcher internal state is not Home", + isInState(() -> LauncherState.NORMAL)); } @Test @@ -229,11 +229,11 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest { startTestAppsWithCheck(); startAppFast(CALCULATOR_APP_PACKAGE); // Ensure Calculator is last opened app. Workspace home = mLauncher.goHome(); - assertIsInState("Launcher state is not Home", ExpectedState.HOME); + assertTrue("Launcher state is not Home", isInState(() -> LauncherState.NORMAL)); Overview overview = home.openOverviewFromActionPlusTabKeyboardShortcut(); - assertIsInState("Launcher state is not Overview", ExpectedState.OVERVIEW); + assertTrue("Launcher state is not Overview", isInState(() -> LauncherState.OVERVIEW)); overview.launchFocusedTaskByEnterKey(CALCULATOR_APP_PACKAGE); // Assert app is focused. } @@ -242,14 +242,33 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest { startTestAppsWithCheck(); startAppFast(CALCULATOR_APP_PACKAGE); // Ensure Calculator is last opened app. Workspace home = mLauncher.goHome(); - assertIsInState("Launcher state is not Home", ExpectedState.HOME); + assertTrue("Launcher state is not Home", isInState(() -> LauncherState.NORMAL)); Overview overview = home.openOverviewFromRecentsKeyboardShortcut(); - assertIsInState("Launcher state is not Overview", ExpectedState.OVERVIEW); + assertTrue("Launcher state is not Overview", isInState(() -> LauncherState.OVERVIEW)); overview.launchFocusedTaskByEnterKey(CALCULATOR_APP_PACKAGE); // Assert app is focused. } + private int getCurrentOverviewPage(Launcher launcher) { + return launcher.getOverviewPanel().getCurrentPage(); + } + + private int getTaskCount(Launcher launcher) { + return launcher.getOverviewPanel().getTaskViewCount(); + } + + private int getTopRowTaskCountForTablet(Launcher launcher) { + return launcher.getOverviewPanel().getTopRowTaskCountForTablet(); + } + + private int getBottomRowTaskCountForTablet(Launcher launcher) { + return launcher.getOverviewPanel().getBottomRowTaskCountForTablet(); + } + + // Staging; will be promoted to presubmit if stable + @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) + @Test @NavigationModeSwitch @PortraitLandscape @@ -257,8 +276,8 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest { startTestAppsWithCheck(); assertNotNull("Workspace.switchToOverview() returned null", mLauncher.goHome().switchToOverview()); - assertIsInState( - "Launcher internal state didn't switch to Overview", ExpectedState.OVERVIEW); + assertTrue("Launcher internal state didn't switch to Overview", + isInState(() -> LauncherState.OVERVIEW)); } @Test @@ -274,6 +293,9 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest { } } + // Staging; will be promoted to presubmit if stable + @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) + @Test @NavigationModeSwitch @PortraitLandscape @@ -283,13 +305,27 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest { assertNotNull("Background.switchToOverview() returned null", launchedAppState.switchToOverview()); - assertIsInState( - "Launcher internal state didn't switch to Overview", ExpectedState.OVERVIEW); + assertTrue("Launcher internal state didn't switch to Overview", + isInState(() -> LauncherState.OVERVIEW)); + } + + private void quickSwitchToPreviousAppAndAssert(boolean toRight) { + final LaunchedAppState launchedAppState = getAndAssertLaunchedApp(); + if (toRight) { + launchedAppState.quickSwitchToPreviousApp(); + } else { + launchedAppState.quickSwitchToPreviousAppSwipeLeft(); + } + + // While enable shell transition, Launcher can be resumed due to transient launch. + waitForLauncherCondition("Launcher shouldn't stay in resume forever", + this::isInLaunchedApp, 3000 /* timeout */); } @Test @NavigationModeSwitch @PortraitLandscape + @ScreenRecord // b/313464374 @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/325659406 public void testQuickSwitchFromApp() throws Exception { startTestActivity(2); @@ -357,6 +393,11 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest { } } + private boolean isHardwareKeyboard() { + return Configuration.KEYBOARD_QWERTY + == mTargetContext.getResources().getConfiguration().keyboard; + } + @Test @NavigationModeSwitch @PortraitLandscape @@ -377,11 +418,11 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest { // Debug if we need to goHome to prevent wrong previous state b/315525621 mLauncher.goHome(); mLauncher.getWorkspace().switchToAllApps().pressBackToWorkspace(); - waitForState("Launcher internal state didn't switch to Home", ExpectedState.HOME); + waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL); startAppFast(CALCULATOR_APP_PACKAGE); mLauncher.getLaunchedAppState().pressBackToWorkspace(); - waitForState("Launcher internal state didn't switch to Home", ExpectedState.HOME); + waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL); } @Test @@ -396,14 +437,16 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest { } Overview overview = mLauncher.goHome().switchToOverview(); - runOnRecentsView(recentsView -> assertTrue("Don't have at least 13 tasks", - recentsView.getTaskViewCount() >= 13)); + executeOnLauncher( + launcher -> assertTrue("Don't have at least 13 tasks", + getTaskCount(launcher) >= 13)); // Test scroll the first task off screen overview.scrollCurrentTaskOffScreen(); - assertIsInState("Launcher internal state is not Overview", ExpectedState.OVERVIEW); - runOnRecentsView(recentsView -> assertTrue("Current task in Overview is still 0", - recentsView.getCurrentPage() > 0)); + assertTrue("Launcher internal state is not Overview", + isInState(() -> LauncherState.OVERVIEW)); + executeOnLauncher(launcher -> assertTrue("Current task in Overview is still 0", + getCurrentOverviewPage(launcher) > 0)); // Test opening the task. overview.getCurrentTask().open(); @@ -411,43 +454,45 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest { mDevice.wait(Until.hasObject(By.pkg(getAppPackageName()).text( mLauncher.isGridOnlyOverviewEnabled() ? "TestActivity12" : "TestActivity13")), - TestUtil.DEFAULT_UI_TIMEOUT)); + DEFAULT_UI_TIMEOUT)); // Scroll the task offscreen as it is now first overview = mLauncher.goHome().switchToOverview(); overview.scrollCurrentTaskOffScreen(); - assertIsInState( - "Launcher internal state is not Overview", ExpectedState.OVERVIEW); - runOnRecentsView(recentsView -> assertTrue("Current task in Overview is still 0", - recentsView.getCurrentPage() > 0)); + assertTrue("Launcher internal state is not Overview", + isInState(() -> LauncherState.OVERVIEW)); + executeOnLauncher(launcher -> assertTrue("Current task in Overview is still 0", + getCurrentOverviewPage(launcher) > 0)); // Test dismissing the later task. - final Integer numTasks = getFromRecentsView(RecentsView::getTaskViewCount); + final Integer numTasks = getFromLauncher(this::getTaskCount); overview.getCurrentTask().dismiss(); - runOnRecentsView(recentsView -> assertEquals( - "Dismissing a task didn't remove 1 task from Overview", - numTasks - 1, recentsView.getTaskViewCount())); - runOnRecentsView(recentsView -> assertTrue("Grid did not rebalance after dismissal", - (Math.abs(recentsView.getTopRowTaskCountForTablet() - - recentsView.getBottomRowTaskCountForTablet()) <= 1))); + executeOnLauncher( + launcher -> assertEquals("Dismissing a task didn't remove 1 task from Overview", + numTasks - 1, getTaskCount(launcher))); + executeOnLauncher(launcher -> assertTrue("Grid did not rebalance after dismissal", + (Math.abs(getTopRowTaskCountForTablet(launcher) - getBottomRowTaskCountForTablet( + launcher)) <= 1))); - // Test dismissing more tasks. - assertIsInState( - "Launcher internal state didn't remain in Overview", ExpectedState.OVERVIEW); - overview.getCurrentTask().dismiss(); - assertIsInState( - "Launcher internal state didn't remain in Overview", ExpectedState.OVERVIEW); - overview.getCurrentTask().dismiss(); - runOnRecentsView(recentsView -> assertTrue( - "Grid did not rebalance after multiple dismissals", - (Math.abs(recentsView.getTopRowTaskCountForTablet() - - recentsView.getBottomRowTaskCountForTablet()) <= 1))); + // TODO(b/308841019): Re-enable after fixing Overview jank when dismiss +// // Test dismissing more tasks. +// assertTrue("Launcher internal state didn't remain in Overview", +// isInState(() -> LauncherState.OVERVIEW)); +// overview.getCurrentTask().dismiss(); +// assertTrue("Launcher internal state didn't remain in Overview", +// isInState(() -> LauncherState.OVERVIEW)); +// overview.getCurrentTask().dismiss(); +// executeOnLauncher(launcher -> assertTrue("Grid did not rebalance after multiple dismissals", +// (Math.abs(getTopRowTaskCountForTablet(launcher) - getBottomRowTaskCountForTablet( +// launcher)) <= 1))); // Test dismissing all tasks. mLauncher.goHome().switchToOverview().dismissAllTasks(); - assertIsInState("Launcher internal state is not Home", ExpectedState.HOME); - runOnRecentsView(recentsView -> assertEquals("Still have tasks after dismissing all", - 0, recentsView.getTaskViewCount())); + assertTrue("Launcher internal state is not Home", + isInState(() -> LauncherState.NORMAL)); + executeOnLauncher( + launcher -> assertEquals("Still have tasks after dismissing all", + 0, getTaskCount(launcher))); } @Test @@ -456,18 +501,22 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest { startTestAppsWithCheck(); Overview overview = mLauncher.goHome().switchToOverview(); - assertIsInState("Launcher internal state should be Overview", ExpectedState.OVERVIEW); - runOnRecentsView(recentsView -> assertTrue("Should have at least 3 tasks", - recentsView.getTaskViewCount() >= 3)); + assertTrue("Launcher internal state should be Overview", + isInState(() -> LauncherState.OVERVIEW)); + executeOnLauncher( + launcher -> assertTrue("Should have at least 3 tasks", + getTaskCount(launcher) >= 3)); // It should not dismiss overview when tapping between tasks overview.touchBetweenTasks(); overview = mLauncher.getOverview(); - assertIsInState("Launcher internal state should be Overview", ExpectedState.OVERVIEW); + assertTrue("Launcher internal state should be Overview", + isInState(() -> LauncherState.OVERVIEW)); // Dismiss when tapping to the right of the focused task overview.touchOutsideFirstTask(); - assertIsInState("Launcher internal state should be Home", ExpectedState.HOME); + assertTrue("Launcher internal state should be Home", + isInState(() -> LauncherState.NORMAL)); } @Test @@ -479,28 +528,34 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest { startTestAppsWithCheck(); Overview overview = mLauncher.goHome().switchToOverview(); - assertIsInState("Launcher internal state should be Overview", ExpectedState.OVERVIEW); - runOnRecentsView(recentsView -> assertTrue("Should have at least 3 tasks", - recentsView.getTaskViewCount() >= 3)); + assertTrue("Launcher internal state should be Overview", + isInState(() -> LauncherState.OVERVIEW)); + executeOnLauncher( + launcher -> assertTrue("Should have at least 3 tasks", + getTaskCount(launcher) >= 3)); if (mLauncher.isTransientTaskbar()) { // On transient taskbar, it should dismiss when tapping outside taskbar bounds. overview.touchTaskbarBottomCorner(/* tapRight= */ false); - assertIsInState("Launcher internal state should be Normal", ExpectedState.HOME); + assertTrue("Launcher internal state should be Normal", + isInState(() -> LauncherState.NORMAL)); overview = mLauncher.getWorkspace().switchToOverview(); // On transient taskbar, it should dismiss when tapping outside taskbar bounds. overview.touchTaskbarBottomCorner(/* tapRight= */ true); - assertIsInState("Launcher internal state should be Normal", ExpectedState.HOME); + assertTrue("Launcher internal state should be Normal", + isInState(() -> LauncherState.NORMAL)); } else { // On persistent taskbar, it should not dismiss when tapping the taskbar overview.touchTaskbarBottomCorner(/* tapRight= */ false); - assertIsInState("Launcher internal state should be Overview", ExpectedState.OVERVIEW); + assertTrue("Launcher internal state should be Overview", + isInState(() -> LauncherState.OVERVIEW)); // On persistent taskbar, it should not dismiss when tapping the taskbar overview.touchTaskbarBottomCorner(/* tapRight= */ true); - assertIsInState("Launcher internal state should be Overview", ExpectedState.OVERVIEW); + assertTrue("Launcher internal state should be Overview", + isInState(() -> LauncherState.OVERVIEW)); } } @@ -513,7 +568,7 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest { mLauncher.getDevice().setOrientationLeft(); startTestActivity(7); Wait.atMost("Device should not be in natural orientation", - () -> !mDevice.isNaturalOrientation(), mLauncher); + () -> !mDevice.isNaturalOrientation(), DEFAULT_UI_TIMEOUT, mLauncher); mLauncher.goHome(); } finally { mLauncher.setExpectedRotationCheckEnabled(true); @@ -526,164 +581,20 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest { public void testExcludeFromRecents() throws Exception { startExcludeFromRecentsTestActivity(); OverviewTask currentTask = getAndAssertLaunchedApp().switchToOverview().getCurrentTask(); + // TODO(b/326565120): the expected content description shouldn't be null but for now there + // is a bug that causes it to sometimes be for excludeForRecents tasks. assertTrue("Can't find ExcludeFromRecentsTestActivity after entering Overview from it", - currentTask.containsContentDescription("ExcludeFromRecents")); + currentTask.containsContentDescription("ExcludeFromRecents") + || currentTask.containsContentDescription(null)); // Going home should clear out the excludeFromRecents task. BaseOverview overview = mLauncher.goHome().switchToOverview(); if (overview.hasTasks()) { currentTask = overview.getCurrentTask(); assertFalse("Found ExcludeFromRecentsTestActivity after entering Overview from Home", - currentTask.containsContentDescription("ExcludeFromRecents")); + currentTask.containsContentDescription("ExcludeFromRecents") + || currentTask.containsContentDescription(null)); } else { // Presumably the test started with 0 tasks and remains that way after going home. } } - - @Test - @PortraitLandscape - @ScreenRecordRule.ScreenRecord // TODO(b/396447643): Remove screen record. - public void testDismissCancel() throws Exception { - startTestAppsWithCheck(); - Overview overview = mLauncher.goHome().switchToOverview(); - assertIsInState("Launcher internal state didn't switch to Overview", - ExpectedState.OVERVIEW); - final Integer numTasks = getFromRecentsView(RecentsView::getTaskViewCount); - OverviewTask task = overview.getCurrentTask(); - assertNotNull("overview.getCurrentTask() returned null (2)", task); - - task.dismissCancel(); - - runOnRecentsView(recentsView -> assertEquals( - "Canceling dismissing a task removed a task from Overview", - numTasks == null ? 0 : numTasks, recentsView.getTaskViewCount())); - } - - @Test - @PortraitLandscape - public void testDismissBottomRow() throws Exception { - assumeTrue(mLauncher.isTablet()); - mLauncher.goHome().switchToOverview().dismissAllTasks(); - startTestAppsWithCheck(); - Overview overview = mLauncher.goHome().switchToOverview(); - assertIsInState("Launcher internal state didn't switch to Overview", - ExpectedState.OVERVIEW); - final Integer numTasks = getFromRecentsView(RecentsView::getTaskViewCount); - OverviewTask bottomTask = overview.getCurrentTasksForTablet().stream().max( - Comparator.comparingInt(OverviewTask::getTaskCenterY)).get(); - assertNotNull("bottomTask null", bottomTask); - - bottomTask.dismiss(); - - runOnRecentsView(recentsView -> assertEquals( - "Dismissing a bottomTask didn't remove 1 bottomTask from Overview", - numTasks - 1, recentsView.getTaskViewCount())); - } - - @Test - @PortraitLandscape - public void testDismissLastGridRow() throws Exception { - assumeTrue(mLauncher.isTablet()); - mLauncher.goHome().switchToOverview().dismissAllTasks(); - startTestAppsWithCheck(); - startTestActivity(3); - startTestActivity(4); - runOnRecentsView( - recentsView -> assertNotEquals("Grid overview should have unequal row counts", - recentsView.getTopRowTaskCountForTablet(), - recentsView.getBottomRowTaskCountForTablet())); - Overview overview = mLauncher.goHome().switchToOverview(); - assertIsInState("Launcher internal state didn't switch to Overview", - ExpectedState.OVERVIEW); - overview.flingForwardUntilClearAllVisible(); - assertTrue("Clear All not visible.", overview.isClearAllVisible()); - final Integer numTasks = getFromRecentsView(RecentsView::getTaskViewCount); - OverviewTask lastGridTask = overview.getCurrentTasksForTablet().stream().min( - Comparator.comparingInt(OverviewTask::getTaskCenterX)).get(); - assertNotNull("lastGridTask null.", lastGridTask); - - lastGridTask.dismiss(); - - runOnRecentsView(recentsView -> assertEquals( - "Dismissing a lastGridTask didn't remove 1 lastGridTask from Overview", - numTasks - 1, recentsView.getTaskViewCount())); - runOnRecentsView(recentsView -> assertEquals("Grid overview should have equal row counts.", - recentsView.getTopRowTaskCountForTablet(), - recentsView.getBottomRowTaskCountForTablet())); - assertTrue("Clear All not visible.", overview.isClearAllVisible()); - } - - private void startTestAppsWithCheck() throws Exception { - startTestApps(); - expectLaunchedAppState(); - } - - private void quickSwitchToPreviousAppAndAssert(boolean toRight) { - final LaunchedAppState launchedAppState = getAndAssertLaunchedApp(); - if (toRight) { - launchedAppState.quickSwitchToPreviousApp(); - } else { - launchedAppState.quickSwitchToPreviousAppSwipeLeft(); - } - - // While enable shell transition, Launcher can be resumed due to transient launch. - waitForLauncherCondition("Launcher shouldn't stay in resume forever", - this::isInLaunchedApp, 3000 /* timeout */); - } - - private boolean isHardwareKeyboard() { - return Configuration.KEYBOARD_QWERTY - == mTargetContext.getResources().getConfiguration().keyboard; - } - - private void assertIsInState( - @NonNull String failureMessage, @NonNull ExpectedState expectedState) { - assertTrue(failureMessage, enableLauncherOverviewInWindow() - ? isInRecentsWindowState(() -> expectedState.mRecentsState) - : isInState(() -> expectedState.mLauncherState)); - } - - private void waitForState( - @NonNull String failureMessage, @NonNull ExpectedState expectedState) { - if (enableLauncherOverviewInWindow()) { - waitForRecentsWindowState(failureMessage, () -> expectedState.mRecentsState); - } else { - waitForState(failureMessage, () -> expectedState.mLauncherState); - } - } - - private void expectLaunchedAppState() { - executeOnLauncher(launcher -> assertTrue( - "Launcher activity is the top activity; expecting another activity to be the top " - + "one", - isInLaunchedApp(launcher))); - } - - private T getFromRecentsView(Function f) { - return getFromRecentsView(f, false); - } - - private T getFromRecentsView(Function f, boolean forTearDown) { - if (enableLauncherOverviewInWindow()) { - return getFromRecentsWindow(recentsWindowManager -> - (forTearDown && recentsWindowManager == null) - ? null : f.apply(recentsWindowManager.getOverviewPanel())); - } else { - return getFromLauncher(launcher -> (forTearDown && launcher == null) - ? null : f.apply(launcher.getOverviewPanel())); - } - } - - private void runOnRecentsView(Consumer f) { - runOnRecentsView(f, false); - } - - private void runOnRecentsView(Consumer f, boolean forTearDown) { - getFromRecentsView(recentsView -> { - if (forTearDown && recentsView == null) { - return null; - } - f.accept(recentsView); - return null; - }, forTearDown); - } } diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java b/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java index 37ac4a0ed2..8adf79318b 100644 --- a/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java +++ b/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java @@ -16,6 +16,10 @@ package com.android.quickstep; +import static com.android.launcher3.config.FeatureFlags.enableSplitContextually; +import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL; +import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT; + import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeTrue; @@ -29,7 +33,8 @@ import androidx.test.platform.app.InstrumentationRegistry; import com.android.launcher3.tapl.Overview; import com.android.launcher3.tapl.Taskbar; import com.android.launcher3.tapl.TaskbarAppIcon; -import com.android.quickstep.util.SplitScreenTestUtils; +import com.android.launcher3.util.rule.TestStabilityRule; +import com.android.wm.shell.Flags; import org.junit.After; import org.junit.Before; @@ -67,6 +72,7 @@ public class TaplTestsSplitscreen extends AbstractQuickStepTest { } @Test + @TestStabilityRule.Stability(flavors = PLATFORM_POSTSUBMIT | LOCAL) // b/295225524 public void testSplitAppFromHomeWithItself() throws Exception { // Currently only tablets have Taskbar in Overview, so test is only active on tablets assumeTrue(mLauncher.isTablet()); @@ -86,16 +92,28 @@ public class TaplTestsSplitscreen extends AbstractQuickStepTest { .getSplitScreenMenuItem() .click(); - // We're staying in all apps, use same instance - mLauncher.getAllApps() - .getAppIcon(CALCULATOR_APP_NAME) - .launchIntoSplitScreen(); + if (enableSplitContextually()) { + // We're staying in all apps, use same instance + mLauncher.getAllApps() + .getAppIcon(CALCULATOR_APP_NAME) + .launchIntoSplitScreen(); + } else { + // We're in overview, use taskbar instance + mLauncher.getLaunchedAppState() + .getTaskbar() + .getAppIcon(CALCULATOR_APP_NAME) + .launchIntoSplitScreen(); + } } @Test public void testSaveAppPairMenuItemOrActionExistsOnSplitPair() { - Overview overview = SplitScreenTestUtils.createAndLaunchASplitPairInOverview(mLauncher); + assumeTrue("App pairs feature is currently not enabled, no test needed", + Flags.enableAppPairs()); + createAndLaunchASplitPair(); + + Overview overview = mLauncher.goHome().switchToOverview(); if (mLauncher.isGridOnlyOverviewEnabled() || !mLauncher.isTablet()) { assertTrue("Save app pair menu item is missing", overview.getCurrentTask() @@ -106,6 +124,9 @@ public class TaplTestsSplitscreen extends AbstractQuickStepTest { @Test public void testSaveAppPairMenuItemDoesNotExistOnSingleTask() throws Exception { + assumeTrue("App pairs feature is currently not enabled, no test needed", + Flags.enableAppPairs()); + startAppFast(CALCULATOR_APP_PACKAGE); assertFalse("Save app pair menu item is erroneously appearing on single task", @@ -136,4 +157,24 @@ public class TaplTestsSplitscreen extends AbstractQuickStepTest { TaskbarAppIcon firstApp = taskbar.getAppIcon(firstAppName); firstApp.launchIntoSplitScreen(); } + + private void createAndLaunchASplitPair() { + clearAllRecentTasks(); + + startTestActivity(2); + startTestActivity(3); + + if (mLauncher.isTablet() && !mLauncher.isGridOnlyOverviewEnabled()) { + mLauncher.goHome().switchToOverview().getOverviewActions() + .clickSplit() + .getTestActivityTask(2) + .open(); + } else { + mLauncher.goHome().switchToOverview().getCurrentTask() + .tapMenu() + .tapSplitMenuItem() + .getCurrentTask() + .open(); + } + } } diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsTrackpad.java b/quickstep/tests/src/com/android/quickstep/TaplTestsTrackpad.java index 710ad6f6dc..2c23f867cb 100644 --- a/quickstep/tests/src/com/android/quickstep/TaplTestsTrackpad.java +++ b/quickstep/tests/src/com/android/quickstep/TaplTestsTrackpad.java @@ -16,6 +16,8 @@ package com.android.quickstep; +import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL; +import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT; import static com.android.quickstep.NavigationModeSwitchRule.Mode.ZERO_BUTTON; import static org.junit.Assert.assertNotNull; @@ -30,6 +32,8 @@ import androidx.test.runner.AndroidJUnit4; import com.android.launcher3.tapl.LauncherInstrumentation.TrackpadGestureType; import com.android.launcher3.tapl.Workspace; import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape; +import com.android.launcher3.util.rule.ScreenRecordRule; +import com.android.launcher3.util.rule.TestStabilityRule; import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch; import org.junit.After; @@ -91,6 +95,8 @@ public class TaplTestsTrackpad extends AbstractQuickStepTest { @Test @PortraitLandscape @NavigationModeSwitch + @ScreenRecordRule.ScreenRecord // b/336606166 + @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/336606166 public void switchToOverview() throws Exception { assumeTrue(mLauncher.isTablet()); diff --git a/quickstep/tests/src/com/android/quickstep/TaskThumbnailCacheTest.java b/quickstep/tests/src/com/android/quickstep/TaskThumbnailCacheTest.java new file mode 100644 index 0000000000..4e04261e5c --- /dev/null +++ b/quickstep/tests/src/com/android/quickstep/TaskThumbnailCacheTest.java @@ -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.quickstep; + +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.res.Resources; + +import androidx.test.filters.SmallTest; + +import com.android.launcher3.R; +import com.android.quickstep.util.TaskKeyCache; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.concurrent.Executor; + +@SmallTest +public class TaskThumbnailCacheTest { + @Mock + private Context mContext; + + @Mock + private Resources mResource; + + @Mock + private TaskKeyCache mTaskKeyCache; + + @Before + public void setup() throws NoSuchFieldException { + MockitoAnnotations.initMocks(this); + when(mContext.getResources()).thenReturn(mResource); + } + + @Test + public void increaseCacheSize() { + // Mock a cache size increase from 3 to 8 + when(mTaskKeyCache.getMaxSize()).thenReturn(3); + when(mResource.getInteger((R.integer.recentsThumbnailCacheSize))).thenReturn(8); + TaskThumbnailCache thumbnailCache = new TaskThumbnailCache(mContext, mock(Executor.class), + mTaskKeyCache); + + // Preload is needed when increasing size + assertTrue(thumbnailCache.updateCacheSizeAndRemoveExcess()); + verify(mTaskKeyCache, times(1)).updateCacheSizeAndRemoveExcess(8); + } + + @Test + public void decreaseCacheSize() { + // Mock a cache size decrease from 8 to 3 + when(mTaskKeyCache.getMaxSize()).thenReturn(8); + when(mResource.getInteger((R.integer.recentsThumbnailCacheSize))).thenReturn(3); + TaskThumbnailCache thumbnailCache = new TaskThumbnailCache(mContext, mock(Executor.class), + mTaskKeyCache); + // Preload is not needed when decreasing size + assertFalse(thumbnailCache.updateCacheSizeAndRemoveExcess()); + verify(mTaskKeyCache, times(1)).updateCacheSizeAndRemoveExcess(3); + } + + @Test + public void keepSameCacheSize() { + when(mTaskKeyCache.getMaxSize()).thenReturn(3); + when(mResource.getInteger((R.integer.recentsThumbnailCacheSize))).thenReturn(3); + TaskThumbnailCache thumbnailCache = new TaskThumbnailCache(mContext, mock(Executor.class), + mTaskKeyCache); + // Preload is not needed when it has the same cache size + assertFalse(thumbnailCache.updateCacheSizeAndRemoveExcess()); + verify(mTaskKeyCache, never()).updateCacheSizeAndRemoveExcess(anyInt()); + } +} diff --git a/quickstep/tests/src/com/android/quickstep/TaskViewTest.java b/quickstep/tests/src/com/android/quickstep/TaskViewTest.java index dc1da69342..512557bf3a 100644 --- a/quickstep/tests/src/com/android/quickstep/TaskViewTest.java +++ b/quickstep/tests/src/com/android/quickstep/TaskViewTest.java @@ -87,6 +87,18 @@ public class TaskViewTest { true); } + @Test + public void showBorderOnHoverEvent() { + mTaskView.setBorderEnabled(/* enabled= */ true); + MotionEvent event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_HOVER_ENTER, 0.0f, 0.0f, 0); + mTaskView.onHoverEvent(MotionEvent.obtain(event)); + verify(mHoverAnimator, times(1)).setBorderVisibility(/* visible= */ true, /* animated= */ + true); + mTaskView.onFocusChanged(true, 0, new Rect()); + verify(mFocusAnimator, times(1)).setBorderVisibility(/* visible= */ true, /* animated= */ + true); + } + @Test public void showBorderOnBorderEnabled() { presetBorderStatus(/* enabled= */ false); diff --git a/quickstep/tests/src/com/android/quickstep/taskbar/controllers/TaskbarPinningControllerTest.kt b/quickstep/tests/src/com/android/quickstep/taskbar/controllers/TaskbarPinningControllerTest.kt new file mode 100644 index 0000000000..4d10f0f51e --- /dev/null +++ b/quickstep/tests/src/com/android/quickstep/taskbar/controllers/TaskbarPinningControllerTest.kt @@ -0,0 +1,219 @@ +/* + * 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.quickstep.taskbar.controllers + +import android.animation.AnimatorSet +import android.animation.ObjectAnimator +import android.view.View +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.launcher3.LauncherPrefs +import com.android.launcher3.LauncherPrefs.Companion.TASKBAR_PINNING +import com.android.launcher3.LauncherPrefs.Companion.TASKBAR_PINNING_IN_DESKTOP_MODE +import com.android.launcher3.logging.StatsLogManager +import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_DIVIDER_MENU_CLOSE +import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_DIVIDER_MENU_OPEN +import com.android.launcher3.taskbar.TaskbarActivityContext +import com.android.launcher3.taskbar.TaskbarBaseTestCase +import com.android.launcher3.taskbar.TaskbarDividerPopupView +import com.android.launcher3.taskbar.TaskbarDragLayer +import com.android.launcher3.taskbar.TaskbarPinningController +import com.android.launcher3.taskbar.TaskbarPinningController.Companion.PINNING_PERSISTENT +import com.android.launcher3.taskbar.TaskbarPinningController.Companion.PINNING_TRANSIENT +import com.android.launcher3.taskbar.TaskbarSharedState +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.doNothing +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.never +import org.mockito.kotlin.spy +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +@SmallTest +@RunWith(AndroidJUnit4::class) +class TaskbarPinningControllerTest : TaskbarBaseTestCase() { + private val taskbarDragLayer = mock() + private val taskbarSharedState = mock() + private var isInDesktopMode = false + private val isInDesktopModeProvider = { isInDesktopMode } + private val launcherPrefs = + mock { + on { get(TASKBAR_PINNING) } doReturn false + on { get(TASKBAR_PINNING_IN_DESKTOP_MODE) } doReturn false + } + private val statsLogger = mock() + private val statsLogManager = mock { on { logger() } doReturn statsLogger } + private lateinit var pinningController: TaskbarPinningController + + @Before + override fun setup() { + super.setup() + whenever(taskbarActivityContext.launcherPrefs).thenReturn(launcherPrefs) + whenever(taskbarActivityContext.dragLayer).thenReturn(taskbarDragLayer) + whenever(taskbarActivityContext.statsLogManager).thenReturn(statsLogManager) + pinningController = + spy(TaskbarPinningController(taskbarActivityContext, isInDesktopModeProvider)) + pinningController.init(taskbarControllers, taskbarSharedState) + } + + @Test + fun testOnCloseCallback_whenClosingPopupView_shouldLogStatsForClosingPopupMenu() { + pinningController.onCloseCallback(false) + verify(statsLogger, times(1)).log(LAUNCHER_TASKBAR_DIVIDER_MENU_CLOSE) + } + + @Test + fun testOnCloseCallback_whenClosingPopupView_shouldPostVisibilityChangedToDragLayer() { + val argumentCaptor = argumentCaptor() + pinningController.onCloseCallback(false) + verify(taskbarDragLayer, times(1)).post(argumentCaptor.capture()) + + val runnable = argumentCaptor.lastValue + assertThat(runnable).isNotNull() + + runnable.run() + verify(taskbarActivityContext, times(1)).onPopupVisibilityChanged(false) + } + + @Test + fun testOnCloseCallback_whenPreferenceUnchanged_shouldNotAnimateTaskbarPinning() { + pinningController.onCloseCallback(false) + verify(taskbarSharedState, never()).taskbarWasPinned = true + verify(pinningController, never()).animateTaskbarPinning(any()) + } + + @Test + fun testOnCloseCallback_whenLauncherPreferenceChanged_shouldAnimateToPinnedTaskbar() { + whenever(launcherPrefs.get(TASKBAR_PINNING)).thenReturn(false) + doNothing().whenever(pinningController).animateTaskbarPinning(any()) + + pinningController.onCloseCallback(true) + + verify(taskbarSharedState, times(1)).taskbarWasPinned = false + verify(pinningController, times(1)).animateTaskbarPinning(PINNING_PERSISTENT) + } + + @Test + fun testOnCloseCallback_whenLauncherPreferenceChanged_shouldAnimateToTransientTaskbar() { + whenever(launcherPrefs.get(TASKBAR_PINNING)).thenReturn(true) + doNothing().whenever(pinningController).animateTaskbarPinning(any()) + + pinningController.onCloseCallback(true) + + verify(taskbarSharedState, times(1)).taskbarWasPinned = true + verify(pinningController, times(1)).animateTaskbarPinning(PINNING_TRANSIENT) + } + + @Test + fun testShowPinningView_whenShowingPinningView_shouldSetTaskbarWindowFullscreenAndPostRunnableToView() { + val popupView = + mock> { + on { requestFocus() } doReturn true + } + val view = mock() + val argumentCaptor = argumentCaptor() + doReturn(popupView).whenever(pinningController).getPopupView(view) + + pinningController.showPinningView(view) + + verify(view, times(1)).post(argumentCaptor.capture()) + + val runnable = argumentCaptor.lastValue + assertThat(runnable).isNotNull() + runnable.run() + + verify(pinningController, times(1)).getPopupView(view) + verify(popupView, times(1)).requestFocus() + verify(popupView, times(1)).onCloseCallback = any() + verify(taskbarActivityContext, times(1)).onPopupVisibilityChanged(true) + verify(popupView, times(1)).show() + verify(statsLogger, times(1)).log(LAUNCHER_TASKBAR_DIVIDER_MENU_OPEN) + } + + @Test + fun testAnimateTaskbarPinning_whenAnimationEnds_shouldInvokeCallbackDoOnEnd() { + val animatorSet = spy(AnimatorSet()) + doReturn(animatorSet) + .whenever(pinningController) + .getAnimatorSetForTaskbarPinningAnimation(PINNING_PERSISTENT) + doNothing().whenever(animatorSet).start() + pinningController.animateTaskbarPinning(PINNING_PERSISTENT) + animatorSet.listeners[0].onAnimationEnd(ObjectAnimator()) + verify(pinningController, times(1)).recreateTaskbarAndUpdatePinningValue() + } + + @Test + fun testAnimateTaskbarPinning_whenAnimatingToPersistentTaskbar_shouldAnimateToPinnedTaskbar() { + val animatorSet = spy(AnimatorSet()) + doReturn(animatorSet) + .whenever(pinningController) + .getAnimatorSetForTaskbarPinningAnimation(PINNING_PERSISTENT) + doNothing().whenever(animatorSet).start() + pinningController.animateTaskbarPinning(PINNING_PERSISTENT) + + verify(taskbarOverlayController, times(1)).hideWindow() + verify(pinningController, times(1)) + .getAnimatorSetForTaskbarPinningAnimation(PINNING_PERSISTENT) + verify(taskbarViewController, times(1)) + .animateAwayNotificationDotsDuringTaskbarPinningAnimation() + verify(taskbarDragLayer, times(1)).setAnimatingTaskbarPinning(true) + assertThat(pinningController.isAnimatingTaskbarPinning).isTrue() + assertThat(animatorSet.listeners).isNotNull() + } + + @Test + fun testAnimateTaskbarPinning_whenAnimatingToTransientTaskbar_shouldAnimateToTransientTaskbar() { + val animatorSet = spy(AnimatorSet()) + doReturn(animatorSet) + .whenever(pinningController) + .getAnimatorSetForTaskbarPinningAnimation(PINNING_TRANSIENT) + doNothing().whenever(animatorSet).start() + pinningController.animateTaskbarPinning(PINNING_TRANSIENT) + + verify(taskbarOverlayController, times(1)).hideWindow() + verify(pinningController, times(1)) + .getAnimatorSetForTaskbarPinningAnimation(PINNING_TRANSIENT) + verify(taskbarDragLayer, times(1)).setAnimatingTaskbarPinning(true) + assertThat(pinningController.isAnimatingTaskbarPinning).isTrue() + verify(taskbarViewController, times(1)) + .animateAwayNotificationDotsDuringTaskbarPinningAnimation() + assertThat(animatorSet.listeners).isNotNull() + } + + @Test + fun testRecreateTaskbarAndUpdatePinningValue_whenAnimationEnds_shouldUpdateTaskbarPinningLauncherPref() { + pinningController.recreateTaskbarAndUpdatePinningValue() + verify(taskbarDragLayer, times(1)).setAnimatingTaskbarPinning(false) + assertThat(pinningController.isAnimatingTaskbarPinning).isFalse() + verify(launcherPrefs, times(1)).put(TASKBAR_PINNING, true) + } + + @Test + fun testRecreateTaskbarAndUpdatePinningValue_whenAnimationEnds_shouldUpdateTaskbarPinningDesktopModePref() { + isInDesktopMode = true + pinningController.recreateTaskbarAndUpdatePinningValue() + verify(taskbarDragLayer, times(1)).setAnimatingTaskbarPinning(false) + assertThat(pinningController.isAnimatingTaskbarPinning).isFalse() + verify(launcherPrefs, times(1)).put(TASKBAR_PINNING_IN_DESKTOP_MODE, true) + } +} diff --git a/quickstep/tests/src/com/android/quickstep/taskbar/customization/TaskbarSpecsEvaluatorTest.kt b/quickstep/tests/src/com/android/quickstep/taskbar/customization/TaskbarSpecsEvaluatorTest.kt new file mode 100644 index 0000000000..b637e7d4cf --- /dev/null +++ b/quickstep/tests/src/com/android/quickstep/taskbar/customization/TaskbarSpecsEvaluatorTest.kt @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.quickstep.taskbar.customization + +import com.android.launcher3.taskbar.customization.TaskbarFeatureEvaluator +import com.android.launcher3.taskbar.customization.TaskbarIconSpecs +import com.android.launcher3.taskbar.customization.TaskbarSpecsEvaluator +import com.android.launcher3.util.LauncherMultivalentJUnit +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.spy +import org.mockito.kotlin.whenever + +@RunWith(LauncherMultivalentJUnit::class) +class TaskbarSpecsEvaluatorTest { + + private val taskbarFeatureEvaluator = mock() + private val taskbarSpecsEvaluator = spy(TaskbarSpecsEvaluator(taskbarFeatureEvaluator)) + + @Test + fun testGetIconSizeByGrid_whenTaskbarIsTransient_withValidRowAndColumn() { + doReturn(true).whenever(taskbarFeatureEvaluator).isTransient + assertThat(taskbarSpecsEvaluator.getIconSizeByGrid(6, 5)) + .isEqualTo(TaskbarIconSpecs.iconSize52dp) + } + + @Test + fun testGetIconSizeByGrid_whenTaskbarIsTransient_withInvalidRowAndColumn() { + doReturn(true).whenever(taskbarFeatureEvaluator).isTransient + assertThat(taskbarSpecsEvaluator.getIconSizeByGrid(1, 2)) + .isEqualTo(TaskbarIconSpecs.defaultTransientIconSize) + } + + @Test + fun testGetIconSizeByGrid_whenTaskbarIsPersistent() { + doReturn(false).whenever(taskbarFeatureEvaluator).isTransient + assertThat(taskbarSpecsEvaluator.getIconSizeByGrid(6, 5)) + .isEqualTo(TaskbarIconSpecs.defaultPersistentIconSize) + } + + @Test + fun testGetIconSizeStepDown_whenTaskbarIsPersistent() { + doReturn(false).whenever(taskbarFeatureEvaluator).isTransient + assertThat(taskbarSpecsEvaluator.getIconSizeStepDown(TaskbarIconSpecs.iconSize44dp)) + .isEqualTo(TaskbarIconSpecs.defaultPersistentIconSize) + } + + @Test + fun testGetIconSizeStepDown_whenTaskbarIsTransientAndIconSizeAreInBound() { + doReturn(true).whenever(taskbarFeatureEvaluator).isTransient + assertThat(taskbarSpecsEvaluator.getIconSizeStepDown(TaskbarIconSpecs.iconSize52dp)) + .isEqualTo(TaskbarIconSpecs.iconSize48dp) + } + + @Test + fun testGetIconSizeStepDown_whenTaskbarIsTransientAndIconSizeAreOutOfBound() { + doReturn(true).whenever(taskbarFeatureEvaluator).isTransient + assertThat(taskbarSpecsEvaluator.getIconSizeStepDown(TaskbarIconSpecs.iconSize44dp)) + .isEqualTo(TaskbarIconSpecs.iconSize44dp) + } + + @Test + fun testGetIconSizeStepUp_whenTaskbarIsPersistent() { + doReturn(false).whenever(taskbarFeatureEvaluator).isTransient + assertThat(taskbarSpecsEvaluator.getIconSizeStepUp(TaskbarIconSpecs.iconSize40dp)) + .isEqualTo(TaskbarIconSpecs.iconSize40dp) + } + + @Test + fun testGetIconSizeStepUp_whenTaskbarIsTransientAndIconSizeAreInBound() { + doReturn(true).whenever(taskbarFeatureEvaluator).isTransient + assertThat(taskbarSpecsEvaluator.getIconSizeStepUp(TaskbarIconSpecs.iconSize44dp)) + .isEqualTo(TaskbarIconSpecs.iconSize48dp) + } + + @Test + fun testGetIconSizeStepUp_whenTaskbarIsTransientAndIconSizeAreOutOfBound() { + doReturn(true).whenever(taskbarFeatureEvaluator).isTransient + assertThat(taskbarSpecsEvaluator.getIconSizeStepUp(TaskbarIconSpecs.iconSize52dp)) + .isEqualTo(TaskbarIconSpecs.iconSize52dp) + } +} diff --git a/quickstep/tests/src/com/android/quickstep/util/GestureExclusionManagerTest.kt b/quickstep/tests/src/com/android/quickstep/util/GestureExclusionManagerTest.kt new file mode 100644 index 0000000000..c190cfebab --- /dev/null +++ b/quickstep/tests/src/com/android/quickstep/util/GestureExclusionManagerTest.kt @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.quickstep.util + +import android.graphics.Rect +import android.graphics.Region +import android.testing.AndroidTestingRunner +import android.view.Display.DEFAULT_DISPLAY +import android.view.IWindowManager +import androidx.test.filters.SmallTest +import com.android.launcher3.util.Executors +import com.android.quickstep.util.GestureExclusionManager.ExclusionListener +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.MockitoAnnotations +import org.mockito.kotlin.reset +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyZeroInteractions + +/** Unit test for [GestureExclusionManager]. */ +@SmallTest +@RunWith(AndroidTestingRunner::class) +class GestureExclusionManagerTest { + + @Mock private lateinit var windowManager: IWindowManager + + @Mock private lateinit var listener1: ExclusionListener + @Mock private lateinit var listener2: ExclusionListener + + private val r1 = Region().apply { union(Rect(0, 0, 100, 200)) } + private val r2 = Region().apply { union(Rect(200, 200, 500, 800)) } + + private lateinit var underTest: GestureExclusionManager + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + underTest = GestureExclusionManager(windowManager) + } + + @Test + fun addListener_registers() { + underTest.addListener(listener1) + + awaitTasksCompleted() + verify(windowManager) + .registerSystemGestureExclusionListener(underTest.exclusionListener, DEFAULT_DISPLAY) + } + + @Test + fun addListener_again_skips_register() { + underTest.addListener(listener1) + awaitTasksCompleted() + reset(windowManager) + + underTest.addListener(listener2) + + awaitTasksCompleted() + verifyZeroInteractions(windowManager) + } + + @Test + fun removeListener_unregisters() { + underTest.addListener(listener1) + awaitTasksCompleted() + reset(windowManager) + + underTest.removeListener(listener1) + + awaitTasksCompleted() + verify(windowManager) + .unregisterSystemGestureExclusionListener(underTest.exclusionListener, DEFAULT_DISPLAY) + } + + @Test + fun removeListener_again_skips_unregister() { + underTest.addListener(listener1) + underTest.addListener(listener2) + awaitTasksCompleted() + reset(windowManager) + + underTest.removeListener(listener1) + + awaitTasksCompleted() + verifyZeroInteractions(windowManager) + } + + @Test + fun onSystemGestureExclusionChanged_dispatches_to_listeners() { + underTest.addListener(listener1) + underTest.addListener(listener2) + awaitTasksCompleted() + + underTest.exclusionListener.onSystemGestureExclusionChanged(DEFAULT_DISPLAY, r1, r2) + awaitTasksCompleted() + verify(listener1).onGestureExclusionChanged(r1, r2) + verify(listener2).onGestureExclusionChanged(r1, r2) + } + + @Test + fun addLister_dispatches_second_time() { + underTest.exclusionListener.onSystemGestureExclusionChanged(DEFAULT_DISPLAY, r1, r2) + awaitTasksCompleted() + underTest.addListener(listener1) + awaitTasksCompleted() + verifyZeroInteractions(listener1) + + underTest.addListener(listener2) + awaitTasksCompleted() + + verifyZeroInteractions(listener1) + verify(listener2).onGestureExclusionChanged(r1, r2) + } + + private fun awaitTasksCompleted() { + Executors.UI_HELPER_EXECUTOR.submit { null }.get() + Executors.MAIN_EXECUTOR.submit { null }.get() + } +} diff --git a/res/anim-v33/shared_x_axis_activity_close_enter.xml b/res/anim-v33/shared_x_axis_activity_close_enter.xml new file mode 100644 index 0000000000..3d7ad2bd60 --- /dev/null +++ b/res/anim-v33/shared_x_axis_activity_close_enter.xml @@ -0,0 +1,42 @@ + + + + + + + + + + \ No newline at end of file diff --git a/res/anim-v33/shared_x_axis_activity_close_exit.xml b/res/anim-v33/shared_x_axis_activity_close_exit.xml new file mode 100644 index 0000000000..fb63602d4e --- /dev/null +++ b/res/anim-v33/shared_x_axis_activity_close_exit.xml @@ -0,0 +1,41 @@ + + + + + + + + + + \ No newline at end of file diff --git a/res/anim-v33/shared_x_axis_activity_open_enter.xml b/res/anim-v33/shared_x_axis_activity_open_enter.xml new file mode 100644 index 0000000000..cba74ba0ec --- /dev/null +++ b/res/anim-v33/shared_x_axis_activity_open_enter.xml @@ -0,0 +1,42 @@ + + + + + + + + + + \ No newline at end of file diff --git a/res/anim-v33/shared_x_axis_activity_open_exit.xml b/res/anim-v33/shared_x_axis_activity_open_exit.xml new file mode 100644 index 0000000000..22e878d7f1 --- /dev/null +++ b/res/anim-v33/shared_x_axis_activity_open_exit.xml @@ -0,0 +1,41 @@ + + + + + + + + + + \ No newline at end of file diff --git a/res/color-night-v31/material_color_surface.xml b/res/color-night-v31/material_color_surface.xml new file mode 100644 index 0000000000..a645f24dd9 --- /dev/null +++ b/res/color-night-v31/material_color_surface.xml @@ -0,0 +1,19 @@ + + + + + \ No newline at end of file diff --git a/res/color-night-v31/material_color_surface_bright.xml b/res/color-night-v31/material_color_surface_bright.xml new file mode 100644 index 0000000000..f34ed6c548 --- /dev/null +++ b/res/color-night-v31/material_color_surface_bright.xml @@ -0,0 +1,19 @@ + + + + + \ No newline at end of file diff --git a/res/color-night-v31/material_color_surface_container.xml b/res/color-night-v31/material_color_surface_container.xml new file mode 100644 index 0000000000..002b88eba4 --- /dev/null +++ b/res/color-night-v31/material_color_surface_container.xml @@ -0,0 +1,19 @@ + + + + + \ No newline at end of file diff --git a/res/color-night-v31/material_color_surface_container_high.xml b/res/color-night-v31/material_color_surface_container_high.xml new file mode 100644 index 0000000000..edd36fcd23 --- /dev/null +++ b/res/color-night-v31/material_color_surface_container_high.xml @@ -0,0 +1,19 @@ + + + + + \ No newline at end of file diff --git a/res/color-night-v31/material_color_surface_container_highest.xml b/res/color-night-v31/material_color_surface_container_highest.xml new file mode 100644 index 0000000000..e54f95380e --- /dev/null +++ b/res/color-night-v31/material_color_surface_container_highest.xml @@ -0,0 +1,19 @@ + + + + + \ No newline at end of file diff --git a/res/color-night-v31/material_color_surface_container_low.xml b/res/color-night-v31/material_color_surface_container_low.xml new file mode 100644 index 0000000000..40f0d4c9de --- /dev/null +++ b/res/color-night-v31/material_color_surface_container_low.xml @@ -0,0 +1,19 @@ + + + + + \ No newline at end of file diff --git a/res/color-night-v31/material_color_surface_container_lowest.xml b/res/color-night-v31/material_color_surface_container_lowest.xml index 4396f6d013..24f559b494 100644 --- a/res/color-night-v31/material_color_surface_container_lowest.xml +++ b/res/color-night-v31/material_color_surface_container_lowest.xml @@ -1,5 +1,6 @@ - - \ No newline at end of file diff --git a/res/color-night-v31/material_color_surface_dim.xml b/res/color-night-v31/material_color_surface_dim.xml new file mode 100644 index 0000000000..a645f24dd9 --- /dev/null +++ b/res/color-night-v31/material_color_surface_dim.xml @@ -0,0 +1,19 @@ + + + + + \ No newline at end of file diff --git a/res/color-night-v31/material_color_surface_inverse.xml b/res/color-night-v31/material_color_surface_inverse.xml new file mode 100644 index 0000000000..ac63072a9e --- /dev/null +++ b/res/color-night-v31/material_color_surface_inverse.xml @@ -0,0 +1,19 @@ + + + + + \ No newline at end of file diff --git a/res/color-night-v31/material_color_surface_variant.xml b/res/color-night-v31/material_color_surface_variant.xml new file mode 100644 index 0000000000..a645f24dd9 --- /dev/null +++ b/res/color-night-v31/material_color_surface_variant.xml @@ -0,0 +1,19 @@ + + + + + \ No newline at end of file diff --git a/res/color-night-v31/popup_color_background.xml b/res/color-night-v31/popup_color_background.xml new file mode 100644 index 0000000000..13ceaa09e7 --- /dev/null +++ b/res/color-night-v31/popup_color_background.xml @@ -0,0 +1,20 @@ + + + + + diff --git a/res/color-night-v31/popup_shade_first.xml b/res/color-night-v31/popup_shade_first.xml index e62ed9c8a9..6909f81587 100644 --- a/res/color-night-v31/popup_shade_first.xml +++ b/res/color-night-v31/popup_shade_first.xml @@ -12,6 +12,7 @@ See the License for the specific language governing permissions and limitations under the License. --> - - + + diff --git a/res/color-v31/material_color_surface.xml b/res/color-v31/material_color_surface.xml new file mode 100644 index 0000000000..b049851ff4 --- /dev/null +++ b/res/color-v31/material_color_surface.xml @@ -0,0 +1,19 @@ + + + + + \ No newline at end of file diff --git a/res/color-v31/material_color_surface_bright.xml b/res/color-v31/material_color_surface_bright.xml new file mode 100644 index 0000000000..b049851ff4 --- /dev/null +++ b/res/color-v31/material_color_surface_bright.xml @@ -0,0 +1,19 @@ + + + + + \ No newline at end of file diff --git a/res/color-v31/material_color_surface_container.xml b/res/color-v31/material_color_surface_container.xml new file mode 100644 index 0000000000..b031c081a9 --- /dev/null +++ b/res/color-v31/material_color_surface_container.xml @@ -0,0 +1,19 @@ + + + + + \ No newline at end of file diff --git a/res/color-v31/material_color_surface_container_high.xml b/res/color-v31/material_color_surface_container_high.xml new file mode 100644 index 0000000000..a996d51eba --- /dev/null +++ b/res/color-v31/material_color_surface_container_high.xml @@ -0,0 +1,19 @@ + + + + + \ No newline at end of file diff --git a/res/color-v31/material_color_surface_container_highest.xml b/res/color-v31/material_color_surface_container_highest.xml new file mode 100644 index 0000000000..e7a535af53 --- /dev/null +++ b/res/color-v31/material_color_surface_container_highest.xml @@ -0,0 +1,19 @@ + + + + + \ No newline at end of file diff --git a/res/color-v31/material_color_surface_container_low.xml b/res/color-v31/material_color_surface_container_low.xml new file mode 100644 index 0000000000..b8fe01e484 --- /dev/null +++ b/res/color-v31/material_color_surface_container_low.xml @@ -0,0 +1,19 @@ + + + + + \ No newline at end of file diff --git a/res/color-v31/material_color_surface_container_lowest.xml b/res/color-v31/material_color_surface_container_lowest.xml index f726aea081..25e8666862 100644 --- a/res/color-v31/material_color_surface_container_lowest.xml +++ b/res/color-v31/material_color_surface_container_lowest.xml @@ -1,5 +1,6 @@ - - \ No newline at end of file diff --git a/res/color-v31/material_color_surface_dim.xml b/res/color-v31/material_color_surface_dim.xml new file mode 100644 index 0000000000..e2d226fa89 --- /dev/null +++ b/res/color-v31/material_color_surface_dim.xml @@ -0,0 +1,19 @@ + + + + + \ No newline at end of file diff --git a/res/color-v31/material_color_surface_inverse.xml b/res/color-v31/material_color_surface_inverse.xml new file mode 100644 index 0000000000..e189862856 --- /dev/null +++ b/res/color-v31/material_color_surface_inverse.xml @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file diff --git a/res/color-v31/material_color_surface_variant.xml b/res/color-v31/material_color_surface_variant.xml new file mode 100644 index 0000000000..e2d226fa89 --- /dev/null +++ b/res/color-v31/material_color_surface_variant.xml @@ -0,0 +1,19 @@ + + + + + \ No newline at end of file diff --git a/res/color-v31/popup_color_background.xml b/res/color-v31/popup_color_background.xml new file mode 100644 index 0000000000..99155d8972 --- /dev/null +++ b/res/color-v31/popup_color_background.xml @@ -0,0 +1,20 @@ + + + + + diff --git a/res/color-v31/popup_shade_first.xml b/res/color-v31/popup_shade_first.xml index 9a71caeb51..4b50cba3c2 100644 --- a/res/color-v31/popup_shade_first.xml +++ b/res/color-v31/popup_shade_first.xml @@ -13,6 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. --> - - + + diff --git a/res/color/overview_button.xml b/res/color/overview_button.xml index aa6c618ef7..3cca9c91b7 100644 --- a/res/color/overview_button.xml +++ b/res/color/overview_button.xml @@ -1,11 +1,12 @@ - + \ No newline at end of file diff --git a/res/color/popup_color_background.xml b/res/color/popup_color_background.xml new file mode 100644 index 0000000000..e87e77231c --- /dev/null +++ b/res/color/popup_color_background.xml @@ -0,0 +1,18 @@ + + + + + diff --git a/res/color/popup_shade_first.xml b/res/color/popup_shade_first.xml index 9a71caeb51..4b50cba3c2 100644 --- a/res/color/popup_shade_first.xml +++ b/res/color/popup_shade_first.xml @@ -13,6 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. --> - - + + diff --git a/res/drawable-sw720dp/ic_transient_taskbar_all_apps_button.xml b/res/drawable-sw720dp/ic_transient_taskbar_all_apps_button.xml new file mode 100644 index 0000000000..47f2a5d73a --- /dev/null +++ b/res/drawable-sw720dp/ic_transient_taskbar_all_apps_button.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + diff --git a/res/drawable/add_item_dialog_background.xml b/res/drawable/add_item_dialog_background.xml index 39af989a1d..e279fa051a 100644 --- a/res/drawable/add_item_dialog_background.xml +++ b/res/drawable/add_item_dialog_background.xml @@ -1,7 +1,7 @@ - + diff --git a/res/drawable/all_apps_tabs_background.xml b/res/drawable/all_apps_tabs_background.xml index d200b9f961..7b27273f28 100644 --- a/res/drawable/all_apps_tabs_background.xml +++ b/res/drawable/all_apps_tabs_background.xml @@ -13,25 +13,36 @@ See the License for the specific language governing permissions and limitations under the License. --> - - - - - - - - - - - + + + + + + + - - - - - - + + + + + + + + - + + + + + + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/res/drawable/bg_ps_header.xml b/res/drawable/bg_ps_header.xml index 87a21e485f..da314452ad 100644 --- a/res/drawable/bg_ps_header.xml +++ b/res/drawable/bg_ps_header.xml @@ -20,7 +20,7 @@ - + diff --git a/res/drawable/bg_ps_lock_button.xml b/res/drawable/bg_ps_lock_button.xml index 7a20e49b22..aef1e816ef 100644 --- a/res/drawable/bg_ps_lock_button.xml +++ b/res/drawable/bg_ps_lock_button.xml @@ -21,12 +21,12 @@ android:viewportHeight="36"> + android:fillColor="?attr/materialColorPrimaryFixedDim"/> + android:fillColor="?attr/materialColorOnPrimaryFixed"/> \ No newline at end of file diff --git a/res/drawable/bg_ps_transition_image.xml b/res/drawable/bg_ps_transition_image.xml index 694303c517..dfad3cf5b3 100644 --- a/res/drawable/bg_ps_transition_image.xml +++ b/res/drawable/bg_ps_transition_image.xml @@ -23,13 +23,13 @@ + android:fillColor="?attr/materialColorOnPrimaryFixed"/> + android:fillColor="?attr/materialColorPrimaryFixedDim"/> + android:fillColor="?attr/materialColorOnPrimaryFixed"/> \ No newline at end of file diff --git a/res/drawable/bg_ps_unlock_button.xml b/res/drawable/bg_ps_unlock_button.xml index 563c3f6d68..d5eedd293e 100644 --- a/res/drawable/bg_ps_unlock_button.xml +++ b/res/drawable/bg_ps_unlock_button.xml @@ -21,9 +21,9 @@ android:viewportHeight="36"> + android:fillColor="?attr/materialColorPrimaryFixedDim"/> \ No newline at end of file diff --git a/res/drawable/bg_rounded_corner_bottom_sheet_handle.xml b/res/drawable/bg_rounded_corner_bottom_sheet_handle.xml index b0bd33bb51..ca9448964b 100644 --- a/res/drawable/bg_rounded_corner_bottom_sheet_handle.xml +++ b/res/drawable/bg_rounded_corner_bottom_sheet_handle.xml @@ -15,7 +15,8 @@ --> - + diff --git a/res/drawable/bg_widgets_header_states_two_pane.xml b/res/drawable/bg_widgets_header_states_two_pane.xml index 1ec41a9a0a..5f4b8c66b4 100644 --- a/res/drawable/bg_widgets_header_states_two_pane.xml +++ b/res/drawable/bg_widgets_header_states_two_pane.xml @@ -14,16 +14,18 @@ limitations under the License. --> - - - - + + + + + + - - - - + + + + + + - - diff --git a/res/drawable/bg_widgets_header_two_pane.xml b/res/drawable/bg_widgets_header_two_pane.xml index e237002898..ca3feef1f0 100644 --- a/res/drawable/bg_widgets_header_two_pane.xml +++ b/res/drawable/bg_widgets_header_two_pane.xml @@ -14,10 +14,13 @@ limitations under the License. --> - + android:insetTop="@dimen/widget_list_entry_spacing" > + + - - \ No newline at end of file + + diff --git a/res/drawable/button_top_rounded_bordered_ripple.xml b/res/drawable/button_top_rounded_bordered_ripple.xml index 723668fb39..f5b68866cb 100644 --- a/res/drawable/button_top_rounded_bordered_ripple.xml +++ b/res/drawable/button_top_rounded_bordered_ripple.xml @@ -25,7 +25,7 @@ android:topRightRadius="12dp" android:bottomLeftRadius="4dp" android:bottomRightRadius="4dp" /> - + diff --git a/res/drawable/ic_corp_off.xml b/res/drawable/ic_corp_off.xml index e58e1729cf..117258e3bd 100644 --- a/res/drawable/ic_corp_off.xml +++ b/res/drawable/ic_corp_off.xml @@ -16,9 +16,9 @@ android:width="24dp" android:height="24dp" android:viewportWidth="24" - android:viewportHeight="24"> + android:viewportHeight="24" + android:tint="?android:attr/textColorHint"> - + android:fillColor="@android:color/white" + android:pathData="M20,6h-4L16,4c0,-1.11 -0.89,-2 -2,-2h-4c-1.11,0 -2,0.89 -2,2v1.17L10.83,8L20,8v9.17l1.98,1.98c0,-0.05 0.02,-0.1 0.02,-0.16L22,8c0,-1.11 -0.89,-2 -2,-2zM14,6h-4L10,4h4v2zM19,19L8,8 6,6 2.81,2.81 1.39,4.22 3.3,6.13C2.54,6.41 2.01,7.14 2.01,8L2,19c0,1.11 0.89,2 2,2h14.17l1.61,1.61 1.41,-1.41 -0.37,-0.37L19,19zM4,19L4,8h1.17l11,11L4,19z" /> + \ No newline at end of file diff --git a/res/drawable/ic_info_no_shadow.xml b/res/drawable/ic_info_no_shadow.xml index 31cf51200a..29a81bd27c 100644 --- a/res/drawable/ic_info_no_shadow.xml +++ b/res/drawable/ic_info_no_shadow.xml @@ -18,7 +18,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24" - android:tint="@color/materialColorOnSurface"> + android:tint="?attr/materialColorOnSurface"> + android:tint="?attr/materialColorOnSurface"> + android:tint="?attr/materialColorOnSurface"> diff --git a/res/drawable/ic_private_space_with_background.xml b/res/drawable/ic_private_space_with_background.xml index d66549d90d..fe85168d3b 100644 --- a/res/drawable/ic_private_space_with_background.xml +++ b/res/drawable/ic_private_space_with_background.xml @@ -13,19 +13,20 @@ limitations under the License. --> + android:fillColor="?attr/materialColorSurfaceContainerLowest" /> + android:fillColor="?attr/materialColorOnSurface" /> + android:fillColor="?attr/materialColorOnSurface" /> diff --git a/res/drawable/ic_ps_settings.xml b/res/drawable/ic_ps_settings.xml index 5453f35776..47edeb85ef 100644 --- a/res/drawable/ic_ps_settings.xml +++ b/res/drawable/ic_ps_settings.xml @@ -24,9 +24,9 @@ android:pathData="M10,10h20v20h-20z"/> + android:fillColor="?attr/materialColorOnSurfaceVariant"/> + android:fillColor="?attr/materialColorOnSurfaceVariant"/> \ No newline at end of file diff --git a/res/drawable/ic_smartspace.xml b/res/drawable/ic_smartspace.xml index 3c18c866ae..d5a719afb0 100644 --- a/res/drawable/ic_smartspace.xml +++ b/res/drawable/ic_smartspace.xml @@ -1,5 +1,17 @@ - - - - + + + diff --git a/res/drawable/ic_split_horizontal.xml b/res/drawable/ic_split_horizontal.xml index 26efedc4d9..2efd2b9bc2 100644 --- a/res/drawable/ic_split_horizontal.xml +++ b/res/drawable/ic_split_horizontal.xml @@ -1,25 +1,9 @@ - - + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + diff --git a/res/drawable/ic_split_vertical.xml b/res/drawable/ic_split_vertical.xml index 787953a52f..9bc97851ab 100644 --- a/res/drawable/ic_split_vertical.xml +++ b/res/drawable/ic_split_vertical.xml @@ -1,25 +1,9 @@ - - + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + diff --git a/res/drawable/ic_taskbar_all_apps_button.xml b/res/drawable/ic_taskbar_all_apps_button.xml new file mode 100644 index 0000000000..82fbbea617 --- /dev/null +++ b/res/drawable/ic_taskbar_all_apps_button.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + diff --git a/res/drawable/ic_transient_taskbar_all_apps_button.xml b/res/drawable/ic_transient_taskbar_all_apps_button.xml new file mode 100644 index 0000000000..6e740aed4f --- /dev/null +++ b/res/drawable/ic_transient_taskbar_all_apps_button.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + diff --git a/res/drawable/ic_uninstall_no_shadow.xml b/res/drawable/ic_uninstall_no_shadow.xml index 829e590780..6200054f1b 100644 --- a/res/drawable/ic_uninstall_no_shadow.xml +++ b/res/drawable/ic_uninstall_no_shadow.xml @@ -18,7 +18,7 @@ android:height="20dp" android:viewportWidth="24.0" android:viewportHeight="24.0" - android:tint="@color/materialColorOnSurface" > + android:tint="?attr/materialColorOnSurface" > diff --git a/res/drawable/ic_widget.xml b/res/drawable/ic_widget.xml index 4f4358d073..460fe94f6f 100644 --- a/res/drawable/ic_widget.xml +++ b/res/drawable/ic_widget.xml @@ -1,10 +1,26 @@ + - + android:width="@dimen/options_menu_icon_size" + android:height="@dimen/options_menu_icon_size" + android:viewportWidth="24.0" + android:viewportHeight="24.0" + android:tint="?android:attr/textColorPrimary"> + diff --git a/res/drawable/icon_menu_arrow_background.xml b/res/drawable/icon_menu_arrow_background.xml index 1de111ab29..8af3c00fad 100644 --- a/res/drawable/icon_menu_arrow_background.xml +++ b/res/drawable/icon_menu_arrow_background.xml @@ -15,13 +15,14 @@ limitations under the License. --> + android:centerColor="?attr/materialColorSurfaceBright" + android:endColor="?attr/materialColorSurfaceBright" /> \ No newline at end of file diff --git a/res/drawable/popup_background.xml b/res/drawable/popup_background.xml index 686456f7ab..6eedecb6ca 100644 --- a/res/drawable/popup_background.xml +++ b/res/drawable/popup_background.xml @@ -15,6 +15,6 @@ --> - + \ No newline at end of file diff --git a/res/drawable/private_space_app_divider.xml b/res/drawable/private_space_app_divider.xml index f92dca73d5..1ea12b3328 100644 --- a/res/drawable/private_space_app_divider.xml +++ b/res/drawable/private_space_app_divider.xml @@ -16,6 +16,6 @@ - + \ No newline at end of file diff --git a/res/drawable/private_space_install_app_icon.xml b/res/drawable/private_space_install_app_icon.xml index 1e7fe43527..cfec2b126d 100644 --- a/res/drawable/private_space_install_app_icon.xml +++ b/res/drawable/private_space_install_app_icon.xml @@ -13,7 +13,19 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - - - - + + + + + + + diff --git a/res/drawable/ps_lock_background.xml b/res/drawable/ps_lock_background.xml index bc66595ac3..0be83dbd92 100644 --- a/res/drawable/ps_lock_background.xml +++ b/res/drawable/ps_lock_background.xml @@ -21,7 +21,7 @@ - + diff --git a/res/drawable/ps_settings_background.xml b/res/drawable/ps_settings_background.xml index 7746012d40..b0c6b5b0d1 100644 --- a/res/drawable/ps_settings_background.xml +++ b/res/drawable/ps_settings_background.xml @@ -18,6 +18,6 @@ android:inset="4dp"> - + \ No newline at end of file diff --git a/res/drawable/rounded_action_button.xml b/res/drawable/rounded_action_button.xml index 6ee6d65d1f..9deab6eb57 100644 --- a/res/drawable/rounded_action_button.xml +++ b/res/drawable/rounded_action_button.xml @@ -7,7 +7,7 @@ ~ ~ http://www.apache.org/licenses/LICENSE-2.0 ~ - ~ Unless required by applicable law or agreed to in writing, soft]ware + ~ Unless required by applicable law or agreed to in writing, software ~ distributed under the License is distributed on an "AS IS" BASIS, ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ~ See the License for the specific language governing permissions and @@ -16,11 +16,15 @@ - + + android:color="?attr/colorSurfaceVariant" /> + diff --git a/res/drawable/widget_picker_tabs_background.xml b/res/drawable/widget_picker_tabs_background.xml index f6607b7ad1..a874dd8b90 100644 --- a/res/drawable/widget_picker_tabs_background.xml +++ b/res/drawable/widget_picker_tabs_background.xml @@ -13,39 +13,36 @@ See the License for the specific language governing permissions and limitations under the License. --> - - + - - - - - - + + + + + + - - - - - - - - + + + + + + + + - - - - - - - - - - \ No newline at end of file + + + + + + + + + + \ No newline at end of file diff --git a/res/drawable/work_card.xml b/res/drawable/work_card.xml index 0e37d4f7b9..1437c6f7d7 100644 --- a/res/drawable/work_card.xml +++ b/res/drawable/work_card.xml @@ -17,7 +17,6 @@ - + - diff --git a/res/drawable/work_mode_fab_background.xml b/res/drawable/work_mode_fab_background.xml index ad795eb478..6be33e86ce 100644 --- a/res/drawable/work_mode_fab_background.xml +++ b/res/drawable/work_mode_fab_background.xml @@ -18,10 +18,7 @@ - - + diff --git a/res/layout/add_item_confirmation_activity.xml b/res/layout/add_item_confirmation_activity.xml index 2bb2eb3097..d113a38ac8 100644 --- a/res/layout/add_item_confirmation_activity.xml +++ b/res/layout/add_item_confirmation_activity.xml @@ -71,8 +71,7 @@ android:id="@+id/widget_preview_scroll_view" android:layout_width="match_parent" android:layout_height="0dp" - android:layout_margin="16dp" - android:background="@drawable/widgets_surface_background" + android:layout_marginVertical="16dp" android:layout_weight="1"> - \ No newline at end of file diff --git a/res/layout/all_apps_personal_work_tabs.xml b/res/layout/all_apps_personal_work_tabs.xml index 1435f8203d..e04b207a3e 100644 --- a/res/layout/all_apps_personal_work_tabs.xml +++ b/res/layout/all_apps_personal_work_tabs.xml @@ -21,6 +21,8 @@ android:layout_width="match_parent" android:layout_height="@dimen/all_apps_header_pill_height" android:layout_gravity="center_horizontal" + android:paddingTop="@dimen/all_apps_tabs_vertical_padding" + android:paddingBottom="@dimen/all_apps_tabs_vertical_padding" android:layout_marginTop="@dimen/all_apps_tabs_margin_top" android:orientation="horizontal" style="@style/TextHeadline" @@ -34,7 +36,6 @@ android:layout_weight="1" android:background="@drawable/all_apps_tabs_background" android:text="@string/all_apps_personal_tab" - android:contentDescription="@string/all_apps_personal_tab_content_description" android:textColor="@color/all_apps_tab_text" android:textSize="14sp" style="?android:attr/borderlessButtonStyle" /> @@ -47,7 +48,6 @@ android:layout_weight="1" android:background="@drawable/all_apps_tabs_background" android:text="@string/all_apps_work_tab" - android:contentDescription="@string/all_apps_work_tab_content_description" android:textColor="@color/all_apps_tab_text" android:textSize="14sp" style="?android:attr/borderlessButtonStyle" /> diff --git a/res/layout/launcher.xml b/res/layout/launcher.xml index 0b3385238b..fe06f455c2 100644 --- a/res/layout/launcher.xml +++ b/res/layout/launcher.xml @@ -29,7 +29,6 @@ android:importantForAccessibility="no"> - + + + \ No newline at end of file diff --git a/res/layout/private_space_header.xml b/res/layout/private_space_header.xml index a92391448e..128d87e7d7 100644 --- a/res/layout/private_space_header.xml +++ b/res/layout/private_space_header.xml @@ -43,7 +43,6 @@ android:layout_height="@dimen/ps_header_image_height" android:background="@drawable/ps_settings_background" android:src="@drawable/ic_ps_settings" - android:visibility="gone" android:contentDescription="@string/ps_container_settings" /> @@ -100,8 +97,7 @@ android:gravity="center_vertical" android:layout_marginStart="@dimen/ps_header_layout_margin" android:text="@string/ps_container_title" - android:maxLines="1" android:theme="@style/PrivateSpaceHeaderTextStyle" android:importantForAccessibility="no"/> - + \ No newline at end of file diff --git a/res/layout/user_folder_icon_normalized.xml b/res/layout/user_folder_icon_normalized.xml index 002e7b7eec..43a8aac8b2 100644 --- a/res/layout/user_folder_icon_normalized.xml +++ b/res/layout/user_folder_icon_normalized.xml @@ -40,11 +40,12 @@ - + android:layout_height="match_parent" + android:focusable="true" + android:importantForAccessibility="no"> - - + + \ No newline at end of file diff --git a/res/layout/widgets_full_sheet_paged_view.xml b/res/layout/widgets_full_sheet_paged_view.xml index a7c4fbe7e0..a292d67cc2 100644 --- a/res/layout/widgets_full_sheet_paged_view.xml +++ b/res/layout/widgets_full_sheet_paged_view.xml @@ -81,7 +81,6 @@ android:layout_marginTop="8dp" android:layout_marginBottom="8dp" android:background="@drawable/widgets_surface_background" - android:clipToOutline="true" android:orientation="vertical" android:layout_marginHorizontal="@dimen/widget_list_horizontal_margin" android:visibility="gone"> @@ -104,6 +103,7 @@ android:layout_width="0dp" android:layout_height="match_parent" android:layout_marginEnd="@dimen/widget_tabs_button_horizontal_padding" + android:layout_marginVertical="@dimen/widget_apps_tabs_vertical_padding" android:layout_weight="1" android:background="@drawable/widget_picker_tabs_background" android:text="@string/widgets_full_sheet_personal_tab" @@ -116,6 +116,7 @@ android:layout_width="0dp" android:layout_height="match_parent" android:layout_marginEnd="@dimen/widget_tabs_button_horizontal_padding" + android:layout_marginVertical="@dimen/widget_apps_tabs_vertical_padding" android:layout_weight="1" android:background="@drawable/widget_picker_tabs_background" android:text="@string/widgets_full_sheet_work_tab" diff --git a/res/layout/widgets_full_sheet_recyclerview.xml b/res/layout/widgets_full_sheet_recyclerview.xml index 1ce1c55ec4..5427732c4d 100644 --- a/res/layout/widgets_full_sheet_recyclerview.xml +++ b/res/layout/widgets_full_sheet_recyclerview.xml @@ -64,7 +64,6 @@ android:layout_marginTop="8dp" android:layout_marginBottom="8dp" android:background="@drawable/widgets_surface_background" - android:clipToOutline="true" android:orientation="vertical" android:visibility="gone"> diff --git a/res/layout/widgets_list_row_header.xml b/res/layout/widgets_list_row_header.xml index 2a7d22cff3..98f9dac57b 100644 --- a/res/layout/widgets_list_row_header.xml +++ b/res/layout/widgets_list_row_header.xml @@ -45,19 +45,25 @@ android:id="@+id/app_title" android:layout_width="wrap_content" android:layout_height="wrap_content" - style="@style/WidgetListHeader.Title" android:layout_gravity="start|center_vertical" android:ellipsize="end" android:maxLines="1" + android:textColor="?attr/widgetPickerHeaderAppTitleColor" + android:textSize="@dimen/widget_picker_header_app_title_font_size" + android:textFontWeight="@integer/widget_picker_header_app_title_font_weight" + android:lineHeight="@dimen/widget_picker_header_app_title_line_height" tools:text="App name" /> diff --git a/res/layout/widgets_list_row_header_two_pane.xml b/res/layout/widgets_list_row_header_two_pane.xml index 4827ed4e69..d4baf0aed3 100644 --- a/res/layout/widgets_list_row_header_two_pane.xml +++ b/res/layout/widgets_list_row_header_two_pane.xml @@ -47,10 +47,13 @@ android:id="@+id/app_title" android:layout_width="wrap_content" android:layout_height="wrap_content" - style="@style/WidgetListHeader.Title" android:layout_gravity="start|center_vertical" android:ellipsize="end" android:maxLines="1" + android:textColor="?attr/widgetPickerHeaderAppTitleColor" + android:textSize="@dimen/widget_picker_header_app_title_font_size" + android:textFontWeight="@integer/widget_picker_header_app_title_font_weight" + android:lineHeight="@dimen/widget_picker_header_app_title_line_height" android:duplicateParentState="true" tools:text="App name" /> @@ -58,9 +61,12 @@ android:id="@+id/app_subtitle" android:layout_width="wrap_content" android:layout_height="wrap_content" - style="@style/WidgetListHeader.SubTitle" android:ellipsize="end" android:maxLines="1" + android:textColor="?attr/widgetPickerHeaderAppSubtitleColor" + android:textSize="@dimen/widget_picker_header_app_subtitle_font_size" + android:textFontWeight="@integer/widget_picker_header_app_subtitle_font_weight" + android:lineHeight="@dimen/widget_picker_header_app_subtitle_line_height" android:duplicateParentState="true" tools:text="m widgets, n shortcuts" /> diff --git a/res/layout/widgets_two_pane_sheet.xml b/res/layout/widgets_two_pane_sheet.xml index cf090ad92e..bb2b7bd744 100644 --- a/res/layout/widgets_two_pane_sheet.xml +++ b/res/layout/widgets_two_pane_sheet.xml @@ -23,7 +23,9 @@ + android:layout_height="match_parent" + android:focusable="true" + android:importantForAccessibility="no"> + tools:text="@string/no_widgets_available" /> + android:layout_below="@id/title"> + + - - diff --git a/res/layout/widgets_two_pane_sheet_paged_view.xml b/res/layout/widgets_two_pane_sheet_paged_view.xml index 0528d3bddc..887efb809c 100644 --- a/res/layout/widgets_two_pane_sheet_paged_view.xml +++ b/res/layout/widgets_two_pane_sheet_paged_view.xml @@ -15,67 +15,64 @@ - + android:layout_alignParentStart="true"> + + + + + + + + - - - - - - - - - + + @@ -97,6 +94,7 @@ android:layout_width="0dp" android:layout_height="match_parent" android:layout_marginEnd="@dimen/widget_tabs_button_horizontal_padding" + android:layout_marginVertical="@dimen/widget_apps_tabs_vertical_padding" android:layout_weight="1" android:background="@drawable/widget_picker_tabs_background" android:text="@string/widgets_full_sheet_personal_tab" @@ -109,6 +107,7 @@ android:layout_width="0dp" android:layout_height="match_parent" android:layout_marginEnd="@dimen/widget_tabs_button_horizontal_padding" + android:layout_marginVertical="@dimen/widget_apps_tabs_vertical_padding" android:layout_weight="1" android:background="@drawable/widget_picker_tabs_background" android:text="@string/widgets_full_sheet_work_tab" @@ -117,39 +116,6 @@ style="?android:attr/borderlessButtonStyle" /> - - - - - - - - - - - - + + diff --git a/res/layout/widgets_two_pane_sheet_recyclerview.xml b/res/layout/widgets_two_pane_sheet_recyclerview.xml index 45a9ac0197..f3d3b16aa0 100644 --- a/res/layout/widgets_two_pane_sheet_recyclerview.xml +++ b/res/layout/widgets_two_pane_sheet_recyclerview.xml @@ -15,58 +15,43 @@ - + android:layout_alignParentStart="true"> + + + - - - - - - - - + + - - - - - - + + \ No newline at end of file diff --git a/res/layout/work_apps_edu.xml b/res/layout/work_apps_edu.xml index a19d13a264..99db8c6f41 100644 --- a/res/layout/work_apps_edu.xml +++ b/res/layout/work_apps_edu.xml @@ -25,8 +25,9 @@ android:orientation="horizontal" android:background="@drawable/work_card" android:layout_gravity="center_horizontal" + android:paddingEnd="@dimen/work_card_margin" android:paddingStart="@dimen/work_card_margin" - android:paddingEnd="@dimen/work_card_margin_end" + android:paddingTop="@dimen/work_card_margin" android:paddingBottom="@dimen/work_card_margin" android:id="@+id/wrapper"> + android:background="@drawable/rounded_action_button" + android:padding="@dimen/rounded_button_padding"> + android:src="@drawable/ic_remove_no_shadow" /> diff --git a/res/layout/work_mode_fab.xml b/res/layout/work_mode_fab.xml index d6d83e4405..b3484c9410 100644 --- a/res/layout/work_mode_fab.xml +++ b/res/layout/work_mode_fab.xml @@ -12,38 +12,42 @@ See the License for the specific language governing permissions and limitations under the License. --> - + android:contentDescription="@string/work_apps_pause_btn_text" + android:paddingStart="@dimen/work_mode_fab_background_start_padding" + android:paddingEnd="@dimen/work_mode_fab_background_end_padding" + android:animateLayoutChanges="true"> - + diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml index a5ad05c455..1a8f8e26af 100644 --- a/res/values-af/strings.xml +++ b/res/values-af/strings.xml @@ -21,19 +21,16 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Launcher3" "Werk" - "App is nie geïnstalleer nie." - "App is nie beskikbaar nie" - "Afgelaaide app in veiligmodus gedeaktiveer" + "Program is nie geïnstalleer nie." + "Program is nie beskikbaar nie" + "Afgelaaide program in veiligmodus gedeaktiveer" "Legstukke gedeaktiveer in Veiligmodus" "Kortpad is nie beskikbaar nie" "Tuis" "Stel %1$s as verstektuisskermapp in Instellings" "Verdeelde skerm" - "Verander aspekverhouding" "Programinligting vir %1$s" "Gebruikinstellings vir %1$s" - "Nuwe venster" - "Bestuur vensters" "Stoor apppaar" "%1$s | %2$s" "Hierdie apppaar word nie op hierdie toestel gesteun nie" @@ -41,8 +38,6 @@ "Apppaar is nie beskikbaar nie" "Raak en hou om \'n legstuk te skuif." "Dubbeltik en hou om \'n legstuk te skuif of gebruik gepasmaakte handelinge." - "Meer opsies" - "Wys alle legstukke" "%1$d × %2$d" "%1$d breed by %2$d hoog" "%1$s-legstuk" @@ -69,21 +64,15 @@ "Werk" "Gesprekke" "Neem notas" - "Wys Voeg By-knoppie" - "Versteek Voeg By-knoppie" "Voeg by" "Voeg %1$s-legstuk by" - "Wys almal" - "Wys alle legstukke" - "Wys tans alle legstukke" "Tik om legstukinstellings te verander" "Verander legstukinstellings" "Deursoek programme" "Laai tans programme …" "Kon geen programme kry wat by \"%1$s\" pas nie" - "App" + "Program" "Alle apps" - "Applys" "Kennisgewings" "Raak en hou om \'n kortpad te skuif." "Dubbeltik en hou om \'n kortpad te skuif of gebruik gepasmaakte handelinge." @@ -101,13 +90,12 @@ "Installeer" "Moenie voorstel nie" "Vasspeldvoorspelling" - "Borrel" "installeer kortpaaie" - "Laat \'n app toe om kortpaaie by te voeg sonder gebruikerinmenging." + "Laat \'n program toe om kortpaaie by te voeg sonder gebruikerinmenging." "lees tuis-instellings en -kortpaaie" - "Laat die app toe om die instellings en kortpaaie op tuisskerm te lees." + "Laat die program toe om die instellings en kortpaaie op tuisskerm te lees." "skryf tuis-instellings en -kortpaaie" - "Laat die app toe om die instellings en kortpaaie op tuisskerm te verander." + "Laat die program toe om die instellings en kortpaaie op tuisskerm te verander." "Kan nie legstuk laai nie" "Legstukinstellings" "Tik om opstelling te voltooi" @@ -118,8 +106,6 @@ "Bladsy %1$d van %2$d" "Tuisskerm %1$d van %2$d" "Nuwe tuisskermbladsy" - "Aktief" - "Geminimeer" "Vouer oopgemaak, %1$d by %2$d" "Tik om die vouer toe te maak" "Tik om nuwe naam te stoor" @@ -127,7 +113,6 @@ "Vouer hernoem na %1$s" "Vouer: %1$s, %2$d items" "Vouer: %1$s, %2$d of meer items" - "Naamlose vouer" "Apppaar: %1$s en %2$s" "Muurpapier en styl" "Wysig tuisskerm" @@ -135,8 +120,6 @@ "Gedeaktiveer deur jou administrateur" "Laat toe dat tuisskerm gedraai word" "Wanneer foon gedraai word" - "Landskapmodus" - "Stel foon op landskapmodus" "Kennisgewingkolle" "Aan" "Af" @@ -150,21 +133,21 @@ "Onbekend" "Verwyder" "Soek" - "Hierdie app is nie geïnstalleer nie" - "Die app vir hierdie ikoon is nie geïnstalleer nie. Jy kan dit verwyder of die app soek en dit self installeer." + "Hierdie program is nie geïnstalleer nie" + "Die program vir hierdie ikoon is nie geïnstalleer nie. Jy kan dit verwyder of die program soek en dit self installeer." "%1$s installeer tans; %2$s voltooi" "%1$s laai tans af, %2$s voltooid" "%1$s wag tans om te installeer" - "%1$s is geargiveer." - "laai af en stel terug" + "%1$s is geargiveer. Tik om af te laai en terug te stel." "Programopdatering word vereis" - "Die app vir hierdie ikoon is nie opgedateer nie. Jy kan dit handmatig opdateer om hierdie kortpad weer te aktiveer, of die ikoon verwyder." + "Die program vir hierdie ikoon is nie opgedateer nie. Jy kan dit handmatig opdateer om hierdie kortpad weer te aktiveer, of die ikoon verwyder." "Dateer op" "Verwyder" "Legstukkelys" "Legstukkelys is toegemaak" "Voeg by tuisskerm" "Skuif item hierheen" + "Item is by tuisskerm gevoeg" "Item is verwyder" "Ontdoen" "Skuif item" @@ -184,15 +167,11 @@ "Verminder breedte" "Verminder hoogte" "Legstukgrootte is verander na breedte %1$s hoogte %2$s" - "Kortpadkieslys" - "Verander grootte van legstukraam vir %1$s" - "Maak toe" + "Kortpaaie" "Maak toe" "Maak toe" "Persoonlik" "Werk" - "Persoonlike Apps-oortjie" - "Werkapps-oortjie" "Werkprofiel" "Werkprogramme het \'n kenteken en is sigbaar vir jou IT-administrateur" "Het dit" @@ -204,7 +183,6 @@ "Het dit" "Onderbreek werkprogramme" "Hervat" - "Skedule van werkapps" "Filter" "Misluk: %1$s" "Privaat ruimte" @@ -217,5 +195,4 @@ "Privaat Ruimte-oorgang" "Installeer" "Installeer apps in privaat ruimte" - "Voeg lêers en meer by privaat ruimte" diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml index dca355c201..80447c5940 100644 --- a/res/values-am/strings.xml +++ b/res/values-am/strings.xml @@ -29,11 +29,8 @@ "መነሻ" "በቅንብሮች ውስጥ %1$sን እንደ ነባሪ የHome መተግበሪያ ያቀናብሩ" "የተከፈለ ማያ ገፅ" - "የምጥጥነ ገፅታ ለውጥ" "የመተግበሪያ መረጃ ለ%1$s" "የ%1$s የአጠቃቀም ቅንብሮች" - "አዲስ መስኮት" - "መስኮቶችን ያስተዳድሩ" "የመተግበሪያ ጥምረትን ያስቀምጡ" "%1$s | %2$s" "ይህ የመተግበሪያ ጥምረት በዚህ መሣሪያ ላይ አይደገፍም" @@ -41,8 +38,6 @@ "የመተግበሪያ ጥምረት አይገኝም" "ምግብርን ለማንቀሳቀስ ይንኩ እና ይያዙ።" "ምግብርን ለማንቀሳቀስ ወይም ብጁ እርምጃዎችን ለመጠቀም ሁለቴ መታ ያድርጉ እና ይያዙ።" - "ተጨማሪ አማራጮች" - "ሁሉንም ምግብሮች አሳይ" "%1$d × %2$d" "%1$d ስፋት በ%2$d ከፍታ" "የ%1$s ምግብር" @@ -69,13 +64,8 @@ "ሥራ" "ውይይቶች" "የማስታወሻ አያያዝ" - "የማከል አዝራርን አሳይ" - "የማከል አዝራርን ደብቅ" "አክል" "ምግብር %1$sን አክል" - "ሁሉንም አሳይ" - "ሁሉንም ምግብሮች አሳይ" - "ሁሉንም ምግብሮች በማሳየት ላይ" "የምግብር ቅንብሮችን ለመለወጥ መታ ያድርጉ" "የምግብር ቅንብሮችን ይለውጡ" "መተግበሪያዎችን ፈልግ" @@ -83,7 +73,6 @@ "ከ«%1$s» ጋር የሚዛመዱ ምንም መተግበሪያዎች አልተገኙም" "መተግበሪያ" "ሁሉም መተግበሪያዎች" - "የመተግበሪያዎች ዝርዝር" "ማሳወቂያዎች" "አቋራጭን ለማንቀሳቀስ ይንኩ እና ይያዙ" "አቋራጭን ለማንቀሳቀስ ወይም ብጁ እርምጃዎችን ለመጠቀም ሁለቴ መታ ያድርጉ እና ይያዙ።" @@ -101,7 +90,6 @@ "ጫን" "መተግበሪያውን አይጠቁሙ" "የፒን ግምት" - "አረፋ" "አቋራጮችን ይጭናል" "መተግበሪያው ያለተጠቃሚ ጣልቃ ገብነት አቋራጭ እንዲያክል ያስችለዋል።" "የመነሻ ቅንብሮች እና አቋራጮችን ያነባል" @@ -118,8 +106,6 @@ "ገፅ %1$d ከ%2$d" "መነሻ ማያ ገፅ %1$d ከ%2$d" "አዲስ የመነሻ ማያ ገፅ" - "ገቢር" - "አንሷል" "አቃፊ ተከፍቷል፣ %1$d%2$d" "አቃፊን ለመዝጋት መታ ያድርጉ" "ዳግም የተሰጠውን ስም ለማስቀመጥ መታ ያድርጉ" @@ -127,7 +113,6 @@ "አቃፊ %1$s ተብሎ ዳግም ተሰይሟል" "አቃፊ፦ %1$s%2$d ንጥሎች" "አቃፊ፦ %1$s%2$d ወይም ተጨማሪ ንጥሎች" - "ያልተሰየመ አቃፊ" "የመተግበሪያ ጥምረት፦ %1$s እና %2$s" "ልጣፍ እና ቅጥ" "መነሻ ማያ ገጽን አርትዕ" @@ -135,8 +120,6 @@ "በእርስዎ አስተዳዳሪ የተሰናከለ" "የመነሻ ማያ ገፅ ማሽከርከርን ይፍቀዱ" "ስልኩ ሲዞር" - "የመሬት አቀማመጥ ሁኔታ" - "ስልክን ወደ የመሬት አቀማመጥ ሁኔታ ያቀናብሩ" "የማሳወቂያ ነጥቦች" "አብራ" "ጠፍቷል" @@ -155,8 +138,7 @@ "%1$s በመጫን ላይ፣ %2$s ተጠናቅቋል" "%1$s በመውረድ ላይ፣ %2$s ተጠናቋል" "%1$s ለመጫን በመጠበቅ ላይ" - "%1$s በማህደር ተቀምጧል።" - "አውርድ እና ወደነበረበት መልስ" + "%1$s በማህደር ተቀምጧል። ለማወረድ እና ወደነበረበት ለመመለስ መታ ያድርጉ።" "መተግበሪያ ማዘመን አስፈላጊ ነው" "የዚህ አዶ መተግበሪያ አልተዘመነም። ይህን አቋራጭ ዳግም ለማንቃት በራስዎ ማዘመን ወይም አዶውን ማስወገድ ይችላሉ።" "አዘምን" @@ -165,6 +147,7 @@ "የመግብሮች ዝርዝር ተዘግቷል" "ወደ መነሻ ማያ ገፅ አክል" "ንጥልን ወደዚህ ውሰድ" + "ወደ መነሻ ማያ ገፅ ንጥል ታክሏል" "ንጥል ነገር ተንቀሳቅሷል" "ቀልብስ" "ንጥልን አንቀሳቅስ" @@ -184,15 +167,11 @@ "ስፋት ይቀንሱ" "ቁመት ይቀንሱ" "የመግብር መጠን ወደ ስፋት %1$s ቁመት %2$s ተለውጧል" - "የአቋራጭ ምናሌ" - "ለ%1$s የሚሆን የምግብር መጠን መቀይር ክፍለ ገጸ ድር" - "ዝጋ" + "አቋራጮች" "አሰናብት" "ዝጋ" "የግል" "ሥራ" - "የግል መተግበሪያዎች ትር" - "የሥራ መተግበሪያዎች ትር" "የሥራ መገለጫ" "የሥራ መተግበሪያዎች ባጅ የተደረገባቸው እና ለእርስዎ የአይቲ አስተዳዳሪ የሚታዩ ናቸው" "ገባኝ" @@ -204,7 +183,6 @@ "ገባኝ" "የሥራ መተግበሪያዎችን ባሉበት አቁም" "ካቆመበት ቀጥል" - "የሥራ መተግበሪያዎች መርሐግብር" "አጣራ" "አልተሳካም፦ %1$s" "የግል ቦታ" @@ -217,5 +195,4 @@ "የግል ቦታ ሽግግር" "ይጫኑ" "መተግበሪያዎችን ወደ የግል ቦታ ይጫኑ" - "ወደ የግል ቦታ ፋይሎች እና ሌሎችንም ያክሉ" diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml index bd316b673e..4eee1217a7 100644 --- a/res/values-ar/strings.xml +++ b/res/values-ar/strings.xml @@ -24,30 +24,25 @@ "لم يتم تثبيت التطبيق." "التطبيق ليس متاحًا" "تم إيقاف التطبيق الذي تم تنزيله في الوضع الآمن" - "التطبيقات المصغَّرة غير مفعّلة في الوضع الآمن" + "الأدوات غير مفعّلة في الوضع الآمن" "الاختصار غير متاح" "الشاشة الرئيسية" "يمكن ضبط \"%1$s\" كتطبيق الشاشة الرئيسية التلقائي من خلال \"الإعدادات\"" "تقسيم الشاشة" - "تغيير نسبة العرض إلى الارتفاع" "‏معلومات تطبيق %1$s" "‏إعدادات استخدام \"%1$s\"" - "نافذة جديدة" - "إدارة النوافذ" "حفظ استخدام التطبيقين معًا" "%1$s | ‏%2$s" "لا يمكن استخدام هذين التطبيقَين في الوقت نفسه على هذا الجهاز" "افتح الجهاز لاستخدام هذين التطبيقَين في الوقت نفسه" "ميزة \"استخدام تطبيقين في الوقت نفسه\" غير متوفّرة" - "انقر مع الاستمرار لنقل تطبيق مصغَّر." - "انقر مرتين مع تثبيت إصبعك لنقل تطبيق مصغَّر أو استخدام الإجراءات المخصّصة." - "خيارات إضافية" - "عرض كل التطبيقات المصغّرة" + "انقر مع الاستمرار لنقل أداة." + "انقر مرتين مع تثبيت إصبعك لنقل أداة أو استخدام الإجراءات المخصّصة." "%1$d × %2$d" "‏العرض %1$d الطول %2$d" - "التطبيق المصغَّر %1$s" + "أداة %1$s" "‏التطبيق المصغّرة \"%1$s\"، بعرض ‎%2$d وارتفاع ‎%3$d" - "يُرجى النقر مع الاستمرار على التطبيق المصغّر لنقله إلى الشاشة الرئيسية" + "انقر مع الاستمرار على التطبيق المصغّر لنقله إلى الشاشة الرئيسية" "إضافة إلى الشاشة الرئيسية" "تمت إضافة الأداة %1$s إلى الشاشة الرئيسية." "اقتراحات" @@ -63,27 +58,21 @@ "التطبيقات المصغّرة" "بحث" "محو النص من مربّع البحث" - "التطبيقات المصغَّرة والاختصارات غير متاحة." + "الأدوات والاختصارات غير متاحة." "لم يتم العثور على تطبيقات مصغّرة أو اختصارات." "التطبيقات الشخصية" "تطبيقات العمل" "المحادثات" "تدوين الملاحظات" - "إظهار زر الإضافة" - "إخفاء زر الإضافة" "إضافة" "إضافة التطبيق المصغّر \"%1$s\"" - "عرض الكل" - "عرض كل التطبيقات المصغّرة" - "جارٍ عرض كل التطبيقات المصغّرة" - "انقر لتغيير إعدادات التطبيق المصغَّر" - "تغيير إعدادات التطبيق المصغَّر" + "انقر لتغيير إعدادات الأداة" + "تغيير إعدادات الأداة" "بحث في التطبيقات" "جارٍ تحميل التطبيقات…" "لم يتم العثور على أي تطبيقات تتطابق مع \"%1$s\"" "تطبيق" "جميع التطبيقات" - "قائمة التطبيقات" "الإشعارات" "انقر مع الاستمرار لنقل اختصار" "انقر مرتين مع تثبيت إصبعك لنقل اختصار أو استخدام الإجراءات المخصّصة." @@ -101,14 +90,13 @@ "تثبيت" "عدم اقتراح التطبيق" "تثبيت التطبيق المتوقّع" - "فقاعة" "تثبيت اختصارات" "للسماح لتطبيق ما بإضافة اختصارات بدون تدخل المستخدم." "الاطلاع على الإعدادات والاختصارات على الشاشة الرئيسية" "يسمح هذا الإذن للتطبيق بالاطلاع على الإعدادات والاختصارات على الشاشة الرئيسية." "تعديل الإعدادات والاختصارات على الشاشة الرئيسية" "يسمح هذا الإذن للتطبيق بتغيير الإعدادات والاختصارات على الشاشة الرئيسية." - "يتعذّر تحميل التطبيق المصغَّر." + "يتعذّر تحميل الأداة." "إعدادات التطبيق المصغّر" "انقر لإكمال الإعداد." "هذا تطبيق نظام وتتعذر إزالته." @@ -118,8 +106,6 @@ "‏الصفحة %1$d من %2$d" "‏الشاشة الرئيسية %1$d من %2$d" "صفحة الشاشة الرئيسية الجديدة" - "نشط" - "تم التصغير" "تم فتح المجلد، بمقاس %1$d في %2$d" "انقر لإغلاق المجلد" "انقر لحفظ الاسم الجديد" @@ -127,7 +113,6 @@ "تمت إعادة تسمية المجلد إلى %1$s" "المجلد: %1$s، %2$d عنصر" "المجلد: %1$s، %2$d عنصر أو أكثر" - "مجلد بدون اسم" "استخدام تطبيقين في الوقت نفسه: تطبيق \"%1$s\" و\"%2$s\"" "الخلفية والأسلوب" "تعديل الشاشة الرئيسية" @@ -135,8 +120,6 @@ "أوقف المشرف هذه الميزة" "السماح بتدوير الشاشة الرئيسية" "عند تدوير الهاتف" - "الوضع الأفقي" - "ضبط الهاتف على الوضع الأفقي" "نقاط الإشعارات" "الإعداد مفعّل" "غير مفعّل" @@ -155,16 +138,16 @@ "جارٍ تثبيت %1$s، مستوى التقدم: %2$s" "جارٍ تنزيل %1$s، اكتمل %2$s" "%1$s في انتظار التثبيت" - "تمت أرشفة \"%1$s\"." - "تنزيل التطبيق واستعادته" + "تمت أرشفة تطبيق \"%1$s\". انقر لتنزيله واستعادته." "مطلوب تحديث التطبيق" "لم يتمّ تحديث التطبيق الخاص بهذا الرمز. يمكنك تحديث التطبيق يدويًا لإعادة تفعيل هذا الاختصار أو إزالة الرمز." "تحديث" "إزالة" - "قائمة التطبيقات المصغَّرة" - "تم إغلاق قائمة التطبيقات المصغَّرة." + "قائمة الأدوات" + "تم إغلاق قائمة الأدوات." "إضافة تطبيق للشاشة الرئيسية" "نقل العنصر إلى هنا" + "تمت إضافة العنصر إلى الشاشة الرئيسية" "تمّت إزالة العنصر." "تراجع" "نقل العنصر" @@ -183,16 +166,12 @@ "زيادة الارتفاع" "تقليل العرض" "تقليل الارتفاع" - "تم تغيير حجم التطبيق المصغَّر إلى العرض %1$s والارتفاع %2$s" - "قائمة الاختصارات" - "تغيير حجم إطار تطبيق \"%1$s\" المصغّر" - "إغلاق" + "تم تغيير حجم الأداة إلى العرض %1$s والارتفاع %2$s" + "الاختصارات" "تجاهل" "إغلاق" "شخصية" "للعمل" - "علامة تبويب التطبيقات الشخصية" - "علامة تبويب تطبيقات العمل" "ملف العمل" "تحمل تطبيقات العمل مميّزة بشارة ومرئية لمشرف تكنولوجيا المعلومات." "حسنًا" @@ -204,7 +183,6 @@ "حسنًا" "إيقاف تطبيقات العمل مؤقتًا" "إلغاء الإيقاف المؤقت" - "الجدول الزمني لتطبيقات العمل" "فلتر" "تعذَّر %1$s." "مساحة خاصة" @@ -217,5 +195,4 @@ "النقل إلى المساحة الخاصة" "تثبيت" "تثبيت التطبيقات في المساحة الخاصّة" - "إضافة ملفات والمزيد إلى \"المساحة الخاصّة\"" diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml index c13ead7815..52ec7ea8dd 100644 --- a/res/values-as/strings.xml +++ b/res/values-as/strings.xml @@ -29,11 +29,8 @@ "গৃহ স্ক্ৰীন" "ছেটিঙত %1$sক ডিফ’ল্ট গৃহপৃষ্ঠা এপ্‌ হিচাপে ছেট কৰক" "বিভাজিত স্ক্ৰীন" - "আকাৰৰ অনুপাত সলনি কৰক" "%1$sৰ বাবে এপৰ তথ্য" "%1$sৰ বাবে ব্যৱহাৰৰ ছেটিং" - "নতুন ৱিণ্ড’" - "Windows পৰিচালনা কৰক" "এপৰ পেয়াৰ ছেভ কৰক" "%1$s | %2$s" "এই ডিভাইচটোত এই এপ্‌ পেয়াৰ কৰাৰ সুবিধাটো সমৰ্থিত নহয়" @@ -41,8 +38,6 @@ "এপ্‌ পেয়াৰ কৰাৰ সুবিধাটো উপলব্ধ নহয়" "ৱিজেট স্থানান্তৰ কৰিবলৈ টিপি ধৰি ৰাখক।" "কোনো ৱিজেট স্থানান্তৰ কৰিবলৈ দুবাৰ টিপি ধৰি ৰাখক অথবা কাষ্টম কাৰ্য ব্যৱহাৰ কৰক।" - "অধিক বিকল্প" - "আটাইবোৰ ৱিজেট দেখুৱাওক" "%1$d × %2$d" "%1$d বহল x %2$d ওখ" "%1$s ৱিজেট" @@ -69,13 +64,8 @@ "কৰ্মস্থান" "বাৰ্তালাপ" "টোকা গ্ৰহণ কৰা" - "যোগ দিয়ক বুটামটো দেখুৱাওক" - "যোগ দিয়ক বুটামটো লুকুৱাওক" "যোগ দিয়ক" "%1$s ৱিজেট যোগ দিয়ক" - "আটাইবোৰ দেখুৱাওক" - "আটাইবোৰ ৱিজেট দেখুৱাওক" - "আটাইবোৰ ৱিজেট দেখুৱাই থকা হৈছে" "ৱিজেটৰ ছেটিং সলনি কৰিবলৈ টিপক" "ৱিজেটৰ ছেটিং সলনি কৰক" "এপ্‌সমূহ সন্ধান কৰক" @@ -83,7 +73,6 @@ "\"%1$s\"ৰ সৈতে মিলা কোনো এপ্ বিচাৰি পোৱা নগ\'ল" "এপ্" "আটাইবোৰ এপ্" - "এপৰ সূচী" "জাননীসমূহ" "শ্বৰ্টকাট স্থানান্তৰ কৰিবলৈ দুবাৰ টিপি ধৰি ৰাখক।" "কোনো শ্বৰ্টকাট স্থানান্তৰ কৰিবলৈ দুবাৰ টিপি ধৰি ৰাখক অথবা কাষ্টম কাৰ্য ব্যৱহাৰ কৰক।" @@ -101,7 +90,6 @@ "ইনষ্টল কৰক" "পৰামৰ্শ নিদিব" "পূৰ্বানুমান কৰা এপ্‌টো পিন কৰক" - "বাবল" "শ্বৰ্টকাট ইনষ্টল কৰিব পাৰে" "ব্য়ৱহাৰকাৰীৰ হস্তক্ষেপ অবিহনেই কোনো এপক শ্বৰ্টকাটবোৰ যোগ কৰাৰ অনুমতি দিয়ে।" "গৃহ স্ক্ৰীনত ছেটিং আৰু শ্বৰ্টকাটসমূহ পঢ়া" @@ -118,8 +106,6 @@ "%2$dৰ %1$d পৃষ্ঠা" "গৃহ স্ক্ৰীন %2$dৰ %1$d" "গৃহ স্ক্ৰীনৰ নতুন পৃষ্ঠা" - "সক্ৰিয়" - "মিনিমাইজ কৰা হৈছে" "ফ’ল্ডাৰ খোলা হ’ল, %1$d x %2$d" "ফ\'ল্ডাৰ বন্ধ কৰিবলৈ টিপক" "সলনি কৰা নাম ছেভ কৰিবলৈ টিপক" @@ -127,7 +113,6 @@ "ফ\'ল্ডাৰৰ নাম সলনি কৰি %1$s কৰা হৈছে" "ফ’ল্ডাৰ: %1$s, %2$d টা বস্তু" "ফ’ল্ডাৰ: %1$s, %2$d টা অথবা তাতকৈ অধিক বস্তু" - "নামবিহীন ফ’ল্ডাৰ" "এপ্ পেয়াৰ কৰা: %1$s আৰু %2$s" "ৱালপেপাৰ আৰু শৈলী" "গৃহ স্ক্ৰীন সম্পাদনা কৰক" @@ -135,8 +120,6 @@ "আপোনাৰ প্ৰশাসকে অক্ষম কৰি ৰাখিছে" "গৃহ স্ক্ৰীন ঘূৰোৱাৰ অনুমতি দিয়ক" "ফ\'নটো যেতিয়া ঘূৰোৱা হয়" - "লেণ্ডস্কেইপ ম’ড" - "ফ’নটো লেণ্ডস্কেইপ ম’ডলৈ ছেট কৰক" "জাননী বিন্দু" "অন কৰা আছে" "অফ আছে" @@ -155,8 +138,7 @@ "%1$s ইনষ্টল কৰি থকা হৈছে, %2$s সম্পূৰ্ণ হৈছে" "%1$s ডাউনল’ড কৰি থকা হৈছে, %2$s সম্পূৰ্ণ হ’ল" "%1$s ইনষ্টল হোৱালৈ অপেক্ষা কৰি থকা হৈছে" - "%1$s আৰ্কাইভ কৰা হৈছে।" - "ডাউনল’ড আৰু পুনঃস্থাপন কৰক" + "%1$s আৰ্কাইভ কৰা হৈছে। ডাউনল’ড আৰু পুনঃস্থাপন কৰিবলৈ টিপক।" "এপ্‌টো আপডে’ট কৰা প্ৰয়োজন" "এই চিহ্নটোৰ এপ্‌টো আপডে’ট কৰা হোৱা নাই। আপুনি এই শ্বৰ্টকাটটো পুনৰ সক্ষম কৰিবলৈ মেনুৱেলী আপডে’ট কৰিব পাৰে অথবা চিহ্নটো আঁতৰাব পাৰে।" "আপডে’ট কৰক" @@ -165,6 +147,7 @@ "ৱিজেটৰ তালিকা বন্ধ কৰা হ’ল" "গৃহ স্ক্ৰীনত যোগ কৰক" "বস্তুটো ইয়ালৈ স্থানান্তৰ কৰক" + "বস্তুটো গৃহ স্ক্ৰীনত যোগ কৰা হ’ল" "বস্তুটো আঁতৰোৱা হ’ল" "আনডু কৰক" "বস্তু স্থানান্তৰ কৰক" @@ -184,15 +167,11 @@ "প্ৰস্থ হ্ৰাস কৰক" "উচ্চতা হ্ৰাস কৰক" "ৱিজেটৰ আকাৰ সলনি কৰি প্ৰস্থ %1$s আৰু উচ্চতা %2$s কৰা হ’ল" - "শ্বৰ্টকাটৰ মেনু" - "%1$sৰ বাবে ৱিজেটৰ আকাৰ সলনি কৰা ফ্রে’ম" - "বন্ধ কৰক" + "শ্বৰ্টকাটসমূহ" "অগ্ৰাহ্য কৰক" "বন্ধ কৰক" "ব্যক্তিগত" "কৰ্মস্থান" - "ব্যক্তিগত এপৰ টেব" - "কৰ্মস্থানৰ এপৰ টেব" "কৰ্মস্থানৰ প্ৰ\'ফাইল" "কৰ্মস্থানৰ এপ্‌সমূহ প্ৰতীকেৰে চিহ্নিত কৰা হয় আৰু সেইবোৰ আপোনাৰ আইটি প্ৰশাসকৰ বাবে দৃশ্যমান হয়" "বুজি পালোঁ" @@ -204,12 +183,11 @@ "বুজি পালোঁ" "কৰ্মস্থানৰ এপ্‌ পজ কৰক" "আনপজ কৰক" - "কাম সম্পৰ্কীয় এপৰ সময়সূচী" "ফিল্টাৰ" "বিফল: %1$s" "প্ৰাইভেট স্পে\'চ" "ছেট আপ কৰিবলৈ টিপক অথবা খোলক" - "প্ৰাইভেট" + "ব্যক্তিগত" "ব্যক্তিগত স্পে’চৰ ছেটিং" "ব্যক্তিগত, আনলক কৰা আছে।" "ব্যক্তিগত, লক কৰা আছে।" @@ -217,5 +195,4 @@ "ব্যক্তিগত স্পে’চৰ স্থানান্তৰণ" "ইনষ্টল কৰক" "এপ্‌সমূহ প্ৰাইভেট স্পেচত ইনষ্টল কৰক" - "প্ৰাইভেট স্পে’চত ফাইল আৰু অধিক যোগ দিয়ক" diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml index 76b9596a6f..6c1cc468a7 100644 --- a/res/values-az/strings.xml +++ b/res/values-az/strings.xml @@ -29,11 +29,8 @@ "Əsas səhifə" "Ayarlarda %1$s tətbiqini defolt əsas ekran tətbiqi kimi ayarlayın" "Ekran bölünməsi" - "Tərəflər nisbətini dəyişin" "%1$s ilə bağlı tətbiq məlumatı" "%1$s üzrə istifadə ayarları" - "Yeni Pəncərə" - "Pəncərələri idarə edin" "Tətbiq cütünü saxlayın" "%1$s | %2$s" "Bu tətbiq cütü bu cihazda dəstəklənmir" @@ -41,8 +38,6 @@ "Tətbiq cütü əlçatan deyil" "Vidceti daşımaq üçün toxunub saxlayın." "Vidceti daşımaq üçün iki dəfə toxunub saxlayın və ya fərdi əməliyyatlardan istifadə edin." - "Digər seçimlər" - "Bütün vidcetləri göstərin" "%1$d × %2$d" "%2$d hündürlük %1$d enində" "%1$s vidceti" @@ -69,13 +64,8 @@ "İş" "Söhbətlər" "Qeydgötürmə" - "Əlavə edin düyməsini göstərin" - "Əlavə edin düyməsini gizlədin" "Əlavə edin" "%1$s vidcet əlavə edin" - "Hamısını göstər" - "Bütün vidcetləri göstərin" - "Bütün vidcetlər göstərilir" "Vidcet ayarlarını dəyişmək üçün toxunun" "Vidcet ayarlarını dəyişin" "Tətbiqləri axtarın" @@ -83,7 +73,6 @@ "%1$s sorğusuna uyğun tətbiq tapılmadı" "Tətbiq" "Bütün tətbiqlər" - "Tətbiq siyahısı" "Bildirişlər" "Qısayolu daşımaq üçün toxunub saxlayın." "Qısayolu daşımaq üçün iki dəfə toxunub saxlayın və ya fərdi əməliyyatlardan istifadə edin." @@ -101,7 +90,6 @@ "Quraşdırın" "Tətbiq təklif olunmasın" "Proqnozlaşdırılan tətbiqi bərkidin" - "Qabarcıq" "qısayolları quraşdır" "Tətbiqə istifadəçi müdaxiləsi olmadan qısayolları əlavə etməyə icazə verir." "Əsas səhifə ayarlarını və qısayollarını oxumaq" @@ -118,8 +106,6 @@ "Səhifə %1$d of %2$d" "Əsas Səhifə ekranı %1$d of %2$d" "Yeni əsas ekran səhifəsi" - "Aktiv" - "Kiçildildi" "Qovluq açıldı, %2$d hündürlük ilə %1$d enində" "Qovluq bağlamaq üçün toxunun" "Ad dəyişikliyini yadda saxlamaq üçün toxunun" @@ -127,7 +113,6 @@ "Qovluq adı %1$s ilə dəyişdirildi" "Qovluq: %1$s, %2$d element" "Qovluq: %1$s, %2$d və ya daha çox element" - "Adsız qovluq" "Tətbiq cütü: %1$s%2$s" "Divar kağızı və üslub" "Əsas ekranı redaktə edin" @@ -135,8 +120,6 @@ "Admininiz tərəfindən deaktiv edilib" "Əsas ekran çevrilsin" "Telefon çevrilən zaman" - "Landşaft rejimi" - "Telefonu landşaft rejiminə ayarlayın" "Bildiriş nöqtələri" "Aktiv" "Deaktiv" @@ -155,8 +138,7 @@ "%1$s quraşdırır, %2$s tamamlanıb" "%1$s endirilir, %2$s tamamlandı" "%1$s yüklənmək üçün gözləyir" - "%1$s arxivləndi." - "endirin və bərpa edin" + "%1$s arxivləndi. Toxunaraq endirin və bərpa edin." "Tətbiqin güncəllənməsi tələb edilir" "Bu ikona üçün tətbiq güncəllənməyib. Bu qısayolu yenidən aktivləşdirmək üçün manual olaraq güncəlləyə və ya ikonanı silə bilərsiniz." "Güncəlləyin" @@ -165,6 +147,7 @@ "Vidcet siyahısı bağlandı" "Əsas ekrana əlavə edin" "Elementi bura köçürün" + "Element əsas ekrana əlavə edildi" "Element silindi" "Ləğv edin" "Elementi köçürün" @@ -184,15 +167,11 @@ "Eni azaldın" "Hündürlüyü azaldın" "Vidcetin eni %1$s hündürlüyü %2$s kimi ölçüləndirildi" - "Qısayol menyusu" - "%1$s üçün vidcet ölçüsünü dəyişdirmə çərçivəsi" - "Bağlayın" + "Qısa yollar" "Rədd edin" "Bağlayın" "Şəxsi" "İş" - "Şəxsi tətbiqlər tabı" - "İş tətbiqləri tabı" "İş profili" "İş tətbiqləri nişanlanıb və İT administratorunuza görünür" "Anladım" @@ -204,12 +183,11 @@ "Anladım" "İş tətbiqlərini durdurun" "Davam etdirin" - "İş tətbiqləri cədvəli" "Filtr" "Alınmadı: %1$s" "Məxfi sahə" "Toxunaraq ayarlayın və ya açın" - "Məxfi" + "Şəxsi" "Şəxsi məkan ayarları" "Şəxsi, kilidli deyil." "Şəxsi, kilidli." @@ -217,5 +195,4 @@ "Şəxsi məkana keçid" "Quraşdırın" "Tətbiqləri şəxsi sahədə quraşdırın" - "Private Space-ə fayllar və s. əlavə edin" diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml index 74f00e8614..24328cfaee 100644 --- a/res/values-b+sr+Latn/strings.xml +++ b/res/values-b+sr+Latn/strings.xml @@ -29,11 +29,8 @@ "Početni ekran" "Podesite %1$s kao podrazumevanu početnu aplikaciju u Podešavanjima" "Podeljeni ekran" - "Promeni razmeru" "Informacije o aplikaciji za: %1$s" "Podešavanja potrošnje za %1$s" - "Novi prozor" - "Upravljajte prozorima" "Sačuvaj par aplikacija" "%1$s | %2$s" "Ovaj par aplikacija nije podržan na ovom uređaju" @@ -41,8 +38,6 @@ "Par aplikacija nije dostupan" "Dodirnite i zadržite radi pomeranja vidžeta." "Dvaput dodirnite i zadržite da biste pomerali vidžet ili koristite prilagođene radnje." - "Još opcija" - "Prikaži sve vidžete" "%1$d×%2$d" "širina od %1$d i visina od %2$d" "%1$s vidžet" @@ -69,13 +64,8 @@ "Posao" "Konverzacije" "Pravljenje beležaka" - "Prikažite dugme za dodavanje" - "Sakrijte dugme za dodavanje" "Dodaj" "Dodajte vidžet %1$s" - "Prikaži sve" - "Prikažite sve vidžete" - "Prikazuju se svi vidžeti" "Dodirnite da biste promenili podešavanja vidžeta" "Promenite podešavanja vidžeta" "Pretražite aplikacije" @@ -83,7 +73,6 @@ "Nije pronađena nijedna aplikacija za „%1$s“" "Aplikacija" "Sve aplikacije" - "Lista aplikacija" "Obaveštenja" "Dodirnite i zadržite radi pomeranja prečice." "Dvaput dodirnite i zadržite da biste pomerali prečicu ili koristite prilagođene radnje." @@ -101,7 +90,6 @@ "Instaliraj" "Ne predlaži aplikaciju" "Zakači predviđanje" - "Oblačić" "instaliranje prečica" "Dozvoljava aplikaciji da dodaje prečice bez intervencije korisnika." "čitanje podešavanja i prečica na početnom ekranu" @@ -118,8 +106,6 @@ "%1$d. stranica od %2$d" "%1$d. početni ekran od %2$d" "Nova stranica početnog ekrana" - "Aktivno" - "Smanjeno" "Folder je otvoren, %1$d puta %2$d" "Dodirnite da biste zatvorili folder" "Dodirnite da biste sačuvali preimenovanje" @@ -127,7 +113,6 @@ "Folder je preimenovan u %1$s" "Folder: %1$s, %2$d stavke" "Folder: %1$s, %2$d ili više stavki" - "Neimenovani folder" "Par aplikacija: %1$s i %2$s" "Pozadina i stil" "Izmeni početni ekran" @@ -135,8 +120,6 @@ "Administrator je onemogućio" "Dozvoli rotaciju početnog ekrana" "Kada se telefon rotira" - "Vodoravni režim" - "Podesite telefon na vodoravni režim" "Tačke za obaveštenja" "Uključeno" "Isključeno" @@ -155,8 +138,7 @@ "%1$s se instalira, %2$s gotovo" "%1$s se preuzima, završeno je %2$s" "%1$s čeka na instaliranje" - "Aplikacija %1$s je arhivirana." - "preuzmite i vratite" + "Aplikacija %1$s je arhivirana. Dodirnite da biste je preuzeli i vratili." "Treba da ažurirate aplikaciju" "Aplikacija za ovu ikonu nije ažurirana. Možete da je ručno ažurirate da biste ponovo omogućili ovu prečicu ili uklonite ikonu." "Ažuriraj" @@ -165,6 +147,7 @@ "Lista vidžeta je zatvorena" "Dodajte na početni ekran" "Premesti stavku ovde" + "Stavka je dodata na početni ekran" "Stavka je uklonjena" "Opozovi" "Premesti stavku" @@ -184,15 +167,11 @@ "Smanji širinu" "Smanji visinu" "Veličina vidžeta je promenjena na širinu %1$s i visinu %2$s" - "Meni sa prečicama" - "Promena veličine okvira vidžeta za: %1$s" - "Zatvorite" + "Prečice" "Odbaci" "Zatvori" "Lično" "Posao" - "Kartica Lične aplikacije" - "Kartica Poslovne aplikacije" "Poslovni profil" "Poslovne aplikacije su označene značkom i IT administrator može da ih vidi" "Važi" @@ -204,7 +183,6 @@ "Važi" "Pauziraj poslovne aplikacije" "Ponovo aktiviraj" - "Raspored za poslovne aplikacije" "Filter" "Nije uspelo: %1$s" "Privatni prostor" @@ -217,5 +195,4 @@ "Prenos privatnog prostora" "Instalirajte" "Instaliraj aplikacije u privatan prostor" - "Dodajte fajlove i drugo u privatan prostor" diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml index 877513e1d6..ebbb378123 100644 --- a/res/values-be/strings.xml +++ b/res/values-be/strings.xml @@ -29,11 +29,8 @@ "Галоўны экран" "Зрабіць %1$s стандартнай праграмай для галоўнага экрана, перайшоўшы ў Налады" "Падзелены экран" - "Змяніць суадносіны бакоў" "Інфармацыя пра праграму для: %1$s" "%1$s: налады выкарыстання" - "Новае акно" - "Кіраваць вокнамі" "Захаваць спалучэнне праграм" "%1$s | %2$s" "Дадзенае спалучэнне праграм не падтрымліваецца на гэтай прыладзе" @@ -41,8 +38,6 @@ "Спалучэнне праграм недаступнае" "Націсніце і ўтрымлівайце віджэт для перамяшчэння." "Дакраніцеся двойчы і ўтрымлівайце, каб перамясціць віджэт або выкарыстоўваць спецыяльныя дзеянні." - "Дадатковыя параметры" - "Паказваць усе віджэты" "%1$d × %2$d" "Шырына: %1$d, вышыня: %2$d" "Віджэт \"%1$s\"" @@ -69,13 +64,8 @@ "Працоўныя" "Размовы" "Стварэнне нататак" - "Паказаць кнопку \"Дадаць\"" - "Схаваць кнопку \"Дадаць\"" "Дадаць" "Дадаць віджэт \"%1$s\"" - "Паказаць усе" - "Паказаць усе віджэты" - "Паказаны ўсе віджэты" "Націсніце, каб змяніць налады віджэта" "Змяніць налады віджэта" "Пошук праграм" @@ -83,7 +73,6 @@ "Праграм, якія адпавядаюць запыту \"%1$s\", не знойдзена" "Праграма" "Усе праграмы" - "Спіс праграм" "Апавяшчэнні" "Націсніце і ўтрымлівайце ярлык для перамяшчэння." "Дакраніцеся двойчы і ўтрымлівайце, каб перамясціць ярлык або выкарыстоўваць спецыяльныя дзеянні." @@ -101,7 +90,6 @@ "Усталяваць" "Не прапаноўваць праграму" "Замацаваць прапанаваную праграму" - "Бурбалка" "Стварэнне ярлыкоў" "Дазваляе праграмам дадаваць ярлыкі без умяшання карыстальніка." "счытваць налады і ярлыкі на галоўным экране" @@ -118,8 +106,6 @@ "Старонка %1$d з %2$d" "Галоўны экран %1$d з %2$d" "Новая старонка галоўнага экрана" - "Актыўна" - "Згорнута" "Папка адкрыта, %1$d на %2$d" "Краніце, каб закрыць папку" "Краніце, каб захаваць новую назву" @@ -127,7 +113,6 @@ "Папка перайменавана ў %1$s" "Папка: %1$s, элементы: %2$d" "Папка: %1$s, элементы: %2$d ці больш" - "Папка без назвы" "Спалучэнне праграм: %1$s і %2$s" "Шпалеры і стыль" "Змяніць Галоўны экран" @@ -135,8 +120,6 @@ "Адключаная адміністратарам" "Дазволіць паварот галоўнага экрана" "Пры павароце тэлефона" - "Альбомная арыентацыя" - "Перавядзіце тэлефон у альбомную арыентацыю" "Значкі апавяшчэнняў" "Уключана" "Выкл." @@ -155,8 +138,7 @@ "Усталёўваецца праграма \"%1$s\", завершана %2$s" "Ідзе спампоўка %1$s, %2$s завершана" "%1$s чакае ўсталёўкі" - "Праграма \"%1$s\" знаходзіцца ў архіве." - "спампаваць і аднавіць" + "Праграма \"%1$s\" знаходзіцца ў архіве. Націсніце, каб спампаваць яе і аднавіць." "Неабходна абнавіць праграму" "Гэта версія праграмы састарэла. Абнавіце праграму ўручную, каб зноў карыстацца гэтым ярлыком, або выдаліце значок." "Абнавіць" @@ -165,6 +147,7 @@ "Спіс віджэтаў закрыты" "Дадаць на галоўны экран" "Перамясціць элемент сюды" + "Элемент дададзены на галоўны экран" "Элемент выдалены" "Адрабіць" "Перамясціць элемент" @@ -184,15 +167,11 @@ "Паменшыць шырыню" "Паменшыць вышыню" "Памеры віджэта зменены на: шырыня %1$s, вышыня %2$s" - "Меню спалучэнняў клавіш" - "Рамка змянення памеру для віджэта \"%1$s\"" - "Закрыць" + "Ярлыкі" "Адхіліць" "Закрыць" "Асабістыя" "Працоўныя" - "Укладка \"Асабістыя праграмы\"" - "Укладка \"Працоўныя праграмы\"" "Працоўны профіль" "Працоўныя праграмы пазначаны спецыяльнымі значкамі, а таксама бачныя IT-адміністратару" "Зразумела" @@ -204,7 +183,6 @@ "Зразумела" "Прыпыніць працоўныя праграмы" "Актываваць" - "Расклад працоўных праграм" "Фільтр" "Не ўдалося: %1$s" "Прыватная прастора" @@ -217,5 +195,4 @@ "Пераход у прыватную вобласць" "Усталяваць" "Усталяваць праграмы ў прыватнай прасторы" - "Дадавайце файлы і іншае змесціва ў прыватную прастору" diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml index 6721c56d76..d5d948eed1 100644 --- a/res/values-bg/strings.xml +++ b/res/values-bg/strings.xml @@ -29,11 +29,8 @@ "Начален екран" "От настройките задайте %1$s като основното приложение за начален екран" "Разделен екран" - "Промяна на съотношението" "Информация за приложението за %1$s" "Настройки за използването на %1$s" - "Нов прозорец" - "Управление на прозорците" "Запазване на двойката приложения" "%1$s | %2$s" "Тази двойка приложения не се поддържа на устройството" @@ -41,8 +38,6 @@ "Двойката приложения не е налице" "Докоснете и задръжте за преместване на приспособление" "Докоснете двукратно и задръжте за преместване на приспособление или използвайте персонал. действия." - "Още опции" - "Показв. на всички присп." "%1$d × %2$d" "Ширина %1$d и височина %2$d" "%1$s приспособление" @@ -63,19 +58,14 @@ "Приспособления" "Търсене" "Изчистване на текста от полето за търсене" - "Няма налични преки пътища и приспособления" + "Няма налице преки пътища и приспособления" "Няма открити преки пътища или приспособления" "Лични" "Служебни" "Разговори" "Водене на бележки" - "Показване на бутона за добавяне" - "Скриване на бутона за добавяне" "Добавяне" "Добавяне на приспособлението „%1$s“" - "Вижте всички" - "Показване на всички приспособления" - "Показват се всички приспособления" "Докоснете, за да промените настройките на приспособлението" "Промяна на настройките на приспособлението" "Търсене в приложенията" @@ -83,7 +73,6 @@ "Няма намерени приложения, съответстващи на „%1$s“" "Приложение" "Всички приложения" - "Списък с приложения" "Известия" "Докоснете и задръжте за преместване на пряк път." "Докоснете двукратно и задръжте за преместване на пряк път или използвайте персонализирани действия." @@ -101,7 +90,6 @@ "Инсталиране" "Без предлагане на приложение" "Фиксиране на предвиждането" - "Балонче" "инсталиране на преки пътища" "Разрешава на приложението да добавя преки пътища без намеса на потребителя." "четене на настройките и преките пътища на началния екран" @@ -118,8 +106,6 @@ "Страница %1$d от %2$d" "Начален екран %1$d от %2$d" "Нова страница на началния екран" - "Активно" - "Намалено" "Папката е отворена – %1$d на %2$d" "Докоснете, за да затворите папката" "Докоснете, за да запазите новото име" @@ -127,7 +113,6 @@ "Папката е преименувана на „%1$s“" "Папка: „%1$s“ – %2$d елемента" "Папка: „%1$s“ – %2$d или повече елементи" - "Папка без име" "Двойка приложения: %1$s и %2$s" "Тапет и стил" "Редактиране на началния екран" @@ -135,8 +120,6 @@ "Деактивирано от администратора ви" "Разрешаване на завъртането на началния екран" "При завъртане на телефона" - "Хоризонтален режим" - "Поставете телефона в хоризонтален режим" "Точки за известия" "Вкл." "Изкл." @@ -155,8 +138,7 @@ "%1$s се инсталира, %2$s завършено" "%1$s се изтегля. Завършено: %2$s" "%1$s изчаква инсталиране" - "Приложението %1$s е архивирано." - "изтегляне и възстановяване" + "Приложението %1$s е архивирано. Докоснете за изтегляне и възстановяване." "Изисква се актуализация на приложението" "Приложението за тази икона не е актуализирано. Можете да го актуализирате ръчно, за да активирате отново този пряк път, или да премахнете иконата." "Актуализиране" @@ -165,6 +147,7 @@ "Списъкът с приспособления е затворен" "Добавяне към началния екран" "Преместване на елемента тук" + "Елементът е добавен към началния екран" "Елементът е премахнат" "Отмяна" "Преместване на елемента" @@ -184,15 +167,11 @@ "Намаляване на ширината" "Намаляване на височината" "Приспособлението е преоразмерено към ширина %1$s и височина %2$s" - "Меню за клавишните комбинации" - "Рамка за преоразмеряване на приспособлението за %1$s" - "Затваряне" + "Преки пътища" "Отхвърляне" "Затваряне" "Лични" "Служебни" - "Раздел „Лични приложения“" - "Раздел „Служебни приложения“" "Служебен потребителски профил" "Служебните приложения са означени със значка и са видими за системния администратор" "Разбрах" @@ -204,12 +183,11 @@ "Разбрах" "Поставяне на пауза на служебните приложения" "Отмяна на паузата" - "График за служебните приложения" "Филтър" "Неуспешно: %1$s" "Частно пространство" "Докоснете за настройване или отваряне" - "Частно" + "Лично" "Настройки за частното пространство" "Частно, отключено." "Частно, заключено." @@ -217,5 +195,4 @@ "Преминаване към частното пространство" "Инсталиране" "Инсталиране на приложения в частно пространство" - "Добавяне на файлове и др. в частно пространство" diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml index 5e90fbe104..cf75fb58d5 100644 --- a/res/values-bn/strings.xml +++ b/res/values-bn/strings.xml @@ -29,11 +29,8 @@ "হোম" "সেটিংসে গিয়ে %1$s অ্যাপকে ডিফল্ট হোম অ্যাপ হিসেবে সেট করুন" "স্প্লিট স্ক্রিন" - "অ্যাস্পেক্ট রেশিও পরিবর্তন করুন" "%1$s-এর জন্য অ্যাপ সম্পর্কিত তথ্য" "%1$s-এর জন্য ব্যবহারের সেটিংস" - "নতুন উইন্ডো" - "উইন্ডো ম্যানেজ করুন" "অ্যাপ পেয়ার সেভ করুন" "%1$s | %2$s" "এই ডিভাইসে এই অ্যাপ পেয়ারটি কাজ করে না" @@ -41,8 +38,6 @@ "অ্যাপ পেয়ার উপলভ্য নেই" "কোনও উইজেট সরাতে সেটি টাচ করে ধরে রাখুন।" "একটি উইজেট সরাতে বা কাস্টম অ্যাকশন ব্যবহার করতে ডবল ট্যাপ করে ধরে রাখুন।" - "আরও বিকল্প" - "সব উইজেট দেখুন" "%1$d × %2$d" "%2$d উচ্চতা অনুযায়ী %1$d প্রস্থ" "%1$sটি উইজেট" @@ -69,13 +64,8 @@ "অফিস" "কথোপকথন" "নোট নেওয়া" - "যোগ করার বোতাম দেখুন" - "যোগ করার বোতাম লুকান" "যোগ করুন" "%1$s উইজেট যোগ করুন" - "সব দেখুন" - "সব উইজেট দেখুন" - "সব উইজেট দেখানো হচ্ছে" "উইজেট সেটিংস পরিবর্তন করতে ট্যাপ করুন" "উইজেট সেটিংস পরিবর্তন করুন" "অ্যাপ খুঁজুন" @@ -83,7 +73,6 @@ "\"%1$s\" এর সাথে মেলে এমন কোনো অ্যাপ পাওয়া যায়নি" "অ্যাপ" "সব অ্যাপ" - "অ্যাপ তালিকা" "বিজ্ঞপ্তি" "একটি শর্টকাট সরাতে টাচ করে ধরে রাখুন।" "একটি শর্টকাট সরাতে বা কাস্টম অ্যাকশন ব্যবহার করতে ডবল ট্যাপ করে ধরে রাখুন।" @@ -101,7 +90,6 @@ "ইনস্টল করুন" "অ্যাপ সাজেস্ট করবেন না" "আপনার প্রয়োজন হতে পারে এমন অ্যাপ পিন করুন" - "বাবল" "শর্টকাটগুলি ইনস্টল করে" "একটি অ্যাপ্লিকেশানকে ব্যবহারকারীর হস্তক্ষেপ ছাড়াই শর্টকাটগুলি যোগ করার অনুমতি দেয়৷" "হোম স্ক্রিনে সেটিংস ও শর্টকাট পড়ুন" @@ -118,8 +106,6 @@ "%2$dটির মধ্যে %1$dটি পৃষ্ঠা" "%2$dটির %1$d নম্বর হোম স্ক্রিন" "নতুন হোম স্ক্রীনের পৃষ্ঠা" - "অ্যাক্টিভ" - "ছোট করা হয়েছে" "ফোল্ডার খোলা হয়েছে, %1$d বাই %2$d" "ফোল্ডার বন্ধ করতে আলতো চাপ দিন" "পুনঃনামকরণ সংরক্ষণ করতে আলতো চাপ দিন" @@ -127,7 +113,6 @@ "ফোল্ডারের নাম পাল্টে %1$s করা হয়েছে" "ফোল্ডার: %1$s, %2$dটি আইটেম" "ফোল্ডার: %1$s, %2$dটি বা তার বেশি আইটেম" - "নামবিহীন ফোল্ডার" "অ্যাপ পেয়ার: %1$s%2$s" "ওয়ালপেপার এবং স্টাইল" "হোম স্ক্রিন এডিট করুন" @@ -135,8 +120,6 @@ "আপনার প্রশাসক দ্বারা অক্ষম করা হয়েছে" "হোম স্ক্রিন রোটেট করার অনুমতি দিন" "যখন ফোনটি ঘোরানো হয়" - "ভূদৃশ্য মোড" - "ভূদৃশ্য মোডে ফোন সেট করুন" "বিজ্ঞপ্তি ডট" "চালু করা আছে" "বন্ধ" @@ -155,8 +138,7 @@ "%1$s ইনস্টল করা হচ্ছে, %2$s সম্পূর্ণ হয়েছে" "%1$s ডাউনলোড হচ্ছে %2$s সম্পন্ন হয়েছে" "%1$s ইনস্টলের অপেক্ষায় রয়েছে" - "%1$s আর্কাইভ করা হয়েছে।" - "ডাউনলোড করুন ও ফিরিয়ে আনুন" + "%1$s আর্কাইভ করা হয়েছে। ডাউনলোড করতে এবং ফিরিয়ে আনতে ট্যাপ করুন।" "অ্যাপটি আপডেট করা প্রয়োজন" "এই আইকনের জন্য অ্যাপটি আপডেট করা নেই। এই শর্টকার্ট আবার চালু করতে, আপনি ম্যানুয়ালি আপডেট করতে বা সরিয়ে দিতে পারবেন।" "আপডেট করুন" @@ -165,6 +147,7 @@ "উইজেটের তালিকা বন্ধ করা হয়েছে" "হোম স্ক্রিনে যোগ করুন" "এখানে আইটেম সরান" + "হোম স্ক্রীনে আইটেম যোগ করা হয়েছে" "আইটেম সরানো হয়েছে" "ফিরিয়ে আনুন" "আইটেম সরান" @@ -184,15 +167,11 @@ "প্রস্থ কমান" "উচ্চতা কমান" "উইজেটের আকার প্রস্থ %1$s উচ্চতা %2$s তে পরিবর্তন করা হয়েছে" - "শর্টকাট মেনু" - "%1$s-এর জন্য উইজেট ছোট বড় করার ফ্রেম" - "বন্ধ করুন" + "শর্টকাট" "খারিজ করুন" "বন্ধ করুন" "ব্যক্তিগত" "অফিস" - "ব্যক্তিগত অ্যাপ সম্পর্কিত ট্যাব" - "অফিস অ্যাপ সম্পর্কিত ট্যাব" "অফিসের প্রোফাইল" "অফিস অ্যাপে ব্যাজ যোগ করা হয়েছে এবং আপনার আইটি অ্যাডমিন সেগুলি দেখতে পাবেন" "বুঝেছি" @@ -204,12 +183,11 @@ "বুঝেছি" "অফিসের অ্যাপ পজ করুন" "আনপজ করুন" - "অফিসের অ্যাপের শিডিউল" "ফিল্টার" "কাজটি করা যায়নি: %1$s" "ব্যক্তিগত স্পেস" "সেট-আপ করতে বা খুলতে ট্যাপ করুন" - "প্রাইভেট" + "ব্যক্তিগত" "ব্যক্তিগত স্পেসের সেটিংস" "ব্যক্তিগত, আনলক করা আছে।" "ব্যক্তিগত, লক করা আছে।" @@ -217,5 +195,4 @@ "ব্যক্তিগত স্পেস ট্রানজিট করা" "ইনস্টল করুন" "প্রাইভেট স্পেসে অ্যাপ ইনস্টল করুন" - "প্রাইভেট স্পেসে ফাইল ও আরও অনেক কিছু যোগ করুন" diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml index 714807e0b2..1758c39bd8 100644 --- a/res/values-bs/strings.xml +++ b/res/values-bs/strings.xml @@ -29,11 +29,8 @@ "Početni ekran" "Postavite %1$s kao zadanu aplikaciju za početni ekran u Postavkama" "Podijeljeni ekran" - "Promijenite format slike" "Informacije o aplikaciji %1$s" "Postavke korištenja za: %1$s" - "Novi prozor" - "Upravljajte prozorima" "Sačuvaj par aplikacija" "%1$s | %2$s" "Par aplikacija nije podržan na uređaju" @@ -41,8 +38,6 @@ "Par aplikacija nije dostupan" "Dodirnite i zadržite da pomjerite vidžet." "Dvaput dodirnite i zadržite da pomjerite vidžet ili da koristite prilagođene radnje." - "Više opcija" - "Prikaži sve vidžete" "%1$d × %2$d" "Širina %1$d, visina %2$d" "Vidžet %1$s" @@ -69,13 +64,8 @@ "Posao" "Razgovori" "Pisanje bilješki" - "Prikazivanje dugmeta za dodavanje" - "Sakrivanje dugmeta za dodavanje" "Dodajte" "Dodavanje vidžeta %1$s" - "Prikaži sve" - "Prikaz svih vidžeta" - "Prikazivanje svih vidžeta" "Dodirnite da promijenite postavke vidžeta" "Promjena postavki vidžeta" "Pretražite aplikacije" @@ -83,7 +73,6 @@ "Nije pronađena nijedna aplikacija za upit \"%1$s\"" "Aplikacija" "Sve aplikacije" - "Lista aplikacija" "Obavještenja" "Dodirnite i zadržite da pomjerite prečicu." "Dvaput dodirnite i zadržite da pomjerite prečicu ili da koristite prilagođene radnje." @@ -101,7 +90,6 @@ "Instaliraj" "Ne predlaži aplikaciju" "Zakači predviđanje" - "Oblačić" "instaliraj prečice" "Dopušta aplikaciji dodavanje prečica bez posredovanja korisnika." "čita postavke na početnom ekranu i prečice" @@ -118,8 +106,6 @@ "Strana %1$d od %2$d" "Početni ekran %1$d od %2$d" "Nova stranica početnog ekrana" - "Aktivno" - "Minimizirano" "Folder je otvoren, (š) %1$d (v) %2$d" "Dodirnite da zatvorite folder" "Dodirnite da sačuvate promjenu naziva" @@ -127,7 +113,6 @@ "Ime foldera je promijenjeno u %1$s" "Folder: %1$s, br. stavki: %2$d" "Folder: %1$s, %2$d ili više stavki" - "Neimenovani folder" "Par aplikacija: %1$s i %2$s" "Pozadinska slika i stil" "Uredi Početni ekran" @@ -135,8 +120,6 @@ "Onemogućio vaš administrator" "Dozvoli rotiranje početnog ekrana" "Kada se telefon zarotira" - "Vodoravni način" - "Postavite telefon u vodoravni način" "Tačke za obavještenja" "Uključeno" "Isključeno" @@ -145,7 +128,7 @@ "Promijeni postavke" "Prikaži tačke za obavještenja" "Opcije za programere" - "Dodavanje ikona aplikacija na početni ekran" + "Dodaj ikone aplikacija na početni ekran" "Za nove aplikacije" "Nepoznato" "Ukloni" @@ -155,8 +138,7 @@ "Instaliranje aplikacije %1$s, završeno je %2$s" "%1$s se preuzima, završeno %2$s" "%1$s čeka da se instalira" - "Arhivirana je aplikacija %1$s." - "preuzimanje i vraćanje" + "Arhivirana je aplikacija %1$s. Dodirnite da je preuzmete i vratite." "Potrebno je ažurirati aplikaciju" "Aplikacija za ovu ikonu nije ažurirana. Možete je ažurirati ručno da ponovo omogućite ovu prečicu ili možete ukloniti ikonu." "Ažuriraj" @@ -165,6 +147,7 @@ "Spisak vidžeta je zatvoren" "Dodavanje na početni ekran" "Premjesti stavku ovdje" + "Stavka je dodana na Početni ekran." "Stavka je uklonjena" "Poništi" "Premjesti stavku" @@ -184,15 +167,11 @@ "Smanji širinu" "Smanji visinu" "Veličina vidžeta je promijenjena na širinu %1$s visinu %2$s" - "Meni prečica" - "Promjena veličine okvira vidžeta za aplikaciju %1$s" - "Zatvaranje" + "Prečice" "Odbaci" "Zatvaranje" "Lično" "Poslovno" - "Kartica ličnih aplikacija" - "Kartica poslovnih aplikacija" "Radni profil" "Poslovne aplikacije su označene i vaš IT administrator ih može vidjeti" "Razumijem" @@ -204,7 +183,6 @@ "Razumijem" "Pauziraj poslovne aplikacije" "Ponovo pokreni" - "Raspored poslovnih aplikacija" "Filtrirajte" "Nije uspjelo: %1$s" "Privatni prostor" @@ -217,5 +195,4 @@ "Prelazak u privatan prostor" "Instalirajte" "Instaliranje aplikacija u privatni prostor" - "Dodavanje fajlova i drugih stavki u privatni prostor" diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml index f0f652c968..bf578f5cf1 100644 --- a/res/values-ca/strings.xml +++ b/res/values-ca/strings.xml @@ -29,11 +29,8 @@ "Inici" "Defineix %1$s com a aplicació d\'inici predeterminada a Configuració" "Pantalla dividida" - "Canvia la relació d\'aspecte" "Informació de l\'aplicació %1$s" "Configuració d\'ús de %1$s" - "Finestra nova" - "Gestiona les finestres" "Desa la parella d\'aplicacions" "%1$s | %2$s" "Aquesta parella d\'aplicacions no s\'admet en aquest dispositiu" @@ -41,8 +38,6 @@ "La parella d\'aplicacions no està disponible" "Fes doble toc i mantén premut per moure un widget." "Fes doble toc i mantén premut per moure un widget o per utilitzar accions personalitzades." - "Més opcions" - "Mostra tots els widgets" "%1$d × %2$d" "%1$d d\'amplada per %2$d d\'alçada" "Widget de %1$s" @@ -69,13 +64,8 @@ "Treball" "Converses" "Presa de notes" - "Mostra el botó Afegeix" - "Amaga el botó Afegeix" "Afegeix" "Afegeix el widget %1$s" - "Mostra-ho tot" - "Mostra tots els widgets" - "S\'estan mostrant tots els widgets" "Toca per canviar la configuració del widget" "Canvia la configuració del widget" "Cerca aplicacions" @@ -83,7 +73,6 @@ "No s\'ha trobat cap aplicació que coincideixi amb \"%1$s\"" "Aplicació" "Totes les aplicacions" - "Llista d\'aplicacions" "Notificacions" "Fes doble toc i mantén premut per moure una drecera." "Fes doble toc i mantén premut per moure una drecera o per utilitzar accions personalitzades." @@ -101,7 +90,6 @@ "Instal·la" "No suggereixis l\'aplicació" "Fixa la predicció" - "Bombolla" "instal·la dreceres" "Permet que una aplicació afegeixi dreceres sense la intervenció de l\'usuari." "llegir la configuració i les dreceres de la pantalla d\'inici" @@ -118,8 +106,6 @@ "Pàgina %1$d de %2$d" "Pantalla d\'inici %1$d de %2$d" "Pàgina de la pantalla d\'inici nova" - "Actiu" - "Minimitzat" "S\'ha obert la carpeta, %1$d per %2$d" "Toca per tancar la carpeta" "Toca per desar el nom nou" @@ -127,7 +113,6 @@ "S\'ha canviat el nom de la carpeta a %1$s" "Carpeta: %1$s, %2$d elements" "Carpeta: %1$s, %2$d o més elements" - "Carpeta sense nom" "Parella d\'aplicacions: %1$s i %2$s" "Estil i fons de pantalla" "Edita la pantalla d\'inici" @@ -135,8 +120,6 @@ "Desactivada per l\'administrador" "Permet la rotació de la pantalla d\'inici" "En girar el telèfon" - "Mode horitzontal" - "Posa el telèfon en mode horitzontal" "Punts de notificació" "Activats" "Desactivats" @@ -155,8 +138,7 @@ "S\'està instal·lant %1$s; s\'ha completat un %2$s" "S\'està baixant %1$s, %2$s completat" "S\'està esperant per instal·lar %1$s" - "L\'aplicació %1$s està arxivada." - "baixa i restaura" + "L\'aplicació %1$s està arxivada. Toca per baixar-la i restaurar-la." "Cal actualitzar l\'aplicació" "L\'aplicació d\'aquesta icona no està actualitzada. Pots actualitzar-la manualment per tornar a activar aquesta drecera o pots suprimir la icona." "Actualitza" @@ -165,6 +147,7 @@ "S\'ha tancat la llista de widgets" "Afegeix a la pantalla d\'inici" "Mou l\'element aquí" + "S\'ha afegit l\'element a la pantalla d\'inici" "S\'ha suprimit l\'element" "Desfés" "Desplaça l\'element" @@ -184,15 +167,11 @@ "Redueix l\'amplada" "Redueix l\'alçada" "S\'ha canviat la mida del widget a l\'amplada %1$s i l\'alçada %2$s" - "Menú de dreceres" - "Marc de canvi de mida del widget per a %1$s" - "Tanca" + "Dreceres" "Ignora" "Tanca" "Personal" "Treball" - "Pestanya Aplicacions personals" - "Pestanya Aplicacions de treball" "Perfil de treball" "Les aplicacions de treball tenen una insígnia i el teu administrador de TI les pot veure" "Entesos" @@ -204,7 +183,6 @@ "Entesos" "Posa en pausa les aplicacions de treball" "Reactiva" - "Programació de les aplicacions de treball" "Filtra" "Error: %1$s" "Espai privat" @@ -217,5 +195,4 @@ "Canvia a Espai privat" "Instal·la" "Instal·la les aplicacions a Espai privat" - "Afegeix fitxers i més a Espai privat" diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml index 5d83b8d9a3..9a8fc6ffb8 100644 --- a/res/values-cs/strings.xml +++ b/res/values-cs/strings.xml @@ -29,11 +29,8 @@ "Domů" "Nastavit %1$s jako výchozí vstupní aplikaci v Nastavení" "Rozdělit obrazovku" - "Změnit poměr stran" "Informace o aplikaci %1$s" "Nastavení využití pro aplikaci %1$s" - "Nové okno" - "Spravovat okna" "Uložit dvojici aplikací" "%1$s | %2$s" "Tento pár aplikací není na tomto zařízení podporován" @@ -41,18 +38,16 @@ "Dvojice aplikací není k dispozici" "Widget přesunete klepnutím a podržením." "Dvojitým klepnutím a podržením přesunete widget, případně použijte vlastní akce." - "Další možnosti" - "Zobrazit všechny widgety" "%1$d × %2$d" "šířka %1$d, výška %2$d" "%1$s widget" "Widget %1$s, šířka %2$d, výška %3$d" - "Pokud chcete widgetem pohybovat po ploše, podržte ho." + "Pokud chcete widgetem pohybovat po ploše, podržte ho" "Přidat na plochu" "Widget %1$s byl přidán na plochu" "Návrhy" "Nejdůležitější aplikace" - "Noviny a časopisy" + "Zprávy a časopisy" "Zábava" "Sociální sítě" "Návrhy pro vás" @@ -69,13 +64,8 @@ "Práce" "Konverzace" "Psaní poznámek" - "Zobrazit tlačítko přidání" - "Skrýt tlačítko přidání" "Přidat" "Přidat widget %1$s" - "Zobrazit vše" - "Zobrazit všechny widgety" - "Zobrazují se všechny widgety" "Klepnutím změníte nastavení widgetu" "Změnit nastavení widgetu" "Hledat v aplikacích" @@ -83,7 +73,6 @@ "Dotazu „%1$s“ neodpovídají žádné aplikace" "Aplikace" "Všechny aplikace" - "Seznam aplikací" "Oznámení" "Klepnutím a podržením přesunete zkratku." "Dvojitým klepnutím a podržením přesunete zkratku, případně použijte vlastní akce." @@ -101,7 +90,6 @@ "Nainstalovat" "Nenavrhovat aplikaci" "Připnout předpověď" - "Bublat" "instalace zástupce" "Umožňuje aplikaci přidat zástupce bez zásahu uživatele." "čtení nastavení a zkratek plochy" @@ -118,8 +106,6 @@ "Strana %1$d z %2$d" "Plocha %1$d z %2$d" "Nová stránka plochy" - "Aktivní" - "Minimalizováno" "Složka otevřena, rozměry %1$d x %2$d" "Klepnutím složku zavřete" "Klepnutím změnu názvu uložíte" @@ -127,16 +113,13 @@ "Složka přejmenována na %1$s" "Složka: %1$s, počet položek: %2$d" "Složka: %1$s, počet položek: %2$d nebo více" - "Nepojmenovaná složka" "Dvojice aplikací: %1$s%2$s" - "Tapety a styl" + "Tapeta a styl" "Upravit plochu" "Nastavení plochy" "Zakázáno administrátorem" "Povolit otáčení plochy" "Při otočení telefonu" - "Režim na šířku" - "Nastavit telefon do režimu na šířku" "Puntíky s oznámením" "Zapnuto" "Vypnuto" @@ -155,8 +138,7 @@ "Instalace aplikace %1$s, dokončeno %2$s" "Stahování aplikace %1$s (dokončeno %2$s)" "Instalace aplikace %1$s čeká na zahájení" - "Aplikace %1$s je archivována." - "stáhnout a obnovit" + "Aplikace %1$s je archivována. Klepnutím ji můžete stáhnout a obnovit." "Je nutná aktualizace aplikace" "Aplikace pro tuto ikonu není nainstalována. Můžete ji ručně aktualizovat, aby zkratka znovu fungovala, případně můžete ikonu odstranit." "Aktualizovat" @@ -165,6 +147,7 @@ "Seznam widgetů zavřen" "Přidat na plochu" "Přesunout položku sem" + "Položka byla přidána na plochu" "Položka byla odstraněna" "Zpět" "Přesunout položku" @@ -184,15 +167,11 @@ "Snížit šířku" "Snížit výšku" "Velikost widgetu upravena: šířka %1$s, výška %2$s" - "Nabídka zkratek" - "Rámec widgetu pro změnu velikosti pro %1$s" - "Zavřít" + "Zkratky" "Zavřít" "Zavřít" "Osobní" "Pracovní" - "Karta osobních aplikací" - "Karta pracovních aplikací" "Pracovní profil" "Pracovní aplikace jsou označené a váš administrátor IT je vidí" "Rozumím" @@ -204,7 +183,6 @@ "OK" "Pozastavit pracovní aplikace" "Zrušit pozastavení" - "Plán pracovních aplikací" "Filtr" "Selhalo: %1$s" "Soukromý prostor" @@ -217,5 +195,4 @@ "Převádění soukromého prostoru" "Nainstalovat" "Instalovat aplikace do soukromého prostoru" - "Přidejte do soukromého prostoru soubory a další položky" diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml index cee6299fc2..208c2ef1d6 100644 --- a/res/values-da/strings.xml +++ b/res/values-da/strings.xml @@ -29,11 +29,8 @@ "Startskærm" "Angiv %1$s som standardstartapp i Indstillinger" "Opdel skærm" - "Skift billedformat" "Appinfo for %1$s" "Indstillinger for brug af %1$s" - "Nyt vindue" - "Administrer vinduer" "Gem appsammenknytning" "%1$s | %2$s" "Denne appsammenknytning understøttes ikke på enheden" @@ -41,8 +38,6 @@ "Appsammenknytning er ikke tilgængelig" "Hold en widget nede for at flytte den." "Tryk to gange, og hold en widget nede for at flytte den eller bruge tilpassede handlinger." - "Flere valgmuligheder" - "Vis alle widgets" "%1$d × %2$d" "%1$d i bredden og %2$d i højden" "Widgetten %1$s" @@ -69,13 +64,8 @@ "Arbejde" "Samtaler" "Notetagning" - "Vis knappen Tilføj" - "Skjul knappen Tilføj" "Tilføj" "Tilføj %1$s-widget" - "Vis alle" - "Vis alle widgets" - "Viser alle widgets" "Tryk for at ændre widgetindstillinger" "Skift widgetindstillinger" "Søg efter apps" @@ -83,7 +73,6 @@ "Der blev ikke fundet nogen apps, som matcher \"%1$s\"" "App" "Alle apps" - "Liste over apps" "Notifikationer" "Hold en genvej nede for at flytte den." "Tryk to gange, og hold en genvej nede for at flytte den eller bruge tilpassede handlinger." @@ -101,7 +90,6 @@ "Installer" "Foreslå ikke en app" "Fastgør forslaget" - "Boble" "installere genveje" "Tillader, at en app tilføjer genveje uden brugerens indgriben." "læs indstillinger og genveje for startskærm" @@ -118,8 +106,6 @@ "Side %1$d ud af %2$d" "Startskærm %1$d ud af %2$d" "Ny startskærm" - "Aktiv" - "Minimeret" "Mappen er åben, %1$d gange %2$d" "Tryk for at lukke mappen" "Tryk for at gemme omdøbningen" @@ -127,7 +113,6 @@ "Mappen er omdøbt til %1$s" "Mappe: %1$s, %2$d elementer" "Mappe: %1$s, %2$d eller flere elementer" - "Unavngiven mappe" "Appsammenknytning: %1$s og %2$s" "Baggrund og stil" "Rediger startskærm" @@ -135,8 +120,6 @@ "Deaktiveret af din administrator" "Tillad rotation af startskærmen" "Når telefonen roteres" - "Liggende format" - "Indstil telefonen til liggende format" "Notifikationsprikker" "Til" "Fra" @@ -155,8 +138,7 @@ "%1$s installeres. %2$s fuldført" "%1$s downloades. %2$s er gennemført" "%1$s venter på at installere" - "%1$s er arkiveret." - "download og gendan" + "%1$s er arkiveret Tryk for at downloade og gendanne." "Appen skal opdateres" "Appen, der tilhører dette ikon, er ikke opdateret. Du kan opdatere appen manuelt for at genaktivere denne genvej, eller du kan fjerne ikonet." "Opdater" @@ -165,6 +147,7 @@ "Listen med widgets blev lukket" "Føj til startskærm" "Flyt elementet hertil" + "Elementet er føjet til startskærmen" "Elementet er fjernet" "Fortryd" "Flyt element" @@ -184,15 +167,11 @@ "Reducer bredden" "Reducer højden" "Størrelsen for widgetten er ændret til bredde %1$s og højde %2$s" - "Genvejsmenu" - "Widget til at tilpasse størrelsen på rammen for %1$s" - "Luk" + "Genveje" "Afvis" "Luk" - "Personlig" + "Personlige" "Arbejde" - "Fanen Personlige apps" - "Fanen Arbejdsapps" "Arbejdsprofil" "Arbejdsapps har badges og kan ses af din it-administrator" "OK" @@ -204,7 +183,6 @@ "OK" "Sæt arbejdsapps på pause" "Genoptag" - "Tidsplan for arbejdsapps" "Filter" "Mislykket: %1$s" "Privat område" @@ -217,5 +195,4 @@ "Ændringer af tilstanden for det private område" "Installer" "Installer apps i privat område" - "Føj filer m.m. til dit private område" diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml index 6fcae2e09f..380030b981 100644 --- a/res/values-de/strings.xml +++ b/res/values-de/strings.xml @@ -29,11 +29,8 @@ "Startbildschirm" "%1$s in den Einstellungen als Stand-Start-App festlegen" "Splitscreen" - "Seitenverhältnis ändern" "App-Info für %1$s" "Nutzungseinstellungen für %1$s" - "Neues Fenster" - "Fenster verwalten" "App-Paar speichern" "%1$s | %2$s" "Dieses App-Paar wird auf diesem Gerät nicht unterstützt" @@ -41,8 +38,6 @@ "App-Paar nicht verfügbar" "Zum Verschieben des Widgets gedrückt halten" "Doppeltippen und halten, um ein Widget zu bewegen oder benutzerdefinierte Aktionen zu nutzen." - "Weitere Optionen" - "Alle Widgets anzeigen" "%1$d × %2$d" "%1$d breit und %2$d hoch" "Widget „%1$s“" @@ -69,13 +64,8 @@ "Geschäftlich" "Unterhaltungen" "Notizen" - "Schaltfläche „Hinzufügen“ anzeigen" - "Schaltfläche „Hinzufügen“ ausblenden" "Hinzufügen" "Widget „%1$s“ hinzufügen" - "Alle anzeigen" - "Alle Widgets anzeigen" - "Es werden alle Widgets angezeigt" "Tippen, um die Widget-Einstellungen zu ändern" "Widget-Einstellungen ändern" "Apps finden" @@ -83,7 +73,6 @@ "Keine Apps für \"%1$s\" gefunden" "App" "Alle Apps" - "Liste der Apps" "Benachrichtigungen" "Zum Verschieben einer Verknüpfung gedrückt halten" "Doppeltippen und halten, um eine Verknüpfung zu bewegen oder benutzerdefinierte Aktionen zu nutzen." @@ -101,7 +90,6 @@ "Installieren" "App nicht vorschlagen" "Vorgeschlagene App fixieren" - "Bubble" "Verknüpfungen installieren" "Ermöglicht einer App das Hinzufügen von Verknüpfungen ohne Eingreifen des Nutzers" "Einstellungen und Verknüpfungen auf dem Startbildschirm lesen" @@ -118,8 +106,6 @@ "Seite %1$d von %2$d" "Startbildschirm %1$d von %2$d" "Neue Startbildschirmseite" - "Aktiv" - "Minimiert" "Ordner geöffnet, %1$d x %2$d" "Ordner zum Schließen antippen" "Neuen Namen zum Speichern antippen" @@ -127,7 +113,6 @@ "Ordner umbenannt in %1$s" "Ordner: %1$s, %2$d Elemente" "Ordner: %1$s, %2$d oder mehr Elemente" - "Unbenannter Ordner" "App-Paar: %1$s und %2$s" "Hintergrund & Stil" "Startbildschirm bearbeiten" @@ -135,8 +120,6 @@ "Von deinem Administrator deaktiviert" "Drehen des Startbildschirms zulassen" "Beim Drehen des Smartphones" - "Querformat" - "Smartphone auf Querformat einstellen" "App-Benachrichtigungspunkte" "An" "Aus" @@ -155,8 +138,7 @@ "%1$s wird installiert, %2$s abgeschlossen" "%1$s wird heruntergeladen, %2$s abgeschlossen" "Warten auf Installation von %1$s" - "%1$s ist archiviert." - "herunterladen und wiederherstellen" + "%1$s ist archiviert. Tippe, um die App herunterzuladen und wiederherzustellen." "App-Update erforderlich" "Die App für dieses Symbol wurde noch nicht aktualisiert. Du kannst sie manuell aktualisieren, um die Verknüpfung wieder zu aktivieren, oder das Symbol entfernen." "Aktualisieren" @@ -165,6 +147,7 @@ "Widgetliste geschlossen" "Zum Startbildschirm hinzufügen" "Element hierhin verschieben" + "Element zum Startbildschirm hinzugefügt" "Element entfernt" "Rückgängig" "Element verschieben" @@ -184,15 +167,11 @@ "Breite verringern" "Höhe verringern" "Größe des Widgets zu Breite %1$s und Höhe %2$s geändert" - "Menü für Tastenkombinationen" - "Frame „Widget-Größe anpassen“ für %1$s" - "Schließen" + "Verknüpfungen" "Schließen" "Schließen" "Privat" "Geschäftlich" - "Tab „Private Apps“" - "Tab „Geschäftliche Apps“" "Arbeitsprofil" "Geschäftliche Apps sind gekennzeichnet und für deinen IT-Administrator sichtbar" "OK" @@ -204,7 +183,6 @@ "OK" "Geschäftliche Apps pausieren" "Nicht mehr pausieren" - "Zeitplan für geschäftliche Apps" "Filter" "Fehler: %1$s" "Vertrauliches Profil" @@ -217,5 +195,4 @@ "Sperrzustand des vertraulichen Profils wird gerade geändert" "Installieren" "Apps im vertraulichen Profil installieren" - "Dateien und mehr zum vertraulichen Profil hinzufügen" diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml index 0aaac0b491..e86ebae4d9 100644 --- a/res/values-el/strings.xml +++ b/res/values-el/strings.xml @@ -29,11 +29,8 @@ "Αρχική οθόνη" "Ορίστε το %1$s ως την προεπιλεγμένη εφαρμογή αρχικής οθόνης στις Ρυθμίσεις" "Διαχωρισμός οθόνης" - "Αλλαγή λόγου διαστάσεων" "Πληροφορίες εφαρμογής για %1$s" "Ρυθμίσεις χρήσης για %1$s" - "Νέο παράθυρο" - "Διαχείριση παραθύρων" "Αποθήκευση ζεύγους εφαρμογών" "%1$s | %2$s" "Αυτό το ζεύγος εφαρμογών δεν υποστηρίζεται σε αυτή τη συσκευή" @@ -41,8 +38,6 @@ "Το ζεύγος εφαρμογών δεν είναι διαθέσιμο" "Πατήστε παρατετ. για μετακίνηση γραφ. στοιχείου." "Πατήστε δύο φορές παρατεταμένα για μετακίνηση γραφικού στοιχείου ή χρήση προσαρμοσμένων ενεργειών." - "Περισσότερες επιλογές" - "Εμφ. συνόλου γραφ. στοιχ." "%1$d × %2$d" "Πλάτος %1$d επί ύψος %2$d" "Γραφικό στοιχείο %1$s" @@ -67,15 +62,10 @@ "Δεν βρέθηκαν γραφικά στοιχεία ή συντομεύσεις." "Προσωπικά" "Εργασίας" - "Συνομιλίες" + "Συζητήσεις" "Δημιουργία σημειώσεων" - "Εμφάνιση κουμπιού προσθήκης" - "Απόκρυψη κουμπιού προσθήκης" "Προσθήκη" "Προσθήκη του γραφικού στοιχείου %1$s" - "Εμφάνιση όλων" - "Εμφάνιση συνόλου γραφικών στοιχείων" - "Εμφάνιση όλων των γραφικών στοιχείων" "Πατήστε για αλλαγή των ρυθμίσεων του γραφικού στοιχείου" "Αλλαγή ρυθμίσεων γραφικού στοιχείου" "Αναζήτηση εφαρμογών" @@ -83,7 +73,6 @@ "Δεν βρέθηκαν εφαρμογές αντιστοίχισης για \"%1$s\"" "Εφαρμογή" "Όλες οι εφαρμογές" - "Λίστα εφαρμογών" "Ειδοποιήσεις" "Πατήστε παρατεταμένα για μετακίνηση συντόμευσης." "Πατήστε δύο φορές παρατεταμένα για μετακίνηση συντόμευσης ή χρήση προσαρμοσμένων ενεργειών." @@ -101,7 +90,6 @@ "Εγκατάσταση" "Να μην προτείνεται" "Καρφίτσωμα πρόβλεψης" - "Συννεφάκι" "εγκατάσταση συντομεύσεων" "Επιτρέπει σε μια εφαρμογή την προσθήκη συντομεύσεων χωρίς την παρέμβαση του χρήστη." "ανάγνωση ρυθμίσεων και συντομεύσεων αρχικής οθόνης" @@ -118,8 +106,6 @@ "Σελίδα %1$d από %2$d" "Αρχική οθόνη %1$d από %2$d" "Νέα σελίδα αρχικής οθόνης" - "Ενεργό" - "Ελαχιστοποιήθηκε" "Άνοιγμα φακέλου, %1$d επί %2$d" "Πατήστε για να κλείσετε το φάκελο" "Πατήστε για να αποθηκεύσετε τη νέα ονομασία" @@ -127,16 +113,13 @@ "Ο φάκελος μετονομάστηκε σε %1$s" "Φάκελος: %1$s, %2$d στοιχεία" "Φάκελος: %1$s, %2$d ή περισσότερα στοιχεία" - "Φάκελος χωρίς όνομα" "Ζεύγος εφαρμογών: %1$s και %2$s" "Ταπετσαρία και στιλ" "Επεξεργασία αρχικής οθόνης" - "Ρυθμ. Αρχικής οθόνης" + "Ρυθμίσεις Αρχ. Οθ." "Απενεργοποιήθηκε από τον διαχειριστή σας" "Να επιτρέπεται η περιστροφή της αρχικής οθόνης" "Όταν το τηλέφωνο περιστρέφεται" - "Οριζόντιος προσανατολισμός" - "Ορισμός τηλεφώνου σε οριζόντιο προσανατολισμό" "Κουκκίδες ειδοποίησης" "Ενεργοποίηση" "Απενεργοποίηση" @@ -155,8 +138,7 @@ "Έχει ολοκληρωθεί το %2$s της εγκατάστασης της εφαρμογής %1$s" "Λήψη %1$s, ολοκληρώθηκε %2$s" "%1$s σε αναμονή για εγκατάσταση" - "Η εφαρμογή %1$s είναι αρχειοθετημένη." - "λήψη και επαναφορά" + "Η εφαρμογή %1$s είναι αρχειοθετημένη. Πατήστε για λήψη και επαναφορά." "Απαιτείται ενημέρωση της εφαρμογής" "Η εφαρμογή για αυτό το εικονίδιο δεν έχει ενημερωθεί. Μπορείτε να την ενημερώσετε μη αυτόματα για να ενεργοποιήσετε ξανά τη συγκεκριμένη συντόμευση ή να καταργήσετε το εικονίδιο." "Ενημέρωση" @@ -165,6 +147,7 @@ "Η λίστα γραφικών στοιχείων έκλεισε" "Προσθήκη στην αρχική οθόνη" "Μετακίνηση στοιχείου εδώ" + "Το στοιχείο προστέθηκε στην αρχική οθόνη" "Το στοιχείο καταργήθηκε" "Αναίρεση" "Μετακίνηση στοιχείου" @@ -184,15 +167,11 @@ "μείωση του πλάτους" "Μείωση του ύψους" "Έγινε προσαρμογή του μεγέθους του γραφικού στοιχείου σε %1$s πλάτος και %2$s ύψος" - "Μενού συντομεύσεων" - "Πλαίσιο αλλαγής μεγέθους γραφικού στοιχείου για %1$s" - "Κλείσιμο" + "Συντομεύσεις" "Παράβλεψη" "Κλείσιμο" "Προσωπικές" "Εργασίας" - "Καρτέλα προσωπικών εφαρμογών" - "Καρτέλα εφαρμογών εργασιών" "Προφίλ εργασίας" "Οι εφαρμογές εργασιών φέρουν σήμα και είναι ορατές στον διαχειριστή IT σας" "Το κατάλαβα" @@ -204,7 +183,6 @@ "Το κατάλαβα" "Παύση εφαρμογών εργασιών" "Αναίρεση παύσης" - "Πρόγραμμα εφαρμογών εργασιών" "Φίλτρο" "Αποτυχία: %1$s" "Ιδιωτικός χώρος" @@ -217,5 +195,4 @@ "Μετάβαση στον Ιδιωτικό χώρο" "Εγκατάσταση" "Εγκατάσταση εφαρμογών στον ιδιωτικό χώρο" - "Προσθέστε αρχεία και άλλα στοιχεία στον Ιδιωτικό χώρο" diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml index 3b12d259ae..50c597690f 100644 --- a/res/values-en-rAU/strings.xml +++ b/res/values-en-rAU/strings.xml @@ -29,11 +29,8 @@ "Home" "Set %1$s as the default home app in Settings" "Split screen" - "Change aspect ratio" "App info for %1$s" "Usage settings for %1$s" - "New window" - "Manage windows" "Save app pair" "%1$s | %2$s" "This app pair isn\'t supported on this device" @@ -41,8 +38,6 @@ "App pair isn\'t available" "Touch and hold to move a widget." "Double-tap & hold to move a widget or use custom actions." - "More options" - "Show all widgets" "%1$d × %2$d" "%1$d wide by %2$d high" "%1$s widget" @@ -69,13 +64,8 @@ "Work" "Conversations" "Note-taking" - "Show add button" - "Hide add button" "Add" "Add %1$s widget" - "Show all" - "Show all widgets" - "Showing all widgets" "Tap to change widget settings" "Change widget settings" "Search apps" @@ -83,7 +73,6 @@ "No apps found matching \'%1$s\'" "App" "All apps" - "Apps list" "Notifications" "Touch & hold to move a shortcut." "Double-tap & hold to move a shortcut or use custom actions." @@ -101,7 +90,6 @@ "Install" "Don\'t suggest app" "Pin prediction" - "Bubble" "install shortcuts" "Allows an app to add shortcuts without user intervention." "read Home settings and shortcuts" @@ -118,8 +106,6 @@ "Page %1$d of %2$d" "Home screen %1$d of %2$d" "New home screen page" - "Active" - "Minimised" "Folder opened, %1$d by %2$d" "Tap to close folder" "Tap to save rename" @@ -127,7 +113,6 @@ "Folder renamed to %1$s" "Folder: %1$s, %2$d items" "Folder: %1$s, %2$d or more items" - "Unnamed folder" "App pair: %1$s and %2$s" "Wallpaper and style" "Edit home screen" @@ -135,8 +120,6 @@ "Disabled by your admin" "Allow home screen rotation" "When phone is rotated" - "Landscape mode" - "Set phone to landscape mode" "Notification dots" "On" "Off" @@ -155,8 +138,7 @@ "%1$s installing, %2$s complete" "%1$s downloading, %2$s complete" "%1$s waiting to install" - "%1$s is archived." - "download and restore" + "%1$s is archived. Tap to download and restore." "App update required" "The app for this icon isn\'t updated. You can update manually to re-enable this shortcut or remove the icon." "Update" @@ -165,6 +147,7 @@ "Widgets list closed" "Add to home screen" "Move item here" + "Item added to home screen" "Item removed" "Undo" "Move item" @@ -184,15 +167,11 @@ "Decrease width" "Decrease height" "Widget re-sized to width %1$s height %2$s" - "Shortcut menu" - "Widget resize frame for %1$s" - "Close" + "Short cuts" "Dismiss" "Close" "Personal" "Work" - "Personal apps tab" - "Work apps tab" "Work profile" "Work apps are badged and visible to your IT admin" "OK" @@ -204,7 +183,6 @@ "OK" "Pause work apps" "Unpause" - "Work apps schedule" "Filter" "Failed: %1$s" "Private space" @@ -217,5 +195,4 @@ "Private Space transitioning" "Install" "Install apps to private space" - "Add files and more to private space" diff --git a/res/values-en-rCA/strings.xml b/res/values-en-rCA/strings.xml index 3aa6607646..08ff6e7f82 100644 --- a/res/values-en-rCA/strings.xml +++ b/res/values-en-rCA/strings.xml @@ -29,11 +29,8 @@ "Home" "Set %1$s as default home app in Settings" "Split screen" - "Change aspect ratio" "App info for %1$s" "Usage settings for %1$s" - "New Window" - "Manage Windows" "Save app pair" "%1$s | %2$s" "This app pair isn\'t supported on this device" @@ -41,8 +38,6 @@ "App pair isn\'t available" "Touch and hold to move a widget." "Double-tap and hold to move a widget or use custom actions." - "More options" - "Show all widgets" "%1$d × %2$d" "%1$d wide by %2$d high" "%1$s widget" @@ -69,13 +64,8 @@ "Work" "Conversations" "Note-taking" - "Show add button" - "Hide add button" "Add" "Add %1$s widget" - "Show all" - "Show all widgets" - "Showing all widgets" "Tap to change widget settings" "Change widget settings" "Search apps" @@ -83,7 +73,6 @@ "No apps found matching \"%1$s\"" "App" "All apps" - "Apps list" "Notifications" "Touch and hold to move a shortcut." "Double-tap and hold to move a shortcut or use custom actions." @@ -101,7 +90,6 @@ "Install" "Don\'t suggest app" "Pin Prediction" - "Bubble" "install shortcuts" "Allows an app to add shortcuts without user intervention." "read home settings and shortcuts" @@ -118,8 +106,6 @@ "Page %1$d of %2$d" "Home screen %1$d of %2$d" "New home screen page" - "Active" - "Minimized" "Folder opened, %1$d by %2$d" "Tap to close folder" "Tap to save rename" @@ -127,7 +113,6 @@ "Folder renamed to %1$s" "Folder: %1$s, %2$d items" "Folder: %1$s, %2$d or more items" - "Unnamed folder" "App pair: %1$s and %2$s" "Wallpaper and style" "Edit Home Screen" @@ -135,8 +120,6 @@ "Disabled by your admin" "Allow home screen rotation" "When phone is rotated" - "Landscape mode" - "Set phone into landscape mode" "Notification dots" "On" "Off" @@ -155,8 +138,7 @@ "%1$s installing, %2$s complete" "%1$s downloading, %2$s complete" "%1$s waiting to install" - "%1$s is archived." - "download and restore" + "%1$s is archived. Tap to download and restore." "App update required" "The app for this icon isn\'t updated. You can update manually to re-enable this shortcut, or remove the icon." "Update" @@ -165,6 +147,7 @@ "Widgets list closed" "Add to home screen" "Move item here" + "Item added to home screen" "Item removed" "Undo" "Move item" @@ -184,15 +167,11 @@ "Decrease width" "Decrease height" "Widget resized to width %1$s height %2$s" - "Shortcut Menu" - "Widget Resize Frame for %1$s" - "Close" + "Shortcuts" "Dismiss" "Close" "Personal" "Work" - "Personal apps tab" - "Work apps tab" "Work profile" "Work apps are badged and visible to your IT admin" "Got it" @@ -204,7 +183,6 @@ "Got it" "Pause work apps" "Unpause" - "Work apps schedule" "Filter" "Failed: %1$s" "Private space" @@ -217,5 +195,4 @@ "Private Space Transitioning" "Install" "Install apps to Private Space" - "Add files and more to Private Space" diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml index 3b12d259ae..50c597690f 100644 --- a/res/values-en-rGB/strings.xml +++ b/res/values-en-rGB/strings.xml @@ -29,11 +29,8 @@ "Home" "Set %1$s as the default home app in Settings" "Split screen" - "Change aspect ratio" "App info for %1$s" "Usage settings for %1$s" - "New window" - "Manage windows" "Save app pair" "%1$s | %2$s" "This app pair isn\'t supported on this device" @@ -41,8 +38,6 @@ "App pair isn\'t available" "Touch and hold to move a widget." "Double-tap & hold to move a widget or use custom actions." - "More options" - "Show all widgets" "%1$d × %2$d" "%1$d wide by %2$d high" "%1$s widget" @@ -69,13 +64,8 @@ "Work" "Conversations" "Note-taking" - "Show add button" - "Hide add button" "Add" "Add %1$s widget" - "Show all" - "Show all widgets" - "Showing all widgets" "Tap to change widget settings" "Change widget settings" "Search apps" @@ -83,7 +73,6 @@ "No apps found matching \'%1$s\'" "App" "All apps" - "Apps list" "Notifications" "Touch & hold to move a shortcut." "Double-tap & hold to move a shortcut or use custom actions." @@ -101,7 +90,6 @@ "Install" "Don\'t suggest app" "Pin prediction" - "Bubble" "install shortcuts" "Allows an app to add shortcuts without user intervention." "read Home settings and shortcuts" @@ -118,8 +106,6 @@ "Page %1$d of %2$d" "Home screen %1$d of %2$d" "New home screen page" - "Active" - "Minimised" "Folder opened, %1$d by %2$d" "Tap to close folder" "Tap to save rename" @@ -127,7 +113,6 @@ "Folder renamed to %1$s" "Folder: %1$s, %2$d items" "Folder: %1$s, %2$d or more items" - "Unnamed folder" "App pair: %1$s and %2$s" "Wallpaper and style" "Edit home screen" @@ -135,8 +120,6 @@ "Disabled by your admin" "Allow home screen rotation" "When phone is rotated" - "Landscape mode" - "Set phone to landscape mode" "Notification dots" "On" "Off" @@ -155,8 +138,7 @@ "%1$s installing, %2$s complete" "%1$s downloading, %2$s complete" "%1$s waiting to install" - "%1$s is archived." - "download and restore" + "%1$s is archived. Tap to download and restore." "App update required" "The app for this icon isn\'t updated. You can update manually to re-enable this shortcut or remove the icon." "Update" @@ -165,6 +147,7 @@ "Widgets list closed" "Add to home screen" "Move item here" + "Item added to home screen" "Item removed" "Undo" "Move item" @@ -184,15 +167,11 @@ "Decrease width" "Decrease height" "Widget re-sized to width %1$s height %2$s" - "Shortcut menu" - "Widget resize frame for %1$s" - "Close" + "Short cuts" "Dismiss" "Close" "Personal" "Work" - "Personal apps tab" - "Work apps tab" "Work profile" "Work apps are badged and visible to your IT admin" "OK" @@ -204,7 +183,6 @@ "OK" "Pause work apps" "Unpause" - "Work apps schedule" "Filter" "Failed: %1$s" "Private space" @@ -217,5 +195,4 @@ "Private Space transitioning" "Install" "Install apps to private space" - "Add files and more to private space" diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml index 3b12d259ae..50c597690f 100644 --- a/res/values-en-rIN/strings.xml +++ b/res/values-en-rIN/strings.xml @@ -29,11 +29,8 @@ "Home" "Set %1$s as the default home app in Settings" "Split screen" - "Change aspect ratio" "App info for %1$s" "Usage settings for %1$s" - "New window" - "Manage windows" "Save app pair" "%1$s | %2$s" "This app pair isn\'t supported on this device" @@ -41,8 +38,6 @@ "App pair isn\'t available" "Touch and hold to move a widget." "Double-tap & hold to move a widget or use custom actions." - "More options" - "Show all widgets" "%1$d × %2$d" "%1$d wide by %2$d high" "%1$s widget" @@ -69,13 +64,8 @@ "Work" "Conversations" "Note-taking" - "Show add button" - "Hide add button" "Add" "Add %1$s widget" - "Show all" - "Show all widgets" - "Showing all widgets" "Tap to change widget settings" "Change widget settings" "Search apps" @@ -83,7 +73,6 @@ "No apps found matching \'%1$s\'" "App" "All apps" - "Apps list" "Notifications" "Touch & hold to move a shortcut." "Double-tap & hold to move a shortcut or use custom actions." @@ -101,7 +90,6 @@ "Install" "Don\'t suggest app" "Pin prediction" - "Bubble" "install shortcuts" "Allows an app to add shortcuts without user intervention." "read Home settings and shortcuts" @@ -118,8 +106,6 @@ "Page %1$d of %2$d" "Home screen %1$d of %2$d" "New home screen page" - "Active" - "Minimised" "Folder opened, %1$d by %2$d" "Tap to close folder" "Tap to save rename" @@ -127,7 +113,6 @@ "Folder renamed to %1$s" "Folder: %1$s, %2$d items" "Folder: %1$s, %2$d or more items" - "Unnamed folder" "App pair: %1$s and %2$s" "Wallpaper and style" "Edit home screen" @@ -135,8 +120,6 @@ "Disabled by your admin" "Allow home screen rotation" "When phone is rotated" - "Landscape mode" - "Set phone to landscape mode" "Notification dots" "On" "Off" @@ -155,8 +138,7 @@ "%1$s installing, %2$s complete" "%1$s downloading, %2$s complete" "%1$s waiting to install" - "%1$s is archived." - "download and restore" + "%1$s is archived. Tap to download and restore." "App update required" "The app for this icon isn\'t updated. You can update manually to re-enable this shortcut or remove the icon." "Update" @@ -165,6 +147,7 @@ "Widgets list closed" "Add to home screen" "Move item here" + "Item added to home screen" "Item removed" "Undo" "Move item" @@ -184,15 +167,11 @@ "Decrease width" "Decrease height" "Widget re-sized to width %1$s height %2$s" - "Shortcut menu" - "Widget resize frame for %1$s" - "Close" + "Short cuts" "Dismiss" "Close" "Personal" "Work" - "Personal apps tab" - "Work apps tab" "Work profile" "Work apps are badged and visible to your IT admin" "OK" @@ -204,7 +183,6 @@ "OK" "Pause work apps" "Unpause" - "Work apps schedule" "Filter" "Failed: %1$s" "Private space" @@ -217,5 +195,4 @@ "Private Space transitioning" "Install" "Install apps to private space" - "Add files and more to private space" diff --git a/res/values-en-rXC/strings.xml b/res/values-en-rXC/strings.xml index 769567cdcf..fa6d1f1a87 100644 --- a/res/values-en-rXC/strings.xml +++ b/res/values-en-rXC/strings.xml @@ -31,7 +31,6 @@ "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‏‏‏‎‎‏‏‎‏‏‎‎‏‎‏‎‎‏‎‏‏‎‎‏‏‏‏‏‏‎‎‎‎‏‏‎‏‏‎‎‏‏‎‎‎‏‏‎‏‎‏‎‏‎‎‏‏‏‏‏‏‎Split screen‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‎‏‎‏‏‏‏‏‏‏‏‎‎‎‎‏‎‎‎‎‏‏‏‎‏‏‏‏‏‏‎‎‏‏‎‏‏‎‎‏‏‏‎‏‎‏‎‏‎‎‎‎‏‎‎‎‏‎‏‎‎‎App info for %1$s‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‎‎‏‎‎‎‎‎‏‏‏‏‎‏‎‎‏‏‏‎‎‎‎‎‏‎‏‎‎‎‎‏‎‎‎‎‎‎‎‏‏‏‎‎‎‎‏‏‏‏‎‎‎‏‎‏‏‏‏‎‏‎Usage settings for %1$s‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‏‏‎‎‏‎‏‏‏‏‏‏‎‏‎‏‎‏‎‏‏‏‎‏‏‏‏‎‎‏‎‎‎‏‎‏‏‏‎‏‏‏‏‎‏‎‏‎‎‎‏‎‎‏‎‏‎‏‎‏‏‎New Window‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‎‏‏‏‎‎‏‏‎‎‎‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‎‏‏‏‏‎‎‏‏‎‎‎‎‏‏‏‎‎‎‎‎‎‏‏‎‎‎‎‏‎‎‎‏‏‎Save app pair‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‏‏‎‎‎‎‎‏‎‎‎‏‏‏‎‎‏‎‏‎‏‏‏‏‎‏‎‏‏‎‎‏‎‎‎‏‏‎‎‎‎‏‏‏‎‏‏‏‏‎‏‎‏‏‎‏‏‏‎‎‏‎‎‏‎‎‏‏‎%1$s‎‏‎‎‏‏‏‎ | ‎‏‎‎‏‏‎%2$s‎‏‎‎‏‏‏‎‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‎‏‏‏‏‏‏‎‏‎‏‎‎‏‎‎‏‏‏‎‎‎‏‏‎‎‏‎‏‏‎‏‏‎‏‎‏‏‏‏‎‎‏‏‎‏‎‎‎‎‏‎‎‏‏‎‎‎‎‎‎‎This app pair isn\'t supported on this device‎‏‎‎‏‎" @@ -39,8 +38,6 @@ "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‏‎‎‎‏‎‏‎‏‏‏‎‎‎‎‏‎‏‏‎‏‎‎‏‎‎‏‎‎‏‎‎‏‎‎‏‎‎‎‎‎‏‎‎‏‏‎‏‏‎‏‏‏‎‎‎‏‏‏‏‏‎App pair isn\'t available‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‏‎‎‎‏‏‏‎‎‏‎‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‏‎‎‏‏‎‎‎‏‎‎‎‎‏‎‏‎‎‏‎‎‎‏‏‎‎‎‎‎‏‎‏‎‎‏‎Touch & hold to move a widget.‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‎‎‏‎‏‏‏‏‎‏‏‏‏‏‎‏‎‏‎‎‎‎‎‎‎‏‏‏‏‎‎‎‎‏‏‎‏‎‏‏‎‎‎‎‎‎‏‎‏‎‎‎‎‎‎‎‎‎‏‏‎‎Double-tap & hold to move a widget or use custom actions.‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‎‎‎‏‎‎‎‏‏‎‎‏‎‏‏‎‎‏‏‎‎‎‎‎‎‎‎‏‎‎‎‎‎‏‎‏‎‎‏‏‏‏‏‏‎‏‎‏‏‎‎‎‎‏‎‎‎‏‎‏‏‎More options‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‎‏‏‏‎‏‎‎‏‏‎‎‏‏‏‎‎‏‎‎‏‏‏‏‏‏‎‏‏‏‎‎‎‏‎‎‎‏‏‏‎‏‎‎‎‎‏‏‏‎‏‎‏‏‏‎Show all widgets‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‎‎‎‎‎‏‏‏‎‎‏‏‎‏‎‎‏‏‏‏‏‏‎‎‏‏‎‎‏‏‎‏‎‎‎‎‎‎‏‎‏‎‎‎‏‎‎‎‎‎‏‎‎‎‏‎‏‏‏‏‏‎%1$d × %2$d‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‏‎‎‏‎‏‎‎‎‎‏‎‎‎‏‏‎‏‎‎‎‎‏‏‎‎‎‏‎‎‎‏‏‏‎‎‎‎‏‏‏‎‎‏‎‎‏‎‎‏‏‎‎‎‎‎‏‏‏‏‎‎%1$d wide by %2$d high‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‎‏‎‎‏‎‏‎‎‏‎‎‎‏‏‎‎‏‎‏‎‏‎‏‎‎‎‎‎‎‎‎‎‎‏‏‏‎‏‏‏‎‎‎‎‎‎‏‏‎‏‏‎‎‎‏‎‎‏‏‎%1$s‎‏‎‎‏‏‏‎ widget‎‏‎‎‏‎" @@ -93,7 +90,6 @@ "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‎‎‎‏‏‎‎‏‏‏‏‎‎‏‎‏‎‏‏‏‏‎‎‏‎‎‎‏‎‏‎‏‏‏‏‎‏‏‎‎‎‎‎‎‏‎‏‎‎‎‏‎‏‏‎‎‎‏‏‎‏‎Install‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‎‏‏‏‎‏‎‎‏‏‎‎‎‎‏‏‏‎‏‎‏‏‎‏‏‏‏‏‏‏‏‏‏‏‎‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‏‎‏‏‎‏‎‎‏‎‎Don\'t suggest app‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‎‏‏‏‏‎‎‏‎‏‏‎‎‎‎‏‏‎‎‎‏‏‏‎‎‎‎‎‏‏‎‎‎‎‏‏‎‎‏‎‎‏‏‏‎‎‏‎‏‎‏‎‎‏‎‎Pin Prediction‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‎‏‎‏‎‏‎‏‎‎‏‎‏‎‏‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‎‏‏‎‏‎‏‎‎‏‏‎‎‎‎‎‏‎‏‎‎‏‏‏‏‏‏‏‎‎Bubble‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‎‏‏‏‎‎‎‏‎‏‎‏‎‎‏‏‎‎‎‏‏‎‎‏‏‏‎‏‏‎‏‎‎‏‏‏‎‏‎‏‏‎‎‎‏‎‎‎‏‎‎‏‏‎‎‎‎‎‏‎‏‎install shortcuts‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‎‎‏‏‎‎‏‏‎‏‎‎‎‎‏‏‎‎‏‏‏‏‏‏‏‎‎‏‎‏‏‏‎‏‏‏‏‏‏‏‏‎‏‎‎‏‎‎‏‎‎‏‏‏‎‎‎‎‎‎‏‏‎Allows an app to add shortcuts without user intervention.‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‎‎‏‏‏‎‏‎‎‏‎‎‎‏‎‎‎‎‎‏‏‏‏‎‏‏‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‎‏‎‏‎‏‏‏‎‏‏‏‎‏‏‏‏‎‏‏‎read home settings and shortcuts‎‏‎‎‏‎" diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml index 34c7d13c05..5879129996 100644 --- a/res/values-es-rUS/strings.xml +++ b/res/values-es-rUS/strings.xml @@ -29,20 +29,15 @@ "Pantalla principal" "Establece %1$s como la app de inicio predeterminada en Configuración" "Pantalla dividida" - "Cambiar relación de aspecto" "Información de la app de %1$s" "Configuración del uso de %1$s" - "Ventana nueva" - "Administrar ventanas" - "Guardar grupo de apps" + "Guardar vinculación" "%1$s | %2$s" "No se admite esta vinculación de apps en este dispositivo" "Abre el dispositivo para usar esta vinculación de apps" "La vinculación de apps no está disponible" "Mantén presionado para mover un widget." "Presiona dos veces y mantén presionado para mover un widget o usar acciones personalizadas." - "Más opciones" - "Mostrar todos los widgets" "%1$d × %2$d" "%1$d de ancho por %2$d de alto" "%1$s widget" @@ -69,13 +64,8 @@ "Trabajo" "Conversaciones" "Tomar notas" - "Mostrar botón Agregar" - "Ocultar botón Agregar" "Agregar" "Agregar widget %1$s" - "Mostrar todos" - "Mostrar todos los widgets" - "Mostrando todos los widgets" "Presiona para cambiar la configuración del widget" "Cambiar la configuración del widget" "Buscar apps" @@ -83,7 +73,6 @@ "No hay apps que coincidan con \"%1$s\"" "App" "Todas las apps" - "Lista de apps" "Notificaciones" "Mantén presionado para mover un acceso directo." "Presiona dos veces y mantén presionado para mover un acceso directo o usar acciones personalizadas." @@ -101,7 +90,6 @@ "Instalar" "No sugerir app" "Fijar predicción" - "Burbuja" "instalar accesos directos" "Permite que una aplicación agregue accesos directos sin que el usuario intervenga." "leer parámetros de configuración y accesos directos de la página principal" @@ -118,8 +106,6 @@ "Página %1$d de %2$d" "Pantalla principal %1$d de %2$d" "Nueva página en la pantalla principal" - "Activo" - "Minimizado" "Carpeta abierta, %1$d por %2$d" "Presiona para cerrar la carpeta" "Presiona para guardar el cambio de nombre" @@ -127,7 +113,6 @@ "El nombre de la carpeta se cambió a %1$s." "Carpeta: %1$s, %2$d elementos" "Carpeta: %1$s, %2$d o más elementos" - "Carpeta sin nombre" "Vinculación de apps: %1$s y %2$s" "Fondo de pantalla y estilo" "Editar pantalla principal" @@ -135,8 +120,6 @@ "El administrador inhabilitó esta función" "Permitir la rotación de la pantalla principal" "Al girar el teléfono" - "Modo horizontal" - "Establece el teléfono en modo horizontal" "Puntos de notificación" "Activados" "Desactivados" @@ -155,8 +138,7 @@ "Se está instalando %1$s; %2$s completado" "Se completó el %2$s de la descarga de %1$s" "Instalación de %1$s en espera" - "%1$s está archivada." - "descargar y restablecer" + "%1$s está archivada. Presiona para descargar y restablecer." "Es necesario actualizar la app" "No se actualizó la app de este ícono. Puedes actualizarla manualmente para rehabilitar el acceso directo, o bien quitar el ícono." "Actualizar" @@ -165,6 +147,7 @@ "Se cerró la lista de widgets" "Agregar a pantalla principal" "Mover elemento aquí" + "Se agregó el elemento a la pantalla principal." "Se eliminó el elemento." "Deshacer" "Mover elemento" @@ -184,15 +167,11 @@ "Reducir el ancho" "Reducir la altura" "Se cambió la dimensión del widget a %1$s de ancho y %2$s de alto." - "Menú de accesos directos" - "Marco de cambio de tamaño del widget para %1$s" - "Cerrar" + "Accesos directos" "Descartar" "Cerrar" "Personal" "Trabajo" - "Pestaña de apps personales" - "Pestaña de apps de trabajo" "Perfil de trabajo" "Las apps de trabajo tienen una insignia y el administrador de TI las puede ver" "Entendido" @@ -204,7 +183,6 @@ "Entendido" "Detener apps de trabajo" "Reanudar" - "Programa de las apps de trabajo" "Filtro" "Error: %1$s" "Espacio privado" @@ -217,5 +195,4 @@ "Pasar a Espacio privado" "Instalar" "Instala las apps en el espacio privado" - "Agrega archivos y mucho más al Espacio privado" diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml index ad5833b0ae..8d384acba4 100644 --- a/res/values-es/strings.xml +++ b/res/values-es/strings.xml @@ -29,11 +29,8 @@ "Inicio" "Define %1$s como aplicación de inicio predeterminada en Ajustes" "Pantalla dividida" - "Cambiar relación de aspecto" "Información de la aplicación %1$s" "Ajustes de uso para %1$s" - "Ventana nueva" - "Gestionar ventanas" "Guardar apps emparejadas" "%1$s | %2$s" "El dispositivo no admite esta aplicación emparejada" @@ -41,8 +38,6 @@ "La aplicación emparejada no está disponible" "Mantén pulsado un widget para moverlo" "Toca dos veces y mantén pulsado un widget para moverlo o usar acciones personalizadas." - "Más opciones" - "Mostrar todos los widgets" "%1$d × %2$d" "%1$d de ancho por %2$d de alto" "Widget de %1$s" @@ -69,13 +64,8 @@ "Trabajo" "Conversaciones" "Toma de notas" - "Mostrar el botón Añadir" - "Ocultar el botón Añadir" "Añadir" "Añadir widget %1$s" - "Mostrar todo" - "Mostrar todos los widgets" - "Mostrando todos los widgets" "Toca para cambiar los ajustes del widget" "Cambiar ajustes del widget" "Buscar aplicaciones" @@ -83,7 +73,6 @@ "No se han encontrado aplicaciones que contengan \"%1$s\"" "Aplicación" "Todas las aplicaciones" - "Lista de aplicaciones" "Notificaciones" "Mantén pulsado un acceso directo para moverlo." "Toca dos veces y mantén pulsado un acceso directo para moverlo o usar acciones personalizadas." @@ -101,7 +90,6 @@ "Instalar" "No sugerir aplicación" "Fijar predicción" - "Burbuja" "instalar accesos directos" "Permite que una aplicación añada accesos directos sin intervención del usuario." "leer ajustes y accesos directos de la pantalla de inicio" @@ -118,8 +106,6 @@ "Página %1$d de %2$d" "Pantalla de inicio %1$d de %2$d" "Nueva página de pantalla de inicio" - "Activa" - "Minimizada" "Carpeta abierta, %1$d por %2$d" "Toca para cerrar la carpeta" "Toca para guardar el nuevo nombre" @@ -127,7 +113,6 @@ "Se ha cambiado el nombre de la carpeta a %1$s" "Carpeta: %1$s (%2$d elementos)" "Carpeta: %1$s (%2$d o más elementos)" - "Carpeta sin nombre" "Aplicaciones emparejadas: %1$s y %2$s" "Fondo de pantalla y estilo" "Editar pantalla de inicio" @@ -135,8 +120,6 @@ "Inhabilitado por el administrador" "Permitir rotación de la pantalla de inicio" "Al girar el teléfono" - "Modo de vista horizontal" - "Pon el teléfono en modo de vista horizontal" "Burbujas de notificación" "Activado" "Desactivadas" @@ -155,8 +138,7 @@ "Instalando %1$s, %2$s completado" "Descargando %1$s (%2$s completado)" "Esperando para instalar %1$s" - "%1$s está archivada." - "descargar y restaurar" + "%1$s está archivada. Toca para descargar y restaurar." "Debes actualizar la aplicación" "La aplicación de este icono no está actualizada. Puedes actualizarla manualmente para volver a habilitar este acceso directo o puedes eliminar el icono." "Actualizar" @@ -165,6 +147,7 @@ "Lista de widgets cerrada" "Añadir a pantalla de inicio" "Mover elemento aquí" + "Elemento añadido a la pantalla de inicio" "Elemento quitado" "Deshacer" "Mover elemento" @@ -184,15 +167,11 @@ "Reducir ancho" "Reducir altura" "Se ha modificado el tamaño del widget a %1$s de ancho y %2$s de alto" - "Menú de combinaciones de teclas" - "Marco de cambio de tamaño del widget de %1$s" - "Cerrar" + "Accesos directos" "Cerrar" "Cerrar" "Personal" "Trabajo" - "Pestaña Aplicaciones personales" - "Pestaña Aplicaciones de trabajo" "Perfil de trabajo" "Las aplicaciones de trabajo tienen una insignia, y tu administrador de TI las puede ver" "Entendido" @@ -204,7 +183,6 @@ "Entendido" "Pausar aplicaciones de trabajo" "Reanudar" - "Horario de aplicaciones de trabajo" "Filtro" "Se ha producido un error: %1$s" "Espacio privado" @@ -217,5 +195,4 @@ "Cambiar a espacio privado" "Instalar" "Descargar aplicaciones en el espacio privado" - "Añade archivos y más al espacio privado" diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml index 5f76a0b28c..44448a6b0e 100644 --- a/res/values-et/strings.xml +++ b/res/values-et/strings.xml @@ -29,11 +29,8 @@ "Avakuva" "Määrake rakendus %1$s seadetes avakuva vaikerakenduseks" "Jagatud ekraanikuva" - "Kuvasuhte muutmine" "Rakenduse teave: %1$s" "Kasutuse seaded: %1$s" - "Uus aken" - "Akende haldamine" "Salvesta rakendusepaar" "%1$s | %2$s" "See rakendusepaar ei ole selles seadmes toetatud" @@ -41,13 +38,11 @@ "Rakendusepaar ei ole saadaval" "Vidina teisaldamiseks puudutage ja hoidke all." "Vidina teisaldamiseks või kohandatud toimingute kasutamiseks topeltpuudutage ja hoidke all." - "Rohkem valikuid" - "Kuva kõik vidinad" "%1$d × %2$d" "%1$d lai ja %2$d kõrge" "Vidin %1$s" "Vidin %1$s, %2$d lai ja %3$d kõrge" - "Vidina teisaldamiseks avakuval puudutage vidinat pikalt." + "Vidina teisaldamiseks avakuval puudutage vidinat pikalt" "Lisa avakuvale" "Vidin %1$s lisati avakuvale" "Soovitused" @@ -69,13 +64,8 @@ "Töö" "Vestlused" "Märkmete tegemine" - "Kuva lisamisnupp" - "Peida lisamisnupp" "Lisa" "Lisa vidin %1$s" - "Kuva kõik" - "Kuva kõik vidinad" - "Kõik vidinad on kuvatud" "Puudutage vidina seadete muutmiseks" "Vidina seadete muutmine" "Otsige rakendusi" @@ -83,7 +73,6 @@ "Päringule „%1$s” ei vastanud ükski rakendus" "Rakendus" "Kõik rakendused" - "Rakenduste loend" "Märguanded" "Otsetee teisaldamiseks puudutage ja hoidke all." "Otsetee teisaldamiseks või kohandatud toimingute kasutamiseks topeltpuudutage ja hoidke all." @@ -101,7 +90,6 @@ "Installimine" "Ära soovita rakendust" "Kinnita ennustus" - "Mull" "installi otseteed" "Võimaldab rakendusel lisada otseteid kasutaja sekkumiseta." "avakuva seadete ja otseteede lugemine" @@ -118,8 +106,6 @@ "Leht %1$d/%2$d" "Avakuva %1$d/%2$d" "Uus avakuva leht" - "Ühendatud" - "Minimeeritud" "Kaust on avatud, %1$d x %2$d" "Puudutage kausta sulgemiseks" "Puudutage ümbernimetamise salvestamiseks" @@ -127,7 +113,6 @@ "Kausta uus nimi: %1$s" "Kaust: %1$s, %2$d üksust" "Kaust: %1$s, %2$d või rohkem üksust" - "Nimetu kaust" "Rakendusepaar: %1$s ja %2$s" "Taustapilt ja stiil" "Muuda avaekraani" @@ -135,8 +120,6 @@ "Keelas administraator" "Luba avakuva pööramine" "Kui telefoni pööratakse" - "Horisontaalrežiim" - "Sea telefon horisontaalrežiimi" "Märguandetäpid" "Sees" "Väljas" @@ -155,8 +138,7 @@ "Üksust %1$s installitakse, %2$s on valmis" "Rakenduse %1$s allalaadimine, %2$s on valmis" "%1$s on installimise ootel" - "%1$s on arhiivitud." - "laadi alla ja taasta" + "%1$s on arhiivitud. Puudutage allalaadimiseks ja taastamiseks." "Rakendust tuleb värskendada" "Selle ikooni rakendust pole värskendatud. Otsetee uuesti lubamiseks võite rakendust käsitsi värskendada või ikooni eemaldada." "Värskenda" @@ -165,6 +147,7 @@ "Vidinate loend on suletud" "Lisa avakuvale" "Teisalda üksus siia" + "Üksus lisati avaekraanile" "Üksus eemaldati" "Võta tagasi" "Teisalda üksus" @@ -184,15 +167,11 @@ "Vähenda laiust" "Vähenda kõrgust" "Vidina suurust muudeti. Laius: %1$s. Kõrgus: %2$s" - "Kiirmenüü" - "Vidina %1$s suuruse muutmise raam" - "Sule" + "Otseteed" "Loobu" "Sule" "Isiklik" "Töö" - "Vahekaart Isiklikud rakendused" - "Vahekaart töörakendused" "Tööprofiil" "Töörakendustel on märk ja need on teie IT-administraatorile nähtavad" "Selge" @@ -204,7 +183,6 @@ "Selge" "Peata töörakendused" "Lõpeta peatamine" - "Töörakenduste ajakava" "Filter" "Nurjus: %1$s" "Privaatne ruum" @@ -217,5 +195,4 @@ "Privaatse ruumi üleviimine" "Installi" "Rakenduste installimine privaatses ruumis" - "Privaatsesse ruumi failide ja muu sisu lisamine" diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml index 3601f0ae19..6fc4cf458f 100644 --- a/res/values-eu/strings.xml +++ b/res/values-eu/strings.xml @@ -29,20 +29,15 @@ "Orri nagusia" "Ezarri %1$s hasierako aplikazio lehenetsi gisa ezarpenetan" "Pantaila zatitzea" - "Aldatu aspektu-erlazioa" "%1$s aplikazioari buruzko informazioa" "%1$s aplikazioaren erabilera-ezarpenak" - "Leiho berria" - "Kudeatu leihoak" "Gorde aplikazio parea" "%1$s | %2$s" - "Aplikazio pare hori ez da gailu honekin bateragarria" + "Aplikazio pare hori ez da onartzen gailu honetan" "Zabaldu gailua aplikazio pare hau erabiltzeko" "Aplikazio parea ez dago erabilgarri" "Eduki sakatuta widget bat mugitzeko." "Sakatu birritan eta eduki sakatuta widget bat mugitzeko edo ekintza pertsonalizatuak erabiltzeko." - "Aukera gehiago" - "Erakutsi widget guztiak" "%1$d × %2$d" "%1$d zabal eta %2$d luze" "%1$s widgeta" @@ -69,13 +64,8 @@ "Lanekoak" "Elkarrizketak" "Oharrak idazteko" - "Erakutsi gehitzeko botoia" - "Ezkutatu gehitzeko botoia" "Gehitu" "Gehitu %1$s widgeta" - "Erakutsi guztiak" - "Erakutsi widget guztiak" - "Widget guztiak erakusten" "Sakatu hau widgeten ezarpenak aldatzeko" "Aldatu widgeten ezarpenak" "Bilatu aplikazioetan" @@ -83,7 +73,6 @@ "Ez da aurkitu \"%1$s\" bilaketaren emaitzarik" "Aplikazioa" "Aplikazio guztiak" - "Aplikazioen zerrenda" "Jakinarazpenak" "Eduki sakatuta lasterbide bat mugitzeko." "Sakatu birritan eta eduki sakatuta lasterbide bat mugitzeko edo ekintza pertsonalizatuak erabiltzeko." @@ -95,13 +84,12 @@ "Laneko aplikazioen zerrenda" "Kendu" "Desinstalatu" - "Aplik. buruzko info." + "Aplikazioaren informazioa" "Instalatu pribatuan" "Desinstalatu aplikazioa" "Instalatu" "Ez iradoki aplikazioa" "Ainguratu iragarpena" - "Burbuila" "Instalatu lasterbideak" "Erabiltzaileak ezer egin gabe lasterbideak gehitzeko baimena ematen die aplikazioei." "irakurri hasierako pantailako ezarpenak eta lasterbideak" @@ -118,8 +106,6 @@ "%1$d/%2$d orria" "%1$d/%2$d orri nagusi" "Orri nagusiaren orri berria" - "Aktibo" - "Minimizatuta" "Karpeta ireki da: %1$d × %2$d" "Karpeta ixteko, sakatu hau" "Izen berria gordetzeko, sakatu hau" @@ -127,16 +113,13 @@ "Karpetari %1$s izena eman zaio" "%1$s karpeta (%2$d elementu)" "%1$s karpeta (%2$d elementu edo gehiago)" - "Izenik gabeko karpeta" "Aplikazio parea: %1$s eta %2$s" "Horma-papera eta estiloa" "Editatu orri nagusia" - "Orri nagusiaren ezarpenak" + "Orri nagusiko ezarpenak" "Administratzaileak desgaitu du" "Eman orri nagusia biratzeko baimena" "Telefonoa biratzean" - "Ikuspegi horizontala" - "Ezarri telefonoa ikuspegi horizontalean" "Jakinarazpen-biribiltxoak" "Aktibatuta" "Desaktibatuta" @@ -155,8 +138,7 @@ "%1$s instalatzen, %2$s osatuta" "%1$s deskargatzen, %2$s osatuta" "%1$s instalatzeko zain" - "%1$s artxibatuta dago." - "deskargatu eta leheneratu" + "%1$s artxibatuta dago. Sakatu deskargatzeko eta leheneratzeko." "Aplikazioa eguneratu egin behar da" "Ikonoaren aplikazioa ez dago eguneratuta. Lasterbidea berriro gaitzeko, eskuz egunera dezakezu aplikazioa. Bestela, kendu ikonoa." "Eguneratu" @@ -165,6 +147,7 @@ "Itxi da widget-zerrenda" "Gehitu orri nagusian" "Ekarri elementua hona" + "Gehitu da elementua orri nagusian" "Kendu da elementua" "Desegin" "Mugitu elementua" @@ -184,15 +167,11 @@ "Txikitu zabalera" "Txikitu altuera" "Aldatu da widgetaren tamaina. Zabalera: %1$s. Altuera: %2$s." - "Lasterbideen menua" - "%1$s aplikazioaren widgetaren tamaina aldatzeko markoa" - "Itxi" + "Lasterbideak" "Baztertu" "Itxi" "Pertsonalak" "Lanekoak" - "Aplikazio pertsonalen fitxa" - "Laneko aplikazioen fitxa" "Laneko profila" "Laneko aplikazioek bereizgarriak dituzte, eta IKT saileko administratzaileak ikus ditzake" "Ados" @@ -204,7 +183,6 @@ "Ados" "Pausatu laneko aplikazioak" "Aktibatu berriro" - "Laneko aplikazioen programazioa" "Iragazi" "Huts egin du: %1$s" "Eremu pribatua" @@ -217,5 +195,4 @@ "Eremu pribaturako trantsizioa" "Instalatu" "Instalatu aplikazioak eremu pribatuan" - "Gehitu fitxategiak eta gauza gehiago eremu pribatuan" diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml index f2fb9fd4c7..fffce130a3 100644 --- a/res/values-fa/strings.xml +++ b/res/values-fa/strings.xml @@ -24,66 +24,55 @@ "برنامه نصب نشده است." "برنامه در دسترس نیست" "برنامه بارگیری شده در حالت ایمن غیرفعال شد" - "ابزاره‌ها در حالت ایمن غیرفعال هستند" + "ابزارک‌ها در حالت ایمن غیرفعال هستند" "میان‌بر دردسترس نیست" "صفحه اصلی" "تنظیم %1$s به‌عنوان برنامه صفحه اصلی پیش‌فرض در «تنظیمات»" "صفحهٔ دونیمه" - "تغییر نسبت ابعادی" "‏اطلاعات برنامه %1$s" "‏تنظیمات مصرف برای %1$s" - "پنجره جدید" - "مدیریت کردن پنجره‌ها" "ذخیره جفت برنامه" "%1$s | %2$s" "از این جفت برنامه در این دستگاه پشتیبانی نمی‌شود" "برای استفاده از این جفت برنامه، دستگاه را باز کنید" "جفت برنامه دردسترس نیست" - "برای جابه‌جا کردن ابزاره، لمس کنید و نگه دارید." - "برای جابه‌جا کردن ابزاره یا استفاده از کنش‌های سفارشی، دو تک‌ضرب بزنید و نگه دارید." - "گزینه‌های بیشتر" - "نمایش همه ابزاره‌ها" + "برای جابه‌جا کردن ابزارک، لمس کنید و نگه دارید." + "برای جابه‌جا کردن ابزارک یا استفاده از کنش‌های سفارشی، دو تک‌ضرب بزنید و نگه دارید." "%1$d × %2$d" "‏%1$d عرض در %2$d طول" - "ابزاره %1$s" - "‏ابزاره %1$s، %2$d عرض در %3$d ارتفاع" - "ابزاره را لمس کنید و نگه دارید تا بتوانید آن را در صفحه اصلی حرکت دهید" + "ابزارک %1$s" + "‏ابزارک %1$s، %2$d عرض در %3$d ارتفاع" + "ابزارک را لمس کنید و نگه دارید تا بتوانید آن را در صفحه اصلی حرکت دهید" "افزودن به صفحه اصلی" - "ابزاره %1$s به صفحه اصلی اضافه شد" + "ابزارک %1$s به صفحه اصلی اضافه شد" "پیشنهادها" - "ضروریات" + "بایدها" "اخبار و مجله" "سرگرمی" "اجتماعی" "پیشنهاداتی برای شما" - "ابزاره‌های %1$s در سمت چپ، «جستجو» و گزینه‌ها در سمت راست" - "{count,plural, =1{‏# ابزاره}one{‏# ابزاره}other{‏# ابزاره}}" + "ابزارک‌های %1$s در سمت چپ، جستجو و گزینه‌ها در سمت راست" + "{count,plural, =1{‏# ابزارک}one{‏# ابزارک}other{‏# ابزارک}}" "{count,plural, =1{‏# میان‌بر}one{‏# میان‌بر}other{‏# میان‌بر}}" "%1$s،%2$s" - "ابزاره‌ها" + "ابزارک‌ها" "جستجو" "پاک کردن نوشتار از چارگوش جستجو" - "ابزاره و میان‌بری دردسترس نیست" - "هیچ ابزاره یا میان‌بری پیدا نشد" - "شخصی" - "کاری" + "ابزارک و میان‌بری دردسترس نیست" + "هیچ ابزارک یا میان‌بری پیدا نشد" + "ابزارک‌های شخصی" + "کار" "مکالمه‌ها" "یادداشت‌برداری" - "نشان دادن دکمه افزودن" - "پنهان کردن دکمه افزودن" "افزودن" - "افزودن ابزاره %1$s" - "نمایش همه" - "نمایش همه ابزاره‌ها" - "درحال نمایش دادن همه ابزاره‌ها" - "برای تغییر تنظیمات ابزاره، تک‌ضرب بزنید" - "تغییر تنظیمات ابزاره" + "افزودن ابزارک %1$s" + "برای تغییر تنظیمات ابزارک، تک‌ضرب بزنید" + "تغییر تنظیمات ابزارک" "جستجوی برنامه‌ها" "درحال بارگیری برنامه‌‌ها…" "هیچ برنامه‌ای در مطابقت با «%1$s» پیدا نشد" "برنامه" "همه برنامه‌ها" - "فهرست برنامه‌ها" "اعلان‌ها" "برای جابه‌جا کردن میان‌بر، لمس کنید و نگه دارید." "برای جابه‌جا کردن میان‌بر یا استفاده از کنش‌های سفارشی، دو تک‌ضرب بزنید و نگه دارید." @@ -101,16 +90,15 @@ "نصب" "برنامه پیشنهاد داده نشود" "سنجاق کردن پیشنهاد" - "حبابک" "نصب میان‌برها" "به برنامه اجازه می‌دهد میان‌برها را بدون دخالت کاربر اضافه کند." "خواندن تنظیمات و میان‌برهای صفحه اصلی" "به برنامه اجازه می‌دهد تنظیمات و میان‌برهای صفحه اصلی را بخواند." "نوشتن تنظیمات و میان‌برهای صفحه اصلی" "به برنامه اجازه می‌دهد تنظیمات و میان‌برهای صفحه اصلی را تغییر دهد." - "ابزاره را نمی‌توان بار کرد" - "تنظیمات ابزاره" - "برای تمام کردن راه‌اندازی تک‌ضرب بزنید" + "ابزارک را نمی‌توان بار کرد" + "تنظیمات ابزارک" + "برای تکمیل راه‌اندازی تک‌ضرب بزنید" "این برنامه سیستمی است و حذف نصب نمی‌شود." "ویرایش نام" "%1$s غیرفعال شد" @@ -118,8 +106,6 @@ "‏صفحه %1$d از %2$d" "‏صفحه اصلی %1$d از %2$d" "صفحه اصلی جدید" - "فعال" - "کوچک‌شده" "پوشه باز شده، %1$d در %2$d" "برای بستن پوشه، تک‌ضرب بزنید" "برای ذخیره تغییر نام، تک‌ضرب بزنید" @@ -127,7 +113,6 @@ "نام پوشه به %1$s تغییر کرد" "پوشه: %1$s، %2$d مورد" "پوشه: %1$s، %2$d مورد یا بیشتر" - "پوشه بی‌نام" "جفت برنامه: %1$s و %2$s" "کاغذدیواری و سبک" "ویرایش «صفحه اصلی»" @@ -135,8 +120,6 @@ "توسط سرپرست سیستم غیرفعال شده است" "مجاز کردن چرخش صفحه اصلی" "وقتی تلفن چرخانده می‌شود" - "حالت افقی" - "تنظیم تلفن روی حالت افقی" "نقطه‌های اعلان" "روشن" "خاموش" @@ -144,7 +127,7 @@ "برای نمایش «نقطه‌های اعلان»، اعلان‌های برنامه را برای %1$s روشن کنید" "تغییر تنظیمات" "نمایش نقطه‌های اعلان" - "گزینه‌های توسعه‌دهندگان" + "گزینه‌های برنامه‌نویس" "افزودن نماد برنامه‌ها به صفحه اصلی" "برای برنامه‌های جدید" "نامشخص" @@ -155,16 +138,16 @@ "%1$s درحال نصب است، %2$s تکمیل شده است" "درحال بارگیری %1$s، %2$s کامل شد" "%1$s درانتظار نصب" - "‫%1$s بایگانی شده است." - "بارگیری و بازیابی کردن" + "%1$s بایگانی شده است. برای بارگیری و بازیابی تک‌ضرب بزنید." "برنامه باید به‌روز شود" "برنامه برای این نماد به‌روز نشده است. می‌توانید آن را به‌صورت دستی به‌روز کنید تا میان‌بر دوباره فعال شود، یا نماد را بردارید." "به‌روزرسانی" "برداشتن" - "فهرست ابزاره‌ها" - "فهرست ابزاره‌ها بسته شد" + "فهرست ابزارک‌ها" + "فهرست ابزارک‌ها بسته شد" "افزودن به صفحه اصلی" "انتقال مورد به اینجا" + "مورد به صفحه اصلی اضافه شد" "مورد حذف شد" "واگرد" "انتقال مورد" @@ -183,28 +166,23 @@ "افزایش ارتفاع" "کاهش عرض" "کاهش ارتفاع" - "اندازه ابزاره به عرض %1$s ارتفاع %2$s تغییر کرد" - "منو میان‌بر" - "قاب تغییر اندازه ابزاره برای %1$s" - "بستن" + "اندازه ابزارک به عرض %1$s ارتفاع %2$s تغییر کرد" + "میان‌برها" "رد کردن" "بستن" "شخصی" "کاری" - "برگه برنامه‌های شخصی" - "برگه برنامه‌های کاری" "نمایه کاری" "برنامه‌های کاری نشان‌دار هستند و سرپرست فناوری اطلاعات می‌تواند آن‌ها را ببیند" - "متوجهم" + "متوجه‌ام" "برنامه‌های کاری موقتاً متوقف شده‌اند." "از برنامه‌های کاری‌تان اعلان دریافت نخواهید کرد" "برنامه‌های کاری نمی‌توانند برای شما اعلان ارسال کنند، از باتری استفاده کنند، یا به مکانتان دسترسی داشته باشند" "از برنامه‌های کاری‌تان تماس تلفنی، پیام نوشتاری، یا اعلان دریافت نخواهید کرد" "برنامه‌های کاری نشان‌دار هستند و سرپرست فناوری اطلاعات می‌تواند آن‌ها را ببیند." - "متوجهم" + "متوجه‌ام" "توقف موقت برنامه‌های کاری" "ازسرگیری" - "برنامه زمانی برنامه‌های کاری" "فیلتر" "ناموفق بود: %1$s" "فضای خصوصی" @@ -217,5 +195,4 @@ "انتقال «فضای خصوصی»" "نصب" "نصب برنامه‌ها در «فضای خصوصی»" - "افزودن فایل‌ها و موارد دیگر به «فضای خصوصی»" diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml index 2b2af8a355..310053e557 100644 --- a/res/values-fi/strings.xml +++ b/res/values-fi/strings.xml @@ -29,11 +29,8 @@ "Etusivu" "Valitse %1$s oletusaloitusnäyttösovellukseksi asetuksissa" "Jaettu näyttö" - "Vaihda kuvasuhdetta" "Sovellustiedot: %1$s" "Käyttöasetus tälle: %1$s" - "Uusi ikkuna" - "Hallinnoi ikkunoita" "Tallenna sovelluspari" "%1$s | %2$s" "Sovellusparia ei tueta tällä laitteella" @@ -41,8 +38,6 @@ "Sovelluspari ei ole saatavilla" "Kosketa pitkään, niin voit siirtää widgetiä." "Kaksoisnapauta ja paina pitkään, niin voit siirtää widgetiä tai käyttää muokattuja toimintoja." - "Lisäasetukset" - "Näytä kaikki widgetit" "%1$d × %2$d" "Leveys: %1$d, korkeus: %2$d" "%1$s widget" @@ -69,13 +64,8 @@ "Työ" "Keskustelut" "Muistiinpanojen tekeminen" - "Näytä lisää-painike" - "Piilota Lisää-painike" "Lisää" "Lisää widget: %1$s" - "Näytä kaikki" - "Näytä kaikki widgetit" - "Näytetään kaikki widgetit" "Napauta, niin voit muuttaa widgetin asetuksia" "Muuta widgetin asetuksia" "Hae sovelluksia" @@ -83,7 +73,6 @@ "%1$s ei palauttanut sovelluksia." "Sovellus" "Kaikki sovellukset" - "Sovellusluettelo" "Ilmoitukset" "Kosketa pitkään, niin voit siirtää pikakuvaketta." "Kaksoisnapauta ja paina pitkään, niin voit siirtää pikakuvaketta tai käyttää muokattuja toimintoja." @@ -101,7 +90,6 @@ "Asenna" "Älä ehdota sovellusta" "Kiinnitä sovellus" - "Kupla" "asenna pikakuvakkeita" "Antaa sovelluksen lisätä pikakuvakkeita itsenäisesti ilman käyttäjän valintaa." "lukea aloitusnäytön asetuksia ja pikakuvakkeita" @@ -118,8 +106,6 @@ "Sivu %1$d / %2$d" "Aloitusruutu %1$d/%2$d" "Uusi aloitusnäytön sivu" - "Aktiivinen" - "Pienennetty" "Kansio avattu, koko %1$d x %2$d" "Sulje kansio koskettamalla." "Tallenna uusi nimi koskettamalla." @@ -127,7 +113,6 @@ "Kansion nimeksi vaihdettiin %1$s" "Kansio: %1$s, %2$d kohdetta" "Kansio: %1$s, ainakin %2$d kohdetta" - "Nimeämätön kansio" "Sovelluspari: %1$s ja %2$s" "Taustakuva ja tyyli" "Muokkaa aloitusnäyttöä" @@ -135,8 +120,6 @@ "Järjestelmänvalvoja on poistanut toiminnon käytöstä." "Salli aloitusnäytön kiertäminen" "Kun puhelinta kierretään" - "Vaakasuunta" - "Aseta puhelin vaakasuuntaan" "Pistemerkit" "Päällä" "Ei päällä" @@ -155,8 +138,7 @@ "%1$s asennetaan, %2$s valmis" "%1$s latautuu, valmiina %2$s" "%1$s odottaa asennusta" - "%1$s on arkistoitu." - "lataa ja palauta" + "%1$s on arkistoitu. Lataa ja palauta napauttamalla." "Sovelluspäivitys vaaditaan" "Kuvakkeen sovellusta ei ole päivitetty. Voit ottaa pikakuvakkeen uudelleen käyttöön päivittämällä sovelluksen tai poistaa kuvakkeen." "Päivitä" @@ -165,6 +147,7 @@ "Widget-luettelo suljettu" "Lisää aloitusnäytölle" "Siirrä kohde tänne" + "Kohde lisättiin aloitusnäytölle." "Kohde poistettiin" "Kumoa" "Siirrä kohde" @@ -184,15 +167,11 @@ "Vähennä leveyttä" "Vähennä korkeutta" "Widgetin kokoa muutettiin. Sen leveys on nyt %1$s ja korkeus %2$s." - "Pikanäppäinvalikko" - "Widgetin koon muuttaminen (%1$s)" - "Sulje" + "Pikakuvakkeet" "Hylkää" "Sulje" "Henkilökohtaiset" "Työsovellukset" - "Henkilökohtaiset sovellukset ‑välilehti" - "Työsovellukset-välilehti" "Työprofiili" "Työsovellukset on merkitty sellaisiksi ja näkyvät IT-järjestelmänvalvojille" "Selvä" @@ -204,7 +183,6 @@ "OK" "Keskeytä työsovellusten käyttö" "Jatka" - "Työsovellusten aikataulu" "Suodatin" "Epäonnistui: %1$s" "Yksityinen tila" @@ -217,5 +195,4 @@ "Yksityisen tilan siirtäminen" "Asenna" "Asenna sovelluksia yksityiseen tilaan" - "Lisää tiedostoja ja muuta yksityiseen tilaan" diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml index 432af26995..11b2c010e5 100644 --- a/res/values-fr-rCA/strings.xml +++ b/res/values-fr-rCA/strings.xml @@ -29,11 +29,8 @@ "Accueil" "Définissez %1$s comme appli d\'accueil par défaut dans les paramètres" "Écran divisé" - "Modifier les proportions" "Renseignements sur l\'appli pour %1$s" "Paramètres d\'utilisation pour %1$s" - "Nouvelle fenêtre" - "Gérer les fenêtres" "Enr. paire d\'applis" "%1$s | %2$s" "Cette paire d\'applis n\'est pas prise en charge sur cet appareil" @@ -41,8 +38,6 @@ "La Paire d\'applis n\'est pas offerte" "Maintenez le doigt sur un widget pour le déplacer." "Touchez 2x un widget et maintenez le doigt dessus pour le déplacer ou utiliser des actions personnalisées." - "Autres options" - "Afficher tous les widgets" "%1$d × %2$d" "%1$d de largeur sur %2$d de hauteur" "Widget %1$s" @@ -69,13 +64,8 @@ "Professionnels" "Conversations" "Prise de note" - "Afficher le bouton Ajouter" - "Masquer le bouton Ajouter" "Ajouter" "Ajoutez le widget %1$s" - "Tout afficher" - "Afficher tous les widgets" - "Tous les widgets affichés" "Touchez pour modifier les paramètres du widget" "Modifier les paramètres du widget" "Rechercher dans les applis" @@ -83,7 +73,6 @@ "Aucune appli trouvée correspondant à « %1$s »" "Appli" "Toutes les applis" - "Liste des applis" "Notifications" "Maintenez le doigt sur un raccourci pour le déplacer." "Touchez deux fois un raccourci et maintenez le doigt dessus pour le déplacer ou utiliser des actions personnalisées." @@ -101,7 +90,6 @@ "Installer" "Ne pas suggérer d\'appli" "Épingler la prédiction" - "Bulle" "installer des raccourcis" "Permet à une appli d\'ajouter des raccourcis sans l\'intervention de l\'utilisateur." "lire les paramètres et les raccourcis de la page d\'accueil" @@ -118,8 +106,6 @@ "Page %1$d sur %2$d" "Écran d\'accueil %1$d sur %2$d" "Nouvelle page d\'écran d\'accueil" - "Actif" - "Réduit" "Dossier ouvert, %1$d par %2$d" "Touchez pour fermer le dossier" "Touchez pour enregistrer le nouveau nom" @@ -127,7 +113,6 @@ "Nouveau nom du dossier : %1$s" "Dossier : %1$s, %2$d élément(s)" "Dossier : %1$s, %2$d éléments ou plus" - "Dossier sans nom" "Paire d\'applis : %1$s et %2$s" "Fond d\'écran et style" "Modifier l\'écran d\'accueil" @@ -135,10 +120,8 @@ "Cette fonction est désactivée par votre administrateur" "Autoriser la rotation de l\'écran d\'accueil" "Lorsque vous faites pivoter le téléphone" - "Mode paysage" - "Configurez le téléphone en mode Paysage" "Pastilles de notification" - "Activées" + "Activé" "Désactivé" "L\'accès aux notifications est requis" "Pour afficher les points de notification, activez les notifications d\'appli pour %1$s" @@ -155,8 +138,7 @@ "Installation de l\'appli %1$s en cours, %2$s terminée" "Téléchargement de %1$s : %2$s" "%1$s en attente d\'installation" - "L\'appli %1$s est archivée." - "télécharger et restaurer" + "L\'appli %1$s est archivée. Touchez le bouton pour télécharger et restaurer l\'appli." "Mise à jour de l\'appli requise" "L\'appli pour cette icône n\'est pas à jour. Vous pouvez soit la mettre à jour manuellement pour réactiver ce raccourci, soit retirer l\'icône." "Mettre à jour" @@ -165,6 +147,7 @@ "Liste des widgets fermée" "Ajouter à l\'écran d\'accueil" "Déplacer l\'élément ici" + "Élément ajouté à l\'écran d\'accueil" "Élément retiré" "Annuler" "Déplacer l\'élément" @@ -184,15 +167,11 @@ "Diminuer la largeur" "Diminuer la hauteur" "Le widget a été redimensionné (largeur : %1$s, hauteur : %2$s)" - "Menu des raccourcis" - "Cadre de redimensionnement du widget pour %1$s" - "Fermer" + "Raccourcis" "Ignorer" "Fermer" "Personnel" "Travail" - "Onglet Applis personnelles" - "Onglet Applis professionnelles" "Profil professionnel" "Les applis professionnelles sont indiquées par un badge et elles sont visibles pour votre administrateur informatique" "OK" @@ -204,18 +183,16 @@ "OK" "Mettre en pause les applis professionnelles" "Réactiver" - "Horaire des applis professionnelles" "Filtrer" "Échec : %1$s" "Espace privé" "Touchez pour configurer ou ouvrir" "Privé" - "Paramètres de l\'espace privé" + "Paramètres de l\'Espace privé" "Privé, déverrouillé." "Privé, verrouillé." "Verrouiller" "Transition vers l\'Espace privé" "Installer" "Installer des applis dans l\'Espace privé" - "Ajouter des fichiers et plus à l\'espace privé" diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml index 87ad31a565..b4c5ec6d0b 100644 --- a/res/values-fr/strings.xml +++ b/res/values-fr/strings.xml @@ -29,20 +29,15 @@ "Accueil" "Définissez %1$s comme application d\'accueil par défaut dans Paramètres" "Écran partagé" - "Modifier le format" "Infos sur l\'appli pour %1$s" "Paramètres d\'utilisation pour %1$s" - "Nouvelle fenêtre" - "Gérer les fenêtres" - "Enregistrer une paire d\'applis" + "Enregistrer la paire d\'applis" "%1$s | %2$s" "Cette paire d\'applications n\'est pas prise en charge sur cet appareil" "Dépliez l\'appareil pour utiliser cette paire d\'applications" "La paire d\'applications n\'est pas disponible" "Appuyez de manière prolongée sur un widget pour le déplacer." "Appuyez deux fois et maintenez la pression pour déplacer widget ou utiliser actions personnalisées." - "Autres options" - "Afficher tous les widgets" "%1$d x %2$d" "%1$d de largeur et %2$d de hauteur" "Widget %1$s" @@ -69,13 +64,8 @@ "Professionnels" "Conversations" "Prise de notes" - "Afficher le bouton \"Ajouter\"" - "Masquer le bouton \"Ajouter\"" "Ajouter" "Ajoutez un widget %1$s" - "Tout afficher" - "Afficher tous les widgets" - "Afficher tous les widgets" "Appuyez pour modifier les paramètres du widget" "Modifier les paramètres du widget" "Rechercher dans les applications" @@ -83,7 +73,6 @@ "Aucune application ne correspond à la requête \"%1$s\"" "Application" "Toutes les applis" - "Liste des applis" "Notifications" "Appuyez de manière prolongée pour déplacer un raccourci." "Appuyez deux fois et maintenez la pression pour déplacer un raccourci ou utiliser les actions personnalisées." @@ -101,7 +90,6 @@ "Installer" "Ne pas suggérer d\'appli" "Épingler la prédiction" - "Bulle" "installer des raccourcis" "Permettre à une application d\'ajouter des raccourcis sans l\'intervention de l\'utilisateur" "Lire les paramètres et les raccourcis de la page d\'accueil" @@ -118,8 +106,6 @@ "Page %1$d sur %2$d" "Écran d\'accueil %1$d sur %2$d" "Nouvelle page d\'écran d\'accueil" - "Actif" - "Minimisé" "Dossier ouvert, %1$d par %2$d" "Appuyez pour fermer le dossier." "Appuyez pour enregistrer le nouveau nom du dossier." @@ -127,7 +113,6 @@ "Nouveau nom du dossier : %1$s" "Dossier : %1$s, %2$d éléments" "Dossier : %1$s, %2$d éléments ou plus" - "Dossier sans nom" "Paire d\'applications : %1$s et %2$s" "Fond d\'écran et style" "Modifier l\'écran d\'accueil" @@ -135,10 +120,8 @@ "Désactivé par votre administrateur" "Autoriser la rotation de l\'écran d\'accueil" "Lorsque vous faites pivoter le téléphone" - "Mode Paysage" - "Paramétrer le téléphone en mode Paysage" "Pastilles de notification" - "Activé" + "Activées" "Désactivées" "Accès aux notifications requis" "Pour afficher les pastilles de notification, activez les notifications de l\'application %1$s" @@ -155,8 +138,7 @@ "Installation de %1$s… (%2$s terminés)" "%1$s en cours de téléchargement, %2$s effectué(s)" "%1$s en attente d\'installation" - "L\'application %1$s est archivée." - "télécharger et restaurer" + "L\'application %1$s est archivée. Appuyez pour la télécharger et la restaurer." "Mise à jour de l\'appli requise" "L\'appli correspondant à cette icône n\'est pas mise à jour. Vous pouvez la mettre à jour manuellement pour réactiver le raccourci ou supprimer l\'icône." "Modifier" @@ -165,6 +147,7 @@ "La liste des widgets est fermée" "Ajouter à l\'écran d\'accueil" "Déplacer l\'élément ici" + "L\'élément a bien été ajouté à l\'écran d\'accueil." "Élément supprimé" "Annuler" "Déplacer l\'élément" @@ -184,15 +167,11 @@ "Diminuer la largeur" "Diminuer la hauteur" "Le widget a bien été redimensionné (largeur : %1$s, hauteur : %2$s)." - "Menu de raccourci" - "Cadre de redimensionnement du widget pour %1$s" - "Fermer" + "Raccourcis" "Ignorer" "Fermer" "Personnel" "Professionnel" - "Onglet \"Applications personnelles\"" - "Onglet \"Applications professionnelles\"" "Profil professionnel" "Les applis professionnelles sont identifiées par un badge et votre administrateur informatique peut les voir" "OK" @@ -204,7 +183,6 @@ "OK" "Mettre en pause les applis professionnelles" "Réactiver" - "Planifier l\'activation des applis pros" "Filtre" "Échec : %1$s" "Espace privé" @@ -217,5 +195,4 @@ "Transition vers Espace privé" "Installer" "Installer des applis dans l\'espace privé" - "Ajoutez des fichiers et bien plus à votre espace privé" diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml index d68a28c19f..ac7280218c 100644 --- a/res/values-gl/strings.xml +++ b/res/values-gl/strings.xml @@ -29,11 +29,8 @@ "Inicio" "Define %1$s como aplicación de inicio predeterminada en Configuración" "Pantalla dividida" - "Cambiar proporción" "Información da aplicación para %1$s" "Configuración de uso para %1$s" - "Ventá nova" - "Xestionar ventás" "Gardar parella de apps" "%1$s | %2$s" "O dispositivo non admite este emparellamento de aplicacións" @@ -41,8 +38,6 @@ "Non está dispoñible o emparellamento de aplicacións" "Mantén premido un widget para movelo." "Toca dúas veces un widget e manteno premido para movelo ou utiliza accións personalizadas." - "Máis opcións" - "Mostrar todos os widgets" "%1$d × %2$d" "%1$d de largo por %2$d de alto" "Widget %1$s" @@ -69,13 +64,8 @@ "Widgets do traballo" "Conversas" "Toma de notas" - "Mostrar o botón de engadir" - "Ocultar o botón de engadir" "Engadir" "Engadir o widget %1$s" - "Mostrar todo" - "Mostrar todos os widgets" - "Mostrando todos os widgets" "Toca para cambiar a configuración do widget" "Cambiar configuración do widget" "Buscar aplicacións" @@ -83,7 +73,6 @@ "Non se atoparon aplicacións que coincidan con \"%1$s\"" "Aplicación" "Todas as aplicacións" - "Lista de aplicacións" "Notificacións" "Mantén premido un atallo para movelo." "Toca dúas veces un atallo e manteno premido para movelo ou utiliza accións personalizadas." @@ -101,7 +90,6 @@ "Instalar" "Non suxerir app" "Fixar predición" - "Burbulla" "instalar atallos" "Permite a unha aplicación engadir atallos sen intervención do usuario." "ler a configuración e os atallos da pantalla de inicio" @@ -118,8 +106,6 @@ "Páxina %1$d de %2$d" "Pantalla de inicio %1$d de %2$d" "Nova páxina da pantalla de inicio" - "Activa" - "Minimizada" "Abriuse o cartafol, %1$d por %2$d" "Toca fóra para pechar o cartafol" "Toca fóra para cambiar o nome do cartafol" @@ -127,16 +113,13 @@ "O cartafol cambiou o nome a %1$s" "Cartafol: %1$s, %2$d elementos" "Cartafol: %1$s, %2$d elementos ou máis" - "Cartafol sen nome" "Emparellamento de aplicacións: %1$s e %2$s" "Estilo e fondo de pantalla" "Editar pantalla de inicio" - "Configuración da pantalla de inicio" + "Axustes de Inicio" "Función desactivada polo administrador" "Permitir xirar a pantalla de inicio" "Ao xirar o teléfono" - "Modo horizontal" - "Pon o teléfono no modo horizontal" "Puntos de notificacións" "Opción activada" "Desactivados" @@ -144,7 +127,7 @@ "Para que se mostren os puntos de notificacións, activa as notificacións da aplicación %1$s" "Cambiar configuración" "Mostra puntos de notificacións" - "Opcións de programación" + "Opcións de programador" "Engadir iconas de aplicacións á pantalla de inicio" "Para novas aplicacións" "Descoñecido" @@ -155,8 +138,7 @@ "Instalando %1$s, %2$s completado" "Descargando %1$s (%2$s completado)" "Esperando para instalar %1$s" - "%1$s está arquivada." - "descargar e restaurar" + "%1$s está no arquivo. Toca para descargar e restaurar." "É necesario actualizar a aplicación" "A aplicación á que corresponde esta icona non está actualizada. Podes actualizala manualmente para activar de novo este atallo, ou ben quitar a icona." "Actualizar" @@ -165,6 +147,7 @@ "Pechouse a lista de widgets" "Engadir á pantalla de inicio" "Mover elemento aquí" + "Engadiuse o elemento á pantalla de inicio" "Quitouse o elemento" "Desfacer" "Mover elemento" @@ -184,15 +167,11 @@ "Reducir ancho" "Reducir altura" "Cambiouse o tamaño do widget polo ancho %1$s e a altura %2$s" - "Menú do atallo" - "Marco do cambio de tamaño do widget para %1$s" - "Pechar" + "Atallos" "Pechar" "Pechar" "Persoal" "Traballo" - "Pestana de aplicacións persoais" - "Pestana de aplicacións do traballo" "Perfil de traballo" "O administrador de TI pode ver as aplicacións do traballo e engadirlles indicadores" "Entendido" @@ -204,7 +183,6 @@ "Entendido" "Pór en pausa aplicacións do traballo" "Volver activar" - "Horario das aplicacións do traballo" "Filtra" "Erro: %1$s" "Espazo privado" @@ -217,5 +195,4 @@ "Transición ao espazo privado" "Instalar" "Instalar as aplicacións no espazo privado" - "Engade ficheiros e máis elementos ao espazo privado" diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml index a49b4d6e32..74747d05a6 100644 --- a/res/values-gu/strings.xml +++ b/res/values-gu/strings.xml @@ -29,11 +29,8 @@ "હોમ સ્ક્રીન" "સેટિંગમાં %1$sને ડિફૉલ્ટ હોમ ઍપ તરીકે સેટ કરો" "સ્ક્રીનને વિભાજિત કરો" - "સાપેક્ષ ગુણોત્તર બદલો" "%1$s માટે ઍપ માહિતી" "%1$sના વપરાશ સંબંધિત સેટિંગ" - "નવી વિન્ડો" - "વિન્ડો મેનેજ કરો" "ઍપની જોડી સાચવો" "%1$s | %2$s" "આ ડિવાઇસ પર, આ ઍપની જોડીને સપોર્ટ આપવામાં આવતો નથી" @@ -41,8 +38,6 @@ "ઍપની જોડી ઉપલબ્ધ નથી" "વિજેટ ખસેડવા ટચ કરીને થોડી વાર દબાવી રાખો." "વિજેટ ખસેડવા બે વાર ટૅપ કરીને દબાવી રાખો અથવા કસ્ટમ ક્રિયાઓનો ઉપયોગ કરો." - "વધુ વિકલ્પો" - "બધા વિજેટ બતાવો" "%1$d × %2$d" "%1$d પહોળાઈ X %2$d ઊંચાઈ" "%1$s વિજેટ" @@ -61,7 +56,7 @@ "{count,plural, =1{# શૉર્ટકટ}one{# શૉર્ટકટ}other{# શૉર્ટકટ}}" "%1$s, %2$s" "વિજેટ" - "શોધો" + "શોધ" "શોધ બૉક્સમાંથી ટેક્સ્ટ સાફ કરો" "વિજેટ અને શૉર્ટકટ ઉપલબ્ધ નથી" "કોઈ વિજેટ અથવા શૉર્ટકટ મળ્યા નથી" @@ -69,13 +64,8 @@ "ઑફિસ" "વાતચીતો" "નોંધ લેવી" - "\'ઉમેરો\' બટન બતાવો" - "\'ઉમેરો\' બટન છુપાવો" "ઉમેરો" "%1$s વિજેટ ઉમેરો" - "બધા બતાવો" - "બધા વિજેટ બતાવો" - "બધા વિજેટ બતાવી રહ્યાં છીએ" "વિજેટના સેટિંગ બદલવા માટે ટૅપ કરો" "વિજેટના સેટિંગ બદલો" "ઍપ શોધો" @@ -83,7 +73,6 @@ "\"%1$s\"થી મેળ ખાતી કોઈ ઍપ્લિકેશનો મળી નથી" "ઍપ" "બધી ઍપ" - "ઍપની સૂચિ" "નોટિફિકેશન" "શૉર્ટકટ ખસેડવા ટચ કરીને થોડી વાર દબાવી રાખો." "શૉર્ટકટ ખસેડવા બે વાર ટૅપ કરીને દબાવી રાખો અથવા કસ્ટમ ક્રિયાઓનો ઉપયોગ કરો." @@ -101,7 +90,6 @@ "ઇન્સ્ટૉલ કરો" "ઍપ સૂચવશો નહીં" "પૂર્વાનુમાનને પિન કરો" - "બબલ" "શૉર્ટકટ ઇન્સ્ટૉલ કરો" "એપ્લિકેશનને વપરાશકર્તા હસ્તક્ષેપ વગર શોર્ટકટ્સ ઉમેરવાની મંજૂરી આપે છે." "હોમ સેટિંગ અને શૉર્ટકટ વાંચો" @@ -115,11 +103,9 @@ "નામમાં ફેરફાર કરો" "%1$s અક્ષમ કરી" "{count,plural, =1{{app_name}ના # નોટિફિકેશન છે}one{{app_name}ના # નોટિફિકેશન છે}other{{app_name}ના # નોટિફિકેશન છે}}" - "%2$dમાંથી %1$d પેજ" + "%2$d માંથી %1$d પૃષ્ઠ" "%2$d માંથી %1$d હોમ સ્ક્રીન" - "નવું હોમ સ્ક્રીન પેજ" - "સક્રિય" - "ન્યૂનતમ" + "નવું હોમ સ્ક્રીન પૃષ્ઠ" "%1$d બાય %2$d નું ફોલ્ડર ખોલ્યું" "ફોલ્ડર બંધ કરવા માટે ટૅપ કરો" "નામ બદલવાનું સાચવવા માટે ટૅપ કરો" @@ -127,7 +113,6 @@ "ફોલ્ડરનું નામ બદલીને %1$s કર્યું" "ફોલ્ડર: %1$s, %2$d આઇટમ" "ફોલ્ડર: %1$s, %2$d કે વધુ આઇટમ" - "અનામાંકિત ફોલ્ડર" "ઍપની જોડી: %1$s અને %2$s" "વૉલપેપર અને સ્ટાઇલ" "હોમ સ્ક્રીનમાં ફેરફાર કરો" @@ -135,9 +120,7 @@ "તમારા વ્યવસ્થાપક દ્વારા અક્ષમ કરેલ" "હોમ સ્ક્રીનને ફેરવવાની મંજૂરી આપો" "જ્યારે ફોન ફેરવવામાં આવે ત્યારે" - "લૅન્ડસ્કેપ મોડ" - "ફોનને લૅન્ડસ્કેપ મોડમાં સેટ કરો" - "નોટિફિકેશન ડૉટ" + "નોટિફિકેશન માટેના ચિહ્નો" "ચાલુ છે" "બંધ છે" "નોટિફિકેશનનો ઍક્સેસની જરૂરી છે" @@ -155,8 +138,7 @@ "%1$s ઇન્સ્ટૉલ કરી રહ્યાં છીએ, %2$s પૂર્ણ થયું" "%1$s ડાઉનલોડ કરી રહ્યાં છે, %2$s પૂર્ણ" "%1$s, ઇન્સ્ટૉલ થવાની રાહ જોઈ રહ્યું છે" - "%1$sને આર્કાઇવ કર્યું છે." - "ડાઉનલોડ અને રિસ્ટોર કરો" + "%1$sને આર્કાઇવ કર્યું છે. ડાઉનલોડ અને રિસ્ટોર કરવા માટે ટૅપ કરો." "ઍપને અપડેટ કરવી જરૂરી છે" "આ આઇકન માટે ઍપ અપડેટ કરવામાં આવી નથી. તમે આ શૉર્ટકટ ફરી ચાલુ કરવા અથવા આઇકન કાઢી નાખવા માટે ઍપને મેન્યુઅલી અપડેટ કરી શકો છો." "અપડેટ કરો" @@ -165,6 +147,7 @@ "વિજેટની સૂચિ બંધ કરવામાં આવી છે" "હોમ સ્ક્રીનમાં ઉમેરો" "આઇટમ અહીં ખસેડો" + "હોમ સ્ક્રીનમાં આઇટમ ઉમેરી" "આઇટમ કાઢી નાખી" "રદ કરો" "આઇટમ ખસેડો" @@ -184,15 +167,11 @@ "પહોળાઈ ઘટાડો" "ઊંચાઈ ઘટાડો" "વિજેટનો આકાર બદલીને %1$s પહોળાઈ %2$s ઊંચાઈ કર્યો" - "શૉર્ટકટ મેનૂ" - "%1$s માટે વિજેટનું કદ બદલવાની ફ્રેમ" - "બંધ કરો" + "શૉર્ટકટ્સ" "છોડી દો" "બંધ કરો" "વ્યક્તિગત ઍપ" "ઑફિસની ઍપ" - "વ્યક્તિગત ઍપનું ટૅબ" - "ઑફિસ માટેની ઍપનું ટૅબ" "ઑફિસની પ્રોફાઇલ" "ઑફિસની ઍપને બૅજ આપેલા હોય છે અને તમારા IT ઍડમિન તેમને જોઈ શકે છે" "સમજાઈ ગયું" @@ -204,7 +183,6 @@ "સમજાઈ ગયું" "ઑફિસની ઍપ થોભાવો" "ફરી ચાલુ કરો" - "ઑફિસ માટેની ઍપનું શેડ્યૂલ" "ફિલ્ટર કરો" "નિષ્ફળ: %1$s" "ખાનગી સ્પેસ" @@ -217,5 +195,4 @@ "ખાનગી સ્પેસ પર સ્થાનાંતરણ" "ઇન્સ્ટૉલ કરો" "ખાનગી સ્પેસમાં ઍપ ઇન્સ્ટૉલ કરો" - "ખાનગી સ્પેસમાં ફાઇલો વગેરે ઉમેરો" diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml index db9360599a..6071935713 100644 --- a/res/values-hi/strings.xml +++ b/res/values-hi/strings.xml @@ -29,11 +29,8 @@ "होम स्क्रीन" "सेटिंग में जाकर, %1$s को डिफ़ॉल्ट होम ऐप्लिकेशन के तौर पर सेट करें" "स्प्लिट स्क्रीन" - "आसपेक्ट रेशियो (लंबाई-चौड़ाई का अनुपात) बदलें" "%1$s के लिए ऐप्लिकेशन की जानकारी" "%1$s के लिए खर्च की सेटिंग" - "नई विंडो" - "विंडो मैनेज करें" "ऐप पेयर सेव करें" "%1$s | %2$s" "साथ में इस्तेमाल किए जा सकने वाले ये ऐप्लिकेशन, इस डिवाइस पर काम नहीं कर सकते" @@ -41,8 +38,6 @@ "साथ में इस्तेमाल किए जा सकने वाले ऐप्लिकेशन की सुविधा उपलब्ध नहीं है" "किसी विजेट को एक से दूसरी जगह ले जाने के लिए, उसे दबाकर रखें." "किसी विजेट को एक से दूसरी जगह ले जाने के लिए, उस पर दो बार टैप करके दबाकर रखें या पसंद के मुताबिक कार्रवाइयां इस्तेमाल करें." - "ज़्यादा विकल्प" - "सभी विजेट दिखाएं" "%1$d × %2$d" "%1$d चौड़ाई गुणा %2$d ऊंचाई" "%1$s विजेट" @@ -51,7 +46,7 @@ "होम स्क्रीन पर जोड़ें" "%1$s विजेट को होम स्क्रीन पर जोड़ा गया" "सुझाव" - "अहम जानकारी" + "ज़रूरी ऐप्लिकेशन" "खबरों और पत्रिकाओं वाले ऐप्लिकेशन" "मनोरंजन से जुड़े ऐप्लिकेशन" "सोशल मीडिया ऐप्लिकेशन" @@ -69,13 +64,8 @@ "वर्क विजेट" "बातचीत" "नोट बनाने से जुड़े विजेट" - "\'जोड़ें\' बटन दिखाएं" - "\'जोड़ें\' बटन छिपाएं" "जोड़ें" "%1$s विजेट जोड़ें" - "सभी दिखाएं" - "सभी विजेट दिखाएं" - "सभी विजेट दिखाए जा रहे हैं" "विजेट की सेटिंग में बदलाव करने के लिए टैप करें" "विजेट की सेटिंग में बदलाव करें" "ऐप्लिकेशन खोजें" @@ -83,7 +73,6 @@ "\"%1$s\" से मिलता-जुलता कोई ऐप्लिकेशन नहीं मिला" "ऐप्लिकेशन" "सभी ऐप्लिकेशन" - "ऐप्लिकेशन की सूची" "सूचनाएं" "किसी शॉर्टकट को एक से दूसरी जगह ले जाने के लिए, उसे दबाकर रखें." "किसी शॉर्टकट को एक से दूसरी जगह ले जाने के लिए, उस पर दो बार टैप करके दबाकर रखें या पसंद के मुताबिक कार्रवाइयां इस्तेमाल करें." @@ -101,7 +90,6 @@ "इंस्‍टॉल करें" "ऐप्लिकेशन का सुझाव न दें" "सुझाए गए ऐप पिन करें" - "बबल" "शॉर्टकट इंस्‍टॉल करें" "ऐप को उपयोगकर्ता के हस्‍तक्षेप के बिना शॉर्टकट जोड़ने देती है." "होम स्क्रीन की सेटिंग और शॉर्टकट पढ़ने की अनुमति" @@ -118,8 +106,6 @@ "पेज %2$d में से %1$d" "होम स्क्रीन %2$d में से %1$d" "नया होम स्‍क्रीन पेज" - "चालू है" - "छोटा किया गया" "फ़ोल्डर खोला गया, %1$d गुणा %2$d" "फ़ोल्डर बंद करने के लिए टैप करें" "नाम बदलना सहेजने के लिए टैप करें" @@ -127,7 +113,6 @@ "फ़ोल्डर का नाम बदलकर %1$s किया गया" "फ़ोल्डर: %1$s, %2$d आइटम" "फ़ोल्डर: %1$s, %2$d या इससे ज़्यादा आइटम" - "बिना नाम का फ़ोल्डर" "साथ में इस्तेमाल किए जा सकने वाले ऐप्लिकेशन: %1$s और %2$s" "वॉलपेपर और स्टाइल" "होम स्क्रीन में बदलाव करें" @@ -135,8 +120,6 @@ "आपके एडमिन ने बंद किया हुआ है" "होम स्क्रीन घुमाने की अनुमति दें" "फ़ोन घुुमाए जाने पर" - "लैंडस्केप मोड" - "फ़ोन को लैंडस्केप मोड में सेट करें" "सूचनाएं बताने वाला डॉट" "चालू है" "चालू" @@ -155,8 +138,7 @@ "%1$s इंस्टॉल किया जा रहा है, %2$s पूरा हो गया" "%1$s डाउनलोड हो रहा है, %2$s पूरी हुई" "%1$s के इंस्टॉल होने की प्रतीक्षा की जा रही है" - "%1$s को संग्रहित किया गया." - "डाउनलोड करें और वापस लाएं" + "%1$s को संग्रहित किया गया. ऐप्लिकेशन को वापस लाने और डाउनलोड करने के लिए टैप करें." "ऐप्लिकेशन को अपडेट करना ज़रूरी है" "इस आइकॉन का ऐप्लिकेशन अपडेट नहीं है. इस शॉर्टकट को फिर से चालू करने या आइकॉन को हटाने के लिए, ऐप्लिकेशन को मैन्युअल रूप से अपडेट किया जा सकता है." "अपडेट करें" @@ -165,6 +147,7 @@ "विजेट की सूची बंद हो गई है" "होम स्क्रीन पर जोड़ें" "आइटम यहां ले जाएं" + "होम स्क्रीन में आइटम जोड़ा गया" "आइटम हटाया गया" "पहले जैसा करें" "आइटम ले जाएं" @@ -184,15 +167,11 @@ "चौड़ाई घटाएं" "ऊंचाई घटाएं" "विजेट का आकार बदलकर उसकी चौड़ाई %1$s और ऊंचाई %2$s कर दी गई" - "शॉर्टकट मेन्यू" - "%1$s के लिए, विजेट का साइज़ बदलने वाला फ़्रेम" - "बंद करें" + "शॉर्टकट" "खारिज करें" "बंद करें" "निजी ऐप्लिकेशन" "वर्क ऐप्लिकेशन" - "निजी ऐप्लिकेशन टैब" - "वर्क ऐप्लिकेशन टैब" "वर्क प्रोफ़ाइल" "वर्क ऐप्लिकेशन बैज किए गए हैं. आईटी एडमिन इन्हें देख सकता है" "ठीक है" @@ -204,12 +183,11 @@ "ठीक है" "वर्क ऐप्लिकेशन रोकें" "चालू करें" - "वर्क ऐप्लिकेशन के लिए शेड्यूल" "फ़िल्टर" "पूरा नहीं हुआ: %1$s" "प्राइवेट स्पेस" "सेट अप करने या खोलने के लिए टैप करें" - "प्राइवेट" + "निजी" "प्राइवेट स्पेस सेटिंग" "प्राइवेट स्पेस को अनलॉक किया गया." "प्राइवेट स्पेस को लॉक किया गया." @@ -217,5 +195,4 @@ "प्राइवेट स्पेस की सेटिंग में बदलाव किया जा रहा है" "इंस्टॉल करें" "प्राइवेट स्पेस में ऐप्लिकेशन इंस्टॉल करें" - "प्राइवेट स्पेस में फ़ाइलें और अन्य चीज़ें जोड़ें" diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml index 8a5e9f83a0..cf7a91a9db 100644 --- a/res/values-hr/strings.xml +++ b/res/values-hr/strings.xml @@ -29,11 +29,8 @@ "Početni zaslon" "Postavite aplikaciju %1$s kao zadanu aplikaciju početnog zaslona u postavkama" "Podijeljeni zaslon" - "Promijeni omjer slike" "Informacije o aplikaciji %1$s" "Postavke upotrebe za %1$s" - "Novi prozor" - "Upravljanje prozorima" "Spremi par aplikacija" "%1$s | %2$s" "Taj par aplikacija nije podržan na ovom uređaju" @@ -41,8 +38,6 @@ "Par aplikacija nije dostupan" "Dodirnite i zadržite da biste premjestili widget." "Dvaput dodirnite i zadržite pritisak da biste premjestili widget ili upotrijebite prilagođene radnje" - "Više opcija" - "Prikaži sve widgete" "%1$d × %2$d" "%1$d širine i %2$d visine" "Widget %1$s" @@ -69,13 +64,8 @@ "Posao" "Razgovori" "Pisanje bilježaka" - "Prikaži gumb za dodavanje" - "Sakrij gumb za dodavanje" "Dodaj" "Dodaj widget %1$s" - "Prikaži sve" - "Prikaži sve widgete" - "Prikazuju se svi widgeti" "Dodirnite da biste promijenili postavke widgeta" "Promijenite postavke widgeta" "Pretraži aplikacije" @@ -83,7 +73,6 @@ "Nema aplikacija podudarnih s upitom \"%1$s\"" "Aplikacija" "Sve aplikacije" - "Popis aplikacija" "Obavijesti" "Dodirnite i zadržite da biste premjestili prečac." "Dvaput dodirnite i zadržite pritisak da biste premjestili prečac ili upotrijebite prilagođene radnje." @@ -101,7 +90,6 @@ "Instaliraj" "Ne predlaži aplikaciju" "Prikvači predviđenu apl." - "Oblačić" "instaliranje prečaca" "Aplikaciji omogućuje dodavanje prečaca bez intervencije korisnika." "čitati postavke i prečace početnog zaslona" @@ -118,8 +106,6 @@ "Stranica %1$d od %2$d" "Početni zaslon %1$d od %2$d" "Nova stranica početnog zaslona" - "Aktivno" - "Minimizirano" "Mapa je otvorena, %1$d x %2$d" "Dodirnite da biste zatvorili mapu" "Dodirnite da biste spremili promijenjeni naziv" @@ -127,7 +113,6 @@ "Mapa je preimenovana u %1$s" "Mapa: %1$s, %2$d stavke" "Mapa: %1$s, %2$d ili više stavki" - "Neimenovana mapa" "Par aplikacija: %1$s i %2$s" "Pozadina i stil" "Uredi početni zaslon" @@ -135,8 +120,6 @@ "Onemogućio administrator" "Dopusti zakretanje početnog zaslona" "Kada se telefon zakrene" - "Pejzažni način" - "Telefon se postavlja u pejzažni način" "Točke obavijesti" "Uključeno" "Isključeno" @@ -155,8 +138,7 @@ "Instaliranje aplikacije %1$s, %2$s dovršeno" "Preuzimanje aplikacije %1$s, dovršeno %2$s" "Čekanje na instaliranje aplikacije %1$s" - "Aplikacija %1$s je arhivirana." - "preuzmi i vrati" + "Aplikacija %1$s je arhivirana. Dodirnite da biste je preuzeli i vratili." "Aplikacija se treba ažurirati" "Aplikacija ove ikone nije ažurirana. Možete ručno ažurirati da biste ponovo omogućili ovaj prečac ili uklonite ikonu." "Ažuriraj" @@ -165,6 +147,7 @@ "Popis widgeta zatvoren" "Dodajte na početni zaslon" "Premjesti stavku ovdje" + "Stavka je dodana na početni zaslon" "Stavka je uklonjena" "Poništi" "Premještanje stavke" @@ -184,15 +167,11 @@ "Smanjenje širine" "Smanjenje visine" "Širina widgeta promijenjena je na %1$s, a visina na %2$s" - "Izbornik prečaca" - "Okvir za promjenu veličine widgeta za %1$s" - "Zatvori" + "Prečaci" "Odbaci" "Zatvori" "Osobno" "Posao" - "Kartica osobnih aplikacija" - "Kartica poslovnih aplikacija" "Poslovni profil" "Poslovne su aplikacije označene i vidljive vašem IT administratoru" "Shvaćam" @@ -204,11 +183,10 @@ "Shvaćam" "Pauziraj poslovne aplikacije" "Ponovno pokreni" - "Raspored za poslovne aplikacije" "Filtrirajte" "Nije uspjelo: %1$s" "Privatni prostor" - "Dodirnite za postavljanje ili otvaranje" + "Dodirnite da biste postavili ili otvorili" "Privatno" "Postavke privatnog prostora" "Privatno, otključano." @@ -217,5 +195,4 @@ "Prelazak na privatni prostor" "Instalirajte" "Instaliranje aplikacija u privatni prostor" - "Dodajte datoteke i druge sadržaje u privatni prostor" diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml index 848335d8d1..f306110f4f 100644 --- a/res/values-hu/strings.xml +++ b/res/values-hu/strings.xml @@ -29,11 +29,8 @@ "Kezdőképernyő" "A(z) %1$s appot a Beállításokban adhatja meg alapértelmezett kezdőalkalmazásként." "Osztott képernyő" - "Méretarány módosítása" "Alkalmazásinformáció a következőhöz: %1$s" "A(z) %1$s használati beállításai" - "Új ablak" - "Ablakok kezelése" "Alkalmazáspár mentése" "%1$s | %2$s" "Ezt az alkalmazáspárt nem támogatja az eszköz" @@ -41,8 +38,6 @@ "Az alkalmazáspár nem áll rendelkezésre" "Tartsa lenyomva a modult az áthelyezéshez." "Modul áthelyezéséhez koppintson duplán, tartsa nyomva az ujját, vagy használjon egyéni műveleteket." - "További lehetőségek" - "Minden modul mutatása" "%1$d × %2$d" "%1$d széles és %2$d magas" "%1$s modul" @@ -55,7 +50,7 @@ "Újságok és magazinok" "Szórakozás" "Közösségi" - "Javaslatok" + "Neked javasolt" "A %1$s-modulok a jobb, a kereső és a beállítások pedig a bal oldalon találhatók" "{count,plural, =1{# modul}other{# modul}}" "{count,plural, =1{# gyorsparancs}other{# gyorsparancs}}" @@ -69,13 +64,8 @@ "Munka" "Beszélgetések" "Jegyzetelés" - "Hozzáadás gomb megjelenítése" - "Hozzáadás gomb elrejtése" "Hozzáadás" "%1$s modul hozzáadása" - "Az összes megjelenítése" - "Minden modul mutatása" - "Összes modul megjelenítése…" "Ide koppintva módosíthatja a modulbeállításokat" "A modulbeállítások módosítása" "Alkalmazások keresése" @@ -83,7 +73,6 @@ "Nem található alkalmazás a(z) „%1$s” lekérdezésre" "Alkalmazás" "Összes alkalmazás" - "Alkalmazások listája" "Értesítések" "Tartsa lenyomva a parancsikont az áthelyezéshez." "Parancsikon áthelyezéséhez koppintson duplán, és tartsa nyomva az ujját, vagy használjon egyéni műveleteket." @@ -101,7 +90,6 @@ "Telepítés" "Ne javasoljon appot" "Várható kitűzése" - "Buborék" "parancsikonok telepítése" "Lehetővé teszi egy alkalmazás számára, hogy felhasználói beavatkozás nélkül adjon hozzá parancsikonokat." "kezdőképernyő beállításainak és parancsikonjainak olvasása" @@ -118,8 +106,6 @@ "%2$d/%1$d. oldal" "%2$d/%1$d. kezdőképernyő" "Új kezdőképernyő oldal" - "Aktív" - "Kis méret" "Mappa megnyitva – szélesség: %1$d; magasság: %2$d" "Érintse meg a mappa bezárásához" "Koppintson ide az átnevezés mentéséhez" @@ -127,7 +113,6 @@ "A mappa új neve: %1$s" "Mappa: %1$s, %2$d elem" "Mappa: %1$s, %2$d vagy több elem" - "Névtelen mappa" "Alkalmazáspár: %1$s és %2$s" "Háttérkép és stílus" "Kezdőképernyő szerkesztése" @@ -135,8 +120,6 @@ "A rendszergazda letiltotta" "A kezdőképernyő elforgatásának engedélyezése" "A telefon elforgatásakor" - "Fekvő tájolás" - "Állítsa a telefont fekvő tájolásúra" "Értesítési pöttyök" "Be" "Ki" @@ -155,8 +138,7 @@ "Folyamatban van a(z) %1$s telepítése, %2$s kész" "A(z) %1$s letöltése, %2$s kész" "A(z) %1$s telepítésre vár" - "%1$s archiválva." - "letöltés és visszaállítás" + "%1$s archiválva. Koppintson a letöltéshez és a visszaállításhoz." "Alkalmazásfrissítés szükséges" "Az ikonhoz tartozó alkalmazás nincs frissítve. A parancsikon újbóli engedélyezéséhez frissítse az alkalmazást, vagy távolítsa ez az ikont." "Frissítés" @@ -165,6 +147,7 @@ "Widgetlista bezárva" "Hozzáadás a kezdőképernyőhöz" "Elem áthelyezése ide" + "Elem hozzáadva a kezdőképernyőhöz" "Elem eltávolítva" "Mégse" "Elem mozgatása" @@ -184,15 +167,11 @@ "Szélesség csökkentése" "Magasság csökkentése" "Modul átméretezve %1$s szélességre és %2$s magasságra" - "Gyorsparancsok menüje" - "%1$s modul átméretezési kerete" - "Bezárás" + "Gyorsparancsok" "Elvetés" "Bezárás" "Személyes" "Munkahelyi" - "Személyes alkalmazások lap" - "Munkahelyi alkalmazások lap" "Munkaprofil" "A munkahelyi alkalmazások jelvénnyel vannak megjelölve, és ezeket láthatja a rendszergazda" "Értem" @@ -204,7 +183,6 @@ "Értem" "Munkahelyi alkalmazások szüneteltetése" "Folytatás" - "Munkahelyi alkalmazások ütemezése" "Szűrő" "Sikertelen: %1$s" "Privát terület" @@ -217,5 +195,4 @@ "Átállás privát területre…" "Telepítés" "Alkalmazások telepítése privát területre" - "Fájlok és egyebek hozzáadása a privát területhez" diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml index d271a9d879..2d345f2931 100644 --- a/res/values-hy/strings.xml +++ b/res/values-hy/strings.xml @@ -29,11 +29,8 @@ "Հիմնական էկրան" "Սահմանել %1$s գործարկիչը որպես մեկնարկի կանխադրված հավելված Կարգավորումներում" "Տրոհել էկրանը" - "Փոխել կողմերի հարաբերակցությունը" "Տեղեկություններ %1$s հավելվածի մասին" "Օգտագործման կարգավորումներ (%1$s)" - "Նոր պատուհան" - "Կառավարել պատուհանները" "Պահել հավելվ. զույգը" "%1$s | %2$s" "Հավելվածների զույգը չի աջակցվում այս սարքում" @@ -41,8 +38,6 @@ "Հավելվածների զույգը հասանելի չէ" "Հպեք և պահեք՝ վիջեթ տեղափոխելու համար։" "Կրկնակի հպեք և պահեք՝ վիջեթ տեղափոխելու համար, կամ օգտվեք հատուկ գործողություններից։" - "Այլ ընտրանքներ" - "Ցույց տալ բոլոր վիջեթները" "%1$d × %2$d" "Լայնությունը՝ %1$d, բարձրությունը՝ %2$d" "%1$s վիջեթ" @@ -69,13 +64,8 @@ "Աշխատանքային" "Զրույցներ" "Նշումների ստեղծում" - "Ցույց տալ «Ավելացնել» կոճակը" - "Թաքցնել «Ավելացնել» կոճակը" "Ավելացնել" "Ավելացնել %1$s վիջեթը" - "Բոլորը" - "Ցույց տալ բոլոր վիջեթները" - "Բոլոր վիջեթները ցուցադրված են" "Հպեք՝ վիջեթի կարգավորումները փոփոխելու համար" "Փոխել վիջեթի կարգավորումները" "Որոնել հավելվածներ" @@ -83,7 +73,6 @@ %1$s» հարցմանը համապատասխանող հավելվածներ չեն գտնվել" "Հավելված" "Բոլոր հավելվածները" - "Հավելվածների ցանկ" "Ծանուցումներ" "Հպեք և պահեք՝ դյուրանցում տեղափոխելու համար։" "Կրկնակի հպեք և պահեք՝ դյուրանցում տեղափոխելու համար, կամ օգտվեք հատուկ գործողություններից։" @@ -101,7 +90,6 @@ "Տեղադրել" "Չառաջարկել" "Ամրացնել առաջարկվող հավելվածը" - "Ամպիկ" "Դյուրանցումների տեղադրում" "Հավելվածին թույլ է տալիս ավելացնել դյուրանցումներ՝ առանց օգտագործողի միջամտության:" "կարդալ հիմնական էկրանի կարգավորումներն ու դյուրանցումները" @@ -118,8 +106,6 @@ "Էջ %1$d՝ %2$d-ից" "Հիմնական էկրան %1$d` %2$d-ից" "Հիմնական էկրանի նոր էջ" - "Ակտիվ է" - "Նվազեցվել է" "Պանակը բաց է, %1$d-ից %2$d" "Հպեք՝ պանակը փակելու համար" "Հպեք՝ նոր անվանումը պահելու համար" @@ -127,7 +113,6 @@ "Պանակը վերանվանվեց %1$s" "Պանակ՝ %1$s, %2$d տարր" "Պանակ՝ %1$s, %2$d կամ ավելի տարրեր" - "Անանուն պանակ" "Հավելվածների զույգ՝ %1$s և %2$s" "Պաստառ և ոճ" "Փոփոխել հիմնական էկրանը" @@ -135,8 +120,6 @@ "Անջատվել է ձեր ադմինիստրատորի կողմից" "Թույլ տալ հիմնական էկրանի պտտումը" "Հեռախոսը պտտելու դեպքում" - "Հորիզոնական" - "Հեռախոսն օգտագործել հորիզոնական ռեժիմում" "Ծանուցումների կետիկներ" "Միացված է" "Անջատված է" @@ -144,7 +127,7 @@ "Ծանուցումների կետիկները ցուցադրելու համար միացրեք ծանուցումները %1$s-ի համար" "Փոխել կարգավորումները" "Ցուցադրել ծանուցումների կետիկները" - "Ծրագրավորողի ընտրանքներ" + "Մշակողի ընտրանքներ" "Ավելացնել պատկերակները հիմնական էկրանին" "Նոր հավելվածների համար" "Անհայտ է" @@ -155,8 +138,7 @@ "%1$s հավելվածը տեղադրվում է, կատարված է %2$s-ը" "%1$s–ի ներբեռնում (%2$s)" "%1$s-ի տեղադրման սպասում" - "%1$s հավելվածն արխիվացված է։" - "ներբեռնել և վերականգնել" + "%1$s հավելվածն արխիվացված է։ Հպեք՝ ներբեռնելու և վերականգնելու համար։" "Պահանջվում է թարմացնել հավելվածը" "Հավելվածը հնացել է։ Թարմացրեք այն ձեռքով, որպեսզի շարունակեք օգտագործել դյուրանցումը, կամ հեռացրեք հավելվածի պատկերակը։" "Թարմացնել" @@ -165,6 +147,7 @@ "Վիջեթների ցանկը փակվեց" "Ավելացնել հիմնական էկրանին" "Տեղափոխել տարրն այստեղ" + "Տարրն ավելացվեց հիմնական էկրանին" "Տարրը հեռացվեց" "Հետարկել" "Տեղափոխել տարրը" @@ -184,15 +167,11 @@ "Նվազեցնել լայնությունը" "Նվազեցնել բարձրությունը" "Վիջեթի լայնությունը փոխվել է %1$s-ի, իսկ բարձրությունը՝ %2$s-ի" - "Դյուրանցման ընտրացանկ" - %1$s» վիջեթի չափսի փոփոխման շրջանակ" - "Փակել" + "Դյուրանցումներ" "Անտեսել" "Փակել" "Անձնական" "Աշխատանքային" - "Անձնական հավելվածների ներդիր" - "Աշխատանքային հավելվածների ներդիր" "Աշխատանքային պրոֆիլ" "Աշխատանքային հավելվածները հատուկ նշանակ ունեն և տեսանելի են ՏՏ ադմինիստրատորին" "Եղավ" @@ -204,10 +183,9 @@ "Եղավ" "Դադարեցնել աշխատանքային հավելվածները" "Վերսկսել" - "Աշխատանքային հավելվածների ժամանակացույց" "Զտեք" "Չհաջողվեց կատարել գործողությունը (%1$s)" - "Մասնավոր տարածք" + "Անձնական տարածք" "Հպեք կարգավորելու կամ բացելու համար" "Մասնավոր" "Անձնական տարածքի կարգավորումներ" @@ -217,5 +195,4 @@ "Անցում մասնավոր տարածք" "Տեղադրել" "Հավելվածների տեղադրում անձնական տարածքում" - "Ձեր մասնավոր տարածքում ավելացրեք ֆայլեր և ավելին" diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml index 314f481a22..9ced9f4832 100644 --- a/res/values-in/strings.xml +++ b/res/values-in/strings.xml @@ -29,11 +29,8 @@ "Layar utama" "Jadikan %1$s sebagai aplikasi layar utama default di Setelan" "Layar terpisah" - "Ubah rasio aspek" "Info aplikasi untuk %1$s" "Setelan penggunaan untuk %1$s" - "Jendela Baru" - "Kelola Jendela" "Simpan pasangan aplikasi" "%1$s | %2$s" "Pasangan aplikasi ini tidak didukung di perangkat ini" @@ -41,8 +38,6 @@ "Pasangan aplikasi tidak tersedia" "Sentuh lama untuk memindahkan widget." "Ketuk dua kali & tahan untuk memindahkan widget atau gunakan tindakan khusus." - "Opsi lainnya" - "Tampilkan semua widget" "%1$d × %2$d" "lebar %1$d x tinggi %2$d" "Widget %1$s" @@ -69,13 +64,8 @@ "Kerja" "Percakapan" "Pembuatan catatan" - "Tampilkan tombol tambahkan" - "Sembunyikan tombol tambahkan" "Tambahkan" "Tambahkan widget %1$s" - "Tampilkan semua" - "Tampilkan semua widget" - "Menampilkan semua widget" "Ketuk untuk mengubah setelan widget" "Ubah setelan widget" "Telusuri aplikasi" @@ -83,7 +73,6 @@ "Tidak ditemukan aplikasi yang cocok dengan \"%1$s\"" "Aplikasi" "Semua aplikasi" - "Daftar aplikasi" "Notifikasi" "Sentuh lama untuk memindahkan pintasan." "Ketuk dua kali & tahan untuk memindahkan pintasan atau gunakan tindakan khusus." @@ -101,7 +90,6 @@ "Instal" "Jangan sarankan apl" "Pin Prediksi" - "Balon" "memasang pintasan" "Mengizinkan aplikasi menambahkan pintasan tanpa campur tangan pengguna." "membaca setelan dan pintasan layar utama" @@ -118,8 +106,6 @@ "Halaman %1$d dari %2$d" "Layar utama %1$d dari %2$d" "Halaman layar utama baru" - "Aktif" - "Diperkecil" "Folder dibuka, %1$d x %2$d" "Ketuk untuk menutup folder" "Ketuk untuk menyimpan ganti nama" @@ -127,7 +113,6 @@ "Folder diganti namanya menjadi %1$s" "Folder: %1$s, %2$d item" "Folder: %1$s, %2$d item atau lebih" - "Folder tanpa nama" "Pasangan aplikasi: %1$s dan %2$s" "Wallpaper & gaya" "Edit Layar Utama" @@ -135,8 +120,6 @@ "Dinonaktifkan oleh admin" "Izinkan layar utama diputar" "Saat ponsel diputar" - "Mode lanskap" - "Setel ponsel ke mode lanskap" "Titik notifikasi" "Aktif" "Nonaktif" @@ -155,8 +138,7 @@ "%1$s sedang diinstal, %2$s selesai" "%1$s sedang didownload, %2$s selesai" "%1$s menunggu dipasang" - "%1$s diarsipkan." - "download dan pulihkan" + "%1$s diarsipkan. Ketuk untuk mendownload dan memulihkan." "Aplikasi perlu diupdate" "Aplikasi untuk ikon ini belum diupdate. Anda dapat mengupdate secara manual untuk mengaktifkan kembali pintasan ini, atau hapus ikon." "Update" @@ -165,6 +147,7 @@ "Daftar widget ditutup" "Tambahkan ke layar utama" "Pindahkan item ke sini" + "Item ditambahkan ke layar utama" "Item dihapus" "Urungkan" "Pindahkan item" @@ -184,15 +167,11 @@ "Kurangi lebar" "Kurangi tinggi" "Widget diubah ukurannya menjadi lebar %1$s tinggi %2$s" - "Menu Pintasan" - "Bingkai Ubah Ukuran Widget untuk %1$s" - "Tutup" + "Pintasan" "Tutup" "Tutup" "Pribadi" "Kerja" - "Tab aplikasi pribadi" - "Tab aplikasi kerja" "Profil kerja" "Aplikasi kerja diberi badge dan terlihat oleh admin IT" "Oke" @@ -204,7 +183,6 @@ "Oke" "Jeda aplikasi kerja" "Aktifkan lagi" - "Jadwal aplikasi kerja" "Filter" "Gagal: %1$s" "Ruang privasi" @@ -217,5 +195,4 @@ "Ruang Pribadi Bertransisi" "Instal" "Instal aplikasi ke Ruang Pribadi" - "Tambahkan file dan lainnya ke Ruang Privasi" diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml index 3f18c8d617..2ab781732c 100644 --- a/res/values-is/strings.xml +++ b/res/values-is/strings.xml @@ -29,11 +29,8 @@ "Heim" "Stilltu %1$s sem sjálfgefið heimaforrit í stillingunum" "Skipta skjá" - "Breyta myndhlutfalli" "Upplýsingar um forrit fyrir %1$s" "Notkunarstillingar fyrir %1$s" - "Nýr gluggi" - "Stjórna gluggum" "Vista forritapar" "%1$s | %2$s" "Þetta forritapar er ekki stutt í þessu tæki" @@ -41,8 +38,6 @@ "Forritapar er ekki í boði" "Haltu fingri á græju til að færa hana." "Ýttu tvisvar og haltu fingri á græju til að færa hana eða notaðu sérsniðnar aðgerðir." - "Fleiri valkostir" - "Sýna allar græjur" "%1$d × %2$d" "%1$d á breidd og %2$d á hæð" "Græjan %1$s" @@ -69,13 +64,8 @@ "Vinna" "Samtöl" "Glósugerð" - "Sýna hnapp til að bæta við" - "Fela hnapp til að bæta við" "Bæta við" "Bæta græjunni %1$s við" - "Sýna allt" - "Sýna allar græjur" - "Sýnir allar græjur" "Ýttu til að breyta græjustillingum" "Breyta græjustillingum" "Leita í forritum" @@ -83,7 +73,6 @@ "Ekki fundust forrit sem samsvara „%1$s“" "Forrit" "Öll forrit" - "Forritalisti" "Tilkynningar" "Haltu fingri á flýtileið til að færa hana." "Ýttu tvisvar og haltu fingri á flýtileið til að færa hana eða notaðu sérsniðnar aðgerðir." @@ -101,7 +90,6 @@ "Setja upp" "Ekki fá tillögu að forriti" "Festa tillögu" - "Blaðra" "setja upp flýtileiðir" "Leyfir forriti að bæta við flýtileiðum án íhlutunar notanda." "lesa stillingar og flýtileiðir heimaskjás" @@ -118,8 +106,6 @@ "Síða %1$d af %2$d" "Heimaskjár %1$d af %2$d" "Ný síða á heimaskjá" - "Virkt" - "Minnkað" "Mappa opnuð, %1$d sinnum %2$d" "Ýttu til að loka möppunni" "Ýttu til að vista breytt heiti" @@ -127,7 +113,6 @@ "Heiti möppu breytt í %1$s" "Mappa: %1$s, %2$d atriði" "Mappa: %1$s, %2$d eða fleiri atriði" - "Mappa án heitis" "Forritapar: %1$s og %2$s" "Veggfóður og stíll" "Breyta heimaskjá" @@ -135,8 +120,6 @@ "Gert óvirkt af kerfisstjóra" "Leyfa snúning á heimaskjá" "Þegar símanum er snúið" - "Langsnið" - "Stilla síma á langsnið" "Tilkynningapunktar" "Kveikt" "Slökkt" @@ -155,8 +138,7 @@ "Setur upp %1$s, %2$s lokið" "%1$s í niðurhali, %2$s lokið" "%1$s bíður uppsetningar" - "%1$s er í geymslu." - "sækja og endurheimta" + "%1$s er í geymslu. Ýttu til að sækja og endurheimta." "Uppfæra þarf forritið" "Forritið fyrir þetta tákn er ekki uppfært. Þú getur uppfært það handvirkt til að kveikja aftur á þessari flýtileið eða fjarlægt táknið." "Uppfæra" @@ -165,6 +147,7 @@ "Græjulista lokað" "Bæta á heimaskjá" "Færa atriði hingað" + "Atriði bætt á heimaskjáinn" "Atriði fjarlægt" "Afturkalla" "Færa atriði" @@ -184,15 +167,11 @@ "Minnka breidd" "Minnka hæð" "Stærð græju breytt í %1$s á breidd og %2$s á hæð" - "Flýtileiðavalmynd" - "Rammi til að breyta stærð græjunnar „%1$s“" - "Loka" + "Flýtileiðir" "Hunsa" "Loka" "Persónulegt" "Vinna" - "Flipi forrita til einkanota" - "Flipi vinnuforrita" "Vinnusnið" "Vinnuforrit eru merkt og kerfisstjórinn getur séð þau" "Ég skil" @@ -204,12 +183,11 @@ "Ég skil" "Setja vinnuforrit í bið" "Ljúka hléi" - "Áætlun vinnuforrita" "Sía" "Mistókst: %1$s" "Leynirými" "Ýttu til að setja upp eða opna" - "Leynilegt" + "Lokað" "Stillingar einkarýmis" "Lokað, ólæst." "Lokað, læst." @@ -217,5 +195,4 @@ "Leynirými að breytast" "Setja upp" "Setja upp forrit í leynirými" - "Bættu skrám og fleiru við leynirými" diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml index 994e555971..a59de6c049 100644 --- a/res/values-it/strings.xml +++ b/res/values-it/strings.xml @@ -29,11 +29,8 @@ "Home" "Imposta %1$s come app iniziale predefinita nelle Impostazioni" "Schermo diviso" - "Cambia proporzioni" "Informazioni sull\'app %1$s" "Impostazioni di utilizzo per %1$s" - "Nuova finestra" - "Gestisci finestre" "Salva coppia di app" "%1$s | %2$s" "Questa coppia di app non è supportata su questo dispositivo" @@ -41,13 +38,11 @@ "La coppia di app non è disponibile" "Tocca e tieni premuto per spostare un widget." "Tocca due volte e tieni premuto per spostare un widget o per usare le azioni personalizzate." - "Altre opzioni" - "Mostra tutti i widget" "%1$d × %2$d" "%1$d di larghezza per %2$d di altezza" "Widget %1$s" "Widget %1$s, %2$d di larghezza per %3$d di altezza" - "Tieni premuto il widget per spostarlo nella schermata Home" + "Tocca e tieni premuto il widget per spostarlo nella schermata Home" "Aggiungi alla schermata Home" "Widget %1$s aggiunto alla schermata Home" "Suggerimenti" @@ -69,13 +64,8 @@ "Lavoro" "Conversazioni" "Aggiunta di note" - "Mostra pulsante Aggiungi" - "Nascondi pulsante Aggiungi" "Aggiungi" "Aggiungi widget %1$s" - "Mostra tutto" - "Mostra tutti i widget" - "Visualizzazione di tutti i widget" "Tocca per modificare le impostazioni del widget" "Modifica le impostazioni del widget" "Cerca nelle app" @@ -83,7 +73,6 @@ "Nessuna app trovata corrispondente a \"%1$s\"" "App" "Tutte le app" - "Elenco di app" "Notifiche" "Tocca e tieni premuto per spostare una scorciatoia." "Tocca due volte e tieni premuto per spostare una scorciatoia o per usare le azioni personalizzate." @@ -101,7 +90,6 @@ "Installa" "Non suggerire app" "Blocca previsione" - "Fumetto" "Aggiunta di scorciatoie" "Consente a un\'app di aggiungere scorciatoie automaticamente." "leggere le impostazioni e le scorciatoie nella schermata Home" @@ -118,8 +106,6 @@ "Pagina %1$d di %2$d" "Schermata Home %1$d di %2$d" "Nuova pagina Schermata Home" - "Attiva" - "Ridotta a icona" "Cartella aperta, %1$d per %2$d" "Tocca per chiudere la cartella" "Tocca per salvare il nuovo nome" @@ -127,16 +113,13 @@ "Nome della cartella sostituito con %1$s" "Cartella: %1$s, %2$d elementi" "Cartella: %1$s, %2$d o più elementi" - "Cartella senza nome" "Coppia di app: %1$s and %2$s" "Sfondo e stile" - "Modifica schermata Home" + "Modifica la schermata Home" "Impostazioni schermata Home" "Disattivata dall\'amministratore" "Consenti rotazione della schermata Home" "Con il telefono ruotato" - "Modalità Orizzontale" - "Imposta lo smartphone in modalità Orizzontale" "Indicatori di notifica" "On" "Off" @@ -155,8 +138,7 @@ "Installazione di %1$s, completamento: %2$s" "Download di %1$s in corso, %2$s completato" "%1$s in attesa di installazione" - "App %1$s archiviata." - "download e ripristino" + "App %1$s archiviata. Tocca per scaricare e ripristinare." "È necessario aggiornare l\'app" "L\'app relativa a questa icona non è aggiornata. Puoi eseguire manualmente l\'aggiornamento per riattivare questa scorciatoia oppure rimuovere l\'icona." "Aggiorna" @@ -165,6 +147,7 @@ "Elenco di widget chiuso" "Aggiungi alla schermata Home" "Sposta elemento qui" + "Elemento aggiunto alla schermata Home" "Elemento rimosso" "Annulla" "Sposta elemento" @@ -184,15 +167,11 @@ "Riduci larghezza" "Riduci altezza" "Widget ridimensionato a larghezza %1$s, altezza %2$s" - "Menu scorciatoie" - "Frame ridimensionamento widget per %1$s" - "Chiudi" + "Scorciatoie" "Ignora" "Esci" "Personali" "Lavoro" - "Scheda App personali" - "Scheda App di lavoro" "Profilo di lavoro" "Le app di lavoro sono contrassegnate con un badge e visibili all\'amministratore IT" "OK" @@ -204,18 +183,16 @@ "OK" "Metti in pausa le app di lavoro" "Riattiva" - "Programmazione app di lavoro" "Filtra" "Operazione non riuscita: %1$s" "Spazio privato" "Tocca per configurare o aprire" "Privato" - "Impostazioni dello spazio privato" + "Impostazioni dello Spazio privato" "Privato, sbloccato." "Privato, bloccato." "Blocca" "Transizione dello Spazio privato in corso…" "Installa" "Installa le app su spazi privati" - "Aggiungi file e altro allo spazio privato" diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml index 3ef72d9afa..89a0c8aa8b 100644 --- a/res/values-iw/strings.xml +++ b/res/values-iw/strings.xml @@ -29,11 +29,8 @@ "בית" "הגדרה של %1$s כאפליקציית הבית ב\'הגדרות\'" "מסך מפוצל" - "שינוי של יחס הגובה-רוחב" "‏פרטים על האפליקציה %1$s" "‏הגדרות שימוש ב-%1$s" - "חלון חדש" - "ניהול החלונות" "שמירת צמד אפליקציות" "%1$s | %2$s" "צמד האפליקציות הזה לא נתמך במכשיר הזה" @@ -41,8 +38,6 @@ "צמד האפליקציות לא זמין" "להעברת ווידג\'ט למקום אחר לוחצים עליו לחיצה ארוכה." "כדי להעביר ווידג\'ט למקום אחר או להשתמש בפעולות מותאמות אישית, יש ללחוץ פעמיים ולא להרפות." - "אפשרויות נוספות" - "הצגת כל הווידג\'טים" "%1$d × %2$d" "‏רוחב %1$d על גובה %2$d" "ווידג\'ט %1$s" @@ -66,24 +61,18 @@ "אין ווידג\'טים או קיצורי דרך" "לא נמצאו ווידג\'טים או קיצורי דרך" "ווידג\'טים אישיים" - "ווידג\'טים לעבודה" + "עבודה" "שיחות" "כתיבת הערות" - "הצגת כפתור ההוספה" - "הסתרת כפתור ההוספה" "הוספה" "הוספת הווידג\'ט %1$s" - "הצגת הכול" - "הצגת כל הווידג\'טים" - "כל הווידג\'טים מוצגים" - "אפשר לשנות את הגדרות הווידג\'ט בלחיצה" + "אפשר לשנות את הגדרות הווידג\'ט בהקשה" "שינוי הגדרות הווידג\'ט" "חיפוש אפליקציות" "טעינת אפליקציות מתבצעת…" "לא נמצאו אפליקציות התואמות ל-\"%1$s\"" "אפליקציה" "כל האפליקציות" - "רשימת האפליקציות" "התראות" "כדי להעביר קיצור דרך למקום אחר יש לגעת ולא להרפות." "כדי להעביר קיצור דרך למקום אחר או להשתמש בפעולות מותאמות אישית\' יש ללחוץ פעמיים ולא להרפות." @@ -94,14 +83,13 @@ "רשימת אפליקציות אישיות" "רשימת אפליקציות עבודה" "הסרה" - "הסרת ההתקנה" + "להסרת התקנה" "פרטי האפליקציה" "התקנה במרחב הפרטי" "הסרת האפליקציה" "התקנה" "בלי להציע את האפליקציה" "הצמדת החיזוי" - "בועה" "התקנת קיצורי דרך" "מאפשר לאפליקציה להוסיף קיצורי דרך ללא התערבות המשתמש." "קריאת ההגדרות וקיצורי הדרך בדף הבית" @@ -110,7 +98,7 @@ "מאפשרת לאפליקציה לשנות את ההגדרות וקיצורי הדרך בדף הבית." "לא ניתן לטעון את הווידג\'ט" "הגדרות הווידג\'ט" - "צריך ללחוץ כדי לסיים את תהליך ההגדרה" + "צריך להקיש כדי לסיים את תהליך ההגדרה" "זוהי אפליקציית מערכת ולא ניתן להסיר את התקנתה." "עריכת השם" "%1$s מושבתת" @@ -118,16 +106,13 @@ "‏דף %1$d מתוך %2$d" "‏מסך הבית %1$d מתוך %2$d" "מסך הבית חדש" - "פעיל" - "ממוזער" "תיקייה פתוחה, %1$d על %2$d" - "יש ללחוץ כדי לסגור את התיקייה" - "יש ללחוץ כדי לשמור שינוי שם" + "יש להקיש כדי לסגור את התיקייה" + "יש להקיש כדי לשמור שינוי שם" "התיקייה נסגרה" "שם התיקייה שונה ל-%1$s" "תיקייה: %1$s, מספר הפריטים: %2$d" "תיקייה: %1$s, %2$d פריטים או יותר" - "תיקייה ללא שם" "צמד אפליקציות: %1$s ו-%2$s" "טפט וסגנון" "עריכה של מסך הבית" @@ -135,8 +120,6 @@ "הושבת על ידי מנהל המערכת שלך" "סיבוב מסך הבית" "כאשר מסובבים את הטלפון" - "פריסה לרוחב" - "העברת הטלפון לפריסה לרוחב" "סימני ההתראות" "מופעל" "כבוי" @@ -155,8 +138,7 @@ "%1$s בתהליך התקנה, %2$s הושלמו" "הורדת %1$s מתבצעת, %2$s הושלמו" "מחכה להתקנה של %1$s" - "אפליקציית %1$s הועברה לארכיון." - "הורדה ושחזור" + "אפליקציית %1$s הועברה לארכיון. אפשר להקיש כדי להוריד ולשחזר אותה." "נדרש עדכון לאפליקציה" "האפליקציה של הסמל הזה לא מעודכנת. אפשר לעדכן אותה ידנית כדי להפעיל מחדש את קיצור הדרך הזה, או להסיר את הסמל." "עדכון" @@ -165,6 +147,7 @@ "רשימת הווידג\'טים נסגרה" "הוספה למסך הבית" "העברת הפריט לכאן" + "הפריט הועבר אל מסך הבית" "הפריט הוסר" "ביטול" "העברת הפריט" @@ -184,15 +167,11 @@ "הקטנת רוחב" "הקטנת גובה" "גודל הווידג\'ט שונה - רוחב %1$s גובה %2$s" - "תפריט קיצורי הדרך" - "מסגרת שינוי גודל הווידג\'ט של %1$s" - "סגירה" + "קיצורי דרך" "סגירה" "סגירה" "אישי" "עבודה" - "הכרטיסייה \"אפליקציות לשימוש אישי\"" - "הכרטיסייה \"אפליקציות לעבודה\"" "פרופיל עבודה" "‏האפליקציות לעבודה מתויגות ומוצגות למנהל ה-IT" "הבנתי" @@ -204,11 +183,10 @@ "הבנתי" "השהיית האפליקציות לעבודה" "ביטול ההשהיה" - "לוח זמנים להשהיית אפליקציות לעבודה" "סינון" "הפעולה נכשלה: %1$s" "מרחב פרטי" - "יש ללחוץ כדי להגדיר או לפתוח" + "יש להקיש כדי להגדיר או לפתוח" "פרטי" "הגדרות המרחב הפרטי" "פרטי, פתוח." @@ -217,5 +195,4 @@ "מעבר למרחב הפרטי" "התקנה" "התקנת אפליקציות במרחב הפרטי" - "הוספת קבצים ופריטים נוספים למרחב הפרטי" diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml index 09d4ecf55c..b48c7bea3e 100644 --- a/res/values-ja/strings.xml +++ b/res/values-ja/strings.xml @@ -29,11 +29,8 @@ "ホーム" "[設定] で %1$s をデフォルトのホームアプリに設定します" "分割画面" - "アスペクト比を変更" "%1$s のアプリ情報" "%1$s の使用設定" - "新しいウィンドウ" - "ウィンドウを管理" "アプリのペア設定を保存" "%1$s | %2$s" "このデバイスは、このアプリのペア設定に対応していません" @@ -41,13 +38,11 @@ "アプリのペア設定は利用できません" "長押ししてウィジェットを移動させます。" "ウィジェットをダブルタップして長押ししながら移動するか、カスタム操作を使用してください。" - "その他のオプション" - "すべてのウィジェットを表示" "%1$dx%2$d" "幅 %1$d、高さ %2$d" "%1$s ウィジェット" "%1$sウィジェット、幅%2$d、高さ%3$d" - "ウィジェットを長押しすると、ホーム画面上の任意の場所に移動できます" + "ウィジェットを押し続けると、ホーム画面上に移動できます" "ホーム画面に追加" "「%1$s」ウィジェットをホーム画面に追加しました" "候補" @@ -69,13 +64,8 @@ "仕事用" "会話" "メモ" - "追加ボタンを表示する" - "追加ボタンを非表示にする" "追加" "%1$sウィジェットを追加" - "すべて表示" - "すべてのウィジェットを表示" - "すべてのウィジェットを表示しています" "タップしてウィジェットの設定を変更する" "ウィジェットの設定を変更します" "アプリを検索" @@ -83,7 +73,6 @@ "「%1$s」に一致するアプリは見つかりませんでした" "アプリ" "すべてのアプリ" - "アプリ一覧" "通知" "長押ししてショートカットを移動してください。" "ショートカットをダブルタップして長押ししながら移動するか、カスタム操作を使用してください。" @@ -101,7 +90,6 @@ "インストール" "アプリを表示しない" "アプリの候補を固定" - "ふきだし" "ショートカットのインストール" "ユーザー操作なしでショートカットを追加することをアプリに許可します。" "ホームの設定とショートカットの読み取り" @@ -118,8 +106,6 @@ "%1$d/%2$dページ" "ホーム画面: %1$d/%2$d" "新しいホーム画面ページ" - "有効" - "最小化" "フォルダが開いています。%1$dx%2$dの大きさです" "タップしてフォルダを閉じます" "タップして変更後の名前を保存します" @@ -127,7 +113,6 @@ "フォルダの名前を「%1$s」に変更しました" "フォルダ: %1$s%2$d 件のアイテム" "フォルダ: %1$s%2$d 件以上のアイテム" - "名前のないフォルダ" "アプリのペア設定: %1$s%2$s" "壁紙とスタイル" "ホーム画面を編集" @@ -135,8 +120,6 @@ "管理者により無効にされています" "ホーム画面の回転を許可" "スマートフォンの向きに合わせます" - "横表示" - "スマートフォンを横表示にします" "通知ドット" "ON" "OFF" @@ -155,8 +138,7 @@ "%1$s をインストールしています: %2$s 完了" "%1$sをダウンロード中、%2$s完了" "%1$sのインストール待ち" - "%1$s はアーカイブ済みです。" - "ダウンロードして復元" + "%1$sはアーカイブ済みです。ダウンロードして復元するには、タップしてください。" "アプリの更新が必要" "このアイコンのアプリは更新されていません。手動で更新して、このショートカットを再度有効にできます。また、アイコンを削除することもできます。" "更新" @@ -165,6 +147,7 @@ "ウィジェット リストを閉じました" "ホーム画面に追加" "アイテムをここに移動" + "アイテムをホーム画面に追加しました" "アイテムを削除しました" "元に戻す" "アイテムを移動" @@ -184,15 +167,11 @@ "幅を狭くする" "高さを低くする" "ウィジェットのサイズを幅%1$s、高さ%2$sに変更しました" - "ショートカット メニュー" - "%1$s のウィジェットのサイズ変更フレーム" - "閉じる" + "ショートカット" "表示しない" "閉じる" "個人用" "仕事用" - "個人用アプリのタブ" - "仕事用アプリのタブ" "仕事用プロファイル" "仕事用アプリはバッジ付きで表示され、IT 管理者に公開されます" "OK" @@ -204,7 +183,6 @@ "OK" "仕事用アプリを一時停止" "停止解除" - "仕事用アプリのスケジュール" "フィルタ" "失敗: %1$s" "プライベート スペース" @@ -217,5 +195,4 @@ "プライベート スペース移行中" "インストール" "プライベート スペースにアプリをインストールします" - "プライベート スペースにファイルなどを追加する" diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml index f29c9e8768..76b8b5d6c0 100644 --- a/res/values-ka/strings.xml +++ b/res/values-ka/strings.xml @@ -29,11 +29,8 @@ "მთავარი გვერდი" "%1$s-ის დაყენება ნაგულისხმევ Home აპად პარამეტრებში" "ეკრანის გაყოფა" - "თანაფარდობის შეცვლა" "%1$s-ის აპის ინფო" "გამოყენების პარამეტრები %1$s-ისთვის" - "ახალი ფანჯარა" - "ფანჯრების მართვა" "აპთა წყვილის შენახვა" "%1$s | %2$s" "ამ მოწყობილობაზე აღნიშნული აპთა წყვილი არ არის მხარდაჭერილი" @@ -41,8 +38,6 @@ "აპთა წყვილი მიუწვდომელია" "შეხებით აირჩიეთ და გეჭიროთ ვიჯეტის გადასაადგილებლად." "ორმაგი შეხებით აირჩიეთ და გეჭიროთ ვიჯეტის გადასაადგილებლად ან მორგებული მოქმედებების გამოსაყენებლად." - "სხვა ვარიანტები" - "ყველა ვიჯეტის ჩვენება" "%1$d × %2$d" "სიგრძე: %1$d, სიგანე: %2$d" "%1$s ვიჯეტი" @@ -69,13 +64,8 @@ "სამსახური" "მიმოწერები" "ჩანიშვნა" - "დამატების ღილაკის ჩვენება" - "დამატების ღილაკის დამალვა" "დამატება" "%1$s ვიჯეტის დამატება" - "ყველას ჩვენება" - "ყველა ვიჯეტის ჩვენება" - "ნაჩვენებია ყველა ვიჯეტი" "შეეხეთ ვიჯეტის პარამეტრების შესაცვლელად" "ვიჯეტის პარამეტრების შეცვლა" "აპების ძიება" @@ -83,7 +73,6 @@ "„%1$s“-ის თანხვედრი აპები არ მოიძებნა" "აპი" "ყველა აპი" - "აპების სია" "შეტყობინებები" "შეხებით აირჩიეთ და გეჭიროთ მალსახმობის გადასაადგილებლად." "ორმაგი შეხებით აირჩიეთ და გეჭიროთ მალსახმობის გადასაადგილებლად ან მორგებული მოქმედებების გამოსაყენებლად." @@ -101,7 +90,6 @@ "ინსტალაცია" "არ შემომთავაზო აპი" "ჩამაგრების პროგნოზირება" - "ბუშტი" "მალსახმობების დაყენება" "აპისთვის მალსახმობების დამოუკიდებლად დამატების უფლების მიცემა." "მთავარი ეკრანის პარამეტრებისა და მალსახმობების წაკითხვა" @@ -118,8 +106,6 @@ "გვერდი %1$d %2$d-დან" "მთავარი ეკრანი %1$d, %2$d-დან" "მთავარი ეკრანის ახალი გვერდი" - "აქტიური" - "დაპატარავებული" "საქაღალდე გახსნილია, %1$d x %2$d" "შეეხეთ საქაღალდის დასახურად" "შეეხეთ გადარქმეული სახელის შესანახად" @@ -127,7 +113,6 @@ "საქაღალდეს შეეცვალა სახელი „%1$s“-ად" "საქაღალდე: %1$s, %2$d ერთეული" "საქაღალდე: %1$s, %2$d ან მეტი ერთეული" - "უსახელო საქაღალდე" "აპთა წყვილი: %1$s და %2$s" "ფონი და სტილი" "მთავარი ეკრანის რედაქტირება" @@ -135,8 +120,6 @@ "გათიშულია თქვენი ადმინისტრატორის მიერ" "მთავარი ეკრანის შეტრიალების დაშვება" "ტელეფონის შეტრიალებისას" - "პეიზაჟის რეჟიმი" - "ტელეფონის დაყენება პეიზაჟის რეჟიმში" "შეტყობინების ნიშნულები" "ჩართულია" "გამორთულია" @@ -155,8 +138,7 @@ "ინსტალირდება %1$s, %2$s დასრულებულია" "მიმდინარეობს %1$s-ის ჩამოტვირთვა, %2$s დასრულდა" "%1$s ელოდება ინსტალაციას" - "%1$s დაარქივებულია." - "ჩამოტვირთვა და აღდგენა" + "%1$s დაარქივებულია. შეეხეთ გადმოსაწერად და აღსადგენად." "საჭიროა აპის განახლება" "ამ ხატულის აპი განახლებული არ არის. შეგიძლიათ, ხელით განაახლოთ ამ მალსახმობის ხელახლა გასააქტიურებლად, ან ამოშალოთ ხატულა." "განახლება" @@ -165,6 +147,7 @@ "ვიჯეტების სია დაიხურა" "მთავარ ეკრანზე დამატება" "ერთეულის გადაადგილება აქ" + "ერთეული დაემატა მთავარ ეკრანს" "ერთეული წაიშალა" "მოქმედების გაუქმება" "ერთეულის გადაადგილება" @@ -184,15 +167,11 @@ "სიგანის შემცირება" "სიმაღლის შემცირება" "ვიჯეტის ზომები შეიცვალა: სიგანე %1$s სიმაღლე %2$s" - "მალსახმობის მენიუ" - "ვიჯეტის ზომის შეცვლის ფრეიმი %1$s-ისთვის" - "დახურვა" + "მალსახმობები" "დახურვა" "დახურვა" "პირადი" "სამსახური" - "პირადი აპების ჩანართი" - "სამსახურის აპების ჩანართი" "სამსახურის პროფილი" "სამსახურის აპები ბეჯით არის მონიშნული და ხილულია თქვენი IT ადმინისტრატორისთვის" "გასაგებია" @@ -204,12 +183,11 @@ "გასაგებია" "სამსახურის აპების დაპაუზება" "პაუზის გაუქმება" - "სამსახურის აპების განრიგი" "ფილტრი" "ვერ მოხერხდა: %1$s" "პირადი სივრცე" "დასაყენებლად ან გასახსნელად შეეხეთ" - "კერძო" + "პირადი" "პირადი სივრცის პარამეტრები" "პირადი (განბლოკილი)." "პირადი (ჩაკეტილი)." @@ -217,5 +195,4 @@ "პირად სივრცეზე გადასვლა" "ინსტალაცია" "კერძო სივრცეში აპების ინსტალაცია" - "კერძო სივრცეში დაამატეთ ფაილები და სხვა" diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml index 96e82868cd..95d4420157 100644 --- a/res/values-kk/strings.xml +++ b/res/values-kk/strings.xml @@ -29,11 +29,8 @@ "Негізгі экран" "Параметрлерден %1$s қолданбасын әдепкі негізгі экран қолданбасы ретінде орнатыңыз." "Экранды бөлу" - "Арақатынасты өзгерту" "%1$s қолданбасы туралы ақпарат" "%1$s пайдалану параметрлері" - "Жаңа терезе" - "Терезелерді басқару" "Қолданбаларды жұптау әрекетін сақтау" "%1$s | %2$s" "Бұл құрылғы қолданбаларды жұптау функциясын қолдамайды." @@ -41,8 +38,6 @@ "Қолданбаларды жұптау функциясы қолжетімді емес." "Виджетті жылжыту үшін басып тұрыңыз." "Виджетті жылжыту үшін екі рет түртіңіз де, ұстап тұрыңыз немесе арнаулы әрекеттерді пайдаланыңыз." - "Басқа опциялар" - "Барлық виджетті көрсету" "%1$d × %2$d" "Ені: %1$d, биіктігі: %2$d" "%1$s виджеті" @@ -69,13 +64,8 @@ "Жұмыс виджеттері" "Әңгімелер" "Ескертпе жазу" - "Қосу түймесін көрсету" - "Қосу түймесін жасыру" "Қосу" "Виджет (%1$s) қосу" - "Барлығын көру" - "Барлық виджетті көрсету" - "Барлық виджет көрсетіліп тұр." "Виджет параметрлерін өзгерту үшін түртіңіз." "Виджет параметрлерін өзгерту" "Қолданбаларды іздеу" @@ -83,7 +73,6 @@ "\"%1$s\" сұрауына сәйкес келетін қолданбалар жоқ" "Қолданба" "Барлық қолданба" - "Қолданбалар тізімі" "Хабарландырулар" "Таңбашаны жылжыту үшін басып тұрыңыз." "Таңбашаны жылжыту үшін екі рет түртіңіз де, ұстап тұрыңыз немесе арнаулы әрекеттерді пайдаланыңыз." @@ -101,7 +90,6 @@ "Орнату" "Қолданба ұсынбау" "Болжамды бекіту" - "Қалқыма терезе" "таңбаша орнату" "Қолданбаға пайдаланушының қатысуынсыз төте пернелерді қосу мүмкіндігін береді." "негізгі экран параметрлері мен таңбашаларын оқу" @@ -118,8 +106,6 @@ "%1$d бет, барлығы %2$d" "%1$d негізгі экран, барлығы %2$d" "Жаңа негізгі экран беті" - "Белсенді" - "Кішірейтілген" "Қалта ашылды, %1$d және %2$d" "Қалтаны жабу үшін түртіңіз" "Қайта атауды сақтау үшін түртіңіз" @@ -127,7 +113,6 @@ "Қалта атауы %1$s болып өзгертілді" "Қалта: %1$s, %2$d элемент бар" "Қалта: %1$s, %2$d не одан көп элемент бар" - "Атауы жоқ қалта" "Қолданбаларды жұптау: %1$s және %2$s" "Тұсқағаз және стиль" "Негізгі экранды өзгерту" @@ -135,8 +120,6 @@ "Әкімші өшірді" "Негізгі экранды бұруға рұқсат ету" "Телефон бұрылғанда" - "Альбом режимі" - "Телефонды альбом режиміне қою" "Хабарландыру белгілері" "Қосулы" "Өшірулі" @@ -155,8 +138,7 @@ "%1$s орнатылуда, %2$s аяқталды" "%1$s жүктелуде, %2$s аяқталды" "%1$s орнату күтілуде" - "%1$s мұрағатталды." - "жүктеп алу және қалпына келтіру" + "%1$s мұрағатталды. Жүктеп алу және қалпына келтіру үшін түртіңіз." "Қолданбаны жаңарту қажет" "Осы белгіше үшін қолданба жаңартылмаған. Оны қолмен жаңартып, осы таңбашаны қайта іске қоса аласыз немесе белгішені өшіріп тастаңыз." "Жаңарту" @@ -165,6 +147,7 @@ "Видджеттер тізімі жабылды" "Негізгі экранға қосу" "Элементті мұнда жылжыту" + "Элемент негізгі экранға қосылды" "Элемент жойылды" "Қайтару" "Элементті жылжыту" @@ -184,15 +167,11 @@ "Енін азайту" "Биіктігін азайту" "Виджет өлшемінің ені %1$s, биіктігі %2$s болып өзгертілді" - "Жылдам пәрмен мәзірі" - "%1$s үшін виджет өлшемін өзгерту" - "Жабу" + "Жылдам пәрмендер" "Бас тарту" "Жабу" "Жеке" "Жұмыс" - "Жеке қолданбалар қойындысы" - "Жұмыс қолданбалары қойындысы" "Жұмыс профилі" "Жұмыс қолданбаларының танымбелгілері бар және олар әкімшіңізге көрінеді." "Түсінікті" @@ -204,12 +183,11 @@ "Түсінікті" "Жұмыс қолданбаларын кідірту" "Қайта қосу" - "Жұмыс қолданбаларының кестесі" "Сүзгі" "Қате шықты: %1$s" "Құпия кеңістік" "Реттеу немесе ашу үшін түртіңіз" - "Құпия" + "Жеке" "Құпия кеңістік параметрлері" "Құпия (құлыпталмаған)." "Құпия (құлыптаулы)." @@ -217,5 +195,4 @@ "Жеке бөлмеге өту" "Орнату" "Қолданбаларды \"Құпия кеңістікке\" орнатыңыз." - "Құпия кеңістікке файлдар мен басқа да элементтерді қосыңыз" diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml index cba66d7cc7..5c71276583 100644 --- a/res/values-km/strings.xml +++ b/res/values-km/strings.xml @@ -29,11 +29,8 @@ "អេក្រង់ដើម" "កំណត់ %1$s ជាកម្មវិធីអេក្រង់ដើមលំនាំដើមនៅក្នុង \"ការកំណត់\"" "មុខងារ​បំបែកអេក្រង់" - "ប្ដូរ​​សមាមាត្រ" "ព័ត៌មានកម្មវិធី​សម្រាប់ %1$s" "ការកំណត់ការប្រើប្រាស់សម្រាប់ %1$s" - "វិនដូ​ថ្មី" - "គ្រប់គ្រង​វិនដូ" "រក្សាទុកគូកម្មវិធី" "%1$s | %2$s" "មិនអាចប្រើគូកម្មវិធីនេះនៅលើឧបករណ៍នេះបានទេ" @@ -41,8 +38,6 @@ "មិនអាចប្រើគូកម្មវិធីបានទេ" "ចុចឱ្យជាប់​ដើម្បីផ្លាស់ទី​ធាតុក្រាហ្វិក​។" "ចុចពីរដង រួចសង្កត់ឱ្យជាប់ ដើម្បីផ្លាស់ទី​ធាតុក្រាហ្វិក ឬប្រើ​សកម្មភាព​តាមបំណង​។" - "ជម្រើស​ច្រើនទៀត" - "បង្ហាញគ្រប់ធាតុ​ក្រាហ្វិក" "%1$d × %2$d" "ទទឺង %1$d គុណនឹងកម្ពស់ %2$d" "ធាតុ​ក្រាហ្វិក %1$s" @@ -69,13 +64,8 @@ "ការងារ" "ការសន្ទនា" "ការកត់ត្រា" - "បង្ហាញប៊ូតុង \"បញ្ចូល\"" - "លាក់ប៊ូតុង \"បញ្ចូល\"" "បញ្ចូល" "បញ្ចូលធាតុ​ក្រាហ្វិក %1$s" - "បង្ហាញ​ទាំងអស់" - "បង្ហាញធាតុ​ក្រាហ្វិកទាំងអស់" - "កំពុងបង្ហាញធាតុ​ក្រាហ្វិកទាំងអស់" "ចុចដើម្បីប្ដូរការកំណត់ធាតុ​ក្រាហ្វិក" "ប្ដូរការកំណត់ធាតុ​ក្រាហ្វិក" "ស្វែងរក​កម្មវិធី" @@ -83,7 +73,6 @@ "រកមិនឃើញកម្មវិធី​ដែលត្រូវគ្នាជាមួយ \"%1$s\" ទេ" "កម្មវិធី" "កម្មវិធី​ទាំងអស់" - "បញ្ជី​កម្មវិធី" "ការ​ជូនដំណឹង" "ចុចឱ្យជាប់​ដើម្បីផ្លាស់ទី​ផ្លូវកាត់​។" "ចុចពីរដង រួចសង្កត់ឱ្យជាប់ ដើម្បីផ្លាស់ទី​ផ្លូវកាត់ ឬប្រើ​សកម្មភាព​តាមបំណង​។" @@ -101,7 +90,6 @@ "ដំឡើង" "កុំណែនាំកម្មវិធី" "ខ្ទាស់ការ​ព្យាករ" - "ពពុះ" "ដំឡើង​ផ្លូវកាត់" "អនុញ្ញាត​ឲ្យ​កម្មវិធី​បន្ថែម​ផ្លូវកាត់​ ដោយ​មិន​ចាំបាច់​​អំពើ​ពី​អ្នក​ប្រើ។" "អានការកំណត់ និងផ្លូវកាត់របស់អេក្រង់ដើម" @@ -118,8 +106,6 @@ "ទំព័រ %1$d នៃ %2$d" "អេក្រង់​ដើម %1$d នៃ %2$d" "ទំព័រអេក្រង់ដើមថ្មី" - "សកម្ម" - "បានបង្រួម" "បាន​បើក​ថត %1$d ដោយ %2$d" "ប៉ះ ដើម្បីបិទថត" "ប៉ះដើម្បីរក្សាទុកឈ្មោះដែលបានប្តូរ" @@ -127,16 +113,13 @@ "បាន​ប្ដូរ​ឈ្មោះ​ថត​ជា %1$s" "ថត៖ %1$s, ធាតុ %2$d" "ថត៖ %1$s, ធាតុ %2$d ឬច្រើនជាងនេះ" - "ថត​ដែលគ្មាន​ឈ្មោះ" "គូកម្មវិធី៖ %1$s និង %2$s" - "ផ្ទាំងរូបភាព និងរចនាបថ" + "ផ្ទាំងរូបភាព និងរចនាប័ទ្ម" "កែអេក្រង់ដើម" "ការកំណត់​ទំព័រដើម" "បានបិទដំណើរការដោយអ្នកគ្រប់គ្រងរបស់អ្នក" "អនុញ្ញាតការបងិ្វលអេក្រង់ដើម" "នៅពេលដែលបង្វិលទូរសព្ទ" - "ផ្ដេក" - "កំណត់ទូរសព្ទទៅផ្ដេក" "ស្លាកជូនដំណឹង" "បើក" "បិទ" @@ -155,8 +138,7 @@ "កំពុង​ដំឡើង %1$s, បាន​បញ្ចប់ %2$s" "កំពុងដោនឡូត %1$s បានបញ្ចប់ %2$s" "%1$s កំពុងរង់ចាំការដំឡើង" - "%1$s ត្រូវបានទុក​ក្នុង​បណ្ណសារ។" - "ទាញយក និងស្ដារ" + "%1$s ត្រូវបានទុក​ក្នុង​បណ្ណសារ។ សូមចុចដើម្បីទាញយក និងស្ដារ។" "តម្រូវឱ្យមាន​កំណែកម្មវិធីថ្មី" "កម្មវិធីសម្រាប់​រូបតំណាងនេះ​មិនត្រូវបានដំឡើងកំណែ​ទេ។ អ្នកអាច​ដំឡើងកំណែ​ដោយផ្ទាល់ ដើម្បីបើក​ផ្លូវកាត់នេះឡើងវិញ ឬលុបរូបតំណាងនេះ។" "ដំឡើងកំណែ" @@ -165,6 +147,7 @@ "បាន​បិទ​បញ្ជីធាតុ​ក្រាហ្វិក" "បញ្ចូល​ទៅក្នុង​អេក្រង់​ដើម" "ផ្លាស់ធាតុមកទីនេះ" + "ធាតុដែលត្រូវបានបន្ថែមទៅអេក្រង់ដើម" "បានដកធាតុចេញ" "ត្រឡប់វិញ" "ផ្លាស់ទីធាតុ" @@ -184,15 +167,11 @@ "បន្ថយទទឹង" "បន្ថយកម្ពស់" "ធាតុក្រាហ្វិកដែលបានប្តូរទំហំទៅទទឹងប្រវែង %1$s កម្ពស់ប្រវែង %2$s" - "ម៉ឺនុយផ្លូវ​កាត់" - "ហ្វ្រេមប្ដូរទំហំធាតុក្រាហ្វិកសម្រាប់ %1$s" - "បិទ" + "ផ្លូវកាត់" "ច្រានចោល" "បិទ" "ផ្ទាល់ខ្លួន" "ការងារ" - "ផ្ទាំងកម្មវិធី​ផ្ទាល់ខ្លួន" - "ផ្ទាំងកម្មវិធី​ការងារ" "កម្រងព័ត៌មានការងារ" "កម្មវិធីការងារ​ត្រូវបានដាក់​គ្រឿងសម្គាល់ ហើយ​អ្នកគ្រប់គ្រង​ផ្នែកព័ត៌មានវិទ្យា​របស់អ្នក​អាចមើលឃើញ" "យល់ហើយ" @@ -204,7 +183,6 @@ "យល់ហើយ" "ផ្អាក​កម្មវិធី​ការងារ" "ឈប់ផ្អាក" - "កាលវិភាគកម្មវិធី​ការងារ" "តម្រង" "បានបរាជ័យ៖ %1$s" "បន្ទប់​ឯកជន" @@ -217,5 +195,4 @@ "ការផ្លាស់ប្ដូរ Private Space" "ដំឡើង" "ដំឡើងកម្មវិធីទៅលំហឯកជន" - "បញ្ចូលឯកសារ និងអ្វីៗជាច្រើនទៀតទៅលំហឯកជន" diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml index 73d470dd0a..931ca30b58 100644 --- a/res/values-kn/strings.xml +++ b/res/values-kn/strings.xml @@ -21,7 +21,7 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Launcher3" "ಕೆಲಸ" - "ಆ್ಯಪ್‌ ಅನ್ನು ಸ್ಥಾಪಿಸಲಾಗಿಲ್ಲ" + "ಅಪ್ಲಿಕೇಶನ್‌ ಅನ್ನು ಸ್ಥಾಪಿಸಲಾಗಿಲ್ಲ" "ಅಪ್ಲಿಕೇಶನ್ ಲಭ್ಯವಿಲ್ಲ" "ಡೌನ್‌ಲೋಡ್ ಮಾಡಲಾದ ಅಪ್ಲಿಕೇಶನ್ ಅನ್ನು ಸುರಕ್ಷಿತ ಮೋಡ್‌ನಲ್ಲಿ ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ" "ಸುರಕ್ಷಿತ ಮೋಡ್‌ನಲ್ಲಿ ವಿಜೆಟ್‌ಗಳನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ" @@ -29,11 +29,8 @@ "ಹೋಮ್" "ಸೆಟ್ಟಿಂಗ್‌ಗಳಲ್ಲಿ %1$s ಅನ್ನು ಡೀಫಾಲ್ಟ್ ಹೋಮ್ ಆ್ಯಪ್ ಆಗಿ ಸೆಟ್‌ ಮಾಡಿ" "ಸ್ಪ್ಲಿಟ್ ಸ್ಕ್ರೀನ್" - "ದೃಶ್ಯಾನುಪಾತವನ್ನು ಬದಲಾಯಿಸಿ" "%1$s ಗಾಗಿ ಆ್ಯಪ್ ಮಾಹಿತಿ" "%1$s ಗೆ ಸಂಬಂಧಿಸಿದ ಬಳಕೆಯ ಸೆಟ್ಟಿಂಗ್‌ಗಳು" - "ಹೊಸ ವಿಂಡೋ" - "ವಿಂಡೋಗಳನ್ನು ನಿರ್ವಹಿಸಿ" "ಆ್ಯಪ್ ಪೇರ್ ಸೇವ್ ಮಾಡಿ" "%1$s | %2$s" "ಈ ಆ್ಯಪ್ ಜೋಡಿಯು ಈ ಸಾಧನದಲ್ಲಿ ಬೆಂಬಲಿತವಾಗಿಲ್ಲ" @@ -41,8 +38,6 @@ "ಆ್ಯಪ್ ಜೋಡಿ ಲಭ್ಯವಿಲ್ಲ" "ವಿಜೆಟ್ ಸರಿಸಲು ಸ್ಪರ್ಶಿಸಿ ಮತ್ತು ಹಿಡಿದುಕೊಳ್ಳಿ." "ವಿಜೆಟ್ ಸರಿಸಲು ಅಥವಾ ಕಸ್ಟಮ್ ಕ್ರಿಯೆಗಳನ್ನು ಬಳಸಲು ಡಬಲ್-ಟ್ಯಾಪ್ ಮಾಡಿ ಮತ್ತು ಹಿಡಿದುಕೊಳ್ಳಿ." - "ಇನ್ನಷ್ಟು ಆಯ್ಕೆಗಳು" - "ಎಲ್ಲಾ ವಿಜೆಟ್‌ ತೋರಿಸಿ" "%1$d × %2$d" "%1$d ಅಗಲ ಮತ್ತು %2$d ಎತ್ತರ" "%1$s ವಿಜೆಟ್" @@ -69,13 +64,8 @@ "ಕೆಲಸ" "ಸಂಭಾಷಣೆಗಳು" "ಟಿಪ್ಪಣಿ ತೆಗೆದುಕೊಳ್ಳುವುದು" - "ಸೇರಿಸಿ ಬಟನ್ ಅನ್ನು ತೋರಿಸಿ" - "ಸೇರಿಸಿ ಬಟನ್ ಅನ್ನು ಮರೆಮಾಡಿ" "ಸೇರಿಸಿ" "%1$s ವಿಜೆಟ್ ಸೇರಿಸಿ" - "ಎಲ್ಲಾ ತೋರಿಸಿ" - "ಎಲ್ಲಾ ವಿಜೆಟ್‌ಗಳನ್ನು ತೋರಿಸಿ" - "ಎಲ್ಲಾ ವಿಜೆಟ್‌ಗಳನ್ನು ತೋರಿಸಲಾಗುತ್ತಿದೆ" "ವಿಜೆಟ್ ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ಬದಲಾಯಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ" "ವಿಜೆಟ್ ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ಬದಲಾಯಿಸಿ" "ಆ್ಯಪ್‍ಗಳನ್ನು ಹುಡುಕಿ" @@ -83,7 +73,6 @@ "\"%1$s\" ಹೊಂದಿಕೆಯ ಯಾವುದೇ ಅಪ್ಲಿಕೇಶನ್‌ಗಳು ಕಂಡುಬಂದಿಲ್ಲ" "ಆ್ಯಪ್" "ಎಲ್ಲಾ ಆ್ಯಪ್‌ಗಳು" - "ಆ್ಯಪ್‌ಗಳ ಪಟ್ಟಿ" "ನೋಟಿಫಿಕೇಶನ್‌ಗಳು" "ಶಾರ್ಟ್‌ಕಟ್ ಸರಿಸಲು ಸ್ಪರ್ಶಿಸಿ ಮತ್ತು ಹಿಡಿದುಕೊಳ್ಳಿ." "ಶಾರ್ಟ್‌ಕಟ್ ಸರಿಸಲು ಅಥವಾ ಕಸ್ಟಮ್ ಕ್ರಿಯೆಗಳನ್ನು ಬಳಸಲು ಡಬಲ್-ಟ್ಯಾಪ್ ಮಾಡಿ ಮತ್ತು ಹಿಡಿದುಕೊಳ್ಳಿ." @@ -101,7 +90,6 @@ "ಸ್ಥಾಪಿಸಿ" "ಆ್ಯಪ್ ಅನ್ನು ಸೂಚಿಸಬೇಡಿ" "ಮುನ್ನೋಟ ಪಿನ್ ಮಾಡಿ" - "ಬಬಲ್" "ಶಾರ್ಟ್‌ಕಟ್‌ಗಳನ್ನು ಸ್ಥಾಪಿಸಿ" "ಬಳಕೆದಾರರ ಹಸ್ತಕ್ಷೇಪವಿಲ್ಲದೆ ಶಾರ್ಟ್‌ಕಟ್‌ಗಳನ್ನು ಸೇರಿಸಲು ಅಪ್ಲಿಕೇಶನ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ." "ಹೋಮ್ ಸ್ಕ್ರೀನ್ ಸೆಟ್ಟಿಂಗ್‌ಗಳು ಮತ್ತು ಶಾರ್ಟ್‌ಕಟ್‌ಗಳನ್ನು ಓದಿ" @@ -111,15 +99,13 @@ "ವಿಜೆಟ್ ಅನ್ನು ಲೋಡ್ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ" "ವಿಜೆಟ್ ಸೆಟ್ಟಿಂಗ್‌ಗಳು" "ಸೆಟಪ್ ಪೂರ್ಣಗೊಳಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ" - "ಇದೊಂದು ಆ್ಯಪ್‌ ಆಗಿದೆ ಮತ್ತು ಅಸ್ಥಾಪಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ." + "ಇದೊಂದು ಅಪ್ಲಿಕೇಶನ್ ಆಗಿದೆ ಮತ್ತು ಅಸ್ಥಾಪಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ." "ಹೆಸರನ್ನು ಎಡಿಟ್ ಮಾಡಿ" "%1$s ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ" "{count,plural, =1{{app_name} ಆ್ಯಪ್ # ಅಧಿಸೂಚನೆಯನ್ನು ಹೊಂದಿದೆ}one{{app_name} ಆ್ಯಪ್ # ಅಧಿಸೂಚನೆಗಳನ್ನು ಹೊಂದಿದೆ}other{{app_name} ಆ್ಯಪ್ # ಅಧಿಸೂಚನೆಗಳನ್ನು ಹೊಂದಿದೆ}}" "%2$d ರಲ್ಲಿ %1$d ಪುಟ" - "%2$d ರಲ್ಲಿ %1$d ಮುಖಪುಟದ ಸ್ಕ್ರೀನ್" - "ಹೊಸ ಮುಖಪುಟ ಸ್ಕ್ರೀನ್" - "ಸಕ್ರಿಯವಾಗಿದೆ" - "ಮಿನಿಮೈಸ್ ಮಾಡಲಾಗಿದೆ" + "%2$d ರಲ್ಲಿ %1$d ಮುಖಪುಟದ ಪರದೆ" + "ಹೊಸ ಮುಖಪುಟ ಪರದೆ" "ಫೋಲ್ಡರ್ ತೆರೆಯಲಾಗಿದೆ, %1$d ಬೈ %2$d" "ಫೋಲ್ಡರ್‌ ಮುಚ್ಚಲು ಟ್ಯಾಪ್ ಮಾಡಿ" "ಮರುಹೆಸರನ್ನು ಉಳಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ" @@ -127,16 +113,13 @@ "ಫೋಲ್ಡರ್‌ ಅನ್ನು %1$s ಗೆ ಮರುಹೆಸರಿಸಲಾಗಿದೆ" "ಫೋಲ್ಡರ್: %1$s, %2$d ಐಟಂಗಳು" "ಫೋಲ್ಡರ್: %1$s, %2$d ಅಥವಾ ಹೆಚ್ಚಿನ ಐಟಂಗಳು" - "ಹೆಸರಿಲ್ಲದ ಫೋಲ್ಡರ್" "ಆ್ಯಪ್ ಜೋಡಿ: %1$s ಮತ್ತು %2$s" "ವಾಲ್‌ಪೇಪರ್ ಮತ್ತು ಶೈಲಿ" "ಹೋಮ್ ಸ್ಕ್ರೀನ್ ಅನ್ನು ಎಡಿಟ್ ಮಾಡಿ" - "ಹೋಮ್ ಸೆಟ್ಟಿಂಗ್‌ಗಳು" + "ಮುಖಪುಟ ಸೆಟ್ಟಿಂಗ್‌ಗಳು" "ನಿಮ್ಮ ನಿರ್ವಾಹಕರು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿದ್ದಾರೆ" "ಹೋಮ್ ಸ್ಕ್ರೀನ್ ತಿರುಗುವಿಕೆಯನ್ನು ಅನುಮತಿಸಿ" "ಫೋನ್‌ ತಿರುಗಿಸಿದಾಗ" - "ಲ್ಯಾಂಡ್‌ಸ್ಕೇಪ್ ಮೋಡ್" - "ಫೋನ್ ಅನ್ನು ಲ್ಯಾಂಡ್‌ಸ್ಕೇಪ್ ಮೋಡ್‌ಗೆ ಸೆಟ್ ಮಾಡಿ" "ನೋಟಿಫಿಕೇಶನ್ ಡಾಟ್‌ಗಳು" "ಆನ್ ಆಗಿದೆ" "ಆಫ್ ಆಗಿದೆ" @@ -155,8 +138,7 @@ "%1$s ಇನ್‌ಸ್ಟಾಲ್ ಮಾಡಲಾಗುತ್ತಿದೆ, %2$s ಪೂರ್ಣಗೊಂಡಿದೆ" "%1$s ಡೌನ್‌ಲೋಡ್‌ ಮಾಡಲಾಗುತ್ತಿದೆ, %2$s ಪೂರ್ಣಗೊಂಡಿದೆ" "%1$s ಸ್ಥಾಪಿಸಲು ಕಾಯಲಾಗುತ್ತಿದೆ" - "%1$s ಅನ್ನು ಆರ್ಕೈವ್ ಮಾಡಲಾಗಿದೆ." - "ಡೌನ್‌ಲೋಡ್ ಮಾಡಿ ಮತ್ತು ಮರುಸ್ಥಾಪಿಸಿ" + "%1$s ಅನ್ನು ಆರ್ಕೈವ್ ಮಾಡಲಾಗಿದೆ. ಡೌನ್‌ಲೋಡ್ ಮಾಡಲು ಮತ್ತು ಮರುಸ್ಥಾಪಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ." "ಆ್ಯಪ್ ಅಪ್‌ಡೇಟ್ ಅಗತ್ಯವಿದೆ" "ಈ ಐಕಾನ್‌ಗಾಗಿ ಆ್ಯಪ್ ಅನ್ನು ಅಪ್‌ಡೇಟ್ ಮಾಡಲಾಗಿಲ್ಲ. ಈ ಶಾರ್ಟ್‌ಕಟ್ ಅನ್ನು ಮರು-ಸಕ್ರಿಯಗೊಳಿಸಲು ನೀವು ಹಸ್ತಚಾಲಿತವಾಗಿ ಅಪ್‌ಡೇಟ್ ಮಾಡಬಹುದು ಅಥವಾ ಐಕಾನ್ ಅನ್ನು ತೆಗೆದುಹಾಕಬಹುದು." "ಅಪ್‌ಡೇಟ್ ಮಾಡಿ" @@ -165,6 +147,7 @@ "ವಿಜೆಟ್ ಪಟ್ಟಿಯನ್ನು ಮುಚ್ಚಲಾಗಿದೆ" "ಹೋಮ್ ಸ್ಕ್ರೀನ್‌ಗೆ ಸೇರಿಸಿ" "ಐಟಂ ಇಲ್ಲಿಗೆ ಸರಿಸಿ" + "ಹೋಮ್ ಸ್ಕ್ರೀನ್‌ಗೆ ಐಟಂ ಸೇರಿಸಲಾಗಿದೆ" "ಐಟಂ ತೆಗೆದುಹಾಕಲಾಗಿದೆ" "ರದ್ದುಮಾಡಿ" "ಐಟಂ ಸರಿಸಿ" @@ -184,15 +167,11 @@ "ಅಗಲವನ್ನು ಕಡಿಮೆ ಮಾಡಿ" "ಎತ್ತರವನ್ನು ಕಡಿಮೆ ಮಾಡಿ" "ವಿಜೆಟ್ ಅನ್ನು %1$s ಅಗಲ %2$s ಎತ್ತರಕ್ಕೆ ಮರುಗಾತ್ರಗೊಳಿಸಲಾಗಿದೆ" - "ಶಾರ್ಟ್‌ಕಟ್ ಮೆನು" - "%1$s ಗಾಗಿ ವಿಜೆಟ್ ಮರುಗಾತ್ರಗೊಳಿಸುವಿಕೆ ಫ್ರೇಮ್" - "ಮುಚ್ಚಿರಿ" + "ಶಾರ್ಟ್‌ಕಟ್‌ಗಳು" "ವಜಾಗೊಳಿಸಿ" "ಮುಚ್ಚಿರಿ" "ವೈಯಕ್ತಿಕ" "ಕೆಲಸ" - "ವೈಯಕ್ತಿಕ ಆ್ಯಪ್‌ಗಳ ಟ್ಯಾಬ್" - "ಕೆಲಸಕ್ಕೆ ಸಂಬಂಧಿಸಿದ ಆ್ಯಪ್‌ಗಳ ಟ್ಯಾಬ್" "ಕೆಲಸದ ಪ್ರೊಫೈಲ್" "ಕೆಲಸಕ್ಕೆ ಸಂಬಂಧಿಸಿದ ಆ್ಯಪ್‌ಗಳನ್ನು ಬ್ಯಾಡ್ಜ್ ಮಾಡಲಾಗಿದೆ ಮತ್ತು ಅವುಗಳು ನಿಮ್ಮ IT ನಿರ್ವಾಹಕರಿಗೆ ಗೋಚರಿಸುತ್ತವೆ" "ಸರಿ" @@ -204,12 +183,11 @@ "ಅರ್ಥವಾಯಿತು" "ಕೆಲಸಕ್ಕೆ ಸಂಬಂಧಿಸಿದ ಆ್ಯಪ್‌ಗಳನ್ನು ವಿರಾಮಗೊಳಿಸಿ" "ವಿರಾಮವನ್ನು ರದ್ದುಗೊಳಿಸಿ" - "ಕೆಲಸಕ್ಕೆ ಸಂಬಂಧಿಸಿದ ಆ್ಯಪ್‌ಗಳ ವೇಳಾಪಟ್ಟಿ" "ಫಿಲ್ಟರ್‌" "ವಿಫಲವಾಗಿದೆ: %1$s" "ಖಾಸಗಿ ಸ್ಪೇಸ್" "ಸೆಟಪ್ ಮಾಡಲು ಅಥವಾ ತೆರೆಯಲು ಟ್ಯಾಪ್ ಮಾಡಿ" - "ಪ್ರೈವೆಟ್" + "ಖಾಸಗಿ" "ಖಾಸಗಿ ಸ್ಪೇಸ್ ಸೆಟ್ಟಿಂಗ್‌ಗಳು" "ಖಾಸಗಿ, ಅನ್‌ಲಾಕ್ ಮಾಡಲಾಗಿದೆ." "ಖಾಸಗಿ, ಲಾಕ್ ಮಾಡಲಾಗಿದೆ." @@ -217,5 +195,4 @@ "ಖಾಸಗಿ ಸ್ಪೇಸ್ ಪರಿವರ್ತನೆಯಾಗುತ್ತಿದೆ" "ಇನ್‌ಸ್ಟಾಲ್" "ಆ್ಯಪ್‌ಗಳನ್ನು ಪ್ರೈವೇಟ್ ಸ್ಪೇಸ್‌ನಲ್ಲಿ ಇನ್‌ಸ್ಟಾಲ್ ಮಾಡಿ" - "ಪ್ರೈವೆಟ್ ಸ್ಪೇಸ್‌ಗೆ ಫೈಲ್‌ಗಳು ಮತ್ತು ಹೆಚ್ಚಿನದನ್ನು ಸೇರಿಸಿ" diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml index 71c47eba80..9f64cfb24e 100644 --- a/res/values-ko/strings.xml +++ b/res/values-ko/strings.xml @@ -29,11 +29,8 @@ "홈" "설정에서 %1$s 런처를 기본 홈 앱으로 설정하세요." "화면 분할" - "가로세로 비율 변경" "%1$s 앱 정보" "%1$s의 사용량 설정" - "새 창" - "창 관리" "앱 페어링 저장" "%1$s | %2$s" "이 앱 페어링은 이 기기에서 지원되지 않습니다" @@ -41,8 +38,6 @@ "앱 페어링을 사용할 수 없습니다." "길게 터치하여 위젯을 이동하세요." "두 번 탭한 다음 길게 터치하여 위젯을 이동하거나 맞춤 작업을 사용하세요." - "옵션 더보기" - "모든 위젯 표시" "%1$d×%2$d" "너비 %1$d, 높이 %2$d" "위젯 %1$s개" @@ -69,13 +64,8 @@ "직장 위젯" "대화" "메모" - "추가 버튼 표시" - "추가 버튼 숨기기" "추가" "%1$s 위젯 추가" - "모두 표시" - "모든 위젯 표시" - "모든 위젯 표시" "탭하여 위젯 설정 변경" "위젯 설정 변경" "앱 검색" @@ -83,7 +73,6 @@ "\'%1$s\'과(와) 일치하는 앱이 없습니다." "앱" "모든 앱" - "앱 목록" "알림" "길게 터치하여 바로가기를 이동하세요." "두 번 탭한 다음 길게 터치하여 바로가기를 이동하거나 맞춤 작업을 사용하세요." @@ -101,7 +90,6 @@ "설치" "앱 제안 받지 않음" "예상 앱 고정" - "풍선" "바로가기 설치" "앱이 사용자의 작업 없이 바로가기를 추가할 수 있도록 합니다." "홈 설정 및 바로가기 읽기" @@ -118,8 +106,6 @@ "페이지 %1$d/%2$d" "홈 화면 %1$d/%2$d" "새로운 홈 화면 페이지" - "활성" - "최소화" "폴더 열림(%1$dX%2$d)" "탭하여 폴더 닫기" "탭하여 변경된 이름 저장" @@ -127,7 +113,6 @@ "폴더 이름 변경: %1$s" "폴더: %1$s, 항목 %2$d개" "폴더: %1$s, 항목 %2$d개 이상" - "이름 없는 폴더" "앱 페어링: %1$s%2$s" "배경화면 및 스타일" "홈 화면 수정" @@ -135,8 +120,6 @@ "관리자가 사용 중지함" "홈 화면 회전 허용" "휴대전화 회전 시" - "가로 모드" - "휴대전화를 가로 모드로 설정" "알림 표시 점" "사용" "사용 안함" @@ -155,8 +138,7 @@ "%1$s 설치 중, %2$s 완료" "%1$s 다운로드 중, %2$s 완료" "%1$s 설치 대기 중" - "%1$s 앱이 보관처리되었습니다" - "다운로드 및 복원" + "%1$s 앱이 보관처리되었습니다. 탭하여 다운로드하고 복원하세요" "앱 업데이트 필요" "바로가기 아이콘의 앱이 업데이트되지 않았습니다. 직접 업데이트하여 앱 바로가기를 다시 사용할 수 있도록 하거나 아이콘을 삭제하세요." "업데이트" @@ -165,6 +147,7 @@ "위젯 목록 닫힘" "홈 화면에 추가" "여기에 항목을 이동" + "홈 화면에 항목 추가됨" "항목 삭제됨" "실행취소" "항목 이동" @@ -184,15 +167,11 @@ "폭 줄이기" "높이 줄이기" "폭 %1$s, 높이 %2$s로 위젯 크기 조정됨" - "바로가기 메뉴" - "%1$s의 위젯 크기 조절 프레임" - "닫기" + "바로가기" "닫기" "닫기" "개인" "직장" - "개인 앱 탭" - "직장 앱 탭" "직장 프로필" "직장 앱에는 배지가 있으며, IT 관리자는 직장 앱을 확인할 수 있습니다" "확인" @@ -204,7 +183,6 @@ "확인" "직장 앱 일시중지" "일시중지 해제" - "직장 앱 일정" "필터" "실패: %1$s" "비공개 스페이스" @@ -217,5 +195,4 @@ "비공개 스페이스 전환" "설치" "비공개 스페이스에 앱 설치" - "비공개 스페이스에 파일 등 추가" diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml index 6611b56e38..3e67c365fb 100644 --- a/res/values-ky/strings.xml +++ b/res/values-ky/strings.xml @@ -29,11 +29,8 @@ "Башкы экран" "Параметрлерден %1$s жүргүзгүчүн демейки башкы бет колдонмосу катары коюу" "Экранды бөлүү" - "Тараптардын катнашын өзгөртүү" "%1$s колдонмосу жөнүндө маалымат" "%1$s колдонмосун пайдалануу параметрлери" - "Жаңы терезе" - "Терезелерди тескөө" "Колдонмолорду сактап коюу" "%1$s | %2$s" "Бул эки колдонмону бул түзмөктө бир маалда пайдаланууга болбойт" @@ -41,13 +38,11 @@ "Эки колдонмону бир маалда пайдаланууга болбойт" "Виджетти кое бербей басып туруп жылдырыңыз." "Виджетти жылдыруу үчүн эки жолу таптап, кармап туруңуз же ыңгайлаштырылган аракеттерди колдонуңуз." - "Дагы параметрлер" - "Виджеттин баарын көрсөтүү" "%1$d × %2$d" "Туурасы: %1$d, бийиктиги: %2$d" "%1$s виджети" "%1$s виджети, кеңдиги %2$d жана бийиктиги %3$d" - "Вижетти коё бербей басып туруп башкы экранга жылдырыңыз" + "Башкы экранга жылдыруу үчүн виджетти коё бербей басып туруңуз" "Башкы экранга кошуу" "%1$s виджети башкы экранга кошулду" "Сунуштар" @@ -69,13 +64,8 @@ "Жумуш" "Сүйлөшүүлөр" "Эскертме жазуу" - "Кошуу баскычын көрсөтүү" - "Кошуу баскычын жашыруу" "Кошуу" "%1$s виджетин кошуу" - "Баарын көрсөтүү" - "Виджеттин баарын көрсөтүү" - "Бардык виджеттерди көрсөтүү" "Виджеттин параметрлерин өзгөртүү үчүн таптап коюңуз" "Виджеттин параметрлерин өзгөртүү" "Колдонмолорду издөө" @@ -83,7 +73,6 @@ "\"%1$s\" сурамына дал келген колдонмолор табылган жок" "Колдонмо" "Бардык колдонмолор" - "Колдонмолор тизмеси" "Билдирмелер" "Ыкчам баскычты жылдыруу үчүн коё бербей басып туруңуз." "Ыкчам баскычты жылдыруу үчүн эки жолу таптап, кармап туруңуз же ыңгайлаштырылган аракеттерди колдонуңуз." @@ -101,7 +90,6 @@ "Орнотуу" "Cунушталбасын" "Божомолдонгон колдонмону кадап коюу" - "Көбүкчө" "тез чакырмаларды орнотуу" "Колдонмого колдонуучуга кайрылбастан тез чакырма кошууга уруксат берет." "үйдүн параметрлерин жана ыкчам баскычтарын окуу" @@ -118,8 +106,6 @@ "%2$d ичинен %1$d барак" "Үй экраны %2$d ичинен %1$d" "Жаңы башкы экран барагы" - "Жигердүү" - "Кичирейтилди" "Фолдер ачылды, туурасы %1$d, бийиктиги %2$d" "Куржунду жабуу үчүн таптаңыз" "Өзгөртүлгөн аталышын сактоо үчүн таптаңыз" @@ -127,7 +113,6 @@ "Фолдердин аты %1$s деп өзгөртүлдү" "%1$s папкасындагы объекттер: %2$d" "%1$s папкасындагы объекттер: %2$d же андан көбүрөөк" - "Аталышы жок папка" "Эки колдонмону бир маалда пайдалануу: %1$s жана %2$s" "Тушкагаз жана стиль" "Башкы экранды түзөтүү" @@ -135,8 +120,6 @@ "Администраторуңуз өчүрүп койгон" "Башкы экранды бурууга уруксат берүү" "Телефон бурулганда" - "Туурасынан" - "Телефонду туурасынан коюңуз" "Билдирмелер белгилери" "Күйүк" "Өчүк" @@ -155,8 +138,7 @@ "%1$s орнотулууда, %2$s аткарылды" "%1$s жүктөлүп алынууда, %2$s аяктады" "%1$s орнотулушу күтүлүүдө" - "%1$s архивделди." - "жүктөп алуу жана калыбына келтирүү" + "%1$s архивделди. Жүктөп алуу жана калыбына келтирүү үчүн таптаңыз." "Колдонмону жаңыртыңыз" "Бул сүрөтчөнүн колдонмосу жаңыртылган эмес. Ыкчам баскычты кайра иштетүү үчүн аны кол менен жаңыртып же сүрөтчөнү өчүрүп койсоңуз болот." "Жаңыртуу" @@ -165,6 +147,7 @@ "Виджеттердин тизмеси жабык" "Башкы экранга кошуу" "Бул нерсени бул жерге жылдыруу" + "Башкы экранга кошулду" "Жоюлду" "Кайтаруу" "Муну жылдыруу" @@ -184,15 +167,11 @@ "Ичкертүү" "Жапыздатуу" "Виджеттин кеңдиги %1$s бийиктиги %2$s болду" - "Ыкчам баскычтын менюсу" - "%1$s үчүн виджеттин өлчөмүн өзгөртүү" - "Жабуу" + "Кыска жолдор" "Этибарга албоо" "Жабуу" "Жеке колдонмолор" "Жумуш колдонмолору" - "Жеке колдонмолор өтмөгү" - "Жумуш колдонмолор өтмөгү" "Жумуш профили" "Жумуш колдонмолору белгиленип, аларды IT администраторлору көрөт" "Түшүндүм" @@ -204,7 +183,6 @@ "Түшүндүм" "Жумуш колдонмолорун тындыруу" "Улантуу" - "Жумуш колдонмолорунун графиги" "Чыпкалоо" "Аткарылган жок: %1$s" "Жеке мейкиндик" @@ -217,5 +195,4 @@ "Жеке чөйрөгө өтүү" "Орнотуу" "Колдонмолорду Жеке мейкиндикке орнотуe" - "Жеке мейкиндикке файлдарды жана башкаларды кошуу" diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml index d52652ee4d..e0cd3ee883 100644 --- a/res/values-lo/strings.xml +++ b/res/values-lo/strings.xml @@ -29,11 +29,8 @@ "ໂຮມສະກຣີນ" "ຕັ້ງ %1$s ເປັນແອັບໂຮມເລີ່ມຕົ້ນໃນການຕັ້ງຄ່າ" "ແບ່ງໜ້າຈໍ" - "ປ່ຽນອັດຕາສ່ວນຮູບ" "ຂໍ້ມູນແອັບສຳລັບ %1$s" "ການຕັ້ງຄ່າການນຳໃຊ້ສຳລັບ %1$s" - "ໜ້າຈໍໃໝ່" - "ຈັດການໜ້າຈໍ" "ບັນທຶກຈັບຄູ່ແອັບ" "%1$s | %2$s" "ການຈັບຄູ່ແອັບນີ້ບໍ່ຮອງຮັບຢູ່ອຸປະກອນນີ້" @@ -41,8 +38,6 @@ "ການຈັບຄູ່ແອັບບໍ່ມີໃຫ້" "ແຕະຄ້າງໄວ້ເພື່ອຍ້າຍວິດເຈັດ." "ແຕະສອງເທື່ອຄ້າງໄວ້ເພື່ອຍ້າຍວິດເຈັດ ຫຼື ໃຊ້ຄຳສັ່ງກຳນົດເອງ." - "ຕົວເລືອກເພີ່ມເຕີມ" - "ສະແດງວິດເຈັດທັງໝົດ" "%1$d × %2$d" "ກວ້າງ %1$d ຄູນສູງ %2$d" "ວິດເຈັດ %1$s" @@ -69,13 +64,8 @@ "ວຽກ" "ການສົນທະນາ" "ການຈົດບັນທຶກ" - "ສະແດງປຸ່ມເພີ່ມ" - "ເຊື່ອງປຸ່ມເພີ່ມ" "ເພີ່ມ" "ເພີ່ມວິດເຈັດ %1$s" - "ສະແດງທັງໝົດ" - "ສະແດງວິດເຈັດທັງໝົດ" - "ກໍາລັງສະແດງວິດເຈັດທັງໝົດ" "ແຕະເພື່ອປ່ຽນການຕັ້ງຄ່າວິດເຈັດ" "ປ່ຽນການຕັ້ງຄ່າວິດເຈັດ" "ຊອກຫາແອັບ" @@ -83,7 +73,6 @@ "ບໍ່ພົບແອັບທີ່ກົງກັບ \"%1$s\"" "ແອັບ" "ແອັບທັງໝົດ" - "ລາຍຊື່ແອັບ" "ການແຈ້ງເຕືອນ" "ແຕະຄ້າງໄວ້ເພື່ອຍ້າຍທາງລັດ." "ແຕະສອງເທື່ອຄ້າງໄວ້ເພື່ອຍ້າຍທາງລັດ ຫຼື ໃຊ້ຄຳສັ່ງກຳນົດເອງ." @@ -101,7 +90,6 @@ "ຕິດຕັ້ງ" "ຢ່າແນະນຳແອັບ" "ປັກໝຸດການຄາດເດົາ" - "ຟອງ" "ຕິດຕັ້ງທາງລັດ" "ອະນຸຍາດໃຫ້ແອັບຯ ເພີ່ມທາງລັດໂດຍບໍ່ຕ້ອງຮັບການຢືນຢັນຈາກຜູ່ໃຊ້." "ອ່ານການຕັ້ງຄ່າໜ້າຫຼັກ ແລະ ທາງລັດ" @@ -118,8 +106,6 @@ "ໜ້າ %1$d ຈາກ %2$d" "ໜ້າຈໍຫຼັກ %1$d ໃນ %2$d" "ໜ້າ​ຂອງ​ໜ້າ​ຈໍ​ຫຼັກ​ໃໝ່" - "ນຳໃຊ້ຢູ່" - "ຫຍໍ້ລົງແລ້ວ" "ເປີດໂຟນເດີແລ້ວ, %1$d ຄູນ %2$d" "ແຕະເພື່ອປິດໂຟນເດີ" "ແຕະເພື່ອບັນທຶກການປ່ຽນຊື່" @@ -127,7 +113,6 @@ "ປ່ຽນຊື່ໂຟນເດີເປັນ %1$s ແລ້ວ" "ໂຟນເດີ: %1$s, %2$d ລາຍການ" "ໂຟນເດີ: %1$s, %2$d ຫຼື ລາຍການເພີ່ມເຕີມ" - "ໂຟນເດີທີ່ບໍ່ມີຊື່" "ຈັບຄູ່ແອັບ: %1$s ແລະ %2$s" "ຮູບພື້ນຫຼັງ ແລະ ຮູບແບບ" "ແກ້ໄຂໂຮມສະກຣີນ" @@ -135,8 +120,6 @@ "ຖືກປິດການນຳໃຊ້ໂດຍຜູ້ເບິ່ງແຍງລະບົບຂອງທ່ານ" "ອະນຸຍາດໃຫ້ໝຸນໜ້າຈໍຢູ່ໂຮມສະກຣີນໄດ້" "ເມື່ອໝຸນໂທລະສັບ" - "ໂໝດແນວນອນ" - "ຕັ້ງຄ່າໂທລະສັບເປັນໂໝດແນວນອນ" "ຈຸດການແຈ້ງເຕືອນ" "ເປີດ" "ປິດ" @@ -155,8 +138,7 @@ "ກຳລັງຕິດຕັ້ງ %1$s, %2$s ສຳເລັດແລ້ວ" "%1$s ກຳ​ລັງ​ດາວ​ໂຫຼດ, %2$s ສຳ​ເລັດ" "%1$s ກຳ​ລັງ​ລໍ​ຖ້າ​ຕິດ​ຕັ້ງ" - "%1$s ຖືກເກັບໄວ້ໃນແຟ້ມ." - "ດາວໂຫຼດ ແລະ ກູ້ຄືນ" + "%1$s ຖືກເກັບໄວ້ໃນແຟ້ມ. ແຕະເພື່ອດາວໂຫຼດ ແລະ ກູ້ຄືນ." "ຈຳເປັນຕ້ອງອັບເດດແອັບ" "ບໍ່ໄດ້ອັບເດດແອັບສຳລັບໄອຄອນນີ້. ທ່ານສາມາດອັບເດດເອງໄດ້ເພື່ອເປີດການນຳໃຊ້ທາງລັດນີ້ຄືນໃໝ່ ຫຼື ລຶບໄອຄອນດັ່ງກ່າວອອກ." "ອັບເດດ" @@ -165,6 +147,7 @@ "ປິດລາຍຊື່ວິດເຈັດແລ້ວ" "ເພີ່ມໃສ່ໂຮມສະກຣີນ" "Move item here" + "ເພີ່ມ​ລາຍ​ການ​ໃສ່​ໜ້າ​ຈໍ​ຫຼັກ​ແລ້ວ" "ເອົາ​ລາຍ​ການ​ອອກ​ໄປ​ແລ້ວ" "ຍົກເລີກ" "ຍ້າຍ​ລາຍ​ການ" @@ -184,15 +167,11 @@ "ຫຼຸດ​ລວງ​ກ້​ວາງ​ລົງ" "ຫຼຸດ​ລວງ​ສູງ​ລົງ" "ປ່ຽນ​ຂະ​ໜາດ​ວິດ​ເຈັດ​ເປັນ​ລວງ​ກ້​ວາງ %1$s ລວງ​ສູງ %2$s ແລ້ວ" - "ເມນູທາງລັດ" - "ປັບຂະໜາດກອບວິດເຈັດສຳລັບ %1$s" - "ປິດ" + "ທາງລັດ" "ປິດໄວ້" "ປິດ" "ສ່ວນຕົວ" "ວຽກ" - "ແຖບແອັບສ່ວນຕົວ" - "ແຖບແອັບບ່ອນເຮັດວຽກ" "ໂປຣໄຟລ໌ບ່ອນເຮັດວຽກ" "ແອັບບ່ອນເຮັດວຽກແມ່ນຖືກຕິດປ້າຍ ແລະ ສະແດງໃຫ້ຜູ້ເບິ່ງແຍງໄອທີຂອງທ່ານເຫັນ" "ເຂົ້າໃຈແລ້ວ" @@ -204,7 +183,6 @@ "ເຂົ້າໃຈແລ້ວ" "ຢຸດແອັບບ່ອນເຮັດວຽກຊົ່ວຄາວ" "ຍົກເລີກການຢຸດຊົ່ວຄາວ" - "ກຳນົດເວລາຂອງແອັບບ່ອນເຮັດວຽກ" "ກັ່ນຕອງ" "ບໍ່ສຳເລັດ: %1$s" "ພື້ນທີ່ສ່ວນຕົວ" @@ -217,5 +195,4 @@ "ການປ່ຽນແປງພື້ນທີ່ສ່ວນຕົວ" "ຕິດຕັ້ງ" "ຕິດຕັ້ງແອັບໄປໃສ່ພື້ນທີ່ສ່ວນບຸກຄົນ" - "ເພີ່ມໄຟລ໌ ແລະ ອື່ນໆໃສ່ພື້ນທີ່ສ່ວນບຸກຄົນ" diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml index 57e4aa75c6..9846525aa7 100644 --- a/res/values-lt/strings.xml +++ b/res/values-lt/strings.xml @@ -28,12 +28,9 @@ "Sparčiojo klavišo negalima naudoti" "Pagrindinis" "Nustatykite „%1$s“ kaip numatytąją pagrindinę programą skiltyje „Nustatymai“" - "Išskaidytas ekranas" - "Keisti kraštinių santykį" + "Išskaidyto ekrano režimas" "Programos „%1$s“ informacija" "„%1$s“ naudojimo nustatymai" - "Naujas langas" - "Tvarkyti langus" "Išsaugoti programų porą" "%1$s | %2$s" "Ši programų pora šiame įrenginyje nepalaikoma" @@ -41,8 +38,6 @@ "Programų pora nepasiekiama" "Dukart pal. ir palaik., kad perkeltumėte valdiklį." "Dukart palieskite ir palaikykite, kad perkeltumėte valdiklį ar naudotumėte tinkintus veiksmus." - "Daugiau parinkčių" - "Rodyti visus valdiklius" "%1$d × %2$d" "%1$d plotis ir %2$d aukštis" "%1$s valdiklis" @@ -69,13 +64,8 @@ "Darbas" "Pokalbiai" "Užrašų kūrimas" - "Rodyti mygtuką „Pridėti“" - "Slėpti mygtuką „Pridėti“" "Pridėti" "Pridėti valdiklį: %1$s" - "Rodyti viską" - "Rodyti visus valdiklius" - "Rodomi visi valdikliai" "Palieskite, kad pakeistumėte valdiklio nustatymus" "Pakeisti valdiklio nustatymus" "Paieškos programos" @@ -83,7 +73,6 @@ "Nerasta jokių užklausą „%1$s“ atitinkančių programų" "Programa" "Visos programos" - "Programų sąrašas" "Pranešimai" "Dukart pal. ir palaik., kad perk. spart. klavišą." "Dukart palieskite ir palaikykite, kad perkeltumėte spartųjį klavišą ar naudotumėte tinkintus veiksmus." @@ -101,7 +90,6 @@ "Įdiegti" "Nesiūlyti programos" "Prisegti numatymą" - "Debesėlis" "įdiegti sparčiuosius klavišus" "Programai leidžiama pridėti sparčiuosius klavišus be naudotojo įsikišimo." "skaityti pagrindinio ekrano nustatymus ir sparčiuosius klavišus" @@ -118,8 +106,6 @@ "%1$d psl. iš %2$d" "%1$d pagrindinis ekranas iš %2$d" "Naujas pagrindinio ekrano puslapis" - "Aktyvi" - "Sumažinta" "Atidarytas aplankas, %1$d ir %2$d" "Palieskite, kad uždarytumėte aplanką" "Palieskite, kad išsaugotumėte pakeistą pavadinimą" @@ -127,16 +113,13 @@ "Aplankas pervardytas kaip „%1$s“" "Aplankas: „%1$s“, elementų: %2$d" "Aplankas: „%1$s“, elementų: %2$d ar daugiau" - "Aplankas be pavadinimo" "Programų pora: „%1$s“ ir „%2$s“" "Ekrano fonas ir stilius" "Redaguoti pagrindinį ekraną" - "Pagrindinio ekrano nustatymai" + "„Home“ nustatymai" "Išjungė administratorius" "Leisti pasukti pagrindinį ekraną" "Kai telefonas pasukamas" - "Gulsčiojo ekrano režimas" - "Nustatykite telefoną į gulsčiojo ekrano režimą" "Pranešimų taškai" "Įjungta" "Išjungta" @@ -155,8 +138,7 @@ "Įdiegiama: „%1$s“; baigta: %2$s" "Atsisiunčiama programa „%1$s“, %2$s baigta" "Laukiama, kol bus įdiegta programa „%1$s“" - "Programa „%1$s“ suarchyvuota." - "atsisiųsti ir atkurti" + "Programa „%1$s“ suarchyvuota. Palieskite, jei norite atsisiųsti ir atkurti." "Būtina atnaujinti programą" "Šios piktogramos programa neatnaujinta. Galite patys atnaujinti, kad iš naujo įgalintumėte šį spartųjį klavišą, arba pašalinkite piktogramą." "Atnaujinti" @@ -165,6 +147,7 @@ "Valdiklių sąrašas uždarytas" "Pridėti prie pagrind. ekrano" "Perkelti elementą čia" + "Elementas pridėtas prie pagrindinio ekrano" "Elementas perkeltas" "Anuliuoti" "Perkelti elementą" @@ -184,15 +167,11 @@ "Sumažinti plotį" "Sumažinti aukštį" "Valdiklio dydis pakeistas: plotis – %1$s, aukštis – %2$s" - "Sparčiųjų klavišų meniu" - "„%1$s“ valdiklio dydžio keitimo rėmelis" - "Uždaryti" + "Spartieji klavišai" "Atsisakyti" "Uždaryti" "Asmeninės" "Darbo" - "Asmeninių programų skirtukas" - "Darbo programų skirtukas" "Darbo profilis" "Darbo programos yra pažymėtos ženkleliu ir matomos IT administratoriui" "Supratau" @@ -204,12 +183,11 @@ "Supratau" "Pristabdyti darbo programas" "Atšaukti pristabdymą" - "Darbo programų tvarkaraštis" "Filtruoti" "Nepavyko: %1$s" "Privati erdvė" "Palieskite, kad nustatytumėte arba atidarytumėte" - "Privati" + "Privatus" "Privačios erdvės nustatymai" "Privatus, atrakintas." "Privatus, užrakintas." @@ -217,5 +195,4 @@ "Privačios erdvės perkėlimas" "Įdiegti" "Įdiegti programas privačioje erdvėje" - "Pridėkite failų ir kt. prie privačios erdvės" diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml index daeef1587c..49f7ffe3a4 100644 --- a/res/values-lv/strings.xml +++ b/res/values-lv/strings.xml @@ -29,11 +29,8 @@ "Sākums" "Iestatiet %1$s kā noklusējuma sākuma lietotni iestatījumos." "Sadalīt ekrānu" - "Mainīt malu attiecību" "%1$s: informācija par lietotni" "Lietojuma iestatījumi: %1$s" - "Jauns logs" - "Pārvaldīt logus" "Saglabāt lietotņu pāri" "%1$s | %2$s" "Šis lietotņu pāris netiek atbalstīts šajā ierīcē" @@ -41,8 +38,6 @@ "Lietotņu pāris nav pieejams" "Lai pārvietotu logrīku, pieskarieties un turiet." "Lai pārvietotu logrīku, uz tā veiciet dubultskārienu un turiet. Varat arī veikt pielāgotas darbības." - "Citas iespējas" - "Rādīt visus logrīkus" "%1$d × %2$d" "%1$d plats un %2$d augsts" "Logrīks %1$s" @@ -69,13 +64,8 @@ "Darba" "Sarunas" "Piezīmju pierakstīšana" - "Rādīt pogu Pievienot" - "Paslēpt pogu Pievienot" "Pievienot" "Pievienot logrīku %1$s" - "Rādīt visus" - "Rādīt visus logrīkus" - "Tiek rādīti visi logrīki" "Pieskarieties, lai mainītu logrīka iestatījumus." "Mainīt logrīka iestatījumus" "Meklēt lietotnes" @@ -83,7 +73,6 @@ "Vaicājumam “%1$s” neatbilda neviena lietotne" "Lietotne" "Visas lietotnes" - "Lietotņu saraksts" "Paziņojumi" "Lai pārvietotu saīsni, pieskarieties un turiet." "Lai pārvietotu saīsni, uz tās veiciet dubultskārienu un turiet. Varat arī veikt pielāgotas darbības." @@ -101,7 +90,6 @@ "Instalēt" "Neieteikt lietotni" "Piespraust prognozēto lietotni" - "Burbulis" "instalēt saīsnes" "Ļauj lietotnei pievienot saīsnes, nejautājot lietotājam." "sākuma ekrāna iestatījumu un saīšņu lasīšana" @@ -118,8 +106,6 @@ "%1$d. lapa no %2$d" "Sākuma ekrāns: %1$d no %2$d" "Jauna sākuma ekrāna lapa" - "Aktīva" - "Minimizēta" "Atvērta mape: %1$d x %2$d" "Pieskarieties, lai aizvērtu mapi." "Pieskarieties, lai saglabātu jauno nosaukumu." @@ -127,7 +113,6 @@ "Mape pārdēvēta par: %1$s" "Mape %1$s, %2$d vienumi" "Mape %1$s, vienumu skaits mapē: vismaz %2$d" - "Mape bez nosaukuma" "Lietotņu pāris: %1$s un %2$s" "Fona tapete un stils" "Rediģēt sākuma ekrānu" @@ -135,8 +120,6 @@ "Atspējojis administrators" "Atļaut sākuma ekrāna pagriešanu" "Pagriežot tālruni" - "Ainavas režīms" - "Iestatīt tālrunī ainavas režīmu" "Paziņojumu punkti" "Ieslēgti" "Izslēgts" @@ -155,8 +138,7 @@ "Notiek lietotnes “%1$s” instalēšana. Norise: %2$s." "Lietotnes %1$s lejupielāde (%2$s pabeigti)" "Notiek %1$s instalēšana" - "Lietotne %1$s ir arhivēta." - "lejupielādēt un atjaunot" + "Lietotne %1$s ir arhivēta; lai lejupielādētu un atjaunotu, pieskarieties" "Lietotne ir jāatjaunina" "Šai ikonai paredzētā lietotne nav atjaunināta. Varat to atjaunināt manuāli, lai atkārtoti iespējotu šo saīsni, vai noņemt ikonu." "Atjaunināt" @@ -165,6 +147,7 @@ "Logrīku saraksts aizvērts" "Pievienot sākuma ekrānam" "Pārvietot vienumu šeit" + "Vienums pievienots sākuma ekrānam" "Vienums noņemts" "Atsaukt" "Pārvietot vienumu" @@ -184,15 +167,11 @@ "Samazināt platumu" "Samazināt augstumu" "Logrīka lielums mainīts — platums: %1$s, augstums: %2$s." - "Saīsnes izvēlne" - "Logrīka lieluma maiņas rāmis lietotnei %1$s" - "Aizvērt" + "Saīsnes" "Nerādīt" "Aizvērt" "Personīgās lietotnes" "Darba lietotnes" - "Cilne Personīgās lietotnes" - "Cilne Darba lietotnes" "Darba profils" "Darba lietotnēm ir pievienota emblēma, un tās ir redzamas jūsu IT administratoram" "Labi" @@ -204,12 +183,11 @@ "Labi" "Pārtraukt darba lietotņu darbību" "Atsākt" - "Darba lietotņu grafiks" "Filtrs" "Neizdevās: %1$s" "Privātā telpa" "Pieskarieties, lai iestatītu vai atvērtu" - "Privātā telpa" + "Privātā mape" "Privātās mapes iestatījumi" "Privāta un nav bloķēta." "Privāta un bloķēta." @@ -217,5 +195,4 @@ "Pāriet uz privāto mapi" "Instalēt" "Instalējiet lietotnes privātajā telpā." - "Pievienojiet failus un citu saturu privātajai telpai" diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml index 1bafbdee8a..2bfa089ad3 100644 --- a/res/values-mk/strings.xml +++ b/res/values-mk/strings.xml @@ -29,11 +29,8 @@ "Почетен екран" "Поставете %1$s да биде стандардна апликација за почетен екран во „Поставки“" "Поделен екран" - "Променете го соодносот" "Податоци за апликација за %1$s" "Поставки за користење за %1$s" - "Нов прозорец" - "Управувајте со прозорците" "Зачувај го парот апликации" "%1$s | %2$s" "Паров апликации не е поддржан на уредов" @@ -41,8 +38,6 @@ "Парот апликации не е достапен" "Допрете и задржете за да преместите виџет." "Допрете двапати и задржете за да преместите виџет или користете приспособени дејства." - "Повеќе опции" - "Прикажи ги сите виџети" "%1$d × %2$d" "%1$d широк на %2$d висок" "Виџет %1$s" @@ -69,13 +64,8 @@ "Работни" "Разговори" "Фаќање белешки" - "Прикажи го копчето за додавање" - "Скриј го копчето за додавање" "Додај" "Додај го виџетот %1$s" - "Прикажи ги сите" - "Прикажи ги сите виџети" - "Се прикажуваат сите виџети" "Допрете за да ги промените поставките за виџетот" "Промени ги поставките за виџетот" "Пребарувајте апликации" @@ -83,7 +73,6 @@ "Не се најдени апликации што одговараат на „%1$s“" "Апликација" "Сите апликации" - "Список со апликации" "Известувања" "Допрете и задржете за да преместите кратенка." "Допрете двапати и задржете за да преместите кратенка или користете приспособени дејства." @@ -101,7 +90,6 @@ "Инсталирај" "Не предлагај апл." "Закачи го предвидувањето" - "Балонче" "инсталирање кратенки" "Овозможува апликацијата да додава кратенки без интервенција на корисникот." "да чита поставки и кратенки на почетна страница" @@ -118,8 +106,6 @@ "Страница %1$d од %2$d" "Екран на почетна страница %1$d од %2$d" "Нова страница на почетен екран" - "Активно" - "Минимизирано" "Отворена е папка, %1$d на %2$d" "Допрете за да ја затворите папката" "Допрете за да го зачувате преименувањето" @@ -127,7 +113,6 @@ "Папката е преименувана во %1$s" "Папка: %1$s, %2$d ставки" "Папка: %1$s, %2$d или повеќе ставки" - "Неименувана папка" "Пар апликации: %1$s и %2$s" "Тапет и стил" "Изменете го почетниот екран" @@ -135,8 +120,6 @@ "Оневозможено од администраторот" "Дозволи ротирање на почетниот екран" "Кога телефонот се ротира" - "Хоризонтален режим" - "Поставете го телефонот во „Хоризонтален режим“" "Точки за известување" "Вклучено" "Исклучено" @@ -155,8 +138,7 @@ "%1$s се инсталира, %2$s завршено" "Се презема %1$s, %2$s завршено" "%1$s чека да се инсталира" - "Апликацијата %1$s е архивирана." - "преземете и вратете" + "Апликацијата %1$s е архивирана. Допрете за да преземете и вратите." "Потребно е ажурирање на апликацијата" "Апликацијата за оваа икона не е ажурирана. Може да ажурирате рачно за да повторно се овозможи кратенкава или отстранете ја иконата." "Ажурирај" @@ -165,6 +147,7 @@ "Списокот со виџети е затворен" "Додај на почетниот екран" "Премести ја ставката овде" + "Ставката е додадена на почетниот екран" "Ставката е отстранета" "Врати" "Премести ја ставката" @@ -184,15 +167,11 @@ "Намали ширина" "Намали висина" "Големината на виџетот е променета на ширина %1$s висина %2$s" - "Мени за кратенки" - "Рамка за промена на големината на виџетот за %1$s" - "Затвори" + "Кратенки" "Отфрли" "Затвори" "Лично" "За работа" - "Картичка „Лични апликации“" - "Картичка „Работни апликации“" "Работен профил" "Работните апликации имаат значка и се видливи за IT-администраторот" "Сфатив" @@ -204,12 +183,11 @@ "Сфатив" "Паузирај ги работните апликации" "Прекини ја паузата" - "Распоред на работните апликации" "Филтер" "Не успеа: %1$s" "Приватен простор" "Допрете за да поставите или отворите" - "Приватен" + "Приватен простор" "Поставки за „Приватен простор“" "Приватно, отклучено." "Приватно, заклучено." @@ -217,5 +195,4 @@ "Префрлање на „Приватен простор“" "Инсталирајте" "Инсталирање апликации во „Приватен простор“" - "Додавање датотеки и друго во „Приватен простор“" diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml index 4090b9a592..a2babd510c 100644 --- a/res/values-ml/strings.xml +++ b/res/values-ml/strings.xml @@ -29,11 +29,8 @@ "ഹോം" "ക്രമീകരണത്തിൽ %1$s എന്നത് ഡിഫോൾട്ട് ഹോം ആപ്പായി സജ്ജീകരിക്കുക" "സ്‌ക്രീൻ വിഭജന മോഡ്" - "വീക്ഷണ അനുപാതം മാറ്റുക" "%1$s എന്നതിന്റെ ആപ്പ് വിവരങ്ങൾ" "%1$s എന്നതിനുള്ള ഉപയോഗ ക്രമീകരണം" - "പുതിയ വിന്‍ഡോ" - "വിൻഡോകൾ മാനേജ് ചെയ്യുക" "ആപ്പ് ജോടി സംരക്ഷിക്കുക" "%1$s | %2$s" "ഈ ഉപകരണത്തിൽ ഈ ആപ്പ് ജോടിക്ക് പിന്തുണയില്ല" @@ -41,8 +38,6 @@ "ആപ്പ് ജോടി ലഭ്യമല്ല" "വിജറ്റ് നീക്കാൻ സ്‌പർശിച്ച് പിടിക്കുക." "വിജറ്റ് നീക്കാൻ ഡബിൾ ടാപ്പ് ചെയ്യൂ, ഹോൾഡ് ചെയ്യൂ അല്ലെങ്കിൽ ഇഷ്‌ടാനുസൃത പ്രവർത്തനങ്ങൾ ഉപയോഗിക്കൂ." - "കൂടുതൽ ഓപ്ഷനുകൾ" - "എല്ലാ വിജറ്റും കാണിക്കുക" "%1$d × %2$d" "%1$d വീതിയും %2$d ഉയരവും" "%1$s വിജറ്റ്" @@ -69,13 +64,8 @@ "ജോലി" "സംഭാഷണങ്ങൾ" "കുറിപ്പ് രേഖപ്പെടുത്തൽ" - "\'ചേർക്കുക ബട്ടൺ\' കാണിക്കുക" - "\'ചേർക്കുക ബട്ടൺ\' മറയ്ക്കുക" "ചേർക്കുക" "%1$s വിജറ്റ് ചേർക്കുക" - "എല്ലാം കാണിക്കൂ" - "എല്ലാ വിജറ്റും കാണിക്കുക" - "എല്ലാ വിജറ്റുകളും കാണിക്കുന്നു" "വിജറ്റ് ക്രമീകരണം മാറ്റാൻ ടാപ്പ് ചെയ്യുക" "വിജറ്റ് ക്രമീകരണം മാറ്റുക" "ആപ്പുകൾ തിരയുക" @@ -83,7 +73,6 @@ "\"%1$s\" എന്നതുമായി പൊരുത്തപ്പെടുന്ന ആപ്പുകളൊന്നും കണ്ടെത്തിയില്ല" "ആപ്പ്" "എല്ലാ ആപ്പുകളും" - "ആപ്പുകളുടെ ലിസ്റ്റ്" "അറിയിപ്പുകൾ" "കുറുക്കുവഴി നീക്കാൻ സ്‌പർശിച്ച് പിടിക്കുക." "കുറുക്കുവഴി നീക്കാൻ ഡബിൾ ടാപ്പ് ചെയ്യൂ, ഹോൾഡ് ചെയ്യൂ അല്ലെങ്കിൽ ഇഷ്‌ടാനുസൃത പ്രവർത്തനങ്ങൾ ഉപയോഗിക്കൂ." @@ -101,7 +90,6 @@ "ഇൻസ്‌റ്റാൾ ചെയ്യുക" "ആപ്പ് നിർദ്ദേശിക്കേണ്ട" "പ്രവചനം പിൻ ചെയ്യുക" - "ബബിൾ" "കുറുക്കുവഴികൾ ഇൻസ്റ്റാളുചെയ്യുക" "ഉപയോക്തൃ ഇടപെടൽ ഇല്ലാതെ കുറുക്കുവഴികൾ ചേർക്കാൻ അപ്ലിക്കേഷനെ അനുവദിക്കുന്നു." "ഹോം ക്രമീകരണവും കുറുക്കുവഴികളും വായിക്കുക" @@ -118,8 +106,6 @@ "പേജ് %1$d / %2$d" "ഹോം സ്‌ക്രീൻ %1$d / %2$d" "പുതിയ ഹോം സ്ക്രീൻ പേജ്" - "സജീവം" - "ചെറുതാക്കി" "ഫോൾഡർ തുറന്നു, %1$d / %2$d" "ഫോൾഡർ അടയ്ക്കുന്നതിന് ടാപ്പുചെയ്യുക" "പേരുമാറ്റം സംരക്ഷിക്കുന്നതിന് ടാപ്പുചെയ്യുക" @@ -127,7 +113,6 @@ "ഫോൾഡറിന്റെ പേര് %1$s എന്നായി മാറ്റി" "ഫോൾഡർ: %1$s, %2$d ഇനങ്ങൾ" "ഫോൾഡർ: %1$s, %2$d അല്ലെങ്കിൽ അതിലധികം ഇനങ്ങൾ" - "പേരിടാത്ത ഫോൾഡർ" "ആപ്പ് ജോടി: %1$s, %2$s" "വാൾപേപ്പറും സ്‌റ്റൈലും" "ഹോം സ്‌ക്രീൻ എഡിറ്റ് ചെയ്യുക" @@ -135,8 +120,6 @@ "അഡ്മിൻ പ്രവർത്തനരഹിതമാക്കിയിരിക്കുന്നു" "ഹോം സ്ക്രീൻ റൊട്ടേഷൻ അനുവദിക്കുക" "ഫോൺ തിരിച്ച നിലയിലായിരിക്കുമ്പോൾ" - "ലാൻഡ്‌സ്‌കേപ്പ് മോഡ്" - "ഫോൺ ലാൻഡ്‌സ്‌കേപ്പ് മോഡിലേക്ക് സജ്ജീകരിക്കുക" "അറിയിപ്പ് ഡോട്ടുകൾ" "ഓണാണ്" "ഓഫാണ്" @@ -155,8 +138,7 @@ "%1$s ഇൻസ്‌റ്റാൾ ചെയ്യുന്നു, %2$s പൂർത്തിയായി" "%1$s ഡൗൺലോഡ് ചെയ്യുന്നു, %2$s പൂർത്തിയായി" "ഇൻസ്റ്റാൾ ചെയ്യാൻ %1$s കാക്കുന്നു" - "%1$s ആർക്കൈവ് ചെയ്തു." - "ഡൗൺലോഡ് ചെയ്ത് പുനഃസ്ഥാപിക്കുക" + "%1$s ആർക്കൈവ് ചെയ്തു. ഡൗൺലോഡ് ചെയ്യാനും പുനഃസ്ഥാപിക്കാനും ടാപ്പ് ചെയ്യുക." "ആപ്പ് അപ്‌ഡേറ്റ് ചെയ്യേണ്ടതുണ്ട്" "ഈ ഐക്കണിനുള്ള ആപ്പ് അപ്‌ഡേറ്റ് ചെയ്തിട്ടില്ല. ഈ കുറുക്കുവഴി വീണ്ടും പ്രവർത്തനക്ഷമമാക്കാൻ നിങ്ങൾക്ക് നേരിട്ട് അപ്‌ഡേറ്റ് ചെയ്യാം അല്ലെങ്കിൽ ഐക്കൺ നീക്കം ചെയ്യാം." "അപ്ഡേറ്റ് ചെയ്യുക" @@ -165,6 +147,7 @@ "വിജറ്റുകളുടെ ലിസ്‌റ്റ് അവസാനിപ്പിച്ചു" "ഹോം സ്‌ക്രീനിലേക്ക് ചേർക്കുക" "ഇനം ഇവിടേക്ക് നീക്കുക" + "ഹോം സ്‌ക്രീനിൽ ഇനം ചേർത്തു" "ഇനം നീക്കംചെയ്‌തു" "പഴയപടിയാക്കുക" "ഇനം നീക്കുക" @@ -184,15 +167,11 @@ "വീതി കുറയ്‌ക്കുക" "ഉയരം കുറയ്‌ക്കുക" "വീതി %1$s ഉയരം %2$s-ലേക്ക് വിഡ്‌ജെറ്റിന്റെ വലുപ്പം മാറ്റി" - "കുറുക്കുവഴി മെനു" - "%1$s എന്നതിനുള്ള വിജറ്റിന്റെ വലുപ്പം മാറ്റുന്നതിനുള്ള ഫ്രെയിം" - "അടയ്‌ക്കുക" + "കുറുക്കുവഴികൾ" "നിരസിക്കുക" "അടയ്ക്കൂ" "വ്യക്തിപരം" "Work" - "വ്യക്തിപര ആപ്പുകളുടെ ടാബ്" - "ഔദ്യോഗിക ആപ്പുകളുടെ ടാബ്" "ഔദ്യോഗിക പ്രൊഫൈൽ" "ഔദ്യോഗിക ആപ്പുകൾക്ക് ബാഡ്‌ജ് നൽകിയിരിക്കുന്നു, അവ നിങ്ങളുടെ ഐടി അഡ്‌മിന് കാണാനുമാകും" "മനസ്സിലായി" @@ -204,7 +183,6 @@ "മനസ്സിലായി" "ഔദ്യോഗിക ആപ്പുകൾ താൽക്കാലികമായി നിർത്തുക" "താൽക്കാലികമായി നിർത്തിയത് മാറ്റുക" - "ഔദ്യോഗിക ആപ്പുകൾക്കുള്ള ഷെഡ്യൂൾ" "ഫിൽട്ടർ ചെയ്യുക" "പരാജയപ്പെട്ടു: %1$s" "സ്വകാര്യ സ്പേസ്" @@ -217,5 +195,4 @@ "പ്രൈവറ്റ് സ്‌പേസ് ട്രാൻസിഷനിംഗ്" "ഇൻസ്റ്റാൾ ചെയ്യുക" "സ്വകാര്യ സ്പേസിലേക്ക് ആപ്പുകൾ ഇൻസ്റ്റാൾ ചെയ്യുക" - "സ്വകാര്യ സ്‌പേസിലേക്ക് ഫയലുകളും മറ്റും ചേർക്കുക" diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml index f0d511139c..93a1c9c6fa 100644 --- a/res/values-mn/strings.xml +++ b/res/values-mn/strings.xml @@ -29,11 +29,8 @@ "Нүүр" "%1$s-г Тохиргоонд өгөгдмөл үндсэн аппаар тохируулна уу" "Дэлгэцийг хуваах" - "Аспектын харьцааг өөрчлөх" "%1$s-н аппын мэдээлэл" "%1$s-н ашиглалтын тохиргоо" - "Шинэ цонх" - "Цонхнуудыг удирдах" "Апп хослуулалтыг хадгалах" "%1$s | %2$s" "Энэ апп хослуулалтыг уг төхөөрөмж дээр дэмждэггүй" @@ -41,8 +38,6 @@ "Апп хослуулалт боломжгүй байна" "Виджетийг зөөх бол хүрээд, удаан дарна уу." "Виджетийг зөөх эсвэл захиалгат үйлдлийг ашиглахын тулд хоёр товшоод, удаан дарна уу." - "Бусад сонголт" - "Бүх виджетийг харуулах" "%1$d × %2$d" "%1$d өргөн %2$d өндөр" "%1$s жижиг хэрэгсэл" @@ -69,13 +64,8 @@ "Ажил" "Харилцан яриа" "Тэмдэглэл хөтлөх" - "Нэмэх товчийг харуулах" - "Нэмэх товчийг нуух" "Нэмэх" "%1$s виджетийг нэмэх" - "Бүгдийг харуул" - "Бүх виджетийг харуулах" - "Бүх виджетийг харуулж байна" "Жижиг хэрэгслийн тохиргоог өөрчлөхийн тулд товшино уу" "Жижиг хэрэгслийн тохиргоог өөрчлөх" "Апп хайх" @@ -83,7 +73,6 @@ "\"%1$s\"-д тохирох апп олдсонгүй" "Апп" "Бүх апп" - "Аппын жагсаалт" "Мэдэгдэл" "Товчлолыг зөөхийн тулд хүрээд, удаан дарна уу." "Товчлолыг зөөх эсвэл захиалгат үйлдлийг ашиглахын тулд хоёр товшоод, удаан дарна уу." @@ -101,7 +90,6 @@ "Суулгах" "Апп бүү санал болго" "Таамаглалыг бэхлэх" - "Бөмбөлөг" "товчлол суулгах" "Апп нь хэрэглэгчийн оролцоогүйгээр товчлолыг нэмэж чадна" "нүүрний тохиргоо болон товчлолыг унших" @@ -118,8 +106,6 @@ "%2$d-н %1$d хуудас" "%2$d-н Нүүр дэлгэц %1$d" "Шинэ үндсэн нүүр хуудас" - "Идэвхтэй" - "Жижгэрүүлсэн" "%1$d %2$d фолдер нээгдэв" "Фолдерийг хаахын тулд дарна уу" "Шинэ нэрийг хадгалахын тулд дарна уу." @@ -127,7 +113,6 @@ "Фолдерын нэр %1$s болов" "Фолдер: %1$s, %2$d зүйл" "Фолдер: %1$s, %2$d эсвэл үүнээс олон зүйл" - "Нэргүй фолдер" "Апп хослуулалт: %1$s болон %2$s" "Дэлгэцийн зураг, загвар" "Үндсэн нүүрийг засах" @@ -135,8 +120,6 @@ "Таны админ идэвхгүй болгосон" "Үндсэн нүүрийг эргүүлэхийг зөвшөөрөх" "Утсыг эргүүлсэн үед" - "Хэвтээ горим" - "Утсыг хэвтээ горимд тохируулах" "Мэдэгдлийн цэг" "Асаалттай" "Унтраалттай" @@ -155,8 +138,7 @@ "%1$s-г суулгаж байна. %2$s дууссан" "%1$s-г татаж байна, %2$s татсан" "%1$s нь суулгахыг хүлээж байна" - "%1$s-г архивласан." - "татах, сэргээх" + "%1$s-г архивласан. Татаж, сэргээхийн тулд товшино уу." "Аппын шинэчлэлт шаардлагатай" "Энэ дүрс тэмдгийн аппыг шинэчлээгүй. Та энэ товчлолыг дахин идэвхжүүлэх эсвэл дүрсийг хасахын тулд гараар шинэчлэх боломжтой." "Шинэчлэх" @@ -165,6 +147,7 @@ "Жижиг хэрэгслийн жагсаалтыг хаасан" "Үндсэн нүүрэнд нэмэх" "Энд байршуулах" + "Нүүр дэлгэцэнд нэмсэн зүйл" "Зүйлийг устгалаа" "Болих" "Зөөх" @@ -184,15 +167,11 @@ "Нарийсгах" "Намсгах" "Виджэтийн өргөн %1$s, өндөр %2$s болсон" - "Товчлолын цэс" - "%1$s-н виджетийн хэмжээг өөрчлөх фрейм" - "Хаах" + "Товчлол" "Хаах" "Хаах" "Хувийн" "Ажил" - "Хувийн апп таб" - "Ажлын апп таб" "Ажлын профайл" "Ажлын аппуудыг тэмдэглэсэн бөгөөд танай IT админд харагдана" "Ойлголоо" @@ -204,12 +183,11 @@ "Ойлголоо" "Ажлын аппуудыг түр зогсоох" "Түр зогсоохоо болих" - "Ажлын аппуудын хуваарь" "Шүүлтүүр" "Амжилтгүй болсон: %1$s" "Хувийн орон зай" "Тохируулах эсвэл нээхийн тулд товших" - "Хаалттай" + "Хувийн" "Private Space-н тохиргоо" "Хувийн, түгжээг тайлсан." "Хувийн, түгжээтэй." @@ -217,5 +195,4 @@ "Private Space-н шилжилт" "Суулгах" "Хувийн орон зайд аппууд суулгана уу" - "Хаалттай орон зайд файл болон бусад зүйлийг нэмэх" diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml index 570896eb0b..c084fab567 100644 --- a/res/values-mr/strings.xml +++ b/res/values-mr/strings.xml @@ -29,11 +29,8 @@ "होम" "सेटिंग्ज मध्ये %1$s हे डीफॉल्ट होम अ‍ॅप म्हणून सेट करा" "स्प्लिट स्क्रीन" - "आस्पेक्ट रेशो बदला" "%1$s साठी ॲपशी संबंधित माहिती" "%1$s साठी वापरासंबंधित सेटिंग्ज" - "नवीन विंडो" - "विंडो व्यवस्थापित करा" "ॲपची जोडी सेव्ह करा" "%1$s | %2$s" "या ॲपची जोडीला या डिव्हाइसवर सपोर्ट नाही" @@ -41,8 +38,6 @@ "ॲपची जोडी उपलब्ध नाही" "विजेट हलवण्यासाठी स्पर्श करा आणि धरून ठेवा." "विजेट हलवण्यासाठी किंवा कस्टम कृती वापरण्यासाठी दोनदा टॅप करा आणि धरून ठेवा." - "आणखी पर्याय" - "सर्व विजेट दाखवा" "%1$d × %2$d" "%1$d रूंद बाय %2$d उंच" "%1$s विजेट" @@ -69,13 +64,8 @@ "ऑफिस" "संभाषणे" "टिपा घेणे" - "जोडा बटण दाखवा" - "जोडा बटण लपवा" "जोडा" "%1$s विजेट जोडा" - "सर्व दाखवा" - "सर्व विजेट दाखवा" - "सर्व विजेट दाखवत आहे" "विजेट सेटिंग्ज बदलण्यासाठी टॅप करा" "विजेट सेटिंग्ज बदला" "अ‍ॅप्स शोधा" @@ -83,7 +73,6 @@ "\"%1$s\" शी जुळणारे कोणतेही अ‍ॅप्स आढळले नाहीत" "ॲप" "सर्व अ‍ॅप्स" - "अ‍ॅप्स सूची" "सूचना" "शॉर्टकट हलवण्यासाठी स्पर्श करा आणि धरून ठेवा." "शॉर्टकट हलवण्यासाठी किंवा कस्टम कृती वापरण्यासाठी दोनदा टॅप करा आणि धरून ठेवा." @@ -101,7 +90,6 @@ "इंस्टॉल करा" "ॲप सुचवू नका" "पूर्वानुमान पिन करा" - "बबल" "शॉर्टकट इंस्टॉल करा" "वापरकर्ता हस्तक्षेपाशिवाय शॉर्टकट जोडण्यास अ‍ॅप ला अनुमती देते." "होम सेटिंग्ज आणि शॉर्टकट वाचा" @@ -111,15 +99,13 @@ "विजेट लोड करू शकत नाही" "विजेटची सेटिंग्ज" "सेटअप पूर्ण करण्‍यासाठी टॅप करा" - "हे सिस्टीम अ‍ॅप आहे आणि ते अनइंस्टॉल केले जाऊ शकत नाही." + "हा सिस्टम अ‍ॅप आहे आणि अनइंस्टॉल केला जाऊ शकत नाही." "नाव संपादित करा" "%1$s अक्षम केला आहे" "{count,plural, =1{{app_name} संबंधित # सूचना आहे}other{{app_name} संबंधित # सूचना आहेत}}" "%2$d पैकी %1$d पेज" "%2$d पैकी %1$d मुख्य स्क्रीन" "नवीन होम स्क्रीन पेज" - "अ‍ॅक्टिव्ह" - "लहान केलेले" "फोल्डर उघडले, %1$d बाय %2$d" "फोल्डर बंद करण्यासाठी टॅप करा" "पुनर्नामित करणे सेव्ह करण्यासाठी टॅप करा" @@ -127,7 +113,6 @@ "फोल्डरचे नाव बदलून %1$s असे ठेवले" "फोल्डर: %1$s, %2$d आयटम" "फोल्डर: %1$s, %2$d किंवा त्याहून अधिक आयटम" - "नाव नसलेले फोल्डर" "ॲपची जोडी: %1$s आणि %2$s" "वॉलपेपर आणि शैली" "होम स्क्रीन संपादित करा" @@ -135,9 +120,7 @@ "आपल्या प्रशासकाने अक्षम केले" "होम स्क्रीन फिरवण्‍याची अनुमती द्या" "फोन फिरवला जातो तेव्हा" - "लँडस्केप मोड" - "फोन लँडस्केप मोडमध्ये सेट करा" - "नोटिफिकेशन बिंदू" + "नोटिफिकेशन डॉट" "सुरू" "बंद" "सूचनांच्या अ‍ॅक्सेसची आवश्यकता आहे" @@ -155,8 +138,7 @@ "%1$s इंस्टॉल करत आहे, %2$s पूर्ण झाले" "%1$s डाउनलोड होत आहे , %2$s पूर्ण झाले" "%1$s इंस्टॉल करण्याची प्रतिक्षा करत आहे" - "%1$s संग्रहित केले आहे." - "डाउनलोड करून रिस्टोअर करा" + "%1$s संग्रहित केले आहे. डाउनलोड करून रिस्टोअर करण्यासाठी टॅप करा." "अ‍ॅप अपडेट करणे आवश्‍यक आहे" "या आयकनसाठी अ‍ॅप अपडेट केलेले नाही. हा शॉटकर्ट पुन्हा सुरू करण्यासाठी तुम्ही मॅन्युअली अपडेट करू शकता किंवा आयकन काढून टाका." "अपडेट करा" @@ -165,6 +147,7 @@ "विजेट सूची बंद केली" "होम स्क्रीनवर जोडा" "आयटम येथे हलवा" + "आयटम मुख्य स्क्रीनवर जोडला" "आयटम काढून टाकला" "पहिल्यासारखे करा" "आयटम हलवा" @@ -184,15 +167,11 @@ "रुंदी कमी करा" "उंची कमी करा" "विजेटचा आकार रुंदी %1$s उंची %2$s मध्ये बदलला" - "शॉर्टकट मेनू" - "%1$s साठी विजेटचा आकार बदलण्याची फ्रेम" - "बंद करा" + "शॉर्टकट" "डिसमिस करा" "बंद करा" "वैयक्तिक" "कार्य" - "वैयक्तिक ॲप्स टॅब" - "Work apps टॅब" "कार्य प्रोफाइल" "कार्य ॲप्स ही बॅज केलेली असून तुमच्या आयटी ॲडमिनला दृश्यमान आहेत" "समजले" @@ -204,7 +183,6 @@ "समजले" "कार्य ॲप्स थांबवा" "अनपॉझ करा" - "Work apps साठी शेड्यूल" "फिल्टर" "हे करता आले नाही: %1$s" "खाजगी स्पेस" @@ -217,5 +195,4 @@ "खाजगी स्पेस वर स्विच करणे" "इंस्टॉल करा" "अ‍ॅप्स खाजगी स्पेस मध्ये इंस्टॉल करा" - "खाजगी स्पेस मध्ये फाइल आणि आणखी बऱ्याच गोष्टी जोडा" diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml index bb0f194276..72d92e21ce 100644 --- a/res/values-ms/strings.xml +++ b/res/values-ms/strings.xml @@ -29,11 +29,8 @@ "Rumah" "Tetapkan %1$s sebagai apl skrin utama lalai dalam Tetapan" "Skrin pisah" - "Tukar nisbah bidang" "Maklumat apl untuk %1$s" "Tetapan penggunaan sebanyak %1$s" - "Tetingkap Baharu" - "Urus Tetingkap" "Simpan gandingan apl" "%1$s | %2$s" "Gandingan apl ini tidak disokong pada peranti ini" @@ -41,8 +38,6 @@ "Gandingan apl tidak tersedia" "Sentuh & tahan untuk menggerakkan widget." "Ketik dua kali & tahan untuk menggerakkan widget atau menggunakan tindakan tersuai." - "Lagi pilihan" - "Tunjukkan semua widget" "%1$d × %2$d" "Lebar %1$d kali tinggi %2$d" "Widget %1$s" @@ -66,16 +61,11 @@ "Widget dan pintasan tidak tersedia" "Tiada widget atau pintasan yang dijumpai" "Peribadi" - "Kerja" + "Tempat kerja" "Perbualan" "Pengambilan nota" - "Tunjukkan butang tambah" - "Sembunyikan butang tambah" "Tambah" "Tambahkan widget %1$s" - "Tunjukkan semua" - "Tunjukkan semua widget" - "Menunjukkan semua widget" "Ketik untuk menukar tetapan widget" "Tukar tetapan widget" "Cari apl" @@ -83,7 +73,6 @@ "Tiada apl yang ditemui sepadan dengan \"%1$s\"" "Apl" "Semua apl" - "Senarai apl" "Pemberitahuan" "Sentuh & tahan untuk menggerakkan pintasan." "Ketik dua kali & tahan untuk menggerakkan pintasan atau menggunakan tindakan tersuai." @@ -101,7 +90,6 @@ "Pasang" "Jangan cadangkan apl" "Sematkan Ramalan" - "Gelembung" "pasang pintasan" "Membenarkan apl menambah pintasan tanpa campur tangan pengguna." "membaca tetapan dan pintasan skrin utama" @@ -118,8 +106,6 @@ "Halaman %1$d daripada %2$d" "Skrin Laman Utama %1$d daripada %2$d" "Halaman skrin utama baharu" - "Aktif" - "Minimumkan" "Folder dibuka, %1$d kali %2$d" "Ketik untuk menutup folder" "Ketik untuk menyimpan penamaan semula" @@ -127,7 +113,6 @@ "Folder dinamakan semula kepada %1$s" "Folder: %1$s, %2$d item" "Folder: %1$s, %2$d atau lebih banyak item" - "Folder tidak bernama" "Gandingan apl: %1$s dan %2$s" "Hiasan latar & gaya" "Edit Skrin Utama" @@ -135,8 +120,6 @@ "Dilumpuhkan oleh pentadbir anda" "Benarkan putaran skrin utama" "Apabila telefon diputar" - "Mod landskap" - "Tetapkan telefon kepada mod landskap" "Titik pemberitahuan" "Hidup" "Mati" @@ -155,8 +138,7 @@ "%1$s dipasang, %2$s selesai" "%1$s memuat turun, %2$s selesai" "%1$s menunggu untuk dipasang" - "%1$s diarkibkan." - "muat turun dan pulihkan" + "%1$s diarkibkan. Ketik untuk memuat turun dan memulihkan apl tersebut." "Kemas kini apl diperlukan" "Apl untuk ikon ini tidak dikemas kini. Anda boleh mengemas kini secara manual untuk mendayakan semula pintasan atau mengalih keluar ikon." "Kemas kini" @@ -165,6 +147,7 @@ "Senarai widget ditutup" "Tambahkan pada skrin utama" "Alihkan item ke sini" + "Item ditambahkan pada skrin utama" "Item dialih keluar" "Buat asal" "Alihkan Item" @@ -184,15 +167,11 @@ "Kurangkan kelebaran" "Kurangkan ketinggian" "Saiz widget diubah menjadi %1$s lebar %2$s tinggi" - "Menu Pintasan" - "Bingkai Ubah Saiz Widget untuk %1$s" - "Tutup" + "Pintasan" "Ketepikan" "Tutup" "Peribadi" "Kerja" - "Tab apl peribadi" - "Tab apl kerja" "Profil kerja" "Apl kerja mempunyai lencana dan kelihatan kepada pentadbir IT anda" "OK" @@ -204,10 +183,9 @@ "OK" "Jeda apl kerja" "Nyahjeda" - "Jadual apl kerja" "Tapis" "Gagal: %1$s" - "Ruang persendirian" + "Ruang privasi" "Ketik untuk menyediakan atau membuka" "Persendirian" "Tetapan Ruang Peribadi" @@ -217,5 +195,4 @@ "Peralihan Ruang Peribadi" "Pasang" "Pasang apl pada Ruang Peribadi" - "Tambahkan fail dan banyak lagi pada Ruang Persendirian" diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml index 3b7e88697f..194ece73ba 100644 --- a/res/values-my/strings.xml +++ b/res/values-my/strings.xml @@ -29,11 +29,8 @@ "ပင်မစာမျက်နှာ" "ဆက်တင်များတွင် %1$s ကို မူရင်းပင်မစာမျက်နှာအက်ပ်အဖြစ် သတ်မှတ်ရန်" "မျက်နှာပြင် ခွဲ၍ပြသခြင်း" - "အချိုးအစား ပြောင်းရန်" "%1$s အတွက် အက်ပ်အချက်အလက်" "%1$s အတွက် အသုံးပြုမှုဆက်တင်များ" - "ဝင်းဒိုးအသစ်" - "ဝင်းဒိုးများ စီမံရန်" "အက်ပ်တွဲချိတ်ခြင်း သိမ်းရန်" "%1$s | %2$s" "ဤအက်ပ်တွဲချိတ်ခြင်းကို ဤစက်တွင် ပံ့ပိုးမထားပါ" @@ -41,8 +38,6 @@ "အက်ပ်တွဲချိတ်ခြင်းကို မရနိုင်ပါ" "ဝိဂျက်ကို ရွှေ့ရန် တို့ပြီး ဖိထားပါ။" "ဝိဂျက်ကို ရွှေ့ရန် (သို့) စိတ်ကြိုက်လုပ်ဆောင်ချက်များကို သုံးရန် နှစ်ချက်တို့ပြီး ဖိထားပါ။" - "နောက်ထပ် ရွေးစရာများ" - "ဝိဂျက်အားလုံး ပြပါ" "%1$d × %2$d" "အလျား %1$d နှင့် အမြင့် %2$d" "%1$s ဝိဂျက်" @@ -69,13 +64,8 @@ "အလုပ်" "စကားဝိုင်းများ" "မှတ်စုလိုက်ခြင်း" - "ထည့်ရန်ခလုတ် ပြပါ" - "ထည့်ရန်ခလုတ် ဖျောက်ပါ" "ထည့်ရန်" "%1$s ဝိဂျက်ထည့်ရန်" - "အားလုံးပြပါ" - "ဝိဂျက်အားလုံး ပြပါ" - "ဝိဂျက်အားလုံးကို ပြထားသည်" "ဝိဂျက် ဆက်တင်များကို ပြောင်းရန် တို့ပါ" "ဝိဂျက် ဆက်တင်များကို ပြောင်းပါ" "ရှာဖွေမှု အက်ပ်များ" @@ -83,7 +73,6 @@ "\"%1$s\" နှင့်ကိုက်ညီသည့် အပ်ပ်များကို မတွေ့ပါ" "အက်ပ်" "အက်ပ်အားလုံး" - "အက်ပ်စာရင်း" "အကြောင်းကြားချက်များ" "ဖြတ်လမ်းလင့်ခ်ကို ရွှေ့ရန် နှစ်ချက်တို့ပြီး ဖိထားပါ။" "ဖြတ်လမ်းလင့်ခ်ကို ရွှေ့ရန် (သို့) စိတ်ကြိုက်လုပ်ဆောင်ချက်များကို သုံးရန် နှစ်ချက်တို့ပြီး ဖိထားပါ။" @@ -101,7 +90,6 @@ "ထည့်သွင်းရန်" "အက်ပ်အကြံမပြုပါနှင့်" "ခန့်မှန်းချက်ကို ပင်ထိုးရန်" - "ပူဖောင်းကွက်" "ဖြတ်လမ်းလင့်ခ်များ ထည့်သွင်းခြင်း" "အသုံးပြုသူ လုပ်ဆောင်မှုမရှိပဲ အပ်ပလီကေးရှင်းကို အတိုကောက်မှတ်သားမှုများ ပြုလုပ်ခွင့် ပေးခြင်း" "ပင်မဆက်တင်နှင့် ဖြတ်လမ်းလင့်ခ်များ ဖတ်ခြင်း" @@ -118,8 +106,6 @@ "စာမျက်နှာ %1$d မှ %2$d" "ပင်မစာမျက်နှာ %1$d မှ %2$d" "ပင်မမျက်နှာပြင် စာမျက်နှာသစ်" - "သုံးနေသည်" - "ချုံ့ထားသည်" "ဖွင့်ထားသောအကန့်, %1$d နှင့် %2$d" "ဖိုင်တွဲကို ပိတ်ရန် တို့ပါ" "အမည်ပြောင်းခြင်းကို သိမ်းရန် တို့ပါ" @@ -127,7 +113,6 @@ "ပြောင်းလဲလိုက်သော အကန့်အမည် %1$s" "ဖိုင်တွဲ - %1$s%2$d ဖိုင်များ" "ဖိုင်တွဲ - %1$s%2$d သို့မဟုတ် နောက်ထပ်ဖိုင်များ" - "အမည်ပေးမထားသောဖိုင်တွဲ" "အက်ပ်တွဲချိတ်ခြင်း- %1$s နှင့် %2$s" "နောက်ခံနှင့် ပုံစံ" "ပင်မစာမျက်နှာ တည်းဖြတ်ရန်" @@ -135,8 +120,6 @@ "သင့်စီမံခန့်ခွဲသူက ပိတ်လိုက်ပါသည်" "ပင်မစာမျက်နှာလှည့်ခြင်းကို ခွင့်ပြုခြင်း" "ဖုန်းကိုလှည့်ထားစဉ်" - "အလျားလိုက်" - "ဖုန်းကို အလျားလိုက်သို့ သတ်မှတ်နိုင်သည်" "သတိပေးချက် အစက်များ" "ဖွင့်" "ပိတ်" @@ -155,8 +138,7 @@ "%1$s ကို ထည့်သွင်းနေသည်၊ %2$s ပြီးပါပြီ" "%1$s ဒေါင်းလုဒ်လုပ်နေသည်၊ %2$s ပြီးပါပြီ" "%1$s ကိုထည့်သွင်းရန်စောင့်နေသည်" - "%1$s ကို သိမ်းထားသည်။" - "ဒေါင်းလုဒ်လုပ်ပြီး ပြန်ယူရန်" + "%1$s ကို သိမ်းထားသည်။ ဒေါင်းလုဒ်လုပ်ပြီး ပြန်ယူရန် တို့ပါ။" "အက်ပ်ကို အပ်ဒိတ်လုပ်ရန် လိုအပ်သည်" "ဤသင်္ကေတအတွက် အက်ပ်ကို အပ်ဒိတ်လုပ်မထားပါ။ ဤဖြတ်လမ်းလင့်ခ်ကို ပြန်ဖွင့်ရန် ကိုယ်တိုင်အပ်ဒိတ်လုပ်နိုင်သည် (သို့) သင်္ကေတကို ဖယ်ရှားနိုင်သည်။" "အပ်ဒိတ်လုပ်ရန်" @@ -165,6 +147,7 @@ "ဝိဂျက်စာရင်းကို ပိတ်ထားသည်" "ပင်မစာမျက်နှာတွင် ထည့်ရန်" "၎င်းအား ဤသို့ ရွှေ့ပါ" + "ပင်မ ဖန်မျက်နှာပြင်သို့ ထည့်ပြီး၏" "ဖယ်ရှားပြီးပြီ" "နောက်ပြန်ရန်" "၎င်းအား ရွှေ့ပါ" @@ -184,15 +167,11 @@ "အကျယ်အား လျှော့ပါ" "အမြင့်အား လျှော့ပါ" "Widget အား အကျယ် %1$s အမြင့် %2$s အရွယ်အစားပြန်လည်ချိန်ညှိပြီး၏" - "ဖြတ်လမ်းလင့်ခ် မီနူး" - "%1$s အတွက် ဝိဂျက်အရွယ်ပြင်ဖရိမ်" - "ပိတ်ရန်" + "ဖြတ်လမ်းများ" "ပယ်ရန်" "ပိတ်ရန်" "ကိုယ်ပိုင်" "အလုပ်" - "ကိုယ်ရေးသုံးအက်ပ် တဘ်" - "အလုပ်သုံးအက်ပ် တဘ်" "အလုပ်ပရိုဖိုင်" "အလုပ်သုံးအက်ပ်များကို တံဆိပ်တပ်ထားပြီး သင်၏ IT စီမံခန့်ခွဲသူက မြင်နိုင်ပါသည်" "ရပါပြီ" @@ -204,7 +183,6 @@ "နားလည်ပြီ" "အလုပ်သုံးအက်ပ်များကို ခဏရပ်ရန်" "ပြန်ဖွင့်ရန်" - "အလုပ်သုံးအက်ပ်များ အချိန်ဇယား" "စစ်ထုတ်ရန်" "မအောင်မြင်ပါ− %1$s" "သီးသန့်ချတ်ခန်း" @@ -217,5 +195,4 @@ "သီးသန့်ချတ်ခန်း အပြောင်းအလဲ" "ထည့်သွင်းရန်" "‘သီးသန့်နေရာ’ တွင် အက်ပ်များ ထည့်သွင်းနိုင်သည်" - "‘သီးသန့်နေရာ’ တွင် ဖိုင်နှင့် အခြားအရာများ ထည့်နိုင်သည်" diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml index def17e4d63..a9e6c5d6cc 100644 --- a/res/values-nb/strings.xml +++ b/res/values-nb/strings.xml @@ -29,20 +29,15 @@ "Startskjerm" "Angi %1$s som standard startsideapp i innstillingene" "Delt skjerm" - "Endre høyde/bredde-forholdet" "Appinformasjon for %1$s" "Bruksinnstillinger for %1$s" - "Nytt vindu" - "Administrer vinduene" - "Lagre app-paret" + "Lagre apptilkoblingen" "%1$s | %2$s" "Denne apptilkoblingen støttes ikke på denne enheten" "Åpne enheten for å bruke denne apptilkoblingen" "Apptilkoblingen er ikke tilgjengelig" "Trykk og hold for å flytte en modul." "Dobbelttrykk og hold inne for å flytte en modul eller bruke tilpassede handlinger." - "Flere alternativer" - "Vis alle moduler" "%1$d × %2$d" "%1$d bredde x %2$d høyde" "%1$s-modul" @@ -69,13 +64,8 @@ "Jobb" "Samtaler" "Notatskriving" - "Vis Legg til-knappen" - "Skjul Legg til-knappen" "Legg til" "Legg til %1$s-modulen" - "Vis alle" - "Vis alle moduler" - "Viser alle moduler" "Trykk for å endre modulinnstillinger" "Endre modulinnstillinger" "Søk etter apper" @@ -83,7 +73,6 @@ "Fant ingen apper som samsvarer med «%1$s»" "App" "Alle apper" - "Appliste" "Varsler" "Trykk og hold for å flytte en snarvei." "Dobbelttrykk og hold for å flytte en snarvei eller bruke tilpassede handlinger." @@ -101,7 +90,6 @@ "Installer" "Ikke foreslå app" "Fest forslaget" - "Boble" "installere snarveier" "Gir apper tillatelse til å legge til snarveier uten innblanding fra brukeren." "lese startsideinnstillinger og -snarveier" @@ -118,8 +106,6 @@ "Side %1$d av %2$d" "Startside %1$d av %2$d" "Ny side på startskjermen" - "Aktiv" - "Minimert" "Mappen er åpnet – %1$d ganger %2$d" "Trykk for å lukke mappen" "Trykk for å lagre det nye navnet" @@ -127,7 +113,6 @@ "Mappen heter nå %1$s" "Mappe: %1$s, %2$d elementer" "Mappe: %1$s, %2$d eller flere elementer" - "Mappe uten navn" "Apptilkobling: %1$s og %2$s" "Bakgrunn og stil" "Endre startsiden" @@ -135,8 +120,6 @@ "Administratoren har slått av funksjonen" "Tillat at startskjermen roterer" "Når telefonen roteres" - "Liggende retning" - "Plasser telefonen i liggende retning" "Varselsprikker" "På" "Av" @@ -155,8 +138,7 @@ "%1$s installerer, %2$s er fullført" "Laster ned %1$s, %2$s er fullført" "Venter på å installere %1$s" - "%1$s er arkivert." - "last ned og gjenopprett" + "%1$s er arkivert. Trykk for å laste ned og gjenopprette." "Appen må oppdateres" "Appen for dette ikonet er ikke oppdatert. Du kan oppdatere manuelt for å aktivere denne snarveien igjen, eller du kan fjerne ikonet." "Oppdater" @@ -165,6 +147,7 @@ "Modullisten er lukket" "Legg til på startskjermen" "Flytt elementet hit" + "Elementet er lagt til på startskjermen" "Elementet er fjernet" "Angre" "Flytt elementet" @@ -184,15 +167,11 @@ "Reduser bredden" "Reduser høyden" "Størrelsen på modulen er endret til bredde %1$s og høyde %2$s" - "Hurtigtastmeny" - "Modul – endre størrelse på rammen for %1$s" - "Lukk" + "Snarveier" "Avvis" "Lukk" "Personlig" "Jobb" - "Personlige apper-fane" - "Jobbapper-fane" "Jobbprofil" "Jobbapper er merket og synlige for IT-administratoren" "Greit" @@ -204,7 +183,6 @@ "Greit" "Sett jobbapper på pause" "Gjenoppta" - "Tidsplan for jobbapper" "Filter" "Mislyktes: %1$s" "Privat område" @@ -217,5 +195,4 @@ "Private Space-overgang" "Installer" "Installer apper i privat område" - "Legg til filer og annet i det private området" diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml index c26a2ba647..7880c36e85 100644 --- a/res/values-ne/strings.xml +++ b/res/values-ne/strings.xml @@ -29,11 +29,8 @@ "होम" "सेटिङमा गई %1$s लाई डिफल्ट होम एपका रूपमा सेट गर्नुहोस्" "स्प्लिट स्क्रिन" - "एस्पेक्ट रेसियो परिवर्तन गर्नुहोस्" "%1$s का हकमा एपसम्बन्धी जानकारी" "%1$s को प्रयोगसम्बन्धी सेटिङ" - "नयाँ विन्डो" - "विन्डोहरू व्यवस्थापन गर्नुहोस्" "एपको पेयर सेभ गर्नुहोस्" "%1$s | %2$s" "यस डिभाइसमा यो एप पेयर प्रयोग गर्न मिल्दैन" @@ -41,8 +38,6 @@ "एप पेयर उपलब्ध छैन" "कुनै विजेट सार्न डबल ट्याप गरेर छोइराख्नुहोस्।" "कुनै विजेट सार्न वा आफ्नो रोजाइका कारबाही प्रयोग गर्न डबल ट्याप गरेर छोइराख्नुहोस्।" - "थप विकल्पहरू" - "सबै विजेटहरू देखाउनुहोस्" "%1$d × %2$d" "%1$d चौडाइ गुणा %2$d उचाइ" "%1$s विजेट" @@ -69,13 +64,8 @@ "कामसम्बन्धी" "वार्तालापहरू" "नोट लेख्ने कार्य" - "\"हाल्नुहोस्\" नामक बटन देखाउनुहोस्" - "\"हाल्नुहोस्\" नामक बटन लुकाउनुहोस्" "हाल्नुहोस्" "%1$s विजेट हाल्नुहोस्" - "सबै देखाउनुहोस्" - "सबै विजेटहरू देखाउनुहोस्" - "सबै विजेटहरू देखाइँदै छन्" "विजेटका सेटिङ बदल्न ट्याप गर्नुहोस्" "विजेटका सेटिङ बदल्नुहोस्" "एपहरू खोज्नुहोस्" @@ -83,8 +73,7 @@ "\"%1$s\" सँग मिल्दो कुनै एप भेटिएन" "एप" "सबै एप" - "एपहरूको सूची" - "नोटिफिकेसनहरू" + "सूचनाहरू" "कुनै सर्टकट सार्न डबल ट्याप गरेर छोइराख्नुहोस्।" "कुनै सर्टकट सार्न वा आफ्नो रोजाइका कारबाही प्रयोग गर्न डबल ट्याप गरेर छोइराख्नुहोस्।" "यो होम स्क्रिनमा ठाउँ छैन" @@ -98,10 +87,9 @@ "एपसम्बन्धी जानकारी" "निजी प्रोफाइलमा इन्स्टल गर्नुहोस्" "एप अनइन्स्टल गर्नुहोस्" - "इन्स्टल गर्नुहोस्" + "स्थापना गर्नुहोस्" "एप सिफारिस नगर्नुहोस्" "सिफारिस गरिएको एप पिन गर्नुहोस्" - "बबल" "सर्टकट स्थापना गर्नेहोस्" "प्रयोगकर्ताको हस्तक्षेप बिना एउटा एपलाई सर्टकटमा थप्नको लागि अनुमति दिनुहोस्।" "होम स्क्रिनका सेटिङ र सर्टकटहरू रिड गर्नुहोस्" @@ -118,8 +106,6 @@ "पृष्ठ %2$d को %1$d" "होम स्क्रिन %1$d को %2$d" "नयाँ होम स्क्रिन पृष्ठ" - "सक्रिय" - "सानो पारिएको" "फोल्डर खुल्यो %1$d बाट %2$d" "फोल्डरलाई बन्द गर्न ट्याप गर्नुहोस्" "पुनःनामाकरणलाई सुरक्षित गर्न ट्याप गर्नुहोस्" @@ -127,7 +113,6 @@ "फोल्डर %1$s मा पुनःनामाकरण गरियो।" "फोल्डर: %1$s, %2$d वस्तुहरू" "फोल्डर: %1$s, %2$d वा सोभन्दा बढी वस्तुहरू" - "नामरहित फोल्डर" "एप पेयर: %1$s%2$s" "वालपेपर तथा शैली" "होम स्क्रिन बदल्नुहोस्" @@ -135,10 +120,8 @@ "तपाईँको प्रशासकद्वारा असक्षम गरिएको" "होम स्क्रिन रोटेट हुन दिइयोस्" "फोन घुमाउँदा" - "ल्यान्डस्केप मोड" - "फोनमा ल्यान्डस्केप मोड अन गर्नुहोस्" "नोटिफिकेसन डट" - "अन छ" + "सक्रिय" "निष्क्रिय" "सूचनासम्बन्धी पहुँच आवश्यक हुन्छ" "नोटिफिकेसन डट देखाउन %1$s को एपसम्बन्धी सूचनाहरूलाई अन गर्नुहोस्" @@ -155,8 +138,7 @@ "%1$s इन्स्टल गरिँदै छ, %2$s पूरा भयो" "%1$s डाउनलोड गर्दै, %2$s सम्पन्‍न" "%1$s स्थापना गर्न प्रतीक्षा गर्दै" - "%1$s अभिलेखमा राखिएको छ।" - "डाउनलोड र रिस्टोर गर्नुहोस्" + "%1$s अभिलेखमा राखिएको छ। डाउनलोड गरी रिस्टोर गर्न ट्याप गर्नुहोस्।" "एप अपडेट गरिनु पर्छ" "यो आइकनले जनाउने एप अपडेट गरिएको छैन। तपाईं यो सर्टकट फेरि अन गर्न म्यानुअल रूपमा अपडेट गर्न सक्नुहुन्छ वा आइकन नै हटाउनुहोस्।" "अपडेट गर्नुहोस्" @@ -165,6 +147,7 @@ "विजेटहरूको सूची बन्द गरियो" "होम स्क्रिनमा राख्नुहोस्" "वस्तु यहाँ सार्नुहोस्" + "वस्तु गृह स्क्रिनमा थपियो" "वस्तु हटाइयो" "अन्डू गर्नुहोस्" "वस्तु सार्नुहोस्" @@ -184,15 +167,11 @@ "चौडाइ घटाउनुहोस्" "उँचाइ घटाउनुहोस्" "विजेट चौडाइ %1$s उचाइ %2$s मा पुनः आकार मिलाइयो" - "सर्टकटसम्बन्धी मेनु" - "%1$s एपको विजेटको आकार बदल्ने फ्रेम" - "बन्द गर्नुहोस्" + "सर्टकटहरू" "खारेज गर्नुहोस्" "बन्द गर्नुहोस्" "व्यक्तिगत" "कामसम्बन्धी" - "\"व्यक्तिगत एपहरू\" ट्याब" - "\"कामसम्बन्धी एपहरू\" ट्याब" "कार्य प्रोफाइल" "कामसम्बन्धी एपहरूमा ब्याज अङ्कित हुन्छ र तपाईंका IT एड्मिन ती एप हेर्न सक्छन्" "बुझेँ" @@ -204,12 +183,11 @@ "बुझेँ" "कामसम्बन्धी एपहरू पज गर्नुहोस्" "सुचारु गर्नुहोस्" - "कामसम्बन्धी एपहरूको समयतालिका" "फिल्टर" "कार्य पूरा गर्न सकिएन: %1$s" "निजी स्पेस" "सेटअप गर्न वा खोल्न ट्याप गर्नुहोस्" - "निजी स्पेस" + "निजी" "निजी स्पेससम्बन्धी सेटिङ" "निजी, अनलक गरिएको।" "निजी, लक गरिएको।" @@ -217,5 +195,4 @@ "निजी स्पेस ट्रान्जिसन गरिँदै छ" "इन्स्टल गर्नुहोस्" "निजी स्पेसमा एपहरू इन्स्टल गर्नुहोस्" - "निजी स्पेसमा फाइल र अन्य कुरा हाल्नुहोस्" diff --git a/res/values-night-v31/colors.xml b/res/values-night-v31/colors.xml index 4a6340add7..d23f4d13c4 100644 --- a/res/values-night-v31/colors.xml +++ b/res/values-night-v31/colors.xml @@ -26,8 +26,6 @@ @android:color/system_neutral1_700 @android:color/system_neutral1_100 - - @android:color/system_neutral2_200 @android:color/system_neutral1_100 @@ -47,11 +45,49 @@ @android:color/system_neutral2_200 - @android:color/system_neutral2_400 + @android:color/system_neutral2_700 @android:color/system_accent1_200 @android:color/system_accent1_800 + + @android:color/system_accent1_200 + + @android:color/system_accent1_900 + + @android:color/system_accent2_700 + @android:color/system_accent3_700 + @android:color/system_accent1_700 + @android:color/system_accent2_100 + @android:color/system_accent3_100 + @android:color/system_accent1_100 + @android:color/system_accent2_200 + #FFDAD5 + @android:color/system_accent2_900 + @android:color/system_neutral1_900 + @android:color/system_accent3_200 + @android:color/system_accent3_900 + @android:color/system_accent1_200 + @android:color/system_accent2_700 + #930001 + @android:color/system_accent1_900 + @android:color/system_accent1_600 + @android:color/system_accent2_100 + @android:color/system_accent3_700 + @android:color/system_accent3_100 + @android:color/system_accent1_700 + @android:color/system_neutral1_800 + @android:color/system_accent1_100 + @android:color/system_accent2_800 + @android:color/system_accent3_800 + #690001 + @android:color/system_neutral2_200 + @android:color/system_neutral2_400 + @android:color/system_neutral2_700 + @android:color/system_accent1_800 @android:color/system_neutral1_100 + @android:color/system_accent1_200 + @android:color/system_accent2_200 + @android:color/system_accent3_200 \ No newline at end of file diff --git a/res/values-night-v34/colors.xml b/res/values-night-v34/colors.xml index abce763e0a..af2811999c 100644 --- a/res/values-night-v34/colors.xml +++ b/res/values-night-v34/colors.xml @@ -27,7 +27,4 @@ @android:color/system_on_surface_dark @android:color/system_on_surface_variant_dark - - @android:color/system_on_surface_variant_dark - diff --git a/res/values-night/colors.xml b/res/values-night/colors.xml index 004f12f49b..95b3a630ca 100644 --- a/res/values-night/colors.xml +++ b/res/values-night/colors.xml @@ -1,66 +1,64 @@ - - + + + #3F4759 + #583E5B #0D0E11 + #2B4678 + #DBE2F9 + #FBD7FC + #1B1B1F + #D8E2FF + #BFC6DC + #FFDAD5 + #141B2C + #1B1B1F + #DEBCDF + #29132D + #ADC6FF + #3F4759 + #930001 + #001A41 + #445E91 + #DBE2F9 + #FAF9FD + #44474F + #583E5B + #FBD7FC + #2B4678 + #E3E2E6 + #D8E2FF + #293041 + #402843 + #121316 + #38393C + #690001 + #121316 + #292A2D + #343538 + #C4C6D0 + #72747D + #444746 + #102F60 #E3E2E6 - - @color/system_on_secondary_fixed_variant - @color/system_on_tertiary_fixed_variant - @color/system_surface_container_lowest_dark - @color/system_on_primary_fixed_variant - @color/system_on_secondary_container_dark - @color/system_on_tertiary_container_dark - @color/system_surface_container_low_dark - @color/system_on_primary_container_dark - @color/system_secondary_fixed_dim - @color/system_on_error_container_dark - @color/system_on_secondary_fixed - @color/system_on_surface_light - @color/system_tertiary_fixed_dim - @color/system_on_tertiary_fixed - @color/system_primary_fixed_dim - @color/system_secondary_container_dark - @color/system_error_container_dark - @color/system_on_primary_fixed - @color/system_primary_light - @color/system_secondary_fixed - @color/system_surface_light - @color/system_surface_variant_dark - @color/system_tertiary_container_dark - @color/system_tertiary_fixed - @color/system_primary_container_dark - @color/system_on_background_dark - @color/system_primary_fixed - @color/system_on_secondary_dark - @color/system_on_tertiary_dark - @color/system_surface_dim_dark - @color/system_surface_bright_dark - @color/system_on_error_dark - @color/system_surface_dark - @color/system_surface_container_high_dark - @color/system_surface_container_highest_dark - @color/system_on_surface_variant_dark - @color/system_outline_dark - @color/system_outline_variant_dark - @color/system_on_primary_dark - @color/system_on_surface_dark - @color/system_surface_container_dark - @color/system_primary_dark - @color/system_secondary_dark - @color/system_tertiary_dark - @color/system_error_dark - \ No newline at end of file + #1F1F23 + #ADC6FF + #BFC6DC + #DEBCDF + diff --git a/res/values-night/styles.xml b/res/values-night/styles.xml index 89b635d4d4..c95722fd58 100644 --- a/res/values-night/styles.xml +++ b/res/values-night/styles.xml @@ -26,19 +26,9 @@ true - - - diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml index f321f784b0..7b3f5639ae 100644 --- a/res/values-nl/strings.xml +++ b/res/values-nl/strings.xml @@ -29,11 +29,8 @@ "Startscherm" "Stel %1$s in als de standaard startscherm-app in Instellingen" "Gesplitst scherm" - "Beeldverhouding wijzigen" "App-info voor %1$s" "Gebruiksinstellingen voor %1$s" - "Nieuw venster" - "Vensters beheren" "App-paar opslaan" "%1$s | %2$s" "Dit app-paar wordt niet ondersteund op dit apparaat" @@ -41,8 +38,6 @@ "App-paar is niet beschikbaar" "Tik en houd vast om een widget te verplaatsen." "Dubbeltik en houd vast om een widget te verplaatsen of aangepaste acties te gebruiken." - "Meer opties" - "Alle widgets tonen" "%1$d × %2$d" "%1$d breed en %2$d hoog" "Widget %1$s" @@ -69,13 +64,8 @@ "Werk" "Gesprekken" "Aantekeningen maken" - "Knop Toevoegen tonen" - "Knop Toevoegen verbergen" "Toevoegen" "Widget %1$s toevoegen" - "Alles tonen" - "Alle widgets tonen" - "Alle widgets worden getoond" "Tik om de widgetinstellingen te wijzigen" "Widgetinstellingen wijzigen" "Apps zoeken" @@ -83,7 +73,6 @@ "Er zijn geen apps gevonden die overeenkomen met \'%1$s\'" "App" "Alle apps" - "Lijst met apps" "Meldingen" "Tik en houd vast om een snelkoppeling te verplaatsen." "Dubbeltik en houd vast om een snelkoppeling te verplaatsen of aangepaste acties te gebruiken." @@ -101,7 +90,6 @@ "Installeren" "Geen app voorstellen" "Voorspelling vastzetten" - "Bubbel" "Snelle links instellen" "Een app toestaan snelkoppelingen toe te voegen zonder tussenkomst van de gebruiker." "instellingen en snelkoppelingen op startscherm lezen" @@ -118,8 +106,6 @@ "Pagina %1$d van %2$d" "Startscherm %1$d van %2$d" "Nieuwe startschermpagina" - "Actief" - "Geminimaliseerd" "Map geopend, %1$d bij %2$d" "Tik om de map te sluiten" "Tik om de gewijzigde naam op te slaan" @@ -127,16 +113,13 @@ "De naam van de map is gewijzigd in %1$s" "Map: %1$s, %2$d items" "Map: %1$s, %2$d of meer items" - "Naamloze map" "App-paar: %1$s en %2$s" "Achtergrond en stijl" "Startscherm bewerken" - "Instellingen Start" + "Instellingen start" "Uitgezet door je beheerder" "Draaien van startscherm toestaan" "Als de telefoon gedraaid is" - "Liggende modus" - "Stel telefoon in op liggende modus" "Meldingsstipjes" "Aan" "Uit" @@ -155,8 +138,7 @@ "%1$s installeren, %2$s voltooid" "%1$s wordt gedownload, %2$s voltooid" "%1$s wacht op installatie" - "%1$s is gearchiveerd." - "downloaden en herstellen" + "%1$s is gearchiveerd. Tik om te downloaden en te herstellen." "App-update vereist" "De app voor dit icoon is niet geüpdatet. Je kunt handmatig updaten om deze snelkoppeling weer aan te zetten of het icoon verwijderen." "Updaten" @@ -165,6 +147,7 @@ "Lijst met widgets gesloten" "Toevoegen aan startscherm" "Item hier naartoe verplaatsen" + "Item toegevoegd aan startscherm" "Item verwijderd" "Ongedaan maken" "Item verplaatsen" @@ -184,15 +167,11 @@ "Breedte verkleinen" "Hoogte verkleinen" "Formaat van widget gewijzigd in breedte %1$s en hoogte %2$s" - "Snelmenu" - "Frame voor widgetformaat aanpassen voor %1$s" - "Sluiten" + "Snelkoppelingen" "Sluiten" "Sluiten" "Privé" "Werk" - "Tabblad Persoonlijke apps" - "Tabblad Werk-apps" "Werkprofiel" "Werk-apps hebben badges en zijn zichtbaar voor je IT-beheerder" "OK" @@ -204,7 +183,6 @@ "OK" "Werk-apps pauzeren" "Hervatten" - "Planning voor werk-apps" "Filteren" "Mislukt: %1$s" "Privéruimte" @@ -217,5 +195,4 @@ "Overschakelen naar privéruimte" "Installeren" "Apps installeren in privégedeelte" - "Bestanden en meer toevoegen aan privégedeelte" diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml index 14de46758f..33b7664cfb 100644 --- a/res/values-or/strings.xml +++ b/res/values-or/strings.xml @@ -29,11 +29,8 @@ "ହୋମ" "ସେଟିଂସରେ %1$sକୁ ଡିଫଲ୍ଟ Home ଆପ ଭାବରେ ସେଟ କରନ୍ତୁ" "ସ୍କ୍ରିନ‌କୁ ସ୍ପ୍ଲିଟ କରନ୍ତୁ" - "ଚଉଡ଼ା ଓ ଉଚ୍ଚତାର ଅନୁପାତ ପରିବର୍ତ୍ତନ କରନ୍ତୁ" "%1$s ପାଇଁ ଆପ ସୂଚନା" "%1$s ପାଇଁ ବ୍ୟବହାର ସେଟିଂସ" - "ନୂଆ ୱିଣ୍ଡୋ" - "ୱିଣ୍ଡୋଗୁଡ଼ିକୁ ପରିଚାଳନା କରନ୍ତୁ" "ଆପ ପେୟାର ସେଭ କରନ୍ତୁ" "%1$s | %2$s" "ଏହି ଆପ ପେୟାର ଏ ଡିଭାଇସରେ ସମର୍ଥିତ ନୁହେଁ" @@ -41,8 +38,6 @@ "ଆପ ପେୟାର ଉପଲବ୍ଧ ନାହିଁ" "ଏକ ୱିଜେଟକୁ ମୁଭ୍ କରିବା ପାଇଁ ସ୍ପର୍ଶ କରି ଧରି ରଖନ୍ତୁ।" "ଏକ ୱିଜେଟକୁ ମୁଭ୍ କରିବା ପାଇଁ ଦୁଇଥର-ଟାପ୍ କରି ଧରି ରଖନ୍ତୁ କିମ୍ବା କଷ୍ଟମ୍ କାର୍ଯ୍ୟଗୁଡ଼ିକୁ ବ୍ୟବହାର କରନ୍ତୁ।" - "ଅଧିକ ବିକଳ୍ପ" - "ସମସ୍ତ ୱିଜେଟ ଦେଖାନ୍ତୁ" "%1$d × %2$d" "%1$d ଓସାର ଓ %2$d ଉଚ୍ଚ" "%1$s ୱିଜେଟ୍" @@ -61,21 +56,16 @@ "{count,plural, =1{#ଟି ସର୍ଟକଟ୍}other{#ଟି ସର୍ଟକଟ୍}}" "%1$s, %2$s" "ୱିଜେଟ୍‌" - "ସର୍ଚ୍ଚ କରନ୍ତୁ" + "ସନ୍ଧାନ କରନ୍ତୁ" "ସନ୍ଧାନ ବାକ୍ସରୁ ଟେକ୍ସଟ୍ ଖାଲି କରନ୍ତୁ" "ୱିଜେଟ୍ ଏବଂ ସର୍ଟକଟଗୁଡ଼ିକ ଉପଲବ୍ଧ ନାହିଁ" - "କୌଣସି ୱିଜେଟ କିମ୍ବା ସର୍ଟକଟ ମିଳିଲା ନାହିଁ" + "କୌଣସି ୱିଜେଟ୍ କିମ୍ବା ସର୍ଟକଟ୍ ମିଳିଲା ନାହିଁ" "ବ୍ୟକ୍ତିଗତ" "ୱାର୍କ" "ବାର୍ତ୍ତାଳାପଗୁଡ଼ିକ" "ନୋଟ-ଟେକିଂ" - "\'ଯୋଗ କରନ୍ତୁ\' ବଟନକୁ ଦେଖାନ୍ତୁ" - "\'ଯୋଗ କରନ୍ତୁ\' ବଟନକୁ ଲୁଚାନ୍ତୁ" "ଯୋଗ କରନ୍ତୁ" "%1$s ୱିଜେଟ ଯୋଗ କରନ୍ତୁ" - "ସବୁ ଦେଖାନ୍ତୁ" - "ସମସ୍ତ ୱିଜେଟ ଦେଖାନ୍ତୁ" - "ସମସ୍ତ ୱିଜେଟ ଦେଖାଉଛି" "ୱିଜେଟ ସେଟିଂସ ପରିବର୍ତ୍ତନ କରିବାକୁ ଟାପ କରନ୍ତୁ" "ୱିଜେଟ ସେଟିଂସ ପରିବର୍ତ୍ତନ କରନ୍ତୁ" "ଆପ ସର୍ଚ୍ଚ କରନ୍ତୁ" @@ -83,7 +73,6 @@ "\"%1$s\" ସହିତ ମେଳ ହେଉଥିବା କୌଣସି ଆପ୍‌ ମିଳିଲା ନାହିଁ" "ଆପ୍" "ସବୁ ଆପ" - "ଆପ୍ସ ତାଲିକା" "ବିଜ୍ଞପ୍ତି" "ଏକ ସର୍ଟକଟକୁ ମୁଭ୍ କରିବା ପାଇଁ ସ୍ପର୍ଶ କରି ଧରି ରଖନ୍ତୁ।" "ଏକ ସର୍ଟକଟକୁ ମୁଭ୍ କରିବା ପାଇଁ ଦୁଇଥର-ଟାପ୍ କରି ଧରି ରଖନ୍ତୁ କିମ୍ବା କଷ୍ଟମ୍ କାର୍ଯ୍ୟଗୁଡ଼ିକୁ ବ୍ୟବହାର କରନ୍ତୁ।" @@ -101,7 +90,6 @@ "ଇନଷ୍ଟଲ୍‌ କରନ୍ତୁ" "ଆପ ପରାମର୍ଶ ଦିଅନ୍ତୁ ନାହିଁ" "ପୂର୍ବାନୁମାନକୁ ପିନ୍ କରନ୍ତୁ" - "ବବଲ" "ସର୍ଟକଟ୍‍ ଇନଷ୍ଟଲ୍‌ କରନ୍ତୁ" "ୟୁଜରଙ୍କ ବିନା ହସ୍ତକ୍ଷେପରେ ଶର୍ଟକଟ୍‌ ଯୋଡ଼ିବାକୁ ଆପକୁ ଅନୁମତି ଦିଏ।" "ହୋମ ସେଟିଂସ ଏବଂ ସର୍ଟକଟଗୁଡ଼ିକୁ ପଢ଼ନ୍ତୁ" @@ -118,8 +106,6 @@ "ମୋଟ %2$dରୁ %1$d ନମ୍ବର ପୃଷ୍ଠା" "%2$dରୁ %1$d ହୋମ ସ୍କ୍ରିନ" "ନୂଆ ହୋମ ସ୍କ୍ରିନ ପେଜ" - "ସକ୍ରିୟ" - "ଛୋଟ କରାଯାଇଛି" "%2$d / %1$dର ଫୋଲ୍ଡର ଖୋଲାଗଲା" "ଫୋଲ୍ଡର୍‌ ବନ୍ଦ କରିବାକୁ ଟାପ୍‌ କରନ୍ତୁ" "ନାମ ବଦଳାଇବା ସେଭ୍ କରିବାକୁ ଟାପ୍‌ କରନ୍ତୁ" @@ -127,7 +113,6 @@ "ଫୋଲ୍ଡରର ନାମ %1$sକୁ ବଦଳାଗଲା" "ଫୋଲ୍ଡର୍: %1$s, %2$d ଆଇଟମଗୁଡ଼ିକ" "ଫୋଲ୍ଡର୍: %1$s, %2$d କିମ୍ବା ଅଧିକ ଆଇଟମ୍" - "ବେନାମୀ ଫୋଲ୍ଡର" "ଆପ ପେୟାର: %1$s ଏବଂ %2$s" "ୱାଲପେପର ଏବଂ ଷ୍ଟାଇଲ" "ହୋମ ସ୍କ୍ରିନକୁ ଏଡିଟ କରନ୍ତୁ" @@ -135,8 +120,6 @@ "ଆପଣଙ୍କ ଆଡମିନଙ୍କ ଦ୍ୱାରା ଅକ୍ଷମ କରାଯାଇଛି" "ହୋମ ସ୍କ୍ରିନ ରୋଟେସନକୁ ଅନୁମତି ଦିଅନ୍ତୁ" "ଯେତେବେଳେ ଫୋନକୁ ରୋଟେଟ କରାଯାଇଥାଏ" - "ଲେଣ୍ଡସ୍କେପ ମୋଡ" - "ଫୋନକୁ ଲେଣ୍ଡସ୍କେପ ମୋଡରେ ସେଟ କରନ୍ତୁ" "ବିଜ୍ଞପ୍ତି ଡଟ୍ସ" "ଚାଲୁ" "ବନ୍ଦ କରନ୍ତୁ" @@ -155,8 +138,7 @@ "%1$s ଇନଷ୍ଟଲ୍ କରାଯାଉଛି, %2$s ସମ୍ପୂର୍ଣ୍ଣ ହୋଇଛି" "%1$s ଡାଉନଲୋଡ୍‌ ହେଉଛି, %2$s ସମ୍ପୂର୍ଣ୍ଣ" "%1$s ଇନଷ୍ଟଲ୍‌ ହେବାକୁ ଅପେକ୍ଷା କରିଛି" - "%1$sକୁ ଆର୍କାଇଭ କରାଯାଇଛି।" - "ଡାଉନଲୋଡ ଏବଂ ରିଷ୍ଟୋର କରନ୍ତୁ" + "%1$sକୁ ଆର୍କାଇଭ କରାଯାଇଛି। ଡାଉନଲୋଡ ଏବଂ ରିଷ୍ଟୋର କରିବା ପାଇଁ ଟାପ କରନ୍ତୁ।" "ଆପକୁ ଅପଡେଟ କରିବା ଆବଶ୍ୟକ" "ଏହି ଆଇକନ ପାଇଁ ଆପକୁ ଅପଡେଟ କରାଯାଇନାହିଁ। ଏହି ସର୍ଟକଟକୁ ପୁଣି-ସକ୍ଷମ କରିବା ପାଇଁ ଆପଣ ମାନୁଆଲୀ ଅପଡେଟ କରିପାରିବେ କିମ୍ବା ଆଇକନଟିକୁ କାଢ଼ି ଦେଇପାରିବେ।" "ଅପଡେଟ କରନ୍ତୁ" @@ -165,8 +147,9 @@ "ୱିଜେଟ୍ ତାଲିକା ବନ୍ଦ ହୋଇଛି" "ହୋମ ସ୍କ୍ରିନରେ ଯୋଗ କରନ୍ତୁ" "ଆଇଟମ୍‌କୁ ଏଠାକୁ ଘୁଞ୍ଚାନ୍ତୁ" + "ହୋମ ସ୍କ୍ରିନରେ ଆଇଟମ ଯୋଗ କରାଗଲା" "ଆଇଟମକୁ କାଢ଼ି ଦିଆଯାଇଛି" - "ଅନଡୁ କରନ୍ତୁ" + "ପୂର୍ବବତ କରନ୍ତୁ" "ଆଇଟମ୍‌ ଘୁଞ୍ଚାନ୍ତୁ" "%3$sରେ ଧାଡି %1$s ସ୍ତମ୍ଭ %2$sକୁ ମୁଭ କରନ୍ତୁ" "%1$s ସ୍ଥିତିକୁ ନିଅନ୍ତୁ" @@ -184,15 +167,11 @@ "ଚଉଡ଼ା କମ୍‌ କରନ୍ତୁ" "ଉଚ୍ଚତା କମ୍‌ କରନ୍ତୁ" "ୱିଜେଟକୁ %1$s ଓସାର ଓ %2$s ଉଚ୍ଚରେ ପୁନଃଆକାର ଦିଆଗଲା" - "ସର୍ଟକଟ ମେନୁ" - "%1$s ପାଇଁ ୱିଜେଟ ରିସାଇଜ ଫ୍ରେମ" - "ବନ୍ଦ କରନ୍ତୁ" + "ଶର୍ଟକଟ୍‍" "ଖାରଜ କରନ୍ତୁ" "ବନ୍ଦ କରନ୍ତୁ" "ବ୍ୟକ୍ତିଗତ" "ୱାର୍କ" - "ବ୍ୟକ୍ତିଗତ ଆପ୍ସ ଟାବ" - "ୱାର୍କ ଆପ୍ସ ଟାବ" "ୱର୍କ ପ୍ରୋଫାଇଲ୍‌" "ୱାର୍କ ଆପ୍ସକୁ ବେଜ କରାଯାଇଛି ଏବଂ ସେଗୁଡ଼ିକ ଆପଣଙ୍କ IT ଆଡମିନଙ୍କୁ ଦେଖାଯାଉଛି" "ବୁଝିଗଲି" @@ -204,7 +183,6 @@ "ବୁଝିଗଲି" "ୱାର୍କ ଆପ୍ସ ବିରତ କରନ୍ତୁ" "ପୁଣି ଚାଲୁ କରନ୍ତୁ" - "ୱାର୍କ ଆପ୍ସ ସିଡୁଲ" "ଫିଲ୍ଟର୍" "ବିଫଳ ହୋଇଛି: %1$s" "ପ୍ରାଇଭେଟ ସ୍ପେସ" @@ -217,5 +195,4 @@ "ପ୍ରାଇଭେଟ ସ୍ପେସ ଟ୍ରାଞ୍ଜିସନିଂ" "ଇନଷ୍ଟଲ କରନ୍ତୁ" "ଆପ୍ସକୁ ପ୍ରାଇଭେଟ ସ୍ପେସରେ ଇନଷ୍ଟଲ କରନ୍ତୁ" - "ପ୍ରାଇଭେଟ ସ୍ପେସରେ ଫାଇଲ ଏବଂ ଅଧିକ ଯୋଗ କରନ୍ତୁ" diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml index db02f3f984..71c25ebb95 100644 --- a/res/values-pa/strings.xml +++ b/res/values-pa/strings.xml @@ -29,11 +29,8 @@ "ਮੁੱਖ ਪੰਨਾ" "ਸੈਟਿੰਗਾਂ ਵਿੱਚ ਜਾ ਕੇ %1$s ਨੂੰ ਪੂਰਵ-ਨਿਰਧਾਰਿਤ ਹੋਮ ਐਪ ਵਜੋਂ ਸੈੱਟ ਕਰੋ" "ਸਪਲਿਟ ਸਕ੍ਰੀਨ" - "ਆਕਾਰ ਅਨੁਪਾਤ ਬਦਲੋ" "%1$s ਲਈ ਐਪ ਜਾਣਕਾਰੀ" "%1$s ਲਈ ਵਰਤੋਂ ਸੈਟਿੰਗਾਂ" - "ਨਵੀਂ ਵਿੰਡੋ" - "ਵਿੰਡੋਆਂ ਦਾ ਪ੍ਰਬੰਧਨ ਕਰੋ" "ਐਪ ਜੋੜਾਬੱਧ ਰੱਖਿਅਤ ਕਰੋ" "%1$s | %2$s" "ਇਸ ਐਪ ਜੋੜਾਬੱਧ ਦਾ ਇਸ ਡੀਵਾਈਸ \'ਤੇ ਸਮਰਥਨ ਨਹੀਂ ਕੀਤਾ ਜਾਂਦਾ" @@ -41,8 +38,6 @@ "ਐਪ ਜੋੜਾਬੱਧ ਉਪਲਬਧ ਨਹੀਂ ਹੈ" "ਕਿਸੇ ਵਿਜੇਟ ਨੂੰ ਲਿਜਾਉਣ ਲਈ ਸਪਰਸ਼ ਕਰਕੇ ਰੱਖੋ।" "ਵਿਜੇਟ ਲਿਜਾਉਣ ਲਈ ਜਾਂ ਵਿਉਂਂਤੀਆਂ ਕਾਰਵਾਈਆਂ ਵਰਤਣ ਲਈ ਦੋ ਵਾਰ ਟੈਪ ਕਰਕੇ ਦਬਾ ਕੇ ਰੱਖੋ।" - "ਹੋਰ ਵਿਕਲਪ" - "ਸਭ ਵਿਜੇਟ ਦਿਖਾਓ" "%1$d × %2$d" "%1$d ਚੌੜਾਈ ਅਤੇ %2$d ਲੰਬਾਈ" "%1$s ਵਿਜੇਟ" @@ -51,7 +46,7 @@ "ਹੋਮ ਸਕ੍ਰੀਨ \'ਤੇ ਸ਼ਾਮਲ ਕਰੋ" "%1$s ਵਿਜੇਟ ਨੂੰ ਹੋਮ ਸਕ੍ਰੀਨ \'ਤੇ ਸ਼ਾਮਲ ਕੀਤਾ ਗਿਆ" "ਸੁਝਾਅ" - "ਲੋੜੀਂਦੀਆਂ ਐਪਾਂ ਦੇ ਵਿਜੇਟ" + "ਲੋੜੀਂਦੀਆਂ" "ਖਬਰਾਂ ਅਤੇ ਰਸਾਲੇ" "ਮਨੋਰੰਜਨ" "ਸੋਸ਼ਲ" @@ -66,16 +61,11 @@ "ਵਿਜੇਟ ਜਾਂ ਸ਼ਾਰਟਕੱਟ ਉਪਲਬਧ ਨਹੀਂ ਹਨ" "ਕੋਈ ਵੀ ਵਿਜੇਟ ਜਾਂ ਸ਼ਾਰਟਕੱਟ ਨਹੀਂ ਮਿਲਿਆ" "ਨਿੱਜੀ" - "ਕੰਮ" + "ਕਾਰਜ-ਸਥਾਨ" "ਗੱਲਾਂਬਾਤਾਂ" "ਨੋਟ ਬਣਾਉਣਾ" - "\'ਸ਼ਾਮਲ ਕਰੋ\' ਬਟਨ ਦਿਖਾਓ" - "\'ਸ਼ਾਮਲ ਕਰੋ\' ਬਟਨ ਲੁਕਾਓ" "ਸ਼ਾਮਲ ਕਰੋ" "%1$s ਵਿਜੇਟ ਸ਼ਾਮਲ ਕਰੋ" - "ਸਭ ਦਿਖਾਓ" - "ਸਭ ਵਿਜੇਟ ਦਿਖਾਓ" - "ਸਭ ਵਿਜੇਟ ਦਿਖਾਏ ਜਾ ਰਹੇ ਹਨ" "ਵਿਜੇਟ ਸੈਟਿੰਗਾਂ ਨੂੰ ਬਦਲਣ ਲਈ ਟੈਪ ਕਰੋ" "ਵਿਜੇਟ ਸੈਟਿੰਗਾਂ ਬਦਲੋ" "ਐਪਾਂ ਖੋਜੋ" @@ -83,7 +73,6 @@ "\"%1$s\" ਨਾਲ ਮੇਲ ਖਾਂਦੀਆਂ ਕੋਈ ਐਪਾਂ ਨਹੀਂ ਮਿਲੀਆਂ" "ਐਪ" "ਸਾਰੀਆਂ ਐਪਾਂ" - "ਐਪਾਂ ਦੀ ਸੂਚੀ" "ਸੂਚਨਾਵਾਂ" "ਕਿਸੇ ਸ਼ਾਰਟਕੱਟ ਨੂੰ ਲਿਜਾਉਣ ਲਈ ਸਪੱਰਸ਼ ਕਰਕੇ ਦਬਾਈ ਰੱਖੋ।" "ਕਿਸੇ ਸ਼ਾਰਟਕੱਟ ਨੂੰ ਲਿਜਾਉਣ ਲਈ ਡਬਲ ਟੈਪ ਕਰਕੇ ਦਬਾਈ ਰੱਖੋ ਜਾਂ ਵਿਉਂਤੀਆਂ ਕਾਰਵਾਈਆਂ ਵਰਤੋ।" @@ -95,13 +84,12 @@ "ਕਾਰਜ-ਸਥਾਨ ਸੰਬੰਧੀ ਐਪਾਂ ਦੀ ਸੂਚੀ" "ਹਟਾਓ" "ਅਣਸਥਾਪਤ ਕਰੋ" - "ਐਪ ਸੰਬੰਧੀ ਜਾਣਕਾਰੀ" + "ਐਪ ਜਾਣਕਾਰੀ" "ਪ੍ਰਾਈਵੇਟ ਵਜੋਂ ਸਥਾਪਤ ਕਰੋ" "ਐਪ ਅਣਸਥਾਪਤ ਕਰੋ" "ਸਥਾਪਤ ਕਰੋ" "ਐਪ ਦਾ ਸੁਝਾਅ ਨਾ ਦਿਓ" "ਪੂਰਵ-ਅਨੁਮਾਨ ਪਿੰਨ ਕਰੋ" - "ਬਬਲ" "ਸ਼ਾਰਟਕੱਟ ਸਥਾਪਤ ਕਰੋ" "ਇੱਕ ਐਪ ਨੂੰ ਵਰਤੋਂਕਾਰ ਦੇ ਦਖ਼ਲ ਤੋਂ ਬਿਨਾਂ ਸ਼ਾਰਟਕੱਟ ਸ਼ਾਮਲ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ।" "ਹੋਮ ਸੈਟਿੰਗਾਂ ਅਤੇ ਸ਼ਾਰਟਕੱਟ ਪੜ੍ਹੋ" @@ -118,8 +106,6 @@ "ਸਫ਼ਾ %2$d ਦਾ %1$d" "ਹੋਮ ਸਕ੍ਰੀਨ %2$d ਦੀ %1$d" "ਨਵਾਂ ਹੋਮ ਸਕ੍ਰੀਨ ਸਫ਼ਾ" - "ਕਿਰਿਆਸ਼ੀਲ" - "ਛੋਟਾ ਕੀਤਾ ਗਿਆ" "ਫੋਲਡਰ ਖੋਲ੍ਹਿਆ, %1$d ਬਾਇ %2$d" "ਫੋਲਡਰ ਬੰਦ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ" "ਬਦਲੇ ਗਏ ਨਾਮ ਨੂੰ ਰੱਖਿਅਤ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ" @@ -127,7 +113,6 @@ "ਫੋਲਡਰ ਨੂੰ %1$s ਮੁੜ ਨਾਮ ਦਿੱਤਾ ਗਿਆ" "ਫੋਲਡਰ: %1$s, %2$d ਆਈਟਮਾਂ" "ਫੋਲਡਰ: %1$s, %2$d ਜਾਂ ਹੋਰ ਆਈਟਮਾਂ" - "ਬੇਨਾਮ ਫੋਲਡਰ" "ਐਪ ਜੋੜਾਬੱਧ: %1$s ਅਤੇ %2$s" "ਵਾਲਪੇਪਰ ਅਤੇ ਸਟਾਈਲ" "ਹੋਮ ਸਕ੍ਰੀਨ ਦਾ ਸੰਪਾਦਨ ਕਰੋ" @@ -135,8 +120,6 @@ "ਤੁਹਾਡੇ ਪ੍ਰਸ਼ਾਸਕ ਦੁਆਰਾ ਅਯੋਗ ਬਣਾਈ ਗਈ" "ਹੋਮ ਸਕ੍ਰੀਨ ਨੂੰ ਘੁਮਾਉਣ ਦੀ ਆਗਿਆ ਦਿਓ" "ਜਦੋਂ ਫ਼ੋਨ ਘੁਮਾਇਆ ਜਾਂਦਾ ਹੈ" - "ਲੈਂਡਸਕੇਪ ਮੋਡ" - "ਫ਼ੋਨ ਨੂੰ ਲੈਂਡਸਕੇਪ ਮੋਡ ਵਿੱਚ ਸੈੱਟ ਕਰੋ" "ਸੂਚਨਾ ਬਿੰਦੂ" "ਚਾਲੂ" "ਬੰਦ" @@ -155,8 +138,7 @@ "%1$s ਨੂੰ ਸਥਾਪਤ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ, %2$s ਪੂਰਾ ਹੋਇਆ" "%1$s ਡਾਉਨਲੋਡ ਹੋਰ ਰਿਹਾ ਹੈ, %2$s ਸੰਪੂਰਣ" "%1$s ਸਥਾਪਤ ਕਰਨ ਦੀ ਉਡੀਕ ਕਰ ਰਿਹਾ ਹੈ" - "%1$s ਪੁਰਾਲੇਖਬੱਧ ਹੈ।" - "ਡਾਊਨਲੋਡ ਕਰ ਕੇ ਮੁੜ-ਬਹਾਲ ਕਰੋ" + "%1$s ਪੁਰਾਲੇਖਬੱਧ ਹੈ। ਡਾਊਨਲੋਡ ਅਤੇ ਮੁੜ-ਬਹਾਲ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ।" "ਐਪ ਨੂੰ ਅੱਪਡੇਟ ਕਰਨ ਦੀ ਲੋੜ ਹੈ" "ਇਸ ਪ੍ਰਤੀਕ ਲਈ ਐਪ ਨੂੰ ਅੱਪਡੇਟ ਨਹੀਂ ਕੀਤਾ ਗਿਆ ਹੈ। ਇਸ ਸ਼ਾਰਟਕੱਟ ਨੂੰ ਮੁੜ-ਚਾਲੂ ਕਰਨ ਜਾਂ ਪ੍ਰਤੀਕ ਨੂੰ ਹਟਾਉਣ ਲਈ ਤੁਸੀਂ ਹੱਥੀਂ ਅੱਪਡੇਟ ਕਰ ਸਕਦੇ ਹੋ।" "ਅੱਪਡੇਟ ਕਰੋ" @@ -165,6 +147,7 @@ "ਵਿਜੇਟਾਂ ਦੀ ਸੂਚੀ ਬੰਦ ਕੀਤੀ ਗਈ" "ਹੋਮ ਸਕ੍ਰੀਨ \'ਤੇ ਸ਼ਾਮਲ ਕਰੋ" "ਆਈਟਮ ਨੂੰ ਇੱਥੇ ਮੂਵ ਕਰੋ" + "ਆਈਟਮ ਨੂੰ ਹੋਮ ਸਕ੍ਰੀਨ ਵਿੱਚ ਜੋੜਿਆ ਗਿਆ" "ਆਈਟਮ ਹਟਾਈ ਗਈ" "ਅਣਕੀਤਾ ਕਰੋ" "ਆਈਟਮ ਨੂੰ ਮੂਵ ਕਰੋ" @@ -184,15 +167,11 @@ "ਚੌੜਾਈ ਘਟਾਓ" "ਉਂਚਾਈ ਘਟਾਓ" "ਵਿਜੈਟ ਨੂੰ ਚੌੜਾਈ %1$s ਉਂਚਾਈ %2$s ਨੂੰ ਮੁੜ ਆਕਾਰ ਦਿੱਤਾ" - "ਸ਼ਾਰਟਕੱਟ ਮੀਨੂ" - "%1$s ਲਈ, ਵਿਜੇਟ ਦਾ ਆਕਾਰ ਬਦਲਣ ਵਾਲਾ ਫ੍ਰੇਮ" - "ਬੰਦ ਕਰੋ" + "ਸ਼ਾਰਟਕੱਟ" "ਖਾਰਜ ਕਰੋ" "ਬੰਦ ਕਰੋ" "ਨਿੱਜੀ" "ਕੰਮ ਸੰਬੰਧੀ" - "ਨਿੱਜੀ ਐਪਾਂ ਟੈਬ" - "ਕੰਮ ਸੰਬੰਧੀ ਐਪਾਂ ਟੈਬ" "ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ" "ਕੰਮ ਸੰਬੰਧੀ ਐਪਾਂ ਨੂੰ ਬੈਜ ਕੀਤਾ ਗਿਆ ਹੈ ਅਤੇ ਇਹ ਤੁਹਾਡੇ ਆਈ.ਟੀ. ਪ੍ਰਸ਼ਾਸਕ ਨੂੰ ਦਿਸਣਗੀਆਂ" "ਸਮਝ ਲਿਆ" @@ -204,12 +183,11 @@ "ਸਮਝ ਲਿਆ" "ਕੰਮ ਸੰਬੰਧੀ ਐਪਾਂ ਰੋਕੋ" "ਰੋਕ ਹਟਾਓ" - "ਕੰਮ ਸੰਬੰਧੀ ਐਪਾਂ ਦੀ ਸਮਾਂ-ਸੂਚੀ" "ਫਿਲਟਰ" "ਇਹ ਕਾਰਵਾਈ ਅਸਫਲ ਹੋਈ: %1$s" "ਪ੍ਰਾਈਵੇਟ ਸਪੇਸ" "ਸੈੱਟਅੱਪ ਕਰਨ ਜਾਂ ਖੋਲ੍ਹਣ ਲਈ ਟੈਪ ਕਰੋ" - "ਪ੍ਰਾਈਵੇਟ" + "ਨਿੱਜੀ" "ਪ੍ਰਾਈਵੇਟ ਸਪੇਸ ਸੰਬੰਧੀ ਸੈਟਿੰਗਾਂ" "ਨਿੱਜੀ, ਅਣਲਾਕ ਕੀਤਾ ਗਿਆ।" "ਨਿੱਜੀ, ਲਾਕ ਕੀਤਾ ਗਿਆ।" @@ -217,5 +195,4 @@ "ਪ੍ਰਾਈਵੇਟ ਸਪੇਸ ਨੂੰ ਤਬਦੀਲ ਕਰਨਾ" "ਸਥਾਪਤ ਕਰੋ" "ਪ੍ਰਾਈਵੇਟ ਸਪੇਸ ਵਿੱਚ ਐਪਾਂ ਸਥਾਪਤ ਕਰੋ" - "ਪ੍ਰਾਈਵੇਟ ਸਪੇਸ ਵਿੱਚ ਫ਼ਾਈਲਾਂ ਅਤੇ ਹੋਰ ਚੀਜ਼ਾਂ ਨੂੰ ਸ਼ਾਮਲ ਕਰੋ" diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml index 707b78f09c..587c40fdc7 100644 --- a/res/values-pl/strings.xml +++ b/res/values-pl/strings.xml @@ -29,11 +29,8 @@ "Ekran główny" "Otwórz Ustawienia i ustaw aplikację %1$s jako domyślną aplikację ekranu głównego" "Podziel ekran" - "Zmień format obrazu" "Informacje o aplikacji: %1$s" "%1$s – ustawienia użycia" - "Nowe okno" - "Zarządzaj oknami" "Zapisz parę aplikacji" "%1$s | %2$s" "Ta para aplikacji nie jest obsługiwana na tym urządzeniu" @@ -41,13 +38,11 @@ "Para aplikacji jest niedostępna" "Naciśnij i przytrzymaj, aby przenieść widżet." "Naciśnij dwukrotnie i przytrzymaj, aby przenieść widżet lub użyć działań niestandardowych." - "Więcej opcji" - "Pokaż wszystkie widżety" "%1$d × %2$d" "Szerokość %1$d, wysokość %2$d" "Widżet %1$s" "Widżet %1$s, %2$d (szerokość), %3$d (wysokość)" - "Aby przesunąć widżet na ekranie głównym, kliknij go i przytrzymaj" + "Aby poruszać widżetem po ekranie głównym, kliknij go i przytrzymaj" "Dodaj do ekranu głównego" "Widżet %1$s został dodany do ekranu głównego" "Sugestie" @@ -69,13 +64,8 @@ "Służbowe" "Rozmowy" "Notatki" - "Pokaż przycisk Dodaj" - "Ukryj przycisk Dodaj" "Dodaj" "Dodaj widżet %1$s" - "Pokaż wszystko" - "Pokaż wszystkie widżety" - "Wyświetlam wszystkie widżety" "Kliknij, aby zmienić ustawienia widżetu" "Zmień ustawienia widżetu" "Wyszukaj aplikacje" @@ -83,7 +73,6 @@ "Nie znaleziono aplikacji pasujących do zapytania „%1$s”" "Aplikacja" "Wszystkie aplikacje" - "Lista aplikacji" "Powiadomienia" "Naciśnij i przytrzymaj, aby przenieść skrót." "Naciśnij dwukrotnie i przytrzymaj, aby przenieść skrót lub użyć działań niestandardowych." @@ -101,7 +90,6 @@ "Zainstaluj" "Nie proponuj aplikacji" "Przypnij podpowiedź" - "Dymek" "Instalowanie skrótów" "Pozwala aplikacji dodawać skróty bez interwencji użytkownika." "Odczytuje ustawienia i skróty na ekranie głównym" @@ -118,8 +106,6 @@ "Strona %1$d z %2$d" "Ekran główny %1$d z %2$d" "Nowa strona ekranu głównego" - "Aktywna" - "Zminimalizowana" "Folder otwarty, %1$d na %2$d" "Kliknij, by zamknąć folder" "Kliknij, by zapisać nową nazwę" @@ -127,7 +113,6 @@ "Nazwa folderu zmieniona na %1$s" "Folder: %1$s, %2$d elementy" "Folder: %1$s, liczba elementów: %2$d lub więcej" - "Folder bez nazwy" "Para aplikacji: %1$s oraz %2$s" "Tapeta i styl" "Edytuj ekran główny" @@ -135,8 +120,6 @@ "Funkcja wyłączona przez administratora" "Zezwalaj na obrót ekranu głównego" "Po obróceniu telefonu" - "Tryb poziomy" - "Przestaw telefon w tryb poziomy" "Kropki powiadomień" "Włączone" "Wyłączone" @@ -155,8 +138,7 @@ "Instaluję aplikację %1$s, postęp: %2$s" "Pobieranie elementu %1$s, ukończono: %2$s" "%1$s oczekuje na instalację" - "Aplikacja %1$s jest zarchiwizowana." - "pobierz i przywróć" + "Aplikacja %1$s jest zarchiwizowana. Kliknij, aby ją pobrać i przywrócić." "Wymagana aktualizacja aplikacji" "Aplikacja z tą ikoną nie jest aktualizowana. Możesz zaktualizować ją ręcznie, aby ponownie uruchomić ten skrót, lub usunąć ikonę." "Aktualizuj" @@ -165,6 +147,7 @@ "Lista widgetów zamknięta" "Dodaj do ekranu głównego" "Przenieś element tutaj" + "Element został dodany do ekranu głównego" "Element został usunięty" "Cofnij" "Przenieś element" @@ -184,15 +167,11 @@ "Zmniejsz szerokość" "Zmniejsz wysokość" "Szerokość i wysokość widżetu zmieniła się na %1$s x %2$s" - "Menu skrótów" - "Ramka zmiany rozmiaru widżetu dla: %1$s" - "Zamknij" + "Skróty" "Zamknij" "Zamknij" "Osobiste" "Służbowe" - "Karta aplikacji osobistych" - "Karta aplikacji służbowych" "Profil służbowy" "Aplikacje służbowe mają plakietki i są widoczne dla administratora IT" "OK" @@ -204,7 +183,6 @@ "OK" "Wstrzymaj aplikacje służbowe" "Cofnij wstrzymywanie" - "Harmonogram aplikacji służbowych" "Filtruj" "Niepowodzenie: %1$s" "Przestrzeń prywatna" @@ -217,5 +195,4 @@ "Przenoszenie obszaru prywatnego" "Zainstaluj" "Zainstaluj aplikacje w przestrzeni prywatnej" - "Dodawanie plików i innych elementów do przestrzeni prywatnej" diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml index da3fef09aa..67d93deebc 100644 --- a/res/values-pt-rPT/strings.xml +++ b/res/values-pt-rPT/strings.xml @@ -29,11 +29,8 @@ "Página inicial" "Defina a app %1$s como a app inicial predefinida nas Definições" "Ecrã dividido" - "Alterar formato" "Informações da app para %1$s" "Definições de utilização para %1$s" - "Nova janela" - "Gerir janelas" "Guardar par de apps" "%1$s | %2$s" "Este par de apps não é suportado neste dispositivo" @@ -41,8 +38,6 @@ "O par de apps não está disponível" "Toque sem soltar para mover um widget." "Toque duas vezes sem soltar para mover um widget ou utilizar ações personalizadas." - "Mais opções" - "Mostrar todos os widgets" "%1$d × %2$d" "%1$d de largura por %2$d de altura" "Widget %1$s" @@ -65,17 +60,12 @@ "Limpe o texto da caixa de pesquisa" "Os widgets e os atalhos não estão disponíveis" "Nenhum widget ou atalho encontrado" - "Pessoal" + "Pessoais" "Trabalho" "Conversas" "Tomar notas" - "Mostrar botão para adicionar" - "Ocultar botão para adicionar" "Adicionar" "Adicione o widget %1$s" - "Mostrar tudo" - "Mostrar todos os widgets" - "A mostrar todos os widgets" "Toque para alterar as definições do widget" "Alterar definições do widget" "Pesquisar apps" @@ -83,7 +73,6 @@ "Nenhuma app correspondente a \"%1$s\"" "Aplicação" "Todas as apps" - "Lista de apps" "Notificações" "Toque sem soltar para mover um atalho." "Toque duas vezes sem soltar para mover um atalho ou utilizar ações personalizadas." @@ -101,7 +90,6 @@ "Instalar" "Não sugerir app" "Fixar previsão" - "Balão" "instalar atalhos" "Permite a uma app adicionar atalhos sem a intervenção do utilizador." "ler definições e atalhos do ecrã Principal" @@ -118,8 +106,6 @@ "Página %1$d de %2$d" "Ecrã principal %1$d de %2$d" "Nova página do ecrã principal" - "Ativa" - "Minimizada" "Pasta aberta, %1$d por %2$d" "Tocar para fechar a pasta" "Tocar para guardar o nome novo" @@ -127,7 +113,6 @@ "Nome de pasta alterado para %1$s" "Pasta: %1$s, %2$d itens" "Pasta: %1$s, %2$d ou mais itens" - "Pasta sem nome" "Par de apps: %1$s e %2$s" "Imagem fundo/estilo" "Editar ecrã principal" @@ -135,8 +120,6 @@ "Desativada pelo gestor" "Permitir rotação do ecrã principal" "Quando o telemóvel é rodado" - "Modo horizontal" - "Defina o telemóvel para o modo horizontal" "Pontos de notificação" "Ativados" "Desativados" @@ -155,8 +138,7 @@ "A instalar %1$s, %2$s concluído" "A transferir o %1$s, %2$s concluído" "A aguardar a instalação do %1$s" - "A app %1$s está arquivada." - "transferir e restaurar" + "A app %1$s está arquivada. Toque para transferir e restaurar." "Atualização da app necessária" "A app deste ícone não está atualizada. Pode atualizar manualmente para reativar este atalho ou remover o ícone." "Atualizar" @@ -165,6 +147,7 @@ "Lista de widgets fechada." "Adicionar ao ecrã principal" "Mover o item para aqui" + "Item adicionado ao ecrã principal" "Item removido" "Anular" "Mover item" @@ -184,15 +167,11 @@ "Diminuir largura" "Diminuir altura" "Widget redimensionado para a largura %1$s, altura %2$s" - "Menu de atalho" - "Frame de redimensionamento do widget para %1$s" - "Fechar" + "Atalhos" "Ignorar" "Fechar" "Pessoal" "Trabalho" - "Separador de apps pessoais" - "Separador de apps de trabalho" "Perfil de trabalho" "As apps de trabalho têm um emblema e estão visíveis para o seu administrador de TI" "OK" @@ -204,7 +183,6 @@ "OK" "Pausar apps de trabalho" "Retomar" - "Horário das apps de trabalho" "Filtrar" "Falhou: %1$s" "Espaço privado" @@ -217,5 +195,4 @@ "Transição do espaço privado" "Instalar" "Instale apps no espaço privado" - "Adicione ficheiros e muito mais ao espaço privado" diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml index 27cbe0fbf7..4302588495 100644 --- a/res/values-pt/strings.xml +++ b/res/values-pt/strings.xml @@ -29,11 +29,8 @@ "Início" "Definir %1$s como app de início padrão nas Configurações" "Tela dividida" - "Mudar a proporção" "Informações do app %1$s" "Configurações de uso de %1$s" - "Nova janela" - "Gerenciar janelas" "Salvar par de apps" "%1$s | %2$s" "Este Par de apps não está disponível no dispositivo" @@ -41,13 +38,11 @@ "O Par de apps não está disponível" "Toque e pressione para mover um widget." "Toque duas vezes e mantenha a tela pressionada para mover um widget ou usar ações personalizadas." - "Mais opções" - "Mostrar todos os widgets" "%1$d × %2$d" "%1$d de largura por %2$d de altura" "Widget %1$s" "Widget %1$s: %2$d de largura por %3$d de altura" - "Toque no widget e o pressione para definir a posição dele na tela inicial" + "Toque no widget e o pressione para definir a posição dele na tela inicial" "Adicionar à tela inicial" "Widget %1$s adicionado à tela inicial" "Sugestões" @@ -69,13 +64,8 @@ "Trabalho" "Conversas" "Anotações" - "Mostrar botão de adição" - "Ocultar botão de adição" "Adicionar" "Adicionar o widget %1$s" - "Mostrar tudo" - "Mostrar todos os widgets" - "Mostrando todos os widgets" "Toque para mudar as configurações do widget" "Mudar as configurações do widget" "Pesquisar apps" @@ -83,7 +73,6 @@ "Nenhum app encontrado que corresponda a \"%1$s\"" "App" "Todos os apps" - "Lista de apps" "Notificações" "Toque e mantenha a tela pressionada para mover um atalho." "Toque duas vezes e mantenha a tela pressionada para mover um atalho ou usar ações personalizadas." @@ -101,7 +90,6 @@ "Instalar" "Não sugerir esse app" "Fixar previsão" - "Balão" "instalar atalhos" "Permite que um app adicione atalhos sem intervenção do usuário." "ler configurações e atalhos da tela inicial" @@ -118,8 +106,6 @@ "Página %1$d de %2$d" "Tela inicial %1$d de %2$d" "Nova página na tela inicial" - "Ativo" - "Minimizado" "Pasta aberta, %1$d por %2$d" "Toque para fechar a pasta" "Toque para salvar o novo nome" @@ -127,7 +113,6 @@ "Pasta renomeada para %1$s" "Pasta: %1$s, %2$d itens" "Pasta: %1$s, %2$d ou mais itens" - "Pasta sem nome" "Par de apps: %1$s e %2$s" "Plano de fundo e estilo" "Editar tela inicial" @@ -135,8 +120,6 @@ "Desativado pelo administrador" "Permitir a rotação da tela inicial" "Quando o smartphone for girado" - "Modo paisagem" - "Definir o smartphone para o modo paisagem" "Pontos de notificação" "Ativados" "Desativado" @@ -155,8 +138,7 @@ "Instalando %1$s. %2$s concluído" "Fazendo download de %1$s, %2$s concluído" "Aguardando instalação de %1$s" - "O app %1$s está arquivado." - "baixar e restaurar" + "O app %1$s está arquivado. Toque para baixar e restaurar." "Atualização obrigatória do app" "O app desse ícone não está atualizado. Você pode remover o ícone ou atualizar o app manualmente para reativar esse atalho." "Atualizar" @@ -165,6 +147,7 @@ "Lista de widgets fechada" "Adicionar à tela inicial" "Mover item para cá" + "Item adicionado à tela inicial" "Item removido" "Desfazer" "Mover item" @@ -184,15 +167,11 @@ "Diminuir largura" "Diminuir altura" "Widget redimensionado para a largura %1$s, altura %2$s" - "Menu de atalhos" - "Frame de redimensionamento do widget para %1$s" - "Fechar" + "Atalhos" "Dispensar" "Fechar" "Pessoais" "Trabalho" - "Guia de apps pessoais" - "Guia de apps de trabalho" "Perfil de trabalho" "Os apps de trabalho são identificados e ficam visíveis para o adm. de TI" "Ok" @@ -204,12 +183,11 @@ "Ok" "Pausar apps de trabalho" "Ativar" - "Programação de apps de trabalho" "Filtrar" "Falha: %1$s" "Espaço privado" "Toque para configurar ou abrir" - "Privado" + "Particular" "Configurações do Espaço particular" "Privada, desbloqueado." "Privada, bloqueado." @@ -217,5 +195,4 @@ "Espaço particular em transição" "Instalar" "Instalar apps no espaço privado" - "Adicione arquivos e muito mais ao espaço privado" diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml index 84461be935..d9e0413491 100644 --- a/res/values-ro/strings.xml +++ b/res/values-ro/strings.xml @@ -29,11 +29,8 @@ "Pagina de pornire" "Setează %1$s drept aplicație ecran de pornire prestabilită, în Setări" "Ecran împărțit" - "Schimbă raportul de dimensiuni" "Informații despre aplicație pentru %1$s" "Setări de utilizare pentru %1$s" - "Fereastră nouă" - "Gestionează ferestrele" "Salvează perechea de aplicații" "%1$s | %2$s" "Perechea de aplicații nu este acceptată pe acest dispozitiv" @@ -41,8 +38,6 @@ "Perechea de aplicații nu este disponibilă" "Atinge și ține apăsat pentru a muta un widget." "Atinge de două ori și ține apăsat pentru a muta un widget sau folosește acțiuni personalizate." - "Mai multe opțiuni" - "Afișează toate widgeturile" "%1$d × %2$d" "%1$d lățime și %2$d înălțime" "Widgetul %1$s" @@ -69,13 +64,8 @@ "Serviciu" "Conversații" "Luare de notițe" - "Afișează butonul de adăugare" - "Ascunde butonul de adăugare" "Adaugă" "Adaugă widgetul %1$s" - "Afișează tot" - "Afișează toate widgeturile" - "Se afișează toate widgeturile" "Atinge ca să schimbi setările pentru widgeturi" "Modifică setările pentru widgeturi" "Caută aplicații" @@ -83,7 +73,6 @@ "Nu s-a găsit nicio aplicație pentru „%1$s\"" "Aplicație" "Toate aplicațiile" - "Listă de aplicații" "Notificări" "Atinge și ține apăsat ca să muți comanda rapidă." "Atinge de două ori și ține apăsat pentru a muta o comandă rapidă sau folosește acțiuni personalizate." @@ -101,7 +90,6 @@ "Instalează" "Nu sugera aplicația" "Fixează predicția" - "Balon" "instalează comenzi rapide" "Permite unei aplicații să adauge comenzi rapide fără intervenția utilizatorului." "citește setările și comenzile rapide de pe ecranul de pornire" @@ -118,8 +106,6 @@ "Pagina %1$d din %2$d" "Ecranul de pornire %1$d din %2$d" "Pagină nouă pe ecranul de pornire" - "Activă" - "Minimizată" "Dosar deschis, %1$d pe %2$d" "Atinge pentru a închide dosarul" "Atinge pentru a salva noul nume" @@ -127,7 +113,6 @@ "Dosar redenumit %1$s" "Dosar: %1$s, %2$d elemente" "Dosar: %1$s, %2$d sau mai multe elemente" - "Dosar fără nume" "Pereche de aplicații: %1$s și %2$s" "Imagine de fundal și stil" "Editează ecranul de pornire" @@ -135,8 +120,6 @@ "Dezactivată de administrator" "Permite rotirea ecranului de pornire" "Când telefonul este rotit" - "Modul Peisaj" - "Setează telefonul în modul Peisaj" "Puncte de notificare" "Activate" "Dezactivate" @@ -155,8 +138,7 @@ "%1$s se instalează, %2$s finalizat" "%1$s se descarcă (finalizat %2$s)" "%1$s așteaptă instalarea" - "%1$s s-a arhivat." - "descarcă și restabilește" + "%1$s s-a arhivat. Atinge pentru a descărca și restabili." "Este necesară actualizarea aplicației" "Aplicația pentru această pictogramă nu este actualizată. Poți să actualizezi manual ca să reactivezi comanda rapidă sau să elimini pictograma." "Actualizează" @@ -165,6 +147,7 @@ "Lista de widgeturi este închisă" "Adaugă pe ecranul de pornire" "Mută elementul aici" + "Element adăugat pe ecranul de pornire" "Element eliminat" "Anulează" "Mută elementul" @@ -184,15 +167,11 @@ "Redu lățimea" "Redu înălțimea" "Widgetul a fost redimensionat la lățimea %1$s și înălțimea %2$s" - "Meniu de comenzi rapide" - "Cadru de redimensionare a widgetului pentru %1$s" - "Închide" + "Comenzi rapide" "Închide" "Închide" "Personale" "Profesionale" - "Fila Aplicații personale" - "Fila Aplicații pentru lucru" "Profil de serviciu" "Aplicațiile pentru lucru sunt marcate și vizibile pentru administratorul IT" "OK" @@ -204,7 +183,6 @@ "OK" "Întrerupe aplicațiile pentru lucru" "Anulează întreruperea" - "Programul aplicațiilor pentru lucru" "Filtru" "Eșuare: %1$s" "Spațiu privat" @@ -217,5 +195,4 @@ "Tranziție pentru spațiul privat" "Instalează" "Instalează aplicații în Spațiul privat" - "Adaugă fișiere și altele în Spațiul privat" diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml index 2591a7c0a6..64a21f7278 100644 --- a/res/values-ru/strings.xml +++ b/res/values-ru/strings.xml @@ -29,11 +29,8 @@ "Главный экран" "Установить \"%1$s\" в качестве приложения главного экрана по умолчанию в настройках" "Разделить экран" - "Изменить соотношение сторон" "Сведения о приложении \"%1$s\"" "Настройки использования приложения \"%1$s\"" - "Новое окно" - "Управление окнами" "Сохранить приложения" "%1$s | %2$s" "Одновременно использовать эти два приложения на устройстве нельзя." @@ -41,8 +38,6 @@ "Одновременное использование двух приложений недоступно" "Чтобы переместить виджет, нажмите на него и удерживайте" "Чтобы использовать специальные действия или перенести виджет, нажмите на него дважды и удерживайте." - "Другие параметры" - "Показать все виджеты" "%1$d x %2$d" "Ширина %1$d, высота %2$d" "Виджет \"%1$s\"" @@ -50,7 +45,7 @@ "Нажмите на виджет и удерживайте его, чтобы переместить в нужное место на главном экране." "Добавить на главный экран" "Виджет \"%1$s\" добавлен на главный экран" - "Рекомендации" + "Подсказки" "Основное" "Новости и журналы" "Развлечения" @@ -69,13 +64,8 @@ "Рабочие виджеты" "Разговоры" "Создание заметок" - "Показать кнопку добавления виджета" - "Скрыть кнопку добавления виджета" "Добавить" "Добавить виджет \"%1$s\"" - "Показать все" - "Показать все виджеты" - "Показаны все виджеты" "Нажмите, чтобы изменить настройки виджета" "Изменить настройки виджета" "Поиск приложений" @@ -83,7 +73,6 @@ "По запросу \"%1$s\" ничего не найдено" "Приложение" "Все приложения" - "Список приложений" "Уведомления" "Нажмите и удерживайте для переноса ярлыка." "Чтобы использовать специальные действия или перенести ярлык, нажмите на него дважды и удерживайте." @@ -101,7 +90,6 @@ "Установить" "Не рекомендовать" "Закрепить рекомендацию" - "Подсказка" "Создание ярлыков" "Приложение сможет самостоятельно добавлять ярлыки." "Доступ к данным о настройках и ярлыках на главном экране" @@ -118,8 +106,6 @@ "Стр. %1$d из %2$d" "Главный экран %1$d из %2$d" "Новый экран" - "Активно" - "Свернуто" "Папка открыта, %1$d x %2$d" "Нажмите, чтобы закрыть папку" "Нажмите, чтобы подтвердить переименование" @@ -127,7 +113,6 @@ "Папка переименована в \"%1$s\"" "Папка \"%1$s\" (объектов: %2$d)" "Папка \"%1$s\" (объектов: %2$d или больше)" - "Папка без названия" "Одновременное использование двух приложений: %1$s и %2$s" "Обои и стиль" "Изменить главный экран" @@ -135,8 +120,6 @@ "Функция отключена администратором" "Разрешить поворачивать главный экран" "При повороте телефона" - "Горизонтальный режим" - "Перевести телефон в горизонтальный режим" "Значки уведомлений" "Включены" "Отключены" @@ -155,8 +138,7 @@ "Установка приложения \"%1$s\" (выполнено %2$s)" "Скачивается \"%1$s\" (%2$s)" "Ожидание установки \"%1$s\"" - "Приложение \"%1$s\" находится в архиве." - "скачать и восстановить" + "Приложение \"%1$s\" находится в архиве. Нажмите, чтобы скачать его и восстановить" "Обновите приложение" "Эта версия приложения устарела. Обновите его вручную, чтобы снова пользоваться ярлыком, или удалите значок." "Обновить" @@ -165,6 +147,7 @@ "Список виджетов закрыт" "Добавить на главный экран" "Переместить элемент сюда" + "Элемент добавлен на главный экран" "Объект убран." "Отменить" "Переместить элемент" @@ -184,15 +167,11 @@ "Уменьшить ширину" "Уменьшить высоту" "Изменен размер виджета: до %1$s в ширину и %2$s в высоту" - "Быстрое меню" - "Рамка изменения размеров виджета \"%1$s\"" - "Закрыть" + "Ярлыки" "Закрыть" "Закрыть" "Личные" "Рабочие" - "Вкладка \"Личные приложения\"" - "Вкладка \"Рабочие приложения\"" "Рабочий профиль" "У рабочих приложений есть специальный значок. Они видны системному администратору." "ОК" @@ -204,7 +183,6 @@ "ОК" "Приостановить рабочие приложения" "Возобновить" - "Расписание рабочих приложений" "Фильтр" "Не удалось выполнить действие (%1$s)." "Частное пространство" @@ -217,5 +195,4 @@ "Переход к личному пространству" "Установить" "Установить приложения в личном пространстве" - "Добавляйте в частное пространство файлы и многое другое" diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml index 7d2a9f8d34..71efc03311 100644 --- a/res/values-si/strings.xml +++ b/res/values-si/strings.xml @@ -29,11 +29,8 @@ "මුල් පිටුව" "%1$s සැකසීම් තුළ පෙරනිමි මුල් පිටුව යෙදුම ලෙස සකසන්න" "බෙදුම් තිරය" - "දර්ශන අනුපාතය වෙනස් කරන්න" "%1$s සඳහා යෙදුම් තතු" "%1$s සඳහා භාවිත සැකසීම්" - "නව කවුළුව" - "කවුළු කළමනාකරණය කරන්න" "යෙදුම් යුගල සුරකින්න" "%1$s | %2$s" "මෙම යෙදුම් යුගලය මෙම උපාංගයෙහි සහාය නොදක්වයි" @@ -41,8 +38,6 @@ "යෙදුම් යුගලයක් නොමැත" "විජට් එකක් ගෙන යාමට ස්පර්ශ කර අල්ලා ගෙන සිටින්න." "විජට් එකක් ගෙන යාමට හෝ අභිරුචි ක්‍රියා භාවිත කිරීමට දෙවරක් තට්ටු කර අල්ලා ගෙන සිටින්න." - "තවත් විකල්ප" - "සියලු ම විජට් පෙන්වන්න" "%1$d × %2$d" "පළල %1$d උස %2$d" "%1$s විජට්ටුව" @@ -69,13 +64,8 @@ "කාර්යාලය" "සංවාද" "සටහන් කර ගැනීම" - "එක් කිරීමේ බොත්තම පෙන්වන්න" - "එක් කිරීමේ බොත්තම සඟවන්න" "එක් කරන්න" "%1$s විජට්ටුව එක් කරන්න" - "සියල්ල පෙන්වන්න" - "සියලු ම විජට් පෙන්වන්න" - "සියලුම විජට් පෙන්වමින්" "විජට් සැකසීම් වෙනස් කිරීමට තට්ටු කරන්න" "විජට් සැකසීම් වෙනස් කරන්න" "යෙදුම් සොයන්න" @@ -83,7 +73,6 @@ "\"%1$s\" සමග ගැළපෙන යෙදුම් හමු නොවිණි" "යෙදුම" "සියලු යෙදුම්" - "යෙදුම් ලැයිස්තුව" "දැනුම්දීම්" "කෙටි මගක් ගෙන යාමට ස්පර්ශ කර අල්ලාගෙන සිටින්න." "කෙටි මගක් ගෙන යාමට හෝ අභිරුචි ක්‍රියා භාවිත කිරීමට දෙවරක් තට්ටු කර අල්ලා ගෙන සිටින්න." @@ -101,7 +90,6 @@ "ස්ථාපනය කරන්න" "යෙදුම යෝජනා නොකරන්න" "පුරෝකථනය අමුණන්න" - "බුබුළ" "කෙටිමං ස්ථාපනය කරන්න" "පරිශීලක මැදිහත්වීමෙන් තොරව කෙටිමං එක් කිරීමට යෙදුමකට අවසර දෙයි." "මුල් පිටු සැකසීම් සහ කෙටි මං කියවන්න" @@ -118,8 +106,6 @@ "%2$d හි %1$d පිටුව" "මුල් පිටු තිරය %2$d හි %1$d" "නව මුල් පිටුව" - "සක්‍රිය" - "කුඩා කරන ලදි" "ෆෝල්ඩරය විවෘත විය, %1$d හි %2$d" "ෆෝල්ඩරය වැසීමට තට්ටු කරන්න" "යළි නම් කිරීම සුරැකීමට තට්ටු කරන්න" @@ -127,7 +113,6 @@ "%1$s වෙත ෆෝල්ඩරය නැවත නම් කෙරිණි" "ෆෝල්ඩරය: %1$s, අයිතම %2$d" "ෆෝල්ඩර: %1$s, අයිතම %2$dක් හෝ වැඩි ගණනක්" - "නම් නොකළ ෆෝල්ඩරය" "යෙදුම් යුගල: %1$s සහ %2$s" "වෝල්පේපරය සහ මෝස්තරය" "මුල් තිරය සංස්කරණය කරන්න" @@ -135,8 +120,6 @@ "ඔබගේ පරිපාලක විසින් අබල කරන ලදී" "මුල් තිරය කරකැවීමට ඉඩ දෙන්න" "දුරකථනය කරකවන විට" - "භූ දර්ශන ආකාරය" - "දුරකථනය භූ දර්ශන ආකාරයට සකසන්න" "දැනුම්දීම් තිත්" "ක්‍රියාත්මකයි" "ක්‍රියාවිරහිතයි" @@ -155,8 +138,7 @@ "%1$s ස්ථාපනය කරමින්, %2$s සම්පූර්ණයි" "%1$s බාගත කරමින්, %2$s සම්පූර්ණයි" "%1$s ස්ථාපනය කිරීමට බලා සිටිමින්" - "%1$s ලේඛනාරක්ෂණය කර ඇත." - "බාගත කර ප්‍රතිසාධනය කරන්න" + "%1$s සංරක්‍ෂිතයි. බා ගෙන ප්‍රතිසාධන කිරීමට තට්ටු කරන්න." "යෙදුම් යාවත්කාලීනයක් අවශ්‍යයි" "මෙම නිරූපකය සඳහා යෙදුම යාවත්කාලීන කර නැත. ඔබට මෙම කෙටි මඟ යළි සබල කිරීමට හෝ නිරූපකය ඉවත් කිරීමට හස්තීයව යාවත්කාලීන කළ හැකිය." "යාවත්කාලීන කරන්න" @@ -165,6 +147,7 @@ "විජට් ලැයිස්තුව වසා ඇත" "මුල් තිරය වෙත එක් කරන්න" "මෙතනට අයිතමය ගෙන එන්න" + "අයිතමය මුල් තිරය වෙත එකතු කරන ලදි" "අයිතමය ඉවත් කරන ලදි" "අස් කරන්න" "අයිතමය ගෙනයන්න" @@ -184,15 +167,11 @@ "පළල අඩු කරන්න" "උස අඩු කරන්න" "විජට් පළල %1$s උස %2$s වෙත ප්‍රමාණකරණය කරන ලදි" - "කෙටිමං මෙනුව" - "%1$s සඳහා විජට් ප්‍රමාණය වෙනස් කිරීමේ රාමුව" - "වසන්න" + "කෙටිමං" "ඉවතලන්න" "වසන්න" "පුද්ගලික" "කාර්යාලය" - "පුද්ගලික යෙදුම් පටිත්ත" - "කාර්ය යෙදුම් ටැබය" "කාර්යාල පැතිකඩ" "කාර්යාල යෙදුම්වලට ලාංඡන යොදා ඇති අතර ඔබගේ IT පරිපාලකට දෘශ්‍යමාන වේ" "තේරුණා" @@ -204,7 +183,6 @@ "තේරුණා" "කාර්යාල යෙදුම් විරාම කරන්න" "විරාම නොකරන්න" - "කාර්යාල යෙදුම් කාල සටහන" "පෙරහන" "අසාර්ථකයි: %1$s" "පෞද්ගලික ඉඩ" @@ -217,5 +195,4 @@ "පෞද්ගලික අවකාශ සංක්‍රමණය" "ස්ථාපන කරන්න" "පෞද්ගලික අවකාශයට යෙදුම් ස්ථාපනය කරන්න" - "රහසිගත අවකාශයට ගොනු සහ තවත් දේ එක් කරන්න" diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml index 57272773c6..43bc29c326 100644 --- a/res/values-sk/strings.xml +++ b/res/values-sk/strings.xml @@ -29,11 +29,8 @@ "Domov" "Nastaviť aplikáciu %1$s ako predvolenú vstupnú aplikáciu v Nastaveniach" "Rozdeliť obrazovku" - "Zmeniť pomer strán" "Informácie o aplikácii pre %1$s" "Nastavenia používania pre %1$s" - "Nové okno" - "Spravovať okná" "Uložiť pár aplikácií" "%1$s | %2$s" "Tento pár aplikácií nie je v tomto zariadení podporovaný" @@ -41,8 +38,6 @@ "Pár aplikácií nie je k dispozícii" "Pridržaním presuňte miniaplikáciu." "Dvojitým klepnutím a pridržaním presuňte miniaplikáciu alebo použite vlastné akcie." - "Ďalšie možnosti" - "Zobrazovať všetky miniaplikácie" "%1$d × %2$d" "šírka %1$d, výška %2$d" "Miniaplikácia %1$s" @@ -61,21 +56,16 @@ "{count,plural, =1{# odkaz}few{# odkazy}many{# shortcuts}other{# odkazov}}" "%1$s, %2$s" "Miniaplikácie" - "Vyhľadávanie" + "Vyhľadajte" "Vymazať text z vyhľadávacieho poľa" "Miniaplikácie a odkazy nie sú k dispozícii" "Nenašli sa žiadne miniaplikácie ani odkazy" "Osobné" - "Pracovné" + "Práca" "Konverzácie" "Zapisovanie poznámok" - "Zobraziť tlačidlo Pridať" - "Skryť tlačidlo Pridať" "Pridať" "Pridať miniaplikáciu %1$s" - "Zobraziť všetko" - "Zobraziť všetky miniaplikácie" - "Zobrazujú sa všetky miniaplikácie" "Klepnutím zmeňte nastavenia miniaplikácie" "Zmena nastavení miniaplikácie" "Hľadať aplikácie" @@ -83,7 +73,6 @@ "Nenašli sa žiadne aplikácie zodpovedajúce dopytu %1$s" "Aplikácia" "Všetky aplikácie" - "Zoznam aplikácií" "Upozornenia" "Pridržaním presuňte skratku." "Dvojitým klepnutím a pridržaním presuňte odkaz alebo použite vlastné akcie." @@ -101,7 +90,6 @@ "Inštalovať" "Nenavrhovať aplikáciu" "Pripnúť predpoveď" - "Bublina" "inštalácia odkazov" "Povoľuje aplikácii pridať odkazy bez zásahu používateľa." "čítanie nastavení a odkazov plochy" @@ -118,8 +106,6 @@ "Stránka %1$d z %2$d" "Plocha %1$d z %2$d" "Nová stránka plochy" - "Aktívne" - "Minimalizované" "Otvorený priečinok, %1$d x %2$d" "Priečinok zavriete klepnutím" "Nový názov uložíte klepnutím" @@ -127,7 +113,6 @@ "Priečinok bol premenovaný na %1$s" "Priečinok: %1$s, %2$d položky" "Priečinok: %1$s, %2$d alebo viac položiek" - "Nepomenovaný priečinok" "Pár aplikácií: %1$s%2$s" "Tapeta a štýl" "Upraviť plochu" @@ -135,8 +120,6 @@ "Zakázané vaším správcom" "Povoliť otáčanie plochy" "Pri otočení telefónu" - "Režim na šírku" - "Nastavte v telefóne režim na šírku" "Bodky upozornení" "Zapnuté" "Vypnuté" @@ -155,8 +138,7 @@ "Inštaluje sa %1$s. Dokončené: %2$s." "Sťahuje sa aplikácia %1$s. Stiahnuté: %2$s" "Aplikácia %1$s čaká na inštaláciu" - "Aplikácia %1$s je archivovaná." - "stiahnuť a obnoviť" + "Aplikácia %1$s je archivovaná. Klepnutím ju stiahnite a obnovte." "Vyžaduje sa aktualizácia aplikácie" "Aplikácia, ktorú zastupuje táto ikona, nie je aktualizovaná. Môžete ju ručne aktualizovať, aby odkaz znova fungoval, prípadne môžete ikonu odstrániť." "Aktualizovať" @@ -165,6 +147,7 @@ "Zoznam miniaplikácií je zavretý" "Pridať na plochu" "Presunúť položku sem" + "Položka bola pridaná na plochu" "Položka bola odstránená" "Späť" "Presunúť položku" @@ -184,15 +167,11 @@ "Znížiť šírku" "Znížiť výšku" "Veľkosť miniaplikácie bola zmenená na %1$s x %2$s (šírka x výška)" - "Ponuka skratiek" - "Rám na zmenu veľkosti miniaplikácie pre %1$s" - "Zavrieť" + "Skratky" "Zavrieť" "Zavrieť" "Osobné" "Pracovné" - "Karta Osobné aplikácie" - "Karta Pracovné aplikácie" "Pracovný profil" "Pracovné aplikácie majú odznak a zobrazujú sa správcovi IT" "Dobre" @@ -204,7 +183,6 @@ "Dobre" "Pozastaviť pracovné aplikácie" "Zrušiť pozastavenie" - "Plán pre pracovné aplikácie" "Filtrujte" "Zlyhalo: %1$s" "Súkromný priestor" @@ -217,5 +195,4 @@ "Prechod súkromného priestoru" "Inštalovať" "Inštalácia aplikácií v súkromnom priestore" - "Pridajte do súkromného priestoru súbory a ďalšie položky" diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml index c93f1ae7b3..5d423ca27b 100644 --- a/res/values-sl/strings.xml +++ b/res/values-sl/strings.xml @@ -29,11 +29,8 @@ "Začetni zaslon" "Nastavitev zaganjalnika %1$s kot privzete aplikacije za začetni zaslon v nastavitvah" "Razdeljen zaslon" - "Sprememba razmerja stranic" "Podatki o aplikaciji za: %1$s" "Nastavitve uporabe za »%1$s«" - "Novo okno" - "Upravljanje oken" "Shrani par aplikacij" "%1$s | %2$s" "Ta par aplikacij ni podprt v tej napravi" @@ -41,8 +38,6 @@ "Par aplikacij ni na voljo" "Pridržite pripomoček, da ga premaknete." "Dvakrat se dotaknite pripomočka in ga pridržite, da ga premaknete, ali pa uporabite dejanja po meri." - "Več možnosti" - "Pokaži vse pripomočke" "%1$d × %2$d" "Širina %1$d, višina %2$d" "Pripomoček %1$s" @@ -69,13 +64,8 @@ "Služba" "Pogovori" "Ustvarjanje zapiskov" - "Pokaži gumb za dodajanje" - "Skrij gumb za dodajanje" "Dodaj" "Dodajanje pripomočka »%1$s«" - "Pokaži vse" - "Prikaz vseh pripomočkov" - "Prikazani so vsi pripomočki" "Dotaknite se, če želite spremeniti nastavitve pripomočka." "Spreminjanje nastavitev pripomočka" "Iskanje programov" @@ -83,7 +73,6 @@ "Ni aplikacij, ki bi ustrezale poizvedbi »%1$s«" "Aplikacija" "Vse aplikacije" - "Seznam aplikacij" "Obvestila" "Pridržite bližnjico, da jo premaknete." "Dvakrat se dotaknite bližnjice in jo pridržite, da jo premaknete, ali pa uporabite dejanja po meri." @@ -101,7 +90,6 @@ "Namesti" "Ne predlagaj" "Predvidevanje pripenjanja" - "Mehurček" "namestitev bližnjic" "Aplikaciji dovoli dodajanje bližnjic brez posredovanja uporabnika." "branje nastavitev in bližnjic na začetnem zaslonu" @@ -118,8 +106,6 @@ "Stran %1$d od %2$d" "Začetni zaslon %1$d od %2$d" "Nova stran na začetnem zaslonu" - "Aktivno" - "Minimirano" "Mapa je odprta, %1$d krat %2$d" "Dotaknite se, da zaprete mapo" "Dotaknite se, da shranite preimenovanje" @@ -127,16 +113,13 @@ "Mapa je preimenovana v %1$s" "Mapa: %1$s, št. elementov: %2$d" "Mapa: %1$s, %2$d ali več elementov" - "Neimenovana mapa" "Par aplikacij: %1$s in %2$s" "Zaslonsko ozadje in slog" "Urejanje začetnega zaslona" - "Začetni zaslon" + "Domače nastavitve" "Onemogočil skrbnik." "Dovoli sukanje začetnega zaslona" "Ko se telefon zasuka" - "Ležeči način" - "Telefon preklopite v ležeči način" "Obvestilne pike" "Vklopljeno" "Izklopljeno" @@ -155,8 +138,7 @@ "%1$s se namešča, dokončano: %2$s" "Prenašanje aplikacije %1$s; preneseno %2$s" "Aplikacija %1$s čaka na namestitev" - "Aplikacija %1$s je arhivirana." - "prenos in obnovitev" + "Aplikacija %1$s je arhivirana. Dotaknite se za prenos in obnovitev." "Zahtevana je posodobitev aplikacije" "Aplikacija za to ikono ni posodobljena. Lahko jo ročno posodobite, da znova omogočite to bližnjico, ali pa odstranite ikono." "Posodobi" @@ -165,6 +147,7 @@ "Seznam pripomočkov se je zaprl" "Dodajanje na začetni zaslon" "Premik elementa sem" + "Element je bil dodan na začetni zaslon" "Element je bil odstranjen." "Razveljavi" "Premik elementa" @@ -184,15 +167,11 @@ "Zmanjšanje širine" "Zmanjšanje višine" "Velikost pripomočka je bila spremenjena na %1$s širine in %2$s višine" - "Meni z bližnjicami" - "Okvir za spreminjanje velikosti pripomočka za aplikacijo %1$s" - "Zapri" + "Bližnjice" "Opusti" "Zapri" "Osebno" "Delo" - "Zavihek z osebnimi aplikacijami" - "Zavihek z delovnimi aplikacijami" "Delovni profil" "Delovne aplikacije so označene z značko in vidne skrbniku za IT." "Razumem" @@ -204,7 +183,6 @@ "V redu" "Začasno zaustavi delovne aplikacije" "Znova aktiviraj" - "Razpored delovnih aplikacij" "Filtriranje" "Ni uspelo: %1$s" "Zasebni prostor" @@ -217,5 +195,4 @@ "Preklapljanje zasebnega prostora" "Namestitev" "Nameščanje aplikacij v zasebni prostor" - "Dodajte datoteke in drugo v zasebni prostor" diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml index 81328bcc9b..8f4133dcf7 100644 --- a/res/values-sq/strings.xml +++ b/res/values-sq/strings.xml @@ -29,11 +29,8 @@ "Ekrani bazë" "Cakto %1$s si aplikacionin e parazgjedhur të ekranit bazë te \"Cilësimet\"" "Ekrani i ndarë" - "Ndrysho raportin e pamjes" "Informacioni i aplikacionit për %1$s" "Cilësimet e përdorimit për \"%1$s\"" - "Dritare e re" - "Menaxho dritaret" "Ruaj çiftin e aplikacioneve" "%1$s | %2$s" "Ky çift aplikacionesh nuk mbështetet në këtë pajisje" @@ -41,8 +38,6 @@ "Çifti i aplikacioneve nuk ofrohet" "Prek dhe mbaj shtypur një miniaplikacion për ta zhvendosur." "Trokit dy herë dhe mbaje shtypur një miniapliikacion për ta zhvendosur atë ose për të përdorur veprimet e personalizuara." - "Opsione të tjera" - "Shfaq të gjitha miniapl." "%1$d × %2$d" "%1$d i gjerë me %2$d i lartë" "%1$s miniaplikacion" @@ -69,13 +64,8 @@ "Puna" "Bisedat" "Mbajtja e shënimeve" - "Shfaq butonin e shtimit" - "Fshih butonin e shtimit" "Shto" "Shto miniaplikacionin %1$s" - "Shfaq të gjitha" - "Shfaq të gjitha miniaplikacionet" - "Po shfaqen të gjitha miniaplikacionet" "Trokit për të ndryshuar cilësimet e miniaplikacionit" "Ndrysho cilësimet e miniaplikacionit" "Kërko për aplikacione" @@ -83,7 +73,6 @@ "Nuk u gjet asnjë aplikacion që përputhet me \"%1$s\"" "Aplikacioni" "Të gjitha aplikacionet" - "Lista e aplikacioneve" "Njoftimet" "Prek dhe mbaj shtypur një shkurtore për ta zhvendosur." "Trokit dy herë dhe mbaje shtypur një shkurtore për ta zhvendosur atë ose për të përdorur veprimet e personalizuara." @@ -101,7 +90,6 @@ "Instalo" "Mos sugjero aplikacion" "Gozhdo parashikimin" - "Flluskë" "instalimi i shkurtoreve" "Lejon një aplikacion të shtojë shkurtore pa ndërhyrjen e përdoruesit." "lexo cilësimet dhe shkurtoret e ekranit bazë" @@ -118,8 +106,6 @@ "Faqja: %1$d nga gjithsej %2$d" "Ekrani bazë: %1$d nga gjithsej %2$d" "Faqja e ekranit të ri kryesor" - "Aktiv" - "Minimizuar" "Dosja u hap, %1$d me %2$d" "Trokit për të mbyllur dosjen" "Trokit për të ruajtur riemërtimin" @@ -127,7 +113,6 @@ "Dosja u riemërtua në %1$s" "Dosja: %1$s, %2$d artikuj" "Dosja: %1$s, %2$d ose më shumë artikuj" - "Dosje pa emër" "Çifti i aplikacioneve: %1$s dhe %2$s" "Imazhi i sfondit dhe stili" "Modifiko ekranin bazë" @@ -135,8 +120,6 @@ "Çaktivizuar nga administratori" "Lejo rrotullimin e ekranit bazë" "Kur telefoni rrotullohet" - "Modaliteti horizontal" - "Vendose telefonin në modalitetin horizontal" "Pikat e njoftimeve" "Aktiv" "Joaktiv" @@ -155,8 +138,7 @@ "%1$s po instalohet, %2$s i përfunduar" "%1$s po shkarkohet, %2$s të përfunduara" "%1$s po pret të instalohet" - "%1$s është arkivuar." - "shkarko dhe restauro" + "%1$s është arkivuar. Trokit për ta shkarkuar dhe restauruar." "Kërkohet përditësimi i aplikacionit" "Aplikacioni për këtë ikonë nuk është përditësuar. Mund ta përditësosh manualisht për të riaktivizuar këtë shkurtore ose hiq ikonën." "Përditëso" @@ -165,6 +147,7 @@ "Lista e miniaplikacioneve u mbyll" "Shto në ekranin bazë" "Zhvendose artikullin këtu" + "Artikulli u shtua tek ekrani bazë" "Artikulli u hoq" "Zhbëj" "Zhvendose artikullin" @@ -184,15 +167,11 @@ "Zvogëlo gjerësinë" "Zvogëlo lartësinë" "Madhësia e miniaplikacionit u ndryshua me gjerësinë %1$s dhe lartësinë %2$s" - "Menyja e shkurtoreve" - "Kuadri i ndryshimit të përmasave të miniaplikacionit për %1$s" - "Mbyll" + "Shkurtoret" "Hiqe" "Mbyll" "Personale" "Punë" - "Skeda e aplikacioneve personale" - "Skeda e aplikacioneve të punës" "Profili i punës" "Aplikacionet e punës janë të shënuara dhe të dukshme për administratorin e teknologjisë së informacionit" "E kuptova" @@ -204,7 +183,6 @@ "E kuptova" "Vendos në pauzë aplikacionet e punës" "Hiq nga pauza" - "Orari për aplikacionet e punës" "Filtro" "Dështoi: %1$s" "Hapësira private" @@ -217,5 +195,4 @@ "Kalimi te \"Hapësira private\"" "Instalo" "Instalo aplikacionet në hapësirën private" - "Shto skedarë dhe më shumë te \"Hapësira private\"" diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml index 065661b512..cd6523ccb9 100644 --- a/res/values-sr/strings.xml +++ b/res/values-sr/strings.xml @@ -29,11 +29,8 @@ "Почетни екран" "Подесите %1$s као подразумевану почетну апликацију у Подешавањима" "Подељени екран" - "Промени размеру" "Информације о апликацији за: %1$s" "Подешавања потрошње за %1$s" - "Нови прозор" - "Управљајте прозорима" "Сачувај пар апликација" "%1$s | %2$s" "Овај пар апликација није подржан на овом уређају" @@ -41,8 +38,6 @@ "Пар апликација није доступан" "Додирните и задржите ради померања виџета." "Двапут додирните и задржите да бисте померали виџет или користите прилагођене радње." - "Још опција" - "Прикажи све виџете" "%1$d×%2$d" "ширина од %1$d и висина од %2$d" "%1$s виџет" @@ -69,13 +64,8 @@ "Посао" "Конверзације" "Прављење бележака" - "Прикажите дугме за додавање" - "Сакријте дугме за додавање" "Додај" "Додајте виџет %1$s" - "Прикажи све" - "Прикажите све виџете" - "Приказују се сви виџети" "Додирните да бисте променили подешавања виџета" "Промените подешавања виџета" "Претражите апликације" @@ -83,7 +73,6 @@ "Није пронађена ниједна апликација за „%1$s“" "Апликација" "Све апликације" - "Листа апликација" "Обавештења" "Додирните и задржите ради померања пречице." "Двапут додирните и задржите да бисте померали пречицу или користите прилагођене радње." @@ -101,7 +90,6 @@ "Инсталирај" "Не предлажи апликацију" "Закачи предвиђање" - "Облачић" "инсталирање пречица" "Дозвољава апликацији да додаје пречице без интервенције корисника." "читање подешавања и пречица на почетном екрану" @@ -118,8 +106,6 @@ "%1$d. страница од %2$d" "%1$d. почетни екран од %2$d" "Нова страница почетног екрана" - "Активно" - "Смањено" "Фолдер је отворен, %1$d пута %2$d" "Додирните да бисте затворили фолдер" "Додирните да бисте сачували преименовање" @@ -127,7 +113,6 @@ "Фолдер је преименован у %1$s" "Фолдер: %1$s, %2$d ставке" "Фолдер: %1$s, %2$d или више ставки" - "Неименовани фолдер" "Пар апликација: %1$s и %2$s" "Позадина и стил" "Измени почетни екран" @@ -135,8 +120,6 @@ "Администратор је онемогућио" "Дозволи ротацију почетног екрана" "Када се телефон ротира" - "Водоравни режим" - "Подесите телефон на водоравни режим" "Тачке за обавештења" "Укључено" "Искључено" @@ -155,8 +138,7 @@ "%1$s се инсталира, %2$s готово" "%1$s се преузима, завршено је %2$s" "%1$s чека на инсталирање" - "Апликација %1$s је архивирана." - "преузмите и вратите" + "Апликација %1$s је архивирана. Додирните да бисте је преузели и вратили." "Треба да ажурирате апликацију" "Апликација за ову икону није ажурирана. Можете да је ручно ажурирате да бисте поново омогућили ову пречицу или уклоните икону." "Ажурирај" @@ -165,6 +147,7 @@ "Листа виџета је затворена" "Додајте на почетни екран" "Премести ставку овде" + "Ставка је додата на почетни екран" "Ставка је уклоњена" "Опозови" "Премести ставку" @@ -184,15 +167,11 @@ "Смањи ширину" "Смањи висину" "Величина виџета је промењена на ширину %1$s и висину %2$s" - "Мени са пречицама" - "Промена величине оквира виџета за: %1$s" - "Затворите" + "Пречице" "Одбаци" "Затвори" "Лично" "Посао" - "Картица Личне апликације" - "Картица Пословне апликације" "Пословни профил" "Пословне апликације су означене значком и ИТ администратор може да их види" "Важи" @@ -204,7 +183,6 @@ "Важи" "Паузирај пословне апликације" "Поново активирај" - "Распоред за пословне апликације" "Филтер" "Није успело: %1$s" "Приватни простор" @@ -217,5 +195,4 @@ "Пренос приватног простора" "Инсталирајте" "Инсталирај апликације у приватан простор" - "Додајте фајлове и друго у приватан простор" diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml index 64558b6c2e..4f95c6708e 100644 --- a/res/values-sv/strings.xml +++ b/res/values-sv/strings.xml @@ -29,11 +29,8 @@ "Startskärm" "Ställ in %1$s som standardstartskärmsapp i Inställningar" "Delad skärm" - "Ändra bildformat" "Appinformation för %1$s" "Användningsinställningar för %1$s" - "Nytt fönster" - "Hantera fönster" "Spara app-par" "%1$s | %2$s" "De här apparna som ska användas tillsammans stöds inte på den här enheten" @@ -41,8 +38,6 @@ "App-paret är inte tillgängligt" "Tryck länge för att flytta en widget." "Tryck snabbt två gånger och håll kvar för att flytta en widget eller använda anpassade åtgärder." - "Fler alternativ" - "Visa alla widgetar" "%1$d × %2$d" "%1$d bred gånger %2$d hög" "Widget för %1$s" @@ -65,17 +60,12 @@ "Rensa texten från sökrutan" "Widgetar och genvägar är inte tillgängliga" "Inga widgetar eller genvägar hittades" - "Personligt" - "Jobb" + "Privata" + "Arbete" "Konversationer" "Anteckna" - "Visa knappen Lägg till" - "Dölj knappen Lägg till" "Lägg till" "Lägg till widgeten %1$s" - "Visa alla" - "Visa alla widgetar" - "Visar alla widgetar" "Tryck för att ändra inställningarna för widgeten" "Ändra inställningarna för widgeten" "Sök efter appar" @@ -83,7 +73,6 @@ "Inga appar som matchar %1$s hittades" "App" "Alla appar" - "Applista" "Aviseringar" "Tryck länge för att flytta en genväg." "Tryck snabbt två gånger och håll kvar för att flytta en genväg eller använda anpassade åtgärder." @@ -101,7 +90,6 @@ "Installera" "Föreslå inte app" "Fäst förslag" - "Bubbla" "installera genvägar" "Tillåter att en app lägger till genvägar utan åtgärd från användaren." "läsa inställningar och genvägar på startskärmen" @@ -118,8 +106,6 @@ "Sidan %1$d av %2$d" "Startskärmen %1$d av %2$d" "Ny sida på startskärmen" - "Aktivt" - "Minimerat" "Mappen är öppen, %1$d gånger %2$d" "Tryck för att stänga mappen" "Tryck för att spara namnändringen" @@ -127,7 +113,6 @@ "Mappen har bytt namn till %1$s" "Mapp: %1$s, %2$d objekt" "Mapp: %1$s, %2$d eller fler objekt" - "Namnlös mapp" "Appar som ska användas tillsammans: %1$s och %2$s" "Bakgrund och utseende" "Redigera startskärm" @@ -135,8 +120,6 @@ "Inaktiverat av administratören" "Tillåt rotering av startskärmen" "När telefonen vrids" - "Liggande" - "Ställ in telefonen på liggande läge" "Aviseringsprickar" "På" "Av" @@ -155,8 +138,7 @@ "%1$s installeras. %2$s har slutförts" "%1$s laddas ned, %2$s klart" "%1$s väntar på installation" - "%1$s har arkiverats." - "ladda ned och återställ" + "%1$s har arkiverats. Tryck för att ladda ner och återställa." "Du måste uppdatera appen" "Appen för den här ikonen har inte uppdaterats. Du kan uppdatera den manuellt för att återaktivera genvägen eller ta bort ikonen." "Uppdatera" @@ -165,6 +147,7 @@ "Widgetslistan har stängts" "Lägg till på startskärmen" "Flytta objekt hit" + "Objektet har lagts till på startskärmen" "Objektet har tagits bort" "Ångra" "Flytta objekt" @@ -184,15 +167,11 @@ "Minska bredden" "Minska höjden" "Widgetens storlek har ändrats till: bredd %1$s, höjd %2$s" - "Snabbmeny" - "Widget för att ändra ramstorlek för %1$s" - "Stäng" + "Genvägar" "Ignorera" "Stäng" "Privat" "Arbete" - "Fliken Privata appar" - "Fliken Jobbappar" "Jobbprofil" "Jobbappar är märkta och synliga för IT-administratören" "OK" @@ -204,7 +183,6 @@ "OK" "Pausa jobbappar" "Återuppta" - "Schema för jobbappar" "Filter" "Misslyckades: %1$s" "Privat rum" @@ -217,5 +195,4 @@ "Överföring av privat rum" "Installera" "Installera appar i privat rum" - "Lägg till filer och annat i det privata utrymmet" diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml index 4b1adb0377..53a9abe056 100644 --- a/res/values-sw/strings.xml +++ b/res/values-sw/strings.xml @@ -29,11 +29,8 @@ "Skrini ya kwanza" "Weka %1$s iwe programu chaguomsingi ya mwanzo kwenye Mipangilio" "Gawa skrini" - "Badilisha uwiano" "Maelezo ya programu ya %1$s" "Mipangilio ya matumizi ya %1$s" - "Dirisha Jipya" - "Dhibiti Windows" "Hifadhi jozi ya programu" "%1$s | %2$s" "Jozi hii ya programu haitumiki kwenye kifaa hiki" @@ -41,8 +38,6 @@ "Kipengele cha jozi ya programu hakipatikani" "Gusa na ushikilie ili usogeze wijeti." "Gusa mara mbili na ushikilie ili usogeze wijeti au utumie vitendo maalum." - "Chaguo zaidi" - "Onyesha wijeti zote" "%1$d × %2$d" "Upana wa %1$d na kimo cha %2$d" "Wijeti ya %1$s" @@ -69,13 +64,8 @@ "Kazini" "Mazungumzo" "Kuandika madokezo" - "Onyesha kitufe cha kuweka" - "Ficha kitufe cha kuweka" "Weka" "Weka wijeti ya %1$s" - "Onyesha zote" - "Onyesha wijeti zote" - "Inaonyesha wijeti zote" "Gusa ili ubadilishe mipangilio ya wijeti" "Badilisha mipangilio ya wijeti" "Tafuta programu" @@ -83,7 +73,6 @@ "Haikupata programu zozote zinazolingana na \"%1$s\"" "Programu" "Programu zote" - "Orodha ya programu" "Arifa" "Gusa na ushikilie ili usogeze njia ya mkato." "Gusa mara mbili na ushikilie ili usogeze njia ya mkato au utumie vitendo maalum." @@ -101,7 +90,6 @@ "Sakinisha" "Isipendekeze programu" "Bandika Utabiri" - "Kiputo" "kuweka njia za mkato" "Huruhusu programu kuongeza njia za mkato bila mtumiaji kuingilia kati." "kusoma mipangilio ya skrini ya kwanza na njia za mkato" @@ -118,8 +106,6 @@ "Ukurasa%1$d wa %2$d" "Skrini ya mwanzo %1$d ya %2$d" "Ukurasa mpya wa skrini ya kwanza" - "Inatumika" - "Imepunguzwa" "Folda imefunguliwa, %1$d kwa %2$d" "Gusa ili ufunge folda" "Gusa ili ubadilishe jina" @@ -127,7 +113,6 @@ "Folda imebadilishwa jina kuwa %1$s" "Folda: %1$s, vipengee %2$d" "Folda: %1$s, vipengee %2$d au zaidi" - "Folda isiyo na jina" "Jozi ya programu: %1$s na %2$s" "Mandhari na mtindo" "Badilisha Skrini ya Kwanza" @@ -135,8 +120,6 @@ "Imezimwa na msimamizi wako" "Ruhusu kipengele cha kuzungusha skrini ya kwanza" "Simu inapozungushwa" - "Mkao wa mlalo" - "Weka simu katika mkao wa mlalo" "Vitone vya arifa" "Imewashwa" "Imezimwa" @@ -146,7 +129,7 @@ "Onyesha vitone vya arifa" "Chaguo za Wasanidi Programu" "Weka aikoni za programu kwenye skrini ya kwanza" - "Kwa programu mpya" + "Kwa ajili ya programu mpya" "Yasiyojulikana" "Ondoa" "Tafuta" @@ -155,8 +138,7 @@ "Inasakinisha %1$s, imekamilika %2$s" "%1$s inapakuliwa, %2$s imekamilika" "%1$s inasubiri kusakinisha" - "%1$s imewekwa kwenye kumbukumbu." - "pakua na urejeshe" + "%1$s imewekwa kwenye kumbukumbu. Gusa ili upakue na urejeshe." "Unahitaji kusasisha programu" "Programu ya aikoni hii haijasasishwa. Unaweza kusasisha mwenyewe ili uruhusu upya njia hii ya mkato au uondoe aikoni." "Sasisha" @@ -165,6 +147,7 @@ "Orodha ya wijeti imefungwa" "Weka kwenye skrini ya kwanza" "Hamishia kipengee hapa" + "Kipengee kimeongezwa kwenye skrini ya kwanza" "Kipengee kimeondolewa" "Tendua" "Hamisha kipengee" @@ -184,15 +167,11 @@ "Punguza upana" "Punguza urefu" "Wijeti imepunguzwa hadi upana %1$s urefu %2$s" - "Menyu ya Njia za Mkato" - "Fremu ya Kubadilisha Ukubwa wa Wijeti ya %1$s" - "Funga" + "Njia za mkato" "Ondoa" "Funga" "Binafsi" "Kazini" - "Kichupo cha programu za binafsi" - "Kichupo cha programu za kazini" "Wasifu wa kazini" "Programu za kazini zina beji na msimamizi wako wa TEHAMA anaziona" "Nimeelewa" @@ -204,12 +183,11 @@ "Nimeelewa" "Simamisha programu za kazini" "Acha kusimamisha" - "Ratiba ya programu za kazini" "Kichujio" "Hitilafu: %1$s" "Nafasi ya faragha" "Gusa uweke mipangilio au ufungue" - "Sehemu ya Faragha" + "Faragha" "Mipangilio ya Nafasi ya Faragha" "Ya faragha, imefunguliwa." "Ya faragha, imefungwa." @@ -217,5 +195,4 @@ "Mabadiliko ya Nafasi ya Faragha" "Weka" "Sakinisha programu kwenye Sehemu ya Faragha" - "Weka faili na mengineyo kwenye Sehemu ya Faragha" diff --git a/res/values-sw600dp/styles.xml b/res/values-sw600dp/styles.xml index db49a3e518..63bd46b2c3 100644 --- a/res/values-sw600dp/styles.xml +++ b/res/values-sw600dp/styles.xml @@ -14,11 +14,8 @@ ~ limitations under the License. --> - + - \ No newline at end of file diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml index a1f1ecd80c..738f85cafe 100644 --- a/res/values-ta/strings.xml +++ b/res/values-ta/strings.xml @@ -29,11 +29,8 @@ "முகப்பு" "அமைப்புகளில் %1$s என்பதை இயல்பு முகப்பு ஆப்ஸாக அமையுங்கள்" "திரைப் பிரிப்பு" - "தோற்ற விகிதத்தை மாற்று" "%1$sக்கான ஆப்ஸ் தகவல்கள்" "%1$sக்கான உபயோக அமைப்புகள்" - "புதிய சாளரம்" - "சாளரங்களை நிர்வகியுங்கள்" "ஆப்ஸ் ஜோடியைச் சேமி" "%1$s | %2$s" "இந்தச் சாதனத்தில் இந்த ஆப்ஸ் ஜோடி ஆதரிக்கப்படவில்லை" @@ -41,8 +38,6 @@ "ஆப்ஸ் ஜோடி கிடைக்கவில்லை" "விட்ஜெட்டை நகர்த்தத் தொட்டுப் பிடிக்கவும்." "விட்ஜெட்டை நகர்த்த இருமுறை தட்டிப் பிடிக்கவும் அல்லது பிரத்தியேகச் செயல்களைப் பயன்படுத்தவும்." - "கூடுதல் விருப்பங்கள்" - "விட்ஜெட்டுகளைக் காட்டு" "%1$d × %2$d" "%1$d அகலத்திற்கு %2$d உயரம்" "%1$s விட்ஜெட்" @@ -69,13 +64,8 @@ "பணி" "உரையாடல்கள்" "குறிப்பெடுத்தல்" - "சேர்ப்பதற்கான பட்டனைக் காட்டும்" - "சேர்ப்பதற்கான பட்டனை மறைக்கும்" "சேர்" "%1$s விட்ஜெட்டைச் சேர்க்கும்" - "எல்லாம் காட்டு" - "அனைத்து விட்ஜெட்களையும் காட்டும்" - "அனைத்து விட்ஜெட்களையும் காட்டுகிறது" "விட்ஜெட் அமைப்புகளை மாற்றத் தட்டவும்" "விட்ஜெட் அமைப்புகளை மாற்றும்" "ஆப்ஸில் தேடுக" @@ -83,7 +73,6 @@ "\"%1$s\" உடன் பொருந்தும் ஆப்ஸ் இல்லை" "ஆப்ஸ்" "அனைத்து ஆப்ஸும்" - "ஆப்ஸ் பட்டியல்" "அறிவிப்புகள்" "ஷார்ட்கட்டை நகர்த்தத் தொட்டுப் பிடிக்கவும்." "ஷார்ட்கட்டை நகர்த்த இருமுறை தட்டிப் பிடிக்கவும் அல்லது பிரத்தியேகச் செயல்களைப் பயன்படுத்தவும்." @@ -101,7 +90,6 @@ "நிறுவு" "பரிந்துரைக்காதே" "கணிக்கப்பட்டதைப் பின் செய்" - "குமிழ்" "குறுக்குவழிகளை நிறுவுதல்" "பயனரின் அனுமதி இல்லாமல் குறுக்குவழிகளைச் சேர்க்கப் ஆப்ஸை அனுமதிக்கிறது." "முகப்புத் திரையின் அமைப்புகளையும் ஷார்ட்கட்களையும் படித்தல்" @@ -118,8 +106,6 @@ "பக்கம் %1$d / %2$d" "முகப்புத் திரை %1$d of %2$d" "புதிய முகப்புத் திரை பக்கம்" - "செயலில் உள்ளது" - "சிறிதாக்கப்பட்டது" "திறக்கப்பட்ட ஃபோல்டர், %1$d x %2$d" "ஃபோல்டரை மூட, தட்டவும்" "மாற்றிய பெயரைச் சேமிக்க, தட்டவும்" @@ -127,7 +113,6 @@ "ஃபோல்டர் %1$s என மறுபெயரிடப்பட்டது" "ஃபோல்டர்: %1$s, %2$d ஃபைல்கள்" "ஃபோல்டர்: %1$s, %2$d அல்லது அதற்கு அதிகமான ஃபைல்கள்" - "பெயரிடப்படாத ஃபோல்டர்" "ஆப்ஸ் ஜோடி: %1$s மற்றும் %2$s" "வால்பேப்பர் & ஸ்டைல்" "முகப்புத் திரையில் மாற்று" @@ -135,8 +120,6 @@ "உங்கள் நிர்வாகி முடக்கியுள்ளார்" "முகப்புத் திரை சுழற்சியை அனுமதித்தல்" "மொபைலைச் சுழற்றும் போது" - "லேண்ட்ஸ்கேப் பயன்முறை" - "மொபைலை லேண்ட்ஸ்கேப் பயன்முறையில் அமைக்கலாம்" "அறிவிப்புப் புள்ளிகள்" "ஆன்" "ஆஃப்" @@ -155,8 +138,7 @@ "%1$s நிறுவப்படுகிறது, %2$s முடிந்தது" "%1$sஐப் பதிவிறக்குகிறது, %2$s முடிந்தது" "%1$sஐ நிறுவுவதற்காகக் காத்திருக்கிறது" - "%1$s காப்பிடப்பட்டுள்ளது." - "பதிவிறக்கி மீட்டெடுக்கும்" + "%1$s காப்பிடப்பட்டுள்ளது. அதைப் பதிவிறக்கி மீட்டெடுக்க தட்டுங்கள்." "ஆப்ஸைப் புதுப்பியுங்கள்" "இந்த ஐகானுக்கான ஆப்ஸ் புதுப்பிக்கப்படவில்லை. இந்த ஷார்ட்கட்டை மீண்டும் இயக்கவோ ஐகானை அகற்றவோ நீங்களாகவே புதுப்பிக்கலாம்." "புதுப்பி" @@ -165,6 +147,7 @@ "விட்ஜெட்கள் பட்டியல் மூடப்பட்டது" "முகப்புத் திரையில் சேர்" "இங்கு நகர்த்து" + "முகப்புத் திரையில் சேர்க்கப்பட்டது" "அகற்றப்பட்டது" "செயல்தவிர்" "நகர்த்து" @@ -184,15 +167,11 @@ "அகலத்தைக் குறை" "உயரத்தைக் குறை" "அகலம் %1$s மற்றும் உயரம் %2$sக்கு விட்ஜெட் அளவு மாற்றப்பட்டது" - "ஷார்ட்கட் மெனு" - "%1$sக்கான விட்ஜெட்டின் அளவை மாற்றும் சட்டம்" - "மூடும்" + "ஷார்ட்கட்கள்" "நிராகரி" "மூடும் பட்டன்" "தனிப்பட்டவை" "பணி" - "தனிப்பட்ட ஆப்ஸ் பிரிவு" - "பணி ஆப்ஸ் பிரிவு" "பணிக் கணக்கு" "பணி ஆப்ஸில் பேட்ஜ் இடப்பட்டிருக்கும், உங்கள் IT நிர்வாகியால் பணி ஆப்ஸைப் பார்க்க முடியும்" "முடிந்தது" @@ -204,12 +183,11 @@ "சரி" "பணி ஆப்ஸை இடைநிறுத்து" "மீண்டும் இயக்கு" - "பணி ஆப்ஸுக்கான திட்ட அட்டவணை" "வடிப்பான்" "தோல்வி: %1$s" - "ரகசிய இடம்" + "தனிப்பட்ட சேமிப்பிடம்" "அமைக்கவோ திறக்கவோ தட்டுங்கள்" - "ரகசிய இடம்" + "தனிப்பட்டது" "தனிப்பட்ட சேமிப்பிட அமைப்புகள்" "தனிப்பட்டது, அன்லாக் செய்யப்பட்டுள்ளது." "தனிப்பட்டது, லாக் செய்யப்பட்டுள்ளது." @@ -217,5 +195,4 @@ "தனிப்பட்ட சேமிப்பிடத்திற்கு மாற்றுகிறது" "நிறுவுக" "தனிப்பட்ட சேமிப்பிடத்தில் ஆப்ஸை நிறுவும்" - "ரகசிய இடத்தில் ஃபைல்கள் மற்றும் பலவற்றைச் சேர்க்கலாம்" diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml index 5431846c7b..0520ebf65e 100644 --- a/res/values-te/strings.xml +++ b/res/values-te/strings.xml @@ -29,11 +29,8 @@ "మొదటి ట్యాబ్" "సెట్టింగ్‌లలో %1$s‌ను ఆటోమేటిక్ సెట్టింగ్ మొదటి స్క్రీన్ యాప్‌గా సెట్ చేయండి" "స్ప్లిట్ స్క్రీన్" - "ఆకార నిష్పత్తిని మార్చండి" "%1$s కోసం యాప్ సమాచారం" "%1$sకు సంబంధించిన వినియోగ సెట్టింగ్‌లు" - "కొత్త విండో" - "విండోలను మేనేజ్ చేయండి" "యాప్ పెయిర్‌ను సేవ్ చేయండి" "%1$s | %2$s" "ఈ పరికరంలో ఈ యాప్ పెయిర్ సపోర్ట్ చేయదు" @@ -41,20 +38,18 @@ "యాప్ పెయిర్ అందుబాటులో లేదు" "విడ్జెట్‌ను తరలించడానికి తాకి & నొక్కి ఉంచండి." "విడ్జెట్‌ను తరలించడానికి లేదా అనుకూల చర్యలను ఉపయోగించడానికి రెండుసార్లు నొక్కండి & హోల్డ్ చేయి." - "మరిన్ని ఆప్షన్‌లు" - "అన్ని విడ్జెట్‌లను చూడండి" "%1$d × %2$d" "%1$d వెడల్పు X %2$d ఎత్తు" "%1$s విడ్జెట్" "%1$s విడ్జెట్, %2$d వెడల్పు %3$d ఎత్తు ఉండాలి" - "విడ్జెట్‌ను మొదటి స్క్రీన్‌లో అటు, ఇటు కదపడానికి దాన్ని తాకి, నొక్కి పట్టుకోండి" + "విడ్జెట్‌ను మొదటి స్క్రీన్‌లో తిప్పడానికి దాన్ని తాకి, & నొక్కి పట్టుకోండి" "మొదటి స్క్రీన్‌కు జోడించండి" "మొదటి స్క్రీన్‌కు %1$s విడ్జెట్ జోడించబడింది" "సూచనలు" "నిత్యావసరాలు" "వార్తలు & మ్యాగజైన్లు" "వినోదం" - "సోషల్ మీడియా" + "సామాజికం" "మీ కోసం సూచించినవి" "కుడి వైపున %1$s విడ్జెట్‌లు, ఎడమ వైపున సెర్చ్, ఇతర ఆప్షన్‌లు" "{count,plural, =1{# విడ్జెట్}other{# విడ్జెట్‌లు}}" @@ -65,17 +60,12 @@ "సెర్చ్ బాక్స్ నుండి టెక్స్ట్‌ను క్లియర్ చేయండి" "విడ్జెట్‌లు, షార్ట్‌కట్‌లు అందుబాటులో లేవు" "విడ్జెట్‌లు లేదా షార్ట్‌కట్‌లు కనుగొనబడలేదు" - "వ్యక్తిగత విడ్జెట్స్" - "వర్క్ విడ్జెట్స్" + "వ్యక్తిగత గ్యాడ్జెట్స్" + "ఆఫీస్" "సంభాషణలు" "నోట్-టేకింగ్" - "యాడ్‌ చేసే (జోడించే) బటన్‌ను చూపండి" - "యాడ్‌ చేసే (జోడించే) బటన్‌ను దాచండి" "జోడించండి" "%1$s విడ్జెట్‌ను జోడించండి" - "అన్నీ చూడండి" - "అన్ని విడ్జెట్‌లను చూపండి" - "అన్ని విడ్జెట్‌లు చూపబడుతున్నాయి" "విడ్జెట్ సెట్టింగ్‌లను మార్చడానికి ట్యాప్ చేయండి" "విడ్జెట్ సెట్టింగ్‌లను మార్చండి" "యాప్‌ల కోసం సెర్చ్ చేయండి" @@ -83,7 +73,6 @@ "\"%1$s\"కి మ్యాచ్ అయ్యే అప్లికేషన్‌లేవీ కనుగొనబడలేదు" "యాప్" "అన్ని యాప్‌లు" - "యాప్‌ల లిస్ట్" "నోటిఫికేషన్‌లు" "షార్ట్‌కట్‌ను తరలించడానికి తాకి & నొక్కి ఉంచు." "షార్ట్‌కట్‌ను తరలించడానికి లేదా అనుకూల చర్యలను ఉపయోగించడానికి రెండుసార్లు నొక్కండి & హోల్డ్ చేయండి." @@ -101,7 +90,6 @@ "ఇన్‌స్టాల్ చేయండి" "యాప్ సూచించకు" "సూచనను పిన్ చేయండి" - "బబుల్" "షార్ట్‌కట్‌లను ఇన్‌స్టాల్ చేయడం" "వినియోగదారు ప్రమేయం లేకుండా షార్ట్‌కట్‌లను జోడించడానికి యాప్‌ను అనుమతిస్తుంది." "హోమ్ సెట్టింగ్‌లు, షార్ట్‌కట్‌లను చదవండి" @@ -118,8 +106,6 @@ "%2$dలో %1$dవ పేజీ" "%2$dలో %1$dవ హోమ్ స్క్రీన్" "కొత్త హోమ్ స్క్రీన్ పేజీ" - "యాక్టివ్" - "కుదించబడింది" "ఫోల్డర్ తెరవబడింది, %1$d X %2$d" "ఫోల్డర్‌ను మూసివేయడానికి నొక్కండి" "పేరు మార్పును సేవ్ చేయడానికి నొక్కండి" @@ -127,7 +113,6 @@ "ఫోల్డర్ పేరు %1$sగా మార్చబడింది" "ఫోల్డర్: %1$s, %2$d ఐటెమ్‌లు" "ఫోల్డర్: %1$s, %2$d లేదా అంతకంటే ఎక్కువ ఐటెమ్‌లు" - "పేరు లేని ఫోల్డర్" "యాప్ పెయిర్: %1$s, %2$s" "వాల్‌పేపర్ & స్టయిల్" "మొదటి స్క్రీన్‌ను ఎడిట్ చేయండి" @@ -135,8 +120,6 @@ "మీ నిర్వాహకులు నిలిపివేసారు" "మొదటి స్క్రీన్ రొటేషన్‌ను అనుమతించండి" "ఫోన్‌‌ను తిప్పినప్పుడు" - "ల్యాండ్‌స్కేప్ మోడ్" - "ఫోన్‌ను ల్యాండ్‌స్కేప్ మోడ్‌కు సెట్ చేయండి" "నోటిఫికేషన్ డాట్‌లు" "ఆన్" "ఆఫ్" @@ -155,8 +138,7 @@ "%1$s‌ను ఇన్‌స్టాల్ చేయడం, %2$s పూర్తయింది" "%1$s డౌన్‌లోడ్ అవుతోంది, %2$s పూర్తయింది" "%1$s ఇన్‌స్టాల్ కావడానికి వేచి ఉంది" - "%1$s ఆర్కైవ్ చేయబడింది." - "డౌన్‌లోడ్ చేసి, రీస్టోర్ చేయండి" + "%1$s ఆర్కైవ్ చేయబడింది. డౌన్‌లోడ్ చేయడానికి, రీస్టోర్ చేయడానికి ట్యాప్ చేయండి." "యాప్‌ను అప్‌డేట్ చేయడం అవసరం" "ఈ చిహ్నం కోసం యాప్ అప్‌డేట్ చేయబడలేదు. మీరు ఈ షార్ట్‌కట్‌ను మళ్లీ ఎనేబుల్ చేయడానికి మాన్యువల్‌గా అప్‌డేట్ చేయవచ్చు లేదా చిహ్నాన్ని తీసివేయవచ్చు." "అప్‌డేట్ చేయండి" @@ -165,6 +147,7 @@ "విడ్జెట్‌ల లిస్ట్‌ మూసివేయబడింది" "మొదటి స్క్రీన్‌కు జోడించండి" "అంశాన్ని ఇక్కడికి తరలించు" + "అంశం హోమ్‌స్క్రీన్‌కి జోడించబడింది" "ఐటెమ్ తీసివేయబడింది" "చర్య రద్దు" "అంశాన్ని తరలించు" @@ -184,15 +167,11 @@ "వెడల్పును తగ్గించు" "ఎత్తును తగ్గించు" "విడ్జెట్ సైజ్‌ వెడల్పు %1$sకి, ఎత్తు %2$sకి మార్చబడింది" - "షార్ట్‌కట్ మెనూ" - "%1$s విడ్జెట్ సైజ్‌ను మార్చే ఫ్రేమ్" - "మూసివేయండి" + "షార్ట్‌కట్స్" "తీసివేయండి" "మూసివేస్తుంది" "వ్యక్తిగతం" "వర్క్" - "వ్యక్తిగత యాప్‌ల ట్యాబ్" - "వర్క్ యాప్‌ల ట్యాబ్" "కార్యాలయ ప్రొఫైల్" "వర్క్ యాప్‌లకు బ్యాడ్జ్ ఉంటుంది, అవి మీ IT అడ్మిన్‌కు కనిపిస్తాయి" "అర్థమైంది" @@ -204,7 +183,6 @@ "అర్థమైంది" "వర్క్ యాప్‌లను పాజ్ చేయండి" "పాజ్ నుండి తీసివేయండి" - "వర్క్ యాప్‌ల షెడ్యూల్" "ఫిల్టర్ చేయి" "విఫలమైంది: %1$s" "ప్రైవేట్ స్పేస్" @@ -217,5 +195,4 @@ "ప్రైవేట్ స్పేస్ కేటాయించడం జరుగుతుంది" "ఇన్‌స్టాల్ చేయండి" "ప్రైవేట్ స్పేస్‌కు యాప్‌లను ఇన్‌స్టాల్ చేయండి" - "ప్రైవేట్ స్పేస్‌కు ఫైళ్లను, మరిన్నింటిని జోడించండి" diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml index 63d053011d..554fd94e5f 100644 --- a/res/values-th/strings.xml +++ b/res/values-th/strings.xml @@ -29,11 +29,8 @@ "หน้าแรก" "ตั้ง %1$s เป็นแอปหน้าแรกเริ่มต้นในการตั้งค่า" "แยกหน้าจอ" - "เปลี่ยนสัดส่วนการแสดงผล" "ข้อมูลแอปสำหรับ %1$s" "การตั้งค่าการใช้งานสำหรับ %1$s" - "หน้าต่างใหม่" - "จัดการหน้าต่าง" "บันทึกคู่แอป" "%1$s | %2$s" "ไม่รองรับคู่แอปนี้ในอุปกรณ์เครื่องนี้" @@ -41,8 +38,6 @@ "การจับคู่อุปกรณ์ไม่พร้อมให้บริการ" "แตะค้างไว้เพื่อย้ายวิดเจ็ต" "แตะสองครั้งค้างไว้เพื่อย้ายวิดเจ็ตหรือใช้การดำเนินการที่กำหนดเอง" - "ตัวเลือกเพิ่มเติม" - "แสดงวิดเจ็ตทั้งหมด" "%1$d × %2$d" "กว้าง %1$d x สูง %2$d" "วิดเจ็ต %1$s" @@ -69,13 +64,8 @@ "งาน" "การสนทนา" "การจดบันทึก" - "แสดงปุ่มเพิ่ม" - "ซ่อนปุ่มเพิ่ม" "เพิ่ม" "เพิ่มวิดเจ็ต %1$s" - "แสดงทั้งหมด" - "แสดงวิดเจ็ตทั้งหมด" - "กำลังแสดงวิดเจ็ตทั้งหมด" "แตะเพื่อเปลี่ยนการตั้งค่าวิดเจ็ต" "เปลี่ยนการตั้งค่าวิดเจ็ต" "ค้นหาแอป" @@ -83,7 +73,6 @@ "ไม่พบแอปที่ตรงกับ \"%1$s\"" "แอป" "แอปทั้งหมด" - "รายชื่อแอป" "การแจ้งเตือน" "แตะค้างไว้เพื่อย้ายทางลัด" "แตะสองครั้งค้างไว้เพื่อย้ายทางลัดหรือใช้การดำเนินการที่กำหนดเอง" @@ -101,7 +90,6 @@ "ติดตั้ง" "ไม่ต้องแนะนำแอป" "ปักหมุดแอปที่คาดการณ์ไว้" - "บับเบิล" "ติดตั้งทางลัด" "อนุญาตให้แอปเพิ่มทางลัดโดยไม่ต้องให้ผู้ใช้จัดการ" "อ่านการตั้งค่าและทางลัดในหน้าแรก" @@ -118,8 +106,6 @@ "หน้า %1$d จาก %2$d" "หน้าจอหลัก %1$d จาก %2$d" "หน้าใหม่ในหน้าจอหลัก" - "ใช้งานอยู่" - "ลดขนาดเล็กสุด" "เปิดโฟลเดอร์ %1$d x %2$d" "แตะเพื่อปิดโฟลเดอร์" "แตะเพื่อบันทึกการเปลี่ยนชื่อ" @@ -127,7 +113,6 @@ "เปลี่ยนชื่อโฟลเดอร์เป็น %1$s" "โฟลเดอร์: %1$s, %2$d รายการ" "โฟลเดอร์: %1$s, อย่างน้อย %2$d รายการ" - "โฟลเดอร์ที่ไม่มีชื่อ" "คู่แอป: %1$s และ %2$s" "วอลเปเปอร์และสไตล์" "แก้ไขหน้าจอหลัก" @@ -135,8 +120,6 @@ "ปิดใช้โดยผู้ดูแลระบบ" "อนุญาตให้หมุนหน้าจอหลัก" "เมื่อหมุนโทรศัพท์" - "โหมดแนวนอน" - "ตั้งค่าโทรศัพท์เป็นโหมดแนวนอน" "เครื่องหมายจุดแสดงการแจ้งเตือน" "เปิด" "ปิด" @@ -155,8 +138,7 @@ "กำลังติดตั้ง %1$s เสร็จแล้ว %2$s" "กำลังดาวน์โหลด %1$s เสร็จแล้ว %2$s" "%1$s กำลังรอติดตั้ง" - "เก็บถาวร %1$s แล้ว" - "ดาวน์โหลดและกู้คืน" + "เก็บถาวร %1$s แล้ว แตะเพื่อดาวน์โหลดและกู้คืน" "ต้องอัปเดตแอป" "แอปสำหรับไอคอนนี้ยังไม่ได้อัปเดต คุณอัปเดตด้วยตนเองได้โดยเปิดใช้ทางลัดนี้อีกครั้งหรือนำไอคอนออก" "อัปเดต" @@ -165,6 +147,7 @@ "ปิดรายการวิดเจ็ตแล้ว" "เพิ่มลงในหน้าจอหลัก" "ย้ายรายการมาที่นี่" + "เพิ่มรายการไปยังหน้าจอหลักแล้ว" "นำรายการออกแล้ว" "เลิกทำ" "ย้ายรายการ" @@ -184,15 +167,11 @@ "ลดความกว้าง" "ลดความสูง" "ปรับขนาดของวิดเจ็ตเป็นกว้าง %1$s สูง %2$s แล้ว" - "เมนูแป้นพิมพ์ลัด" - "เฟรมปรับขนาดวิดเจ็ตสำหรับ %1$s" - "ปิด" + "ทางลัด" "ปิด" "ปิด" "ส่วนตัว" "งาน" - "แท็บแอปส่วนตัว" - "แท็บแอปงาน" "โปรไฟล์งาน" "แอปงานจะติดป้ายไว้และผู้ดูแลระบบไอทีจะมองเห็น" "รับทราบ" @@ -204,7 +183,6 @@ "รับทราบ" "หยุดแอปงานชั่วคราว" "ยกเลิกการหยุดชั่วคราว" - "กำหนดเวลาของแอปงาน" "ตัวกรอง" "ไม่สำเร็จ: %1$s" "พื้นที่ส่วนตัว" @@ -217,5 +195,4 @@ "การเปลี่ยนไปใช้พื้นที่ส่วนตัว" "ติดตั้ง" "ติดตั้งแอปไปยังพื้นที่ส่วนตัว" - "เพิ่มไฟล์และอื่นๆ ไปยังพื้นที่ส่วนตัว" diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml index 4e829a2920..cb6fe66f8f 100644 --- a/res/values-tl/strings.xml +++ b/res/values-tl/strings.xml @@ -29,11 +29,8 @@ "Home" "Itakda ang %1$s bilang default na home app sa Mga Setting" "Split screen" - "Baguhin ang aspect ratio" "Impormasyon ng app para sa %1$s" "Mga setting ng paggamit para sa %1$s" - "Bagong Window" - "Pamahalaan ang Mga Window" "I-save ang app pair" "%1$s | %2$s" "Hindi sinusuportahan sa device na ito ang pares ng app na ito" @@ -41,8 +38,6 @@ "Hindi available ang pares ng app" "Pindutin nang matagal para ilipat ang widget." "I-double tap at pindutin nang matagal para ilipat ang widget o gumamit ng mga custom na pagkilos." - "Higit pang opsyon" - "Ipakita lahat ng widget" "%1$d × %2$d" "%1$d ang lapad at %2$d ang taas" "%1$s widget" @@ -69,13 +64,8 @@ "Trabaho" "Mga Pag-uusap" "Pagtatala" - "Ipakita ang button na magdagdag" - "I-hide ang button na magdagdag" "Idagdag" "Idagdag ang widget na %1$s" - "Ipakita lahat" - "Ipakita ang lahat ng widget" - "Ipinapakita ang lahat ng widget" "I-tap para baguhin ang mga setting ng widget" "Baguhin ang mga setting ng widget" "Maghanap ng mga app" @@ -83,7 +73,6 @@ "Walang nahanap na app na tumutugma sa \"%1$s\"" "App" "Lahat ng app" - "Listahan ng mga app" "Mga Notification" "Pindutin nang matagal para ilipat ang shortcut." "I-double tap at pindutin nang matagal para ilipat ang shortcut o gumamit ng mga custom na pagkilos." @@ -101,7 +90,6 @@ "I-install" "Huwag magmungkahi" "I-pin ang Hula" - "Bubble" "i-install ang mga shortcut" "Pinapayagan ang isang app na magdagdag ng mga shortcut nang walang panghihimasok ng user." "basahin ang mga setting at shortcut ng home" @@ -118,8 +106,6 @@ "Pahina %1$d ng %2$d" "Home screen %1$d ng %2$d" "Bagong page ng home screen" - "Aktibo" - "Na-minimize" "Binuksan ang folder, %1$d by %2$d" "I-tap upang isara ang folder" "I-tap upang i-save ang bagong pangalan" @@ -127,7 +113,6 @@ "Pinalitan ang pangalan ng folder ng %1$s" "Folder: %1$s, %2$d (na) item" "Folder: %1$s, %2$d o higit pang item" - "Walang pangalang folder" "Pares ng app: %1$s at %2$s" "Wallpaper & istilo" "I-edit ang Home Screen" @@ -135,8 +120,6 @@ "Na-disable ng iyong admin" "Payagan ang pag-rotate ng home screen" "Kailan maro-rotate ang telepono" - "Landscape mode" - "Itakda ang telepono sa landscape mode" "Mga notification dot" "Naka-on" "Naka-off" @@ -155,8 +138,7 @@ "Ini-install ang %1$s, %2$s kumpleto" "Dina-download na ang %1$s, tapos na ang %2$s" "Hinihintay nang mag-install ang %1$s" - "Naka-archive ang %1$s." - "i-download at i-restore" + "Naka-archive ang %1$s. I-tap para i-download at i-restore." "Kinakailangang i-update ang app" "Hindi updated ang app para sa icon na ito. Puwede kang manual na mag-update para ma-enable ulit ang shortcut na ito, o alisin ang icon." "I-update" @@ -165,6 +147,7 @@ "Nakasara ang listahan ng mga widget" "Idagdag sa home screen" "Ilipat ang item dito" + "Naidagdag sa home screen ang item" "Naalis na ang item" "I-undo" "Ilipat ang item" @@ -184,15 +167,11 @@ "Bawasan ang lapad" "Bawasan ang taas" "Na-resize ang widget sa lapad %1$s taas %2$s" - "Menu ng Shortcut" - "Frame sa Pag-resize ng Widget para sa %1$s" - "Isara" + "Mga Shortcut" "I-dismiss" "Isara" "Personal" "Trabaho" - "Tab ng mga personal na app" - "Tab ng mga app para sa trabaho" "Profile sa trabaho" "May badge at nakikita ng iyong IT admin ang mga app para sa trabaho" "OK" @@ -204,7 +183,6 @@ "OK" "I-pause ang mga app para sa trabaho" "I-unpause" - "Iskedyul ng mga app para sa trabaho" "Filter" "Hindi nagawa: %1$s" "Pribadong space" @@ -217,5 +195,4 @@ "Pag-transition ng Pribadong Space" "I-install" "Mag-install ng mga app sa Pribadong Space" - "Magdagdag ng mga file at higit pa sa Pribadong Space" diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml index 114693c053..b12ec27378 100644 --- a/res/values-tr/strings.xml +++ b/res/values-tr/strings.xml @@ -29,11 +29,8 @@ "Ana ekran" "%1$s başlatıcısını Ayarlar\'da varsayılan ana ekran uygulaması olarak ayarlayın" "Bölünmüş ekran" - "En boy oranını değiştir" "%1$s uygulama bilgileri" "%1$s ile ilgili kullanım ayarları" - "Yeni pencere" - "Pencereleri yönet" "Uygulama çiftini kaydedin" "%1$s | %2$s" "Bu uygulama çifti bu cihazda desteklenmiyor" @@ -41,8 +38,6 @@ "Uygulama çifti kullanılamıyor" "Widget\'ı taşımak için dokunup basılı tutun." "Widget\'ı taşımak veya özel işlemleri kullanmak için iki kez dokunup basılı tutun." - "Diğer seçenekler" - "Tüm widget\'ları göster" "%1$d × %2$d" "genişlik: %1$d, yükseklik: %2$d" "%1$s widget\'ı" @@ -69,13 +64,8 @@ "İş" "Görüşmeler" "Not alma" - "Ekle düğmesini göster" - "Ekle düğmesini gizle" "Ekle" "%1$s widget\'ı ekle" - "Tümünü göster" - "Tüm widget\'ları göster" - "Tüm widget\'lar gösteriliyor" "Widget ayarlarını değiştirmek için dokunun" "Widget ayarlarını değiştir" "Uygulamalarda ara" @@ -83,7 +73,6 @@ "\"%1$s\" ile eşleşen uygulama bulunamadı" "Uygulama" "Tüm uygulamalar" - "Uygulama listesi" "Bildirimler" "Kısayolu taşımak için dokunup basılı tutun." "Kısayolu taşımak veya özel işlemleri kullanmak için iki kez dokunup basılı tutun." @@ -101,7 +90,6 @@ "Yükle" "Uygulamayı önerme" "Tahmini Sabitle" - "Balon" "kısayolları yükle" "Uygulamaya, kullanıcı müdahalesi olmadan kısayol ekleme izni verir." "ana ekran ayarlarını ve kısayollarını oku" @@ -118,8 +106,6 @@ "Sayfa %1$d / %2$d" "Ana ekran %1$d / %2$d" "Yeni ana ekran sayfası" - "Etkin" - "Simge durumuna küçültülmüş" "Klasör açıldı, %1$d x %2$d" "Klasörü kapatmak için dokunun" "Yeni adın kaydedilmesi için dokunun" @@ -127,7 +113,6 @@ "Klasörün adı %1$s olarak değiştirildi" "Klasör: %1$s, %2$d öğe" "Klasör: %1$s, %2$d veya daha fazla öğe" - "Adsız klasör" "Uygulama çifti: %1$s ve %2$s" "Duvar kağıdı ve stil" "Ana ekranı düzenleyin" @@ -135,8 +120,6 @@ "Yöneticiniz tarafından devre dışı bırakıldı" "Ana ekranı döndürmeye izin ver" "Telefon döndürüldüğünde" - "Yatay mod" - "Telefonu yatay moda ayarlayın" "Bildirim noktaları" "Açık" "Kapalı" @@ -155,8 +138,7 @@ "%1$s yükleniyor, %2$s tamamlandı" "%1$s indiriliyor, %2$s tamamlandı" "%1$s uygulaması yüklenmek için bekliyor" - "%1$s arşivlendi." - "indir ve geri yükle" + "%1$s arşivlendi. İndirip geri yüklemek için dokunun." "Uygulama güncellemesi gerekli" "Bu simgenin uygulaması güncellenmemiş. Simgeyi kaldırabilir ya da uygulamayı manuel olarak güncelleyerek bu kısayolu yeniden etkinleştirebilirsiniz." "Güncelle" @@ -165,6 +147,7 @@ "Widget listesi kapalı" "Ana ekrana ekle" "Öğeyi buraya taşı" + "Öğe ana ekrana eklendi" "Öğe silindi" "Geri al" "Öğeyi taşı" @@ -184,15 +167,11 @@ "Genişliği azalt" "Yüksekliği azalt" "Widget, %1$s genişlik ve %2$s yükseklik değerine yeniden boyutlandırıldı" - "Kısayol Menüsü" - "%1$s Widget\'ını Yeniden Boyutlandırma Çerçevesi" - "Kapat" + "Kısayollar" "Kapat" "Kapat" "Kişisel" "İş" - "Kişisel uygulamalar sekmesi" - "İş uygulamaları sekmesi" "İş profili" "İş uygulamaları rozetle işaretlenmiş olup BT yöneticisi tarafından görülebilir" "Anladım" @@ -204,12 +183,11 @@ "Anladım" "İş uygulamalarını duraklat" "Devam ettir" - "İş uygulamaları programı" "Filtre" "Başarısız: %1$s" "Gizli alan" "Kurmak veya açmak için dokunun" - "Özel" + "Gizli" "Gizli Alan Ayarları" "Gizli, kilidi açık." "Gizli, kilitli." @@ -217,5 +195,4 @@ "Gizli Alana Geçiş" "Yükle" "Uygulamaları özel alana yükleyin" - "Özel alana dosya ve başka öğeler ekleyin" diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml index 81e8f27491..2a01daeb5c 100644 --- a/res/values-uk/strings.xml +++ b/res/values-uk/strings.xml @@ -29,11 +29,8 @@ "Головний екран" "Зробити %1$s додатком головного екрана за умовчанням у налаштуваннях" "Розділити екран" - "Змінити формат" "Інформація про додаток для %1$s" "Параметри використання (%1$s)" - "Нове вікно" - "Керувати вікнами" "Зберегти пару додатків" "%1$s | %2$s" "Ці два додатки не можна одночасно використовувати на цьому пристрої" @@ -41,8 +38,6 @@ "Одночасне використання двох додатків недоступне" "Натисніть і втримуйте, щоб перемістити віджет." "Двічі натисніть і втримуйте віджет, щоб перемістити його або виконати інші дії." - "Інші опції" - "Показати всі віджети" "%1$d × %2$d" "Ширина – %1$d, висота – %2$d" "Віджет %1$s" @@ -69,13 +64,8 @@ "Робочі" "Розмови" "Створення нотаток" - "Показати кнопку \"Додати\"" - "Сховати кнопку \"Додати\"" "Додати" "Додати віджет \"%1$s\"" - "Показати всі" - "Показати всі віджети" - "Показано всі віджети" "Натисніть, щоб змінити налаштування віджета" "Змінити налаштування віджета" "Пошук додатків" @@ -83,7 +73,6 @@ "Немає додатків для запиту \"%1$s\"" "Додаток" "Усі додатки" - "Список додатків" "Сповіщення" "Натисніть і втримуйте, щоб перемістити ярлик." "Двічі натисніть і втримуйте ярлик, щоб перемістити його або виконати інші дії." @@ -101,7 +90,6 @@ "Установити" "Не пропонувати додаток" "Закріпити передбачений додаток" - "Повідомлення" "створення ярликів" "Дозволяє програмі самостійно додавати ярлики." "читати налаштування та ярлики головного екрана" @@ -118,8 +106,6 @@ "Сторінка %1$d з %2$d" "Головний екран %1$d з %2$d" "Нова сторінка головного екрана" - "Додаток активний" - "Додаток згорнуто" "Папку відкрито (%1$d х %2$d)" "Торкніться, щоб закрити папку" "Торкніться, щоб зберегти зміни" @@ -127,7 +113,6 @@ "Папку перейменовано на %1$s" "Папка \"%1$s\", елементів: %2$d" "Папка \"%1$s\", елементів: %2$d або більше" - "Папка без назви" "Одночасне використання двох додатків: %1$s і %2$s" "Оформлення й стиль" "Редагувати головний екран" @@ -135,8 +120,6 @@ "Вимкнув адміністратор" "Дозволити обертання головного екрана" "Коли телефон обертається" - "Альбомна орієнтація" - "Змінити орієнтацію екрана телефона на альбомну" "Значки сповіщень" "Увімкнено" "Вимкнено" @@ -155,8 +138,7 @@ "%1$s встановлюється, виконано %2$s" "%1$s завантажується, %2$s" "%1$s очікує на завантаження" - "Додаток %1$s заархівовано." - "завантажити й відновити" + "Додаток %1$s заархівовано. Натисніть, щоб завантажити й відновити." "Потрібно оновити додаток" "Додаток для цього значка не оновлено. Ви можете оновити його вручну, щоб знову ввімкнути цю швидку команду, або можете вилучити значок." "Оновити" @@ -165,6 +147,7 @@ "Список віджектів закрито" "Додати на головний екран" "Перемістити елемент сюди" + "Елемент додано на головний екран" "Елемент вилучено" "Відмінити" "Перемістити елемент" @@ -184,15 +167,11 @@ "Зменшити ширину" "Зменшити висоту" "Розміри віджета змінено на %1$s завширшки та %2$s заввишки" - "Меню швидкого доступу" - "Рамка для зміни розміру віджета \"%1$s\"" - "Закрити" + "Ярлики" "Закрити" "Закрити" "Особисті додатки" "Робочі додатки" - "Вкладка особистих додатків" - "Вкладка робочих додатків" "Робочий профіль" "Робочі додатки мають спеціальну позначку. Їх бачить системний адміністратор." "OK" @@ -204,7 +183,6 @@ "Зрозуміло" "Призупинити робочі додатки" "Відновити" - "Розклад призупинення робочих додатків" "Фільтр" "Не вдалося %1$s" "Приватний простір" @@ -217,5 +195,4 @@ "Перехід у приватний простір" "Установити" "Установити додатки в особистому просторі" - "Додавайте файли й інші об’єкти в приватний простір" diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml index e67e4a147e..544357e96a 100644 --- a/res/values-ur/strings.xml +++ b/res/values-ur/strings.xml @@ -29,11 +29,8 @@ "ہوم" "ترتیبات میں %1$s کو بطور ڈیفالٹ ہوم ایپ سیٹ کریں" "اسپلٹ اسکرین" - "تناسبی شرح کو تبدیل کریں" "‏%1$s کے لیے ایپ کی معلومات" "‏%1$s کیلئے استعمال کی ترتیبات" - "نئی ونڈو" - "ونڈوز کا نظم کریں" "ایپس کے جوڑے کو محفوظ کریں" "%1$s | %2$s" "ایپس کا یہ جوڑا اس آلے پر تعاون یافتہ نہیں ہے" @@ -41,8 +38,6 @@ "ایپ کا جوڑا دستیاب نہیں ہے" "ویجیٹ منتقل کرنے کے لیے ٹچ کریں اور پکڑ کر رکھیں۔" "ویجیٹ کو منتقل کرنے یا حسب ضرورت کارروائیاں استعمال کرنے کے لیے دوبار تھپتھپائیں اور پکڑ کر رکھیں۔" - "مزید اختیارات" - "سبھی ویجیٹس دکھائیں" "%1$d × %2$d" "‏%1$d چوڑا اور ‎%2$d اونچا" "%1$s ویجیٹ" @@ -69,13 +64,8 @@ "دفتری ویجیٹس" "گفتگوئیں" "نوٹ لکھنا" - "شامل کریں بٹن دکھائیں" - "شامل کریں بٹن چھپائیں" "شامل کریں" "%1$s ویجیٹ شامل کریں" - "سبھی دکھائیں" - "سبھی ویجیٹس دکھائیں" - "سبھی ویجیٹس دکھائے جا رہے ہیں" "ویجیٹ ترتیبات تبدیل کرنے کے لیے تھپتھپائیں" "ویجیٹ ترتیبات تبدیل کریں" "ایپس تلاش کریں" @@ -83,7 +73,6 @@ "\"%1$s\" سے مماثل کوئی ایپس نہیں ملیں" "ایپ" "سبھی ایپس" - "ایپس کی فہرست" "اطلاعات" "شارٹ کٹ منتقل کرنے کیلیے ٹچ کریں اور پکڑ کر رکھیں۔" "شارٹ کٹ کو منتقل کرنے یا حسب ضرورت کارروائیاں استعمال کرنے کے لیے دوبار تھپتھپائیں اور پکڑ کر رکھیں۔" @@ -101,7 +90,6 @@ "انسٹال کریں" "ایپ تجویز نہ کریں" "پیشگوئی پن کریں" - "بلبلہ" "شارٹ کٹس انسٹال کریں" "کسی ایپ کو صارف کی مداخلت کے بغیر شارٹ کٹس شامل کرنے کی اجازت دیتا ہے۔" "ہوم ترتیبات اور شارٹ کٹس کو پڑھیں" @@ -118,8 +106,6 @@ "‏صفحہ ‎%1$d از ‎%2$d" "‏ہوم اسکرین ‎%1$d از ‎%2$d" "نیا ہوم اسکرین صفحہ" - "فعال" - "کم کر دیا گیا" "فولڈر کھولا گیا، %1$d × %2$d" "فولڈر کو بند کرنے کیلئے تھپتھپائیں" "نام کی تبدیلی محفوظ کرنے کیلئے تھپتھپائیں" @@ -127,7 +113,6 @@ "فولڈر کا نام تبدیل کر کے %1$s کر دیا گیا" "فولڈر: %1$s، %2$d آئٹمز" "فولڈر: %1$s، %2$d یا مزید آئٹمز" - "بلا نام فولڈر" "ایپس کا جوڑا: %1$s اور %2$s" "وال پیپر اور طرز" "ہوم اسکرین میں ترمیم کریں" @@ -135,8 +120,6 @@ "آپ کے منتظم کی طرف سے غیر فعال کر دیا گیا" "ہوم اسکرین گھمانے کی اجازت دیں" "جب فون گھمایا جاتا ہے" - "لینڈ اسکیپ وضع" - "فون کو لینڈ اسکیپ وضع میں سیٹ کریں" "اطلاعاتی ڈاٹس" "آن ہے" "آف ہے" @@ -155,8 +138,7 @@ "%1$s انسٹال کی جا رہی ہے، %2$s مکمل ہو گئی" "%1$s ڈاؤن لوڈ ہو رہا ہے، %2$s مکمل ہو گیا" "%1$s انسٹال ہونے کا انتظار کر رہی ہے" - "‫%1$s کو آرکائیو کر لیا گیا ہے۔" - "ڈاؤن لوڈ کریں اور بحال کریں" + "%1$s کو آرکائیو کر لیا گیا ہے۔ ڈاؤن لوڈ اور بحال کرنے کیلئے تھپتھپائیں۔" "ایپ کی اپ ڈیٹ درکار ہے" "اس آئیکن کیلئے ایپ کو اپ ڈیٹ نہیں کیا گیا ہے۔ آپ اس شارٹ کٹ کو دوبارہ فعال کرنے کے لیے دستی طور پر اپ ڈیٹ کر سکتے ہیں، یا آئیکن کو ہٹا سکتے ہیں۔" "اپ ڈیٹ کریں" @@ -165,6 +147,7 @@ "ویجیٹس کی فہرست بند کر دی گئی" "ہوم اسکرین میں شامل کریں" "آئٹم یہاں منتقل کریں" + "آئٹم کو ہوم اسکرین میں شامل کر دیا گیا" "آئٹم ہٹا دیا گیا" "کالعدم کریں" "آئٹم منتقل کریں" @@ -184,15 +167,11 @@ "چوڑائی کم کریں" "اونچائی کم کریں" "ویجیٹ کے سائز کو چوڑائی %1$s اونچائی %2$s میں تبدیل کر دیا گیا" - "شارٹ کٹ مینیو" - "‫%1$s کے لیے ویجیٹ کا سائز تبدیل کرنے کا فریم" - "بند کریں" + "شارٹ کٹس" "برخاست کریں" "بند کریں" "ذاتی" "دفتری" - "ذاتی ایپس کا ٹیب" - "ورک ایپس کا ٹیب" "دفتری پروفائل" "‏ورک ایپس پر بَیج لگا ہوتا ہے اور آپ کا IT منتظم انہیں دیکھ سکتا ہے" "سمجھ آ گئی" @@ -204,12 +183,11 @@ "سمجھ آ گئی" "ورک ایپس موقوف کریں" "چلائیں" - "ورک ایپس کا شیڈول" "فلٹر" "ناکام ہو گيا: %1$s" "نجی اسپیس" "سیٹ اپ کرنے یا کھولنے کے لیے تھپتھپائیں" - "پرائیویٹ" + "نجی" "نجی اسپیس کی ترتیبات" "نجی اسپیس غیر مقفل ہے۔" "نجی اسپیس مقفل ہے۔" @@ -217,5 +195,4 @@ "نجی اسپیس کی منتقلی" "انسٹال کریں" "پرائیویٹ اسپیس میں ایپس انسٹال کریں" - "پرائیویٹ اسپیس میں فائلز وغیرہ شامل کریں" diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml index 32e5f8c237..5038d4f6ab 100644 --- a/res/values-uz/strings.xml +++ b/res/values-uz/strings.xml @@ -29,11 +29,8 @@ "Bosh ekran" "Sozlamalar orqali %1$s ilovasini birlamchi bosh ekran ilovasi sifatida belgilash" "Ekranni ikkiga ajratish" - "Tomonlar nisbatini oʻzgartirish" "%1$s ilovasi axboroti" "%1$s uchun sarf sozlamalari" - "Yangi oyna" - "Oynalarni boshqarish" "Ilova juftini saqlash" "%1$s | %2$s" "Bu ilova jufti ushbu qurilmada ishlamaydi" @@ -41,8 +38,6 @@ "Ikkita ilovadan bir vaqtda foydalanish mumkin emas" "Vidjetni bosib turgan holatda suring." "Ikki marta bosib va bosib turgan holatda vidjetni tanlang yoki maxsus amaldan foydalaning." - "Yana" - "Barcha vidjetlar" "%1$d × %2$d" "Eni %1$d, bo‘yi %2$d" "%1$s ta vidjet" @@ -69,13 +64,8 @@ "Ish" "Suhbatlar" "Qayd olish" - "Qoʻshish tugmasini koʻrsatish" - "Qoʻshish tugmasini berkitish" "Chiqarish" "%1$s vidjetini chiqarish" - "Hammasi" - "Barcha vidjetlar" - "Barcha vidjetlar chiqarilgan" "Vidjet sozlamalarini oʻzgartirish uchun bosing" "Vidjet sozlamalarini oʻzgartirish" "Ilovalarni qidirish" @@ -83,7 +73,6 @@ "“%1$s” bilan mos hech qanday ilova topilmadi" "Ilova" "Barcha ilovalar" - "Ilovalar roʻyxati" "Bildirishnomalar" "Yorliqni bosib turgan holatda suring." "Ikki marta bosing va yorliqni bosib turgan holatda suring yoki maxsus amaldan foydalaning." @@ -101,7 +90,6 @@ "O‘rnatish" "Tavsiya qilinmasin" "Tavsiyani mahkamlash" - "Pufaklar" "yorliqlar yaratish" "Ilovalarga foydalanuvchidan so‘ramasdan yorliqlar qo‘shishga ruxsat beradi." "Bosh ekrandagi sozlamalar va yorliqlarni koʻrish" @@ -118,8 +106,6 @@ "%2$ddan %1$d ta sahifa" "Uy ekrani %2$ddan %1$d" "Yangi bosh ekran sahifasi" - "Faol" - "Yigʻilgan" "Jild ochildi, %1$d ga %2$d" "Jildni yopish uchun ustiga bosing" "O‘zgarishni saqlash uchun ustiga bosing" @@ -127,7 +113,6 @@ "Jild nomi %1$sga o‘zgartirildi" "Jild: %1$s, %2$d fayllar" "Jild: %1$s, %2$d va undan ortiq fayllar" - "Nomsiz jild" "Ilovani juftlash: %1$s va %2$s" "Fon rasmi va uslubi" "Bosh ekranni tahrirlash" @@ -135,8 +120,6 @@ "Administrator tomonidan o‘chirilgan" "Bosh ekranni burishga ruxsat" "Telefon burilganda" - "Yotiq rejim" - "Telefonni yotiq rejimga oʻtkazish" "Bildirishnoma belgilari" "Yoniq" "Oʻchiq" @@ -155,8 +138,7 @@ "%1$s oʻrnatlmoqda, %2$s yakunlandi" "%1$s yuklab olinmoqda, %2$s bajarildi" "%1$s ilovasi o‘rnatilishi kutilmoqda" - "%1$s arxivlangan." - "yuklab olish va tiklash" + "%1$s arxivlangan. Yuklab olish va tiklash uchun bosing." "Ilovani yangilash zarur" "Bu belgi uchun ilova yangilanmagan. Ushbu yorliqni qayta yoqish uchun oddiy usulda yangilashingiz yoki belgini olib tashlashingiz mumkin." "Yangilash" @@ -165,6 +147,7 @@ "Vidjetlar ro‘yxati yopildi" "Bosh ekranga chiqarish" "Obyektni bu yerga ko‘chirish" + "Obyekt bosh ekranga qo‘shildi" "Element olib tashlandi" "Qaytarish" "Obyektni ko‘chirib o‘tkazish" @@ -184,15 +167,11 @@ "Enini kichraytirish" "Bo‘yini kichraytirish" "Vidjetning eni %1$s, bo‘yi %2$s qilib o‘zgartirildi" - "Tezkor tugma menyusi" - "%1$s vidjeti oʻlchamini oʻzgartirish freymi" - "Yopish" + "Tezkor tugmalar" "Yopish" "Yopish" "Shaxsiy" "Ish" - "Shaxsiy ilovalar sahifasi" - "Ishga oid ilovalar sahifasi" "Ish profili" "Ishga oid ilovalarning maxsus belgisi bor hamda ular administratoringizga koʻrinadi" "OK" @@ -204,7 +183,6 @@ "OK" "Ishga oid ilovalarni pauza qilish" "Pauzadan chiqarish" - "Ishga oid ilovalar jadvali" "Saralash" "Xato: %1$s" "Shaxsiy xona" @@ -217,5 +195,4 @@ "Maxfiy joyga almashtirish" "Oʻrnatish" "Ilovalarni Maxfiy makonga oʻrnatish" - "Maxfiy makonga fayllar va boshqalarni qoʻshish" diff --git a/res/values-v31/colors.xml b/res/values-v31/colors.xml index 8b43f203f5..fa87221f52 100644 --- a/res/values-v31/colors.xml +++ b/res/values-v31/colors.xml @@ -75,8 +75,6 @@ @android:color/system_neutral1_900 - - @android:color/system_neutral2_700 @android:color/system_neutral1_900 @@ -98,13 +96,51 @@ @android:color/system_neutral2_700 - @android:color/system_neutral2_500 + @android:color/system_neutral2_200 @android:color/system_accent1_600 @android:color/system_accent1_0 + + @android:color/system_accent1_200 + + @android:color/system_accent1_900 + @android:color/system_neutral1_1000 + @android:color/system_accent2_700 + @android:color/system_accent3_700 + @android:color/system_accent1_700 + @android:color/system_accent2_900 + @android:color/system_accent3_900 + @android:color/system_accent1_900 + @android:color/system_accent2_200 + #410000 + @android:color/system_accent2_900 + @android:color/system_neutral1_100 + @android:color/system_accent3_200 + @android:color/system_accent3_900 + @android:color/system_accent1_200 + @android:color/system_accent2_100 + #FFDAD5 + @android:color/system_accent1_900 + @android:color/system_accent1_200 + @android:color/system_accent2_100 + @android:color/system_accent3_100 + @android:color/system_accent3_100 + @android:color/system_accent1_100 + @android:color/system_neutral1_50 + @android:color/system_accent1_100 + @android:color/system_accent2_0 + @android:color/system_accent3_0 + #FFFFFF + @android:color/system_neutral2_700 + @android:color/system_neutral2_500 + @android:color/system_neutral2_200 + @android:color/system_accent1_0 @android:color/system_neutral1_900 + @android:color/system_accent1_600 + @android:color/system_accent2_600 + @android:color/system_accent3_600 diff --git a/res/values-v31/styles.xml b/res/values-v31/styles.xml index 6ed7dd67bd..932ce38e59 100644 --- a/res/values-v31/styles.xml +++ b/res/values-v31/styles.xml @@ -17,7 +17,7 @@ */ --> - + \ No newline at end of file diff --git a/res/values-v33/style.xml b/res/values-v33/style.xml new file mode 100644 index 0000000000..1261b232cc --- /dev/null +++ b/res/values-v33/style.xml @@ -0,0 +1,40 @@ + + + + + + + + \ No newline at end of file diff --git a/res/values-v34/colors.xml b/res/values-v34/colors.xml index 4f3a769a61..26d3712bb1 100644 --- a/res/values-v34/colors.xml +++ b/res/values-v34/colors.xml @@ -27,108 +27,4 @@ @android:color/system_on_surface_light @android:color/system_on_surface_variant_light - - @android:color/system_on_surface_variant_light - - - @android:color/system_primary_container_light - @android:color/system_on_primary_container_light - @android:color/system_primary_light - @android:color/system_on_primary_light - @android:color/system_secondary_container_light - @android:color/system_on_secondary_container_light - @android:color/system_secondary_light - @android:color/system_on_secondary_light - @android:color/system_tertiary_container_light - @android:color/system_on_tertiary_container_light - @android:color/system_tertiary_light - @android:color/system_on_tertiary_light - @android:color/system_background_light - @android:color/system_on_background_light - @android:color/system_surface_light - @android:color/system_on_surface_light - @android:color/system_surface_container_low_light - @android:color/system_surface_container_lowest_light - @android:color/system_surface_container_light - @android:color/system_surface_container_high_light - @android:color/system_surface_container_highest_light - @android:color/system_surface_bright_light - @android:color/system_surface_dim_light - @android:color/system_surface_variant_light - @android:color/system_on_surface_variant_light - @android:color/system_outline_light - @android:color/system_outline_variant_light - @android:color/system_error_light - @android:color/system_on_error_light - @android:color/system_error_container_light - @android:color/system_on_error_container_light - @android:color/system_control_activated_light - @android:color/system_control_normal_light - @android:color/system_control_highlight_light - @android:color/system_text_primary_inverse_light - @android:color/system_text_secondary_and_tertiary_inverse_light - @android:color/system_text_primary_inverse_disable_only_light - @android:color/system_text_secondary_and_tertiary_inverse_disabled_light - @android:color/system_text_hint_inverse_light - @android:color/system_palette_key_color_primary_light - @android:color/system_palette_key_color_secondary_light - @android:color/system_palette_key_color_tertiary_light - @android:color/system_palette_key_color_neutral_light - @android:color/system_palette_key_color_neutral_variant_light - @android:color/system_primary_container_dark - @android:color/system_on_primary_container_dark - @android:color/system_primary_dark - @android:color/system_on_primary_dark - @android:color/system_secondary_container_dark - @android:color/system_on_secondary_container_dark - @android:color/system_secondary_dark - @android:color/system_on_secondary_dark - @android:color/system_tertiary_container_dark - @android:color/system_on_tertiary_container_dark - @android:color/system_tertiary_dark - @android:color/system_on_tertiary_dark - @android:color/system_background_dark - @android:color/system_on_background_dark - @android:color/system_surface_dark - @android:color/system_on_surface_dark - @android:color/system_surface_container_low_dark - @android:color/system_surface_container_lowest_dark - @android:color/system_surface_container_dark - @android:color/system_surface_container_high_dark - @android:color/system_surface_container_highest_dark - @android:color/system_surface_bright_dark - @android:color/system_surface_dim_dark - @android:color/system_surface_variant_dark - @android:color/system_on_surface_variant_dark - @android:color/system_outline_dark - @android:color/system_outline_variant_dark - @android:color/system_error_dark - @android:color/system_on_error_dark - @android:color/system_error_container_dark - @android:color/system_on_error_container_dark - @android:color/system_control_activated_dark - @android:color/system_control_normal_dark - @android:color/system_control_highlight_dark - @android:color/system_text_primary_inverse_dark - @android:color/system_text_secondary_and_tertiary_inverse_dark - @android:color/system_text_primary_inverse_disable_only_dark - @android:color/system_text_secondary_and_tertiary_inverse_disabled_dark - @android:color/system_text_hint_inverse_dark - @android:color/system_palette_key_color_primary_dark - @android:color/system_palette_key_color_secondary_dark - @android:color/system_palette_key_color_tertiary_dark - @android:color/system_palette_key_color_neutral_dark - @android:color/system_palette_key_color_neutral_variant_dark - @android:color/system_primary_fixed - @android:color/system_primary_fixed_dim - @android:color/system_on_primary_fixed - @android:color/system_on_primary_fixed_variant - @android:color/system_secondary_fixed - @android:color/system_secondary_fixed_dim - @android:color/system_on_secondary_fixed - @android:color/system_on_secondary_fixed_variant - @android:color/system_tertiary_fixed - @android:color/system_tertiary_fixed_dim - @android:color/system_on_tertiary_fixed - @android:color/system_on_tertiary_fixed_variant diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml index 6f3b09d870..e5252b1fc6 100644 --- a/res/values-vi/strings.xml +++ b/res/values-vi/strings.xml @@ -29,11 +29,8 @@ "Màn hình chính" "Đặt %1$s làm trình chạy mặc định trong phần Cài đặt" "Chia đôi màn hình" - "Thay đổi tỷ lệ khung hình" "Thông tin ứng dụng cho %1$s" "Chế độ cài đặt mức sử dụng %1$s" - "Cửa sổ mới" - "Quản lý cửa sổ" "Lưu cặp ứng dụng" "%1$s | %2$s" "Cặp ứng dụng này không hoạt động được trên thiết bị này" @@ -41,8 +38,6 @@ "Hiện không có cặp ứng dụng này" "Chạm và giữ để di chuyển một tiện ích." "Nhấn đúp và giữ để di chuyển một tiện ích hoặc sử dụng các thao tác tùy chỉnh." - "Lựa chọn khác" - "Hiện tất cả tiện ích" "%1$d × %2$d" "Rộng %1$d x cao %2$d" "Tiện ích %1$s" @@ -69,13 +64,8 @@ "Công việc" "Cuộc trò chuyện" "Ghi chú" - "Hiện nút thêm" - "Ẩn nút thêm" "Thêm" "Thêm tiện ích %1$s" - "Hiện tất cả" - "Hiện tất cả tiện ích" - "Đang hiện tất cả tiện ích" "Nhấn để thay đổi chế độ cài đặt tiện ích" "Thay đổi chế độ cài đặt tiện ích" "Tìm kiếm ứng dụng" @@ -83,7 +73,6 @@ "Không tìm thấy ứng dụng nào phù hợp với \"%1$s\"" "Ứng dụng" "Tất cả ứng dụng" - "Danh sách ứng dụng" "Thông báo" "Chạm và giữ để di chuyển một lối tắt." "Nhấn đúp và giữ để di chuyển một lối tắt hoặc sử dụng các thao tác tùy chỉnh." @@ -101,7 +90,6 @@ "Cài đặt" "Không gợi ý ứng dụng" "Ghim ứng dụng dự đoán" - "Bong bóng" "cài đặt lối tắt" "Cho phép ứng dụng thêm lối tắt mà không cần sự can thiệp của người dùng." "đọc lối tắt và các chế độ cài đặt trên màn hình chính" @@ -118,8 +106,6 @@ "Trang %1$d / %2$d" "Màn hình chính %1$d / %2$d" "Trang màn hình chính mới" - "Đang hoạt động" - "Đã thu nhỏ" "Đã mở thư mục, %1$d x %2$d" "Nhấn để đóng thư mục" "Nhấn để lưu đổi tên" @@ -127,7 +113,6 @@ "Đã đổi tên thư mục thành %1$s" "Thư mục: %1$s, %2$d mục" "Thư mục: %1$s, %2$d mục trở lên" - "Thư mục chưa đặt tên" "Cặp ứng dụng: %1$s%2$s" "Hình nền và phong cách" "Chỉnh sửa Màn hình chính" @@ -135,8 +120,6 @@ "Bị tắt bởi quản trị viên của bạn" "Cho phép xoay màn hình chính" "Khi xoay điện thoại" - "Chế độ ngang" - "Đặt điện thoại ở chế độ ngang" "Dấu chấm thông báo" "Đang bật" "Tắt" @@ -144,7 +127,7 @@ "Để hiển thị Dấu chấm thông báo, hãy bật thông báo ứng dụng cho %1$s" "Thay đổi cài đặt" "Hiện dấu chấm thông báo" - "Tuỳ chọn cho nhà phát triển" + "Tùy chọn cho nhà phát triển" "Thêm biểu tượng ứng dụng vào màn hình chính" "Cho ứng dụng mới" "Không xác định" @@ -155,8 +138,7 @@ "Đang cài đặt %1$s, hoàn tất %2$s" "Đang tải xuống %1$s, %2$s hoàn tất" "Đang chờ cài đặt %1$s" - "%1$s đã được lưu trữ." - "tải xuống và khôi phục" + "%1$s đã được lưu trữ. Hãy nhấn để tải xuống và khôi phục." "Cần cập nhật ứng dụng" "Ứng dụng cho biểu tượng này chưa được cập nhật. Bạn có thể cập nhật theo cách thủ công để bật lại phím tắt này hoặc xóa biểu tượng." "Cập nhật" @@ -165,6 +147,7 @@ "Đã đóng danh sách tiện ích" "Thêm vào màn hình chính" "Di chuyển mục vào đây" + "Đã thêm mục vào màn hình chính" "Đã xóa mục" "Hủy" "Di chuyển mục" @@ -184,15 +167,11 @@ "Giảm chiều rộng" "Giảm chiều cao" "Đã đổi kích thước tiện ích thành chiều rộng %1$s chiều cao %2$s" - "Trình đơn lối tắt" - "Khung đổi kích thước tiện ích của %1$s" - "Đóng" + "Lối tắt" "Loại bỏ" "Đóng" "Cá nhân" "Công việc" - "Thẻ ứng dụng cá nhân" - "Thẻ ứng dụng công việc" "Hồ sơ công việc" "Các ứng dụng công việc được gắn huy hiệu và quản trị viên CNTT có thể nhìn thấy" "OK" @@ -204,7 +183,6 @@ "Tôi hiểu" "Tạm dừng các ứng dụng công việc" "Bỏ tạm dừng" - "Lịch biểu cho ứng dụng công việc" "Bộ lọc" "Không thực hiện được thao tác: %1$s" "Không gian riêng tư" @@ -217,5 +195,4 @@ "Chuyển đổi sang không gian riêng tư" "Cài đặt" "Cài đặt ứng dụng vào Không gian riêng tư" - "Thêm tệp và nội dung khác vào Không gian riêng tư" diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml index 617a15834c..7a761585f4 100644 --- a/res/values-zh-rCN/strings.xml +++ b/res/values-zh-rCN/strings.xml @@ -29,11 +29,8 @@ "主屏幕" "在“设置”中将“%1$s”设为默认主屏幕应用" "分屏" - "更改宽高比" "%1$s 的应用信息" "%1$s的使用设置" - "新窗口" - "管理窗口" "保存应用组合" "%1$s | %2$s" "在该设备上无法使用此应用对" @@ -41,13 +38,11 @@ "应用对不可用" "轻触并按住即可移动微件。" "点按两次并按住微件即可移动该微件或使用自定义操作。" - "更多选项" - "显示所有微件" "%1$d × %2$d" "宽 %1$d,高 %2$d" "“%1$s”微件" "“%1$s”微件,宽 %2$d,高 %3$d" - "轻触并按住此微件即可将其拖拽到主屏幕上任意位置" + "轻触并按住此微件即可在主屏幕上随意移动" "添加到主屏幕" "已将“%1$s”微件添加到主屏幕" "建议" @@ -63,19 +58,14 @@ "微件" "搜索" "清除搜索框中的文字" - "没有可用的微件和快捷方式" + "无法使用微件和快捷方式" "未找到任何微件或快捷方式" "个人" "工作" "对话" "记事" - "显示“添加”按钮" - "隐藏“添加”按钮" "添加" "添加“%1$s”微件" - "全部显示" - "显示所有微件" - "正在显示所有微件" "点按即可更改微件设置" "更改微件设置" "搜索应用" @@ -83,7 +73,6 @@ "未找到与“%1$s”相符的应用" "应用" "所有应用" - "应用列表" "通知" "轻触并按住快捷方式即可移动该快捷方式。" "点按两次并按住快捷方式即可移动该快捷方式或使用自定义操作。" @@ -101,7 +90,6 @@ "安装" "不要推荐此应用" "固定预测的应用" - "气泡框" "安装快捷方式" "允许应用自行添加快捷方式。" "读取主屏幕设置和快捷方式" @@ -118,8 +106,6 @@ "第%1$d页,共%2$d页" "主屏幕:第%1$d屏,共%2$d屏" "主屏幕新页面" - "活跃" - "已最小化" "文件夹已打开,大小为%1$d×%2$d" "点按可关闭文件夹" "点按可保存新名称" @@ -127,16 +113,13 @@ "已将文件夹重命名为“%1$s”" "文件夹:%1$s%2$d 个项目" "文件夹:%1$s%2$d 个或更多项目" - "未命名文件夹" "应用对:“%1$s”和“%2$s”" - "壁纸与风格" + "壁纸与个性化" "修改主屏幕" "主屏幕设置" "已被您的管理员停用" "允许旋转主屏幕" "手机旋转时" - "横屏模式" - "将手机设为横屏模式" "通知圆点" "已开启" "已关闭" @@ -155,8 +138,7 @@ "正在安装%1$s,已完成 %2$s" "正在下载%1$s,已完成 %2$s" "%1$s正在等待安装" - "已归档“%1$s”。" - "下载并恢复" + "已归档“%1$s”。点按即可进行下载并恢复。" "需要更新应用" "此图标对应的应用未更新。您可以手动更新以重新启用该快捷方式,或者移除此图标。" "更新" @@ -165,6 +147,7 @@ "微件列表已关闭" "添加到主屏幕" "将项目移至此处" + "已将项目添加到主屏幕" "项目已移除" "撤消" "移动项目" @@ -184,15 +167,11 @@ "减小宽度" "减小高度" "微件尺寸已调整为:宽度 %1$s,高度 %2$s" - "快捷键菜单" - "%1$s的微件调整大小框架" - "关闭" + "快捷方式" "关闭" "关闭" "个人" "工作" - "“个人应用”标签页" - "“工作应用”标签页" "工作资料" "工作应用都有相应的标志,且您的 IT 管理员可以看到它们" "知道了" @@ -204,7 +183,6 @@ "知道了" "暂停工作应用" "取消暂停" - "工作应用时间表" "过滤器" "失败:%1$s" "私密空间" @@ -217,5 +195,4 @@ "私密空间转换" "安装" "将应用安装到私密空间" - "将文件等内容添加到私密空间" diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml index d343779877..ffd9e0b80c 100644 --- a/res/values-zh-rHK/strings.xml +++ b/res/values-zh-rHK/strings.xml @@ -29,11 +29,8 @@ "主畫面" "在「設定」中將「%1$s」設定為預設主頁應用程式" "分割螢幕" - "變更長寬比" "%1$s 的應用程式資料" "「%1$s」的用量設定" - "新視窗" - "管理視窗" "儲存應用程式配對" "%1$s | %2$s" "此裝置不支援此應用程式配對" @@ -41,8 +38,6 @@ "應用程式配對無法使用" "輕觸並按住即可移動小工具。" "㩒兩下之後㩒住,就可以郁小工具或者用自訂操作。" - "更多選項" - "顯示所有小工具" "%1$d × %2$d" "%1$d 闊,%2$d 高" "「%1$s」小工具" @@ -69,13 +64,8 @@ "工作" "對話" "做筆記" - "顯示新增按鈕" - "隱藏新增按鈕" "新增" "加%1$s小工具" - "顯示全部" - "顯示所有小工具" - "顯示所有小工具" "輕按即可變更小工具設定" "變更小工具設定" "搜尋應用程式" @@ -83,7 +73,6 @@ "找不到與「%1$s」相符的應用程式" "應用程式" "所有應用程式" - "應用程式清單" "通知" "輕觸並按住即可移動捷徑。" "㩒兩下之後㩒住,就可以郁捷徑或者用自訂操作。" @@ -101,7 +90,6 @@ "安裝" "不要提供應用程式建議" "固定預測" - "氣泡" "安裝捷徑" "允許應用程式無需使用者許可也可新增捷徑。" "讀取主畫面設定和捷徑" @@ -118,8 +106,6 @@ "第 %1$d 頁,共 %2$d 頁" "主畫面 %1$d,共 %2$d 個" "新主畫面頁面" - "運作中" - "已縮到最小" "資料夾已開啟 (%1$d x %2$d)" "輕按即可關閉資料夾" "輕按即可儲存新名稱" @@ -127,7 +113,6 @@ "資料夾已重新命名為「%1$s」" "資料夾:%1$s%2$d 個項目" "資料夾:%1$s%2$d 個或以上的項目" - "未命名的資料夾" "應用程式配對:%1$s%2$s" "桌布和樣式" "編輯主畫面" @@ -135,8 +120,6 @@ "已由你的管理員停用" "允許旋轉主畫面" "隨手機旋轉" - "水平模式" - "將手機設定為水平模式" "通知圓點" "開啟" "關閉" @@ -155,8 +138,7 @@ "正在安裝「%1$s」(已完成 %2$s)" "正在下載 %1$s,已完成 %2$s" "正在等待安裝 %1$s" - "「%1$s」已封存。" - "下載及還原" + "「%1$s」已封存。輕按即可下載並還原。" "必須更新應用程式" "你尚未更新這個圖示代表的應用程式。你可以手動更新以重新啟用此快速鍵,或者移除圖示。" "更新" @@ -165,6 +147,7 @@ "已經關閉嘅小工具清單" "加去主畫面" "移動項目至這裡" + "已將項目加入至主畫面" "項目已移除" "復原" "移動項目" @@ -184,15 +167,11 @@ "減少闊度" "減少高度" "已調整小工具的大小至闊 %1$s%2$s" - "快速鍵選單" - "「%1$s」嘅小工具調整大小框架" - "閂" + "捷徑" "關閉" "關閉" "個人" "工作" - "個人應用程式分頁" - "工作應用程式分頁" "工作設定檔" "工作應用程式均加有標誌。你的 IT 管理員可以看到這些應用程式" "知道了" @@ -204,7 +183,6 @@ "知道了" "暫停工作應用程式" "取消暫停" - "工作應用程式時間表" "篩選器" "操作失敗:%1$s" "私人空間" @@ -217,5 +195,4 @@ "轉為「私人空間」" "安裝" "將應用程式安裝在「私人空間」中" - "新增檔案和其他內容到「私人空間」" diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml index 75743f35c5..264d6079bf 100644 --- a/res/values-zh-rTW/strings.xml +++ b/res/values-zh-rTW/strings.xml @@ -29,11 +29,8 @@ "主畫面" "前往「設定」將「%1$s」設為預設主畫面應用程式" "分割畫面" - "變更顯示比例" "「%1$s」的應用程式資訊" "「%1$s」的用量設定" - "新視窗" - "管理視窗" "儲存應用程式配對" "%1$s | %2$s" "這部裝置不支援這組應用程式配對" @@ -41,8 +38,6 @@ "這個應用程式配對無法使用" "按住即可移動小工具。" "輕觸兩下並按住即可移動小工具或使用自訂操作。" - "更多選項" - "顯示所有小工具" "%1$d × %2$d" "寬度為 %1$d,高度為 %2$d" "「%1$s」小工具" @@ -69,13 +64,8 @@ "工作" "對話" "做筆記" - "顯示新增按鈕" - "隱藏新增按鈕" "新增" "新增「%1$s」小工具" - "全部顯示" - "顯示所有小工具" - "現已顯示所有小工具" "輕觸即可變更小工具設定" "變更小工具設定" "搜尋應用程式" @@ -83,7 +73,6 @@ "找不到與「%1$s」相符的應用程式" "應用程式" "所有應用程式" - "應用程式清單" "通知" "按住即可移動捷徑。" "輕觸兩下並按住即可移動捷徑或使用自訂操作。" @@ -101,7 +90,6 @@ "安裝" "不要建議此應用程式" "固定預測的應用程式" - "泡泡" "安裝捷徑" "允許應用程式自動新增捷徑。" "讀取主畫面設定和捷徑" @@ -118,8 +106,6 @@ "第 %1$d 頁,共 %2$d 頁" "主畫面:第 %1$d 頁,共 %2$d 頁" "新的主畫面頁面" - "運作中" - "最小化" "資料夾已開啟 (%1$d x %2$d)" "輕觸即可關閉資料夾" "輕觸即可儲存新名稱" @@ -127,7 +113,6 @@ "已將資料夾重新命名為「%1$s」" "資料夾:%1$s%2$d 個項目" "資料夾:%1$s%2$d 個以上的項目" - "未命名的資料夾" "應用程式配對:「%1$s」與「%2$s」" "桌布和樣式" "編輯主畫面" @@ -135,8 +120,6 @@ "已由你的管理員停用" "允許旋轉主畫面" "當手機旋轉時" - "橫向模式" - "將手機設為橫向模式" "通知圓點" "開啟" "關閉" @@ -155,8 +138,7 @@ "正在安裝「%1$s」(已完成 %2$s)" "正在下載「%1$s」,已完成 %2$s" "正在等待安裝「%1$s」" - "「%1$s」已封存。" - "下載及還原" + "「%1$s」已封存。輕觸即可下載並還原。" "必須更新應用程式" "這個圖示代表的應用程式未更新。手動更新即可重新啟用這個捷徑,你也可以移除圖示。" "更新" @@ -165,6 +147,7 @@ "已關閉小工具清單" "新增至主畫面" "將項目移至這裡" + "已將項目新增到主畫面" "已移除項目" "復原" "移動項目" @@ -184,15 +167,11 @@ "減少寬度" "減少高度" "已將小工具的寬度和高度分別調整為 %1$s%2$s" - "快速鍵選單" - "「%1$s」的小工具調整大小框架" - "關閉" + "捷徑" "關閉" "關閉" "個人" "工作" - "個人應用程式分頁" - "工作應用程式分頁" "工作資料夾" "工作應用程式會加上標記,且你的 IT 管理員可以看到這類應用程式" "我知道了" @@ -204,7 +183,6 @@ "我知道了" "暫停工作應用程式" "取消暫停" - "工作應用程式時間表" "篩選器" "失敗:%1$s" "私人空間" @@ -217,5 +195,4 @@ "轉換私人空間狀態" "安裝" "將應用程式安裝在私人空間中" - "將檔案和其他內容新增至私人空間" diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml index a6f4d59964..be6cd493fe 100644 --- a/res/values-zu/strings.xml +++ b/res/values-zu/strings.xml @@ -29,11 +29,8 @@ "Ikhaya" "Setha i-%1$s njenge-app yasekhaya ezenzakalelayo Kumasethingi" "Hlukanisa isikrini" - "Shintsha ukubukeka kwesilinganiselo" "Ulwazi lwe-App ye-%1$s" "Amasethingi okusetshenziswa ka-%1$s" - "Iwindi Elisha" - "Phatha Amawindi" "Londoloza i-app ebhangqiwe" "%1$s | %2$s" "Lokhu kubhanqwa kwe-app akusekelwa kule divayisi" @@ -41,8 +38,6 @@ "Ukubhangqwa kwe-app akutholakali" "Thinta uphinde ubambe ukuze uhambise iwijethi." "Thepha kabili uphinde ubambe ukuze uhambise iwijethi noma usebenzise izindlela ezingokwezifiso." - "Okungakhethwa kukho okuningi" - "Bonisa wonke amawijethi" "%1$d × %2$d" "%1$d ububanzi ngokungu-%2$d ukuya phezulu" "Iwijethi elingu-%1$s" @@ -69,13 +64,8 @@ "Umsebenzi" "Izingxoxo" "Ukuthatha amanothi" - "Bonisa inkinobho yokwengeza" - "Fihla inkinobho yokwengeza" "Engeza" "Engeza iwijethi ye-%1$s" - "Bonisa konke" - "Bonisa wonke amawijethi" - "Ibonisa wonke amawijethi" "Thepha ukuze ushintshe amasethingi ewijethi" "Shintsha amasethingi ewijethi" "Sesha izinhlelo zokusebenza" @@ -83,7 +73,6 @@ "Azikho izinhlelo zokusebenza ezitholiwe ezifana ne-\"%1$s\"" "Uhlelo lokusebenza" "Wonke ama-app" - "Uhlu lwama-app" "Izaziso" "Thinta uphinde ubambe ukuze uhambise isinqamuleli." "Thepha kabili uphinde ubambe ukuze uhambise isinqamuleli noma usebenzise izenzo ezingokwezifiso." @@ -101,7 +90,6 @@ "Faka" "Ungaphakamisi uhlelo lokusebenza" "Ukubikezela Iphinikhodi" - "Ibhamuza" "faka izinqamuleli" "Ivumela uhlelo lokusebenza ukufaka izinqamuleli ngaphandle kokungenelela komsebenzisi." "funda amasethingi wasekhaya nezinqamuleli" @@ -118,8 +106,6 @@ "Ikhasi elingu-%1$d kwangu-%2$d" "Isikrini sasekhaya esingu-%1$d se-%2$d" "Ikhasi elisha lesikrini sasekhaya" - "Kuyasebenza" - "Kuncishisiwe" "Ifolda ivuliwe, %1$d nge-%2$d" "Thepa ukuze uvale ifolda" "Thepha ukuze ulondoloze ukuqamba kabusha" @@ -127,7 +113,6 @@ "Ifolda iqanjwe kabusha ngo-%1$s" "Ifolda: %1$s, %2$d izinto" "Ifolda: %1$s, %2$d noma izinto eziningi" - "Ifolda engenagama" "Ama-app abhangqwayo: I-%1$s ne-%2$s" "Isithombe sangemuva nesitayela" "Hlela Isikrini Sasekhaya" @@ -135,8 +120,6 @@ "Kukhutshazwe umlawuli wakho" "Vumela ukuzungezisa kwesikrini sasekhaya" "Uma ifoni iphendukiswa" - "Imodi yokuvundla" - "Setha ifoni kumodi yokuvundla" "Amacashazi esaziso" "Vuliwe" "Valiwe" @@ -155,8 +138,7 @@ "I-%1$s iyafakwa, seyiqede %2$s" "I-%1$s iyalandwa, %2$s kuqediwe" "%1$s ilinde ukufakwa" - "Okuthi %1$s kufakwe kungobo yomlando." - "dawuniloda uphinde ubuyisele" + "Okuthi %1$s kufakwe kungobo yomlando. Thepha ukuze udawunilode futhi ubuyisele." "Kudingeka isibuyekezo se-app" "I-app yalesi sithonjana ibuyekeziwe. Ungabuyekeza mathupha ukuze uphinde unike amandla lesi sinqamuleli, noma ususe isithonjana." "Vuselela" @@ -165,6 +147,7 @@ "Uhlu lwamawijethi luvaliwe" "Faka kusikrini sasekhaya" "Hambisa into lapha" + "Into ingezwe kusikrini sasekhaya" "Into isusiwe" "Susa" "Hambisa into" @@ -184,15 +167,11 @@ "Nciphisa ububanzi" "Nciphisa ubude" "Iwijethi inikezwe usayizi omusha ngobubanzi obungu-%1$s ubude obungu-%2$s" - "Imenyu Yezinqamuleli" - "Uhlaka Lokushintsha Usayizi Wewijethi we-%1$s" - "Vala" + "Izinqamuleli" "Cashisa" "Vala" "Okomuntu siqu" "Umsebenzi" - "Ithebhu yama-app omuntu siqu" - "Ithebhu yama-app omsebenzi" "Iphrofayela yomsebenzi" "Ama-app omsebenzi anebheji futhi ayabonakala kumphathi wakho we-IT" "Ngiyezwa" @@ -204,7 +183,6 @@ "Ngiyezwa" "Misa ama-app omsebenzi" "Susa ukumisa" - "Ishejuli yama-app omsebenzi" "Hlunga" "Yehlulekile: %1$s" "Isikhala esiyimfihlo" @@ -217,5 +195,4 @@ "Ukuguqulwa Kwendawo Yangasese" "Faka" "Faka ama-app Endaweni Engasese" - "Faka amafayela nokunye Endaweni engasese" diff --git a/res/values/attrs.xml b/res/values/attrs.xml index dfcc390455..c46b7c7e3e 100644 --- a/res/values/attrs.xml +++ b/res/values/attrs.xml @@ -19,13 +19,13 @@ - + @@ -40,13 +40,11 @@ - - @@ -63,8 +61,6 @@ - - @@ -80,8 +76,6 @@ - - @@ -165,20 +159,8 @@ - - - - - - - - - - - - @@ -229,9 +211,6 @@ defaults to @dimen/taskbar_button_margin_default --> - - - @@ -266,18 +245,8 @@ - - - - - - - - - - @@ -612,6 +581,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/values/colors.xml b/res/values/colors.xml index 914ffd64c9..ce8096404b 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -17,7 +17,8 @@ ** limitations under the License. */ --> - + #FFC1C1C1 @@ -90,7 +91,6 @@ #0842A0 #000000 - #000000 #00668B #B5CAD7 @@ -98,12 +98,12 @@ #40484D ?android:attr/colorAccent + #A8C7FA + #041E49 #EFEDED #FAF9F8 #1F1F1F - #4C4D50 - @color/system_on_surface_variant_light #1F1F1F #444746 #C2E7FF @@ -114,23 +114,15 @@ #E3E3E3 #FFFFFF #444746 - #747775 + #C4C7C5 #0B57D0 #0B57D0 - - @color/widget_picker_secondary_surface_color_light - - - @color/widget_picker_header_app_title_color_light - - @color/system_on_surface_light - @color/system_on_surface_variant_light + @color/material_color_on_surface + @color/material_color_on_surface_variant #1F2020 #393939 #E3E3E3 - #CCCDCF - @color/system_on_surface_variant_dark #E3E3E3 #C4C7C5 #004A77 @@ -141,165 +133,54 @@ #343535 #2D312F #C4C7C5 - #8e918f + #444746 #062E6F #FFFFFF - - @color/widget_picker_secondary_surface_color_dark - - - @color/widget_picker_header_app_title_color_dark - - @color/system_on_surface_dark - @color/system_on_surface_variant_dark + @color/material_color_on_surface + @color/material_color_on_surface_variant + #3F4759 + #583E5B #FFFFFF + #2B4678 + #141B2C + #29132D + #F5F3F7 + #001A41 + #BFC6DC + #410000 + #141B2C + #E3E2E6 + #DEBCDF + #29132D + #ADC6FF + #DBE2F9 + #FFDAD5 + #001A41 + #ADC6FF + #DBE2F9 + #121316 + #E1E2EC + #FBD7FC + #FBD7FC + #D8E2FF + #1B1B1F + #D8E2FF + #FFFFFF + #FFFFFF + #DBD9DD + #FAF9FD + #FFFFFF + #FAF9FD + #E9E7EC + #E3E2E6 + #44474F + #72747D + #C4C7C5 + #FFFFFF #1B1B1F - - #D9E2FF - #001945 - #475D92 - #FFFFFF - #DCE2F9 - #151B2C - #575E71 - #FFFFFF - #FDD7FA - #2A122C - #725572 - #FFFFFF - #FAF8FF - #1A1B20 - #FAF8FF - #1A1B20 - #F4F3FA - #FFFFFF - #EEEDF4 - #E8E7EF - #E2E2E9 - #FAF8FF - #DAD9E0 - #E1E2EC - #44464F - #757780 - #C5C6D0 - #BA1A1A - #FFFFFF - #FFDAD6 - #410002 - #D9E2FF - #44464F - #000000 - #E2E2E9 - #C5C6D0 - #E2E2E9 - #E2E2E9 - #E2E2E9 - #6076AC - #70778B - #8C6D8C - #76777D - #757780 - #2F4578 - #D9E2FF - #B0C6FF - #152E60 - #404659 - #DCE2F9 - #C0C6DC - #2A3042 - #593D59 - #FDD7FA - #E0BBDD - #412742 - #121318 - #E2E2E9 - #121318 - #E2E2E9 - #1A1B20 - #0C0E13 - #1E1F25 - #282A2F - #33343A - #38393F - #121318 - #44464F - #C5C6D0 - #8F9099 - #44464F - #FFB4AB - #690005 - #93000A - #FFDAD6 - #2F4578 - #C5C6D0 - #FFFFFF - #1A1B20 - #44464F - #1A1B20 - #1A1B20 - #1A1B20 - #6076AC - #70778B - #8C6D8C - #76777D - #757780 - #D9E2FF - #B0C6FF - #001945 - #2F4578 - #DCE2F9 - #C0C6DC - #151B2C - #404659 - #FDD7FA - #E0BBDD - #2A122C - #593D59 - - @color/system_on_secondary_fixed_variant - @color/system_on_tertiary_fixed_variant - @color/system_surface_container_lowest_light - @color/system_on_primary_fixed_variant - @color/system_on_secondary_container_light - @color/system_on_tertiary_container_light - @color/system_surface_container_low_light - @color/system_on_primary_container_light - @color/system_secondary_fixed_dim - @color/system_on_error_container_light - @color/system_on_secondary_fixed - @color/system_on_surface_dark - @color/system_tertiary_fixed_dim - @color/system_on_tertiary_fixed - @color/system_primary_fixed_dim - @color/system_secondary_container_light - @color/system_error_container_light - @color/system_on_primary_fixed - @color/system_primary_dark - @color/system_secondary_fixed - @color/system_surface_dark - @color/system_surface_variant_light - @color/system_tertiary_container_light - @color/system_tertiary_fixed - @color/system_primary_container_light - @color/system_on_background_light - @color/system_primary_fixed - @color/system_on_secondary_light - @color/system_on_tertiary_light - @color/system_surface_dim_light - @color/system_surface_bright_light - @color/system_on_error_light - @color/system_surface_light - @color/system_surface_container_high_light - @color/system_surface_container_highest_light - @color/system_on_surface_variant_light - @color/system_outline_light - @color/system_outline_variant_light - @color/system_on_primary_light - @color/system_on_surface_light - @color/system_surface_container_light - @color/system_primary_light - @color/system_secondary_light - @color/system_tertiary_light - @color/system_error_light + #EFEDF1 + #445E91 + #575E71 + #715573 diff --git a/res/values/config.xml b/res/values/config.xml index fc636a5055..2a3b588245 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -67,17 +67,25 @@ + + + + + + + + @@ -110,12 +118,6 @@ 0.95 400 - - 0.6 - 900 - 0.8 - 900 - 0 @@ -223,7 +225,4 @@ of the same name in SystemUI. --> - - - false diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 3f9c535ee6..05724e2a85 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -23,11 +23,6 @@ 8dp 48dp - 1.0 - - 1.05 - 10 16dp 10.77dp @@ -86,11 +81,6 @@ 32dp 19dp - 5dp - 14sp - -10dp - 20sp - 56dp 16dp - 6dp 24dp - 16dp - 4dp - 8dp - 10dp + 12dp + 16dp 10dp 214dp 52dp 16dp 20dp - 16dp - 16dp - 8dp - 56dp + 16dp + 4dp 20dp 16dp 16dp 26dp - 12dp + 32dp 1dp - 12dp 24dp - 12dp - 48dp + 24dp 16dp - 12dp 48dp 48dp 200dp + 8dp + 8dp - 4dp + 8dp 14sp 500 20sp @@ -226,11 +207,9 @@ 0dp 16sp 500 - 600 24sp 14sp 400 - 600 20sp 8dp @@ -238,18 +217,8 @@ 10dp - 3dp - 28dp - 5dp - 28dp 4dp - - 8dp - 16dp - 24dp - 16dp - 14dp 20dp 2dp @@ -307,6 +276,10 @@ 30dp + + 30dp + -1500dp 2dp @@ -314,10 +287,9 @@ 6dp - 4dp 10dp - 100dp + 9dp 6dp @@ -336,7 +308,6 @@ 2dp 4dp 2dp - 0.5dp 8dp @@ -447,6 +418,9 @@ 0dp 0dp 0dp + 0dp + 0dp + 0dp 0dp @@ -483,11 +457,9 @@ 0dp 0dp - 0dp 0dp 0dp 0dp - 0dp 0dp 0dp 0dp @@ -496,6 +468,8 @@ 0dp 0dp 0dp + 0dp + 0dp 72dp 16dp @@ -509,6 +483,8 @@ 24dp 60dp 8dp + 96dp + 48dp 16dp 14dp @@ -573,8 +549,4 @@ 136px - - - 4dp - 2dp diff --git a/res/values/id.xml b/res/values/id.xml index 78b8308cc9..28496b5a19 100644 --- a/res/values/id.xml +++ b/res/values/id.xml @@ -19,7 +19,6 @@ - @@ -79,7 +78,6 @@ - diff --git a/res/values/strings.xml b/res/values/strings.xml index fc2d88aa95..e8568491ef 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -42,15 +42,9 @@ Split screen - Change aspect ratio App info for %1$s Usage settings for %1$s - - New Window - - Manage Windows - Save app pair @@ -67,12 +61,6 @@ Touch & hold to move a widget. Double-tap & hold to move a widget or use custom actions. - - More options - - Show all widgets %1$d \u00d7 %2$d @@ -155,21 +143,11 @@ Note-taking - - Show add button - - Hide add button Add Add %1$s widget - - Show all - - Show all widgets - - Showing all widgets @@ -191,8 +169,6 @@ All apps - Apps list - Notifications @@ -215,7 +191,6 @@ Personal apps list Work apps list - Remove @@ -234,8 +209,7 @@ Don\'t suggest app Pin Prediction - - Bubble + @@ -292,9 +266,6 @@ New home screen page - Active - Minimized - Folder opened, %1$d by %2$d @@ -310,8 +281,6 @@ Folder: %1$s, %2$d items Folder: %1$s, %2$d or more items - - Unnamed folder @@ -332,12 +301,6 @@ Allow home screen rotation When phone is rotated - - - Landscape mode - - Set phone into landscape mode - Notification dots @@ -382,9 +345,8 @@ %1$s waiting to install - %1$s is archived. - - download and restore + %1$s is archived. Tap to download and restore. + @@ -410,6 +372,9 @@ Move item here + + Item added to home screen + Item removed @@ -468,13 +433,7 @@ Widget resized to width %1$s height %2$s - Shortcut Menu - - - Widget Resize Frame for %1$s - - - Close + Shortcuts Dismiss @@ -484,12 +443,9 @@ Personal + Work - - Personal apps tab - - Work apps tab Work profile @@ -499,8 +455,6 @@ Got it \u24D8 - - Work apps are paused @@ -519,8 +473,6 @@ Pause work apps Unpause - - Work apps schedule Filter @@ -548,5 +500,4 @@ Install Install apps to Private Space - Add files and more to Private Space diff --git a/res/values/styles.xml b/res/values/styles.xml index 65b1693a4d..7f29bd8c21 100644 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -17,7 +17,7 @@ */ --> - + - - - - - @@ -207,8 +243,6 @@ @color/widget_picker_secondary_surface_color_light @color/widget_picker_title_color_light - @color/widget_picker_description_color_light - @color/widget_picker_menu_options_color_light @color/widget_picker_header_app_title_color_light @@ -234,12 +268,6 @@ @color/widget_picker_add_button_background_color_light @color/widget_picker_add_button_text_color_light - - @color/widget_picker_expand_button_background_color_light - - - @color/widget_picker_expand_button_text_color_light - @color/widget_cell_title_color_light @@ -254,8 +282,6 @@ @color/widget_picker_secondary_surface_color_dark @color/widget_picker_title_color_dark - @color/widget_picker_description_color_dark - @color/widget_picker_menu_options_color_dark @color/widget_picker_header_app_title_color_dark @@ -281,48 +307,12 @@ @color/widget_picker_add_button_background_color_dark @color/widget_picker_add_button_text_color_dark - - @color/widget_picker_expand_button_background_color_dark - - - @color/widget_picker_expand_button_text_color_dark - @color/widget_cell_title_color_dark @color/widget_cell_subtitle_color_dark - - - - - - - - - @@ -405,15 +398,17 @@ @drawable/drop_target_background - + diff --git a/res/xml/backupscheme.xml b/res/xml/backupscheme.xml index 56378c9b95..0f0dde24e2 100644 --- a/res/xml/backupscheme.xml +++ b/res/xml/backupscheme.xml @@ -2,20 +2,12 @@ - - - - - - - - - + \ No newline at end of file diff --git a/res/xml/device_profiles.xml b/res/xml/device_profiles.xml index 5fac66f233..1d0dbffc21 100644 --- a/res/xml/device_profiles.xml +++ b/res/xml/device_profiles.xml @@ -207,45 +207,4 @@ - - - - - - \ No newline at end of file diff --git a/res/xml/folder_shapes.xml b/res/xml/folder_shapes.xml new file mode 100644 index 0000000000..e60d333ba6 --- /dev/null +++ b/res/xml/folder_shapes.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/xml/launcher_preferences.xml b/res/xml/launcher_preferences.xml index 32fb5149f7..284ab9e718 100644 --- a/res/xml/launcher_preferences.xml +++ b/res/xml/launcher_preferences.xml @@ -50,13 +50,4 @@ launcher:logIdOn="615" launcher:logIdOff="616" /> - - - - diff --git a/res/xml/paddings_6x5.xml b/res/xml/paddings_6x5.xml index debfcfd136..2f421b78e3 100644 --- a/res/xml/paddings_6x5.xml +++ b/res/xml/paddings_6x5.xml @@ -30,27 +30,16 @@ launcher:b="0"/> - + - - - - - - - + \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 62cef97638..764ef0e7e2 100644 --- a/settings.gradle +++ b/settings.gradle @@ -55,12 +55,6 @@ project(':searchuilib').projectDir = new File(rootDir, 'platform_frameworks_libs include ':animationlib' project(':animationlib').projectDir = new File(rootDir, 'platform_frameworks_libs_systemui/animationlib') -include ':msdllib' -project(':msdllib').projectDir = new File(rootDir, 'platform_frameworks_libs_systemui/msdllib') - -include ':contextualeducationlib' -project(':contextualeducationlib').projectDir = new File(rootDir, 'platform_frameworks_libs_systemui/contextualeducationlib') - include ':hidden-api' include ':shared' @@ -87,9 +81,6 @@ project(':unfold').projectDir = new File(rootDir, 'systemUI/unfold') include ':viewcapture' project(':viewcapture').projectDir = new File(rootDir, 'systemUI/viewcapture') -include ':utils' -project(':utils').projectDir = new File(rootDir, 'systemUI/utils') - include ':compatLib' include ':compatLib:compatLibVQ' @@ -98,7 +89,6 @@ include ':compatLib:compatLibVS' include ':compatLib:compatLibVT' include ':compatLib:compatLibVU' include ':compatLib:compatLibVV' -include ':compatLib:compatLibVBaklava' include ':baseline-profile' diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java index 3272ff68a1..6a682b2dd3 100644 --- a/src/com/android/launcher3/AbstractFloatingView.java +++ b/src/com/android/launcher3/AbstractFloatingView.java @@ -34,8 +34,6 @@ import android.view.View; import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.Interpolator; import android.widget.LinearLayout; -import android.window.OnBackAnimationCallback; - import androidx.annotation.IntDef; import com.android.launcher3.anim.PendingAnimation; @@ -51,7 +49,7 @@ import java.lang.annotation.RetentionPolicy; */ @TargetApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public abstract class AbstractFloatingView extends LinearLayout implements TouchController, - OnBackAnimationCallback { + OnBackPressedHandler { @IntDef(flag = true, value = { TYPE_COMPOSE_VIEW, @@ -75,12 +73,12 @@ public abstract class AbstractFloatingView extends LinearLayout implements Touch TYPE_TASKBAR_ALL_APPS, TYPE_ADD_TO_HOME_CONFIRMATION, TYPE_TASKBAR_OVERLAY_PROXY, - TYPE_TASKBAR_PINNING_POPUP, - TYPE_PIN_IME_POPUP, - TYPE_ONE_GRID_MIGRATION_EDU, + TYPE_TASKBAR_PINNING_POPUP }) @Retention(RetentionPolicy.SOURCE) - public @interface FloatingViewType {} + public @interface FloatingViewType { + } + public static final int TYPE_FOLDER = 1 << 0; public static final int TYPE_ACTION_POPUP = 1 << 1; public static final int TYPE_WIDGETS_BOTTOM_SHEET = 1 << 2; @@ -106,10 +104,9 @@ public abstract class AbstractFloatingView extends LinearLayout implements Touch public static final int TYPE_TASKBAR_OVERLAY_PROXY = 1 << 20; public static final int TYPE_TASKBAR_PINNING_POPUP = 1 << 21; public static final int TYPE_PIN_IME_POPUP = 1 << 22; - public static final int TYPE_ONE_GRID_MIGRATION_EDU = 1 << 23; // Custom compose popups - public static final int TYPE_COMPOSE_VIEW = 1 << 24; + public static final int TYPE_COMPOSE_VIEW = 1 << 23; public static final int TYPE_ALL = TYPE_FOLDER | TYPE_ACTION_POPUP | TYPE_WIDGETS_BOTTOM_SHEET | TYPE_WIDGET_RESIZE_FRAME | TYPE_WIDGETS_FULL_SHEET @@ -118,34 +115,37 @@ public abstract class AbstractFloatingView extends LinearLayout implements Touch | TYPE_ICON_SURFACE | TYPE_DRAG_DROP_POPUP | TYPE_PIN_WIDGET_FROM_EXTERNAL_POPUP | TYPE_TASKBAR_EDUCATION_DIALOG | TYPE_TASKBAR_ALL_APPS | TYPE_OPTIONS_POPUP_DIALOG | TYPE_ADD_TO_HOME_CONFIRMATION | TYPE_TASKBAR_OVERLAY_PROXY - | TYPE_TASKBAR_PINNING_POPUP | TYPE_PIN_IME_POPUP | TYPE_ONE_GRID_MIGRATION_EDU | TYPE_COMPOSE_VIEW; + | TYPE_TASKBAR_PINNING_POPUP | TYPE_PIN_IME_POPUP | TYPE_COMPOSE_VIEW; // Type of popups which should be kept open during launcher rebind public static final int TYPE_REBIND_SAFE = TYPE_WIDGETS_FULL_SHEET | TYPE_WIDGETS_BOTTOM_SHEET | TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE | TYPE_ALL_APPS_EDU | TYPE_ICON_SURFACE | TYPE_TASKBAR_EDUCATION_DIALOG | TYPE_TASKBAR_ALL_APPS | TYPE_OPTIONS_POPUP_DIALOG | TYPE_TASKBAR_OVERLAY_PROXY - | TYPE_PIN_IME_POPUP | TYPE_ONE_GRID_MIGRATION_EDU | TYPE_COMPOSE_VIEW; + | TYPE_PIN_IME_POPUP | TYPE_COMPOSE_VIEW; /** Type of popups that should get exclusive accessibility focus. */ public static final int TYPE_ACCESSIBLE = TYPE_ALL & ~TYPE_DISCOVERY_BOUNCE & ~TYPE_LISTENER & ~TYPE_ALL_APPS_EDU & ~TYPE_TASKBAR_ALL_APPS & ~TYPE_PIN_IME_POPUP - & ~TYPE_WIDGET_RESIZE_FRAME & ~TYPE_ONE_GRID_MIGRATION_EDU & ~TYPE_ON_BOARD_POPUP - & ~TYPE_TASKBAR_OVERLAY_PROXY; + & ~TYPE_WIDGET_RESIZE_FRAME; - // These view all have particular operation associated with swipe down interaction. + // These view all have particular operation associated with swipe down + // interaction. public static final int TYPE_STATUS_BAR_SWIPE_DOWN_DISALLOW = TYPE_WIDGETS_BOTTOM_SHEET | TYPE_WIDGETS_FULL_SHEET | TYPE_WIDGET_RESIZE_FRAME | TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE | TYPE_TASK_MENU | TYPE_DRAG_DROP_POPUP | TYPE_COMPOSE_VIEW; // Floating views that are exclusive to the taskbar overlay window. - public static final int TYPE_TASKBAR_OVERLAYS = - TYPE_TASKBAR_ALL_APPS | TYPE_TASKBAR_EDUCATION_DIALOG; + public static final int TYPE_TASKBAR_OVERLAYS = TYPE_TASKBAR_ALL_APPS | TYPE_TASKBAR_EDUCATION_DIALOG; - // Floating views that a TouchController should not try to intercept touches from. + // Floating views that a TouchController should not try to intercept touches + // from. public static final int TYPE_TOUCH_CONTROLLER_NO_INTERCEPT = TYPE_ALL & ~TYPE_DISCOVERY_BOUNCE & ~TYPE_LISTENER & ~TYPE_TASKBAR_OVERLAYS; + public static final int TYPE_ALL_EXCEPT_ON_BOARD_POPUP = TYPE_ALL & ~TYPE_ON_BOARD_POPUP + & ~TYPE_PIN_IME_POPUP; + protected boolean mIsOpen; public AbstractFloatingView(Context context, AttributeSet attrs) { @@ -157,7 +157,8 @@ public abstract class AbstractFloatingView extends LinearLayout implements Touch } /** - * We need to handle touch events to prevent them from falling through to the workspace below. + * We need to handle touch events to prevent them from falling through to the + * workspace below. */ @SuppressLint("ClickableViewAccessibility") @Override @@ -177,11 +178,15 @@ public abstract class AbstractFloatingView extends LinearLayout implements Touch protected abstract void handleClose(boolean animate); /** - * Creates a user-controlled animation to hint that the view will be closed if completed. - * @param distanceToMove The max distance that elements should move from their starting point. + * Creates a user-controlled animation to hint that the view will be closed if + * completed. + * + * @param distanceToMove The max distance that elements should move from their + * starting point. */ public void addHintCloseAnim( - float distanceToMove, Interpolator interpolator, PendingAnimation target) { } + float distanceToMove, Interpolator interpolator, PendingAnimation target) { + } public final boolean isOpen() { return mIsOpen; @@ -238,14 +243,16 @@ public abstract class AbstractFloatingView extends LinearLayout implements Touch } /** - * Returns whether there is at least one view of the given type where {@link #isOpen()} == true. + * Returns whether there is at least one view of the given type where + * {@link #isOpen()} == true. */ public static boolean hasOpenView(ActivityContext activity, @FloatingViewType int type) { return getOpenView(activity, type) != null; } /** - * Returns a view matching FloatingViewType, and {@link #isOpen()} may be false (if animating + * Returns a view matching FloatingViewType, and {@link #isOpen()} may be false + * (if animating * closed). */ public static T getAnyView( @@ -256,8 +263,10 @@ public abstract class AbstractFloatingView extends LinearLayout implements Touch private static T getView( ActivityContext activity, @FloatingViewType int type, boolean mustBeOpen) { BaseDragLayer dragLayer = activity.getDragLayer(); - if (dragLayer == null) return null; - // Iterate in reverse order. AbstractFloatingView is added later to the dragLayer, + if (dragLayer == null) + return null; + // Iterate in reverse order. AbstractFloatingView is added later to the + // dragLayer, // and will be one of the last views. for (int i = dragLayer.getChildCount() - 1; i >= 0; i--) { View child = dragLayer.getChildAt(i); @@ -294,13 +303,13 @@ public abstract class AbstractFloatingView extends LinearLayout implements Touch } public static void closeAllOpenViewsExcept(ActivityContext activity, boolean animate, - @FloatingViewType int type) { + @FloatingViewType int type) { closeOpenViews(activity, animate, TYPE_ALL & ~type); activity.finishAutoCancelActionMode(); } public static void closeAllOpenViewsExcept(ActivityContext activity, - @FloatingViewType int type) { + @FloatingViewType int type) { closeAllOpenViewsExcept(activity, true, type); } diff --git a/src/com/android/launcher3/Alarm.java b/src/com/android/launcher3/Alarm.java index e516ad088b..fb8088c13b 100644 --- a/src/com/android/launcher3/Alarm.java +++ b/src/com/android/launcher3/Alarm.java @@ -20,8 +20,6 @@ import android.os.Handler; import android.os.Looper; import android.os.SystemClock; -import androidx.annotation.VisibleForTesting; - public class Alarm implements Runnable{ // if we reach this time and the alarm hasn't been cancelled, call the listener private long mAlarmTriggerTime; @@ -98,13 +96,4 @@ public class Alarm implements Runnable{ public long getLastSetTimeout() { return mLastSetTimeout; } - - /** Simulates the alarm firing for tests. */ - @VisibleForTesting - public void finishAlarm() { - if (!mAlarmPending) return; - mAlarmPending = false; - mHandler.removeCallbacks(this); - mAlarmListener.onAlarm(this); - } } diff --git a/src/com/android/launcher3/AppFilter.java b/src/com/android/launcher3/AppFilter.java index b0b21837fa..f854693673 100644 --- a/src/com/android/launcher3/AppFilter.java +++ b/src/com/android/launcher3/AppFilter.java @@ -7,16 +7,11 @@ import android.os.Process; import com.android.launcher3.util.ComponentKey; import com.patrykmichalik.opto.core.PreferenceExtensionsKt; -import com.android.launcher3.dagger.ApplicationContext; - import java.util.Arrays; import java.util.Set; import java.util.stream.Collectors; -import javax.inject.Inject; - import app.lawnchair.preferences2.PreferenceManager2; - /** * Utility class to filter out components from various lists */ @@ -24,8 +19,7 @@ public class AppFilter { private final Set mFilteredComponents; - @Inject - public AppFilter(@ApplicationContext Context context) { + public AppFilter(Context context) { mFilteredComponents = Arrays.stream( context.getResources().getStringArray(R.array.filtered_components)) .map(ComponentName::unflattenFromString) diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java index 41c24ccf10..be7756234d 100644 --- a/src/com/android/launcher3/AppWidgetResizeFrame.java +++ b/src/com/android/launcher3/AppWidgetResizeFrame.java @@ -49,11 +49,11 @@ import com.android.launcher3.widget.LauncherAppWidgetHostView; import com.android.launcher3.widget.LauncherAppWidgetProviderInfo; import com.android.launcher3.widget.PendingAppWidgetHostView; import com.android.launcher3.widget.util.WidgetSizes; +import com.patrykmichalik.opto.core.PreferenceExtensionsKt; import java.util.ArrayList; import java.util.List; -import com.patrykmichalik.opto.core.PreferenceExtensionsKt; import app.lawnchair.preferences2.PreferenceManager2; import app.lawnchair.theme.color.tokens.ColorTokens; import app.lawnchair.theme.drawable.DrawableTokens; @@ -110,7 +110,8 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O /** * In the two panel UI, it is not possible to resize a widget to cross its host - * {@link CellLayout}'s sibling. When this happens, we gradually reduce the opacity of the + * {@link CellLayout}'s sibling. When this happens, we gradually reduce the + * opacity of the * sibling {@link CellLayout} from 1f to * {@link #MIN_OPACITY_FOR_CELL_LAYOUT_DURING_INVALID_RESIZE}. */ @@ -141,8 +142,7 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O private int[] mWidgetViewWindowPos; private final Rect mWidgetViewOldRect = new Rect(); private final Rect mWidgetViewNewRect = new Rect(); - private final @Nullable LauncherAppWidgetHostView.CellChildViewPreLayoutListener - mCellChildViewPreLayoutListener; + private final @Nullable LauncherAppWidgetHostView.CellChildViewPreLayoutListener mCellChildViewPreLayoutListener; private final @NonNull OnLayoutChangeListener mWidgetViewLayoutListener; private int mXDown, mYDown; @@ -163,14 +163,14 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O mCellChildViewPreLayoutListener = FeatureFlags.ENABLE_WIDGET_TRANSITION_FOR_RESIZING.get() ? (v, left, top, right, bottom) -> { - if (mWidgetViewWindowPos == null) { - mWidgetViewWindowPos = new int[2]; - } - v.getLocationInWindow(mWidgetViewWindowPos); - mWidgetViewOldRect.set(v.getLeft(), v.getTop(), v.getRight(), - v.getBottom()); - mWidgetViewNewRect.set(left, top, right, bottom); - } + if (mWidgetViewWindowPos == null) { + mWidgetViewWindowPos = new int[2]; + } + v.getLocationInWindow(mWidgetViewWindowPos); + mWidgetViewOldRect.set(v.getLeft(), v.getTop(), v.getRight(), + v.getBottom()); + mWidgetViewNewRect.set(left, top, right, bottom); + } : null; mBackgroundPadding = getResources() @@ -186,8 +186,7 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O R.dimen.resize_frame_invalid_drag_across_two_panel_opacity_margin); mDragLayerRelativeCoordinateHelper = new ViewGroupFocusHelper(mLauncher.getDragLayer()); - mWidgetViewLayoutListener = - (v, l, t, r, b, oldL, oldT, oldR, oldB) -> setCornerRadiusFromWidget(); + mWidgetViewLayoutListener = (v, l, t, r, b, oldL, oldT, oldR, oldB) -> setCornerRadiusFromWidget(); } @Override @@ -222,7 +221,8 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O boolean force = PreferenceExtensionsKt.firstBlocking(pref2.getForceWidgetResize()); boolean unlimited = PreferenceExtensionsKt.firstBlocking(pref2.getWidgetUnlimitedSize()); - // If widget is not added to view hierarchy, we cannot show resize frame at correct location + // If widget is not added to view hierarchy, we cannot show resize frame at + // correct location if (widget.getParent() == null) { return; } @@ -232,14 +232,17 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O DragLayer dl = launcher.getDragLayer(); AppWidgetResizeFrame frame = (AppWidgetResizeFrame) launcher.getLayoutInflater() .inflate(R.layout.app_widget_resize_frame, dl, false); + ImageView imageView = frame.findViewById(R.id.widget_resize_frame); + imageView.setImageDrawable(DrawableTokens.WidgetResizeFrame.resolve(launcher)); + if (widget.hasEnforcedCornerRadius()) { + float enforcedCornerRadius = widget.getEnforcedCornerRadius(); + Drawable d = imageView.getDrawable(); + if (d instanceof GradientDrawable) { + GradientDrawable gd = (GradientDrawable) d.mutate(); + gd.setCornerRadius(enforcedCornerRadius); + } + } frame.setupForWidget(widget, cellLayout, dl, force, unlimited); - // Save widget item info as tag on resize frame; so that, the accessibility delegate can - // attach actions that typically happen on widget (e.g. resize, move) also on the resize - // frame. - frame.setTag(widget.getTag()); - frame.setAccessibilityDelegate(launcher.getAccessibilityDelegate()); - frame.setContentDescription(launcher.asContext().getString(R.string.widget_frame_name, - widget.getContentDescription())); ((DragLayer.LayoutParams) frame.getLayoutParams()).customPosition = true; dl.addView(frame); @@ -259,19 +262,11 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O } } - /** - * Retrieves the view where accessibility actions happen. - */ - public View getViewForAccessibility() { - return mWidgetView; - } - private void setupForWidget(LauncherAppWidgetHostView widgetView, CellLayout cellLayout, DragLayer dragLayer, boolean force, boolean unlimited) { mCellLayout = cellLayout; mWidgetView = widgetView; - LauncherAppWidgetProviderInfo info = (LauncherAppWidgetProviderInfo) - widgetView.getAppWidgetInfo(); + LauncherAppWidgetProviderInfo info = (LauncherAppWidgetProviderInfo) widgetView.getAppWidgetInfo(); mDragLayer = dragLayer; InvariantDeviceProfile idp = LauncherAppState.getIDP(cellLayout.getContext()); @@ -281,13 +276,12 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O } else { resizeMode = info.resizeMode; } - - if (unlimited) { mMinHSpan = 1; mMinVSpan = 1; mMaxHSpan = idp.numColumns; mMaxVSpan = idp.numRows; + // If widget is resizable in any direction, make it resizable in both if (resizeMode != AppWidgetProviderInfo.RESIZE_NONE) { resizeMode = AppWidgetProviderInfo.RESIZE_BOTH; @@ -362,8 +356,10 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O lp.cellVSpan = widgetInfo.spanY; lp.isLockedToGrid = true; - // When we create the resize frame, we first mark all cells as unoccupied. The appropriate - // cells (same if not resized, or different) will be marked as occupied when the resize + // When we create the resize frame, we first mark all cells as unoccupied. The + // appropriate + // cells (same if not resized, or different) will be marked as occupied when the + // resize // frame is dismissed. mCellLayout.markCellsAsUnoccupiedForView(mWidgetView); @@ -380,19 +376,17 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O } public boolean beginResizeIfPointInRegion(int x, int y) { - mLeftBorderActive = (x < mTouchTargetWidth) && mHorizontalResizeActive; - mRightBorderActive = (x > getWidth() - mTouchTargetWidth) && mHorizontalResizeActive; - mTopBorderActive = (y < mTouchTargetWidth + mTopTouchRegionAdjustment) - && mVerticalResizeActive; - mBottomBorderActive = (y > getHeight() - mTouchTargetWidth + mBottomTouchRegionAdjustment) - && mVerticalResizeActive; + mLeftBorderActive = x < mTouchTargetWidth; + mRightBorderActive = x > getWidth() - mTouchTargetWidth; + mTopBorderActive = y < mTouchTargetWidth + mTopTouchRegionAdjustment; + mBottomBorderActive = y > getHeight() - mTouchTargetWidth + mBottomTouchRegionAdjustment; boolean anyBordersActive = mLeftBorderActive || mRightBorderActive || mTopBorderActive || mBottomBorderActive; if (anyBordersActive) { mDragHandles[INDEX_LEFT].setAlpha(mLeftBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA); - mDragHandles[INDEX_RIGHT].setAlpha(mRightBorderActive ? 1.0f :DIMMED_HANDLE_ALPHA); + mDragHandles[INDEX_RIGHT].setAlpha(mRightBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA); mDragHandles[INDEX_TOP].setAlpha(mTopBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA); mDragHandles[INDEX_BOTTOM].setAlpha(mBottomBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA); } @@ -419,7 +413,7 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O } /** - * Based on the deltas, we resize the frame. + * Based on the deltas, we resize the frame. */ public void visualizeResizeForDelta(int deltaX, int deltaY) { mDeltaX = mDeltaXRange.clamp(deltaX); @@ -438,6 +432,24 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O resizeWidgetIfNeeded(false); + // When the widget resizes in multi-window mode, the translation value changes + // to maintain + // a center fit. These overrides ensure the resize frame always aligns with the + // widget view. + getSnappedRectRelativeToDragLayer(sTmpRect); + if (mLeftBorderActive) { + lp.width = sTmpRect.width() + sTmpRect.left - lp.x; + } + if (mTopBorderActive) { + lp.height = sTmpRect.height() + sTmpRect.top - lp.y; + } + if (mRightBorderActive) { + lp.x = sTmpRect.left; + } + if (mBottomBorderActive) { + lp.y = sTmpRect.top; + } + // Handle invalid resize across CellLayouts in the two panel UI. if (mCellLayout.getParent() instanceof Workspace) { Workspace workspace = (Workspace) mCellLayout.getParent(); @@ -454,8 +466,7 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O // Resize from right to left. progress = (mDragAcrossTwoPanelOpacityMargin + mDeltaX) / mDragAcrossTwoPanelOpacityMargin; - } else if (workspace.indexOfChild(pairedCellLayout) - > workspace.indexOfChild(mCellLayout) + } else if (workspace.indexOfChild(pairedCellLayout) > workspace.indexOfChild(mCellLayout) && mDeltaX > 0 && resizeFrameBound.right > focusedCellLayoutBound.right) { // Resize from left to right. @@ -477,7 +488,7 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O } /** - * Based on the current deltas, we determine if and how to resize the widget. + * Based on the current deltas, we determine if and how to resize the widget. */ private void resizeWidgetIfNeeded(boolean onDismiss) { ViewGroup.LayoutParams wlp = mWidgetView.getLayoutParams(); @@ -491,7 +502,8 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O int hSpanInc = getSpanIncrement((mDeltaX + mDeltaXAddOn) / xThreshold - mRunningHInc); int vSpanInc = getSpanIncrement((mDeltaY + mDeltaYAddOn) / yThreshold - mRunningVInc); - if (!onDismiss && (hSpanInc == 0 && vSpanInc == 0)) return; + if (!onDismiss && (hSpanInc == 0 && vSpanInc == 0)) + return; mDirectionVector[0] = 0; mDirectionVector[1] = 0; @@ -503,7 +515,8 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O int cellX = lp.useTmpCoords ? lp.getTmpCellX() : lp.getCellX(); int cellY = lp.useTmpCoords ? lp.getTmpCellY() : lp.getCellY(); - // For each border, we bound the resizing based on the minimum width, and the maximum + // For each border, we bound the resizing based on the minimum width, and the + // maximum // expandability. mTempRange1.set(cellX, spanX + cellX); int hSpanDelta = mTempRange1.applyDeltaAndBound(mLeftBorderActive, mRightBorderActive, @@ -523,9 +536,11 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O mDirectionVector[1] = mTopBorderActive ? -1 : 1; } - if (!onDismiss && vSpanDelta == 0 && hSpanDelta == 0) return; + if (!onDismiss && vSpanDelta == 0 && hSpanDelta == 0) + return; - // We always want the final commit to match the feedback, so we make sure to use the + // We always want the final commit to match the feedback, so we make sure to use + // the // last used direction vector when committing the resize / reorder. if (onDismiss) { mDirectionVector[0] = mLastDirectionVector[0]; @@ -535,12 +550,14 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O mLastDirectionVector[1] = mDirectionVector[1]; } - // We don't want to evaluate resize if a widget was pending config activity and was already - // occupying a space on the screen. This otherwise will cause reorder algorithm evaluate a + // We don't want to evaluate resize if a widget was pending config activity and + // was already + // occupying a space on the screen. This otherwise will cause reorder algorithm + // evaluate a // different location for the widget and cause a jump. if (!(mWidgetView instanceof PendingAppWidgetHostView) && mCellLayout.createAreaForResize( cellX, cellY, spanX, spanY, mWidgetView, mDirectionVector, onDismiss)) { - if (mStateAnnouncer != null && (lp.cellHSpan != spanX || lp.cellVSpan != spanY) ) { + if (mStateAnnouncer != null && (lp.cellHSpan != spanX || lp.cellVSpan != spanY)) { mStateAnnouncer.announce( mLauncher.getString(R.string.widget_resized, spanX, spanY)); } @@ -563,7 +580,8 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O protected void onDetachedFromWindow() { super.onDetachedFromWindow(); - // We are done with resizing the widget. Save the widget size & position to LauncherModel + // We are done with resizing the widget. Save the widget size & position to + // LauncherModel resizeWidgetIfNeeded(true); mLauncher.getStatsLogManager() .logger() @@ -586,7 +604,8 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O } /** - * Returns the rect of this view when the frame is snapped around the widget, with the bounds + * Returns the rect of this view when the frame is snapped around the widget, + * with the bounds * relative to the {@link DragLayer}. */ private void getSnappedRectRelativeToDragLayer(@NonNull Rect out) { @@ -614,7 +633,10 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O afterPos[1] + mWidgetViewNewRect.height()); } - /** Returns the relative x and y values of the widget view after the layout transition */ + /** + * Returns the relative x and y values of the widget view after the layout + * transition + */ private int[] getViewPosRelativeToDragLayer() { mDragLayer.getLocationInWindow(sDragLayerLoc); int x = sDragLayerLoc[0]; @@ -628,12 +650,13 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O int leftOffset = mWidgetViewNewRect.left - mWidgetViewOldRect.left; int topOffset = mWidgetViewNewRect.top - mWidgetViewOldRect.top; - return new int[] {mWidgetViewWindowPos[0] - x + leftOffset, - mWidgetViewWindowPos[1] - y + topOffset}; + return new int[] { mWidgetViewWindowPos[0] - x + leftOffset, + mWidgetViewWindowPos[1] - y + topOffset }; } private void snapToWidget(boolean animate) { - // The widget is guaranteed to be attached to the cell layout at this point, thus setting + // The widget is guaranteed to be attached to the cell layout at this point, + // thus setting // the transition here if (FeatureFlags.ENABLE_WIDGET_TRANSITION_FOR_RESIZING.get() && mWidgetView.getLayoutTransition() == null) { @@ -649,17 +672,21 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O int newX = sTmpRect.left; int newY = sTmpRect.top; - // We need to make sure the frame's touchable regions lie fully within the bounds of the - // DragLayer. We allow the actual handles to be clipped, but we shift the touch regions + // We need to make sure the frame's touchable regions lie fully within the + // bounds of the + // DragLayer. We allow the actual handles to be clipped, but we shift the touch + // regions // down accordingly to provide a proper touch target. if (newY < 0) { - // In this case we shift the touch region down to start at the top of the DragLayer + // In this case we shift the touch region down to start at the top of the + // DragLayer mTopTouchRegionAdjustment = -newY; } else { mTopTouchRegionAdjustment = 0; } if (newY + newHeight > mDragLayer.getHeight()) { - // In this case we shift the touch region up to end at the bottom of the DragLayer + // In this case we shift the touch region up to end at the bottom of the + // DragLayer mBottomTouchRegionAdjustment = -(newY + newHeight - mDragLayer.getHeight()); } else { mBottomTouchRegionAdjustment = 0; @@ -714,7 +741,8 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O @Override public boolean onKey(View v, int keyCode, KeyEvent event) { - // Clear the frame and give focus to the widget host view when a directional key is pressed. + // Clear the frame and give focus to the widget host view when a directional key + // is pressed. if (shouldConsume(keyCode)) { close(false); mWidgetView.requestFocus(); @@ -773,7 +801,8 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O if (ev.getAction() == MotionEvent.ACTION_DOWN && handleTouchDown(ev)) { return true; } - // Keep the resize frame open but let a click on the reconfigure button fall through to the + // Keep the resize frame open but let a click on the reconfigure button fall + // through to the // button's OnClickListener. if (isTouchOnReconfigureButton(ev)) { return false; @@ -864,7 +893,8 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O } /** - * Moves either the start or end edge (but never both) by {@param delta} and sets the + * Moves either the start or end edge (but never both) by {@param delta} and + * sets the * result in {@param out} */ public void applyDelta(boolean moveStart, boolean moveEnd, int delta, IntRange out) { @@ -873,17 +903,25 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O } /** - * Applies delta similar to {@link #applyDelta(boolean, boolean, int, IntRange)}, + * Applies delta similar to + * {@link #applyDelta(boolean, boolean, int, IntRange)}, * with extra conditions. - * @param minSize minimum size after with the moving edge should not be shifted any further. - * For eg, if delta = -3 when moving the endEdge brings the size to less than + * + * @param minSize minimum size after with the moving edge should not be shifted + * any further. + * For eg, if delta = -3 when moving the endEdge brings the size + * to less than * minSize, only delta = -2 will applied - * @param maxSize maximum size after with the moving edge should not be shifted any further. - * For eg, if delta = -3 when moving the endEdge brings the size to greater + * @param maxSize maximum size after with the moving edge should not be shifted + * any further. + * For eg, if delta = -3 when moving the endEdge brings the size + * to greater * than maxSize, only delta = -2 will applied - * @param maxEnd The maximum value to the end edge (start edge is always restricted to 0) - * @return the amount of increase when endEdge was moves and the amount of decrease when - * the start edge was moved. + * @param maxEnd The maximum value to the end edge (start edge is always + * restricted to 0) + * @return the amount of increase when endEdge was moves and the amount of + * decrease when + * the start edge was moved. */ public int applyDeltaAndBound(boolean moveStart, boolean moveEnd, int delta, int minSize, int maxSize, int maxEnd, IntRange out) { @@ -922,12 +960,14 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O || keyCode == KeyEvent.KEYCODE_PAGE_UP || keyCode == KeyEvent.KEYCODE_PAGE_DOWN); } - @Nullable private ArrowTipView showReconfigurableWidgetEducationTip() { + @Nullable + private ArrowTipView showReconfigurableWidgetEducationTip() { Rect rect = new Rect(); if (!mReconfigureButton.getGlobalVisibleRect(rect)) { return null; } - @Px int tipMargin = mLauncher.getResources() + @Px + int tipMargin = mLauncher.getResources() .getDimensionPixelSize(R.dimen.widget_reconfigure_tip_top_margin); return new ArrowTipView(mLauncher, /* isPointingUp= */ true) .showAroundRect( diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java index 3315e764ab..f1e4bdc8e9 100644 --- a/src/com/android/launcher3/AutoInstallsLayout.java +++ b/src/com/android/launcher3/AutoInstallsLayout.java @@ -94,11 +94,6 @@ public class AutoInstallsLayout { public static AutoInstallsLayout get(Context context, LauncherWidgetHolder appWidgetHolder, LayoutParserCallback callback) { - // LC: c51b2a221838aefb610b7146fc4ef7cb34e5e495 - if (!BuildConfig.ENABLE_AUTO_INSTALLS_LAYOUT) { - return null; - } - Partner partner = Partner.get(context.getPackageManager(), ACTION_LAUNCHER_CUSTOMIZATION); if (partner == null) { return null; @@ -269,13 +264,6 @@ public class AutoInstallsLayout { return count; } - private void addProfileId(XmlPullParser parser) { - Long profileId = mUserTypeToSerial.get(getAttributeValue(parser, ATTR_USER_TYPE)); - if (profileId != null) { - mValues.put(Favorites.PROFILE_ID, profileId); - } - } - /** * Parses container and screenId attribute from the current tag, and puts it in * the out. @@ -322,6 +310,10 @@ public class AutoInstallsLayout { convertToDistanceFromEnd(getAttributeValue(parser, ATTR_X), mColumnCount)); mValues.put(Favorites.CELLY, convertToDistanceFromEnd(getAttributeValue(parser, ATTR_Y), mRowCount)); + Long profileId = mUserTypeToSerial.get(getAttributeValue(parser, ATTR_USER_TYPE)); + if (profileId != null) { + mValues.put(Favorites.PROFILE_ID, profileId); + } TagParser tagParser = tagParserMap.get(parser.getName()); if (tagParser == null) { @@ -397,7 +389,7 @@ public class AutoInstallsLayout { public int parseAndAdd(XmlPullParser parser) { final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME); final String className = getAttributeValue(parser, ATTR_CLASS_NAME); - addProfileId(parser); + if (!TextUtils.isEmpty(packageName) && !TextUtils.isEmpty(className)) { ActivityInfo info; try { @@ -446,7 +438,6 @@ public class AutoInstallsLayout { public int parseAndAdd(XmlPullParser parser) { final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME); final String className = getAttributeValue(parser, ATTR_CLASS_NAME); - addProfileId(parser); if (TextUtils.isEmpty(packageName) || TextUtils.isEmpty(className)) { if (LOGD) Log.d(TAG, "Skipping invalid with no component"); @@ -469,7 +460,7 @@ public class AutoInstallsLayout { public int parseAndAdd(XmlPullParser parser) { final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME); final String shortcutId = getAttributeValue(parser, ATTR_SHORTCUT_ID); - addProfileId(parser); + try { LauncherApps launcherApps = mContext.getSystemService(LauncherApps.class); launcherApps.pinShortcuts(packageName, Collections.singletonList(shortcutId), @@ -503,7 +494,6 @@ public class AutoInstallsLayout { public ComponentName getComponentName(XmlPullParser parser) { final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME); final String className = getAttributeValue(parser, ATTR_CLASS_NAME); - addProfileId(parser); if (TextUtils.isEmpty(packageName) || TextUtils.isEmpty(className)) { return null; } diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java index 9e97d73f1f..e991a0bdee 100644 --- a/src/com/android/launcher3/BaseActivity.java +++ b/src/com/android/launcher3/BaseActivity.java @@ -16,7 +16,6 @@ package com.android.launcher3; -import static com.android.launcher3.util.DisplayController.CHANGE_ROTATION; import static com.android.launcher3.util.FlagDebugUtils.appendFlag; import static com.android.launcher3.util.FlagDebugUtils.formatFlagChange; import static com.android.launcher3.util.SystemUiController.UI_STATE_FULLSCREEN_TASK; @@ -32,34 +31,18 @@ import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.util.Log; -import android.view.ActionMode; -import android.view.View; import android.window.OnBackInvokedDispatcher; import androidx.activity.ComponentActivity; import androidx.annotation.IntDef; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.lifecycle.Lifecycle; -import androidx.lifecycle.LifecycleRegistry; -import androidx.savedstate.SavedStateRegistry; -import androidx.savedstate.SavedStateRegistryController; import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener; import com.android.launcher3.logging.StatsLogManager; -import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.testing.TestLogging; import com.android.launcher3.testing.shared.TestProtocol; -import com.android.launcher3.util.ActivityOptionsWrapper; -import com.android.launcher3.util.DisplayController; -import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener; -import com.android.launcher3.util.DisplayController.Info; -import com.android.launcher3.util.LifecycleHelper; import com.android.launcher3.util.RunnableList; import com.android.launcher3.util.SystemUiController; import com.android.launcher3.util.ViewCache; -import com.android.launcher3.util.WeakCleanupSet; -import com.android.launcher3.util.WindowBounds; import com.android.launcher3.views.ActivityContext; import com.android.launcher3.views.ScrimView; @@ -72,8 +55,7 @@ import java.util.StringJoiner; /** * Launcher BaseActivity */ -public abstract class BaseActivity extends Activity implements ActivityContext, - DisplayInfoChangeListener { +public abstract class BaseActivity extends ComponentActivity implements ActivityContext { private static final String TAG = "BaseActivity"; static final boolean DEBUG = false; @@ -106,15 +88,11 @@ public abstract class BaseActivity extends Activity implements ActivityContext, private final ArrayList mMultiWindowModeChangedListeners = new ArrayList<>(); - private final SavedStateRegistryController mSavedStateRegistryController = - SavedStateRegistryController.create(this); - private final LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this); - private final WeakCleanupSet mCleanupSet = new WeakCleanupSet(this); - protected DeviceProfile mDeviceProfile; protected SystemUiController mSystemUiController; private StatsLogManager mStatsLogManager; + public static final int ACTIVITY_STATE_STARTED = 1 << 0; public static final int ACTIVITY_STATE_RESUMED = 1 << 1; @@ -134,6 +112,11 @@ public abstract class BaseActivity extends Activity implements ActivityContext, */ public static final int ACTIVITY_STATE_USER_ACTIVE = 1 << 4; + /** + * State flag indicating if the user will be active shortly. + */ + public static final int ACTIVITY_STATE_USER_WILL_BE_ACTIVE = 1 << 5; + /** * State flag indicating that a state transition is in progress */ @@ -151,10 +134,6 @@ public abstract class BaseActivity extends Activity implements ActivityContext, public @interface ActivityFlags { } - // When starting an action mode, setting this tag will cause the action mode to be cancelled - // automatically when user interacts with the launcher. - public static final Object AUTO_CANCEL_ACTION_MODE = new Object(); - /** Returns a human-readable string for the specified {@link ActivityFlags}. */ public static String getActivityStateString(@ActivityFlags int flags) { StringJoiner result = new StringJoiner("|"); @@ -189,14 +168,6 @@ public abstract class BaseActivity extends Activity implements ActivityContext, private final RunnableList[] mEventCallbacks = {new RunnableList(), new RunnableList(), new RunnableList(), new RunnableList()}; - private ActionMode mCurrentActionMode; - - public BaseActivity() { - mSavedStateRegistryController.performAttach(); - registerActivityLifecycleCallbacks( - new LifecycleHelper(this, mSavedStateRegistryController, mLifecycleRegistry)); - } - @Override public ViewCache getViewCache() { return mViewCache; @@ -225,7 +196,7 @@ public abstract class BaseActivity extends Activity implements ActivityContext, public SystemUiController getSystemUiController() { if (mSystemUiController == null) { - mSystemUiController = new SystemUiController(getWindow().getDecorView()); + mSystemUiController = new SystemUiController(getWindow()); } return mSystemUiController; } @@ -243,7 +214,6 @@ public abstract class BaseActivity extends Activity implements ActivityContext, protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); registerBackDispatcher(); - DisplayController.INSTANCE.get(this).addChangeListener(this); } @Override @@ -291,7 +261,6 @@ public abstract class BaseActivity extends Activity implements ActivityContext, protected void onDestroy() { super.onDestroy(); mEventCallbacks[EVENT_DESTROYED].executeAllAndClear(); - DisplayController.INSTANCE.get(this).removeChangeListener(this); } @Override @@ -350,6 +319,7 @@ public abstract class BaseActivity extends Activity implements ActivityContext, */ public void setResumed() { addActivityFlags(ACTIVITY_STATE_RESUMED | ACTIVITY_STATE_USER_ACTIVE); + removeActivityFlags(ACTIVITY_STATE_USER_WILL_BE_ACTIVE); } public boolean isUserActive() { @@ -424,6 +394,15 @@ public abstract class BaseActivity extends Activity implements ActivityContext, mEventCallbacks[event].add(callback); } + @Override + public Handler getMainThreadHandler() { + try { + return new Handler(Looper.getMainLooper()); + } catch (Throwable t) { + return super.getMainThreadHandler(); + } + } + /** Removes a previously added callback */ public void removeEventCallback(@ActivityEvent int event, Runnable callback) { mEventCallbacks[event].remove(callback); @@ -442,83 +421,13 @@ public abstract class BaseActivity extends Activity implements ActivityContext, writer.println(prefix + "mForceInvisible: " + mForceInvisible); } - - @Override - public void onActionModeStarted(ActionMode mode) { - super.onActionModeStarted(mode); - mCurrentActionMode = mode; - } - - @Override - public void onActionModeFinished(ActionMode mode) { - super.onActionModeFinished(mode); - mCurrentActionMode = null; - } - - protected boolean isInAutoCancelActionMode() { - return mCurrentActionMode != null && AUTO_CANCEL_ACTION_MODE == mCurrentActionMode.getTag(); - } - - @Override - public boolean finishAutoCancelActionMode() { - if (isInAutoCancelActionMode()) { - mCurrentActionMode.finish(); - return true; - } - return false; - } - - @Override - @NonNull - public ActivityOptionsWrapper getActivityLaunchOptions(View v, @Nullable ItemInfo item) { - ActivityOptionsWrapper wrapper = ActivityContext.super.getActivityLaunchOptions(v, item); - addEventCallback(EVENT_RESUMED, wrapper.onEndCallback::executeAllAndDestroy); - return wrapper; - } - - @Override - public ActivityOptionsWrapper makeDefaultActivityOptions(int splashScreenStyle) { - ActivityOptionsWrapper wrapper = - ActivityContext.super.makeDefaultActivityOptions(splashScreenStyle); - addEventCallback(EVENT_RESUMED, wrapper.onEndCallback::executeAllAndDestroy); - return wrapper; - } - - protected WindowBounds getMultiWindowDisplaySize() { - return WindowBounds.fromWindowMetrics(getWindowManager().getCurrentWindowMetrics()); - } - - @Override - public void onDisplayInfoChanged(Context context, Info info, int flags) { - if ((flags & CHANGE_ROTATION) != 0 && mDeviceProfile.isVerticalBarLayout()) { - reapplyUi(); - } - } - - protected void reapplyUi() {} - - @NonNull - @Override - public SavedStateRegistry getSavedStateRegistry() { - return mSavedStateRegistryController.getSavedStateRegistry(); - } - - @NonNull - @Override - public Lifecycle getLifecycle() { - return mLifecycleRegistry; - } - - @Override - public WeakCleanupSet getOwnerCleanupSet() { - return mCleanupSet; - } - public static T fromContext(Context context) { if (context instanceof BaseActivity) { return (T) context; - } else if (context instanceof ContextWrapper cw) { - return fromContext(cw.getBaseContext()); + } else if (context instanceof ActivityContextDelegate) { + return (T) ((ActivityContextDelegate) context).mDelegate; + } else if (context instanceof ContextWrapper) { + return fromContext(((ContextWrapper) context).getBaseContext()); } else { throw new IllegalArgumentException("Cannot find BaseActivity in parent tree"); } diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index 23b35c82df..c1ef97ba38 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -13,36 +13,25 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * Modifications copyright 2025, Lawnchair + * Modifications copyright 2021, Lawnchair */ package com.android.launcher3; -import static android.graphics.fonts.FontStyle.FONT_WEIGHT_BOLD; -import static android.graphics.fonts.FontStyle.FONT_WEIGHT_NORMAL; import static com.android.launcher3.graphics.PreloadIconDrawable.newPendingIcon; import static android.text.Layout.Alignment.ALIGN_NORMAL; -import static com.android.app.animation.Interpolators.EMPHASIZED; -import static com.android.launcher3.BubbleTextView.RunningAppState.RUNNING; -import static com.android.launcher3.BubbleTextView.RunningAppState.NOT_RUNNING; -import static com.android.launcher3.BubbleTextView.RunningAppState.MINIMIZED; -import static com.android.launcher3.Flags.enableContrastTiles; import static com.android.launcher3.Flags.enableCursorHoverStates; -import static com.android.launcher3.allapps.AlphabeticalAppsList.PRIVATE_SPACE_PACKAGE; import static com.android.launcher3.graphics.PreloadIconDrawable.newPendingIcon; import static com.android.launcher3.icons.BitmapInfo.FLAG_NO_BADGE; import static com.android.launcher3.icons.BitmapInfo.FLAG_SKIP_USER_BADGE; import static com.android.launcher3.icons.BitmapInfo.FLAG_THEMED; import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound; -import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR; import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_INCREMENTAL_DOWNLOAD_ACTIVE; import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE; -import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_SHOW_DOWNLOAD_PROGRESS_MASK; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.content.Context; import android.content.res.ColorStateList; @@ -51,40 +40,35 @@ import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; -import android.graphics.RectF; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.icu.text.MessageFormat; -import android.text.Spannable; -import android.text.SpannableString; import android.text.StaticLayout; import android.text.TextPaint; import android.text.TextUtils; import android.text.TextUtils.TruncateAt; -import android.text.style.ImageSpan; import android.util.AttributeSet; import android.util.Log; import android.util.Property; +import android.util.Size; import android.util.TypedValue; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.view.ViewDebug; -import android.view.accessibility.AccessibilityNodeInfo; import android.widget.TextView; -import androidx.annotation.DrawableRes; -import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.UiThread; import androidx.annotation.VisibleForTesting; -import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; import com.android.launcher3.accessibility.BaseAccessibilityDelegate; import com.android.launcher3.dot.DotInfo; import com.android.launcher3.dragndrop.DragOptions.PreDragCondition; import com.android.launcher3.dragndrop.DraggableView; import com.android.launcher3.folder.FolderIcon; +import com.android.launcher3.graphics.IconPalette; +import com.android.launcher3.graphics.IconShape; import com.android.launcher3.graphics.PreloadIconDrawable; import com.android.launcher3.icons.DotRenderer; import com.android.launcher3.icons.FastBitmapDrawable; @@ -101,16 +85,14 @@ import com.android.launcher3.util.IntArray; import com.android.launcher3.util.MultiTranslateDelegate; import com.android.launcher3.util.SafeCloseable; import com.android.launcher3.util.ShortcutUtil; -import com.android.launcher3.util.Themes; import com.android.launcher3.views.ActivityContext; import com.android.launcher3.views.FloatingIconViewCompanion; +import com.patrykmichalik.opto.core.PreferenceExtensionsKt; import java.text.NumberFormat; import java.util.HashMap; import java.util.Locale; -import java.util.Objects; -import com.patrykmichalik.opto.core.PreferenceExtensionsKt; import app.lawnchair.LawnchairApp; import app.lawnchair.font.FontManager; import app.lawnchair.gestures.IconGestureListener; @@ -119,15 +101,15 @@ import app.lawnchair.preferences2.PreferenceManager2; import app.lawnchair.util.LawnchairUtilsKt; /** - * TextView that draws a bubble behind the text. We cannot use a LineBackgroundSpan - * because we want to make the bubble taller than the text and TextView's clip is + * TextView that draws a bubble behind the text. We cannot use a + * LineBackgroundSpan + * because we want to make the bubble taller than the text and TextView's clip + * is * too aggressive. */ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, FloatingIconViewCompanion, DraggableView, Reorderable { - public static final String TAG = "BubbleTextView"; - public static final int DISPLAY_WORKSPACE = 0; public static final int DISPLAY_ALL_APPS = 1; public static final int DISPLAY_FOLDER = 2; @@ -143,14 +125,9 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, private static final int MAX_SEARCH_LOOP_COUNT = 20; private static final Character NEW_LINE = '\n'; private static final String EMPTY = ""; - private static final StringMatcherUtility.StringMatcher MATCHER = - StringMatcherUtility.StringMatcher.getInstance(); - private static final int BOLD_TEXT_ADJUSTMENT = FONT_WEIGHT_BOLD - FONT_WEIGHT_NORMAL; + private static final StringMatcherUtility.StringMatcher MATCHER = StringMatcherUtility.StringMatcher.getInstance(); - public static final int LINE_INDICATOR_ANIM_DURATION = 150; - private static final float MINIMIZED_APP_INDICATOR_SCALE = 0.5f; - - private static final int[] STATE_PRESSED = new int[]{android.R.attr.state_pressed}; + private static final int[] STATE_PRESSED = new int[] { android.R.attr.state_pressed }; private float mScaleForReorderBounce = 1f; @@ -158,8 +135,8 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, private CharSequence mLastOriginalText; private CharSequence mLastModifiedText; - private static final Property DOT_SCALE_PROPERTY - = new Property(Float.TYPE, "dotScale") { + private static final Property DOT_SCALE_PROPERTY = new Property( + Float.TYPE, "dotScale") { @Override public Float get(BubbleTextView bubbleTextView) { return bubbleTextView.mDotParams.scale; @@ -172,8 +149,8 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, } }; - public static final Property TEXT_ALPHA_PROPERTY - = new Property(Float.class, "textAlpha") { + public static final Property TEXT_ALPHA_PROPERTY = new Property( + Float.class, "textAlpha") { @Override public Float get(BubbleTextView bubbleTextView) { return bubbleTextView.mTextAlpha; @@ -185,38 +162,8 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, } }; - private static final Property LINE_INDICATOR_COLOR_PROPERTY = - new Property<>(Integer.class, "lineIndicatorColor") { - - @Override - public Integer get(BubbleTextView bubbleTextView) { - return bubbleTextView.mLineIndicatorColor; - } - - @Override - public void set(BubbleTextView bubbleTextView, Integer color) { - bubbleTextView.mLineIndicatorColor = color; - bubbleTextView.invalidate(); - } - }; - - private static final Property LINE_INDICATOR_SCALE_PROPERTY = - new Property<>(Float.TYPE, "lineIndicatorScale") { - - @Override - public Float get(BubbleTextView bubbleTextView) { - return bubbleTextView.mLineIndicatorScale; - } - - @Override - public void set(BubbleTextView bubbleTextView, Float scale) { - bubbleTextView.mLineIndicatorScale = scale; - bubbleTextView.invalidate(); - } - }; - private final MultiTranslateDelegate mTranslateDelegate = new MultiTranslateDelegate(this); - protected final ActivityContext mActivity; + private final ActivityContext mActivity; private FastBitmapDrawable mIcon; private DeviceProfile mDeviceProfile; private boolean mCenterVertically; @@ -234,7 +181,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, @ViewDebug.ExportedProperty(category = "launcher") private boolean mSkipUserBadge = false; @ViewDebug.ExportedProperty(category = "launcher") - protected boolean mIsIconVisible = true; + private boolean mIsIconVisible = true; @ViewDebug.ExportedProperty(category = "launcher") private int mTextColor; @ViewDebug.ExportedProperty(category = "launcher") @@ -245,29 +192,20 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, @ViewDebug.ExportedProperty(category = "launcher") private DotInfo mDotInfo; private DotRenderer mDotRenderer; + private Locale mCurrentLocale; @ViewDebug.ExportedProperty(category = "launcher", deepExport = true) protected DotRenderer.DrawParams mDotParams; private Animator mDotScaleAnim; private boolean mForceHideDot; // These fields, related to showing running apps, are only used for Taskbar. - private final int mRunningAppIndicatorWidth; - private final int mRunningAppIndicatorHeight; + private final Size mRunningAppIndicatorSize; private final int mRunningAppIndicatorTopMargin; + private final Size mMinimizedAppIndicatorSize; + private final int mMinimizedAppIndicatorTopMargin; private final Paint mRunningAppIndicatorPaint; private final Rect mRunningAppIconBounds = new Rect(); private RunningAppState mRunningAppState; - private final int mRunningAppIndicatorColor; - private final int mMinimizedAppIndicatorColor; - @ViewDebug.ExportedProperty(category = "launcher") - private int mLineIndicatorColor; - @ViewDebug.ExportedProperty(category = "launcher") - private float mLineIndicatorScale; - private int mLineIndicatorAnimStartDelay; - private Animator mLineIndicatorAnim; - - private final String mMinimizedStateDescription; - private final String mRunningStateDescription; /** * Various options for the running state of an app. @@ -287,7 +225,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, private CancellableTask mIconLoadRequest; - private boolean mHighResUpdateInProgress = false; + private boolean mEnableIconUpdateAnimation = false; private final PreferenceManager2 pref2; private IconGestureListener mGestureListener; @@ -305,15 +243,11 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, mActivity = ActivityContext.lookupContext(context); FastBitmapDrawable.setFlagHoverEnabled(enableCursorHoverStates()); pref2 = PreferenceManager2.getInstance(context); - mMinimizedStateDescription = getContext().getString( - R.string.app_minimized_state_description); - mRunningStateDescription = getContext().getString(R.string.app_running_state_description); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.BubbleTextView, defStyle, 0); mLayoutHorizontal = a.getBoolean(R.styleable.BubbleTextView_layoutHorizontal, false); - mIsRtl = (getResources().getConfiguration().getLayoutDirection() - == View.LAYOUT_DIRECTION_RTL); + mIsRtl = (getResources().getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL); mDeviceProfile = mActivity.getDeviceProfile(); mCenterVertically = a.getBoolean(R.styleable.BubbleTextView_centerVertically, false); @@ -352,24 +286,26 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, defaultIconSize); a.recycle(); - mRunningAppIndicatorWidth = - getResources().getDimensionPixelSize(R.dimen.taskbar_running_app_indicator_width); - mRunningAppIndicatorHeight = - getResources().getDimensionPixelSize(R.dimen.taskbar_running_app_indicator_height); - mRunningAppIndicatorTopMargin = + mRunningAppIndicatorSize = new Size( + getResources().getDimensionPixelSize(R.dimen.taskbar_running_app_indicator_width), + getResources().getDimensionPixelSize(R.dimen.taskbar_running_app_indicator_height)); + mMinimizedAppIndicatorSize = new Size( + getResources().getDimensionPixelSize(R.dimen.taskbar_minimized_app_indicator_width), getResources().getDimensionPixelSize( - R.dimen.taskbar_running_app_indicator_top_margin); - + R.dimen.taskbar_minimized_app_indicator_height)); + mRunningAppIndicatorTopMargin = getResources().getDimensionPixelSize( + R.dimen.taskbar_running_app_indicator_top_margin); + mMinimizedAppIndicatorTopMargin = getResources().getDimensionPixelSize( + R.dimen.taskbar_minimized_app_indicator_top_margin); mRunningAppIndicatorPaint = new Paint(); - mRunningAppIndicatorColor = getResources().getColor( - R.color.taskbar_running_app_indicator_color, context.getTheme()); - mMinimizedAppIndicatorColor = getResources().getColor( - R.color.taskbar_minimized_app_indicator_color, context.getTheme()); + mRunningAppIndicatorPaint.setColor(getResources().getColor( + R.color.taskbar_running_app_indicator_color, context.getTheme())); mLongPressHelper = new CheckLongPressHelper(this); mDotParams = new DotRenderer.DrawParams(); + mCurrentLocale = context.getResources().getConfiguration().locale; setEllipsize(TruncateAt.END); setAccessibilityDelegate(mActivity.getAccessibilityDelegate()); setTextAlpha(1f); @@ -377,8 +313,10 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, @Override protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { - // Disable marques when not focused to that, so that updating text does not cause relayout. + // Disable marques when not focused to that, so that updating text does not + // cause relayout. setEllipsize(focused ? TruncateAt.MARQUEE : TruncateAt.END); + resetIconScale(true); super.onFocusChanged(focused, direction, previouslyFocusedRect); } @@ -401,11 +339,10 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, mDotParams.scale = 0f; mForceHideDot = false; setBackground(null); - - mLineIndicatorColor = Color.TRANSPARENT; - mLineIndicatorScale = 0; - mLineIndicatorAnimStartDelay = 0; - cancelLineIndicatorAnim(); + if (com.android.launcher3.config.FeatureFlags.twoLineAllApps(this.getContext()) + || com.android.launcher3.config.FeatureFlags.ENABLE_TWOLINE_DEVICESEARCH.get()) { + setMaxLines(1); + } setTag(null); if (mIconLoadRequest != null) { @@ -419,6 +356,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, setTranslationY(0); setMaxLines(1); setVisibility(VISIBLE); + resetIconScale(true); } private void cancelDotScaleAnim() { @@ -439,6 +377,32 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, mDotScaleAnim.start(); } + @UiThread + public void applyFromWorkspaceItem(WorkspaceItemInfo info) { + applyFromWorkspaceItem(info, /* animate = */ false, /* staggerIndex = */ 0); + if (info != null) { + mGestureListener = new IconGestureListener(mContext, pref2, info); + } + } + + @UiThread + public void applyFromWorkspaceItem(WorkspaceItemInfo info, boolean animate, int staggerIndex) { + applyFromWorkspaceItem(info, null); + } + + /** + * Returns whether the newInfo differs from the current getTag(). + */ + public boolean shouldAnimateIconChange(WorkspaceItemInfo newInfo) { + WorkspaceItemInfo oldInfo = getTag() instanceof WorkspaceItemInfo + ? (WorkspaceItemInfo) getTag() + : null; + boolean changedIcons = oldInfo != null && oldInfo.getTargetComponent() != null + && newInfo.getTargetComponent() != null + && !oldInfo.getTargetComponent().equals(newInfo.getTargetComponent()); + return changedIcons && isShown(); + } + @Override public void setAccessibilityDelegate(AccessibilityDelegate delegate) { if (delegate instanceof BaseAccessibilityDelegate) { @@ -452,10 +416,10 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, } @UiThread - public void applyFromWorkspaceItem(WorkspaceItemInfo info) { + public void applyFromWorkspaceItem(WorkspaceItemInfo info, PreloadIconDrawable icon) { applyIconAndLabel(info); setItemInfo(info); - + applyLoadingState(icon); applyDotState(info, false /* animate */); setDownloadStateContentDescription(info, info.getProgressLevel()); } @@ -463,11 +427,16 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, @UiThread public void applyFromApplicationInfo(AppInfo info) { applyIconAndLabel(info); + + // We don't need to check the info since it's not a WorkspaceItemInfo setItemInfo(info); // Verify high res immediately verifyHighRes(); + if ((info.runtimeStatusFlags & ItemInfoWithIcon.FLAG_SHOW_DOWNLOAD_PROGRESS_MASK) != 0) { + applyProgressLevel(); + } applyDotState(info, false /* animate */); setDownloadStateContentDescription(info, info.getProgressLevel()); } @@ -487,75 +456,10 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, setDownloadStateContentDescription(info, info.getProgressLevel()); } - /** - * Directly set the icon and label. - */ - @UiThread - public void applyIconAndLabel(Drawable icon, CharSequence label) { - applyCompoundDrawables(icon); - setText(label); - setContentDescription(label); - } - /** Updates whether the app this view represents is currently running. */ @UiThread - public void updateRunningState(RunningAppState runningAppState, boolean animate) { - if (runningAppState.equals(mRunningAppState)) { - return; - } + public void updateRunningState(RunningAppState runningAppState) { mRunningAppState = runningAppState; - cancelLineIndicatorAnim(); - - int color = switch (mRunningAppState) { - case NOT_RUNNING -> Color.TRANSPARENT; - case RUNNING -> mRunningAppIndicatorColor; - case MINIMIZED -> mMinimizedAppIndicatorColor; - }; - float scale = switch (mRunningAppState) { - case NOT_RUNNING -> 0; - case RUNNING -> 1; - case MINIMIZED -> MINIMIZED_APP_INDICATOR_SCALE; - }; - - if (!animate) { - mLineIndicatorColor = color; - mLineIndicatorScale = scale; - invalidate(); - return; - } - - AnimatorSet lineIndicatorAnim = new AnimatorSet(); - mLineIndicatorAnim = lineIndicatorAnim; - Animator colorAnimator = ObjectAnimator.ofArgb(this, LINE_INDICATOR_COLOR_PROPERTY, color); - Animator scaleAnimator = ObjectAnimator.ofFloat(this, LINE_INDICATOR_SCALE_PROPERTY, scale); - lineIndicatorAnim.playTogether(colorAnimator, scaleAnimator); - - lineIndicatorAnim.setInterpolator(EMPHASIZED); - lineIndicatorAnim.setStartDelay(mLineIndicatorAnimStartDelay); - lineIndicatorAnim.setDuration(LINE_INDICATOR_ANIM_DURATION).start(); - } - - public void setLineIndicatorAnimStartDelay(int lineIndicatorAnimStartDelay) { - mLineIndicatorAnimStartDelay = lineIndicatorAnimStartDelay; - } - - private void cancelLineIndicatorAnim() { - if (mLineIndicatorAnim != null) { - mLineIndicatorAnim.cancel(); - } - } - - /** - * Returns state description of this icon. - */ - public String getIconStateDescription() { - if (mRunningAppState == MINIMIZED) { - return mMinimizedStateDescription; - } else if (mRunningAppState == RUNNING) { - return mRunningStateDescription; - } else { - return ""; - } } protected void setItemInfo(ItemInfoWithIcon itemInfo) { @@ -564,81 +468,31 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, @VisibleForTesting @UiThread - public void applyIconAndLabel(ItemInfoWithIcon info) { - FastBitmapDrawable oldIcon = mIcon; - if (!canReuseIcon(info)) { - setNonPendingIcon(info); - } - applyLabel(info); - maybeApplyProgressLevel(info, oldIcon); - } - - /** - * Check if we can reuse icon so that any animation is preserved - */ - private boolean canReuseIcon(ItemInfoWithIcon info) { - return mIcon instanceof PreloadIconDrawable p - && p.hasNotCompleted() && p.isSameInfo(info.bitmap); - } - - /** - * Apply progress level to the icon if necessary - */ - private void maybeApplyProgressLevel(ItemInfoWithIcon info, FastBitmapDrawable oldIcon) { - if (!shouldApplyProgressLevel(info, oldIcon)) { - return; - } - PreloadIconDrawable pendingIcon = applyProgressLevel(info); - boolean isNoLongerPending = info instanceof WorkspaceItemInfo wii - ? !wii.hasPromiseIconUi() : !info.isArchived(); - if (isNoLongerPending && info.getProgressLevel() == 100 && pendingIcon != null) { - pendingIcon.maybePerformFinishedAnimation( - (oldIcon instanceof PreloadIconDrawable p) ? p : pendingIcon, - () -> setNonPendingIcon( - (getTag() instanceof ItemInfoWithIcon iiwi) ? iiwi : info)); - } - } - - /** - * Check if progress level should be applied to the icon - */ - private boolean shouldApplyProgressLevel(ItemInfoWithIcon info, FastBitmapDrawable oldIcon) { - return (info.runtimeStatusFlags & FLAG_SHOW_DOWNLOAD_PROGRESS_MASK) != 0 - || (info instanceof WorkspaceItemInfo wii && wii.hasPromiseIconUi()) - || (oldIcon instanceof PreloadIconDrawable p && p.hasNotCompleted()); - } - - private void setNonPendingIcon(ItemInfoWithIcon info) { - // Set nonPendingIcon acts as a restart which should refresh the flag state when applicable. - int flags = Objects.equals(info.getTargetPackage(), PRIVATE_SPACE_PACKAGE) - ? info.bitmap.creationFlags : shouldUseTheme() ? FLAG_THEMED : 0; - // Remove badge on icons smaller than 48dp. - if (mHideBadge || mDisplay == DISPLAY_SEARCH_RESULT_SMALL) { - flags |= FLAG_NO_BADGE; - } - if (mSkipUserBadge) { - flags |= FLAG_SKIP_USER_BADGE; - } - FastBitmapDrawable iconDrawable = info.newIcon(getContext(), flags); + protected void applyIconAndLabel(ItemInfoWithIcon info) { + boolean useTheme = shouldUseTheme(); + FastBitmapDrawable iconDrawable = info.newIcon(getContext(), useTheme); mDotParams.appColor = iconDrawable.getIconColor(); - mDotParams.dotColor = Themes.getAttrColor(getContext(), R.attr.notificationDotColor); + mDotParams.dotColor = IconPalette.getMutedColor(iconDrawable.getIconColor(), 0.54f); setIcon(iconDrawable); + applyLabel(info); + resetIconScale(true); } public boolean shouldUseTheme() { if (mDisplay == DISPLAY_ALL_APPS || mDisplay == DISPLAY_DRAWER_FOLDER) { return PreferenceManager.getInstance(getContext()).getDrawerThemedIcons().get(); } - return mDisplay == DISPLAY_WORKSPACE || mDisplay == DISPLAY_FOLDER - || mDisplay == DISPLAY_TASKBAR; + return mDisplay == DISPLAY_WORKSPACE || mDisplay == DISPLAY_FOLDER || mDisplay == DISPLAY_TASKBAR; } /** - * Only if actual text can be displayed in two line, the {@code true} value will be effective. + * Only if actual text can be displayed in two line, the {@code true} value will + * be effective. */ protected boolean shouldUseTwoLine() { - return mDeviceProfile.inv.enableTwoLinesInAllApps - && (mDisplay == DISPLAY_ALL_APPS || mDisplay == DISPLAY_PREDICTION_ROW); + return (com.android.launcher3.config.FeatureFlags.twoLineAllApps(this.getContext()) && mDisplay == DISPLAY_ALL_APPS) + || (com.android.launcher3.config.FeatureFlags.ENABLE_TWOLINE_DEVICESEARCH.get() + && mDisplay == DISPLAY_SEARCH_RESULT); } @UiThread @@ -648,13 +502,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, mLastOriginalText = label; mLastModifiedText = mLastOriginalText; mBreakPointsIntArray = StringMatcherUtility.getListOfBreakpoints(label, MATCHER); - if (Flags.useNewIconForArchivedApps() - && info instanceof ItemInfoWithIcon infoWithIcon - && infoWithIcon.isInactiveArchive()) { - setTextWithArchivingIcon(label); - } else { - setText(label); - } + setText(label); } if (info.contentDescription != null) { setContentDescription(info.isDisabled() @@ -663,16 +511,6 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, } } - @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); - if (getTag() instanceof ItemInfoWithIcon infoWithIcon && infoWithIcon.isInactiveArchive()) { - info.addAction(new AccessibilityNodeInfo.AccessibilityAction( - AccessibilityNodeInfoCompat.ACTION_CLICK, - getContext().getString(R.string.app_unarchiving_action))); - } - } - /** This is used for testing to forcefully set the display. */ @VisibleForTesting public void setDisplay(int display) { @@ -709,6 +547,12 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, @Override public boolean onTouchEvent(MotionEvent event) { + + if (mGestureListener != null) { + mGestureListener.onTouch(this, event); + resetIconScale(true); + } + // ignore events if they happen in padding area if (event.getAction() == MotionEvent.ACTION_DOWN && shouldIgnoreTouchDown(event.getX(), event.getY())) { @@ -758,12 +602,17 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, @Override public boolean onKeyUp(int keyCode, KeyEvent event) { - // Unlike touch events, keypress event propagate pressed state change immediately, - // without waiting for onClickHandler to execute. Disable pressed state changes here + // Unlike touch events, keypress event propagate pressed state change + // immediately, + // without waiting for onClickHandler to execute. Disable pressed state changes + // here // to avoid flickering. mIgnorePressedStateChange = true; + mStayPressed = true; boolean result = super.onKeyUp(keyCode, event); mIgnorePressedStateChange = false; + mStayPressed = false; + resetIconScale(true); refreshDrawableState(); return result; } @@ -854,7 +703,8 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, protected void drawDotIfNecessary(Canvas canvas) { if (!mForceHideDot && (hasDot() || mDotParams.scale > 0)) { getIconBounds(mDotParams.iconBounds); - Utilities.scaleRectAboutCenter(mDotParams.iconBounds, ICON_VISIBLE_AREA_FACTOR); + Utilities.scaleRectAboutCenter(mDotParams.iconBounds, + IconShape.INSTANCE.get(getContext()).getNormalizationScale()); final int scrollX = getScrollX(); final int scrollY = getScrollY(); canvas.translate(scrollX, scrollY); @@ -863,63 +713,23 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, } } - /** Draws a background behind the App Title label when required. **/ - public void drawAppContrastTile(Canvas canvas) { - RectF appTitleBounds; - Paint.FontMetrics fm = getPaint().getFontMetrics(); - Rect tmpRect = new Rect(); - getDrawingRect(tmpRect); - CharSequence text = getText(); - - int mAppTitleHorizontalPadding = getResources().getDimensionPixelSize( - R.dimen.app_title_pill_horizontal_padding); - int mRoundRectPadding = getResources().getDimensionPixelSize( - R.dimen.app_title_pill_round_rect_padding); - - float titleLength = (getPaint().measureText(text, 0, text.length()) - + (mAppTitleHorizontalPadding + mRoundRectPadding) * 2); - titleLength = Math.min(titleLength, tmpRect.width()); - appTitleBounds = new RectF((tmpRect.width() - titleLength) / 2.f - getCompoundPaddingLeft(), - 0, (tmpRect.width() + titleLength) / 2.f + getCompoundPaddingRight(), - (int) Math.ceil(fm.bottom - fm.top)); - appTitleBounds.inset((mAppTitleHorizontalPadding) * 2, 0); - - - if (mIcon != null) { - Rect iconBounds = new Rect(); - getIconBounds(iconBounds); - int textStart = iconBounds.bottom + getCompoundDrawablePadding(); - appTitleBounds.offset(0, textStart); - } - - canvas.drawRoundRect(appTitleBounds, appTitleBounds.height() / 2, - appTitleBounds.height() / 2, - PillColorProvider.getInstance(getContext()).getAppTitlePillPaint()); - } - - /** Draws a line under the app icon if this is representing a running app in Desktop Mode. */ + /** + * Draws a line under the app icon if this is representing a running app in + * Desktop Mode. + */ protected void drawRunningAppIndicatorIfNecessary(Canvas canvas) { - if (mDisplay != DISPLAY_TASKBAR - || mLineIndicatorScale == 0 - || mLineIndicatorColor == Color.TRANSPARENT) { + if (mRunningAppState == RunningAppState.NOT_RUNNING || mDisplay != DISPLAY_TASKBAR) { return; } getIconBounds(mRunningAppIconBounds); - Utilities.scaleRectAboutCenter(mRunningAppIconBounds, ICON_VISIBLE_AREA_FACTOR); - - final int indicatorTop = mRunningAppIconBounds.bottom + mRunningAppIndicatorTopMargin; - final float indicatorWidth = mRunningAppIndicatorWidth * mLineIndicatorScale; - final float cornerRadius = mRunningAppIndicatorHeight / 2f; - mRunningAppIndicatorPaint.setColor(mLineIndicatorColor); - - canvas.drawRoundRect( - mRunningAppIconBounds.centerX() - indicatorWidth / 2f, - indicatorTop, - mRunningAppIconBounds.centerX() + indicatorWidth / 2f, - indicatorTop + mRunningAppIndicatorHeight, - cornerRadius, - cornerRadius, - mRunningAppIndicatorPaint); + // TODO(b/333872717): update color, shape, and size of indicator + boolean isMinimized = mRunningAppState == RunningAppState.MINIMIZED; + int indicatorTop = mRunningAppIconBounds.bottom + (isMinimized ? mMinimizedAppIndicatorTopMargin + : mRunningAppIndicatorTopMargin); + final Size indicatorSize = isMinimized ? mMinimizedAppIndicatorSize : mRunningAppIndicatorSize; + canvas.drawRect(mRunningAppIconBounds.centerX() - indicatorSize.getWidth() / 2, + indicatorTop, mRunningAppIconBounds.centerX() + indicatorSize.getWidth() / 2, + indicatorTop + indicatorSize.getHeight(), mRunningAppIndicatorPaint); } @Override @@ -998,16 +808,6 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, setPadding(getPaddingLeft(), (height - cellHeightPx) / 2, getPaddingRight(), getPaddingBottom()); } - if (shouldDrawAppContrastTile()) { - int mAppTitleHorizontalPadding = getResources().getDimensionPixelSize( - R.dimen.app_title_pill_horizontal_padding); - int mRoundRectPadding = getResources().getDimensionPixelSize( - R.dimen.app_title_pill_round_rect_padding); - - setPadding(mAppTitleHorizontalPadding + mRoundRectPadding, getPaddingTop(), - mAppTitleHorizontalPadding + mRoundRectPadding, - getPaddingBottom()); - } // Only apply two line for all_apps and device search only if necessary. if (shouldUseTwoLine() && (mLastOriginalText != null)) { int allowedVerticalSpace = height - getPaddingTop() - getPaddingBottom() @@ -1024,13 +824,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, getLineSpacingExtra()); if (!TextUtils.equals(modifiedString, mLastModifiedText)) { mLastModifiedText = modifiedString; - if (Flags.useNewIconForArchivedApps() - && getTag() instanceof ItemInfoWithIcon infoWithIcon - && infoWithIcon.isInactiveArchive()) { - setTextWithArchivingIcon(modifiedString); - } else { - setText(modifiedString); - } + setText(modifiedString); // if text contains NEW_LINE, set max lines to 2 if (TextUtils.indexOf(modifiedString, NEW_LINE) != -1) { setSingleLine(false); @@ -1051,54 +845,10 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, super.setTextColor(getModifiedColor()); } - /** - * Sets text with a start icon for App Archiving. - * Uses a bolded drawable if text is bolded. - * @param text - */ - private void setTextWithArchivingIcon(CharSequence text) { - var drawableId = R.drawable.cloud_download_24px; - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S - && getResources().getConfiguration().fontWeightAdjustment >= BOLD_TEXT_ADJUSTMENT) { - // If System bold text setting is on, then use a bolded icon - drawableId = R.drawable.cloud_download_semibold_24px; - } - setTextWithStartIcon(text, drawableId); - } - - /** - * Uses a SpannableString to set text with a Drawable at the start of the TextView - * @param text text to use for TextView - * @param drawableId Drawable Resource to use for drawing image at start of text - */ - @VisibleForTesting - public void setTextWithStartIcon(CharSequence text, @DrawableRes int drawableId) { - Drawable drawable = getContext().getDrawable(drawableId); - if (drawable == null) { - setText(text); - Log.w(TAG, "setTextWithStartIcon: start icon Drawable not found from resources" - + ", will just set text instead."); - return; - } - drawable.setTint(getCurrentTextColor()); - drawable.setBounds(0, 0, Math.round(getTextSize()), Math.round(getTextSize())); - ImageSpan imageSpan = new ImageSpan(drawable, ImageSpan.ALIGN_CENTER); - // First space will be replaced with Drawable, second space is for space before text. - SpannableString spannable = new SpannableString(" " + text); - spannable.setSpan(imageSpan, 0, 1, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); - setText(spannable); - } - @Override public void setTextColor(ColorStateList colors) { - if (shouldDrawAppContrastTile()) { - mTextColor = PillColorProvider.getInstance( - getContext()).getAppTitleTextPaint().getColor(); - } else { - mTextColor = colors.getDefaultColor(); - mTextColorStateList = colors; - } - + mTextColor = colors.getDefaultColor(); + mTextColorStateList = colors; if (Float.compare(mTextAlpha, 1) == 0) { super.setTextColor(colors); } else { @@ -1107,23 +857,15 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, } public boolean shouldTextBeVisible() { - // Text should be visible everywhere but the hotseat. + // Text should be visible everywhere, and in hotseat if getEnableLabelInDock is enabled. Object tag = getParent() instanceof FolderIcon ? ((View) getParent()).getTag() : getTag(); ItemInfo info = tag instanceof ItemInfo ? (ItemInfo) tag : null; + return info == null || info.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT && info.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION || PreferenceExtensionsKt.firstBlocking(pref2.getEnableLabelInDock()); } - /** - * Whether or not an App title contrast tile should be drawn for this element. - **/ - public boolean shouldDrawAppContrastTile() { - return mDisplay == DISPLAY_WORKSPACE && shouldTextBeVisible() - && PillColorProvider.getInstance(getContext()).isMatchaEnabled() - && enableContrastTiles(); - } - public void setTextVisibility(boolean visible) { setTextAlpha(visible ? 1 : 0); } @@ -1156,19 +898,30 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, } /** - * Generate a new string that will support two line text depending on the current string. - * This method calculates the limited width of a text view and creates a string to fit as - * many words as it can until the limit is reached. Once the limit is reached, we decide to - * either return the original title or continue on a new line. How to get the new string is by - * iterating through the list of break points and determining if the strings between the break - * points can fit within the line it is in. We will show the modified string if there is enough - * horizontal and vertical space, otherwise this method will just return the original string. + * Generate a new string that will support two line text depending on the + * current string. + * This method calculates the limited width of a text view and creates a string + * to fit as + * many words as it can until the limit is reached. Once the limit is reached, + * we decide to + * either return the original title or continue on a new line. How to get the + * new string is by + * iterating through the list of break points and determining if the strings + * between the break + * points can fit within the line it is in. We will show the modified string if + * there is enough + * horizontal and vertical space, otherwise this method will just return the + * original string. * Example assuming each character takes up one spot: * title = "Battery Stats", breakpoint = [6], stringPtr = 0, limitedWidth = 7 - * We get the current word -> from sublist(0, breakpoint[i]+1) so sublist (0,7) -> Battery, - * now stringPtr = 7 then from sublist(7) the current string is " Stats" and the runningWidth - * at this point exceeds limitedWidth and so we put " Stats" onto the next line (after checking - * if the first char is a SPACE, we trim to append "Stats". So resulting string would be + * We get the current word -> from sublist(0, breakpoint[i]+1) so sublist (0,7) + * -> Battery, + * now stringPtr = 7 then from sublist(7) the current string is " Stats" and the + * runningWidth + * at this point exceeds limitedWidth and so we put " Stats" onto the next line + * (after checking + * if the first char is a SPACE, we trim to append "Stats". So resulting string + * would be * "Battery\nStats" */ public static CharSequence modifyTitleToSupportMultiLine(int limitedWidth, int limitedHeight, @@ -1200,8 +953,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, // character in the beginning of the current word and just put in the rest of // the characters. CharSequence lastCharacters = title.subSequence(stringPtr, title.length()); - int beginningLetterType = - Character.getType(Character.codePointAt(lastCharacters, 0)); + int beginningLetterType = Character.getType(Character.codePointAt(lastCharacters, 0)); if (beginningLetterType == Character.SPACE_SEPARATOR || beginningLetterType == Character.LINE_SEPARATOR) { lastCharacters = lastCharacters.length() > 1 @@ -1233,46 +985,82 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, mLongPressHelper.cancelLongPress(); } + /** + * Applies the loading progress value to the progress bar. + * + * If this app is installing, the progress bar will be updated with the + * installation progress. + * If this app is installed and downloading incrementally, the progress bar will + * be updated + * with the total download progress. + */ + public void applyLoadingState(PreloadIconDrawable icon) { + if (getTag() instanceof ItemInfoWithIcon) { + WorkspaceItemInfo info = (WorkspaceItemInfo) getTag(); + if ((info.runtimeStatusFlags & FLAG_INCREMENTAL_DOWNLOAD_ACTIVE) != 0 + || info.hasPromiseIconUi() + || (info.runtimeStatusFlags & FLAG_INSTALL_SESSION_ACTIVE) != 0 + || (icon != null)) { + updateProgressBarUi(info.getProgressLevel() == 100 ? icon : null); + } + } + } + + private void updateProgressBarUi(PreloadIconDrawable oldIcon) { + FastBitmapDrawable originalIcon = mIcon; + PreloadIconDrawable preloadDrawable = applyProgressLevel(); + if (preloadDrawable != null && oldIcon != null) { + preloadDrawable.maybePerformFinishedAnimation(oldIcon, () -> setIcon(originalIcon)); + } + } + /** Applies the given progress level to the this icon's progress bar. */ @Nullable - private PreloadIconDrawable applyProgressLevel(ItemInfoWithIcon info) { - if (info.isInactiveArchive()) { + public PreloadIconDrawable applyProgressLevel() { + if (!(getTag() instanceof ItemInfoWithIcon) + || ((ItemInfoWithIcon) getTag()).isInactiveArchive()) { return null; } + ItemInfoWithIcon info = (ItemInfoWithIcon) getTag(); int progressLevel = info.getProgressLevel(); if (progressLevel >= 100) { setContentDescription(info.contentDescription != null - ? info.contentDescription : ""); + ? info.contentDescription + : ""); } else if (progressLevel > 0) { setDownloadStateContentDescription(info, progressLevel); } else { setContentDescription(getContext() .getString(R.string.app_waiting_download_title, info.title)); } - PreloadIconDrawable pid; - if (mIcon instanceof PreloadIconDrawable p) { - pid = p; - pid.setLevel(progressLevel); - pid.setIsDisabled(isIconDisabled(info)); - } else { - pid = makePreloadIcon(info); - setIcon(pid); + if (mIcon != null) { + PreloadIconDrawable preloadIconDrawable; + if (mIcon instanceof PreloadIconDrawable) { + preloadIconDrawable = (PreloadIconDrawable) mIcon; + preloadIconDrawable.setLevel(progressLevel); + preloadIconDrawable.setIsDisabled(isIconDisabled(info)); + } else { + preloadIconDrawable = makePreloadIcon(); + setIcon(preloadIconDrawable); + } + return preloadIconDrawable; } - return pid; + return null; } /** - * Creates a PreloadIconDrawable with the appropriate progress level without mutating this + * Creates a PreloadIconDrawable with the appropriate progress level without + * mutating this * object. */ @Nullable public PreloadIconDrawable makePreloadIcon() { - return getTag() instanceof ItemInfoWithIcon info ? makePreloadIcon(info) : null; - } + if (!(getTag() instanceof ItemInfoWithIcon)) { + return null; + } - @NonNull - private PreloadIconDrawable makePreloadIcon(ItemInfoWithIcon info) { + ItemInfoWithIcon info = (ItemInfoWithIcon) getTag(); int progressLevel = info.getProgressLevel(); final PreloadIconDrawable preloadDrawable = newPendingIcon(getContext(), info); @@ -1282,16 +1070,16 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, } /** - * Returns true to grey the icon if the icon is either suspended or if the icon is pending + * Returns true to grey the icon if the icon is either suspended or if the icon + * is pending * download */ public boolean isIconDisabled(ItemInfoWithIcon info) { return info.isDisabled() || info.isPendingDownload(); } - public void applyDotState(ItemInfo itemInfo, boolean animate) { - if (mIcon != null) { + if (mIcon instanceof FastBitmapDrawable) { boolean wasDotted = mDotInfo != null; mDotInfo = mActivity.getDotInfoForItem(itemInfo); boolean isDotted = mDotInfo != null; @@ -1315,9 +1103,6 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, if (itemInfo.isDisabled()) { setContentDescription(getContext().getString(R.string.disabled_app_label, itemInfo.contentDescription)); - } else if (itemInfo instanceof WorkspaceItemInfo wai && wai.isArchived()) { - setContentDescription( - getContext().getString(R.string.app_archived_title, itemInfo.title)); } else if (hasDot()) { int count = mDotInfo.getNotificationCount(); setContentDescription( @@ -1330,18 +1115,9 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, } private void setDownloadStateContentDescription(ItemInfoWithIcon info, int progressLevel) { - if ((info.runtimeStatusFlags & ItemInfoWithIcon.FLAG_ARCHIVED) != 0 - && progressLevel == 0) { - if (mIcon instanceof PreloadIconDrawable) { - // Tell user that download is pending and not to tap to download again. - setContentDescription(getContext().getString( - R.string.app_waiting_download_title, info.title)); - } else { - setContentDescription(getContext().getString( - R.string.app_archived_title, info.title)); - } - } else if ((info.runtimeStatusFlags & FLAG_SHOW_DOWNLOAD_PROGRESS_MASK) - != 0) { + if ((info.runtimeStatusFlags & ItemInfoWithIcon.FLAG_ARCHIVED) != 0 && progressLevel == 0) { + setContentDescription(getContext().getString(R.string.app_archived_title, info.title)); + } else if ((info.runtimeStatusFlags & ItemInfoWithIcon.FLAG_SHOW_DOWNLOAD_PROGRESS_MASK) != 0) { String percentageString = NumberFormat.getPercentInstance() .format(progressLevel * 0.01); if ((info.runtimeStatusFlags & FLAG_INSTALL_SESSION_ACTIVE) != 0) { @@ -1367,7 +1143,6 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, mIcon = icon; if (mIcon != null) { mIcon.setVisible(getWindowVisibility() == VISIBLE && isShown(), false); - mIcon.setHoverScaleEnabledForDisplay(mDisplay != DISPLAY_TASKBAR); } } @@ -1392,13 +1167,18 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, } } + protected boolean iconUpdateAnimationEnabled() { + return mEnableIconUpdateAnimation; + } + protected void applyCompoundDrawables(Drawable icon) { if (icon == null) { // Icon can be null when we use the BubbleTextView for text only. return; } - // If we had already set an icon before, disable relayout as the icon size is the + // If we had already set an icon before, disable relayout as the icon size is + // the // same as before. mDisableRelayout = mIcon != null; @@ -1409,7 +1189,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, // If the current icon is a placeholder color, animate its update. if (mIcon != null && mIcon instanceof PlaceHolderIconDrawable - && mHighResUpdateInProgress) { + && iconUpdateAnimationEnabled()) { ((PlaceHolderIconDrawable) mIcon).animateIconUpdate(icon); } @@ -1424,14 +1204,15 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, } /** - * Applies the item info if it is same as what the view is pointing to currently. + * Applies the item info if it is same as what the view is pointing to + * currently. */ @Override public void reapplyItemInfo(ItemInfoWithIcon info) { if (getTag() == info) { mIconLoadRequest = null; mDisableRelayout = true; - mHighResUpdateInProgress = true; + mEnableIconUpdateAnimation = true; // Optimization: Starting in N, pre-uploads the bitmap to RenderThread. info.bitmap.icon.prepareToDraw(); @@ -1440,26 +1221,31 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, applyFromApplicationInfo((AppInfo) info); } else if (info instanceof WorkspaceItemInfo) { applyFromWorkspaceItem((WorkspaceItemInfo) info); + mActivity.invalidateParent(info); } else if (info != null) { applyFromItemInfoWithIcon(info); } mDisableRelayout = false; - mHighResUpdateInProgress = false; + mEnableIconUpdateAnimation = false; } } /** - * Verifies that the current icon is high-res otherwise posts a request to load the icon. + * Verifies that the current icon is high-res otherwise posts a request to load + * the icon. */ public void verifyHighRes() { - if (getTag() instanceof ItemInfoWithIcon info && !mHighResUpdateInProgress - && info.getMatchingLookupFlag().useLowRes()) { - if (mIconLoadRequest != null) { - mIconLoadRequest.cancel(); + if (mIconLoadRequest != null) { + mIconLoadRequest.cancel(); + mIconLoadRequest = null; + } + if (getTag() instanceof ItemInfoWithIcon) { + ItemInfoWithIcon info = (ItemInfoWithIcon) getTag(); + if (info.usingLowResIcon()) { + mIconLoadRequest = LauncherAppState.getInstance(getContext()).getIconCache() + .updateIconInBackground(BubbleTextView.this, info); } - mIconLoadRequest = LauncherAppState.getInstance(getContext()).getIconCache() - .updateIconInBackground(BubbleTextView.this, info); } } @@ -1510,15 +1296,23 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, @Override public SafeCloseable prepareDrawDragView() { - resetIconScale(); + resetIconScale(true); setForceHideDot(true); return () -> { }; } + @Override + public void resetIconScale(boolean shouldReset) { + if (shouldReset) { + resetIconScale(); + } + } + private void resetIconScale() { if (mIcon != null) { mIcon.resetScale(); + setStayPressed(false); } } @@ -1554,4 +1348,22 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, public boolean canShowLongPressPopup() { return getTag() instanceof ItemInfo && ShortcutUtil.supportsShortcuts((ItemInfo) getTag()); } + + /** Returns the package name of the app this icon represents. */ + public String getTargetPackageName() { + Object tag = getTag(); + if (tag instanceof ItemInfo itemInfo) { + return itemInfo.getTargetPackage(); + } + return null; + } + + /** Returns the ItemInfo of the app this icon represents. */ + public ItemInfo getItemInfo() { + Object tag = getTag(); + if (tag instanceof ItemInfo itemInfo) { + return itemInfo; + } + return null; + } } diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java index 1cf9ae379e..680d9cb809 100644 --- a/src/com/android/launcher3/ButtonDropTarget.java +++ b/src/com/android/launcher3/ButtonDropTarget.java @@ -42,14 +42,10 @@ import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.dragndrop.DragOptions; import com.android.launcher3.dragndrop.DragView; import com.android.launcher3.model.data.ItemInfo; -import com.android.launcher3.util.MSDLPlayerWrapper; import com.android.launcher3.util.Themes; -import com.android.launcher3.views.ActivityContext; - -import com.google.android.msdl.data.model.MSDLToken; - import app.lawnchair.theme.color.tokens.ColorTokens; import app.lawnchair.theme.drawable.DrawableTokens; +import com.android.launcher3.views.ActivityContext; /** * Implements a DropTarget. @@ -70,13 +66,15 @@ public abstract class ButtonDropTarget extends TextView protected final ActivityContext mActivityContext; protected final DropTargetHandler mDropTargetHandler; protected DropTargetBar mDropTargetBar; - private MSDLPlayerWrapper mMSDLPlayerWrapper; /** Whether this drop target is active for the current drag */ protected boolean mActive; /** Whether an accessible drag is in progress */ private boolean mAccessibleDrag; - /** An item must be dragged at least this many pixels before this drop target is enabled. */ + /** + * An item must be dragged at least this many pixels before this drop target is + * enabled. + */ private final int mDragDistanceThreshold; /** The size of the drawable shown in the drop target. */ private final int mDrawableSize; @@ -95,6 +93,7 @@ public abstract class ButtonDropTarget extends TextView public ButtonDropTarget(Context context) { this(context, null, 0); } + public ButtonDropTarget(Context context, AttributeSet attrs) { this(context, attrs, 0); } @@ -103,7 +102,6 @@ public abstract class ButtonDropTarget extends TextView super(context, attrs, defStyle); mActivityContext = ActivityContext.lookupContext(context); mDropTargetHandler = mActivityContext.getDropTargetHandler(); - mMSDLPlayerWrapper = MSDLPlayerWrapper.INSTANCE.get(context); Resources resources = getResources(); mDragDistanceThreshold = resources.getDimensionPixelSize(R.dimen.drag_distanceThreshold); @@ -143,7 +141,8 @@ public abstract class ButtonDropTarget extends TextView } protected void setDrawable(int resId) { - // We do not set the drawable in the xml as that inflates two drawables corresponding to + // We do not set the drawable in the xml as that inflates two drawables + // corresponding to // drawableLeft and drawableStart. mDrawable = getContext().getDrawable(resId).mutate(); mDrawable.setTintList(getTextColors()); @@ -163,10 +162,6 @@ public abstract class ButtonDropTarget extends TextView @Override public final void onDragEnter(DragObject d) { - // Perform Haptic feedback - if (Flags.msdlFeedback()) { - mMSDLPlayerWrapper.playToken(MSDLToken.SWIPE_THRESHOLD_INDICATOR); - } if (!mAccessibleDrag && !mTextVisible) { // Show tooltip hideTooltip(); @@ -245,8 +240,7 @@ public abstract class ButtonDropTarget extends TextView @Override public boolean isDropEnabled() { return mActive && (mAccessibleDrag || - mActivityContext.getDragController().getDistanceDragged() - >= mDragDistanceThreshold); + mActivityContext.getDragController().getDistanceDragged() >= mDragDistanceThreshold); } @Override @@ -290,7 +284,8 @@ public abstract class ButtonDropTarget extends TextView public abstract int getAccessibilityAction(); @Override - public void prepareAccessibilityDrop() { } + public void prepareAccessibilityDrop() { + } public abstract void onAccessibilityDrop(View view, ItemInfo item); @@ -365,7 +360,8 @@ public abstract class ButtonDropTarget extends TextView } /** - * Display button text over multiple lines when isMultiLine is true, single line otherwise. + * Display button text over multiple lines when isMultiLine is true, single line + * otherwise. */ public void setTextMultiLine(boolean isMultiLine) { if (mTextMultiLine != isMultiLine) { @@ -431,19 +427,20 @@ public abstract class ButtonDropTarget extends TextView return !TextUtils.equals(mText, firstLine); } if (TextUtils.equals(mText, firstLine)) { - // When multi-line is active, if it can display as one line, then text is not truncated. + // When multi-line is active, if it can display as one line, then text is not + // truncated. return false; } - CharSequence secondLine = - TextUtils.ellipsize(mText.subSequence(firstLine.length(), mText.length()), - getPaint(), availableWidth, TextUtils.TruncateAt.END); + CharSequence secondLine = TextUtils.ellipsize(mText.subSequence(firstLine.length(), mText.length()), + getPaint(), availableWidth, TextUtils.TruncateAt.END); return !(TextUtils.equals(mText.subSequence(0, firstLine.length()), firstLine) && TextUtils.equals(mText.subSequence(firstLine.length(), secondLine.length()), - secondLine)); + secondLine)); } /** - * Returns if the text will be clipped vertically within the provided availableHeight. + * Returns if the text will be clipped vertically within the provided + * availableHeight. */ @VisibleForTesting protected boolean isTextClippedVertically(int availableHeight) { @@ -454,22 +451,21 @@ public abstract class ButtonDropTarget extends TextView return textHeight + getPaddingTop() + getPaddingBottom() >= availableHeight; } - @VisibleForTesting - public void setMSDLPlayerWrapper(MSDLPlayerWrapper wrapper) { - mMSDLPlayerWrapper = wrapper; - } - /** - * Reduce the size of the text until it fits the measured width or reaches a minimum. + * Reduce the size of the text until it fits the measured width or reaches a + * minimum. * - * The minimum size is defined by {@code R.dimen.button_drop_target_min_text_size} and + * The minimum size is defined by + * {@code R.dimen.button_drop_target_min_text_size} and * it diminishes by intervals defined by * {@code R.dimen.button_drop_target_resize_text_increment} * This functionality is very similar to the option - * {@link TextView#setAutoSizeTextTypeWithDefaults(int)} but can't be used in this view because + * {@link TextView#setAutoSizeTextTypeWithDefaults(int)} but can't be used in + * this view because * the layout width is {@code WRAP_CONTENT}. * - * @return The biggest text size in SP that makes the text fit or if the text can't fit returns + * @return The biggest text size in SP that makes the text fit or if the text + * can't fit returns * the min available value */ public float resizeTextToFit() { diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java index 3fbf335c11..92277800f1 100644 --- a/src/com/android/launcher3/CellLayout.java +++ b/src/com/android/launcher3/CellLayout.java @@ -47,7 +47,6 @@ import android.view.View; import android.view.ViewDebug; import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; -import android.view.accessibility.AccessibilityNodeInfo; import androidx.annotation.IntDef; import androidx.annotation.Nullable; @@ -72,7 +71,6 @@ import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.LauncherAppWidgetInfo; import com.android.launcher3.util.CellAndSpan; import com.android.launcher3.util.GridOccupancy; -import com.android.launcher3.util.MSDLPlayerWrapper; import com.android.launcher3.util.MultiTranslateDelegate; import com.android.launcher3.util.ParcelableSparseArray; import com.android.launcher3.util.Themes; @@ -81,8 +79,6 @@ import com.android.launcher3.views.ActivityContext; import com.android.launcher3.widget.LauncherAppWidgetHostView; import com.patrykmichalik.opto.core.PreferenceExtensionsKt; -import com.google.android.msdl.data.model.MSDLToken; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -95,7 +91,7 @@ import app.lawnchair.theme.drawable.DrawableTokens; public class CellLayout extends ViewGroup { private static final String TAG = "CellLayout"; - private static final boolean LOGD = true; + private static final boolean LOGD = false; /** The color of the "leave-behind" shape when a folder is opened from Hotseat. */ private static final int FOLDER_LEAVE_BEHIND_COLOR = Color.argb(160, 245, 245, 245); @@ -175,8 +171,6 @@ public class CellLayout extends ViewGroup { private final int[] mDragCellSpan = new int[2]; private boolean mDragging = false; - public boolean mHasOnLayoutBeenCalled = false; - private boolean mPlayDragHaptics = false; private final TimeInterpolator mEaseOutInterpolator; protected final ShortcutAndWidgetContainer mShortcutsAndWidgets; @@ -214,8 +208,6 @@ public class CellLayout extends ViewGroup { private static final Paint sPaint = new Paint(); - private final MSDLPlayerWrapper mMSDLPlayerWrapper; - // Related to accessible drag and drop DragAndDropAccessibilityDelegate mTouchHelper; @@ -251,8 +243,6 @@ public class CellLayout extends ViewGroup { mContainerType = a.getInteger(R.styleable.CellLayout_containerType, WORKSPACE); a.recycle(); - mMSDLPlayerWrapper = MSDLPlayerWrapper.INSTANCE.get(context); - // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show // the user where a dragged item will land when dropped. setWillNotDraw(false); @@ -539,11 +529,9 @@ public class CellLayout extends ViewGroup { for (int i = 0; i < mDelegatedCellDrawings.size(); i++) { DelegatedCellDrawing cellDrawing = mDelegatedCellDrawings.get(i); + cellToPoint(cellDrawing.mDelegateCellX, cellDrawing.mDelegateCellY, mTempLocation); canvas.save(); - if (cellDrawing.mDelegateCellX >= 0 && cellDrawing.mDelegateCellY >= 0) { - cellToPoint(cellDrawing.mDelegateCellX, cellDrawing.mDelegateCellY, mTempLocation); - canvas.translate(mTempLocation[0], mTempLocation[1]); - } + canvas.translate(mTempLocation[0], mTempLocation[1]); cellDrawing.drawUnderItem(canvas); canvas.restore(); } @@ -672,11 +660,9 @@ public class CellLayout extends ViewGroup { for (int i = 0; i < mDelegatedCellDrawings.size(); i++) { DelegatedCellDrawing bg = mDelegatedCellDrawings.get(i); + cellToPoint(bg.mDelegateCellX, bg.mDelegateCellY, mTempLocation); canvas.save(); - if (bg.mDelegateCellX >= 0 && bg.mDelegateCellY >= 0) { - cellToPoint(bg.mDelegateCellX, bg.mDelegateCellY, mTempLocation); - canvas.translate(mTempLocation[0], mTempLocation[1]); - } + canvas.translate(mTempLocation[0], mTempLocation[1]); bg.drawOverItem(canvas); canvas.restore(); } @@ -797,12 +783,6 @@ public class CellLayout extends ViewGroup { } mShortcutsAndWidgets.addView(child, index, lp); - // Whenever an app is added, if Accessibility service is enabled, focus on that app. - if (mActivity instanceof Launcher) { - child.setTag(R.id.perform_a11y_action_on_launcher_state_normal_tag, - AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS); - } - if (markCells) markCellsAsOccupiedForView(child); return true; @@ -1039,7 +1019,6 @@ public class CellLayout extends ViewGroup { @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { - mHasOnLayoutBeenCalled = true; // b/349929393 - is the required call to onLayout not done? int left = getPaddingLeft(); left += (int) Math.ceil(getUnusedHorizontalSpace() / 2f); int right = r - l - getPaddingRight(); @@ -1182,10 +1161,6 @@ public class CellLayout extends ViewGroup { DropTarget.DragObject dragObject) { if (mDragCell[0] != cellX || mDragCell[1] != cellY || mDragCellSpan[0] != spanX || mDragCellSpan[1] != spanY) { - determineIfDragHapticsPlay(); - if (mPlayDragHaptics && Flags.msdlFeedback()) { - mMSDLPlayerWrapper.playToken(MSDLToken.DRAG_INDICATOR_DISCRETE); - } mDragCell[0] = cellX; mDragCell[1] = cellY; mDragCellSpan[0] = spanX; @@ -1211,14 +1186,6 @@ public class CellLayout extends ViewGroup { } } - private void determineIfDragHapticsPlay() { - if (mDragCell[0] != -1 || mDragCell[1] != -1 - || mDragCellSpan[0] != -1 || mDragCellSpan[1] != -1) { - // The nearest cell is known and we can play haptics - mPlayDragHaptics = true; - } - } - @SuppressLint("StringFormatMatches") public String getItemMoveDescription(int cellX, int cellY) { if (mContainerType == HOTSEAT) { @@ -1820,7 +1787,6 @@ public class CellLayout extends ViewGroup { * @param child The child that is being dropped */ void onDropChild(View child) { - mPlayDragHaptics = false; if (child != null) { CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams(); @@ -1848,8 +1814,7 @@ public class CellLayout extends ViewGroup { + (int) Math.ceil(getUnusedHorizontalSpace() / 2f); final int vStartPadding = getPaddingTop(); - int x = hStartPadding + (cellX * mBorderSpace.x) + (cellX * cellWidth) - + getTranslationXForCell(cellX, cellY); + int x = hStartPadding + (cellX * mBorderSpace.x) + (cellX * cellWidth); int y = vStartPadding + (cellY * mBorderSpace.y) + (cellY * cellHeight); int width = cellHSpan * cellWidth + ((cellHSpan - 1) * mBorderSpace.x); @@ -1858,11 +1823,6 @@ public class CellLayout extends ViewGroup { resultRect.set(x, y, x + width, y + height); } - /** Enables successors to provide an X adjustment for the cell. */ - protected int getTranslationXForCell(int cellX, int cellY) { - return 0; - } - public void markCellsAsOccupiedForView(View view) { if (view instanceof LauncherAppWidgetHostView && view.getTag() instanceof LauncherAppWidgetInfo) { diff --git a/src/com/android/launcher3/CheckLongPressHelper.java b/src/com/android/launcher3/CheckLongPressHelper.java index e9cd16c99a..3e4e96bec0 100644 --- a/src/com/android/launcher3/CheckLongPressHelper.java +++ b/src/com/android/launcher3/CheckLongPressHelper.java @@ -151,8 +151,6 @@ public class CheckLongPressHelper { handled = mView.performLongClick(); } if (handled) { - // Cancel any default long-press action on the view - mView.cancelLongPress(); mView.setPressed(false); mHasPerformedLongPress = true; } diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java index 82a398553d..0728d4aa7a 100644 --- a/src/com/android/launcher3/DeleteDropTarget.java +++ b/src/com/android/launcher3/DeleteDropTarget.java @@ -22,7 +22,6 @@ import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCH import android.content.Context; import android.text.TextUtils; import android.util.AttributeSet; -import android.util.Log; import android.view.View; import com.android.launcher3.accessibility.LauncherAccessibilityDelegate; @@ -143,11 +142,8 @@ public class DeleteDropTarget extends ButtonDropTarget { public void completeDrop(DragObject d) { ItemInfo item = d.dragInfo; if (canRemove(item)) { + onAccessibilityDrop(null, item); mDropTargetHandler.onDeleteComplete(item); - } else if (mText == getResources().getText(R.string.remove_drop_target_label)) { - Log.wtf("b/379606516", "If the drop target text is 'remove', then" - + " users should always be able to delete the item from launcher's db." - + " Invalid drag ItemInfo: " + item); } } diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java index ab0fa6f9f8..26f657bf4f 100644 --- a/src/com/android/launcher3/DeviceProfile.java +++ b/src/com/android/launcher3/DeviceProfile.java @@ -23,15 +23,14 @@ import static com.android.launcher3.InvariantDeviceProfile.INDEX_DEFAULT; import static com.android.launcher3.InvariantDeviceProfile.INDEX_LANDSCAPE; import static com.android.launcher3.InvariantDeviceProfile.INDEX_TWO_PANEL_LANDSCAPE; import static com.android.launcher3.InvariantDeviceProfile.INDEX_TWO_PANEL_PORTRAIT; -import static com.android.launcher3.InvariantDeviceProfile.deviceType; import static com.android.launcher3.Utilities.dpiFromPx; import static com.android.launcher3.Utilities.pxFromSp; +import static com.android.launcher3.config.FeatureFlags.ENABLE_MULTI_DISPLAY_PARTIAL_DEPTH; +import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ICON_OVERLAP_FACTOR; import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR; import static com.android.launcher3.testing.shared.ResourceUtils.INVALID_RESOURCE_HANDLE; import static com.android.launcher3.testing.shared.ResourceUtils.pxFromDp; import static com.android.launcher3.testing.shared.ResourceUtils.roundPxValueFromFloat; -import static com.android.wm.shell.Flags.enableBubbleBar; -import static com.android.wm.shell.Flags.enableBubbleBarOnPhones; import static com.android.wm.shell.Flags.enableTinyTaskbar; import android.annotation.SuppressLint; @@ -39,6 +38,7 @@ import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; +import android.graphics.Path; import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; @@ -53,12 +53,11 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.core.content.res.ResourcesCompat; -import app.lawnchair.DeviceProfileOverrides.TextFactors; import com.android.launcher3.CellLayout.ContainerType; import com.android.launcher3.DevicePaddings.DevicePadding; -import com.android.launcher3.folder.ClippedFolderIconLayoutRule; -import com.android.launcher3.graphics.ThemeManager; import com.android.launcher3.icons.DotRenderer; +import com.android.launcher3.icons.GraphicsUtils; +import com.android.launcher3.icons.IconNormalizer; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.responsive.CalculatedCellSpec; import com.android.launcher3.responsive.CalculatedHotseatSpec; @@ -68,20 +67,20 @@ import com.android.launcher3.responsive.ResponsiveCellSpecsProvider; import com.android.launcher3.responsive.ResponsiveSpec.Companion.ResponsiveSpecType; import com.android.launcher3.responsive.ResponsiveSpec.DimensionType; import com.android.launcher3.responsive.ResponsiveSpecsProvider; -import com.android.launcher3.testing.shared.ResourceUtils; +import com.android.launcher3.uioverrides.ApiWrapper; import com.android.launcher3.util.CellContentDimensions; import com.android.launcher3.util.DisplayController; import com.android.launcher3.util.DisplayController.Info; import com.android.launcher3.util.IconSizeSteps; import com.android.launcher3.util.ResourceHelper; import com.android.launcher3.util.WindowBounds; +import com.patrykmichalik.opto.core.PreferenceExtensionsKt; import com.android.launcher3.util.window.WindowManagerProxy; import java.io.PrintWriter; import java.util.Locale; import java.util.function.Consumer; -import com.patrykmichalik.opto.core.PreferenceExtensionsKt; import app.lawnchair.DeviceProfileOverrides; import app.lawnchair.LawnchairApp; import app.lawnchair.LawnchairAppKt; @@ -94,9 +93,10 @@ public class DeviceProfile { private static final int DEFAULT_DOT_SIZE = 100; private static final float MIN_FOLDER_TEXT_SIZE_SP = 16f; - private static final float MIN_WIDGET_PADDING_DP = 6f; + private static final float MIN_WIDGET_PADDING_DP = 8f; - // Minimum aspect ratio beyond which an extra top padding may be applied to a bottom sheet. + // Minimum aspect ratio beyond which an extra top padding may be applied to a + // bottom sheet. private static final float MIN_ASPECT_RATIO_FOR_EXTRA_TOP_PADDING = 1.5f; private static final float MAX_ASPECT_RATIO_FOR_ALTERNATE_EDIT_STATE = 1.5f; @@ -152,8 +152,10 @@ public class DeviceProfile { private CalculatedCellSpec mResponsiveAllAppsCellSpec; /** - * The maximum amount of left/right workspace padding as a percentage of the screen width. - * To be clear, this means that up to 7% of the screen width can be used as left padding, and + * The maximum amount of left/right workspace padding as a percentage of the + * screen width. + * To be clear, this means that up to 7% of the screen width can be used as left + * padding, and * 7% of the screen width can be used as right padding. */ private static final float MAX_HORIZONTAL_PADDING_PERCENT = 0.14f; @@ -234,8 +236,6 @@ public class DeviceProfile { public int hotseatBarBottomSpacePx; public int hotseatBarEndOffset; public int hotseatQsbSpace; - public int inlineNavButtonsEndSpacingPx; - public int navButtonsLayoutWidthPx; public int springLoadedHotseatBarTopMarginPx; // These 2 values are only used for isVerticalBar // Padding between edge of screen and hotseat @@ -250,7 +250,9 @@ public class DeviceProfile { private final int mMinHotseatIconSpacePx; private final int mMinHotseatQsbWidthPx; private final int mMaxHotseatIconSpacePx; - // Space required for the bubble bar between the hotseat and the edge of the screen. If there's + public final int inlineNavButtonsEndSpacingPx; + // Space required for the bubble bar between the hotseat and the edge of the + // screen. If there's // not enough space, the hotseat will adjust itself for the bubble bar. private final int mBubbleBarSpaceThresholdPx; @@ -280,6 +282,7 @@ public class DeviceProfile { public int overviewTaskIconSizePx; public int overviewTaskIconDrawableSizePx; public int overviewTaskIconDrawableSizeGridPx; + public int overviewTaskIconAppChipMenuDrawableSizePx; public int overviewTaskThumbnailTopMarginPx; public final int overviewActionsHeight; public final int overviewActionsTopMarginPx; @@ -308,10 +311,14 @@ public class DeviceProfile { // Insets private final Rect mInsets = new Rect(); public final Rect workspacePadding = new Rect(); - // Additional padding added to the widget inside its cellSpace. It is applied outside + // Additional padding added to the widget inside its cellSpace. It is applied + // outside // the widgetView, such that the actual view size is same as the widget size. public final Rect widgetPadding = new Rect(); + // When true, nav bar is on the left side of the screen. + private boolean mIsSeascape; + // Notification dots public final DotRenderer mDotRendererWorkSpace; public final DotRenderer mDotRendererAllApps; @@ -331,83 +338,12 @@ public class DeviceProfile { // DragController public int flingToDeleteThresholdVelocity; - /** Used only as an alternative to mocking when null values cannot be used. */ - @VisibleForTesting - public DeviceProfile() { - inv = null; - mInfo = null; - mMetrics = null; - mIconSizeSteps = null; - isTablet = false; - isPhone = false; - transposeLayoutWithOrientation = false; - isMultiDisplay = false; - isTwoPanels = false; - isPredictiveBackSwipe = false; - isQsbInline = false; - isLandscape = false; - isMultiWindowMode = false; - isGestureMode = false; - isLeftRightSplit = false; - windowX = 0; - windowY = 0; - widthPx = 0; - heightPx = 0; - availableWidthPx = 0; - availableHeightPx = 0; - rotationHint = 0; - aspectRatio = 1; - mIsScalableGrid = false; - mTypeIndex = 0; - mIsResponsiveGrid = false; - desiredWorkspaceHorizontalMarginOriginalPx = 0; - edgeMarginPx = 0; - workspaceContentScale = 0; - workspaceSpringLoadedMinNextPageVisiblePx = 0; - extraSpace = 0; - workspacePageIndicatorHeight = 0; - mWorkspacePageIndicatorOverlapWorkspace = 0; - numFolderRows = 0; - numFolderColumns = 0; - folderLabelTextScale = 0; - areNavButtonsInline = false; - mHotseatBarEdgePaddingPx = 0; - mHotseatBarWorkspaceSpacePx = 0; - hotseatQsbWidth = 0; - hotseatQsbHeight = 0; - hotseatQsbVisualHeight = 0; - hotseatQsbShadowHeight = 0; - hotseatBorderSpace = 0; - mMinHotseatIconSpacePx = 0; - mMinHotseatQsbWidthPx = 0; - mMaxHotseatIconSpacePx = 0; - inlineNavButtonsEndSpacingPx = 0; - mBubbleBarSpaceThresholdPx = 0; - numShownAllAppsColumns = 0; - overviewActionsHeight = 0; - overviewActionsTopMarginPx = 0; - overviewActionsButtonSpacing = 0; - mViewScaleProvider = null; - mDotRendererWorkSpace = null; - mDotRendererAllApps = null; - taskbarHeight = 0; - stashedTaskbarHeight = 0; - taskbarBottomMargin = 0; - taskbarIconSize = 0; - mTransientTaskbarClaimedSpace = 0; - startAlignTaskbar = false; - isTransientTaskbar = false; - mTextFactors = new TextFactors(0,0,0); - preferenceManager2 = null; - } - - private final TextFactors mTextFactors; + private final DeviceProfileOverrides.TextFactors mTextFactors; private float allAppsCellHeightMultiplier; - private PreferenceManager2 preferenceManager2 = null; + private final PreferenceManager2 preferenceManager2; /** TODO: Once we fully migrate to staged split, remove "isMultiWindowMode" */ - DeviceProfile(Context context, InvariantDeviceProfile inv, Info info, - WindowManagerProxy wmProxy, ThemeManager themeManager, WindowBounds windowBounds, + DeviceProfile(Context context, InvariantDeviceProfile inv, Info info, WindowBounds windowBounds, SparseArray dotRendererCache, boolean isMultiWindowMode, boolean transposeLayoutWithOrientation, boolean isMultiDisplay, boolean isGestureMode, @NonNull final ViewScaleProvider viewScaleProvider, @@ -444,17 +380,13 @@ public class DeviceProfile { isPhone = !isTablet; isTwoPanels = isMultiDisplay; boolean isTaskBarEnabled = PreferenceExtensionsKt.firstBlocking(preferenceManager2.getEnableTaskbarOnPhone()); - boolean taskbarOrBubbleBarOnPhones = enableTinyTaskbar() - || (enableBubbleBar() && enableBubbleBarOnPhones()); - isTaskbarPresent = isTaskBarEnabled && (isTablet || (taskbarOrBubbleBarOnPhones && isGestureMode)) - && wmProxy.isTaskbarDrawnInProcess(); + isTaskbarPresent = isTaskBarEnabled && (isTablet || (enableTinyTaskbar() && isGestureMode)) + && WindowManagerProxy.INSTANCE.get(context).isTaskbarDrawnInProcess(); // Some more constants. - context = getContext(context, info, inv.isFixedLandscape - || isVerticalBarLayout() - || (isTablet && isLandscape) - ? Configuration.ORIENTATION_LANDSCAPE - : Configuration.ORIENTATION_PORTRAIT, + context = getContext(context, info, isVerticalBarLayout() || (isTablet && isLandscape) + ? Configuration.ORIENTATION_LANDSCAPE + : Configuration.ORIENTATION_PORTRAIT, windowBounds); final Resources res = context.getResources(); mMetrics = res.getDisplayMetrics(); @@ -484,11 +416,9 @@ public class DeviceProfile { this.isTransientTaskbar = isTransientTaskbar; int transientTaskbarIconSize = pxFromDp(inv.transientTaskbarIconSize[mTypeIndex], mMetrics); - int transientTaskbarBottomMargin = - res.getDimensionPixelSize(R.dimen.transient_taskbar_bottom_margin); - int transientTaskbarHeight = - Math.round((transientTaskbarIconSize * ICON_VISIBLE_AREA_FACTOR) - + (2 * res.getDimensionPixelSize(R.dimen.transient_taskbar_padding))); + int transientTaskbarBottomMargin = res.getDimensionPixelSize(R.dimen.transient_taskbar_bottom_margin); + int transientTaskbarHeight = Math.round((transientTaskbarIconSize * ICON_VISIBLE_AREA_FACTOR) + + (2 * res.getDimensionPixelSize(R.dimen.transient_taskbar_padding))); mTransientTaskbarClaimedSpace = transientTaskbarHeight + 2 * transientTaskbarBottomMargin; if (!isTaskbarPresent) { @@ -497,8 +427,7 @@ public class DeviceProfile { } else if (isTransientTaskbar) { taskbarIconSize = transientTaskbarIconSize; taskbarHeight = transientTaskbarHeight; - stashedTaskbarHeight = - res.getDimensionPixelSize(R.dimen.transient_taskbar_stashed_height); + stashedTaskbarHeight = res.getDimensionPixelSize(R.dimen.transient_taskbar_stashed_height); taskbarBottomMargin = transientTaskbarBottomMargin; startAlignTaskbar = false; } else { @@ -519,7 +448,8 @@ public class DeviceProfile { R.dimen.grid_visualization_vertical_cell_spacing); { - // In large screens, in portrait mode, a bottom sheet can appear too elongated, so, we + // In large screens, in portrait mode, a bottom sheet can appear too elongated, + // so, we // apply additional padding. final boolean applyExtraTopPadding = isTablet && !isLandscape @@ -532,12 +462,11 @@ public class DeviceProfile { bottomSheetOpenDuration = res.getInteger(R.integer.config_bottomSheetOpenDuration); bottomSheetCloseDuration = res.getInteger(R.integer.config_bottomSheetCloseDuration); - if (shouldShowAllAppsOnSheet()) { + if (isTablet) { bottomSheetWorkspaceScale = workspaceContentScale; - if (Flags.allAppsBlur()) { - bottomSheetDepth = 2f; - } else if (isMultiDisplay) { - // TODO(b/259893832): Revert to use maxWallpaperScale to calculate bottomSheetDepth + if (isMultiDisplay && !ENABLE_MULTI_DISPLAY_PARTIAL_DEPTH.get()) { + // TODO(b/259893832): Revert to use maxWallpaperScale to calculate + // bottomSheetDepth // when screen recorder bug is fixed. if (enableScalingRevealHomeAnimation()) { bottomSheetDepth = 0.3f; @@ -545,7 +474,8 @@ public class DeviceProfile { bottomSheetDepth = 1f; } } else { - // The goal is to set wallpaper to zoom at workspaceContentScale when in AllApps. + // The goal is to set wallpaper to zoom at workspaceContentScale when in + // AllApps. // When depth is 0, wallpaper zoom is set to maxWallpaperScale. // When depth is 1, wallpaper zoom is set to 1. // For depth to achieve zoom set to maxWallpaperScale * workspaceContentScale: @@ -615,7 +545,8 @@ public class DeviceProfile { } dropTargetBarSizePx = res.getDimensionPixelSize(R.dimen.dynamic_grid_drop_target_size); - // Some foldable portrait modes are too wide in terms of aspect ratio so we need to tweak + // Some foldable portrait modes are too wide in terms of aspect ratio so we need + // to tweak // the dimensions for edit state. final boolean shouldApplyWidePortraitDimens = isTablet && !isLandscape @@ -645,11 +576,7 @@ public class DeviceProfile { boolean isQsbEnable = hotseatMode.getLayoutResourceId() != R.layout.empty_view; hotseatQsbHeight = isQsbEnable ? res.getDimensionPixelSize(R.dimen.qsb_widget_height) : 0; - if (inv.inlineQsb[INDEX_DEFAULT] && !isPhone) { - hotseatQsbShadowHeight = res.getDimensionPixelSize(R.dimen.taskbar_size); - } else { - hotseatQsbShadowHeight = res.getDimensionPixelSize(R.dimen.qsb_shadow_height); - } + hotseatQsbShadowHeight = res.getDimensionPixelSize(R.dimen.qsb_shadow_height); hotseatQsbVisualHeight = isQsbEnable ? hotseatQsbHeight - 2 * hotseatQsbShadowHeight : 0; // Whether QSB might be inline in appropriate orientation (e.g. landscape). @@ -657,34 +584,28 @@ public class DeviceProfile { || inv.inlineQsb[INDEX_TWO_PANEL_LANDSCAPE] : inv.inlineQsb[INDEX_DEFAULT] || inv.inlineQsb[INDEX_LANDSCAPE]) && hotseatQsbHeight > 0; - isQsbInline = isQsbInline(inv); + isQsbInline = mIsScalableGrid && inv.inlineQsb[mTypeIndex] && canQsbInline; areNavButtonsInline = isTaskbarPresent && !isGestureMode; - numShownHotseatIcons = - isTwoPanels ? inv.numDatabaseHotseatIcons : inv.numShownHotseatIcons; + numShownHotseatIcons = isTwoPanels ? inv.numDatabaseHotseatIcons : inv.numShownHotseatIcons; mHotseatColumnSpan = inv.numColumns; - numShownAllAppsColumns = - isTwoPanels ? inv.numDatabaseAllAppsColumns : inv.numAllAppsColumns; + numShownAllAppsColumns = isTwoPanels ? inv.numDatabaseAllAppsColumns : inv.numAllAppsColumns; int hotseatBarBottomSpace = !isQsbEnable ? 0 : pxFromDp(inv.hotseatBarBottomSpace[mTypeIndex], mMetrics); int minQsbMargin = res.getDimensionPixelSize(R.dimen.min_qsb_margin); if (mIsResponsiveGrid) { float responsiveAspectRatio = (float) widthPx / heightPx; - HotseatSpecsProvider hotseatSpecsProvider = - HotseatSpecsProvider.create(new ResourceHelper(context, - isTwoPanels ? inv.hotseatSpecsTwoPanelId : inv.hotseatSpecsId)); - mResponsiveHotseatSpec = - isVerticalBarLayout() ? hotseatSpecsProvider.getCalculatedSpec( - responsiveAspectRatio, DimensionType.WIDTH, widthPx) - : hotseatSpecsProvider.getCalculatedSpec(responsiveAspectRatio, - DimensionType.HEIGHT, heightPx); + HotseatSpecsProvider hotseatSpecsProvider = HotseatSpecsProvider.create(new ResourceHelper(context, + isTwoPanels ? inv.hotseatSpecsTwoPanelId : inv.hotseatSpecsId)); + mResponsiveHotseatSpec = isVerticalBarLayout() ? hotseatSpecsProvider.getCalculatedSpec( + responsiveAspectRatio, DimensionType.WIDTH, widthPx) + : hotseatSpecsProvider.getCalculatedSpec(responsiveAspectRatio, + DimensionType.HEIGHT, heightPx); hotseatQsbSpace = mResponsiveHotseatSpec.getHotseatQsbSpace(); - hotseatBarBottomSpace = - isVerticalBarLayout() ? 0 : mResponsiveHotseatSpec.getEdgePadding(); - mHotseatBarEdgePaddingPx = - isVerticalBarLayout() ? mResponsiveHotseatSpec.getEdgePadding() : 0; + hotseatBarBottomSpace = isVerticalBarLayout() ? 0 : mResponsiveHotseatSpec.getEdgePadding(); + mHotseatBarEdgePaddingPx = isVerticalBarLayout() ? mResponsiveHotseatSpec.getEdgePadding() : 0; mHotseatBarWorkspaceSpacePx = 0; ResponsiveCellSpecsProvider workspaceCellSpecs = ResponsiveCellSpecsProvider.create( @@ -696,10 +617,8 @@ public class DeviceProfile { } else { hotseatQsbSpace = pxFromDp(inv.hotseatQsbSpace[mTypeIndex], mMetrics); hotseatBarBottomSpace = pxFromDp(inv.hotseatBarBottomSpace[mTypeIndex], mMetrics); - mHotseatBarEdgePaddingPx = - isVerticalBarLayout() ? workspacePageIndicatorHeight : 0; - mHotseatBarWorkspaceSpacePx = - res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_side_padding); + mHotseatBarEdgePaddingPx = isVerticalBarLayout() ? workspacePageIndicatorHeight : 0; + mHotseatBarWorkspaceSpacePx = res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_side_padding); } if (!isVerticalBarLayout()) { @@ -735,24 +654,26 @@ public class DeviceProfile { } if (areNavButtonsInline && !isPhone) { - inlineNavButtonsEndSpacingPx = - res.getDimensionPixelSize(inv.inlineNavButtonsEndSpacing); - /* 3 nav buttons + Spacing between nav buttons */ - navButtonsLayoutWidthPx = 3 * res.getDimensionPixelSize( - R.dimen.taskbar_nav_buttons_size) - + 2 * res.getDimensionPixelSize(R.dimen.taskbar_button_space_inbetween); - /* nav buttons layout width + Space at the end for contextual buttons */ - hotseatBarEndOffset = navButtonsLayoutWidthPx + inlineNavButtonsEndSpacingPx; + inlineNavButtonsEndSpacingPx = res.getDimensionPixelSize(inv.inlineNavButtonsEndSpacing); + /* + * 3 nav buttons + + * Spacing between nav buttons + + * Space at the end for contextual buttons + */ + hotseatBarEndOffset = 3 * res.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size) + + 2 * res.getDimensionPixelSize(R.dimen.taskbar_button_space_inbetween) + + inlineNavButtonsEndSpacingPx; + } else { + inlineNavButtonsEndSpacingPx = 0; + hotseatBarEndOffset = 0; } - mBubbleBarSpaceThresholdPx = - res.getDimensionPixelSize(R.dimen.bubblebar_hotseat_adjustment_threshold); + mBubbleBarSpaceThresholdPx = res.getDimensionPixelSize(R.dimen.bubblebar_hotseat_adjustment_threshold); // Needs to be calculated after hotseatBarSizePx is correct, // for the available height to be correct if (mIsResponsiveGrid) { - int availableResponsiveWidth = - availableWidthPx - (isVerticalBarLayout() ? hotseatBarSizePx : 0); + int availableResponsiveWidth = availableWidthPx - (isVerticalBarLayout() ? hotseatBarSizePx : 0); int numWorkspaceColumns = getPanelCount() * inv.numColumns; // don't use availableHeightPx because it subtracts mInsets.bottom int availableResponsiveHeight = heightPx - mInsets.top @@ -807,14 +728,12 @@ public class DeviceProfile { overviewTaskMarginPx = res.getDimensionPixelSize(R.dimen.overview_task_margin); overviewTaskIconSizePx = enableOverviewIconMenu() ? res.getDimensionPixelSize( - R.dimen.task_thumbnail_icon_menu_drawable_touch_size) : res.getDimensionPixelSize( - R.dimen.task_thumbnail_icon_size); - overviewTaskIconDrawableSizePx = - res.getDimensionPixelSize(R.dimen.task_thumbnail_icon_drawable_size); - overviewTaskIconDrawableSizeGridPx = - res.getDimensionPixelSize(R.dimen.task_thumbnail_icon_drawable_size_grid); - overviewTaskThumbnailTopMarginPx = - enableOverviewIconMenu() ? 0 : overviewTaskIconSizePx + overviewTaskMarginPx; + R.dimen.task_thumbnail_icon_menu_drawable_touch_size) + : res.getDimensionPixelSize( + R.dimen.task_thumbnail_icon_size); + overviewTaskIconDrawableSizePx = res.getDimensionPixelSize(R.dimen.task_thumbnail_icon_drawable_size); + overviewTaskIconDrawableSizeGridPx = res.getDimensionPixelSize(R.dimen.task_thumbnail_icon_drawable_size_grid); + overviewTaskThumbnailTopMarginPx = enableOverviewIconMenu() ? 0 : overviewTaskIconSizePx + overviewTaskMarginPx; // Don't add margin with floating search bar to minimize risk of overlapping. overviewActionsTopMarginPx = Flags.floatingSearchBar() ? 0 : res.getDimensionPixelSize(R.dimen.overview_actions_top_margin); @@ -826,14 +745,16 @@ public class DeviceProfile { overviewGridSideMargin = res.getDimensionPixelSize(R.dimen.overview_grid_side_margin); splitPlaceholderInset = res.getDimensionPixelSize(R.dimen.split_placeholder_inset); - // We need to use the full window bounds for split determination because on near-square - // devices, the available bounds (bounds minus insets) may actually be in landscape while + // We need to use the full window bounds for split determination because on + // near-square + // devices, the available bounds (bounds minus insets) may actually be in + // landscape while // actually portrait int leftRightSplitPortraitResId = Resources.getSystem().getIdentifier( "config_leftRightSplitInPortrait", "bool", "android"); - boolean allowLeftRightSplitInPortrait = - leftRightSplitPortraitResId > 0 - && res.getBoolean(leftRightSplitPortraitResId); + boolean allowLeftRightSplitInPortrait = com.android.wm.shell.Flags.enableLeftRightSplitInPortrait() + && leftRightSplitPortraitResId > 0 + && res.getBoolean(leftRightSplitPortraitResId); if (allowLeftRightSplitInPortrait && isTablet) { isLeftRightSplit = !isLandscape; } else { @@ -845,8 +766,8 @@ public class DeviceProfile { calculateAndSetWorkspaceVerticalPadding(context, inv, extraSpace); - int cellLayoutPadding = - isTwoPanels ? cellLayoutBorderSpacePx.x / 2 : res.getDimensionPixelSize( + int cellLayoutPadding = isTwoPanels ? cellLayoutBorderSpacePx.x / 2 + : res.getDimensionPixelSize( R.dimen.cell_layout_padding); cellLayoutPaddingPx = new Rect(cellLayoutPadding, cellLayoutPadding, cellLayoutPadding, cellLayoutPadding); @@ -858,7 +779,8 @@ public class DeviceProfile { mMinHotseatIconSpacePx = res.getDimensionPixelSize(R.dimen.min_hotseat_icon_space); mMinHotseatQsbWidthPx = res.getDimensionPixelSize(R.dimen.min_hotseat_qsb_width); mMaxHotseatIconSpacePx = areNavButtonsInline - ? res.getDimensionPixelSize(R.dimen.max_hotseat_icon_space) : Integer.MAX_VALUE; + ? res.getDimensionPixelSize(R.dimen.max_hotseat_icon_space) + : Integer.MAX_VALUE; // Hotseat and QSB width depends on updated cellSize and workspace padding recalculateHotseatWidthAndBorderSpace(); @@ -866,13 +788,12 @@ public class DeviceProfile { hotseatBorderSpace = cellLayoutBorderSpacePx.y; } - if (shouldShowAllAppsOnSheet()) { + if (isTablet) { allAppsPadding.top = mInsets.top; allAppsShiftRange = heightPx; } else { allAppsPadding.top = 0; - allAppsShiftRange = - res.getDimensionPixelSize(R.dimen.all_apps_starting_vertical_translate); + allAppsShiftRange = res.getDimensionPixelSize(R.dimen.all_apps_starting_vertical_translate); } allAppsOpenDuration = res.getInteger(R.integer.config_allAppsOpenDuration); allAppsCloseDuration = res.getInteger(R.integer.config_allAppsCloseDuration); @@ -891,7 +812,7 @@ public class DeviceProfile { // Load the default font to use on notification dots Typeface typeface = null; if (showNotificationCount) { - typeface = ResourcesCompat.getFont(context, R.font.googlesansflex_variable); + typeface = ResourcesCompat.getFont(context, R.font.inter_regular); } // Load dot color @@ -904,65 +825,43 @@ public class DeviceProfile { int countColor = counterColorOption.getColorPreferenceEntry().getLightColor().invoke(context); // This is done last, after iconSizePx is calculated above. - mDotRendererWorkSpace = createDotRenderer(themeManager, iconSizePx, dotRendererCache, showNotificationCount, typeface, dotColor, countColor); - mDotRendererAllApps = createDotRenderer(themeManager, allAppsIconSizePx, dotRendererCache, showNotificationCount, typeface, dotColor, countColor); + Path dotPath = GraphicsUtils.getShapePath(context, DEFAULT_DOT_SIZE); + + mDotRendererWorkSpace = createDotRenderer(iconSizePx, dotPath, showNotificationCount, typeface, dotColor, + countColor, dotRendererCache); + mDotRendererAllApps = createDotRenderer(allAppsIconSizePx, dotPath, showNotificationCount, typeface, dotColor, + countColor, dotRendererCache); } - /** - * Takes care of the logic that determines if we show a the QSB inline or not. - */ - private boolean isQsbInline(InvariantDeviceProfile inv) { - // For foldable (two panel), we inline the qsb if we have the screen open and we are in - // either Landscape or Portrait. This cal also be disabled in the device_profile.xml - boolean twoPanelCanInline = inv.inlineQsb[INDEX_TWO_PANEL_PORTRAIT] - || inv.inlineQsb[INDEX_TWO_PANEL_LANDSCAPE]; - - // In tablets we inline in both orientations but only if we have enough space in the QSB - boolean tabletInlineQsb = inv.inlineQsb[INDEX_DEFAULT] || inv.inlineQsb[INDEX_LANDSCAPE]; - boolean canQsbInline = isTwoPanels ? twoPanelCanInline : tabletInlineQsb; - canQsbInline = canQsbInline && hotseatQsbHeight > 0; - - return (mIsScalableGrid && inv.inlineQsb[mTypeIndex] && canQsbInline) - || inv.isFixedLandscape; - } - - private static DotRenderer createDotRenderer( - @NonNull ThemeManager themeManager, int size, @NonNull SparseArray cache) { + private static DotRenderer createDotRenderer(int size, + Path dotPath, + boolean showNotificationCount, + Typeface typeface, + int dotColor, + int countColor, + @NonNull SparseArray cache) { DotRenderer renderer = cache.get(size); if (renderer == null) { - renderer = new DotRenderer( - size, - themeManager.getIconShape().getPath(DEFAULT_DOT_SIZE), - DEFAULT_DOT_SIZE); - cache.put(size, renderer); - } - return renderer; - } - - // Lawnchair - private static DotRenderer createDotRenderer( - @NonNull ThemeManager themeManager, int size, @NonNull SparseArray cache, boolean showNotificationCount, Typeface typeface, int dotColor, int countColor) { - DotRenderer renderer = cache.get(size); - - if (renderer == null) { - renderer = new DotRenderer( - size, - themeManager.getIconShape().getPath(DEFAULT_DOT_SIZE), - DEFAULT_DOT_SIZE, - showNotificationCount, - typeface, - dotColor, - countColor); + renderer = new DotRenderer(size, + dotPath, + DEFAULT_DOT_SIZE, + showNotificationCount, + typeface, + dotColor, + countColor); cache.put(size, renderer); } return renderer; } /** - * Return maximum of all apps row count displayed on screen. Note that 1) Partially displayed - * row is counted as 1 row, and 2) we don't exclude the space of floating search bar. This - * method is used for calculating number of {@link BubbleTextView} we need to pre-inflate. Thus + * Return maximum of all apps row count displayed on screen. Note that 1) + * Partially displayed + * row is counted as 1 row, and 2) we don't exclude the space of floating search + * bar. This + * method is used for calculating number of {@link BubbleTextView} we need to + * pre-inflate. Thus * reasonable over estimation is fine. */ public int getMaxAllAppsRowCount() { @@ -971,7 +870,8 @@ public class DeviceProfile { } /** - * QSB width is always calculated because when in 3 button nav the width doesn't follow the + * QSB width is always calculated because when in 3 button nav the width doesn't + * follow the * width of the hotseat. */ private int calculateQsbWidth(int hotseatBorderSpace) { @@ -1014,7 +914,8 @@ public class DeviceProfile { workspaceTopPadding = mResponsiveWorkspaceHeightSpec.getStartPaddingPx(); workspaceBottomPadding = mResponsiveWorkspaceHeightSpec.getEndPaddingPx(); } else if (mIsScalableGrid && inv.devicePaddingId != INVALID_RESOURCE_HANDLE) { - // Paddings were created assuming no scaling, so we first unscale the extra space. + // Paddings were created assuming no scaling, so we first unscale the extra + // space. int unscaledExtraSpace = (int) (extraSpace / cellScaleToFit); DevicePaddings devicePaddings = new DevicePaddings(context, inv.devicePaddingId); DevicePadding padding = devicePaddings.getDevicePadding(unscaledExtraSpace); @@ -1030,26 +931,26 @@ public class DeviceProfile { /** Updates hotseatCellHeightPx and hotseatBarSizePx */ private void updateHotseatSizes(int hotseatIconSizePx) { + // Ensure there is enough space for folder icons, which have a slightly larger + // radius. int iconTextHeight = Utilities.calculateTextHeight(iconTextSizePx); - boolean isLabelInDock = PreferenceExtensionsKt.firstBlocking(preferenceManager2.getEnableLabelInDock()); - // Ensure there is enough space for folder icons, which have a slightly larger radius. + var isLabelInDock = PreferenceExtensionsKt.firstBlocking(preferenceManager2.getEnableLabelInDock()); + hotseatCellHeightPx = getIconSizeWithOverlap(hotseatIconSizePx * 2) - hotseatIconSizePx / 2; hotseatCellHeightPx += isLabelInDock ? iconTextHeight : 0; hotseatQsbSpace += isLabelInDock ? (iconTextHeight / 2) : 0; - - int space = Math.abs(hotseatCellHeightPx / 2) - 16; + + var space = Math.abs(hotseatCellHeightPx / 2) - 16; hotseatBarBottomSpacePx *= PreferenceExtensionsKt - .firstBlocking(preferenceManager2.getHotseatBottomFactor()); + .firstBlocking(preferenceManager2.getHotseatBottomFactor()); if (isVerticalBarLayout()) { hotseatBarSizePx = hotseatIconSizePx + mHotseatBarEdgePaddingPx - + mHotseatBarWorkspaceSpacePx - + space; + + mHotseatBarWorkspaceSpacePx + space; } else if (isQsbInline) { hotseatBarSizePx = Math.max(hotseatIconSizePx, hotseatQsbVisualHeight) - + hotseatBarBottomSpacePx - + space; + + hotseatBarBottomSpacePx + space; } else { hotseatBarSizePx = hotseatIconSizePx + hotseatQsbSpace @@ -1064,17 +965,20 @@ public class DeviceProfile { } /** - * Calculates the width of the hotseat, changing spaces between the icons and removing icons if + * Calculates the width of the hotseat, changing spaces between the icons and + * removing icons if * necessary. */ public void recalculateHotseatWidthAndBorderSpace() { - if (!mIsScalableGrid) return; + if (!mIsScalableGrid || isTablet) + return; updateHotseatWidthAndBorderSpace(inv.numColumns); int numWorkspaceColumns = getPanelCount() * inv.numColumns; if (isTwoPanels) { updateHotseatWidthAndBorderSpace(inv.numDatabaseHotseatIcons); - // If hotseat doesn't fit with current width, increase column span to fit by multiple + // If hotseat doesn't fit with current width, increase column span to fit by + // multiple // of 2. while (hotseatBorderSpace < mMinHotseatIconSpacePx && mHotseatColumnSpan < numWorkspaceColumns) { @@ -1084,13 +988,11 @@ public class DeviceProfile { if (isQsbInline) { // If QSB is inline, reduce column span until it fits. int maxHotseatWidthAllowedPx = getIconToIconWidthForColumns(numWorkspaceColumns); - int minHotseatWidthRequiredPx = - mMinHotseatQsbWidthPx + hotseatBorderSpace + mHotseatWidthPx; + int minHotseatWidthRequiredPx = mMinHotseatQsbWidthPx + hotseatBorderSpace + mHotseatWidthPx; while (minHotseatWidthRequiredPx > maxHotseatWidthAllowedPx && mHotseatColumnSpan > 1) { updateHotseatWidthAndBorderSpace(mHotseatColumnSpan - 1); - minHotseatWidthRequiredPx = - mMinHotseatQsbWidthPx + hotseatBorderSpace + mHotseatWidthPx; + minHotseatWidthRequiredPx = mMinHotseatQsbWidthPx + hotseatBorderSpace + mHotseatWidthPx; } } hotseatQsbWidth = calculateQsbWidth(hotseatBorderSpace); @@ -1100,7 +1002,8 @@ public class DeviceProfile { return; } - // The side space with inline buttons should be what is defined in InvariantDeviceProfile + // The side space with inline buttons should be what is defined in + // InvariantDeviceProfile int sideSpacePx = inlineNavButtonsEndSpacingPx; int maxHotseatWidthPx = availableWidthPx - sideSpacePx - hotseatBarEndOffset; int maxHotseatIconsWidthPx = maxHotseatWidthPx - (isQsbInline ? hotseatQsbWidth : 0); @@ -1185,7 +1088,7 @@ public class DeviceProfile { dotRendererCache.put(iconSizePx, mDotRendererWorkSpace); dotRendererCache.put(allAppsIconSizePx, mDotRendererAllApps); - return inv.newDPBuilder(context, mInfo) + return new Builder(context, inv, mInfo) .setWindowBounds(bounds) .setIsMultiDisplay(isMultiDisplay) .setMultiWindowMode(isMultiWindowMode) @@ -1206,8 +1109,10 @@ public class DeviceProfile { .setMultiWindowMode(true) .build(); - // We use these scales to measure and layout the widgets using their full invariant profile - // sizes and then draw them scaled and centered to fit in their multi-window mode cellspans. + // We use these scales to measure and layout the widgets using their full + // invariant profile + // sizes and then draw them scaled and centered to fit in their multi-window + // mode cellspans. float appWidgetScaleX = (float) profile.getCellSize().x / getCellSize().x; float appWidgetScaleY = (float) profile.getCellSize().y / getCellSize().y; if (appWidgetScaleX != 1 || appWidgetScaleY != 1) { @@ -1225,7 +1130,8 @@ public class DeviceProfile { /** * Checks if there is enough space for labels on the workspace. * If there is not, labels on the Workspace are hidden. - * It is important to call this method after the All Apps variables have been set. + * It is important to call this method after the All Apps variables have been + * set. */ private void hideWorkspaceLabelsIfNotEnoughSpace() { float iconTextHeight = Utilities.calculateTextHeight(iconTextSizePx); @@ -1274,11 +1180,12 @@ public class DeviceProfile { float scaleX = 1f; if (mIsScalableGrid) { // We scale to fit the cellWidth and cellHeight in the available space. - // The benefit of scalable grids is that we can get consistent aspect ratios between + // The benefit of scalable grids is that we can get consistent aspect ratios + // between // devices. - float usedWidth = - getCellLayoutWidthSpecification() + (desiredWorkspaceHorizontalMarginPx * 2); - // We do not subtract padding here, as we also scale the workspace padding if needed. + float usedWidth = getCellLayoutWidthSpecification() + (desiredWorkspaceHorizontalMarginPx * 2); + // We do not subtract padding here, as we also scale the workspace padding if + // needed. scaleX = availableWidthPx / usedWidth; shouldScale = true; } @@ -1323,13 +1230,15 @@ public class DeviceProfile { } private int getIconSizeWithOverlap(int iconSize) { - return (int) Math.ceil(iconSize * ClippedFolderIconLayoutRule.getIconOverlapFactor()); + return (int) Math.ceil(iconSize * ICON_OVERLAP_FACTOR); } /** - * Updating the iconSize affects many aspects of the launcher layout, such as: iconSizePx, + * Updating the iconSize affects many aspects of the launcher layout, such as: + * iconSizePx, * iconTextSizePx, iconDrawablePaddingPx, cellWidth/Height, allApps* variants, - * hotseat sizes, workspaceSpringLoadedShrinkFactor, folderIconSizePx, and folderIconOffsetYPx. + * hotseat sizes, workspaceSpringLoadedShrinkFactor, folderIconSizePx, and + * folderIconOffsetYPx. */ public void updateIconSize(float scale, Context context) { // Icon scale should never exceed 1, otherwise pixellation may occur. @@ -1377,7 +1286,8 @@ public class DeviceProfile { cellHeightPx = pxFromDp(inv.minCellSize[mTypeIndex].y, mMetrics, scale); if (cellWidthPx < iconSizePx) { - // If cellWidth no longer fit iconSize, reduce borderSpace to make cellWidth bigger. + // If cellWidth no longer fit iconSize, reduce borderSpace to make cellWidth + // bigger. int numColumns = getPanelCount() * inv.numColumns; int numBorders = numColumns - 1; int extraWidthRequired = (iconSizePx - cellWidthPx) * numColumns; @@ -1394,8 +1304,7 @@ public class DeviceProfile { } } - int cellTextAndPaddingHeight = - iconDrawablePaddingPx + Utilities.calculateTextHeight(iconTextSizePx); + int cellTextAndPaddingHeight = iconDrawablePaddingPx + Utilities.calculateTextHeight(iconTextSizePx); int cellContentHeight = iconSizePx + cellTextAndPaddingHeight; if (cellHeightPx < cellContentHeight) { // If cellHeight no longer fit iconSize, reduce borderSpace to make cellHeight @@ -1422,14 +1331,12 @@ public class DeviceProfile { iconSizePx = (int) (iconSizePx * ratio); iconTextSizePx = (int) (iconTextSizePx * ratio); } - cellTextAndPaddingHeight = - iconDrawablePaddingPx + Utilities.calculateTextHeight(iconTextSizePx); + cellTextAndPaddingHeight = iconDrawablePaddingPx + Utilities.calculateTextHeight(iconTextSizePx); } cellContentHeight = iconSizePx + cellTextAndPaddingHeight; } cellYPaddingPx = Math.max(0, cellHeightPx - cellContentHeight) / 2; - desiredWorkspaceHorizontalMarginPx = - (int) (desiredWorkspaceHorizontalMarginOriginalPx * scale); + desiredWorkspaceHorizontalMarginPx = (int) (desiredWorkspaceHorizontalMarginOriginalPx * scale); } else { iconDrawablePaddingPx = (int) (getNormalizedIconDrawablePadding() * iconScale); cellWidthPx = iconSizePx + iconDrawablePaddingPx; @@ -1439,7 +1346,8 @@ public class DeviceProfile { int cellPaddingY = (getCellSize().y - cellHeightPx) / 2; if (iconDrawablePaddingPx > cellPaddingY && !isVerticalLayout && !isMultiWindowMode) { - // Ensures that the label is closer to its corresponding icon. This is not an issue + // Ensures that the label is closer to its corresponding icon. This is not an + // issue // with vertical bar layout or multi-window mode since the issue is handled // separately with their calls to {@link #adjustToHideWorkspaceLabels}. cellHeightPx -= (iconDrawablePaddingPx - cellPaddingY); @@ -1453,7 +1361,7 @@ public class DeviceProfile { if (mIsResponsiveGrid) { updateAllAppsWithResponsiveMeasures(); } else { - // LC: All apps should use scale 1.0, not workspace scale + // All apps should use scale 1.0, not workspace scale // This ensures drawer icons are independent of workspace scaling updateAllAppsIconSize(1.0f, context.getResources()); } @@ -1461,7 +1369,8 @@ public class DeviceProfile { if (isVerticalLayout && !mIsResponsiveGrid) { hideWorkspaceLabelsIfNotEnoughSpace(); } - if (inv.enableTwoLinesInAllApps) { + if ((Flags.enableTwolineToggle() + && LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE.get(context))) { // Add extra textHeight to the existing allAppsCellHeight. allAppsCellHeightPx += Utilities.calculateTextHeight(allAppsIconTextSizePx); } @@ -1469,28 +1378,28 @@ public class DeviceProfile { updateHotseatSizes(iconSizePx); // Folder icon - folderIconSizePx = Math.round(iconSizePx * ICON_VISIBLE_AREA_FACTOR); + folderIconSizePx = IconNormalizer.getNormalizedCircleSize(iconSizePx); folderIconOffsetYPx = (iconSizePx - folderIconSizePx) / 2; // Update widget padding: float minSpacing = pxFromDp(MIN_WIDGET_PADDING_DP, mMetrics); if (cellLayoutBorderSpacePx.x < minSpacing || cellLayoutBorderSpacePx.y < minSpacing) { - widgetPadding.left = widgetPadding.right = - Math.round(Math.max(0, minSpacing - cellLayoutBorderSpacePx.x)); - widgetPadding.top = widgetPadding.bottom = - Math.round(Math.max(0, minSpacing - cellLayoutBorderSpacePx.y)); + widgetPadding.left = widgetPadding.right = Math.round(Math.max(0, minSpacing - cellLayoutBorderSpacePx.x)); + widgetPadding.top = widgetPadding.bottom = Math.round(Math.max(0, minSpacing - cellLayoutBorderSpacePx.y)); } else { widgetPadding.setEmpty(); } } /** - * This method calculates the space between the icons to achieve a certain width. + * This method calculates the space between the icons to achieve a certain + * width. */ private int calculateHotseatBorderSpace(float hotseatWidthPx, int numExtraBorder) { int numBorders = (numShownHotseatIcons - 1 + numExtraBorder); - if (numBorders <= 0) return 0; + if (numBorders <= 0) + return 0; float hotseatIconsTotalPx = iconSizePx * numShownHotseatIcons; int hotseatBorderSpacePx = (int) (hotseatWidthPx - hotseatIconsTotalPx) / numBorders; @@ -1507,7 +1416,7 @@ public class DeviceProfile { pxFromDp(inv.allAppsBorderSpaces[mTypeIndex].y, mMetrics, borderScale)); // AllApps cells don't have real space between cells, // so we add the border space to the cell height - allAppsCellHeightPx = pxFromDp(inv.allAppsCellSize[mTypeIndex].y, mMetrics) + allAppsCellHeightPx = pxFromDp(inv.allAppsCellSize[mTypeIndex].y, mMetrics, allAppsCellHeightMultiplier) + allAppsBorderSpacePx.y; // but width is just the cell, // the border is added in #updateAllAppsContainerWidth @@ -1519,11 +1428,11 @@ public class DeviceProfile { allAppsCellWidthPx = pxFromDp(inv.allAppsCellSize[mTypeIndex].x, mMetrics); if (allAppsCellWidthPx < allAppsIconSizePx) { - // If allAppsCellWidth no longer fit allAppsIconSize, reduce allAppsBorderSpace to + // If allAppsCellWidth no longer fit allAppsIconSize, reduce allAppsBorderSpace + // to // make allAppsCellWidth bigger. int numBorders = inv.numAllAppsColumns - 1; - int extraWidthRequired = - (allAppsIconSizePx - allAppsCellWidthPx) * inv.numAllAppsColumns; + int extraWidthRequired = (allAppsIconSizePx - allAppsCellWidthPx) * inv.numAllAppsColumns; if (allAppsBorderSpacePx.x * numBorders >= extraWidthRequired) { allAppsCellWidthPx = allAppsIconSizePx; allAppsBorderSpacePx.x -= extraWidthRequired / numBorders; @@ -1561,8 +1470,7 @@ public class DeviceProfile { mResponsiveAllAppsCellSpec.getIconDrawablePadding()); allAppsBorderSpacePx = new Point( mResponsiveAllAppsWidthSpec.getGutterPx(), - mResponsiveAllAppsHeightSpec.getGutterPx() - ); + mResponsiveAllAppsHeightSpec.getGutterPx()); allAppsCellHeightPx = mResponsiveAllAppsHeightSpec.getCellSizePx(); allAppsCellWidthPx = mResponsiveAllAppsWidthSpec.getCellSizePx(); @@ -1572,7 +1480,6 @@ public class DeviceProfile { allAppsPadding.left = mResponsiveAllAppsWidthSpec.getStartPaddingPx() - halfBorder; allAppsPadding.right = mResponsiveAllAppsWidthSpec.getEndPaddingPx() - halfBorder; - // Reduce the size of the app icon if it doesn't fit if (allAppsCellWidthPx < allAppsIconSizePx) { // get a smaller icon size @@ -1615,17 +1522,16 @@ public class DeviceProfile { } private void updateAllAppsContainerWidth() { - int cellLayoutHorizontalPadding = - (cellLayoutPaddingPx.left + cellLayoutPaddingPx.right) / 2; + int cellLayoutHorizontalPadding = (cellLayoutPaddingPx.left + cellLayoutPaddingPx.right) / 2; if (isTablet) { int usedWidth = (allAppsCellWidthPx * numShownAllAppsColumns) + (allAppsBorderSpacePx.x * (numShownAllAppsColumns - 1)) + allAppsPadding.left + allAppsPadding.right; allAppsLeftRightMargin = Math.max(1, (availableWidthPx - usedWidth) / 2); } else if (!mIsResponsiveGrid) { - allAppsPadding.left = allAppsPadding.right = - Math.max(0, desiredWorkspaceHorizontalMarginPx + cellLayoutHorizontalPadding - - (allAppsBorderSpacePx.x / 2)); + allAppsPadding.left = allAppsPadding.right = Math.max(0, + desiredWorkspaceHorizontalMarginPx + cellLayoutHorizontalPadding + - (allAppsBorderSpacePx.x / 2)); } var allAppLeftRightMarginMultiplier = PreferenceExtensionsKt .firstBlocking(preferenceManager2.getDrawerLeftRightMarginFactor()); @@ -1641,15 +1547,11 @@ public class DeviceProfile { allAppsPadding.right = rightPadding; } - /** Whether All Apps should be presented on a bottom sheet. */ - public boolean shouldShowAllAppsOnSheet() { - return isTablet || Flags.allAppsSheetForHandheld(); - } - private void setupAllAppsStyle(Context context) { TypedArray allAppsStyle = context.obtainStyledAttributes( inv.allAppsStyle != INVALID_RESOURCE_HANDLE ? inv.allAppsStyle - : R.style.AllAppsStyleDefault, R.styleable.AllAppsStyle); + : R.style.AllAppsStyleDefault, + R.styleable.AllAppsStyle); allAppsPadding.left = allAppsPadding.right = allAppsStyle.getDimensionPixelSize( R.styleable.AllAppsStyle_horizontalPadding, 0); @@ -1660,7 +1562,8 @@ public class DeviceProfile { updateFolderCellSize(1f, res); // Responsive grid doesn't need to scale the folder - if (mIsResponsiveGrid) return; + if (mIsResponsiveGrid) + return; // For usability we can't have the folder use the whole width of the screen Point totalWorkspacePadding = getTotalWorkspacePadding(); @@ -1757,8 +1660,7 @@ public class DeviceProfile { folderContentPaddingTop = roundPxValueFromFloat(folderContentPaddingTop * scale); folderCellLayoutBorderSpacePx = new Point( roundPxValueFromFloat(folderCellLayoutBorderSpacePx.x * scale), - roundPxValueFromFloat(folderCellLayoutBorderSpacePx.y * scale) - ); + roundPxValueFromFloat(folderCellLayoutBorderSpacePx.y * scale)); folderFooterHeightPx = roundPxValueFromFloat(folderFooterHeightPx * scale); folderContentPaddingLeftRight = folderCellLayoutBorderSpacePx.x; } else { @@ -1770,12 +1672,10 @@ public class DeviceProfile { folderCellWidthPx = folderChildIconSizePx + 2 * cellPaddingX; folderCellHeightPx = folderChildIconSizePx + 2 * cellPaddingY + textHeight; folderContentPaddingTop = roundPxValueFromFloat(folderContentPaddingTop * scale); - folderContentPaddingLeftRight = - res.getDimensionPixelSize(R.dimen.folder_content_padding_left_right); - folderFooterHeightPx = - roundPxValueFromFloat( - res.getDimensionPixelSize(R.dimen.folder_footer_height_default) - * scale); + folderContentPaddingLeftRight = res.getDimensionPixelSize(R.dimen.folder_content_padding_left_right); + folderFooterHeightPx = roundPxValueFromFloat( + res.getDimensionPixelSize(R.dimen.folder_footer_height_default) + * scale); folderChildDrawablePaddingPx = getNormalizedFolderChildDrawablePaddingPx(textHeight); } @@ -1789,8 +1689,10 @@ public class DeviceProfile { } /** - * The current device insets. This is generally same as the insets being dispatched to - * {@link Insettable} elements, but can differ if the element is using a different profile. + * The current device insets. This is generally same as the insets being + * dispatched to + * {@link Insettable} elements, but can differ if the element is using a + * different profile. */ public Rect getInsets() { return mInsets; @@ -1805,19 +1707,20 @@ public class DeviceProfile { result = new Point(); } - int shortcutAndWidgetContainerWidth = - getCellLayoutWidth() - (cellLayoutPaddingPx.left + cellLayoutPaddingPx.right); + int shortcutAndWidgetContainerWidth = getCellLayoutWidth() + - (cellLayoutPaddingPx.left + cellLayoutPaddingPx.right); result.x = calculateCellWidth(shortcutAndWidgetContainerWidth, cellLayoutBorderSpacePx.x, inv.numColumns); - int shortcutAndWidgetContainerHeight = - getCellLayoutHeight() - (cellLayoutPaddingPx.top + cellLayoutPaddingPx.bottom); + int shortcutAndWidgetContainerHeight = getCellLayoutHeight() + - (cellLayoutPaddingPx.top + cellLayoutPaddingPx.bottom); result.y = calculateCellHeight(shortcutAndWidgetContainerHeight, cellLayoutBorderSpacePx.y, inv.numRows); return result; } /** - * Returns the left and right space on the cell, which is the cell width - icon size + * Returns the left and right space on the cell, which is the cell width - icon + * size */ public int getCellHorizontalSpace() { return getCellSize().x - iconSizePx; @@ -1831,7 +1734,8 @@ public class DeviceProfile { } /** - * Gets the space in px from the bottom of last item in the vertical-bar hotseat to the + * Gets the space in px from the bottom of last item in the vertical-bar hotseat + * to the * bottom of the screen. */ private int getVerticalHotseatLastItemBottomOffset(Context context) { @@ -1852,21 +1756,22 @@ public class DeviceProfile { } /** - * Gets the scaled bottom of the workspace in px for the spring-loaded edit state. + * Gets the scaled bottom of the workspace in px for the spring-loaded edit + * state. */ public float getCellLayoutSpringLoadShrunkBottom(Context context) { int topOfHotseat = hotseatBarSizePx + springLoadedHotseatBarTopMarginPx; return heightPx - (isVerticalBarLayout() - ? getVerticalHotseatLastItemBottomOffset(context) : topOfHotseat); + ? getVerticalHotseatLastItemBottomOffset(context) + : topOfHotseat); } /** * Gets the scale of the workspace for the spring-loaded edit state. */ public float getWorkspaceSpringLoadScale(Context context) { - float scale = - (getCellLayoutSpringLoadShrunkBottom(context) - getCellLayoutSpringLoadShrunkTop()) - / getCellLayoutHeight(); + float scale = (getCellLayoutSpringLoadShrunkBottom(context) - getCellLayoutSpringLoadShrunkTop()) + / getCellLayoutHeight(); scale = Math.min(scale, 1f); // Reduce scale if next pages would not be visible after scaling the workspace. @@ -1880,9 +1785,12 @@ public class DeviceProfile { } /** - * Gets the width of a single Cell Layout, aka a single panel within a Workspace. + * Gets the width of a single Cell Layout, aka a single panel within a + * Workspace. * - *

This is the width of a Workspace, less its horizontal padding. Note that two-panel + *

+ * This is the width of a Workspace, less its horizontal padding. Note that + * two-panel * layouts have two Cell Layouts per workspace. */ public int getCellLayoutWidth() { @@ -1890,9 +1798,11 @@ public class DeviceProfile { } /** - * Gets the height of a single Cell Layout, aka a single panel within a Workspace. + * Gets the height of a single Cell Layout, aka a single panel within a + * Workspace. * - *

This is the height of a Workspace, less its vertical padding. + *

+ * This is the height of a Workspace, less its vertical padding. */ public int getCellLayoutHeight() { return availableHeightPx - getTotalWorkspacePadding().y; @@ -1904,7 +1814,8 @@ public class DeviceProfile { } /** - * Updates {@link #workspacePadding} as a result of any internal value change to reflect the + * Updates {@link #workspacePadding} as a result of any internal value change to + * reflect the * new workspace padding */ private void updateWorkspacePadding() { @@ -1915,13 +1826,11 @@ public class DeviceProfile { padding.bottom = Math.max(0, mResponsiveWorkspaceHeightSpec.getEndPaddingPx() - mInsets.bottom); if (isSeascape()) { - padding.left = - hotseatBarSizePx + mResponsiveWorkspaceWidthSpec.getEndPaddingPx(); + padding.left = hotseatBarSizePx + mResponsiveWorkspaceWidthSpec.getEndPaddingPx(); padding.right = mResponsiveWorkspaceWidthSpec.getStartPaddingPx(); } else { padding.left = mResponsiveWorkspaceWidthSpec.getStartPaddingPx(); - padding.right = - hotseatBarSizePx + mResponsiveWorkspaceWidthSpec.getEndPaddingPx(); + padding.right = hotseatBarSizePx + mResponsiveWorkspaceWidthSpec.getEndPaddingPx(); } } else { padding.top = 0; @@ -1939,21 +1848,12 @@ public class DeviceProfile { // and leave a bit of space in case a widget go all the way down int paddingBottom = hotseatBarSizePx + workspaceBottomPadding - mInsets.bottom; if (!mIsResponsiveGrid) { - paddingBottom += - workspacePageIndicatorHeight - mWorkspacePageIndicatorOverlapWorkspace; + paddingBottom += workspacePageIndicatorHeight - mWorkspacePageIndicatorOverlapWorkspace; } int paddingTop = workspaceTopPadding + (mIsScalableGrid ? 0 : edgeMarginPx); - int paddingLeft = desiredWorkspaceHorizontalMarginPx; - int paddingRight = desiredWorkspaceHorizontalMarginPx; + int paddingSide = desiredWorkspaceHorizontalMarginPx; - // In fixed Landscape we don't need padding on the side next to the cutout because - // the cutout is already adding padding to all of Launcher, we only need on the other - // side - if (inv.isFixedLandscape) { - paddingLeft = isSeascape() ? desiredWorkspaceHorizontalMarginPx : 0; - paddingRight = isSeascape() ? 0 : desiredWorkspaceHorizontalMarginPx; - } - padding.set(paddingLeft, paddingTop, paddingRight, paddingBottom); + padding.set(paddingSide, paddingTop, paddingSide, paddingBottom); } insetPadding(workspacePadding, cellLayoutPaddingPx); } @@ -1972,66 +1872,33 @@ public class DeviceProfile { paddings.bottom -= insets.bottom; } - /** - * Returns the new border space that should be used between hotseat icons after adjusting it to + * Returns the new border space that should be used between hotseat icons after + * adjusting it to * the bubble bar. * - *

Does not check for visible bubbles persistence, so caller should call - * {@link #shouldAdjustHotseatOrQsbForBubbleBar} first. - * - *

If there's no adjustment needed, this method returns {@code 0}. - * @see #shouldAdjustHotseatOrQsbForBubbleBar(Context, boolean) + *

+ * If there's no adjustment needed, this method returns {@code 0}. */ public float getHotseatAdjustedBorderSpaceForBubbleBar(Context context) { - if (shouldAlignBubbleBarWithQSB() || !shouldAdjustHotseatOrQsbForBubbleBar(context)) { + // only need to adjust when QSB is on top of the hotseat. + if (isQsbInline) { return 0; } + + // no need to adjust if there's enough space for the bubble bar to the right of + // the hotseat. + if (getHotseatLayoutPadding(context).right > mBubbleBarSpaceThresholdPx) { + return 0; + } + // The adjustment is shrinking the hotseat's width by 1 icon on either side. - int iconsWidth = - iconSizePx * numShownHotseatIcons + hotseatBorderSpace * (numShownHotseatIcons - 1); + int iconsWidth = iconSizePx * numShownHotseatIcons + hotseatBorderSpace * (numShownHotseatIcons - 1); int newWidth = iconsWidth - 2 * iconSizePx; // Evenly space the icons within the boundaries of the new width. return (float) (newWidth - iconSizePx * numShownHotseatIcons) / (numShownHotseatIcons - 1); } - /** - * Returns the hotseat icon translation X for the cellX index. - * - *

Does not check for visible bubbles persistence, so caller should call - * {@link #shouldAdjustHotseatOrQsbForBubbleBar} first. - * - *

If there's no adjustment needed, this method returns {@code 0}. - * @see #shouldAdjustHotseatOrQsbForBubbleBar(Context, boolean) - */ - public float getHotseatAdjustedTranslation(Context context, int cellX) { - float borderSpace = getHotseatAdjustedBorderSpaceForBubbleBar(context); - if (borderSpace == 0) return borderSpace; - float borderSpaceDelta = borderSpace - hotseatBorderSpace; - return iconSizePx + cellX * borderSpaceDelta; - } - - /** Returns whether hotseat or QSB should be adjusted for the bubble bar. */ - public boolean shouldAdjustHotseatOrQsbForBubbleBar(Context context, boolean hasBubbles) { - return hasBubbles && shouldAdjustHotseatOrQsbForBubbleBar(context); - } - - /** Returns whether hotseat should be adjusted for the bubble bar. */ - public boolean shouldAdjustHotseatForBubbleBar(Context context, boolean hasBubbles) { - return shouldAlignBubbleBarWithHotseat() - && shouldAdjustHotseatOrQsbForBubbleBar(context, hasBubbles); - } - - /** Returns whether hotseat or QSB should be adjusted for the bubble bar. */ - public boolean shouldAdjustHotseatOrQsbForBubbleBar(Context context) { - // only need to adjust if QSB is on top of the hotseat and there's not enough space for the - // bubble bar to either side of the hotseat. - if (isQsbInline) return false; - Rect hotseatPadding = getHotseatLayoutPadding(context); - int hotseatMinHorizontalPadding = Math.min(hotseatPadding.left, hotseatPadding.right); - return hotseatMinHorizontalPadding <= mBubbleBarSpaceThresholdPx; - } - /** * Returns the padding for hotseat view */ @@ -2051,33 +1918,10 @@ public class DeviceProfile { hotseatBarPadding.set(mHotseatBarWorkspaceSpacePx, paddingTop, mInsets.right + mHotseatBarEdgePaddingPx, paddingBottom); } - } else if (inv.isFixedLandscape) { + } else if (isTaskbarPresent || isTablet) { // Center the QSB vertically with hotseat int hotseatBarBottomPadding = getHotseatBarBottomPadding(); - int hotseatPlusQSBWidth = getHotseatRequiredWidth(); - int qsbWidth = getAdditionalQsbSpace(); - int availableWidthPxForHotseat = availableWidthPx - Math.abs(workspacePadding.width()) - - Math.abs(cellLayoutPaddingPx.width()); - int remainingSpaceOnSide = (availableWidthPxForHotseat - hotseatPlusQSBWidth) / 2; - - hotseatBarPadding.set( - remainingSpaceOnSide + mInsets.left + workspacePadding.left - + cellLayoutPaddingPx.left, - hotseatBarSizePx - hotseatBarBottomPadding - hotseatCellHeightPx, - remainingSpaceOnSide + mInsets.right + workspacePadding.right - + cellLayoutPaddingPx.right, - hotseatBarBottomPadding - ); - if (Utilities.isRtl(context.getResources())) { - hotseatBarPadding.right += qsbWidth; - } else { - hotseatBarPadding.left += qsbWidth; - } - } else if (isTaskbarPresent || isQsbInline) { - // Center the QSB vertically with hotseat - int hotseatBarBottomPadding = getHotseatBarBottomPadding(); - int hotseatBarTopPadding = - hotseatBarSizePx - hotseatBarBottomPadding - hotseatCellHeightPx; + int hotseatBarTopPadding = hotseatBarSizePx - hotseatBarBottomPadding - hotseatCellHeightPx; int hotseatWidth = getHotseatRequiredWidth(); int startSpacing; @@ -2111,9 +1955,12 @@ public class DeviceProfile { sideSpacing, getHotseatBarBottomPadding()); } else { - // We want the edges of the hotseat to line up with the edges of the workspace, but the - // icons in the hotseat are a different size, and so don't line up perfectly. To account - // for this, we pad the left and right of the hotseat with half of the difference of a + // We want the edges of the hotseat to line up with the edges of the workspace, + // but the + // icons in the hotseat are a different size, and so don't line up perfectly. To + // account + // for this, we pad the left and right of the hotseat with half of the + // difference of a // workspace cell vs a hotseat cell. float workspaceCellWidth = (float) widthPx / inv.numColumns; float hotseatCellWidth = (float) widthPx / numShownHotseatIcons; @@ -2136,8 +1983,7 @@ public class DeviceProfile { // On phones, the landscape layout uses a different setup. allAppsSpacing = workspacePadding.left + workspacePadding.right; } else { - allAppsSpacing = - allAppsPadding.left + allAppsPadding.right + allAppsLeftRightMargin * 2; + allAppsSpacing = allAppsPadding.left + allAppsPadding.right + allAppsLeftRightMargin * 2; } int cellWidth = DeviceProfile.calculateCellWidth( @@ -2152,10 +1998,12 @@ public class DeviceProfile { /** * TODO(b/235886078): workaround needed because of this bug - * Icons are 10% larger on XML than their visual size, so remove that extra space to get + * Icons are 10% larger on XML than their visual size, so remove that extra + * space to get * some dimensions correct. * - * When this bug is resolved this method will no longer be needed and we would be able to + * When this bug is resolved this method will no longer be needed and we would + * be able to * replace all instances where this method is called with iconSizePx. */ private int getIconVisibleSizePx(int iconSizePx) { @@ -2177,12 +2025,13 @@ public class DeviceProfile { } /** - * Returns the number of pixels the QSB is translated from the bottom of the screen. + * Returns the number of pixels the QSB is translated from the bottom of the + * screen. */ public int getQsbOffsetY() { - if (isPhone && isQsbInline) { + if (isQsbInline) { return getHotseatBarBottomPadding() - ((hotseatQsbHeight - hotseatCellHeightPx) / 2); - } else if (isTaskbarPresent || (isLandscape && isQsbInline)) { // QSB on top + } else if (isTaskbarPresent) { // QSB on top return hotseatBarSizePx - hotseatQsbHeight + hotseatQsbShadowHeight; } else { return hotseatBarBottomSpacePx - hotseatQsbShadowHeight; @@ -2190,10 +2039,11 @@ public class DeviceProfile { } /** - * Returns the number of pixels the hotseat is translated from the bottom of the screen. + * Returns the number of pixels the hotseat is translated from the bottom of the + * screen. */ private int getHotseatBarBottomPadding() { - if (isTaskbarPresent || isQsbInline) { // QSB on top or inline + if (isTaskbarPresent) { // QSB on top or inline return hotseatBarBottomSpacePx - (Math.abs(hotseatCellHeightPx - iconSizePx) / 2); } else { return hotseatBarSizePx - hotseatCellHeightPx; @@ -2201,38 +2051,12 @@ public class DeviceProfile { } /** - * Returns the number of pixels the hotseat icons or QSB vertical center is translated from the - * bottom of the screen. - */ - public int getBubbleBarVerticalCenterForHome() { - if (shouldAlignBubbleBarWithHotseat()) { - return hotseatBarSizePx - - (isQsbInline ? 0 : hotseatQsbVisualHeight) - - hotseatQsbSpace - - (hotseatCellHeightPx / 2) - + ((hotseatCellHeightPx - iconSizePx) / 2); - } else { - return hotseatBarSizePx - (hotseatQsbVisualHeight / 2); - } - } - - /** Returns whether bubble bar should be aligned with the hotseat. */ - public boolean shouldAlignBubbleBarWithQSB() { - return !shouldAlignBubbleBarWithHotseat(); - } - - /** Returns whether bubble bar should be aligned with the hotseat. */ - public boolean shouldAlignBubbleBarWithHotseat() { - return isQsbInline || isGestureMode; - } - - /** - * Returns the number of pixels the taskbar is translated from the bottom of the screen. + * Returns the number of pixels the taskbar is translated from the bottom of the + * screen. */ public int getTaskbarOffsetY() { int taskbarIconBottomSpace = (taskbarHeight - iconSizePx) / 2; - int launcherIconBottomSpace = - Math.min((hotseatCellHeightPx - iconSizePx) / 2, gridVisualizationPaddingY); + int launcherIconBottomSpace = Math.min((hotseatCellHeightPx - iconSizePx) / 2, gridVisualizationPaddingY); return getHotseatBarBottomPadding() + launcherIconBottomSpace - taskbarIconBottomSpace; } @@ -2241,7 +2065,9 @@ public class DeviceProfile { return isTaskbarPresent ? mTransientTaskbarClaimedSpace : mInsets.bottom; } - /** Gets the space that the overview actions will take, including bottom margin. */ + /** + * Gets the space that the overview actions will take, including bottom margin. + */ public int getOverviewActionsClaimedSpace() { int overviewActionsSpace = isTablet && Flags.enableGridOnlyOverview() ? 0 @@ -2250,12 +2076,14 @@ public class DeviceProfile { } /** - * Takes the View and return the scales of width and height depending on the DeviceProfile + * Takes the View and return the scales of width and height depending on the + * DeviceProfile * specifications * * @param itemInfo The tag of the widget view - * @return A PointF instance with the x set to be the scale of width, and y being the scale of - * height + * @return A PointF instance with the x set to be the scale of width, and y + * being the scale of + * height */ @NonNull public PointF getAppWidgetScale(@Nullable final ItemInfo itemInfo) { @@ -2267,7 +2095,8 @@ public class DeviceProfile { */ public Rect getAbsoluteOpenFolderBounds() { if (isVerticalBarLayout()) { - // Folders should only appear right of the drop target bar and left of the hotseat + // Folders should only appear right of the drop target bar and left of the + // hotseat return new Rect(mInsets.left + dropTargetBarSizePx + edgeMarginPx, mInsets.top, mInsets.left + availableWidthPx - hotseatBarSizePx - edgeMarginPx, @@ -2292,17 +2121,36 @@ public class DeviceProfile { } /** - * When {@code true}, the device is in landscape mode and the hotseat is on the right column. - * When {@code false}, either device is in portrait mode or the device is in landscape mode and + * When {@code true}, the device is in landscape mode and the hotseat is on the + * right column. + * When {@code false}, either device is in portrait mode or the device is in + * landscape mode and * the hotseat is on the bottom row. */ public boolean isVerticalBarLayout() { return isLandscape && transposeLayoutWithOrientation; } + /** + * Updates orientation information and returns true if it has changed from the + * previous value. + */ + public boolean updateIsSeascape(Context context) { + if (isVerticalBarLayout()) { + boolean isSeascape = DisplayController.INSTANCE.get(context) + .getInfo().rotation == Surface.ROTATION_270; + if (mIsSeascape != isSeascape) { + mIsSeascape = isSeascape; + // Hotseat changing sides requires updating workspace left/right paddings + updateWorkspacePadding(); + return true; + } + } + return false; + } + public boolean isSeascape() { - return rotationHint == Surface.ROTATION_270 - && (isVerticalBarLayout() || inv.isFixedLandscape); + return isVerticalBarLayout() && mIsSeascape; } public boolean shouldFadeAdjacentWorkspaceScreens() { @@ -2444,10 +2292,6 @@ public class DeviceProfile { mHotseatBarEdgePaddingPx)); writer.println(prefix + pxToDpStr("mHotseatBarWorkspaceSpacePx", mHotseatBarWorkspaceSpacePx)); - writer.println(prefix - + pxToDpStr("inlineNavButtonsEndSpacingPx", inlineNavButtonsEndSpacingPx)); - writer.println(prefix - + pxToDpStr("navButtonsLayoutWidthPx", navButtonsLayoutWidthPx)); writer.println(prefix + pxToDpStr("hotseatBarEndOffset", hotseatBarEndOffset)); writer.println(prefix + pxToDpStr("hotseatQsbSpace", hotseatQsbSpace)); writer.println(prefix + pxToDpStr("hotseatQsbHeight", hotseatQsbHeight)); @@ -2496,6 +2340,8 @@ public class DeviceProfile { overviewTaskIconDrawableSizePx)); writer.println(prefix + pxToDpStr("overviewTaskIconDrawableSizeGridPx", overviewTaskIconDrawableSizeGridPx)); + writer.println(prefix + pxToDpStr("overviewTaskIconAppChipMenuDrawableSizePx", + overviewTaskIconAppChipMenuDrawableSizePx)); writer.println(prefix + pxToDpStr("overviewTaskThumbnailTopMarginPx", overviewTaskThumbnailTopMarginPx)); writer.println(prefix + pxToDpStr("overviewActionsTopMarginPx", @@ -2562,36 +2408,17 @@ public class DeviceProfile { } /** - * Returns whether Taskbar and Hotseat should adjust horizontally on bubble bar location update. - */ - public boolean shouldAdjustHotseatOnNavBarLocationUpdate(Context context) { - return enableBubbleBar() - && !DisplayController.getNavigationMode(context).hasGestures; - } - - /** Returns hotseat translation X for the bubble bar position. */ - public int getHotseatTranslationXForNavBar(Context context, boolean isBubblesOnLeft) { - if (shouldAdjustHotseatOnNavBarLocationUpdate(context)) { - boolean isRtl = Utilities.isRtl(context.getResources()); - if (isBubblesOnLeft) { - return isRtl ? -navButtonsLayoutWidthPx : 0; - } else { - return isRtl ? 0 : navButtonsLayoutWidthPx; - } - } else { - return 0; - } - } - - /** - * Callback when a component changes the DeviceProfile associated with it, as a result of + * Callback when a component changes the DeviceProfile associated with it, as a + * result of * configuration change */ public interface OnDeviceProfileChangeListener { /** - * Called when the device profile is reassigned. Note that for layout and measurements, it - * is sufficient to listen for inset changes. Use this callback when you need to perform + * Called when the device profile is reassigned. Note that for layout and + * measurements, it + * is sufficient to listen for inset changes. Use this callback when you need to + * perform * a one time operation. */ void onDeviceProfileChanged(DeviceProfile dp); @@ -2606,19 +2433,18 @@ public class DeviceProfile { * Get the scales from the view * * @param itemInfo The tag of the widget view - * @return PointF instance containing the scale information, or null if using the default - * app widget scale of this device profile. + * @return PointF instance containing the scale information, or null if using + * the default + * app widget scale of this device profile. */ @NonNull PointF getScaleFromItemInfo(@Nullable ItemInfo itemInfo); } public static class Builder { - private final Context mContext; - private final InvariantDeviceProfile mInv; - private final Info mInfo; - private final WindowManagerProxy mWMProxy; - private final ThemeManager mThemeManager; + private Context mContext; + private InvariantDeviceProfile mInv; + private Info mInfo; private WindowBounds mWindowBounds; private boolean mIsMultiDisplay; @@ -2634,13 +2460,10 @@ public class DeviceProfile { private boolean mIsTransientTaskbar; - public Builder(Context context, InvariantDeviceProfile inv, Info info, - WindowManagerProxy wmProxy, ThemeManager themeManager) { + public Builder(Context context, InvariantDeviceProfile inv, Info info) { mContext = context; mInv = inv; mInfo = info; - mWMProxy = wmProxy; - mThemeManager = themeManager; mIsTransientTaskbar = info.isTransientTaskbar(); } @@ -2694,6 +2517,7 @@ public class DeviceProfile { /** * Set the isTransientTaskbar for the builder + * * @return This Builder */ public Builder setIsTransientTaskbar(boolean isTransientTaskbar) { @@ -2706,8 +2530,7 @@ public class DeviceProfile { throw new IllegalArgumentException("Window bounds not set"); } if (mTransposeLayoutWithOrientation == null) { - mTransposeLayoutWithOrientation = - !(mInfo.isTablet(mWindowBounds) || mInv.isFixedLandscape); + mTransposeLayoutWithOrientation = !mInfo.isTablet(mWindowBounds); } if (mIsGestureMode == null) { mIsGestureMode = mInfo.getNavigationMode().hasGestures; @@ -2721,8 +2544,7 @@ public class DeviceProfile { if (mOverrideProvider == null) { mOverrideProvider = DEFAULT_DIMENSION_PROVIDER; } - return new DeviceProfile(mContext, mInv, mInfo, mWMProxy, mThemeManager, - mWindowBounds, mDotRendererCache, + return new DeviceProfile(mContext, mInv, mInfo, mWindowBounds, mDotRendererCache, mIsMultiWindowMode, mTransposeLayoutWithOrientation, mIsMultiDisplay, mIsGestureMode, mViewScaleProvider, mOverrideProvider, mIsTransientTaskbar); } diff --git a/src/com/android/launcher3/DropTarget.java b/src/com/android/launcher3/DropTarget.java index 7f0c7b50c0..2d995103e7 100644 --- a/src/com/android/launcher3/DropTarget.java +++ b/src/com/android/launcher3/DropTarget.java @@ -18,7 +18,6 @@ package com.android.launcher3; import android.content.Context; import android.graphics.Rect; -import android.view.View; import com.android.launcher3.accessibility.DragViewStateAnnouncer; import com.android.launcher3.dragndrop.DragOptions; @@ -146,9 +145,4 @@ public interface DropTarget { // These methods are implemented in Views void getHitRectRelativeToDragLayer(Rect outRect); - - /** Returns the drop target view. By default, the implementor class is cast to the view. */ - default View getDropView() { - return (View) this; - } } diff --git a/src/com/android/launcher3/DropTargetHandler.kt b/src/com/android/launcher3/DropTargetHandler.kt index 3c162a2f85..e022159d15 100644 --- a/src/com/android/launcher3/DropTargetHandler.kt +++ b/src/com/android/launcher3/DropTargetHandler.kt @@ -2,7 +2,7 @@ package com.android.launcher3 import android.content.ComponentName import android.view.View -import com.android.launcher3.BaseActivity.EVENT_RESUMED +import com.android.launcher3.BaseDraggingActivity.EVENT_RESUMED import com.android.launcher3.DropTarget.DragObject import com.android.launcher3.LauncherConstants.ActivityCodes import com.android.launcher3.SecondaryDropTarget.DeferredOnComplete @@ -35,7 +35,8 @@ class DropTargetHandler(launcher: Launcher) { target?.let { deferred.mPackageName = it.packageName mLauncher.addEventCallback(EVENT_RESUMED) { deferred.onLauncherResume() } - } ?: deferred.sendFailure() + } + ?: deferred.sendFailure() } } } @@ -46,10 +47,19 @@ class DropTargetHandler(launcher: Launcher) { mLauncher.appWidgetHolder.startConfigActivity( mLauncher, widgetId, - ActivityCodes.REQUEST_RECONFIGURE_APPWIDGET, + ActivityCodes.REQUEST_RECONFIGURE_APPWIDGET ) } + fun dismissPrediction( + announcement: CharSequence, + onActionClicked: Runnable, + onDismiss: Runnable? + ) { + mLauncher.dragLayer.announceForAccessibility(announcement) + Snackbar.show(mLauncher, R.string.item_removed, R.string.undo, onDismiss, onActionClicked) + } + fun getViewUnderDrag(info: ItemInfo): View? { return if ( info is LauncherAppWidgetInfo && @@ -65,17 +75,10 @@ class DropTargetHandler(launcher: Launcher) { } fun onDeleteComplete(item: ItemInfo) { - removeItemAndStripEmptyScreens(null /* view */, item) - AbstractFloatingView.closeOpenViews( - mLauncher, - false, - AbstractFloatingView.TYPE_WIDGET_RESIZE_FRAME, - ) var pageItem: ItemInfo = item - if (item.container >= 0) { - mLauncher.workspace.getViewByItemId(item.container)?.let { - pageItem = it.tag as ItemInfo - } + if (item.container <= 0) { + val v = mLauncher.workspace.getHomescreenIconByItemId(item.container) + v?.let { pageItem = v.tag as ItemInfo } } val pageIds = if (pageItem.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) @@ -92,12 +95,16 @@ class DropTargetHandler(launcher: Launcher) { R.string.item_removed, R.string.undo, mLauncher.modelWriter::commitDelete, - onUndoClicked, + onUndoClicked ) } fun onAccessibilityDelete(view: View?, item: ItemInfo, announcement: CharSequence) { - removeItemAndStripEmptyScreens(view, item) + // Remove the item from launcher and the db, we can ignore the containerInfo in this call + // because we already remove the drag view from the folder (if the drag originated from + // a folder) in Folder.beginDrag() + mLauncher.removeItem(view, item, true /* deleteFromDb */, "removed by accessibility drop") + mLauncher.workspace.stripEmptyScreens() mLauncher.dragLayer.announceForAccessibility(announcement) } @@ -108,12 +115,4 @@ class DropTargetHandler(launcher: Launcher) { fun onClick(buttonDropTarget: ButtonDropTarget) { mLauncher.accessibilityDelegate.handleAccessibleDrop(buttonDropTarget, null, null) } - - private fun removeItemAndStripEmptyScreens(view: View?, item: ItemInfo) { - // Remove the item from launcher and the db, we can ignore the containerInfo in this call - // because we already remove the drag view from the folder (if the drag originated from - // a folder) in Folder.beginDrag() - mLauncher.removeItem(view, item, true /* deleteFromDb */, "removed by accessibility drop") - mLauncher.workspace.stripEmptyScreens() - } } diff --git a/src/com/android/launcher3/FastScrollRecyclerView.java b/src/com/android/launcher3/FastScrollRecyclerView.java index 456dbc4eed..5ad0d65e38 100644 --- a/src/com/android/launcher3/FastScrollRecyclerView.java +++ b/src/com/android/launcher3/FastScrollRecyclerView.java @@ -25,23 +25,24 @@ import android.view.View; import android.view.accessibility.AccessibilityNodeInfo; import androidx.annotation.Nullable; -import androidx.constraintlayout.widget.ConstraintLayout; import androidx.recyclerview.widget.RecyclerView; +import com.android.app.animation.Interpolators; import com.android.launcher3.compat.AccessibilityManagerCompat; import com.android.launcher3.views.RecyclerViewFastScroller; - import com.patrykmichalik.opto.core.PreferenceExtensionsKt; + import app.lawnchair.preferences2.PreferenceManager2; /** * A base {@link RecyclerView}, which does the following: *

    - *
  • NOT intercept a touch unless the scrolling velocity is below a predefined threshold. - *
  • Enable fast scroller. + *
  • NOT intercept a touch unless the scrolling velocity is below a predefined + * threshold. + *
  • Enable fast scroller. *
*/ -public abstract class FastScrollRecyclerView extends RecyclerView { +public abstract class FastScrollRecyclerView extends RecyclerView { protected RecyclerViewFastScroller mScrollbar; @@ -62,12 +63,9 @@ public abstract class FastScrollRecyclerView extends RecyclerView { pref2 = PreferenceManager2.getInstance(context); } - public void bindFastScrollbar(RecyclerViewFastScroller scrollbar, - RecyclerViewFastScroller.FastScrollerLocation location) { + public void bindFastScrollbar(RecyclerViewFastScroller scrollbar) { mScrollbar = scrollbar; mScrollbar.setRecyclerView(this); - mScrollbar.setFastScrollerLocation(location); - scrollToTop(); onUpdateScrollbar(0); } @@ -115,7 +113,7 @@ public abstract class FastScrollRecyclerView extends RecyclerView { /** * Returns the available scroll height: - * AvailableScrollHeight = Total height of the all items - last page height + * AvailableScrollHeight = Total height of the all items - last page height */ protected int getAvailableScrollHeight() { // AvailableScrollHeight = Total height of the all items - first page height @@ -126,15 +124,17 @@ public abstract class FastScrollRecyclerView extends RecyclerView { /** * Returns the available scroll bar height: - * AvailableScrollBarHeight = Total height of the visible view - thumb height + * AvailableScrollBarHeight = Total height of the visible view - thumb height */ protected int getAvailableScrollBarHeight() { return getScrollbarTrackHeight() - mScrollbar.getThumbHeight(); } /** - * Updates the scrollbar thumb offset to match the visible scroll of the recycler view. It does - * this by mapping the available scroll area of the recycler view to the available space for the + * Updates the scrollbar thumb offset to match the visible scroll of the + * recycler view. It does + * this by mapping the available scroll area of the recycler view to the + * available space for the * scroll bar. * * @param scrollY the current scroll y @@ -147,11 +147,12 @@ public abstract class FastScrollRecyclerView extends RecyclerView { return; } - // Calculate the current scroll position, the scrollY of the recycler view accounts for the - // view padding, while the scrollBarY is drawn right up to the background padding (ignoring + // Calculate the current scroll position, the scrollY of the recycler view + // accounts for the + // view padding, while the scrollBarY is drawn right up to the background + // padding (ignoring // padding) - int scrollBarY = - (int) (((float) scrollY / availableScrollHeight) * getAvailableScrollBarHeight()); + int scrollBarY = (int) (((float) scrollY / availableScrollHeight) * getAvailableScrollBarHeight()); // Calculate the position and size of the scroll bar mScrollbar.setThumbOffsetY(scrollBarY); @@ -159,6 +160,7 @@ public abstract class FastScrollRecyclerView extends RecyclerView { /** * Returns whether the view itself will handle the touch event or not. + * * @param ev MotionEvent in {@param eventSource} */ public boolean shouldContainerScroll(MotionEvent ev, View eventSource) { @@ -171,7 +173,8 @@ public abstract class FastScrollRecyclerView extends RecyclerView { return false; } - // IF scroller is at the very top OR there is no scroll bar because there is probably not + // IF scroller is at the very top OR there is no scroll bar because there is + // probably not // enough items to scroll, THEN it's okay for the container to be pulled down. return computeVerticalScrollOffset() == 0; } @@ -185,28 +188,25 @@ public abstract class FastScrollRecyclerView extends RecyclerView { /** * Maps the touch (from 0..1) to the adapter position that should be visible. - *

Override in each subclass of this base class. + *

+ * Override in each subclass of this base class. */ public abstract CharSequence scrollToPositionAtProgress(float touchFraction); /** * Updates the bounds for the scrollbar. - *

Override in each subclass of this base class. + *

+ * Override in each subclass of this base class. */ public abstract void onUpdateScrollbar(int dy); /** - * Return the fast scroll letter list view in the A-Z list. + *

+ * Override in each subclass of this base class. */ - public ConstraintLayout getLetterList() { - return null; + public void onFastScrollCompleted() { } - /** - *

Override in each subclass of this base class. - */ - public void onFastScrollCompleted() {} - @Override public void onScrollStateChanged(int state) { super.onScrollStateChanged(state); @@ -221,7 +221,8 @@ public abstract class FastScrollRecyclerView extends RecyclerView { @Override public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); - if (isLayoutSuppressed()) info.setScrollable(false); + if (isLayoutSuppressed()) + info.setScrollable(false); } /** diff --git a/src/com/android/launcher3/GestureNavContract.java b/src/com/android/launcher3/GestureNavContract.java index e285930495..9ef6edc6cf 100644 --- a/src/com/android/launcher3/GestureNavContract.java +++ b/src/com/android/launcher3/GestureNavContract.java @@ -19,13 +19,10 @@ import static android.content.Intent.EXTRA_COMPONENT_NAME; import static android.content.Intent.EXTRA_USER; import static com.android.launcher3.AbstractFloatingView.TYPE_ICON_SURFACE; -import static com.android.launcher3.Utilities.ATLEAST_Q; import android.content.ComponentName; import android.content.Intent; import android.graphics.RectF; -import android.os.Build.VERSION; -import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -74,9 +71,7 @@ public class GestureNavContract { @Nullable SurfaceControl surfaceControl) { Bundle result = new Bundle(); result.putParcelable(EXTRA_ICON_POSITION, position); - if (ATLEAST_Q) { - result.putParcelable(EXTRA_ICON_SURFACE, surfaceControl); - } + result.putParcelable(EXTRA_ICON_SURFACE, surfaceControl); if (sMessageReceiver == null) { sMessageReceiver = new StaticMessageReceiver(); } diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java index a5c1baa5ce..c83f8d9da3 100644 --- a/src/com/android/launcher3/Hotseat.java +++ b/src/com/android/launcher3/Hotseat.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * Modifications copyright 2025 Lawnchair + * Modifications copyright 2022 Lawnchair */ package com.android.launcher3; @@ -40,27 +40,10 @@ import android.view.ViewDebug; import android.view.ViewGroup; import android.widget.FrameLayout; -import androidx.annotation.IntDef; -import androidx.annotation.Nullable; - -import com.android.launcher3.ShortcutAndWidgetContainer.TranslationProvider; -import com.android.launcher3.celllayout.CellLayoutLayoutParams; import com.android.launcher3.taskbar.BlurredBitmapDrawable; - -import com.android.launcher3.util.HorizontalInsettableView; -import com.android.launcher3.util.MultiPropertyFactory; -import com.android.launcher3.util.MultiPropertyFactory.MultiProperty; -import com.android.launcher3.util.MultiTranslateDelegate; -import com.android.launcher3.util.MultiValueAlpha; -import com.android.launcher3.views.ActivityContext; -import android.graphics.drawable.Drawable; - -import java.io.PrintWriter; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - import com.hoko.blur.HokoBlur; import com.patrykmichalik.opto.core.PreferenceExtensionsKt; + import app.lawnchair.hotseat.DisabledHotseat; import app.lawnchair.hotseat.HotseatMode; import app.lawnchair.hotseat.LawnchairHotseat; @@ -68,30 +51,16 @@ import app.lawnchair.preferences.PreferenceManager; import app.lawnchair.preferences2.PreferenceManager2; import app.lawnchair.theme.drawable.DrawableTokens; +import com.android.launcher3.util.HorizontalInsettableView; +import com.android.launcher3.util.MultiTranslateDelegate; +import com.android.launcher3.views.ActivityContext; +import android.graphics.drawable.Drawable; + /** * View class that represents the bottom row of the home screen. */ public class Hotseat extends CellLayout implements Insettable { - public static final int ALPHA_CHANNEL_TASKBAR_ALIGNMENT = 0; - public static final int ALPHA_CHANNEL_PREVIEW_RENDERER = 1; - public static final int ALPHA_CHANNEL_TASKBAR_STASH = 2; - public static final int ALPHA_CHANNEL_CHANNELS_COUNT = 3; - - @Retention(RetentionPolicy.RUNTIME) - @IntDef({ALPHA_CHANNEL_TASKBAR_ALIGNMENT, ALPHA_CHANNEL_PREVIEW_RENDERER, - ALPHA_CHANNEL_TASKBAR_STASH}) - public @interface HotseatQsbAlphaId { - } - - public static final int ICONS_TRANSLATION_X_NAV_BAR_ALIGNMENT = 0; - public static final int ICONS_TRANSLATION_X_CHANNELS_COUNT = 1; - - @Retention(RetentionPolicy.RUNTIME) - @IntDef({ICONS_TRANSLATION_X_NAV_BAR_ALIGNMENT}) - public @interface IconsTranslationX { - } - // Ratio of empty space, qsb should take up to appear visually centered. public static final float QSB_CENTER_FACTOR = .325f; private static final int BUBBLE_BAR_ADJUSTMENT_ANIMATION_DURATION_MS = 250; @@ -100,12 +69,6 @@ public class Hotseat extends CellLayout implements Insettable { private boolean mHasVerticalHotseat; private Workspace mWorkspace; private boolean mSendTouchToWorkspace; - private final MultiValueAlpha mIconsAlphaChannels; - private final MultiValueAlpha mQsbAlphaChannels; - - private @Nullable MultiProperty mQsbTranslationX; - - private final MultiPropertyFactory mIconsTranslationXFactory; private final View mQsb; @@ -142,48 +105,28 @@ public class Hotseat extends CellLayout implements Insettable { mQsb = LayoutInflater.from(context).inflate(layoutId, this, false); addView(mQsb); - mIconsAlphaChannels = new MultiValueAlpha(getShortcutsAndWidgets(), - ALPHA_CHANNEL_CHANNELS_COUNT); - if (mQsb instanceof Reorderable qsbReorderable) { - mQsbTranslationX = qsbReorderable.getTranslateDelegate() - .getTranslationX(MultiTranslateDelegate.INDEX_NAV_BAR_ANIM); - } - mIconsTranslationXFactory = new MultiPropertyFactory<>(getShortcutsAndWidgets(), - VIEW_TRANSLATE_X, ICONS_TRANSLATION_X_CHANNELS_COUNT, Float::sum); - mQsbAlphaChannels = new MultiValueAlpha(mQsb, ALPHA_CHANNEL_CHANNELS_COUNT); setUpBackground(); } private void setUpBackground() { if(!preferenceManager.getHotseatBG().get()) return; - + var bgColor = PreferenceExtensionsKt.firstBlocking(preferenceManager2.getHotseatBackgroundColor()); var transparency = preferenceManager.getHotseatBGAlpha().get(); var alphaValue = (transparency * 255) / 100; - var baseColor = bgColor.getColorPreferenceEntry().getLightColor().invoke(getContext()); + var baseColor = bgColor.getColorPreferenceEntry().getLightColor().invoke(mContext); var finalColor = Color.argb(alphaValue, Color.red(baseColor), Color.green(baseColor), Color.blue(baseColor)); int insetHorizontalLeft = preferenceManager.getHotseatBGHorizontalInsetLeft().get(); int insetHorizontalRight = preferenceManager.getHotseatBGHorizontalInsetRight().get(); int insetVerticalTop = preferenceManager.getHotseatBGVerticalInsetTop().get(); int insetVerticalBottom = preferenceManager.getHotseatBGVerticalInsetBottom().get(); InsetDrawable bg = new InsetDrawable(DrawableTokens.BgCellLayout.resolve(getContext()), - insetHorizontalLeft, insetVerticalTop, insetHorizontalRight, insetVerticalBottom); + insetHorizontalLeft, insetVerticalTop, insetHorizontalRight, insetVerticalBottom); bg.setTint(finalColor); setBackground(bg); } - /** Provides translation X for hotseat icons for the channel. */ - public MultiProperty getIconsTranslationX(@IconsTranslationX int channelId) { - return mIconsTranslationXFactory.get(channelId); - } - - /** Provides translation X for hotseat Qsb. */ - @Nullable - public MultiProperty getQsbTranslationX() { - return mQsbTranslationX; - } - /** * Returns orientation specific cell X given invariant order in the hotseat */ @@ -211,9 +154,13 @@ public class Hotseat extends CellLayout implements Insettable { DeviceProfile dp = mActivity.getDeviceProfile(); if (bubbleBarEnabled) { - if (dp.shouldAdjustHotseatForBubbleBar(getContext(), hasBubbles)) { - getShortcutsAndWidgets().setTranslationProvider( - cellX -> dp.getHotseatAdjustedTranslation(getContext(), cellX)); + float adjustedBorderSpace = dp.getHotseatAdjustedBorderSpaceForBubbleBar(getContext()); + if (hasBubbles && Float.compare(adjustedBorderSpace, 0f) != 0) { + getShortcutsAndWidgets().setTranslationProvider(child -> { + int index = getShortcutsAndWidgets().indexOfChild(child); + float borderSpaceDelta = adjustedBorderSpace - dp.hotseatBorderSpace; + return dp.iconSizePx + index * borderSpaceDelta; + }); if (mQsb instanceof HorizontalInsettableView) { HorizontalInsettableView insettableQsb = (HorizontalInsettableView) mQsb; final float insetFraction = (float) dp.iconSizePx / dp.hotseatQsbWidth; @@ -240,52 +187,57 @@ public class Hotseat extends CellLayout implements Insettable { /** * Adjust the hotseat icons for the bubble bar. * - *

When the bubble bar becomes visible, if needed, this method animates the hotseat icons - * to reduce the spacing between them and make room for the bubble bar. The QSB width is + *

+ * When the bubble bar becomes visible, if needed, this method animates the + * hotseat icons + * to reduce the spacing between them and make room for the bubble bar. The QSB + * width is * animated as well to align with the hotseat icons. * - *

When the bubble bar goes away, any adjustments that were previously made are reversed. + *

+ * When the bubble bar goes away, any adjustments that were previously made are + * reversed. */ public void adjustForBubbleBar(boolean isBubbleBarVisible) { DeviceProfile dp = mActivity.getDeviceProfile(); - boolean shouldAdjust = isBubbleBarVisible - && dp.shouldAdjustHotseatOrQsbForBubbleBar(getContext()); - boolean shouldAdjustHotseat = shouldAdjust - && dp.shouldAlignBubbleBarWithHotseat(); + float adjustedBorderSpace = dp.getHotseatAdjustedBorderSpaceForBubbleBar(getContext()); + if (Float.compare(adjustedBorderSpace, 0f) == 0) { + return; + } + ShortcutAndWidgetContainer icons = getShortcutsAndWidgets(); + AnimatorSet animatorSet = new AnimatorSet(); + float borderSpaceDelta = adjustedBorderSpace - dp.hotseatBorderSpace; + // update the translation provider for future layout passes of hotseat icons. - if (shouldAdjustHotseat) { - icons.setTranslationProvider( - cellX -> dp.getHotseatAdjustedTranslation(getContext(), cellX)); + if (isBubbleBarVisible) { + icons.setTranslationProvider(child -> { + int index = icons.indexOfChild(child); + return dp.iconSizePx + index * borderSpaceDelta; + }); } else { icons.setTranslationProvider(null); } - AnimatorSet animatorSet = new AnimatorSet(); + for (int i = 0; i < icons.getChildCount(); i++) { View child = icons.getChildAt(i); - if (child.getLayoutParams() instanceof CellLayoutLayoutParams lp) { - float tx = shouldAdjustHotseat - ? dp.getHotseatAdjustedTranslation(getContext(), lp.getCellX()) : 0; - if (child instanceof Reorderable) { - MultiTranslateDelegate mtd = ((Reorderable) child).getTranslateDelegate(); - animatorSet.play( - mtd.getTranslationX(INDEX_BUBBLE_ADJUSTMENT_ANIM).animateToValue(tx)); - } else { - animatorSet.play(ObjectAnimator.ofFloat(child, VIEW_TRANSLATE_X, tx)); - } + float tx = isBubbleBarVisible ? dp.iconSizePx + i * borderSpaceDelta : 0; + if (child instanceof Reorderable) { + MultiTranslateDelegate mtd = ((Reorderable) child).getTranslateDelegate(); + animatorSet.play( + mtd.getTranslationX(INDEX_BUBBLE_ADJUSTMENT_ANIM).animateToValue(tx)); + } else { + animatorSet.play(ObjectAnimator.ofFloat(child, VIEW_TRANSLATE_X, tx)); } } - //TODO(b/381109832) refactor & simplify adjustment logic - boolean shouldAdjustQsb = - shouldAdjustHotseat || (shouldAdjust && dp.shouldAlignBubbleBarWithQSB()); - if (mQsb instanceof HorizontalInsettableView horizontalInsettableQsb) { - final float currentInsetFraction = horizontalInsettableQsb.getHorizontalInsets(); - final float targetInsetFraction = shouldAdjustQsb - ? (float) dp.iconSizePx / dp.hotseatQsbWidth : 0; - ValueAnimator qsbAnimator = - ValueAnimator.ofFloat(currentInsetFraction, targetInsetFraction); + if (mQsb instanceof HorizontalInsettableView) { + HorizontalInsettableView horizontalInsettableQsb = (HorizontalInsettableView) mQsb; + ValueAnimator qsbAnimator = ValueAnimator.ofFloat(0f, 1f); qsbAnimator.addUpdateListener(animation -> { - float insetFraction = (float) animation.getAnimatedValue(); + float fraction = qsbAnimator.getAnimatedFraction(); + float insetFraction = isBubbleBarVisible + ? (float) dp.iconSizePx * fraction / dp.hotseatQsbWidth + : (float) dp.iconSizePx * (1 - fraction) / dp.hotseatQsbWidth; horizontalInsettableQsb.setHorizontalInsets(insetFraction); }); animatorSet.play(qsbAnimator); @@ -293,13 +245,6 @@ public class Hotseat extends CellLayout implements Insettable { animatorSet.setDuration(BUBBLE_BAR_ADJUSTMENT_ANIMATION_DURATION_MS).start(); } - @Override - protected int getTranslationXForCell(int cellX, int cellY) { - TranslationProvider translationProvider = getShortcutsAndWidgets().getTranslationProvider(); - if (translationProvider == null) return 0; - return (int) translationProvider.getTranslationX(cellX); - } - @Override public void setInsets(Rect insets) { FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams(); @@ -320,6 +265,7 @@ public class Hotseat extends CellLayout implements Insettable { lp.gravity = Gravity.BOTTOM; lp.width = ViewGroup.LayoutParams.MATCH_PARENT; lp.height = grid.hotseatBarSizePx; + lp.topMargin = grid.hotseatBarBottomSpacePx; } Rect padding = grid.getHotseatLayoutPadding(getContext()); @@ -335,8 +281,10 @@ public class Hotseat extends CellLayout implements Insettable { @Override public boolean onInterceptTouchEvent(MotionEvent ev) { - // We allow horizontal workspace scrolling from within the Hotseat. We do this by delegating - // touch intercept the Workspace, and if it intercepts, delegating touch to the Workspace + // We allow horizontal workspace scrolling from within the Hotseat. We do this + // by delegating + // touch intercept the Workspace, and if it intercepts, delegating touch to the + // Workspace // for the remainder of the this input stream. int yThreshold = getMeasuredHeight() - getPaddingBottom(); if (mWorkspace != null && ev.getY() <= yThreshold) { @@ -368,14 +316,7 @@ public class Hotseat extends CellLayout implements Insettable { DeviceProfile dp = mActivity.getDeviceProfile(); - // LC: Fix weird sizing with hotseatQsbWidth being 0 on phone - int width; - if (dp.isQsbInline) { - width = dp.hotseatQsbWidth; - } else { - width = getShortcutsAndWidgets().getMeasuredWidth(); - } - + int width = getShortcutsAndWidgets().getMeasuredWidth(); mQsb.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(dp.hotseatQsbHeight, MeasureSpec.EXACTLY)); } @@ -402,27 +343,21 @@ public class Hotseat extends CellLayout implements Insettable { } /** - * Sets the alpha value of the specified alpha channel of just our ShortcutAndWidgetContainer. + * Sets the alpha value of just our ShortcutAndWidgetContainer. */ - public void setIconsAlpha(float alpha, @HotseatQsbAlphaId int channelId) { - getIconsAlpha(channelId).setValue(alpha); + public void setIconsAlpha(float alpha) { + getShortcutsAndWidgets().setAlpha(alpha); } /** * Sets the alpha value of just our QSB. */ - public void setQsbAlpha(float alpha, @HotseatQsbAlphaId int channelId) { - getQsbAlpha(channelId).setValue(alpha); + public void setQsbAlpha(float alpha) { + mQsb.setAlpha(alpha); } - /** Returns the alpha channel for ShortcutAndWidgetContainer */ - public MultiProperty getIconsAlpha(@HotseatQsbAlphaId int channelId) { - return mIconsAlphaChannels.get(channelId); - } - - /** Returns the alpha channel for Qsb */ - public MultiProperty getQsbAlpha(@HotseatQsbAlphaId int channelId) { - return mQsbAlphaChannels.get(channelId); + public float getIconsAlpha() { + return getShortcutsAndWidgets().getAlpha(); } /** @@ -432,24 +367,4 @@ public class Hotseat extends CellLayout implements Insettable { return mQsb; } - /** Dumps the Hotseat internal state */ - public void dump(String prefix, PrintWriter writer) { - writer.println(prefix + "Hotseat:"); - mIconsAlphaChannels.dump( - prefix + "\t", - writer, - "mIconsAlphaChannels", - "ALPHA_CHANNEL_TASKBAR_ALIGNMENT", - "ALPHA_CHANNEL_PREVIEW_RENDERER", - "ALPHA_CHANNEL_TASKBAR_STASH"); - mQsbAlphaChannels.dump( - prefix + "\t", - writer, - "mQsbAlphaChannels", - "ALPHA_CHANNEL_TASKBAR_ALIGNMENT", - "ALPHA_CHANNEL_PREVIEW_RENDERER", - "ALPHA_CHANNEL_TASKBAR_STASH" - ); - } - } diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java index 09e0797be8..93319af50f 100644 --- a/src/com/android/launcher3/InvariantDeviceProfile.java +++ b/src/com/android/launcher3/InvariantDeviceProfile.java @@ -16,14 +16,7 @@ package com.android.launcher3; -import static com.android.launcher3.GridType.GRID_TYPE_ANY; -import static com.android.launcher3.GridType.GRID_TYPE_NON_ONE_GRID; -import static com.android.launcher3.GridType.GRID_TYPE_ONE_GRID; -import static com.android.launcher3.LauncherPrefs.DB_FILE; -import static com.android.launcher3.LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE; -import static com.android.launcher3.LauncherPrefs.FIXED_LANDSCAPE_MODE; import static com.android.launcher3.LauncherPrefs.GRID_NAME; -import static com.android.launcher3.LauncherPrefs.NON_FIXED_LANDSCAPE_GRID_NAME; import static com.android.launcher3.Utilities.dpiFromPx; import static com.android.launcher3.testing.shared.ResourceUtils.INVALID_RESOURCE_HANDLE; import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY; @@ -33,15 +26,14 @@ import static com.android.launcher3.util.DisplayController.CHANGE_SUPPORTED_BOUN import static com.android.launcher3.util.DisplayController.CHANGE_TASKBAR_PINNING; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; +import android.annotation.TargetApi; import android.content.Context; -import android.content.Intent; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; - import android.text.TextUtils; import android.util.AttributeSet; import android.util.DisplayMetrics; @@ -57,26 +49,19 @@ import androidx.annotation.VisibleForTesting; import androidx.annotation.XmlRes; import androidx.core.content.res.ResourcesCompat; -import app.lawnchair.DeviceProfileOverrides.DBGridInfo; import com.android.launcher3.config.FeatureFlags; -import com.android.launcher3.dagger.ApplicationContext; -import com.android.launcher3.dagger.LauncherAppComponent; -import com.android.launcher3.dagger.LauncherAppSingleton; -import com.android.launcher3.graphics.ThemeManager; import com.android.launcher3.icons.DotRenderer; import com.android.launcher3.logging.FileLog; import com.android.launcher3.model.DeviceGridState; import com.android.launcher3.provider.RestoreDbTask; import com.android.launcher3.testing.shared.ResourceUtils; -import com.android.launcher3.util.DaggerSingletonObject; -import com.android.launcher3.util.DaggerSingletonTracker; import com.android.launcher3.util.DisplayController; import com.android.launcher3.util.DisplayController.Info; +import com.android.launcher3.util.LockedUserState; +import com.android.launcher3.util.MainThreadInitializedObject; import com.android.launcher3.util.Partner; -import com.android.launcher3.util.ResourceHelper; -import com.android.launcher3.util.SimpleBroadcastReceiver; +import com.android.launcher3.util.SafeCloseable; import com.android.launcher3.util.WindowBounds; -import com.android.launcher3.util.window.CachedDisplayInfo; import com.android.launcher3.util.window.WindowManagerProxy; import org.xmlpull.v1.XmlPullParser; @@ -90,81 +75,67 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Objects; -import java.util.concurrent.CopyOnWriteArrayList; import java.util.stream.Collectors; -import javax.inject.Inject; - import app.lawnchair.DeviceProfileOverrides; -@LauncherAppSingleton -public class InvariantDeviceProfile { +public class InvariantDeviceProfile implements SafeCloseable { - public static final String TAG = "IDP"; - // We do not need any synchronization for this variable as its only written on UI thread. - public static final DaggerSingletonObject INSTANCE = - new DaggerSingletonObject<>(LauncherAppComponent::getIDP); + public static final String TAG = "IDP"; + // We do not need any synchronization for this variable as its only written on UI thread. + public static final MainThreadInitializedObject INSTANCE = + new MainThreadInitializedObject<>(InvariantDeviceProfile::new); - public static final String GRID_NAME_PREFS_KEY = "idp_grid_name"; - public static final String NON_FIXED_LANDSCAPE_GRID_NAME_PREFS_KEY = - "idp_non_fixed_landscape_grid_name"; + @Retention(RetentionPolicy.SOURCE) + @IntDef({TYPE_PHONE, TYPE_MULTI_DISPLAY, TYPE_TABLET}) + public @interface DeviceType {} - @Retention(RetentionPolicy.SOURCE) - @IntDef({TYPE_PHONE, TYPE_MULTI_DISPLAY, TYPE_TABLET}) - public @interface DeviceType { - } + public static final int TYPE_PHONE = 0; + public static final int TYPE_MULTI_DISPLAY = 1; + public static final int TYPE_TABLET = 2; - public static final int TYPE_PHONE = 0; - public static final int TYPE_MULTI_DISPLAY = 1; - public static final int TYPE_TABLET = 2; + private static final float ICON_SIZE_DEFINED_IN_APP_DP = 48; - private static final float ICON_SIZE_DEFINED_IN_APP_DP = 48; + // Constants that affects the interpolation curve between statically defined device profile + // buckets. + private static final float KNEARESTNEIGHBOR = 3; + private static final float WEIGHT_POWER = 5; - // Constants that affects the interpolation curve between statically defined device profile - // buckets. - private static final float KNEARESTNEIGHBOR = 3; - private static final float WEIGHT_POWER = 5; + // used to offset float not being able to express extremely small weights in extreme cases. + private static final float WEIGHT_EFFICIENT = 100000f; - // used to offset float not being able to express extremely small weights in extreme cases. - private static final float WEIGHT_EFFICIENT = 100000f; + // Used for arrays to specify different sizes (e.g. border spaces, width/height) in different + // constraints + public static final int COUNT_SIZES = 4; + public static final int INDEX_DEFAULT = 0; + public static final int INDEX_LANDSCAPE = 1; + public static final int INDEX_TWO_PANEL_PORTRAIT = 2; + public static final int INDEX_TWO_PANEL_LANDSCAPE = 3; - // Used for arrays to specify different sizes (e.g. border spaces, width/height) in different - // constraints - public static final int COUNT_SIZES = 4; - public static final int INDEX_DEFAULT = 0; - public static final int INDEX_LANDSCAPE = 1; - public static final int INDEX_TWO_PANEL_PORTRAIT = 2; - public static final int INDEX_TWO_PANEL_LANDSCAPE = 3; + /** These resources are used to override the device profile */ + private static final String RES_GRID_NUM_ROWS = "grid_num_rows"; + private static final String RES_GRID_NUM_COLUMNS = "grid_num_columns"; + private static final String RES_GRID_ICON_SIZE_DP = "grid_icon_size_dp"; - /** These resources are used to override the device profile */ - private static final String RES_GRID_NUM_ROWS = "grid_num_rows"; - private static final String RES_GRID_NUM_COLUMNS = "grid_num_columns"; - private static final String RES_GRID_ICON_SIZE_DP = "grid_icon_size_dp"; + /** + * Number of icons per row and column in the workspace. + */ + public int numRows; + public int numColumns; + public int numSearchContainerColumns; - private DisplayController mDisplayController; - private WindowManagerProxy mWMProxy; - private LauncherPrefs mPrefs; - private ThemeManager mThemeManager; - - /** - * Number of icons per row and column in the workspace. - */ - public int numRows; - public int numColumns; - public int numSearchContainerColumns; - - /** - * Number of icons per row and column in the folder. - */ - public int[] numFolderRows; - public int[] numFolderColumns; - public float[] iconSize; - public float[] iconTextSize; + /** + * Number of icons per row and column in the folder. + */ + public int[] numFolderRows; + public int[] numFolderColumns; + public float[] iconSize; + public float[] iconTextSize; /** * Bitmap size for workspace icons. This is calculated independently from all apps * to allow different icon size factors without affecting each other. */ - public int iconBitmapSize; + public int iconBitmapSize; /** * Bitmap size for all apps icons. This is calculated independently from workspace * to allow different icon size factors without affecting each other. @@ -174,1455 +145,1315 @@ public class InvariantDeviceProfile { * The icon density used for loading resources. This is based on the maximum of * iconBitmapSize and allAppsIconBitmapSize to ensure adequate quality for both. */ - public int fillResIconDpi; - public static @DeviceType int deviceType; - public static Info displayInfo; + public int fillResIconDpi; + public static @DeviceType int deviceType; - public PointF[] minCellSize; + public PointF[] minCellSize; - public PointF[] borderSpaces; - public @DimenRes int inlineNavButtonsEndSpacing; + public PointF[] borderSpaces; + public @DimenRes int inlineNavButtonsEndSpacing; - public @StyleRes int folderStyle; + public @StyleRes int folderStyle; - public @StyleRes int cellStyle; + public @StyleRes int cellStyle; - public float[] horizontalMargin; + public float[] horizontalMargin; - public PointF[] allAppsCellSize; - public float[] allAppsIconSize; - public float[] allAppsIconTextSize; - public PointF[] allAppsBorderSpaces; + public PointF[] allAppsCellSize; + public float[] allAppsIconSize; + public float[] allAppsIconTextSize; + public PointF[] allAppsBorderSpaces; - public float[] transientTaskbarIconSize; + public float[] transientTaskbarIconSize; - public boolean[] startAlignTaskbar; + public boolean[] startAlignTaskbar; - /** - * Original profile before preference overrides - */ - public GridOption closestProfile; + /** + * Original profile before preference overrides + */ + public GridOption closestProfile; - /** - * Number of icons inside the hotseat area. - */ - public int numShownHotseatIcons; + /** + * Number of icons inside the hotseat area. + */ + public int numShownHotseatIcons; - /** - * Number of icons inside the hotseat area that is stored in the database. This is greater than - * or equal to numnShownHotseatIcons, allowing for a seamless transition between two hotseat - * sizes that share the same DB. - */ - public int numDatabaseHotseatIcons; + /** + * Number of icons inside the hotseat area that is stored in the database. This is greater than + * or equal to numnShownHotseatIcons, allowing for a seamless transition between two hotseat + * sizes that share the same DB. + */ + public int numDatabaseHotseatIcons; - public float[] hotseatBarBottomSpace; - public float[] hotseatQsbSpace; + public float[] hotseatBarBottomSpace; + public float[] hotseatQsbSpace; - /** - * Number of columns in the all apps list. - */ - public int numAllAppsColumns; - public int numAllAppsRowsForCellHeightCalculation; - public int numDatabaseAllAppsColumns; - public @StyleRes int allAppsStyle; + /** + * Number of columns in the all apps list. + */ + public int numAllAppsColumns; + public int numAllAppsRowsForCellHeightCalculation; + public int numDatabaseAllAppsColumns; + public @StyleRes int allAppsStyle; - /** - * Do not query directly. see {@link DeviceProfile#isScalableGrid}. - */ - protected boolean isScalable; - @XmlRes - public int devicePaddingId = INVALID_RESOURCE_HANDLE; - @XmlRes - public int workspaceSpecsId = INVALID_RESOURCE_HANDLE; - @XmlRes - public int gridSizeSpecsId = INVALID_RESOURCE_HANDLE;; - @XmlRes - public int workspaceSpecsTwoPanelId = INVALID_RESOURCE_HANDLE; - @XmlRes - public int allAppsSpecsId = INVALID_RESOURCE_HANDLE; - @XmlRes - public int allAppsSpecsTwoPanelId = INVALID_RESOURCE_HANDLE; - @XmlRes - public int folderSpecsId = INVALID_RESOURCE_HANDLE; - @XmlRes - public int folderSpecsTwoPanelId = INVALID_RESOURCE_HANDLE; - @XmlRes - public int hotseatSpecsId = INVALID_RESOURCE_HANDLE; - @XmlRes - public int hotseatSpecsTwoPanelId = INVALID_RESOURCE_HANDLE; - @XmlRes - public int workspaceCellSpecsId = INVALID_RESOURCE_HANDLE; - @XmlRes - public int workspaceCellSpecsTwoPanelId = INVALID_RESOURCE_HANDLE; - @XmlRes - public int allAppsCellSpecsId = INVALID_RESOURCE_HANDLE; - @XmlRes - public int allAppsCellSpecsTwoPanelId = INVALID_RESOURCE_HANDLE; + /** + * Do not query directly. see {@link DeviceProfile#isScalableGrid}. + */ + protected boolean isScalable; + @XmlRes + public int devicePaddingId = INVALID_RESOURCE_HANDLE; + @XmlRes + public int workspaceSpecsId = INVALID_RESOURCE_HANDLE; + @XmlRes + public int workspaceSpecsTwoPanelId = INVALID_RESOURCE_HANDLE; + @XmlRes + public int allAppsSpecsId = INVALID_RESOURCE_HANDLE; + @XmlRes + public int allAppsSpecsTwoPanelId = INVALID_RESOURCE_HANDLE; + @XmlRes + public int folderSpecsId = INVALID_RESOURCE_HANDLE; + @XmlRes + public int folderSpecsTwoPanelId = INVALID_RESOURCE_HANDLE; + @XmlRes + public int hotseatSpecsId = INVALID_RESOURCE_HANDLE; + @XmlRes + public int hotseatSpecsTwoPanelId = INVALID_RESOURCE_HANDLE; + @XmlRes + public int workspaceCellSpecsId = INVALID_RESOURCE_HANDLE; + @XmlRes + public int workspaceCellSpecsTwoPanelId = INVALID_RESOURCE_HANDLE; + @XmlRes + public int allAppsCellSpecsId = INVALID_RESOURCE_HANDLE; + @XmlRes + public int allAppsCellSpecsTwoPanelId = INVALID_RESOURCE_HANDLE; - private String mLocale = ""; - public boolean enableTwoLinesInAllApps = false; - /** - * Fixed landscape mode is the landscape on the phones. - */ - public static boolean isFixedLandscape = false; + public String dbFile; + public int defaultLayoutId; + public int demoModeLayoutId; + public boolean[] inlineQsb = new boolean[COUNT_SIZES]; - @GridType - public int gridType; - public String dbFile; - public int defaultLayoutId; - public int demoModeLayoutId; - public boolean[] inlineQsb = new boolean[COUNT_SIZES]; + /** + * An immutable list of supported profiles. + */ + public List supportedProfiles = Collections.EMPTY_LIST; - /** - * An immutable list of supported profiles. - */ - public List supportedProfiles = Collections.EMPTY_LIST; + public Point defaultWallpaperSize; - public Point defaultWallpaperSize; + private final ArrayList mChangeListeners = new ArrayList<>(); - private final List mChangeListeners = new CopyOnWriteArrayList<>(); + @VisibleForTesting + public InvariantDeviceProfile() { } - @Inject - public InvariantDeviceProfile( - @ApplicationContext Context context, - LauncherPrefs prefs, - DisplayController dc, - WindowManagerProxy wmProxy, - ThemeManager themeManager, - DaggerSingletonTracker lifeCycle) { - mDisplayController = dc; - mWMProxy = wmProxy; - mPrefs = prefs; - mThemeManager = themeManager; - - String gridName = prefs.get(GRID_NAME); - initGrid(context, gridName); - - dc.setPriorityListener( - (displayContext, info, flags) -> { - if ((flags & (CHANGE_DENSITY | CHANGE_SUPPORTED_BOUNDS - | CHANGE_NAVIGATION_MODE | CHANGE_TASKBAR_PINNING - | CHANGE_DESKTOP_MODE)) != 0) { - onConfigChanged(displayContext); - } - }); - lifeCycle.addCloseable(() -> dc.setPriorityListener(null)); - - LauncherPrefChangeListener prefListener = key -> { - if (FIXED_LANDSCAPE_MODE.getSharedPrefKey().equals(key) - && isFixedLandscape != prefs.get(FIXED_LANDSCAPE_MODE)) { - if (isFixedLandscape) { - setCurrentGrid(context, prefs.get(NON_FIXED_LANDSCAPE_GRID_NAME)); - } else { - prefs.put(NON_FIXED_LANDSCAPE_GRID_NAME, getCurrentGridName(context)); - onConfigChanged(context); + @TargetApi(23) + private InvariantDeviceProfile(Context context) { + String gridName = getCurrentGridName(context); + String newGridName = initGrid(context, gridName); + if (!newGridName.equals(gridName)) { + LauncherPrefs.get(context).put(GRID_NAME, newGridName); } - } else if (ENABLE_TWOLINE_ALLAPPS_TOGGLE.getSharedPrefKey().equals(key) - && enableTwoLinesInAllApps != prefs.get(ENABLE_TWOLINE_ALLAPPS_TOGGLE)) { - onConfigChanged(context); - } - }; - prefs.addListener(prefListener, FIXED_LANDSCAPE_MODE, ENABLE_TWOLINE_ALLAPPS_TOGGLE); - lifeCycle.addCloseable(() -> prefs.removeListener(prefListener, - FIXED_LANDSCAPE_MODE, ENABLE_TWOLINE_ALLAPPS_TOGGLE)); + LockedUserState.get(context).runOnUserUnlocked(() -> + new DeviceGridState(this).writeToPrefs(context)); - SimpleBroadcastReceiver localeReceiver = new SimpleBroadcastReceiver(context, - MAIN_EXECUTOR, i -> onConfigChanged(context)); - localeReceiver.register(Intent.ACTION_LOCALE_CHANGED); - lifeCycle.addCloseable(() -> localeReceiver.unregisterReceiverSafely()); - } - - public static String getCurrentGridName(Context context) { - return DeviceProfileOverrides.INSTANCE.get(context).getCurrentGridName(); - } - - public InvariantDeviceProfile(Context context, DeviceProfileOverrides.DBGridInfo dbGridInfo) { - this.mPrefs = LauncherPrefs.get(context.getApplicationContext()); - this.mThemeManager = ThemeManager.INSTANCE.get(context.getApplicationContext()); - this.mDisplayController = DisplayController.INSTANCE.get(context.getApplicationContext()); - String gridName = DeviceProfileOverrides.INSTANCE.get(context).getGridName(dbGridInfo); - initGrid(context, gridName); - } - - private String initGrid(Context context, String gridName) { - Info displayInfo = mDisplayController.getInfo(); - List allOptions = getPredefinedDeviceProfiles( - context, - gridName, - displayInfo, - (RestoreDbTask.isPending(mPrefs) && !Flags.oneGridSpecs()), - mPrefs.get(FIXED_LANDSCAPE_MODE) - ); - - // Filter out options that don't have the same number of columns as the grid - DeviceGridState deviceGridState = new DeviceGridState(mPrefs); - List allOptionsFilteredByColCount = - filterByColumnCount(allOptions, deviceGridState.getColumns()); - - DisplayOption displayOption = - invDistWeightedInterpolate(displayInfo, allOptionsFilteredByColCount.isEmpty() - ? new ArrayList<>(allOptions) - : new ArrayList<>(allOptionsFilteredByColCount), - displayInfo.getDeviceType()); - - if (!displayOption.grid.name.equals(gridName)) { - mPrefs.put(GRID_NAME, displayOption.grid.name); + DisplayController.INSTANCE.get(context).setPriorityListener( + (displayContext, info, flags) -> { + if ((flags & (CHANGE_DENSITY | CHANGE_SUPPORTED_BOUNDS + | CHANGE_NAVIGATION_MODE | CHANGE_TASKBAR_PINNING + | CHANGE_DESKTOP_MODE)) != 0) { + onConfigChanged(displayContext); + } + }); } - DeviceProfileOverrides.DBGridInfo dbGridInfo = DeviceProfileOverrides.INSTANCE.get(context) - .getGridInfo(); - initGrid(context, displayInfo, displayOption, dbGridInfo); - FileLog.d(TAG, "After initGrid:" - + "gridName:" + gridName - + ", dbFile:" + dbFile - + ", LauncherPrefs GRID_NAME:" + mPrefs.get(GRID_NAME) - + ", LauncherPrefs DB_FILE:" + mPrefs.get(DB_FILE)); - return displayOption.grid.name; - } - private List filterByColumnCount( - List allOptions, int numColumns) { - return allOptions.stream() - .filter(option -> option.grid.numColumns == numColumns) - .collect(Collectors.toList()); - } - - /** - * @deprecated This is a temporary solution because on the backup and restore case we modify the - * IDP, this resets it. b/332974074 - */ - @Deprecated - public void reset(Context context) { - initGrid(context, getCurrentGridName(context)); - } - - private void initGrid(Context context, Info displayInfo, DisplayOption displayOption, DeviceProfileOverrides.DBGridInfo dbGridInfo) { - this.closestProfile = displayOption.grid; - - enableTwoLinesInAllApps = Flags.enableTwolineToggle() - && Utilities.isEnglishLanguage(context) - && mPrefs.get(ENABLE_TWOLINE_ALLAPPS_TOGGLE); - mLocale = context.getResources().getConfiguration().locale.toString(); - - DeviceProfileOverrides.Options overrideOptions = DeviceProfileOverrides.INSTANCE.get(context) - .getOverrides(displayOption.grid); - DisplayMetrics metrics = context.getResources().getDisplayMetrics(); - GridOption closestProfile = displayOption.grid; - numRows = dbGridInfo.getNumRows(); - numColumns = dbGridInfo.getNumColumns(); - numSearchContainerColumns = closestProfile.numSearchContainerColumns; - dbFile = dbGridInfo.getDbFile(); - gridType = closestProfile.gridType; - defaultLayoutId = closestProfile.defaultLayoutId; - demoModeLayoutId = closestProfile.demoModeLayoutId; - - numFolderRows = closestProfile.numFolderRows; - numFolderColumns = closestProfile.numFolderColumns; - folderStyle = closestProfile.folderStyle; - - cellStyle = closestProfile.cellStyle; - - isScalable = closestProfile.isScalable; - devicePaddingId = closestProfile.devicePaddingId; - workspaceSpecsId = closestProfile.mWorkspaceSpecsId; - gridSizeSpecsId = closestProfile.mGridSizeSpecsId; - workspaceSpecsTwoPanelId = closestProfile.mWorkspaceSpecsTwoPanelId; - allAppsSpecsId = closestProfile.mAllAppsSpecsId; - allAppsSpecsTwoPanelId = closestProfile.mAllAppsSpecsTwoPanelId; - folderSpecsId = closestProfile.mFolderSpecsId; - folderSpecsTwoPanelId = closestProfile.mFolderSpecsTwoPanelId; - hotseatSpecsId = closestProfile.mHotseatSpecsId; - hotseatSpecsTwoPanelId = closestProfile.mHotseatSpecsTwoPanelId; - workspaceCellSpecsId = closestProfile.mWorkspaceCellSpecsId; - workspaceCellSpecsTwoPanelId = closestProfile.mWorkspaceCellSpecsTwoPanelId; - allAppsCellSpecsId = closestProfile.mAllAppsCellSpecsId; - allAppsCellSpecsTwoPanelId = closestProfile.mAllAppsCellSpecsTwoPanelId; - numAllAppsRowsForCellHeightCalculation = - closestProfile.mNumAllAppsRowsForCellHeightCalculation; - this.deviceType = displayInfo.getDeviceType(); - this.displayInfo = displayInfo; - - inlineNavButtonsEndSpacing = closestProfile.inlineNavButtonsEndSpacing; - - iconSize = displayOption.iconSizes; - allAppsIconSize = displayOption.allAppsIconSizes; - float maxIconSize = iconSize[0]; - for (int i = 1; i < iconSize.length; i++) { - maxIconSize = Math.max(maxIconSize, iconSize[i]); + /** + * This constructor should NOT have any monitors by design. + */ + public InvariantDeviceProfile(Context context, String gridName) { + this(context, DeviceProfileOverrides.INSTANCE.get(context).getGridInfo(gridName)); } - float maxAllAppsIconSize = allAppsIconSize[0]; - for (int i = 1; i < allAppsIconSize.length; i++) { - maxAllAppsIconSize = Math.max(maxAllAppsIconSize, allAppsIconSize[i]); + + public InvariantDeviceProfile(Context context, DeviceProfileOverrides.DBGridInfo dbGridInfo) { + String gridName = DeviceProfileOverrides.INSTANCE.get(context).getGridName(dbGridInfo); + initGrid(context, gridName, dbGridInfo); } + + private String initGrid(Context context, String gridName, DeviceProfileOverrides.DBGridInfo dbGridInfo) { + Info displayInfo = DisplayController.INSTANCE.get(context).getInfo(); + @DeviceType int deviceType = displayInfo.getDeviceType(); + + ArrayList allOptions = + getPredefinedDeviceProfiles(context, gridName, deviceType, + RestoreDbTask.isPending(context)); + DisplayOption displayOption = + invDistWeightedInterpolate(displayInfo, allOptions, deviceType); + initGrid(context, displayInfo, displayOption, deviceType, dbGridInfo); + return displayOption.grid.name; + } + + /** + * This constructor should NOT have any monitors by design. + */ + public InvariantDeviceProfile(Context context, Display display) { + // Ensure that the main device profile is initialized + INSTANCE.get(context); + String gridName = getCurrentGridName(context); + + // Get the display info based on default display and interpolate it to existing display + Info defaultInfo = DisplayController.INSTANCE.get(context).getInfo(); + @DeviceType int defaultDeviceType = defaultInfo.getDeviceType(); + DisplayOption defaultDisplayOption = invDistWeightedInterpolate( + defaultInfo, + getPredefinedDeviceProfiles(context, gridName, defaultDeviceType, + /*allowDisabledGrid=*/false), + defaultDeviceType); + + Context displayContext = context.createDisplayContext(display); + Info myInfo = new Info(displayContext); + @DeviceType int deviceType = myInfo.getDeviceType(); + DisplayOption myDisplayOption = invDistWeightedInterpolate( + myInfo, + getPredefinedDeviceProfiles(context, gridName, deviceType, + /*allowDisabledGrid=*/false), + deviceType); + + DisplayOption result = new DisplayOption(defaultDisplayOption.grid) + .add(myDisplayOption); + result.iconSizes[INDEX_DEFAULT] = + defaultDisplayOption.iconSizes[INDEX_DEFAULT]; + for (int i = 1; i < COUNT_SIZES; i++) { + result.iconSizes[i] = Math.min( + defaultDisplayOption.iconSizes[i], myDisplayOption.iconSizes[i]); + } + + System.arraycopy(defaultDisplayOption.minCellSize, 0, result.minCellSize, 0, + COUNT_SIZES); + System.arraycopy(defaultDisplayOption.borderSpaces, 0, result.borderSpaces, 0, + COUNT_SIZES); + + initGrid(context, myInfo, result, deviceType); + } + + private void initGrid(Context context, Info displayInfo, DisplayOption displayOption, + @DeviceType int deviceType) { + DeviceProfileOverrides.DBGridInfo dbGridInfo = DeviceProfileOverrides.INSTANCE.get(context) + .getGridInfo(); + initGrid(context, displayInfo, displayOption, deviceType, dbGridInfo); + } + + @Override + public void close() { + DisplayController.INSTANCE.executeIfCreated(dc -> dc.setPriorityListener(null)); + } + + /** + * Reinitialize the current grid after a restore, where some grids might now be disabled. + */ + public void reinitializeAfterRestore(Context context) { + String currentGridName = getCurrentGridName(context); + String currentDbFile = dbFile; + String newGridName = initGrid(context, currentGridName); + String newDbFile = dbFile; + FileLog.d(TAG, "Reinitializing grid after restore." + + " currentGridName=" + currentGridName + + ", currentDbFile=" + currentDbFile + + ", newGridName=" + newGridName + + ", newDbFile=" + newDbFile); + if (!newDbFile.equals(currentDbFile)) { + FileLog.d(TAG, "Restored grid is disabled : " + currentGridName + + ", migrating to: " + newGridName + + ", removing all other grid db files"); + for (String gridDbFile : LauncherFiles.GRID_DB_FILES) { + if (gridDbFile.equals(currentDbFile)) { + continue; + } + if (context.getDatabasePath(gridDbFile).delete()) { + FileLog.d(TAG, "Removed old grid db file: " + gridDbFile); + } + } + setCurrentGrid(context, newGridName); + } + } + + public static String getCurrentGridName(Context context) { + return DeviceProfileOverrides.INSTANCE.get(context).getCurrentGridName(); + } + + private String initGrid(Context context, String gridName) { + Info displayInfo = DisplayController.INSTANCE.get(context).getInfo(); + @DeviceType int deviceType = displayInfo.getDeviceType(); + + ArrayList allOptions = + getPredefinedDeviceProfiles(context, gridName, deviceType, + RestoreDbTask.isPending(context)); + DisplayOption displayOption = + invDistWeightedInterpolate(displayInfo, allOptions, deviceType); + initGrid(context, displayInfo, displayOption, deviceType); + return displayOption.grid.name; + } + + /** + * @deprecated This is a temporary solution because on the backup and restore case we modify the + * IDP, this resets it. b/332974074 + */ + @Deprecated + public void reset(Context context) { + initGrid(context, getCurrentGridName(context)); + } + + @VisibleForTesting + public static String getDefaultGridName(Context context) { + return new InvariantDeviceProfile().initGrid(context, null); + } + + private void initGrid(Context context, Info displayInfo, DisplayOption displayOption, + @DeviceType int deviceType, DeviceProfileOverrides.DBGridInfo dbGridInfo) { + DeviceProfileOverrides.Options overrideOptions = DeviceProfileOverrides.INSTANCE.get(context) + .getOverrides(displayOption.grid); + DisplayMetrics metrics = context.getResources().getDisplayMetrics(); + closestProfile = displayOption.grid; + numRows = dbGridInfo.getNumRows(); + numColumns = dbGridInfo.getNumColumns(); + numSearchContainerColumns = deviceType == TYPE_MULTI_DISPLAY + ? closestProfile.numSearchContainerColumns : dbGridInfo.getNumHotseatColumns(); + dbFile = dbGridInfo.getDbFile(); + defaultLayoutId = closestProfile.defaultLayoutId; + demoModeLayoutId = closestProfile.demoModeLayoutId; + + numFolderRows = closestProfile.numFolderRows; + numFolderColumns = closestProfile.numFolderColumns; + folderStyle = closestProfile.folderStyle; + + cellStyle = closestProfile.cellStyle; + + isScalable = closestProfile.isScalable; + devicePaddingId = closestProfile.devicePaddingId; + workspaceSpecsId = closestProfile.mWorkspaceSpecsId; + workspaceSpecsTwoPanelId = closestProfile.mWorkspaceSpecsTwoPanelId; + allAppsSpecsId = closestProfile.mAllAppsSpecsId; + allAppsSpecsTwoPanelId = closestProfile.mAllAppsSpecsTwoPanelId; + folderSpecsId = closestProfile.mFolderSpecsId; + folderSpecsTwoPanelId = closestProfile.mFolderSpecsTwoPanelId; + hotseatSpecsId = closestProfile.mHotseatSpecsId; + hotseatSpecsTwoPanelId = closestProfile.mHotseatSpecsTwoPanelId; + workspaceCellSpecsId = closestProfile.mWorkspaceCellSpecsId; + workspaceCellSpecsTwoPanelId = closestProfile.mWorkspaceCellSpecsTwoPanelId; + allAppsCellSpecsId = closestProfile.mAllAppsCellSpecsId; + allAppsCellSpecsTwoPanelId = closestProfile.mAllAppsCellSpecsTwoPanelId; + numAllAppsRowsForCellHeightCalculation = + closestProfile.mNumAllAppsRowsForCellHeightCalculation; + this.deviceType = deviceType; + + inlineNavButtonsEndSpacing = closestProfile.inlineNavButtonsEndSpacing; + + iconSize = displayOption.iconSizes; + allAppsIconSize = displayOption.allAppsIconSizes; + float maxIconSize = iconSize[0]; + for (int i = 1; i < iconSize.length; i++) { + maxIconSize = Math.max(maxIconSize, iconSize[i]); + } + float maxAllAppsIconSize = allAppsIconSize[0]; + for (int i = 1; i < allAppsIconSize.length; i++) { + maxAllAppsIconSize = Math.max(maxAllAppsIconSize, allAppsIconSize[i]); + } // Calculate separate bitmap sizes for workspace and all apps iconBitmapSize = ResourceUtils.pxFromDp(maxIconSize, metrics); allAppsIconBitmapSize = ResourceUtils.pxFromDp(maxAllAppsIconSize, metrics); // Use the larger of the two for fillResIconDpi to ensure we have adequate resources fillResIconDpi = getLauncherIconDensity(Math.max(iconBitmapSize, allAppsIconBitmapSize)); - iconTextSize = displayOption.textSizes; + iconTextSize = displayOption.textSizes; - minCellSize = displayOption.minCellSize; + minCellSize = displayOption.minCellSize; - borderSpaces = displayOption.borderSpaces; + borderSpaces = displayOption.borderSpaces; - horizontalMargin = displayOption.horizontalMargin; + horizontalMargin = displayOption.horizontalMargin; numShownHotseatIcons = deviceType == TYPE_MULTI_DISPLAY ? closestProfile.numDatabaseHotseatIcons : dbGridInfo.getNumHotseatColumns(); - numDatabaseHotseatIcons = deviceType == TYPE_MULTI_DISPLAY + numDatabaseHotseatIcons = deviceType == TYPE_MULTI_DISPLAY ? closestProfile.numDatabaseHotseatIcons : numShownHotseatIcons; - hotseatBarBottomSpace = displayOption.hotseatBarBottomSpace; - hotseatQsbSpace = displayOption.hotseatQsbSpace; + hotseatBarBottomSpace = displayOption.hotseatBarBottomSpace; + hotseatQsbSpace = displayOption.hotseatQsbSpace; - allAppsStyle = closestProfile.allAppsStyle; + allAppsStyle = closestProfile.allAppsStyle; - numAllAppsColumns = closestProfile.numAllAppsColumns; + numAllAppsColumns = closestProfile.numAllAppsColumns; - numDatabaseAllAppsColumns = deviceType == TYPE_MULTI_DISPLAY - ? closestProfile.numDatabaseAllAppsColumns : numAllAppsColumns; + numDatabaseAllAppsColumns = deviceType == TYPE_MULTI_DISPLAY + ? closestProfile.numDatabaseAllAppsColumns : numAllAppsColumns; - allAppsCellSize = displayOption.allAppsCellSize; - allAppsBorderSpaces = displayOption.allAppsBorderSpaces; - allAppsIconSize = displayOption.allAppsIconSizes; - allAppsIconTextSize = displayOption.allAppsIconTextSizes; + allAppsCellSize = displayOption.allAppsCellSize; + allAppsBorderSpaces = displayOption.allAppsBorderSpaces; + allAppsIconSize = displayOption.allAppsIconSizes; + allAppsIconTextSize = displayOption.allAppsIconTextSizes; - inlineQsb = closestProfile.inlineQsb; + inlineQsb = closestProfile.inlineQsb; - transientTaskbarIconSize = displayOption.transientTaskbarIconSize; + transientTaskbarIconSize = displayOption.transientTaskbarIconSize; - startAlignTaskbar = displayOption.startAlignTaskbar; + startAlignTaskbar = displayOption.startAlignTaskbar; - // Fixed Landscape mode - isFixedLandscape = closestProfile.mIsFixedLandscape; + // If the partner customization apk contains any grid overrides, apply them + // Supported overrides: numRows, numColumns, iconSize + applyPartnerDeviceProfileOverrides(context, metrics); - // If the partner customization apk contains any grid overrides, apply them - // Supported overrides: numRows, numColumns, iconSize - applyPartnerDeviceProfileOverrides(context, metrics); + // Lawnchair ignores partner overrides and allows the user to customize the grid themselves + overrideOptions.applyUi(this); - // Lawnchair ignores partner overrides and allows the user to customize the grid themselves - overrideOptions.applyUi(this); + final List localSupportedProfiles = new ArrayList<>(); + defaultWallpaperSize = new Point(displayInfo.currentSize); + SparseArray dotRendererCache = new SparseArray<>(); + for (WindowBounds bounds : displayInfo.supportedBounds) { + localSupportedProfiles.add(new DeviceProfile.Builder(context, this, displayInfo) + .setIsMultiDisplay(deviceType == TYPE_MULTI_DISPLAY) + .setWindowBounds(bounds) + .setDotRendererCache(dotRendererCache) + .build()); - final List localSupportedProfiles = new ArrayList<>(); - defaultWallpaperSize = new Point(displayInfo.currentSize); - SparseArray dotRendererCache = new SparseArray<>(); - for (WindowBounds bounds : displayInfo.supportedBounds) { - localSupportedProfiles.add(newDPBuilder(context, displayInfo) - .setIsMultiDisplay(deviceType == TYPE_MULTI_DISPLAY) - .setWindowBounds(bounds) - .setDotRendererCache(dotRendererCache) - .build()); + // Wallpaper size should be the maximum of the all possible sizes Launcher expects + int displayWidth = bounds.bounds.width(); + int displayHeight = bounds.bounds.height(); + defaultWallpaperSize.y = Math.max(defaultWallpaperSize.y, displayHeight); - // Wallpaper size should be the maximum of the all possible sizes Launcher expects - int displayWidth = bounds.bounds.width(); - int displayHeight = bounds.bounds.height(); - defaultWallpaperSize.y = Math.max(defaultWallpaperSize.y, displayHeight); + // We need to ensure that there is enough extra space in the wallpaper + // for the intended parallax effects + float parallaxFactor = + dpiFromPx(Math.min(displayWidth, displayHeight), displayInfo.getDensityDpi()) + < 720 + ? 2 + : wallpaperTravelToScreenWidthRatio(displayWidth, displayHeight); + defaultWallpaperSize.x = + Math.max(defaultWallpaperSize.x, Math.round(parallaxFactor * displayWidth)); + } + supportedProfiles = Collections.unmodifiableList(localSupportedProfiles); - // We need to ensure that there is enough extra space in the wallpaper - // for the intended parallax effects - float parallaxFactor = - dpiFromPx(Math.min(displayWidth, displayHeight), displayInfo.getDensityDpi()) - < 720 - ? 2 - : wallpaperTravelToScreenWidthRatio(displayWidth, displayHeight); - defaultWallpaperSize.x = - Math.max(defaultWallpaperSize.x, Math.round(parallaxFactor * displayWidth)); + int numMinShownHotseatIconsForTablet = supportedProfiles + .stream() + .filter(deviceProfile -> deviceProfile.isTablet) + .mapToInt(deviceProfile -> deviceProfile.numShownHotseatIcons) + .min() + .orElse(0); + + supportedProfiles + .stream() + .filter(deviceProfile -> deviceProfile.isTablet) + .forEach(deviceProfile -> { + deviceProfile.numShownHotseatIcons = numMinShownHotseatIconsForTablet; + deviceProfile.recalculateHotseatWidthAndBorderSpace(); + }); } - supportedProfiles = Collections.unmodifiableList(localSupportedProfiles); - int numMinShownHotseatIconsForTablet = supportedProfiles - .stream() - .filter(deviceProfile -> deviceProfile.isTablet) - .mapToInt(deviceProfile -> deviceProfile.numShownHotseatIcons) - .min() - .orElse(0); + public void addOnChangeListener(OnIDPChangeListener listener) { + mChangeListeners.add(listener); + } - supportedProfiles - .stream() - .filter(deviceProfile -> deviceProfile.isTablet) - .forEach(deviceProfile -> { - deviceProfile.numShownHotseatIcons = numMinShownHotseatIconsForTablet; - deviceProfile.recalculateHotseatWidthAndBorderSpace(); - }); - } + public void removeOnChangeListener(OnIDPChangeListener listener) { + mChangeListeners.remove(listener); + } - DeviceProfile.Builder newDPBuilder(Context context, Info info) { - return new DeviceProfile.Builder(context, this, info, mWMProxy, mThemeManager); - } + public void onPreferencesChanged(Context context) { + Context appContext = context.getApplicationContext(); + MAIN_EXECUTOR.execute(() -> onConfigChanged(appContext)); + } - public void addOnChangeListener(OnIDPChangeListener listener) { - mChangeListeners.add(listener); - } + public void setCurrentGrid(Context context, String gridName) { + DeviceProfileOverrides.INSTANCE.get(context).setCurrentGrid(gridName); + MAIN_EXECUTOR.execute(() -> onConfigChanged(context.getApplicationContext())); + } - public void removeOnChangeListener(OnIDPChangeListener listener) { - mChangeListeners.remove(listener); - } - - public void onPreferencesChanged(Context context) { - Context appContext = context.getApplicationContext(); - MAIN_EXECUTOR.execute(() -> onConfigChanged(appContext)); - } - - /** - * Updates the current grid, this triggers a new IDP, reloads the database and triggers a grid - * migration. - */ - @VisibleForTesting - public void setCurrentGrid(Context context, String newGridName) { - DeviceProfileOverrides.INSTANCE.get(context).setCurrentGrid(newGridName); - MAIN_EXECUTOR.execute(() -> { - onConfigChanged(context.getApplicationContext()); - }); - } - - private Object[] toModelState() { - return new Object[]{ - numColumns, numRows, numSearchContainerColumns, numDatabaseHotseatIcons, + private Object[] toModelState() { + return new Object[]{ + numColumns, numRows, numSearchContainerColumns, numDatabaseHotseatIcons, iconBitmapSize, allAppsIconBitmapSize, fillResIconDpi, numDatabaseAllAppsColumns, dbFile}; - } - - /** Updates IDP using the provided context. Notifies listeners of change. */ - @VisibleForTesting - public void onConfigChanged(Context context) { - Object[] oldState = toModelState(); - - // Re-init grid - initGrid(context, getCurrentGridName(context)); - - boolean modelPropsChanged = !Arrays.equals(oldState, toModelState()); - for (OnIDPChangeListener listener : mChangeListeners) { - listener.onIdpChanged(modelPropsChanged); } - } - private static boolean firstGridFilter(GridOption gridOption, int deviceType, - boolean allowDisabledGrid, boolean isFixedLandscapeMode) { - return (gridOption.isEnabled(deviceType) || allowDisabledGrid) - && gridOption.filterByFlag(deviceType, isFixedLandscapeMode); - } + /** Updates IDP using the provided context. Notifies listeners of change. */ + @VisibleForTesting + public void onConfigChanged(Context context) { + Object[] oldState = toModelState(); - private static List getPredefinedDeviceProfiles( - Context context, - String gridName, - Info displayInfo, - boolean allowDisabledGrid, - boolean isFixedLandscapeMode - ) { - ArrayList profiles = new ArrayList<>(); + // Re-init grid + String gridName = getCurrentGridName(context); + initGrid(context, gridName); - try (XmlResourceParser parser = context.getResources().getXml(R.xml.device_profiles)) { - final int depth = parser.getDepth(); - int type; - while (((type = parser.next()) != XmlPullParser.END_TAG || - parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { - if ((type == XmlPullParser.START_TAG) - && GridOption.TAG_NAME.equals(parser.getName())) { - GridOption gridOption = new GridOption( - context, Xml.asAttributeSet(parser), displayInfo); - if (firstGridFilter(gridOption, displayInfo.getDeviceType(), allowDisabledGrid, - isFixedLandscapeMode)) { - final int displayDepth = parser.getDepth(); - while (((type = parser.next()) != XmlPullParser.END_TAG - || parser.getDepth() > displayDepth) - && type != XmlPullParser.END_DOCUMENT) { - if ((type == XmlPullParser.START_TAG) && "display-option".equals( - parser.getName())) { - profiles.add(new DisplayOption(gridOption, context, - Xml.asAttributeSet(parser))); - } + boolean modelPropsChanged = !Arrays.equals(oldState, toModelState()); + for (OnIDPChangeListener listener : mChangeListeners) { + listener.onIdpChanged(modelPropsChanged); + } + LauncherAppState.getInstance(context).reloadIcons(); + } + + private static ArrayList getPredefinedDeviceProfiles(Context context, + String gridName, @DeviceType int deviceType, boolean allowDisabledGrid) { + ArrayList profiles = new ArrayList<>(); + + try (XmlResourceParser parser = context.getResources().getXml(R.xml.device_profiles)) { + final int depth = parser.getDepth(); + int type; + while (((type = parser.next()) != XmlPullParser.END_TAG || + parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { + if ((type == XmlPullParser.START_TAG) + && GridOption.TAG_NAME.equals(parser.getName())) { + + GridOption gridOption = new GridOption(context, Xml.asAttributeSet(parser)); + if (gridOption.isEnabled(deviceType) || allowDisabledGrid) { + final int displayDepth = parser.getDepth(); + while (((type = parser.next()) != XmlPullParser.END_TAG + || parser.getDepth() > displayDepth) + && type != XmlPullParser.END_DOCUMENT) { + if ((type == XmlPullParser.START_TAG) && "display-option".equals( + parser.getName())) { + profiles.add(new DisplayOption(gridOption, context, + Xml.asAttributeSet(parser))); + } + } + } + } } - } + } catch (IOException | XmlPullParserException e) { + throw new RuntimeException(e); } - } - } catch (IOException | XmlPullParserException e) { - throw new RuntimeException(e); - } - ArrayList filteredProfiles = new ArrayList<>(); - if (!TextUtils.isEmpty(gridName)) { - for (DisplayOption option : profiles) { - if (gridName.equals(option.grid.name) && (option.grid.isEnabled( - displayInfo.getDeviceType()) || allowDisabledGrid)) { - filteredProfiles.add(option); + + ArrayList filteredProfiles = new ArrayList<>(); + if (!TextUtils.isEmpty(gridName)) { + for (DisplayOption option : profiles) { + if (gridName.equals(option.grid.name) + && (option.grid.isEnabled(deviceType) || allowDisabledGrid)) { + filteredProfiles.add(option); + } + } } - } - } - if (filteredProfiles.isEmpty() && TextUtils.isEmpty(gridName)) { - // Use the default options since gridName is empty and there's no valid grids. - for (DisplayOption option : profiles) { - if (option.canBeDefault) { - filteredProfiles.add(option); + if (filteredProfiles.isEmpty()) { + // No grid found, use the default options + for (DisplayOption option : profiles) { + if (option.canBeDefault) { + filteredProfiles.add(option); + } + } } - } - } else if (filteredProfiles.isEmpty()) { - // In this case we had a grid selected but we couldn't find it. - filteredProfiles.addAll(profiles); - } - if (filteredProfiles.isEmpty()) { - throw new RuntimeException("No display option with canBeDefault=true"); - } - return filteredProfiles; - } - - /** - * Parses through the xml to find GridSize specs. Then calls findBestGridSize to get the - * correct grid size for this GridOption. - * - * @return the result of {@link #findBestGridSize(List, int, int)}. - */ - private static GridSize getGridSize(ResourceHelper resourceHelper, Context context, - Info displayInfo) { - ArrayList gridSizes = new ArrayList<>(); - - try (XmlResourceParser parser = resourceHelper.getXml()) { - final int depth = parser.getDepth(); - int type; - while (((type = parser.next()) != XmlPullParser.END_TAG - || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { - if ((type == XmlPullParser.START_TAG) - && "GridSize".equals(parser.getName())) { - gridSizes.add(new GridSize(context, Xml.asAttributeSet(parser))); + if (filteredProfiles.isEmpty()) { + throw new RuntimeException("No display option with canBeDefault=true"); } - } - } catch (IOException | XmlPullParserException e) { - throw new RuntimeException(e); - } - - // Finds the min width and height in dp for all displays. - int[] dimens = findMinWidthAndHeightPxForDevice(displayInfo); - - return findBestGridSize(gridSizes, dimens[0], dimens[1]); - } - - /** - * @return the biggest grid size that fits the display dimensions. - * If no best grid size is found, return null. - */ - private static GridSize findBestGridSize(List list, int minWidthPx, - int minHeightPx) { - GridSize selectedGridSize = null; - for (GridSize item: list) { - if (minWidthPx >= item.mMinDeviceWidthPx && minHeightPx >= item.mMinDeviceHeightPx) { - if (selectedGridSize == null - || (selectedGridSize.mNumColumns <= item.mNumColumns - && selectedGridSize.mNumRows <= item.mNumRows)) { - selectedGridSize = item; - } - } - } - return selectedGridSize; - } - - private static int[] findMinWidthAndHeightPxForDevice(Info displayInfo) { - int minDisplayWidthPx = Integer.MAX_VALUE; - int minDisplayHeightPx = Integer.MAX_VALUE; - for (CachedDisplayInfo display: displayInfo.getAllDisplays()) { - minDisplayWidthPx = Math.min(minDisplayWidthPx, display.size.x); - minDisplayHeightPx = Math.min(minDisplayHeightPx, display.size.y); - } - return new int[]{minDisplayWidthPx, minDisplayHeightPx}; - } - - /** - * Returns the GridOption associated to the given file name or null if the fileName is not - * supported. - * Ej, launcher.db -> "normal grid", launcher_4_by_4.db -> "practical grid" - */ - public GridOption getGridOptionFromFileName(Context context, String fileName) { - return parseAllGridOptions(context).stream() - .filter(gridOption -> Objects.equals(gridOption.dbFile, fileName)) - .findFirst() - .orElse(null); - } - - /** - * Returns the name of the given size on the current device or empty string if the size is not - * supported. Ej. 4x4 -> normal, 5x4 -> practical, etc. - * (Note: the name of the grid can be different for the same grid size depending of - * the values of the InvariantDeviceProfile) - */ - public String getGridNameFromSize(Context context, Point size) { - return parseAllGridOptions(context).stream() - .filter(gridOption -> gridOption.numColumns == size.x - && gridOption.numRows == size.y) - .map(gridOption -> gridOption.name) - .findFirst() - .orElse(""); - } - - /** - * Returns the grid option for the given gridName on the current device (Note: the gridOption - * be different for the same gridName depending on the values of the InvariantDeviceProfile). - */ - public GridOption getGridOptionFromName(Context context, String gridName) { - return parseAllGridOptions(context).stream() - .filter(gridOption -> Objects.equals(gridOption.name, gridName)) - .findFirst() - .orElse(null); - } - - /** - * @return all the grid options that can be shown on the device - */ - public static List parseAllGridOptions(Context context) { - return parseAllDefinedGridOptions(context, displayInfo) - .stream() - .filter(go -> go.isEnabled(deviceType)) - .filter(go -> go.filterByFlag(deviceType, isFixedLandscape)) - .collect(Collectors.toList()); - } - - /** - * @return all the grid options that can be shown on the device - */ - public static List parseAllDefinedGridOptions(Context context, Info displayInfo) { - List result = new ArrayList<>(); - try (XmlResourceParser parser = context.getResources().getXml(R.xml.device_profiles)) { - final int depth = parser.getDepth(); - int type; - while (((type = parser.next()) != XmlPullParser.END_TAG - || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { - if ((type == XmlPullParser.START_TAG) - && GridOption.TAG_NAME.equals(parser.getName())) { - result.add(new GridOption(context, Xml.asAttributeSet(parser), displayInfo)); - } - } - } catch (IOException | XmlPullParserException e) { - Log.e(TAG, "Error parsing device profile", e); - return Collections.emptyList(); - } - return result; - } - - private int getLauncherIconDensity(int requiredSize) { - // Densities typically defined by an app. - int[] densityBuckets = new int[]{ - DisplayMetrics.DENSITY_LOW, - DisplayMetrics.DENSITY_MEDIUM, - DisplayMetrics.DENSITY_TV, - DisplayMetrics.DENSITY_HIGH, - DisplayMetrics.DENSITY_XHIGH, - DisplayMetrics.DENSITY_XXHIGH, - DisplayMetrics.DENSITY_XXXHIGH - }; - - int density = DisplayMetrics.DENSITY_XXXHIGH; - for (int i = densityBuckets.length - 1; i >= 0; i--) { - float expectedSize = ICON_SIZE_DEFINED_IN_APP_DP * densityBuckets[i] - / DisplayMetrics.DENSITY_DEFAULT; - if (expectedSize >= requiredSize) { - density = densityBuckets[i]; - } - } - - return density; - } - - /** - * Apply any Partner customization grid overrides. - * - * Currently we support: all apps row / column count. - */ - private void applyPartnerDeviceProfileOverrides(Context context, DisplayMetrics dm) { - Partner p = Partner.get(context.getPackageManager()); - if (p == null) { - return; - } - try { - int numRows = p.getIntValue(RES_GRID_NUM_ROWS, -1); - int numColumns = p.getIntValue(RES_GRID_NUM_COLUMNS, -1); - float iconSizePx = p.getDimenValue(RES_GRID_ICON_SIZE_DP, -1); - - if (numRows > 0 && numColumns > 0) { - this.numRows = numRows; - this.numColumns = numColumns; - } - if (iconSizePx > 0) { - this.iconSize[InvariantDeviceProfile.INDEX_DEFAULT] = - Utilities.dpiFromPx(iconSizePx, dm.densityDpi); - } - } catch (Resources.NotFoundException ex) { - Log.e(TAG, "Invalid Partner grid resource!", ex); - } - } - - private static float dist(float x0, float y0, float x1, float y1) { - return (float) Math.hypot(x1 - x0, y1 - y0); - } - - private static DisplayOption invDistWeightedInterpolate( - Info displayInfo, List points, @DeviceType int deviceType) { - int minWidthPx = Integer.MAX_VALUE; - int minHeightPx = Integer.MAX_VALUE; - for (WindowBounds bounds : displayInfo.supportedBounds) { - boolean isTablet = displayInfo.isTablet(bounds); - if (isTablet && deviceType == TYPE_MULTI_DISPLAY) { - // For split displays, take half width per page - minWidthPx = Math.min(minWidthPx, bounds.availableSize.x / 2); - minHeightPx = Math.min(minHeightPx, bounds.availableSize.y); - - } else if (!isTablet && bounds.isLandscape()) { - // We will use transposed layout in this case - minWidthPx = Math.min(minWidthPx, bounds.availableSize.y); - minHeightPx = Math.min(minHeightPx, bounds.availableSize.x); - } else { - minWidthPx = Math.min(minWidthPx, bounds.availableSize.x); - minHeightPx = Math.min(minHeightPx, bounds.availableSize.y); - } - } - - float width = dpiFromPx(minWidthPx, displayInfo.getDensityDpi()); - float height = dpiFromPx(minHeightPx, displayInfo.getDensityDpi()); - - // Sort the profiles based on the closeness to the device size - points.sort((a, b) -> - Float.compare(dist(width, height, a.minWidthDps, a.minHeightDps), - dist(width, height, b.minWidthDps, b.minHeightDps))); - - DisplayOption closestPoint = points.get(0); - GridOption closestOption = closestPoint.grid; - float weights = 0; - - if (dist(width, height, closestPoint.minWidthDps, closestPoint.minHeightDps) == 0) { - return closestPoint; - } - - DisplayOption out = new DisplayOption(closestOption); - for (int i = 0; i < points.size() && i < KNEARESTNEIGHBOR; ++i) { - DisplayOption p = points.get(i); - float w = weight(width, height, p.minWidthDps, p.minHeightDps, WEIGHT_POWER); - weights += w; - out.add(new DisplayOption().add(p).multiply(w)); - } - out.multiply(1.0f / weights); - - // Since the bitmaps are persisted, ensure that all bitmap sizes are not larger than - // predefined size to avoid cache invalidation - for (int i = INDEX_DEFAULT; i < COUNT_SIZES; i++) { - out.iconSizes[i] = Math.min(out.iconSizes[i], closestPoint.iconSizes[i]); - } - - return out; - } - - public DeviceProfile createDeviceProfileForSecondaryDisplay(Context displayContext) { - // Disable transpose layout and use multi-window mode so that the icons are scaled properly - return newDPBuilder(displayContext, new Info(displayContext)) - .setIsMultiDisplay(false) - .setMultiWindowMode(true) - .setWindowBounds(mWMProxy.getRealBounds( - displayContext, mWMProxy.getDisplayInfo(displayContext))) - .setTransposeLayoutWithOrientation(false) - .build(); - } - - public DeviceProfile getDeviceProfile(Context context) { - Rect bounds = mWMProxy.getCurrentBounds(context); - int rotation = mWMProxy.getRotation(context); - return getBestMatch(bounds.width(), bounds.height(), rotation); - } - - /** - * Returns the device profile matching the provided screen configuration - */ - public DeviceProfile getBestMatch(float screenWidth, float screenHeight, int rotation) { - DeviceProfile bestMatch = supportedProfiles.get(0); - float minDiff = Float.MAX_VALUE; - - for (DeviceProfile profile : supportedProfiles) { - float diff = Math.abs(profile.widthPx - screenWidth) - + Math.abs(profile.heightPx - screenHeight); - if (diff < minDiff) { - minDiff = diff; - bestMatch = profile; - } else if (diff == minDiff && profile.rotationHint == rotation) { - bestMatch = profile; - } - } - return bestMatch; - } - - private static float weight(float x0, float y0, float x1, float y1, float pow) { - float d = dist(x0, y0, x1, y1); - if (Float.compare(d, 0f) == 0) { - return Float.POSITIVE_INFINITY; - } - return (float) (WEIGHT_EFFICIENT / Math.pow(d, pow)); - } - - /** - * As a ratio of screen height, the total distance we want the parallax effect to span - * horizontally - */ - private static float wallpaperTravelToScreenWidthRatio(int width, int height) { - float aspectRatio = width / (float) height; - - // At an aspect ratio of 16/10, the wallpaper parallax effect should span 1.5 * screen width - // At an aspect ratio of 10/16, the wallpaper parallax effect should span 1.2 * screen width - // We will use these two data points to extrapolate how much the wallpaper parallax effect - // to span (ie travel) at any aspect ratio: - - final float ASPECT_RATIO_LANDSCAPE = 16 / 10f; - final float ASPECT_RATIO_PORTRAIT = 10 / 16f; - final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE = 1.5f; - final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT = 1.2f; - - // To find out the desired width at different aspect ratios, we use the following two - // formulas, where the coefficient on x is the aspect ratio (width/height): - // (16/10)x + y = 1.5 - // (10/16)x + y = 1.2 - // We solve for x and y and end up with a final formula: - final float x = - (WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE - - WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT) / - (ASPECT_RATIO_LANDSCAPE - ASPECT_RATIO_PORTRAIT); - final float y = WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT - x * ASPECT_RATIO_PORTRAIT; - return x * aspectRatio + y; - } - - public interface OnIDPChangeListener { - - /** - * Called when the device provide changes - */ - void onIdpChanged(boolean modelPropertiesChanged); - } - - - public static final class GridOption { - - public static final String TAG_NAME = "grid-option"; - - private static final int DEVICE_CATEGORY_PHONE = 1 << 0; - private static final int DEVICE_CATEGORY_TABLET = 1 << 1; - private static final int DEVICE_CATEGORY_MULTI_DISPLAY = 1 << 2; - private static final int DEVICE_CATEGORY_ANY = - DEVICE_CATEGORY_PHONE | DEVICE_CATEGORY_TABLET | DEVICE_CATEGORY_MULTI_DISPLAY; - - private static final int INLINE_QSB_FOR_PORTRAIT = 1 << 0; - private static final int INLINE_QSB_FOR_LANDSCAPE = 1 << 1; - private static final int INLINE_QSB_FOR_TWO_PANEL_PORTRAIT = 1 << 2; - private static final int INLINE_QSB_FOR_TWO_PANEL_LANDSCAPE = 1 << 3; - private static final int DONT_INLINE_QSB = 0; - - public final String name; - public final String gridTitle; - public final int gridIconId; - public final int numRows; - public final int numColumns; - public final int numSearchContainerColumns; - public final int deviceCategory; - @GridType - public final int gridType; - - public final int[] numFolderRows = new int[COUNT_SIZES]; - public final int[] numFolderColumns = new int[COUNT_SIZES]; - private final @StyleRes int folderStyle; - private final @StyleRes int cellStyle; - - private final @StyleRes int allAppsStyle; - public final int numAllAppsColumns; - private final int mNumAllAppsRowsForCellHeightCalculation; - private final int numDatabaseAllAppsColumns; - public final int numHotseatIcons; - private final int numDatabaseHotseatIcons; - - private final boolean[] inlineQsb = new boolean[COUNT_SIZES]; - - private @DimenRes int inlineNavButtonsEndSpacing; - private final String dbFile; - - private final int defaultLayoutId; - private final int demoModeLayoutId; - - private final boolean isScalable; - private final boolean mIsDualGrid; - private final int devicePaddingId; - private final int mWorkspaceSpecsId; - private final int mWorkspaceSpecsTwoPanelId; - private final int mAllAppsSpecsId; - private final int mAllAppsSpecsTwoPanelId; - private final int mFolderSpecsId; - private final int mFolderSpecsTwoPanelId; - private final int mHotseatSpecsId; - private final int mHotseatSpecsTwoPanelId; - private final int mWorkspaceCellSpecsId; - private final int mWorkspaceCellSpecsTwoPanelId; - private final int mAllAppsCellSpecsId; - private final int mAllAppsCellSpecsTwoPanelId; - private final int mGridSizeSpecsId; - private final boolean mIsFixedLandscape; - - public GridOption(Context context, AttributeSet attrs, Info displayInfo) { - TypedArray a = context.obtainStyledAttributes( - attrs, R.styleable.GridDisplayOption); - name = a.getString(R.styleable.GridDisplayOption_name); - gridTitle = a.getString(R.styleable.GridDisplayOption_gridTitle); - gridIconId = a.getResourceId( - R.styleable.GridDisplayOption_gridIconId, INVALID_RESOURCE_HANDLE); - deviceCategory = a.getInt(R.styleable.GridDisplayOption_deviceCategory, - DEVICE_CATEGORY_ANY); - mGridSizeSpecsId = a.getResourceId( - R.styleable.GridDisplayOption_gridSizeSpecsId, INVALID_RESOURCE_HANDLE); - mIsDualGrid = a.getBoolean(R.styleable.GridDisplayOption_isDualGrid, false); - if (mGridSizeSpecsId != INVALID_RESOURCE_HANDLE) { - ResourceHelper resourceHelper = new ResourceHelper(context, mGridSizeSpecsId); - GridSize gridSize = getGridSize(resourceHelper, context, displayInfo); - numColumns = gridSize.mNumColumns; - numRows = gridSize.mNumRows; - dbFile = gridSize.mDbFile; - defaultLayoutId = gridSize.mDefaultLayoutId; - demoModeLayoutId = gridSize.mDemoModeLayoutId; - } else { - numRows = a.getInt(R.styleable.GridDisplayOption_numRows, 0); - numColumns = a.getInt(R.styleable.GridDisplayOption_numColumns, 0); - dbFile = a.getString(R.styleable.GridDisplayOption_dbFile); - defaultLayoutId = a.getResourceId( - R.styleable.GridDisplayOption_defaultLayoutId, 0); - demoModeLayoutId = a.getResourceId( - R.styleable.GridDisplayOption_demoModeLayoutId, defaultLayoutId); - } - - numSearchContainerColumns = a.getInt( - R.styleable.GridDisplayOption_numSearchContainerColumns, numColumns); - - allAppsStyle = a.getResourceId(R.styleable.GridDisplayOption_allAppsStyle, - R.style.AllAppsStyleDefault); - numAllAppsColumns = a.getInt( - R.styleable.GridDisplayOption_numAllAppsColumns, numColumns); - numDatabaseAllAppsColumns = a.getInt( - R.styleable.GridDisplayOption_numExtendedAllAppsColumns, 2 * numAllAppsColumns); - - numHotseatIcons = a.getInt( - R.styleable.GridDisplayOption_numHotseatIcons, numColumns); - numDatabaseHotseatIcons = a.getInt( - R.styleable.GridDisplayOption_numExtendedHotseatIcons, 2 * numHotseatIcons); - - inlineNavButtonsEndSpacing = - a.getResourceId(R.styleable.GridDisplayOption_inlineNavButtonsEndSpacing, - R.dimen.taskbar_button_margin_default); - - numFolderRows[INDEX_DEFAULT] = a.getInt( - R.styleable.GridDisplayOption_numFolderRows, numRows); - numFolderColumns[INDEX_DEFAULT] = a.getInt( - R.styleable.GridDisplayOption_numFolderColumns, numColumns); - - if (FeatureFlags.enableResponsiveWorkspace()) { - numFolderRows[INDEX_LANDSCAPE] = a.getInt( - R.styleable.GridDisplayOption_numFolderRowsLandscape, - numFolderRows[INDEX_DEFAULT]); - numFolderColumns[INDEX_LANDSCAPE] = a.getInt( - R.styleable.GridDisplayOption_numFolderColumnsLandscape, - numFolderColumns[INDEX_DEFAULT]); - numFolderRows[INDEX_TWO_PANEL_PORTRAIT] = a.getInt( - R.styleable.GridDisplayOption_numFolderRowsTwoPanelPortrait, - numFolderRows[INDEX_DEFAULT]); - numFolderColumns[INDEX_TWO_PANEL_PORTRAIT] = a.getInt( - R.styleable.GridDisplayOption_numFolderColumnsTwoPanelPortrait, - numFolderColumns[INDEX_DEFAULT]); - numFolderRows[INDEX_TWO_PANEL_LANDSCAPE] = a.getInt( - R.styleable.GridDisplayOption_numFolderRowsTwoPanelLandscape, - numFolderRows[INDEX_DEFAULT]); - numFolderColumns[INDEX_TWO_PANEL_LANDSCAPE] = a.getInt( - R.styleable.GridDisplayOption_numFolderColumnsTwoPanelLandscape, - numFolderColumns[INDEX_DEFAULT]); - } else { - numFolderRows[INDEX_LANDSCAPE] = numFolderRows[INDEX_DEFAULT]; - numFolderColumns[INDEX_LANDSCAPE] = numFolderColumns[INDEX_DEFAULT]; - numFolderRows[INDEX_TWO_PANEL_PORTRAIT] = numFolderRows[INDEX_DEFAULT]; - numFolderColumns[INDEX_TWO_PANEL_PORTRAIT] = numFolderColumns[INDEX_DEFAULT]; - numFolderRows[INDEX_TWO_PANEL_LANDSCAPE] = numFolderRows[INDEX_DEFAULT]; - numFolderColumns[INDEX_TWO_PANEL_LANDSCAPE] = numFolderColumns[INDEX_DEFAULT]; - } - - folderStyle = a.getResourceId(R.styleable.GridDisplayOption_folderStyle, - INVALID_RESOURCE_HANDLE); - - cellStyle = a.getResourceId(R.styleable.GridDisplayOption_cellStyle, - R.style.CellStyleDefault); - - isScalable = a.getBoolean( - R.styleable.GridDisplayOption_isScalable, false); - devicePaddingId = a.getResourceId( - R.styleable.GridDisplayOption_devicePaddingId, INVALID_RESOURCE_HANDLE); - - if (FeatureFlags.enableResponsiveWorkspace()) { - mWorkspaceSpecsId = a.getResourceId( - R.styleable.GridDisplayOption_workspaceSpecsId, INVALID_RESOURCE_HANDLE); - mWorkspaceSpecsTwoPanelId = a.getResourceId( - R.styleable.GridDisplayOption_workspaceSpecsTwoPanelId, - mWorkspaceSpecsId); - mAllAppsSpecsId = a.getResourceId( - R.styleable.GridDisplayOption_allAppsSpecsId, INVALID_RESOURCE_HANDLE); - mAllAppsSpecsTwoPanelId = a.getResourceId( - R.styleable.GridDisplayOption_allAppsSpecsTwoPanelId, - mAllAppsSpecsId); - mFolderSpecsId = a.getResourceId( - R.styleable.GridDisplayOption_folderSpecsId, INVALID_RESOURCE_HANDLE); - mFolderSpecsTwoPanelId = a.getResourceId( - R.styleable.GridDisplayOption_folderSpecsTwoPanelId, - mFolderSpecsId); - mHotseatSpecsId = a.getResourceId( - R.styleable.GridDisplayOption_hotseatSpecsId, INVALID_RESOURCE_HANDLE); - mHotseatSpecsTwoPanelId = a.getResourceId( - R.styleable.GridDisplayOption_hotseatSpecsTwoPanelId, - mHotseatSpecsId); - mWorkspaceCellSpecsId = a.getResourceId( - R.styleable.GridDisplayOption_workspaceCellSpecsId, - INVALID_RESOURCE_HANDLE); - mWorkspaceCellSpecsTwoPanelId = a.getResourceId( - R.styleable.GridDisplayOption_workspaceCellSpecsTwoPanelId, - mWorkspaceCellSpecsId); - mAllAppsCellSpecsId = a.getResourceId( - R.styleable.GridDisplayOption_allAppsCellSpecsId, - INVALID_RESOURCE_HANDLE); - mAllAppsCellSpecsTwoPanelId = a.getResourceId( - R.styleable.GridDisplayOption_allAppsCellSpecsTwoPanelId, - mAllAppsCellSpecsId); - mNumAllAppsRowsForCellHeightCalculation = a.getInt( - R.styleable.GridDisplayOption_numAllAppsRowsForCellHeightCalculation, - numRows); - } else { - mWorkspaceSpecsId = INVALID_RESOURCE_HANDLE; - mWorkspaceSpecsTwoPanelId = INVALID_RESOURCE_HANDLE; - mAllAppsSpecsId = INVALID_RESOURCE_HANDLE; - mAllAppsSpecsTwoPanelId = INVALID_RESOURCE_HANDLE; - mFolderSpecsId = INVALID_RESOURCE_HANDLE; - mFolderSpecsTwoPanelId = INVALID_RESOURCE_HANDLE; - mHotseatSpecsId = INVALID_RESOURCE_HANDLE; - mHotseatSpecsTwoPanelId = INVALID_RESOURCE_HANDLE; - mWorkspaceCellSpecsId = INVALID_RESOURCE_HANDLE; - mWorkspaceCellSpecsTwoPanelId = INVALID_RESOURCE_HANDLE; - mAllAppsCellSpecsId = INVALID_RESOURCE_HANDLE; - mAllAppsCellSpecsTwoPanelId = INVALID_RESOURCE_HANDLE; - mNumAllAppsRowsForCellHeightCalculation = numRows; - } - - mIsFixedLandscape = a.getBoolean(R.styleable.GridDisplayOption_isFixedLandscape, false); - gridType = a.getInt(R.styleable.GridDisplayOption_gridType, GRID_TYPE_ANY); - - int inlineForRotation = a.getInt(R.styleable.GridDisplayOption_inlineQsb, - DONT_INLINE_QSB); - inlineQsb[INDEX_DEFAULT] = - (inlineForRotation & INLINE_QSB_FOR_PORTRAIT) == INLINE_QSB_FOR_PORTRAIT; - inlineQsb[INDEX_LANDSCAPE] = - (inlineForRotation & INLINE_QSB_FOR_LANDSCAPE) == INLINE_QSB_FOR_LANDSCAPE; - inlineQsb[INDEX_TWO_PANEL_PORTRAIT] = - (inlineForRotation & INLINE_QSB_FOR_TWO_PANEL_PORTRAIT) - == INLINE_QSB_FOR_TWO_PANEL_PORTRAIT; - inlineQsb[INDEX_TWO_PANEL_LANDSCAPE] = - (inlineForRotation & INLINE_QSB_FOR_TWO_PANEL_LANDSCAPE) - == INLINE_QSB_FOR_TWO_PANEL_LANDSCAPE; - - a.recycle(); - } - - public boolean isEnabled(@DeviceType int deviceType) { - switch (deviceType) { - case TYPE_PHONE: - return (deviceCategory & DEVICE_CATEGORY_PHONE) == DEVICE_CATEGORY_PHONE; - case TYPE_TABLET: - return (deviceCategory & DEVICE_CATEGORY_TABLET) == DEVICE_CATEGORY_TABLET; - case TYPE_MULTI_DISPLAY: - return (deviceCategory & DEVICE_CATEGORY_MULTI_DISPLAY) - == DEVICE_CATEGORY_MULTI_DISPLAY; - default: - return false; - } + return filteredProfiles; } /** - * Returns true if the grid option should be used given the flags that are toggled on/off. + * Returns the GridOption associated to the given file name or null if the fileName is not + * supported. + * Ej, launcher.db -> "normal grid", launcher_4_by_4.db -> "practical grid" */ - public boolean filterByFlag(int deviceType, boolean isFixedLandscape) { - if (deviceType == TYPE_TABLET) { - return Flags.oneGridRotationHandling() == mIsDualGrid; - } - - // Here we return true if fixed landscape mode should be on. - if (mIsFixedLandscape || isFixedLandscape) { - return mIsFixedLandscape && isFixedLandscape && Flags.oneGridSpecs(); - } - - // If the grid type is one grid we return true when the flag is on, if the grid type - // is non-one grid we return true when the flag is off. Otherwise, we return true. - if (gridType == GRID_TYPE_ONE_GRID) { - return Flags.oneGridSpecs(); - } else if (gridType == GRID_TYPE_NON_ONE_GRID) { - return !Flags.oneGridSpecs(); - } - - return true; - } - } - - public static final class GridSize { - final int mNumRows; - final int mNumColumns; - final float mMinDeviceWidthPx; - final float mMinDeviceHeightPx; - final String mDbFile; - final int mDefaultLayoutId; - final int mDemoModeLayoutId; - - - GridSize(Context context, AttributeSet attrs) { - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.GridSize); - - mNumRows = (int) a.getFloat(R.styleable.GridSize_numGridRows, 0); - mNumColumns = (int) a.getFloat(R.styleable.GridSize_numGridColumns, 0); - mMinDeviceWidthPx = a.getFloat(R.styleable.GridSize_minDeviceWidthPx, 0); - mMinDeviceHeightPx = a.getFloat(R.styleable.GridSize_minDeviceHeightPx, 0); - mDbFile = a.getString(R.styleable.GridSize_dbFile); - mDefaultLayoutId = a.getResourceId( - R.styleable.GridSize_defaultLayoutId, 0); - mDemoModeLayoutId = a.getResourceId( - R.styleable.GridSize_demoModeLayoutId, mDefaultLayoutId); - - a.recycle(); - } - } - - @VisibleForTesting - static final class DisplayOption { - public final GridOption grid; - - private final float minWidthDps; - private final float minHeightDps; - private final boolean canBeDefault; - - private final PointF[] minCellSize = new PointF[COUNT_SIZES]; - - private final PointF[] borderSpaces = new PointF[COUNT_SIZES]; - private final float[] horizontalMargin = new float[COUNT_SIZES]; - private final float[] hotseatBarBottomSpace = new float[COUNT_SIZES]; - private final float[] hotseatQsbSpace = new float[COUNT_SIZES]; - - private final float[] iconSizes = new float[COUNT_SIZES]; - private final float[] textSizes = new float[COUNT_SIZES]; - - private final PointF[] allAppsCellSize = new PointF[COUNT_SIZES]; - private final float[] allAppsIconSizes = new float[COUNT_SIZES]; - private final float[] allAppsIconTextSizes = new float[COUNT_SIZES]; - private final PointF[] allAppsBorderSpaces = new PointF[COUNT_SIZES]; - - private final float[] transientTaskbarIconSize = new float[COUNT_SIZES]; - - private final boolean[] startAlignTaskbar = new boolean[COUNT_SIZES]; - - DisplayOption(GridOption grid, Context context, AttributeSet attrs) { - this.grid = grid; - - Resources res = context.getResources(); - - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ProfileDisplayOption); - - minWidthDps = a.getFloat(R.styleable.ProfileDisplayOption_minWidthDps, 0); - minHeightDps = a.getFloat(R.styleable.ProfileDisplayOption_minHeightDps, 0); - - canBeDefault = a.getBoolean(R.styleable.ProfileDisplayOption_canBeDefault, false); - - float x; - float y; - - x = a.getFloat(R.styleable.ProfileDisplayOption_minCellWidth, 0); - y = a.getFloat(R.styleable.ProfileDisplayOption_minCellHeight, 0); - minCellSize[INDEX_DEFAULT] = new PointF(x, y); - - x = a.getFloat(R.styleable.ProfileDisplayOption_minCellWidthLandscape, - minCellSize[INDEX_DEFAULT].x); - y = a.getFloat(R.styleable.ProfileDisplayOption_minCellHeightLandscape, - minCellSize[INDEX_DEFAULT].y); - minCellSize[INDEX_LANDSCAPE] = new PointF(x, y); - - x = a.getFloat(R.styleable.ProfileDisplayOption_minCellWidthTwoPanelPortrait, - minCellSize[INDEX_DEFAULT].x); - y = a.getFloat(R.styleable.ProfileDisplayOption_minCellHeightTwoPanelPortrait, - minCellSize[INDEX_DEFAULT].y); - minCellSize[INDEX_TWO_PANEL_PORTRAIT] = new PointF(x, y); - - x = a.getFloat(R.styleable.ProfileDisplayOption_minCellWidthTwoPanelLandscape, - minCellSize[INDEX_DEFAULT].x); - y = a.getFloat(R.styleable.ProfileDisplayOption_minCellHeightTwoPanelLandscape, - minCellSize[INDEX_DEFAULT].y); - minCellSize[INDEX_TWO_PANEL_LANDSCAPE] = new PointF(x, y); - - float borderSpace = a.getFloat(R.styleable.ProfileDisplayOption_borderSpace, 0); - float borderSpaceLandscape = a.getFloat( - R.styleable.ProfileDisplayOption_borderSpaceLandscape, borderSpace); - float borderSpaceTwoPanelPortrait = a.getFloat( - R.styleable.ProfileDisplayOption_borderSpaceTwoPanelPortrait, borderSpace); - float borderSpaceTwoPanelLandscape = a.getFloat( - R.styleable.ProfileDisplayOption_borderSpaceTwoPanelLandscape, borderSpace); - - x = a.getFloat(R.styleable.ProfileDisplayOption_borderSpaceHorizontal, borderSpace); - y = a.getFloat(R.styleable.ProfileDisplayOption_borderSpaceVertical, borderSpace); - borderSpaces[INDEX_DEFAULT] = new PointF(x, y); - - x = a.getFloat(R.styleable.ProfileDisplayOption_borderSpaceLandscapeHorizontal, - borderSpaceLandscape); - y = a.getFloat(R.styleable.ProfileDisplayOption_borderSpaceLandscapeVertical, - borderSpaceLandscape); - borderSpaces[INDEX_LANDSCAPE] = new PointF(x, y); - - x = a.getFloat( - R.styleable.ProfileDisplayOption_borderSpaceTwoPanelPortraitHorizontal, - borderSpaceTwoPanelPortrait); - y = a.getFloat( - R.styleable.ProfileDisplayOption_borderSpaceTwoPanelPortraitVertical, - borderSpaceTwoPanelPortrait); - borderSpaces[INDEX_TWO_PANEL_PORTRAIT] = new PointF(x, y); - - x = a.getFloat( - R.styleable.ProfileDisplayOption_borderSpaceTwoPanelLandscapeHorizontal, - borderSpaceTwoPanelLandscape); - y = a.getFloat( - R.styleable.ProfileDisplayOption_borderSpaceTwoPanelLandscapeVertical, - borderSpaceTwoPanelLandscape); - borderSpaces[INDEX_TWO_PANEL_LANDSCAPE] = new PointF(x, y); - - x = a.getFloat(R.styleable.ProfileDisplayOption_allAppsCellWidth, - minCellSize[INDEX_DEFAULT].x); - y = a.getFloat(R.styleable.ProfileDisplayOption_allAppsCellHeight, - minCellSize[INDEX_DEFAULT].y); - allAppsCellSize[INDEX_DEFAULT] = new PointF(x, y); - - x = a.getFloat(R.styleable.ProfileDisplayOption_allAppsCellWidthLandscape, - allAppsCellSize[INDEX_DEFAULT].x); - y = a.getFloat(R.styleable.ProfileDisplayOption_allAppsCellHeightLandscape, - allAppsCellSize[INDEX_DEFAULT].y); - allAppsCellSize[INDEX_LANDSCAPE] = new PointF(x, y); - - x = a.getFloat(R.styleable.ProfileDisplayOption_allAppsCellWidthTwoPanelPortrait, - allAppsCellSize[INDEX_DEFAULT].x); - y = a.getFloat(R.styleable.ProfileDisplayOption_allAppsCellHeightTwoPanelPortrait, - allAppsCellSize[INDEX_DEFAULT].y); - allAppsCellSize[INDEX_TWO_PANEL_PORTRAIT] = new PointF(x, y); - - x = a.getFloat(R.styleable.ProfileDisplayOption_allAppsCellWidthTwoPanelLandscape, - allAppsCellSize[INDEX_DEFAULT].x); - y = a.getFloat(R.styleable.ProfileDisplayOption_allAppsCellHeightTwoPanelLandscape, - allAppsCellSize[INDEX_DEFAULT].y); - allAppsCellSize[INDEX_TWO_PANEL_LANDSCAPE] = new PointF(x, y); - - float allAppsBorderSpace = a.getFloat( - R.styleable.ProfileDisplayOption_allAppsBorderSpace, borderSpace); - float allAppsBorderSpaceLandscape = a.getFloat( - R.styleable.ProfileDisplayOption_allAppsBorderSpaceLandscape, - allAppsBorderSpace); - float allAppsBorderSpaceTwoPanelPortrait = a.getFloat( - R.styleable.ProfileDisplayOption_allAppsBorderSpaceTwoPanelPortrait, - allAppsBorderSpace); - float allAppsBorderSpaceTwoPanelLandscape = a.getFloat( - R.styleable.ProfileDisplayOption_allAppsBorderSpaceTwoPanelLandscape, - allAppsBorderSpace); - - x = a.getFloat(R.styleable.ProfileDisplayOption_allAppsBorderSpaceHorizontal, - allAppsBorderSpace); - y = a.getFloat(R.styleable.ProfileDisplayOption_allAppsBorderSpaceVertical, - allAppsBorderSpace); - allAppsBorderSpaces[INDEX_DEFAULT] = new PointF(x, y); - - x = a.getFloat(R.styleable.ProfileDisplayOption_allAppsBorderSpaceLandscapeHorizontal, - allAppsBorderSpaceLandscape); - y = a.getFloat(R.styleable.ProfileDisplayOption_allAppsBorderSpaceLandscapeVertical, - allAppsBorderSpaceLandscape); - allAppsBorderSpaces[INDEX_LANDSCAPE] = new PointF(x, y); - - x = a.getFloat( - R.styleable.ProfileDisplayOption_allAppsBorderSpaceTwoPanelPortraitHorizontal, - allAppsBorderSpaceTwoPanelPortrait); - y = a.getFloat( - R.styleable.ProfileDisplayOption_allAppsBorderSpaceTwoPanelPortraitVertical, - allAppsBorderSpaceTwoPanelPortrait); - allAppsBorderSpaces[INDEX_TWO_PANEL_PORTRAIT] = new PointF(x, y); - - x = a.getFloat( - R.styleable.ProfileDisplayOption_allAppsBorderSpaceTwoPanelLandscapeHorizontal, - allAppsBorderSpaceTwoPanelLandscape); - y = a.getFloat( - R.styleable.ProfileDisplayOption_allAppsBorderSpaceTwoPanelLandscapeVertical, - allAppsBorderSpaceTwoPanelLandscape); - allAppsBorderSpaces[INDEX_TWO_PANEL_LANDSCAPE] = new PointF(x, y); - - iconSizes[INDEX_DEFAULT] = - a.getFloat(R.styleable.ProfileDisplayOption_iconImageSize, 0); - iconSizes[INDEX_LANDSCAPE] = - a.getFloat(R.styleable.ProfileDisplayOption_iconSizeLandscape, - iconSizes[INDEX_DEFAULT]); - iconSizes[INDEX_TWO_PANEL_PORTRAIT] = - a.getFloat(R.styleable.ProfileDisplayOption_iconSizeTwoPanelPortrait, - iconSizes[INDEX_DEFAULT]); - iconSizes[INDEX_TWO_PANEL_LANDSCAPE] = - a.getFloat(R.styleable.ProfileDisplayOption_iconSizeTwoPanelLandscape, - iconSizes[INDEX_DEFAULT]); - - allAppsIconSizes[INDEX_DEFAULT] = a.getFloat( - R.styleable.ProfileDisplayOption_allAppsIconSize, iconSizes[INDEX_DEFAULT]); - allAppsIconSizes[INDEX_LANDSCAPE] = a.getFloat( - R.styleable.ProfileDisplayOption_allAppsIconSizeLandscape, - allAppsIconSizes[INDEX_DEFAULT]); - allAppsIconSizes[INDEX_TWO_PANEL_PORTRAIT] = a.getFloat( - R.styleable.ProfileDisplayOption_allAppsIconSizeTwoPanelPortrait, - allAppsIconSizes[INDEX_DEFAULT]); - allAppsIconSizes[INDEX_TWO_PANEL_LANDSCAPE] = a.getFloat( - R.styleable.ProfileDisplayOption_allAppsIconSizeTwoPanelLandscape, - allAppsIconSizes[INDEX_DEFAULT]); - - textSizes[INDEX_DEFAULT] = - a.getFloat(R.styleable.ProfileDisplayOption_iconTextSize, 0); - textSizes[INDEX_LANDSCAPE] = - a.getFloat(R.styleable.ProfileDisplayOption_iconTextSizeLandscape, - textSizes[INDEX_DEFAULT]); - textSizes[INDEX_TWO_PANEL_PORTRAIT] = - a.getFloat(R.styleable.ProfileDisplayOption_iconTextSizeTwoPanelPortrait, - textSizes[INDEX_DEFAULT]); - textSizes[INDEX_TWO_PANEL_LANDSCAPE] = - a.getFloat(R.styleable.ProfileDisplayOption_iconTextSizeTwoPanelLandscape, - textSizes[INDEX_DEFAULT]); - - allAppsIconTextSizes[INDEX_DEFAULT] = a.getFloat( - R.styleable.ProfileDisplayOption_allAppsIconTextSize, textSizes[INDEX_DEFAULT]); - allAppsIconTextSizes[INDEX_LANDSCAPE] = allAppsIconTextSizes[INDEX_DEFAULT]; - allAppsIconTextSizes[INDEX_TWO_PANEL_PORTRAIT] = a.getFloat( - R.styleable.ProfileDisplayOption_allAppsIconTextSizeTwoPanelPortrait, - allAppsIconTextSizes[INDEX_DEFAULT]); - allAppsIconTextSizes[INDEX_TWO_PANEL_LANDSCAPE] = a.getFloat( - R.styleable.ProfileDisplayOption_allAppsIconTextSizeTwoPanelLandscape, - allAppsIconTextSizes[INDEX_DEFAULT]); - - horizontalMargin[INDEX_DEFAULT] = a.getFloat( - R.styleable.ProfileDisplayOption_horizontalMargin, 0); - horizontalMargin[INDEX_LANDSCAPE] = a.getFloat( - R.styleable.ProfileDisplayOption_horizontalMarginLandscape, - horizontalMargin[INDEX_DEFAULT]); - horizontalMargin[INDEX_TWO_PANEL_LANDSCAPE] = a.getFloat( - R.styleable.ProfileDisplayOption_horizontalMarginTwoPanelLandscape, - horizontalMargin[INDEX_DEFAULT]); - horizontalMargin[INDEX_TWO_PANEL_PORTRAIT] = a.getFloat( - R.styleable.ProfileDisplayOption_horizontalMarginTwoPanelPortrait, - horizontalMargin[INDEX_DEFAULT]); - - hotseatBarBottomSpace[INDEX_DEFAULT] = a.getFloat( - R.styleable.ProfileDisplayOption_hotseatBarBottomSpace, - ResourcesCompat.getFloat(res, R.dimen.hotseat_bar_bottom_space_default)); - hotseatBarBottomSpace[INDEX_LANDSCAPE] = a.getFloat( - R.styleable.ProfileDisplayOption_hotseatBarBottomSpaceLandscape, - hotseatBarBottomSpace[INDEX_DEFAULT]); - hotseatBarBottomSpace[INDEX_TWO_PANEL_LANDSCAPE] = a.getFloat( - R.styleable.ProfileDisplayOption_hotseatBarBottomSpaceTwoPanelLandscape, - hotseatBarBottomSpace[INDEX_DEFAULT]); - hotseatBarBottomSpace[INDEX_TWO_PANEL_PORTRAIT] = a.getFloat( - R.styleable.ProfileDisplayOption_hotseatBarBottomSpaceTwoPanelPortrait, - hotseatBarBottomSpace[INDEX_DEFAULT]); - - hotseatQsbSpace[INDEX_DEFAULT] = a.getFloat( - R.styleable.ProfileDisplayOption_hotseatQsbSpace, - ResourcesCompat.getFloat(res, R.dimen.hotseat_qsb_space_default)); - hotseatQsbSpace[INDEX_LANDSCAPE] = a.getFloat( - R.styleable.ProfileDisplayOption_hotseatQsbSpaceLandscape, - hotseatQsbSpace[INDEX_DEFAULT]); - hotseatQsbSpace[INDEX_TWO_PANEL_LANDSCAPE] = a.getFloat( - R.styleable.ProfileDisplayOption_hotseatQsbSpaceTwoPanelLandscape, - hotseatQsbSpace[INDEX_DEFAULT]); - hotseatQsbSpace[INDEX_TWO_PANEL_PORTRAIT] = a.getFloat( - R.styleable.ProfileDisplayOption_hotseatQsbSpaceTwoPanelPortrait, - hotseatQsbSpace[INDEX_DEFAULT]); - - transientTaskbarIconSize[INDEX_DEFAULT] = a.getFloat( - R.styleable.ProfileDisplayOption_transientTaskbarIconSize, - ResourcesCompat.getFloat(res, R.dimen.taskbar_icon_size)); - transientTaskbarIconSize[INDEX_LANDSCAPE] = a.getFloat( - R.styleable.ProfileDisplayOption_transientTaskbarIconSizeLandscape, - transientTaskbarIconSize[INDEX_DEFAULT]); - transientTaskbarIconSize[INDEX_TWO_PANEL_LANDSCAPE] = a.getFloat( - R.styleable.ProfileDisplayOption_transientTaskbarIconSizeTwoPanelLandscape, - transientTaskbarIconSize[INDEX_DEFAULT]); - transientTaskbarIconSize[INDEX_TWO_PANEL_PORTRAIT] = a.getFloat( - R.styleable.ProfileDisplayOption_transientTaskbarIconSizeTwoPanelPortrait, - transientTaskbarIconSize[INDEX_DEFAULT]); - - startAlignTaskbar[INDEX_DEFAULT] = a.getBoolean( - R.styleable.ProfileDisplayOption_startAlignTaskbar, false); - startAlignTaskbar[INDEX_LANDSCAPE] = a.getBoolean( - R.styleable.ProfileDisplayOption_startAlignTaskbarLandscape, - startAlignTaskbar[INDEX_DEFAULT]); - startAlignTaskbar[INDEX_TWO_PANEL_LANDSCAPE] = a.getBoolean( - R.styleable.ProfileDisplayOption_startAlignTaskbarTwoPanelLandscape, - startAlignTaskbar[INDEX_LANDSCAPE]); - startAlignTaskbar[INDEX_TWO_PANEL_PORTRAIT] = a.getBoolean( - R.styleable.ProfileDisplayOption_startAlignTaskbarTwoPanelPortrait, - startAlignTaskbar[INDEX_DEFAULT]); - - a.recycle(); + public GridOption getGridOptionFromFileName(Context context, String fileName) { + return parseAllGridOptions(context).stream() + .filter(gridOption -> Objects.equals(gridOption.dbFile, fileName)) + .findFirst() + .orElse(null); } - DisplayOption() { - this(null); + /** + * Returns the name of the given size on the current device or empty string if the size is not + * supported. Ej. 4x4 -> normal, 5x4 -> practical, etc. + * (Note: the name of the grid can be different for the same grid size depending of + * the values of the InvariantDeviceProfile) + * + */ + public String getGridNameFromSize(Context context, Point size) { + return parseAllGridOptions(context).stream() + .filter(gridOption -> gridOption.numColumns == size.x + && gridOption.numRows == size.y) + .map(gridOption -> gridOption.name) + .findFirst() + .orElse(""); } - DisplayOption(GridOption grid) { - this.grid = grid; - minWidthDps = 0; - minHeightDps = 0; - canBeDefault = false; - for (int i = 0; i < COUNT_SIZES; i++) { - iconSizes[i] = 0; - textSizes[i] = 0; - borderSpaces[i] = new PointF(); - minCellSize[i] = new PointF(); - allAppsCellSize[i] = new PointF(); - allAppsIconSizes[i] = 0; - allAppsIconTextSizes[i] = 0; - allAppsBorderSpaces[i] = new PointF(); - transientTaskbarIconSize[i] = 0; - startAlignTaskbar[i] = false; - } + /** + * Returns the grid option for the given gridName on the current device (Note: the gridOption + * be different for the same gridName depending on the values of the InvariantDeviceProfile). + */ + public GridOption getGridOptionFromName(Context context, String gridName) { + return parseAllGridOptions(context).stream() + .filter(gridOption -> Objects.equals(gridOption.name, gridName)) + .findFirst() + .orElse(null); } - private DisplayOption multiply(float w) { - for (int i = 0; i < COUNT_SIZES; i++) { - iconSizes[i] *= w; - textSizes[i] *= w; - borderSpaces[i].x *= w; - borderSpaces[i].y *= w; - minCellSize[i].x *= w; - minCellSize[i].y *= w; - horizontalMargin[i] *= w; - hotseatBarBottomSpace[i] *= w; - hotseatQsbSpace[i] *= w; - allAppsCellSize[i].x *= w; - allAppsCellSize[i].y *= w; - allAppsIconSizes[i] *= w; - allAppsIconTextSizes[i] *= w; - allAppsBorderSpaces[i].x *= w; - allAppsBorderSpaces[i].y *= w; - transientTaskbarIconSize[i] *= w; - } - - return this; + /** + * @return all the grid options that can be shown on the device + */ + public static List parseAllGridOptions(Context context) { + return parseAllDefinedGridOptions(context) + .stream() + .filter(go -> go.isEnabled(deviceType)) + .collect(Collectors.toList()); } - private DisplayOption add(DisplayOption p) { - for (int i = 0; i < COUNT_SIZES; i++) { - iconSizes[i] += p.iconSizes[i]; - textSizes[i] += p.textSizes[i]; - borderSpaces[i].x += p.borderSpaces[i].x; - borderSpaces[i].y += p.borderSpaces[i].y; - minCellSize[i].x += p.minCellSize[i].x; - minCellSize[i].y += p.minCellSize[i].y; - horizontalMargin[i] += p.horizontalMargin[i]; - hotseatBarBottomSpace[i] += p.hotseatBarBottomSpace[i]; - hotseatQsbSpace[i] += p.hotseatQsbSpace[i]; - allAppsCellSize[i].x += p.allAppsCellSize[i].x; - allAppsCellSize[i].y += p.allAppsCellSize[i].y; - allAppsIconSizes[i] += p.allAppsIconSizes[i]; - allAppsIconTextSizes[i] += p.allAppsIconTextSizes[i]; - allAppsBorderSpaces[i].x += p.allAppsBorderSpaces[i].x; - allAppsBorderSpaces[i].y += p.allAppsBorderSpaces[i].y; - transientTaskbarIconSize[i] += p.transientTaskbarIconSize[i]; - startAlignTaskbar[i] |= p.startAlignTaskbar[i]; - } + /** + * @return all the grid options that can be shown on the device + */ + public static List parseAllDefinedGridOptions(Context context) { + List result = new ArrayList<>(); - return this; + try (XmlResourceParser parser = context.getResources().getXml(R.xml.device_profiles)) { + final int depth = parser.getDepth(); + int type; + while (((type = parser.next()) != XmlPullParser.END_TAG + || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { + if ((type == XmlPullParser.START_TAG) + && GridOption.TAG_NAME.equals(parser.getName())) { + result.add(new GridOption(context, Xml.asAttributeSet(parser))); + } + } + } catch (IOException | XmlPullParserException e) { + Log.e(TAG, "Error parsing device profile", e); + return Collections.emptyList(); + } + return result; + } + + private int getLauncherIconDensity(int requiredSize) { + // Densities typically defined by an app. + int[] densityBuckets = new int[]{ + DisplayMetrics.DENSITY_LOW, + DisplayMetrics.DENSITY_MEDIUM, + DisplayMetrics.DENSITY_TV, + DisplayMetrics.DENSITY_HIGH, + DisplayMetrics.DENSITY_XHIGH, + DisplayMetrics.DENSITY_XXHIGH, + DisplayMetrics.DENSITY_XXXHIGH + }; + + int density = DisplayMetrics.DENSITY_XXXHIGH; + for (int i = densityBuckets.length - 1; i >= 0; i--) { + float expectedSize = ICON_SIZE_DEFINED_IN_APP_DP * densityBuckets[i] + / DisplayMetrics.DENSITY_DEFAULT; + if (expectedSize >= requiredSize) { + density = densityBuckets[i]; + } + } + + return density; + } + + /** + * Apply any Partner customization grid overrides. + * + * Currently we support: all apps row / column count. + */ + private void applyPartnerDeviceProfileOverrides(Context context, DisplayMetrics dm) { + Partner p = Partner.get(context.getPackageManager()); + if (p == null) { + return; + } + try { + int numRows = p.getIntValue(RES_GRID_NUM_ROWS, -1); + int numColumns = p.getIntValue(RES_GRID_NUM_COLUMNS, -1); + float iconSizePx = p.getDimenValue(RES_GRID_ICON_SIZE_DP, -1); + + if (numRows > 0 && numColumns > 0) { + this.numRows = numRows; + this.numColumns = numColumns; + } + if (iconSizePx > 0) { + this.iconSize[InvariantDeviceProfile.INDEX_DEFAULT] = + Utilities.dpiFromPx(iconSizePx, dm.densityDpi); + } + } catch (Resources.NotFoundException ex) { + Log.e(TAG, "Invalid Partner grid resource!", ex); + } + } + + private static float dist(float x0, float y0, float x1, float y1) { + return (float) Math.hypot(x1 - x0, y1 - y0); + } + + private static DisplayOption invDistWeightedInterpolate( + Info displayInfo, ArrayList points, @DeviceType int deviceType) { + int minWidthPx = Integer.MAX_VALUE; + int minHeightPx = Integer.MAX_VALUE; + for (WindowBounds bounds : displayInfo.supportedBounds) { + boolean isTablet = displayInfo.isTablet(bounds); + if (isTablet && deviceType == TYPE_MULTI_DISPLAY) { + // For split displays, take half width per page + minWidthPx = Math.min(minWidthPx, bounds.availableSize.x / 2); + minHeightPx = Math.min(minHeightPx, bounds.availableSize.y); + + } else if (!isTablet && bounds.isLandscape()) { + // We will use transposed layout in this case + minWidthPx = Math.min(minWidthPx, bounds.availableSize.y); + minHeightPx = Math.min(minHeightPx, bounds.availableSize.x); + } else { + minWidthPx = Math.min(minWidthPx, bounds.availableSize.x); + minHeightPx = Math.min(minHeightPx, bounds.availableSize.y); + } + } + + float width = dpiFromPx(minWidthPx, displayInfo.getDensityDpi()); + float height = dpiFromPx(minHeightPx, displayInfo.getDensityDpi()); + + // Sort the profiles based on the closeness to the device size + Collections.sort(points, (a, b) -> + Float.compare(dist(width, height, a.minWidthDps, a.minHeightDps), + dist(width, height, b.minWidthDps, b.minHeightDps))); + + DisplayOption closestPoint = points.get(0); + GridOption closestOption = closestPoint.grid; + float weights = 0; + + if (dist(width, height, closestPoint.minWidthDps, closestPoint.minHeightDps) == 0) { + return closestPoint; + } + + DisplayOption out = new DisplayOption(closestOption); + for (int i = 0; i < points.size() && i < KNEARESTNEIGHBOR; ++i) { + DisplayOption p = points.get(i); + float w = weight(width, height, p.minWidthDps, p.minHeightDps, WEIGHT_POWER); + weights += w; + out.add(new DisplayOption().add(p).multiply(w)); + } + out.multiply(1.0f / weights); + + // Since the bitmaps are persisted, ensure that all bitmap sizes are not larger than + // predefined size to avoid cache invalidation + for (int i = INDEX_DEFAULT; i < COUNT_SIZES; i++) { + out.iconSizes[i] = Math.min(out.iconSizes[i], closestPoint.iconSizes[i]); + } + + return out; + } + + public DeviceProfile getDeviceProfile(Context context) { + WindowManagerProxy windowManagerProxy = WindowManagerProxy.INSTANCE.get(context); + Rect bounds = windowManagerProxy.getCurrentBounds(context); + int rotation = windowManagerProxy.getRotation(context); + + return getBestMatch(bounds.width(), bounds.height(), rotation); + } + + /** + * Returns the device profile matching the provided screen configuration + */ + public DeviceProfile getBestMatch(float screenWidth, float screenHeight, int rotation) { + DeviceProfile bestMatch = supportedProfiles.get(0); + float minDiff = Float.MAX_VALUE; + + for (DeviceProfile profile : supportedProfiles) { + float diff = Math.abs(profile.widthPx - screenWidth) + + Math.abs(profile.heightPx - screenHeight); + if (diff < minDiff) { + minDiff = diff; + bestMatch = profile; + } else if (diff == minDiff && profile.rotationHint == rotation) { + bestMatch = profile; + } + } + return bestMatch; + } + + private static float weight(float x0, float y0, float x1, float y1, float pow) { + float d = dist(x0, y0, x1, y1); + if (Float.compare(d, 0f) == 0) { + return Float.POSITIVE_INFINITY; + } + return (float) (WEIGHT_EFFICIENT / Math.pow(d, pow)); + } + + /** + * As a ratio of screen height, the total distance we want the parallax effect to span + * horizontally + */ + private static float wallpaperTravelToScreenWidthRatio(int width, int height) { + float aspectRatio = width / (float) height; + + // At an aspect ratio of 16/10, the wallpaper parallax effect should span 1.5 * screen width + // At an aspect ratio of 10/16, the wallpaper parallax effect should span 1.2 * screen width + // We will use these two data points to extrapolate how much the wallpaper parallax effect + // to span (ie travel) at any aspect ratio: + + final float ASPECT_RATIO_LANDSCAPE = 16 / 10f; + final float ASPECT_RATIO_PORTRAIT = 10 / 16f; + final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE = 1.5f; + final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT = 1.2f; + + // To find out the desired width at different aspect ratios, we use the following two + // formulas, where the coefficient on x is the aspect ratio (width/height): + // (16/10)x + y = 1.5 + // (10/16)x + y = 1.2 + // We solve for x and y and end up with a final formula: + final float x = + (WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE + - WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT) / + (ASPECT_RATIO_LANDSCAPE - ASPECT_RATIO_PORTRAIT); + final float y = WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT - x * ASPECT_RATIO_PORTRAIT; + return x * aspectRatio + y; + } + + public interface OnIDPChangeListener { + + /** + * Called when the device provide changes + */ + void onIdpChanged(boolean modelPropertiesChanged); + } + + + public static final class GridOption { + + public static final String TAG_NAME = "grid-option"; + + private static final int DEVICE_CATEGORY_PHONE = 1 << 0; + private static final int DEVICE_CATEGORY_TABLET = 1 << 1; + private static final int DEVICE_CATEGORY_MULTI_DISPLAY = 1 << 2; + private static final int DEVICE_CATEGORY_ALL = + DEVICE_CATEGORY_PHONE | DEVICE_CATEGORY_TABLET | DEVICE_CATEGORY_MULTI_DISPLAY; + + private static final int INLINE_QSB_FOR_PORTRAIT = 1 << 0; + private static final int INLINE_QSB_FOR_LANDSCAPE = 1 << 1; + private static final int INLINE_QSB_FOR_TWO_PANEL_PORTRAIT = 1 << 2; + private static final int INLINE_QSB_FOR_TWO_PANEL_LANDSCAPE = 1 << 3; + private static final int DONT_INLINE_QSB = 0; + + public final String name; + public final int numRows; + public final int numColumns; + public final int numSearchContainerColumns; + public final int deviceCategory; + + public final int[] numFolderRows = new int[COUNT_SIZES]; + public final int[] numFolderColumns = new int[COUNT_SIZES]; + private final @StyleRes int folderStyle; + private final @StyleRes int cellStyle; + + private final @StyleRes int allAppsStyle; + public final int numAllAppsColumns; + private final int mNumAllAppsRowsForCellHeightCalculation; + private final int numDatabaseAllAppsColumns; + public final int numHotseatIcons; + private final int numDatabaseHotseatIcons; + + private final boolean[] inlineQsb = new boolean[COUNT_SIZES]; + + private @DimenRes int inlineNavButtonsEndSpacing; + private final String dbFile; + + private final int defaultLayoutId; + private final int demoModeLayoutId; + + private final boolean isScalable; + private final int devicePaddingId; + private final int mWorkspaceSpecsId; + private final int mWorkspaceSpecsTwoPanelId; + private final int mAllAppsSpecsId; + private final int mAllAppsSpecsTwoPanelId; + private final int mFolderSpecsId; + private final int mFolderSpecsTwoPanelId; + private final int mHotseatSpecsId; + private final int mHotseatSpecsTwoPanelId; + private final int mWorkspaceCellSpecsId; + private final int mWorkspaceCellSpecsTwoPanelId; + private final int mAllAppsCellSpecsId; + private final int mAllAppsCellSpecsTwoPanelId; + + public GridOption(Context context, AttributeSet attrs) { + TypedArray a = context.obtainStyledAttributes( + attrs, R.styleable.GridDisplayOption); + name = a.getString(R.styleable.GridDisplayOption_name); + numRows = a.getInt(R.styleable.GridDisplayOption_numRows, 0); + numColumns = a.getInt(R.styleable.GridDisplayOption_numColumns, 0); + numSearchContainerColumns = a.getInt( + R.styleable.GridDisplayOption_numSearchContainerColumns, numColumns); + + dbFile = a.getString(R.styleable.GridDisplayOption_dbFile); + defaultLayoutId = a.getResourceId( + R.styleable.GridDisplayOption_defaultLayoutId, 0); + demoModeLayoutId = a.getResourceId( + R.styleable.GridDisplayOption_demoModeLayoutId, defaultLayoutId); + + allAppsStyle = a.getResourceId(R.styleable.GridDisplayOption_allAppsStyle, + R.style.AllAppsStyleDefault); + numAllAppsColumns = a.getInt( + R.styleable.GridDisplayOption_numAllAppsColumns, numColumns); + numDatabaseAllAppsColumns = a.getInt( + R.styleable.GridDisplayOption_numExtendedAllAppsColumns, 2 * numAllAppsColumns); + + numHotseatIcons = a.getInt( + R.styleable.GridDisplayOption_numHotseatIcons, numColumns); + numDatabaseHotseatIcons = a.getInt( + R.styleable.GridDisplayOption_numExtendedHotseatIcons, 2 * numHotseatIcons); + + inlineNavButtonsEndSpacing = + a.getResourceId(R.styleable.GridDisplayOption_inlineNavButtonsEndSpacing, + R.dimen.taskbar_button_margin_default); + + numFolderRows[INDEX_DEFAULT] = a.getInt( + R.styleable.GridDisplayOption_numFolderRows, numRows); + numFolderColumns[INDEX_DEFAULT] = a.getInt( + R.styleable.GridDisplayOption_numFolderColumns, numColumns); + + if (FeatureFlags.enableResponsiveWorkspace()) { + numFolderRows[INDEX_LANDSCAPE] = a.getInt( + R.styleable.GridDisplayOption_numFolderRowsLandscape, + numFolderRows[INDEX_DEFAULT]); + numFolderColumns[INDEX_LANDSCAPE] = a.getInt( + R.styleable.GridDisplayOption_numFolderColumnsLandscape, + numFolderColumns[INDEX_DEFAULT]); + numFolderRows[INDEX_TWO_PANEL_PORTRAIT] = a.getInt( + R.styleable.GridDisplayOption_numFolderRowsTwoPanelPortrait, + numFolderRows[INDEX_DEFAULT]); + numFolderColumns[INDEX_TWO_PANEL_PORTRAIT] = a.getInt( + R.styleable.GridDisplayOption_numFolderColumnsTwoPanelPortrait, + numFolderColumns[INDEX_DEFAULT]); + numFolderRows[INDEX_TWO_PANEL_LANDSCAPE] = a.getInt( + R.styleable.GridDisplayOption_numFolderRowsTwoPanelLandscape, + numFolderRows[INDEX_DEFAULT]); + numFolderColumns[INDEX_TWO_PANEL_LANDSCAPE] = a.getInt( + R.styleable.GridDisplayOption_numFolderColumnsTwoPanelLandscape, + numFolderColumns[INDEX_DEFAULT]); + } else { + numFolderRows[INDEX_LANDSCAPE] = numFolderRows[INDEX_DEFAULT]; + numFolderColumns[INDEX_LANDSCAPE] = numFolderColumns[INDEX_DEFAULT]; + numFolderRows[INDEX_TWO_PANEL_PORTRAIT] = numFolderRows[INDEX_DEFAULT]; + numFolderColumns[INDEX_TWO_PANEL_PORTRAIT] = numFolderColumns[INDEX_DEFAULT]; + numFolderRows[INDEX_TWO_PANEL_LANDSCAPE] = numFolderRows[INDEX_DEFAULT]; + numFolderColumns[INDEX_TWO_PANEL_LANDSCAPE] = numFolderColumns[INDEX_DEFAULT]; + } + + folderStyle = a.getResourceId(R.styleable.GridDisplayOption_folderStyle, + INVALID_RESOURCE_HANDLE); + + cellStyle = a.getResourceId(R.styleable.GridDisplayOption_cellStyle, + R.style.CellStyleDefault); + + isScalable = a.getBoolean( + R.styleable.GridDisplayOption_isScalable, false); + devicePaddingId = a.getResourceId( + R.styleable.GridDisplayOption_devicePaddingId, INVALID_RESOURCE_HANDLE); + deviceCategory = a.getInt(R.styleable.GridDisplayOption_deviceCategory, + DEVICE_CATEGORY_ALL); + + if (FeatureFlags.enableResponsiveWorkspace()) { + mWorkspaceSpecsId = a.getResourceId( + R.styleable.GridDisplayOption_workspaceSpecsId, INVALID_RESOURCE_HANDLE); + mWorkspaceSpecsTwoPanelId = a.getResourceId( + R.styleable.GridDisplayOption_workspaceSpecsTwoPanelId, + mWorkspaceSpecsId); + mAllAppsSpecsId = a.getResourceId( + R.styleable.GridDisplayOption_allAppsSpecsId, INVALID_RESOURCE_HANDLE); + mAllAppsSpecsTwoPanelId = a.getResourceId( + R.styleable.GridDisplayOption_allAppsSpecsTwoPanelId, + mAllAppsSpecsId); + mFolderSpecsId = a.getResourceId( + R.styleable.GridDisplayOption_folderSpecsId, INVALID_RESOURCE_HANDLE); + mFolderSpecsTwoPanelId = a.getResourceId( + R.styleable.GridDisplayOption_folderSpecsTwoPanelId, + mFolderSpecsId); + mHotseatSpecsId = a.getResourceId( + R.styleable.GridDisplayOption_hotseatSpecsId, INVALID_RESOURCE_HANDLE); + mHotseatSpecsTwoPanelId = a.getResourceId( + R.styleable.GridDisplayOption_hotseatSpecsTwoPanelId, + mHotseatSpecsId); + mWorkspaceCellSpecsId = a.getResourceId( + R.styleable.GridDisplayOption_workspaceCellSpecsId, + INVALID_RESOURCE_HANDLE); + mWorkspaceCellSpecsTwoPanelId = a.getResourceId( + R.styleable.GridDisplayOption_workspaceCellSpecsTwoPanelId, + mWorkspaceCellSpecsId); + mAllAppsCellSpecsId = a.getResourceId( + R.styleable.GridDisplayOption_allAppsCellSpecsId, + INVALID_RESOURCE_HANDLE); + mAllAppsCellSpecsTwoPanelId = a.getResourceId( + R.styleable.GridDisplayOption_allAppsCellSpecsTwoPanelId, + mAllAppsCellSpecsId); + mNumAllAppsRowsForCellHeightCalculation = a.getInt( + R.styleable.GridDisplayOption_numAllAppsRowsForCellHeightCalculation, + numRows); + } else { + mWorkspaceSpecsId = INVALID_RESOURCE_HANDLE; + mWorkspaceSpecsTwoPanelId = INVALID_RESOURCE_HANDLE; + mAllAppsSpecsId = INVALID_RESOURCE_HANDLE; + mAllAppsSpecsTwoPanelId = INVALID_RESOURCE_HANDLE; + mFolderSpecsId = INVALID_RESOURCE_HANDLE; + mFolderSpecsTwoPanelId = INVALID_RESOURCE_HANDLE; + mHotseatSpecsId = INVALID_RESOURCE_HANDLE; + mHotseatSpecsTwoPanelId = INVALID_RESOURCE_HANDLE; + mWorkspaceCellSpecsId = INVALID_RESOURCE_HANDLE; + mWorkspaceCellSpecsTwoPanelId = INVALID_RESOURCE_HANDLE; + mAllAppsCellSpecsId = INVALID_RESOURCE_HANDLE; + mAllAppsCellSpecsTwoPanelId = INVALID_RESOURCE_HANDLE; + mNumAllAppsRowsForCellHeightCalculation = numRows; + } + + int inlineForRotation = a.getInt(R.styleable.GridDisplayOption_inlineQsb, + DONT_INLINE_QSB); + inlineQsb[INDEX_DEFAULT] = + (inlineForRotation & INLINE_QSB_FOR_PORTRAIT) == INLINE_QSB_FOR_PORTRAIT; + inlineQsb[INDEX_LANDSCAPE] = + (inlineForRotation & INLINE_QSB_FOR_LANDSCAPE) == INLINE_QSB_FOR_LANDSCAPE; + inlineQsb[INDEX_TWO_PANEL_PORTRAIT] = + (inlineForRotation & INLINE_QSB_FOR_TWO_PANEL_PORTRAIT) + == INLINE_QSB_FOR_TWO_PANEL_PORTRAIT; + inlineQsb[INDEX_TWO_PANEL_LANDSCAPE] = + (inlineForRotation & INLINE_QSB_FOR_TWO_PANEL_LANDSCAPE) + == INLINE_QSB_FOR_TWO_PANEL_LANDSCAPE; + + a.recycle(); + } + + public boolean isEnabled(@DeviceType int deviceType) { + switch (deviceType) { + case TYPE_PHONE: + return (deviceCategory & DEVICE_CATEGORY_PHONE) == DEVICE_CATEGORY_PHONE; + case TYPE_TABLET: + return (deviceCategory & DEVICE_CATEGORY_TABLET) == DEVICE_CATEGORY_TABLET; + case TYPE_MULTI_DISPLAY: + return (deviceCategory & DEVICE_CATEGORY_MULTI_DISPLAY) + == DEVICE_CATEGORY_MULTI_DISPLAY; + default: + return false; + } + } + } + + @VisibleForTesting + static final class DisplayOption { + public final GridOption grid; + + private final float minWidthDps; + private final float minHeightDps; + private final boolean canBeDefault; + + private final PointF[] minCellSize = new PointF[COUNT_SIZES]; + + private final PointF[] borderSpaces = new PointF[COUNT_SIZES]; + private final float[] horizontalMargin = new float[COUNT_SIZES]; + private final float[] hotseatBarBottomSpace = new float[COUNT_SIZES]; + private final float[] hotseatQsbSpace = new float[COUNT_SIZES]; + + private final float[] iconSizes = new float[COUNT_SIZES]; + private final float[] textSizes = new float[COUNT_SIZES]; + + private final PointF[] allAppsCellSize = new PointF[COUNT_SIZES]; + private final float[] allAppsIconSizes = new float[COUNT_SIZES]; + private final float[] allAppsIconTextSizes = new float[COUNT_SIZES]; + private final PointF[] allAppsBorderSpaces = new PointF[COUNT_SIZES]; + + private final float[] transientTaskbarIconSize = new float[COUNT_SIZES]; + + private final boolean[] startAlignTaskbar = new boolean[COUNT_SIZES]; + + DisplayOption(GridOption grid, Context context, AttributeSet attrs) { + this.grid = grid; + + Resources res = context.getResources(); + + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ProfileDisplayOption); + + minWidthDps = a.getFloat(R.styleable.ProfileDisplayOption_minWidthDps, 0); + minHeightDps = a.getFloat(R.styleable.ProfileDisplayOption_minHeightDps, 0); + + canBeDefault = a.getBoolean(R.styleable.ProfileDisplayOption_canBeDefault, false); + + float x; + float y; + + x = a.getFloat(R.styleable.ProfileDisplayOption_minCellWidth, 0); + y = a.getFloat(R.styleable.ProfileDisplayOption_minCellHeight, 0); + minCellSize[INDEX_DEFAULT] = new PointF(x, y); + + x = a.getFloat(R.styleable.ProfileDisplayOption_minCellWidthLandscape, + minCellSize[INDEX_DEFAULT].x); + y = a.getFloat(R.styleable.ProfileDisplayOption_minCellHeightLandscape, + minCellSize[INDEX_DEFAULT].y); + minCellSize[INDEX_LANDSCAPE] = new PointF(x, y); + + x = a.getFloat(R.styleable.ProfileDisplayOption_minCellWidthTwoPanelPortrait, + minCellSize[INDEX_DEFAULT].x); + y = a.getFloat(R.styleable.ProfileDisplayOption_minCellHeightTwoPanelPortrait, + minCellSize[INDEX_DEFAULT].y); + minCellSize[INDEX_TWO_PANEL_PORTRAIT] = new PointF(x, y); + + x = a.getFloat(R.styleable.ProfileDisplayOption_minCellWidthTwoPanelLandscape, + minCellSize[INDEX_DEFAULT].x); + y = a.getFloat(R.styleable.ProfileDisplayOption_minCellHeightTwoPanelLandscape, + minCellSize[INDEX_DEFAULT].y); + minCellSize[INDEX_TWO_PANEL_LANDSCAPE] = new PointF(x, y); + + float borderSpace = a.getFloat(R.styleable.ProfileDisplayOption_borderSpace, 0); + float borderSpaceLandscape = a.getFloat( + R.styleable.ProfileDisplayOption_borderSpaceLandscape, borderSpace); + float borderSpaceTwoPanelPortrait = a.getFloat( + R.styleable.ProfileDisplayOption_borderSpaceTwoPanelPortrait, borderSpace); + float borderSpaceTwoPanelLandscape = a.getFloat( + R.styleable.ProfileDisplayOption_borderSpaceTwoPanelLandscape, borderSpace); + + x = a.getFloat(R.styleable.ProfileDisplayOption_borderSpaceHorizontal, borderSpace); + y = a.getFloat(R.styleable.ProfileDisplayOption_borderSpaceVertical, borderSpace); + borderSpaces[INDEX_DEFAULT] = new PointF(x, y); + + x = a.getFloat(R.styleable.ProfileDisplayOption_borderSpaceLandscapeHorizontal, + borderSpaceLandscape); + y = a.getFloat(R.styleable.ProfileDisplayOption_borderSpaceLandscapeVertical, + borderSpaceLandscape); + borderSpaces[INDEX_LANDSCAPE] = new PointF(x, y); + + x = a.getFloat( + R.styleable.ProfileDisplayOption_borderSpaceTwoPanelPortraitHorizontal, + borderSpaceTwoPanelPortrait); + y = a.getFloat( + R.styleable.ProfileDisplayOption_borderSpaceTwoPanelPortraitVertical, + borderSpaceTwoPanelPortrait); + borderSpaces[INDEX_TWO_PANEL_PORTRAIT] = new PointF(x, y); + + x = a.getFloat( + R.styleable.ProfileDisplayOption_borderSpaceTwoPanelLandscapeHorizontal, + borderSpaceTwoPanelLandscape); + y = a.getFloat( + R.styleable.ProfileDisplayOption_borderSpaceTwoPanelLandscapeVertical, + borderSpaceTwoPanelLandscape); + borderSpaces[INDEX_TWO_PANEL_LANDSCAPE] = new PointF(x, y); + + x = a.getFloat(R.styleable.ProfileDisplayOption_allAppsCellWidth, + minCellSize[INDEX_DEFAULT].x); + y = a.getFloat(R.styleable.ProfileDisplayOption_allAppsCellHeight, + minCellSize[INDEX_DEFAULT].y); + allAppsCellSize[INDEX_DEFAULT] = new PointF(x, y); + + x = a.getFloat(R.styleable.ProfileDisplayOption_allAppsCellWidthLandscape, + allAppsCellSize[INDEX_DEFAULT].x); + y = a.getFloat(R.styleable.ProfileDisplayOption_allAppsCellHeightLandscape, + allAppsCellSize[INDEX_DEFAULT].y); + allAppsCellSize[INDEX_LANDSCAPE] = new PointF(x, y); + + x = a.getFloat(R.styleable.ProfileDisplayOption_allAppsCellWidthTwoPanelPortrait, + allAppsCellSize[INDEX_DEFAULT].x); + y = a.getFloat(R.styleable.ProfileDisplayOption_allAppsCellHeightTwoPanelPortrait, + allAppsCellSize[INDEX_DEFAULT].y); + allAppsCellSize[INDEX_TWO_PANEL_PORTRAIT] = new PointF(x, y); + + x = a.getFloat(R.styleable.ProfileDisplayOption_allAppsCellWidthTwoPanelLandscape, + allAppsCellSize[INDEX_DEFAULT].x); + y = a.getFloat(R.styleable.ProfileDisplayOption_allAppsCellHeightTwoPanelLandscape, + allAppsCellSize[INDEX_DEFAULT].y); + allAppsCellSize[INDEX_TWO_PANEL_LANDSCAPE] = new PointF(x, y); + + float allAppsBorderSpace = a.getFloat( + R.styleable.ProfileDisplayOption_allAppsBorderSpace, borderSpace); + float allAppsBorderSpaceLandscape = a.getFloat( + R.styleable.ProfileDisplayOption_allAppsBorderSpaceLandscape, + allAppsBorderSpace); + float allAppsBorderSpaceTwoPanelPortrait = a.getFloat( + R.styleable.ProfileDisplayOption_allAppsBorderSpaceTwoPanelPortrait, + allAppsBorderSpace); + float allAppsBorderSpaceTwoPanelLandscape = a.getFloat( + R.styleable.ProfileDisplayOption_allAppsBorderSpaceTwoPanelLandscape, + allAppsBorderSpace); + + x = a.getFloat(R.styleable.ProfileDisplayOption_allAppsBorderSpaceHorizontal, + allAppsBorderSpace); + y = a.getFloat(R.styleable.ProfileDisplayOption_allAppsBorderSpaceVertical, + allAppsBorderSpace); + allAppsBorderSpaces[INDEX_DEFAULT] = new PointF(x, y); + + x = a.getFloat(R.styleable.ProfileDisplayOption_allAppsBorderSpaceLandscapeHorizontal, + allAppsBorderSpaceLandscape); + y = a.getFloat(R.styleable.ProfileDisplayOption_allAppsBorderSpaceLandscapeVertical, + allAppsBorderSpaceLandscape); + allAppsBorderSpaces[INDEX_LANDSCAPE] = new PointF(x, y); + + x = a.getFloat( + R.styleable.ProfileDisplayOption_allAppsBorderSpaceTwoPanelPortraitHorizontal, + allAppsBorderSpaceTwoPanelPortrait); + y = a.getFloat( + R.styleable.ProfileDisplayOption_allAppsBorderSpaceTwoPanelPortraitVertical, + allAppsBorderSpaceTwoPanelPortrait); + allAppsBorderSpaces[INDEX_TWO_PANEL_PORTRAIT] = new PointF(x, y); + + x = a.getFloat( + R.styleable.ProfileDisplayOption_allAppsBorderSpaceTwoPanelLandscapeHorizontal, + allAppsBorderSpaceTwoPanelLandscape); + y = a.getFloat( + R.styleable.ProfileDisplayOption_allAppsBorderSpaceTwoPanelLandscapeVertical, + allAppsBorderSpaceTwoPanelLandscape); + allAppsBorderSpaces[INDEX_TWO_PANEL_LANDSCAPE] = new PointF(x, y); + + iconSizes[INDEX_DEFAULT] = + a.getFloat(R.styleable.ProfileDisplayOption_iconImageSize, 0); + iconSizes[INDEX_LANDSCAPE] = + a.getFloat(R.styleable.ProfileDisplayOption_iconSizeLandscape, + iconSizes[INDEX_DEFAULT]); + iconSizes[INDEX_TWO_PANEL_PORTRAIT] = + a.getFloat(R.styleable.ProfileDisplayOption_iconSizeTwoPanelPortrait, + iconSizes[INDEX_DEFAULT]); + iconSizes[INDEX_TWO_PANEL_LANDSCAPE] = + a.getFloat(R.styleable.ProfileDisplayOption_iconSizeTwoPanelLandscape, + iconSizes[INDEX_DEFAULT]); + + allAppsIconSizes[INDEX_DEFAULT] = a.getFloat( + R.styleable.ProfileDisplayOption_allAppsIconSize, iconSizes[INDEX_DEFAULT]); + allAppsIconSizes[INDEX_LANDSCAPE] = a.getFloat( + R.styleable.ProfileDisplayOption_allAppsIconSizeLandscape, + allAppsIconSizes[INDEX_DEFAULT]); + allAppsIconSizes[INDEX_TWO_PANEL_PORTRAIT] = a.getFloat( + R.styleable.ProfileDisplayOption_allAppsIconSizeTwoPanelPortrait, + allAppsIconSizes[INDEX_DEFAULT]); + allAppsIconSizes[INDEX_TWO_PANEL_LANDSCAPE] = a.getFloat( + R.styleable.ProfileDisplayOption_allAppsIconSizeTwoPanelLandscape, + allAppsIconSizes[INDEX_DEFAULT]); + + textSizes[INDEX_DEFAULT] = + a.getFloat(R.styleable.ProfileDisplayOption_iconTextSize, 0); + textSizes[INDEX_LANDSCAPE] = + a.getFloat(R.styleable.ProfileDisplayOption_iconTextSizeLandscape, + textSizes[INDEX_DEFAULT]); + textSizes[INDEX_TWO_PANEL_PORTRAIT] = + a.getFloat(R.styleable.ProfileDisplayOption_iconTextSizeTwoPanelPortrait, + textSizes[INDEX_DEFAULT]); + textSizes[INDEX_TWO_PANEL_LANDSCAPE] = + a.getFloat(R.styleable.ProfileDisplayOption_iconTextSizeTwoPanelLandscape, + textSizes[INDEX_DEFAULT]); + + allAppsIconTextSizes[INDEX_DEFAULT] = a.getFloat( + R.styleable.ProfileDisplayOption_allAppsIconTextSize, textSizes[INDEX_DEFAULT]); + allAppsIconTextSizes[INDEX_LANDSCAPE] = allAppsIconTextSizes[INDEX_DEFAULT]; + allAppsIconTextSizes[INDEX_TWO_PANEL_PORTRAIT] = a.getFloat( + R.styleable.ProfileDisplayOption_allAppsIconTextSizeTwoPanelPortrait, + allAppsIconTextSizes[INDEX_DEFAULT]); + allAppsIconTextSizes[INDEX_TWO_PANEL_LANDSCAPE] = a.getFloat( + R.styleable.ProfileDisplayOption_allAppsIconTextSizeTwoPanelLandscape, + allAppsIconTextSizes[INDEX_DEFAULT]); + + horizontalMargin[INDEX_DEFAULT] = a.getFloat( + R.styleable.ProfileDisplayOption_horizontalMargin, 0); + horizontalMargin[INDEX_LANDSCAPE] = a.getFloat( + R.styleable.ProfileDisplayOption_horizontalMarginLandscape, + horizontalMargin[INDEX_DEFAULT]); + horizontalMargin[INDEX_TWO_PANEL_LANDSCAPE] = a.getFloat( + R.styleable.ProfileDisplayOption_horizontalMarginTwoPanelLandscape, + horizontalMargin[INDEX_DEFAULT]); + horizontalMargin[INDEX_TWO_PANEL_PORTRAIT] = a.getFloat( + R.styleable.ProfileDisplayOption_horizontalMarginTwoPanelPortrait, + horizontalMargin[INDEX_DEFAULT]); + + hotseatBarBottomSpace[INDEX_DEFAULT] = a.getFloat( + R.styleable.ProfileDisplayOption_hotseatBarBottomSpace, + ResourcesCompat.getFloat(res, R.dimen.hotseat_bar_bottom_space_default)); + hotseatBarBottomSpace[INDEX_LANDSCAPE] = a.getFloat( + R.styleable.ProfileDisplayOption_hotseatBarBottomSpaceLandscape, + hotseatBarBottomSpace[INDEX_DEFAULT]); + hotseatBarBottomSpace[INDEX_TWO_PANEL_LANDSCAPE] = a.getFloat( + R.styleable.ProfileDisplayOption_hotseatBarBottomSpaceTwoPanelLandscape, + hotseatBarBottomSpace[INDEX_DEFAULT]); + hotseatBarBottomSpace[INDEX_TWO_PANEL_PORTRAIT] = a.getFloat( + R.styleable.ProfileDisplayOption_hotseatBarBottomSpaceTwoPanelPortrait, + hotseatBarBottomSpace[INDEX_DEFAULT]); + + hotseatQsbSpace[INDEX_DEFAULT] = a.getFloat( + R.styleable.ProfileDisplayOption_hotseatQsbSpace, + ResourcesCompat.getFloat(res, R.dimen.hotseat_qsb_space_default)); + hotseatQsbSpace[INDEX_LANDSCAPE] = a.getFloat( + R.styleable.ProfileDisplayOption_hotseatQsbSpaceLandscape, + hotseatQsbSpace[INDEX_DEFAULT]); + hotseatQsbSpace[INDEX_TWO_PANEL_LANDSCAPE] = a.getFloat( + R.styleable.ProfileDisplayOption_hotseatQsbSpaceTwoPanelLandscape, + hotseatQsbSpace[INDEX_DEFAULT]); + hotseatQsbSpace[INDEX_TWO_PANEL_PORTRAIT] = a.getFloat( + R.styleable.ProfileDisplayOption_hotseatQsbSpaceTwoPanelPortrait, + hotseatQsbSpace[INDEX_DEFAULT]); + + transientTaskbarIconSize[INDEX_DEFAULT] = a.getFloat( + R.styleable.ProfileDisplayOption_transientTaskbarIconSize, + ResourcesCompat.getFloat(res, R.dimen.taskbar_icon_size)); + transientTaskbarIconSize[INDEX_LANDSCAPE] = a.getFloat( + R.styleable.ProfileDisplayOption_transientTaskbarIconSizeLandscape, + transientTaskbarIconSize[INDEX_DEFAULT]); + transientTaskbarIconSize[INDEX_TWO_PANEL_LANDSCAPE] = a.getFloat( + R.styleable.ProfileDisplayOption_transientTaskbarIconSizeTwoPanelLandscape, + transientTaskbarIconSize[INDEX_DEFAULT]); + transientTaskbarIconSize[INDEX_TWO_PANEL_PORTRAIT] = a.getFloat( + R.styleable.ProfileDisplayOption_transientTaskbarIconSizeTwoPanelPortrait, + transientTaskbarIconSize[INDEX_DEFAULT]); + + startAlignTaskbar[INDEX_DEFAULT] = a.getBoolean( + R.styleable.ProfileDisplayOption_startAlignTaskbar, false); + startAlignTaskbar[INDEX_LANDSCAPE] = a.getBoolean( + R.styleable.ProfileDisplayOption_startAlignTaskbarLandscape, + startAlignTaskbar[INDEX_DEFAULT]); + startAlignTaskbar[INDEX_TWO_PANEL_LANDSCAPE] = a.getBoolean( + R.styleable.ProfileDisplayOption_startAlignTaskbarTwoPanelLandscape, + startAlignTaskbar[INDEX_LANDSCAPE]); + startAlignTaskbar[INDEX_TWO_PANEL_PORTRAIT] = a.getBoolean( + R.styleable.ProfileDisplayOption_startAlignTaskbarTwoPanelPortrait, + startAlignTaskbar[INDEX_DEFAULT]); + + a.recycle(); + } + + DisplayOption() { + this(null); + } + + DisplayOption(GridOption grid) { + this.grid = grid; + minWidthDps = 0; + minHeightDps = 0; + canBeDefault = false; + for (int i = 0; i < COUNT_SIZES; i++) { + iconSizes[i] = 0; + textSizes[i] = 0; + borderSpaces[i] = new PointF(); + minCellSize[i] = new PointF(); + allAppsCellSize[i] = new PointF(); + allAppsIconSizes[i] = 0; + allAppsIconTextSizes[i] = 0; + allAppsBorderSpaces[i] = new PointF(); + transientTaskbarIconSize[i] = 0; + startAlignTaskbar[i] = false; + } + } + + private DisplayOption multiply(float w) { + for (int i = 0; i < COUNT_SIZES; i++) { + iconSizes[i] *= w; + textSizes[i] *= w; + borderSpaces[i].x *= w; + borderSpaces[i].y *= w; + minCellSize[i].x *= w; + minCellSize[i].y *= w; + horizontalMargin[i] *= w; + hotseatBarBottomSpace[i] *= w; + hotseatQsbSpace[i] *= w; + allAppsCellSize[i].x *= w; + allAppsCellSize[i].y *= w; + allAppsIconSizes[i] *= w; + allAppsIconTextSizes[i] *= w; + allAppsBorderSpaces[i].x *= w; + allAppsBorderSpaces[i].y *= w; + transientTaskbarIconSize[i] *= w; + } + + return this; + } + + private DisplayOption add(DisplayOption p) { + for (int i = 0; i < COUNT_SIZES; i++) { + iconSizes[i] += p.iconSizes[i]; + textSizes[i] += p.textSizes[i]; + borderSpaces[i].x += p.borderSpaces[i].x; + borderSpaces[i].y += p.borderSpaces[i].y; + minCellSize[i].x += p.minCellSize[i].x; + minCellSize[i].y += p.minCellSize[i].y; + horizontalMargin[i] += p.horizontalMargin[i]; + hotseatBarBottomSpace[i] += p.hotseatBarBottomSpace[i]; + hotseatQsbSpace[i] += p.hotseatQsbSpace[i]; + allAppsCellSize[i].x += p.allAppsCellSize[i].x; + allAppsCellSize[i].y += p.allAppsCellSize[i].y; + allAppsIconSizes[i] += p.allAppsIconSizes[i]; + allAppsIconTextSizes[i] += p.allAppsIconTextSizes[i]; + allAppsBorderSpaces[i].x += p.allAppsBorderSpaces[i].x; + allAppsBorderSpaces[i].y += p.allAppsBorderSpaces[i].y; + transientTaskbarIconSize[i] += p.transientTaskbarIconSize[i]; + startAlignTaskbar[i] |= p.startAlignTaskbar[i]; + } + + return this; + } } - } } diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 9461ced6c0..0cad4ecbfc 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * Modifications copyright 2025, Lawnchair + * Modifications copyright 2021, Lawnchair */ package com.android.launcher3; @@ -31,7 +31,6 @@ import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE; import static com.android.launcher3.AbstractFloatingView.getTopOpenViewWithType; import static com.android.launcher3.Flags.enableAddAppWidgetViaConfigActivityV2; import static com.android.launcher3.Flags.enableSmartspaceRemovalToggle; -import static com.android.launcher3.Flags.enableStrictMode; import static com.android.launcher3.Flags.enableWorkspaceInflation; import static com.android.launcher3.LauncherAnimUtils.HOTSEAT_SCALE_PROPERTY_FACTORY; import static com.android.launcher3.LauncherAnimUtils.SCALE_INDEX_WIDGET_TRANSITION; @@ -49,7 +48,6 @@ import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_ import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE_PENDING_ACTIVITY_RESULT; import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE_PENDING_REQUEST_ARGS; import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE_PENDING_REQUEST_CODE; -import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE_RECREATE_TO_UPDATE_THEME; import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE_WIDGET_PANEL; import static com.android.launcher3.LauncherConstants.TraceEvents.COLD_STARTUP_TRACE_COOKIE; import static com.android.launcher3.LauncherConstants.TraceEvents.COLD_STARTUP_TRACE_METHOD_NAME; @@ -61,7 +59,6 @@ import static com.android.launcher3.LauncherConstants.TraceEvents.ON_CREATE_EVT; import static com.android.launcher3.LauncherConstants.TraceEvents.ON_NEW_INTENT_EVT; import static com.android.launcher3.LauncherConstants.TraceEvents.ON_RESUME_EVT; import static com.android.launcher3.LauncherConstants.TraceEvents.ON_START_EVT; -import static com.android.launcher3.LauncherPrefs.FIXED_LANDSCAPE_MODE; import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP; import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; import static com.android.launcher3.LauncherState.ALL_APPS; @@ -72,20 +69,16 @@ import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.NO_OFFSET; import static com.android.launcher3.LauncherState.NO_SCALE; import static com.android.launcher3.LauncherState.SPRING_LOADED; -import static com.android.launcher3.Utilities.ATLEAST_Q; -import static com.android.launcher3.Utilities.ATLEAST_R; import static com.android.launcher3.Utilities.postAsyncCallback; -import static com.android.launcher3.Workspace.mapOverCellLayouts; -import static com.android.launcher3.anim.AnimatorListeners.forEndCallback; import static com.android.launcher3.config.FeatureFlags.FOLDABLE_SINGLE_PAGE; import static com.android.launcher3.config.FeatureFlags.MULTI_SELECT_EDIT_MODE; -import static com.android.launcher3.icons.BitmapRenderer.createHardwareBitmap; import static com.android.launcher3.folder.FolderGridOrganizer.createFolderGridOrganizer; import static com.android.launcher3.logging.KeyboardStateManager.KeyboardState.HIDE; import static com.android.launcher3.logging.KeyboardStateManager.KeyboardState.SHOW; import static com.android.launcher3.logging.StatsLogManager.EventEnum; import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND; import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME; +import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_ENTRY; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_ENTRY_WITH_DEVICE_SEARCH; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_EXIT; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ONRESUME; @@ -111,7 +104,6 @@ import static com.android.launcher3.testing.shared.TestProtocol.LAUNCHER_ACTIVIT import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.ItemInfoMatcher.forFolderMatch; import static com.android.launcher3.util.SettingsCache.TOUCHPAD_NATURAL_SCROLLING; -import static com.android.launcher3.util.WallpaperThemeManager.setWallpaperDependentTheme; import android.animation.Animator; import android.animation.AnimatorSet; @@ -132,12 +124,11 @@ import android.content.SharedPreferences; import android.content.res.Configuration; import android.database.sqlite.SQLiteDatabase; import android.graphics.Bitmap; +import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Rect; import android.graphics.RectF; import android.os.Build; -import android.os.Build.VERSION; -import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.os.Parcelable; import android.os.StrictMode; @@ -146,33 +137,31 @@ import android.os.Trace; import android.os.UserHandle; import android.text.TextUtils; import android.text.method.TextKeyListener; +import android.util.AttributeSet; import android.util.FloatProperty; import android.util.Log; import android.util.Pair; import android.util.SparseArray; import android.view.KeyEvent; import android.view.KeyboardShortcutGroup; +import android.view.LayoutInflater; import android.view.Menu; import android.view.MotionEvent; import android.view.View; -import android.view.View.OnClickListener; import android.view.ViewGroup; import android.view.ViewTreeObserver.OnPreDrawListener; import android.view.WindowInsets; -import android.view.WindowInsets.Type; import android.view.WindowInsetsAnimation; -import android.view.WindowInsetsAnimation.Callback; import android.view.WindowManager.LayoutParams; import android.view.accessibility.AccessibilityEvent; import android.view.animation.OvershootInterpolator; import android.widget.Toast; -import android.window.BackEvent; import android.window.OnBackAnimationCallback; import androidx.annotation.CallSuper; +import androidx.annotation.FloatRange; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; import androidx.annotation.StringRes; import androidx.annotation.UiThread; import androidx.annotation.VisibleForTesting; @@ -182,6 +171,7 @@ import androidx.window.embedding.RuleController; import com.android.launcher3.DropTarget.DragObject; import com.android.launcher3.accessibility.LauncherAccessibilityDelegate; import com.android.launcher3.allapps.ActivityAllAppsContainerView; +import com.android.launcher3.allapps.AllAppsRecyclerView; import com.android.launcher3.allapps.AllAppsTransitionController; import com.android.launcher3.allapps.DiscoveryBounce; import com.android.launcher3.anim.AnimationSuccessListener; @@ -192,14 +182,13 @@ import com.android.launcher3.celllayout.CellPosMapper.CellPos; import com.android.launcher3.celllayout.CellPosMapper.TwoPanelCellPosMapper; import com.android.launcher3.compat.AccessibilityManagerCompat; import com.android.launcher3.config.FeatureFlags; -import com.android.launcher3.debug.TestEventEmitter; -import com.android.launcher3.debug.TestEventEmitter.TestEvent; import com.android.launcher3.dot.DotInfo; import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.dragndrop.DragView; import com.android.launcher3.dragndrop.LauncherDragController; import com.android.launcher3.folder.Folder; +import com.android.launcher3.folder.FolderGridOrganizer; import com.android.launcher3.folder.FolderIcon; import com.android.launcher3.icons.IconCache; import com.android.launcher3.keyboard.ViewGroupFocusHelper; @@ -218,12 +207,14 @@ import com.android.launcher3.model.ItemInstallQueue; import com.android.launcher3.model.ModelWriter; import com.android.launcher3.model.StringCache; import com.android.launcher3.model.data.AppInfo; +import com.android.launcher3.model.data.AppPairInfo; import com.android.launcher3.model.data.CollectionInfo; import com.android.launcher3.model.data.FolderInfo; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.LauncherAppWidgetInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.notification.NotificationListener; +import com.android.launcher3.pageindicators.WorkspacePageIndicator; import com.android.launcher3.pm.PinRequestHelper; import com.android.launcher3.popup.ArrowPopup; import com.android.launcher3.popup.PopupDataProvider; @@ -235,20 +226,17 @@ import com.android.launcher3.states.RotationHelper; import com.android.launcher3.testing.TestLogging; import com.android.launcher3.testing.shared.TestProtocol; import com.android.launcher3.touch.AllAppsSwipeController; -import com.android.launcher3.touch.ItemClickHandler; import com.android.launcher3.touch.ItemLongClickListener; import com.android.launcher3.util.ActivityResultInfo; +import com.android.launcher3.util.ActivityTracker; import com.android.launcher3.util.BackPressHandler; import com.android.launcher3.util.CannedAnimationCoordinator; import com.android.launcher3.util.ComponentKey; -import com.android.launcher3.util.ContextTracker; import com.android.launcher3.util.IntArray; import com.android.launcher3.util.IntSet; import com.android.launcher3.util.ItemInflater; import com.android.launcher3.util.KeyboardShortcutsDelegate; -import com.android.launcher3.util.LauncherBindableItemsContainer; import com.android.launcher3.util.LockedUserState; -import com.android.launcher3.util.MSDLPlayerWrapper; import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.util.PendingRequestArgs; import com.android.launcher3.util.PluginManagerWrapper; @@ -278,24 +266,22 @@ import com.android.launcher3.widget.WidgetManagerHelper; import com.android.launcher3.widget.custom.CustomWidgetManager; import com.android.launcher3.widget.model.WidgetsListBaseEntry; import com.android.launcher3.widget.picker.WidgetsFullSheet; -import com.android.launcher3.widget.picker.model.WidgetPickerDataProvider; import com.android.launcher3.widget.util.WidgetSizes; import com.android.systemui.plugins.LauncherOverlayPlugin; import com.android.systemui.plugins.PluginListener; import com.android.systemui.plugins.shared.LauncherOverlayManager; import com.android.systemui.plugins.shared.LauncherOverlayManager.LauncherOverlayTouchProxy; -import com.android.window.flags.Flags; +import com.android.window.flags2.Flags; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Optional; -import java.util.Set; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -311,8 +297,7 @@ public class Launcher extends StatefulActivity PluginListener { public static final String TAG = "Launcher"; - public static final ContextTracker.ActivityTracker ACTIVITY_TRACKER = - new ContextTracker.ActivityTracker<>(); + public static final ActivityTracker ACTIVITY_TRACKER = new ActivityTracker<>(); static final boolean LOGD = false; @@ -321,13 +306,13 @@ public class Launcher extends StatefulActivity private static final float BOUNCE_ANIMATION_TENSION = 1.3f; /** - * IntentStarter uses request codes starting with this. This must be greater than all activity + * IntentStarter uses request codes starting with this. This must be greater + * than all activity * request codes used internally. */ protected static final int REQUEST_LAST = 100; - public static final String INTENT_ACTION_ALL_APPS_TOGGLE = - "launcher.intent_action_all_apps_toggle"; + public static final String INTENT_ACTION_ALL_APPS_TOGGLE = "launcher.intent_action_all_apps_toggle"; private static boolean sIsNewProcess = true; @@ -335,20 +320,23 @@ public class Launcher extends StatefulActivity private static final int ON_ACTIVITY_RESULT_ANIMATION_DELAY = 500; - // How long to wait before the new-shortcut animation automatically pans the workspace - @VisibleForTesting public static final int NEW_APPS_PAGE_MOVE_DELAY = 500; + // How long to wait before the new-shortcut animation automatically pans the + // workspace + @VisibleForTesting + public static final int NEW_APPS_PAGE_MOVE_DELAY = 500; private static final int NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS = 5; - @Thunk @VisibleForTesting public static final int NEW_APPS_ANIMATION_DELAY = 500; + @Thunk + @VisibleForTesting + public static final int NEW_APPS_ANIMATION_DELAY = 500; - private static final FloatProperty> WORKSPACE_WIDGET_SCALE = - WORKSPACE_SCALE_PROPERTY_FACTORY.get(SCALE_INDEX_WIDGET_TRANSITION); - private static final FloatProperty HOTSEAT_WIDGET_SCALE = - HOTSEAT_SCALE_PROPERTY_FACTORY.get(SCALE_INDEX_WIDGET_TRANSITION); + private static final FloatProperty> WORKSPACE_WIDGET_SCALE = WORKSPACE_SCALE_PROPERTY_FACTORY + .get(SCALE_INDEX_WIDGET_TRANSITION); + private static final FloatProperty HOTSEAT_WIDGET_SCALE = HOTSEAT_SCALE_PROPERTY_FACTORY + .get(SCALE_INDEX_WIDGET_TRANSITION); private final ModelCallbacks mModelCallbacks = createModelCallbacks(); - private final KeyboardShortcutsDelegate mKeyboardShortcutsDelegate = - new KeyboardShortcutsDelegate(this); + private final KeyboardShortcutsDelegate mKeyboardShortcutsDelegate = new KeyboardShortcutsDelegate(this); @Thunk Workspace mWorkspace; @@ -378,7 +366,8 @@ public class Launcher extends StatefulActivity // UI and state for the overview panel private View mOverviewPanel; - // Used to notify when an activity launch has been deferred because launcher is not yet resumed + // Used to notify when an activity launch has been deferred because launcher is + // not yet resumed // TODO: See if we can remove this later private Runnable mOnDeferredActivityLaunchCallback; private OnPreDrawListener mOnInitialBindListener; @@ -389,17 +378,19 @@ public class Launcher extends StatefulActivity private LauncherAccessibilityDelegate mAccessibilityDelegate; private PopupDataProvider mPopupDataProvider; - private WidgetPickerDataProvider mWidgetPickerDataProvider; - // We only want to get the SharedPreferences once since it does an FS stat each time we get + // We only want to get the SharedPreferences once since it does an FS stat each + // time we get // it from the context. private SharedPreferences mSharedPrefs; // Activity result which needs to be processed after workspace has loaded. private ActivityResultInfo mPendingActivityResult; /** - * Holds extra information required to handle a result from an external call, like - * {@link #startActivityForResult(Intent, int)} or {@link #requestPermissions(String[], int)} + * Holds extra information required to handle a result from an external call, + * like + * {@link #startActivityForResult(Intent, int)} or + * {@link #requestPermissions(String[], int)} */ private PendingRequestArgs mPendingRequestArgs; // Request id for any pending activity result @@ -420,23 +411,22 @@ public class Launcher extends StatefulActivity // New InstanceId is assigned to mAllAppsSessionLogId for each AllApps sessions. // When Launcher is not in AllApps state mAllAppsSessionLogId will be null. - // User actions within AllApps state are logged with this InstanceId, to recreate AllApps + // User actions within AllApps state are logged with this InstanceId, to + // recreate AllApps // session on the server side. protected InstanceId mAllAppsSessionLogId; private LauncherState mPrevLauncherState; private StartupLatencyLogger mStartupLatencyLogger; private CellPosMapper mCellPosMapper = CellPosMapper.DEFAULT; - private final CannedAnimationCoordinator mAnimationCoordinator = - new CannedAnimationCoordinator(this); + private final CannedAnimationCoordinator mAnimationCoordinator = new CannedAnimationCoordinator(this); private final List mBackPressedHandlers = new ArrayList<>(); private boolean mIsColdStartupAfterReboot; private boolean mIsNaturalScrollingEnabled; - private final SettingsCache.OnChangeListener mNaturalScrollingChangedListener = - enabled -> mIsNaturalScrollingEnabled = enabled; + private final SettingsCache.OnChangeListener mNaturalScrollingChangedListener = enabled -> mIsNaturalScrollingEnabled = enabled; public static Launcher getLauncher(Context context) { return fromContext(context); @@ -448,17 +438,13 @@ public class Launcher extends StatefulActivity mStartupLatencyLogger = createStartupLatencyLogger( sIsNewProcess ? LockedUserState.get(this).isUserUnlockedAtLauncherStartup() - ? COLD - : COLD_DEVICE_REBOOTING + ? COLD + : COLD_DEVICE_REBOOTING : WARM); mIsColdStartupAfterReboot = sIsNewProcess - && !LockedUserState.get(this).isUserUnlockedAtLauncherStartup(); + && !LockedUserState.get(this).isUserUnlockedAtLauncherStartup(); if (mIsColdStartupAfterReboot) { - /* - * This trace is used to calculate the time from create to the point that icons are - * visible. - */ Trace.beginAsyncSection( COLD_STARTUP_TRACE_METHOD_NAME, COLD_STARTUP_TRACE_COOKIE); } @@ -475,18 +461,16 @@ public class Launcher extends StatefulActivity DISPLAY_ALL_APPS_TRACE_COOKIE); } TraceHelper.INSTANCE.beginSection(ON_CREATE_EVT); - if (DEBUG_STRICT_MODE - || (FeatureFlags.IS_STUDIO_BUILD && enableStrictMode())) { + if (DEBUG_STRICT_MODE) { StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .detectDiskReads() .detectDiskWrites() - .detectNetwork() // or .detectAll() for all detectable problems + .detectNetwork() // or .detectAll() for all detectable problems .penaltyLog() .build()); StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() .detectLeakedSqlLiteObjects() .detectLeakedClosableObjects() - .detectActivityLeaks() .penaltyLog() .penaltyDeath() .build()); @@ -521,8 +505,8 @@ public class Launcher extends StatefulActivity .build(); notificationManager.notify(notificationTag, notificationId, notification); - Thread.UncaughtExceptionHandler defaultUncaughtExceptionHandler = - Thread.getDefaultUncaughtExceptionHandler(); + Thread.UncaughtExceptionHandler defaultUncaughtExceptionHandler = Thread + .getDefaultUncaughtExceptionHandler(); if (defaultUncaughtExceptionHandler != null) { defaultUncaughtExceptionHandler.uncaughtException(thread, throwable); } @@ -530,9 +514,10 @@ public class Launcher extends StatefulActivity } super.onCreate(savedInstanceState); - setWallpaperDependentTheme(this); LauncherAppState app = LauncherAppState.getInstance(this); + app.setLauncher(this); + mOldConfig = new Configuration(getResources().getConfiguration()); mModel = app.getModel(); mRotationHelper = new RotationHelper(this); @@ -547,22 +532,17 @@ public class Launcher extends StatefulActivity mAllAppsController = new AllAppsTransitionController(this); mStateManager = new StateManager<>(this, NORMAL); - mAppWidgetManager = new WidgetManagerHelper(this); - mAppWidgetHolder = LauncherWidgetHolder.newInstance(this); - mAppWidgetHolder.setAppWidgetRemovedCallback( - appWidgetId -> getWorkspace().removeWidget(appWidgetId)); - setupViews(); updateDisallowBack(); + mAppWidgetManager = new WidgetManagerHelper(this); + mAppWidgetHolder = createAppWidgetHolder(); mAppWidgetHolder.startListening(); mAppWidgetHolder.addProviderChangeListener(() -> refreshAndBindWidgetsForPackageUser(null)); mItemInflater = new ItemInflater<>(this, mAppWidgetHolder, getItemOnClickListener(), mFocusHandler, new CellLayout(mWorkspace.getContext(), mWorkspace)); - mPopupDataProvider = new PopupDataProvider(this); - mWidgetPickerDataProvider = new WidgetPickerDataProvider(this); - PillColorProvider.getInstance(mWorkspace.getContext()).registerObserver(); + mPopupDataProvider = new PopupDataProvider(this::updateNotificationDots); boolean internalStateHandled = ACTIVITY_TRACKER.handleCreate(this); if (internalStateHandled) { @@ -585,7 +565,8 @@ public class Launcher extends StatefulActivity mStartupLatencyLogger.logWorkspaceLoadStartTime(); if (!mModel.addCallbacksAndLoad(this)) { if (!internalStateHandled) { - // If we are not binding synchronously, pause drawing until initial bind complete, + // If we are not binding synchronously, pause drawing until initial bind + // complete, // so that the system could continue to show the device loading prompt mOnInitialBindListener = Boolean.FALSE::booleanValue; } @@ -637,7 +618,8 @@ public class Launcher extends StatefulActivity } /** - * We only log startup latency in {@link COLD_DEVICE_REBOOTING} type. For other latency types, + * We only log startup latency in {@link COLD_DEVICE_REBOOTING} type. For other + * latency types, * create a no op implementation. */ private StartupLatencyLogger createStartupLatencyLogger( @@ -649,37 +631,37 @@ public class Launcher extends StatefulActivity } /** - * Create {@link ColdRebootStartupLatencyLogger} that only collects launcher startup latency - * metrics without sending them anywhere. Child class can override this method to create logger + * Create {@link ColdRebootStartupLatencyLogger} that only collects launcher + * startup latency + * metrics without sending them anywhere. Child class can override this method + * to create logger * that overrides {@link StartupLatencyLogger#log()} to report those metrics. */ protected ColdRebootStartupLatencyLogger createColdRebootStartupLatencyLogger() { return new ColdRebootStartupLatencyLogger(); } - @NonNull View getAccessibilityActionView() { - return findViewById(R.id.accessibility_action_view); - } - /** * Provide {@link OnBackAnimationCallback} in below order: *

    - *
  1. auto cancel action mode handler - *
  2. drag handler - *
  3. view handler - *
  4. registered {@link BackPressHandler} - *
  5. state handler + *
  6. auto cancel action mode handler + *
  7. drag handler + *
  8. view handler + *
  9. registered {@link BackPressHandler} + *
  10. state handler *
* - * A back gesture (a single click on back button, or a swipe back gesture that contains a series - * of swipe events) should be handled by the same handler from above list. For a new back + * A back gesture (a single click on back button, or a swipe back gesture that + * contains a series + * of swipe events) should be handled by the same handler from above list. For a + * new back * gesture, a new handler should be regenerated. * - * Note that state handler will always be handling the back press event if the previous 3 don't. + * Note that state handler will always be handling the back press event if the + * previous 3 don't. */ @NonNull - @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) - protected OnBackAnimationCallback getOnBackAnimationCallback() { + protected OnBackPressedHandler getOnBackPressedHandler() { // #1 auto cancel action mode handler if (isInAutoCancelActionMode()) { return this::finishAutoCancelActionMode; @@ -691,23 +673,15 @@ public class Launcher extends StatefulActivity } // #3 view handler - AbstractFloatingView topView = - AbstractFloatingView.getTopOpenView(Launcher.this); + AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(Launcher.this); if (topView != null && topView.canHandleBack()) { return topView; } - // #4 Custom back handlers - for (BackPressHandler handler : mBackPressedHandlers) { - if (handler.canHandleBack()) { - return handler; - } - } - - // #5 state handler - return new OnBackAnimationCallback() { + // #4 state handler + return new OnBackPressedHandler() { @Override - public void onBackStarted(BackEvent backEvent) { + public void onBackStarted() { Launcher.this.onBackStarted(); } @@ -717,9 +691,10 @@ public class Launcher extends StatefulActivity } @Override - public void onBackProgressed(@NonNull BackEvent backEvent) { + public void onBackProgressed( + @FloatRange(from = 0.0, to = 1.0) float backProgress) { mStateManager.getState().onBackProgressed( - Launcher.this, backEvent.getProgress()); + Launcher.this, backProgress); } @Override @@ -730,7 +705,8 @@ public class Launcher extends StatefulActivity } protected LauncherOverlayManager getDefaultOverlay() { - return new LauncherOverlayManager() { }; + return new LauncherOverlayManager() { + }; } @Override @@ -745,7 +721,7 @@ public class Launcher extends StatefulActivity private void switchOverlay(Supplier overlaySupplier) { if (mOverlayManager != null) { - mOverlayManager.onActivityDestroyed(); + mOverlayManager.onActivityDestroyed(this); } mOverlayManager = overlaySupplier.get(); if (getRootView().isAttachedToWindow()) { @@ -765,6 +741,15 @@ public class Launcher extends StatefulActivity public void onEnterAnimationComplete() { super.onEnterAnimationComplete(); mRotationHelper.setCurrentTransitionRequest(REQUEST_NONE); + // Starting with Android S, onEnterAnimationComplete is sent immediately + // causing the surface to get removed before the animation completed + // (b/175345344). + // Instead we rely on next user touch event to remove the view and optionally a + // callback + // from system from Android T onwards. + if (!Utilities.ATLEAST_S) { + AbstractFloatingView.closeOpenViews(this, false, TYPE_ICON_SURFACE); + } } @Override @@ -794,6 +779,7 @@ public class Launcher extends StatefulActivity if (!initDeviceProfile(mDeviceProfile.inv)) { return; } + dispatchDeviceProfileChanged(); reapplyUi(); mDragLayer.recreateControllers(); @@ -802,36 +788,18 @@ public class Launcher extends StatefulActivity // initialized properly. onSaveInstanceState(new Bundle()); mModel.rebindCallbacks(); - updateDisallowBack(); } finally { Trace.endSection(); } } - private void updateFixedLandscape() { - if (!com.android.launcher3.Flags.oneGridSpecs()) { - return; - } - // When the flag oneGridSpecs is on we want to disable ALLOW_ROTATION which is replaced - // by FIXED_LANDSCAPE_MODE, ALLOW_ROTATION will only be used on Tablets and foldables - // afterwards. - if (getDeviceProfile().isPhone) { - LauncherPrefs.get(this).put(LauncherPrefs.ALLOW_ROTATION, false); - } else if (getDeviceProfile().isTablet) { - // Tablet do not use fixed landscape mode, make sure it can't be activated by mistake - LauncherPrefs.get(this).put(FIXED_LANDSCAPE_MODE, false); - } - getRotationHelper().setFixedLandscape( - Objects.requireNonNull(mDeviceProfile.inv).isFixedLandscape - ); - } - public void onAssistantVisibilityChanged(float visibility) { mHotseat.getQsb().setAlpha(1f - visibility); } /** - * Returns {@code true} if a new DeviceProfile is initialized, and {@code false} otherwise. + * Returns {@code true} if a new DeviceProfile is initialized, and {@code false} + * otherwise. */ protected boolean initDeviceProfile(InvariantDeviceProfile idp) { // Load configuration-specific DeviceProfile @@ -846,6 +814,7 @@ public class Launcher extends StatefulActivity this, getMultiWindowDisplaySize()); } + onDeviceProfileInitiated(); if (FOLDABLE_SINGLE_PAGE.get() && mDeviceProfile.isTwoPanels) { mCellPosMapper = new TwoPanelCellPosMapper(mDeviceProfile.inv.numColumns); } else { @@ -853,20 +822,42 @@ public class Launcher extends StatefulActivity mDeviceProfile.numShownHotseatIcons); } mModelWriter = mModel.getWriter(true, mCellPosMapper, this); - updateFixedLandscape(); return true; } + @Override + public void invalidateParent(ItemInfo info) { + if (info.container >= 0) { + View collectionIcon = getWorkspace().getHomescreenIconByItemId(info.container); + if (collectionIcon instanceof FolderIcon folderIcon + && collectionIcon.getTag() instanceof FolderInfo) { + if (createFolderGridOrganizer(getDeviceProfile()) + .setFolderInfo((FolderInfo) folderIcon.getTag()) + .isItemInPreview(info.rank)) { + folderIcon.invalidate(); + } + } else if (collectionIcon instanceof AppPairIcon appPairIcon + && collectionIcon.getTag() instanceof AppPairInfo appPairInfo) { + if (appPairInfo.getContents().contains(info)) { + appPairIcon.getIconDrawableArea().redraw(); + } + } + } + } + /** - * Returns whether we should delay spring loaded mode -- for shortcuts and widgets that have - * a configuration step, this allows the proper animations to run after other transitions. + * Returns whether we should delay spring loaded mode -- for shortcuts and + * widgets that have + * a configuration step, this allows the proper animations to run after other + * transitions. */ private int completeAdd( int requestCode, Intent intent, int appWidgetId, PendingRequestArgs info) { CellPos cellPos = getCellPosMapper().mapModelToPresenter(info); int screenId = cellPos.screenId; if (info.container == CONTAINER_DESKTOP) { - // When the screen id represents an actual screen (as opposed to a rank) we make sure + // When the screen id represents an actual screen (as opposed to a rank) we make + // sure // that the drop page actually exists. screenId = ensurePendingDropLayoutExists(cellPos.screenId); } @@ -875,6 +866,7 @@ public class Launcher extends StatefulActivity case REQUEST_CREATE_SHORTCUT: completeAddShortcut(intent, info.container, screenId, cellPos.cellX, cellPos.cellY, info); + announceForAccessibility(R.string.item_added_to_workspace); break; case REQUEST_CREATE_APPWIDGET: completeAddAppWidget(appWidgetId, info, null, null, false, true, null); @@ -885,8 +877,8 @@ public class Launcher extends StatefulActivity break; case REQUEST_BIND_PENDING_APPWIDGET: { int widgetId = appWidgetId; - LauncherAppWidgetInfo widgetInfo = - completeRestoreAppWidget(widgetId, LauncherAppWidgetInfo.FLAG_UI_NOT_READY); + LauncherAppWidgetInfo widgetInfo = completeRestoreAppWidget(widgetId, + LauncherAppWidgetInfo.FLAG_UI_NOT_READY); if (widgetInfo != null) { // Since the view was just bound, also launch the configure activity if needed LauncherAppWidgetProviderInfo provider = mAppWidgetManager @@ -904,7 +896,8 @@ public class Launcher extends StatefulActivity } /** - * Process any pending activity result if it was put on hold for any reason like item binding. + * Process any pending activity result if it was put on hold for any reason like + * item binding. */ public void processActivityResult() { if (mPendingActivityResult != null) { @@ -947,9 +940,9 @@ public class Launcher extends StatefulActivity : () -> mStateManager.goToState(NORMAL, SPRING_LOADED_EXIT_DELAY); if (requestCode == REQUEST_BIND_APPWIDGET) { - // This is called only if the user did not previously have permissions to bind widgets - final int appWidgetId = data != null ? - data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) : -1; + // This is called only if the user did not previously have permissions to bind + // widgets + final int appWidgetId = data != null ? data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) : -1; if (resultCode == RESULT_CANCELED) { completeTwoStageWidgetDrop(RESULT_CANCELED, appWidgetId, requestArgs); mWorkspace.removeExtraEmptyScreenDelayed( @@ -993,12 +986,11 @@ public class Launcher extends StatefulActivity // we make sure that the drop page actually exists. int newScreenId = ensurePendingDropLayoutExists(presenterPos.screenId); requestArgs.screenId = getCellPosMapper().mapPresenterToModel( - presenterPos.cellX, presenterPos.cellY, newScreenId, CONTAINER_DESKTOP) - .screenId; + presenterPos.cellX, presenterPos.cellY, newScreenId, CONTAINER_DESKTOP).screenId; } - final CellLayout dropLayout = - mWorkspace.getScreenWithId(presenterPos.screenId); - + final CellLayout dropLayout = mWorkspace.getScreenWithId(presenterPos.screenId); + if (dropLayout == null) + return; dropLayout.setDropPending(true); final Runnable onComplete = new Runnable() { @Override @@ -1047,7 +1039,8 @@ public class Launcher extends StatefulActivity } /** - * Check to see if a given screen id exists. If not, create it at the end, return the new id. + * Check to see if a given screen id exists. If not, create it at the end, + * return the new id. * * @param screenId the screen id to check * @return the new screen, or screenId if it exists @@ -1077,7 +1070,8 @@ public class Launcher extends StatefulActivity animationType = Workspace.COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION; // Now that we are exiting the config activity with RESULT_OK. - // If FLAG_ENABLE_ADD_APP_WIDGET_VIA_CONFIG_ACTIVITY_V2 is enabled, we can retrieve the + // If FLAG_ENABLE_ADD_APP_WIDGET_VIA_CONFIG_ACTIVITY_V2 is enabled, we can + // retrieve the // PendingAppWidgetHostView from LauncherWidgetHolder (it was added to // LauncherWidgetHolder when starting the config activity). final AppWidgetHostView layout = enableAddAppWidgetViaConfigActivityV2() @@ -1100,7 +1094,8 @@ public class Launcher extends StatefulActivity (DragView) mDragLayer.getAnimatedView(), onCompleteRunnable, animationType, boundWidget, true); } else if (onCompleteRunnable != null) { - // The animated view may be null in the case of a rotation during widget configuration + // The animated view may be null in the case of a rotation during widget + // configuration onCompleteRunnable.run(); } } @@ -1111,7 +1106,7 @@ public class Launcher extends StatefulActivity if (mDeferOverlayCallbacks) { checkIfOverlayStillDeferred(); } else { - mOverlayManager.onActivityStopped(); + mOverlayManager.onActivityStopped(this); } hideKeyboard(); logStopAndResume(false /* isResume */); @@ -1127,7 +1122,7 @@ public class Launcher extends StatefulActivity TraceHelper.INSTANCE.beginSection(ON_START_EVT); super.onStart(); if (!mDeferOverlayCallbacks) { - mOverlayManager.onActivityStarted(); + mOverlayManager.onActivityStarted(this); } mAppWidgetHolder.setActivityStarted(true); @@ -1153,20 +1148,19 @@ public class Launcher extends StatefulActivity mAppWidgetHolder.setActivityResumed(true); // Listen for IME changes to keep state up to date. - if (ATLEAST_R) { + if (Utilities.ATLEAST_T) { getRootView().setWindowInsetsAnimationCallback( - new Callback(DISPATCH_MODE_CONTINUE_ON_SUBTREE) { + new WindowInsetsAnimation.Callback(DISPATCH_MODE_CONTINUE_ON_SUBTREE) { @Override public WindowInsets onProgress(WindowInsets windowInsets, - List windowInsetsAnimations) { + List windowInsetsAnimations) { return windowInsets; } - + @Override public void onEnd(WindowInsetsAnimation animation) { WindowInsets insets = getRootView().getRootWindowInsets(); - boolean isImeVisible = - insets != null && insets.isVisible(Type.ime()); + boolean isImeVisible = insets != null && insets.isVisible(WindowInsets.Type.ime()); getStatsLogManager().keyboardStateManager().setKeyboardState( isImeVisible ? SHOW : HIDE); } @@ -1175,7 +1169,8 @@ public class Launcher extends StatefulActivity } private void logStopAndResume(boolean isResume) { - if (mModelCallbacks.getPendingExecutor() != null) return; + if (mModelCallbacks.getPendingExecutor() != null) + return; int pageIndex = mWorkspace.isOverlayShown() ? -1 : mWorkspace.getCurrentPage(); int statsLogOrdinal = mStateManager.getState().statsLogOrdinal; @@ -1183,7 +1178,7 @@ public class Launcher extends StatefulActivity StatsLogManager.StatsLogger logger = getStatsLogManager().logger(); if (isResume) { logger.withSrcState(LAUNCHER_STATE_BACKGROUND) - .withDstState(mStateManager.getState().statsLogOrdinal); + .withDstState(mStateManager.getState().statsLogOrdinal); event = LAUNCHER_ONRESUME; } else { /* command == Action.Command.STOP */ logger.withSrcState(mStateManager.getState().statsLogOrdinal) @@ -1195,7 +1190,8 @@ public class Launcher extends StatefulActivity logger.withContainerInfo(LauncherAtom.ContainerInfo.newBuilder() .setWorkspace( LauncherAtom.WorkspaceContainer.newBuilder() - .setPageIndex(pageIndex)).build()); + .setPageIndex(pageIndex)) + .build()); } logger.log(event); } @@ -1217,15 +1213,15 @@ public class Launcher extends StatefulActivity // Move the client to the correct state. Calling the same method twice is no-op. if (isStarted()) { - mOverlayManager.onActivityStarted(); + mOverlayManager.onActivityStarted(this); } if (hasBeenResumed()) { - mOverlayManager.onActivityResumed(); + mOverlayManager.onActivityResumed(this); } else { - mOverlayManager.onActivityPaused(); + mOverlayManager.onActivityPaused(this); } if (!isStarted()) { - mOverlayManager.onActivityStopped(); + mOverlayManager.onActivityStopped(this); } } @@ -1255,7 +1251,7 @@ public class Launcher extends StatefulActivity mPrevLauncherState = mStateManager.getCurrentStableState(); if (mPrevLauncherState != state && ALL_APPS.equals(state) - // Making sure mAllAppsSessionLogId is null to avoid double logging. + // Making sure mAllAppsSessionLogId is null to avoid double logging. && mAllAppsSessionLogId == null) { // creates new instance ID since new all apps session is started. mAllAppsSessionLogId = new InstanceIdSequence().newInstanceId(); @@ -1263,7 +1259,8 @@ public class Launcher extends StatefulActivity getStatsLogManager().logger() .withContainerInfo(ContainerInfo.newBuilder() .setWorkspace(WorkspaceContainer.newBuilder() - .setPageIndex(getWorkspace().getCurrentPage())).build()) + .setPageIndex(getWorkspace().getCurrentPage())) + .build()) .log(getAllAppsEntryEvent().get()); } } @@ -1271,10 +1268,13 @@ public class Launcher extends StatefulActivity } /** - * Returns {@link EventEnum} that should be logged when Launcher enters into AllApps state. + * Returns {@link EventEnum} that should be logged when Launcher enters into + * AllApps state. */ protected Optional getAllAppsEntryEvent() { - return Optional.of(LAUNCHER_ALLAPPS_ENTRY_WITH_DEVICE_SEARCH); + return Optional.of(FeatureFlags.ENABLE_DEVICE_SEARCH.get() + ? LAUNCHER_ALLAPPS_ENTRY_WITH_DEVICE_SEARCH + : LAUNCHER_ALLAPPS_ENTRY); } @Override @@ -1300,7 +1300,7 @@ public class Launcher extends StatefulActivity } if (ALL_APPS.equals(mPrevLauncherState) && !ALL_APPS.equals(state) - // Making sure mAllAppsSessionLogId is not null to avoid double logging. + // Making sure mAllAppsSessionLogId is not null to avoid double logging. && mAllAppsSessionLogId != null) { getAppsView().reset(false); getAllAppsExitEvent().ifPresent(getStatsLogManager().logger()::log); @@ -1312,7 +1312,8 @@ public class Launcher extends StatefulActivity } /** - * Returns {@link EventEnum} that should be logged when Launcher exists from AllApps state. + * Returns {@link EventEnum} that should be logged when Launcher exists from + * AllApps state. */ protected Optional getAllAppsExitEvent() { return Optional.of(LAUNCHER_ALLAPPS_EXIT); @@ -1326,7 +1327,7 @@ public class Launcher extends StatefulActivity if (mDeferOverlayCallbacks) { scheduleDeferredCheck(); } else { - mOverlayManager.onActivityResumed(); + mOverlayManager.onActivityResumed(this); } DragView.removeAllViews(this); @@ -1344,7 +1345,7 @@ public class Launcher extends StatefulActivity mDropTargetBar.animateToVisibility(false); if (!mDeferOverlayCallbacks) { - mOverlayManager.onActivityPaused(); + mOverlayManager.onActivityPaused(this); } mAppWidgetHolder.setActivityResumed(false); } @@ -1363,10 +1364,9 @@ public class Launcher extends StatefulActivity LauncherState[] stateValues = LauncherState.values(); LauncherState state = stateValues[stateOrdinal]; - NonConfigInstance lastInstance = (NonConfigInstance) getLastNonConfigurationInstance(); + NonConfigInstance lastInstance = (NonConfigInstance) getLastCustomNonConfigurationInstance(); boolean forceRestore = lastInstance != null - && ((lastInstance.config.diff(mOldConfig) & CONFIG_UI_MODE) != 0 - || savedState.getBoolean(RUNTIME_STATE_RECREATE_TO_UPDATE_THEME)); + && (lastInstance.config.diff(mOldConfig) & CONFIG_UI_MODE) != 0; if (forceRestore || !state.shouldDisableRestore()) { mStateManager.goToState(state, false /* animated */); } @@ -1376,13 +1376,11 @@ public class Launcher extends StatefulActivity if (requestArgs != null) { setWaitingForResult(requestArgs); } - mPendingActivityRequestCode = savedState.getInt( - RUNTIME_STATE_PENDING_REQUEST_CODE, mPendingActivityRequestCode); + mPendingActivityRequestCode = savedState.getInt(RUNTIME_STATE_PENDING_REQUEST_CODE); mPendingActivityResult = savedState.getParcelable(RUNTIME_STATE_PENDING_ACTIVITY_RESULT); - SparseArray widgetsState = - savedState.getSparseParcelableArray(RUNTIME_STATE_WIDGET_PANEL); + SparseArray widgetsState = savedState.getSparseParcelableArray(RUNTIME_STATE_WIDGET_PANEL); if (widgetsState != null) { WidgetsFullSheet.show(this, false).restoreHierarchyState(widgetsState); } @@ -1408,7 +1406,8 @@ public class Launcher extends StatefulActivity mDragLayer.setup(mDragController, mWorkspace); mWorkspace.setup(mDragController); - // Until the workspace is bound, ensure that we keep the wallpaper offset locked to the + // Until the workspace is bound, ensure that we keep the wallpaper offset locked + // to the // default state, otherwise we will update to the wrong offsets in RTL mWorkspace.lockWallpaperToDefaultPage(); if (!enableSmartspaceRemovalToggle()) { @@ -1426,13 +1425,27 @@ public class Launcher extends StatefulActivity // Setup Scrim mScrimView = findViewById(R.id.scrim_view); - // Setup the drag controller (drop targets have to be added in reverse order in priority) + // Setup the drag controller (drop targets have to be added in reverse order in + // priority) mDropTargetBar.setup(mDragController); mAllAppsController.setupViews(mScrimView, mAppsView); - mWorkspace.getPageIndicator().setShouldAutoHide(true); - mWorkspace.getPageIndicator().setPaintColor(Themes.getAttrBoolean( - this, R.attr.isWorkspaceDarkText) ? Color.BLACK : Color.WHITE); + if (FeatureFlags.showDotPagination(this)) { + mWorkspace.getPageIndicator().setShouldAutoHide(true); + mWorkspace.getPageIndicator().setPaintColor( + Themes.getAttrBoolean(this, R.attr.isWorkspaceDarkText) + ? Color.BLACK + : Color.WHITE); + } + } + + @Override + public View onCreateView(View parent, String name, Context context, AttributeSet attrs) { + if (FeatureFlags.showDotPagination(this) && WorkspacePageIndicator.class.getName().equals(name)) { + return LayoutInflater.from(context).inflate(R.layout.page_indicator_dots, + (ViewGroup) parent, false); + } + return super.onCreateView(parent, name, context, attrs); } /** @@ -1450,7 +1463,7 @@ public class Launcher extends StatefulActivity CellLayout layout = getCellLayout(container, screenId); WorkspaceItemInfo info = PinRequestHelper.createWorkspaceItemFromPinItemRequest( - this, PinRequestHelper.getPinItemRequest(data), 0); + this, PinRequestHelper.getPinItemRequest(data), 0); if (info == null) { Log.e(TAG, "Unable to parse a valid shortcut result"); return; @@ -1460,7 +1473,8 @@ public class Launcher extends StatefulActivity // Adding a shortcut to the Workspace. final View view = mItemInflater.inflateItem(info, getModelWriter()); boolean foundCellSpan = false; - // First we check if we already know the exact location where we want to add this item. + // First we check if we already know the exact location where we want to add + // this item. if (cellX >= 0 && cellY >= 0) { cellXY[0] = cellX; cellXY[1] = cellY; @@ -1487,15 +1501,13 @@ public class Launcher extends StatefulActivity } getModelWriter().addItemToDatabase(info, container, screenId, cellXY[0], cellXY[1]); - AnimatorSet anim = new AnimatorSet(); - anim.addListener(forEndCallback(() -> - view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED))); - bindInflatedItems(Collections.singletonList(Pair.create(info, view)), anim); + mWorkspace.addInScreen(view, info); } else { // Adding a shortcut to a Folder. FolderIcon folderIcon = findFolderIcon(container); if (folderIcon != null) { - folderIcon.getFolder().addFolderContent(info, args.rank, false); + FolderInfo folderInfo = (FolderInfo) folderIcon.getTag(); + folderInfo.add(info, args.rank, false); } else { Log.e(TAG, "Could not find folder with id " + container + " to add shortcut."); } @@ -1504,7 +1516,7 @@ public class Launcher extends StatefulActivity @Override public @Nullable FolderIcon findFolderIcon(final int folderIconId) { - return (FolderIcon) mWorkspace.getViewByItemId(folderIconId); + return (FolderIcon) mWorkspace.getHomescreenIconByItemId(folderIconId); } /** @@ -1538,9 +1550,8 @@ public class Launcher extends StatefulActivity return; } LauncherAppWidgetInfo launcherInfo; - launcherInfo = - new LauncherAppWidgetInfo( - appWidgetId, appWidgetInfo.provider, appWidgetInfo, hostView); + launcherInfo = new LauncherAppWidgetInfo( + appWidgetId, appWidgetInfo.provider, appWidgetInfo, hostView); launcherInfo.spanX = itemInfo.spanX; launcherInfo.spanY = itemInfo.spanY; launcherInfo.minSpanX = itemInfo.minSpanX; @@ -1554,14 +1565,14 @@ public class Launcher extends StatefulActivity hostView = pendingAppWidgetHostView; } else if (hostView instanceof PendingAppWidgetHostView) { ((PendingAppWidgetHostView) hostView).setPreviewBitmapAndUpdateBackground(null); - // User has selected a widget config and exited the config activity, we can trigger + // User has selected a widget config and exited the config activity, we can + // trigger // re-inflation of PendingAppWidgetHostView to replace it with // LauncherAppWidgetHostView in workspace. completeRestoreAppWidget(appWidgetId, LauncherAppWidgetInfo.RESTORE_COMPLETED); // Show resize frame on the newly inflated LauncherAppWidgetHostView. - LauncherAppWidgetHostView reInflatedHostView = - getWorkspace().getWidgetForAppWidgetId(appWidgetId); + LauncherAppWidgetHostView reInflatedHostView = getWorkspace().getWidgetForAppWidgetId(appWidgetId); showWidgetResizeFrame( reInflatedHostView, (LauncherAppWidgetInfo) reInflatedHostView.getTag(), @@ -1577,8 +1588,7 @@ public class Launcher extends StatefulActivity if (itemInfo instanceof PendingAddWidgetInfo) { launcherInfo.sourceContainer = ((PendingAddWidgetInfo) itemInfo).sourceContainer; } else if (itemInfo instanceof PendingRequestArgs) { - launcherInfo.sourceContainer = - ((PendingRequestArgs) itemInfo).getWidgetSourceContainer(); + launcherInfo.sourceContainer = ((PendingRequestArgs) itemInfo).getWidgetSourceContainer(); } getModelWriter().addItemToDatabase(launcherInfo, itemInfo.container, presenterPos.screenId, presenterPos.cellX, presenterPos.cellY); @@ -1588,6 +1598,7 @@ public class Launcher extends StatefulActivity if (!enableAddAppWidgetViaConfigActivityV2() || hostView.getParent() == null) { mWorkspace.addInScreen(hostView, launcherInfo); } + announceForAccessibility(R.string.item_added_to_workspace); // Show the widget resize frame. if (hostView instanceof LauncherAppWidgetHostView) { @@ -1603,9 +1614,11 @@ public class Launcher extends StatefulActivity CellPos presenterPos) { CellLayout cellLayout = getCellLayout(launcherInfo.container, presenterPos.screenId); // We should wait until launcher is not animating to show resize frame so that - // {@link View#hasIdentityMatrix()} returns true (no scale effect) from CellLayout and + // {@link View#hasIdentityMatrix()} returns true (no scale effect) from + // CellLayout and // Workspace (they are widget's parent view). Otherwise widget's - // {@link View#getLocationInWindow(int[])} will set skewed location, causing resize + // {@link View#getLocationInWindow(int[])} will set skewed location, causing + // resize // frame not showing at skewed location in // {@link AppWidgetResizeFrame#snapToWidget(boolean)}. if (mStateManager.getState() == NORMAL && !mStateManager.isInTransition()) { @@ -1626,6 +1639,11 @@ public class Launcher extends StatefulActivity private final ScreenOnListener mScreenOnListener = this::onScreenOnChanged; + private void updateNotificationDots(Predicate updatedDots) { + mWorkspace.updateNotificationDots(updatedDots); + mAppsView.getAppsStore().updateNotificationDots(updatedDots); + } + @Override public void onAttachedToWindow() { super.onAttachedToWindow(); @@ -1639,6 +1657,19 @@ public class Launcher extends StatefulActivity closeContextMenu(); } + @Nullable + @Override + public Object onRetainCustomNonConfigurationInstance() { + NonConfigInstance instance = new NonConfigInstance(); + instance.config = new Configuration(mOldConfig); + return instance; + } + + protected LauncherWidgetHolder createAppWidgetHolder() { + return LauncherWidgetHolder.HolderFactory.newFactory(this).newInstance( + this, appWidgetId -> getWorkspace().removeWidget(appWidgetId)); + } + @Override protected void onNewIntent(Intent intent) { if (Utilities.isRunningInTestHarness()) { @@ -1648,8 +1679,7 @@ public class Launcher extends StatefulActivity super.onNewIntent(intent); boolean alreadyOnHome = hasWindowFocus() && ((intent.getFlags() & - Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) - != Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT); + Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) != Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT); // Check this condition before handling isActionMain, as this will get reset. boolean shouldMoveToDefaultScreen = alreadyOnHome && isInState(NORMAL) @@ -1687,13 +1717,15 @@ public class Launcher extends StatefulActivity } } - handleSplitAnimationGoingToHome(LAUNCHER_SPLIT_SELECTION_EXIT_HOME); - mOverlayManager.hideOverlay(isStarted()); + if (FeatureFlags.enableSplitContextually()) { + handleSplitAnimationGoingToHome(LAUNCHER_SPLIT_SELECTION_EXIT_HOME); + } + mOverlayManager.hideOverlay(isStarted() && !isForceInvisible()); handleGestureContract(intent); } else if (Intent.ACTION_ALL_APPS.equals(intent.getAction())) { showAllAppsFromIntent(alreadyOnHome); } else if (INTENT_ACTION_ALL_APPS_TOGGLE.equals(intent.getAction())) { - toggleAllApps(alreadyOnHome, true); + toggleAllAppsSearch(alreadyOnHome); } else if (Intent.ACTION_SHOW_WORK_APPS.equals(intent.getAction())) { showAllAppsWithSelectedTabFromIntent(alreadyOnHome, ActivityAllAppsContainerView.AdapterHolder.WORK); @@ -1702,20 +1734,19 @@ public class Launcher extends StatefulActivity TraceHelper.INSTANCE.endSection(); } - /** Handle animating away split placeholder view when user taps on home button */ + /** + * Handle animating away split placeholder view when user taps on home button + */ protected void handleSplitAnimationGoingToHome(EventEnum splitDismissReason) { // Overridden } - /** - * Toggles Launcher All Apps. - * @param focusSearch Indicates whether to make All Apps keyboard ready for search. - */ - public void toggleAllApps(boolean focusSearch) { - toggleAllApps(/* alreadyOnHome= */ true, focusSearch); + /** Toggles Launcher All Apps with keyboard ready for search. */ + public void toggleAllAppsSearch() { + toggleAllAppsSearch(/* alreadyOnHome= */ true); } - private void toggleAllApps(boolean alreadyOnHome, boolean focusSearch) { + protected void toggleAllAppsSearch(boolean alreadyOnHome) { if (getStateManager().isInStableState(ALL_APPS)) { getStateManager().goToState(NORMAL, alreadyOnHome); } else { @@ -1727,8 +1758,7 @@ public class Launcher extends StatefulActivity new AnimationSuccessListener() { @Override public void onAnimationSuccess(Animator animator) { - if (focusSearch - && mAppsView.getSearchUiManager().getEditText() != null) { + if (mAppsView.getSearchUiManager().getEditText() != null) { mAppsView.getSearchUiManager().getEditText().requestFocus(); } } @@ -1796,7 +1826,8 @@ public class Launcher extends StatefulActivity outState.remove(RUNTIME_STATE_WIDGET_PANEL); } - // We close any open folders and shortcut containers that are not safe for rebind, + // We close any open folders and shortcut containers that are not safe for + // rebind, // and we need to make sure this state is reflected. AbstractFloatingView.closeAllOpenViewsExcept( this, isStarted() && !isForceInvisible(), TYPE_REBIND_SAFE); @@ -1817,11 +1848,12 @@ public class Launcher extends StatefulActivity @Override public void onDestroy() { super.onDestroy(); - ACTIVITY_TRACKER.onContextDestroyed(this); + ACTIVITY_TRACKER.onActivityDestroyed(this); SettingsCache.INSTANCE.get(this).unregister(TOUCHPAD_NATURAL_SCROLLING, mNaturalScrollingChangedListener); ScreenOnTracker.INSTANCE.get(this).removeListener(mScreenOnListener); + mWorkspace.removeFolderListeners(); PluginManagerWrapper.INSTANCE.get(this).removePluginListener(this); mModel.removeCallbacks(this); @@ -1829,18 +1861,19 @@ public class Launcher extends StatefulActivity mAppWidgetHolder.stopListening(); mAppWidgetHolder.destroy(); - mWidgetPickerDataProvider.destroy(); TextKeyListener.getInstance().release(); mModelCallbacks.clearPendingBinds(); LauncherAppState.getIDP(this).removeOnChangeListener(this); - // if Launcher activity is recreated, {@link Window} including {@link ViewTreeObserver} - // could be preserved in {@link ActivityThread#scheduleRelaunchActivity(IBinder)} if the - // previous activity has not stopped, which could happen when wallpaper detects a color + // if Launcher activity is recreated, {@link Window} including {@link + // ViewTreeObserver} + // could be preserved in {@link + // ActivityThread#scheduleRelaunchActivity(IBinder)} if the + // previous activity has not stopped, which could happen when wallpaper detects + // a color // changes while launcher is still loading. getRootView().getViewTreeObserver().removeOnPreDrawListener(mOnInitialBindListener); - mOverlayManager.onActivityDestroyed(); - PillColorProvider.getInstance(mWorkspace.getContext()).unregisterObserver(); + mOverlayManager.onActivityDestroyed(this); } public LauncherAccessibilityDelegate getAccessibilityDelegate() { @@ -1887,8 +1920,10 @@ public class Launcher extends StatefulActivity } /** - * If FLAG_ENABLE_ADD_APP_WIDGET_VIA_CONFIG_ACTIVITY_V2 is enabled, we always add widget - * host view to workspace, otherwise we only add widget to host view if config activity is + * If FLAG_ENABLE_ADD_APP_WIDGET_VIA_CONFIG_ACTIVITY_V2 is enabled, we always + * add widget + * host view to workspace, otherwise we only add widget to host view if config + * activity is * not started. */ void addAppWidgetImpl(int appWidgetId, ItemInfo info, @@ -1900,33 +1935,31 @@ public class Launcher extends StatefulActivity return; } - // If FLAG_ENABLE_ADD_APP_WIDGET_VIA_CONFIG_ACTIVITY_V2 is enabled and config activity is - // started, we should remove the dropped AppWidgetHostView from drag layer and extract the - // Bitmap that shows the preview. Then pass the Bitmap to completeAddAppWidget() to create + // If FLAG_ENABLE_ADD_APP_WIDGET_VIA_CONFIG_ACTIVITY_V2 is enabled and config + // activity is + // started, we should remove the dropped AppWidgetHostView from drag layer and + // extract the + // Bitmap that shows the preview. Then pass the Bitmap to completeAddAppWidget() + // to create // a PendingWidgetHostView. Bitmap widgetPreviewBitmap = null; if (isActivityStarted) { DragView dropView = getDragLayer().clearAnimatedView(); if (dropView != null && dropView.containsAppWidgetHostView()) { - // Extracting Bitmap from dropView instead of its content view produces the correct + // Extracting Bitmap from dropView instead of its content view produces the + // correct // bitmap. - widgetPreviewBitmap = createHardwareBitmap( - dropView.getWidth(), dropView.getHeight(), dropView::draw); + widgetPreviewBitmap = getBitmapFromView(dropView); } } - // Exit spring loaded mode if necessary after adding the widget; unless config activity was - // started. - Runnable onComplete = MULTI_SELECT_EDIT_MODE.get() ? null : () -> mStateManager.goToState( - NORMAL, SPRING_LOADED_EXIT_DELAY); + // Exit spring loaded mode if necessary after adding the widget + Runnable onComplete = MULTI_SELECT_EDIT_MODE.get() ? null + : () -> mStateManager.goToState(NORMAL, SPRING_LOADED_EXIT_DELAY); completeAddAppWidget(appWidgetId, info, boundWidget, addFlowHandler.getProviderInfo(this), addFlowHandler.needsConfigure(), false, widgetPreviewBitmap); - // Remove extra screen if widget drop concluded. If a config activity was started, extra - // screen will be removed when we get back its result. - if (!isActivityStarted) { - mWorkspace.removeExtraEmptyScreenDelayed(delay, false, onComplete); - } + mWorkspace.removeExtraEmptyScreenDelayed(delay, false, onComplete); } public void addPendingItem(PendingAddItemInfo info, int container, int screenId, @@ -1936,7 +1969,7 @@ public class Launcher extends StatefulActivity info.screenId = modelPos.screenId; } else { CellPos modelPos = getCellPosMapper().mapPresenterToModel( - cell[0], cell[1], screenId, container); + cell[0], cell[1], screenId, container); info.screenId = modelPos.screenId; info.cellX = modelPos.cellX; info.cellY = modelPos.cellY; @@ -2042,14 +2075,16 @@ public class Launcher extends StatefulActivity // of centeredness. left = (grid.availableWidthPx - width) / 2; } else if (width >= bounds.width()) { - // If the folder doesn't fit within the bounds, center it about the desired bounds + // If the folder doesn't fit within the bounds, center it about the desired + // bounds left = bounds.left + (bounds.width() - width) / 2; } if (height >= bounds.height()) { // Folder height is greater than page height, center on page top = bounds.top + (bounds.height() - height) / 2; } else { - // Folder height is less than page height, so bound it to the absolute open folder + // Folder height is less than page height, so bound it to the absolute open + // folder // bounds if necessary Rect folderBounds = grid.getAbsoluteOpenFolderBounds(); left = Math.max(folderBounds.left, Math.min(left, folderBounds.right - width)); @@ -2060,10 +2095,11 @@ public class Launcher extends StatefulActivity } /** - * Unbinds the view for the specified item, and removes the item and all its children. + * Unbinds the view for the specified item, and removes the item and all its + * children. * - * @param v the view being removed. - * @param itemInfo the {@link ItemInfo} for this view. + * @param v the view being removed. + * @param itemInfo the {@link ItemInfo} for this view. * @param deleteFromDb whether or not to delete this item from the db. */ public boolean removeItem(View v, final ItemInfo itemInfo, boolean deleteFromDb) { @@ -2071,21 +2107,21 @@ public class Launcher extends StatefulActivity } /** - * Unbinds the view for the specified item, and removes the item and all its children. + * Unbinds the view for the specified item, and removes the item and all its + * children. * - * @param v the view being removed. - * @param itemInfo the {@link ItemInfo} for this view. + * @param v the view being removed. + * @param itemInfo the {@link ItemInfo} for this view. * @param deleteFromDb whether or not to delete this item from the db. - * @param reason the resaon for removal. + * @param reason the resaon for removal. */ public boolean removeItem(View v, final ItemInfo itemInfo, boolean deleteFromDb, @Nullable final String reason) { if (itemInfo instanceof WorkspaceItemInfo) { - View collectionIcon = mWorkspace.getViewByItemId(itemInfo.container); - if (collectionIcon instanceof FolderIcon folderIcon) { + View collectionIcon = mWorkspace.getHomescreenIconByItemId(itemInfo.container); + if (collectionIcon instanceof FolderIcon) { // Remove the shortcut from the folder before removing it from launcher - Folder folder = folderIcon.getFolder(); - folder.removeFolderContent(true, itemInfo); + ((FolderInfo) collectionIcon.getTag()).remove((WorkspaceItemInfo) itemInfo, true); } else if (collectionIcon instanceof AppPairIcon appPairIcon) { removeItem(appPairIcon, appPairIcon.getInfo(), deleteFromDb, "removing app pair because one of its member apps was removed"); @@ -2096,6 +2132,9 @@ public class Launcher extends StatefulActivity getModelWriter().deleteItemFromDatabase(itemInfo, reason); } } else if (itemInfo instanceof CollectionInfo ci) { + if (v instanceof FolderIcon) { + ((FolderIcon) v).removeListeners(); + } mWorkspace.removeWorkspaceItem(v); if (deleteFromDb) { getModelWriter().deleteCollectionAndContentsFromDatabase(ci); @@ -2138,7 +2177,7 @@ public class Launcher extends StatefulActivity @Override @TargetApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public void onBackPressed() { - getOnBackAnimationCallback().onBackInvoked(); + getOnBackPressedHandler().onBackInvoked(); } protected void onBackStarted() { @@ -2168,8 +2207,10 @@ public class Launcher extends StatefulActivity public RunnableList startActivitySafely(View v, Intent intent, ItemInfo item) { if (!hasBeenResumed()) { RunnableList result = new RunnableList(); - // Workaround an issue where the WM launch animation is clobbered when finishing the - // recents animation into launcher. Defer launching the activity until Launcher is + // Workaround an issue where the WM launch animation is clobbered when finishing + // the + // recents animation into launcher. Defer launching the activity until Launcher + // is // next resumed. addEventCallback(EVENT_RESUMED, () -> { RunnableList actualResult = startActivitySafely(v, intent, item); @@ -2188,13 +2229,15 @@ public class Launcher extends StatefulActivity RunnableList result = super.startActivitySafely(v, intent, item); if (result != null && v instanceof BubbleTextView) { - // This is set to the view that launched the activity that navigated the user away + // This is set to the view that launched the activity that navigated the user + // away // from launcher. Since there is no callback for when the activity has finished // launching, enable the press state and keep this reference to reset the press // state when we return to launcher. BubbleTextView btv = (BubbleTextView) v; btv.setStayPressed(true); result.add(() -> btv.setStayPressed(false)); + btv.resetIconScale(true); } return result; } @@ -2251,7 +2294,8 @@ public class Launcher extends StatefulActivity } /** - * Remove odd number because they are already included when isTwoPanels and add the pair screen + * Remove odd number because they are already included when isTwoPanels and add + * the pair screen * if not present. */ private IntArray filterTwoPanelScreenIds(IntArray orderedScreenIds) { @@ -2286,9 +2330,8 @@ public class Launcher extends StatefulActivity */ @Override public void bindItems(final List items, final boolean forceAnimateIcons) { - bindInflatedItems(items.stream() - .map(i -> Pair.create(i, getItemInflater().inflateItem(i, getModelWriter()))) - .collect(Collectors.toList()), + bindInflatedItems(items.stream().map(i -> Pair.create( + i, getItemInflater().inflateItem(i, getModelWriter()))).collect(Collectors.toList()), forceAnimateIcons ? new AnimatorSet() : null); } @@ -2300,7 +2343,8 @@ public class Launcher extends StatefulActivity /** * Bind all the items in the map, ignoring any null views * - * @param boundAnim if non-null, uses it to create and play the bounce animation for added views + * @param boundAnim if non-null, uses it to create and play the bounce animation + * for added views */ public void bindInflatedItems( List> shortcuts, @Nullable AnimatorSet boundAnim) { @@ -2316,8 +2360,7 @@ public class Launcher extends StatefulActivity if (item.container == CONTAINER_DESKTOP) { CellLayout cl = mWorkspace.getScreenWithId(presenterPos.screenId); if (cl != null && cl.isOccupied(presenterPos.cellX, presenterPos.cellY)) { - View occupiedView = cl.getChildAt(presenterPos.cellX, presenterPos.cellY); - Object tag = occupiedView == null ? null : occupiedView.getTag(); + Object tag = cl.getChildAt(presenterPos.cellX, presenterPos.cellY).getTag(); String desc = "Collision while binding workspace item: " + item + ". Collides with " + tag; if (FeatureFlags.IS_STUDIO_BUILD) { @@ -2425,6 +2468,10 @@ public class Launcher extends StatefulActivity .logEnd(LAUNCHER_LATENCY_STARTUP_TOTAL_DURATION) .log() .reset(); + if (mIsColdStartupAfterReboot) { + Trace.endAsyncSection(COLD_STARTUP_TRACE_METHOD_NAME, + COLD_STARTUP_TRACE_COOKIE); + } }); } @@ -2433,12 +2480,6 @@ public class Launcher extends StatefulActivity RunnableList onCompleteSignal, int workspaceItemCount, boolean isBindSync) { mModelCallbacks.onInitialBindComplete(boundPages, pendingTasks, onCompleteSignal, workspaceItemCount, isBindSync); - if (mIsColdStartupAfterReboot) { - if (ATLEAST_Q) { - Trace.endAsyncSection(COLD_STARTUP_TRACE_METHOD_NAME, - COLD_STARTUP_TRACE_COOKIE); - } - } } /** @@ -2454,31 +2495,58 @@ public class Launcher extends StatefulActivity if (mDragController.isDragging()) { return false; } else { - return (SystemClock.uptimeMillis() - mLastTouchUpTime) - > (NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS * 1000); + return (SystemClock.uptimeMillis() - mLastTouchUpTime) > (NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS + * 1000); } } /** - * Finds the first view on homescreen matching the provided parameters, optimized to finding a - * suitable view for the app close animation. + * Similar to {@link #getFirstMatch} but optimized to finding a suitable view for the app close + * animation. * * @param svi The StableViewInfo of the preferred item to match to if it exists or null * @param packageName The package name of the app to match. * @param user The user of the app to match. + * @param supportsAllAppsState If true and we are in All Apps state, looks for view in All Apps. + * Else we only looks on the workspace. */ - public @Nullable View getFirstHomeElementForAppClose( - @Nullable StableViewInfo svi, String packageName, UserHandle user) { + public @Nullable View getFirstMatchForAppClose( + @Nullable StableViewInfo svi, String packageName, + UserHandle user, boolean supportsAllAppsState) { final Predicate preferredItem = svi == null ? i -> false : svi::matches; - final Predicate packageAndUserAndApp = info -> info != null - && info.itemType == ITEM_TYPE_APPLICATION - && info.user.equals(user) - && TextUtils.equals(info.getTargetPackage(), packageName); + final Predicate packageAndUserAndApp = info -> + info != null + && info.itemType == ITEM_TYPE_APPLICATION + && info.user.equals(user) + && info.getTargetComponent() != null + && TextUtils.equals(info.getTargetComponent().getPackageName(), + packageName); + + if (supportsAllAppsState && isInState(LauncherState.ALL_APPS)) { + AllAppsRecyclerView activeRecyclerView = mAppsView.getActiveRecyclerView(); + View v = getFirstMatch(Collections.singletonList(activeRecyclerView), + preferredItem, packageAndUserAndApp); + + if (v != null && activeRecyclerView.computeVerticalScrollOffset() > 0) { + RectF locationBounds = new RectF(); + FloatingIconView.getLocationBoundsForView(this, v, false, locationBounds, + new Rect()); + if (locationBounds.top < mAppsView.getHeaderBottom()) { + // Icon is covered by scrim, return null to play fallback animation. + return null; + } + } + + return v; + } // Look for the item inside the folder at the current page Folder folder = Folder.getOpen(this); if (folder != null) { - View v = folder.getFirstMatch(preferredItem, packageAndUserAndApp); + View v = getFirstMatch(Collections.singletonList( + folder.getContent().getCurrentCellLayout().getShortcutsAndWidgets()), + preferredItem, + packageAndUserAndApp); if (v == null) { folder.close(isStarted() && !isForceInvisible()); } else { @@ -2486,19 +2554,71 @@ public class Launcher extends StatefulActivity } } - List containers = new ArrayList<>(mWorkspace.getPanelCount() + 1); - containers.add(mWorkspace.getHotseat()); - mWorkspace.forEachVisiblePage(page -> containers.add((CellLayout) page)); - CellLayout[] containerArray = containers.toArray(new CellLayout[0]); - LauncherBindableItemsContainer visibleContainer = - op -> mapOverCellLayouts(containerArray, op); + List containers = new ArrayList<>(mWorkspace.getPanelCount() + 1); + containers.add(mWorkspace.getHotseat().getShortcutsAndWidgets()); + mWorkspace.forEachVisiblePage(page + -> containers.add(((CellLayout) page).getShortcutsAndWidgets())); // Order: Preferred item by itself or in folder, then by matching package/user - return visibleContainer.getFirstMatch( - preferredItem, forFolderMatch(preferredItem), + return getFirstMatch(containers, preferredItem, forFolderMatch(preferredItem), packageAndUserAndApp, forFolderMatch(packageAndUserAndApp)); } + /** + * Finds the first view matching the ordered operators across the given viewgroups in order. + * @param containers List of ViewGroups to scan, in order of preference. + * @param operators List of operators, in order starting from best matching operator. + */ + @Nullable + private static View getFirstMatch(Iterable containers, + final Predicate... operators) { + for (Predicate operator : operators) { + for (ViewGroup container : containers) { + View match = mapOverViewGroup(container, operator); + if (match != null) { + return match; + } + } + } + return null; + } + + /** Convert a {@link View} to {@link Bitmap}. */ + private static Bitmap getBitmapFromView(@Nullable View view) { + if (view == null) { + return null; + } + Bitmap returnedBitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(returnedBitmap); + view.draw(canvas); + return returnedBitmap; + } + + /** + * Returns the first view matching the operator in the given ViewGroups, or null if none. + * Forward iteration matters. + */ + @Nullable + private static View mapOverViewGroup(ViewGroup container, Predicate op) { + final int itemCount = container.getChildCount(); + for (int itemIdx = 0; itemIdx < itemCount; itemIdx++) { + View item = container.getChildAt(itemIdx); + if (item.getVisibility() != View.VISIBLE) { + continue; + } + if (item instanceof ViewGroup viewGroup) { + View view = mapOverViewGroup(viewGroup, op); + if (view != null) { + return view; + } + } + if (item.getTag() instanceof ItemInfo itemInfo && op.test(itemInfo)) { + return item; + } + } + return null; + } + private ValueAnimator createNewAppBounceAnimation(View v, int i) { ValueAnimator bounceAnim = new PropertyListBuilder().alpha(1).scale(1).build(v) .setDuration(ItemInstallQueue.NEW_SHORTCUT_BOUNCE_DURATION); @@ -2507,8 +2627,13 @@ public class Launcher extends StatefulActivity return bounceAnim; } + private void announceForAccessibility(@StringRes int stringResId) { + getDragLayer().announceForAccessibility(getString(stringResId)); + } + /** - * Informs us that the overlay (-1 screen, typically), has either become visible or invisible. + * Informs us that the overlay (-1 screen, typically), has either become visible + * or invisible. */ public void onOverlayVisibilityChanged(boolean visible) { getStatsLogManager().logger() @@ -2522,10 +2647,12 @@ public class Launcher extends StatefulActivity } /** - * Informs us that the page transition has ended, so that we can react to the newly selected + * Informs us that the page transition has ended, so that we can react to the + * newly selected * page if we want to. */ - public void onPageEndTransition() {} + public void onPageEndTransition() { + } /** * See {@code LauncherBindingDelegate} @@ -2555,12 +2682,25 @@ public class Launcher extends StatefulActivity mModelCallbacks.bindIncrementalDownloadProgressUpdated(app); } + @Override + public void bindWidgetsRestored(ArrayList widgets) { + mModelCallbacks.bindWidgetsRestored(widgets); + } + /** * See {@code LauncherBindingDelegate} */ @Override - public void bindItemsUpdated(Set updates) { - mModelCallbacks.bindItemsUpdated(updates); + public void bindWorkspaceItemsChanged(List updated) { + mModelCallbacks.bindWorkspaceItemsChanged(updated); + } + + /** + * See {@code LauncherBindingDelegate} + */ + @Override + public void bindRestoreItemsChange(HashSet updates) { + mModelCallbacks.bindRestoreItemsChange(updates); } /** @@ -2575,7 +2715,7 @@ public class Launcher extends StatefulActivity * See {@code LauncherBindingDelegate} */ @Override - public void bindAllWidgets(@NonNull final List allWidgets) { + public void bindAllWidgets(final List allWidgets) { mModelCallbacks.bindAllWidgets(allWidgets); } @@ -2590,8 +2730,10 @@ public class Launcher extends StatefulActivity } /** - * @param packageUser if null, refreshes all widgets and shortcuts, otherwise only - * refreshes the widgets and shortcuts associated with the given package/user + * @param packageUser if null, refreshes all widgets and shortcuts, otherwise + * only + * refreshes the widgets and shortcuts associated with the + * given package/user */ public void refreshAndBindWidgetsForPackageUser(@Nullable PackageUserKey packageUser) { mModel.refreshAndBindWidgetsAndShortcuts(packageUser); @@ -2619,7 +2761,6 @@ public class Launcher extends StatefulActivity } writer.println(prefix + " Hotseat"); - mHotseat.dump(prefix, writer); ViewGroup layout = mHotseat.getShortcutsAndWidgets(); for (int j = 0; j < layout.getChildCount(); j++) { Object tag = layout.getChildAt(j).getTag(); @@ -2638,31 +2779,12 @@ public class Launcher extends StatefulActivity writer.println(prefix + "\tmAppWidgetHolder.isListening: " + mAppWidgetHolder.isListening()); - // b/349929393 - // The only way to reproduce this bug is to ensure that onLayout never gets called. This - // theoretically is impossible, so these logs are being added to test if that actually is - // what is happening. - writer.println(prefix + "\tmWorkspace.mHasOnLayoutBeenCalled=" - + mWorkspace.mHasOnLayoutBeenCalled); - for (int i = 0; i < mWorkspace.getPageCount(); i++) { - CellLayout cellLayout = (CellLayout) mWorkspace.getPageAt(i); - writer.println(prefix + "\tcellLayout." + i + ".mHasOnLayoutBeenCalled=" - + cellLayout.mHasOnLayoutBeenCalled); - writer.println(prefix + "\tshortcutAndWidgetContainer." + i + ".mHasOnLayoutBeenCalled=" - + cellLayout.getShortcutsAndWidgets().mHasOnLayoutBeenCalled); - } - // Extra logging for general debugging mDragLayer.dump(prefix, writer); mStateManager.dump(prefix, writer); mPopupDataProvider.dump(prefix, writer); - mWidgetPickerDataProvider.dump(prefix, writer); mDeviceProfile.dump(this, prefix, writer); mAppsView.getAppsStore().dump(prefix, writer); - mAppsView.getPersonalAppList().dump(prefix, writer); - if (mAppsView.shouldShowTabs()) { - mAppsView.getWorkAppList().dump(prefix, writer); - } try { FileLog.flushAll(writer); @@ -2673,15 +2795,16 @@ public class Launcher extends StatefulActivity mModel.dumpState(prefix, fd, writer, args); mOverlayManager.dump(prefix, writer); ACTIVITY_TRACKER.dump(prefix, writer); - MSDLPlayerWrapper.INSTANCE.get(getApplicationContext()).dump(prefix, writer); } /** - * Populates the list of shortcuts. Logic delegated to {@Link KeyboardShortcutsDelegate}. + * Populates the list of shortcuts. Logic delegated to + * {@Link KeyboardShortcutsDelegate}. * - * @param data The data list to populate with shortcuts. - * @param menu The current menu, which may be null. - * @param deviceId The id for the connected device the shortcuts should be provided for. + * @param data The data list to populate with shortcuts. + * @param menu The current menu, which may be null. + * @param deviceId The id for the connected device the shortcuts should be + * provided for. */ @Override public void onProvideKeyboardShortcuts( @@ -2692,8 +2815,9 @@ public class Launcher extends StatefulActivity /** * Logic delegated to {@Link KeyboardShortcutsDelegate}. + * * @param keyCode The value in event.getKeyCode(). - * @param event Description of the key event. + * @param event Description of the key event. */ @Override public boolean onKeyShortcut(int keyCode, KeyEvent event) { @@ -2703,8 +2827,9 @@ public class Launcher extends StatefulActivity /** * Logic delegated to {@Link KeyboardShortcutsDelegate}. + * * @param keyCode The value in event.getKeyCode(). - * @param event Description of the key event. + * @param event Description of the key event. */ @Override public boolean onKeyDown(int keyCode, KeyEvent event) { @@ -2714,8 +2839,9 @@ public class Launcher extends StatefulActivity /** * Logic delegated to {@Link KeyboardShortcutsDelegate}. + * * @param keyCode The value in event.getKeyCode(). - * @param event Description of the key event. + * @param event Description of the key event. */ @Override public boolean onKeyUp(int keyCode, KeyEvent event) { @@ -2738,13 +2864,13 @@ public class Launcher extends StatefulActivity } @Override - public void collectStateHandlers(List> out) { + public void collectStateHandlers(List> out) { out.add(getAllAppsController()); out.add(getWorkspace()); } public TouchController[] createTouchControllers() { - return new TouchController[] {getDragController(), new AllAppsSwipeController(this)}; + return new TouchController[] { getDragController(), new AllAppsSwipeController(this) }; } public void onDragLayerHierarchyChanged() { @@ -2760,16 +2886,10 @@ public class Launcher extends StatefulActivity } private void updateDisallowBack() { - try { - if (BuildCompat.isAtLeastV() - && Flags.enableDesktopWindowingMode() - && !Flags.enableDesktopWindowingWallpaperActivity() + if (BuildCompat.isAtLeastV() && Flags.enableDesktopWindowingMode() && mDeviceProfile.isTablet) { - // TODO(b/333533253): Clean up after desktop wallpaper activity flag is rolled out - return; - } - } catch (Throwable t) { - // LC-Ignored + // TODO(b/330183377) disable back in launcher when when we productionize + return; } LauncherRootView rv = getRootView(); if (rv != null) { @@ -2793,13 +2913,21 @@ public class Launcher extends StatefulActivity } /** - * Callback for when launcher state transition completes after user swipes to home. + * Callback for when launcher state transition completes after user swipes to + * home. + * * @param finalState The final state of the transition. */ public void onStateTransitionCompletedAfterSwipeToHome(LauncherState finalState) { // Overridden } + @Override + public void returnToHomescreen() { + super.returnToHomescreen(); + getStateManager().goToState(LauncherState.NORMAL); + } + public void closeOpenViews() { closeOpenViews(true); } @@ -2814,8 +2942,8 @@ public class Launcher extends StatefulActivity /** Enables/disabled the hotseat prediction icon long press edu for testing. */ @VisibleForTesting - public void enableHotseatEdu(boolean enable) {} - + public void enableHotseatEdu(boolean enable) { + } /** * Just a wrapper around the type cast to allow easier tracking of calls. @@ -2831,7 +2959,8 @@ public class Launcher extends StatefulActivity /** * Animates Launcher elements during a transition to the All Apps page. * - * @param progress Transition progress from 0 to 1; where 0 => home and 1 => all apps. + * @param progress Transition progress from 0 to 1; where 0 => home and 1 => all + * apps. */ public void onAllAppsTransition(float progress) { // No-Op @@ -2840,7 +2969,8 @@ public class Launcher extends StatefulActivity /** * Animates Launcher elements during a transition to the Widgets pages. * - * @param progress Transition progress from 0 to 1; where 0 => home and 1 => widgets. + * @param progress Transition progress from 0 to 1; where 0 => home and 1 => + * widgets. */ public void onWidgetsTransition(float progress) { float scale = Utilities.mapToRange(progress, 0f, 1f, 1f, @@ -2853,7 +2983,9 @@ public class Launcher extends StatefulActivity public Configuration config; } - /** Pauses view updates that should not be run during the app launch animation. */ + /** + * Pauses view updates that should not be run during the app launch animation. + */ public void pauseExpensiveViewUpdates() { // Pause page indicator animations as they lead to layer trashing. getWorkspace().getPageIndicator().pauseAnimations(); @@ -2896,6 +3028,11 @@ public class Launcher extends StatefulActivity return mModelCallbacks.getWorkspaceLoading(); } + @Override + public boolean isBindingItems() { + return isWorkspaceLoading(); + } + /** * Returns true if a touch interaction is in progress */ @@ -2904,7 +3041,8 @@ public class Launcher extends StatefulActivity } public boolean isDraggingEnabled() { - // We prevent dragging when we are loading the workspace as it is possible to pick up a view + // We prevent dragging when we are loading the workspace as it is possible to + // pick up a view // that is subsequently removed from the workspace in startBinding(). return !isWorkspaceLoading(); } @@ -2925,7 +3063,8 @@ public class Launcher extends StatefulActivity } /** - * Persistent callback which notifies when an activity launch is deferred because the activity + * Persistent callback which notifies when an activity launch is deferred + * because the activity * was not yet resumed. */ public void setOnDeferredActivityLaunchCallback(Runnable callback) { @@ -2934,6 +3073,7 @@ public class Launcher extends StatefulActivity /** * Sets the next pages to bind synchronously on next bind. + * * @param pages should not be null. */ public void setPagesToBindSynchronously(@NonNull IntSet pages) { @@ -2964,13 +3104,11 @@ public class Launcher extends StatefulActivity return mPopupDataProvider; } - @NonNull @Override - public WidgetPickerDataProvider getWidgetPickerDataProvider() { - return mWidgetPickerDataProvider; + public DotInfo getDotInfoForItem(ItemInfo info) { + return mPopupDataProvider.getDotInfoForItem(info); } - @NonNull public LauncherOverlayManager getOverlayManager() { return mOverlayManager; } @@ -2984,12 +3122,6 @@ public class Launcher extends StatefulActivity return mDragLayer; } - @NonNull - @Override - public LauncherBindableItemsContainer getContent() { - return mWorkspace; - } - @Override public ActivityAllAppsContainerView getAppsView() { return mAppsView; @@ -3025,7 +3157,8 @@ public class Launcher extends StatefulActivity } /** - * Returns the ModelWriter writer, make sure to call the function every time you want to use it. + * Returns the ModelWriter writer, make sure to call the function every time you + * want to use it. */ public ModelWriter getModelWriter() { return mModelWriter; @@ -3046,7 +3179,8 @@ public class Launcher extends StatefulActivity */ public CellLayout getCellLayout(int container, int screenId) { return (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) - ? mHotseat : mWorkspace.getScreenWithId(screenId); + ? mHotseat + : mWorkspace.getScreenWithId(screenId); } @Override @@ -3074,7 +3208,7 @@ public class Launcher extends StatefulActivity * @see LauncherState#getOverviewScaleAndOffset(Launcher) */ public float[] getNormalOverviewScaleAndOffset() { - return new float[] {NO_SCALE, NO_OFFSET}; + return new float[] { NO_SCALE, NO_OFFSET }; } /** @@ -3120,10 +3254,5 @@ public class Launcher extends StatefulActivity return findViewById(R.id.popup_container); } - @Override - public OnClickListener getItemOnClickListener() { - return ItemClickHandler.INSTANCE; - } - // End of Getters and Setters } diff --git a/src/com/android/launcher3/LauncherApplication.java b/src/com/android/launcher3/LauncherApplication.java index 03eaeea178..40873be369 100644 --- a/src/com/android/launcher3/LauncherApplication.java +++ b/src/com/android/launcher3/LauncherApplication.java @@ -17,51 +17,14 @@ package com.android.launcher3; import android.app.Application; -import com.android.launcher3.dagger.DaggerLauncherAppComponent; -import com.android.launcher3.dagger.LauncherAppComponent; -import com.android.launcher3.dagger.LauncherBaseAppComponent; -import com.android.launcher3.util.TraceHelper; - /** * Main application class for Launcher */ public class LauncherApplication extends Application { - private volatile LauncherBaseAppComponent mAppComponent; @Override public void onCreate() { super.onCreate(); MainProcessInitializer.initialize(this); } - - public LauncherAppComponent getAppComponent() { - if (mAppComponent == null) { - synchronized (this) { - // Check for null again, as it may have been assigned on a different thread. This - // avoids holding synchronization locks everytime. - if (mAppComponent == null) { - // Initialize the dagger component on demand as content providers can get - // accessed before the Launcher application (b/36917845#comment4) - initDaggerComponent(DaggerLauncherAppComponent.builder() - .iconsDbName(LauncherFiles.APP_ICONS_DB)); - } - } - } - // Since supertype setters will return a supertype.builder and @Component.Builder types - // must not have any generic types. - // We need to cast mAppComponent to {@link LauncherAppComponent} since appContext() - // method is defined in the super class LauncherBaseComponent#Builder. - return (LauncherAppComponent) mAppComponent; - } - - /** - * Init with the desired dagger component. - */ - public void initDaggerComponent(LauncherBaseAppComponent.Builder componentBuilder) { - mAppComponent = componentBuilder - .appContext(this) - .setSafeModeEnabled(TraceHelper.allowIpcs( - "isSafeMode", () -> getPackageManager().isSafeMode())) - .build(); - } } diff --git a/src/com/android/launcher3/LauncherBackupAgent.java b/src/com/android/launcher3/LauncherBackupAgent.java index a96495de83..2617b9337d 100644 --- a/src/com/android/launcher3/LauncherBackupAgent.java +++ b/src/com/android/launcher3/LauncherBackupAgent.java @@ -1,7 +1,5 @@ package com.android.launcher3; -import static com.android.launcher3.LauncherPrefs.NO_DB_FILES_RESTORED; - import android.app.backup.BackupAgent; import android.app.backup.BackupDataInput; import android.app.backup.BackupDataOutput; @@ -12,13 +10,10 @@ import com.android.launcher3.provider.RestoreDbTask; import java.io.File; import java.io.IOException; -import java.util.Arrays; -import java.util.stream.Collectors; public class LauncherBackupAgent extends BackupAgent { + private static final String TAG = "LauncherBackupAgent"; - private static final String DB_FILE_PREFIX = "launcher"; - private static final String DB_FILE_SUFFIX = ".db"; @Override public void onCreate() { @@ -52,34 +47,7 @@ public class LauncherBackupAgent extends BackupAgent { @Override public void onRestoreFinished() { - RestoreDbTask.setPending(this); FileLog.d(TAG, "onRestoreFinished: set pending for RestoreDbTask"); - markIfFilesWereNotActuallyRestored(); - } - - /** - * When restore is finished, we check to see if any db files were successfully restored. If not, - * our restore will fail later, but will report a different cause. This is important to split - * out the metric failures that are launcher's fault, and those that are due to bugs in the - * backup/restore code itself. - */ - private void markIfFilesWereNotActuallyRestored() { - File directory = new File(getDatabasePath(InvariantDeviceProfile.INSTANCE.get(this).dbFile) - .getParent()); - if (!directory.exists()) { - FileLog.e(TAG, "restore failed as target database directory doesn't exist"); - } else { - // Check for any db file that was restored, and collect as list - String fileNames = Arrays.stream(directory.listFiles()) - .map(File::getName) - .filter(n -> n.startsWith(DB_FILE_PREFIX) && n.endsWith(DB_FILE_SUFFIX)) - .collect(Collectors.joining(", ")); - if (fileNames.isBlank()) { - FileLog.e(TAG, "no database files were successfully restored"); - LauncherPrefs.get(this).putSync(NO_DB_FILES_RESTORED.to(true)); - } else { - FileLog.d(TAG, "database files successfully restored: " + fileNames); - } - } + RestoreDbTask.setPending(this); } } diff --git a/src/com/android/launcher3/LauncherConstants.java b/src/com/android/launcher3/LauncherConstants.java index 5cba82fc3d..445fb4134c 100644 --- a/src/com/android/launcher3/LauncherConstants.java +++ b/src/com/android/launcher3/LauncherConstants.java @@ -67,8 +67,5 @@ public class LauncherConstants { static final String RUNTIME_STATE_WIDGET_PANEL = "launcher.widget_panel"; // Type int[] static final String RUNTIME_STATE_CURRENT_SCREEN_IDS = "launcher.current_screen_ids"; - // Type: boolean - public static final String RUNTIME_STATE_RECREATE_TO_UPDATE_THEME = - "launcher.recreate_to_update_theme"; } } diff --git a/src/com/android/launcher3/LauncherFiles.java b/src/com/android/launcher3/LauncherFiles.java index 484fe07818..d730cea804 100644 --- a/src/com/android/launcher3/LauncherFiles.java +++ b/src/com/android/launcher3/LauncherFiles.java @@ -16,17 +16,11 @@ public class LauncherFiles { private static final String XML = ".xml"; public static final String LAUNCHER_DB = "launcher.db"; - public static final String LAUNCHER_5_BY_8_DB = "launcher_5_by_8.db"; public static final String LAUNCHER_6_BY_5_DB = "launcher_6_by_5.db"; public static final String LAUNCHER_4_BY_5_DB = "launcher_4_by_5.db"; - public static final String LAUNCHER_4_BY_6_DB = "launcher_4_by_6.db"; - public static final String LAUNCHER_4_BY_7_DB = "launcher_4_by_7.db"; - public static final String LAUNCHER_5_BY_6_DB = "launcher_5_by_6.db"; public static final String LAUNCHER_4_BY_4_DB = "launcher_4_by_4.db"; public static final String LAUNCHER_3_BY_3_DB = "launcher_3_by_3.db"; public static final String LAUNCHER_2_BY_2_DB = "launcher_2_by_2.db"; - public static final String LAUNCHER_7_BY_3_DB = "launcher_7_by_3.db"; - public static final String LAUNCHER_8_BY_3_DB = "launcher_8_by_3.db"; public static final String BACKUP_DB = "backup.db"; public static final String SHARED_PREFERENCES_KEY = "com.android.launcher3.prefs"; public static final String MANAGED_USER_PREFERENCES_KEY = @@ -39,17 +33,11 @@ public class LauncherFiles { public static final List GRID_DB_FILES = Collections.unmodifiableList(Arrays.asList( LAUNCHER_DB, - LAUNCHER_5_BY_8_DB, LAUNCHER_6_BY_5_DB, LAUNCHER_4_BY_5_DB, - LAUNCHER_4_BY_6_DB, - LAUNCHER_4_BY_7_DB, - LAUNCHER_5_BY_6_DB, LAUNCHER_4_BY_4_DB, LAUNCHER_3_BY_3_DB, - LAUNCHER_2_BY_2_DB, - LAUNCHER_7_BY_3_DB, - LAUNCHER_8_BY_3_DB)); + LAUNCHER_2_BY_2_DB)); public static final List OTHER_FILES = Collections.unmodifiableList(Arrays.asList( BACKUP_DB, diff --git a/src/com/android/launcher3/LauncherPrefs.kt b/src/com/android/launcher3/LauncherPrefs.kt index c8e9e26e7e..dbba548dfc 100644 --- a/src/com/android/launcher3/LauncherPrefs.kt +++ b/src/com/android/launcher3/LauncherPrefs.kt @@ -18,54 +18,46 @@ package com.android.launcher3 import android.content.Context import android.content.Context.MODE_PRIVATE import android.content.SharedPreferences +import android.content.SharedPreferences.OnSharedPreferenceChangeListener import androidx.annotation.VisibleForTesting import com.android.launcher3.BuildConfigs.WIDGET_ON_FIRST_SCREEN -import com.android.launcher3.GridType.Companion.GRID_TYPE_ANY -import com.android.launcher3.InvariantDeviceProfile.GRID_NAME_PREFS_KEY -import com.android.launcher3.InvariantDeviceProfile.NON_FIXED_LANDSCAPE_GRID_NAME_PREFS_KEY import com.android.launcher3.LauncherFiles.DEVICE_PREFERENCES_KEY import com.android.launcher3.LauncherFiles.SHARED_PREFERENCES_KEY -import com.android.launcher3.dagger.ApplicationContext -import com.android.launcher3.dagger.LauncherAppComponent -import com.android.launcher3.dagger.LauncherAppSingleton import com.android.launcher3.model.DeviceGridState import com.android.launcher3.pm.InstallSessionHelper import com.android.launcher3.provider.RestoreDbTask import com.android.launcher3.provider.RestoreDbTask.FIRST_LOAD_AFTER_RESTORE_KEY -import com.android.launcher3.settings.SettingsActivity import com.android.launcher3.states.RotationHelper -import com.android.launcher3.util.DaggerSingletonObject import com.android.launcher3.util.DisplayController -import java.util.concurrent.ConcurrentHashMap -import javax.inject.Inject +import com.android.launcher3.util.MainThreadInitializedObject +import com.android.launcher3.util.SafeCloseable +import com.android.launcher3.util.Themes /** - * Manages Launcher [SharedPreferences] through [Item] instances. + * Use same context for shared preferences, so that we use a single cached instance * * TODO(b/262721340): Replace all direct SharedPreference refs with LauncherPrefs / Item methods. */ -@LauncherAppSingleton -open class LauncherPrefs -@Inject -constructor(@ApplicationContext private val encryptedContext: Context) { +class LauncherPrefs(private val encryptedContext: Context) : SafeCloseable { + private val deviceProtectedStorageContext = + encryptedContext.createDeviceProtectedStorageContext() - private val deviceProtectedSharedPrefs: SharedPreferences by lazy { - encryptedContext - .createDeviceProtectedStorageContext() - .getSharedPreferences(BOOT_AWARE_PREFS_KEY, MODE_PRIVATE) - } + private val bootAwarePrefs + get() = + deviceProtectedStorageContext.getSharedPreferences(BOOT_AWARE_PREFS_KEY, MODE_PRIVATE) - open protected fun getSharedPrefs(item: Item): SharedPreferences = - item.run { - if (encryptionType == EncryptionType.DEVICE_PROTECTED) deviceProtectedSharedPrefs - else encryptedContext.getSharedPreferences(sharedPrefFile, MODE_PRIVATE) - } + private val Item.encryptedPrefs + get() = encryptedContext.getSharedPreferences(sharedPrefFile, MODE_PRIVATE) - /** Returns the value with type [T] for [item]. */ + private fun chooseSharedPreferences(item: Item): SharedPreferences = + if (item.encryptionType == EncryptionType.DEVICE_PROTECTED) bootAwarePrefs + else item.encryptedPrefs + + /** Wrapper around `getInner` for a `ContextualItem` */ fun get(item: ContextualItem): T = getInner(item, item.defaultValueFromContext(encryptedContext)) - /** Returns the value with type [T] for [item]. */ + /** Wrapper around `getInner` for an `Item` */ fun get(item: ConstantItem): T = getInner(item, item.defaultValue) /** @@ -75,19 +67,19 @@ constructor(@ApplicationContext private val encryptedContext: Context) { */ @Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST") private fun getInner(item: Item, default: T): T { - val sp = getSharedPrefs(item) - return when { - item.type == String::class.java -> sp.getString(item.sharedPrefKey, default as? String) - item.type == Boolean::class.java || item.type == java.lang.Boolean::class.java -> - sp.getBoolean(item.sharedPrefKey, default as Boolean) - item.type == Int::class.java || item.type == java.lang.Integer::class.java -> - sp.getInt(item.sharedPrefKey, default as Int) - item.type == Float::class.java || item.type == java.lang.Float::class.java -> - sp.getFloat(item.sharedPrefKey, default as Float) - item.type == Long::class.java || item.type == java.lang.Long::class.java -> - sp.getLong(item.sharedPrefKey, default as Long) - Set::class.java.isAssignableFrom(item.type) -> - sp.getStringSet(item.sharedPrefKey, default as? Set) + val sp = chooseSharedPreferences(item) + + return when (item.type) { + String::class.java -> sp.getString(item.sharedPrefKey, default as? String) + Boolean::class.java, + java.lang.Boolean::class.java -> sp.getBoolean(item.sharedPrefKey, default as Boolean) + Int::class.java, + java.lang.Integer::class.java -> sp.getInt(item.sharedPrefKey, default as Int) + Float::class.java, + java.lang.Float::class.java -> sp.getFloat(item.sharedPrefKey, default as Float) + Long::class.java, + java.lang.Long::class.java -> sp.getLong(item.sharedPrefKey, default as Long) + Set::class.java -> sp.getStringSet(item.sharedPrefKey, default as? Set) else -> throw IllegalArgumentException( "item type: ${item.type}" + " is not compatible with sharedPref methods" @@ -131,10 +123,24 @@ constructor(@ApplicationContext private val encryptedContext: Context) { private fun prepareToPutValues( updates: Array> ): List { - val updatesPerPrefFile = updates.groupBy { getSharedPrefs(it.first) }.toMap() + val updatesPerPrefFile = + updates + .filter { it.first.encryptionType != EncryptionType.DEVICE_PROTECTED } + .groupBy { it.first.encryptedPrefs } + .toMutableMap() - return updatesPerPrefFile.map { (sharedPref, itemList) -> - sharedPref.edit().apply { itemList.forEach { (item, value) -> putValue(item, value) } } + val bootAwareUpdates = + updates.filter { it.first.encryptionType == EncryptionType.DEVICE_PROTECTED } + if (bootAwareUpdates.isNotEmpty()) { + updatesPerPrefFile[bootAwarePrefs] = bootAwareUpdates + } + + return updatesPerPrefFile.map { prefToItemValueList -> + prefToItemValueList.key.edit().apply { + prefToItemValueList.value.forEach { itemToValue: Pair -> + putValue(itemToValue.first, itemToValue.second) + } + } } } @@ -144,22 +150,21 @@ constructor(@ApplicationContext private val encryptedContext: Context) { * types of Item values. */ @Suppress("UNCHECKED_CAST") - internal fun SharedPreferences.Editor.putValue( + private fun SharedPreferences.Editor.putValue( item: Item, - value: Any?, + value: Any? ): SharedPreferences.Editor = - when { - item.type == String::class.java -> putString(item.sharedPrefKey, value as? String) - item.type == Boolean::class.java || item.type == java.lang.Boolean::class.java -> - putBoolean(item.sharedPrefKey, value as Boolean) - item.type == Int::class.java || item.type == java.lang.Integer::class.java -> - putInt(item.sharedPrefKey, value as Int) - item.type == Float::class.java || item.type == java.lang.Float::class.java -> - putFloat(item.sharedPrefKey, value as Float) - item.type == Long::class.java || item.type == java.lang.Long::class.java -> - putLong(item.sharedPrefKey, value as Long) - Set::class.java.isAssignableFrom(item.type) -> - putStringSet(item.sharedPrefKey, value as? Set) + when (item.type) { + String::class.java -> putString(item.sharedPrefKey, value as? String) + Boolean::class.java, + java.lang.Boolean::class.java -> putBoolean(item.sharedPrefKey, value as Boolean) + Int::class.java, + java.lang.Integer::class.java -> putInt(item.sharedPrefKey, value as Int) + Float::class.java, + java.lang.Float::class.java -> putFloat(item.sharedPrefKey, value as Float) + Long::class.java, + java.lang.Long::class.java -> putLong(item.sharedPrefKey, value as Long) + Set::class.java -> putStringSet(item.sharedPrefKey, value as? Set) else -> throw IllegalArgumentException( "item type: ${item.type} is not compatible with sharedPref methods" @@ -171,9 +176,9 @@ constructor(@ApplicationContext private val encryptedContext: Context) { * `SharedPreferences` files associated with the provided list of items. The listener will need * to filter update notifications so they don't activate for non-relevant updates. */ - fun addListener(listener: LauncherPrefChangeListener, vararg items: Item) { + fun addListener(listener: OnSharedPreferenceChangeListener, vararg items: Item) { items - .map { getSharedPrefs(it) } + .map { chooseSharedPreferences(it) } .distinct() .forEach { it.registerOnSharedPreferenceChangeListener(listener) } } @@ -182,10 +187,10 @@ constructor(@ApplicationContext private val encryptedContext: Context) { * Stops the listener from getting notified of any more updates to any of the * `SharedPreferences` files associated with any of the provided list of [Item]. */ - fun removeListener(listener: LauncherPrefChangeListener, vararg items: Item) { + fun removeListener(listener: OnSharedPreferenceChangeListener, vararg items: Item) { // If a listener is not registered to a SharedPreference, unregistering it does nothing items - .map { getSharedPrefs(it) } + .map { chooseSharedPreferences(it) } .distinct() .forEach { it.unregisterOnSharedPreferenceChangeListener(listener) } } @@ -196,7 +201,7 @@ constructor(@ApplicationContext private val encryptedContext: Context) { */ fun has(vararg items: Item): Boolean { items - .groupBy { getSharedPrefs(it) } + .groupBy { chooseSharedPreferences(it) } .forEach { (prefs, itemsSublist) -> if (!itemsSublist.none { !prefs.contains(it.sharedPrefKey) }) return false } @@ -220,7 +225,16 @@ constructor(@ApplicationContext private val encryptedContext: Context) { * .apply() or .commit() */ private fun prepareToRemove(items: Array): List { - val itemsPerFile = items.groupBy { getSharedPrefs(it) }.toMap() + val itemsPerFile = + items + .filter { it.encryptionType != EncryptionType.DEVICE_PROTECTED } + .groupBy { it.encryptedPrefs } + .toMutableMap() + + val bootAwareUpdates = items.filter { it.encryptionType == EncryptionType.DEVICE_PROTECTED } + if (bootAwareUpdates.isNotEmpty()) { + itemsPerFile[bootAwarePrefs] = bootAwareUpdates + } return itemsPerFile.map { (prefs, items) -> prefs.edit().also { editor -> @@ -229,19 +243,25 @@ constructor(@ApplicationContext private val encryptedContext: Context) { } } + override fun close() {} + companion object { @VisibleForTesting const val BOOT_AWARE_PREFS_KEY = "boot_aware_prefs" - @JvmField val INSTANCE = DaggerSingletonObject(LauncherAppComponent::getLauncherPrefs) + @JvmField var INSTANCE = MainThreadInitializedObject { LauncherPrefs(it) } @JvmStatic fun get(context: Context): LauncherPrefs = INSTANCE.get(context) const val TASKBAR_PINNING_KEY = "TASKBAR_PINNING_KEY" const val TASKBAR_PINNING_DESKTOP_MODE_KEY = "TASKBAR_PINNING_DESKTOP_MODE_KEY" const val SHOULD_SHOW_SMARTSPACE_KEY = "SHOULD_SHOW_SMARTSPACE_KEY" + @JvmField + val ICON_STATE = nonRestorableItem("pref_icon_shape_path", "", EncryptionType.ENCRYPTED) @JvmField val ENABLE_TWOLINE_ALLAPPS_TOGGLE = backedUpItem("pref_enable_two_line_toggle", false) + @JvmField + val THEMED_ICONS = backedUpItem(Themes.KEY_THEMED_ICONS, false, EncryptionType.ENCRYPTED) @JvmField val PROMISE_ICON_IDS = backedUpItem(InstallSessionHelper.PROMISE_ICON_IDS, "") @JvmField val WORK_EDU_STEP = backedUpItem("showed_work_profile_edu", 0) @JvmField @@ -262,44 +282,37 @@ constructor(@ApplicationContext private val encryptedContext: Context) { backedUpItem( DeviceGridState.KEY_DEVICE_TYPE, InvariantDeviceProfile.TYPE_PHONE, - EncryptionType.ENCRYPTED, + EncryptionType.ENCRYPTED ) @JvmField val DB_FILE = backedUpItem(DeviceGridState.KEY_DB_FILE, "", EncryptionType.ENCRYPTED) @JvmField - val GRID_TYPE = - backedUpItem(DeviceGridState.KEY_GRID_TYPE, GRID_TYPE_ANY, EncryptionType.ENCRYPTED) - @JvmField val SHOULD_SHOW_SMARTSPACE = backedUpItem( SHOULD_SHOW_SMARTSPACE_KEY, WIDGET_ON_FIRST_SCREEN, - EncryptionType.DEVICE_PROTECTED, + EncryptionType.DEVICE_PROTECTED ) @JvmField val RESTORE_DEVICE = backedUpItem( RestoreDbTask.RESTORED_DEVICE_TYPE, InvariantDeviceProfile.TYPE_PHONE, - EncryptionType.ENCRYPTED, + EncryptionType.ENCRYPTED ) @JvmField - val NO_DB_FILES_RESTORED = - nonRestorableItem("no_db_files_restored", false, EncryptionType.DEVICE_PROTECTED) - @JvmField val IS_FIRST_LOAD_AFTER_RESTORE = nonRestorableItem(FIRST_LOAD_AFTER_RESTORE_KEY, false, EncryptionType.ENCRYPTED) @JvmField val APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_IDS, "") @JvmField val OLD_APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_OLD_IDS, "") - @JvmField val GRID_NAME = ConstantItem( - GRID_NAME_PREFS_KEY, + "idp_grid_name", isBackedUp = true, defaultValue = null, encryptionType = EncryptionType.ENCRYPTED, - type = String::class.java, + type = String::class.java ) @JvmField val ALLOW_ROTATION = @@ -307,19 +320,6 @@ constructor(@ApplicationContext private val encryptedContext: Context) { RotationHelper.getAllowRotationDefaultValue(DisplayController.INSTANCE.get(it).info) } - @JvmField - val FIXED_LANDSCAPE_MODE = backedUpItem(SettingsActivity.FIXED_LANDSCAPE_MODE, false) - - @JvmField - val NON_FIXED_LANDSCAPE_GRID_NAME = - ConstantItem( - NON_FIXED_LANDSCAPE_GRID_NAME_PREFS_KEY, - isBackedUp = true, - defaultValue = null, - encryptionType = EncryptionType.ENCRYPTED, - type = String::class.java, - ) - // Preferences for widget configurations @JvmField val RECONFIGURABLE_WIDGET_EDUCATION_TIP_SEEN = @@ -329,7 +329,7 @@ constructor(@ApplicationContext private val encryptedContext: Context) { fun backedUpItem( sharedPrefKey: String, defaultValue: T, - encryptionType: EncryptionType = EncryptionType.ENCRYPTED, + encryptionType: EncryptionType = EncryptionType.ENCRYPTED ): ConstantItem = ConstantItem(sharedPrefKey, isBackedUp = true, defaultValue, encryptionType) @@ -338,21 +338,21 @@ constructor(@ApplicationContext private val encryptedContext: Context) { sharedPrefKey: String, type: Class, encryptionType: EncryptionType = EncryptionType.ENCRYPTED, - defaultValueFromContext: (c: Context) -> T, + defaultValueFromContext: (c: Context) -> T ): ContextualItem = ContextualItem( sharedPrefKey, isBackedUp = true, defaultValueFromContext, encryptionType, - type, + type ) @JvmStatic fun nonRestorableItem( sharedPrefKey: String, defaultValue: T, - encryptionType: EncryptionType = EncryptionType.ENCRYPTED, + encryptionType: EncryptionType = EncryptionType.ENCRYPTED ): ConstantItem = ConstantItem(sharedPrefKey, isBackedUp = false, defaultValue, encryptionType) @@ -362,7 +362,7 @@ constructor(@ApplicationContext private val encryptedContext: Context) { // Use application context for shared preferences, so we use single cached instance return context.applicationContext.getSharedPreferences( SHARED_PREFERENCES_KEY, - MODE_PRIVATE, + MODE_PRIVATE ) } @@ -372,7 +372,7 @@ constructor(@ApplicationContext private val encryptedContext: Context) { // Use application context for shared preferences, so we use a single cached instance return context.applicationContext.getSharedPreferences( DEVICE_PREFERENCES_KEY, - MODE_PRIVATE, + MODE_PRIVATE ) } } @@ -395,7 +395,7 @@ data class ConstantItem( val defaultValue: T, override val encryptionType: EncryptionType, // The default value can be null. If so, the type needs to be explicitly stated, or else NPE - override val type: Class = defaultValue!!::class.java, + override val type: Class = defaultValue!!::class.java ) : Item() { fun get(c: Context): T = LauncherPrefs.get(c).get(this) @@ -406,7 +406,7 @@ data class ContextualItem( override val isBackedUp: Boolean, private val defaultSupplier: (c: Context) -> T, override val encryptionType: EncryptionType, - override val type: Class, + override val type: Class ) : Item() { private var default: T? = null @@ -424,26 +424,3 @@ enum class EncryptionType { ENCRYPTED, DEVICE_PROTECTED, } - -/** - * LauncherPrefs which delegates all lookup to [prefs] but uses the real prefs for initial values - */ -class ProxyPrefs(context: Context, private val prefs: SharedPreferences) : LauncherPrefs(context) { - - private val copiedPrefs = ConcurrentHashMap() - - override fun getSharedPrefs(item: Item): SharedPreferences { - val originalPrefs = super.getSharedPrefs(item) - // Copy all existing values, when the pref is accessed for the first time - copiedPrefs.computeIfAbsent(originalPrefs) { op -> - val editor = prefs.edit() - op.all.forEach { (key, value) -> - if (value != null) { - editor.putValue(backedUpItem(key, value), value) - } - } - editor.commit() - } - return prefs - } -} diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java index e51faef6ae..16d7ec5705 100644 --- a/src/com/android/launcher3/LauncherProvider.java +++ b/src/com/android/launcher3/LauncherProvider.java @@ -24,47 +24,35 @@ import android.content.ComponentName; import android.content.ContentProvider; import android.content.ContentUris; import android.content.ContentValues; -import android.content.pm.PackageManager; import android.database.Cursor; +import android.database.sqlite.SQLiteQueryBuilder; import android.net.Uri; import android.os.Binder; -import android.os.Bundle; import android.os.Process; import android.text.TextUtils; import android.util.Log; -import android.util.Pair; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.model.ModelDbController; -import com.android.launcher3.util.LayoutImportExportHelper; import com.android.launcher3.widget.LauncherWidgetHolder; import java.io.FileDescriptor; import java.io.PrintWriter; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; import java.util.function.ToIntFunction; public class LauncherProvider extends ContentProvider { private static final String TAG = "LauncherProvider"; - // Method API For Provider#call method. - private static final String METHOD_EXPORT_LAYOUT_XML = "EXPORT_LAYOUT_XML"; - private static final String METHOD_IMPORT_LAYOUT_XML = "IMPORT_LAYOUT_XML"; - private static final String KEY_RESULT = "KEY_RESULT"; - private static final String KEY_LAYOUT = "KEY_LAYOUT"; - private static final String SUCCESS = "success"; - private static final String FAILURE = "failure"; - /** * $ adb shell dumpsys activity provider com.android.launcher3 */ @Override public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { - LauncherModel model = LauncherAppState.INSTANCE.get(getContext()).getModel(); - if (model.isModelLoaded()) { - model.dumpState("", fd, writer, args); - } + LauncherAppState.INSTANCE.executeIfCreated(appState -> { + if (appState.getModel().isModelLoaded()) { + appState.getModel().dumpState("", fd, writer, args); + } + }); } @Override @@ -79,20 +67,24 @@ public class LauncherProvider extends ContentProvider { @Override public String getType(Uri uri) { - if (TextUtils.isEmpty(parseUri(uri, null, null).first)) { - return "vnd.android.cursor.dir/" + Favorites.TABLE_NAME; + SqlArguments args = new SqlArguments(uri, null, null); + if (TextUtils.isEmpty(args.where)) { + return "vnd.android.cursor.dir/" + args.table; } else { - return "vnd.android.cursor.item/" + Favorites.TABLE_NAME; + return "vnd.android.cursor.item/" + args.table; } } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { - Pair args = parseUri(uri, selection, selectionArgs); + SqlArguments args = new SqlArguments(uri, selection, selectionArgs); + SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); + qb.setTables(args.table); + Cursor[] result = new Cursor[1]; executeControllerTask(controller -> { - result[0] = controller.query(projection, args.first, args.second, sortOrder); + result[0] = controller.query(args.table, projection, args.where, args.args, sortOrder); return 0; }); return result[0]; @@ -109,7 +101,7 @@ public class LauncherProvider extends ContentProvider { // attempt allocate and bind the widget. Integer itemType = values.getAsInteger(Favorites.ITEM_TYPE); if (itemType != null - && itemType == Favorites.ITEM_TYPE_APPWIDGET + && itemType.intValue() == Favorites.ITEM_TYPE_APPWIDGET && !values.containsKey(Favorites.APPWIDGET_ID)) { ComponentName cn = ComponentName.unflattenFromString( @@ -136,7 +128,8 @@ public class LauncherProvider extends ContentProvider { } } - return controller.insert(values); + SqlArguments args = new SqlArguments(uri); + return controller.insert(args.table, values); }); return rowId < 0 ? null : ContentUris.withAppendedId(uri, rowId); @@ -144,51 +137,14 @@ public class LauncherProvider extends ContentProvider { @Override public int delete(Uri uri, String selection, String[] selectionArgs) { - Pair args = parseUri(uri, selection, selectionArgs); - return executeControllerTask(c -> c.delete(args.first, args.second)); + SqlArguments args = new SqlArguments(uri, selection, selectionArgs); + return executeControllerTask(c -> c.delete(args.table, args.where, args.args)); } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { - Pair args = parseUri(uri, selection, selectionArgs); - return executeControllerTask(c -> c.update(values, args.first, args.second)); - } - - @Override - public Bundle call(String method, String arg, Bundle extras) { - Bundle b = new Bundle(); - - // The caller must have the read or write permission for this content provider to - // access the "call" method at all. We also enforce the appropriate per-method permissions. - switch(method) { - case METHOD_EXPORT_LAYOUT_XML: - if (getContext().checkCallingOrSelfPermission(getReadPermission()) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Caller doesn't have read permission"); - } - - CompletableFuture resultFuture = LayoutImportExportHelper.INSTANCE - .exportModelDbAsXmlFuture(getContext()); - try { - b.putString(KEY_LAYOUT, resultFuture.get()); - b.putString(KEY_RESULT, SUCCESS); - } catch (ExecutionException | InterruptedException e) { - b.putString(KEY_RESULT, FAILURE); - } - return b; - - case METHOD_IMPORT_LAYOUT_XML: - if (getContext().checkCallingOrSelfPermission(getWritePermission()) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Caller doesn't have write permission"); - } - - LayoutImportExportHelper.INSTANCE.importModelFromXml(getContext(), arg); - b.putString(KEY_RESULT, SUCCESS); - return b; - default: - return null; - } + SqlArguments args = new SqlArguments(uri, selection, selectionArgs); + return executeControllerTask(c -> c.update(args.table, values, args.where, args.args)); } private int executeControllerTask(ToIntFunction task) { @@ -209,24 +165,35 @@ public class LauncherProvider extends ContentProvider { } } - /** - * Parses the uri and returns the where and arg clause. - * - * Note: This should be called on the binder thread (before posting on any executor) so that - * any parsing error gets propagated to the caller. - */ - private static Pair parseUri(Uri url, String where, String[] args) { - switch (url.getPathSegments().size()) { - case 1 -> { - return Pair.create(where, args); + static class SqlArguments { + public final String table; + public final String where; + public final String[] args; + + SqlArguments(Uri url, String where, String[] args) { + if (url.getPathSegments().size() == 1) { + this.table = url.getPathSegments().get(0); + this.where = where; + this.args = args; + } else if (url.getPathSegments().size() != 2) { + throw new IllegalArgumentException("Invalid URI: " + url); + } else if (!TextUtils.isEmpty(where)) { + throw new UnsupportedOperationException("WHERE clause not supported: " + url); + } else { + this.table = url.getPathSegments().get(0); + this.where = "_id=" + ContentUris.parseId(url); + this.args = null; } - case 2 -> { - if (!TextUtils.isEmpty(where)) { - throw new UnsupportedOperationException("WHERE clause not supported: " + url); - } - return Pair.create("_id=" + ContentUris.parseId(url), null); + } + + SqlArguments(Uri url) { + if (url.getPathSegments().size() == 1) { + table = url.getPathSegments().get(0); + where = null; + args = null; + } else { + throw new IllegalArgumentException("Invalid URI: " + url); } - default -> throw new IllegalArgumentException("Invalid URI: " + url); } } } diff --git a/src/com/android/launcher3/LauncherRootView.java b/src/com/android/launcher3/LauncherRootView.java index 5aaecfdf3c..3f7bca4537 100644 --- a/src/com/android/launcher3/LauncherRootView.java +++ b/src/com/android/launcher3/LauncherRootView.java @@ -16,16 +16,14 @@ import android.view.ViewDebug; import android.view.WindowInsets; import com.android.launcher3.graphics.SysUiScrim; -import com.android.launcher3.statemanager.StatefulContainer; - +import com.android.launcher3.statemanager.StatefulActivity; +import com.hoko.blur.HokoBlur; +import com.patrykmichalik.opto.core.PreferenceExtensionsKt; import com.android.launcher3.util.window.WindowManagerProxy; -import com.android.launcher3.views.ActivityContext; import java.util.Collections; import java.util.List; -import com.hoko.blur.HokoBlur; -import com.patrykmichalik.opto.core.PreferenceExtensionsKt; import app.lawnchair.preferences.PreferenceManager; import app.lawnchair.preferences2.PreferenceManager2; import app.lawnchair.util.FileAccessManager; @@ -35,11 +33,10 @@ public class LauncherRootView extends InsettableFrameLayout { private final Rect mTempRect = new Rect(); - private final StatefulContainer mStatefulContainer; + private final StatefulActivity mActivity; @ViewDebug.ExportedProperty(category = "launcher") - private static final List SYSTEM_GESTURE_EXCLUSION_RECT = - Collections.singletonList(new Rect()); + private static final List SYSTEM_GESTURE_EXCLUSION_RECT = Collections.singletonList(new Rect()); private WindowStateListener mWindowStateListener; @ViewDebug.ExportedProperty(category = "launcher") @@ -54,7 +51,7 @@ public class LauncherRootView extends InsettableFrameLayout { public LauncherRootView(Context context, AttributeSet attrs) { super(context, attrs); - mStatefulContainer = ActivityContext.lookupContext(context); + mActivity = StatefulActivity.fromContext(context); mSysUiScrim = new SysUiScrim(this); pref = PreferenceManager.getInstance(context); @@ -70,7 +67,7 @@ public class LauncherRootView extends InsettableFrameLayout { } private void setUpBlur(Context context) { - var display = mStatefulContainer.getDeviceProfile(); + var display = mActivity.getDeviceProfile(); int width = display.widthPx; int height = display.heightPx; @@ -120,23 +117,19 @@ public class LauncherRootView extends InsettableFrameLayout { private void handleSystemWindowInsets(Rect insets) { // Update device profile before notifying the children. - mStatefulContainer.getDeviceProfile().updateInsets(insets); + mActivity.getDeviceProfile().updateInsets(insets); boolean resetState = !insets.equals(mInsets); setInsets(insets); if (resetState) { - mStatefulContainer.getStateManager().reapplyState(true /* cancelCurrentAnimation */); + mActivity.getStateManager().reapplyState(true /* cancelCurrentAnimation */); } } @Override public WindowInsets onApplyWindowInsets(WindowInsets insets) { - mStatefulContainer.handleConfigurationChanged( - mStatefulContainer.asContext().getResources().getConfiguration()); - return updateInsets(insets); - } + mActivity.handleConfigurationChanged(mActivity.getResources().getConfiguration()); - private WindowInsets updateInsets(WindowInsets insets) { insets = WindowManagerProxy.INSTANCE.get(getContext()) .normalizeWindowInsets(getContext(), insets, mTempRect); handleSystemWindowInsets(mTempRect); @@ -145,7 +138,8 @@ public class LauncherRootView extends InsettableFrameLayout { @Override public void setInsets(Rect insets) { - // If the insets haven't changed, this is a no-op. Avoid unnecessary layout caused by + // If the insets haven't changed, this is a no-op. Avoid unnecessary layout + // caused by // modifying child layout params. if (!insets.equals(mInsets)) { super.setInsets(insets); @@ -154,11 +148,7 @@ public class LauncherRootView extends InsettableFrameLayout { } public void dispatchInsets() { - if (isAttachedToWindow()) { - updateInsets(getRootWindowInsets()); - } else { - mStatefulContainer.getDeviceProfile().updateInsets(mInsets); - } + mActivity.getDeviceProfile().updateInsets(mInsets); super.setInsets(mInsets); } @@ -191,8 +181,6 @@ public class LauncherRootView extends InsettableFrameLayout { @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); - SYSTEM_GESTURE_EXCLUSION_RECT.get(0).set(l, t, r, b); - setDisallowBackGesture(mDisallowBackGesture); mSysUiScrim.setSize(r - l, b - t); } diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java index 178bc35e29..ca228cf3ea 100644 --- a/src/com/android/launcher3/LauncherSettings.java +++ b/src/com/android/launcher3/LauncherSettings.java @@ -16,18 +16,11 @@ package com.android.launcher3; -import static android.util.Base64.NO_PADDING; -import static android.util.Base64.NO_WRAP; - -import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG; - import android.database.sqlite.SQLiteDatabase; import android.provider.BaseColumns; -import android.util.Base64; import androidx.annotation.NonNull; -import com.android.launcher3.icons.cache.CacheLookupFlag; import com.android.launcher3.model.data.ItemInfo; import java.util.LinkedHashMap; @@ -413,28 +406,14 @@ public class LauncherSettings { public static String getColumns(long profileId) { return String.join(", ", getColumnsToTypes(profileId).keySet()); } - - /** - * Lookup flag to be used for items which are visible on the home screen - */ - public static final CacheLookupFlag DESKTOP_ICON_FLAG = DEFAULT_LOOKUP_FLAG; } /** * Launcher settings */ public static final class Settings { - public static final String LAYOUT_PROVIDER_KEY = "launcher3.layout.provider"; + public static final String LAYOUT_DIGEST_KEY = "launcher3.layout.provider.blob"; public static final String LAYOUT_DIGEST_LABEL = "launcher-layout"; public static final String LAYOUT_DIGEST_TAG = "ignore"; - public static final String BLOB_KEY_PREFIX = "blob://"; - - /** - * Creates a key to be used for {@link #LAYOUT_PROVIDER_KEY} - * @param digest byte[] representing the message digest for the blob handle - */ - public static String createBlobProviderKey(byte[] digest) { - return BLOB_KEY_PREFIX + Base64.encodeToString(digest, NO_WRAP | NO_PADDING); - } } } diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java index 117e486726..a3f4d350c3 100644 --- a/src/com/android/launcher3/LauncherState.java +++ b/src/com/android/launcher3/LauncherState.java @@ -72,7 +72,6 @@ public abstract class LauncherState implements BaseState { public static final int WORKSPACE_PAGE_INDICATOR = 1 << 5; public static final int SPLIT_PLACHOLDER_VIEW = 1 << 6; public static final int FLOATING_SEARCH_BAR = 1 << 7; - public static final int ADD_DESK_BUTTON = 1 << 8; // Flag indicating workspace has multiple pages visible. public static final int FLAG_MULTI_PAGE = BaseState.getFlag(0); @@ -121,7 +120,7 @@ public abstract class LauncherState implements BaseState { LAUNCHER_STATE_HOME, FLAG_DISABLE_RESTORE | FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED | FLAG_HAS_SYS_UI_SCRIM) { @Override - public int getTransitionDuration(ActivityContext context, boolean isToState) { + public int getTransitionDuration(Context context, boolean isToState) { // Arbitrary duration, when going to NORMAL we use the state we're coming from instead. return 0; } @@ -356,7 +355,7 @@ public abstract class LauncherState implements BaseState { public final float getDepth(DEVICE_PROFILE_CONTEXT context) { return getDepth(context, - ActivityContext.lookupContext(context).getDeviceProfile().isMultiWindowMode); + BaseDraggingActivity.fromContext(context).getDeviceProfile().isMultiWindowMode); } /** @@ -386,14 +385,8 @@ public abstract class LauncherState implements BaseState { } public PageAlphaProvider getWorkspacePageAlphaProvider(Launcher launcher) { - DeviceProfile dp = launcher.getDeviceProfile(); - boolean shouldFadeAdjacentScreens = (this == NORMAL || this == HINT_STATE) - && dp.shouldFadeAdjacentWorkspaceScreens(); - // Avoid showing adjacent screens behind handheld All Apps sheet. - if (Flags.allAppsSheetForHandheld() && dp.isPhone && this == ALL_APPS) { - shouldFadeAdjacentScreens = true; - } - if (!shouldFadeAdjacentScreens) { + if ((this != NORMAL && this != HINT_STATE) + || !launcher.getDeviceProfile().shouldFadeAdjacentWorkspaceScreens()) { return DEFAULT_ALPHA_PROVIDER; } final int centerPage = launcher.getWorkspace().getNextPage(); diff --git a/src/com/android/launcher3/ModelCallbacks.kt b/src/com/android/launcher3/ModelCallbacks.kt index d18ea64879..e22369cfb7 100644 --- a/src/com/android/launcher3/ModelCallbacks.kt +++ b/src/com/android/launcher3/ModelCallbacks.kt @@ -11,12 +11,12 @@ import com.android.launcher3.Utilities.SHOULD_SHOW_FIRST_PAGE_WIDGET import com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID import com.android.launcher3.allapps.AllAppsStore import com.android.launcher3.config.FeatureFlags -import com.android.launcher3.debug.TestEventEmitter -import com.android.launcher3.debug.TestEventEmitter.TestEvent import com.android.launcher3.model.BgDataModel import com.android.launcher3.model.StringCache import com.android.launcher3.model.data.AppInfo import com.android.launcher3.model.data.ItemInfo +import com.android.launcher3.model.data.LauncherAppWidgetInfo +import com.android.launcher3.model.data.WorkspaceItemInfo import com.android.launcher3.popup.PopupContainerWithArrow import com.android.launcher3.util.ComponentKey import com.android.launcher3.util.IntArray as LIntArray @@ -59,7 +59,7 @@ class ModelCallbacks(private var launcher: Launcher) : BgDataModel.Callbacks { AbstractFloatingView.closeOpenViews( launcher, true, - AbstractFloatingView.TYPE_ALL and AbstractFloatingView.TYPE_REBIND_SAFE.inv(), + AbstractFloatingView.TYPE_ALL and AbstractFloatingView.TYPE_REBIND_SAFE.inv() ) workspaceLoading = true @@ -74,7 +74,7 @@ class ModelCallbacks(private var launcher: Launcher) : BgDataModel.Callbacks { TAG, "startBinding: " + "hotseat layout was vertical: ${launcher.hotseat?.isHasVerticalHotseat}" + - " and is setting to ${launcher.deviceProfile.isVerticalBarLayout}", + " and is setting to ${launcher.deviceProfile.isVerticalBarLayout}" ) launcher.hotseat?.resetLayout(launcher.deviceProfile.isVerticalBarLayout) TraceHelper.INSTANCE.endSection() @@ -86,16 +86,18 @@ class ModelCallbacks(private var launcher: Launcher) : BgDataModel.Callbacks { pendingTasks: RunnableList, onCompleteSignal: RunnableList, workspaceItemCount: Int, - isBindSync: Boolean, + isBindSync: Boolean ) { - Trace.endAsyncSection( - TraceEvents.DISPLAY_WORKSPACE_TRACE_METHOD_NAME, - TraceEvents.DISPLAY_WORKSPACE_TRACE_COOKIE, - ) + if (Utilities.ATLEAST_S) { + Trace.endAsyncSection( + TraceEvents.DISPLAY_WORKSPACE_TRACE_METHOD_NAME, + TraceEvents.DISPLAY_WORKSPACE_TRACE_COOKIE + ) + } synchronouslyBoundPages = boundPages pagesToBindSynchronously = LIntSet() clearPendingBinds() - if (!launcher.isInState(LauncherState.ALL_APPS) && !Flags.enableWorkspaceInflation()) { + if (!launcher.isInState(LauncherState.ALL_APPS)) { launcher.appsView.appsStore.enableDeferUpdates(AllAppsStore.DEFER_UPDATES_NEXT_DRAW) pendingTasks.add { launcher.appsView.appsStore.disableDeferUpdates( @@ -145,16 +147,15 @@ class ModelCallbacks(private var launcher: Launcher) : BgDataModel.Callbacks { // Cache one page worth of icons launcher.viewCache.setCacheSize( R.layout.folder_application, - deviceProfile.numFolderColumns * deviceProfile.numFolderRows, + deviceProfile.numFolderColumns * deviceProfile.numFolderRows ) launcher.viewCache.setCacheSize(R.layout.folder_page, 2) TraceHelper.INSTANCE.endSection() launcher.workspace.removeExtraEmptyScreen(/* stripEmptyScreens= */ true) launcher.workspace.pageIndicator.setPauseScroll( /*pause=*/ false, - deviceProfile.isTwoPanels, + deviceProfile.isTwoPanels ) - TestEventEmitter.sendEvent(TestEvent.WORKSPACE_FINISH_LOADING) } /** @@ -178,7 +179,7 @@ class ModelCallbacks(private var launcher: Launcher) : BgDataModel.Callbacks { val snackbar = AbstractFloatingView.getOpenView( launcher, - AbstractFloatingView.TYPE_SNACKBAR, + AbstractFloatingView.TYPE_SNACKBAR ) snackbar?.post { snackbar.close(true) } } @@ -187,7 +188,7 @@ class ModelCallbacks(private var launcher: Launcher) : BgDataModel.Callbacks { override fun bindAllApplications( apps: Array?, flags: Int, - packageUserKeytoUidMap: Map?, + packageUserKeytoUidMap: Map? ) { Preconditions.assertUIThread() val hadWorkApps = launcher.appsView.shouldShowTabs() @@ -213,13 +214,29 @@ class ModelCallbacks(private var launcher: Launcher) : BgDataModel.Callbacks { launcher.appsView.appsStore.updateProgressBar(app) } + override fun bindWidgetsRestored(widgets: ArrayList?) { + launcher.workspace.widgetsRestored(widgets) + } + + /** + * Some shortcuts were updated in the background. Implementation of the method from + * LauncherModel.Callbacks. + * + * @param updated list of shortcuts which have changed. + */ + override fun bindWorkspaceItemsChanged(updated: List) { + if (updated.isNotEmpty()) { + launcher.workspace.updateWorkspaceItems(updated, launcher) + PopupContainerWithArrow.dismissInvalidPopup(launcher) + } + } + /** * Update the state of a package, typically related to install state. Implementation of the * method from LauncherModel.Callbacks. */ - override fun bindItemsUpdated(updates: Set) { - launcher.workspace.updateContainerItems(updates, launcher) - PopupContainerWithArrow.dismissInvalidPopup(launcher) + override fun bindRestoreItemsChange(updates: HashSet?) { + launcher.workspace.updateRestoreItems(updates, launcher) } /** @@ -234,8 +251,8 @@ class ModelCallbacks(private var launcher: Launcher) : BgDataModel.Callbacks { PopupContainerWithArrow.dismissInvalidPopup(launcher) } - override fun bindAllWidgets(allWidgets: List) { - launcher.widgetPickerDataProvider.setWidgets(allWidgets) + override fun bindAllWidgets(allWidgets: List?) { + launcher.popupDataProvider.allWidgets = allWidgets } /** Returns the ids of the workspaces to bind. */ @@ -284,15 +301,14 @@ class ModelCallbacks(private var launcher: Launcher) : BgDataModel.Callbacks { } val widgetsListBaseEntry: WidgetsListBaseEntry = - launcher.widgetPickerDataProvider.get().allWidgets.firstOrNull { - item: WidgetsListBaseEntry -> + launcher.popupDataProvider.allWidgets.firstOrNull { item: WidgetsListBaseEntry -> item.mPkgItem.packageName == BuildConfig.APPLICATION_ID } ?: return val info = PendingAddWidgetInfo( widgetsListBaseEntry.mWidgets[0].widgetInfo, - LauncherSettings.Favorites.CONTAINER_DESKTOP, + LauncherSettings.Favorites.CONTAINER_DESKTOP ) launcher.addPendingItem( info, @@ -300,14 +316,14 @@ class ModelCallbacks(private var launcher: Launcher) : BgDataModel.Callbacks { WorkspaceLayoutManager.FIRST_SCREEN_ID, intArrayOf(0, 0), info.spanX, - info.spanY, + info.spanY ) } override fun bindScreens(orderedScreenIds: LIntArray) { launcher.workspace.pageIndicator.setPauseScroll( /*pause=*/ true, - launcher.deviceProfile.isTwoPanels, + launcher.deviceProfile.isTwoPanels ) val firstScreenPosition = 0 if ( @@ -334,7 +350,7 @@ class ModelCallbacks(private var launcher: Launcher) : BgDataModel.Callbacks { override fun bindAppsAdded( newScreens: LIntArray?, addNotAnimated: java.util.ArrayList?, - addAnimated: java.util.ArrayList?, + addAnimated: java.util.ArrayList? ) { // Add the new screens if (newScreens != null) { diff --git a/src/com/android/launcher3/MotionEventsUtils.java b/src/com/android/launcher3/MotionEventsUtils.java index 41c776b0bc..aae029fbe4 100644 --- a/src/com/android/launcher3/MotionEventsUtils.java +++ b/src/com/android/launcher3/MotionEventsUtils.java @@ -18,6 +18,8 @@ package com.android.launcher3; import static android.view.MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE; +import static com.android.launcher3.config.FeatureFlags.ENABLE_TRACKPAD_GESTURE; + import android.annotation.TargetApi; import android.os.Build; import android.view.MotionEvent; @@ -33,12 +35,14 @@ public class MotionEventsUtils { @TargetApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public static boolean isTrackpadScroll(MotionEvent event) { - return event.getClassification() == CLASSIFICATION_TWO_FINGER_SWIPE; + return ENABLE_TRACKPAD_GESTURE.get() + && event.getClassification() == CLASSIFICATION_TWO_FINGER_SWIPE; } @TargetApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public static boolean isTrackpadMultiFingerSwipe(MotionEvent event) { - return Utilities.ATLEAST_U && event.getClassification() == CLASSIFICATION_MULTI_FINGER_SWIPE; + return Utilities.ATLEAST_U && ENABLE_TRACKPAD_GESTURE.get ( ) + && event.getClassification ( ) == CLASSIFICATION_MULTI_FINGER_SWIPE; } public static boolean isTrackpadThreeFingerSwipe(MotionEvent event) { diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java index 80c6dd3a26..5428534892 100644 --- a/src/com/android/launcher3/PagedView.java +++ b/src/com/android/launcher3/PagedView.java @@ -17,7 +17,6 @@ package com.android.launcher3; import static com.android.app.animation.Interpolators.SCROLL; -import static com.android.launcher3.RemoveAnimationSettingsTracker.WINDOW_ANIMATION_SCALE_URI; import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled; import static com.android.launcher3.compat.AccessibilityManagerCompat.isObservedEventType; import static com.android.launcher3.testing.shared.TestProtocol.SCROLL_FINISHED_MESSAGE; @@ -34,6 +33,7 @@ import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Rect; import android.os.Bundle; +import android.provider.Settings; import android.util.AttributeSet; import android.util.Log; import android.view.InputDevice; @@ -723,14 +723,12 @@ public abstract class PagedView extends ViewGrou } /** - * Run the given `callback` immediately once {@code mPageScrolls} has been initialized, - * otherwise queue the callback to `mOnPageScrollsInitializedCallbacks`. + * Queues the given callback to be run once {@code mPageScrolls} has been initialized. */ public void runOnPageScrollsInitialized(Runnable callback) { + mOnPageScrollsInitializedCallbacks.add(callback); if (isPageScrollsInitialized()) { - callback.run(); - } else { - mOnPageScrollsInitializedCallbacks.add(callback); + onPageScrollsInitialized(); } } @@ -1478,15 +1476,6 @@ public abstract class PagedView extends ViewGrou mEdgeGlowLeft.onFlingVelocity(velocity); mEdgeGlowRight.onFlingVelocity(velocity); } - - // Detect if user tries to swipe to -1 page but gets disallowed by checking if there was - // left-over values in mEdgeGlowLeft (or mEdgeGlowRight in RLT). - final int layoutDir = getLayoutDirection(); - if ((mEdgeGlowLeft.getDistance() > 0 && layoutDir == LAYOUT_DIRECTION_LTR) - || (mEdgeGlowRight.getDistance() > 0 && layoutDir == LAYOUT_DIRECTION_RTL)) { - onDisallowSwipeToMinusOnePage(); - } - mEdgeGlowLeft.onRelease(ev); mEdgeGlowRight.onRelease(ev); // End any intermediate reordering states @@ -1511,8 +1500,6 @@ public abstract class PagedView extends ViewGrou return true; } - protected void onDisallowSwipeToMinusOnePage() {} - protected void onNotSnappingToPageInFreeScroll() { } /** @@ -1769,8 +1756,8 @@ public abstract class PagedView extends ViewGrou } if (FeatureFlags.IS_STUDIO_BUILD && !Utilities.isRunningInTestHarness()) { - duration *= RemoveAnimationSettingsTracker.INSTANCE.get(getContext()).getValue( - WINDOW_ANIMATION_SCALE_URI); + duration *= Settings.Global.getFloat(getContext().getContentResolver(), + Settings.Global.WINDOW_ANIMATION_SCALE, 1); } whichPage = validateNewPage(whichPage); diff --git a/src/com/android/launcher3/SecondaryDropTarget.java b/src/com/android/launcher3/SecondaryDropTarget.java index f4d3146ed5..0a4fb73a86 100644 --- a/src/com/android/launcher3/SecondaryDropTarget.java +++ b/src/com/android/launcher3/SecondaryDropTarget.java @@ -7,6 +7,7 @@ import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate. import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.INVALID; import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.RECONFIGURE; import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.UNINSTALL; +import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_DISMISS_PREDICTION_UNDO; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROPPED_ON_DONT_SUGGEST; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROPPED_ON_UNINSTALL; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_UNINSTALL_CANCELLED; @@ -22,6 +23,7 @@ import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.LauncherActivityInfo; import android.content.pm.LauncherApps; +import android.content.pm.PackageManager; import android.net.Uri; import android.os.Bundle; import android.os.UserHandle; @@ -34,6 +36,7 @@ import android.widget.Toast; import androidx.annotation.Nullable; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.dragndrop.DragOptions; import com.android.launcher3.logging.FileLog; import com.android.launcher3.logging.InstanceId; @@ -43,7 +46,7 @@ import com.android.launcher3.logging.StatsLogManager.StatsLogger; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.ItemInfoWithIcon; import com.android.launcher3.pm.UserCache; -import com.android.launcher3.util.ApplicationInfoWrapper; +import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.widget.LauncherAppWidgetProviderInfo; import java.net.URISyntaxException; @@ -239,7 +242,8 @@ public class SecondaryDropTarget extends ButtonDropTarget implements OnAlarmList @Override public void completeDrop(final DragObject d) { - ComponentName target = performDropAction(getViewUnderDrag(d.dragInfo), d.dragInfo); + ComponentName target = performDropAction(getViewUnderDrag(d.dragInfo), d.dragInfo, + d.logInstanceId); mDropTargetHandler.onSecondaryTargetCompleteDrop(target, d); } @@ -271,7 +275,7 @@ public class SecondaryDropTarget extends ButtonDropTarget implements OnAlarmList * Performs the drop action and returns the target component for the dragObject or null if * the action was not performed. */ - protected ComponentName performDropAction(View view, ItemInfo info) { + protected ComponentName performDropAction(View view, ItemInfo info, InstanceId instanceId) { if (mCurrentAccessibilityAction == RECONFIGURE) { int widgetId = getReconfigurableWidgetId(view); if (widgetId != INVALID_APPWIDGET_ID) { @@ -279,6 +283,21 @@ public class SecondaryDropTarget extends ButtonDropTarget implements OnAlarmList } return null; } + if (mCurrentAccessibilityAction == DISMISS_PREDICTION) { + if (FeatureFlags.ENABLE_DISMISS_PREDICTION_UNDO.get()) { + CharSequence announcement = getContext().getString(R.string.item_removed); + mDropTargetHandler + .dismissPrediction(announcement, () -> { + }, () -> { + mStatsLogManager.logger() + .withInstanceId(instanceId) + .withItemInfo(info) + .log(LAUNCHER_DISMISS_PREDICTION_UNDO); + }); + } + return null; + } + return performUninstall(getContext(), getUninstallTarget(getContext(), info), info); } @@ -303,19 +322,19 @@ public class SecondaryDropTarget extends ButtonDropTarget implements OnAlarmList .setData(Uri.fromParts("package", cn.getPackageName(), cn.getClassName())) .putExtra(Intent.EXTRA_USER, info.user); context.startActivity(i); - FileLog.d(TAG, "start uninstall activity from drop target " + cn.getPackageName()); + FileLog.d(TAG, "start uninstall activity " + cn.getPackageName()); return cn; } catch (URISyntaxException e) { - Log.e(TAG, "Failed to parse intent to start drop target uninstall activity for" - + " item=" + info); + Log.e(TAG, "Failed to parse intent to start uninstall activity for item=" + info); return null; } } @Override public void onAccessibilityDrop(View view, ItemInfo item) { - doLog(new InstanceIdSequence().newInstanceId(), item); - performDropAction(view, item); + InstanceId instanceId = new InstanceIdSequence().newInstanceId(); + doLog(instanceId, item); + performDropAction(view, item, instanceId); } /** @@ -342,8 +361,9 @@ public class SecondaryDropTarget extends ButtonDropTarget implements OnAlarmList } public void onLauncherResume() { - if (new ApplicationInfoWrapper(mContext, mPackageName, mDragObject.dragInfo.user) - .getInfo() == null) { + // We use MATCH_UNINSTALLED_PACKAGES as the app can be on SD card as well. + if (PackageManagerHelper.INSTANCE.get(mContext).getApplicationInfo(mPackageName, + mDragObject.dragInfo.user, PackageManager.MATCH_UNINSTALLED_PACKAGES) == null) { mDragObject.dragSource = mOriginal; mOriginal.onDropCompleted(SecondaryDropTarget.this, mDragObject, true); mStatsLogManager.logger().withInstanceId(mDragObject.logInstanceId) diff --git a/src/com/android/launcher3/SessionCommitReceiver.java b/src/com/android/launcher3/SessionCommitReceiver.java index c46d92c52b..3145375073 100644 --- a/src/com/android/launcher3/SessionCommitReceiver.java +++ b/src/com/android/launcher3/SessionCommitReceiver.java @@ -58,7 +58,7 @@ public class SessionCommitReceiver extends BroadcastReceiver { @WorkerThread private static void processIntent(Context context, Intent intent) { UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER); - if (!isEnabled(context, user)) { + if (!isEnabled(context)) { // User has decided to not add icons on homescreen. return; } @@ -71,21 +71,17 @@ public class SessionCommitReceiver extends BroadcastReceiver { } InstallSessionHelper packageInstallerCompat = InstallSessionHelper.INSTANCE.get(context); - boolean alreadyAddedPromiseIcon = - packageInstallerCompat.promiseIconAddedForId(info.getSessionId()); + boolean alreadyAddedPromiseIcon = packageInstallerCompat.promiseIconAddedForId(info.getSessionId()); if (TextUtils.isEmpty(info.getAppPackageName()) || info.getInstallReason() != PackageManager.INSTALL_REASON_USER || alreadyAddedPromiseIcon) { FileLog.d(LOG, String.format(Locale.ENGLISH, - "Removing unneeded PromiseIcon for package: %s" - + ", install reason: %d," + "Removing PromiseIcon for package: %s, install reason: %d," + " alreadyAddedPromiseIcon: %s", - info.getAppPackageName(), - info.getInstallReason(), - alreadyAddedPromiseIcon - ) - ); + info.getAppPackageName(), + info.getInstallReason(), + alreadyAddedPromiseIcon)); packageInstallerCompat.removePromiseIconId(info.getSessionId()); return; } @@ -99,19 +95,9 @@ public class SessionCommitReceiver extends BroadcastReceiver { .queueItem(info.getAppPackageName(), user); } - /** - * Returns whether adding Installed App Icons to home screen is allowed or not. - * Not allowed when: - * - User belongs to {@link com.android.launcher3.util.UserIconInfo.TYPE_PRIVATE} or - * - Home Settings preference to add App Icons on Home Screen is set as disabled - */ - public static boolean isEnabled(Context context, UserHandle user) { - if (Flags.privateSpaceRestrictItemDrag() - && PreferenceExtensionsKt.firstBlocking(PreferenceManager2.getInstance(context).getLockHomeScreen()) - && user != null - && UserCache.getInstance(context).getUserInfo(user).isPrivate()) { + public static boolean isEnabled(Context context) { + if (PreferenceExtensionsKt.firstBlocking(PreferenceManager2.getInstance(context).getLockHomeScreen())) return false; - } - return LauncherPrefs.getPrefs(context).getBoolean(ADD_ICON_PREFERENCE_KEY, true); + return Utilities.getPrefs(context).getBoolean(ADD_ICON_PREFERENCE_KEY, true); } } diff --git a/src/com/android/launcher3/ShortcutAndWidgetContainer.java b/src/com/android/launcher3/ShortcutAndWidgetContainer.java index 881adfc69a..664b96a3a3 100644 --- a/src/com/android/launcher3/ShortcutAndWidgetContainer.java +++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java @@ -69,7 +69,6 @@ public class ShortcutAndWidgetContainer extends ViewGroup implements FolderIcon. private final ActivityContext mActivity; private boolean mInvertIfRtl = false; - public boolean mHasOnLayoutBeenCalled = false; private final PreferenceManager2 mPreferenceManager2; @Nullable @@ -218,7 +217,6 @@ public class ShortcutAndWidgetContainer extends ViewGroup implements FolderIcon. @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { Trace.beginSection("ShortcutAndWidgetConteiner#onLayout"); - mHasOnLayoutBeenCalled = true; // b/349929393 - is the required call to onLayout not done? int count = getChildCount(); for (int i = 0; i < count; i++) { final View child = getChildAt(i); @@ -267,7 +265,7 @@ public class ShortcutAndWidgetContainer extends ViewGroup implements FolderIcon. } child.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height); if (mTranslationProvider != null) { - final float tx = mTranslationProvider.getTranslationX(lp.getCellX()); + final float tx = mTranslationProvider.getTranslationX(child); if (child instanceof Reorderable) { ((Reorderable) child).getTranslateDelegate() .getTranslationX(INDEX_BUBBLE_ADJUSTMENT_ANIM) @@ -349,13 +347,8 @@ public class ShortcutAndWidgetContainer extends ViewGroup implements FolderIcon. mTranslationProvider = provider; } - /** Returns the current {@link TranslationProvider translation provider}. */ - public @Nullable TranslationProvider getTranslationProvider() { - return mTranslationProvider; - } - /** Provides translation values to apply when laying out child views. */ - public interface TranslationProvider { - float getTranslationX(int cellX); + interface TranslationProvider { + float getTranslationX(View child); } } diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java index d1f5d8133b..67f63dd87a 100644 --- a/src/com/android/launcher3/Utilities.java +++ b/src/com/android/launcher3/Utilities.java @@ -16,9 +16,10 @@ package com.android.launcher3; +import static android.graphics.drawable.AdaptiveIconDrawable.getExtraInsetFraction; + import static com.android.launcher3.BuildConfigs.WIDGET_ON_FIRST_SCREEN; import static com.android.launcher3.Flags.enableSmartspaceAsAWidget; -import static com.android.launcher3.graphics.ShapeDelegate.DEFAULT_PATH_SIZE; import static com.android.launcher3.icons.BitmapInfo.FLAG_THEMED; import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT; import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT; @@ -30,25 +31,30 @@ import android.app.ActivityOptions; import android.app.Person; import android.app.WallpaperManager; import android.content.Context; +import android.content.SharedPreferences; import android.content.pm.LauncherActivityInfo; import android.content.pm.LauncherApps; import android.content.pm.ResolveInfo; import android.content.pm.ShortcutInfo; import android.content.res.Configuration; import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BlendMode; +import android.graphics.BlendModeColorFilter; import android.graphics.Color; import android.graphics.ColorFilter; import android.graphics.LightingColorFilter; import android.graphics.Matrix; import android.graphics.Paint; -import android.graphics.Path; import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.AdaptiveIconDrawable; +import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; +import android.graphics.drawable.InsetDrawable; import android.os.Build; import android.os.Build.VERSION_CODES; import android.os.DeadObjectException; @@ -78,13 +84,11 @@ import androidx.annotation.WorkerThread; import androidx.core.graphics.ColorUtils; import com.android.launcher3.dragndrop.FolderAdaptiveIcon; -import com.android.launcher3.graphics.ThemeManager; import com.android.launcher3.graphics.TintedDrawableSpan; import com.android.launcher3.icons.BitmapInfo; -import com.android.launcher3.icons.CacheableShortcutCachingLogic; -import com.android.launcher3.icons.CacheableShortcutInfo; -import com.android.launcher3.icons.IconThemeController; import com.android.launcher3.icons.LauncherIcons; +import com.android.launcher3.icons.ShortcutCachingLogic; +import com.android.launcher3.icons.ThemedIconDrawable; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.ItemInfoWithIcon; import com.android.launcher3.pm.ShortcutConfigActivityInfo; @@ -96,6 +100,7 @@ import com.android.launcher3.util.FlagOp; import com.android.launcher3.util.IntArray; import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption; +import com.android.launcher3.util.Themes; import com.android.launcher3.views.ActivityContext; import com.android.launcher3.views.BaseDragLayer; import com.android.launcher3.widget.PendingAddShortcutInfo; @@ -104,8 +109,9 @@ import java.lang.reflect.Method; import java.util.Collections; import java.util.List; import java.util.Locale; -import java.util.Objects; import java.util.function.Predicate; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import app.lawnchair.icons.ExtendedBitmapDrawable; import app.lawnchair.preferences.PreferenceManager; @@ -117,7 +123,8 @@ public final class Utilities { private static final String TAG = "Launcher.Utilities"; - private static final String TRIM_PATTERN = "(^\\h+|\\h+$)"; + private static final Pattern sTrimPattern = Pattern + .compile("^[\\s|\\p{javaSpaceChar}]*(.*)[\\s|\\p{javaSpaceChar}]*$"); private static final Matrix sMatrix = new Matrix(); private static final Matrix sInverseMatrix = new Matrix(); @@ -140,50 +147,42 @@ public final class Utilities { @ChecksSdkIntAtLeast(api = VERSION_CODES.S) public static final boolean ATLEAST_S = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S; - @ChecksSdkIntAtLeast(api = VERSION_CODES.S_V2) - public static final boolean ATLEAST_S_V2 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S_V2; - @ChecksSdkIntAtLeast(api = VERSION_CODES.TIRAMISU, codename = "T") public static final boolean ATLEAST_T = Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU; + public static final boolean ATLEAST_S_V2 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S_V2; @ChecksSdkIntAtLeast(api = VERSION_CODES.UPSIDE_DOWN_CAKE, codename = "U") public static final boolean ATLEAST_U = Build.VERSION.SDK_INT >= VERSION_CODES.UPSIDE_DOWN_CAKE; @ChecksSdkIntAtLeast(api = VERSION_CODES.VANILLA_ICE_CREAM, codename = "V") - public static final boolean ATLEAST_V = Build.VERSION.SDK_INT - >= VERSION_CODES.VANILLA_ICE_CREAM; - - @ChecksSdkIntAtLeast(api = VERSION_CODES.BAKLAVA) - public static final boolean ATLEAST_BAKLAVA = Build.VERSION.SDK_INT >= VERSION_CODES.BAKLAVA; - - @ChecksSdkIntAtLeast(api = 36, codename = "BAKLAVA_1") - public static final boolean ATLEAST_BAKLAVA_1 = - (Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA) - && (Build.VERSION.SDK_INT_FULL >= 3600001); // pE-TODO(): Why Build.VERSION_CODES_FULL.BAKLAVA_1 failed? + public static final boolean ATLEAST_V = Build.VERSION.SDK_INT >= VERSION_CODES.VANILLA_ICE_CREAM; /** - * Set on a motion event dispatched from the nav bar. See {@link MotionEvent#setEdgeFlags(int)}. + * Set on a motion event dispatched from the nav bar. See + * {@link MotionEvent#setEdgeFlags(int)}. */ public static final int EDGE_NAV_BAR = 1 << 8; /** - * Indicates if the device has a debug build. Should only be used to store additional info or + * Indicates if the device has a debug build. Should only be used to store + * additional info or * add extra logging and not for changing the app behavior. + * * @deprecated Use {@link BuildConfig#IS_DEBUG_DEVICE} directly */ @Deprecated - public static final boolean IS_DEBUG_DEVICE = false; + public static final boolean IS_DEBUG_DEVICE = true; public static final int TRANSLATE_UP = 0; public static final int TRANSLATE_DOWN = 1; public static final int TRANSLATE_LEFT = 2; public static final int TRANSLATE_RIGHT = 3; - public static final boolean SHOULD_SHOW_FIRST_PAGE_WIDGET = - enableSmartspaceAsAWidget() && WIDGET_ON_FIRST_SCREEN; + public static final boolean SHOULD_SHOW_FIRST_PAGE_WIDGET = WIDGET_ON_FIRST_SCREEN; - @IntDef({TRANSLATE_UP, TRANSLATE_DOWN, TRANSLATE_LEFT, TRANSLATE_RIGHT}) - public @interface AdjustmentDirection{} + @IntDef({ TRANSLATE_UP, TRANSLATE_DOWN, TRANSLATE_LEFT, TRANSLATE_RIGHT }) + public @interface AdjustmentDirection { + } /** * Returns true if theme is dark. @@ -203,6 +202,11 @@ public final class Utilities { return nightMode == Configuration.UI_MODE_NIGHT_YES; } + public static boolean isDevelopersOptionsEnabled(Context context) { + return Settings.Global.getInt(context.getApplicationContext().getContentResolver(), + Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0; + } + public static Drawable loadFullDrawableWithoutTheme(Context context, ItemInfo info, int width, int height, Object[] outObj) { ActivityContext activity = ActivityContext.lookupContext(context); @@ -219,7 +223,7 @@ public final class Utilities { return activityInfo == null ? null : LauncherAppState.getInstance(context) .getIconProvider().getIcon( - activityInfo.getActivityInfo(), activity.getDeviceProfile().inv.fillResIconDpi); + activityInfo, activity.getDeviceProfile().inv.fillResIconDpi); } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) { List si = ShortcutKey.fromItemInfo(info) .buildRequest(context) @@ -228,9 +232,8 @@ public final class Utilities { return null; } else { outObj[0] = si.get(0); - BitmapInfo bi = CacheableShortcutCachingLogic.INSTANCE.loadIcon( - context, appState.getIconCache(), new CacheableShortcutInfo(si.get(0), context)); - return bi.newIcon(context); + return ShortcutCachingLogic.getIcon(context, si.get(0), + appState.getInvariantDeviceProfile().fillResIconDpi); } } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) { FolderAdaptiveIcon icon = FolderAdaptiveIcon.createFolderAdaptiveIcon( @@ -258,15 +261,10 @@ public final class Utilities { sIsRunningInTestHarness = true; } - /** Disables running test in test harness mode */ - public static void disableRunningInTestHarnessForTests() { - sIsRunningInTestHarness = false; - } - public static boolean isPropertyEnabled(String propertyName) { return Log.isLoggable(propertyName, Log.VERBOSE); } - + public static boolean showStyleWallpapers(Context context) { return existsStyleWallpapers(context) || existsStyleWallpapersAlt(context); } @@ -284,16 +282,22 @@ public final class Utilities { } /** - * Given a coordinate relative to the descendant, find the coordinate in a parent view's + * Given a coordinate relative to the descendant, find the coordinate in a + * parent view's * coordinates. * - * @param descendant The descendant to which the passed coordinate is relative. - * @param ancestor The root view to make the coordinates relative to. - * @param coord The coordinate that we want mapped. - * @param includeRootScroll Whether or not to account for the scroll of the descendant: - * sometimes this is relevant as in a child's coordinates within the descendant. - * @return The factor by which this descendant is scaled relative to this DragLayer. Caution - * this scale factor is assumed to be equal in X and Y, and so if at any point this + * @param descendant The descendant to which the passed coordinate is + * relative. + * @param ancestor The root view to make the coordinates relative to. + * @param coord The coordinate that we want mapped. + * @param includeRootScroll Whether or not to account for the scroll of the + * descendant: + * sometimes this is relevant as in a child's + * coordinates within the descendant. + * @return The factor by which this descendant is scaled relative to this + * DragLayer. Caution + * this scale factor is assumed to be equal in X and Y, and so if at any + * point this * assumption fails, we will need to return a pair of scale factors. */ public static float getDescendantCoordRelativeToAncestor( @@ -303,24 +307,30 @@ public final class Utilities { } /** - * Given a coordinate relative to the descendant, find the coordinate in a parent view's + * Given a coordinate relative to the descendant, find the coordinate in a + * parent view's * coordinates. * - * @param descendant The descendant to which the passed coordinate is relative. - * @param ancestor The root view to make the coordinates relative to. - * @param coord The coordinate that we want mapped. - * @param includeRootScroll Whether or not to account for the scroll of the descendant: - * sometimes this is relevant as in a child's coordinates within the descendant. - * @param ignoreTransform If true, view transform is ignored - * @return The factor by which this descendant is scaled relative to this DragLayer. Caution - * this scale factor is assumed to be equal in X and Y, and so if at any point this + * @param descendant The descendant to which the passed coordinate is + * relative. + * @param ancestor The root view to make the coordinates relative to. + * @param coord The coordinate that we want mapped. + * @param includeRootScroll Whether or not to account for the scroll of the + * descendant: + * sometimes this is relevant as in a child's + * coordinates within the descendant. + * @param ignoreTransform If true, view transform is ignored + * @return The factor by which this descendant is scaled relative to this + * DragLayer. Caution + * this scale factor is assumed to be equal in X and Y, and so if at any + * point this * assumption fails, we will need to return a pair of scale factors. */ public static float getDescendantCoordRelativeToAncestor(View descendant, View ancestor, float[] coord, boolean includeRootScroll, boolean ignoreTransform) { float scale = 1.0f; View v = descendant; - while(v != ancestor && v != null) { + while (v != ancestor && v != null) { // For TextViews, scroll has a meaning which relates to the text position // which is very strange... ignore the scroll. if (v != descendant || includeRootScroll) { @@ -343,10 +353,12 @@ public final class Utilities { * * see {@link com.android.launcher3.dragndrop.DragLayer}. * - * @param viewBounds Bounds of the view wanted in drag layer coordinates, relative to the view - * itself. eg. (0, 0, view.getWidth, view.getHeight) + * @param viewBounds Bounds of the view wanted in drag layer coordinates, + * relative to the view + * itself. eg. (0, 0, view.getWidth, view.getHeight) * @param ignoreTransform If true, view transform is ignored - * @param outRect The out rect where we return the bounds of {@param view} in drag layer coords. + * @param outRect The out rect where we return the bounds of + * {@param view} in drag layer coords. */ public static void getBoundsForViewInDragLayer(BaseDragLayer dragLayer, View view, Rect viewBounds, boolean ignoreTransform, float[] recycle, RectF outRect) { @@ -366,24 +378,24 @@ public final class Utilities { } /** - * Similar to {@link #mapCoordInSelfToDescendant(View descendant, View root, float[] coord)} + * Similar to + * {@link #mapCoordInSelfToDescendant(View descendant, View root, float[] coord)} * but accepts a Rect instead of float[]. */ public static void mapRectInSelfToDescendant(View descendant, View root, Rect rect) { - float[] coords = new float[]{rect.left, rect.top, rect.right, rect.bottom}; + float[] coords = new float[] { rect.left, rect.top, rect.right, rect.bottom }; mapCoordInSelfToDescendant(descendant, root, coords); rect.set((int) coords[0], (int) coords[1], (int) coords[2], (int) coords[3]); } /** - * Inverse of {@link #getDescendantCoordRelativeToAncestor(View, View, float[], boolean)}. + * Inverse of + * {@link #getDescendantCoordRelativeToAncestor(View, View, float[], boolean)}. */ public static void mapCoordInSelfToDescendant(View descendant, View root, float[] coord) { - sMatrix.reset(); - //TODO(b/307488755) when implemented this check should be removed - if (!Objects.equals(descendant.getWindowId(), root.getWindowId())) { + if (descendant == null || root == null || coord == null || coord.length < 2) return; - } + sMatrix.reset(); View v = descendant; while (v != root) { sMatrix.postTranslate(-v.getScrollX(), -v.getScrollY()); @@ -414,8 +426,10 @@ public final class Utilities { /** * Utility method to determine whether the given point, in local coordinates, - * is inside the view, where the area of the view is expanded by the slop factor. - * This method is called while processing touch-move events to determine if the event + * is inside the view, where the area of the view is expanded by the slop + * factor. + * This method is called while processing touch-move events to determine if the + * event * is still within the view. */ public static boolean pointInView(View v, float localX, float localY, float slop) { @@ -428,7 +442,8 @@ public final class Utilities { } /** - * Similar to {@link #scaleRectAboutCenter(Rect, float)} except this allows different scales + * Similar to {@link #scaleRectAboutCenter(Rect, float)} except this allows + * different scales * for X and Y */ public static void scaleRectFAboutCenter(RectF r, float scaleX, float scaleY) { @@ -470,8 +485,8 @@ public final class Utilities { /** * Sets the x and y pivots for scaling from one Rect to another. * - * @param src the source rectangle to scale from. - * @param dst the destination rectangle to scale to. + * @param src the source rectangle to scale from. + * @param dst the destination rectangle to scale to. * @param outPivot the pivots set for scaling from src to dst. */ public static void getPivotsForScalingRectToRect(Rect src, Rect dst, PointF outPivot) { @@ -482,35 +497,14 @@ public final class Utilities { outPivot.y = dst.top + dst.height() * pivotYPct; } - /** - * Scales a {@code RectF} in place about a specified pivot point. - * - *

This method modifies the given {@code RectF} directly to scale it proportionally - * by the given {@code scale}, while preserving its center at the specified - * {@code (pivotX, pivotY)} coordinates. - * - * @param rectF the {@code RectF} to scale, modified directly. - * @param pivotX the x-coordinate of the pivot point about which to scale. - * @param pivotY the y-coordinate of the pivot point about which to scale. - * @param scale the factor by which to scale the rectangle. Values less than 1 will - * shrink the rectangle, while values greater than 1 will enlarge it. - */ - public static void scaleRectFAboutPivot(RectF rectF, float pivotX, float pivotY, float scale) { - rectF.offset(-pivotX, -pivotY); - rectF.left *= scale; - rectF.top *= scale; - rectF.right *= scale; - rectF.bottom *= scale; - rectF.offset(pivotX, pivotY); - } - /** * Maps t from one range to another range. - * @param t The value to map. + * + * @param t The value to map. * @param fromMin The lower bound of the range that t is being mapped from. * @param fromMax The upper bound of the range that t is being mapped from. - * @param toMin The lower bound of the range that t is being mapped to. - * @param toMax The upper bound of the range that t is being mapped to. + * @param toMin The lower bound of the range that t is being mapped to. + * @param toMax The upper bound of the range that t is being mapped to. * @return The mapped value of t. */ public static float mapToRange(float t, float fromMin, float fromMax, float toMin, float toMax, @@ -523,25 +517,6 @@ public final class Utilities { return mapRange(interpolator.getInterpolation(progress), toMin, toMax); } - /** - * Maps t from one range to another range. - * @param t The value to map. - * @param fromMin The lower bound of the range that t is being mapped from. - * @param fromMax The upper bound of the range that t is being mapped from. - * @param toMin The lower bound of the range that t is being mapped to. - * @param toMax The upper bound of the range that t is being mapped to. - * @return The mapped value of t. - */ - public static int mapToRange(int t, int fromMin, int fromMax, int toMin, int toMax, - Interpolator interpolator) { - if (fromMin == fromMax || toMin == toMax) { - Log.e(TAG, "mapToRange: range has 0 length"); - return toMin; - } - float progress = getProgress(t, fromMin, fromMax); - return (int) mapRange(interpolator.getInterpolation(progress), toMin, toMax); - } - /** Bounds t between a lower and upper bound and maps the result to a range. */ public static float mapBoundToRange(float t, float lowerBound, float upperBound, float toMin, float toMax, Interpolator interpolator) { @@ -558,7 +533,8 @@ public final class Utilities { } /** - * Trims the string, removing all whitespace at the beginning and end of the string. + * Trims the string, removing all whitespace at the beginning and end of the + * string. * Non-breaking whitespaces are also removed. */ @NonNull @@ -566,7 +542,11 @@ public final class Utilities { if (s == null) { return ""; } - return s.toString().replaceAll(TRIM_PATTERN, "").trim(); + + // Just strip any sequence of whitespace or java space characters from the + // beginning and end + Matcher m = sTrimPattern.matcher(s); + return m.replaceAll("$1"); } /** @@ -583,7 +563,9 @@ public final class Utilities { return res.getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; } - /** Converts a pixel value (px) to scale pixel value (SP) for the current device. */ + /** + * Converts a pixel value (px) to scale pixel value (SP) for the current device. + */ public static float pxToSp(float size) { return size / Resources.getSystem().getDisplayMetrics().scaledDensity; } @@ -637,7 +619,8 @@ public final class Utilities { /** * Ensures that a value is within given bounds. Specifically: - * If value is less than lowerBound, return lowerBound; else if value is greater than upperBound, + * If value is less than lowerBound, return lowerBound; else if value is greater + * than upperBound, * return upperBound; else return value unchanged. */ public static int boundToRange(int value, int lowerBound, int upperBound) { @@ -661,7 +644,8 @@ public final class Utilities { /** * Wraps a message with a TTS span, so that a different message is spoken than * what is getting displayed. - * @param msg original message + * + * @param msg original message * @param ttsMsg message to be spoken */ public static CharSequence wrapForTts(CharSequence msg, String ttsMsg) { @@ -676,8 +660,10 @@ public final class Utilities { */ public static CharSequence prefixTextWithIcon(Context context, int iconRes, CharSequence msg) { // Update the hint to contain the icon. - // Prefix the original hint with two spaces. The first space gets replaced by the icon - // using span. The second space is used for a singe space character between the hint + // Prefix the original hint with two spaces. The first space gets replaced by + // the icon + // using span. The second space is used for a singe space character between the + // hint // and the icon. SpannableString spanned = new SpannableString(" " + msg); spanned.setSpan(new TintedDrawableSpan(context, iconRes), @@ -699,7 +685,8 @@ public final class Utilities { } /** - * Utility method to post a runnable on the handler, skipping the synchronization barriers. + * Utility method to post a runnable on the handler, skipping the + * synchronization barriers. */ public static void postAsyncCallback(Handler handler, Runnable callback) { Message msg = Message.obtain(handler, callback); @@ -708,7 +695,8 @@ public final class Utilities { } /** - * Utility method to allow background activity launch for the provided activity options + * Utility method to allow background activity launch for the provided activity + * options */ public static ActivityOptions allowBGLaunch(ActivityOptions options) { if (ATLEAST_V) { @@ -719,15 +707,8 @@ public final class Utilities { } /** - * Utility method to know if a device's primary language is English. - */ - public static boolean isEnglishLanguage(Context context) { - return context.getResources().getConfiguration().locale.getLanguage() - .equals(Locale.ENGLISH.getLanguage()); - } - - /** - * Returns the full drawable for info as multiple layers of AdaptiveIconDrawable. The second + * Returns the full drawable for info as multiple layers of + * AdaptiveIconDrawable. The second * drawable in the Pair is the badge used with the icon. * * @param useTheme If true, will theme icons when applicable @@ -735,19 +716,19 @@ public final class Utilities { @SuppressLint("UseCompatLoadingForDrawables") @Nullable @WorkerThread - public static Pair - getFullDrawable(T context, ItemInfo info, int width, int height, boolean useTheme) { + public static Pair getFullDrawable(T context, + ItemInfo info, int width, int height, boolean useTheme) { + useTheme &= Themes.isThemedIconEnabled(context); LauncherAppState appState = LauncherAppState.getInstance(context); Drawable mainIcon = null; Drawable badge = null; - if ((info instanceof ItemInfoWithIcon iiwi) && !iiwi.getMatchingLookupFlag().useLowRes()) { - badge = iiwi.bitmap.getBadgeDrawable(context, useTheme, getIconShapeOrNull(context)); + if ((info instanceof ItemInfoWithIcon iiwi) && !iiwi.usingLowResIcon()) { + badge = iiwi.bitmap.getBadgeDrawable(context, useTheme); } if (info instanceof PendingAddShortcutInfo) { - ShortcutConfigActivityInfo activityInfo = - ((PendingAddShortcutInfo) info).getActivityInfo(context); + ShortcutConfigActivityInfo activityInfo = ((PendingAddShortcutInfo) info).getActivityInfo(context); mainIcon = activityInfo.getFullResIcon(appState.getIconCache()); } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { LauncherActivityInfo activityInfo = context.getSystemService(LauncherApps.class) @@ -755,7 +736,8 @@ public final class Utilities { if (activityInfo == null) { return null; } - mainIcon = appState.getIconCache().getFullResIcon(activityInfo.getActivityInfo()); + mainIcon = appState.getIconProvider().getIcon( + activityInfo, appState.getInvariantDeviceProfile().fillResIconDpi); } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) { List siList = ShortcutKey.fromItemInfo(info) .buildRequest(context) @@ -764,16 +746,12 @@ public final class Utilities { return null; } else { ShortcutInfo si = siList.get(0); - mainIcon = CacheableShortcutInfo.getIcon(context, si, + mainIcon = ShortcutCachingLogic.getIcon(context, si, appState.getInvariantDeviceProfile().fillResIconDpi); // Only fetch badge if the icon is on workspace if (info.id != ItemInfo.NO_ID && badge == null) { - badge = appState.getIconCache().getShortcutInfoBadge(si).newIcon( - context, - ThemeManager.INSTANCE.get(context).isIconThemeEnabled() - ? FLAG_THEMED : 0, - getIconShapeOrNull(context) - ); + badge = appState.getIconCache().getShortcutInfoBadge(si) + .newIcon(context, FLAG_THEMED); } } } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) { @@ -782,16 +760,19 @@ public final class Utilities { if (icon == null) { return null; } - mainIcon = icon; + mainIcon = icon; badge = icon.getBadge(); } + if (useTheme && mainIcon instanceof BitmapInfo.Extender) { + mainIcon = ((BitmapInfo.Extender) mainIcon).getThemedDrawable(context); + } + if (mainIcon == null) { return null; } AdaptiveIconDrawable result; - // Lawnchair-TODO-Merge: From LC, maybe heavily affected by L3, Allow 3p icon if (ExtendedBitmapDrawable.isFromIconPack(mainIcon) || (!PreferenceManager.INSTANCE.get(context).getWrapAdaptiveIcons().get() && info.itemType != LauncherSettings.Favorites.ITEM_TYPE_FOLDER)) return null; if (mainIcon instanceof AdaptiveIconDrawable aid) { @@ -799,35 +780,49 @@ public final class Utilities { } else { // Wrap the main icon in AID try (LauncherIcons li = LauncherIcons.obtain(context)) { - result = li.wrapToAdaptiveIcon(mainIcon); + result = li.wrapToAdaptiveIcon(mainIcon, null); } } if (result == null) { return null; } - // Inject theme icon drawable - if (ATLEAST_T && useTheme) { - IconThemeController themeController = - ThemeManager.INSTANCE.get(context).getThemeController(); - if (themeController != null) { - AdaptiveIconDrawable themed = themeController.createThemedAdaptiveIcon( - context, - result, - info instanceof ItemInfoWithIcon iiwi ? iiwi.bitmap : null); - if (themed != null) { - result = themed; + // Inject monochrome icon drawable + if (useTheme) { + result.mutate(); + int[] colors = ThemedIconDrawable.getColors(context); + Drawable mono = null; + if (ATLEAST_T) { + mono = result.getMonochrome(); + } + + if (mono != null) { + mono.setTint(colors[1]); + } else if (info instanceof ItemInfoWithIcon iiwi) { + // Inject a previously generated monochrome icon + Bitmap monoBitmap = iiwi.bitmap.getMono(); + if (monoBitmap != null) { + // Use BitmapDrawable instead of FastBitmapDrawable so that the colorState is + // preserved in constantState + mono = new BitmapDrawable(monoBitmap); + if (ATLEAST_Q) { + mono.setColorFilter(new BlendModeColorFilter(colors[1], BlendMode.SRC_IN)); + } + // Inset the drawable according to the AdaptiveIconDrawable layers + mono = new InsetDrawable(mono, getExtraInsetFraction() / 2); } } + if (mono != null) { + result = new AdaptiveIconDrawable(new ColorDrawable(colors[0]), mono); + } } if (badge == null) { badge = BitmapInfo.LOW_RES_INFO.withFlags( UserCache.INSTANCE.get(context) .getUserInfo(info.user) - .applyBitmapInfoFlags(FlagOp.NO_OP) - ) - .getBadgeDrawable(context, useTheme, getIconShapeOrNull(context)); + .applyBitmapInfoFlags(FlagOp.NO_OP)) + .getBadgeDrawable(context, useTheme); if (badge == null) { badge = new ColorDrawable(Color.TRANSPARENT); } @@ -845,54 +840,11 @@ public final class Utilities { } /** - * Rotates `inOutBounds` by `delta` 90-degree increments. Rotation is visually CW. Parent - * sizes represent the "space" that will rotate carrying inOutBounds along with it to determine + * Rotates `inOutBounds` by `delta` 90-degree increments. Rotation is visually + * CCW. Parent + * sizes represent the "space" that will rotate carrying inOutBounds along with + * it to determine * the final bounds. - * - * As an example if this is the input: - * +-------------+ - * | +-----+ | - * | | | | - * | +-----+ | - * | | - * | | - * | | - * +-------------+ - * This would be case delta % 4 == 0: - * +-------------+ - * | +-----+ | - * | | | | - * | +-----+ | - * | | - * | | - * | | - * +-------------+ - * This would be case delta % 4 == 1: - * +----------------+ - * | +--+ | - * | | | | - * | | | | - * | +--+ | - * | | - * +----------------+ - * This would be case delta % 4 == 2: // This is case was reverted to previous behaviour which - * doesn't match the illustration due to b/353965234 - * +-------------+ - * | | - * | | - * | | - * | +-----+ | - * | | | | - * | +-----+ | - * +-------------+ - * This would be case delta % 4 == 3: - * +----------------+ - * | +--+ | - * | | | | - * | | | | - * | +--+ | - * | | - * +----------------+ */ public static void rotateBounds(Rect inOutBounds, int parentWidth, int parentHeight, int delta) { @@ -921,9 +873,10 @@ public final class Utilities { } /** - * Make a color filter that blends a color into the destination based on a scalable amout. + * Make a color filter that blends a color into the destination based on a + * scalable amout. * - * @param color to blend in. + * @param color to blend in. * @param tintAmount [0-1] 0 no tinting, 1 full color. * @return ColorFilter for tinting, or {@code null} if no filter is needed. */ @@ -932,7 +885,8 @@ public final class Utilities { return null; } return new LightingColorFilter( - // This isn't blending in white, its making a multiplication mask for the base color + // This isn't blending in white, its making a multiplication mask for the base + // color ColorUtils.blendARGB(Color.WHITE, 0, tintAmount), ColorUtils.blendARGB(0, color, tintAmount)); } @@ -944,7 +898,8 @@ public final class Utilities { } /** - * Returns a list of screen-splitting options depending on the device orientation (split top for + * Returns a list of screen-splitting options depending on the device + * orientation (split top for * portrait, split right for landscape) */ public static List getSplitPositionOptions( @@ -959,37 +914,61 @@ public final class Utilities { splitIconRes, R.string.recent_task_option_split_screen, stagePosition, - STAGE_TYPE_MAIN - )); + STAGE_TYPE_MAIN)); } - /** Logs the Scale and Translate properties of a matrix. Ignores skew and perspective. */ + /** + * Logs the Scale and Translate properties of a matrix. Ignores skew and + * perspective. + */ public static void logMatrix(String label, Matrix matrix) { float[] matrixValues = new float[9]; matrix.getValues(matrixValues); Log.d(label, String.format("%s: %s\nscale (x,y) = (%f, %f)\ntranslate (x,y) = (%f, %f)", label, matrix, matrixValues[Matrix.MSCALE_X], matrixValues[Matrix.MSCALE_Y], - matrixValues[Matrix.MTRANS_X], matrixValues[Matrix.MTRANS_Y] - )); + matrixValues[Matrix.MTRANS_X], matrixValues[Matrix.MTRANS_Y])); + } + + public static SharedPreferences getPrefs(Context context) { + // Use application context for shared preferences, so that we use a single + // cached instance + return context.getApplicationContext().getSharedPreferences( + LauncherFiles.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE); + } + + public static SharedPreferences getDevicePrefs(Context context) { + // Use application context for shared preferences, so that we use a single + // cached instance + return context.getApplicationContext().getSharedPreferences( + LauncherFiles.DEVICE_PREFERENCES_KEY, Context.MODE_PRIVATE); } /** - * Translates the {@code targetView} so that it overlaps with {@code exclusionBounds} as little + * Translates the {@code targetView} so that it overlaps with + * {@code exclusionBounds} as little * as possible, while remaining within {@code inclusionBounds}. *

- * {@code inclusionBounds} will always take precedence over {@code exclusionBounds}, so if - * {@code targetView} needs to be translated outside of {@code inclusionBounds} to fully fix an - * overlap with {@code exclusionBounds}, then {@code targetView} will only be translated up to + * {@code inclusionBounds} will always take precedence over + * {@code exclusionBounds}, so if + * {@code targetView} needs to be translated outside of {@code inclusionBounds} + * to fully fix an + * overlap with {@code exclusionBounds}, then {@code targetView} will only be + * translated up to * the border of {@code inclusionBounds}. *

- * Note: {@code targetViewBounds}, {@code inclusionBounds} and {@code exclusionBounds} must all + * Note: {@code targetViewBounds}, {@code inclusionBounds} and + * {@code exclusionBounds} must all * be in relation to the same reference point on screen. *

- * @param targetView the view being translated - * @param targetViewBounds the bounds of the {@code targetView} - * @param inclusionBounds the bounds the {@code targetView} absolutely must stay within - * @param exclusionBounds the bounds to try to move the {@code targetView} away from - * @param adjustmentDirection the translation direction that should be attempted to fix an + * + * @param targetView the view being translated + * @param targetViewBounds the bounds of the {@code targetView} + * @param inclusionBounds the bounds the {@code targetView} absolutely must + * stay within + * @param exclusionBounds the bounds to try to move the {@code targetView} + * away from + * @param adjustmentDirection the translation direction that should be attempted + * to fix an * overlap */ public static void translateOverlappingView( @@ -998,9 +977,6 @@ public final class Utilities { @NonNull Rect inclusionBounds, @NonNull Rect exclusionBounds, @AdjustmentDirection int adjustmentDirection) { - if (!Rect.intersects(targetViewBounds, exclusionBounds)) { - return; - } switch (adjustmentDirection) { case TRANSLATE_RIGHT: targetView.setTranslationX(Math.min( @@ -1036,8 +1012,10 @@ public final class Utilities { } /** - * Does a depth-first search through the View hierarchy starting at root, to find a view that - * matches the predicate. Returns null if no View was found. View has a findViewByPredicate + * Does a depth-first search through the View hierarchy starting at root, to + * find a view that + * matches the predicate. Returns null if no View was found. View has a + * findViewByPredicate * member function but it is currently a @hide API. */ @Nullable @@ -1057,29 +1035,4 @@ public final class Utilities { } return null; } - - /** - * Returns current icon shape to use for badges if flag is on, otherwise null. - */ - @Nullable - public static Path getIconShapeOrNull(Context context) { - if (Flags.enableLauncherIconShapes()) { - return ThemeManager.INSTANCE.get(context) - .getIconShape() - .getPath(DEFAULT_PATH_SIZE); - } else { - return null; - } - } - - /** - * Logs with DEBUG priority if the current device is a debug device. - * - *

Debug devices by default include -eng and -userdebug builds, but not -user builds. - */ - public static void debugLog(String tag, String message) { - if (false) { - Log.d(tag, message); - } - } } diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 97a35fd43d..534e8e05b3 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -13,12 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * Modifications copyright 2025, Lawnchair + * Modifications copyright 2021, Lawnchair */ package com.android.launcher3; -import static com.android.launcher3.AbstractFloatingView.TYPE_WIDGET_RESIZE_FRAME; import static com.android.launcher3.BubbleTextView.DISPLAY_FOLDER; import static com.android.launcher3.Flags.enableSmartspaceRemovalToggle; import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY; @@ -32,13 +31,14 @@ import static com.android.launcher3.LauncherState.HINT_STATE; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.SPRING_LOADED; import static com.android.launcher3.MotionEventsUtils.isTrackpadMultiFingerSwipe; -import static com.android.launcher3.Utilities.SHOULD_SHOW_FIRST_PAGE_WIDGET; import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback; import static com.android.launcher3.config.FeatureFlags.FOLDABLE_SINGLE_PAGE; import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SWIPELEFT; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SWIPERIGHT; +import static app.lawnchair.util.LawnchairUtilsKt.toBitmap; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.LayoutTransition; @@ -55,7 +55,8 @@ import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.drawable.Drawable; -import android.os.Bundle; +import android.os.Handler; +import android.os.Message; import android.os.Parcelable; import android.util.AttributeSet; import android.util.Log; @@ -70,10 +71,8 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.widget.FrameLayout; import android.widget.Toast; -import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; -import androidx.core.view.ViewCompat; import com.android.app.animation.Interpolators; import com.android.launcher3.accessibility.AccessibleDragListenerAdapter; @@ -85,8 +84,7 @@ import com.android.launcher3.celllayout.CellLayoutLayoutParams; import com.android.launcher3.celllayout.CellPosMapper; import com.android.launcher3.celllayout.CellPosMapper.CellPos; import com.android.launcher3.config.FeatureFlags; -import com.android.launcher3.debug.TestEventEmitter; -import com.android.launcher3.debug.TestEventEmitter.TestEvent; +import com.android.launcher3.dot.FolderDotInfo; import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.dragndrop.DragOptions; @@ -111,7 +109,6 @@ import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.pageindicators.PageIndicator; import com.android.launcher3.statemanager.StateManager; import com.android.launcher3.statemanager.StateManager.StateHandler; -import com.android.launcher3.statemanager.StateManager.StateListener; import com.android.launcher3.states.StateAnimationConfig; import com.android.launcher3.touch.WorkspaceTouchListener; import com.android.launcher3.util.EdgeEffectCompat; @@ -120,21 +117,24 @@ import com.android.launcher3.util.IntArray; import com.android.launcher3.util.IntSet; import com.android.launcher3.util.IntSparseArrayMap; import com.android.launcher3.util.LauncherBindableItemsContainer; -import com.android.launcher3.util.MSDLPlayerWrapper; import com.android.launcher3.util.OverlayEdgeEffect; +import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.util.RunnableList; import com.android.launcher3.util.Thunk; import com.android.launcher3.util.WallpaperOffsetInterpolator; import com.android.launcher3.widget.LauncherAppWidgetHostView; +import com.android.launcher3.widget.LauncherWidgetHolder; +import com.android.launcher3.widget.LauncherWidgetHolder.ProviderChangedListener; import com.android.launcher3.widget.NavigableAppWidgetHostView; import com.android.launcher3.widget.PendingAddShortcutInfo; import com.android.launcher3.widget.PendingAddWidgetInfo; +import com.android.launcher3.widget.PendingAppWidgetHostView; +import com.android.launcher3.widget.WidgetManagerHelper; import com.android.launcher3.widget.util.WidgetSizes; +import com.patrykmichalik.opto.core.PreferenceExtensionsKt; import com.android.systemui.plugins.shared.LauncherOverlayManager.LauncherOverlayCallbacks; import com.android.systemui.plugins.shared.LauncherOverlayManager.LauncherOverlayTouchProxy; -import com.google.android.msdl.data.model.MSDLToken; - import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -142,8 +142,6 @@ import java.util.function.Consumer; import java.util.function.Predicate; import java.util.stream.Collectors; -import com.patrykmichalik.opto.core.PreferenceExtensionsKt; -import static app.lawnchair.util.LawnchairUtilsKt.toBitmap; import app.lawnchair.LawnchairApp; import app.lawnchair.LawnchairAppKt; import app.lawnchair.preferences.PreferenceManager; @@ -251,7 +249,6 @@ public class Workspace extends PagedView boolean mChildrenLayersEnabled = true; private boolean mStripScreensOnPageStopMoving = false; - public boolean mHasOnLayoutBeenCalled = false; private boolean mWorkspaceFadeInAdjacentScreens; @@ -267,7 +264,8 @@ public class Workspace extends PagedView private boolean mCreateUserFolderOnDrop = false; private boolean mAddToExistingFolderOnDrop = false; - // Variables relating to touch disambiguation (scrolling workspace vs. scrolling a widget) + // Variables relating to touch disambiguation (scrolling workspace vs. scrolling + // a widget) private float mXDown; private float mYDown; private View mFirstPagePinnedItem; @@ -314,21 +312,6 @@ public class Workspace extends PagedView private final StatsLogManager mStatsLogManager; - private final MSDLPlayerWrapper mMSDLPlayerWrapper; - - private final StateManager.StateListener mAccessibilityDropListener = - new StateListener<>() { - @Override - public void onStateTransitionComplete(LauncherState finalState) { - if (finalState == NORMAL) { - performAccessibilityActionOnViewTree(Workspace.this); - } - } - }; - - @Nullable - private DragController.DragListener mAccessibilityDragListener; - PreferenceManager2 mPreferenceManager2; PreferenceManager mPreferenceManger; @@ -336,7 +319,8 @@ public class Workspace extends PagedView * Used to inflate the Workspace from XML. * * @param context The application's context. - * @param attrs The attributes set containing the Workspace's customization values. + * @param attrs The attributes set containing the Workspace's customization + * values. */ public Workspace(Context context, AttributeSet attrs) { this(context, attrs, 0); @@ -346,15 +330,17 @@ public class Workspace extends PagedView * Used to inflate the Workspace from XML. * * @param context The application's context. - * @param attrs The attributes set containing the Workspace's customization values. + * @param attrs The attributes set containing the Workspace's customization + * values. * @param defStyle Unused. */ public Workspace(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); + mLauncher = Launcher.getLauncher(context); mStateTransitionAnimation = new WorkspaceStateTransitionAnimation(mLauncher, this); - mWallpaperManager = WallpaperManager.getInstance(context); mAllAppsIconSize = mLauncher.getDeviceProfile().allAppsIconSizePx; + mWallpaperManager = WallpaperManager.getInstance(context); mPreferenceManager2 = PreferenceManager2.getInstance(context); mPreferenceManger = PreferenceManager.getInstance(context); @@ -367,7 +353,6 @@ public class Workspace extends PagedView setMotionEventSplittingEnabled(true); setOnTouchListener(new WorkspaceTouchListener(mLauncher, this)); mStatsLogManager = StatsLogManager.newInstance(context); - mMSDLPlayerWrapper = MSDLPlayerWrapper.INSTANCE.get(context); } @Override @@ -395,6 +380,36 @@ public class Workspace extends PagedView updateCellLayoutMeasures(); updateWorkspaceWidgetsSizes(); setPageIndicatorInset(); + updateWorkspaceScreensPadding(); + } + + private void updateWorkspaceScreensPadding() { + DeviceProfile grid = mLauncher.getDeviceProfile(); + int paddingLeftRight = grid.cellLayoutPaddingPx.right; + int paddingBottom = grid.cellLayoutPaddingPx.bottom; + + int panelCount = getPanelCount(); + int rightPanelModulus = mIsRtl ? 0 : panelCount - 1; + int leftPanelModulus = mIsRtl ? panelCount - 1 : 0; + int numberOfScreens = mScreenOrder.size(); + for (int i = 0; i < numberOfScreens; i++) { + int paddingLeft = paddingLeftRight; + int paddingRight = paddingLeftRight; + // Add missing cellLayout border in-between panels. + if (panelCount > 1) { + if (i % panelCount == leftPanelModulus) { + paddingRight += grid.cellLayoutBorderSpacePx.x / 2; + } else if (i % panelCount == rightPanelModulus) { // right side panel + paddingLeft += grid.cellLayoutBorderSpacePx.x / 2; + } else { // middle panel + paddingLeft += grid.cellLayoutBorderSpacePx.x / 2; + paddingRight += grid.cellLayoutBorderSpacePx.x / 2; + } + } + // SparseArrayMap doesn't keep the order + mWorkspaceScreens.get(mScreenOrder.get(i)) + .setPadding(paddingLeft, 0, paddingRight, paddingBottom); + } } private void setPageIndicatorInset() { @@ -509,19 +524,22 @@ public class Workspace extends PagedView if (mDragInfo != null && mDragInfo.cell != null) { CellLayout layout = (CellLayout) (mDragInfo.cell instanceof LauncherAppWidgetHostView - // LC: https://github.com/LawnchairLauncher/lawnchair/issues/3143 + // https://github.com/LawnchairLauncher/lawnchair/issues/3143 && dragObject.dragView.getContentViewParent() != null - ? dragObject.dragView.getContentViewParent().getParent() - : mDragInfo.cell.getParent().getParent()); + ? dragObject.dragView.getContentViewParent().getParent() + : mDragInfo.cell.getParent().getParent()); layout.markCellsAsUnoccupiedForView(mDragInfo.cell); } updateChildrenLayersEnabled(); - // Do not add a new page if it is a accessible drag which was not started by the workspace. - // We do not support accessibility drag from other sources and instead provide a direct + // Do not add a new page if it is a accessible drag which was not started by the + // workspace. + // We do not support accessibility drag from other sources and instead provide a + // direct // action for move/add to homescreen. - // When a accessible drag is started by the folder, we only allow rearranging withing the + // When a accessible drag is started by the folder, we only allow rearranging + // withing the // folder. boolean addNewPage = !(options.isAccessibleDrag && dragObject.dragSource != this); if (addNewPage) { @@ -531,10 +549,12 @@ public class Workspace extends PagedView if (dragObject.dragInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET && dragObject.dragSource != this) { // When dragging a widget from different source, move to a page which has - // enough space to place this widget (after rearranging/resizing). We special case + // enough space to place this widget (after rearranging/resizing). We special + // case // widgets as they cannot be placed inside a folder. // Start at the current page and search right (on LTR) until finding a page with - // enough space. Since an empty screen is the furthest right, a page must be found. + // enough space. Since an empty screen is the furthest right, a page must be + // found. int currentPage = getDestinationPage(); for (int pageIndex = currentPage; pageIndex < getPageCount(); pageIndex++) { CellLayout page = (CellLayout) getPageAt(pageIndex); @@ -546,9 +566,6 @@ public class Workspace extends PagedView } } - if (mAccessibilityDragListener != null) { - mAccessibilityDragListener.onDragStart(dragObject, options); - } if (!mLauncher.isInState(EDIT_MODE)) { mLauncher.getStateManager().goToState(SPRING_LOADED); } @@ -585,9 +602,6 @@ public class Workspace extends PagedView } }); - if (mAccessibilityDragListener != null) { - mAccessibilityDragListener.onDragEnd(); - } mDragInfo = null; mDragSourceInternal = null; } @@ -619,7 +633,8 @@ public class Workspace extends PagedView mLayoutTransition.enableTransitionType(LayoutTransition.DISAPPEARING); mLayoutTransition.enableTransitionType(LayoutTransition.CHANGE_DISAPPEARING); - // Change the interpolators such that the fade animation plays before the move animation. + // Change the interpolators such that the fade animation plays before the move + // animation. // This prevents empty adjacent pages to overlay during animation mLayoutTransition.setInterpolator(LayoutTransition.DISAPPEARING, Interpolators.clampToProgress(Interpolators.ACCELERATE_DECELERATE, 0, 0.5f)); @@ -654,24 +669,25 @@ public class Workspace extends PagedView * Initializes and binds the first page */ public void bindAndInitFirstWorkspaceScreen() { - // Add the first page - CellLayout firstPage = insertNewWorkspaceScreen(Workspace.FIRST_SCREEN_ID, getChildCount()); - if (!PreferenceExtensionsKt.firstBlocking(mPreferenceManager2.getEnableSmartspace())) { - mFirstPagePinnedItem = null; + if (!FeatureFlags.topQsbOnFirstScreenEnabled(mLauncher)) { return; } + + // Add the first page + CellLayout firstPage = insertNewWorkspaceScreen(Workspace.FIRST_SCREEN_ID, getChildCount()); + // Always add a QSB on the first screen. if (mFirstPagePinnedItem == null) { SmartspaceMode smartspaceMode = PreferenceExtensionsKt - .firstBlocking(mPreferenceManager2.getSmartspaceMode()); + .firstBlocking(mPreferenceManager2.getSmartspaceMode()); if (!smartspaceMode.isAvailable(this.mLauncher)) { // The current smartspace mode is not available, // setting the smartspace mode to one that is always available smartspaceMode = LawnchairSmartspace.INSTANCE; PreferenceExtensionsKt.setBlocking(mPreferenceManager2.getSmartspaceMode(), smartspaceMode); } - // In transposed layout, we add the first page pinned widget in the Grid. - // As workspace does not touch the edges, we do not need a full - // width first page pinned item. + // In transposed layout, we add the QSB in the Grid. As workspace does not touch + // the + // edges, we do not need a full width QSB. mFirstPagePinnedItem = LayoutInflater.from(getContext()) .inflate(smartspaceMode.getLayoutResourceId(), firstPage, false); } @@ -686,16 +702,21 @@ public class Workspace extends PagedView } public void removeAllWorkspaceScreens() { - // Disable all layout transitions before removing all pages to ensure that we don't get the + // Disable all layout transitions before removing all pages to ensure that we + // don't get the // transition animations competing with us changing the scroll when we add pages disableLayoutTransitions(); // Recycle the first page pinned item if (mFirstPagePinnedItem != null) { - ((ViewGroup) mFirstPagePinnedItem.getParent()).removeView(mFirstPagePinnedItem); + ViewGroup viewGroup = (ViewGroup) mFirstPagePinnedItem.getParent(); + if (viewGroup != null) { + viewGroup.removeView(mFirstPagePinnedItem); + } } // Remove the pages and clear the screen models + removeFolderListeners(); removeAllViews(); mScreenOrder.clear(); mWorkspaceScreens.clear(); @@ -705,12 +726,15 @@ public class Workspace extends PagedView bindAndInitFirstWorkspaceScreen(); } + // Remove any deferred refresh callbacks + mLauncher.mHandler.removeCallbacksAndMessages(DeferredWidgetRefresh.class); + // Re-enable the layout transitions enableLayoutTransitions(); } public void insertNewWorkspaceScreenBeforeEmptyScreen(int screenId) { - // Find the index to insert this view into. If the empty screen exists, then + // Find the index to insert this view into. If the empty screen exists, then // insert it before that. int insertIndex = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID); if (insertIndex < 0) { @@ -729,7 +753,8 @@ public class Workspace extends PagedView return mWorkspaceScreens.get(screenId); } - // Inflate the cell layout, but do not add it automatically so that we can get the newly + // Inflate the cell layout, but do not add it automatically so that we can get + // the newly // created CellLayout. DeviceProfile dp = mLauncher.getDeviceProfile(); CellLayout newScreen; @@ -768,8 +793,10 @@ public class Workspace extends PagedView dragSourceChildCount += pagePair.getShortcutsAndWidgets().getChildCount(); } - // When the drag view content is a LauncherAppWidgetHostView, we should increment the - // drag source child count by 1 because the widget in drag has been detached from its + // When the drag view content is a LauncherAppWidgetHostView, we should + // increment the + // drag source child count by 1 because the widget in drag has been detached + // from its // original parent, ShortcutAndWidgetContainer, and reattached to the DragView. if (dragObject.dragView.getContentView() instanceof LauncherAppWidgetHostView) { dragSourceChildCount++; @@ -779,8 +806,8 @@ public class Workspace extends PagedView lastChildOnScreen = true; } CellLayout cl = (CellLayout) mDragSourceInternal.getParent(); - if (!FOLDABLE_SINGLE_PAGE.get() && getLeftmostVisiblePageForIndex(indexOfChild(cl)) - == getLeftmostVisiblePageForIndex(getPageCount() - 1)) { + if (!FOLDABLE_SINGLE_PAGE.get() && getLeftmostVisiblePageForIndex( + indexOfChild(cl)) == getLeftmostVisiblePageForIndex(getPageCount() - 1)) { childOnFinalScreen = true; } } @@ -797,7 +824,6 @@ public class Workspace extends PagedView }); } - /** * Returns if the given screenId is already in the Workspace */ @@ -807,7 +833,8 @@ public class Workspace extends PagedView /** * Inserts extra empty pages to the end of the existing workspaces. - * Usually we add one extra empty screen, but when two panel home is enabled we add + * Usually we add one extra empty screen, but when two panel home is enabled we + * add * two extra screens. **/ public void addExtraEmptyScreens() { @@ -831,7 +858,8 @@ public class Workspace extends PagedView } /** - * If two panel home is enabled we convert the last two screens that are visible at the same + * If two panel home is enabled we convert the last two screens that are visible + * at the same * time. In other cases we only convert the last page. */ private void convertFinalScreenToEmptyScreenIfNecessary() { @@ -848,7 +876,8 @@ public class Workspace extends PagedView SparseArray finalScreens = new SparseArray<>(); int pageCount = mScreenOrder.size(); - // First we add the last page(s) to the finalScreens collection. The number of final pages + // First we add the last page(s) to the finalScreens collection. The number of + // final pages // depends on the panel count. for (int pageIndex = pageCount - panelCount; pageIndex < pageCount; pageIndex++) { int screenId = mScreenOrder.get(pageIndex); @@ -861,12 +890,14 @@ public class Workspace extends PagedView finalScreens.append(screenId, screen); } - // Then we remove the final screens from the collections (but not from the view hierarchy) + // Then we remove the final screens from the collections (but not from the view + // hierarchy) // and we store them as extra empty screens. for (int i = 0; i < finalScreens.size(); i++) { int screenId = finalScreens.keyAt(i); - // We don't want to remove the first screen even if it's empty because that's where + // We don't want to remove the first screen even if it's empty because that's + // where // first page pinned item would go if it gets turned back on. if (enableSmartspaceRemovalToggle() && screenId == FIRST_SCREEN_ID) { continue; @@ -878,7 +909,8 @@ public class Workspace extends PagedView mScreenOrder.removeValue(screenId); int newScreenId = mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID) - ? EXTRA_EMPTY_SCREEN_SECOND_ID : EXTRA_EMPTY_SCREEN_ID; + ? EXTRA_EMPTY_SCREEN_SECOND_ID + : EXTRA_EMPTY_SCREEN_ID; mWorkspaceScreens.put(newScreenId, screen); mScreenOrder.add(newScreenId); } @@ -890,12 +922,17 @@ public class Workspace extends PagedView /** * The purpose of this method is to remove empty pages from Workspace. - * Empty page(s) from the end of mWorkspaceScreens will always be removed. The pages with - * ID = Workspace.EXTRA_EMPTY_SCREEN_IDS will be removed if there are other non-empty pages. - * If there are no more non-empty pages left, extra empty page(s) will either stay or get added. + * Empty page(s) from the end of mWorkspaceScreens will always be removed. The + * pages with + * ID = Workspace.EXTRA_EMPTY_SCREEN_IDS will be removed if there are other + * non-empty pages. + * If there are no more non-empty pages left, extra empty page(s) will either + * stay or get added. *

- * If stripEmptyScreens is true, all empty pages (not just the ones on the end) will be removed - * from the Workspace, and if there are no more pages left then extra empty page(s) will be + * If stripEmptyScreens is true, all empty pages (not just the ones on the end) + * will be removed + * from the Workspace, and if there are no more pages left then extra empty + * page(s) will be * added. *

* The number of extra empty pages is equal to what getPanelCount() returns. @@ -921,7 +958,8 @@ public class Workspace extends PagedView // First we convert the last page to an extra page if the last page is empty // and we don't already have an extra page. convertFinalScreenToEmptyScreenIfNecessary(); - // Then we remove the extra page(s) if they are not the only pages left in Workspace. + // Then we remove the extra page(s) if they are not the only pages left in + // Workspace. if (hasExtraEmptyScreens()) { forEachExtraEmptyPageId(extraEmptyPageId -> { removeView(mWorkspaceScreens.get(extraEmptyPageId)); @@ -929,9 +967,6 @@ public class Workspace extends PagedView mScreenOrder.removeValue(extraEmptyPageId); }); - // Since we removed some screens, before moving to next page, update the state - // description with correct page numbers. - updateAccessibilityViewPageDescription(); setCurrentPage(getNextPage()); // Update the page indicator to reflect the removed page. @@ -939,7 +974,8 @@ public class Workspace extends PagedView } if (stripEmptyScreens) { - // This will remove all empty pages from the Workspace. If there are no more pages left, + // This will remove all empty pages from the Workspace. If there are no more + // pages left, // it will add extra page(s) so that users can put items on at least one page. stripEmptyScreens(); } @@ -953,12 +989,14 @@ public class Workspace extends PagedView return mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID) && getChildCount() > getPanelCount() && (!isTwoPanelEnabled() - || mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_SECOND_ID)); + || mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_SECOND_ID)); } /** - * Commits the extra empty pages then returns the screen ids of those new screens. - * Usually there's only one extra empty screen, but when two panel home is enabled we commit + * Commits the extra empty pages then returns the screen ids of those new + * screens. + * Usually there's only one extra empty screen, but when two panel home is + * enabled we commit * two extra screens. *

* Returns an empty IntSet in case we cannot commit any new screens. @@ -970,8 +1008,7 @@ public class Workspace extends PagedView } IntSet extraEmptyPageIds = new IntSet(); - forEachExtraEmptyPageId(extraEmptyPageId -> - extraEmptyPageIds.add(commitExtraEmptyScreen(extraEmptyPageId))); + forEachExtraEmptyPageId(extraEmptyPageId -> extraEmptyPageIds.add(commitExtraEmptyScreen(extraEmptyPageId))); return extraEmptyPageIds; } @@ -983,7 +1020,8 @@ public class Workspace extends PagedView int newScreenId = LauncherAppState.getInstance(getContext()) .getModel().getModelDbController().getNewScreenId(); - // Launcher database isn't aware of empty pages that are already bound, so we need to + // Launcher database isn't aware of empty pages that are already bound, so we + // need to // skip those IDs manually. while (mWorkspaceScreens.containsKey(newScreenId)) { newScreenId++; @@ -1049,7 +1087,8 @@ public class Workspace extends PagedView } /** - * Returns the screen ID of a page that is shown together with the given page screen ID when the + * Returns the screen ID of a page that is shown together with the given page + * screen ID when the * two panel UI is enabled. */ public int getScreenPair(int screenId) { @@ -1065,7 +1104,8 @@ public class Workspace extends PagedView } /** - * Returns {@link CellLayout} that is shown together with the given {@link CellLayout} when the + * Returns {@link CellLayout} that is shown together with the given + * {@link CellLayout} when the * two panel UI is enabled. */ @Nullable @@ -1086,7 +1126,6 @@ public class Workspace extends PagedView // This is dangerous and can result in data loss. return; } - if (isPageInTransition()) { mStripScreensOnPageStopMoving = true; return; @@ -1099,16 +1138,18 @@ public class Workspace extends PagedView int id = mWorkspaceScreens.keyAt(i); CellLayout cl = mWorkspaceScreens.valueAt(i); // FIRST_SCREEN_ID can never be removed. - if ((!PreferenceExtensionsKt.firstBlocking(PreferenceManager2.INSTANCE.get(getContext()).getEnableSmartspace()) || id > FIRST_SCREEN_ID) + if ((!FeatureFlags.topQsbOnFirstScreenEnabled(mLauncher) || id > FIRST_SCREEN_ID) && cl.getShortcutsAndWidgets().getChildCount() == 0) { removeScreens.add(id); } } - // When two panel home is enabled we only remove an empty page if both visible pages are + // When two panel home is enabled we only remove an empty page if both visible + // pages are // empty. if (isTwoPanelEnabled()) { - // We go through all the pages that were marked as removable and check their page pair + // We go through all the pages that were marked as removable and check their + // page pair Iterator removeScreensIterator = removeScreens.iterator(); while (removeScreensIterator.hasNext()) { int pageToRemove = removeScreensIterator.next(); @@ -1121,8 +1162,10 @@ public class Workspace extends PagedView } } - // We enforce at least one page (two pages on two panel home) to add new items to. - // In the case that we remove the last such screen(s), we convert the last screen(s) + // We enforce at least one page (two pages on two panel home) to add new items + // to. + // In the case that we remove the last such screen(s), we convert the last + // screen(s) // to the empty screen(s) int minScreens = getPanelCount(); @@ -1155,16 +1198,24 @@ public class Workspace extends PagedView if (pageShift >= 0) { setCurrentPage(currentPage - pageShift); } - - // Now that we have removed some pages, ensure state description is up to date. - updateAccessibilityViewPageDescription(); } /** - * Needed here because launcher has a fullscreen exclusion rect and doesn't pilfer the pointers. + * Needed here because launcher has a fullscreen exclusion rect and doesn't + * pilfer the pointers. */ @Override public boolean onInterceptTouchEvent(MotionEvent ev) { + if (ev.getAction() == MotionEvent.ACTION_DOWN) { + View touchedView = findViewAtPosition(ev.getX(), ev.getY()); + Boolean iconSwipeGestures = PreferenceExtensionsKt.firstBlocking(mPreferenceManager2.getIconSwipeGestures()); + + if (iconSwipeGestures && touchedView instanceof ShortcutAndWidgetContainer container) { + container.onTouchEvent(ev); + return false; + } + } + if (isTrackpadMultiFingerSwipe(ev)) { return false; } @@ -1202,7 +1253,8 @@ public class Workspace extends PagedView } /** - * Needed here because launcher has a fullscreen exclusion rect and doesn't pilfer the pointers. + * Needed here because launcher has a fullscreen exclusion rect and doesn't + * pilfer the pointers. */ @SuppressLint("ClickableViewAccessibility") @Override @@ -1213,15 +1265,13 @@ public class Workspace extends PagedView return super.onTouchEvent(ev); } - @Override - protected void onDisallowSwipeToMinusOnePage() { - mLauncher.getOverlayManager().onDisallowSwipeToMinusOnePage(); - } - /** - * Called directly from a CellLayout (not by the framework), after we've been added as a - * listener via setOnInterceptTouchEventListener(). This allows us to tell the CellLayout - * that it should intercept touch events, which is not something that is normally supported. + * Called directly from a CellLayout (not by the framework), after we've been + * added as a + * listener via setOnInterceptTouchEventListener(). This allows us to tell the + * CellLayout + * that it should intercept touch events, which is not something that is + * normally supported. */ @SuppressLint("ClickableViewAccessibility") @Override @@ -1239,7 +1289,8 @@ public class Workspace extends PagedView } /** - * This differs from isSwitchingState in that we take into account how far the transition + * This differs from isSwitchingState in that we take into account how far the + * transition * has completed. */ public boolean isFinishedSwitchingState() { @@ -1306,13 +1357,15 @@ public class Workspace extends PagedView @Override protected void determineScrollingStart(MotionEvent ev) { - if (!isFinishedSwitchingState() || mIsEventOverFirstPagePinnedItem) return; + if (!isFinishedSwitchingState() || mIsEventOverFirstPagePinnedItem) + return; float deltaX = ev.getX() - mXDown; float absDeltaX = Math.abs(deltaX); float absDeltaY = Math.abs(ev.getY() - mYDown); - if (Float.compare(absDeltaX, 0f) == 0) return; + if (Float.compare(absDeltaX, 0f) == 0) + return; float slope = absDeltaY / absDeltaX; float theta = (float) Math.atan(slope); @@ -1326,12 +1379,13 @@ public class Workspace extends PagedView return; } else if (theta > START_DAMPING_TOUCH_SLOP_ANGLE) { // Above START_DAMPING_TOUCH_SLOP_ANGLE and below MAX_SWIPE_ANGLE, we want to - // increase the touch slop to make it harder to begin scrolling the workspace. This - // results in vertically scrolling widgets to more easily. The higher the angle, the + // increase the touch slop to make it harder to begin scrolling the workspace. + // This + // results in vertically scrolling widgets to more easily. The higher the angle, + // the // more we increase touch slop. theta -= START_DAMPING_TOUCH_SLOP_ANGLE; - float extraRatio = (float) - Math.sqrt((theta / (MAX_SWIPE_ANGLE - START_DAMPING_TOUCH_SLOP_ANGLE))); + float extraRatio = (float) Math.sqrt((theta / (MAX_SWIPE_ANGLE - START_DAMPING_TOUCH_SLOP_ANGLE))); super.determineScrollingStart(ev, 1 + TOUCH_SLOP_DAMPING_FACTOR * extraRatio); } else { // Below START_DAMPING_TOUCH_SLOP_ANGLE, we don't do anything special @@ -1340,10 +1394,6 @@ public class Workspace extends PagedView } protected void onPageBeginTransition() { - // Widget resize frame doesn't receive events to close when talkback is enabled. For that - // case, close it here. - AbstractFloatingView.closeOpenViews(mLauncher, false, TYPE_WIDGET_RESIZE_FRAME); - super.onPageBeginTransition(); updateChildrenLayersEnabled(); } @@ -1354,7 +1404,8 @@ public class Workspace extends PagedView if (mDragController.isDragging()) { if (workspaceInModalState()) { - // If we are in springloaded mode, then force an event to check if the current touch + // If we are in springloaded mode, then force an event to check if the current + // touch // is under a new page (to scroll to) mDragController.forceTouchMove(); } @@ -1365,7 +1416,8 @@ public class Workspace extends PagedView mStripScreensOnPageStopMoving = false; } - // Inform the Launcher activity that the page transition ended so that it can react to the + // Inform the Launcher activity that the page transition ended so that it can + // react to the // newly visible page if it wants to. mLauncher.onPageEndTransition(); } @@ -1406,7 +1458,8 @@ public class Workspace extends PagedView super.onScrollChanged(l, t, oldl, oldt); // Update the page indicator progress. - // Unlike from other states, we show the page indicator when transitioning from HINT_STATE. + // Unlike from other states, we show the page indicator when transitioning from + // HINT_STATE. boolean isSwitchingState = mIsSwitchingState && mLauncher.getStateManager().getCurrentStableState() != HINT_STATE; boolean isTransitioning = isSwitchingState @@ -1430,13 +1483,15 @@ public class Workspace extends PagedView @Override protected boolean shouldFlingForVelocity(int velocityX) { - // When the overlay is moving, the fling or settle transition is controlled by the overlay. + // When the overlay is moving, the fling or settle transition is controlled by + // the overlay. return Float.compare(Math.abs(mOverlayProgress), 0) == 0 && super.shouldFlingForVelocity(velocityX); } /** - * The overlay scroll is being controlled locally, just update our overlay effect + * The overlay scroll is being controlled locally, just update our overlay + * effect */ @Override public void onOverlayScrollChanged(float scroll) { @@ -1478,14 +1533,16 @@ public class Workspace extends PagedView super.notifyPageSwitchListener(prevPage); if (prevPage != mCurrentPage) { StatsLogManager.EventEnum event = (prevPage < mCurrentPage) - ? LAUNCHER_SWIPERIGHT : LAUNCHER_SWIPELEFT; + ? LAUNCHER_SWIPERIGHT + : LAUNCHER_SWIPELEFT; mLauncher.getStatsLogManager().logger() .withSrcState(LAUNCHER_STATE_HOME) .withDstState(LAUNCHER_STATE_HOME) .withContainerInfo(LauncherAtom.ContainerInfo.newBuilder() .setWorkspace( LauncherAtom.WorkspaceContainer.newBuilder() - .setPageIndex(prevPage)).build()) + .setPageIndex(prevPage)) + .build()) .log(event); updateStatusbarClock(); } @@ -1532,7 +1589,8 @@ public class Workspace extends PagedView } private void updatePageAlphaValues() { - // We need to check the isDragging case because updatePageAlphaValues is called between + // We need to check the isDragging case because updatePageAlphaValues is called + // between // goToState(SPRING_LOADED) and onStartStateTransition. if (!workspaceInModalState() && !mIsSwitchingState && !mDragController.isDragging()) { int screenCenter = getScrollX() + getMeasuredWidth() / 2; @@ -1569,18 +1627,15 @@ public class Workspace extends PagedView super.onAttachedToWindow(); mWallpaperOffset.setWindowToken(getWindowToken()); computeScroll(); - mLauncher.getStateManager().addStateListener(mAccessibilityDropListener); } protected void onDetachedFromWindow() { super.onDetachedFromWindow(); mWallpaperOffset.setWindowToken(null); - mLauncher.getStateManager().removeStateListener(mAccessibilityDropListener); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - mHasOnLayoutBeenCalled = true; // b/349929393 - is the required call to onLayout not done? if (mUnlockWallpaperFromDefaultPageOnLayout) { mWallpaperOffset.setLockToDefaultPage(false); mUnlockWallpaperFromDefaultPageOnLayout = false; @@ -1611,7 +1666,8 @@ public class Workspace extends PagedView } /** - * Returns whether a drag should be allowed to be started from the current workspace state. + * Returns whether a drag should be allowed to be started from the current + * workspace state. */ public boolean workspaceIconsCanBeDragged() { return mLauncher.getStateManager().getState().hasFlag(FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED); @@ -1675,7 +1731,8 @@ public class Workspace extends PagedView mWallpaperManager.sendWallpaperCommand(getWindowToken(), ev.getAction() == MotionEvent.ACTION_UP - ? WallpaperManager.COMMAND_TAP : WallpaperManager.COMMAND_SECONDARY_TAP, + ? WallpaperManager.COMMAND_TAP + : WallpaperManager.COMMAND_SECONDARY_TAP, position[0], position[1], 0, null); } @@ -1696,7 +1753,8 @@ public class Workspace extends PagedView } /** - * Sets the current workspace {@link LauncherState} and updates the UI without any animations + * Sets the current workspace {@link LauncherState} and updates the UI without + * any animations */ @Override public void setState(LauncherState toState) { @@ -1735,10 +1793,9 @@ public class Workspace extends PagedView public void updateAccessibilityFlags() { // TODO: Update the accessibility flags appropriately when dragging. - int accessibilityFlag = - mLauncher.getStateManager().getState().hasFlag(FLAG_WORKSPACE_INACCESSIBLE) - ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS - : IMPORTANT_FOR_ACCESSIBILITY_AUTO; + int accessibilityFlag = mLauncher.getStateManager().getState().hasFlag(FLAG_WORKSPACE_INACCESSIBLE) + ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS + : IMPORTANT_FOR_ACCESSIBILITY_AUTO; if (!mLauncher.getAccessibilityDelegate().isInAccessibleDrag()) { int total = getPageCount(); for (int i = 0; i < total; i++) { @@ -1751,8 +1808,10 @@ public class Workspace extends PagedView @Override public AccessibilityNodeInfo createAccessibilityNodeInfo() { if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS) { - // TAPL tests verify that workspace is not present in Overview and AllApps states. - // TAPL can work only if UIDevice is set up as setCompressedLayoutHeirarchy(false). + // TAPL tests verify that workspace is not present in Overview and AllApps + // states. + // TAPL can work only if UIDevice is set up as + // setCompressedLayoutHeirarchy(false). // Hiding workspace from the tests when it's // IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS. return AccessibilityNodeInfo.obtain(); @@ -1774,7 +1833,7 @@ public class Workspace extends PagedView child.setVisibility(INVISIBLE); if (options.isAccessibleDrag) { - mAccessibilityDragListener = + mDragController.addDragListener( new AccessibleDragListenerAdapter(this, WorkspaceAccessibilityHelper::new) { @Override protected void enableAccessibleDrag(boolean enable, @@ -1787,7 +1846,7 @@ public class Workspace extends PagedView IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); } } - }; + }); } beginDragShared(child, this, options); @@ -1806,7 +1865,8 @@ public class Workspace extends PagedView } /** - * Core functionality for beginning a drag operation for an item that will be dropped within + * Core functionality for beginning a drag operation for an item that will be + * dropped within * the workspace */ public DragView beginDragShared(View child, DraggableView draggableView, DragSource source, @@ -1854,7 +1914,6 @@ public class Workspace extends PagedView dragLayerY += dragRect.top; } - if (child.getParent() instanceof ShortcutAndWidgetContainer) { mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent(); } @@ -1874,11 +1933,7 @@ public class Workspace extends PagedView child.setVisibility(View.VISIBLE); if (dragOptions.preDragCondition != null) { - if (Flags.msdlFeedback()) { - mMSDLPlayerWrapper.playToken(MSDLToken.LONG_PRESS); - } else { - mLauncher.getDragLayer().performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); - } + mLauncher.getDragLayer().performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); } return null; } @@ -1892,7 +1947,7 @@ public class Workspace extends PagedView } final DragView dv; - if (contentView != null) { + if (contentView instanceof View) { dv = mDragController.startDrag( contentView, draggableView, @@ -1937,7 +1992,8 @@ public class Workspace extends PagedView if (dropTargetLayout == null) { return false; } - if (!transitionStateShouldAllowDrop()) return false; + if (!transitionStateShouldAllowDrop()) + return false; mDragViewVisualCenter = d.getVisualCenter(mDragViewVisualCenter); @@ -1999,8 +2055,9 @@ public class Workspace extends PagedView } boolean willCreateUserFolder(ItemInfo info, CellLayout target, int[] targetCell, - float distance, boolean considerTimeout) { - if (distance > target.getFolderCreationRadius(targetCell)) return false; + float distance, boolean considerTimeout) { + if (distance > target.getFolderCreationRadius(targetCell)) + return false; View dropOverView = target.getChildAt(targetCell[0], targetCell[1]); return willCreateUserFolder(info, dropOverView, considerTimeout); } @@ -2025,14 +2082,15 @@ public class Workspace extends PagedView boolean aboveShortcut = Folder.willAccept(dropOverView.getTag()) && ((ItemInfo) dropOverView.getTag()).container != CONTAINER_HOTSEAT_PREDICTION; - boolean willBecomeShortcut = FolderInfo.willAcceptItemType(info.itemType); + boolean willBecomeShortcut = Folder.willAcceptItemType(info.itemType); return (aboveShortcut && willBecomeShortcut); } boolean willAddToExistingUserFolder(ItemInfo dragInfo, CellLayout target, int[] targetCell, - float distance) { - if (distance > target.getFolderCreationRadius(targetCell)) return false; + float distance) { + if (distance > target.getFolderCreationRadius(targetCell)) + return false; View dropOverView = target.getChildAt(targetCell[0], targetCell[1]); return willAddToExistingUserFolder(dragInfo, dropOverView); @@ -2058,7 +2116,8 @@ public class Workspace extends PagedView boolean createUserFolderIfNecessary(View newView, int container, CellLayout target, int[] targetCell, float distance, boolean external, DragObject d) { - if (distance > target.getFolderCreationRadius(targetCell)) return false; + if (distance > target.getFolderCreationRadius(targetCell)) + return false; View v = target.getChildAt(targetCell[0], targetCell[1]); boolean hasntMoved = false; @@ -2068,7 +2127,8 @@ public class Workspace extends PagedView mDragInfo.cellY == targetCell[1]) && (cellParent == target); } - if (v == null || hasntMoved || !mCreateUserFolderOnDrop) return false; + if (v == null || hasntMoved || !mCreateUserFolderOnDrop) + return false; mCreateUserFolderOnDrop = false; final int screenId = getCellLayoutId(target); @@ -2105,8 +2165,8 @@ public class Workspace extends PagedView fi.performCreateAnimation(destInfo, v, sourceInfo, d, folderLocation, scale); } else { fi.prepareCreateAnimation(v); - fi.getFolder().addFolderContent(destInfo); - fi.getFolder().addFolderContent(sourceInfo); + fi.addItem(destInfo); + fi.addItem(sourceInfo); } return true; } @@ -2115,10 +2175,12 @@ public class Workspace extends PagedView boolean addToExistingFolderIfNecessary(View newView, CellLayout target, int[] targetCell, float distance, DragObject d, boolean external) { - if (distance > target.getFolderCreationRadius(targetCell)) return false; + if (distance > target.getFolderCreationRadius(targetCell)) + return false; View dropOverView = target.getChildAt(targetCell[0], targetCell[1]); - if (!mAddToExistingFolderOnDrop) return false; + if (!mAddToExistingFolderOnDrop) + return false; mAddToExistingFolderOnDrop = false; if (dropOverView instanceof FolderIcon) { @@ -2138,7 +2200,8 @@ public class Workspace extends PagedView } @Override - public void prepareAccessibilityDrop() {} + public void prepareAccessibilityDrop() { + } @Override public void onDrop(final DragObject d, DragOptions options) { @@ -2157,8 +2220,8 @@ public class Workspace extends PagedView Runnable onCompleteRunnable = null; boolean forceWidgetResize = PreferenceExtensionsKt.firstBlocking(mPreferenceManager2.getForceWidgetResize()); if (d.dragSource != this || mDragInfo == null) { - final int[] touchXY = new int[]{(int) mDragViewVisualCenter[0], - (int) mDragViewVisualCenter[1]}; + final int[] touchXY = new int[] { (int) mDragViewVisualCenter[0], + (int) mDragViewVisualCenter[1] }; onDropExternal(touchXY, dropTargetLayout, d); } else { final View cell = mDragInfo.cell; @@ -2168,18 +2231,16 @@ public class Workspace extends PagedView // Move internally boolean hasMovedLayouts = (getParentCellLayoutForView(cell) != dropTargetLayout); boolean hasMovedIntoHotseat = mLauncher.isHotseatLayout(dropTargetLayout); - int container = hasMovedIntoHotseat ? - LauncherSettings.Favorites.CONTAINER_HOTSEAT : - LauncherSettings.Favorites.CONTAINER_DESKTOP; - int screenId = (mTargetCell[0] < 0) ? - mDragInfo.screenId : getCellLayoutId(dropTargetLayout); + int container = hasMovedIntoHotseat ? LauncherSettings.Favorites.CONTAINER_HOTSEAT + : LauncherSettings.Favorites.CONTAINER_DESKTOP; + int screenId = (mTargetCell[0] < 0) ? mDragInfo.screenId : getCellLayoutId(dropTargetLayout); int spanX = mDragInfo != null ? mDragInfo.spanX : 1; int spanY = mDragInfo != null ? mDragInfo.spanY : 1; // First we find the cell nearest to point at which the item is // dropped, without any consideration to whether there is an item there. - mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], (int) - mDragViewVisualCenter[1], spanX, spanY, dropTargetLayout, mTargetCell); + mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], spanX, + spanY, dropTargetLayout, mTargetCell); float distance = dropTargetLayout.getDistanceFromWorkspaceCellVisualCenter( mDragViewVisualCenter[0], mDragViewVisualCenter[1], mTargetCell); @@ -2188,7 +2249,7 @@ public class Workspace extends PagedView if (createUserFolderIfNecessary(cell, container, dropTargetLayout, mTargetCell, distance, false, d) || addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell, - distance, d, false)) { + distance, d, false)) { if (!mLauncher.isInState(EDIT_MODE)) { mLauncher.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY); } @@ -2216,7 +2277,7 @@ public class Workspace extends PagedView // workspace. So instead we move the icon back safely to its original position. boolean returnToOriginalCellToPreventShuffling = !isFinishedSwitchingState() && !droppedOnOriginalCellDuringTransition && !dropTargetLayout - .isRegionVacant(mTargetCell[0], mTargetCell[1], spanX, spanY); + .isRegionVacant(mTargetCell[0], mTargetCell[1], spanX, spanY); int[] resultSpan = new int[2]; if (returnToOriginalCellToPreventShuffling) { mTargetCell[0] = mTargetCell[1] = -1; @@ -2342,8 +2403,8 @@ public class Workspace extends PagedView || info.itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET; if (isWidget && dropTargetLayout != null) { // animate widget to a valid place - int animationType = resizeOnDrop ? ANIMATE_INTO_POSITION_AND_RESIZE : - ANIMATE_INTO_POSITION_AND_DISAPPEAR; + int animationType = resizeOnDrop ? ANIMATE_INTO_POSITION_AND_RESIZE + : ANIMATE_INTO_POSITION_AND_DISAPPEAR; animateWidgetDrop(info, parent, d.dragView, null, animationType, cell, false); } else { int duration = snappedToNewPage ? ADJACENT_SCREEN_DROP_DURATION : -1; @@ -2364,21 +2425,11 @@ public class Workspace extends PagedView } mStatsLogManager.logger().withItemInfo(d.dragInfo).withInstanceId(d.logInstanceId) .log(LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED); - - if (mAccessibilityDragListener != null) { - // This code needs to be called after StateManager.cancelAnimation. Before changing - // the order of operations in this method related to the StateListener below, please - // test that accessibility moves retain focus after accessibility dropping an item. - // Accessibility focus must be requested after launcher is back to a normal state - cell.setTag(R.id.perform_a11y_action_on_launcher_state_normal_tag, - AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS); - } } if (d.stateAnnouncer != null && !droppedOnOriginalCell) { d.stateAnnouncer.completeAction(R.string.item_moved); } - TestEventEmitter.sendEvent(TestEvent.WORKSPACE_ON_DROP); } @Nullable @@ -2399,7 +2450,8 @@ public class Workspace extends PagedView public void onNoCellFound( View dropTargetLayout, ItemInfo itemInfo, @Nullable InstanceId logInstanceId) { int strId = mLauncher.isHotseatLayout(dropTargetLayout) - ? R.string.hotseat_out_of_space : R.string.out_of_space; + ? R.string.hotseat_out_of_space + : R.string.out_of_space; Toast.makeText(mLauncher, mLauncher.getString(strId), Toast.LENGTH_SHORT).show(); StatsLogManager.StatsLogger logger = mStatsLogManager.logger().withItemInfo(itemInfo); if (logInstanceId != null) { @@ -2409,8 +2461,10 @@ public class Workspace extends PagedView } /** - * Computes and returns the area relative to dragLayer which is used to display a page. - * In case we have multiple pages displayed at the same time, we return the union of the areas. + * Computes and returns the area relative to dragLayer which is used to display + * a page. + * In case we have multiple pages displayed at the same time, we return the + * union of the areas. */ public Rect getPageAreaRelativeToDragLayer() { Rect area = new Rect(); @@ -2451,7 +2505,8 @@ public class Workspace extends PagedView enforceDragParity("onDragExit", -1, 0); } - // Here we store the final page that will be dropped to, if the workspace in fact + // Here we store the final page that will be dropped to, if the workspace in + // fact // receives the drop mDropToLayout = mDragTargetLayout; if (mDragMode == DRAG_MODE_CREATE_FOLDER) { @@ -2568,7 +2623,8 @@ public class Workspace extends PagedView /* * - * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's + * Convert the 2D coordinate xy from the parent View's coordinate space to this + * CellLayout's * coordinate space. The argument xy is modified with the return result. */ private void mapPointFromSelfToChild(View v, float[] xy) { @@ -2577,7 +2633,8 @@ public class Workspace extends PagedView } /** - * Updates the point in {@param xy} to point to the co-ordinate space of {@param layout} + * Updates the point in {@param xy} to point to the co-ordinate space of + * {@param layout} * * @param layout either hotseat of a page in workspace * @param xy the point location in workspace co-ordinate space @@ -2598,7 +2655,8 @@ public class Workspace extends PagedView public void onDragOver(DragObject d) { // Skip drag over events while we are dragging over side pages - if (!transitionStateShouldAllowDrop()) return; + if (!transitionStateShouldAllowDrop()) + return; ItemInfo item = d.dragInfo; if (item == null) { @@ -2609,7 +2667,8 @@ public class Workspace extends PagedView } // Ensure that we have proper spans for the item that we are dropping - if (item.spanX < 0 || item.spanY < 0) throw new RuntimeException("Improper spans found"); + if (item.spanX < 0 || item.spanY < 0) + throw new RuntimeException("Improper spans found"); mDragViewVisualCenter = d.getVisualCenter(mDragViewVisualCenter); final View child = (mDragInfo == null) ? null : mDragInfo.cell; @@ -2646,8 +2705,8 @@ public class Workspace extends PagedView manageFolderFeedback(targetCellDistance, d); - boolean nearestDropOccupied = mDragTargetLayout.isNearestDropLocationOccupied((int) - mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], item.spanX, + boolean nearestDropOccupied = mDragTargetLayout.isNearestDropLocationOccupied( + (int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], item.spanX, item.spanY, child, mTargetCell); manageReorderOnDragOver(d, targetCellDistance, nearestDropOccupied, minSpanX, minSpanY, @@ -2674,13 +2733,13 @@ public class Workspace extends PagedView child, mTargetCell, span, CellLayout.MODE_SHOW_REORDER_HINT); mDragTargetLayout.visualizeDropLocation(mTargetCell[0], mTargetCell[1], span[0], span[1], d); - nearestDropOccupied = mDragTargetLayout.isNearestDropLocationOccupied((int) - mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], item.spanX, + nearestDropOccupied = mDragTargetLayout.isNearestDropLocationOccupied((int) mDragViewVisualCenter[0], + (int) mDragViewVisualCenter[1], item.spanX, item.spanY, child, mTargetCell); } else if ((mDragMode == DRAG_MODE_NONE || mDragMode == DRAG_MODE_REORDER) && (mLastReorderX != reorderX || mLastReorderY != reorderY) && targetCellDistance < mDragTargetLayout.getReorderRadius(mTargetCell, item.spanX, - item.spanY)) { + item.spanY)) { mReorderAlarm.cancelAlarm(); mLastReorderX = reorderX; mLastReorderY = reorderY; @@ -2705,7 +2764,8 @@ public class Workspace extends PagedView * - A side page if we are in spring-loaded mode and the drag object is over it * - The current page otherwise * - * @return whether the layout is different from the current {@link #mDragTargetLayout}. + * @return whether the layout is different from the current + * {@link #mDragTargetLayout}. */ private boolean setDropLayoutForDragObject(DragObject d, float centerX, float centerY) { CellLayout layout = null; @@ -2722,7 +2782,8 @@ public class Workspace extends PagedView IntSet visiblePageIndices = getVisiblePageIndices(); for (int visiblePageIndex : visiblePageIndices) { layout = verifyInsidePage(visiblePageIndex, d.x, d.y); - if (layout != null) break; + if (layout != null) + break; } } } @@ -2738,8 +2799,7 @@ public class Workspace extends PagedView private boolean shouldUseHotseatAsDropLayout(DragObject dragObject) { if (mLauncher.getHotseat() == null - || mLauncher.getHotseat().getShortcutsAndWidgets() == null - || isDragWidget(dragObject)) { + || mLauncher.getHotseat().getShortcutsAndWidgets() == null) { return false; } View hotseatShortcuts = mLauncher.getHotseat().getShortcutsAndWidgets(); @@ -2762,12 +2822,14 @@ public class Workspace extends PagedView // Check the workspace pages whether the object is over any of them - // Note, centerX represents the center of the object that is being dragged, visually. + // Note, centerX represents the center of the object that is being dragged, + // visually. // d.x represents the location of the finger within the dragged item. float touchX; float touchY = d.y; - // Go through the pages and check if the dragged item is inside one of them. This block + // Go through the pages and check if the dragged item is inside one of them. + // This block // is responsible for determining whether we need to snap to a different screen. int nextPage = getNextPage(); IntSet pageIndexesToVerify = IntSet.wrap(nextPage - 1, @@ -2776,10 +2838,12 @@ public class Workspace extends PagedView for (int pageIndex : pageIndexesToVerify) { // When deciding whether to perform a page switch, we need to consider the most // extreme X coordinate between the finger location and the center of the object - // being dragged. This is either the max or the min of the two depending on whether + // being dragged. This is either the max or the min of the two depending on + // whether // dragging to the left / right, respectively. touchX = (((pageIndex < nextPage) && !mIsRtl) || (pageIndex > nextPage && mIsRtl)) - ? Math.min(d.x, centerX) : Math.max(d.x, centerX); + ? Math.min(d.x, centerX) + : Math.max(d.x, centerX); CellLayout layout = verifyInsidePage(pageIndex, touchX, touchY); if (layout != null) { return layout; @@ -2800,7 +2864,8 @@ public class Workspace extends PagedView } /** - * Returns the child CellLayout if the point is inside the page coordinates, null otherwise. + * Returns the child CellLayout if the point is inside the page coordinates, + * null otherwise. */ private CellLayout verifyInsidePage(int pageNo, float x, float y) { if (pageNo >= 0 && pageNo < getPageCount()) { @@ -2827,9 +2892,7 @@ public class Workspace extends PagedView ItemInfo info = dragObject.dragInfo; boolean userFolderPending = willCreateUserFolder(info, mDragOverView, false); if (mDragMode == DRAG_MODE_NONE && userFolderPending) { - if (Flags.msdlFeedback()) { - mMSDLPlayerWrapper.playToken(MSDLToken.DRAG_INDICATOR_DISCRETE); - } + mFolderCreateBg = new PreviewBackground(getContext()); mFolderCreateBg.setup(mLauncher, mLauncher, null, mDragOverView.getMeasuredWidth(), mDragOverView.getPaddingTop()); @@ -2883,7 +2946,7 @@ public class Workspace extends PagedView final View child; public ReorderAlarmListener(float[] dragViewCenter, int minSpanX, int minSpanY, int spanX, - int spanY, DragObject dragObject, View child) { + int spanY, DragObject dragObject, View child) { this.dragViewCenter = dragViewCenter; this.minSpanX = minSpanX; this.minSpanY = minSpanY; @@ -2916,17 +2979,20 @@ public class Workspace extends PagedView @Override public void getHitRectRelativeToDragLayer(Rect outRect) { - // We want the workspace to have the whole area of the display (it will find the correct + // We want the workspace to have the whole area of the display (it will find the + // correct // cell layout to drop to in the existing drag/drop logic. mLauncher.getDragLayer().getDescendantRectRelativeToSelf(this, outRect); } /** * Drop an item that didn't originate on one of the workspace screens. - * It may have come from Launcher (e.g. from all apps or customize), or it may have + * It may have come from Launcher (e.g. from all apps or customize), or it may + * have * come from another app altogether. *

- * NOTE: This can also be called when we are outside of a drag event, when we want + * NOTE: This can also be called when we are outside of a drag event, when we + * want * to add an item to one of the workspace screens. */ private void onDropExternal(final int[] touchXY, final CellLayout cellLayout, DragObject d) { @@ -2968,7 +3034,7 @@ public class Workspace extends PagedView mDragViewVisualCenter[0], mDragViewVisualCenter[1], mTargetCell); if (willCreateUserFolder(d.dragInfo, cellLayout, mTargetCell, distance, true) || willAddToExistingUserFolder( - d.dragInfo, cellLayout, mTargetCell, distance)) { + d.dragInfo, cellLayout, mTargetCell, distance)) { findNearestVacantCell = false; } } @@ -3014,8 +3080,7 @@ public class Workspace extends PagedView boolean isWidget = pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET || pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET; - AppWidgetHostView finalView = isWidget ? - ((PendingAddWidgetInfo) pendingInfo).boundWidget : null; + AppWidgetHostView finalView = isWidget ? ((PendingAddWidgetInfo) pendingInfo).boundWidget : null; if (finalView != null && updateWidgetSize) { WidgetSizes.updateWidgetSizeRanges(finalView, mLauncher, item.spanX, item.spanY); @@ -3060,7 +3125,8 @@ public class Workspace extends PagedView } else { cellLayout.findCellForSpan(mTargetCell, 1, 1); } - // Add the item to DB before adding to screen ensures that the container and other + // Add the item to DB before adding to screen ensures that the container and + // other // values of the info is properly updated. mLauncher.getModelWriter().addOrMoveItemInDatabase(info, container, screenId, mTargetCell[0], mTargetCell[1]); @@ -3102,7 +3168,8 @@ public class Workspace extends PagedView private void getFinalPositionForDropAnimation(int[] loc, float[] scaleXY, DragView dragView, CellLayout layout, ItemInfo info, int[] targetCell, boolean scale, final View finalView) { - // Now we animate the dragView, (ie. the widget or shortcut preview) into its final + // Now we animate the dragView, (ie. the widget or shortcut preview) into its + // final // location and size on the home screen. int spanX = info.spanX; int spanY = info.spanY; @@ -3124,8 +3191,7 @@ public class Workspace extends PagedView mTempFXY[0] = r.left; mTempFXY[1] = r.top; setFinalTransitionTransform(); - float cellLayoutScale = - mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(layout, mTempFXY, true); + float cellLayoutScale = mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(layout, mTempFXY, true); resetTransitionTransform(); Utilities.roundArray(mTempFXY, loc); @@ -3133,7 +3199,8 @@ public class Workspace extends PagedView float dragViewScaleX = (1.0f * r.width()) / dragView.getMeasuredWidth(); float dragViewScaleY = (1.0f * r.height()) / dragView.getMeasuredHeight(); - // The animation will scale the dragView about its center, so we need to center about + // The animation will scale the dragView about its center, so we need to center + // about // the final location. loc[0] -= (dragView.getMeasuredWidth() - cellLayoutScale * r.width()) / 2 - Math.ceil(layout.getUnusedHorizontalSpace() / 2f); @@ -3226,7 +3293,8 @@ public class Workspace extends PagedView /** * Return the current CellInfo describing our current drag; this method exists - * so that Launcher can sync this object with the correct info when the activity is created/ + * so that Launcher can sync this object with the correct info when the activity + * is created/ * destroyed */ public CellInfo getDragInfo() { @@ -3240,7 +3308,7 @@ public class Workspace extends PagedView */ @Thunk int[] findNearestArea(int pixelX, int pixelY, - int spanX, int spanY, CellLayout layout, int[] recycle) { + int spanX, int spanY, CellLayout layout, int[] recycle) { return layout.findNearestAreaIgnoreOccupied( pixelX, pixelY, spanX, spanY, recycle); } @@ -3249,7 +3317,8 @@ public class Workspace extends PagedView mSpringLoadedDragController = new SpringLoadedDragController(mLauncher); mDragController = dragController; - // hardware layers on children are enabled on startup, but should be disabled until + // hardware layers on children are enabled on startup, but should be disabled + // until // needed updateChildrenLayersEnabled(); } @@ -3258,7 +3327,7 @@ public class Workspace extends PagedView * Called at the end of a drag which originated on the workspace. */ public void onDropCompleted(final View target, final DragObject d, - final boolean success) { + final boolean success) { if (success) { if (target != this && mDragInfo != null) { removeWorkspaceItem(mDragInfo.cell); @@ -3277,7 +3346,7 @@ public class Workspace extends PagedView + "Workspace#onDropCompleted. Please file a bug. "); } } - View cell = getViewByItemId(d.originalDragInfo.id); + View cell = getHomescreenIconByItemId(d.originalDragInfo.id); if (d.cancelled && cell != null) { cell.setVisibility(VISIBLE); } @@ -3292,10 +3361,12 @@ public class Workspace extends PagedView if (parentCell != null) { parentCell.removeView(v); } else if (FeatureFlags.IS_STUDIO_BUILD) { - // When an app is uninstalled using the drop target, we wait until resume to remove + // When an app is uninstalled using the drop target, we wait until resume to + // remove // the icon. We also remove all the corresponding items from the workspace at // {@link Launcher#bindComponentsRemoved}. That call can come before or after - // {@link Launcher#mOnResumeCallbacks} depending on how busy the worker thread is. + // {@link Launcher#mOnResumeCallbacks} depending on how busy the worker thread + // is. Log.e(TAG, "mDragInfo.cell has null parent"); } if (v instanceof DropTarget) { @@ -3322,6 +3393,21 @@ public class Workspace extends PagedView }); } + /** + * Removes all folder listeners + */ + public void removeFolderListeners() { + mapOverItems(new ItemOperator() { + @Override + public boolean evaluate(ItemInfo info, View view) { + if (view instanceof FolderIcon) { + ((FolderIcon) view).removeListeners(); + } + return false; + } + }); + } + public boolean isDropEnabled() { return true; } @@ -3329,7 +3415,8 @@ public class Workspace extends PagedView @Override protected void dispatchRestoreInstanceState(SparseArray container) { // We don't dispatch restoreInstanceState to our children using this code path. - // Some pages will be restored immediately as their items are bound immediately, and + // Some pages will be restored immediately as their items are bound immediately, + // and // others we will need to wait until after their items are bound. mSavedStates = container; } @@ -3411,9 +3498,28 @@ public class Workspace extends PagedView return layouts; } + public View getHomescreenIconByItemId(final int id) { + return getFirstMatch((info, v) -> info != null && info.id == id); + } + public LauncherAppWidgetHostView getWidgetForAppWidgetId(final int appWidgetId) { - return (LauncherAppWidgetHostView) mapOverItems((info, v) -> - (info instanceof LauncherAppWidgetInfo lawi) && lawi.appWidgetId == appWidgetId); + return (LauncherAppWidgetHostView) getFirstMatch((info, v) -> (info instanceof LauncherAppWidgetInfo) && + ((LauncherAppWidgetInfo) info).appWidgetId == appWidgetId); + } + + public View getFirstMatch(final ItemOperator operator) { + final View[] value = new View[1]; + mapOverItems(new ItemOperator() { + @Override + public boolean evaluate(ItemInfo info, View v) { + if (operator.evaluate(info, v)) { + value[0] = v; + return true; + } + return false; + } + }); + return value[0]; } void clearDropTargets() { @@ -3431,7 +3537,8 @@ public class Workspace extends PagedView /** * Removes items that match the {@param matcher}. When applications are removed - * as a part of an update, this is called to ensure that other widgets and application + * as a part of an update, this is called to ensure that other widgets and + * application * shortcuts are not removed. */ public void removeItemsByMatcher(final Predicate matcher) { @@ -3447,15 +3554,15 @@ public class Workspace extends PagedView if (child instanceof DropTarget) { mDragController.removeDropTarget((DropTarget) child); } - } else if (child instanceof FolderIcon folderIcon) { + } else if (child instanceof FolderIcon) { FolderInfo folderInfo = (FolderInfo) info; - ItemInfo[] matches = folderInfo.getContents().stream() + List matches = folderInfo.getContents().stream() .filter(matcher) - .toArray(ItemInfo[]::new); - if (matches.length > 0) { - folderIcon.getFolder().removeFolderContent(false, matches); - if (folderIcon.getFolder().isOpen()) { - folderIcon.getFolder().close(false /* animate */); + .collect(Collectors.toList()); + if (!matches.isEmpty()) { + folderInfo.removeAll(matches, false); + if (((FolderIcon) child).getFolder().isOpen()) { + ((FolderIcon) child).getFolder().close(false /* animate */); } } } else if (info instanceof AppPairInfo api) { @@ -3472,48 +3579,123 @@ public class Workspace extends PagedView } @Override - public View mapOverItems(@NonNull ItemOperator op) { - return mapOverCellLayouts(getWorkspaceAndHotseatCellLayouts(), op); + public void mapOverItems(ItemOperator op) { + for (CellLayout layout : getWorkspaceAndHotseatCellLayouts()) { + if (mapOverCellLayout(layout, op) != null) { + return; + } + } } /** - * Perform {param op} over all the items in the provided {param layouts} until a match is found + * Perform {param operator} over all the items in a given {param layout}. + * + * @return The first item that satisfies the operator or null. */ - public static View mapOverCellLayouts(CellLayout[] layouts, ItemOperator op) { - for (CellLayout layout : layouts) { - // TODO(b/128460496) Potential race condition where layout is not yet loaded - if (layout == null) continue; - - ShortcutAndWidgetContainer container = layout.getShortcutsAndWidgets(); - // map over all the shortcuts on the layout - final int itemCount = container.getChildCount(); - for (int itemIdx = 0; itemIdx < itemCount; itemIdx++) { - View item = container.getChildAt(itemIdx); - if (op.evaluate((ItemInfo) item.getTag(), item)) { - return item; - } + public View mapOverCellLayout(CellLayout layout, ItemOperator operator) { + // TODO(b/128460496) Potential race condition where layout is not yet loaded + if (layout == null) { + return null; + } + ShortcutAndWidgetContainer container = layout.getShortcutsAndWidgets(); + // map over all the shortcuts on the workspace + final int itemCount = container.getChildCount(); + for (int itemIdx = 0; itemIdx < itemCount; itemIdx++) { + View item = container.getChildAt(itemIdx); + if (operator.evaluate((ItemInfo) item.getTag(), item)) { + return item; } } return null; } + public void updateNotificationDots(Predicate updatedDots) { + final PackageUserKey packageUserKey = new PackageUserKey(null, null); + Predicate matcher = info -> !packageUserKey.updateFromItemInfo(info) + || updatedDots.test(packageUserKey); + + ItemOperator op = (info, v) -> { + if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView) { + if (matcher.test(info)) { + ((BubbleTextView) v).applyDotState(info, true /* animate */); + } + } else if (info instanceof FolderInfo && v instanceof FolderIcon) { + FolderInfo fi = (FolderInfo) info; + if (fi.anyMatch(matcher)) { + FolderDotInfo folderDotInfo = new FolderDotInfo(); + for (ItemInfo si : fi.getContents()) { + folderDotInfo.addDotInfo(mLauncher.getDotInfoForItem(si)); + } + ((FolderIcon) v).setDotInfo(folderDotInfo); + } + } + + // process all the shortcuts + return false; + }; + + mapOverItems(op); + Folder folder = Folder.getOpen(mLauncher); + if (folder != null) { + folder.iterateOverItems(op); + } + } + /** * Remove workspace icons & widget information related to items in matcher. * * @param matcher the matcher generated by the caller. */ public void persistRemoveItemsByMatcher(Predicate matcher, - @Nullable final String reason) { + @Nullable final String reason) { mLauncher.getModelWriter().deleteItemsFromDatabase(matcher, reason); removeItemsByMatcher(matcher); } + public void widgetsRestored(final ArrayList changedInfo) { + if (!changedInfo.isEmpty()) { + DeferredWidgetRefresh widgetRefresh = new DeferredWidgetRefresh(changedInfo, + mLauncher.getAppWidgetHolder()); + + LauncherAppWidgetInfo item = changedInfo.get(0); + final AppWidgetProviderInfo widgetInfo; + WidgetManagerHelper widgetHelper = new WidgetManagerHelper(getContext()); + if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) { + widgetInfo = widgetHelper.findProvider(item.providerName, item.user); + } else { + widgetInfo = widgetHelper.getLauncherAppWidgetInfo(item.appWidgetId, + item.getTargetComponent()); + } + + if (widgetInfo != null) { + // Re-inflate the widgets which have changed status + widgetRefresh.run(); + } else { + // widgetRefresh will automatically run when the packages are updated. + // For now just update the progress bars + mapOverItems(new ItemOperator() { + @Override + public boolean evaluate(ItemInfo info, View view) { + if (view instanceof PendingAppWidgetHostView + && changedInfo.contains(info)) { + ((LauncherAppWidgetInfo) info).installProgress = 100; + ((PendingAppWidgetHostView) view).applyState(); + } + // process all the shortcuts + return false; + } + }); + } + } + } + public boolean isOverlayShown() { return mOverlayShown; } /** - * Calls {@link #snapToPage(int)} on the {@link #DEFAULT_PAGE}, then requests focus on it. + * Calls {@link #snapToPage(int)} on the {@link #DEFAULT_PAGE}, then requests + * focus on it. */ public void moveToDefaultScreen() { int page = DEFAULT_PAGE; @@ -3527,8 +3709,10 @@ public class Workspace extends PagedView } /** - * Set the given view's pivot point to match the workspace's, so that it scales together. Since - * both this view and workspace can move, transform the point manually instead of using + * Set the given view's pivot point to match the workspace's, so that it scales + * together. Since + * both this view and workspace can move, transform the point manually instead + * of using * dragLayer.getDescendantCoordRelativeToSelf and related methods. */ public void setPivotToScaleWithSelf(View sibling) { @@ -3541,40 +3725,25 @@ public class Workspace extends PagedView @Override public int getExpectedHeight() { return getMeasuredHeight() <= 0 || !mIsLayoutValid - ? mLauncher.getDeviceProfile().heightPx : getMeasuredHeight(); + ? mLauncher.getDeviceProfile().heightPx + : getMeasuredHeight(); } @Override public int getExpectedWidth() { return getMeasuredWidth() <= 0 || !mIsLayoutValid - ? mLauncher.getDeviceProfile().widthPx : getMeasuredWidth(); + ? mLauncher.getDeviceProfile().widthPx + : getMeasuredWidth(); } @Override protected boolean canAnnouncePageDescription() { + // Disable announcements while overscrolling potentially to overlay screen + // because if we end + // up on the overlay screen, it will take care of announcing itself. return Float.compare(mOverlayProgress, 0f) == 0; } - @Override - protected void announcePageForAccessibility() { - // Talkback focuses on AccessibilityActionView by default, so we need to modify the state - // description there in order for the change in page scroll to be announced. - updateAccessibilityViewPageDescription(); - } - - /** - * Updates the state description that is set on the accessibility actions view for the - * workspace. - *

The updated value is called out when talkback focuses on the view and is not disruptive. - *

- */ - protected void updateAccessibilityViewPageDescription() { - // Set the state description on accessibility action view so that when it is focused, - // talkback describes the correct state of home screen pages. - ViewCompat.setStateDescription(mLauncher.getAccessibilityActionView(), - getCurrentPageDescription()); - } - @Override protected String getCurrentPageDescription() { int pageIndex = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage; @@ -3590,23 +3759,19 @@ public class Workspace extends PagedView int nScreens = getChildCount(); int extraScreenId = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID); if (extraScreenId >= 0 && nScreens > 1) { - if (page == extraScreenId || (isTwoPanelEnabled() && page == extraScreenId + 1)) { + if (page == extraScreenId) { return getContext().getString(R.string.workspace_new_page); } nScreens--; } if (nScreens == 0) { - // When the workspace is not loaded, we do not know how many screen will be bound. + // When the workspace is not loaded, we do not know how many screen will be + // bound. return getContext().getString(R.string.home_screen); } int panelCount = getPanelCount(); int currentPage = (page / panelCount) + 1; int totalPages = nScreens / panelCount + nScreens % panelCount; - - // When dragging, a blank screen is added. This increases the total page count, but we still - // want to describe the original page count where icons are currently pinned - if (extraScreenId > 0) totalPages--; - return getContext().getString(R.string.workspace_scroll_format, currentPage, totalPages); } @@ -3617,8 +3782,7 @@ public class Workspace extends PagedView return super.isSignificantMove(absoluteDelta, pageOrientedSize); } - return absoluteDelta - > deviceProfile.availableWidthPx * SIGNIFICANT_MOVE_SCREEN_WIDTH_PERCENTAGE; + return absoluteDelta > deviceProfile.availableWidthPx * SIGNIFICANT_MOVE_SCREEN_WIDTH_PERCENTAGE; } @Override @@ -3626,6 +3790,62 @@ public class Workspace extends PagedView return mLauncher.getCellPosMapper(); } + /** + * Used as a workaround to ensure that the AppWidgetService receives the + * PACKAGE_ADDED broadcast before updating widgets. + */ + private class DeferredWidgetRefresh implements Runnable, ProviderChangedListener { + private final ArrayList mInfos; + private final LauncherWidgetHolder mWidgetHolder; + private final Handler mHandler; + + private boolean mRefreshPending; + + DeferredWidgetRefresh(ArrayList infos, + LauncherWidgetHolder holder) { + mInfos = infos; + mWidgetHolder = holder; + mHandler = mLauncher.mHandler; + mRefreshPending = true; + + mWidgetHolder.addProviderChangeListener(this); + // Force refresh after 10 seconds, if we don't get the provider changed event. + // This could happen when the provider is no longer available in the app. + Message msg = Message.obtain(mHandler, this); + msg.obj = DeferredWidgetRefresh.class; + mHandler.sendMessageDelayed(msg, 10000); + } + + @Override + public void run() { + mWidgetHolder.removeProviderChangeListener(this); + mHandler.removeCallbacks(this); + + if (!mRefreshPending) { + return; + } + + mRefreshPending = false; + + ArrayList views = new ArrayList<>(mInfos.size()); + mapOverItems((info, view) -> { + if (view instanceof PendingAppWidgetHostView && mInfos.contains(info)) { + views.add((PendingAppWidgetHostView) view); + } + // process all children + return false; + }); + for (PendingAppWidgetHostView view : views) { + view.reInflate(); + } + } + + @Override + public void notifyWidgetProvidersChanged() { + run(); + } + } + private class StateTransitionListener extends AnimatorListenerAdapter implements AnimatorUpdateListener { @@ -3644,22 +3864,4 @@ public class Workspace extends PagedView onEndStateTransition(); } } - - /** - * Recursively check view tag {@link R.id.perform_a11y_action_on_launcher_state_normal_tag} and - * call {@link View#performAccessibilityAction(int, Bundle)} on view tree. The tag is cleared - * after this call. - */ - private static void performAccessibilityActionOnViewTree(View view) { - Object tag = view.getTag(R.id.perform_a11y_action_on_launcher_state_normal_tag); - if (tag instanceof Integer) { - view.performAccessibilityAction((int) tag, null); - view.setTag(R.id.perform_a11y_action_on_launcher_state_normal_tag, null); - } - if (view instanceof ViewGroup viewgroup) { - for (int i = 0; i < viewgroup.getChildCount(); i++) { - performAccessibilityActionOnViewTree(viewgroup.getChildAt(i)); - } - } - } } diff --git a/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.java b/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.java new file mode 100644 index 0000000000..79b81871cc --- /dev/null +++ b/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.accessibility; + +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewGroup.OnHierarchyChangeListener; + +import androidx.annotation.Nullable; + +import com.android.launcher3.CellLayout; +import com.android.launcher3.DropTarget.DragObject; +import com.android.launcher3.Launcher; +import com.android.launcher3.dragndrop.DragController.DragListener; +import com.android.launcher3.dragndrop.DragOptions; + +import java.util.function.Function; + +/** + * Utility listener to enable/disable accessibility drag flags for a ViewGroup + * containing CellLayouts + */ +public class AccessibleDragListenerAdapter implements DragListener, OnHierarchyChangeListener { + + private final ViewGroup mViewGroup; + private final Function mDelegateFactory; + + /** + * @param parent the viewgroup containing all the children + * @param delegateFactory function to create no delegates + */ + public AccessibleDragListenerAdapter(ViewGroup parent, + Function delegateFactory) { + mViewGroup = parent; + mDelegateFactory = delegateFactory; + } + + @Override + public void onDragStart(DragObject dragObject, DragOptions options) { + mViewGroup.setOnHierarchyChangeListener(this); + enableAccessibleDrag(true, dragObject); + } + + @Override + public void onDragEnd() { + mViewGroup.setOnHierarchyChangeListener(null); + enableAccessibleDrag(false, null); + Launcher.getLauncher(mViewGroup.getContext()).getDragController().removeDragListener(this); + } + + + @Override + public void onChildViewAdded(View parent, View child) { + if (parent == mViewGroup) { + setEnableForLayout((CellLayout) child, true); + } + } + + @Override + public void onChildViewRemoved(View parent, View child) { + if (parent == mViewGroup) { + setEnableForLayout((CellLayout) child, false); + } + } + + protected void enableAccessibleDrag(boolean enable, @Nullable DragObject dragObject) { + for (int i = 0; i < mViewGroup.getChildCount(); i++) { + setEnableForLayout((CellLayout) mViewGroup.getChildAt(i), enable); + } + } + + protected final void setEnableForLayout(CellLayout layout, boolean enable) { + layout.setDragAndDropAccessibilityDelegate(enable ? mDelegateFactory.apply(layout) : null); + } +} diff --git a/src/com/android/launcher3/accessibility/DragAndDropAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/DragAndDropAccessibilityDelegate.java index 6f73e07d62..d0fc17534e 100644 --- a/src/com/android/launcher3/accessibility/DragAndDropAccessibilityDelegate.java +++ b/src/com/android/launcher3/accessibility/DragAndDropAccessibilityDelegate.java @@ -29,9 +29,9 @@ import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; import androidx.customview.widget.ExploreByTouchHelper; import com.android.launcher3.CellLayout; +import com.android.launcher3.Launcher; import com.android.launcher3.R; -import com.android.launcher3.views.ActivityContext; -import com.android.launcher3.views.BaseDragLayer; +import com.android.launcher3.dragndrop.DragLayer; import java.util.List; @@ -47,17 +47,16 @@ public abstract class DragAndDropAccessibilityDelegate extends ExploreByTouchHel protected final CellLayout mView; protected final Context mContext; - protected final ActivityContext mActivityContext; protected final LauncherAccessibilityDelegate mDelegate; - protected final BaseDragLayer mDragLayer; + protected final DragLayer mDragLayer; public DragAndDropAccessibilityDelegate(CellLayout forView) { super(forView); mView = forView; mContext = mView.getContext(); - mActivityContext = ActivityContext.lookupContext(mContext); - mDelegate = (LauncherAccessibilityDelegate) mActivityContext.getAccessibilityDelegate(); - mDragLayer = mActivityContext.getDragLayer(); + Launcher launcher = Launcher.getLauncher(mContext); + mDelegate = launcher.getAccessibilityDelegate(); + mDragLayer = launcher.getDragLayer(); } @Override diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java index df34ccf090..814d1427ec 100644 --- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java +++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java @@ -24,8 +24,6 @@ import android.view.accessibility.AccessibilityEvent; import androidx.annotation.Nullable; -import com.android.launcher3.AbstractFloatingView; -import com.android.launcher3.AppWidgetResizeFrame; import com.android.launcher3.BubbleTextView; import com.android.launcher3.ButtonDropTarget; import com.android.launcher3.CellLayout; @@ -50,7 +48,6 @@ import com.android.launcher3.model.data.WorkspaceItemFactory; import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.popup.ArrowPopup; import com.android.launcher3.popup.PopupContainerWithArrow; -import com.android.launcher3.shortcuts.DeepShortcutView; import com.android.launcher3.touch.ItemLongClickListener; import com.android.launcher3.util.IntArray; import com.android.launcher3.util.IntSet; @@ -83,7 +80,6 @@ public class LauncherAccessibilityDelegate extends BaseAccessibilityDelegate out) { // If the request came from keyboard, do not add custom shortcuts as that is already // exposed as a direct shortcut - if (isNotInShortcutMenu(host) && ShortcutUtil.supportsShortcuts(item)) { + if (ShortcutUtil.supportsShortcuts(item)) { out.add(mActions.get(DEEP_SHORTCUTS)); } @@ -141,10 +131,6 @@ public class LauncherAccessibilityDelegate extends BaseAccessibilityDelegate actions = getSupportedResizeActions(itemView, info); + List actions = getSupportedResizeActions(host, info); Rect pos = new Rect(); - mContext.getDragLayer().getDescendantRectRelativeToSelf(itemView, pos); + mContext.getDragLayer().getDescendantRectRelativeToSelf(host, pos); ArrowPopup popup = OptionsPopupView.show(mContext, new RectF(pos), actions, false); popup.requestFocus(); popup.addOnCloseCallback(() -> { - itemView.requestFocus(); - itemView.sendAccessibilityEvent(TYPE_VIEW_FOCUSED); - itemView.performAccessibilityAction(ACTION_ACCESSIBILITY_FOCUS, null); - AbstractFloatingView.closeOpenViews(mContext, /* animate= */ false, - AbstractFloatingView.TYPE_WIDGET_RESIZE_FRAME); + host.requestFocus(); + host.sendAccessibilityEvent(TYPE_VIEW_FOCUSED); + host.performAccessibilityAction(ACTION_ACCESSIBILITY_FOCUS, null); }); return true; } else if (action == DEEP_SHORTCUTS) { @@ -222,11 +200,6 @@ public class LauncherAccessibilityDelegate extends BaseAccessibilityDelegate getSupportedResizeActions(View host, LauncherAppWidgetInfo info) { List actions = new ArrayList<>(); - if (host instanceof AppWidgetResizeFrame) { - return getSupportedResizeActions( - ((AppWidgetResizeFrame) host).getViewForAccessibility(), info); - } AppWidgetProviderInfo providerInfo = ((LauncherAppWidgetHostView) host).getAppWidgetInfo(); if (providerInfo == null) { return actions; @@ -330,6 +299,7 @@ public class LauncherAccessibilityDelegate extends BaseAccessibilityDelegate finishCallback) { - // Dismiss widget resize frame if it is showing. The frame marks its cells as unoccupied - // while it is showing, so findSpaceOnWorkspace may try to use those cells. - AbstractFloatingView.closeOpenViews(mContext, /* animate= */ false, - AbstractFloatingView.TYPE_WIDGET_RESIZE_FRAME); - final int[] coordinates = new int[2]; final int screenId = findSpaceOnWorkspace(item, coordinates); if (screenId == -1) { @@ -450,6 +415,7 @@ public class LauncherAccessibilityDelegate extends BaseAccessibilityDelegate Type of context inflating all apps. */ public class ActivityAllAppsContainerView - extends SpringRelativeLayout implements DragSource, Insettable, + extends StretchRecyclerViewContainer implements DragSource, Insettable, OnDeviceProfileChangeListener, PersonalWorkSlidingTabStrip.OnActivePageChangedListener, ScrimView.ScrimDrawingController { + public static final FloatProperty> BOTTOM_SHEET_ALPHA = new FloatProperty<>( + "bottomSheetAlpha") { + @Override + public Float get(ActivityAllAppsContainerView containerView) { + return containerView.mBottomSheetAlpha; + } + + @Override + public void setValue(ActivityAllAppsContainerView containerView, float v) { + containerView.setBottomSheetAlpha(v); + } + }; - private static final String TAG = "ActivityAllAppsContainerView"; public static final float PULL_MULTIPLIER = .02f; public static final float FLING_VELOCITY_MULTIPLIER = 1200f; protected static final String BUNDLE_KEY_CURRENT_PAGE = "launcher.allapps.current_page"; - private static final long DEFAULT_SEARCH_TRANSITION_DURATION_MS = 300; + // As of this writing, search transition does not seem to work properly, so set + // duration to 0. + private static final long DEFAULT_SEARCH_TRANSITION_DURATION_MS = 0; // Render the header protection at all times to debug clipping issues. private static final boolean DEBUG_HEADER_PROTECTION = false; /** Context of an activity or window that is inflating this container. */ @@ -151,13 +160,12 @@ public class ActivityAllAppsContainerView private final Paint mHeaderPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private final Rect mInsets = new Rect(); private final AllAppsStore mAllAppsStore; - private final RecyclerView.OnScrollListener mScrollListener = - new RecyclerView.OnScrollListener() { - @Override - public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { - updateHeaderScroll(recyclerView.computeVerticalScrollOffset()); - } - }; + private final RecyclerView.OnScrollListener mScrollListener = new RecyclerView.OnScrollListener() { + @Override + public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { + updateHeaderScroll(recyclerView.computeVerticalScrollOffset()); + } + }; private final Paint mNavBarScrimPaint; private final int mHeaderProtectionColor; private final int mPrivateSpaceBottomExtraSpace; @@ -165,20 +173,22 @@ public class ActivityAllAppsContainerView private final RectF mTmpRectF = new RectF(); protected AllAppsPagedView mViewPager; protected FloatingHeaderView mHeader; - protected final List mAdditionalHeaderRows = new ArrayList<>(); protected View mBottomSheetBackground; protected RecyclerViewFastScroller mFastScroller; - private ConstraintLayout mFastScrollLetterLayout; /** - * View that defines the search box. Result is rendered inside {@link #mSearchRecyclerView}. + * View that defines the search box. Result is rendered inside + * {@link #mSearchRecyclerView}. */ protected View mSearchContainer; protected SearchUiManager mSearchUiManager; protected boolean mUsingTabs; protected RecyclerViewFastScroller mTouchHandler; - /** {@code true} when rendered view is in search state instead of the scroll state. */ + /** + * {@code true} when rendered view is in search state instead of the scroll + * state. + */ private boolean mIsSearching; boolean showFastScroller; private boolean mRebindAdaptersAfterSearchAnimation; @@ -192,13 +202,16 @@ public class ActivityAllAppsContainerView private ScrimView mScrimView; private int mHeaderColor; private int mBottomSheetBackgroundColor; - private float mBottomSheetBackgroundAlpha = 1f; + private float mBottomSheetAlpha = 1f; + private boolean mForceBottomSheetVisible; private int mTabsProtectionAlpha; - @Nullable private AllAppsTransitionController mAllAppsTransitionController; private final PreferenceManager2 pref2; private final PreferenceManager pref; + @Nullable + private AllAppsTransitionController mAllAppsTransitionController; + public ActivityAllAppsContainerView(Context context) { this(context, null); } @@ -211,10 +224,8 @@ public class ActivityAllAppsContainerView super(context, attrs, defStyleAttr); mActivityContext = ActivityContext.lookupContext(context); mAllAppsStore = new AllAppsStore<>(mActivityContext); - pref2 = PreferenceManager2.getInstance(mActivityContext); pref = PreferenceManager.getInstance(mActivityContext); - mScrimColor = ColorTokens.AllAppsScrimColor.resolveColor(context); mHeaderThreshold = getResources().getDimensionPixelSize( R.dimen.dynamic_grid_cell_border_spacing); @@ -239,7 +250,8 @@ public class ActivityAllAppsContainerView AllAppsStore.OnUpdateListener onAppsUpdated = this::onAppsUpdated; mAllAppsStore.addUpdateListener(onAppsUpdated); - // This is a focus listener that proxies focus from a view into the list view. This is to + // This is a focus listener that proxies focus from a view into the list view. + // This is to // work around the search box from getting first focus and showing the cursor. setOnFocusChangeListener((v, hasFocus) -> { if (hasFocus && getActiveRecyclerView() != null) { @@ -262,12 +274,14 @@ public class ActivityAllAppsContainerView } /** - * Initializes the view hierarchy and internal variables. Any initialization which actually uses + * Initializes the view hierarchy and internal variables. Any initialization + * which actually uses * these members should be done in {@link #onFinishInflate()}. - * In terms of subclass initialization, the following would be parallel order for activity: - * initContent -> onPreCreate - * constructor/init -> onCreate - * onFinishInflate -> onPostCreate + * In terms of subclass initialization, the following would be parallel order + * for activity: + * initContent -> onPreCreate + * constructor/init -> onCreate + * onFinishInflate -> onPostCreate */ protected void initContent() { showFastScroller = PreferenceExtensionsKt.firstBlocking(pref2.getShowScrollbar()); @@ -275,10 +289,7 @@ public class ActivityAllAppsContainerView mMainAdapterProvider = mSearchUiDelegate.createMainAdapterProvider(); mAH.set(AdapterHolder.MAIN, new AdapterHolder(AdapterHolder.MAIN, - new LawnchairAlphabeticalAppsList<>(mActivityContext, - mAllAppsStore, - null, - mPrivateProfileManager))); + new LawnchairAlphabeticalAppsList<>(mActivityContext, mAllAppsStore, null, mPrivateProfileManager))); mAH.set(AdapterHolder.WORK, new AdapterHolder(AdapterHolder.WORK, new LawnchairAlphabeticalAppsList<>(mActivityContext, mAllAppsStore, mWorkManager, null))); mAH.set(SEARCH, new AdapterHolder(SEARCH, @@ -286,34 +297,22 @@ public class ActivityAllAppsContainerView getLayoutInflater().inflate(R.layout.all_apps_content, this); mHeader = findViewById(R.id.all_apps_header); - mAdditionalHeaderRows.clear(); - mAdditionalHeaderRows.addAll(getAdditionalHeaderRows()); mBottomSheetBackground = findViewById(R.id.bottom_sheet_background); mBottomSheetHandleArea = findViewById(R.id.bottom_sheet_handle_area); mSearchRecyclerView = findViewById(R.id.search_results_list_view); mFastScroller = findViewById(R.id.fast_scroller); mFastScroller.setPopupView(findViewById(R.id.fast_scroller_popup)); mFastScroller.setVisibility(showFastScroller ? VISIBLE : INVISIBLE); - mFastScrollLetterLayout = findViewById(R.id.scroll_letter_layout); - setClipChildren(false); - mSearchContainer = inflateSearchBar(); if (!isSearchBarFloating()) { - // Add the search box above everything else in this container (if the flag is enabled, + // Add the search box above everything else in this container (if the flag is + // enabled, // it's added to drag layer in onAttach instead). addView(mSearchContainer); - // The search container is visually at the top of the all apps UI, and should thus be - // focused by default. It's added to end of the children list, so it needs to be - // explicitly marked as focused by default. - mSearchContainer.setFocusedByDefault(true); } mSearchUiManager = (SearchUiManager) mSearchContainer; } - public List getAdditionalHeaderRows() { - return List.of(); - } - @Override protected void onFinishInflate() { super.onFinishInflate(); @@ -322,7 +321,7 @@ public class ActivityAllAppsContainerView /* Filter out A-Z apps */ itemInfo -> false); rebindAdapters(true /* force */); float cornerRadius = Themes.getDialogCornerRadius(getContext()); - mBottomSheetCornerRadii = new float[]{ + mBottomSheetCornerRadii = new float[] { cornerRadius, cornerRadius, // Top left radius in px cornerRadius, @@ -332,17 +331,7 @@ public class ActivityAllAppsContainerView 0, 0 // Bottom left }; - if (Flags.allAppsBlur()) { - // LC: Return int color value directly, not a resource ID for custom colours - int baseColor = ColorTokens.ExpressiveAllApps.resolveColor(getContext()); - - int layerAbove = ColorUtils.setAlphaComponent(baseColor, (int) (0.4f * 255)); - int layerBelow = ColorUtils.setAlphaComponent(Color.WHITE, (int) (0.1f * 255)); - mBottomSheetBackgroundColor = ColorUtils.compositeColors(layerAbove, layerBelow); - } else { - mBottomSheetBackgroundColor = ColorTokens.SurfaceDimColor.resolveColor(getContext()); - } - mBottomSheetBackgroundAlpha = Color.alpha(mBottomSheetBackgroundColor) / 255.0f; + mBottomSheetBackgroundColor = ColorTokens.SurfaceDimColor.resolveColor(getContext()); updateBackgroundVisibility(mActivityContext.getDeviceProfile()); mSearchUiManager.initializeSearch(this); } @@ -351,8 +340,10 @@ public class ActivityAllAppsContainerView protected void onAttachedToWindow() { super.onAttachedToWindow(); if (isSearchBarFloating()) { - // Note: for Taskbar this is removed in TaskbarAllAppsController#cleanUpOverlay when the - // panel is closed. Can't do so in onDetach because we are also a child of drag layer + // Note: for Taskbar this is removed in TaskbarAllAppsController#cleanUpOverlay + // when the + // panel is closed. Can't do so in onDetach because we are also a child of drag + // layer // so can't remove its views during that dispatch. mActivityContext.getDragLayer().addView(mSearchContainer); mSearchUiDelegate.onInitializeSearchBar(); @@ -370,6 +361,21 @@ public class ActivityAllAppsContainerView return mSearchUiManager; } + public View getBottomSheetBackground() { + return mBottomSheetBackground; + } + + /** + * Temporarily force the bottom sheet to be visible on non-tablets. + * + * @param force {@code true} means bottom sheet will be visible on phones until + * {@code reset()}. + */ + public void forceBottomSheetVisible(boolean force) { + mForceBottomSheetVisible = force; + updateBackgroundVisibility(mActivityContext.getDeviceProfile()); + } + public View getSearchView() { return mSearchContainer; } @@ -397,8 +403,10 @@ public class ActivityAllAppsContainerView /** * Sets results list for search. * - * @param searchResultCode indicates if the result is final or intermediate for a given query - * since we can get search results from multiple sources. + * @param searchResultCode indicates if the result is final or intermediate for + * a given query + * since we can get search results from multiple + * sources. */ public void setSearchResults(ArrayList results, int searchResultCode) { setSearchResults(results); @@ -451,7 +459,8 @@ public class ActivityAllAppsContainerView public boolean shouldContainerScroll(MotionEvent ev) { BaseDragLayer dragLayer = mActivityContext.getDragLayer(); - // IF the MotionEvent is inside the search box or handle area, and the container keeps on + // IF the MotionEvent is inside the search box or handle area, and the container + // keeps on // receiving touch input, container should move down. if (dragLayer.isEventOverView(mSearchContainer, ev) || dragLayer.isEventOverView(mBottomSheetHandleArea, ev)) { @@ -474,10 +483,12 @@ public class ActivityAllAppsContainerView } /** - * Resets the UI to be ready for fresh interactions in the future. Exits search and returns to + * Resets the UI to be ready for fresh interactions in the future. Exits search + * and returns to * A-Z apps list. * - * @param animate Whether to animate the header during the reset (e.g. switching profile tabs). + * @param animate Whether to animate the header during the reset (e.g. switching + * profile tabs). */ public void reset(boolean animate) { reset(animate, true); @@ -486,14 +497,15 @@ public class ActivityAllAppsContainerView /** * Resets the UI to be ready for fresh interactions in the future. * - * @param animate Whether to animate the header during the reset (e.g. switching profile tabs). - * @param exitSearch Whether to force exit the search state and return to A-Z apps list. + * @param animate Whether to animate the header during the reset (e.g. + * switching profile tabs). + * @param exitSearch Whether to force exit the search state and return to A-Z + * apps list. */ public void reset(boolean animate, boolean exitSearch) { - // Scroll Main and Work RV to top. Search RV is done in `resetSearch`. if (!PreferenceExtensionsKt.firstBlocking(pref2.getRememberPosition())) { for (int i = 0; i < mAH.size(); i++) { - if (i != SEARCH && mAH.get(i).mRecyclerView != null) { + if (mAH.get(i).mRecyclerView != null) { mAH.get(i).mRecyclerView.scrollToTop(); } } @@ -504,7 +516,7 @@ public class ActivityAllAppsContainerView if (mHeader != null && mHeader.getVisibility() == VISIBLE) { mHeader.reset(animate); } - updateBackgroundVisibility(mActivityContext.getDeviceProfile()); + forceBottomSheetVisible(false); // Reset the base recycler view after transitioning home. updateHeaderScroll(0); if (exitSearch) { @@ -517,17 +529,21 @@ public class ActivityAllAppsContainerView } /** - * Exits search and returns to A-Z apps list. Scroll to the private space header. + * Exits search and returns to A-Z apps list. Scroll to the private space + * header. */ public void resetAndScrollToPrivateSpaceHeader() { - // Animate to A-Z with 0 time to reset the animation with proper state management. - // We can't rely on `animateToSearchState` with delay inside `resetSearch` because that will + // Animate to A-Z with 0 time to reset the animation with proper state + // management. + // We can't rely on `animateToSearchState` with delay inside `resetSearch` + // because that will // conflict with following scrolling to bottom, so we need it with 0 time here. animateToSearchState(false, 0); MAIN_EXECUTOR.getHandler().post(() -> { // Reset the search bar after transitioning home. - // When `resetSearch` is called after `animateToSearchState` is finished, the inside + // When `resetSearch` is called after `animateToSearchState` is finished, the + // inside // `animateToSearchState` with delay is a just no-op and return early. mSearchUiManager.resetSearch(); // Switch to the main tab @@ -579,17 +595,12 @@ public class ActivityAllAppsContainerView // Will be called at the end of the animation. return; } - if (currentActivePage != SEARCH) { - mActivityContext.hideKeyboard(); - } if (mAH.get(currentActivePage).mRecyclerView != null) { - mAH.get(currentActivePage).mRecyclerView.bindFastScrollbar(mFastScroller, - ALL_APPS_SCROLLER); + mAH.get(currentActivePage).mRecyclerView.bindFastScrollbar(mFastScroller); } - // Header keeps track of active recycler view to properly render header protection. + // Header keeps track of active recycler view to properly render header + // protection. mHeader.setActiveRV(currentActivePage); - reset(true /* animate */, !isSearching() /* exitSearch */); - mWorkManager.onActivePageChanged(currentActivePage); } @@ -598,7 +609,6 @@ public class ActivityAllAppsContainerView } protected void rebindAdapters(boolean force) { - Log.d(TAG, "rebindAdapters: force: " + force); if (mSearchTransitionController.isRunning()) { mRebindAdaptersAfterSearchAnimation = true; return; @@ -607,12 +617,13 @@ public class ActivityAllAppsContainerView boolean showTabs = shouldShowTabs(); if (showTabs == mUsingTabs && !force) { - Log.d(TAG, "rebindAdapters: Not needed."); return; } - // replaceAppsRVcontainer() needs to use both mUsingTabs value to remove the old view AND - // showTabs value to create new view. Hence the mUsingTabs new value assignment MUST happen + // replaceAppsRVcontainer() needs to use both mUsingTabs value to remove the old + // view AND + // showTabs value to create new view. Hence the mUsingTabs new value assignment + // MUST happen // after this call. replaceAppsRVContainer(showTabs); mUsingTabs = showTabs; @@ -637,7 +648,6 @@ public class ActivityAllAppsContainerView mViewPager.getPageIndicator().setActiveMarker(AdapterHolder.MAIN); findViewById(R.id.tab_personal) .setOnClickListener((View view) -> { - Log.d(TAG, "rebindAdapters: " + "Clicked personal tab."); if (mViewPager.snapToPage(AdapterHolder.MAIN)) { mActivityContext.getStatsLogManager().logger() .log(LAUNCHER_ALLAPPS_TAP_ON_PERSONAL_TAB); @@ -645,7 +655,6 @@ public class ActivityAllAppsContainerView }); findViewById(R.id.tab_work) .setOnClickListener((View view) -> { - Log.d(TAG, "rebindAdapters: " + "Clicked work tab."); if (mViewPager.snapToPage(AdapterHolder.WORK)) { mActivityContext.getStatsLogManager().logger() .log(LAUNCHER_ALLAPPS_TAP_ON_WORK_TAB); @@ -669,8 +678,7 @@ public class ActivityAllAppsContainerView if (isSearchBarFloating()) { // Keep the scroller above the search bar. - RelativeLayout.LayoutParams scrollerLayoutParams = - (LayoutParams) mFastScroller.getLayoutParams(); + RelativeLayout.LayoutParams scrollerLayoutParams = (LayoutParams) mFastScroller.getLayoutParams(); scrollerLayoutParams.bottomMargin = mSearchContainer.getHeight() + getResources().getDimensionPixelSize( R.dimen.fastscroll_bottom_margin_floating_search); @@ -683,27 +691,34 @@ public class ActivityAllAppsContainerView /** * If {@link ENABLE_ALL_APPS_RV_PREINFLATION} is enabled, wire custom - * {@link RecyclerView.RecycledViewPool} to main and work {@link AllAppsRecyclerView}. + * {@link RecyclerView.RecycledViewPool} to main and work + * {@link AllAppsRecyclerView}. * - * Then if {@link ALL_APPS_GONE_VISIBILITY} is enabled, update max pool size. This is because - * all apps rv's hidden visibility is changed to {@link View#GONE} from {@link View#INVISIBLE), + * Then if {@link ALL_APPS_GONE_VISIBILITY} is enabled, update max pool size. + * This is because + * all apps rv's hidden visibility is changed to {@link View#GONE} from + * {@link View#INVISIBLE), * thus we cannot rely on layout pass to update pool size. */ private static void setUpCustomRecyclerViewPool( @NonNull AllAppsRecyclerView mainRecyclerView, @Nullable AllAppsRecyclerView workRecyclerView, @NonNull AllAppsRecyclerViewPool recycledViewPool) { + if (!ENABLE_ALL_APPS_RV_PREINFLATION.get()) { + return; + } final boolean hasWorkProfile = workRecyclerView != null; recycledViewPool.setHasWorkProfile(hasWorkProfile); mainRecyclerView.setRecycledViewPool(recycledViewPool); if (workRecyclerView != null) { workRecyclerView.setRecycledViewPool(recycledViewPool); } - mainRecyclerView.updatePoolSize(hasWorkProfile); + if (ALL_APPS_GONE_VISIBILITY.get()) { + mainRecyclerView.updatePoolSize(hasWorkProfile); + } } private void replaceAppsRVContainer(boolean showTabs) { - Log.d(TAG, "replaceAppsRVContainer: showTabs: " + showTabs); for (int i = AdapterHolder.MAIN; i <= AdapterHolder.WORK; i++) { AdapterHolder adapterHolder = mAH.get(i); if (adapterHolder.mRecyclerView != null) { @@ -724,9 +739,9 @@ public class ActivityAllAppsContainerView mViewPager.setOutlineProvider(new ViewOutlineProvider() { @Override public void getOutline(View view, Outline outline) { - @Px final int bottomOffsetPx = - (int) (ActivityAllAppsContainerView.this.getMeasuredHeight() - * PREDICTIVE_BACK_MIN_SCALE); + @Px + final int bottomOffsetPx = (int) (ActivityAllAppsContainerView.this.getMeasuredHeight() + * PREDICTIVE_BACK_MIN_SCALE); outline.setRect( 0, 0, @@ -737,14 +752,17 @@ public class ActivityAllAppsContainerView mWorkManager.reset(); post(() -> mAH.get(AdapterHolder.WORK).applyPadding()); + } else { - mWorkManager.detachWorkUtilityViews(); + mWorkManager.detachWorkModeSwitch(); mViewPager = null; } removeCustomRules(rvContainer); removeCustomRules(getSearchRecyclerView()); - if (isSearchBarFloating()) { + if (!isSearchSupported()) { + layoutWithoutSearchContainer(rvContainer, showTabs); + } else if (isSearchBarFloating()) { alignParentTop(rvContainer, showTabs); alignParentTop(getSearchRecyclerView(), /* tabs= */ false); } else { @@ -756,8 +774,6 @@ public class ActivityAllAppsContainerView } void setupHeader() { - mAdditionalHeaderRows.forEach(row -> mHeader.onPluginDisconnected(row)); - var hideHeader = PreferenceExtensionsKt.firstBlocking(pref2.getHideAppDrawerSearchBar()); mHeader.setVisibility(hideHeader ? View.GONE : View.VISIBLE); boolean tabsHidden = !mUsingTabs; @@ -776,32 +792,17 @@ public class ActivityAllAppsContainerView adapterHolder.mRecyclerView.scrollToTop(); } }); - mAdditionalHeaderRows.forEach(row -> mHeader.onPluginConnected(row, mActivityContext)); removeCustomRules(mHeader); - if (isSearchBarFloating()) { + if (!isSearchSupported()) { + layoutWithoutSearchContainer(mHeader, false /* includeTabsMargin */); + } else if (isSearchBarFloating()) { alignParentTop(mHeader, false /* includeTabsMargin */); } else { layoutBelowSearchContainer(mHeader, false /* includeTabsMargin */); } } - @Override - public void addChildrenForAccessibility(ArrayList arrayList) { - super.addChildrenForAccessibility(arrayList); - if (!Flags.floatingSearchBar()) { - // Searchbox container is visually at the top of the all apps UI but it's present in - // end of the children list. - // We need to move the searchbox to the top in a11y tree for a11y services to read the - // all apps screen in same as visual order. - arrayList.stream().filter(v -> v.getId() == R.id.search_container_all_apps) - .findFirst().ifPresent(v -> { - arrayList.remove(v); - arrayList.add(0, v); - }); - } - } - protected void updateHeaderScroll(int scrolledOffset) { if (PreferenceExtensionsKt.firstBlocking(pref2.getHideAppDrawerSearchBar())) return; @@ -820,74 +821,86 @@ public class ActivityAllAppsContainerView return; } - float prog = Utilities.boundToRange((float) scrolledOffset / mHeaderThreshold, 0f, 1f); boolean bgVisible = mSearchUiManager.getBackgroundVisibility(); - if (scrolledOffset == 0 && !isSearching()) { + if (!isSearching()) { bgVisible = true; - } else if (scrolledOffset > mHeaderThreshold) { + } else { bgVisible = false; } - mSearchUiManager.setBackgroundVisibility(bgVisible, 1 - prog); + mSearchUiManager.setBackgroundVisibility(bgVisible, 1); } protected int getHeaderColor(float blendRatio) { - float opacity = 0.0f; var showHeaderBackground = PreferenceExtensionsKt.firstBlocking( pref2.getAppDrawerSearchBarBackground()); + + var opacity = 0.0f; + if (showHeaderBackground) { opacity = pref.getDrawerOpacity().get(); } var colorOptions = PreferenceExtensionsKt.firstBlocking(pref2.getAppDrawerBackgroundColor()); - var color = colorOptions.getColorPreferenceEntry().getLightColor().invoke(mActivityContext); + var color = colorOptions.getColorPreferenceEntry().getLightColor().invoke(mContext); if (color != 0) { mScrimColor = color; } return ColorUtils.setAlphaComponent( ColorUtils.blendARGB(mScrimColor, mHeaderProtectionColor, blendRatio), - (int) (opacity * 255)); + Math.round(opacity * 255)); } /** - * @return true if the search bar is floating above this container (at the bottom of the screen) + * @return true if the search bar is floating above this container (at the + * bottom of the screen) */ protected boolean isSearchBarFloating() { return mSearchUiDelegate.isSearchBarFloating(); } /** - * Whether the floating search bar should appear as a small pill when not focused. + * Whether the floating search bar should appear as a small pill when + * not focused. *

- * Note: This method mirrors one in LauncherState. For subclasses that use Launcher, it likely - * makes sense to use that method to derive an appropriate value for the current/target state. + * Note: This method mirrors one in LauncherState. For subclasses that use + * Launcher, it likely + * makes sense to use that method to derive an appropriate value for the + * current/target state. */ public boolean shouldFloatingSearchBarBePillWhenUnfocused() { return false; } /** - * How far from the bottom of the screen the floating search bar should rest when the + * How far from the bottom of the screen the floating search bar should + * rest when the * IME is not present. *

* To hide offscreen, use a negative value. *

- * Note: if the provided value is non-negative but less than the current bottom insets, the + * Note: if the provided value is non-negative but less than the current bottom + * insets, the * insets will be applied. As such, you can use 0 to default to this. *

- * Note: This method mirrors one in LauncherState. For subclasses that use Launcher, it likely - * makes sense to use that method to derive an appropriate value for the current/target state. + * Note: This method mirrors one in LauncherState. For subclasses that use + * Launcher, it likely + * makes sense to use that method to derive an appropriate value for the + * current/target state. */ public int getFloatingSearchBarRestingMarginBottom() { return 0; } /** - * How far from the start of the screen the floating search bar should rest. + * How far from the start of the screen the floating search bar should + * rest. *

* To use original margin, return a negative value. *

- * Note: This method mirrors one in LauncherState. For subclasses that use Launcher, it likely - * makes sense to use that method to derive an appropriate value for the current/target state. + * Note: This method mirrors one in LauncherState. For subclasses that use + * Launcher, it likely + * makes sense to use that method to derive an appropriate value for the + * current/target state. */ public int getFloatingSearchBarRestingMarginStart() { DeviceProfile dp = mActivityContext.getDeviceProfile(); @@ -895,12 +908,15 @@ public class ActivityAllAppsContainerView } /** - * How far from the end of the screen the floating search bar should rest. + * How far from the end of the screen the floating search bar should + * rest. *

* To use original margin, return a negative value. *

- * Note: This method mirrors one in LauncherState. For subclasses that use Launcher, it likely - * makes sense to use that method to derive an appropriate value for the current/target state. + * Note: This method mirrors one in LauncherState. For subclasses that use + * Launcher, it likely + * makes sense to use that method to derive an appropriate value for the + * current/target state. */ public int getFloatingSearchBarRestingMarginEnd() { DeviceProfile dp = mActivityContext.getDeviceProfile(); @@ -908,7 +924,8 @@ public class ActivityAllAppsContainerView } private void layoutBelowSearchContainer(View v, boolean includeTabsMargin) { - if (!(v.getLayoutParams() instanceof RelativeLayout.LayoutParams)) { + if (!(v.getLayoutParams() instanceof RelativeLayout.LayoutParams) + || PreferenceExtensionsKt.firstBlocking(pref2.getHideAppDrawerSearchBar())) { return; } @@ -932,11 +949,10 @@ public class ActivityAllAppsContainerView RelativeLayout.LayoutParams layoutParams = (LayoutParams) v.getLayoutParams(); layoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP); - layoutParams.topMargin = - includeTabsMargin - ? getContext().getResources().getDimensionPixelSize( + layoutParams.topMargin = includeTabsMargin + ? getContext().getResources().getDimensionPixelSize( R.dimen.all_apps_header_pill_height) - : 0; + : 0; } private void removeCustomRules(View v) { @@ -956,6 +972,24 @@ public class ActivityAllAppsContainerView mMainAdapterProvider); } + // TODO(b/216683257): Remove when Taskbar All Apps supports search. + protected boolean isSearchSupported() { + return true; + } + + private void layoutWithoutSearchContainer(View v, boolean includeTabsMargin) { + if (!(v.getLayoutParams() instanceof RelativeLayout.LayoutParams) + || PreferenceExtensionsKt.firstBlocking(pref2.getHideAppDrawerSearchBar())) { + return; + } + + RelativeLayout.LayoutParams layoutParams = (LayoutParams) v.getLayoutParams(); + layoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP); + layoutParams.topMargin = getContext().getResources().getDimensionPixelSize(includeTabsMargin + ? R.dimen.all_apps_header_pill_height + : R.dimen.all_apps_header_top_margin); + } + public boolean isInAllApps() { // TODO: Make this abstract return true; @@ -1045,16 +1079,22 @@ public class ActivityAllAppsContainerView } protected void updateBackgroundVisibility(DeviceProfile deviceProfile) { - mBottomSheetBackground.setVisibility( - deviceProfile.shouldShowAllAppsOnSheet() ? View.VISIBLE : View.GONE); - // Note: The opaque sheet background and header protection are added in drawOnScrim. - // For the taskbar entrypoint, the scrim is drawn by its abstract slide in view container, + boolean visible = deviceProfile.isTablet || mForceBottomSheetVisible; + mBottomSheetBackground.setVisibility(visible ? View.VISIBLE : View.GONE); + // Note: For tablets, the opaque background and header protection are added in + // drawOnScrim. + // For the taskbar entrypoint, the scrim is drawn by its abstract slide in view + // container, // so its header protection is derived from this scrim instead. } + private void setBottomSheetAlpha(float alpha) { + // Bottom sheet alpha is always 1 for tablets. + mBottomSheetAlpha = mActivityContext.getDeviceProfile().isTablet ? 1f : alpha; + } + @VisibleForTesting public void onAppsUpdated() { - Log.d(TAG, "onAppsUpdated; number of apps: " + mAllAppsStore.getApps().length); mHasWorkApps = Stream.of(mAllAppsStore.getApps()) .anyMatch(mWorkManager.getItemInfoMatcher()); mHasPrivateApps = Stream.of(mAllAppsStore.getApps()) @@ -1076,7 +1116,8 @@ public class ActivityAllAppsContainerView @Override public boolean onInterceptTouchEvent(MotionEvent ev) { - // The AllAppsContainerView houses the QSB and is hence visible from the Workspace + // The AllAppsContainerView houses the QSB and is hence visible from the + // Workspace // Overview states. We shouldn't intercept for the scrubber in these cases. if (!isInAllApps()) { mTouchHandler = null; @@ -1126,7 +1167,10 @@ public class ActivityAllAppsContainerView return false; } - /** The current active recycler view (A-Z list from one of the profiles, or search results). */ + /** + * The current active recycler view (A-Z list from one of the profiles, or + * search results). + */ public AllAppsRecyclerView getActiveRecyclerView() { if (isSearching()) { return getSearchRecyclerView(); @@ -1149,7 +1193,8 @@ public class ActivityAllAppsContainerView } /** - * The container for A-Z apps (the ViewPager for main+work tabs, or main RV). This is currently + * The container for A-Z apps (the ViewPager for main+work tabs, or main RV). + * This is currently * hidden while searching. */ public ViewGroup getAppsRecyclerViewContainer() { @@ -1166,7 +1211,8 @@ public class ActivityAllAppsContainerView } /** - * Switches the current page to the provided {@code tab} if tabs are supported, otherwise does + * Switches the current page to the provided {@code tab} if tabs are supported, + * otherwise does * nothing. */ public void switchToTab(int tab) { @@ -1180,7 +1226,8 @@ public class ActivityAllAppsContainerView } @Override - public void onDropCompleted(View target, DragObject d, boolean success) {} + public void onDropCompleted(View target, DragObject d, boolean success) { + } @Override public void setInsets(Rect insets) { @@ -1190,8 +1237,8 @@ public class ActivityAllAppsContainerView applyAdapterSideAndBottomPaddings(grid); MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams(); - // Ignore left/right insets on bottom sheet because we are already centered in-screen. - if (grid.shouldShowAllAppsOnSheet()) { + // Ignore left/right insets on tablet because we are already centered in-screen. + if (grid.isTablet) { mlp.leftMargin = mlp.rightMargin = 0; } else { mlp.leftMargin = insets.left; @@ -1201,7 +1248,7 @@ public class ActivityAllAppsContainerView if (!grid.isVerticalBarLayout() || FeatureFlags.enableResponsiveWorkspace()) { int topPadding = grid.allAppsPadding.top; - if (isSearchBarFloating() && !grid.shouldShowAllAppsOnSheet()) { + if (isSearchBarFloating() && !grid.isTablet) { topPadding += getResources().getDimensionPixelSize( R.dimen.all_apps_additional_top_padding_floating_search); } @@ -1211,7 +1258,8 @@ public class ActivityAllAppsContainerView } /** - * Returns a padding in case a scrim is shown on the bottom of the view and a padding is needed. + * Returns a padding in case a scrim is shown on the bottom of the view and a + * padding is needed. */ protected int computeNavBarScrimHeight(WindowInsets insets) { return 0; @@ -1236,10 +1284,8 @@ public class ActivityAllAppsContainerView super.dispatchDraw(canvas); if (mNavBarScrimHeight > 0) { - float left = (getWidth() - getWidth() / getScaleX()) / 2; - float top = getHeight() / 2f + (getHeight() / 2f - mNavBarScrimHeight) / getScaleY(); - canvas.drawRect(left, top, getWidth() / getScaleX(), - top + mNavBarScrimHeight / getScaleY(), mNavBarScrimPaint); + canvas.drawRect(0, getHeight() - mNavBarScrimHeight, getWidth(), getHeight(), + mNavBarScrimPaint); } } @@ -1262,24 +1308,22 @@ public class ActivityAllAppsContainerView int bottomPadding = Math.max(mInsets.bottom, mNavBarScrimHeight); mAH.forEach(adapterHolder -> { adapterHolder.mPadding.bottom = bottomPadding; - adapterHolder.mPadding.left = grid.allAppsPadding.left; - adapterHolder.mPadding.right = grid.allAppsPadding.right; + adapterHolder.mPadding.left = adapterHolder.mPadding.right = grid.allAppsPadding.left + + grid.allAppsPadding.right; adapterHolder.applyPadding(); }); } private void setDeviceManagementResources() { - if (mActivityContext.getStringCache() != null) { - Button personalTab = findViewById(R.id.tab_personal); - personalTab.setText(R.string.all_apps_personal_tab); - personalTab.setAllCaps(false); - FontManager.INSTANCE.get(getContext()).setCustomFont(personalTab, R.id.font_button); + Button personalTab = findViewById(R.id.tab_personal); + personalTab.setText(R.string.all_apps_personal_tab); + personalTab.setAllCaps(false); + FontManager.INSTANCE.get(getContext()).setCustomFont(personalTab, R.id.font_button); - Button workTab = findViewById(R.id.tab_work); - workTab.setText(R.string.all_apps_work_tab); - workTab.setAllCaps(false); - FontManager.INSTANCE.get(getContext()).setCustomFont(workTab, R.id.font_button); - } + Button workTab = findViewById(R.id.tab_work); + workTab.setText(R.string.all_apps_work_tab); + workTab.setAllCaps(false); + FontManager.INSTANCE.get(getContext()).setCustomFont(workTab, R.id.font_button); } /** @@ -1292,18 +1336,22 @@ public class ActivityAllAppsContainerView // Used by tests only private boolean isDescendantViewVisible(int viewId) { final View view = findViewById(viewId); - if (view == null) return false; + if (view == null) + return false; - if (!view.isShown()) return false; + if (!view.isShown()) + return false; return view.getGlobalVisibleRect(new Rect()); } - /** Called in Launcher#bindStringCache() to update the UI when cache is updated. */ + /** + * Called in Launcher#bindStringCache() to update the UI when cache is updated. + */ public void updateWorkUI() { setDeviceManagementResources(); - if (mWorkManager.getWorkUtilityView() != null) { - mWorkManager.getWorkUtilityView().updateStringFromCache(); + if (mWorkManager.getWorkModeSwitch() != null) { + mWorkManager.getWorkModeSwitch().updateStringFromCache(); } inflateWorkCardsIfNeeded(); } @@ -1312,12 +1360,12 @@ public class ActivityAllAppsContainerView AllAppsRecyclerView workRV = mAH.get(AdapterHolder.WORK).mRecyclerView; if (workRV != null) { for (int i = 0; i < workRV.getChildCount(); i++) { - View currentView = workRV.getChildAt(i); + View currentView = workRV.getChildAt(i); int currentItemViewType = workRV.getChildViewHolder(currentView).getItemViewType(); if (currentItemViewType == VIEW_TYPE_WORK_EDU_CARD) { ((WorkEduCard) currentView).updateStringFromCache(); } else if (currentItemViewType == VIEW_TYPE_WORK_DISABLED_CARD) { - ((WorkPausedCard) currentView).updateStringFromCache(); + ((WorkPausedCard) currentView).setWorkProfilePausedResources(); } } } @@ -1346,10 +1394,6 @@ public class ActivityAllAppsContainerView return mAH.get(MAIN).mAppsList; } - public AlphabeticalAppsList getWorkAppList() { - return mAH.get(WORK).mAppsList; - } - public FloatingHeaderView getFloatingHeaderView() { return mHeader; } @@ -1375,7 +1419,7 @@ public class ActivityAllAppsContainerView */ public void addSpringFromFlingUpdateListener(ValueAnimator animator, float velocity /* release velocity */, - float progress /* portion of the distance to travel*/) { + float progress /* portion of the distance to travel */) { animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animator) { @@ -1409,19 +1453,17 @@ public class ActivityAllAppsContainerView invalidateHeader(); } - @Override - public void setScaleY(float scaleY) { - super.setScaleY(scaleY); - try { - if (predictiveBackThreeButtonNav() && mNavBarScrimHeight > 0) { - // Call invalidate to prevent navbar scrim from scaling. The navbar scrim is drawn - // directly onto the canvas. To prevent it from being scaled with the canvas, there's a - // counter scale applied in dispatchDraw. - invalidate(20, getHeight() - mNavBarScrimHeight, getWidth(), getHeight()); - } - } catch (Throwable t) { - // LC-Ignored + /** + * Set {@link Animator.AnimatorListener} on {@link mAllAppsTransitionController} + * to observe + * animation of backing out of all apps search view to all apps view. + */ + public void setAllAppsSearchBackAnimatorListener(Animator.AnimatorListener listener) { + Preconditions.assertNotNull(mAllAppsTransitionController); + if (mAllAppsTransitionController == null) { + return; } + mAllAppsTransitionController.setAllAppsSearchBackAnimationListener(listener); } public void setScrimView(ScrimView scrimView) { @@ -1446,7 +1488,7 @@ public class ActivityAllAppsContainerView // Draw full background panel for tablets. if (hasBottomSheet) { mHeaderPaint.setColor(mBottomSheetBackgroundColor); - mHeaderPaint.setAlpha((int) (mBottomSheetBackgroundAlpha * 255)); + mHeaderPaint.setAlpha((int) (255 * mBottomSheetAlpha)); mTmpRectF.set( leftWithScale, @@ -1469,13 +1511,8 @@ public class ActivityAllAppsContainerView return; } - if (hasBottomSheet) { - mHeaderPaint.setAlpha((int) (mHeaderPaint.getAlpha() * mBottomSheetBackgroundAlpha)); - } - // Draw header on background panel - final float headerBottomNoScale = - getHeaderBottom() + getVisibleContainerView().getPaddingTop(); + final float headerBottomNoScale = getHeaderBottom() + getVisibleContainerView().getPaddingTop(); final float headerHeightNoScale = headerBottomNoScale - topNoScale; final float headerBottomWithScaleOnTablet = topWithScale + headerHeightNoScale * scale; final float headerBottomOffset = (getVisibleContainerView().getHeight() * (1 - scale) / 2); @@ -1504,11 +1541,7 @@ public class ActivityAllAppsContainerView mHeaderPaint.setColor(Color.BLUE); mHeaderPaint.setAlpha(255); } else { - float tabAlpha = getAlpha() * mTabsProtectionAlpha; - if (hasBottomSheet) { - tabAlpha *= mBottomSheetBackgroundAlpha; - } - mHeaderPaint.setAlpha((int) tabAlpha); + mHeaderPaint.setAlpha((int) (getAlpha() * mTabsProtectionAlpha)); } float left = 0f; float right = canvas.getWidth(); @@ -1532,7 +1565,8 @@ public class ActivityAllAppsContainerView } /** - * The height of the header protection as if the user scrolled down the app list. + * The height of the header protection as if the user scrolled down the app + * list. */ float getHeaderProtectionHeight() { float headerBottom = getHeaderBottom() - getTranslationY(); @@ -1543,10 +1577,6 @@ public class ActivityAllAppsContainerView } } - ConstraintLayout getFastScrollerLetterList() { - return mFastScrollLetterLayout; - } - /** * redraws header protection */ @@ -1560,7 +1590,7 @@ public class ActivityAllAppsContainerView public int getHeaderBottom() { int bottom = (int) getTranslationY() + mHeader.getClipTop(); if (isSearchBarFloating()) { - if (mActivityContext.getDeviceProfile().shouldShowAllAppsOnSheet()) { + if (mActivityContext.getDeviceProfile().isTablet) { return bottom + mBottomSheetBackground.getTop(); } return bottom; @@ -1614,20 +1644,22 @@ public class ActivityAllAppsContainerView void setup(@NonNull View rv, @Nullable Predicate matcher) { mAppsList.updateItemFilter(matcher); mRecyclerView = (AllAppsRecyclerView) rv; - mRecyclerView.bindFastScrollbar(mFastScroller, ALL_APPS_SCROLLER); + mRecyclerView.bindFastScrollbar(mFastScroller); mRecyclerView.setEdgeEffectFactory(createEdgeEffectFactory()); mRecyclerView.setApps(mAppsList); mRecyclerView.setLayoutManager(mLayoutManager); mRecyclerView.setAdapter(mAdapter); mRecyclerView.setHasFixedSize(true); - // No animations will occur when changes occur to the items in this RecyclerView. + // No animations will occur when changes occur to the items in this + // RecyclerView. mRecyclerView.setItemAnimator(null); onInitializeRecyclerView(mRecyclerView); // Use ViewGroupFocusHelper for SearchRecyclerView to draw focus outline for the // buttons in the view (e.g. query builder button and setting button) FocusedItemDecorator focusedItemDecorator = isSearch() ? new FocusedItemDecorator( - new ViewGroupFocusHelper(mRecyclerView)) : new FocusedItemDecorator( - mRecyclerView); + new ViewGroupFocusHelper(mRecyclerView)) + : new FocusedItemDecorator( + mRecyclerView); mRecyclerView.addItemDecoration(focusedItemDecorator); mOnFocusChangeListener = focusedItemDecorator.getFocusListener(); mAdapter.setIconFocusListener(mOnFocusChangeListener); @@ -1637,8 +1669,8 @@ public class ActivityAllAppsContainerView void applyPadding() { if (mRecyclerView != null) { int bottomOffset = 0; - if (isWork() && mWorkManager.getWorkUtilityView() != null) { - bottomOffset = mInsets.bottom + mWorkManager.getWorkUtilityView().getHeight(); + if (isWork() && mWorkManager.getWorkModeSwitch() != null) { + bottomOffset = mInsets.bottom + mWorkManager.getWorkModeSwitch().getHeight(); } else if (isMain() && mPrivateProfileManager != null) { Optional privateSpaceHeaderItem = mAppsList.getAdapterItems() .stream() diff --git a/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java b/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java index 77a0fe331d..911612ff19 100644 --- a/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java +++ b/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java @@ -15,8 +15,6 @@ */ package com.android.launcher3.allapps; -import static android.view.HapticFeedbackConstants.CLOCK_TICK; - import androidx.recyclerview.widget.LinearSmoothScroller; import androidx.recyclerview.widget.RecyclerView.ViewHolder; @@ -73,7 +71,6 @@ public class AllAppsFastScrollHelper { @Override protected int getVerticalSnapPreference() { - mRv.performHapticFeedback(CLOCK_TICK); return SNAP_TO_ANY; } diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java index 0105523808..627047b59a 100644 --- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java +++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java @@ -30,6 +30,7 @@ import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView.Adapter; +import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.BubbleTextView; import com.android.launcher3.R; import com.android.launcher3.config.FeatureFlags; @@ -135,12 +136,6 @@ public class AllAppsGridAdapter extends getRowsNotForAccessibility(mApps.getAdapterItems().size() - 1); } - @Override - public int getColumnCountForAccessibility(RecyclerView.Recycler recycler, - RecyclerView.State state) { - return mAppsPerRow; - } - @Override public void onInitializeAccessibilityNodeInfoForItem(RecyclerView.Recycler recycler, RecyclerView.State state, View host, AccessibilityNodeInfoCompat info) { diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java index 8acb08cc30..8b91d745e1 100644 --- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java +++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java @@ -15,9 +15,8 @@ */ package com.android.launcher3.allapps; -import static androidx.constraintlayout.widget.ConstraintSet.MATCH_CONSTRAINT; -import static androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT; - +import static com.android.launcher3.config.FeatureFlags.ALL_APPS_GONE_VISIBILITY; +import static com.android.launcher3.config.FeatureFlags.ENABLE_ALL_APPS_RV_PREINFLATION; import static com.android.launcher3.logger.LauncherAtom.ContainerInfo; import static com.android.launcher3.logger.LauncherAtom.SearchResultContainer; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_PERSONAL_SCROLLED_DOWN; @@ -37,29 +36,22 @@ import android.content.Context; import android.graphics.Canvas; import android.util.AttributeSet; import android.util.Log; -import android.view.LayoutInflater; import android.view.View; -import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.constraintlayout.widget.ConstraintLayout; -import androidx.constraintlayout.widget.ConstraintSet; import androidx.core.util.Consumer; import androidx.recyclerview.widget.RecyclerView; import com.android.launcher3.DeviceProfile; import com.android.launcher3.ExtendedEditText; import com.android.launcher3.FastScrollRecyclerView; -import com.android.launcher3.Flags; import com.android.launcher3.LauncherAppState; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.logging.StatsLogManager; import com.android.launcher3.views.ActivityContext; -import java.util.ArrayList; -import java.util.Arrays; import java.util.List; /** @@ -74,7 +66,6 @@ public class AllAppsRecyclerView extends FastScrollRecyclerView { protected final int mNumAppsPerRow; private final AllAppsFastScrollHelper mFastScrollHelper; private int mCumulativeVerticalScroll; - private ConstraintLayout mLetterList; public AlphabeticalAppsList mApps; @@ -122,11 +113,13 @@ public class AllAppsRecyclerView extends FastScrollRecyclerView { // all apps. int maxPoolSizeForAppIcons = grid.getMaxAllAppsRowCount() * grid.numShownAllAppsColumns; - // If we set all apps' hidden visibility to GONE and enable pre-inflation, we want to - // preinflate one page of all apps icons plus [PREINFLATE_ICONS_ROW_COUNT] rows + - // [EXTRA_ICONS_COUNT]. Thus we need to bump the max pool size of app icons accordingly. - maxPoolSizeForAppIcons += - PREINFLATE_ICONS_ROW_COUNT * grid.numShownAllAppsColumns + EXTRA_ICONS_COUNT; + if (ALL_APPS_GONE_VISIBILITY.get() && ENABLE_ALL_APPS_RV_PREINFLATION.get()) { + // If we set all apps' hidden visibility to GONE and enable pre-inflation, we want to + // preinflate one page of all apps icons plus [PREINFLATE_ICONS_ROW_COUNT] rows + + // [EXTRA_ICONS_COUNT]. Thus we need to bump the max pool size of app icons accordingly. + maxPoolSizeForAppIcons += + PREINFLATE_ICONS_ROW_COUNT * grid.numShownAllAppsColumns + EXTRA_ICONS_COUNT; + } if (hasWorkProfile) { maxPoolSizeForAppIcons *= 2; } @@ -245,9 +238,6 @@ public class AllAppsRecyclerView extends FastScrollRecyclerView { return; } - if (Flags.letterFastScroller() && !mScrollbar.isDraggingThumb()) { - setLettersToScrollLayout(mApps.getFastScrollerSections()); - } // Only show the scrollbar if there is height to be scrolled int availableScrollBarHeight = getAvailableScrollBarHeight(); int availableScrollHeight = getAvailableScrollHeight(); @@ -329,80 +319,6 @@ public class AllAppsRecyclerView extends FastScrollRecyclerView { return false; } - public void setLettersToScrollLayout( - List fastScrollSections) { - if (fastScrollSections.isEmpty()) { - return; - } - if (mLetterList != null) { - mLetterList.removeAllViews(); - } - Context context = getContext(); - ActivityAllAppsContainerView allAppsContainerView = - ActivityContext.lookupContext(context).getAppsView(); - mLetterList = allAppsContainerView.getFastScrollerLetterList(); - mLetterList.setPadding(0, getScrollBarTop(), 0, getScrollBarMarginBottom()); - List textViews = new ArrayList<>(); - for (int i = 0; i < fastScrollSections.size(); i++) { - AlphabeticalAppsList.FastScrollSectionInfo sectionInfo = fastScrollSections.get(i); - LetterListTextView textView = - (LetterListTextView) LayoutInflater.from(context).inflate( - R.layout.fast_scroller_letter_list_text_view, mLetterList, false); - int viewId = View.generateViewId(); - textView.apply(sectionInfo /* FastScrollSectionInfo */, viewId /* viewId */); - sectionInfo.setId(viewId); - if (i == fastScrollSections.size() - 1) { - // The last section info is just a duplicate so that user can scroll to the bottom. - textView.setVisibility(INVISIBLE); - } - textViews.add(textView); - mLetterList.addView(textView); - } - // Need to add an extra textview to be aligned. - LetterListTextView lastLetterListTextView = new LetterListTextView(context); - int currentId = View.generateViewId(); - lastLetterListTextView.setId(currentId); - lastLetterListTextView.setVisibility(INVISIBLE); - textViews.add(lastLetterListTextView); - mLetterList.addView(lastLetterListTextView); - constraintTextViewsVertically(mLetterList, textViews); - mLetterList.setVisibility(VISIBLE); - // Set the alpha to 0 to avoid the letter list being shown when it shouldn't be. - mLetterList.setAlpha(0); - } - - private void constraintTextViewsVertically(ConstraintLayout constraintLayout, - List textViews) { - ConstraintSet chain = new ConstraintSet(); - chain.clone(constraintLayout); - for (int i = 0; i < textViews.size(); i++) { - LetterListTextView currentView = textViews.get(i); - if (i == 0) { - chain.connect(currentView.getId(), ConstraintSet.TOP, ConstraintSet.PARENT_ID, - ConstraintSet.TOP); - } else { - chain.connect(currentView.getId(), ConstraintSet.TOP, textViews.get(i-1).getId(), - ConstraintSet.BOTTOM); - } - chain.connect(currentView.getId(), ConstraintSet.START, constraintLayout.getId(), - ConstraintSet.START); - chain.connect(currentView.getId(), ConstraintSet.END, constraintLayout.getId(), - ConstraintSet.END); - } - int[] viewIds = textViews.stream().mapToInt(TextView::getId).toArray(); - float[] weights = new float[textViews.size()]; - Arrays.fill(weights,1); // fill with 1 for equal weights - chain.createVerticalChain(constraintLayout.getId(), ConstraintSet.TOP, - constraintLayout.getId(), ConstraintSet.BOTTOM, viewIds, weights, - ConstraintSet.CHAIN_SPREAD); - chain.applyTo(constraintLayout); - } - - @Override - public ConstraintLayout getLetterList() { - return mLetterList; - } - private void logCumulativeVerticalScroll() { ActivityContext context = ActivityContext.lookupContext(getContext()); StatsLogManager mgr = context.getStatsLogManager(); diff --git a/src/com/android/launcher3/allapps/AllAppsStore.java b/src/com/android/launcher3/allapps/AllAppsStore.java index 564719e95a..90d6232a4c 100644 --- a/src/com/android/launcher3/allapps/AllAppsStore.java +++ b/src/com/android/launcher3/allapps/AllAppsStore.java @@ -15,12 +15,13 @@ */ package com.android.launcher3.allapps; +import static com.android.launcher3.config.FeatureFlags.ENABLE_ALL_APPS_RV_PREINFLATION; import static com.android.launcher3.model.data.AppInfo.COMPONENT_KEY_COMPARATOR; import static com.android.launcher3.model.data.AppInfo.EMPTY_ARRAY; +import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_SHOW_DOWNLOAD_PROGRESS_MASK; import android.content.Context; import android.os.UserHandle; -import android.util.Log; import android.view.View; import android.view.ViewGroup; @@ -41,7 +42,6 @@ import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Consumer; @@ -54,7 +54,6 @@ import java.util.function.Predicate; */ public class AllAppsStore { - private static final String TAG = "AllAppsStore"; // Defer updates flag used to defer all apps updates to the next draw. public static final int DEFER_UPDATES_NEXT_DRAW = 1 << 0; // Defer updates flag used to defer all apps updates by a test's request. @@ -111,14 +110,13 @@ public class AllAppsStore { public void setApps(@Nullable AppInfo[] apps, int flags, Map map, boolean shouldPreinflate) { mApps = apps == null ? EMPTY_ARRAY : apps; - Log.d(TAG, "setApps: apps.length=" + mApps.length); mModelFlags = flags; notifyUpdate(); mPackageUserKeytoUidMap = map; // Preinflate all apps RV when apps has changed, which can happen after // unlocking screen, // rotating screen, or downloading/upgrading apps. - if (shouldPreinflate) { + if (shouldPreinflate && ENABLE_ALL_APPS_RV_PREINFLATION.get()) { mAllAppsRecyclerViewPool.preInflateAllAppsViewHolders(mContext); } } @@ -173,12 +171,10 @@ public class AllAppsStore { public void enableDeferUpdates(int flag) { mDeferUpdatesFlags |= flag; - Log.d(TAG, "enableDeferUpdates: " + flag + " mDeferUpdatesFlags=" + mDeferUpdatesFlags); } public void disableDeferUpdates(int flag) { mDeferUpdatesFlags &= ~flag; - Log.d(TAG, "disableDeferUpdates: " + flag + " mDeferUpdatesFlags=" + mDeferUpdatesFlags); if (mDeferUpdatesFlags == 0 && mUpdatePending) { notifyUpdate(); mUpdatePending = false; @@ -187,9 +183,6 @@ public class AllAppsStore { public void disableDeferUpdatesSilently(int flag) { mDeferUpdatesFlags &= ~flag; - Log.d(TAG, "disableDeferUpdatesSilently: " + flag - + " mDeferUpdatesFlags=" + mDeferUpdatesFlags); - } public int getDeferUpdatesFlags() { @@ -198,11 +191,9 @@ public class AllAppsStore { private void notifyUpdate() { if (mDeferUpdatesFlags != 0) { - Log.d(TAG, "notifyUpdate: deferring update"); mUpdatePending = true; return; } - Log.d(TAG, "notifyUpdate: notifying listeners"); for (OnUpdateListener listener : mUpdateListeners) { listener.onAppsUpdated(); } @@ -251,7 +242,11 @@ public class AllAppsStore { public void updateProgressBar(AppInfo app) { updateAllIcons((child) -> { if (child.getTag() == app) { - child.applyFromApplicationInfo(app); + if ((app.runtimeStatusFlags & FLAG_SHOW_DOWNLOAD_PROGRESS_MASK) == 0) { + child.applyFromApplicationInfo(app); + } else { + child.applyProgressLevel(); + } } }); } @@ -280,15 +275,8 @@ public class AllAppsStore { public void dump(String prefix, PrintWriter writer) { writer.println(prefix + "\tAllAppsStore Apps[] size: " + mApps.length); for (int i = 0; i < mApps.length; i++) { - writer.println(String.format(Locale.getDefault(), - "%s\tPackage index, name, class, description, bitmap flag: %d/%s:%s, %s, %s+%s", - prefix, - i, - mApps[i].componentName.getPackageName(), - mApps[i].componentName.getClassName(), - mApps[i].contentDescription, - Integer.toBinaryString(mApps[i].bitmap.flags), - Integer.toBinaryString(mApps[i].bitmap.creationFlags))); + writer.println(String.format("%s\tPackage index and name: %d/%s", prefix, i, + mApps[i].componentName.getPackageName())); } } } diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java index 350f763939..2bb1b4cde9 100644 --- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java +++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java @@ -16,6 +16,7 @@ package com.android.launcher3.allapps; import static com.android.app.animation.Interpolators.DECELERATE_1_7; +import static com.android.app.animation.Interpolators.INSTANT; import static com.android.app.animation.Interpolators.LINEAR; import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y; @@ -27,6 +28,7 @@ import static com.android.launcher3.UtilitiesKt.CLIP_CHILDREN_FALSE_MODIFIER; import static com.android.launcher3.UtilitiesKt.modifyAttributesOnViewTree; import static com.android.launcher3.UtilitiesKt.restoreAttributesOnViewTree; import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER; +import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_BOTTOM_SHEET_FADE; import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE; import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS; import static com.android.launcher3.util.SystemUiController.FLAG_DARK_NAV; @@ -34,8 +36,8 @@ import static com.android.launcher3.util.SystemUiController.FLAG_LIGHT_NAV; import static com.android.launcher3.util.SystemUiController.UI_STATE_ALL_APPS; import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; import android.util.FloatProperty; import android.view.HapticFeedbackConstants; import android.view.View; @@ -44,28 +46,28 @@ import android.view.animation.Interpolator; import androidx.annotation.FloatRange; import androidx.annotation.Nullable; +import com.android.app.animation.Interpolators; import com.android.launcher3.DeviceProfile; import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener; -import com.android.launcher3.Flags; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; import com.android.launcher3.R; +import com.android.launcher3.Utilities; import com.android.launcher3.anim.AnimatedFloat; import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.anim.PropertySetter; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.statemanager.StateManager.StateHandler; import com.android.launcher3.states.StateAnimationConfig; import com.android.launcher3.touch.AllAppsSwipeController; -import com.android.launcher3.util.MSDLPlayerWrapper; import com.android.launcher3.util.MultiPropertyFactory; import com.android.launcher3.util.MultiPropertyFactory.MultiProperty; import com.android.launcher3.util.MultiValueAlpha; import com.android.launcher3.util.ScrollableLayoutManager; import com.android.launcher3.util.Themes; +import com.android.launcher3.util.VibratorWrapper; import com.android.launcher3.views.ScrimView; -import com.google.android.msdl.data.model.MSDLToken; - /** * Handles AllApps view transition. * 1) Slides all apps view using direct manipulation @@ -73,88 +75,90 @@ import com.google.android.msdl.data.model.MSDLToken; *

* Algorithm: * If release velocity > THRES1, snap according to the direction of movement. - * If release velocity < THRES1, snap according to either top or bottom depending on whether it's + * If release velocity < THRES1, snap according to either top or bottom + * depending on whether it's * closer to top or closer to the page indicator. */ public class AllAppsTransitionController implements StateHandler, OnDeviceProfileChangeListener { - // This constant should match the second derivative of the animator interpolator. + // This constant should match the second derivative of the animator + // interpolator. public static final float INTERP_COEFF = 1.7f; public static final int REVERT_SWIPE_ALL_APPS_TO_HOME_ANIMATION_DURATION_MS = 200; private static final float NAV_BAR_COLOR_FORCE_UPDATE_THRESHOLD = 0.1f; - private static final float SWIPE_DRAG_COMMIT_THRESHOLD = - 1 - AllAppsSwipeController.ALL_APPS_STATE_TRANSITION_MANUAL; + private static final float SWIPE_DRAG_COMMIT_THRESHOLD = 1 + - AllAppsSwipeController.ALL_APPS_STATE_TRANSITION_MANUAL; - public static final FloatProperty ALL_APPS_PROGRESS = - new FloatProperty("allAppsProgress") { + public static final FloatProperty ALL_APPS_PROGRESS = new FloatProperty( + "allAppsProgress") { - @Override - public Float get(AllAppsTransitionController controller) { - return controller.mProgress; - } + @Override + public Float get(AllAppsTransitionController controller) { + return controller.mProgress; + } - @Override - public void setValue(AllAppsTransitionController controller, float progress) { - controller.setProgress(progress); - } - }; + @Override + public void setValue(AllAppsTransitionController controller, float progress) { + controller.setProgress(progress); + } + }; private static final float ALL_APPS_PULL_BACK_TRANSLATION_DEFAULT = 0f; - public static final FloatProperty ALL_APPS_PULL_BACK_TRANSLATION = - new FloatProperty("allAppsPullBackTranslation") { + public static final FloatProperty ALL_APPS_PULL_BACK_TRANSLATION = new FloatProperty( + "allAppsPullBackTranslation") { - @Override - public Float get(AllAppsTransitionController controller) { - if (controller.mShouldShowAllAppsOnSheet) { - return controller.mAppsView.getActiveRecyclerView().getTranslationY(); - } else { - return controller.getAppsViewPullbackTranslationY().getValue(); - } - } + @Override + public Float get(AllAppsTransitionController controller) { + if (controller.mIsTablet) { + return controller.mAppsView.getActiveRecyclerView().getTranslationY(); + } else { + return controller.getAppsViewPullbackTranslationY().getValue(); + } + } - @Override - public void setValue(AllAppsTransitionController controller, float translation) { - if (controller.mShouldShowAllAppsOnSheet) { - controller.mAppsView.getActiveRecyclerView().setTranslationY(translation); - controller.getAppsViewPullbackTranslationY().setValue( - ALL_APPS_PULL_BACK_TRANSLATION_DEFAULT); - } else { - controller.getAppsViewPullbackTranslationY().setValue(translation); - controller.mAppsView.getActiveRecyclerView().setTranslationY( - ALL_APPS_PULL_BACK_TRANSLATION_DEFAULT); - } - } - }; + @Override + public void setValue(AllAppsTransitionController controller, float translation) { + if (controller.mIsTablet) { + controller.mAppsView.getActiveRecyclerView().setTranslationY(translation); + controller.getAppsViewPullbackTranslationY().setValue( + ALL_APPS_PULL_BACK_TRANSLATION_DEFAULT); + } else { + controller.getAppsViewPullbackTranslationY().setValue(translation); + controller.mAppsView.getActiveRecyclerView().setTranslationY( + ALL_APPS_PULL_BACK_TRANSLATION_DEFAULT); + } + } + }; private static final float ALL_APPS_PULL_BACK_ALPHA_DEFAULT = 1f; - public static final FloatProperty ALL_APPS_PULL_BACK_ALPHA = - new FloatProperty("allAppsPullBackAlpha") { + public static final FloatProperty ALL_APPS_PULL_BACK_ALPHA = new FloatProperty( + "allAppsPullBackAlpha") { - @Override - public Float get(AllAppsTransitionController controller) { - if (controller.mShouldShowAllAppsOnSheet) { - return controller.mAppsView.getActiveRecyclerView().getAlpha(); - } else { - return controller.getAppsViewPullbackAlpha().getValue(); - } - } + @Override + public Float get(AllAppsTransitionController controller) { + if (controller.mIsTablet) { + return controller.mAppsView.getActiveRecyclerView().getAlpha(); + } else { + return controller.getAppsViewPullbackAlpha().getValue(); + } + } - @Override - public void setValue(AllAppsTransitionController controller, float alpha) { - if (controller.mShouldShowAllAppsOnSheet) { - controller.mAppsView.getActiveRecyclerView().setAlpha(alpha); - controller.getAppsViewPullbackAlpha().setValue( - ALL_APPS_PULL_BACK_ALPHA_DEFAULT); - } else { - controller.getAppsViewPullbackAlpha().setValue(alpha); - controller.mAppsView.getActiveRecyclerView().setAlpha( - ALL_APPS_PULL_BACK_ALPHA_DEFAULT); - } - } - }; + @Override + public void setValue(AllAppsTransitionController controller, float alpha) { + if (controller.mIsTablet) { + controller.mAppsView.getActiveRecyclerView().setAlpha(alpha); + controller.getAppsViewPullbackAlpha().setValue( + ALL_APPS_PULL_BACK_ALPHA_DEFAULT); + } else { + controller.getAppsViewPullbackAlpha().setValue(alpha); + controller.mAppsView.getActiveRecyclerView().setAlpha( + ALL_APPS_PULL_BACK_ALPHA_DEFAULT); + } + } + }; private static final int INDEX_APPS_VIEW_PROGRESS = 0; private static final int INDEX_APPS_VIEW_PULLBACK = 1; @@ -166,41 +170,45 @@ public class AllAppsTransitionController private final AnimatedFloat mAllAppScale = new AnimatedFloat(this::onScaleProgressChanged); private final int mNavScrimFlag; - @Nullable private Animator.AnimatorListener mAllAppsSearchBackAnimationListener; + @Nullable + private Animator.AnimatorListener mAllAppsSearchBackAnimationListener; private boolean mIsVerticalLayout; - private boolean mShouldShowAllAppsOnSheet; // Animation in this class is controlled by a single variable {@link mProgress}. - // Visually, it represents top y coordinate of the all apps container if multiplied with + // Visually, it represents top y coordinate of the all apps container if + // multiplied with // {@link mShiftRange}. // When {@link mProgress} is 0, all apps container is pulled up. // When {@link mProgress} is 1, all apps container is pulled down. - private float mShiftRange; // changes depending on the orientation - private float mProgress; // [0, 1], mShiftRange * mProgress = shiftCurrent + private float mShiftRange; // changes depending on the orientation + private float mProgress; // [0, 1], mShiftRange * mProgress = shiftCurrent private ScrimView mScrimView; private MultiValueAlpha mAppsViewAlpha; private MultiPropertyFactory mAppsViewTranslationY; + private boolean mIsTablet; + private boolean mHasScaleEffect; - private final MSDLPlayerWrapper mMSDLPlayerWrapper; + private final VibratorWrapper mVibratorWrapper; public AllAppsTransitionController(Launcher l) { mLauncher = l; DeviceProfile dp = mLauncher.getDeviceProfile(); mProgress = 1f; mIsVerticalLayout = dp.isVerticalBarLayout(); - mShouldShowAllAppsOnSheet = dp.shouldShowAllAppsOnSheet(); + mIsTablet = dp.isTablet; mNavScrimFlag = Themes.getAttrBoolean(l, R.attr.isMainColorDark) - ? FLAG_DARK_NAV : FLAG_LIGHT_NAV; + ? FLAG_DARK_NAV + : FLAG_LIGHT_NAV; setShiftRange(dp.allAppsShiftRange); mAllAppScale.value = 1; mLauncher.addOnDeviceProfileChangeListener(this); - mMSDLPlayerWrapper = MSDLPlayerWrapper.INSTANCE.get(mLauncher.getApplicationContext()); + mVibratorWrapper = VibratorWrapper.INSTANCE.get(mLauncher.getApplicationContext()); } public float getShiftRange() { @@ -217,21 +225,22 @@ public class AllAppsTransitionController mLauncher.getWorkspace().getPageIndicator().setTranslationY(0); } - mShouldShowAllAppsOnSheet = dp.shouldShowAllAppsOnSheet(); + mIsTablet = dp.isTablet; } /** - * Note this method should not be called outside this class. This is public because it is used + * Note this method should not be called outside this class. This is public + * because it is used * in xml-based animations which also handle updating the appropriate UI. * * @param progress value between 0 and 1, 0 shows all apps and 1 shows workspace * @see #setState(LauncherState) - * @see #setStateWithAnimation(LauncherState, StateAnimationConfig, PendingAnimation) + * @see #setStateWithAnimation(LauncherState, StateAnimationConfig, + * PendingAnimation) */ public void setProgress(float progress) { mProgress = progress; - boolean fromBackground = - mLauncher.getStateManager().getCurrentStableState() == BACKGROUND_APP; + boolean fromBackground = mLauncher.getStateManager().getCurrentStableState() == BACKGROUND_APP; // Allow apps panel to shift the full screen if coming from another app. float shiftRange = fromBackground ? mLauncher.getDeviceProfile().heightPx : mShiftRange; getAppsViewProgressTranslationY().setValue(mProgress * shiftRange); @@ -264,7 +273,8 @@ public class AllAppsTransitionController } /** - * Sets the vertical transition progress to {@param state} and updates all the dependent UI + * Sets the vertical transition progress to {@param state} and updates all the + * dependent UI * accordingly. */ @Override @@ -280,9 +290,10 @@ public class AllAppsTransitionController return; } + float deceleratedProgress = Interpolators.BACK_GESTURE.getInterpolation(backProgress); float scaleProgress = ScrollableLayoutManager.PREDICTIVE_BACK_MIN_SCALE + (1 - ScrollableLayoutManager.PREDICTIVE_BACK_MIN_SCALE) - * (1 - backProgress); + * (1 - deceleratedProgress); mAllAppScale.updateValue(scaleProgress); } @@ -290,16 +301,18 @@ public class AllAppsTransitionController private void onScaleProgressChanged() { final float scaleProgress = mAllAppScale.value; SCALE_PROPERTY.set(mLauncher.getAppsView(), scaleProgress); - if (!mLauncher.getAppsView().isSearching() - || !mLauncher.getDeviceProfile().shouldShowAllAppsOnSheet()) { + if (!mLauncher.getAppsView().isSearching() || !mLauncher.getDeviceProfile().isTablet) { mLauncher.getScrimView().setScrimHeaderScale(scaleProgress); } AllAppsRecyclerView rv = mLauncher.getAppsView().getActiveRecyclerView(); - // Disable view clipping from all apps' RecyclerView up to all apps view during scale - // animation, and vice versa. The goal is to display extra roll(s) app icons (rendered in - // {@link AppsGridLayoutManager#calculateExtraLayoutSpace}) during scale animation. + // Disable view clipping from all apps' RecyclerView up to all apps view during + // scale + // animation, and vice versa. The goal is to display extra roll(s) app icons + // (rendered in + // {@link AppsGridLayoutManager#calculateExtraLayoutSpace}) during scale + // animation. boolean hasScaleEffect = scaleProgress < 1f; if (hasScaleEffect != mHasScaleEffect) { mHasScaleEffect = hasScaleEffect; @@ -313,13 +326,17 @@ public class AllAppsTransitionController } } - /** Set {@link Animator.AnimatorListener} for scaling all apps scale to 1 animation. */ + /** + * Set {@link Animator.AnimatorListener} for scaling all apps scale to 1 + * animation. + */ public void setAllAppsSearchBackAnimationListener(Animator.AnimatorListener listener) { mAllAppsSearchBackAnimationListener = listener; } /** - * Animate all apps view to 1f scale. This is called when backing (exiting) from all apps + * Animate all apps view to 1f scale. This is called when backing (exiting) from + * all apps * search view to all apps view. */ public void animateAllAppsToNoScale() { @@ -335,12 +352,16 @@ public class AllAppsTransitionController } /** - * Creates an animation which updates the vertical transition progress and updates all the + * Creates an animation which updates the vertical transition progress and + * updates all the * dependent UI using various animation events * - * This method also dictates where along the progress the haptics should be played. As the user - * scrolls up from workspace or down from AllApps, a drag haptic is being played until the - * commit point where it plays a commit haptic. Where we play the haptics differs when going + * This method also dictates where along the progress the haptics should be + * played. As the user + * scrolls up from workspace or down from AllApps, a drag haptic is being played + * until the + * commit point where it plays a commit haptic. Where we play the haptics + * differs when going * from workspace -> allApps and vice versa. */ @Override @@ -356,6 +377,22 @@ public class AllAppsTransitionController }); } + if (FeatureFlags.ENABLE_PREMIUM_HAPTICS_ALL_APPS.get() && config.isUserControlled() + && Utilities.ATLEAST_S) { + if (toState == ALL_APPS) { + builder.addOnFrameListener( + new VibrationAnimatorUpdateListener(this, mVibratorWrapper, + SWIPE_DRAG_COMMIT_THRESHOLD, 1)); + } else { + builder.addOnFrameListener( + new VibrationAnimatorUpdateListener(this, mVibratorWrapper, + 0, SWIPE_DRAG_COMMIT_THRESHOLD)); + } + builder.addEndListener((unused) -> { + mVibratorWrapper.cancelVibrate(); + }); + } + float targetProgress = toState.getVerticalProgress(mLauncher); if (Float.compare(mProgress, targetProgress) == 0) { setAlphas(toState, config, builder); @@ -367,28 +404,15 @@ public class AllAppsTransitionController Interpolator verticalProgressInterpolator = config.getInterpolator(ANIM_VERTICAL_PROGRESS, config.isUserControlled() ? LINEAR : DECELERATE_1_7); Animator anim = createSpringAnimation(mProgress, targetProgress); - anim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationCancel(Animator animation) { - setProgress(targetProgress); - } - }); anim.setInterpolator(verticalProgressInterpolator); builder.add(anim); setAlphas(toState, config, builder); // This controls both haptics for tapping on QSB and going to all apps. - if (ALL_APPS.equals(toState) && mLauncher.isInState(NORMAL)) { - if (Flags.msdlFeedback()) { - if (config.isUserControlled()) { - mMSDLPlayerWrapper.playToken(MSDLToken.SWIPE_THRESHOLD_INDICATOR); - } else { - mMSDLPlayerWrapper.playToken(MSDLToken.TAP_HIGH_EMPHASIS); - } - } else { - mLauncher.getAppsView().performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, - HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); - } + if (ALL_APPS.equals(toState) && mLauncher.isInState(NORMAL) && + !FeatureFlags.ENABLE_PREMIUM_HAPTICS_ALL_APPS.get()) { + mLauncher.getAppsView().performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, + HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); } } @@ -409,6 +433,10 @@ public class AllAppsTransitionController setter.setFloat(getAppsViewPullbackAlpha(), MultiPropertyFactory.MULTI_PROPERTY_VALUE, hasAllAppsContent ? 1 : 0, allAppsFade); + setter.setFloat(mLauncher.getAppsView(), + ActivityAllAppsContainerView.BOTTOM_SHEET_ALPHA, hasAllAppsContent ? 1 : 0, + config.getInterpolator(ANIM_ALL_APPS_BOTTOM_SHEET_FADE, INSTANT)); + boolean shouldProtectHeader = !config.hasAnimationFlag(StateAnimationConfig.SKIP_SCRIM) && (ALL_APPS == state || mLauncher.getStateManager().getState() == ALL_APPS); mScrimView.setDrawingController(shouldProtectHeader ? mAppsView : null); @@ -422,7 +450,8 @@ public class AllAppsTransitionController mAppsView = appsView; mAppsView.setScrimView(scrimView); - mAppsViewAlpha = new MultiValueAlpha(mAppsView, APPS_VIEW_INDEX_COUNT, View.GONE); + mAppsViewAlpha = new MultiValueAlpha(mAppsView, APPS_VIEW_INDEX_COUNT, + FeatureFlags.ALL_APPS_GONE_VISIBILITY.get() ? View.GONE : View.INVISIBLE); mAppsViewAlpha.setUpdateVisibility(true); mAppsViewTranslationY = new MultiPropertyFactory<>( mAppsView, VIEW_TRANSLATE_Y, APPS_VIEW_INDEX_COUNT, Float::sum); @@ -434,4 +463,49 @@ public class AllAppsTransitionController public void setShiftRange(float shiftRange) { mShiftRange = shiftRange; } + + /** + * This VibrationAnimatorUpdateListener class takes in four parameters, a + * controller, start + * threshold, end threshold, and a Vibrator wrapper. We use the progress given + * by the controller + * as it gives an accurate progress that dictates where the vibrator should + * vibrate. + * Note: once the user begins a gesture and does the commit haptic, there should + * not be anymore + * haptics played for that gesture. + */ + private static class VibrationAnimatorUpdateListener implements + ValueAnimator.AnimatorUpdateListener { + private final VibratorWrapper mVibratorWrapper; + private final AllAppsTransitionController mController; + private final float mStartThreshold; + private final float mEndThreshold; + private boolean mHasCommitted; + + VibrationAnimatorUpdateListener(AllAppsTransitionController controller, + VibratorWrapper vibratorWrapper, float startThreshold, + float endThreshold) { + mController = controller; + mVibratorWrapper = vibratorWrapper; + mStartThreshold = startThreshold; + mEndThreshold = endThreshold; + } + + @Override + public void onAnimationUpdate(ValueAnimator animation) { + if (mHasCommitted) { + return; + } + float currentProgress = AllAppsTransitionController.ALL_APPS_PROGRESS.get(mController); + if (currentProgress > mStartThreshold && currentProgress < mEndThreshold) { + mVibratorWrapper.vibrateForDragTexture(); + } else if (!(currentProgress == 0 || currentProgress == 1)) { + // This check guards against committing at the location of the start of the + // gesture + mVibratorWrapper.vibrateForDragCommit(); + mHasCommitted = true; + } + } + } } diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java index ca0eab0559..18af2ea326 100644 --- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java +++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java @@ -17,15 +17,9 @@ */ package com.android.launcher3.allapps; -import static android.multiuser.Flags.enableMovingContentIntoPrivateSpace; - -import static com.android.launcher3.Utilities.ATLEAST_BAKLAVA; -import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_BOTTOM_VIEW_TO_SCROLL_TO; -import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_MASK_PRIVATE_SPACE_HEADER; import static com.android.launcher3.allapps.SectionDecorationInfo.ROUND_BOTTOM_LEFT; import static com.android.launcher3.allapps.SectionDecorationInfo.ROUND_BOTTOM_RIGHT; import static com.android.launcher3.allapps.SectionDecorationInfo.ROUND_NOTHING; -import static com.android.launcher3.icons.BitmapInfo.FLAG_NO_BADGE; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_PRIVATE_SPACE_PREINSTALLED_APPS_COUNT; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_PRIVATE_SPACE_USER_INSTALLED_APPS_COUNT; @@ -33,7 +27,6 @@ import android.content.Context; import android.text.Spannable; import android.text.SpannableString; import android.text.style.ImageSpan; -import android.util.Log; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; @@ -47,7 +40,6 @@ import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.util.LabelComparator; import com.android.launcher3.views.ActivityContext; -import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import java.util.Locale; @@ -67,7 +59,6 @@ public class AlphabeticalAppsList implement AllAppsStore.OnUpdateListener { public static final String TAG = "AlphabeticalAppsList"; - public static final String PRIVATE_SPACE_PACKAGE = "com.android.privatespace"; private final WorkProfileManager mWorkProviderManager; @@ -83,17 +74,11 @@ public class AlphabeticalAppsList implement public final CharSequence sectionName; // The item position public final int position; - // The view id associated with this section - public int id = -1; public FastScrollSectionInfo(CharSequence sectionName, int position) { this.sectionName = sectionName; this.position = position; } - - public void setId(int id) { - this.id = id; - } } private final T mActivityContext; @@ -115,7 +100,6 @@ public class AlphabeticalAppsList implement // The of ordered component names as a result of a search query private final ArrayList mSearchResults = new ArrayList<>(); private final SpannableString mPrivateProfileAppScrollerBadge; - private final SpannableString mPrivateProfileDividerBadge; private BaseAllAppsAdapter mAdapter; private AppInfoComparator mAppNameComparator; private int mNumAppsPerRowAllApps; @@ -134,14 +118,9 @@ public class AlphabeticalAppsList implement mAllAppsStore.addUpdateListener(this); } mPrivateProfileAppScrollerBadge = new SpannableString(" "); - mPrivateProfileAppScrollerBadge.setSpan(new ImageSpan(context, Flags.letterFastScroller() - ? R.drawable.ic_private_profile_letter_list_fast_scroller_badge : + mPrivateProfileAppScrollerBadge.setSpan(new ImageSpan(context, R.drawable.ic_private_profile_app_scroller_badge, ImageSpan.ALIGN_CENTER), 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - mPrivateProfileDividerBadge = new SpannableString(" "); - mPrivateProfileDividerBadge.setSpan(new ImageSpan(context, - R.drawable.ic_private_profile_divider_badge, ImageSpan.ALIGN_CENTER), - 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } /** Set the number of apps per row when device profile changes. */ @@ -297,7 +276,6 @@ public class AlphabeticalAppsList implement List oldItems = new ArrayList<>(mAdapterItems); // Prepare to update the list of sections, filtered apps, etc. mFastScrollerSections.clear(); - Log.d(TAG, "Clearing FastScrollerSections."); mAdapterItems.clear(); mAccessibilityResultsCount = 0; @@ -319,22 +297,12 @@ public class AlphabeticalAppsList implement mFastScrollerSections.add(new FastScrollSectionInfo( mActivityContext.getResources().getString( R.string.work_profile_edu_section), 0)); - Log.d(TAG, "Adding FastScrollSection for work edu card."); } position = addAppsWithSections(mApps, position); } if (Flags.enablePrivateSpace()) { position = addPrivateSpaceItems(position); } - if (!mFastScrollerSections.isEmpty()) { - // After all the adapterItems are added, add a view to the bottom so that user can - // scroll all the way down. - mAdapterItems.add(new AdapterItem(VIEW_TYPE_BOTTOM_VIEW_TO_SCROLL_TO)); - mFastScrollerSections.add(new FastScrollSectionInfo( - mFastScrollerSections.get(mFastScrollerSections.size() - 1).sectionName, - position++)); - Log.d(TAG, "Adding FastScrollSection duplicate to scroll to the bottom."); - } } mAccessibilityResultsCount = (int) mAdapterItems.stream() .filter(AdapterItem::isCountedForAccessibility).count(); @@ -378,7 +346,6 @@ public class AlphabeticalAppsList implement && !mPrivateApps.isEmpty()) { // Always add PS Header if Space is present and visible. position = mPrivateProviderManager.addPrivateSpaceHeader(mAdapterItems); - Log.d(TAG, "Adding FastScrollSection for Private Space header. "); mFastScrollerSections.add(new FastScrollSectionInfo( mPrivateProfileAppScrollerBadge, position)); int privateSpaceState = mPrivateProviderManager.getCurrentState(); @@ -396,36 +363,12 @@ public class AlphabeticalAppsList implement } private int addPrivateSpaceApps(int position) { - - /* LC-Note: Fix for missing flags and account for NCDFE */ - boolean enableMovingContentIntoPrivateSpace = false; - if (ATLEAST_BAKLAVA) { - try { - /* LC-Note: Some devices (Android 16 QPR) doesn't have or expose this flag to user. - * Let's assume no, because (the flags) enableMovingContentIntoPrivateSpace seems - * to be False for R8 by default. - * */ - enableMovingContentIntoPrivateSpace = enableMovingContentIntoPrivateSpace(); - } catch (NoClassDefFoundError e) { - /* LC-Ignored: we already set it false by default. */ - } - } - // Add Install Apps Button first. - if (!ATLEAST_BAKLAVA) { - // LC: Baklava added a new behavior for the PS app button. (enableMovingContentIntoPrivateSpace) - if (Flags.privateSpaceAppInstallerButton() && !enableMovingContentIntoPrivateSpace) { - mPrivateProviderManager.addPrivateSpaceInstallAppButton(mAdapterItems); - position++; - } - } else { - if (Flags.privateSpaceAppInstallerButton()) { - mPrivateProviderManager.addPrivateSpaceInstallAppButton(mAdapterItems); - position++; - } + if (Flags.privateSpaceAppInstallerButton()) { + mPrivateProviderManager.addPrivateSpaceInstallAppButton(mAdapterItems); + position++; } - // Split of private space apps into user-installed and system apps. Map> split = mPrivateApps.stream() .collect(Collectors.partitioningBy(mPrivateProviderManager @@ -449,40 +392,10 @@ public class AlphabeticalAppsList implement // Add system apps separator. if (Flags.privateSpaceSysAppsSeparation()) { position = mPrivateProviderManager.addSystemAppsDivider(mAdapterItems); - if (Flags.letterFastScroller()) { - FastScrollSectionInfo sectionInfo = - new FastScrollSectionInfo(mPrivateProfileDividerBadge, position); - mFastScrollerSections.add(sectionInfo); - } } // Add system apps. position = addAppsWithSections(split.get(false), position); - if (enableMovingContentIntoPrivateSpace) { - // Look for the private space app via package and move it after header. - int headerIndex = -1; - int privateSpaceAppIndex = -1; - for (int i = 0; i < mAdapterItems.size(); i++) { - BaseAllAppsAdapter.AdapterItem currentItem = mAdapterItems.get(i); - if (currentItem.viewType == VIEW_TYPE_MASK_PRIVATE_SPACE_HEADER) { - headerIndex = i; - } - if (currentItem.itemInfo != null && Objects.equals( - currentItem.itemInfo.getTargetPackage(), PRIVATE_SPACE_PACKAGE)) { - currentItem.itemInfo.bitmap = mPrivateProviderManager.preparePSBitmapInfo(); - currentItem.itemInfo.bitmap.creationFlags |= FLAG_NO_BADGE; - currentItem.itemInfo.contentDescription = - mPrivateProviderManager.getPsAppContentDesc(); - privateSpaceAppIndex = i; - } - } - if (headerIndex != -1 && privateSpaceAppIndex != -1) { - BaseAllAppsAdapter.AdapterItem movedItem = - mAdapterItems.remove(privateSpaceAppIndex); - // Move the icon after the header. - mAdapterItems.add(headerIndex + 1, movedItem); - } - } return position; } @@ -494,14 +407,14 @@ public class AlphabeticalAppsList implement hasPrivateApps = appList.stream(). allMatch(mPrivateProviderManager.getItemInfoMatcher()); } - Log.d(TAG, "Adding apps with sections. HasPrivateApps: " + hasPrivateApps); for (int i = 0; i < appList.size(); i++) { AppInfo info = appList.get(i); // Apply decorator to private apps. if (hasPrivateApps) { mAdapterItems.add(AdapterItem.asAppWithDecorationInfo(info, - new SectionDecorationInfo(mActivityContext, - getRoundRegions(i, appList.size()), true /* decorateTogether */))); + new SectionDecorationInfo(mActivityContext.getApplicationContext(), + getRoundRegions(i, appList.size()), + true /* decorateTogether */))); } else { mAdapterItems.add(AdapterItem.asApp(info)); } @@ -509,14 +422,9 @@ public class AlphabeticalAppsList implement String sectionName = info.sectionName; // Create a new section if the section names do not match if (!sectionName.equals(lastSectionName)) { - Log.d(TAG, "addAppsWithSections: adding sectionName: " + sectionName - + " with appInfoTitle: " + info.title); lastSectionName = sectionName; - boolean usePrivateAppScrollerBadge = !Flags.letterFastScroller() && hasPrivateApps; - FastScrollSectionInfo sectionInfo = new FastScrollSectionInfo( - usePrivateAppScrollerBadge ? - mPrivateProfileAppScrollerBadge : sectionName, position); - mFastScrollerSections.add(sectionInfo); + mFastScrollerSections.add(new FastScrollSectionInfo(hasPrivateApps ? + mPrivateProfileAppScrollerBadge : sectionName, position)); } position++; } @@ -578,13 +486,6 @@ public class AlphabeticalAppsList implement return mPrivateProviderManager; } - public void dump(String prefix, PrintWriter writer) { - writer.println(prefix + "SectionInfo[] size: " + mFastScrollerSections.size()); - for (int i = 0; i < mFastScrollerSections.size(); i++) { - writer.println("\tFastScrollSection: " + mFastScrollerSections.get(i).sectionName); - } - } - private static class MyDiffCallback extends DiffUtil.Callback { private final List mOldList; diff --git a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java index 406d08478d..c3af72bae6 100644 --- a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java +++ b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java @@ -26,7 +26,6 @@ import static com.android.launcher3.allapps.UserProfileManager.STATE_DISABLED; import static com.android.launcher3.allapps.UserProfileManager.STATE_ENABLED; import android.content.Context; -import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; @@ -41,11 +40,15 @@ import androidx.annotation.Nullable; import androidx.recyclerview.widget.RecyclerView; import com.android.launcher3.BubbleTextView; +import com.android.launcher3.Flags; +import com.android.launcher3.LauncherPrefs; import com.android.launcher3.R; import com.android.launcher3.allapps.search.SearchAdapterProvider; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.folder.FolderIcon; import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.model.data.FolderInfo; +import com.android.launcher3.util.Themes; import com.android.launcher3.views.ActivityContext; /** @@ -62,27 +65,28 @@ public abstract class BaseAllAppsAdapter ex public static final int VIEW_TYPE_ICON = 1 << 1; // The message shown when there are no filtered results public static final int VIEW_TYPE_EMPTY_SEARCH = 1 << 2; - // A divider that separates the apps list and the search market button - public static final int VIEW_TYPE_ALL_APPS_DIVIDER = 1 << 3; - public static final int VIEW_TYPE_WORK_EDU_CARD = 1 << 4; + // The message to continue to a market search when there are no filtered results + public static final int VIEW_TYPE_SEARCH_MARKET = 1 << 3; + + // A divider that separates the apps list and the search market button + public static final int VIEW_TYPE_ALL_APPS_DIVIDER = 1 << 4; + public static final int VIEW_TYPE_WORK_DISABLED_CARD = 1 << 5; public static final int VIEW_TYPE_PRIVATE_SPACE_HEADER = 1 << 6; public static final int VIEW_TYPE_PRIVATE_SPACE_SYS_APPS_DIVIDER = 1 << 7; - public static final int VIEW_TYPE_BOTTOM_VIEW_TO_SCROLL_TO = 1 << 8; - public static final int NEXT_ID = 9; + public static final int VIEW_TYPE_WORK_EDU_CARD = 1 << 8; - // LC-Feature: Folder support in All Apps, can be any ID - public static final int VIEW_TYPE_FOLDER = 1 << 10; + public static final int VIEW_TYPE_FOLDER = 1 << 9; + + public static final int NEXT_ID = 10; // Common view type masks public static final int VIEW_TYPE_MASK_DIVIDER = VIEW_TYPE_ALL_APPS_DIVIDER; - public static final int VIEW_TYPE_MASK_ICON = VIEW_TYPE_FOLDER | VIEW_TYPE_ICON; + public static final int VIEW_TYPE_MASK_ICON = VIEW_TYPE_ICON | VIEW_TYPE_FOLDER; - public static final int VIEW_TYPE_MASK_PRIVATE_SPACE_HEADER = - VIEW_TYPE_PRIVATE_SPACE_HEADER; - public static final int VIEW_TYPE_MASK_PRIVATE_SPACE_SYS_APPS_DIVIDER = - VIEW_TYPE_PRIVATE_SPACE_SYS_APPS_DIVIDER; + public static final int VIEW_TYPE_MASK_PRIVATE_SPACE_HEADER = VIEW_TYPE_PRIVATE_SPACE_HEADER; + public static final int VIEW_TYPE_MASK_PRIVATE_SPACE_SYS_APPS_DIVIDER = VIEW_TYPE_PRIVATE_SPACE_SYS_APPS_DIVIDER; protected final SearchAdapterProvider mAdapterProvider; @@ -96,32 +100,36 @@ public abstract class BaseAllAppsAdapter ex } } - /** Sets the number of apps to be displayed in one row of the all apps screen. */ + /** + * Sets the number of apps to be displayed in one row of the all apps screen. + */ public abstract void setAppsPerRow(int appsPerRow); /** * Info about a particular adapter item (can be either section or app) */ public static class AdapterItem { + /** Common properties */ // The type of this item - public final int viewType; + public int viewType; // The row that this item shows up on public int rowIndex; // The index of this app in the row public int rowAppIndex; // The associated ItemInfoWithIcon for the item - public AppInfo itemInfo = null; + public AppInfo itemInfo = new AppInfo(); + + public FolderInfo folderInfo = new FolderInfo(); + // Private App Decorator public SectionDecorationInfo decorationInfo = null; + public AdapterItem(int viewType) { this.viewType = viewType; } - // LC-Feature: Folder support in All Apps - public FolderInfo folderInfo = new FolderInfo(); - /** * Factory method for AppIcon AdapterItem */ @@ -130,6 +138,12 @@ public abstract class BaseAllAppsAdapter ex item.itemInfo = appInfo; return item; } + + public static AdapterItem asFolder(FolderInfo folderInfo) { + AdapterItem item = new AdapterItem(VIEW_TYPE_FOLDER); + item.folderInfo = folderInfo; + return item; + } public static AdapterItem asAppWithDecorationInfo(AppInfo appInfo, SectionDecorationInfo decorationInfo) { @@ -138,12 +152,6 @@ public abstract class BaseAllAppsAdapter ex return item; } - public static AdapterItem asFolder(FolderInfo folderInfo) { - AdapterItem item = new AdapterItem(VIEW_TYPE_FOLDER); - item.folderInfo = folderInfo; - return item; - } - protected boolean isCountedForAccessibility() { return viewType == VIEW_TYPE_ICON || viewType == VIEW_TYPE_FOLDER; } @@ -156,7 +164,8 @@ public abstract class BaseAllAppsAdapter ex } /** - * This is called only if {@link #isSameAs} returns true to check if the contents are same + * This is called only if {@link #isSameAs} returns true to check if the + * contents are same * as well. Returning true will prevent redrawing of thee item. */ public boolean isContentSame(AdapterItem other) { @@ -178,8 +187,9 @@ public abstract class BaseAllAppsAdapter ex } protected final T mActivityContext; - protected final AlphabeticalAppsList mApps; - // The text to show when there are no search results and no market search handler. + public final AlphabeticalAppsList mApps; + // The text to show when there are no search results and no market search + // handler. protected int mAppsPerRow; protected final LayoutInflater mLayoutInflater; @@ -214,7 +224,9 @@ public abstract class BaseAllAppsAdapter ex return isViewType(viewType, VIEW_TYPE_MASK_PRIVATE_SPACE_HEADER); } - /** Checks if the passed viewType represents private space system apps divider. */ + /** + * Checks if the passed viewType represents private space system apps divider. + */ public static boolean isPrivateSpaceSysAppsDividerView(int viewType) { return isViewType(viewType, VIEW_TYPE_MASK_PRIVATE_SPACE_SYS_APPS_DIVIDER); } @@ -232,8 +244,8 @@ public abstract class BaseAllAppsAdapter ex public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { switch (viewType) { case VIEW_TYPE_ICON: - int layout = mActivityContext.getDeviceProfile().inv.enableTwoLinesInAllApps - ? R.layout.all_apps_icon_twoline : R.layout.all_apps_icon; + int layout = !FeatureFlags.twoLineAllApps(parent.getContext()) ? R.layout.all_apps_icon + : R.layout.all_apps_icon_twoline; BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate( layout, parent, false); icon.setLongPressTimeoutFactor(1f); @@ -241,8 +253,7 @@ public abstract class BaseAllAppsAdapter ex icon.setOnClickListener(mOnIconClickListener); icon.setOnLongClickListener(mOnIconLongClickListener); // Ensure the all apps icon height matches the workspace icons in portrait mode. - icon.getLayoutParams().height = - mActivityContext.getDeviceProfile().allAppsCellHeightPx; + icon.getLayoutParams().height = mActivityContext.getDeviceProfile().allAppsCellHeightPx; return new ViewHolder(icon); case VIEW_TYPE_EMPTY_SEARCH: return new ViewHolder(mLayoutInflater.inflate(R.layout.all_apps_empty_search, @@ -259,10 +270,7 @@ public abstract class BaseAllAppsAdapter ex case VIEW_TYPE_PRIVATE_SPACE_HEADER: return new ViewHolder(mLayoutInflater.inflate( R.layout.private_space_header, parent, false)); - case VIEW_TYPE_BOTTOM_VIEW_TO_SCROLL_TO: - return new ViewHolder(new View(mActivityContext)); case VIEW_TYPE_FOLDER: - // LC-Feature: Folder support in All Apps FrameLayout fl = new FrameLayout(mActivityContext); ViewGroup.MarginLayoutParams lp = new ViewGroup.MarginLayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, @@ -272,7 +280,7 @@ public abstract class BaseAllAppsAdapter ex if (mAdapterProvider.isViewSupported(viewType)) { return mAdapterProvider.onCreateViewHolder(mLayoutInflater, parent, viewType); } - throw new RuntimeException("Unexpected view type" + viewType); + throw new RuntimeException("Unexpected view type : " + viewType); } } @@ -291,22 +299,15 @@ public abstract class BaseAllAppsAdapter ex // Set the alpha of the private space icon to 0 upon expanding the header so the // alpha can animate -> 1. This should only be in effect when doing a // transitioning between Locked/Unlocked state. - boolean isPrivateSpaceItem = - privateProfileManager.isPrivateSpaceItem(adapterItem); + boolean isPrivateSpaceItem = privateProfileManager.isPrivateSpaceItem(adapterItem); if (icon.getAlpha() == 0 || icon.getAlpha() == 1) { icon.setAlpha(isPrivateSpaceItem && privateProfileManager.isStateTransitioning() && (privateProfileManager.isScrolling() || - privateProfileManager.getReadyToAnimate()) + privateProfileManager.getReadyToAnimate()) && privateProfileManager.getCurrentState() == STATE_ENABLED - ? 0 : 1); - Log.d(TAG, "onBindViewHolder: " - + "isPrivateSpaceItem: " + isPrivateSpaceItem - + " isStateTransitioning: " + privateProfileManager.isStateTransitioning() - + " isScrolling: " + privateProfileManager.isScrolling() - + " readyToAnimate: " + privateProfileManager.getReadyToAnimate() - + " currentState: " + privateProfileManager.getCurrentState() - + " currentAlpha: " + icon.getAlpha()); + ? 0 + : 1); } // Views can still be bounded before the app list is updated hence showing icons // after collapsing. @@ -335,17 +336,15 @@ public abstract class BaseAllAppsAdapter ex if (mApps.getPrivateProfileManager().getCurrentState() == STATE_DISABLED) { roundRegions |= (ROUND_BOTTOM_LEFT | ROUND_BOTTOM_RIGHT); } - adapterItem.decorationInfo = - new SectionDecorationInfo(mActivityContext, roundRegions, - false /* decorateTogether */); + adapterItem.decorationInfo = new SectionDecorationInfo(mActivityContext, roundRegions, + false /* decorateTogether */); break; case VIEW_TYPE_PRIVATE_SPACE_SYS_APPS_DIVIDER: adapterItem = mApps.getAdapterItems().get(position); - adapterItem.decorationInfo = mApps.getPrivateProfileManager().getCurrentState() - == STATE_DISABLED ? null : new SectionDecorationInfo(mActivityContext, - ROUND_NOTHING, true /* decorateTogether */); + adapterItem.decorationInfo = mApps.getPrivateProfileManager().getCurrentState() == STATE_DISABLED ? null + : new SectionDecorationInfo(mActivityContext, + ROUND_NOTHING, true /* decorateTogether */); break; - case VIEW_TYPE_BOTTOM_VIEW_TO_SCROLL_TO: case VIEW_TYPE_ALL_APPS_DIVIDER: case VIEW_TYPE_WORK_DISABLED_CARD: // nothing to do @@ -354,12 +353,10 @@ public abstract class BaseAllAppsAdapter ex ((WorkEduCard) holder.itemView).setPosition(position); break; case VIEW_TYPE_FOLDER: - // LC: Caddy/Folder in allapps 86b2b025a4f23a068818274020f37ad6d5268363 FolderInfo folderInfo = mApps.getAdapterItems().get(position).folderInfo; ViewGroup container = (ViewGroup) holder.itemView; container.removeAllViews(); - container.addView( - FolderIcon.inflateFolderAndIcon(R.layout.all_apps_folder_icon, mActivityContext, + container.addView(FolderIcon.inflateFolderAndIcon(R.layout.all_apps_folder_icon, mActivityContext, container, folderInfo)); break; default: diff --git a/src/com/android/launcher3/allapps/FloatingHeaderView.java b/src/com/android/launcher3/allapps/FloatingHeaderView.java index f34e472b8b..e2bf952dce 100644 --- a/src/com/android/launcher3/allapps/FloatingHeaderView.java +++ b/src/com/android/launcher3/allapps/FloatingHeaderView.java @@ -15,8 +15,6 @@ */ package com.android.launcher3.allapps; -import static com.android.launcher3.allapps.FloatingHeaderRow.NO_ROWS; - import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Point; @@ -32,10 +30,10 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.recyclerview.widget.RecyclerView; -import com.android.launcher3.Flags; import com.android.launcher3.Insettable; import com.android.launcher3.R; import com.android.launcher3.allapps.ActivityAllAppsContainerView.AdapterHolder; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.util.PluginManagerWrapper; import com.android.launcher3.views.ActivityContext; import com.android.systemui.plugins.AllAppsRow; @@ -109,16 +107,14 @@ public class FloatingHeaderView extends LinearLayout implements private boolean mFloatingRowsCollapsed; // Total height of all current floating rows. Collapsed rows == 0 height. private int mFloatingRowsHeight; - // Offset of search bar. Adds to the floating view height when multi-line is supported. - private int mSearchBarOffset = 0; // This is initialized once during inflation and stays constant after that. Fixed views // cannot be added or removed dynamically. - private FloatingHeaderRow[] mFixedRows = NO_ROWS; + private FloatingHeaderRow[] mFixedRows = FloatingHeaderRow.NO_ROWS; // Array of all fixed rows and plugin rows. This is initialized every time a plugin is // enabled or disabled, and represent the current set of all rows. - private FloatingHeaderRow[] mAllRows = NO_ROWS; + private FloatingHeaderRow[] mAllRows = FloatingHeaderRow.NO_ROWS; private final PreferenceManager2 pref2; @@ -188,10 +184,6 @@ public class FloatingHeaderView extends LinearLayout implements @Override public void onPluginConnected(AllAppsRow allAppsRowPlugin, Context context) { - if (mPluginRows.containsKey(allAppsRowPlugin)) { - // Plugin has already been connected - return; - } PluginHeaderRow headerRow = new PluginHeaderRow(allAppsRowPlugin, this); addView(headerRow.mView, indexOfChild(mTabLayout)); mPluginRows.put(allAppsRowPlugin, headerRow); @@ -212,20 +204,9 @@ public class FloatingHeaderView extends LinearLayout implements } } - /** - * Offset floating rows height by search bar - */ - void updateSearchBarOffset(int offset) { - mSearchBarOffset = offset; - onHeightUpdated(); - } - @Override public void onPluginDisconnected(AllAppsRow plugin) { PluginHeaderRow row = mPluginRows.get(plugin); - if (row == null) { - return; - } removeView(row.mView); mPluginRows.remove(plugin); recreateAllRowsArray(); @@ -234,12 +215,15 @@ public class FloatingHeaderView extends LinearLayout implements @Override public View getFocusedChild() { - for (FloatingHeaderRow row : mAllRows) { - if (row.hasVisibleContent() && row.isVisible()) { - return row.getFocusedChild(); + if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) { + for (FloatingHeaderRow row : mAllRows) { + if (row.hasVisibleContent() && row.isVisible()) { + return row.getFocusedChild(); + } } + return null; } - return null; + return super.getFocusedChild(); } void setup(AllAppsRecyclerView mainRV, AllAppsRecyclerView workRV, SearchRecyclerView searchRV, @@ -280,18 +264,9 @@ public class FloatingHeaderView extends LinearLayout implements mTabLayout.setVisibility(mTabsHidden ? GONE : visibility); } - /** Returns whether search bar has multi-line support, and is currently in multi-line state. */ - private boolean isSearchBarMultiline() { - return Flags.multilineSearchBar() && mSearchBarOffset > 0; - } - private void updateExpectedHeight() { updateFloatingRowsHeight(); mMaxTranslation = 0; - boolean shouldAddSearchBarHeight = isSearchBarMultiline() && !Flags.floatingSearchBar(); - if (shouldAddSearchBarHeight) { - mMaxTranslation += mSearchBarOffset; - } if (mFloatingRowsCollapsed) { return; } diff --git a/src/com/android/launcher3/allapps/FloatingMaskView.java b/src/com/android/launcher3/allapps/FloatingMaskView.java index cee5e18958..606eb0328e 100644 --- a/src/com/android/launcher3/allapps/FloatingMaskView.java +++ b/src/com/android/launcher3/allapps/FloatingMaskView.java @@ -21,7 +21,6 @@ import android.util.AttributeSet; import android.view.ViewGroup; import android.widget.ImageView; -import androidx.annotation.VisibleForTesting; import androidx.constraintlayout.widget.ConstraintLayout; import com.android.launcher3.R; @@ -54,21 +53,13 @@ public class FloatingMaskView extends ConstraintLayout { @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); - setParameters((ViewGroup.MarginLayoutParams) getLayoutParams(), - mActivityContext.getAppsView().getActiveRecyclerView()); - } - - @VisibleForTesting - void setParameters(ViewGroup.MarginLayoutParams lp, AllAppsRecyclerView recyclerView) { + ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) getLayoutParams(); + AllAppsRecyclerView allAppsContainerView = + mActivityContext.getAppsView().getActiveRecyclerView(); if (lp != null) { - lp.rightMargin = recyclerView.getPaddingRight(); - lp.leftMargin = recyclerView.getPaddingLeft(); - getBottomBox().setMinimumHeight(recyclerView.getPaddingBottom()); + lp.rightMargin = allAppsContainerView.getPaddingRight(); + lp.leftMargin = allAppsContainerView.getPaddingLeft(); + mBottomBox.setMinimumHeight(allAppsContainerView.getPaddingBottom()); } } - - @VisibleForTesting - ImageView getBottomBox() { - return mBottomBox; - } } diff --git a/src/com/android/launcher3/allapps/PrivateProfileManager.java b/src/com/android/launcher3/allapps/PrivateProfileManager.java index 8d5247de55..7bd5abd625 100644 --- a/src/com/android/launcher3/allapps/PrivateProfileManager.java +++ b/src/com/android/launcher3/allapps/PrivateProfileManager.java @@ -41,11 +41,11 @@ import static com.android.launcher3.util.SettingsCache.PRIVATE_SPACE_HIDE_WHEN_L import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; +import android.animation.LayoutTransition; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.content.Context; import android.content.Intent; -import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; import android.util.Log; @@ -67,7 +67,6 @@ import com.android.launcher3.BuildConfig; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Flags; import com.android.launcher3.R; -import com.android.launcher3.Utilities; import com.android.launcher3.anim.AnimatedPropertySetter; import com.android.launcher3.anim.PropertySetter; import com.android.launcher3.icons.BitmapInfo; @@ -91,16 +90,14 @@ import java.util.function.Predicate; * logic in the Personal tab. */ public class PrivateProfileManager extends UserProfileManager { - - private static final String TAG = "PrivateProfileManager"; - private static final int EXPAND_COLLAPSE_DURATION = 400; + private static final int EXPAND_COLLAPSE_DURATION = 800; private static final int SETTINGS_OPACITY_DURATION = 400; private static final int TEXT_UNLOCK_OPACITY_DURATION = 300; private static final int TEXT_LOCK_OPACITY_DURATION = 50; private static final int APP_OPACITY_DURATION = 400; private static final int MASK_VIEW_DURATION = 200; private static final int APP_OPACITY_DELAY = 400; - private static final int PILL_TRANSITION_DELAY = 400; + private static final int SETTINGS_AND_LOCK_GROUP_TRANSITION_DELAY = 400; private static final int SETTINGS_OPACITY_DELAY = 400; private static final int LOCK_TEXT_OPACITY_DELAY = 500; private static final int MASK_VIEW_DELAY = 400; @@ -110,8 +107,6 @@ public class PrivateProfileManager extends UserProfileManager { private final Predicate mPrivateProfileMatcher; private final int mPsHeaderHeight; private final int mFloatingMaskViewCornerRadius; - private final int mLockTextMarginStart; - private final int mLockTextMarginEnd; private final RecyclerView.OnScrollListener mOnIdleScrollListener = new RecyclerView.OnScrollListener() { @Override @@ -136,13 +131,7 @@ public class PrivateProfileManager extends UserProfileManager { private Runnable mOnPSHeaderAdded; @Nullable private RelativeLayout mPSHeader; - @Nullable - private TextView mLockText; - @Nullable - private PrivateSpaceSettingsButton mPrivateSpaceSettingsButton; - @Nullable private ConstraintLayout mFloatingMaskView; - private final String mPrivateSpaceAppContentDesc; private final String mLockedStateContentDesc; private final String mUnLockedStateContentDesc; @@ -158,18 +147,12 @@ public class PrivateProfileManager extends UserProfileManager { UI_HELPER_EXECUTOR.post(() -> initializeInBackgroundThread(appContext)); mPsHeaderHeight = mAllApps.getContext().getResources().getDimensionPixelSize( R.dimen.ps_header_height); - mPrivateSpaceAppContentDesc = mAllApps.getContext() - .getString(R.string.ps_app_content_description); mLockedStateContentDesc = mAllApps.getContext() .getString(R.string.ps_container_lock_button_content_description); mUnLockedStateContentDesc = mAllApps.getContext() .getString(R.string.ps_container_unlock_button_content_description); mFloatingMaskViewCornerRadius = mAllApps.getContext().getResources().getDimensionPixelSize( R.dimen.ps_floating_mask_corner_radius); - mLockTextMarginStart = mAllApps.getContext().getResources().getDimensionPixelSize( - R.dimen.ps_lock_icon_text_margin_start_expanded); - mLockTextMarginEnd = mAllApps.getContext().getResources().getDimensionPixelSize( - R.dimen.ps_lock_icon_text_margin_end_expanded); } /** Adds Private Space Header to the layout. */ @@ -190,11 +173,15 @@ public class PrivateProfileManager extends UserProfileManager { /** Adds Private Space install app button to the layout. */ public void addPrivateSpaceInstallAppButton(List adapterItems) { Context context = mAllApps.getContext(); + // Prepare bitmapInfo + Intent.ShortcutIconResource shortcut = Intent.ShortcutIconResource.fromContext( + context, com.android.launcher3.R.drawable.private_space_install_app_icon); + BitmapInfo bitmapInfo = LauncherIcons.obtain(context).createIconBitmap(shortcut); PrivateSpaceInstallAppButtonInfo itemInfo = new PrivateSpaceInstallAppButtonInfo(); itemInfo.title = context.getResources().getString(R.string.ps_add_button_label); itemInfo.intent = mAppInstallerIntent; - itemInfo.bitmap = preparePSBitmapInfo(); + itemInfo.bitmap = bitmapInfo; itemInfo.contentDescription = context.getResources().getString( com.android.launcher3.R.string.ps_add_button_content_description); itemInfo.runtimeStatusFlags |= FLAG_NOT_PINNABLE; @@ -223,21 +210,13 @@ public class PrivateProfileManager extends UserProfileManager { } } - BitmapInfo preparePSBitmapInfo() { - Context context = mAllApps.getContext(); - Intent.ShortcutIconResource shortcut = Intent.ShortcutIconResource.fromContext( - context, com.android.launcher3.R.drawable.private_space_install_app_icon); - return LauncherIcons.obtain(context).createIconBitmap(shortcut); - } - /** * Resets the current state of Private Profile, w.r.t. to Launcher. The decorator should only * be applied upon expand before animating. When collapsing, reset() will remove the decorator * when animation is not running. */ public void reset() { - Trace.beginSection("PrivateProfileManager#reset"); - // Ensure the state of the header view is what it should be before animating. + // Ensure the state of the header views is what it should be before animating. updateView(); getMainRecyclerView().setChildAttachedConsumer(null); int previousState = getCurrentState(); @@ -256,7 +235,6 @@ public class PrivateProfileManager extends UserProfileManager { executeLock(); } addPrivateSpaceDecorator(updatedState); - Trace.endSection(); } /** Returns whether or not Private Space Settings Page is available. */ @@ -311,11 +289,30 @@ public class PrivateProfileManager extends UserProfileManager { } } + @Override public void setQuietMode(boolean enable) { - setQuietMode(enable, mAllApps.mActivityContext); + UI_HELPER_EXECUTOR.post(() -> + mUserCache.getUserProfiles() + .stream() + .filter(getUserMatcher()) + .findFirst() + .ifPresent(userHandle -> setQuietModeSafely(enable, userHandle))); mReadyToAnimate = true; } + /** + * Sets Quiet Mode for Private Profile. + * If {@link SecurityException} is thrown, prompts the user to set this launcher as HOME app. + */ + private void setQuietModeSafely(boolean enable, UserHandle userHandle) { + try { + mUserManager.requestQuietModeEnabled(enable, userHandle); + } catch (SecurityException ex) { + ApiWrapper.INSTANCE.get(mAllApps.mActivityContext) + .assignDefaultHomeRole(mAllApps.mActivityContext); + } + } + /** * Expand the private space after the app list has been added and updated from * {@link AlphabeticalAppsList#onAppsUpdated()} @@ -330,9 +327,7 @@ public class PrivateProfileManager extends UserProfileManager { /** Collapses the private space before the app list has been updated. */ void executeLock() { - Trace.beginSection("PrivateProfileManager#executeLock"); MAIN_EXECUTOR.execute(() -> updatePrivateStateAnimator(false)); - Trace.endSection(); } void setAnimationRunning(boolean isAnimationRunning) { @@ -366,12 +361,19 @@ public class PrivateProfileManager extends UserProfileManager { /** Add Private Space Header view elements based upon {@link UserProfileState} */ public void bindPrivateSpaceHeaderViewElements(RelativeLayout parent) { mPSHeader = parent; - Log.d(TAG, "bindPrivateSpaceHeaderViewElements: " + "Binding private space."); - updateView(); if (mOnPSHeaderAdded != null) { MAIN_EXECUTOR.execute(mOnPSHeaderAdded); mOnPSHeaderAdded = null; } + // Set the transition duration for the settings and lock button to animate. + ViewGroup settingAndLockGroup = mPSHeader.findViewById(R.id.settingsAndLockGroup); + if (mReadyToAnimate) { + enableLayoutTransition(settingAndLockGroup); + } else { + // Ensure any unwanted animations to not happen. + settingAndLockGroup.setLayoutTransition(null); + } + updateView(); } /** Update the states of the views that make up the header at the state it is called in. */ @@ -379,16 +381,12 @@ public class PrivateProfileManager extends UserProfileManager { if (mPSHeader == null) { return; } - Trace.beginSection("PrivateProfileManager#updateView"); - Log.d(TAG, "bindPrivateSpaceHeaderViewElements: " + "Updating view with state: " - + getCurrentState()); mPSHeader.setAlpha(1); ViewGroup lockPill = mPSHeader.findViewById(R.id.ps_lock_unlock_button); assert lockPill != null; - mLockText = lockPill.findViewById(R.id.lock_text); - assert mLockText != null; - mPrivateSpaceSettingsButton = mPSHeader.findViewById(R.id.ps_settings_button); - assert mPrivateSpaceSettingsButton != null; + TextView lockText = lockPill.findViewById(R.id.lock_text); + PrivateSpaceSettingsButton settingsButton = mPSHeader.findViewById(R.id.ps_settings_button); + assert settingsButton != null; //Add image for private space transitioning view ImageView transitionView = mPSHeader.findViewById(R.id.ps_transition_image); assert transitionView != null; @@ -399,19 +397,12 @@ public class PrivateProfileManager extends UserProfileManager { // Remove header from accessibility target when enabled. mPSHeader.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); - if (!mReadyToAnimate) { - // Don't set visibilities when animating as the animation will handle it. - mLockText.setVisibility(VISIBLE); - mLockText.setAlpha(1); - mLockText.setHorizontallyScrolling(false); - mPrivateSpaceSettingsButton.setVisibility( - isPrivateSpaceSettingsAvailable() ? VISIBLE : GONE); - mPrivateSpaceSettingsButton.setClickable(isPrivateSpaceSettingsAvailable()); - } + lockText.setVisibility(VISIBLE); lockPill.setVisibility(VISIBLE); lockPill.setOnClickListener(view -> lockingAction(/* lock */ true)); lockPill.setContentDescription(mUnLockedStateContentDesc); + settingsButton.setVisibility(isPrivateSpaceSettingsAvailable() ? VISIBLE : GONE); transitionView.setVisibility(GONE); } case STATE_DISABLED -> { @@ -421,15 +412,12 @@ public class PrivateProfileManager extends UserProfileManager { mPSHeader.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); mPSHeader.setContentDescription(mLockedStateContentDesc); - mLockText.setVisibility(GONE); - mLockText.setAlpha(0); - mLockText.setHorizontallyScrolling(false); + lockText.setVisibility(GONE); lockPill.setVisibility(VISIBLE); lockPill.setOnClickListener(view -> lockingAction(/* lock */ false)); lockPill.setContentDescription(mLockedStateContentDesc); - mPrivateSpaceSettingsButton.setVisibility(GONE); - mPrivateSpaceSettingsButton.setClickable(false); + settingsButton.setVisibility(GONE); transitionView.setVisibility(GONE); } case STATE_TRANSITION -> { @@ -437,8 +425,6 @@ public class PrivateProfileManager extends UserProfileManager { lockPill.setVisibility(GONE); } } - mPSHeader.invalidate(); - Trace.endSection(); } /** Sets the enablement of the profile when header or button is clicked. */ @@ -478,8 +464,7 @@ public class PrivateProfileManager extends UserProfileManager { break; } // Make the private space apps gone to "collapse". - if ((mFloatingMaskView == null && isPrivateSpaceItem(currentItem)) || - currentItem.viewType == VIEW_TYPE_PRIVATE_SPACE_SYS_APPS_DIVIDER) { + if (mFloatingMaskView == null && isPrivateSpaceItem(currentItem)) { RecyclerView.ViewHolder viewHolder = allAppsRecyclerView.findViewHolderForAdapterPosition(i); if (viewHolder != null) { @@ -606,51 +591,6 @@ public class PrivateProfileManager extends UserProfileManager { return alphaAnim; } - private ValueAnimator animatePillTransition(boolean isExpanding) { - if (mLockText == null) { - return new ValueAnimator().setDuration(0); - } - mLockText.measure(0,0); - int currentWidth = mLockText.getWidth(); - int fullWidth = mLockText.getMeasuredWidth(); - float from = isExpanding ? 0 : currentWidth; - float to = isExpanding ? fullWidth : 0; - ValueAnimator pillAnim = ObjectAnimator.ofFloat(from, to); - pillAnim.setStartDelay(isExpanding ? PILL_TRANSITION_DELAY : 0); - pillAnim.setDuration(EXPAND_COLLAPSE_DURATION); - pillAnim.setInterpolator(Interpolators.STANDARD); - pillAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator valueAnimator) { - float translation = (float) valueAnimator.getAnimatedValue(); - float translationFraction = translation / fullWidth; - ViewGroup.MarginLayoutParams layoutParams = - (ViewGroup.MarginLayoutParams) mLockText.getLayoutParams(); - layoutParams.width = (int) translation; - layoutParams.setMarginStart((int) (mLockTextMarginStart * translationFraction)); - layoutParams.setMarginEnd((int) (mLockTextMarginEnd * translationFraction)); - mLockText.setLayoutParams(layoutParams); - mLockText.requestLayout(); - } - }); - pillAnim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animator) { - if (!isExpanding) { - mLockText.setVisibility(GONE); - } - mLockText.setHorizontallyScrolling(false); - } - - @Override - public void onAnimationStart(Animator animator) { - mLockText.setHorizontallyScrolling(true); - mLockText.setVisibility(VISIBLE); - } - }); - return pillAnim; - } - /** * Using PropertySetter{@link PropertySetter}, we can update the view's attributes within an * animation. At the moment, collapsing, setting alpha changes, and animating the text is done @@ -662,23 +602,33 @@ public class PrivateProfileManager extends UserProfileManager { } if (mPSHeader == null) { mOnPSHeaderAdded = () -> updatePrivateStateAnimator(expand); - // Set animation to true, because onBind will be called after this return where we want - // the views to be updated accordingly so animation can happen. - setAnimationRunning(true); + setAnimationRunning(false); return; } attachFloatingMaskView(expand); - AnimatorSet animatorSet = new AnimatedPropertySetter().buildAnim(); + ViewGroup settingsAndLockGroup = mPSHeader.findViewById(R.id.settingsAndLockGroup); + if (settingsAndLockGroup.getLayoutTransition() == null) { + // Set a new transition if the current ViewGroup does not already contain one as each + // transition should only happen once when applied. + enableLayoutTransition(settingsAndLockGroup); + } + settingsAndLockGroup.getLayoutTransition().setStartDelay( + LayoutTransition.CHANGING, + expand ? SETTINGS_AND_LOCK_GROUP_TRANSITION_DELAY : NO_DELAY); + PropertySetter headerSetter = new AnimatedPropertySetter(); + headerSetter.add(updateSettingsGearAlpha(expand)); + headerSetter.add(updateLockTextAlpha(expand)); + AnimatorSet animatorSet = headerSetter.buildAnim(); animatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { - Log.d(TAG, "updatePrivateStateAnimator: Private space animation expanding: " - + expand); mStatsLogManager.logger().sendToInteractionJankMonitor( expand ? LAUNCHER_PRIVATE_SPACE_UNLOCK_ANIMATION_BEGIN : LAUNCHER_PRIVATE_SPACE_LOCK_ANIMATION_BEGIN, mAllApps.getActiveRecyclerView()); + // Animate the collapsing of the text at the same time while updating lock button. + mPSHeader.findViewById(R.id.lock_text).setVisibility(expand ? VISIBLE : GONE); setAnimationRunning(true); } @@ -696,18 +646,11 @@ public class PrivateProfileManager extends UserProfileManager { ? LAUNCHER_PRIVATE_SPACE_UNLOCK_ANIMATION_END : LAUNCHER_PRIVATE_SPACE_LOCK_ANIMATION_END, mAllApps.getActiveRecyclerView()); - Log.d(TAG, "updatePrivateStateAnimator: lockText visibility: " - + mLockText.getVisibility() + " lockTextAlpha: " + mLockText.getAlpha()); - Log.d(TAG, "updatePrivateStateAnimator: settingsCog visibility: " - + mPrivateSpaceSettingsButton.getVisibility() - + " settingsCogAlpha: " + mPrivateSpaceSettingsButton.getAlpha()); if (!expand) { mAllApps.mAH.get(MAIN).mRecyclerView.removeItemDecoration( mPrivateAppsSectionDecorator); // Call onAppsUpdated() because it may be canceled when this animation occurs. - if (!Utilities.isRunningInTestHarness()) { - mAllApps.getPersonalAppList().onAppsUpdated(); - } + mAllApps.getPersonalAppList().onAppsUpdated(); if (isPrivateSpaceHidden()) { // TODO (b/325455879): Figure out if we can avoid this. getMainRecyclerView().getAdapter().notifyDataSetChanged(); @@ -715,24 +658,16 @@ public class PrivateProfileManager extends UserProfileManager { } })); if (expand) { - animatorSet.playTogether(updateSettingsGearAlpha(true), - updateLockTextAlpha(true), - animateAlphaOfIcons(true), - animatePillTransition(true), + animatorSet.playTogether(animateAlphaOfIcons(true), translateFloatingMaskView(false)); } else { - AnimatorSet parallelSet = new AnimatorSet(); - parallelSet.playTogether(updateSettingsGearAlpha(false), - updateLockTextAlpha(false), - animateAlphaOfIcons(false), - animatePillTransition(false)); if (isPrivateSpaceHidden()) { - animatorSet.playSequentially(parallelSet, + animatorSet.playSequentially(animateAlphaOfIcons(false), animateAlphaOfPrivateSpaceContainer(), animateCollapseAnimation()); } else { animatorSet.playSequentially(translateFloatingMaskView(true), - parallelSet, + animateAlphaOfIcons(false), animateCollapseAnimation()); } } @@ -763,7 +698,7 @@ public class PrivateProfileManager extends UserProfileManager { /** Fades out the private space container. */ private ValueAnimator translateFloatingMaskView(boolean animateIn) { if (!Flags.privateSpaceAddFloatingMaskView() || mFloatingMaskView == null) { - return new ValueAnimator().setDuration(0); + return new ValueAnimator(); } // Translate base on the height amount. Translates out on expand and in on collapse. float floatingMaskViewHeight = getFloatingMaskViewHeight(); @@ -775,19 +710,38 @@ public class PrivateProfileManager extends UserProfileManager { alphaAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { - if (mFloatingMaskView == null) { - return; - } mFloatingMaskView.setTranslationY((float) valueAnimator.getAnimatedValue()); } }); return alphaAnim; } + /** Animates the layout changes when the text of the button becomes visible/gone. */ + private void enableLayoutTransition(ViewGroup settingsAndLockGroup) { + LayoutTransition settingsAndLockTransition = new LayoutTransition(); + settingsAndLockTransition.enableTransitionType(LayoutTransition.CHANGING); + settingsAndLockTransition.setDuration(EXPAND_COLLAPSE_DURATION); + settingsAndLockTransition.setInterpolator(LayoutTransition.CHANGING, + Interpolators.STANDARD); + settingsAndLockTransition.addTransitionListener(new LayoutTransition.TransitionListener() { + @Override + public void startTransition(LayoutTransition transition, ViewGroup viewGroup, + View view, int i) { + } + @Override + public void endTransition(LayoutTransition transition, ViewGroup viewGroup, + View view, int i) { + settingsAndLockGroup.setLayoutTransition(null); + mReadyToAnimate = false; + } + }); + settingsAndLockGroup.setLayoutTransition(settingsAndLockTransition); + } + /** Change the settings gear alpha when expanded or collapsed. */ private ValueAnimator updateSettingsGearAlpha(boolean expand) { - if (mPrivateSpaceSettingsButton == null || !isPrivateSpaceSettingsAvailable()) { - return new ValueAnimator().setDuration(0); + if (mPSHeader == null) { + return new ValueAnimator(); } float from = expand ? 0 : 1; float to = expand ? 1 : 0; @@ -798,29 +752,16 @@ public class PrivateProfileManager extends UserProfileManager { settingsAlphaAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { - mPrivateSpaceSettingsButton.setAlpha((float) valueAnimator.getAnimatedValue()); - } - }); - settingsAlphaAnim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animator) { - mPrivateSpaceSettingsButton.setVisibility(VISIBLE); - mPrivateSpaceSettingsButton.setClickable(false); - } - - @Override - public void onAnimationEnd(Animator animator) { - if (expand) { - mPrivateSpaceSettingsButton.setClickable(true); - } + mPSHeader.findViewById(R.id.ps_settings_button) + .setAlpha((float) valueAnimator.getAnimatedValue()); } }); return settingsAlphaAnim; } private ValueAnimator updateLockTextAlpha(boolean expand) { - if (mLockText == null) { - return new ValueAnimator().setDuration(0); + if (mPSHeader == null) { + return new ValueAnimator(); } float from = expand ? 0 : 1; float to = expand ? 1 : 0; @@ -831,7 +772,8 @@ public class PrivateProfileManager extends UserProfileManager { alphaAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { - mLockText.setAlpha((float) valueAnimator.getAnimatedValue()); + mPSHeader.findViewById(R.id.lock_text).setAlpha( + (float) valueAnimator.getAnimatedValue()); } }); return alphaAnim; @@ -843,7 +785,6 @@ public class PrivateProfileManager extends UserProfileManager { ActivityAllAppsContainerView.AdapterHolder mainAdapterHolder = mAllApps.mAH.get(MAIN); List adapterItems = mainAdapterHolder.mAppsList.getAdapterItems(); - Trace.beginSection("PrivateProfileManager#expandPrivateSpace"); if (Flags.enablePrivateSpace() && Flags.privateSpaceAnimation() && mAllApps.isPersonalTab()) { // Animate the text and settings icon. @@ -853,7 +794,6 @@ public class PrivateProfileManager extends UserProfileManager { getPsHeaderHeight(), deviceProfile.allAppsCellHeightPx); updatePrivateStateAnimator(true); } - Trace.endSection(); } private void exitSearchAndExpand() { @@ -871,22 +811,8 @@ public class PrivateProfileManager extends UserProfileManager { if (!Flags.privateSpaceAddFloatingMaskView()) { return; } - // Use getLocationOnScreen() as simply checking for mPSHeader.getBottom() is only relative - // to its parent. - int[] psHeaderLocation = new int[2]; - mPSHeader.getLocationOnScreen(psHeaderLocation); - int psHeaderBottomY = psHeaderLocation[1] + mPsHeaderHeight; - // Calculate the topY of the floatingMaskView as if it was added. - int floatingMaskViewBottomBoxTopY = - (int) (mAllApps.getBottom() - getMainRecyclerView().getPaddingBottom()); - // Don't attach if the header will be clipped by the floating mask view. - if (psHeaderBottomY > floatingMaskViewBottomBoxTopY) { - mFloatingMaskView = null; - return; - } mFloatingMaskView = (FloatingMaskView) mAllApps.getLayoutInflater().inflate( R.layout.private_space_mask_view, mAllApps, false); - assert mFloatingMaskView != null; mAllApps.addView(mFloatingMaskView); // Translate off the screen first if its collapsing so this header view isn't visible to // user when animation starts. @@ -944,10 +870,6 @@ public class PrivateProfileManager extends UserProfileManager { return mPsHeaderHeight; } - String getPsAppContentDesc() { - return mPrivateSpaceAppContentDesc; - } - boolean isPrivateSpaceItem(BaseAllAppsAdapter.AdapterItem item) { return getItemInfoMatcher().test(item.itemInfo) || item.decorationInfo != null || (item.itemInfo instanceof PrivateSpaceInstallAppButtonInfo); diff --git a/src/com/android/launcher3/allapps/SectionDecorationHandler.java b/src/com/android/launcher3/allapps/SectionDecorationHandler.java index cae76ec0a6..ac9b146f12 100644 --- a/src/com/android/launcher3/allapps/SectionDecorationHandler.java +++ b/src/com/android/launcher3/allapps/SectionDecorationHandler.java @@ -25,8 +25,10 @@ import android.graphics.drawable.InsetDrawable; import android.view.View; import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; import com.android.launcher3.R; +import com.android.launcher3.util.Themes; public class SectionDecorationHandler { @@ -59,8 +61,10 @@ public class SectionDecorationHandler { mContext = context; mFillAlpha = fillAlpha; - mFocusColor = context.getColor(R.color.materialColorSurfaceBright); // UX recommended - mFillColor = context.getColor(R.color.materialColorSurfaceContainerHigh); // UX recommended + mFocusColor = ContextCompat.getColor(context, + R.color.material_color_surface_bright); // UX recommended + mFillColor = ContextCompat.getColor(context, + R.color.material_color_surface_container_high); // UX recommended mIsTopLeftRound = isTopLeftRound; mIsTopRightRound = isTopRightRound; diff --git a/src/com/android/launcher3/allapps/UserProfileManager.java b/src/com/android/launcher3/allapps/UserProfileManager.java index 9c4daee57f..93b6b29b34 100644 --- a/src/com/android/launcher3/allapps/UserProfileManager.java +++ b/src/com/android/launcher3/allapps/UserProfileManager.java @@ -16,12 +16,8 @@ package com.android.launcher3.allapps; -import static com.android.launcher3.Utilities.ATLEAST_P; import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; -import android.content.Context; -import android.os.Build.VERSION; -import android.os.Build.VERSION_CODES; import android.os.UserHandle; import android.os.UserManager; @@ -30,7 +26,6 @@ import androidx.annotation.IntDef; import com.android.launcher3.logging.StatsLogManager; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.pm.UserCache; -import com.android.launcher3.util.ApiWrapper; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -74,28 +69,14 @@ public abstract class UserProfileManager { } /** Sets quiet mode as enabled/disabled for the profile type. */ - protected void setQuietMode(boolean enabled, Context context) { + protected void setQuietMode(boolean enabled) { UI_HELPER_EXECUTOR.post(() -> mUserCache.getUserProfiles() .stream() .filter(getUserMatcher()) .findFirst() .ifPresent(userHandle -> - setQuietModeSafely(enabled, userHandle, context))); - } - - /** - * Sets Quiet Mode for Private Profile. - * If {@link SecurityException} is thrown, prompts the user to set this launcher as HOME app. - */ - private void setQuietModeSafely(boolean enable, UserHandle userHandle, Context context) { - try { - if (ATLEAST_P) { - mUserManager.requestQuietModeEnabled(enable, userHandle); - } - } catch (SecurityException ex) { - ApiWrapper.INSTANCE.get(context).assignDefaultHomeRole(context); - } + mUserManager.requestQuietModeEnabled(enabled, userHandle))); } /** Sets current state for the profile type. */ diff --git a/src/com/android/launcher3/allapps/WorkPausedCard.java b/src/com/android/launcher3/allapps/WorkPausedCard.java index 15040f4dbd..bf86273b66 100644 --- a/src/com/android/launcher3/allapps/WorkPausedCard.java +++ b/src/com/android/launcher3/allapps/WorkPausedCard.java @@ -20,7 +20,6 @@ import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCH import android.content.Context; import android.content.res.Configuration; import android.util.AttributeSet; -import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.LinearLayout; @@ -39,9 +38,7 @@ import app.lawnchair.theme.color.tokens.ColorTokens; */ public class WorkPausedCard extends LinearLayout implements View.OnClickListener { - private static final String TAG = "WorkPausedCard"; private final ActivityContext mActivityContext; - private Button mBtn; public WorkPausedCard(Context context) { this(context, null, 0); @@ -59,17 +56,7 @@ public class WorkPausedCard extends LinearLayout implements View.OnClickListener @Override protected void onFinishInflate() { super.onFinishInflate(); - mBtn = findViewById(R.id.enable_work_apps); - mBtn.setOnClickListener(this); - - updateStringFromCache(); - } - - public void updateStringFromCache() { - StringCache cache = mActivityContext.getStringCache(); - if (cache != null) { - setWorkProfilePausedResources(); - } + setWorkProfilePausedResources(); } public void setWorkProfilePausedResources() { @@ -91,7 +78,7 @@ public class WorkPausedCard extends LinearLayout implements View.OnClickListener @Override public void onClick(View view) { - Log.d(TAG, "WorkPausedCard clicked."); + setEnabled(false); mActivityContext.getAppsView().getWorkManager().setWorkProfileEnabled(true); mActivityContext.getStatsLogManager().logger().log(LAUNCHER_TURN_ON_WORK_APPS_TAP); } diff --git a/src/com/android/launcher3/allapps/WorkProfileManager.java b/src/com/android/launcher3/allapps/WorkProfileManager.java index 920efa4f64..df7ed72be5 100644 --- a/src/com/android/launcher3/allapps/WorkProfileManager.java +++ b/src/com/android/launcher3/allapps/WorkProfileManager.java @@ -26,6 +26,7 @@ import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_HAS_SHORTCU import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_CHANGE_PERMISSION; import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED; import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_WORK_PROFILE_QUIET_MODE_ENABLED; +import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import android.os.UserHandle; import android.os.UserManager; @@ -50,7 +51,8 @@ import java.util.function.Predicate; import java.util.stream.Stream; /** - * Companion class for {@link ActivityAllAppsContainerView} to manage work tab and personal tab + * Companion class for {@link ActivityAllAppsContainerView} to manage work tab + * and personal tab * related * logic based on {@link UserProfileState}? */ @@ -58,7 +60,7 @@ public class WorkProfileManager extends UserProfileManager implements PersonalWorkSlidingTabStrip.OnActivePageChangedListener { private static final String TAG = "WorkProfileManager"; private final ActivityAllAppsContainerView mAllApps; - private WorkUtilityView mWorkUtilityView; + private WorkModeSwitch mWorkModeSwitch; private final Predicate mWorkProfileMatcher; public WorkProfileManager( @@ -74,26 +76,39 @@ public class WorkProfileManager extends UserProfileManager */ public void setWorkProfileEnabled(boolean enabled) { updateCurrentState(STATE_TRANSITION); - setQuietMode(!enabled, mAllApps.mActivityContext); + UI_HELPER_EXECUTOR.post(() -> { + for (UserHandle userProfile : mUserManager.getUserProfiles()) { + if (android.os.Process.myUserHandle().equals(userProfile)) { + continue; + } + // https://github.com/LawnchairLauncher/lawnchair/issues/3145 + try { + mUserManager.requestQuietModeEnabled(!enabled, userProfile); + } catch (RuntimeException e) { + Log.e(TAG, "Failed to set quiet mode for user " + userProfile, e); + } + } + }); } @Override public void onActivePageChanged(int page) { - updateWorkUtilityViews(page); + updateWorkFAB(page); } - private void updateWorkUtilityViews(int page) { - if (mWorkUtilityView != null) { + private void updateWorkFAB(int page) { + if (mWorkModeSwitch != null) { if (page == MAIN || page == SEARCH) { - mWorkUtilityView.animateVisibility(false); + mWorkModeSwitch.animateVisibility(false); } else if (page == WORK && getCurrentState() == STATE_ENABLED) { - mWorkUtilityView.animateVisibility(true); + mWorkModeSwitch.animateVisibility(true); } } } /** - * Requests work profile state from {@link AllAppsStore} and updates work profile related views + * Requests work profile state from {@link AllAppsStore} and updates work + * profile related views */ public void reset() { int quietModeFlag; @@ -104,10 +119,10 @@ public class WorkProfileManager extends UserProfileManager } boolean isEnabled = !mAllApps.getAppsStore().hasModelFlag(quietModeFlag); updateCurrentState(isEnabled ? STATE_ENABLED : STATE_DISABLED); - if (mWorkUtilityView != null) { + if (mWorkModeSwitch != null) { // reset the position of the button and clear IME insets. - mWorkUtilityView.getImeInsets().setEmpty(); - mWorkUtilityView.updateTranslationY(); + mWorkModeSwitch.getImeInsets().setEmpty(); + mWorkModeSwitch.updateTranslationY(); } } @@ -116,54 +131,56 @@ public class WorkProfileManager extends UserProfileManager if (getAH() != null) { getAH().mAppsList.updateAdapterItems(); } - if (mWorkUtilityView != null) { - updateWorkUtilityViews(mAllApps.getCurrentPage()); + if (mWorkModeSwitch != null) { + updateWorkFAB(mAllApps.getCurrentPage()); } if (getCurrentState() == STATE_ENABLED) { - attachWorkUtilityViews(); + attachWorkModeSwitch(); } else if (getCurrentState() == STATE_DISABLED) { - detachWorkUtilityViews(); + detachWorkModeSwitch(); } } /** - * Creates and attaches for profile toggle button to {@link ActivityAllAppsContainerView} + * Creates and attaches for profile toggle button to + * {@link ActivityAllAppsContainerView} */ - public boolean attachWorkUtilityViews() { + public boolean attachWorkModeSwitch() { if (!mAllApps.getAppsStore().hasModelFlag( FLAG_HAS_SHORTCUT_PERMISSION | FLAG_QUIET_MODE_CHANGE_PERMISSION)) { Log.e(TAG, "unable to attach work mode switch; Missing required permissions"); return false; } - if (mWorkUtilityView == null) { - mWorkUtilityView = (WorkUtilityView) mAllApps.getLayoutInflater().inflate( - R.layout.work_mode_utility_view, mAllApps, false); + if (mWorkModeSwitch == null) { + mWorkModeSwitch = (WorkModeSwitch) mAllApps.getLayoutInflater().inflate( + R.layout.work_mode_fab, mAllApps, false); } - if (mWorkUtilityView.getParent() == null) { - mAllApps.addView(mWorkUtilityView); + if (mWorkModeSwitch.getParent() == null) { + mAllApps.addView(mWorkModeSwitch); } if (mAllApps.getCurrentPage() != WORK) { - mWorkUtilityView.animateVisibility(false); + mWorkModeSwitch.animateVisibility(false); } if (getAH() != null) { getAH().applyPadding(); } - mWorkUtilityView.getWorkFAB().setOnClickListener(this::onWorkFabClicked); + mWorkModeSwitch.setOnClickListener(this::onWorkFabClicked); return true; } + /** * Removes work profile toggle button from {@link ActivityAllAppsContainerView} */ - public void detachWorkUtilityViews() { - if (mWorkUtilityView != null && mWorkUtilityView.getParent() == mAllApps) { - mAllApps.removeView(mWorkUtilityView); + public void detachWorkModeSwitch() { + if (mWorkModeSwitch != null && mWorkModeSwitch.getParent() == mAllApps) { + mAllApps.removeView(mWorkModeSwitch); } - mWorkUtilityView = null; + mWorkModeSwitch = null; } @Nullable - public WorkUtilityView getWorkUtilityView() { - return mWorkUtilityView; + public WorkModeSwitch getWorkModeSwitch() { + return mWorkModeSwitch; } private ActivityAllAppsContainerView.AdapterHolder getAH() { @@ -182,11 +199,12 @@ public class WorkProfileManager extends UserProfileManager } /** - * Adds work profile specific adapter items to adapterItems and returns number of items added + * Adds work profile specific adapter items to adapterItems and returns number + * of items added */ public int addWorkItems(ArrayList adapterItems) { if (getCurrentState() == WorkProfileManager.STATE_DISABLED) { - //add disabled card here. + // add disabled card here. adapterItems.add(new AdapterItem(VIEW_TYPE_WORK_DISABLED_CARD)); } else if (getCurrentState() == WorkProfileManager.STATE_ENABLED && !isEduSeen()) { adapterItems.add(new AdapterItem(VIEW_TYPE_WORK_EDU_CARD)); @@ -199,8 +217,7 @@ public class WorkProfileManager extends UserProfileManager } private void onWorkFabClicked(View view) { - if (getCurrentState() == STATE_ENABLED && mWorkUtilityView.isEnabled()) { - Log.d(TAG, "Work FAB clicked."); + if (getCurrentState() == STATE_ENABLED && mWorkModeSwitch.isEnabled()) { logEvents(LAUNCHER_TURN_OFF_WORK_APPS_TAP); setWorkProfileEnabled(false); } @@ -209,16 +226,18 @@ public class WorkProfileManager extends UserProfileManager public RecyclerView.OnScrollListener newScrollListener() { return new RecyclerView.OnScrollListener() { int totalDelta = 0; + @Override - public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState){ + public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { if (newState == RecyclerView.SCROLL_STATE_IDLE) { totalDelta = 0; } } + @Override public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { - WorkUtilityView fab = getWorkUtilityView(); - if (fab == null){ + WorkModeSwitch fab = getWorkModeSwitch(); + if (fab == null) { return; } totalDelta = Utilities.boundToRange(totalDelta, diff --git a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java index 34cd34e5cb..76717bebd4 100644 --- a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java +++ b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java @@ -22,6 +22,8 @@ import android.text.TextWatcher; import android.text.style.SuggestionSpan; import android.util.Log; import android.view.KeyEvent; +import android.view.View; +import android.view.View.OnFocusChangeListener; import android.view.inputmethod.EditorInfo; import android.widget.TextView; import android.widget.TextView.OnEditorActionListener; @@ -29,6 +31,7 @@ import android.widget.TextView.OnEditorActionListener; import com.android.launcher3.ExtendedEditText; import com.android.launcher3.Utilities; import com.android.launcher3.allapps.BaseAllAppsAdapter.AdapterItem; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.search.SearchAlgorithm; import com.android.launcher3.search.SearchCallback; import com.android.launcher3.views.ActivityContext; @@ -37,7 +40,8 @@ import com.android.launcher3.views.ActivityContext; * An interface to a search box that AllApps can command. */ public class AllAppsSearchBarController - implements TextWatcher, OnEditorActionListener, ExtendedEditText.OnBackKeyListener { + implements TextWatcher, OnEditorActionListener, ExtendedEditText.OnBackKeyListener, + OnFocusChangeListener { private static final String TAG = "AllAppsSearchBarController"; protected ActivityContext mLauncher; @@ -65,6 +69,7 @@ public class AllAppsSearchBarController mInput.addTextChangedListener(this); mInput.setOnEditorActionListener(this); mInput.setOnBackKeyListener(this); + mInput.addOnFocusChangeListener(this); mSearchAlgorithm = searchAlgorithm; } @@ -117,14 +122,8 @@ public class AllAppsSearchBarController @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { - if (actionId == EditorInfo.IME_ACTION_SEARCH || actionId == EditorInfo.IME_ACTION_GO || ( - actionId == EditorInfo.IME_NULL && event != null - && event.getAction() == KeyEvent.ACTION_DOWN)) { - if (actionId == EditorInfo.IME_NULL) { - Log.i(TAG, "User pressed ENTER key"); - } else { - Log.i(TAG, "User tapped ime search button"); - } + if (actionId == EditorInfo.IME_ACTION_SEARCH || actionId == EditorInfo.IME_ACTION_GO) { + Log.i(TAG, "User tapped ime search button"); // selectFocusedView should return SearchTargetEvent that is passed onto onClick return mLauncher.getAppsView().getMainAdapterProvider().launchHighlightedItem(); } @@ -142,6 +141,13 @@ public class AllAppsSearchBarController return false; } + @Override + public void onFocusChange(View view, boolean hasFocus) { + if (!hasFocus && !FeatureFlags.ENABLE_DEVICE_SEARCH.get()) { + mInput.hideKeyboard(); + } + } + /** * Resets the search bar state. */ @@ -149,8 +155,8 @@ public class AllAppsSearchBarController mCallback.clearSearchResult(); mInput.reset(); mInput.clearFocus(); - mInput.hideKeyboard(); mQuery = null; + mInput.removeOnFocusChangeListener(this); } /** diff --git a/src/com/android/launcher3/anim/AnimatedPropertySetter.java b/src/com/android/launcher3/anim/AnimatedPropertySetter.java index 0f1b8ad9c6..82e645a7a0 100644 --- a/src/com/android/launcher3/anim/AnimatedPropertySetter.java +++ b/src/com/android/launcher3/anim/AnimatedPropertySetter.java @@ -20,7 +20,6 @@ import static com.android.launcher3.LauncherAnimUtils.VIEW_BACKGROUND_COLOR; import android.animation.Animator; import android.animation.Animator.AnimatorListener; -import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.TimeInterpolator; @@ -122,31 +121,19 @@ public class AnimatedPropertySetter extends PropertySetter { * Adds a listener to be run on every frame of the animation */ public void addOnFrameListener(ValueAnimator.AnimatorUpdateListener listener) { - getProgressAnimator().addUpdateListener(listener); + if (mProgressAnimator == null) { + mProgressAnimator = ValueAnimator.ofFloat(0, 1); + } + + mProgressAnimator.addUpdateListener(listener); } @Override public void addEndListener(Consumer listener) { - getProgressAnimator().addListener(AnimatorListeners.forEndCallback(listener)); - } - - /** - * Add a callback to run on progress start. - */ - public void addStartListener(Runnable listener) { - getProgressAnimator().addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animation) { - listener.run(); - } - }); - } - - private ValueAnimator getProgressAnimator() { if (mProgressAnimator == null) { mProgressAnimator = ValueAnimator.ofFloat(0, 1); } - return mProgressAnimator; + mProgressAnimator.addListener(AnimatorListeners.forEndCallback(listener)); } /** diff --git a/src/com/android/launcher3/anim/PendingAnimation.java b/src/com/android/launcher3/anim/PendingAnimation.java index 5b0bee3389..47a2bdd6de 100644 --- a/src/com/android/launcher3/anim/PendingAnimation.java +++ b/src/com/android/launcher3/anim/PendingAnimation.java @@ -106,7 +106,11 @@ public class PendingAnimation extends AnimatedPropertySetter { /** If trace is enabled, add counter to trace animation progress. */ public void logAnimationProgressToTrace(String counterName) { - // LC: No-op + if (Trace.isEnabled()) { + super.addOnFrameListener( + animation -> Trace.setCounter( + counterName, (long) (animation.getAnimatedFraction() * 100))); + } } /** diff --git a/src/com/android/launcher3/apppairs/AppPairIcon.java b/src/com/android/launcher3/apppairs/AppPairIcon.java index 870c8769ca..32445ecddb 100644 --- a/src/com/android/launcher3/apppairs/AppPairIcon.java +++ b/src/com/android/launcher3/apppairs/AppPairIcon.java @@ -18,12 +18,10 @@ package com.android.launcher3.apppairs; import static com.android.launcher3.BubbleTextView.DISPLAY_FOLDER; -import android.animation.ObjectAnimator; import android.content.Context; import android.graphics.Paint; import android.graphics.Rect; import android.util.AttributeSet; -import android.util.FloatProperty; import android.view.LayoutInflater; import android.view.ViewGroup; import android.widget.FrameLayout; @@ -56,26 +54,6 @@ import java.util.function.Predicate; public class AppPairIcon extends FrameLayout implements DraggableView, Reorderable { private static final String TAG = "AppPairIcon"; - // The duration of the scaling animation on hover enter/exit. - private static final int HOVER_SCALE_DURATION = 150; - // The default scale of the icon when not hovered. - private static final Float HOVER_SCALE_DEFAULT = 1f; - // The max scale of the icon when hovered. - private static final Float HOVER_SCALE_MAX = 1.1f; - // Animates the scale of the icon background on hover. - private static final FloatProperty HOVER_SCALE_PROPERTY = - new FloatProperty<>("hoverScale") { - @Override - public void setValue(AppPairIcon view, float scale) { - view.mIconGraphic.setHoverScale(scale); - } - - @Override - public Float get(AppPairIcon view) { - return view.mIconGraphic.getHoverScale(); - } - }; - // A view that holds the app pair icon graphic. private AppPairIconGraphic mIconGraphic; // A view that holds the app pair's title. @@ -272,14 +250,4 @@ public class AppPairIcon extends FrameLayout implements DraggableView, Reorderab } super.onMeasure(widthMeasureSpec, heightMeasureSpec); } - - @Override - public void onHoverChanged(boolean hovered) { - super.onHoverChanged(hovered); - ObjectAnimator - .ofFloat(this, HOVER_SCALE_PROPERTY, - hovered ? HOVER_SCALE_MAX : HOVER_SCALE_DEFAULT) - .setDuration(HOVER_SCALE_DURATION) - .start(); - } } diff --git a/src/com/android/launcher3/apppairs/AppPairIconDrawable.java b/src/com/android/launcher3/apppairs/AppPairIconDrawable.java index 114ed2edd5..db83d91a4b 100644 --- a/src/com/android/launcher3/apppairs/AppPairIconDrawable.java +++ b/src/com/android/launcher3/apppairs/AppPairIconDrawable.java @@ -26,7 +26,6 @@ import android.os.Build; import androidx.annotation.NonNull; -import com.android.launcher3.Utilities; import com.android.launcher3.icons.FastBitmapDrawable; /** @@ -129,18 +128,6 @@ public class AppPairIconDrawable extends Drawable { height - (mP.getStandardIconPadding() + mP.getOuterPadding()) ); - // Scale each background from its center edge closest to the center channel. - Utilities.scaleRectFAboutPivot( - leftSide, - leftSide.left + leftSide.width(), - leftSide.top + leftSide.centerY(), - mP.getHoverScale()); - Utilities.scaleRectFAboutPivot( - rightSide, - rightSide.left, - rightSide.top + rightSide.centerY(), - mP.getHoverScale()); - drawCustomRoundedRect(canvas, leftSide, new float[]{ mP.getBigRadius(), mP.getBigRadius(), mP.getSmallRadius(), mP.getSmallRadius(), @@ -176,18 +163,6 @@ public class AppPairIconDrawable extends Drawable { height - (mP.getStandardIconPadding() + mP.getOuterPadding()) ); - // Scale each background from its center edge closest to the center channel. - Utilities.scaleRectFAboutPivot( - topSide, - topSide.left + topSide.centerX(), - topSide.top + topSide.height(), - mP.getHoverScale()); - Utilities.scaleRectFAboutPivot( - bottomSide, - bottomSide.left + bottomSide.centerX(), - bottomSide.top, - mP.getHoverScale()); - drawCustomRoundedRect(canvas, topSide, new float[]{ mP.getBigRadius(), mP.getBigRadius(), mP.getBigRadius(), mP.getBigRadius(), diff --git a/src/com/android/launcher3/apppairs/AppPairIconDrawingParams.kt b/src/com/android/launcher3/apppairs/AppPairIconDrawingParams.kt index 5b546d69de..45dc013348 100644 --- a/src/com/android/launcher3/apppairs/AppPairIconDrawingParams.kt +++ b/src/com/android/launcher3/apppairs/AppPairIconDrawingParams.kt @@ -64,8 +64,6 @@ class AppPairIconDrawingParams(val context: Context, container: Int) { var isLeftRightSplit: Boolean = true // The background paint color (based on container). var bgColor: Int = 0 - // The scale of the icon background while hovered. - var hoverScale: Float = 1f init { val activity: ActivityContext = ActivityContext.lookupContext(context) diff --git a/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt b/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt index 81a92f689b..dce97eb594 100644 --- a/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt +++ b/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt @@ -27,6 +27,7 @@ import com.android.launcher3.DeviceProfile import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener import com.android.launcher3.icons.BitmapInfo import com.android.launcher3.model.data.AppPairInfo +import com.android.launcher3.util.Themes import com.android.launcher3.views.ActivityContext /** @@ -45,11 +46,12 @@ constructor(context: Context, attrs: AttributeSet? = null) : @JvmStatic fun composeDrawable( appPairInfo: AppPairInfo, - p: AppPairIconDrawingParams, + p: AppPairIconDrawingParams ): AppPairIconDrawable { - // Generate new icons, using themed flag since the icon is drawn on homescreen - val appIcon1 = appPairInfo.getFirstApp().newIcon(p.context, BitmapInfo.FLAG_THEMED) - val appIcon2 = appPairInfo.getSecondApp().newIcon(p.context, BitmapInfo.FLAG_THEMED) + // Generate new icons, using themed flag if needed. + val flags = if (Themes.isThemedIconEnabled(p.context)) BitmapInfo.FLAG_THEMED else 0 + val appIcon1 = appPairInfo.getFirstApp().newIcon(p.context, flags) + val appIcon2 = appPairInfo.getSecondApp().newIcon(p.context, flags) appIcon1.setBounds(0, 0, p.memberIconSize.toInt(), p.memberIconSize.toInt()) appIcon2.setBounds(0, 0, p.memberIconSize.toInt(), p.memberIconSize.toInt()) @@ -123,7 +125,7 @@ constructor(context: Context, attrs: AttributeSet? = null) : ((parentIcon.width - drawParams.backgroundSize) / 2).toInt(), // y-coordinate in parent's coordinate system (parentIcon.paddingTop + drawParams.standardIconPadding + drawParams.outerPadding) - .toInt(), + .toInt() ) } @@ -137,15 +139,4 @@ constructor(context: Context, attrs: AttributeSet? = null) : super.dispatchDraw(canvas) drawable.draw(canvas) } - - /** Sets the scale of the icon background while hovered. */ - fun setHoverScale(scale: Float) { - drawParams.hoverScale = scale - redraw() - } - - /** Gets the scale of the icon background while hovered. */ - fun getHoverScale(): Float { - return drawParams.hoverScale - } } diff --git a/src/com/android/launcher3/backuprestore/LauncherRestoreEventLogger.kt b/src/com/android/launcher3/backuprestore/LauncherRestoreEventLogger.kt index 1502811099..e6654b1547 100644 --- a/src/com/android/launcher3/backuprestore/LauncherRestoreEventLogger.kt +++ b/src/com/android/launcher3/backuprestore/LauncherRestoreEventLogger.kt @@ -16,52 +16,32 @@ open class LauncherRestoreEventLogger : ResourceBasedOverride { @Retention(AnnotationRetention.SOURCE) @StringDef( RestoreError.PROFILE_DELETED, + RestoreError.MISSING_INFO, RestoreError.MISSING_WIDGET_PROVIDER, - RestoreError.OVERLAPPING_ITEM, - RestoreError.INVALID_WIDGET_SIZE, - RestoreError.INVALID_WIDGET_CONTAINER, + RestoreError.INVALID_LOCATION, RestoreError.SHORTCUT_NOT_FOUND, - RestoreError.APP_NO_TARGET_PACKAGE, - RestoreError.APP_NO_DB_INTENT, - RestoreError.APP_NO_LAUNCH_INTENT, - RestoreError.APP_NOT_RESTORED_OR_INSTALLING, - RestoreError.APP_NOT_INSTALLED_EXTERNAL_MEDIA, + RestoreError.APP_NOT_INSTALLED, RestoreError.WIDGETS_DISABLED, RestoreError.PROFILE_NOT_RESTORED, RestoreError.WIDGET_REMOVED, - RestoreError.DATABASE_FILE_NOT_RESTORED, RestoreError.GRID_MIGRATION_FAILURE, RestoreError.NO_SEARCH_WIDGET, - RestoreError.INVALID_WIDGET_ID, - RestoreError.OTHER_WIDGET_INFLATION_FAIL, - RestoreError.UNSPECIFIED_WIDGET_INFLATION_RESULT, - RestoreError.UNRESTORED_PENDING_WIDGET, - RestoreError.INVALID_CUSTOM_WIDGET_ID, + RestoreError.INVALID_WIDGET_ID ) annotation class RestoreError { companion object { const val PROFILE_DELETED = "user_profile_deleted" + const val MISSING_INFO = "missing_information_when_loading" const val MISSING_WIDGET_PROVIDER = "missing_widget_provider" - const val OVERLAPPING_ITEM = "overlapping_item" - const val INVALID_WIDGET_SIZE = "invalid_widget_size" - const val INVALID_WIDGET_CONTAINER = "invalid_widget_container" + const val INVALID_LOCATION = "invalid_size_or_location" const val SHORTCUT_NOT_FOUND = "shortcut_not_found" - const val APP_NO_TARGET_PACKAGE = "app_no_target_package" - const val APP_NO_DB_INTENT = "app_no_db_intent" - const val APP_NO_LAUNCH_INTENT = "app_no_launch_intent" - const val APP_NOT_RESTORED_OR_INSTALLING = "app_not_restored_or_installed" - const val APP_NOT_INSTALLED_EXTERNAL_MEDIA = "app_not_installed_external_media" + const val APP_NOT_INSTALLED = "app_not_installed" const val WIDGETS_DISABLED = "widgets_disabled" const val PROFILE_NOT_RESTORED = "profile_not_restored" - const val DATABASE_FILE_NOT_RESTORED = "db_file_not_restored" const val WIDGET_REMOVED = "widget_not_found" const val GRID_MIGRATION_FAILURE = "grid_migration_failed" const val NO_SEARCH_WIDGET = "no_search_widget" const val INVALID_WIDGET_ID = "invalid_widget_id" - const val OTHER_WIDGET_INFLATION_FAIL = "other_widget_fail" - const val UNSPECIFIED_WIDGET_INFLATION_RESULT = "unspecified_widget_inflation_result" - const val UNRESTORED_PENDING_WIDGET = "unrestored_pending_widget" - const val INVALID_CUSTOM_WIDGET_ID = "invalid_custom_widget_id" } } @@ -72,7 +52,7 @@ open class LauncherRestoreEventLogger : ResourceBasedOverride { return ResourceBasedOverride.Overrides.getObject( LauncherRestoreEventLogger::class.java, context, - R.string.launcher_restore_event_logger_class, + R.string.launcher_restore_event_logger_class ) } } @@ -137,7 +117,7 @@ open class LauncherRestoreEventLogger : ResourceBasedOverride { open fun logFavoritesItemsRestoreFailed( favoritesId: Int, count: Int, - @RestoreError error: String?, + @RestoreError error: String? ) { // no-op } diff --git a/src/com/android/launcher3/celllayout/ReorderAlgorithm.java b/src/com/android/launcher3/celllayout/ReorderAlgorithm.java index a56d0811c1..42abecd1fe 100644 --- a/src/com/android/launcher3/celllayout/ReorderAlgorithm.java +++ b/src/com/android/launcher3/celllayout/ReorderAlgorithm.java @@ -22,6 +22,7 @@ import com.android.launcher3.CellLayout; import com.android.launcher3.Utilities; import com.android.launcher3.util.CellAndSpan; import com.android.launcher3.util.GridOccupancy; +import com.patrykmichalik.opto.core.PreferenceExtensionsKt; import java.util.ArrayList; import java.util.Comparator; @@ -29,8 +30,6 @@ import java.util.List; import java.util.Map.Entry; import java.util.stream.Collectors; -import com.patrykmichalik.opto.core.PreferenceExtensionsKt; - /** * Contains the logic of a reorder. * @@ -133,7 +132,6 @@ public class ReorderAlgorithm { ArrayList intersectingViews = new ArrayList<>(); Rect occupiedRect = new Rect(cellX, cellY, cellX + spanX, cellY + spanY); - // Lawnchair: Widget overlap if (PreferenceExtensionsKt.firstBlocking(mCellLayout.pref.getAllowWidgetOverlap())) { solution.intersectingViews = new ArrayList<>(intersectingViews); return true; @@ -153,14 +151,16 @@ public class ReorderAlgorithm { // and not by the views hash which is "random". // The views are sorted twice, once for the X position and a second time for the Y position // to ensure same order everytime. - Comparator comparator = Comparator.comparing( - (View view) -> ((CellLayoutLayoutParams) view.getLayoutParams()).getCellX() + Comparator comparator = Comparator.comparing( + view -> ((CellLayoutLayoutParams) ((View) view).getLayoutParams()).getCellX() ).thenComparing( - (View view) -> ((CellLayoutLayoutParams) view.getLayoutParams()).getCellY() + view -> ((CellLayoutLayoutParams) ((View) view).getLayoutParams()).getCellY() ); List views = solution.map.keySet().stream() - .sorted(comparator) + .filter(View.class::isInstance) + .map(View.class::cast) .collect(Collectors.toList()); + views.sort(comparator); for (View child : views) { if (child == ignoreView) continue; CellAndSpan c = solution.map.get(child); diff --git a/src/com/android/launcher3/compat/AlphabeticIndexCompat.java b/src/com/android/launcher3/compat/AlphabeticIndexCompat.java index d593f80756..4f8d53e11c 100644 --- a/src/com/android/launcher3/compat/AlphabeticIndexCompat.java +++ b/src/com/android/launcher3/compat/AlphabeticIndexCompat.java @@ -3,7 +3,6 @@ package com.android.launcher3.compat; import android.content.Context; import android.icu.text.AlphabeticIndex; import android.os.LocaleList; -import android.util.Log; import androidx.annotation.NonNull; @@ -13,9 +12,6 @@ import java.util.Locale; public class AlphabeticIndexCompat { - // TODO(b/336947811): Set to false after root causing is done. - private static final boolean DEBUG = true; - private static final String TAG = "AlphabeticIndexCompat"; private static final String MID_DOT = "\u2219"; private final String mDefaultMiscLabel; @@ -53,9 +49,6 @@ public class AlphabeticIndexCompat { public String computeSectionName(@NonNull CharSequence cs) { String s = Utilities.trim(cs); String sectionName = mBaseIndex.getBucket(mBaseIndex.getBucketIndex(s)).getLabel(); - if (DEBUG) { - Log.d(TAG, "computeSectionName: cs: " + cs + " sectionName: " + sectionName); - } if (Utilities.trim(sectionName).isEmpty() && s.length() > 0) { int c = s.codePointAt(0); boolean startsWithDigit = Character.isDigit(c); diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java index 9873ee52d3..a757e41b44 100644 --- a/src/com/android/launcher3/config/FeatureFlags.java +++ b/src/com/android/launcher3/config/FeatureFlags.java @@ -18,232 +18,448 @@ package com.android.launcher3.config; import static com.android.launcher3.config.FeatureFlags.BooleanFlag.DISABLED; import static com.android.launcher3.config.FeatureFlags.BooleanFlag.ENABLED; -import static com.android.wm.shell.Flags.enableTaskbarNavbarUnification; -import static com.android.wm.shell.Flags.enableTaskbarOnPhones; +import android.content.Context; import android.content.res.Resources; import androidx.annotation.VisibleForTesting; import com.android.launcher3.Utilities; +import com.patrykmichalik.opto.core.PreferenceExtensionsKt; import com.android.launcher3.BuildConfig; import com.android.launcher3.Flags; -import com.patrykmichalik.opto.core.PreferenceExtensionsKt; import app.lawnchair.preferences2.PreferenceManager2; /** * Defines a set of flags used to control various launcher behaviors. *

- *

All the flags should be defined here with appropriate default values. + *

+ * All the flags should be defined here with appropriate default values. */ public final class FeatureFlags { - private FeatureFlags() { } - - /** - * True when the build has come from Android Studio and is being used for local debugging. - * @deprecated Use {@link BuildConfig#IS_STUDIO_BUILD} directly - */ - @Deprecated - public static final boolean IS_STUDIO_BUILD = false; - - /** - * Enable moving the QSB on the 0th screen of the workspace. This is not a configuration feature - * and should be modified at a project level. - * @deprecated Use {@link BuildConfig#QSB_ON_FIRST_SCREEN} directly - */ - @Deprecated - public static final boolean QSB_ON_FIRST_SCREEN = true; - - /** - * Feature flag to handle define config changes dynamically instead of killing the process. - *

- * - * To add a new flag that can be toggled through the flags UI: - *

- * Declare a new ToggleableFlag below. Give it a unique key (e.g. "QSB_ON_FIRST_SCREEN"), - * and set a default value for the flag. This will be the default value on Debug builds. - *

- */ - // TODO(Block 6): Clean up flags - public static final BooleanFlag SECONDARY_DRAG_N_DROP_TO_PIN = getDebugFlag(270395140, - "SECONDARY_DRAG_N_DROP_TO_PIN", DISABLED, - "Enable dragging and dropping to pin apps within secondary display"); - - // TODO(Block 8): Clean up flags - - // TODO(Block 9): Clean up flags - public static final BooleanFlag MULTI_SELECT_EDIT_MODE = getDebugFlag(270709220, - "MULTI_SELECT_EDIT_MODE", DISABLED, "Enable new multi-select edit mode " - + "for home screen"); - - // TODO(Block 11): Clean up flags - public static final BooleanFlag FOLDABLE_SINGLE_PAGE = getDebugFlag(270395274, - "FOLDABLE_SINGLE_PAGE", DISABLED, "Use a single page for the workspace"); - - // TODO(Block 12): Clean up flags - public static final BooleanFlag ENABLE_MULTI_INSTANCE = getDebugFlag(270396680, - "ENABLE_MULTI_INSTANCE", DISABLED, - "Enables creation and filtering of multiple task instances in overview"); - - // TODO(Block 13): Clean up flags - public static final BooleanFlag ENABLE_DEVICE_SEARCH_PERFORMANCE_LOGGING = getReleaseFlag( - 270391397, "ENABLE_DEVICE_SEARCH_PERFORMANCE_LOGGING", DISABLED, - "Allows on device search in all apps logging"); - - // TODO(Block 14): Cleanup flags - public static final BooleanFlag NOTIFY_CRASHES = getDebugFlag(270393108, "NOTIFY_CRASHES", - DISABLED, "Sends a notification whenever launcher encounters an uncaught exception."); - - public static final boolean ENABLE_TASKBAR_NAVBAR_UNIFICATION = - enableTaskbarNavbarUnification() && (!isPhone() || enableTaskbarOnPhones()); - - private static boolean isPhone() { - final boolean isPhone; - int foldedDeviceStatesId = Resources.getSystem().getIdentifier( - "config_foldedDeviceStates", "array", "android"); - if (foldedDeviceStatesId != 0) { - isPhone = Resources.getSystem().getIntArray(foldedDeviceStatesId).length == 0; - } else { - isPhone = true; + private FeatureFlags() { } - return isPhone; - } - // Aconfig migration complete for ENABLE_TASKBAR_NO_RECREATION. - public static final BooleanFlag ENABLE_TASKBAR_NO_RECREATION = getDebugFlag(299193589, - "ENABLE_TASKBAR_NO_RECREATION", DISABLED, - "Enables taskbar with no recreation from lifecycle changes of TaskbarActivityContext."); - public static boolean enableTaskbarNoRecreate() { - return ENABLE_TASKBAR_NO_RECREATION.get() || Flags.enableTaskbarNoRecreate() + public static boolean showFlagTogglerUi(Context context) { + return Utilities.isDevelopersOptionsEnabled(context); + } + + /** + * True when the build has come from Android Studio and is being used for local + * debugging. + * + * @deprecated Use {@link BuildConfig#IS_STUDIO_BUILD} directly + */ + @Deprecated + public static final boolean IS_STUDIO_BUILD = false; + + /** + * Enable moving the QSB on the 0th screen of the workspace. This is not a + * configuration feature + * and should be modified at a project level. + * + * @deprecated Use {@link BuildConfig#QSB_ON_FIRST_SCREEN} directly + */ + @Deprecated + public static boolean topQsbOnFirstScreenEnabled(Context context) { + PreferenceManager2 preferenceManager2 = PreferenceManager2.getInstance(context); + return PreferenceExtensionsKt.firstBlocking(preferenceManager2.getEnableSmartspace()); + } + + public static boolean showDotPagination(Context context) { + PreferenceManager2 preferenceManager2 = PreferenceManager2.getInstance(context); + return PreferenceExtensionsKt.firstBlocking(preferenceManager2.getEnableDotPagination()); + } + + public static boolean showMaterialUPopup(Context context) { + PreferenceManager2 preferenceManager2 = PreferenceManager2.getInstance(context); + return PreferenceExtensionsKt.firstBlocking(preferenceManager2.getEnableMaterialUPopUp()); + } + + public static boolean twoLineAllApps(Context context) { + PreferenceManager2 preferenceManager2 = PreferenceManager2.getInstance(context); + return PreferenceExtensionsKt.firstBlocking(preferenceManager2.getTwoLineAllApps()); + } + + /** + * Feature flag to handle define config changes dynamically instead of killing + * the process. + *

+ * + * To add a new flag that can be toggled through the flags UI: + *

+ * Declare a new ToggleableFlag below. Give it a unique key (e.g. + * "QSB_ON_FIRST_SCREEN"), + * and set a default value for the flag. This will be the default value on Debug + * builds. + *

+ */ + // TODO(Block 1): Clean up flags + public static final BooleanFlag ENABLE_ONE_SEARCH_MOTION = getReleaseFlag(270394223, + "ENABLE_ONE_SEARCH_MOTION", ENABLED, "Enables animations in OneSearch."); + + public static final BooleanFlag ENABLE_SEARCH_RESULT_BACKGROUND_DRAWABLES = getReleaseFlag( + 270394041, "ENABLE_SEARCH_RESULT_BACKGROUND_DRAWABLES", ENABLED, + "Enable option to replace decorator-based search result backgrounds with drawables"); + + public static final BooleanFlag ENABLE_SEARCH_RESULT_LAUNCH_TRANSITION = getReleaseFlag( + 270394392, "ENABLE_SEARCH_RESULT_LAUNCH_TRANSITION", ENABLED, + "Enable option to launch search results using the new view container transitions"); + + // TODO(Block 2): Clean up flags + public static final BooleanFlag ENABLE_MULTI_DISPLAY_PARTIAL_DEPTH = getDebugFlag(270395073, + "ENABLE_MULTI_DISPLAY_PARTIAL_DEPTH", DISABLED, + "Allow bottom sheet depth to be smaller than 1 for multi-display devices."); + + // TODO(Block 3): Clean up flags + public static final BooleanFlag ENABLE_DISMISS_PREDICTION_UNDO = getDebugFlag(270394476, + "ENABLE_DISMISS_PREDICTION_UNDO", DISABLED, + "Show an 'Undo' snackbar when users dismiss a predicted hotseat item"); + public static final BooleanFlag CONTINUOUS_VIEW_TREE_CAPTURE = getDebugFlag(270395171, + "CONTINUOUS_VIEW_TREE_CAPTURE", DISABLED, "Capture View tree every frame"); + + public static final BooleanFlag ENABLE_WORKSPACE_LOADING_OPTIMIZATION = getDebugFlag(251502424, + "ENABLE_WORKSPACE_LOADING_OPTIMIZATION", DISABLED, + "load the current workspace screen visible to the user before the rest rather than " + + "loading all of them at once."); + + public static final BooleanFlag CHANGE_MODEL_DELEGATE_LOADING_ORDER = getDebugFlag(251502424, + "CHANGE_MODEL_DELEGATE_LOADING_ORDER", ENABLED, + "changes the timing of the loading and binding of delegate items during " + + "data preparation for loading the home screen"); + + // TODO(Block 4): Cleanup flags + public static final BooleanFlag ENABLE_FLOATING_SEARCH_BAR = getReleaseFlag(268388460, + "ENABLE_FLOATING_SEARCH_BAR", DISABLED, + "Allow search bar to persist and animate across states, and attach to" + + " the keyboard from the bottom of the screen"); + public static final BooleanFlag ENABLE_ALL_APPS_FROM_OVERVIEW = getDebugFlag(275132633, + "ENABLE_ALL_APPS_FROM_OVERVIEW", DISABLED, + "Allow entering All Apps from Overview (e.g. long swipe up from app)"); + + public static final BooleanFlag ENABLE_SHOW_KEYBOARD_OPTION_IN_ALL_APPS = getReleaseFlag( + 270394468, "ENABLE_SHOW_KEYBOARD_OPTION_IN_ALL_APPS", ENABLED, + "Enable option to show keyboard when going to all-apps"); + + // TODO(Block 5): Clean up flags + public static final BooleanFlag ENABLE_TWOLINE_DEVICESEARCH = getDebugFlag(201388851, + "ENABLE_TWOLINE_DEVICESEARCH", DISABLED, + "Enable two line label for icons with labels on device search."); + + public static final BooleanFlag ENABLE_ICON_IN_TEXT_HEADER = getDebugFlag(270395143, + "ENABLE_ICON_IN_TEXT_HEADER", DISABLED, "Show icon in textheader"); + + public static final BooleanFlag ENABLE_PREMIUM_HAPTICS_ALL_APPS = getDebugFlag(270396358, + "ENABLE_PREMIUM_HAPTICS_ALL_APPS", DISABLED, + "Enables haptics opening/closing All apps"); + + // TODO(Block 6): Clean up flags + public static final BooleanFlag ENABLE_ALL_APPS_SEARCH_IN_TASKBAR = getDebugFlag(270393900, + "ENABLE_ALL_APPS_SEARCH_IN_TASKBAR", ENABLED, + "Enables Search box in Taskbar All Apps."); + + public static final BooleanFlag SECONDARY_DRAG_N_DROP_TO_PIN = getDebugFlag(270395140, + "SECONDARY_DRAG_N_DROP_TO_PIN", DISABLED, + "Enable dragging and dropping to pin apps within secondary display"); + + // TODO(Block 8): Clean up flags + + // TODO(Block 9): Clean up flags + public static final BooleanFlag MULTI_SELECT_EDIT_MODE = getDebugFlag(270709220, + "MULTI_SELECT_EDIT_MODE", DISABLED, "Enable new multi-select edit mode " + + "for home screen"); + + // TODO(Block 11): Clean up flags + public static final BooleanFlag ENABLE_TWO_PANEL_HOME = getDebugFlag(270392643, + "ENABLE_TWO_PANEL_HOME", ENABLED, + "Uses two panel on home screen. Only applicable on large screen devices."); + + public static final BooleanFlag FOLDABLE_WORKSPACE_REORDER = getDebugFlag(270395070, + "FOLDABLE_WORKSPACE_REORDER", DISABLED, + "In foldables, when reordering the icons and widgets, is now going to use both sides"); + + public static final BooleanFlag FOLDABLE_SINGLE_PAGE = getDebugFlag(270395274, + "FOLDABLE_SINGLE_PAGE", DISABLED, "Use a single page for the workspace"); + + public static final BooleanFlag ENABLE_PARAMETRIZE_REORDER = getDebugFlag(289420844, + "ENABLE_PARAMETRIZE_REORDER", DISABLED, + "Enables generating the reorder using a set of parameters"); + + // TODO(Block 12): Clean up flags + public static final BooleanFlag ENABLE_MULTI_INSTANCE = getDebugFlag(270396680, + "ENABLE_MULTI_INSTANCE", DISABLED, + "Enables creation and filtering of multiple task instances in overview"); + + // TODO(Block 13): Clean up flags + public static final BooleanFlag ENABLE_DEVICE_SEARCH_PERFORMANCE_LOGGING = getReleaseFlag( + 270391397, "ENABLE_DEVICE_SEARCH_PERFORMANCE_LOGGING", DISABLED, + "Allows on device search in all apps logging"); + + // TODO(Block 14): Cleanup flags + public static final BooleanFlag NOTIFY_CRASHES = getDebugFlag(270393108, "NOTIFY_CRASHES", + DISABLED, "Sends a notification whenever launcher encounters an uncaught exception."); + + public static final boolean ENABLE_TASKBAR_NAVBAR_UNIFICATION = !isPhone(); + + private static boolean isPhone() { + final boolean isPhone; + int foldedDeviceStatesId = Resources.getSystem().getIdentifier( + "config_foldedDeviceStates", "array", "android"); + if (foldedDeviceStatesId != 0) { + isPhone = Resources.getSystem().getIntArray(foldedDeviceStatesId).length == 0; + } else { + isPhone = true; + } + return isPhone; + } + + // Aconfig migration complete for ENABLE_TASKBAR_NO_RECREATION. + public static final BooleanFlag ENABLE_TASKBAR_NO_RECREATION = getDebugFlag(299193589, + "ENABLE_TASKBAR_NO_RECREATION", DISABLED, + "Enables taskbar with no recreation from lifecycle changes of TaskbarActivityContext."); + + public static boolean enableTaskbarNoRecreate() { + return ENABLE_TASKBAR_NO_RECREATION.get() || Flags.enableTaskbarNoRecreate() // Task bar pinning and task bar nav bar unification are both dependent on // ENABLE_TASKBAR_NO_RECREATION. We want to turn ENABLE_TASKBAR_NO_RECREATION on // when either of the dependent features is turned on. - || enableTaskbarPinning() || ENABLE_TASKBAR_NAVBAR_UNIFICATION; - } - - // TODO(Block 16): Clean up flags - // When enabled the promise icon is visible in all apps while installation an app. - public static final BooleanFlag PROMISE_APPS_IN_ALL_APPS = getDebugFlag(270390012, - "PROMISE_APPS_IN_ALL_APPS", DISABLED, "Add promise icon in all-apps"); - - // Aconfig migration complete for ENABLE_EXPANDING_PAUSE_WORK_BUTTON. - public static final BooleanFlag ENABLE_EXPANDING_PAUSE_WORK_BUTTON = getDebugFlag(270390779, - "ENABLE_EXPANDING_PAUSE_WORK_BUTTON", DISABLED, - "Expand and collapse pause work button while scrolling"); - - public static final BooleanFlag INJECT_FALLBACK_APP_CORPUS_RESULTS = getReleaseFlag(270391706, - "INJECT_FALLBACK_APP_CORPUS_RESULTS", DISABLED, - "Inject fallback app corpus result when AiAi fails to return it."); - - // TODO(Block 17): Clean up flags - // Aconfig migration complete for ENABLE_TASKBAR_PINNING. - private static final BooleanFlag ENABLE_TASKBAR_PINNING = getDebugFlag(296231746, - "ENABLE_TASKBAR_PINNING", DISABLED, - "Enables taskbar pinning to allow user to switch between transient and persistent " - + "taskbar flavors"); - - public static boolean enableTaskbarPinning() { - return ENABLE_TASKBAR_PINNING.get() || Flags.enableTaskbarPinning(); - } - - // TODO(Block 20): Clean up flags - // Aconfig migration complete for ENABLE_HOME_TRANSITION_LISTENER. - public static final BooleanFlag ENABLE_HOME_TRANSITION_LISTENER = getDebugFlag(306053414, - "ENABLE_HOME_TRANSITION_LISTENER", DISABLED, - "Enables launcher to listen to all transitions that include home activity."); - - public static boolean enableHomeTransitionListener() { - return ENABLE_HOME_TRANSITION_LISTENER.get() || Flags.enableHomeTransitionListener(); - } - - // TODO(Block 21): Clean up flags - public static final BooleanFlag ENABLE_APP_ICON_FOR_INLINE_SHORTCUTS = getDebugFlag(270395087, - "ENABLE_APP_ICON_IN_INLINE_SHORTCUTS", DISABLED, "Show app icon for inline shortcut"); - - // TODO(Block 22): Clean up flags - public static final BooleanFlag ENABLE_WIDGET_TRANSITION_FOR_RESIZING = getDebugFlag(268553314, - "ENABLE_WIDGET_TRANSITION_FOR_RESIZING", ENABLED, - "Enable widget transition animation when resizing the widgets"); - - // TODO(Block 27): Clean up flags - public static final BooleanFlag ENABLE_OVERLAY_CONNECTION_OPTIM = getDebugFlag(270392629, - "ENABLE_OVERLAY_CONNECTION_OPTIM", DISABLED, - "Enable optimizing overlay service connection"); - - /** - * Enables region sampling for text color: Needs system health assessment before turning on - */ - public static final BooleanFlag ENABLE_REGION_SAMPLING = getDebugFlag(270391669, - "ENABLE_REGION_SAMPLING", DISABLED, - "Enable region sampling to determine color of text on screen."); - - public static final BooleanFlag ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS = - getDebugFlag(270393096, "ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS", - DISABLED, "Always use hardware optimization for folder animations."); - - public static final BooleanFlag SEPARATE_RECENTS_ACTIVITY = getDebugFlag(270392980, - "SEPARATE_RECENTS_ACTIVITY", DISABLED, - "Uses a separate recents activity instead of using the integrated recents+Launcher UI"); - - public static final BooleanFlag USE_LOCAL_ICON_OVERRIDES = getDebugFlag(270394973, - "USE_LOCAL_ICON_OVERRIDES", ENABLED, - "Use inbuilt monochrome icons if app doesn't provide one"); - - // TODO(Block 29): Clean up flags - // Aconfig migration complete for ENABLE_ALL_APPS_BUTTON_IN_HOTSEAT. - public static final BooleanFlag ENABLE_ALL_APPS_BUTTON_IN_HOTSEAT = getDebugFlag(270393897, - "ENABLE_ALL_APPS_BUTTON_IN_HOTSEAT", DISABLED, - "Enables displaying the all apps button in the hotseat."); - - public static boolean enableAllAppsButtonInHotseat() { - return ENABLE_ALL_APPS_BUTTON_IN_HOTSEAT.get() || Flags.enableAllAppsButtonInHotseat(); - } - - // TODO(Block 30): Clean up flags - public static final BooleanFlag USE_SEARCH_REQUEST_TIMEOUT_OVERRIDES = getDebugFlag(270395010, - "USE_SEARCH_REQUEST_TIMEOUT_OVERRIDES", DISABLED, - "Use local overrides for search request timeout"); - - // TODO(Block 31): Clean up flags - - // TODO(Block 32): Clean up flags - // Aconfig migration complete for ENABLE_RESPONSIVE_WORKSPACE. - @VisibleForTesting - public static final BooleanFlag ENABLE_RESPONSIVE_WORKSPACE = getDebugFlag(241386436, - "ENABLE_RESPONSIVE_WORKSPACE", ENABLED, - "Enables new workspace grid calculations method."); - public static boolean enableResponsiveWorkspace() { - return ENABLE_RESPONSIVE_WORKSPACE.get() || Flags.enableResponsiveWorkspace(); - } - - public static BooleanFlag getDebugFlag( - int bugId, String key, BooleanFlag flagState, String description) { - return flagState; - } - - public static BooleanFlag getReleaseFlag( - int bugId, String key, BooleanFlag flagState, String description) { - return flagState; - } - - /** - * Enabled state for a flag - */ - public enum BooleanFlag { - ENABLED(true), - DISABLED(false); - - private final boolean mValue; - - BooleanFlag(boolean value) { - mValue = value; + || enableTaskbarPinning() || ENABLE_TASKBAR_NAVBAR_UNIFICATION; } - public boolean get() { - return mValue; + // TODO(Block 16): Clean up flags + // When enabled the promise icon is visible in all apps while installation an + // app. + public static final BooleanFlag PROMISE_APPS_IN_ALL_APPS = getDebugFlag(270390012, + "PROMISE_APPS_IN_ALL_APPS", DISABLED, "Add promise icon in all-apps"); + + public static final BooleanFlag KEYGUARD_ANIMATION = getDebugFlag(270390904, + "KEYGUARD_ANIMATION", DISABLED, + "Enable animation for keyguard going away on wallpaper"); + + public static final BooleanFlag ENABLE_DEVICE_SEARCH = getReleaseFlag(270390907, + "ENABLE_DEVICE_SEARCH", ENABLED, "Allows on device search in all apps"); + + public static final BooleanFlag ENABLE_HIDE_HEADER = getReleaseFlag(270390930, + "ENABLE_HIDE_HEADER", ENABLED, "Hide header on keyboard before typing in all apps"); + + // Aconfig migration complete for ENABLE_EXPANDING_PAUSE_WORK_BUTTON. + public static final BooleanFlag ENABLE_EXPANDING_PAUSE_WORK_BUTTON = getDebugFlag(270390779, + "ENABLE_EXPANDING_PAUSE_WORK_BUTTON", DISABLED, + "Expand and collapse pause work button while scrolling"); + + // Aconfig migration complete for ENABLE_TWOLINE_ALLAPPS. + public static final BooleanFlag ENABLE_TWOLINE_ALLAPPS = getDebugFlag(270390937, + "ENABLE_TWOLINE_ALLAPPS", DISABLED, "Enables two line label inside all apps."); + + public static final BooleanFlag IME_STICKY_SNACKBAR_EDU = getDebugFlag(270391693, + "IME_STICKY_SNACKBAR_EDU", ENABLED, "Show sticky IME edu in AllApps"); + + public static final BooleanFlag FOLDER_NAME_MAJORITY_RANKING = getDebugFlag(270391638, + "FOLDER_NAME_MAJORITY_RANKING", ENABLED, + "Suggests folder names based on majority based ranking."); + + public static final BooleanFlag INJECT_FALLBACK_APP_CORPUS_RESULTS = getReleaseFlag(270391706, + "INJECT_FALLBACK_APP_CORPUS_RESULTS", DISABLED, + "Inject fallback app corpus result when AiAi fails to return it."); + // TODO(Block 17): Clean up flags + // Aconfig migration complete for ENABLE_TASKBAR_PINNING. + private static final BooleanFlag ENABLE_TASKBAR_PINNING = getDebugFlag(296231746, + "ENABLE_TASKBAR_PINNING", DISABLED, + "Enables taskbar pinning to allow user to switch between transient and persistent " + + "taskbar flavors"); + + // TODO(Block 18): Clean up flags + public static final BooleanFlag ENABLE_LAUNCH_FROM_STAGED_APP = getDebugFlag(270395567, + "ENABLE_LAUNCH_FROM_STAGED_APP", ENABLED, + "Enable the ability to tap a staged app during split select to launch it in full " + + "screen"); + + public static boolean enableTaskbarPinning() { + return ENABLE_TASKBAR_PINNING.get() || Flags.enableTaskbarPinning(); + } + + // Aconfig migration complete for ENABLE_APP_PAIRS. + public static final BooleanFlag ENABLE_APP_PAIRS = getDebugFlag(274189428, + "ENABLE_APP_PAIRS", DISABLED, + "Enables the ability to create and save app pairs on the Home screen for easy" + + " split screen launching."); + + public static boolean enableAppPairs() { + return ENABLE_APP_PAIRS.get() || com.android.wm.shell.Flags.enableAppPairs(); + } + + // TODO(Block 19): Clean up flags + public static final BooleanFlag SCROLL_TOP_TO_RESET = getReleaseFlag(270395177, + "SCROLL_TOP_TO_RESET", ENABLED, + "Bring up IME and focus on input when scroll to top if 'Always show keyboard'" + + " is enabled or in prefix state"); + + public static final BooleanFlag ENABLE_SEARCH_UNINSTALLED_APPS = getReleaseFlag(270395269, + "ENABLE_SEARCH_UNINSTALLED_APPS", ENABLED, "Search uninstalled app results."); + + // TODO(Block 20): Clean up flags + public static final BooleanFlag ENABLE_SCRIM_FOR_APP_LAUNCH = getDebugFlag(270393276, + "ENABLE_SCRIM_FOR_APP_LAUNCH", DISABLED, "Enables scrim during app launch animation."); + + public static final BooleanFlag ENABLE_BACK_SWIPE_HOME_ANIMATION = getDebugFlag(270393426, + "ENABLE_BACK_SWIPE_HOME_ANIMATION", ENABLED, + "Enables home animation to icon when user swipes back."); + public static final BooleanFlag ENABLE_DYNAMIC_TASKBAR_THRESHOLDS = getDebugFlag(294252473, + "ENABLE_DYNAMIC_TASKBAR_THRESHOLDS", ENABLED, + "Enables taskbar thresholds that scale based on screen size."); + + // Aconfig migration complete for ENABLE_HOME_TRANSITION_LISTENER. + public static final BooleanFlag ENABLE_HOME_TRANSITION_LISTENER = getDebugFlag(306053414, + "ENABLE_HOME_TRANSITION_LISTENER", DISABLED, + "Enables launcher to listen to all transitions that include home activity."); + + public static boolean enableHomeTransitionListener() { + return ENABLE_HOME_TRANSITION_LISTENER.get() || Flags.enableHomeTransitionListener(); + } + + // TODO(Block 21): Clean up flags + public static final BooleanFlag ENABLE_APP_ICON_FOR_INLINE_SHORTCUTS = getDebugFlag(270395087, + "ENABLE_APP_ICON_IN_INLINE_SHORTCUTS", DISABLED, "Show app icon for inline shortcut"); + + // TODO(Block 22): Clean up flags + public static final BooleanFlag RECEIVE_UNFOLD_EVENTS_FROM_SYSUI = getDebugFlag(270397209, + "RECEIVE_UNFOLD_EVENTS_FROM_SYSUI", ENABLED, + "Enables receiving unfold animation events from sysui instead of calculating " + + "them in launcher process using hinge sensor values."); + + public static final BooleanFlag ENABLE_WIDGET_TRANSITION_FOR_RESIZING = getDebugFlag(268553314, + "ENABLE_WIDGET_TRANSITION_FOR_RESIZING", ENABLED, + "Enable widget transition animation when resizing the widgets"); + + public static final BooleanFlag PREEMPTIVE_UNFOLD_ANIMATION_START = getDebugFlag(270397209, + "PREEMPTIVE_UNFOLD_ANIMATION_START", ENABLED, + "Enables starting the unfold animation preemptively when unfolding, without" + + "waiting for SystemUI and then merging the SystemUI progress whenever we " + + "start receiving the events"); + + // TODO(Block 25): Clean up flags + public static final BooleanFlag ENABLE_NEW_GESTURE_NAV_TUTORIAL = getDebugFlag(270396257, + "ENABLE_NEW_GESTURE_NAV_TUTORIAL", ENABLED, + "Enable the redesigned gesture navigation tutorial"); + + // TODO(Block 26): Clean up flags + public static final BooleanFlag ENABLE_WIDGET_HOST_IN_BACKGROUND = getDebugFlag(270394384, + "ENABLE_WIDGET_HOST_IN_BACKGROUND", ENABLED, + "Enable background widget updates listening for widget holder"); + + // TODO(Block 27): Clean up flags + public static final BooleanFlag ENABLE_OVERLAY_CONNECTION_OPTIM = getDebugFlag(270392629, + "ENABLE_OVERLAY_CONNECTION_OPTIM", DISABLED, + "Enable optimizing overlay service connection"); + + /** + * Enables region sampling for text color: Needs system health assessment before + * turning on + */ + public static final BooleanFlag ENABLE_REGION_SAMPLING = getDebugFlag(270391669, + "ENABLE_REGION_SAMPLING", DISABLED, + "Enable region sampling to determine color of text on screen."); + + public static final BooleanFlag ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS = getDebugFlag(270393096, + "ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS", + DISABLED, "Always use hardware optimization for folder animations."); + + public static final BooleanFlag SEPARATE_RECENTS_ACTIVITY = getDebugFlag(270392980, + "SEPARATE_RECENTS_ACTIVITY", DISABLED, + "Uses a separate recents activity instead of using the integrated recents+Launcher UI"); + + public static final BooleanFlag ENABLE_ENFORCED_ROUNDED_CORNERS = getReleaseFlag(270393258, + "ENABLE_ENFORCED_ROUNDED_CORNERS", ENABLED, + "Enforce rounded corners on all App Widgets"); + + public static final BooleanFlag USE_LOCAL_ICON_OVERRIDES = getDebugFlag(270394973, + "USE_LOCAL_ICON_OVERRIDES", ENABLED, + "Use inbuilt monochrome icons if app doesn't provide one"); + + // Aconfig migration complete for ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE. + public static final BooleanFlag ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE = getDebugFlag( + 270393453, "ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE", DISABLED, + "Enable initiating split screen from workspace to workspace."); + + public static boolean enableSplitContextually() { + return ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get() || + com.android.wm.shell.Flags.enableSplitContextual(); + } + + public static final BooleanFlag ENABLE_TRACKPAD_GESTURE = getDebugFlag(271010401, + "ENABLE_TRACKPAD_GESTURE", ENABLED, "Enables trackpad gesture."); + + // TODO(Block 29): Clean up flags + public static final BooleanFlag ENABLE_ALL_APPS_BUTTON_IN_HOTSEAT = getDebugFlag(270393897, + "ENABLE_ALL_APPS_BUTTON_IN_HOTSEAT", DISABLED, + "Enables displaying the all apps button in the hotseat."); + + public static final BooleanFlag ENABLE_KEYBOARD_QUICK_SWITCH = getDebugFlag(270396844, + "ENABLE_KEYBOARD_QUICK_SWITCH", ENABLED, "Enables keyboard quick switching"); + + public static final BooleanFlag ENABLE_KEYBOARD_TASKBAR_TOGGLE = getDebugFlag(281726846, + "ENABLE_KEYBOARD_TASKBAR_TOGGLE", ENABLED, + "Enables keyboard taskbar stash toggling"); + + // TODO(Block 30): Clean up flags + public static final BooleanFlag USE_SEARCH_REQUEST_TIMEOUT_OVERRIDES = getDebugFlag(270395010, + "USE_SEARCH_REQUEST_TIMEOUT_OVERRIDES", DISABLED, + "Use local overrides for search request timeout"); + + // TODO(Block 31) + public static final BooleanFlag ENABLE_SPLIT_LAUNCH_DATA_REFACTOR = getDebugFlag(279494325, + "ENABLE_SPLIT_LAUNCH_DATA_REFACTOR", DISABLED, + "Use refactored split launching code path"); + + // TODO(Block 32): Empty block + + // TODO(Block 32): Clean up flags + // Aconfig migration complete for ENABLE_RESPONSIVE_WORKSPACE. + @VisibleForTesting + public static final BooleanFlag ENABLE_RESPONSIVE_WORKSPACE = getDebugFlag(241386436, + "ENABLE_RESPONSIVE_WORKSPACE", ENABLED, + "Enables new workspace grid calculations method."); + + public static boolean enableResponsiveWorkspace() { + return ENABLE_RESPONSIVE_WORKSPACE.get() || Flags.enableResponsiveWorkspace(); + } + + // TODO(Block 33): Clean up flags + public static final BooleanFlag ENABLE_ALL_APPS_RV_PREINFLATION = getDebugFlag(288161355, + "ENABLE_ALL_APPS_RV_PREINFLATION", ENABLED, + "Enables preinflating all apps icons to avoid scrolling jank."); + public static final BooleanFlag ALL_APPS_GONE_VISIBILITY = getDebugFlag(291651514, + "ALL_APPS_GONE_VISIBILITY", ENABLED, + "Set all apps container view's hidden visibility to GONE instead of INVISIBLE."); + + public static BooleanFlag getDebugFlag( + int bugId, String key, BooleanFlag flagState, String description) { + return flagState; + } + + public static BooleanFlag getReleaseFlag( + int bugId, String key, BooleanFlag flagState, String description) { + return flagState; + } + + /** + * Enabled state for a flag + */ + public enum BooleanFlag { + ENABLED(true), + DISABLED(false); + + private final boolean mValue; + + BooleanFlag(boolean value) { + mValue = value; + } + + public boolean get() { + return mValue; + } } - } } diff --git a/src/com/android/launcher3/dot/FolderDotInfo.java b/src/com/android/launcher3/dot/FolderDotInfo.java index cb91db7654..54800a07a8 100644 --- a/src/com/android/launcher3/dot/FolderDotInfo.java +++ b/src/com/android/launcher3/dot/FolderDotInfo.java @@ -30,10 +30,6 @@ public class FolderDotInfo extends DotInfo { private int mNumNotifications; - public void reset() { - mNumNotifications = 0; - } - public void addDotInfo(DotInfo dotToAdd) { if (dotToAdd == null) { return; @@ -43,6 +39,15 @@ public class FolderDotInfo extends DotInfo { mNumNotifications, MIN_COUNT, DotInfo.MAX_COUNT); } + public void subtractDotInfo(DotInfo dotToSubtract) { + if (dotToSubtract == null) { + return; + } + mNumNotifications -= dotToSubtract.getNotificationKeys().size(); + mNumNotifications = Utilities.boundToRange( + mNumNotifications, MIN_COUNT, DotInfo.MAX_COUNT); + } + @Override public int getNotificationCount() { return mNumNotifications; diff --git a/src/com/android/launcher3/dragndrop/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java index 84f25b6a9d..56b2c5f267 100644 --- a/src/com/android/launcher3/dragndrop/AddItemActivity.java +++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java @@ -68,7 +68,7 @@ import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.PackageItemInfo; import com.android.launcher3.pm.PinRequestHelper; import com.android.launcher3.util.ApiWrapper; -import com.android.launcher3.util.ApplicationInfoWrapper; +import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.util.SystemUiController; import com.android.launcher3.views.AbstractSlideInView; import com.android.launcher3.views.BaseDragLayer; @@ -164,8 +164,8 @@ public class AddItemActivity extends BaseActivity finish(); return; } - ApplicationInfo info = new ApplicationInfoWrapper( - this, targetApp.packageName, targetApp.user).getInfo(); + ApplicationInfo info = PackageManagerHelper.INSTANCE.get(this) + .getApplicationInfo(targetApp.packageName, targetApp.user, 0); if (info == null) { finish(); return; @@ -281,7 +281,7 @@ public class AddItemActivity extends BaseActivity new PinShortcutRequestActivityInfo(mRequest, this); mWidgetCell.getWidgetView().setTag(new PendingAddShortcutInfo(shortcutInfo)); applyWidgetItemAsync( - () -> new WidgetItem(shortcutInfo, mApp.getIconCache())); + () -> new WidgetItem(shortcutInfo, mApp.getIconCache(), getPackageManager())); return new PackageItemInfo(mRequest.getShortcutInfo().getPackage(), mRequest.getShortcutInfo().getUserHandle()); } diff --git a/src/com/android/launcher3/dragndrop/BaseItemDragListener.java b/src/com/android/launcher3/dragndrop/BaseItemDragListener.java index 43c148a762..981e3a65a4 100644 --- a/src/com/android/launcher3/dragndrop/BaseItemDragListener.java +++ b/src/com/android/launcher3/dragndrop/BaseItemDragListener.java @@ -34,7 +34,7 @@ import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.DragSource; import com.android.launcher3.DropTarget.DragObject; import com.android.launcher3.Launcher; -import com.android.launcher3.util.ContextTracker.SchedulerCallback; +import com.android.launcher3.util.ActivityTracker.SchedulerCallback; import com.android.launcher3.widget.PendingItemDragHelper; import java.util.UUID; @@ -74,9 +74,9 @@ public abstract class BaseItemDragListener implements View.OnDragListener, DragS } @Override - public boolean init(Launcher launcher, boolean isHomeStarted) { - AbstractFloatingView.closeAllOpenViews(launcher, /* animate= */ isHomeStarted); - launcher.getStateManager().goToState(NORMAL, /* animated= */ isHomeStarted); + public boolean init(Launcher launcher, boolean alreadyOnHome) { + AbstractFloatingView.closeAllOpenViews(launcher, alreadyOnHome); + launcher.getStateManager().goToState(NORMAL, alreadyOnHome /* animated */); launcher.getDragLayer().setOnDragListener(this); launcher.getRotationHelper().setStateHandlerRequest(REQUEST_LOCK); diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java index 92fae66dd5..7424808644 100644 --- a/src/com/android/launcher3/dragndrop/DragController.java +++ b/src/com/android/launcher3/dragndrop/DragController.java @@ -16,7 +16,6 @@ package com.android.launcher3.dragndrop; -import static com.android.launcher3.Flags.removeAppsRefreshOnRightClick; import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_NOT_PINNABLE; import android.graphics.Point; @@ -28,7 +27,6 @@ import android.view.MotionEvent; import android.view.View; import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; import com.android.app.animation.Interpolators; import com.android.launcher3.DeleteDropTarget; @@ -79,9 +77,8 @@ public abstract class DragController */ protected DragDriver mDragDriver = null; - @VisibleForTesting /** Options controlling the drag behavior. */ - public DragOptions mOptions; + protected DragOptions mOptions; /** Coordinate for motion down event */ protected final Point mMotionDown = new Point(); @@ -90,8 +87,7 @@ public abstract class DragController protected final Point mTmpPoint = new Point(); - @VisibleForTesting - public DropTarget.DragObject mDragObject; + protected DropTarget.DragObject mDragObject; /** Who can receive drop events */ private final ArrayList mDropTargets = new ArrayList<>(); @@ -537,21 +533,17 @@ public abstract class DragController mDragObject.dragComplete = true; if (mIsInPreDrag) { - if (removeAppsRefreshOnRightClick()) { - mDragObject.cancelled = true; - } else { - if (dropTarget != null) { - dropTarget.onDragExit(mDragObject); - } - return; + if (dropTarget != null) { + dropTarget.onDragExit(mDragObject); } + return; } // Drop onto the target. boolean accepted = false; if (dropTarget != null) { dropTarget.onDragExit(mDragObject); - if (!mIsInPreDrag && dropTarget.acceptDrop(mDragObject)) { + if (dropTarget.acceptDrop(mDragObject)) { if (flingAnimation != null) { flingAnimation.run(); } else { @@ -563,10 +555,9 @@ public abstract class DragController cancelDrag(); } } - - final View dropTargetAsView = dropTarget.getDropView(); - dispatchDropComplete(dropTargetAsView, accepted); } + final View dropTargetAsView = dropTarget instanceof View ? (View) dropTarget : null; + dispatchDropComplete(dropTargetAsView, accepted); } private boolean isNeedCancelDrag(ItemInfo item){ @@ -588,7 +579,7 @@ public abstract class DragController target.getHitRectRelativeToDragLayer(r); if (r.contains(x, y)) { - mActivity.getDragLayer().mapCoordInSelfToDescendant(target.getDropView(), + mActivity.getDragLayer().mapCoordInSelfToDescendant((View) target, mCoordinatesTemp); mDragObject.x = mCoordinatesTemp[0]; mDragObject.y = mCoordinatesTemp[1]; diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java index 41b42b659b..3910da1c2b 100644 --- a/src/com/android/launcher3/dragndrop/DragLayer.java +++ b/src/com/android/launcher3/dragndrop/DragLayer.java @@ -50,7 +50,6 @@ import com.android.launcher3.DropTargetBar; import com.android.launcher3.Launcher; import com.android.launcher3.R; import com.android.launcher3.ShortcutAndWidgetContainer; -import com.android.launcher3.ShortcutAndWidgetContainer.TranslationProvider; import com.android.launcher3.Utilities; import com.android.launcher3.Workspace; import com.android.launcher3.anim.PendingAnimation; @@ -70,9 +69,7 @@ import java.util.ArrayList; public class DragLayer extends BaseDragLayer implements LauncherOverlayCallbacks { public static final int ALPHA_INDEX_OVERLAY = 0; - - public static final int ALPHA_INDEX_LOADER = 1; - private static final int ALPHA_CHANNEL_COUNT = 2; + private static final int ALPHA_CHANNEL_COUNT = 1; public static final int ANIMATION_END_DISAPPEAR = 0; public static final int ANIMATION_END_REMAIN_VISIBLE = 2; @@ -124,7 +121,7 @@ public class DragLayer extends BaseDragLayer implements LauncherOverla @Override public void recreateControllers() { - mControllers = mContainer.createTouchControllers(); + mControllers = mActivity.createTouchControllers(); } public ViewGroupFocusHelper getFocusIndicatorHelper() { @@ -137,15 +134,15 @@ public class DragLayer extends BaseDragLayer implements LauncherOverla } private boolean isEventOverAccessibleDropTargetBar(MotionEvent ev) { - return isInAccessibleDrag() && isEventOverView(mContainer.getDropTargetBar(), ev); + return isInAccessibleDrag() && isEventOverView(mActivity.getDropTargetBar(), ev); } @Override public boolean onInterceptHoverEvent(MotionEvent ev) { - if (mContainer == null || mContainer.getWorkspace() == null) { + if (mActivity == null || mActivity.getWorkspace() == null) { return false; } - AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mContainer); + AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity); if (!(topView instanceof Folder)) { return false; } else { @@ -160,8 +157,7 @@ public class DragLayer extends BaseDragLayer implements LauncherOverla isOverFolderOrSearchBar = isEventOverView(topView, ev) || isEventOverAccessibleDropTargetBar(ev); if (!isOverFolderOrSearchBar) { - sendTapOutsideFolderAccessibilityEvent( - currentFolder.getIsEditingName()); + sendTapOutsideFolderAccessibilityEvent(currentFolder.getIsEditingName()); mHoverPointClosesFolder = true; return true; } @@ -171,8 +167,7 @@ public class DragLayer extends BaseDragLayer implements LauncherOverla isOverFolderOrSearchBar = isEventOverView(topView, ev) || isEventOverAccessibleDropTargetBar(ev); if (!isOverFolderOrSearchBar && !mHoverPointClosesFolder) { - sendTapOutsideFolderAccessibilityEvent( - currentFolder.getIsEditingName()); + sendTapOutsideFolderAccessibilityEvent(currentFolder.getIsEditingName()); mHoverPointClosesFolder = true; return true; } else if (!isOverFolderOrSearchBar) { @@ -200,7 +195,7 @@ public class DragLayer extends BaseDragLayer implements LauncherOverla private boolean isInAccessibleDrag() { - return mContainer.getAccessibilityDelegate().isInAccessibleDrag(); + return mActivity.getAccessibilityDelegate().isInAccessibleDrag(); } @Override @@ -213,12 +208,12 @@ public class DragLayer extends BaseDragLayer implements LauncherOverla @Override public void addChildrenForAccessibility(ArrayList childrenForAccessibility) { - View topView = AbstractFloatingView.getTopOpenViewWithType(mContainer, + View topView = AbstractFloatingView.getTopOpenViewWithType(mActivity, AbstractFloatingView.TYPE_ACCESSIBLE); if (topView != null) { addAccessibleChildToList(topView, childrenForAccessibility); if (isInAccessibleDrag()) { - addAccessibleChildToList(mContainer.getDropTargetBar(), childrenForAccessibility); + addAccessibleChildToList(mActivity.getDropTargetBar(), childrenForAccessibility); } } else { super.addChildrenForAccessibility(childrenForAccessibility); @@ -249,27 +244,23 @@ public class DragLayer extends BaseDragLayer implements LauncherOverla public void animateViewIntoPosition(DragView dragView, final View child, int duration, View anchorView) { - ShortcutAndWidgetContainer childParent = (ShortcutAndWidgetContainer) child.getParent(); + ShortcutAndWidgetContainer parentChildren = (ShortcutAndWidgetContainer) child.getParent(); CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams(); - childParent.measureChild(child); - childParent.layoutChild(child); + parentChildren.measureChild(child); + parentChildren.layoutChild(child); float coord[] = new float[2]; float childScale = child.getScaleX(); coord[0] = lp.x + (child.getMeasuredWidth() * (1 - childScale) / 2); coord[1] = lp.y + (child.getMeasuredHeight() * (1 - childScale) / 2); - TranslationProvider translationProvider = childParent.getTranslationProvider(); - if (translationProvider != null) { - coord[0] = coord[0] + translationProvider.getTranslationX(lp.getCellX()); - } // Since the child hasn't necessarily been laid out, we force the lp to be updated with // the correct coordinates (above) and use these to determine the final location float scale = getDescendantCoordRelativeToSelf((View) child.getParent(), coord); // We need to account for the scale of the child itself, as the above only accounts for - // the scale in parents. + // for the scale in parents. scale *= childScale; int toX = Math.round(coord[0]); int toY = Math.round(coord[1]); @@ -427,14 +418,14 @@ public class DragLayer extends BaseDragLayer implements LauncherOverla public void onViewAdded(View child) { super.onViewAdded(child); updateChildIndices(); - mContainer.onDragLayerHierarchyChanged(); + mActivity.onDragLayerHierarchyChanged(); } @Override public void onViewRemoved(View child) { super.onViewRemoved(child); updateChildIndices(); - mContainer.onDragLayerHierarchyChanged(); + mActivity.onDragLayerHierarchyChanged(); } @Override diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java index 2b7e218491..fd02972363 100644 --- a/src/com/android/launcher3/dragndrop/DragView.java +++ b/src/com/android/launcher3/dragndrop/DragView.java @@ -60,9 +60,8 @@ import androidx.dynamicanimation.animation.SpringForce; import com.android.app.animation.Interpolators; import com.android.launcher3.R; import com.android.launcher3.Utilities; -import com.android.launcher3.graphics.ThemeManager; import com.android.launcher3.icons.FastBitmapDrawable; -import com.android.launcher3.icons.IconNormalizer; +import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.util.RunnableList; import com.android.launcher3.views.ActivityContext; @@ -120,8 +119,8 @@ public abstract class DragView extends Fram private Drawable mBadge; public DragView(T launcher, Drawable drawable, int registrationX, - int registrationY, final float initialScale, final float scaleOnDrop, - final float finalScaleDps) { + int registrationY, final float initialScale, final float scaleOnDrop, + final float finalScaleDps) { this(launcher, getViewFromDrawable(launcher, drawable), drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), registrationX, registrationY, initialScale, scaleOnDrop, finalScaleDps); @@ -143,8 +142,8 @@ public abstract class DragView extends Fram * @param finalScaleDps the scale used in the zoom out animation when the drag view is shown. */ public DragView(T activity, View content, int width, int height, int registrationX, - int registrationY, final float initialScale, final float scaleOnDrop, - final float finalScaleDps) { + int registrationY, final float initialScale, final float scaleOnDrop, + final float finalScaleDps) { super(activity); mActivity = activity; mDragLayer = activity.getDragLayer(); @@ -227,6 +226,7 @@ public abstract class DragView extends Fram measure(makeMeasureSpec(width, EXACTLY), makeMeasureSpec(height, EXACTLY)); mBlurSizeOutline = getResources().getDimensionPixelSize(R.dimen.blur_size_medium_outline); + setElevation(getResources().getDimension(R.dimen.drag_elevation)); setWillNotDraw(false); } @@ -248,12 +248,10 @@ public abstract class DragView extends Fram public void setItemInfo(final ItemInfo info) { // Load the adaptive icon on a background thread and add the view in ui thread. MODEL_EXECUTOR.getHandler().postAtFrontOfQueue(() -> { - ThemeManager themeManager = ThemeManager.INSTANCE.get(getContext()); int w = mWidth; int h = mHeight; Pair fullDrawable = Utilities.getFullDrawable( - mActivity, info, w, h, - themeManager.isIconThemeEnabled()); + mActivity, info, w, h, true /* shouldThemeIcon */); if (fullDrawable != null) { AdaptiveIconDrawable adaptiveIcon = fullDrawable.first; int blurMargin = (int) mActivity.getResources() @@ -265,17 +263,20 @@ public abstract class DragView extends Fram // be scaled down due to icon normalization. mBadge = fullDrawable.second; FastBitmapDrawable.setBadgeBounds(mBadge, bounds); - Utilities.scaleRectAboutCenter(bounds, IconNormalizer.ICON_VISIBLE_AREA_FACTOR); + + try (LauncherIcons li = LauncherIcons.obtain(mActivity)) { + // Since we just want the scale, avoid heavy drawing operations + Utilities.scaleRectAboutCenter(bounds, li.getNormalizer().getScale( + new CustomAdaptiveIconDrawable (new ColorDrawable(Color.BLACK), null), + null, null, null)); + } // Shrink very tiny bit so that the clip path is smaller than the original bitmap // that has anti aliased edges and shadows. Rect shrunkBounds = new Rect(bounds); Utilities.scaleRectAboutCenter(shrunkBounds, 0.98f); adaptiveIcon.setBounds(shrunkBounds); - - final Path mask = (adaptiveIcon instanceof FolderAdaptiveIcon - ? themeManager.getFolderShape() : themeManager.getIconShape()) - .getPath(shrunkBounds); + final Path mask = adaptiveIcon.getIconMask(); mTranslateX = new SpringFloatValue(DragView.this, w * AdaptiveIconDrawable.getExtraInsetFraction()); @@ -461,7 +462,7 @@ public abstract class DragView extends Fram * Animate this DragView to the given DragLayer coordinates and then remove it. */ public abstract void animateTo(int toTouchX, int toTouchY, Runnable onCompleteRunnable, - int duration); + int duration); public void animateShift(final int shiftX, final int shiftY) { if (mShiftAnim.isStarted()) return; @@ -565,7 +566,7 @@ public abstract class DragView extends Fram return mContentViewParent; } - /** Return true if {@link #mContent} is a {@link AppWidgetHostView}. */ + /** Return true if {@link mContent} is a {@link AppWidgetHostView}. */ public boolean containsAppWidgetHostView() { return mContent instanceof AppWidgetHostView; } diff --git a/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java b/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java index df4dd57a24..5622504741 100644 --- a/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java +++ b/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java @@ -20,13 +20,9 @@ import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import android.annotation.TargetApi; import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.ColorFilter; +import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Path; -import android.graphics.Path.Direction; -import android.graphics.Picture; -import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.AdaptiveIconDrawable; @@ -35,9 +31,11 @@ import android.os.Build; import android.util.Log; import androidx.annotation.Nullable; -import androidx.annotation.UiThread; import com.android.launcher3.folder.FolderIcon; +import com.android.launcher3.folder.PreviewBackground; +import com.android.launcher3.graphics.IconShape; +import com.android.launcher3.graphics.ShiftedBitmapDrawable; import com.android.launcher3.icons.BitmapRenderer; import com.android.launcher3.util.Preconditions; import com.android.launcher3.views.ActivityContext; @@ -48,7 +46,7 @@ import app.lawnchair.icons.CustomAdaptiveIconDrawable; * {@link AdaptiveIconDrawable} representation of a {@link FolderIcon} */ @TargetApi(Build.VERSION_CODES.O) -public class FolderAdaptiveIcon extends AdaptiveIconDrawable { +public class FolderAdaptiveIcon extends CustomAdaptiveIconDrawable { private static final String TAG = "FolderAdaptiveIcon"; private final Drawable mBadge; @@ -75,97 +73,85 @@ public class FolderAdaptiveIcon extends AdaptiveIconDrawable { } public static @Nullable FolderAdaptiveIcon createFolderAdaptiveIcon( - ActivityContext activity, int folderId, Point size) { + ActivityContext activity, int folderId, Point dragViewSize) { Preconditions.assertNonUiThread(); - // assume square - if (size.x != size.y) { - return null; - } - int requestedSize = size.x; - - // Only use the size actually needed for drawing the folder icon - int drawingSize = activity.getDeviceProfile().folderIconSizePx; - int foregroundSize = Math.max(requestedSize, drawingSize); - float shift = foregroundSize - requestedSize; - - Picture background = new Picture(); - Picture foreground = new Picture(); - Picture badge = new Picture(); - - Canvas bgCanvas = background.beginRecording(requestedSize, requestedSize); - Canvas badgeCanvas = badge.beginRecording(requestedSize, requestedSize); - - Canvas fgCanvas = foreground.beginRecording(foregroundSize, foregroundSize); - fgCanvas.translate(shift, shift); - - // Do not clip the folder drawing since the icon previews extend outside the background. - Path mask = new Path(); - mask.addRect(-shift, -shift, requestedSize + shift, requestedSize + shift, - Direction.CCW); - - // Initialize the actual draw commands on the UI thread to avoid race conditions with + // Create the actual drawable on the UI thread to avoid race conditions with // FolderIcon draw pass try { - MAIN_EXECUTOR.submit(() -> { + return MAIN_EXECUTOR.submit(() -> { FolderIcon icon = activity.findFolderIcon(folderId); - if (icon == null) { - throw new IllegalArgumentException("Folder not found with id: " + folderId); - } - initLayersOnUiThread(icon, requestedSize, bgCanvas, fgCanvas, badgeCanvas); + return icon == null ? null : createDrawableOnUiThread(icon, dragViewSize); + }).get(); } catch (Exception e) { Log.e(TAG, "Unable to create folder icon", e); return null; - } finally { - background.endRecording(); - foreground.endRecording(); - badge.endRecording(); } - - // Only convert foreground to a bitmap as it can contain multiple draw commands. Other - // layers either draw a nothing or a single draw call. - Bitmap fgBitmap = Bitmap.createBitmap(foreground); - Paint foregroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - - // Do not use PictureDrawable as it moves the picture to the canvas bounds, whereas we want - // to draw it at (0,0) - return new FolderAdaptiveIcon( - new BitmapRendererDrawable(c -> c.drawPicture(background)), - new BitmapRendererDrawable( - c -> c.drawBitmap(fgBitmap, -shift, -shift, foregroundPaint)), - new BitmapRendererDrawable(c -> c.drawPicture(badge)), - mask); } - @UiThread - private static void initLayersOnUiThread(FolderIcon icon, int size, - Canvas backgroundCanvas, Canvas foregroundCanvas, Canvas badgeCanvas) { + private static FolderAdaptiveIcon createDrawableOnUiThread(FolderIcon icon, + Point dragViewSize) { + Preconditions.assertUIThread(); + icon.getPreviewBounds(sTmpRect); + + PreviewBackground bg = icon.getFolderBackground(); + + // assume square + assert (dragViewSize.x == dragViewSize.y); final int previewSize = sTmpRect.width(); - final int margin = (size - previewSize) / 2; + final int margin = (dragViewSize.x - previewSize) / 2; final float previewShiftX = -sTmpRect.left + margin; final float previewShiftY = -sTmpRect.top + margin; // Initialize badge, which consists of the outline stroke, shadow and dot; these // must be rendered above the foreground - badgeCanvas.save(); - badgeCanvas.translate(previewShiftX, previewShiftY); - icon.drawDot(badgeCanvas); - badgeCanvas.restore(); + Bitmap badgeBmp = BitmapRenderer.createHardwareBitmap(dragViewSize.x, dragViewSize.y, + (canvas) -> { + canvas.save(); + canvas.translate(previewShiftX, previewShiftY); + bg.drawShadow(canvas); + bg.drawBackgroundStroke(canvas); + icon.drawDot(canvas); + canvas.restore(); + }); - // Draw foreground - foregroundCanvas.save(); - foregroundCanvas.translate(previewShiftX, previewShiftY); - icon.getPreviewItemManager().draw(foregroundCanvas); - foregroundCanvas.restore(); + // Initialize mask + Path mask = new Path(); + Matrix m = new Matrix(); + m.setTranslate(previewShiftX, previewShiftY); + bg.getClipPath().transform(m, mask); - // Draw background - backgroundCanvas.save(); - backgroundCanvas.translate(previewShiftX, previewShiftY); - icon.getFolderBackground().drawBackground(backgroundCanvas); - backgroundCanvas.restore(); + Bitmap previewBitmap = BitmapRenderer.createHardwareBitmap(dragViewSize.x, dragViewSize.y, + (canvas) -> { + canvas.save(); + canvas.translate(previewShiftX, previewShiftY); + icon.getPreviewItemManager().draw(canvas); + canvas.restore(); + }); + + Bitmap bgBitmap = BitmapRenderer.createHardwareBitmap(dragViewSize.x, dragViewSize.y, + (canvas) -> { + Paint p = new Paint(); + p.setColor(bg.getBgColor()); + + Path bgPath = new Path(); + int radius = bg.getRadius(); + IconShape.INSTANCE.get (icon.getContext()).getShape().addToPath( + bgPath, + dragViewSize.x / 2f - radius, + dragViewSize.y / 2f - radius, + radius); + canvas.drawPath(bgPath, p); + }); + + ShiftedBitmapDrawable badge = new ShiftedBitmapDrawable(badgeBmp, 0, 0); + ShiftedBitmapDrawable foreground = new ShiftedBitmapDrawable(previewBitmap, 0, 0); + ShiftedBitmapDrawable background = new ShiftedBitmapDrawable(bgBitmap, 0, 0); + + return new FolderAdaptiveIcon(background, foreground, badge, mask); } @Override @@ -198,52 +184,4 @@ public class FolderAdaptiveIcon extends AdaptiveIconDrawable { & mBadge.getChangingConfigurations(); } } - - private static class BitmapRendererDrawable extends Drawable { - - private final BitmapRenderer mRenderer; - - BitmapRendererDrawable(BitmapRenderer renderer) { - mRenderer = renderer; - } - - @Override - public void draw(Canvas canvas) { - mRenderer.draw(canvas); - } - - @Override - public void setAlpha(int i) { } - - @Override - public void setColorFilter(ColorFilter colorFilter) { } - - @Override - public int getOpacity() { - return PixelFormat.TRANSLUCENT; - } - - @Override - public ConstantState getConstantState() { - return new MyConstantState(mRenderer); - } - - private static class MyConstantState extends ConstantState { - private final BitmapRenderer mRenderer; - - MyConstantState(BitmapRenderer renderer) { - mRenderer = renderer; - } - - @Override - public Drawable newDrawable() { - return new BitmapRendererDrawable(mRenderer); - } - - @Override - public int getChangingConfigurations() { - return 0; - } - } - } } diff --git a/src/com/android/launcher3/dragndrop/LauncherDragController.java b/src/com/android/launcher3/dragndrop/LauncherDragController.java index dd433c02d3..29fc6139f7 100644 --- a/src/com/android/launcher3/dragndrop/LauncherDragController.java +++ b/src/com/android/launcher3/dragndrop/LauncherDragController.java @@ -15,10 +15,7 @@ */ package com.android.launcher3.dragndrop; -import static android.view.View.VISIBLE; - import static com.android.launcher3.AbstractFloatingView.TYPE_DISCOVERY_BOUNCE; -import static com.android.launcher3.Flags.removeAppsRefreshOnRightClick; import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY; import static com.android.launcher3.LauncherState.EDIT_MODE; import static com.android.launcher3.LauncherState.NORMAL; @@ -28,7 +25,6 @@ import android.content.res.Resources; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.view.HapticFeedbackConstants; -import android.view.MotionEvent; import android.view.View; import androidx.annotation.Nullable; @@ -37,13 +33,10 @@ import androidx.annotation.VisibleForTesting; import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.DragSource; import com.android.launcher3.DropTarget; -import com.android.launcher3.DropTarget.DragObject; import com.android.launcher3.Launcher; import com.android.launcher3.R; import com.android.launcher3.accessibility.DragViewStateAnnouncer; -import com.android.launcher3.dragndrop.DragOptions.PreDragCondition; import com.android.launcher3.model.data.ItemInfo; -import com.android.launcher3.util.TouchUtil; import com.android.launcher3.widget.util.WidgetDragScaleUtils; /** @@ -54,9 +47,6 @@ public class LauncherDragController extends DragController { private static final boolean PROFILE_DRAWING_DURING_DRAG = false; private final FlingToDeleteHelper mFlingToDeleteHelper; - /** Whether or not the drag operation is triggered by mouse right click. */ - private boolean mIsInMouseRightClick = false; - public LauncherDragController(Launcher launcher) { super(launcher); mFlingToDeleteHelper = new FlingToDeleteHelper(launcher); @@ -79,28 +69,6 @@ public class LauncherDragController extends DragController { android.os.Debug.startMethodTracing("Launcher"); } - if (removeAppsRefreshOnRightClick() && mIsInMouseRightClick - && options.preDragCondition == null - && originalView instanceof View v) { - options.preDragCondition = new PreDragCondition() { - - @Override - public boolean shouldStartDrag(double distanceDragged) { - return false; - } - - @Override - public void onPreDragStart(DragObject dragObject) { - // Set it to visible so the text of FolderIcon would not flash (avoid it from - // being invisible and then visible) - v.setVisibility(VISIBLE); - } - - @Override - public void onPreDragEnd(DragObject dragObject, boolean dragStarted) { } - }; - } - mActivity.hideKeyboard(); AbstractFloatingView.closeOpenViews(mActivity, false, TYPE_DISCOVERY_BOUNCE); @@ -151,9 +119,6 @@ public class LauncherDragController extends DragController { initialDragViewScale, dragViewScaleOnDrop, scalePx); - // During a drag, we don't want to expose the descendendants of drag view to a11y users, - // since those decendents are not a valid position in the workspace. - dragView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); dragView.setItemInfo(dragInfo); mDragObject.dragComplete = false; @@ -223,7 +188,7 @@ public class LauncherDragController extends DragController { @Override protected void exitDrag() { - if (!mIsInPreDrag && !mActivity.isInState(EDIT_MODE)) { + if (!mActivity.isInState(EDIT_MODE)) { mActivity.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY); } } @@ -250,13 +215,4 @@ public class LauncherDragController extends DragController { dropCoordinates); return mActivity.getWorkspace(); } - - /** - * Intercepts touch events from a drag source view. - */ - @Override - public boolean onControllerInterceptTouchEvent(MotionEvent ev) { - mIsInMouseRightClick = TouchUtil.isMouseRightClickDownOrMove(ev); - return super.onControllerInterceptTouchEvent(ev); - } } diff --git a/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java index f46fa8a977..4f63a89a37 100644 --- a/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java +++ b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java @@ -30,6 +30,7 @@ import android.content.Context; import android.content.Intent; import android.content.pm.LauncherApps; import android.content.pm.LauncherApps.PinItemRequest; +import android.content.pm.PackageManager; import android.content.pm.ShortcutInfo; import android.graphics.drawable.Drawable; import android.os.Build; @@ -39,7 +40,7 @@ import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherSettings; import com.android.launcher3.R; -import com.android.launcher3.icons.cache.BaseIconCache; +import com.android.launcher3.icons.IconCache; import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.pm.PinRequestHelper; import com.android.launcher3.pm.ShortcutConfigActivityInfo; @@ -69,8 +70,7 @@ public class PinShortcutRequestActivityInfo extends ShortcutConfigActivityInfo { public PinShortcutRequestActivityInfo( ShortcutInfo si, Supplier requestSupplier, Context context) { - super(new ComponentName(si.getPackage(), STUB_COMPONENT_CLASS), - si.getUserHandle(), context); + super(new ComponentName(si.getPackage(), STUB_COMPONENT_CLASS), si.getUserHandle()); mRequestSupplier = requestSupplier; mInfo = si; mContext = context; @@ -82,12 +82,12 @@ public class PinShortcutRequestActivityInfo extends ShortcutConfigActivityInfo { } @Override - public CharSequence getLabel() { + public CharSequence getLabel(PackageManager pm) { return mInfo.getShortLabel(); } @Override - public Drawable getFullResIcon(BaseIconCache cache) { + public Drawable getFullResIcon(IconCache cache) { Drawable d = mContext.getSystemService(LauncherApps.class) .getShortcutIconDrawable(mInfo, LauncherAppState.getIDP(mContext).fillResIconDpi); if (d == null) { diff --git a/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java b/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java new file mode 100644 index 0000000000..fbe9e33d5a --- /dev/null +++ b/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.dragndrop; + +import com.android.launcher3.Alarm; +import com.android.launcher3.CellLayout; +import com.android.launcher3.Launcher; +import com.android.launcher3.OnAlarmListener; +import com.android.launcher3.Utilities; +import com.android.launcher3.Workspace; + +public class SpringLoadedDragController implements OnAlarmListener { + // how long the user must hover over a mini-screen before it unshrinks + private static final long ENTER_SPRING_LOAD_HOVER_TIME = 500; + private static final long ENTER_SPRING_LOAD_HOVER_TIME_IN_TEST = 2000; + private static final long ENTER_SPRING_LOAD_CANCEL_HOVER_TIME = 950; + + Alarm mAlarm; + + // the screen the user is currently hovering over, if any + private CellLayout mScreen; + private Launcher mLauncher; + + public SpringLoadedDragController(Launcher launcher) { + mLauncher = launcher; + mAlarm = new Alarm(); + mAlarm.setOnAlarmListener(this); + } + + private long getEnterSpringLoadHoverTime() { + // Some TAPL tests are flaky on Cuttlefish with a low waiting time + return Utilities.isRunningInTestHarness() + ? ENTER_SPRING_LOAD_HOVER_TIME_IN_TEST + : ENTER_SPRING_LOAD_HOVER_TIME; + } + + public void cancel() { + mAlarm.cancelAlarm(); + } + + // Set a new alarm to expire for the screen that we are hovering over now + public void setAlarm(CellLayout cl) { + mAlarm.cancelAlarm(); + mAlarm.setAlarm((cl == null) ? ENTER_SPRING_LOAD_CANCEL_HOVER_TIME + : getEnterSpringLoadHoverTime()); + mScreen = cl; + } + + // this is called when our timer runs out + public void onAlarm(Alarm alarm) { + if (mScreen != null) { + // Snap to the screen that we are hovering over now + Workspace w = mLauncher.getWorkspace(); + if (!w.isVisible(mScreen)) { + w.snapToPage(w.indexOfChild(mScreen)); + } + } else { + mLauncher.getDragController().cancelDrag(); + } + } +} diff --git a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java index fedc1181c2..8cd91d3a43 100644 --- a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java +++ b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java @@ -1,7 +1,5 @@ package com.android.launcher3.folder; -import com.android.launcher3.Flags; - public class ClippedFolderIconLayoutRule { public static final int MAX_NUM_ITEMS_IN_PREVIEW = 4; @@ -9,12 +7,9 @@ public class ClippedFolderIconLayoutRule { private static final float MIN_SCALE = 0.44f; private static final float MAX_SCALE = 0.51f; - // TODO: figure out exact radius for different icons - private static final float MAX_RADIUS_DILATION_SHAPES = 0.15f; private static final float MAX_RADIUS_DILATION = 0.25f; // The max amount of overlap the preview items can go outside of the background bounds. public static final float ICON_OVERLAP_FACTOR = 1 + (MAX_RADIUS_DILATION / 2f); - public static final float ICON_OVERLAP_FACTOR_SHAPES = 1f; private static final float ITEM_RADIUS_SCALE_FACTOR = 1.15f; public static final int EXIT_INDEX = -2; @@ -33,7 +28,7 @@ public class ClippedFolderIconLayoutRule { mRadius = ITEM_RADIUS_SCALE_FACTOR * availableSpace / 2f; mIconSize = intrinsicIconSize; mIsRtl = rtl; - mBaselineIconScale = availableSpace / intrinsicIconSize; + mBaselineIconScale = availableSpace / (intrinsicIconSize * 1f); } public PreviewItemDrawingParams computePreviewItemDrawingParams(int index, int curNumItems, @@ -53,20 +48,6 @@ public class ClippedFolderIconLayoutRule { } else if (index >= MAX_NUM_ITEMS_IN_PREVIEW) { // Items beyond those displayed in the preview are animated to the center mTmpPoint[0] = mTmpPoint[1] = mAvailableSpace / 2 - (mIconSize * totalScale) / 2; - } else if (Flags.enableLauncherIconShapes()) { - if (index == 0) { - // top left - getGridPosition(0, 0, mTmpPoint); - } else if (index == 1) { - // top right - getGridPosition(0, 1, mTmpPoint); - } else if (index == 2) { - // bottom left - getGridPosition(1, 0, mTmpPoint); - } else if (index == 3) { - // bottom right - getGridPosition(1, 1, mTmpPoint); - } } else { getPosition(index, curNumItems, mTmpPoint); } @@ -103,7 +84,6 @@ public class ClippedFolderIconLayoutRule { result[1] = top + (row * dy); } - // b/392610664 TODO: Change positioning from circular geometry to square / grid-based. private void getPosition(int index, int curNumItems, float[] result) { // The case of two items is homomorphic to the case of one. curNumItems = Math.max(curNumItems, 2); @@ -133,10 +113,8 @@ public class ClippedFolderIconLayoutRule { } // We bump the radius up between 0 and MAX_RADIUS_DILATION % as the number of items increase - float radiusDilation = Flags.enableLauncherIconShapes() ? MAX_RADIUS_DILATION_SHAPES - : MAX_RADIUS_DILATION; - float radius = mRadius * (1 + radiusDilation * (curNumItems - MIN_NUM_ITEMS_IN_PREVIEW) - / (MAX_NUM_ITEMS_IN_PREVIEW - MIN_NUM_ITEMS_IN_PREVIEW)); + float radius = mRadius * (1 + MAX_RADIUS_DILATION * (curNumItems - + MIN_NUM_ITEMS_IN_PREVIEW) / (MAX_NUM_ITEMS_IN_PREVIEW - MIN_NUM_ITEMS_IN_PREVIEW)); double theta = theta0 + index * (2 * Math.PI / curNumItems) * direction; float halfIconSize = (mIconSize * scaleForItem(curNumItems)) / 2; @@ -152,7 +130,7 @@ public class ClippedFolderIconLayoutRule { public float scaleForItem(int numItems) { // Scale is determined by the number of items in the preview. final float scale; - if (numItems <= 3 && !Flags.enableLauncherIconShapes()) { + if (numItems <= 3) { scale = MAX_SCALE; } else { scale = MIN_SCALE; @@ -163,15 +141,4 @@ public class ClippedFolderIconLayoutRule { public float getIconSize() { return mIconSize; } - - /** - * Gets correct constant for icon overlap. - */ - public static float getIconOverlapFactor() { - if (Flags.enableLauncherIconShapes()) { - return ICON_OVERLAP_FACTOR_SHAPES; - } else { - return ICON_OVERLAP_FACTOR; - } - } } diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java index d48361e1fe..3ab49b1f84 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -12,14 +12,18 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. + * + * Modifications copyright 2021, Lawnchair */ package com.android.launcher3.folder; import static android.text.TextUtils.isEmpty; -import static com.android.launcher3.Flags.enableLauncherVisualRefresh; import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY; +import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; +import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR; +import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT; import static com.android.launcher3.LauncherState.EDIT_MODE; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent; @@ -27,7 +31,6 @@ import static com.android.launcher3.config.FeatureFlags.ALWAYS_USE_HARDWARE_OPTI import static com.android.launcher3.folder.FolderGridOrganizer.createFolderGridOrganizer; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_LABEL_UPDATED; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED; -import static com.android.launcher3.model.data.FolderInfo.willAcceptItemType; import static com.android.launcher3.testing.shared.TestProtocol.FOLDER_OPENED_MESSAGE; import static com.android.launcher3.util.window.RefreshRateTracker.getSingleFrameMs; @@ -65,12 +68,11 @@ import android.view.inputmethod.EditorInfo; import android.widget.TextView; import androidx.annotation.IntDef; -import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.core.content.res.ResourcesCompat; - import androidx.core.view.WindowInsetsCompat; + import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.Alarm; import com.android.launcher3.CellLayout; @@ -96,24 +98,26 @@ import com.android.launcher3.logger.LauncherAtom.ToState; import com.android.launcher3.logging.StatsLogManager; import com.android.launcher3.logging.StatsLogManager.StatsLogger; import com.android.launcher3.model.data.FolderInfo; +import com.android.launcher3.model.data.FolderInfo.FolderListener; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.WorkspaceItemFactory; import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.pageindicators.PageIndicatorDots; import com.android.launcher3.util.Executors; -import com.android.launcher3.util.LauncherBindableItemsContainer; +import com.android.launcher3.util.LauncherBindableItemsContainer.ItemOperator; import com.android.launcher3.util.Themes; import com.android.launcher3.util.Thunk; import com.android.launcher3.views.ActivityContext; import com.android.launcher3.views.BaseDragLayer; import com.android.launcher3.views.ClipPathView; import com.android.launcher3.widget.PendingAddShortcutInfo; - import com.androidinternal.graphics.ColorUtils; +import com.patrykmichalik.opto.core.PreferenceExtensionsKt; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; -import java.util.Arrays; +import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Objects; @@ -121,7 +125,6 @@ import java.util.StringJoiner; import java.util.stream.Collectors; import java.util.stream.Stream; -import com.patrykmichalik.opto.core.PreferenceExtensionsKt; import app.lawnchair.preferences2.PreferenceManager2; import app.lawnchair.theme.color.ColorOption; import app.lawnchair.theme.color.tokens.ColorTokens; @@ -133,9 +136,8 @@ import app.lawnchair.util.LawnchairUtilsKt; * Represents a set of icons chosen by the user or generated by the system. */ public class Folder extends AbstractFloatingView implements ClipPathView, DragSource, - View.OnLongClickListener, DropTarget, TextView.OnEditorActionListener, - View.OnFocusChangeListener, DragListener, ExtendedEditText.OnBackKeyListener, - LauncherBindableItemsContainer { + View.OnLongClickListener, DropTarget, FolderListener, TextView.OnEditorActionListener, + View.OnFocusChangeListener, DragListener, ExtendedEditText.OnBackKeyListener { private static final String TAG = "Launcher.Folder"; private static final boolean DEBUG = false; @@ -190,6 +192,15 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo return o instanceof ItemInfo info && willAcceptItemType(info.itemType); } + /** + * Checks if {@code itemType} is a type that can be placed in folders. + */ + public static boolean willAcceptItemType(int itemType) { + return itemType == ITEM_TYPE_APPLICATION + || itemType == ITEM_TYPE_DEEP_SHORTCUT + || itemType == ITEM_TYPE_APP_PAIR; + } + private Alarm mReorderAlarm = new Alarm(Looper.getMainLooper()); private Alarm mOnExitAlarm = new Alarm(Looper.getMainLooper()); private Alarm mOnScrollHintAlarm = new Alarm(Looper.getMainLooper()); @@ -229,26 +240,23 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo private Path mClipPath; @ViewDebug.ExportedProperty(category = "launcher", - mapping = { - @ViewDebug.IntToString(from = STATE_CLOSED, to = "STATE_CLOSED"), - @ViewDebug.IntToString(from = STATE_ANIMATING, to = "STATE_ANIMATING"), - @ViewDebug.IntToString(from = STATE_OPEN, to = "STATE_OPEN"), - }) + mapping = { + @ViewDebug.IntToString(from = STATE_CLOSED, to = "STATE_CLOSED"), + @ViewDebug.IntToString(from = STATE_ANIMATING, to = "STATE_ANIMATING"), + @ViewDebug.IntToString(from = STATE_OPEN, to = "STATE_OPEN"), + }) private int mState = STATE_CLOSED; private final List mOnFolderStateChangedListeners = - new ArrayList<>(); + new ArrayList<>(); private OnFolderStateChangedListener mPriorityOnFolderStateChangedListener; @ViewDebug.ExportedProperty(category = "launcher") private boolean mRearrangeOnClose = false; - boolean mItemsInvalidated = false; + private boolean mItemsInvalidated = false; private View mCurrentDragView; private boolean mIsExternalDrag; private boolean mIsDragInProgress = false; private boolean mDeleteFolderOnDropCompleted = false; - private boolean mSuppressFolderDeletion = false; - private boolean mSuppressContentUpdate = false; - private boolean mItemAddedBackToSelfViaIcon = false; private boolean mIsEditingName = false; @@ -268,8 +276,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo @Nullable private KeyboardInsetAnimationCallback mKeyboardInsetAnimationCallback; - private @NonNull GradientDrawable mBackground; - + private GradientDrawable mBackground; PreferenceManager2 preferenceManager2; /** @@ -291,11 +298,6 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo // reliable behavior when clicking the text field (since it will always gain focus on // click). setFocusableInTouchMode(true); - - mBackground = (GradientDrawable) Objects.requireNonNull( - ResourcesCompat.getDrawable(getResources(), - R.drawable.round_rect_folder, getContext().getTheme())); - mBackground.setCallback(this); preferenceManager2 = PreferenceManager2.INSTANCE.get(context); } @@ -311,7 +313,6 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo final int paddingLeftRight = dp.folderContentPaddingLeftRight; mBackground = DrawableTokens.RoundRectFolder.resolve(getContext()); - // TODO-Myself: this is interesting, we can customise this. var alpha = LawnchairUtilsKt.getFolderBackgroundAlpha(getContext()); mBackground.setAlpha(alpha); @@ -320,13 +321,6 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo mContent.setFolder(this); mPageIndicator = findViewById(R.id.folder_page_indicator); - if (enableLauncherVisualRefresh()) { - MarginLayoutParams params = ((MarginLayoutParams) mPageIndicator.getLayoutParams()); - int horizontalMargin = getContext().getResources() - .getDimensionPixelSize(R.dimen.folder_footer_horiz_padding); - params.setMarginStart(horizontalMargin); - params.setMarginEnd(horizontalMargin); - } mFooter = findViewById(R.id.folder_footer); mFooterHeight = dp.folderFooterHeightPx; mFolderName = findViewById(R.id.folder_name); @@ -335,10 +329,14 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo mFolderName.setOnEditorActionListener(this); mFolderName.setSelectAllOnFocus(true); mFolderName.setInputType(mFolderName.getInputType() - & ~InputType.TYPE_TEXT_FLAG_AUTO_CORRECT - | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS - | InputType.TYPE_TEXT_FLAG_CAP_WORDS); + & ~InputType.TYPE_TEXT_FLAG_AUTO_CORRECT + | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS + | InputType.TYPE_TEXT_FLAG_CAP_WORDS); mFolderName.forceDisableSuggestions(true); + mFolderName.setPadding(mFolderName.getPaddingLeft(), + (getFooterHeight() - mFolderName.getLineHeight()) / 2, + mFolderName.getPaddingRight(), + (getFooterHeight() - mFolderName.getLineHeight()) / 2); @ColorInt int accentColor = Themes.getColorAccent(mFolderName.getContext()); @@ -378,11 +376,6 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo return true; } - @Override - protected boolean verifyDrawable(@NonNull Drawable who) { - return super.verifyDrawable(who) || (who == mBackground); - } - void callBeginDragShared(View v, DragOptions options) { mLauncherDelegate.beginDragShared(v, this, options); } @@ -393,14 +386,14 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo return; } getDragController().addDragListener(new AccessibleDragListenerAdapter( - mContent, FolderAccessibilityHelper::new) { + mContent, FolderAccessibilityHelper::new) { @Override protected void enableAccessibleDrag(boolean enable, - @Nullable DragObject dragObject) { + @Nullable DragObject dragObject) { super.enableAccessibleDrag(enable, dragObject); mFooter.setImportantForAccessibility(enable - ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS - : IMPORTANT_FOR_ACCESSIBILITY_AUTO); + ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS + : IMPORTANT_FOR_ACCESSIBILITY_AUTO); } }); } @@ -412,7 +405,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo } if (isInAppDrawer()) { close(true); - // LC-Note: Do not remove item + // Do not remove item return; } @@ -421,8 +414,9 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo // We do not want to get events for the item being removed, as they will get handled // when the drop completes - executeWithContentUpdateSuppressed(() -> removeFolderContent(true, dragObject.dragInfo)); - + try (SuppressInfoChanges s = new SuppressInfoChanges()) { + mInfo.remove(dragObject.dragInfo, true); + } mIsDragInProgress = true; mItemAddedBackToSelfViaIcon = false; } @@ -465,8 +459,8 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo } sendCustomAccessibilityEvent( - this, AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED, - getContext().getString(R.string.folder_renamed, newTitle)); + this, AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED, + getContext().getString(R.string.folder_renamed, newTitle)); // This ensures that focus is gained every time the field is clicked, which selects all // the text and brings up the soft keyboard if necessary. @@ -480,7 +474,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { if (DEBUG) { Log.d(TAG, "onEditorAction actionId=" + actionId + " key=" - + (event != null ? event.getKeyCode() : "null event")); + + (event != null ? event.getKeyCode() : "null event")); } if (actionId == EditorInfo.IME_ACTION_DONE) { mFolderName.dispatchBackKey(); @@ -505,7 +499,6 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo } } } catch (Throwable t) { - // LC-Catch WindowInsetsCompat insetsCompat = WindowInsetsCompat.toWindowInsetsCompat(windowInsets); if (insetsCompat.isVisible(WindowInsetsCompat.Type.ime())) { androidx.core.graphics.Insets keyboardInsets = insetsCompat.getInsets(WindowInsetsCompat.Type.ime()); @@ -578,6 +571,8 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo mInfo = info; mFromTitle = info.title; mFromLabelState = info.getFromLabelState(); + ArrayList children = info.getContents(); + Collections.sort(children, ITEM_POS_COMPARATOR); updateItemLocationsInDatabaseBatch(true); BaseDragLayer.LayoutParams lp = (BaseDragLayer.LayoutParams) getLayoutParams(); @@ -586,17 +581,8 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo lp.customPosition = true; setLayoutParams(lp); } - reapplyItemInfo(); - // In case any children didn't come across during loading, clean up the folder accordingly - mFolderIcon.post(() -> { - if (getItemCount() <= 1) { - replaceFolderWithFinalItem(); - } - }); - } - - public void reapplyItemInfo() { mItemsInvalidated = true; + mInfo.addListener(this); if (!isEmpty(mInfo.title)) { mFolderName.setText(mInfo.title); @@ -605,8 +591,15 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo mFolderName.setText(""); mFolderName.setHint(R.string.folder_hint_text); } + // In case any children didn't come across during loading, clean up the folder accordingly + mFolderIcon.post(() -> { + if (getItemCount() <= 1 && !isInAppDrawer()) { + replaceFolderWithFinalItem(); + } + }); } + /** * Show suggested folder title in FolderEditText if the first suggestion is non-empty, push * rest of the suggestions to InputMethodManager. @@ -626,12 +619,12 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo } mFolderName.showKeyboard(); mFolderName.displayCompletions( - Stream.of(mInfo.suggestedFolderNames.getLabels()) - .filter(Objects::nonNull) - .map(Object::toString) - .filter(s -> !s.isEmpty()) - .filter(s -> !s.equalsIgnoreCase(mFolderName.getText().toString())) - .collect(Collectors.toList())); + Stream.of(mInfo.suggestedFolderNames.getLabels()) + .filter(Objects::nonNull) + .map(Object::toString) + .filter(s -> !s.isEmpty()) + .filter(s -> !s.equalsIgnoreCase(mFolderName.getText().toString())) + .collect(Collectors.toList())); } } @@ -645,12 +638,12 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo @SuppressLint("InflateParams") static Folder fromXml(T activityContext) { return (Folder) LayoutInflater.from(activityContext).cloneInContext(activityContext) - .inflate(R.layout.user_folder_icon_normalized, null); + .inflate(R.layout.user_folder_icon_normalized, null); } private void addAnimationStartListeners(AnimatorSet a) { mLauncherDelegate.forEachVisibleWorkspacePage( - visiblePage -> addAnimatorListenerForPage(a, (CellLayout) visiblePage)); + visiblePage -> addAnimatorListenerForPage(a, (CellLayout) visiblePage)); a.addListener(new AnimatorListenerAdapter() { @Override @@ -736,11 +729,11 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo if (!shouldAnimateOpen(items)) { return; } + Folder openFolder = getOpen(mActivityContext); closeOpenFolder(openFolder); mContent.bindItems(items); - mContent.setCanAnnouncePageDescriptionForFolder(true); centerAboutIcon(); mItemsInvalidated = true; updateTextViewFocus(); @@ -756,27 +749,19 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo } else { if (FeatureFlags.IS_STUDIO_BUILD) { Log.e(TAG, "Opening folder (" + this + ") which already has a parent:" - + getParent()); + + getParent()); } } - Log.d("b/383526431", "animateOpen: content child count before: " - + mContent.getTotalChildCount()); - mContent.completePendingPageChanges(); mContent.setCurrentPage(pageNo); - Log.d("b/383526431", "animateOpen: content child count after pending page" - + " changes: " + mContent.getTotalChildCount()); - // This is set to true in close(), but isn't reset to false until onDropCompleted(). This // leads to an inconsistent state if you drag out of the folder and drag back in without // dropping. One resulting issue is that replaceFolderWithFinalItem() can be called twice. mDeleteFolderOnDropCompleted = false; cancelRunningAnimations(); - Log.d("b/383526431", "animateOpen: content child count after cancelling" - + " animation: " + mContent.getTotalChildCount()); FolderAnimationManager fam = new FolderAnimationManager(this, true /* isOpening */); AnimatorSet anim = fam.getAnimator(); anim.addListener(new AnimatorListenerAdapter() { @@ -791,7 +776,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo setState(STATE_OPEN); announceAccessibilityChanges(); AccessibilityManagerCompat.sendTestProtocolEventToTest(getContext(), - FOLDER_OPENED_MESSAGE); + FOLDER_OPENED_MESSAGE); mContent.setFocusOnFirstChild(); } @@ -800,7 +785,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo // Footer animation if (mContent.getPageCount() > 1 && !mInfo.hasOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION)) { int footerWidth = mContent.getDesiredWidth() - - mFooter.getPaddingLeft() - mFooter.getPaddingRight(); + - mFooter.getPaddingLeft() - mFooter.getPaddingRight(); float textWidth = mFolderName.getPaint().measureText(mFolderName.getText().toString()); float translation = (footerWidth - textWidth) / 2; @@ -816,14 +801,14 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo @Override public void onAnimationEnd(Animator animation) { mFolderName.animate().setDuration(FOLDER_NAME_ANIMATION_DURATION) - .translationX(0) - .setInterpolator(AnimationUtils.loadInterpolator( - getContext(), android.R.interpolator.fast_out_slow_in)); + .translationX(0) + .setInterpolator(AnimationUtils.loadInterpolator( + getContext(), android.R.interpolator.fast_out_slow_in)); mPageIndicator.playEntryAnimation(); if (updateAnimationFlag) { mInfo.setOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION, true, - mLauncherDelegate.getModelWriter()); + mLauncherDelegate.getModelWriter()); } } }); @@ -882,7 +867,6 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo @Override protected void handleClose(boolean animate) { mIsOpen = false; - mContent.setCanAnnouncePageDescriptionForFolder(false); if (!animate && mCurrentAnimator != null && mCurrentAnimator.isRunning()) { mCurrentAnimator.cancel(); @@ -906,7 +890,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo // Notify the accessibility manager that this folder "window" has disappeared and no // longer occludes the workspace items mActivityContext.getDragLayer().sendAccessibilityEvent( - AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); + AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); } private void cancelRunningAnimations() { @@ -961,7 +945,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo @Override protected Pair getAccessibilityTarget() { return Pair.create(mContent, mIsOpen ? mContent.getAccessibilityDescription() - : getContext().getString(R.string.folder_closed)); + : getContext().getString(R.string.folder_closed)); } @Override @@ -997,7 +981,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo mRearrangeOnClose = false; } if (getItemCount() <= 1) { - if (!mIsDragInProgress && !mSuppressFolderDeletion) { + if (!mIsDragInProgress && !mSuppressFolderDeletion && !isInAppDrawer()) { replaceFolderWithFinalItem(); } else if (mIsDragInProgress) { mDeleteFolderOnDropCompleted = true; @@ -1013,7 +997,9 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo @Override public boolean acceptDrop(DragObject d) { - return willAcceptItemType(d.dragInfo.itemType); + final ItemInfo item = d.dragInfo; + final int itemType = item.itemType; + return Folder.willAcceptItemType(itemType); } public void onDragEnter(DragObject d) { @@ -1038,7 +1024,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo private int getTargetRank(DragObject d, float[] recycle) { recycle = d.getVisualCenter(recycle); return mContent.findNearestArea( - (int) recycle[0] - getPaddingLeft(), (int) recycle[1] - getPaddingTop()); + (int) recycle[0] - getPaddingLeft(), (int) recycle[1] - getPaddingTop()); } @Override @@ -1057,7 +1043,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo if (d.stateAnnouncer != null) { d.stateAnnouncer.announce(getContext().getString(R.string.move_to_position, - mTargetRank + 1)); + mTargetRank + 1)); } } @@ -1065,14 +1051,14 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo int currentPage = mContent.getNextPage(); float cellOverlap = mContent.getCurrentCellLayout().getCellWidth() - * ICON_OVERSCROLL_WIDTH_FACTOR; + * ICON_OVERSCROLL_WIDTH_FACTOR; boolean isOutsideLeftEdge = x < cellOverlap; boolean isOutsideRightEdge = x > (getWidth() - cellOverlap); if (currentPage > 0 && (mContent.mIsRtl ? isOutsideRightEdge : isOutsideLeftEdge)) { showScrollHint(SCROLL_LEFT, d); } else if (currentPage < (mContent.getPageCount() - 1) - && (mContent.mIsRtl ? isOutsideLeftEdge : isOutsideRightEdge)) { + && (mContent.mIsRtl ? isOutsideLeftEdge : isOutsideRightEdge)) { showScrollHint(SCROLL_RIGHT, d); } else { mOnScrollHintAlarm.cancelAlarm(); @@ -1110,7 +1096,6 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo public void completeDragExit() { if (isInAppDrawer()) { - // LC: ff8c5a827b85f47a0d8ed5e6ac449ab8042705c6 return; } if (mIsOpen) { @@ -1161,29 +1146,28 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo @Override public void onDropCompleted(final View target, final DragObject d, - final boolean success) { + final boolean success) { if (success) { if (getItemCount() <= 1) { mDeleteFolderOnDropCompleted = true; } if (mDeleteFolderOnDropCompleted && !mItemAddedBackToSelfViaIcon - && target != this) { + && target != this) { replaceFolderWithFinalItem(); } } else { // The drag failed, we need to return the item to the folder ItemInfo info = d.dragInfo; View icon = (mCurrentDragView != null && mCurrentDragView.getTag() == info) - ? mCurrentDragView : mContent.createNewView(info); + ? mCurrentDragView : mContent.createNewView(info); ArrayList views = getIconsInReadingOrder(); - if (!views.contains(icon)) { - info.rank = Utilities.boundToRange(info.rank, 0, views.size()); - views.add(info.rank, icon); - mContent.arrangeChildren(views); - mItemsInvalidated = true; + info.rank = Utilities.boundToRange(info.rank, 0, views.size()); + views.add(info.rank, icon); + mContent.arrangeChildren(views); + mItemsInvalidated = true; - executeWithContentUpdateSuppressed( - () -> mFolderIcon.onDrop(d, true /* itemReturnedOnFailedDrop */)); + try (SuppressInfoChanges s = new SuppressInfoChanges()) { + mFolderIcon.onDrop(d, true /* itemReturnedOnFailedDrop */); } } @@ -1211,13 +1195,13 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo if (getItemCount() <= mContent.itemsPerPage()) { // Show the animation, next time something is added to the folder. mInfo.setOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION, false, - mLauncherDelegate.getModelWriter()); + mLauncherDelegate.getModelWriter()); } } private void updateItemLocationsInDatabaseBatch(boolean isBind) { FolderGridOrganizer verifier = createFolderGridOrganizer( - mActivityContext.getDeviceProfile()).setFolderInfo(mInfo); + mActivityContext.getDeviceProfile()).setFolderInfo(mInfo); ArrayList items = new ArrayList<>(); int total = mInfo.getContents().size(); @@ -1286,7 +1270,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo protected int getContentAreaHeight() { int height = Math.min(getMaxContentAreaHeight(), - mContent.getDesiredHeight()); + mContent.getDesiredHeight()); return Math.max(height, MIN_CONTENT_DIMEN); } @@ -1294,7 +1278,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo int getMaxContentAreaHeight() { DeviceProfile grid = mActivityContext.getDeviceProfile(); return grid.availableHeightPx - grid.getTotalWorkspacePadding().y - - getFooterHeight(); + - getFooterHeight(); } @VisibleForTesting @@ -1333,30 +1317,13 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo mContent.measure(contentAreaWidthSpec, contentAreaHeightSpec); mFooter.measure(contentAreaWidthSpec, - MeasureSpec.makeMeasureSpec(mFooterHeight, MeasureSpec.EXACTLY)); + MeasureSpec.makeMeasureSpec(mFooterHeight, MeasureSpec.EXACTLY)); int folderWidth = getPaddingLeft() + getPaddingRight() + contentWidth; int folderHeight = getFolderHeight(contentHeight); setMeasuredDimension(folderWidth, folderHeight); } - /** - * If the Folder Title has less than 100dp of available width, we hide it. The reason we do this - * calculation in onSizeChange is because this callback is called 1x when the folder is opened. - *

- * The PageIndicator and the Folder Title share the same horizontal linear layout, but both - * are dynamically sized. Therefore, we are setting visibility of the folder title AFTER the - * layout is measured. - */ - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - super.onSizeChanged(w, h, oldw, oldh); - int minTitleWidth = getResources().getDimensionPixelSize(R.dimen.folder_title_min_width); - if (enableLauncherVisualRefresh() && mFolderName.getMeasuredWidth() < minTitleWidth) { - mFolderName.setVisibility(View.GONE); - } - } - /** * Rearranges the children based on their rank. */ @@ -1404,7 +1371,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo @Override public boolean onKey(View v, int keyCode, KeyEvent event) { boolean isShiftPlusTab = keyCode == KeyEvent.KEYCODE_TAB && - event.hasModifiers(KeyEvent.META_SHIFT_ON); + event.hasModifiers(KeyEvent.META_SHIFT_ON); if (isShiftPlusTab && Folder.this.isFocused()) { return lastChild.requestFocus(); } @@ -1437,16 +1404,16 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo } PendingAddShortcutInfo pasi = d.dragInfo instanceof PendingAddShortcutInfo - ? (PendingAddShortcutInfo) d.dragInfo : null; + ? (PendingAddShortcutInfo) d.dragInfo : null; WorkspaceItemInfo pasiSi = - pasi != null ? pasi.getActivityInfo(launcher).createWorkspaceItemInfo() : null; + pasi != null ? pasi.getActivityInfo(launcher).createWorkspaceItemInfo() : null; if (pasi != null && pasiSi == null) { // There is no WorkspaceItemInfo, so we have to go through a configuration activity. pasi.container = mInfo.id; pasi.rank = mEmptyCellRank; launcher.addPendingItem(pasi, pasi.container, pasi.screenId, null, pasi.spanX, - pasi.spanY); + pasi.spanY); d.deferDragViewCleanupPostAnimation = false; mRearrangeOnClose = true; } else { @@ -1468,7 +1435,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo // Actually move the item in the database if it was an external drag. Call this // before creating the view, so that the ItemInfo is updated appropriately. mLauncherDelegate.getModelWriter().addOrMoveItemInDatabase( - si, mInfo.id, 0, si.cellX, si.cellY); + si, mInfo.id, 0, si.cellX, si.cellY); mIsExternalDrag = false; } else { currentDragView = mCurrentDragView; @@ -1494,7 +1461,9 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo rearrangeChildren(); // Temporarily suppress the listener, as we did all the work already here. - executeWithContentUpdateSuppressed(() -> addFolderContent(si, mEmptyCellRank, false)); + try (SuppressInfoChanges s = new SuppressInfoChanges()) { + mInfo.add(si, mEmptyCellRank, false); + } // We only need to update the locations if it doesn't get handled in // #onDropCompleted. @@ -1509,7 +1478,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo if (mContent.getPageCount() > 1) { // The animation has already been shown while opening the folder. mInfo.setOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION, true, - mLauncherDelegate.getModelWriter()); + mLauncherDelegate.getModelWriter()); } if (!launcher.isInState(EDIT_MODE)) { @@ -1520,7 +1489,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo d.stateAnnouncer.completeAction(R.string.item_moved); } mStatsLogManager.logger().withItemInfo(d.dragInfo).withInstanceId(d.logInstanceId) - .log(LAUNCHER_ITEM_DROP_COMPLETED); + .log(LAUNCHER_ITEM_DROP_COMPLETED); } // This is used so the item doesn't immediately appear in the folder when added. In one case @@ -1540,66 +1509,40 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo } } - /** Add an app or shortcut */ - public void addFolderContent(ItemInfo item) { - addFolderContent(item, mInfo.getContents().size(), true); - } - - /** Add an app or shortcut for a specified rank */ - public void addFolderContent(ItemInfo item, int rank, boolean animate) { - if (!willAcceptItemType(item.itemType)) { - throw new RuntimeException("tried to add an illegal type into a folder"); - } - - rank = Utilities.boundToRange(rank, 0, mInfo.getContents().size()); - mInfo.getContents().add(rank, item); - - if (!mSuppressContentUpdate) { - FolderGridOrganizer verifier = createFolderGridOrganizer( + @Override + public void onAdd(ItemInfo item, int rank) { + FolderGridOrganizer verifier = createFolderGridOrganizer( mActivityContext.getDeviceProfile()).setFolderInfo(mInfo); - verifier.updateRankAndPos(item, rank); - mLauncherDelegate.getModelWriter().addOrMoveItemInDatabase(item, mInfo.id, 0, - item.cellX, + verifier.updateRankAndPos(item, rank); + mLauncherDelegate.getModelWriter().addOrMoveItemInDatabase(item, mInfo.id, 0, item.cellX, item.cellY); - updateItemLocationsInDatabaseBatch(false); + updateItemLocationsInDatabaseBatch(false); - if (mContent.areViewsBound()) { - mContent.createAndAddViewForRank(item, rank); - } - mItemsInvalidated = true; - updateTextViewFocus(); + if (mContent.areViewsBound()) { + mContent.createAndAddViewForRank(item, rank); } - - mLauncherDelegate.getModelWriter().notifyItemModified(mInfo); - mFolderIcon.onItemsChanged(animate); + mItemsInvalidated = true; } - /** Remove all matching app or shortcut. Does not change the DB. */ - public void removeFolderContent(boolean animate, ItemInfo... items) { - List itemArray = Arrays.asList(items); - if (mInfo.getContents().removeAll(itemArray)) { - mLauncherDelegate.getModelWriter().notifyItemModified(mInfo); + @Override + public void onRemove(List items) { + if (isInAppDrawer()) { + return; } - - if (!mSuppressContentUpdate) { - mItemsInvalidated = true; - itemArray.forEach(item -> mContent.removeItem(getViewForInfo(item))); - if (mState == STATE_ANIMATING) { - mRearrangeOnClose = true; + mItemsInvalidated = true; + items.stream().map(this::getViewForInfo).forEach(mContent::removeItem); + if (mState == STATE_ANIMATING) { + mRearrangeOnClose = true; + } else { + rearrangeChildren(); + } + if (getItemCount() <= 1) { + if (mIsOpen) { + close(true); } else { - rearrangeChildren(); + replaceFolderWithFinalItem(); } - if (getItemCount() <= 1) { - if (mIsOpen) { - close(true); - } else { - replaceFolderWithFinalItem(); - } - } - updateTextViewFocus(); } - - mFolderIcon.onItemsChanged(animate); } @VisibleForTesting @@ -1607,13 +1550,21 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo return mContent.iterateOverItems((info, view) -> info == item); } + @Override + public void onItemsChanged(boolean animate) { + updateTextViewFocus(); + } + + @Override + public void onTitleChanged(CharSequence title) { + mFolderName.setText(title); + } + /** * Utility methods to iterate over items of the view */ - @Override - @Nullable - public View mapOverItems(@NonNull ItemOperator op) { - return mContent.iterateOverItems(op); + public void iterateOverItems(ItemOperator op) { + mContent.iterateOverItems(op); } /** @@ -1634,8 +1585,8 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo int totalItemsInFolder = allItems.size(); int itemsPerPage = mContent.itemsPerPage(); int numItemsOnCurrentPage = page == lastPage - ? totalItemsInFolder - (itemsPerPage * page) - : itemsPerPage; + ? totalItemsInFolder - (itemsPerPage * page) + : itemsPerPage; int startIndex = page * itemsPerPage; int endIndex = Math.min(startIndex + numItemsOnCurrentPage, allItems.size()); @@ -1656,8 +1607,8 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo post(this::startEditingFolderName); } else { StatsLogger statsLogger = mStatsLogManager.logger() - .withItemInfo(mInfo) - .withFromState(mFromLabelState); + .withItemInfo(mInfo) + .withFromState(mFromLabelState); // If the folder label is suggested, it is logged to improve prediction model. // When both old and new labels are logged together delimiter is used. @@ -1722,7 +1673,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo // Pause drag event until the scrolling is finished mScrollPauseAlarm.setOnAlarmListener(new OnScrollFinishedListener(mDragObject)); int rescrollDelay = getResources().getInteger( - R.integer.config_pageSnapAnimationDuration) + RESCROLL_EXTRA_DELAY; + R.integer.config_pageSnapAnimationDuration) + RESCROLL_EXTRA_DELAY; mScrollPauseAlarm.setAlarm(rescrollDelay); } } @@ -1760,14 +1711,18 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo } }; - /** Executes the task while suppressing the content update for the folder */ - private void executeWithContentUpdateSuppressed(Runnable task) { - if (mSuppressContentUpdate) { - task.run(); - } else { - mSuppressContentUpdate = true; - task.run(); - mSuppressContentUpdate = false; + /** + * Temporary resource held while we don't want to handle info changes + */ + private class SuppressInfoChanges implements AutoCloseable { + + SuppressInfoChanges() { + mInfo.removeListener(Folder.this); + } + + @Override + public void close() { + mInfo.addListener(Folder.this); updateTextViewFocus(); } } @@ -1801,7 +1756,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo } return false; } else if (!dl.isEventOverView(this, ev) - && mLauncherDelegate.interceptOutsideTouch(ev, dl, this)) { + && mLauncherDelegate.interceptOutsideTouch(ev, dl, this)) { return true; } } @@ -1810,7 +1765,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo @Override public boolean canInterceptEventsInSystemGestureRegion() { - return !mIsEditingName; + return true; } /** diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java index a480e40ea7..5241fe4062 100644 --- a/src/com/android/launcher3/folder/FolderAnimationManager.java +++ b/src/com/android/launcher3/folder/FolderAnimationManager.java @@ -35,7 +35,6 @@ import android.graphics.drawable.GradientDrawable; import android.util.Property; import android.view.View; import android.view.animation.AnimationUtils; -import android.view.animation.Interpolator; import com.android.launcher3.BubbleTextView; import com.android.launcher3.CellLayout; @@ -46,8 +45,8 @@ import com.android.launcher3.Utilities; import com.android.launcher3.anim.PropertyResetListener; import com.android.launcher3.apppairs.AppPairIcon; import com.android.launcher3.celllayout.CellLayoutLayoutParams; -import com.android.launcher3.graphics.ShapeDelegate; -import com.android.launcher3.graphics.ThemeManager; +import com.android.launcher3.graphics.IconShape; +import com.android.launcher3.graphics.IconShape.ShapeDelegate; import com.android.launcher3.util.Themes; import com.android.launcher3.views.BaseDragLayer; import com.androidinternal.graphics.ColorUtils; @@ -86,10 +85,9 @@ public class FolderAnimationManager { private final int mDuration; private final int mDelay; - private final Interpolator mFolderOpenInterpolator; - private final Interpolator mFolderCloseInterpolator; - private final Interpolator mLargeFolderPreviewItemOpenInterpolator; - private final Interpolator mLargeFolderPreviewItemCloseInterpolator; + private final TimeInterpolator mFolderInterpolator; + private final TimeInterpolator mLargeFolderPreviewItemOpenInterpolator; + private final TimeInterpolator mLargeFolderPreviewItemCloseInterpolator; private final PreviewItemDrawingParams mTmpParams = new PreviewItemDrawingParams(0, 0, 0); private final FolderGridOrganizer mPreviewVerifier; @@ -116,9 +114,7 @@ public class FolderAnimationManager { mDuration = res.getInteger(R.integer.config_materialFolderExpandDuration); mDelay = res.getInteger(R.integer.config_folderDelay); - mFolderOpenInterpolator = AnimationUtils.loadInterpolator(mContext, - R.interpolator.standard_interpolator); - mFolderCloseInterpolator = AnimationUtils.loadInterpolator(mContext, + mFolderInterpolator = AnimationUtils.loadInterpolator(mContext, R.interpolator.standard_interpolator); mLargeFolderPreviewItemOpenInterpolator = AnimationUtils.loadInterpolator(mContext, R.interpolator.large_folder_preview_item_open_interpolator); @@ -252,7 +248,7 @@ public class FolderAnimationManager { } play(a, getAnimator(mFolder.mFooter, ALPHA, 0, 1f), footerStartDelay, footerAlphaDuration); - ShapeDelegate shapeDelegate = ThemeManager.INSTANCE.get(mContext).getFolderShape(); + ShapeDelegate shapeDelegate = IconShape.INSTANCE.get(mContext).getShape(); // Create reveal animator for the folder background play(a, shapeDelegate.createRevealAnimator( mFolder, startRect, endRect, finalRadius, !mIsOpening)); @@ -271,7 +267,6 @@ public class FolderAnimationManager { (int) (left + (startRect.right / initialScale)) + extraRadius, (int) (startRect.bottom / initialScale) + extraRadius); Rect contentEnd = new Rect(left, 0, left + lp.width, lp.height); - // animated contents of folder with the folder background play(a, shapeDelegate.createRevealAnimator( mFolder.getContent(), contentStart, contentEnd, finalRadius, !mIsOpening)); @@ -352,11 +347,7 @@ public class FolderAnimationManager { // We set the interpolator on all current child animators here, because the preview item // animators may use a different interpolator. for (Animator animator : a.getChildAnimations()) { - animator.setInterpolator( - mIsOpening - ? mFolderOpenInterpolator - : mFolderCloseInterpolator - ); + animator.setInterpolator(mFolderInterpolator); } int radiusDiff = scaledRadius - mPreviewBackground.getRadius(); @@ -491,7 +482,7 @@ public class FolderAnimationManager { return mFolder.getItemCount() > MAX_NUM_ITEMS_IN_PREVIEW; } - private Interpolator getPreviewItemInterpolator() { + private TimeInterpolator getPreviewItemInterpolator() { if (isLargeFolder()) { // With larger folders, we want the preview items to reach their final positions faster // (when opening) and later (when closing) so that they appear aligned with the rest of @@ -500,7 +491,7 @@ public class FolderAnimationManager { ? mLargeFolderPreviewItemOpenInterpolator : mLargeFolderPreviewItemCloseInterpolator; } - return mIsOpening ? mFolderOpenInterpolator : mFolderCloseInterpolator; + return mFolderInterpolator; } private Animator getAnimator(View view, Property property, float v1, float v2) { diff --git a/src/com/android/launcher3/folder/FolderGridOrganizer.java b/src/com/android/launcher3/folder/FolderGridOrganizer.java index b7aa08d702..6219651e8b 100644 --- a/src/com/android/launcher3/folder/FolderGridOrganizer.java +++ b/src/com/android/launcher3/folder/FolderGridOrganizer.java @@ -19,7 +19,6 @@ package com.android.launcher3.folder; import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW; import android.graphics.Point; -import android.util.Log; import com.android.launcher3.DeviceProfile; import com.android.launcher3.model.data.FolderInfo; @@ -179,14 +178,6 @@ public class FolderGridOrganizer { break; } } - - if (result.isEmpty()) { - // Log specifics since we are getting empty result - Log.d("b/383526431", "previewItemsForPage: " - + "mCountX = " + mCountX - + ", mCountY = " + mCountY - + ", content size = " + contents.size()); - } return result; } diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java index 51891ea288..cc5395b7c2 100644 --- a/src/com/android/launcher3/folder/FolderIcon.java +++ b/src/com/android/launcher3/folder/FolderIcon.java @@ -13,19 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * Modifications copyright 2025 Lawnchair + * Modifications copyright 2022 Lawnchair */ package com.android.launcher3.folder; import static com.android.launcher3.Flags.enableCursorHoverStates; +import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ICON_OVERLAP_FACTOR; import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW; import static com.android.launcher3.folder.FolderGridOrganizer.createFolderGridOrganizer; import static com.android.launcher3.folder.PreviewItemManager.INITIAL_ITEM_ANIMATION_DURATION; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_AUTO_LABELED; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_AUTO_LABELING_SKIPPED_EMPTY_PRIMARY; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_AUTO_LABELING_SKIPPED_EMPTY_SUGGESTIONS; -import static com.android.launcher3.model.data.FolderInfo.willAcceptItemType; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -76,6 +76,7 @@ import com.android.launcher3.logging.InstanceId; import com.android.launcher3.logging.StatsLogManager; import com.android.launcher3.model.data.AppPairInfo; import com.android.launcher3.model.data.FolderInfo; +import com.android.launcher3.model.data.FolderInfo.FolderListener; import com.android.launcher3.model.data.FolderInfo.LabelState; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.WorkspaceItemFactory; @@ -94,7 +95,7 @@ import java.util.function.Predicate; /** * An icon that can appear on in the workspace representing an {@link Folder}. */ -public class FolderIcon extends FrameLayout implements FloatingIconViewCompanion, +public class FolderIcon extends FrameLayout implements FolderListener, FloatingIconViewCompanion, DraggableView, Reorderable { private final MultiTranslateDelegate mTranslateDelegate = new MultiTranslateDelegate(this); @@ -129,7 +130,7 @@ public class FolderIcon extends FrameLayout implements FloatingIconViewCompanion private boolean mForceHideDot; @ViewDebug.ExportedProperty(category = "launcher", deepExport = true) - private final FolderDotInfo mDotInfo = new FolderDotInfo(); + private FolderDotInfo mDotInfo = new FolderDotInfo(); private DotRenderer mDotRenderer; @ViewDebug.ExportedProperty(category = "launcher", deepExport = true) private DotRenderer.DrawParams mDotParams; @@ -172,24 +173,21 @@ public class FolderIcon extends FrameLayout implements FloatingIconViewCompanion } public static FolderIcon inflateFolderAndIcon(int resId, - T activityContext, ViewGroup group, FolderInfo folderInfo) { + T activityContext, ViewGroup group, FolderInfo folderInfo) { Folder folder = Folder.fromXml(activityContext); FolderIcon icon = inflateIcon(resId, activityContext, group, folderInfo); folder.setFolderIcon(icon); folder.bind(folderInfo); - icon.setFolder(folder); return icon; } /** - * Builds a FolderIcon to be added to the activity. - * This method doesn't add any listeners to the FolderInfo, and hence any changes to the info - * will not be reflected in the folder. + * Builds a FolderIcon to be added to the Launcher */ public static FolderIcon inflateIcon(int resId, ActivityContext activity, - @Nullable ViewGroup group, FolderInfo folderInfo) { + @Nullable ViewGroup group, FolderInfo folderInfo) { @SuppressWarnings("all") // suppress dead code warning final boolean error = INITIAL_ITEM_ANIMATION_DURATION >= DROP_IN_ANIMATION_DURATION; if (error) { @@ -223,7 +221,13 @@ public class FolderIcon extends FrameLayout implements FloatingIconViewCompanion icon.mDotRenderer = grid.mDotRendererWorkSpace; icon.setContentDescription(icon.getAccessiblityTitle(folderInfo.title)); - icon.updateDotInfo(); + + // Keep the notification dot up to date with the sum of all the content's dots. + FolderDotInfo folderDotInfo = new FolderDotInfo(); + for (ItemInfo si : folderInfo.getContents()) { + folderDotInfo.addDotInfo(activity.getDotInfoForItem(si)); + } + icon.setDotInfo(folderDotInfo); icon.setAccessibilityDelegate(activity.getAccessibilityDelegate()); @@ -231,6 +235,8 @@ public class FolderIcon extends FrameLayout implements FloatingIconViewCompanion icon.mPreviewVerifier.setFolderInfo(folderInfo); icon.updatePreviewItems(false); + folderInfo.addListener(icon); + return icon; } @@ -247,8 +253,7 @@ public class FolderIcon extends FrameLayout implements FloatingIconViewCompanion mPreviewItemManager.recomputePreviewDrawingParams(); mBackground.getBounds(outBounds); // The preview items go outside of the bounds of the background. - Utilities.scaleRectAboutCenter(outBounds, - ClippedFolderIconLayoutRule.getIconOverlapFactor()); + Utilities.scaleRectAboutCenter(outBounds, ICON_OVERLAP_FACTOR); } public float getBackgroundStrokeWidth() { @@ -264,13 +269,22 @@ public class FolderIcon extends FrameLayout implements FloatingIconViewCompanion } private boolean willAcceptItem(ItemInfo item) { - return (willAcceptItemType(item.itemType) && item != mInfo && !mFolder.isOpen()); + final int itemType = item.itemType; + return (Folder.willAcceptItemType(itemType) && item != mInfo && !mFolder.isOpen()); } public boolean acceptDrop(ItemInfo dragInfo) { return !mFolder.isDestroyed() && willAcceptItem(dragInfo); } + public void addItem(ItemInfo item) { + mInfo.add(item, true); + } + + public void removeItem(ItemInfo item, boolean animate) { + mInfo.remove(item, animate); + } + public void onDragEnter(ItemInfo dragInfo) { if (mFolder.isDestroyed() || !willAcceptItem(dragInfo)) return; CellLayoutLayoutParams lp = (CellLayoutLayoutParams) getLayoutParams(); @@ -297,10 +311,11 @@ public class FolderIcon extends FrameLayout implements FloatingIconViewCompanion } public void performCreateAnimation(final ItemInfo destInfo, final View destView, - final ItemInfo srcInfo, final DragObject d, Rect dstRect, - float scaleRelativeToDragLayer) { + final ItemInfo srcInfo, final DragObject d, Rect dstRect, + float scaleRelativeToDragLayer) { + final DragView srcView = d.dragView; prepareCreateAnimation(destView); - getFolder().addFolderContent(destInfo); + addItem(destInfo); // This will animate the first item from it's position as an icon into its // position as the first item in the preview mPreviewItemManager.createFirstItemAnimation(false /* reverse */, null) @@ -323,7 +338,7 @@ public class FolderIcon extends FrameLayout implements FloatingIconViewCompanion } private void onDrop(final ItemInfo item, DragObject d, Rect finalRect, - float scaleRelativeToDragLayer, int index, boolean itemReturnedOnFailedDrop) { + float scaleRelativeToDragLayer, int index, boolean itemReturnedOnFailedDrop) { item.cellX = -1; item.cellY = -1; DragView animateView = d.dragView; @@ -354,7 +369,7 @@ public class FolderIcon extends FrameLayout implements FloatingIconViewCompanion boolean itemAdded = false; if (itemReturnedOnFailedDrop || index >= MAX_NUM_ITEMS_IN_PREVIEW) { List oldPreviewItems = new ArrayList<>(mCurrentPreviewItems); - getFolder().addFolderContent(item, index, false); + mInfo.add(item, index, false); mCurrentPreviewItems.clear(); mCurrentPreviewItems.addAll(getPreviewItemsOnPage(0)); @@ -370,12 +385,12 @@ public class FolderIcon extends FrameLayout implements FloatingIconViewCompanion mPreviewItemManager.onDrop(oldPreviewItems, mCurrentPreviewItems, item); itemAdded = true; } else { - getFolder().removeFolderContent(false, item); + removeItem(item, false); } } if (!itemAdded) { - getFolder().addFolderContent(item, index, true); + mInfo.add(item, index, true); } int[] center = new int[2]; @@ -421,7 +436,7 @@ public class FolderIcon extends FrameLayout implements FloatingIconViewCompanion }, DROP_IN_ANIMATION_DURATION); }); } else { - getFolder().addFolderContent(item); + addItem(item); } } @@ -494,23 +509,9 @@ public class FolderIcon extends FrameLayout implements FloatingIconViewCompanion ); } - /** Keep the notification dot up to date with the sum of all the content's dots. */ - public void updateDotInfo() { - boolean hadDot = mDotInfo.hasDot(); - mDotInfo.reset(); - for (ItemInfo si : mInfo.getContents()) { - mDotInfo.addDotInfo(mActivity.getDotInfoForItem(si)); - } - boolean isDotted = mDotInfo.hasDot(); - float newDotScale = isDotted ? 1f : 0f; - // Animate when a dot is first added or when it is removed. - if ((hadDot ^ isDotted) && isShown()) { - animateDotScale(newDotScale); - } else { - cancelDotScaleAnim(); - mDotScale = newDotScale; - invalidate(); - } + public void setDotInfo(FolderDotInfo dotInfo) { + updateDotScale(mDotInfo.hasDot(), dotInfo.hasDot()); + mDotInfo = dotInfo; } public ClippedFolderIconLayoutRule getLayoutRule() { @@ -531,6 +532,22 @@ public class FolderIcon extends FrameLayout implements FloatingIconViewCompanion } } + /** + * Sets mDotScale to 1 or 0, animating if wasDotted or isDotted is false + * (the dot is being added or removed). + */ + private void updateDotScale(boolean wasDotted, boolean isDotted) { + float newDotScale = isDotted ? 1f : 0f; + // Animate when a dot is first added or when it is removed. + if ((wasDotted ^ isDotted) && isShown()) { + animateDotScale(newDotScale); + } else { + cancelDotScaleAnim(); + mDotScale = newDotScale; + invalidate(); + } + } + private void cancelDotScaleAnim() { if (mDotScaleAnim != null) { mDotScaleAnim.cancel(); @@ -631,7 +648,7 @@ public class FolderIcon extends FrameLayout implements FloatingIconViewCompanion // If we are animating to the accepting state, animate the dot out. mDotParams.scale = Math.max(0, mDotScale - mBackground.getAcceptScaleProgress()); mDotParams.dotColor = mBackground.getDotColor(); - mDotRenderer.draw(canvas, mDotParams); + mDotRenderer.draw(canvas, mDotParams, mDotInfo == null ? -1 : mDotInfo.getNotificationCount()); } } @@ -674,6 +691,13 @@ public class FolderIcon extends FrameLayout implements FloatingIconViewCompanion return mPreviewItemManager.verifyDrawable(who) || super.verifyDrawable(who); } + @Override + public void onItemsChanged(boolean animate) { + updatePreviewItems(animate); + invalidate(); + requestLayout(); + } + private void updatePreviewItems(boolean animate) { mPreviewItemManager.updatePreviewItems(animate); mCurrentPreviewItems.clear(); @@ -687,15 +711,31 @@ public class FolderIcon extends FrameLayout implements FloatingIconViewCompanion mPreviewItemManager.updatePreviewItems(itemCheck); } - public void onItemsChanged(boolean animate) { + @Override + public void onAdd(ItemInfo item, int rank) { updatePreviewItems(false); - updateDotInfo(); + boolean wasDotted = mDotInfo.hasDot(); + mDotInfo.addDotInfo(mActivity.getDotInfoForItem(item)); + boolean isDotted = mDotInfo.hasDot(); + updateDotScale(wasDotted, isDotted); setContentDescription(getAccessiblityTitle(mInfo.title)); - updatePreviewItems(animate); invalidate(); requestLayout(); } + @Override + public void onRemove(List items) { + updatePreviewItems(false); + boolean wasDotted = mDotInfo.hasDot(); + items.stream().map(mActivity::getDotInfoForItem).forEach(mDotInfo::subtractDotInfo); + boolean isDotted = mDotInfo.hasDot(); + updateDotScale(wasDotted, isDotted); + setContentDescription(getAccessiblityTitle(mInfo.title)); + invalidate(); + requestLayout(); + } + + @Override public void onTitleChanged(CharSequence title) { mFolderName.setText(title); setContentDescription(getAccessiblityTitle(title)); @@ -731,6 +771,11 @@ public class FolderIcon extends FrameLayout implements FloatingIconViewCompanion mLongPressHelper.cancelLongPress(); } + public void removeListeners() { + mInfo.removeListener(this); + mInfo.removeListener(mFolder); + } + private boolean isInHotseat() { return mInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT; } @@ -784,10 +829,6 @@ public class FolderIcon extends FrameLayout implements FloatingIconViewCompanion * Returns a formatted accessibility title for folder */ public String getAccessiblityTitle(CharSequence title) { - if (title == null) { - // Avoids "Talkback -> Folder: null" announcement. - title = getContext().getString(R.string.unnamed_folder); - } int size = mInfo.getContents().size(); if (size < MAX_NUM_ITEMS_IN_PREVIEW) { return getContext().getString(R.string.folder_name_format_exact, title, size); @@ -819,4 +860,4 @@ public class FolderIcon extends FrameLayout implements FloatingIconViewCompanion */ void clearFolderLeaveBehind(FolderIcon child); } -} +} \ No newline at end of file diff --git a/src/com/android/launcher3/folder/FolderNameProvider.java b/src/com/android/launcher3/folder/FolderNameProvider.java index 8a1f96d2dd..be5f8f76ef 100644 --- a/src/com/android/launcher3/folder/FolderNameProvider.java +++ b/src/com/android/launcher3/folder/FolderNameProvider.java @@ -15,8 +15,6 @@ */ package com.android.launcher3.folder; -import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER; - import android.annotation.SuppressLint; import android.app.admin.DevicePolicyManager; import android.content.ComponentName; @@ -39,7 +37,6 @@ import com.android.launcher3.model.ModelTaskController; import com.android.launcher3.model.StringCache; import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.model.data.CollectionInfo; -import com.android.launcher3.model.data.FolderInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.util.IntSparseArrayMap; import com.android.launcher3.util.Preconditions; @@ -200,18 +197,9 @@ public class FolderNameProvider implements ResourceBasedOverride { @Override public void execute(@NonNull ModelTaskController taskController, @NonNull BgDataModel dataModel, @NonNull AllAppsList apps) { - mCollectionInfos = getCollectionForSuggestions(dataModel); + mCollectionInfos = dataModel.collections.clone(); mAppInfos = Arrays.asList(apps.copyData()); } } - public static IntSparseArrayMap getCollectionForSuggestions( - BgDataModel dataModel) { - IntSparseArrayMap result = new IntSparseArrayMap<>(); - dataModel.itemsIdMap.stream() - .filter(item -> item.itemType == ITEM_TYPE_FOLDER) - .forEach(item -> result.put(item.id, (FolderInfo) item)); - return result; - } - } diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java index f00f2dac31..221613312d 100644 --- a/src/com/android/launcher3/folder/FolderPagedView.java +++ b/src/com/android/launcher3/folder/FolderPagedView.java @@ -18,12 +18,12 @@ package com.android.launcher3.folder; import static com.android.launcher3.AbstractFloatingView.TYPE_ALL; import static com.android.launcher3.AbstractFloatingView.TYPE_FOLDER; -import static com.android.launcher3.folder.FolderGridOrganizer.createFolderGridOrganizer; import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Canvas; import android.graphics.Path; +import android.graphics.drawable.Drawable; import android.util.ArrayMap; import android.util.AttributeSet; import android.util.Log; @@ -37,6 +37,7 @@ import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.BubbleTextView; import com.android.launcher3.CellLayout; import com.android.launcher3.DeviceProfile; +import com.android.launcher3.Item; import com.android.launcher3.PagedView; import com.android.launcher3.R; import com.android.launcher3.ShortcutAndWidgetContainer; @@ -48,7 +49,6 @@ import com.android.launcher3.model.data.AppPairInfo; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.ItemInfoWithIcon; import com.android.launcher3.model.data.WorkspaceItemInfo; -import com.android.launcher3.pageindicators.Direction; import com.android.launcher3.pageindicators.PageIndicatorDots; import com.android.launcher3.util.LauncherBindableItemsContainer.ItemOperator; import com.android.launcher3.util.Thunk; @@ -60,7 +60,6 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.concurrent.atomic.AtomicInteger; import java.util.function.ToIntFunction; import java.util.stream.Collectors; @@ -102,24 +101,11 @@ public class FolderPagedView extends PagedView implements Cli // animating or is open. private boolean mViewsBound = false; - private boolean mCanAnnouncePageDescription; - public FolderPagedView(Context context, AttributeSet attrs) { - this( - context, - attrs, - createFolderGridOrganizer(ActivityContext.lookupContext(context).getDeviceProfile()) - ); - } - - public FolderPagedView( - Context context, - AttributeSet attrs, - FolderGridOrganizer folderGridOrganizer - ) { super(context, attrs); ActivityContext activityContext = ActivityContext.lookupContext(context); - mOrganizer = folderGridOrganizer; + DeviceProfile profile = activityContext.getDeviceProfile(); + mOrganizer = FolderGridOrganizer.createFolderGridOrganizer(profile); mIsRtl = Utilities.isRtl(getResources()); setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); @@ -131,8 +117,6 @@ public class FolderPagedView extends PagedView implements Cli public void setFolder(Folder folder) { mFolder = folder; mPageIndicator = folder.findViewById(R.id.folder_page_indicator); - mPageIndicator.setArrowClickListener(direction -> snapToPageImmediately( - (Direction.END == direction) ? mCurrentPage + 1 : mCurrentPage - 1)); initParentViews(folder); } @@ -176,19 +160,6 @@ public class FolderPagedView extends PagedView implements Cli mViewsBound = true; } - void setCanAnnouncePageDescriptionForFolder(boolean canAnnounce) { - mCanAnnouncePageDescription = canAnnounce; - } - - private boolean canAnnouncePageDescriptionForFolder() { - return mCanAnnouncePageDescription; - } - - @Override - protected boolean canAnnouncePageDescription() { - return super.canAnnouncePageDescription() && canAnnouncePageDescriptionForFolder(); - } - /** * Removes all the icons from the folder */ @@ -397,9 +368,8 @@ public class FolderPagedView extends PagedView implements Cli // Update footer mPageIndicator.setVisibility(getPageCount() > 1 ? View.VISIBLE : View.GONE); // Set the gravity as LEFT or RIGHT instead of START, as START depends on the actual text. - int horizontalGravity = getPageCount() > 1 - ? (mIsRtl ? Gravity.RIGHT : Gravity.LEFT) : Gravity.CENTER_HORIZONTAL; - mFolder.getFolderName().setGravity(horizontalGravity | Gravity.CENTER_VERTICAL); + mFolder.mFolderName.setGravity(getPageCount() > 1 ? + (mIsRtl ? Gravity.RIGHT : Gravity.LEFT) : Gravity.CENTER_HORIZONTAL); } public int getDesiredWidth() { @@ -541,16 +511,6 @@ public class FolderPagedView extends PagedView implements Cli verifyVisibleHighResIcons(getCurrentPage() + 1); } - int getTotalChildCount() { - AtomicInteger count = new AtomicInteger(); - iterateOverItems((i, v) -> { - count.getAndIncrement(); - return false; - }); - - return count.get(); - } - /** * Ensures that all the icons on the given page are of high-res */ @@ -560,10 +520,19 @@ public class FolderPagedView extends PagedView implements Cli ShortcutAndWidgetContainer parent = page.getShortcutsAndWidgets(); for (int i = parent.getChildCount() - 1; i >= 0; i--) { View iconView = parent.getChildAt(i); + Drawable d = null; if (iconView instanceof BubbleTextView btv) { btv.verifyHighRes(); + d = btv.getIcon(); } else if (iconView instanceof AppPairIcon api) { api.verifyHighRes(); + d = api.getIconDrawableArea().getDrawable(); + } + + // Set the callback back to the actual icon, in case + // it was captured by the FolderIcon + if (d != null) { + d.setCallback(iconView); } } } diff --git a/src/com/android/launcher3/folder/PreviewBackground.java b/src/com/android/launcher3/folder/PreviewBackground.java index ef2cadbfa3..16405339fb 100644 --- a/src/com/android/launcher3/folder/PreviewBackground.java +++ b/src/com/android/launcher3/folder/PreviewBackground.java @@ -18,6 +18,7 @@ package com.android.launcher3.folder; import static com.android.app.animation.Interpolators.ACCELERATE_DECELERATE; import static com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE; +import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ICON_OVERLAP_FACTOR; import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound; import android.animation.Animator; @@ -49,8 +50,8 @@ import com.android.launcher3.CellLayout; import com.android.launcher3.DeviceProfile; import com.android.launcher3.R; import com.android.launcher3.celllayout.DelegatedCellDrawing; -import com.android.launcher3.graphics.ShapeDelegate; -import com.android.launcher3.graphics.ThemeManager; +import com.android.launcher3.graphics.IconShape; +import com.android.launcher3.graphics.IconShape.ShapeDelegate; import com.android.launcher3.util.Themes; import com.android.launcher3.views.ActivityContext; import com.patrykmichalik.opto.core.PreferenceExtensionsKt; @@ -291,7 +292,7 @@ public class PreviewBackground extends DelegatedCellDrawing { } private ShapeDelegate getShape() { - return ThemeManager.INSTANCE.get(mContext).getFolderShape(); + return IconShape.INSTANCE.get(mContext).getShape(); } public void drawShadow(Canvas canvas) { @@ -403,7 +404,7 @@ public class PreviewBackground extends DelegatedCellDrawing { public Path getClipPath() { mPath.reset(); - float radius = getScaledRadius() * ClippedFolderIconLayoutRule.getIconOverlapFactor(); + float radius = getScaledRadius() * ICON_OVERLAP_FACTOR; // Find the difference in radius so that the clip path remains centered. float radiusDifference = radius - getRadius(); float offsetX = basePreviewOffsetX - radiusDifference; diff --git a/src/com/android/launcher3/folder/PreviewItemManager.java b/src/com/android/launcher3/folder/PreviewItemManager.java index 7e58ff311b..cdc71ef4d7 100644 --- a/src/com/android/launcher3/folder/PreviewItemManager.java +++ b/src/com/android/launcher3/folder/PreviewItemManager.java @@ -17,14 +17,12 @@ package com.android.launcher3.folder; import static com.android.launcher3.BubbleTextView.DISPLAY_FOLDER; -import static com.android.launcher3.LauncherSettings.Favorites.DESKTOP_ICON_FLAG; import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ENTER_INDEX; import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.EXIT_INDEX; import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW; import static com.android.launcher3.folder.FolderIcon.DROP_IN_ANIMATION_DURATION; import static com.android.launcher3.graphics.PreloadIconDrawable.newPendingIcon; import static com.android.launcher3.icons.BitmapInfo.FLAG_THEMED; -import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_SHOW_DOWNLOAD_PROGRESS_MASK; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -44,17 +42,17 @@ import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import com.android.launcher3.BubbleTextView; -import com.android.launcher3.Flags; -import com.android.launcher3.LauncherAppState; import com.android.launcher3.Utilities; import com.android.launcher3.apppairs.AppPairIcon; import com.android.launcher3.apppairs.AppPairIconDrawingParams; import com.android.launcher3.apppairs.AppPairIconGraphic; +import com.android.launcher3.graphics.PreloadIconDrawable; import com.android.launcher3.model.data.AppPairInfo; import com.android.launcher3.model.data.FolderInfo; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.ItemInfoWithIcon; import com.android.launcher3.model.data.WorkspaceItemInfo; +import com.android.launcher3.util.Themes; import com.android.launcher3.views.ActivityContext; import java.util.ArrayList; @@ -456,10 +454,13 @@ public class PreviewItemManager { @VisibleForTesting public void setDrawable(PreviewItemDrawingParams p, ItemInfo item) { if (item instanceof WorkspaceItemInfo wii) { - if (isActivePendingIcon(wii)) { - p.drawable = newPendingIcon(mContext, wii); + if (wii.hasPromiseIconUi() || (wii.runtimeStatusFlags + & ItemInfoWithIcon.FLAG_SHOW_DOWNLOAD_PROGRESS_MASK) != 0) { + PreloadIconDrawable drawable = newPendingIcon(mContext, wii); + p.drawable = drawable; } else { - p.drawable = wii.newIcon(mContext, FLAG_THEMED); + p.drawable = wii.newIcon(mContext, + Themes.isThemedIconEnabled(mContext) ? FLAG_THEMED : 0); } p.drawable.setBounds(0, 0, mIconSize, mIconSize); } else if (item instanceof AppPairInfo api) { @@ -467,7 +468,7 @@ public class PreviewItemManager { p.drawable = AppPairIconGraphic.composeDrawable(api, appPairParams); p.drawable.setBounds(0, 0, mIconSize, mIconSize); } else if (item instanceof ItemInfoWithIcon withIcon){ - var isThemed = PreferenceManager.getInstance(mContext).getDrawerThemedIcons().get() ? 0 : FLAG_THEMED; + var isThemed = PreferenceManager.getInstance(mContext).getDrawerThemedIcons().get(); p.drawable = withIcon.newIcon(mContext, isThemed); p.drawable.setBounds(0, 0, mIconSize, mIconSize); } @@ -476,27 +477,5 @@ public class PreviewItemManager { // Set the callback to FolderIcon as it is responsible to drawing the icon. The // callback will be released when the folder is opened. p.drawable.setCallback(mIcon); - - // Verify high res - if (item instanceof ItemInfoWithIcon info - && info.getMatchingLookupFlag().isVisuallyLessThan(DESKTOP_ICON_FLAG)) { - LauncherAppState.getInstance(mContext).getIconCache().updateIconInBackground( - newInfo -> { - if (p.item == newInfo) { - setDrawable(p, newInfo); - mIcon.invalidate(); - } - }, info); - } - } - - /** - * Returns true if item is a Promise Icon or actively downloading, and the item is not an - * inactive archived app. - */ - private boolean isActivePendingIcon(WorkspaceItemInfo item) { - return (item.hasPromiseIconUi() - || (item.runtimeStatusFlags & FLAG_SHOW_DOWNLOAD_PROGRESS_MASK) != 0) - && !(Flags.useNewIconForArchivedApps() && item.isInactiveArchive()); } } diff --git a/src/com/android/launcher3/graphics/IconPalette.java b/src/com/android/launcher3/graphics/IconPalette.java index 5949d6ebe9..9a31bb90e5 100644 --- a/src/com/android/launcher3/graphics/IconPalette.java +++ b/src/com/android/launcher3/graphics/IconPalette.java @@ -21,7 +21,11 @@ import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound; import android.app.Notification; import android.content.Context; import android.graphics.Color; +import android.util.Log; +import androidx.core.graphics.ColorUtils; + +import com.android.launcher3.R; import com.android.launcher3.util.Themes; import java.lang.IllegalArgumentException; @@ -30,6 +34,8 @@ import java.lang.IllegalArgumentException; * Contains colors based on the dominant color of an icon. */ public class IconPalette { + + private static final boolean DEBUG = false; private static final String TAG = "IconPalette"; private static final float MIN_PRELOAD_COLOR_SATURATION = 0.2f; @@ -52,4 +58,108 @@ public class IconPalette { } return result; } + + /** + * Resolves a color such that it has enough contrast to be used as the + * color of an icon or text on the given background color. + * + * @return a color of the same hue with enough contrast against the background. + * + * This was copied from com.android.internal.util.NotificationColorUtil. + */ + public static int resolveContrastColor(Context context, int color, int background) { + final int resolvedColor = resolveColor(context, color); + + int contrastingColor = ensureTextContrast(resolvedColor, background); + + if (contrastingColor != resolvedColor) { + if (DEBUG){ + Log.w(TAG, String.format( + "Enhanced contrast of notification for %s " + + "%s (over background) by changing #%s to %s", + context.getPackageName(), + contrastChange(resolvedColor, contrastingColor, background), + Integer.toHexString(resolvedColor), Integer.toHexString(contrastingColor))); + } + } + return contrastingColor; + } + + /** + * Resolves {@param color} to an actual color if it is {@link Notification#COLOR_DEFAULT} + * + * This was copied from com.android.internal.util.NotificationColorUtil. + */ + private static int resolveColor(Context context, int color) { + if (color == Notification.COLOR_DEFAULT) { + return context.getColor(R.color.notification_icon_default_color); + } + return color; + } + + /** For debugging. This was copied from com.android.internal.util.NotificationColorUtil. */ + private static String contrastChange(int colorOld, int colorNew, int bg) { + return String.format("from %.2f:1 to %.2f:1", + ColorUtils.calculateContrast(colorOld, bg), + ColorUtils.calculateContrast(colorNew, bg)); + } + + /** + * Finds a text color with sufficient contrast over bg that has the same hue as the original + * color. + * + * This was copied from com.android.internal.util.NotificationColorUtil. + */ + private static int ensureTextContrast(int color, int bg) { + int res = color; + try { + res = findContrastColor(color, bg, 4.5); + } catch (IllegalArgumentException e) { + // Just returning the same color in this case + Log.e(TAG, "ensureTextContrast: Invalid fg/bg color int." + + " fg=" + color + " bg=" + bg); + } + return res; + } + /** + * Finds a suitable color such that there's enough contrast. + * + * @param fg the color to start searching from. + * @param bg the color to ensure contrast against. + * @param minRatio the minimum contrast ratio required. + * @return a color with the same hue as {@param color}, potentially darkened to meet the + * contrast ratio. + * + * This was copied from com.android.internal.util.NotificationColorUtil. + */ + private static int findContrastColor(int fg, int bg, double minRatio) { + if (ColorUtils.calculateContrast(fg, bg) >= minRatio) { + return fg; + } + + double[] lab = new double[3]; + ColorUtils.colorToLAB(bg, lab); + double bgL = lab[0]; + ColorUtils.colorToLAB(fg, lab); + double fgL = lab[0]; + boolean isBgDark = bgL < 50; + + double low = isBgDark ? fgL : 0, high = isBgDark ? 100 : fgL; + final double a = lab[1], b = lab[2]; + for (int i = 0; i < 15 && high - low > 0.00001; i++) { + final double l = (low + high) / 2; + fg = ColorUtils.LABToColor(l, a, b); + if (ColorUtils.calculateContrast(fg, bg) > minRatio) { + if (isBgDark) high = l; else low = l; + } else { + if (isBgDark) low = l; else high = l; + } + } + return ColorUtils.LABToColor(low, a, b); + } + + public static int getMutedColor(int color, float whiteScrimAlpha) { + int whiteScrim = setColorAlphaBound(Color.WHITE, (int) (255 * whiteScrimAlpha)); + return ColorUtils.compositeColors(whiteScrim, color); + } } diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java index 2fbc0d208c..f8a7fca136 100644 --- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java +++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java @@ -23,18 +23,9 @@ import static android.view.View.VISIBLE; import static com.android.launcher3.BubbleTextView.DISPLAY_TASKBAR; import static com.android.launcher3.BubbleTextView.DISPLAY_WORKSPACE; import static com.android.launcher3.DeviceProfile.DEFAULT_SCALE; -import static com.android.launcher3.Flags.extendibleThemeManager; -import static com.android.launcher3.Hotseat.ALPHA_CHANNEL_PREVIEW_RENDERER; -import static com.android.launcher3.LauncherPrefs.GRID_NAME; -import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT; import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION; -import static com.android.launcher3.Utilities.ATLEAST_O_MR1; -import static com.android.launcher3.Utilities.ATLEAST_S; -import static com.android.launcher3.Utilities.SHOULD_SHOW_FIRST_PAGE_WIDGET; -import static com.android.launcher3.graphics.ThemeManager.PREF_ICON_SHAPE; -import static com.android.launcher3.model.ModelUtils.currentScreenContentFilter; -import static com.android.launcher3.util.ResourceBasedOverride.Overrides.getObject; -import static com.android.launcher3.widget.LauncherWidgetHolder.APPWIDGET_HOST_ID; +import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems; +import static com.android.launcher3.model.ModelUtils.getMissingHotseatRanks; import android.app.Fragment; import android.app.WallpaperColors; @@ -43,15 +34,14 @@ import android.appwidget.AppWidgetHost; import android.appwidget.AppWidgetHostView; import android.appwidget.AppWidgetProviderInfo; import android.content.Context; +import android.content.ContextWrapper; import android.content.res.Configuration; import android.content.res.TypedArray; import android.graphics.PointF; import android.graphics.Rect; -import android.os.Build.VERSION; -import android.os.Build.VERSION_CODES; +import android.os.Build; import android.os.Handler; import android.os.Looper; -import android.text.TextUtils; import android.util.AttributeSet; import android.util.Size; import android.util.SparseArray; @@ -62,6 +52,8 @@ import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; +import android.view.WindowInsets; +import android.view.WindowManager; import android.widget.FrameLayout; import android.widget.TextClock; @@ -71,50 +63,38 @@ import androidx.annotation.Nullable; import com.android.launcher3.BubbleTextView; import com.android.launcher3.CellLayout; import com.android.launcher3.DeviceProfile; +import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener; import com.android.launcher3.Hotseat; import com.android.launcher3.InsettableFrameLayout; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.LauncherAppState; -import com.android.launcher3.LauncherPrefs; import com.android.launcher3.LauncherSettings.Favorites; -import com.android.launcher3.ProxyPrefs; import com.android.launcher3.R; +import com.android.launcher3.Utilities; import com.android.launcher3.Workspace; import com.android.launcher3.WorkspaceLayoutManager; import com.android.launcher3.apppairs.AppPairIcon; import com.android.launcher3.celllayout.CellLayoutLayoutParams; import com.android.launcher3.celllayout.CellPosMapper; import com.android.launcher3.config.FeatureFlags; -import com.android.launcher3.dagger.ApiWrapperModule; -import com.android.launcher3.dagger.AppModule; -import com.android.launcher3.dagger.LauncherAppComponent; -import com.android.launcher3.dagger.LauncherAppSingleton; -import com.android.launcher3.dagger.LauncherComponentProvider; -import com.android.launcher3.dagger.PluginManagerWrapperModule; -import com.android.launcher3.dagger.StaticObjectModule; -import com.android.launcher3.dagger.WindowManagerProxyModule; import com.android.launcher3.folder.FolderIcon; -import com.android.launcher3.model.BaseLauncherBinder.BaseLauncherBinderFactory; +import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.model.BgDataModel; import com.android.launcher3.model.BgDataModel.FixedContainerItems; -import com.android.launcher3.model.LayoutParserFactory; -import com.android.launcher3.model.LayoutParserFactory.XmlLayoutParserFactory; -import com.android.launcher3.model.LoaderTask.LoaderTaskFactory; +import com.android.launcher3.model.WidgetItem; +import com.android.launcher3.model.WidgetsModel; import com.android.launcher3.model.data.AppPairInfo; import com.android.launcher3.model.data.CollectionInfo; import com.android.launcher3.model.data.FolderInfo; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.LauncherAppWidgetInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; -import com.android.launcher3.util.BaseContext; import com.android.launcher3.util.ComponentKey; -import com.android.launcher3.util.DaggerSingletonObject; import com.android.launcher3.util.DisplayController; import com.android.launcher3.util.IntArray; import com.android.launcher3.util.IntSet; import com.android.launcher3.util.MainThreadInitializedObject; -import com.android.launcher3.util.SandboxContext; -import com.android.launcher3.util.Themes; +import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext; import com.android.launcher3.util.WindowBounds; import com.android.launcher3.util.window.WindowManagerProxy; import com.android.launcher3.views.ActivityContext; @@ -122,24 +102,15 @@ import com.android.launcher3.views.BaseDragLayer; import com.android.launcher3.widget.BaseLauncherAppWidgetHostView; import com.android.launcher3.widget.LauncherAppWidgetProviderInfo; import com.android.launcher3.widget.LauncherWidgetHolder; -import com.android.launcher3.widget.LauncherWidgetHolder.WidgetHolderFactory; import com.android.launcher3.widget.LocalColorExtractor; import com.android.launcher3.widget.util.WidgetSizes; -import com.android.systemui.shared.Flags; -import dagger.BindsInstance; -import dagger.Component; - -import java.io.File; -import java.util.Arrays; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Map.Entry; -import java.util.UUID; -import java.util.stream.Collectors; -import java.util.stream.IntStream; +import java.util.concurrent.ConcurrentLinkedQueue; import app.lawnchair.DeviceProfileOverrides; import app.lawnchair.data.iconoverride.IconOverrideRepository; @@ -152,88 +123,64 @@ import app.lawnchair.smartspace.provider.SmartspaceProvider; import app.lawnchair.theme.ThemeProvider; /** - * Utility class for generating the preview of Launcher for a given InvariantDeviceProfile. + * Utility class for generating the preview of Launcher for a given + * InvariantDeviceProfile. * Steps: - * 1) Create a dummy icon info with just white icon - * 2) Inflate a strip down layout definition for Launcher - * 3) Place appropriate elements like icons and first-page qsb - * 4) Measure and draw the view on a canvas + * 1) Create a dummy icon info with just white icon + * 2) Inflate a strip down layout definition for Launcher + * 3) Place appropriate elements like icons and first-page qsb + * 4) Measure and draw the view on a canvas */ - -// Lawnchair-TODO-Merge: Merge Lawnchair back, (reason: merge-method: theirs) - -public class LauncherPreviewRenderer extends BaseContext - implements ActivityContext, WorkspaceLayoutManager, LayoutInflater.Factory2 { +public class LauncherPreviewRenderer extends ContextWrapper + implements ActivityContext, WorkspaceLayoutManager, LayoutInflater.Factory2 { /** - * Context used just for preview. It also provides a few objects (e.g. UserCache) just for + * Context used just for preview. It also provides a few objects (e.g. + * UserCache) just for * preview purposes. */ public static class PreviewContext extends SandboxContext { - private final String mPrefName; + private final InvariantDeviceProfile mIdp; + private final ConcurrentLinkedQueue mIconPool = new ConcurrentLinkedQueue<>(); - private final File mDbDir; - - public PreviewContext(Context base, String gridName, String shapeKey) { - this(base, gridName, shapeKey, APPWIDGET_HOST_ID, null); + public PreviewContext(Context base, InvariantDeviceProfile idp) { + super (base); + mIdp = idp; + putBaseInstance(PreferenceManager.INSTANCE); + putBaseInstance(PreferenceManager2.INSTANCE); + putBaseInstance(FontCache.INSTANCE); + putBaseInstance(FontManager.INSTANCE); + putBaseInstance(ThemeProvider.INSTANCE); + putBaseInstance(IconPackProvider.INSTANCE); + putBaseInstance(IconOverrideRepository.INSTANCE); + putBaseInstance(SmartspaceProvider.INSTANCE); + putBaseInstance(DeviceProfileOverrides.INSTANCE); + mObjectMap.put(InvariantDeviceProfile.INSTANCE, idp); + mObjectMap.put(LauncherAppState.INSTANCE, + new LauncherAppState(this, null /* iconCacheFileName */)); } - public PreviewContext(Context base, String gridName, String shapeKey, - int widgetHostId, @Nullable String layoutXml) { - super(base); - String randomUid = UUID.randomUUID().toString(); - mPrefName = "preview-" + randomUid; - LauncherPrefs prefs = - new ProxyPrefs(this, getSharedPreferences(mPrefName, MODE_PRIVATE)); - prefs.put(GRID_NAME, gridName); - prefs.put(PREF_ICON_SHAPE, shapeKey); - - - PreviewAppComponent.Builder builder = - DaggerLauncherPreviewRenderer_PreviewAppComponent.builder().bindPrefs(prefs); - if (TextUtils.isEmpty(layoutXml) || !extendibleThemeManager()) { - mDbDir = null; - builder.bindParserFactory(new LayoutParserFactory(this)) - .bindWidgetsFactory( - LauncherComponentProvider.get(base).getWidgetHolderFactory()); - } else { - mDbDir = new File(base.getFilesDir(), randomUid); - emptyDbDir(); - mDbDir.mkdirs(); - builder.bindParserFactory(new XmlLayoutParserFactory(this, layoutXml)) - .bindWidgetsFactory(c -> new LauncherWidgetHolder(c, widgetHostId)); - } - initDaggerComponent(builder); - - if (!TextUtils.isEmpty(layoutXml)) { - // Use null the DB file so that we use a new in-memory DB - InvariantDeviceProfile.INSTANCE.get(this).dbFile = null; - } + private void putBaseInstance(MainThreadInitializedObject mainThreadInitializedObject) { + mObjectMap.put(mainThreadInitializedObject, mainThreadInitializedObject.get(getBaseContext())); } - private void emptyDbDir() { - if (mDbDir != null && mDbDir.exists()) { - Arrays.stream(mDbDir.listFiles()).forEach(File::delete); - } - } + private final class LauncherIconsForPreview extends LauncherIcons { - @Override - protected void cleanUpObjects() { - super.cleanUpObjects(); - deleteSharedPreferences(mPrefName); - if (mDbDir != null) { - emptyDbDir(); - mDbDir.delete(); + private LauncherIconsForPreview(Context context , int fillResIconDpi , int iconBitmapSize , ConcurrentLinkedQueue pool) { + super (context , fillResIconDpi , iconBitmapSize , pool); } - } - @Override - public File getDatabasePath(String name) { - return mDbDir != null ? new File(mDbDir, name) : super.getDatabasePath(name); + @Override + public void recycle() { + // Clear any temporary state variables + clear(); + mIconPool.offer(this); + } } } + private final List mDpChangeListeners = new ArrayList<>(); private final Handler mUiHandler; private final Context mContext; private final InvariantDeviceProfile mIdp; @@ -248,130 +195,93 @@ public class LauncherPreviewRenderer extends BaseContext private final SparseIntArray mWallpaperColorResources; private final SparseArray mLauncherWidgetSpanInfo; - /** - * Lawnchair - *

- * Default search container layout. - * - * @implNote Launcher3 A16_r2 uses [R.layout.qsb_preview] - */ private int mWorkspaceSearchContainer = R.layout.smartspace_container; public LauncherPreviewRenderer(Context context, - InvariantDeviceProfile idp, - int widgetHostId, - WallpaperColors wallpaperColorsOverride, - @Nullable final SparseArray launcherWidgetSpanInfo) { - this(context, idp, widgetHostId, null, wallpaperColorsOverride, launcherWidgetSpanInfo); - } + InvariantDeviceProfile idp, + WallpaperColors wallpaperColorsOverride, + @Nullable final SparseArray launcherWidgetSpanInfo) { - public LauncherPreviewRenderer(Context context, - InvariantDeviceProfile idp, - int widgetHostId, - SparseIntArray previewColorOverride, - WallpaperColors wallpaperColorsOverride, - @Nullable final SparseArray launcherWidgetSpanInfo) { - - super(context, Themes.getActivityThemeRes(context)); + super(context); mUiHandler = new Handler(Looper.getMainLooper()); mContext = context; mIdp = idp; mDp = getDeviceProfileForPreview(context).toBuilder(context).setViewScaleProvider( - this::getAppWidgetScale).build(); + this::getAppWidgetScale).build(); if (context instanceof PreviewContext) { Context tempContext = ((PreviewContext) context).getBaseContext(); - mDpOrig = InvariantDeviceProfile.INSTANCE.get(tempContext) - .getDeviceProfile(tempContext) - .copy(tempContext); + mDpOrig = new InvariantDeviceProfile(tempContext, InvariantDeviceProfile + .getCurrentGridName(tempContext)).getDeviceProfile(tempContext) + .copy(tempContext); } else { mDpOrig = mDp; } - mInsets = getInsets(context); + + if (Utilities.ATLEAST_R) { + WindowInsets currentWindowInsets = context.getSystemService(WindowManager.class) + .getCurrentWindowMetrics().getWindowInsets(); + mInsets = new Rect( + currentWindowInsets.getSystemWindowInsetLeft(), + currentWindowInsets.getSystemWindowInsetTop(), + currentWindowInsets.getSystemWindowInsetRight(), + currentWindowInsets.getSystemWindowInsetBottom()); + } else { + mInsets = new Rect(); + mInsets.left = mInsets.right = (mDp.widthPx - mDp.availableWidthPx) / 2; + mInsets.top = mInsets.bottom = (mDp.heightPx - mDp.availableHeightPx) / 2; + } mDp.updateInsets(mInsets); mHomeElementInflater = LayoutInflater.from( - new ContextThemeWrapper(this, R.style.HomeScreenElementTheme)); + new ContextThemeWrapper(this, R.style.HomeScreenElementTheme)); mHomeElementInflater.setFactory2(this); int layoutRes = mDp.isTwoPanels ? R.layout.launcher_preview_two_panel_layout - : R.layout.launcher_preview_layout; + : R.layout.launcher_preview_layout; mRootView = (InsettableFrameLayout) mHomeElementInflater.inflate( - layoutRes, null, false); + layoutRes, null, false); mRootView.setInsets(mInsets); measureView(mRootView, mDp.widthPx, mDp.heightPx); mHotseat = mRootView.findViewById(R.id.hotseat); mHotseat.resetLayout(false); - mLauncherWidgetSpanInfo = launcherWidgetSpanInfo == null ? new SparseArray<>() : - launcherWidgetSpanInfo; + mLauncherWidgetSpanInfo = launcherWidgetSpanInfo == null ? new SparseArray<>() : launcherWidgetSpanInfo; CellLayout firstScreen = mRootView.findViewById(R.id.workspace); firstScreen.setPadding( - mDp.workspacePadding.left + mDp.cellLayoutPaddingPx.left, - mDp.workspacePadding.top + mDp.cellLayoutPaddingPx.top, - mDp.isTwoPanels ? (mDp.cellLayoutBorderSpacePx.x / 2) - : (mDp.workspacePadding.right + mDp.cellLayoutPaddingPx.right), - mDp.workspacePadding.bottom + mDp.cellLayoutPaddingPx.bottom - ); + mDp.workspacePadding.left + mDp.cellLayoutPaddingPx.left, + mDp.workspacePadding.top + mDp.cellLayoutPaddingPx.top, + (mDp.isTwoPanels ? mDp.cellLayoutBorderSpacePx.x / 2 + : mDp.workspacePadding.right) + mDp.cellLayoutPaddingPx.right, + mDp.workspacePadding.bottom + mDp.cellLayoutPaddingPx.bottom); mWorkspaceScreens.put(FIRST_SCREEN_ID, firstScreen); if (mDp.isTwoPanels) { CellLayout rightPanel = mRootView.findViewById(R.id.workspace_right); rightPanel.setPadding( - mDp.cellLayoutBorderSpacePx.x / 2, - mDp.workspacePadding.top + mDp.cellLayoutPaddingPx.top, - mDp.workspacePadding.right + mDp.cellLayoutPaddingPx.right, - mDp.workspacePadding.bottom + mDp.cellLayoutPaddingPx.bottom - ); + mDp.cellLayoutBorderSpacePx.x / 2 + mDp.cellLayoutPaddingPx.left, + mDp.workspacePadding.top + mDp.cellLayoutPaddingPx.top, + mDp.workspacePadding.right + mDp.cellLayoutPaddingPx.right, + mDp.workspacePadding.bottom + mDp.cellLayoutPaddingPx.bottom); mWorkspaceScreens.put(Workspace.SECOND_SCREEN_ID, rightPanel); } - if (Flags.newCustomizationPickerUi()) { - if (previewColorOverride != null) { - mWallpaperColorResources = previewColorOverride; - } else if (wallpaperColorsOverride != null) { - mWallpaperColorResources = LocalColorExtractor.newInstance( - context).generateColorsOverride(wallpaperColorsOverride); - } else { - WallpaperColors wallpaperColors = null; - if (ATLEAST_O_MR1) { - wallpaperColors = WallpaperManager.getInstance( - context).getWallpaperColors(FLAG_SYSTEM); - } - mWallpaperColorResources = wallpaperColors != null - ? LocalColorExtractor.newInstance(context).generateColorsOverride( - wallpaperColors) - : null; - } - } else { - WallpaperColors wallpaperColors = null; - if (ATLEAST_O_MR1) { - wallpaperColors = wallpaperColorsOverride != null + if (Utilities.ATLEAST_S) { + WallpaperColors wallpaperColors = wallpaperColorsOverride != null ? wallpaperColorsOverride : WallpaperManager.getInstance(context).getWallpaperColors(FLAG_SYSTEM); - } - mWallpaperColorResources = wallpaperColors != null - ? LocalColorExtractor.newInstance(context).generateColorsOverride( - wallpaperColors) - : null; + mWallpaperColorResources = wallpaperColors != null ? LocalColorExtractor.newInstance( + context).generateColorsOverride(wallpaperColors) : null; + } else { + mWallpaperColorResources = null; } - mAppWidgetHost = new LauncherPreviewAppWidgetHost(context, widgetHostId); - - onViewCreated(); - - if (widgetHostId != APPWIDGET_HOST_ID) { - mAppWidgetHost.stopListening(); - } - } - - @Override - public InsettableFrameLayout getRootView() { - return mRootView; + mAppWidgetHost = new LauncherPreviewAppWidgetHost(context); } /** - * Returns the device profile based on resource configuration for previewing various display + * Returns the device profile based on resource configuration for previewing + * various display * sizes */ private DeviceProfile getDeviceProfileForPreview(Context context) { @@ -379,10 +289,9 @@ public class LauncherPreviewRenderer extends BaseContext Configuration config = context.getResources().getConfiguration(); return mIdp.getBestMatch( - config.screenWidthDp * density, - config.screenHeightDp * density, - WindowManagerProxy.INSTANCE.get(context).getRotation(context) - ); + config.screenWidthDp * density, + config.screenHeightDp * density, + WindowManagerProxy.INSTANCE.get(context).getRotation(context)); } /** @@ -395,9 +304,9 @@ public class LauncherPreviewRenderer extends BaseContext Rect insets = new Rect(); for (WindowBounds supportedBound : info.supportedBounds) { double diff = Math.pow(display.getWidth() - supportedBound.availableSize.x, 2) - + Math.pow(display.getHeight() - supportedBound.availableSize.y, 2); + + Math.pow(display.getHeight() - supportedBound.availableSize.y, 2); if (supportedBound.rotationHint == context.getDisplay().getRotation() - && diff < maxDiff) { + && diff < maxDiff) { maxDiff = (float) diff; insets = supportedBound.insets; } @@ -407,7 +316,7 @@ public class LauncherPreviewRenderer extends BaseContext /** Populate preview and render it. */ public View getRenderedView(BgDataModel dataModel, - Map widgetProviderInfoMap) { + Map widgetProviderInfoMap) { populate(dataModel, widgetProviderInfoMap); return mRootView; } @@ -429,7 +338,7 @@ public class LauncherPreviewRenderer extends BaseContext TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.PreviewFragment); FragmentWithPreview f = (FragmentWithPreview) Fragment.instantiate( - context, ta.getString(R.styleable.PreviewFragment_android_name)); + context, ta.getString(R.styleable.PreviewFragment_android_name)); f.enterPreviewMode(context); f.onInit(null); @@ -453,6 +362,11 @@ public class LauncherPreviewRenderer extends BaseContext return mDp; } + @Override + public List getOnDeviceProfileChangeListeners() { + return mDpChangeListeners; + } + @Override public Hotseat getHotseat() { return mHotseat; @@ -467,12 +381,12 @@ public class LauncherPreviewRenderer extends BaseContext mUiHandler.post(() -> { if (mDp.isTaskbarPresent) { // hotseat icons on bottom - mHotseat.setIconsAlpha(hide ? 0 : 1, ALPHA_CHANNEL_PREVIEW_RENDERER); + mHotseat.setIconsAlpha(hide ? 0 : 1); if (mDp.isQsbInline) { - mHotseat.setQsbAlpha(hide ? 0 : 1, ALPHA_CHANNEL_PREVIEW_RENDERER); + mHotseat.setQsbAlpha(hide ? 0 : 1); } } else { - mHotseat.setQsbAlpha(hide ? 0 : 1, ALPHA_CHANNEL_PREVIEW_RENDERER); + mHotseat.setQsbAlpha(hide ? 0 : 1); } }); } @@ -490,7 +404,7 @@ public class LauncherPreviewRenderer extends BaseContext private void inflateAndAddIcon(WorkspaceItemInfo info) { CellLayout screen = mWorkspaceScreens.get(info.screenId); BubbleTextView icon = (BubbleTextView) mHomeElementInflater.inflate( - R.layout.app_icon, screen, false); + R.layout.app_icon, screen, false); icon.applyFromWorkspaceItem(info); addInScreenFromBind(icon, info); } @@ -498,39 +412,46 @@ public class LauncherPreviewRenderer extends BaseContext private void inflateAndAddCollectionIcon(CollectionInfo info) { boolean isOnDesktop = info.container == Favorites.CONTAINER_DESKTOP; CellLayout screen = isOnDesktop - ? mWorkspaceScreens.get(info.screenId) - : mHotseat; + ? mWorkspaceScreens.get(info.screenId) + : mHotseat; FrameLayout collectionIcon = info.itemType == Favorites.ITEM_TYPE_FOLDER - ? FolderIcon.inflateIcon(R.layout.folder_icon, this, screen, (FolderInfo) info) - : AppPairIcon.inflateIcon(R.layout.app_pair_icon, this, screen, (AppPairInfo) info, - isOnDesktop ? DISPLAY_WORKSPACE : DISPLAY_TASKBAR); + ? FolderIcon.inflateIcon(R.layout.folder_icon, this, screen, (FolderInfo) info) + : AppPairIcon.inflateIcon(R.layout.app_pair_icon, this, screen, (AppPairInfo) info, + isOnDesktop ? DISPLAY_WORKSPACE : DISPLAY_TASKBAR); addInScreenFromBind(collectionIcon, info); } private void inflateAndAddWidgets( - LauncherAppWidgetInfo info, - Map widgetProviderInfoMap) { + LauncherAppWidgetInfo info, + Map widgetProviderInfoMap) { if (widgetProviderInfoMap == null) { return; } AppWidgetProviderInfo providerInfo = widgetProviderInfoMap.get( - new ComponentKey(info.providerName, info.user)); + new ComponentKey(info.providerName, info.user)); if (providerInfo == null) { return; } inflateAndAddWidgets(info, LauncherAppWidgetProviderInfo.fromProviderInfo( - getApplicationContext(), providerInfo)); + getApplicationContext(), providerInfo)); + } + + private void inflateAndAddWidgets(LauncherAppWidgetInfo info, WidgetsModel widgetsModel) { + WidgetItem widgetItem = widgetsModel.getWidgetProviderInfoByProviderName( + info.providerName, info.user, mContext); + if (widgetItem == null) { + return; + } + inflateAndAddWidgets(info, widgetItem.widgetInfo); } private void inflateAndAddWidgets( - LauncherAppWidgetInfo info, LauncherAppWidgetProviderInfo providerInfo) { + LauncherAppWidgetInfo info, LauncherAppWidgetProviderInfo providerInfo) { AppWidgetHostView view = mAppWidgetHost.createView( - mContext, info.appWidgetId, providerInfo); + mContext, info.appWidgetId, providerInfo); if (mWallpaperColorResources != null) { - if (ATLEAST_S) { - view.setColorResources(mWallpaperColorResources); - } + view.setColorResources(mWallpaperColorResources); } view.setTag(info); @@ -548,16 +469,16 @@ public class LauncherPreviewRenderer extends BaseContext return DEFAULT_SCALE; } final Size origSize = WidgetSizes.getWidgetSizePx(mDpOrig, - launcherWidgetSize.getWidth(), launcherWidgetSize.getHeight()); + launcherWidgetSize.getWidth(), launcherWidgetSize.getHeight()); final Size newSize = WidgetSizes.getWidgetSizePx(mDp, info.spanX, info.spanY); return new PointF((float) newSize.getWidth() / origSize.getWidth(), - (float) newSize.getHeight() / origSize.getHeight()); + (float) newSize.getHeight() / origSize.getHeight()); } private void inflateAndAddPredictedIcon(WorkspaceItemInfo info) { CellLayout screen = mWorkspaceScreens.get(info.screenId); BubbleTextView icon = (BubbleTextView) mHomeElementInflater.inflate( - R.layout.predicted_app_icon, screen, false); + R.layout.predicted_app_icon, screen, false); icon.applyFromWorkspaceItem(info); addInScreenFromBind(icon, info); } @@ -581,58 +502,59 @@ public class LauncherPreviewRenderer extends BaseContext } private void populate(BgDataModel dataModel, - Map widgetProviderInfoMap) { - IntSet missingHotseatRank = new IntSet(); - IntStream.range(0, mDp.numShownHotseatIcons).forEach(missingHotseatRank::add); + Map widgetProviderInfoMap) { + // Separate the items that are on the current screen, and the other remaining + // items. + ArrayList currentWorkspaceItems = new ArrayList<>(); + ArrayList otherWorkspaceItems = new ArrayList<>(); + ArrayList currentAppWidgets = new ArrayList<>(); + ArrayList otherAppWidgets = new ArrayList<>(); - Map[] widgetsMap = new Map[] { widgetProviderInfoMap}; - - // Separate the items that are on the current screen, and the other remaining items. - dataModel.itemsIdMap.stream() - .filter(currentScreenContentFilter(IntSet.wrap(mWorkspaceScreens.keySet()))) - .forEach(itemInfo -> { - switch (itemInfo.itemType) { - case Favorites.ITEM_TYPE_APPLICATION: - case Favorites.ITEM_TYPE_DEEP_SHORTCUT: - inflateAndAddIcon((WorkspaceItemInfo) itemInfo); - break; - case Favorites.ITEM_TYPE_FOLDER: - case Favorites.ITEM_TYPE_APP_PAIR: - inflateAndAddCollectionIcon((CollectionInfo) itemInfo); - break; - case Favorites.ITEM_TYPE_APPWIDGET: - case Favorites.ITEM_TYPE_CUSTOM_APPWIDGET: - if (widgetsMap[0] == null) { - widgetsMap[0] = dataModel.widgetsModel.getWidgetsByComponentKey() - .entrySet() - .stream() - .filter(entry -> entry.getValue().widgetInfo != null) - .collect(Collectors.toMap( - Entry::getKey, - entry -> entry.getValue().widgetInfo - )); - } - inflateAndAddWidgets((LauncherAppWidgetInfo) itemInfo, widgetsMap[0]); - break; - default: - break; - } - - if (itemInfo.container == CONTAINER_HOTSEAT) { - missingHotseatRank.remove(itemInfo.screenId); - } - }); - - IntArray ranks = missingHotseatRank.getArray(); - FixedContainerItems hotseatPredictions = - dataModel.extraItems.get(CONTAINER_HOTSEAT_PREDICTION); + IntSet currentScreenIds = IntSet.wrap(mWorkspaceScreens.keySet()); + filterCurrentWorkspaceItems(currentScreenIds, dataModel.workspaceItems, + currentWorkspaceItems, otherWorkspaceItems); + filterCurrentWorkspaceItems(currentScreenIds, dataModel.appWidgets, currentAppWidgets, + otherAppWidgets); + for (ItemInfo itemInfo : currentWorkspaceItems) { + switch (itemInfo.itemType) { + case Favorites.ITEM_TYPE_APPLICATION: + case Favorites.ITEM_TYPE_DEEP_SHORTCUT: + inflateAndAddIcon((WorkspaceItemInfo) itemInfo); + break; + case Favorites.ITEM_TYPE_FOLDER: + case Favorites.ITEM_TYPE_APP_PAIR: + inflateAndAddCollectionIcon((CollectionInfo) itemInfo); + break; + default: + break; + } + } + for (ItemInfo itemInfo : currentAppWidgets) { + switch (itemInfo.itemType) { + case Favorites.ITEM_TYPE_APPWIDGET: + case Favorites.ITEM_TYPE_CUSTOM_APPWIDGET: + if (widgetProviderInfoMap != null) { + inflateAndAddWidgets( + (LauncherAppWidgetInfo) itemInfo, widgetProviderInfoMap); + } else { + inflateAndAddWidgets((LauncherAppWidgetInfo) itemInfo, + dataModel.widgetsModel); + } + break; + default: + break; + } + } + IntArray ranks = getMissingHotseatRanks(currentWorkspaceItems, + mDp.numShownHotseatIcons); + FixedContainerItems hotseatPredictions = dataModel.extraItems.get(CONTAINER_HOTSEAT_PREDICTION); List predictions = hotseatPredictions == null - ? Collections.emptyList() : hotseatPredictions.items; + ? Collections.emptyList() + : hotseatPredictions.items; int count = Math.min(ranks.size(), predictions.size()); for (int i = 0; i < count; i++) { int rank = ranks.get(i); - WorkspaceItemInfo itemInfo = - new WorkspaceItemInfo((WorkspaceItemInfo) predictions.get(i)); + WorkspaceItemInfo itemInfo = new WorkspaceItemInfo((WorkspaceItemInfo) predictions.get(i)); itemInfo.container = CONTAINER_HOTSEAT_PREDICTION; itemInfo.rank = rank; itemInfo.cellX = mHotseat.getCellXFromOrder(rank); @@ -642,13 +564,11 @@ public class LauncherPreviewRenderer extends BaseContext } // Add first page QSB - if (FeatureFlags.QSB_ON_FIRST_SCREEN && dataModel.isFirstPagePinnedItemEnabled - && !SHOULD_SHOW_FIRST_PAGE_WIDGET) { + if (FeatureFlags.topQsbOnFirstScreenEnabled(mContext)) { CellLayout firstScreen = mWorkspaceScreens.get(FIRST_SCREEN_ID); - View qsb = mHomeElementInflater.inflate(mWorkspaceSearchContainer, firstScreen, false); - // TODO: set bgHandler on qsb when it is BaseTemplateCard, which requires API changes. - CellLayoutLayoutParams lp = new CellLayoutLayoutParams( - 0, 0, firstScreen.getCountX(), 1); + View qsb = mHomeElementInflater.inflate(mWorkspaceSearchContainer, firstScreen, + false); + CellLayoutLayoutParams lp = new CellLayoutLayoutParams(0, 0, mDp.inv.numColumns, 1); lp.canReorder = false; firstScreen.addViewToCellLayout(qsb, 0, R.id.search_container_workspace, lp, true); } @@ -659,14 +579,7 @@ public class LauncherPreviewRenderer extends BaseContext // Additional measure for views which use auto text size API measureView(mRootView, mDp.widthPx, mDp.heightPx); } - - /** - * Lawnchair - *

- * Set custom search container for workspace. - * - * @param resId True to hide and false to show. - */ + public void setWorkspaceSearchContainer(int resId) { mWorkspaceSearchContainer = resId; } @@ -678,15 +591,15 @@ public class LauncherPreviewRenderer extends BaseContext private class LauncherPreviewAppWidgetHost extends AppWidgetHost { - private LauncherPreviewAppWidgetHost(Context context, int hostId) { - super(context, hostId); + private LauncherPreviewAppWidgetHost(Context context) { + super(context, LauncherWidgetHolder.APPWIDGET_HOST_ID); } @Override protected AppWidgetHostView onCreateView( - Context context, - int appWidgetId, - AppWidgetProviderInfo appWidget) { + Context context, + int appWidgetId, + AppWidgetProviderInfo appWidget) { return new LauncherPreviewAppWidgetHostView(LauncherPreviewRenderer.this); } } @@ -718,27 +631,4 @@ public class LauncherPreviewRenderer extends BaseContext return true; } } - - @LauncherAppSingleton - // Exclude widget module since we bind widget holder separately - @Component(modules = {WindowManagerProxyModule.class, - ApiWrapperModule.class, - PluginManagerWrapperModule.class, - StaticObjectModule.class, - AppModule.class}) - public interface PreviewAppComponent extends LauncherAppComponent { - - LoaderTaskFactory getLoaderTaskFactory(); - BaseLauncherBinderFactory getBaseLauncherBinderFactory(); - BgDataModel getDataModel(); - - /** Builder for NexusLauncherAppComponent. */ - @Component.Builder - interface Builder extends LauncherAppComponent.Builder { - @BindsInstance Builder bindPrefs(LauncherPrefs prefs); - @BindsInstance Builder bindParserFactory(LayoutParserFactory parserFactory); - @BindsInstance Builder bindWidgetsFactory(WidgetHolderFactory holderFactory); - PreviewAppComponent build(); - } - } } diff --git a/src/com/android/launcher3/graphics/PreloadIconDrawable.java b/src/com/android/launcher3/graphics/PreloadIconDrawable.java index 3bd9fb54ca..9fffcc1bd2 100644 --- a/src/com/android/launcher3/graphics/PreloadIconDrawable.java +++ b/src/com/android/launcher3/graphics/PreloadIconDrawable.java @@ -24,6 +24,7 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.content.Context; +import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Paint; @@ -32,15 +33,14 @@ import android.graphics.PathMeasure; import android.graphics.Rect; import android.util.Property; -import androidx.annotation.VisibleForTesting; import androidx.core.graphics.ColorUtils; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.anim.AnimatedFloat; import com.android.launcher3.anim.AnimatorListeners; -import com.android.launcher3.icons.BitmapInfo; import com.android.launcher3.icons.FastBitmapDrawable; +import com.android.launcher3.icons.GraphicsUtils; import com.android.launcher3.model.data.ItemInfoWithIcon; import com.android.launcher3.util.Themes; @@ -64,6 +64,8 @@ public class PreloadIconDrawable extends FastBitmapDrawable { private static final int DEFAULT_PATH_SIZE = 100; private static final int MAX_PAINT_ALPHA = 255; + private static final int TRACK_ALPHA = (int) (0.27f * MAX_PAINT_ALPHA); + private static final int DISABLED_ICON_ALPHA = (int) (0.6f * MAX_PAINT_ALPHA); private static final long DURATION_SCALE = 500; private static final long SCALE_AND_ALPHA_ANIM_DURATION = 500; @@ -119,8 +121,7 @@ public class PreloadIconDrawable extends FastBitmapDrawable { IconPalette.getPreloadProgressColor(context, info.bitmap.color), getPreloadColors(context), Utilities.isDarkTheme(context), - ThemeManager.INSTANCE.get(context).getIconShape().getPath(DEFAULT_PATH_SIZE) - ); + GraphicsUtils.getShapePath(context, DEFAULT_PATH_SIZE)); } public PreloadIconDrawable( @@ -283,25 +284,20 @@ public class PreloadIconDrawable extends FastBitmapDrawable { (long) ((finalProgress - mInternalStateProgress) * DURATION_SCALE)); mCurrentAnim.setInterpolator(LINEAR); if (isFinish) { + if (onFinishCallback != null) { + mCurrentAnim.addListener(AnimatorListeners.forEndCallback(onFinishCallback)); + } mCurrentAnim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { mRanFinishAnimation = true; } }); - if (onFinishCallback != null) { - mCurrentAnim.addListener(AnimatorListeners.forEndCallback(onFinishCallback)); - } } mCurrentAnim.start(); } } - @VisibleForTesting - public ObjectAnimator getActiveAnimation() { - return mCurrentAnim; - } - /** * Sets the internal progress and updates the UI accordingly * for progress <= 0: @@ -362,7 +358,8 @@ public class PreloadIconDrawable extends FastBitmapDrawable { @Override public FastBitmapConstantState newConstantState() { return new PreloadIconConstantState( - mBitmapInfo, + mBitmap, + mIconColor, mItem, mIndicatorColor, new int[] {mSystemAccentColor, mSystemBackgroundColor}, @@ -380,13 +377,14 @@ public class PreloadIconDrawable extends FastBitmapDrawable { private final Path mShapePath; public PreloadIconConstantState( - BitmapInfo bitmapInfo, + Bitmap bitmap, + int iconColor, ItemInfoWithIcon info, int indicatorColor, int[] preloadColors, boolean isDarkMode, Path shapePath) { - super(bitmapInfo); + super(bitmap, iconColor); mInfo = info; mIndicatorColor = indicatorColor; mPreloadColors = preloadColors; diff --git a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java index 9e52def4bd..409189ed6d 100644 --- a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java +++ b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java @@ -16,42 +16,29 @@ package com.android.launcher3.graphics; -import static android.content.res.Configuration.UI_MODE_NIGHT_NO; -import static android.content.res.Configuration.UI_MODE_NIGHT_YES; import static android.view.Display.DEFAULT_DISPLAY; -import static com.android.launcher3.Flags.extendibleThemeManager; -import static com.android.launcher3.LauncherPrefs.GRID_NAME; -import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID; -import static com.android.launcher3.WorkspaceLayoutManager.SECOND_SCREEN_ID; -import static com.android.launcher3.graphics.ThemeManager.PREF_ICON_SHAPE; -import static com.android.launcher3.provider.LauncherDbUtils.selectionForWorkspaceScreen; +import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; -import static com.android.launcher3.widget.LauncherWidgetHolder.APPWIDGET_HOST_ID; import android.app.WallpaperColors; -import android.appwidget.AppWidgetHost; import android.appwidget.AppWidgetProviderInfo; import android.content.Context; -import android.content.res.Configuration; import android.database.Cursor; import android.hardware.display.DisplayManager; import android.os.Bundle; import android.os.IBinder; -import android.text.TextUtils; import android.util.Log; import android.util.Size; import android.util.SparseArray; -import android.util.SparseIntArray; import android.view.ContextThemeWrapper; import android.view.Display; -import android.view.Surface; import android.view.SurfaceControlViewHost; import android.view.SurfaceControlViewHost.SurfacePackage; import android.view.View; +import android.view.WindowManager; import android.view.animation.AccelerateDecelerateInterpolator; -import android.widget.FrameLayout; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -61,102 +48,83 @@ import androidx.annotation.WorkerThread; import com.android.launcher3.DeviceProfile; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.LauncherAppState; -import com.android.launcher3.LauncherPrefs; import com.android.launcher3.LauncherSettings; -import com.android.launcher3.dagger.LauncherComponentProvider; -import com.android.launcher3.graphics.LauncherPreviewRenderer.PreviewAppComponent; +import com.android.launcher3.Utilities; +import com.android.launcher3.Workspace; import com.android.launcher3.graphics.LauncherPreviewRenderer.PreviewContext; +import com.android.launcher3.model.BaseLauncherBinder; import com.android.launcher3.model.BgDataModel; import com.android.launcher3.model.BgDataModel.Callbacks; +import com.android.launcher3.model.GridSizeMigrationUtil; import com.android.launcher3.model.LoaderTask; import com.android.launcher3.model.ModelDbController; -import com.android.launcher3.model.UserManagerState; +import com.android.launcher3.provider.LauncherDbUtils; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.RunnableList; import com.android.launcher3.util.Themes; import com.android.launcher3.widget.LocalColorExtractor; -import com.android.systemui.shared.Flags; -import java.util.HashMap; +import java.util.ArrayList; import java.util.Map; import java.util.concurrent.TimeUnit; + /** Render preview using surface view. */ @SuppressWarnings("NewApi") public class PreviewSurfaceRenderer { private static final String TAG = "PreviewSurfaceRenderer"; + private static final int FADE_IN_ANIMATION_DURATION = 200; + private static final String KEY_HOST_TOKEN = "host_token"; private static final String KEY_VIEW_WIDTH = "width"; private static final String KEY_VIEW_HEIGHT = "height"; private static final String KEY_DISPLAY_ID = "display_id"; private static final String KEY_COLORS = "wallpaper_colors"; - private static final String KEY_COLOR_RESOURCE_IDS = "color_resource_ids"; - private static final String KEY_COLOR_VALUES = "color_values"; - private static final String KEY_DARK_MODE = "use_dark_mode"; - private static final String KEY_LAYOUT_XML = "layout_xml"; - public static final String KEY_SKIP_ANIMATIONS = "skip_animations"; - private final Context mContext; - private SparseIntArray mPreviewColorOverride; - private String mGridName; - private String mShapeKey; - private String mLayoutXml; - - @Nullable private Boolean mDarkMode; - private boolean mDestroyed = false; - private boolean mHideQsb; - @Nullable private FrameLayout mViewRoot = null; - private boolean mDeletingHostOnExit = false; - - private final int mCallingPid; + private Context mContext; private final IBinder mHostToken; private final int mWidth; private final int mHeight; - private final boolean mSkipAnimations; + private String mGridName; + private final int mDisplayId; private final Display mDisplay; private final WallpaperColors mWallpaperColors; - private final RunnableList mLifeCycleTracker; + private final RunnableList mOnDestroyCallbacks = new RunnableList(); + private final SurfaceControlViewHost mSurfaceControlViewHost; - public PreviewSurfaceRenderer(Context context, RunnableList lifecycleTracker, Bundle bundle, - int callingPid) throws Exception { + private boolean mDestroyed = false; + private LauncherPreviewRenderer mRenderer; + private boolean mHideQsb; + + public PreviewSurfaceRenderer(Context context, Bundle bundle) throws Exception { mContext = context; - mLifeCycleTracker = lifecycleTracker; - mCallingPid = callingPid; mGridName = bundle.getString("name"); bundle.remove("name"); if (mGridName == null) { - mGridName = LauncherPrefs.get(context).get(GRID_NAME); + mGridName = InvariantDeviceProfile.getCurrentGridName(context); } - mShapeKey = LauncherPrefs.get(context).get(PREF_ICON_SHAPE); mWallpaperColors = bundle.getParcelable(KEY_COLORS); - if (Flags.newCustomizationPickerUi()) { - updateColorOverrides(bundle); - } - mHideQsb = bundle.getBoolean(GridCustomizationsProxy.KEY_HIDE_BOTTOM_ROW); + mHideQsb = bundle.getBoolean(GridCustomizationsProvider.KEY_HIDE_BOTTOM_ROW); mHostToken = bundle.getBinder(KEY_HOST_TOKEN); mWidth = bundle.getInt(KEY_VIEW_WIDTH); mHeight = bundle.getInt(KEY_VIEW_HEIGHT); - mSkipAnimations = bundle.getBoolean(KEY_SKIP_ANIMATIONS, false); mDisplayId = bundle.getInt(KEY_DISPLAY_ID); mDisplay = context.getSystemService(DisplayManager.class) .getDisplay(mDisplayId); - mLayoutXml = bundle.getString(KEY_LAYOUT_XML); if (mDisplay == null) { throw new IllegalArgumentException("Display ID does not match any displays."); } - mSurfaceControlViewHost = MAIN_EXECUTOR.submit(() -> new MySurfaceControlViewHost( - mContext, - context.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY), - mHostToken, - mLifeCycleTracker)) - .get(5, TimeUnit.SECONDS); - mLifeCycleTracker.add(this::destroy); + mSurfaceControlViewHost = MAIN_EXECUTOR.submit(() -> + new SurfaceControlViewHost(mContext, context.getSystemService(DisplayManager.class) + .getDisplay(DEFAULT_DISPLAY), mHostToken) + ).get(5, TimeUnit.SECONDS); + mOnDestroyCallbacks.add(mSurfaceControlViewHost::release); } public int getDisplayId() { @@ -171,25 +139,33 @@ public class PreviewSurfaceRenderer { return mSurfaceControlViewHost.getSurfacePackage(); } - private void destroy() { + /** + * Destroys the preview and all associated data + */ + @UiThread + public void destroy() { mDestroyed = true; + mOnDestroyCallbacks.executeAllAndDestroy(); } /** * A function that queries for the launcher app widget span info * - * @return A SparseArray with the app widget id being the key and the span info being the values + * @param context The context to get the content resolver from, should be + * related to launcher + * @return A SparseArray with the app widget id being the key and the span info + * being the values */ @WorkerThread @Nullable - public SparseArray getLoadedLauncherWidgetInfo() { + public SparseArray getLoadedLauncherWidgetInfo( + @NonNull final Context context) { final SparseArray widgetInfo = new SparseArray<>(); final String query = LauncherSettings.Favorites.ITEM_TYPE + " = " + LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET; - ModelDbController mainController = - LauncherAppState.getInstance(mContext).getModel().getModelDbController(); - try (Cursor c = mainController.query( + ModelDbController mainController = LauncherAppState.getInstance(mContext).getModel().getModelDbController(); + try (Cursor c = mainController.query(TABLE_NAME, new String[] { LauncherSettings.Favorites.APPWIDGET_ID, LauncherSettings.Favorites.SPANX, @@ -221,177 +197,91 @@ public class PreviewSurfaceRenderer { MODEL_EXECUTOR.execute(this::loadModelData); } - /** - * Update the grid of the launcher preview - * - * @param gridName Name of the grid, e.g. normal, practical - */ - public void updateGrid(@NonNull String gridName) { - if (gridName.equals(mGridName)) { - return; - } - mGridName = gridName; - loadAsync(); - } - - /** - * Update the shapes of the launcher preview - * - * @param shapeKey key for the IconShape model - */ - public void updateShape(String shapeKey) { - if (shapeKey.equals(mShapeKey)) { - Log.w(TAG, "Preview shape already set, skipping. shape=" + mShapeKey); - return; - } - mShapeKey = shapeKey; - loadAsync(); - } - /** * Hides the components in the bottom row. * * @param hide True to hide and false to show. */ public void hideBottomRow(boolean hide) { - mHideQsb = hide; - loadAsync(); - } - - /** - * Updates the colors of the preview. - * - * @param bundle Bundle with an int array of color ids and an int array of overriding colors. - */ - public void previewColor(Bundle bundle) { - updateColorOverrides(bundle); - loadAsync(); - } - - private void updateColorOverrides(Bundle bundle) { - mDarkMode = - bundle.containsKey(KEY_DARK_MODE) ? bundle.getBoolean(KEY_DARK_MODE) : null; - int[] ids = bundle.getIntArray(KEY_COLOR_RESOURCE_IDS); - int[] colors = bundle.getIntArray(KEY_COLOR_VALUES); - if (ids != null && colors != null) { - mPreviewColorOverride = new SparseIntArray(); - for (int i = 0; i < ids.length; i++) { - mPreviewColorOverride.put(ids[i], colors[i]); - } - } else { - mPreviewColorOverride = null; + if (mRenderer != null) { + // mRenderer.hideBottomRow(hide); } } /*** - * Generates a new context overriding the theme color and the display size without affecting the + * Generates a new context overriding the theme color and the display size + * without affecting the * main application context */ private Context getPreviewContext() { Context context = mContext.createDisplayContext(mDisplay); - if (mDarkMode != null) { - Configuration configuration = new Configuration( - context.getResources().getConfiguration()); - if (mDarkMode) { - configuration.uiMode &= ~UI_MODE_NIGHT_NO; - configuration.uiMode |= UI_MODE_NIGHT_YES; - } else { - configuration.uiMode &= ~UI_MODE_NIGHT_YES; - configuration.uiMode |= UI_MODE_NIGHT_NO; - } - context = context.createConfigurationContext(configuration); - } - if (InvariantDeviceProfile.INSTANCE.get(context).isFixedLandscape) { - Configuration configuration = new Configuration( - context.getResources().getConfiguration() - ); - int width = configuration.screenWidthDp; - int height = configuration.screenHeightDp; - if (configuration.screenHeightDp > configuration.screenWidthDp) { - configuration.screenWidthDp = height; - configuration.screenHeightDp = width; - configuration.orientation = Configuration.ORIENTATION_PORTRAIT; - } - context = context.createConfigurationContext(configuration); - } - - if (Flags.newCustomizationPickerUi()) { - if (mPreviewColorOverride != null) { - LocalColorExtractor.newInstance(context) - .applyColorsOverride(context, mPreviewColorOverride); - } else if (mWallpaperColors != null) { - LocalColorExtractor.newInstance(context) - .applyColorsOverride(context, mWallpaperColors); - } - if (mWallpaperColors != null) { - return new ContextThemeWrapper(context, - Themes.getActivityThemeRes(context, mWallpaperColors.getColorHints())); - } else { - return new ContextThemeWrapper(context, - Themes.getActivityThemeRes(context)); - } - } else { - if (mWallpaperColors == null) { - return new ContextThemeWrapper(context, - Themes.getActivityThemeRes(context)); - } - LocalColorExtractor.newInstance(context) - .applyColorsOverride(context, mWallpaperColors); + if (mWallpaperColors == null) { return new ContextThemeWrapper(context, - Themes.getActivityThemeRes(context, mWallpaperColors.getColorHints())); + Themes.getActivityThemeRes(context)); } + if (Utilities.ATLEAST_R) { + context = context.createWindowContext( + WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, null); + } + LocalColorExtractor.newInstance(mContext) + .applyColorsOverride(context, mWallpaperColors); + return new ContextThemeWrapper(context, + Themes.getActivityThemeRes(context, mWallpaperColors.getColorHints())); } @WorkerThread private void loadModelData() { final Context inflationContext = getPreviewContext(); - if (!mGridName.equals(LauncherPrefs.INSTANCE.get(mContext).get(GRID_NAME)) - || !mShapeKey.equals(LauncherPrefs.INSTANCE.get(mContext).get(PREF_ICON_SHAPE)) - || !TextUtils.isEmpty(mLayoutXml)) { - - boolean isCustomLayout = extendibleThemeManager() && !TextUtils.isEmpty(mLayoutXml); - int widgetHostId = isCustomLayout ? APPWIDGET_HOST_ID + mCallingPid : APPWIDGET_HOST_ID; - + final InvariantDeviceProfile idp = new InvariantDeviceProfile(inflationContext, mGridName); + if (GridSizeMigrationUtil.needsToMigrate(inflationContext, idp)) { // Start the migration - PreviewContext previewContext = new PreviewContext( - inflationContext, mGridName, mShapeKey, widgetHostId, mLayoutXml); - PreviewAppComponent appComponent = - (PreviewAppComponent) LauncherComponentProvider.get(previewContext); + PreviewContext previewContext = new PreviewContext(inflationContext, idp); + // Copy existing data to preview DB + LauncherDbUtils.copyTable(LauncherAppState.getInstance(mContext) + .getModel().getModelDbController().getDb(), + TABLE_NAME, + LauncherAppState.getInstance(previewContext) + .getModel().getModelDbController().getDb(), + TABLE_NAME, + mContext); + LauncherAppState.getInstance(previewContext) + .getModel().getModelDbController().clearEmptyDbFlag(); - if (extendibleThemeManager() && isCustomLayout && !mDeletingHostOnExit) { - mDeletingHostOnExit = true; - mLifeCycleTracker.add(() -> { - AppWidgetHost host = new AppWidgetHost(mContext, widgetHostId); - // Start listening here, so that any previous active host is disabled - host.startListening(); - host.stopListening(); - host.deleteHost(); - }); - } + BgDataModel bgModel = new BgDataModel(); + new LoaderTask( + LauncherAppState.getInstance(previewContext), + /* bgAllAppsList= */ null, + bgModel, + LauncherAppState.getInstance(previewContext).getModel().getModelDelegate(), + new BaseLauncherBinder(LauncherAppState.getInstance(previewContext), bgModel, + /* bgAllAppsList= */ null, new Callbacks[0])) { - LoaderTask task = appComponent.getLoaderTaskFactory().newLoaderTask( - appComponent.getBaseLauncherBinderFactory().createBinder(new Callbacks[0]), - new UserManagerState()); + @Override + public void run() { + DeviceProfile deviceProfile = idp.getDeviceProfile(previewContext); + String query = LauncherSettings.Favorites.SCREEN + " = " + Workspace.FIRST_SCREEN_ID + + " or " + LauncherSettings.Favorites.CONTAINER + " = " + + LauncherSettings.Favorites.CONTAINER_HOTSEAT; + if (deviceProfile.isTwoPanels) { + query += " or " + LauncherSettings.Favorites.SCREEN + " = " + + Workspace.SECOND_SCREEN_ID; + } + loadWorkspace(new ArrayList<>(), query, null, null); - InvariantDeviceProfile idp = appComponent.getIDP(); - DeviceProfile deviceProfile = idp.getDeviceProfile(previewContext); - String query = deviceProfile.isTwoPanels - ? selectionForWorkspaceScreen(FIRST_SCREEN_ID, SECOND_SCREEN_ID) - : selectionForWorkspaceScreen(FIRST_SCREEN_ID); - Map widgetProviderInfoMap = new HashMap<>(); - task.loadWorkspaceForPreview(query, widgetProviderInfoMap); - final SparseArray spanInfo = getLoadedLauncherWidgetInfo(); - MAIN_EXECUTOR.execute(() -> { - renderView(previewContext, appComponent.getDataModel(), widgetHostId, - widgetProviderInfoMap, spanInfo, idp); - mLifeCycleTracker.add(previewContext::onDestroy); - }); + final SparseArray spanInfo = getLoadedLauncherWidgetInfo(previewContext.getBaseContext()); + + MAIN_EXECUTOR.execute(() -> { + renderView(previewContext, mBgDataModel, mWidgetProvidersMap, spanInfo, + idp); + mOnDestroyCallbacks.add(previewContext::onDestroy); + }); + } + }.run(); } else { LauncherAppState.getInstance(inflationContext).getModel().loadAsync(dataModel -> { if (dataModel != null) { - MAIN_EXECUTOR.execute(() -> renderView(inflationContext, dataModel, - APPWIDGET_HOST_ID, null, null, - LauncherAppState.getIDP(inflationContext))); + MAIN_EXECUTOR.execute(() -> renderView(inflationContext, dataModel, null, + null, idp)); } else { Log.e(TAG, "Model loading failed"); } @@ -400,97 +290,30 @@ public class PreviewSurfaceRenderer { } @UiThread - private void renderView(Context inflationContext, BgDataModel dataModel, int widgetHostId, + private void renderView(Context inflationContext, BgDataModel dataModel, Map widgetProviderInfoMap, @Nullable final SparseArray launcherWidgetSpanInfo, InvariantDeviceProfile idp) { if (mDestroyed) { return; } - LauncherPreviewRenderer renderer; - if (Flags.newCustomizationPickerUi()) { - renderer = new LauncherPreviewRenderer(inflationContext, idp, widgetHostId, - mPreviewColorOverride, mWallpaperColors, launcherWidgetSpanInfo); - } else { - renderer = new LauncherPreviewRenderer(inflationContext, idp, widgetHostId, - mWallpaperColors, launcherWidgetSpanInfo); - } - renderer.hideBottomRow(mHideQsb); - View view = renderer.getRenderedView(dataModel, widgetProviderInfoMap); - + mRenderer = new LauncherPreviewRenderer(inflationContext, idp, + mWallpaperColors, launcherWidgetSpanInfo); + // mRenderer.hideBottomRow(mHideQsb); + View view = mRenderer.getRenderedView(dataModel, widgetProviderInfoMap); + // This aspect scales the view to fit in the surface and centers it + final float scale = Math.min(mWidth / (float) view.getMeasuredWidth(), + mHeight / (float) view.getMeasuredHeight()); + view.setScaleX(scale); + view.setScaleY(scale); view.setPivotX(0); view.setPivotY(0); - if (idp.isFixedLandscape) { - final float scale = Math.min(mHeight / (float) view.getMeasuredWidth(), - mWidth / (float) view.getMeasuredHeight()); - view.setScaleX(scale); - view.setScaleY(scale); - view.setRotation(90); - view.setTranslationX((mHeight - scale * view.getWidth()) / 2 + mWidth); - view.setTranslationY((mWidth - scale * view.getHeight()) / 2); - } else { - // This aspect scales the view to fit in the surface and centers it - final float scale = Math.min(mWidth / (float) view.getMeasuredWidth(), - mHeight / (float) view.getMeasuredHeight()); - view.setScaleX(scale); - view.setScaleY(scale); - view.setTranslationX((mWidth - scale * view.getWidth()) / 2); - view.setTranslationY((mHeight - scale * view.getHeight()) / 2); - } - - if (!Flags.newCustomizationPickerUi()) { - view.setAlpha(mSkipAnimations ? 1 : 0); - view.animate().alpha(1) - .setInterpolator(new AccelerateDecelerateInterpolator()) - .setDuration(FADE_IN_ANIMATION_DURATION) - .start(); - mSurfaceControlViewHost.setView( - view, - view.getMeasuredWidth(), - view.getMeasuredHeight() - ); - return; - } - - if (mViewRoot == null) { - mViewRoot = new FrameLayout(inflationContext); - FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams( - FrameLayout.LayoutParams.WRAP_CONTENT, // Width - FrameLayout.LayoutParams.WRAP_CONTENT // Height - ); - mViewRoot.setLayoutParams(layoutParams); - mViewRoot.addView(view); - mViewRoot.setAlpha(mSkipAnimations ? 1 : 0); - mViewRoot.animate().alpha(1) - .setInterpolator(new AccelerateDecelerateInterpolator()) - .setDuration(FADE_IN_ANIMATION_DURATION) - .start(); - mSurfaceControlViewHost.setView( - mViewRoot, - view.getMeasuredWidth(), - view.getMeasuredHeight() - ); - } else { - mViewRoot.removeAllViews(); - mViewRoot.addView(view); - } - } - - private static class MySurfaceControlViewHost extends SurfaceControlViewHost { - - private final RunnableList mLifecycleTracker; - - MySurfaceControlViewHost(Context context, Display display, IBinder hostToken, - RunnableList lifeCycleTracker) { - super(context, display, hostToken); - mLifecycleTracker = lifeCycleTracker; - mLifecycleTracker.add(this::release); - } - - @Override - public void release() { - super.release(); - // RunnableList ensures that the callback is only called once - MAIN_EXECUTOR.execute(mLifecycleTracker::executeAllAndDestroy); - } + view.setTranslationX((mWidth - scale * view.getWidth()) / 2); + view.setTranslationY((mHeight - scale * view.getHeight()) / 2); + view.setAlpha(0); + view.animate().alpha(1) + .setInterpolator(new AccelerateDecelerateInterpolator()) + .setDuration(FADE_IN_ANIMATION_DURATION) + .start(); + mSurfaceControlViewHost.setView(view, view.getMeasuredWidth(), view.getMeasuredHeight()); } } diff --git a/src/com/android/launcher3/graphics/SysUiScrim.java b/src/com/android/launcher3/graphics/SysUiScrim.java index 6bc8185d82..7b596aa0e6 100644 --- a/src/com/android/launcher3/graphics/SysUiScrim.java +++ b/src/com/android/launcher3/graphics/SysUiScrim.java @@ -13,13 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * Modifications copyright 2025, Lawnchair + * Modifications copyright 2022, Lawnchair */ package com.android.launcher3.graphics; import static android.graphics.Paint.DITHER_FLAG; import static android.graphics.Paint.FILTER_BITMAP_FLAG; +import static com.android.launcher3.config.FeatureFlags.KEYGUARD_ANIMATION; + import android.animation.ObjectAnimator; import android.graphics.Bitmap; import android.graphics.Canvas; @@ -34,16 +36,14 @@ import android.view.View; import androidx.annotation.ColorInt; import androidx.annotation.VisibleForTesting; +import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.DeviceProfile; import com.android.launcher3.R; import com.android.launcher3.anim.AnimatedFloat; -import com.android.launcher3.statemanager.StatefulContainer; import com.android.launcher3.testing.shared.ResourceUtils; import com.android.launcher3.util.ScreenOnTracker; import com.android.launcher3.util.ScreenOnTracker.ScreenOnListener; import com.android.launcher3.util.Themes; -import com.android.launcher3.views.ActivityContext; - import com.patrykmichalik.opto.core.PreferenceExtensionsKt; import app.lawnchair.preferences2.PreferenceManager2; import app.lawnchair.util.ViewExtensionsKt; @@ -66,7 +66,8 @@ public class SysUiScrim implements View.OnAttachStateChangeListener { @Override public void onUserPresent() { - // ACTION_USER_PRESENT is sent after onStart/onResume. This covers the case where + // ACTION_USER_PRESENT is sent after onStart/onResume. This covers the case + // where // the user unlocked and the Launcher is not in the foreground. mAnimateScrimOnNextDraw = false; } @@ -91,7 +92,7 @@ public class SysUiScrim implements View.OnAttachStateChangeListener { private final int mBottomMaskHeight; private final View mRoot; - private final StatefulContainer mContainer; + private final BaseDraggingActivity mActivity; private boolean mHideSysUiScrim; private boolean mSkipScrimAnimationForTest = false; @@ -101,8 +102,8 @@ public class SysUiScrim implements View.OnAttachStateChangeListener { public SysUiScrim(View view) { mRoot = view; - mContainer = ActivityContext.lookupContext(view.getContext()); - DisplayMetrics dm = mContainer.asContext().getResources().getDisplayMetrics(); + mActivity = BaseDraggingActivity.fromContext(view.getContext()); + DisplayMetrics dm = mActivity.getResources().getDisplayMetrics(); mTopMaskHeight = ResourceUtils.pxFromDp(TOP_MASK_HEIGHT_DP, dm); mBottomMaskHeight = ResourceUtils.pxFromDp(BOTTOM_MASK_HEIGHT_DP, dm); @@ -118,7 +119,7 @@ public class SysUiScrim implements View.OnAttachStateChangeListener { new int[] { 0x00FFFFFF, 0x2FFFFFFF }, new float[] { 0f, 1f }); - if (!mHideSysUiScrim) { + if (!KEYGUARD_ANIMATION.get() && !mHideSysUiScrim) { view.addOnAttachStateChangeListener(this); } @@ -151,7 +152,7 @@ public class SysUiScrim implements View.OnAttachStateChangeListener { ObjectAnimator oa = mSysUiAnimMultiplier.animateToValue(1); oa.setDuration(600); - oa.setStartDelay(mContainer.getWindow().getTransitionBackgroundFadeDuration()); + oa.setStartDelay(mActivity.getWindow().getTransitionBackgroundFadeDuration()); oa.start(); mAnimateScrimOnNextDraw = false; } @@ -166,7 +167,8 @@ public class SysUiScrim implements View.OnAttachStateChangeListener { } /** - * Returns the sysui multiplier property for controlling fade in/out of the scrim + * Returns the sysui multiplier property for controlling fade in/out of the + * scrim */ public AnimatedFloat getSysUIMultiplier() { return mSysUiAnimMultiplier; @@ -182,24 +184,26 @@ public class SysUiScrim implements View.OnAttachStateChangeListener { /** * Determines whether to draw the top and/or bottom scrim based on new insets. * - * In order for the bottom scrim to be drawn this 3 condition should be meet at the same time: - * the device is in 3 button navigation, the taskbar is not present and the Hotseat is + * In order for the bottom scrim to be drawn this 3 condition should be meet at + * the same time: + * the device is in 3 button navigation, the taskbar is not present and the + * Hotseat is * horizontal */ public void onInsetsChanged(Rect insets) { - DeviceProfile dp = mContainer.getDeviceProfile(); + DeviceProfile dp = mActivity.getDeviceProfile(); mDrawTopScrim = insets.top > 0; mDrawBottomScrim = !dp.isVerticalBarLayout() && !dp.isGestureMode && !dp.isTaskbarPresent; } @Override public void onViewAttachedToWindow(View view) { - ScreenOnTracker.INSTANCE.get(mContainer.asContext()).addListener(mScreenOnListener); + ScreenOnTracker.INSTANCE.get(mActivity).addListener(mScreenOnListener); } @Override public void onViewDetachedFromWindow(View view) { - ScreenOnTracker.INSTANCE.get(mContainer.asContext()).removeListener(mScreenOnListener); + ScreenOnTracker.INSTANCE.get(mActivity).removeListener(mScreenOnListener); } /** @@ -228,13 +232,14 @@ public class SysUiScrim implements View.OnAttachStateChangeListener { private void reapplySysUiAlphaNoInvalidate() { float factor = mSysUiProgress.value * mSysUiAnimMultiplier.value; - if (mSkipScrimAnimationForTest) factor = 1f; + if (mSkipScrimAnimationForTest) + factor = 1f; mBottomMaskPaint.setAlpha(Math.round(MAX_SYSUI_SCRIM_ALPHA * factor)); mTopMaskPaint.setAlpha(Math.round(MAX_SYSUI_SCRIM_ALPHA * factor)); } private Bitmap createDitheredAlphaMask(int height, @ColorInt int[] colors, float[] positions) { - DisplayMetrics dm = mContainer.asContext().getResources().getDisplayMetrics(); + DisplayMetrics dm = mActivity.getResources().getDisplayMetrics(); int width = ResourceUtils.pxFromDp(ALPHA_MASK_BITMAP_WIDTH_DP, dm); Bitmap dst = Bitmap.createBitmap(width, height, Bitmap.Config.ALPHA_8); Canvas c = new Canvas(dst); diff --git a/src/com/android/launcher3/icons/ComponentWithLabel.java b/src/com/android/launcher3/icons/ComponentWithLabel.java new file mode 100644 index 0000000000..30575fcbe5 --- /dev/null +++ b/src/com/android/launcher3/icons/ComponentWithLabel.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.icons; + +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.UserHandle; + +import androidx.annotation.NonNull; + +import com.android.launcher3.icons.cache.CachingLogic; + +public interface ComponentWithLabel { + + ComponentName getComponent(); + + UserHandle getUser(); + + CharSequence getLabel(PackageManager pm); + + + class ComponentCachingLogic implements CachingLogic { + + private final PackageManager mPackageManager; + private final boolean mAddToMemCache; + + public ComponentCachingLogic(Context context, boolean addToMemCache) { + mPackageManager = context.getPackageManager(); + mAddToMemCache = addToMemCache; + } + + @Override + @NonNull + public ComponentName getComponent(@NonNull T object) { + return object.getComponent(); + } + + @NonNull + @Override + public UserHandle getUser(@NonNull T object) { + return object.getUser(); + } + + @NonNull + @Override + public CharSequence getLabel(@NonNull T object) { + return object.getLabel(mPackageManager); + } + + @NonNull + @Override + public BitmapInfo loadIcon(@NonNull Context context, @NonNull T object) { + return BitmapInfo.LOW_RES_INFO; + } + + @Override + public boolean addToMemCache() { + return mAddToMemCache; + } + } +} diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java index 7d856f6d71..64a27e3a1c 100644 --- a/src/com/android/launcher3/icons/IconCache.java +++ b/src/com/android/launcher3/icons/IconCache.java @@ -17,10 +17,8 @@ package com.android.launcher3.icons; import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT; -import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; -import static com.android.launcher3.util.LooperExecutor.CALLER_ICON_CACHE; import static com.android.launcher3.widget.WidgetSections.NO_CATEGORY; import static java.util.stream.Collectors.groupingBy; @@ -31,11 +29,16 @@ import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.LauncherActivityInfo; import android.content.pm.LauncherApps; +import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ShortcutInfo; import android.database.Cursor; import android.database.sqlite.SQLiteException; +import android.graphics.drawable.Drawable; import android.os.Looper; +import android.os.Process; import android.os.Trace; import android.os.UserHandle; import android.text.TextUtils; @@ -52,13 +55,9 @@ import com.android.launcher3.Flags; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.LauncherFiles; import com.android.launcher3.Utilities; -import com.android.launcher3.dagger.ApplicationContext; -import com.android.launcher3.dagger.LauncherAppSingleton; +import com.android.launcher3.icons.ComponentWithLabel.ComponentCachingLogic; import com.android.launcher3.icons.cache.BaseIconCache; -import com.android.launcher3.icons.cache.CacheLookupFlag; -import com.android.launcher3.icons.cache.CachedObject; -import com.android.launcher3.icons.cache.CachedObjectCachingLogic; -import com.android.launcher3.icons.cache.LauncherActivityCachingLogic; +import com.android.launcher3.icons.cache.CachingLogic; import com.android.launcher3.logging.FileLog; import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.model.data.IconRequestInfo; @@ -67,9 +66,9 @@ import com.android.launcher3.model.data.PackageItemInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.pm.InstallSessionHelper; import com.android.launcher3.pm.UserCache; +import com.android.launcher3.shortcuts.ShortcutKey; +import com.android.launcher3.util.FlagOp; import com.android.launcher3.util.CancellableTask; -import com.android.launcher3.util.ComponentKey; -import com.android.launcher3.util.DaggerSingletonTracker; import com.android.launcher3.util.InstantAppResolver; import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.widget.WidgetSections; @@ -83,107 +82,61 @@ import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Stream; -import javax.inject.Inject; -import javax.inject.Named; - import app.lawnchair.icons.LawnchairIconProvider; /** - * Cache of application icons. Icons can be made from any thread. + * Cache of application icons. Icons can be made from any thread. */ -@LauncherAppSingleton public class IconCache extends BaseIconCache { - // Shortcut extra which can point to a packageName and can be used to indicate an alternate + // Shortcut extra which can point to a packageName and can be used to indicate + // an alternate // badge info. Launcher only reads this if the shortcut comes from a system app. - public static final String EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE = - "extra_shortcut_badge_override_package"; + public static final String EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE = "extra_shortcut_badge_override_package"; private static final String TAG = "Launcher.IconCache"; - private final Predicate mIsUsingFallbackOrNonDefaultIconCheck = w -> - w.bitmap != null && (w.bitmap.isNullOrLowRes() || !isDefaultIcon(w.bitmap, w.user)); + private final Predicate mIsUsingFallbackOrNonDefaultIconCheck = w -> w.bitmap != null + && (w.bitmap.isNullOrLowRes() || !isDefaultIcon(w.bitmap, w.user)); + + private final CachingLogic mComponentWithLabelCachingLogic; + private final CachingLogic mLauncherActivityInfoCachingLogic; + private final CachingLogic mShortcutCachingLogic; private final LauncherApps mLauncherApps; private final UserCache mUserManager; - private final InstallSessionHelper mInstallSessionHelper; private final InstantAppResolver mInstantAppResolver; + private final IconProvider mIconProvider; private final CancellableTask mCancelledTask; - private final LauncherIcons.IconPool mIconPool; private final SparseArray mWidgetCategoryBitmapInfos; private int mPendingIconRequestCount = 0; - // Lawnchair: Apply 3p icon pack - public IconCache(Context context, InvariantDeviceProfile idp, LauncherIcons.IconPool iconPool, DaggerSingletonTracker lifecycle) { - this( - context, - idp, - LauncherFiles.APP_ICONS_DB, - UserCache.INSTANCE.get(context), - new LawnchairIconProvider(context), - InstallSessionHelper.INSTANCE.get(context), - iconPool, - lifecycle - ); - } - - @Inject - public IconCache( - @ApplicationContext Context context, - InvariantDeviceProfile idp, - @Nullable @Named("ICONS_DB") String dbFileName, - UserCache userCache, - LauncherIconProvider iconProvider, - // TODO: Lawnchair stuff - // IconProvider iconProvider, - InstallSessionHelper installSessionHelper, - LauncherIcons.IconPool iconPool, - DaggerSingletonTracker lifecycle) { - super(context, dbFileName, MODEL_EXECUTOR.getLooper(), - idp.fillResIconDpi, idp.iconBitmapSize, true /* inMemoryCache */, iconProvider); - mLauncherApps = context.getSystemService(LauncherApps.class); - mUserManager = userCache; - mInstallSessionHelper = installSessionHelper; - mIconPool = iconPool; - - mInstantAppResolver = InstantAppResolver.newInstance(context); - mWidgetCategoryBitmapInfos = new SparseArray<>(); - - mCancelledTask = new CancellableTask(() -> null, MAIN_EXECUTOR, c -> { }); - mCancelledTask.cancel(); - - lifecycle.addCloseable(this::close); + public IconCache(Context context, InvariantDeviceProfile idp) { + this(context, idp, LauncherFiles.APP_ICONS_DB, new LawnchairIconProvider(context)); } - public IconCache( - @ApplicationContext Context context, - InvariantDeviceProfile idp, - @Nullable @Named("ICONS_DB") String dbFileName, - UserCache userCache, - IconProvider iconProvider, - InstallSessionHelper installSessionHelper, - LauncherIcons.IconPool iconPool, - DaggerSingletonTracker lifecycle) { + public IconCache(Context context, InvariantDeviceProfile idp, String dbFileName, + IconProvider iconProvider) { super(context, dbFileName, MODEL_EXECUTOR.getLooper(), - idp.fillResIconDpi, Math.max(idp.iconBitmapSize, idp.allAppsIconBitmapSize), true /* inMemoryCache */, iconProvider); - mLauncherApps = context.getSystemService(LauncherApps.class); - mUserManager = userCache; - mInstallSessionHelper = installSessionHelper; - mIconPool = iconPool; - - mInstantAppResolver = InstantAppResolver.newInstance(context); + idp.fillResIconDpi, Math.max(idp.iconBitmapSize, idp.allAppsIconBitmapSize), true /* inMemoryCache */); + mComponentWithLabelCachingLogic = new ComponentCachingLogic(context, false); + mLauncherActivityInfoCachingLogic = LauncherActivityCachingLogic.newInstance(context); + mShortcutCachingLogic = new ShortcutCachingLogic(); + mLauncherApps = mContext.getSystemService(LauncherApps.class); + mUserManager = UserCache.INSTANCE.get(mContext); + mInstantAppResolver = InstantAppResolver.newInstance(mContext); + mIconProvider = iconProvider; mWidgetCategoryBitmapInfos = new SparseArray<>(); - mCancelledTask = new CancellableTask(() -> null, MAIN_EXECUTOR, c -> { }); + mCancelledTask = new CancellableTask(() -> null, MAIN_EXECUTOR, c -> { + }); mCancelledTask.cancel(); - - lifecycle.addCloseable(this::close); } @Override - public long getSerialNumberForUser(@NonNull UserHandle user) { + protected long getSerialNumberForUser(@NonNull UserHandle user) { return mUserManager.getSerialNumberForUser(user); } @@ -195,26 +148,25 @@ public class IconCache extends BaseIconCache { @NonNull @Override public BaseIconFactory getIconFactory() { - return mIconPool.obtain(); + return LauncherIcons.obtain(mContext); } /** * Updates the entries related to the given package in memory and persistent DB. */ public synchronized void updateIconsForPkg(@NonNull final String packageName, - @NonNull final UserHandle user) { - List apps = mLauncherApps.getActivityList(packageName, user); - if (Utilities.ATLEAST_V) { - if (Flags.restoreArchivedAppIconsFromDb() - && apps.stream().anyMatch(app -> app.getApplicationInfo().isArchived)) { - // When archiving app icon, don't delete old icon so it can be re-used. - return; - } - } + @NonNull final UserHandle user) { removeIconsForPkg(packageName, user); - long userSerial = mUserManager.getSerialNumberForUser(user); - for (LauncherActivityInfo app : apps) { - addIconToDBAndMemCache(app, LauncherActivityCachingLogic.INSTANCE, userSerial); + try { + PackageInfo info = mPackageManager.getPackageInfo(packageName, + PackageManager.GET_UNINSTALLED_PACKAGES); + long userSerial = mUserManager.getSerialNumberForUser(user); + for (LauncherActivityInfo app : mLauncherApps.getActivityList(packageName, user)) { + addIconToDBAndMemCache(app, mLauncherActivityInfoCachingLogic, info, userSerial, + false /* replace existing */); + } + } catch (NameNotFoundException e) { + Log.d(TAG, "Package not found", e); } } @@ -225,55 +177,57 @@ public class IconCache extends BaseIconCache { // This will clear all pending updates getUpdateHandler(); - iconDb.close(); + mIconDb.close(); } /** - * Fetches high-res icon for the provided ItemInfo and updates the caller when done. + * Fetches high-res icon for the provided ItemInfo and updates the caller when + * done. * * @return a request ID that can be used to cancel the request. */ @AnyThread public CancellableTask updateIconInBackground(final ItemInfoUpdateReceiver caller, - final ItemInfoWithIcon info) { + final ItemInfoWithIcon info) { Supplier task; if (info instanceof AppInfo || info instanceof WorkspaceItemInfo) { task = () -> { - getTitleAndIcon(info, DEFAULT_LOOKUP_FLAG); + getTitleAndIcon(info, false); return info; }; } else if (info instanceof PackageItemInfo pii) { task = () -> { - getTitleAndIconForApp(pii, DEFAULT_LOOKUP_FLAG); + getTitleAndIconForApp(pii, false); return pii; }; } else { Log.i(TAG, "Icon update not supported for " - + info == null ? "null" : info.getClass().getName()); + + info == null ? "null" : info.getClass().getName()); return mCancelledTask; } Runnable endRunnable; if (Looper.myLooper() == Looper.getMainLooper()) { if (mPendingIconRequestCount <= 0) { - MODEL_EXECUTOR.elevatePriority(CALLER_ICON_CACHE); + MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND); } mPendingIconRequestCount++; endRunnable = this::onIconRequestEnd; } else { - endRunnable = () -> { }; + endRunnable = () -> { + }; } CancellableTask request = new CancellableTask<>( - task, MAIN_EXECUTOR, caller::reapplyItemInfo, endRunnable); - Utilities.postAsyncCallback(workerHandler, request); + task, MAIN_EXECUTOR, caller::reapplyItemInfo, endRunnable); + Utilities.postAsyncCallback(mWorkerHandler, request); return request; } private void onIconRequestEnd() { mPendingIconRequestCount--; if (mPendingIconRequestCount <= 0) { - MODEL_EXECUTOR.restorePriority(CALLER_ICON_CACHE); + MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); } } @@ -281,62 +235,67 @@ public class IconCache extends BaseIconCache { * Updates {@param application} only if a valid entry is found. */ public synchronized void updateTitleAndIcon(AppInfo application) { + boolean preferPackageIcon = application.isArchived(); CacheEntry entry = cacheLocked(application.componentName, - application.user, () -> null, LauncherActivityCachingLogic.INSTANCE, - application.getMatchingLookupFlag()); - if (entry.bitmap != null || !isDefaultIcon(entry.bitmap, application.user)) { + application.user, () -> null, mLauncherActivityInfoCachingLogic, + false, application.usingLowResIcon()); + if (entry.bitmap == null || isDefaultIcon(entry.bitmap, application.user)) { + return; + } + + if (preferPackageIcon) { + String packageName = application.getTargetPackage(); + CacheEntry packageEntry = cacheLocked(new ComponentName(packageName, packageName + EMPTY_CLASS_NAME), + application.user, () -> null, mLauncherActivityInfoCachingLogic, + true, application.usingLowResIcon()); + applyPackageEntry(packageEntry, application, entry); + } else { applyCacheEntry(entry, application); } } /** - * Fill in {@code info} with the icon and label for {@code activityInfo} + * Fill in {@param info} with the icon and label for {@param activityInfo} */ @SuppressWarnings("NewApi") public synchronized void getTitleAndIcon(ItemInfoWithIcon info, - LauncherActivityInfo activityInfo, @NonNull CacheLookupFlag lookupFlag) { - boolean isAppArchived = Flags.enableSupportForArchiving() && activityInfo != null - && activityInfo.getActivityInfo().isArchived; + LauncherActivityInfo activityInfo, boolean useLowResIcon) { + boolean isAppArchived; + try { + isAppArchived = Flags.enableSupportForArchiving() && activityInfo != null + && activityInfo.getActivityInfo().isArchived; + } catch (Throwable e) { + isAppArchived = false; + } // If we already have activity info, no need to use package icon - getTitleAndIcon(info, () -> activityInfo, lookupFlag.withUsePackageIcon(isAppArchived)); + getTitleAndIcon(info, () -> activityInfo, isAppArchived, useLowResIcon, + isAppArchived); } /** - * Fill in {@code info} with the icon for {@code si} + * Fill in {@param info} with the icon for {@param si} */ public void getShortcutIcon(ItemInfoWithIcon info, ShortcutInfo si) { - getShortcutIcon(info, new CacheableShortcutInfo(si, context)); - } - - /** - * Fill in {@code info} with the icon for {@code si} - */ - public void getShortcutIcon(ItemInfoWithIcon info, CacheableShortcutInfo si) { getShortcutIcon(info, si, mIsUsingFallbackOrNonDefaultIconCheck); } /** - * Fill in {@code info} with the icon and label for {@code si}. If the icon is not + * Fill in {@param info} with the icon and label for {@param si}. If the icon is + * not * available, and fallback check returns true, it keeps the old icon. - * Shortcut entries are not kept in memory since they are not frequently used */ - public void getShortcutIcon(T info, CacheableShortcutInfo si, - @NonNull Predicate fallbackIconCheck) { - UserHandle user = CacheableShortcutCachingLogic.INSTANCE.getUser(si); - BitmapInfo bitmapInfo = cacheLocked( - CacheableShortcutCachingLogic.INSTANCE.getComponent(si), - user, - () -> si, - CacheableShortcutCachingLogic.INSTANCE, - DEFAULT_LOOKUP_FLAG.withSkipAddToMemCache()).bitmap; + public void getShortcutIcon(T info, ShortcutInfo si, + @NonNull Predicate fallbackIconCheck) { + BitmapInfo bitmapInfo = cacheLocked(ShortcutKey.fromInfo(si).componentName, + si.getUserHandle(), () -> si, mShortcutCachingLogic, false, false).bitmap; if (bitmapInfo.isNullOrLowRes()) { - bitmapInfo = getDefaultIcon(user); + bitmapInfo = getDefaultIcon(si.getUserHandle()); } - if (isDefaultIcon(bitmapInfo, user) && fallbackIconCheck.test(info)) { + if (isDefaultIcon(bitmapInfo, si.getUserHandle()) && fallbackIconCheck.test(info)) { return; } - info.bitmap = bitmapInfo.withBadgeInfo(getShortcutInfoBadge(si.getShortcutInfo())); + info.bitmap = bitmapInfo.withBadgeInfo(getShortcutInfoBadge(si)); } /** @@ -351,9 +310,10 @@ public class IconCache extends BaseIconCache { // Check for badge override first. String pkg = shortcutInfo.getPackage(); String override = shortcutInfo.getExtras() == null ? null - : shortcutInfo.getExtras().getString(EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE); + : shortcutInfo.getExtras().getString(EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE); if (!TextUtils.isEmpty(override) - && mInstallSessionHelper.isTrustedPackage(pkg, shortcutInfo.getUserHandle())) { + && InstallSessionHelper.INSTANCE.get(mContext) + .isTrustedPackage(pkg, shortcutInfo.getUserHandle())) { pkg = override; } else { // Try component based badge before trying the normal package badge @@ -364,14 +324,14 @@ public class IconCache extends BaseIconCache { appInfo.user = shortcutInfo.getUserHandle(); appInfo.componentName = cn; appInfo.intent = new Intent(Intent.ACTION_MAIN) - .addCategory(Intent.CATEGORY_LAUNCHER) - .setComponent(cn); - getTitleAndIcon(appInfo, DEFAULT_LOOKUP_FLAG); + .addCategory(Intent.CATEGORY_LAUNCHER) + .setComponent(cn); + getTitleAndIcon(appInfo, false); return appInfo; } } PackageItemInfo pkgInfo = new PackageItemInfo(pkg, shortcutInfo.getUserHandle()); - getTitleAndIconForApp(pkgInfo, DEFAULT_LOOKUP_FLAG); + getTitleAndIconForApp(pkgInfo, false); return pkgInfo; } @@ -379,10 +339,9 @@ public class IconCache extends BaseIconCache { * Fill in {@param info} with the icon and label. If the * corresponding activity is not found, it reverts to the package icon. */ - public synchronized void getTitleAndIcon( - @NonNull ItemInfoWithIcon info, - @NonNull CacheLookupFlag lookupFlag) { - // null info means not installed, but if we have a component from the intent then + public synchronized void getTitleAndIcon(ItemInfoWithIcon info, boolean useLowResIcon) { + // null info means not installed, but if we have a component from the intent + // then // we should still look in the cache for restored app icons. if (info.getTargetComponent() == null) { info.bitmap = getDefaultIcon(info.user); @@ -391,17 +350,14 @@ public class IconCache extends BaseIconCache { } else { Intent intent = info.getIntent(); getTitleAndIcon(info, () -> mLauncherApps.resolveActivity(intent, info.user), - lookupFlag.withUsePackageIcon()); + true, useLowResIcon, info.isArchived()); } } - /** - * Loads and returns the icon for the provided object without adding it to memCache - */ - public synchronized String getTitleNoCache(CachedObject info) { + public synchronized String getTitleNoCache(ComponentWithLabel info) { CacheEntry entry = cacheLocked(info.getComponent(), info.getUser(), () -> info, - CachedObjectCachingLogic.INSTANCE, - DEFAULT_LOOKUP_FLAG.withUseLowRes().withSkipAddToMemCache()); + mComponentWithLabelCachingLogic, false /* usePackageIcon */, + true /* useLowResIcon */); return Utilities.trim(entry.title); } @@ -409,80 +365,108 @@ public class IconCache extends BaseIconCache { * Fill in {@param mWorkspaceItemInfo} with the icon and label for {@param info} */ public synchronized void getTitleAndIcon( - @NonNull ItemInfoWithIcon infoInOut, - @NonNull Supplier activityInfoProvider, - @NonNull CacheLookupFlag lookupFlag) { + @NonNull ItemInfoWithIcon infoInOut, + @NonNull Supplier activityInfoProvider, + boolean usePkgIcon, boolean useLowResIcon) { CacheEntry entry = cacheLocked(infoInOut.getTargetComponent(), infoInOut.user, - activityInfoProvider, LauncherActivityCachingLogic.INSTANCE, lookupFlag); + activityInfoProvider, mLauncherActivityInfoCachingLogic, usePkgIcon, + useLowResIcon); applyCacheEntry(entry, infoInOut); } /** - * Creates an sql cursor for a query of a set of ItemInfoWithIcon icons and titles. - * - * @param iconRequestInfos List of IconRequestInfos representing titles and icons to query. - * @param user UserHandle all the given iconRequestInfos share - * @param lookupFlag what flags to use when loading the icon. + * Fill in {@param mWorkspaceItemInfo} with the icon and label for {@param info} */ - private Cursor createBulkQueryCursor( - List> iconRequestInfos, UserHandle user, CacheLookupFlag lookupFlag) - throws SQLiteException { - String[] queryParams = Stream.concat( - iconRequestInfos.stream() - .map(r -> r.itemInfo.getTargetComponent()) - .filter(Objects::nonNull) - .distinct() - .map(ComponentName::flattenToString), - Stream.of(Long.toString(getSerialNumberForUser(user)))).toArray(String[]::new); - String componentNameQuery = TextUtils.join( - ",", Collections.nCopies(queryParams.length - 1, "?")); - - return iconDb.query( - toLookupColumns(lookupFlag), - COLUMN_COMPONENT - + " IN ( " + componentNameQuery + " )" - + " AND " + COLUMN_USER + " = ?", - queryParams); + public synchronized void getTitleAndIcon( + @NonNull ItemInfoWithIcon infoInOut, + @NonNull Supplier activityInfoProvider, + boolean usePkgIcon, boolean useLowResIcon, boolean preferPackageEntry) { + CacheEntry entry = cacheLocked(infoInOut.getTargetComponent(), infoInOut.user, + activityInfoProvider, mLauncherActivityInfoCachingLogic, usePkgIcon, + useLowResIcon); + if (preferPackageEntry) { + String packageName = infoInOut.getTargetPackage(); + CacheEntry packageEntry = cacheLocked( + new ComponentName(packageName, packageName + EMPTY_CLASS_NAME), + infoInOut.user, activityInfoProvider, mLauncherActivityInfoCachingLogic, + usePkgIcon, useLowResIcon); + applyPackageEntry(packageEntry, infoInOut, entry); + } else if (useLowResIcon || !entry.bitmap.isNullOrLowRes() + || infoInOut.bitmap.isNullOrLowRes()) { + // Only use cache entry if it will not downgrade the current bitmap in infoInOut + applyCacheEntry(entry, infoInOut); + } else { + Log.d(TAG, "getTitleAndIcon: Cache entry bitmap was a downgrade of existing bitmap" + + " in ItemInfo. Skipping."); + } } /** - * Load and fill icons requested in iconRequestInfos using a single bulk sql query. + * Creates an sql cursor for a query of a set of ItemInfoWithIcon icons and + * titles. + * + * @param iconRequestInfos List of IconRequestInfos representing titles and + * icons to query. + * @param user UserHandle all the given iconRequestInfos share + * @param useLowResIcons whether we should exclude the icon column from the + * sql results. + */ + private Cursor createBulkQueryCursor( + List> iconRequestInfos, UserHandle user, boolean useLowResIcons) + throws SQLiteException { + String[] queryParams = Stream.concat( + iconRequestInfos.stream() + .map(r -> r.itemInfo.getTargetComponent()) + .filter(Objects::nonNull) + .distinct() + .map(ComponentName::flattenToString), + Stream.of(Long.toString(getSerialNumberForUser(user)))).toArray(String[]::new); + String componentNameQuery = TextUtils.join( + ",", Collections.nCopies(queryParams.length - 1, "?")); + + return mIconDb.query( + useLowResIcons ? IconDB.COLUMNS_LOW_RES : IconDB.COLUMNS_HIGH_RES, + IconDB.COLUMN_COMPONENT + + " IN ( " + componentNameQuery + " )" + + " AND " + IconDB.COLUMN_USER + " = ?", + queryParams); + } + + /** + * Load and fill icons requested in iconRequestInfos using a single bulk sql + * query. */ public synchronized void getTitlesAndIconsInBulk( - List> iconRequestInfos) { - Map, List>> iconLoadSubsectionsMap = - iconRequestInfos.stream() + List> iconRequestInfos) { + Map, List>> iconLoadSubsectionsMap = iconRequestInfos.stream() .filter(iconRequest -> { if (iconRequest.itemInfo.getTargetComponent() == null) { Log.i(TAG, - "Skipping Item info with null component name: " - + iconRequest.itemInfo); + "Skipping Item info with null component name: " + + iconRequest.itemInfo); iconRequest.itemInfo.bitmap = getDefaultIcon( - iconRequest.itemInfo.user); + iconRequest.itemInfo.user); return false; } return true; }) - .collect(groupingBy(iconRequest -> - Pair.create(iconRequest.itemInfo.user, iconRequest.useLowResIcon))); + .collect(groupingBy(iconRequest -> Pair.create(iconRequest.itemInfo.user, iconRequest.useLowResIcon))); Trace.beginSection("loadIconsInBulk"); iconLoadSubsectionsMap.forEach((sectionKey, filteredList) -> { - Map>> duplicateIconRequestsMap = - filteredList.stream() + Map>> duplicateIconRequestsMap = filteredList.stream() .filter(iconRequest -> { // Filter out icons that should not share the same bitmap and title if (iconRequest.itemInfo.itemType == ITEM_TYPE_DEEP_SHORTCUT) { Log.e(TAG, - "Skipping Item info for deep shortcut: " - + iconRequest.itemInfo, - new IllegalStateException()); + "Skipping Item info for deep shortcut: " + + iconRequest.itemInfo, + new IllegalStateException()); return false; } return true; }) - .collect(groupingBy(iconRequest -> - iconRequest.itemInfo.getTargetComponent())); + .collect(groupingBy(iconRequest -> iconRequest.itemInfo.getTargetComponent())); Trace.beginSection("loadIconSubsectionInBulk"); loadIconSubsection(sectionKey, filteredList, duplicateIconRequestsMap); @@ -492,39 +476,38 @@ public class IconCache extends BaseIconCache { } private void loadIconSubsection( - Pair sectionKey, - List> filteredList, - Map>> duplicateIconRequestsMap) { + Pair sectionKey, + List> filteredList, + Map>> duplicateIconRequestsMap) { Trace.beginSection("loadIconSubsectionWithDatabase"); - CacheLookupFlag lookupFlag = DEFAULT_LOOKUP_FLAG.withUseLowRes(sectionKey.second); try (Cursor c = createBulkQueryCursor( - filteredList, - /* user = */ sectionKey.first, - lookupFlag)) { + filteredList, + /* user = */ sectionKey.first, + /* useLowResIcons = */ sectionKey.second)) { // Database title and icon loading - int componentNameColumnIndex = c.getColumnIndexOrThrow(COLUMN_COMPONENT); + int componentNameColumnIndex = c.getColumnIndexOrThrow(IconDB.COLUMN_COMPONENT); while (c.moveToNext()) { ComponentName cn = ComponentName.unflattenFromString( - c.getString(componentNameColumnIndex)); - List> duplicateIconRequests = - duplicateIconRequestsMap.get(cn); + c.getString(componentNameColumnIndex)); + List> duplicateIconRequests = duplicateIconRequestsMap.get(cn); if (cn != null) { if (duplicateIconRequests != null) { CacheEntry entry = cacheLocked( - cn, - /* user = */ sectionKey.first, - () -> duplicateIconRequests.get(0).launcherActivityInfo, - LauncherActivityCachingLogic.INSTANCE, - lookupFlag, - c); + cn, + /* user = */ sectionKey.first, + () -> duplicateIconRequests.get(0).launcherActivityInfo, + mLauncherActivityInfoCachingLogic, + c, + /* usePackageIcon= */ false, + /* useLowResIcons = */ sectionKey.second); for (IconRequestInfo iconRequest : duplicateIconRequests) { applyCacheEntry(entry, iconRequest.itemInfo); } } else { Log.e(TAG, "Found entry in icon database but no main activity " - + "entry for cn: " + cn); + + "entry for cn: " + cn); } } } @@ -542,13 +525,13 @@ public class IconCache extends BaseIconCache { BitmapInfo icon = itemInfo.bitmap; boolean loadFallbackTitle = TextUtils.isEmpty(itemInfo.title); boolean loadFallbackIcon = icon == null - || isDefaultIcon(icon, itemInfo.user) - || icon == BitmapInfo.LOW_RES_INFO; + || isDefaultIcon(icon, itemInfo.user) + || icon == BitmapInfo.LOW_RES_INFO; if (loadFallbackTitle || loadFallbackIcon) { Log.i(TAG, - "Database bulk icon loading failed, using fallback bulk icon loading " - + "for: " + cn); + "Database bulk icon loading failed, using fallback bulk icon loading " + + "for: " + cn); CacheEntry entry = new CacheEntry(); LauncherActivityInfo lai = iconRequestInfo.launcherActivityInfo; @@ -562,20 +545,20 @@ public class IconCache extends BaseIconCache { if (loadFallbackIcon) { loadFallbackIcon( - lai, - entry, - LauncherActivityCachingLogic.INSTANCE, - /* usePackageIcon= */ false, - /* usePackageTitle= */ loadFallbackTitle, - cn, - sectionKey.first); + lai, + entry, + mLauncherActivityInfoCachingLogic, + /* usePackageIcon= */ false, + /* usePackageTitle= */ loadFallbackTitle, + cn, + sectionKey.first); } if (loadFallbackTitle && TextUtils.isEmpty(entry.title) && lai != null) { loadFallbackTitle( - lai, - entry, - LauncherActivityCachingLogic.INSTANCE, - sectionKey.first); + lai, + entry, + mLauncherActivityInfoCachingLogic, + sectionKey.first); } for (IconRequestInfo iconRequest : duplicateIconRequestsMap.get(cn)) { @@ -590,29 +573,27 @@ public class IconCache extends BaseIconCache { * Fill in {@param infoInOut} with the corresponding icon and label. */ public synchronized void getTitleAndIconForApp( - @NonNull final PackageItemInfo infoInOut, - @NonNull CacheLookupFlag lookupFlag) { + @NonNull final PackageItemInfo infoInOut, final boolean useLowResIcon) { CacheEntry entry = getEntryForPackageLocked( - infoInOut.packageName, infoInOut.user, lookupFlag); + infoInOut.packageName, infoInOut.user, useLowResIcon); applyCacheEntry(entry, infoInOut); if (infoInOut.widgetCategory == NO_CATEGORY) { return; } - WidgetSection widgetSection = WidgetSections.getWidgetSections(context) - .get(infoInOut.widgetCategory); - infoInOut.title = context.getString(widgetSection.mSectionTitle); - infoInOut.contentDescription = getUserBadgedLabel(infoInOut.title, infoInOut.user); + WidgetSection widgetSection = WidgetSections.getWidgetSections(mContext) + .get(infoInOut.widgetCategory); + infoInOut.title = mContext.getString(widgetSection.mSectionTitle); + infoInOut.contentDescription = mPackageManager.getUserBadgedLabel(infoInOut.title, infoInOut.user); final BitmapInfo cachedBitmap = mWidgetCategoryBitmapInfos.get(infoInOut.widgetCategory); if (cachedBitmap != null) { infoInOut.bitmap = getBadgedIcon(cachedBitmap, infoInOut.user); return; } - try (LauncherIcons li = mIconPool.obtain()) { + try (LauncherIcons li = LauncherIcons.obtain(mContext)) { final BitmapInfo tempBitmap = li.createBadgedIconBitmap( - context.getDrawable(widgetSection.mSectionDrawable), - new BaseIconFactory.IconOptions()); + mContext.getDrawable(widgetSection.mSectionDrawable)); mWidgetCategoryBitmapInfos.put(infoInOut.widgetCategory, tempBitmap); infoInOut.bitmap = getBadgedIcon(tempBitmap, infoInOut.user); } catch (Exception e) { @@ -622,53 +603,51 @@ public class IconCache extends BaseIconCache { } private synchronized BitmapInfo getBadgedIcon(@Nullable final BitmapInfo bitmap, - @NonNull final UserHandle user) { + @NonNull final UserHandle user) { if (bitmap == null) { return getDefaultIcon(user); } - return bitmap.withFlags(getUserFlagOpLocked(user)); + return bitmap.withFlags(FlagOp.NO_OP); } protected void applyCacheEntry(@NonNull final CacheEntry entry, - @NonNull final ItemInfoWithIcon info) { + @NonNull final ItemInfoWithIcon info) { info.title = Utilities.trim(entry.title); info.contentDescription = entry.contentDescription; info.bitmap = entry.bitmap; - // Clear any previously set appTitle, if the packageOverride is no longer valid - info.appTitle = null; if (entry.bitmap == null) { // TODO: entry.bitmap can never be null, so this should not happen at all. Log.wtf(TAG, "Cannot find bitmap from the cache, default icon was loaded."); info.bitmap = getDefaultIcon(info.user); } + } - // apply package override - if (!Flags.enableSupportForArchiving() || !info.isArchived()) { - return; - } - String targetPackage = info.getTargetPackage(); - if (targetPackage == null) { - return; - } - CacheEntry packageEntry = getInMemoryPackageEntryLocked(targetPackage, info.user); - if (packageEntry == null || packageEntry.bitmap.isLowRes()) { - return; - } - info.appTitle = Utilities.trim(info.title); + protected void applyPackageEntry(@NonNull final CacheEntry packageEntry, + @NonNull final ItemInfoWithIcon info, @NonNull final CacheEntry fallbackEntry) { info.title = Utilities.trim(packageEntry.title); + info.appTitle = Utilities.trim(fallbackEntry.title); info.contentDescription = packageEntry.contentDescription; info.bitmap = packageEntry.bitmap; + if (packageEntry.bitmap == null) { + // TODO: entry.bitmap can never be null, so this should not happen at all. + Log.wtf(TAG, "Cannot find bitmap from the cache, default icon was loaded."); + info.bitmap = getDefaultIcon(info.user); + } + } + + public Drawable getFullResIcon(LauncherActivityInfo info) { + return mIconProvider.getIcon(info, mIconDpi); } public void updateSessionCache(PackageUserKey key, PackageInstaller.SessionInfo info) { cachePackageInstallInfo(key.mPackageName, key.mUser, info.getAppIcon(), - info.getAppLabel()); + info.getAppLabel()); } - @VisibleForTesting - synchronized boolean isItemInDb(ComponentKey cacheKey) { - return getEntryFromDBLocked(cacheKey, new CacheEntry(), DEFAULT_LOOKUP_FLAG, - LauncherActivityCachingLogic.INSTANCE); + @Override + @NonNull + protected String getIconSystemState(String packageName) { + return mIconProvider.getSystemStateForPackage(mSystemState, packageName); } /** @@ -681,7 +660,7 @@ public class IconCache extends BaseIconCache { /** Log persistently to FileLog.d for debugging. */ @Override - protected void logPersistently(@NonNull String message, @Nullable Exception e) { - FileLog.d(BaseIconCache.TAG, message, e); + protected void logdPersistently(String tag, String message, @Nullable Exception e) { + FileLog.d(tag, message, e); } } diff --git a/src/com/android/launcher3/icons/MonochromeIconFactory.java b/src/com/android/launcher3/icons/MonochromeIconFactory.java new file mode 100644 index 0000000000..2854d51631 --- /dev/null +++ b/src/com/android/launcher3/icons/MonochromeIconFactory.java @@ -0,0 +1,172 @@ +/* + * 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. + */ +package com.android.launcher3.icons; + +import static android.graphics.Paint.FILTER_BITMAP_FLAG; + +import android.annotation.TargetApi; +import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; +import android.graphics.BlendMode; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorFilter; +import android.graphics.ColorMatrix; +import android.graphics.ColorMatrixColorFilter; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.drawable.AdaptiveIconDrawable; +import android.graphics.drawable.Drawable; +import android.os.Build; + +import androidx.annotation.WorkerThread; + +import com.android.launcher3.icons.BaseIconFactory.ClippedMonoDrawable; + +import java.nio.ByteBuffer; + +/** + * Utility class to generate monochrome icons version for a given drawable. + */ +@TargetApi(Build.VERSION_CODES.TIRAMISU) +public class MonochromeIconFactory extends Drawable { + + private final Bitmap mFlatBitmap; + private final Canvas mFlatCanvas; + private final Paint mCopyPaint; + + private final Bitmap mAlphaBitmap; + private final Canvas mAlphaCanvas; + private final byte[] mPixels; + + private final int mBitmapSize; + private final int mEdgePixelLength; + + private final Paint mDrawPaint; + private final Rect mSrcRect; + + MonochromeIconFactory(int iconBitmapSize) { + float extraFactor = AdaptiveIconDrawable.getExtraInsetFraction(); + float viewPortScale = 1 / (1 + 2 * extraFactor); + mBitmapSize = Math.round(iconBitmapSize * 2 * viewPortScale); + mPixels = new byte[mBitmapSize * mBitmapSize]; + mEdgePixelLength = mBitmapSize * (mBitmapSize - iconBitmapSize) / 2; + + mFlatBitmap = Bitmap.createBitmap(mBitmapSize, mBitmapSize, Config.ARGB_8888); + mFlatCanvas = new Canvas(mFlatBitmap); + + mAlphaBitmap = Bitmap.createBitmap(mBitmapSize, mBitmapSize, Config.ALPHA_8); + mAlphaCanvas = new Canvas(mAlphaBitmap); + + mDrawPaint = new Paint(FILTER_BITMAP_FLAG); + mDrawPaint.setColor(Color.WHITE); + mSrcRect = new Rect(0, 0, mBitmapSize, mBitmapSize); + + mCopyPaint = new Paint(FILTER_BITMAP_FLAG); + mCopyPaint.setBlendMode(BlendMode.SRC); + + // Crate a color matrix which converts the icon to grayscale and then uses the average + // of RGB components as the alpha component. + ColorMatrix satMatrix = new ColorMatrix(); + satMatrix.setSaturation(0); + float[] vals = satMatrix.getArray(); + vals[15] = vals[16] = vals[17] = .3333f; + vals[18] = vals[19] = 0; + mCopyPaint.setColorFilter(new ColorMatrixColorFilter(vals)); + } + + private void drawDrawable(Drawable drawable) { + if (drawable != null) { + drawable.setBounds(0, 0, mBitmapSize, mBitmapSize); + drawable.draw(mFlatCanvas); + } + } + + /** + * Creates a monochrome version of the provided drawable + */ + @WorkerThread + public Drawable wrap(AdaptiveIconDrawable icon) { + mFlatCanvas.drawColor(Color.BLACK); + drawDrawable(icon.getBackground()); + drawDrawable(icon.getForeground()); + generateMono(); + return new ClippedMonoDrawable(this); + } + + @WorkerThread + private void generateMono() { + mAlphaCanvas.drawBitmap(mFlatBitmap, 0, 0, mCopyPaint); + + // Scale the end points: + ByteBuffer buffer = ByteBuffer.wrap(mPixels); + buffer.rewind(); + mAlphaBitmap.copyPixelsToBuffer(buffer); + + int min = 0xFF; + int max = 0; + for (byte b : mPixels) { + min = Math.min(min, b & 0xFF); + max = Math.max(max, b & 0xFF); + } + + if (min < max) { + // rescale pixels to increase contrast + float range = max - min; + + // In order to check if the colors should be flipped, we just take the average color + // of top and bottom edge which should correspond to be background color. If the edge + // colors have more opacity, we flip the colors; + int sum = 0; + for (int i = 0; i < mEdgePixelLength; i++) { + sum += (mPixels[i] & 0xFF); + sum += (mPixels[mPixels.length - 1 - i] & 0xFF); + } + float edgeAverage = sum / (mEdgePixelLength * 2f); + float edgeMapped = (edgeAverage - min) / range; + boolean flipColor = edgeMapped > .5f; + + for (int i = 0; i < mPixels.length; i++) { + int p = mPixels[i] & 0xFF; + int p2 = Math.round((p - min) * 0xFF / range); + mPixels[i] = flipColor ? (byte) (255 - p2) : (byte) (p2); + } + buffer.rewind(); + mAlphaBitmap.copyPixelsFromBuffer(buffer); + } + } + + @Override + public void draw(Canvas canvas) { + canvas.drawBitmap(mAlphaBitmap, mSrcRect, getBounds(), mDrawPaint); + } + + @Override + public int getOpacity() { + return PixelFormat.TRANSLUCENT; + } + + @Override + public void setAlpha(int i) { + mDrawPaint.setAlpha(i); + } + + @Override + public void setColorFilter(ColorFilter colorFilter) { + mDrawPaint.setColorFilter(colorFilter); + } +} diff --git a/src/com/android/launcher3/keyboard/ItemFocusIndicatorHelper.java b/src/com/android/launcher3/keyboard/ItemFocusIndicatorHelper.java index ec0efe0244..480e8f3e56 100644 --- a/src/com/android/launcher3/keyboard/ItemFocusIndicatorHelper.java +++ b/src/com/android/launcher3/keyboard/ItemFocusIndicatorHelper.java @@ -199,10 +199,6 @@ public abstract class ItemFocusIndicatorHelper implements AnimatorUpdateListe } protected void changeFocus(T item, boolean hasFocus) { - if (mLastFocusedItem != item && !hasFocus) { - return; - } - if (hasFocus) { endCurrentAnimation(); diff --git a/src/com/android/launcher3/logging/FileLog.java b/src/com/android/launcher3/logging/FileLog.java index 5b254183b1..924a44062c 100644 --- a/src/com/android/launcher3/logging/FileLog.java +++ b/src/com/android/launcher3/logging/FileLog.java @@ -1,6 +1,6 @@ package com.android.launcher3.logging; -import static com.android.launcher3.util.LooperExecutor.createAndStartNewLooper; +import static com.android.launcher3.util.Executors.createAndStartNewLooper; import android.os.Handler; import android.os.HandlerThread; diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java index bec27eedcd..186194eb7f 100644 --- a/src/com/android/launcher3/logging/StatsLogManager.java +++ b/src/com/android/launcher3/logging/StatsLogManager.java @@ -19,14 +19,11 @@ import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCH import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_OPEN_UP; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_GESTURE; - import android.content.Context; import android.view.View; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.slice.SliceItem; - import com.android.launcher3.R; import com.android.launcher3.logger.LauncherAtom; import com.android.launcher3.logger.LauncherAtom.ContainerInfo; @@ -46,7 +43,6 @@ import com.android.launcher3.views.ActivityContext; * */ public class StatsLogManager implements ResourceBasedOverride { - public static final int LAUNCHER_STATE_UNSPECIFIED = 0; public static final int LAUNCHER_STATE_BACKGROUND = 1; public static final int LAUNCHER_STATE_HOME = 2; @@ -90,13 +86,16 @@ public class StatsLogManager implements ResourceBasedOverride { } public interface EventEnum { - /** * Tag used to request new UI Event IDs via presubmit analysis. * - *

Use RESERVE_NEW_UI_EVENT_ID as the constructor parameter for a new {@link EventEnum} - * to signal the presubmit analyzer to reserve a new ID for the event. The new ID will be - * returned as a Gerrit presubmit finding. Do not submit {@code RESERVE_NEW_UI_EVENT_ID} as + *

+ * Use RESERVE_NEW_UI_EVENT_ID as the constructor parameter for a new + * {@link EventEnum} + * to signal the presubmit analyzer to reserve a new ID for the event. The new + * ID will be + * returned as a Gerrit presubmit finding. Do not submit + * {@code RESERVE_NEW_UI_EVENT_ID} as * the constructor parameter for any event. * *

@@ -112,16 +111,12 @@ public class StatsLogManager implements ResourceBasedOverride {
     public enum LauncherEvent implements EventEnum {
         /* Used to prevent double logging. */
         IGNORE(-1),
-
         @UiEvent(doc = "App launched from workspace, hotseat or folder in launcher")
         LAUNCHER_APP_LAUNCH_TAP(338),
-
         @UiEvent(doc = "Task launched from overview using TAP")
         LAUNCHER_TASK_LAUNCH_TAP(339),
-
         @UiEvent(doc = "User tapped on notification inside popup context menu.")
         LAUNCHER_NOTIFICATION_LAUNCH_TAP(516),
-
         @UiEvent(doc = "Task launched from overview using SWIPE DOWN")
         LAUNCHER_TASK_LAUNCH_SWIPE_DOWN(340),
 
@@ -130,87 +125,57 @@ public class StatsLogManager implements ResourceBasedOverride {
 
         @UiEvent(doc = "TASK dismissed from overview using SWIPE UP")
         LAUNCHER_TASK_DISMISS_SWIPE_UP(341),
-
         @UiEvent(doc = "User dragged a launcher item")
         LAUNCHER_ITEM_DRAG_STARTED(383),
-
         @UiEvent(doc = "A dragged launcher item is successfully dropped onto workspace, hotseat "
                 + "open folder etc")
         LAUNCHER_ITEM_DROP_COMPLETED(385),
-
         @UiEvent(doc = "A dragged launcher item is successfully dropped onto a folder icon.")
         LAUNCHER_ITEM_DROP_COMPLETED_ON_FOLDER_ICON(697),
-
         @UiEvent(doc = "A dragged launcher item is successfully dropped on another item "
                 + "resulting in a new folder creation")
         LAUNCHER_ITEM_DROP_FOLDER_CREATED(386),
-
         @UiEvent(doc = "Folder's label is automatically assigned.")
         LAUNCHER_FOLDER_AUTO_LABELED(591),
-
         @UiEvent(doc = "Could not auto-label a folder because primary suggestion is null or empty.")
         LAUNCHER_FOLDER_AUTO_LABELING_SKIPPED_EMPTY_PRIMARY(592),
-
         @UiEvent(doc = "Could not auto-label a folder because no suggestions exist.")
         LAUNCHER_FOLDER_AUTO_LABELING_SKIPPED_EMPTY_SUGGESTIONS(593),
-
         @UiEvent(doc = "User manually updated the folder label.")
         LAUNCHER_FOLDER_LABEL_UPDATED(460),
-
         @UiEvent(doc = "User long pressed on the workspace empty space.")
         LAUNCHER_WORKSPACE_LONGPRESS(461),
-
         @UiEvent(doc = "User tapped or long pressed on a wallpaper icon inside launcher settings.")
         LAUNCHER_WALLPAPER_BUTTON_TAP_OR_LONGPRESS(462),
-
         @UiEvent(doc = "User tapped or long pressed on settings icon inside launcher settings.")
         LAUNCHER_SETTINGS_BUTTON_TAP_OR_LONGPRESS(463),
-
-        @UiEvent(doc = "User tapped or long pressed on apps icon inside launcher settings.")
-        LAUNCHER_ALL_APPS_TAP_OR_LONGPRESS(2204),
-
         @UiEvent(doc = "User tapped or long pressed on widget tray icon inside launcher settings.")
         LAUNCHER_WIDGETSTRAY_BUTTON_TAP_OR_LONGPRESS(464),
-
         @UiEvent(doc = "User expanded the list of widgets for a single app in the widget picker.")
         LAUNCHER_WIDGETSTRAY_APP_EXPANDED(818),
-
         @UiEvent(doc = "User searched for a widget in the widget picker.")
         LAUNCHER_WIDGETSTRAY_SEARCHED(819),
-
-        @UiEvent(doc = "User clicked on view all button to expand the displayed list in the "
-                + "widget picker.")
-        LAUNCHER_WIDGETSTRAY_EXPAND_PRESS(1978),
-
         @UiEvent(doc = "A dragged item is dropped on 'Remove' button in the target bar")
         LAUNCHER_ITEM_DROPPED_ON_REMOVE(465),
-
         @UiEvent(doc = "A dragged item is dropped on 'Cancel' button in the target bar")
         LAUNCHER_ITEM_DROPPED_ON_CANCEL(466),
-
         @UiEvent(doc = "A predicted item is dragged and dropped on 'Don't suggest app'"
                 + " button in the target bar")
         LAUNCHER_ITEM_DROPPED_ON_DONT_SUGGEST(467),
-
         @UiEvent(doc = "A dragged item is dropped on 'Uninstall' button in target bar")
         LAUNCHER_ITEM_DROPPED_ON_UNINSTALL(468),
-
         @UiEvent(doc = "User completed uninstalling the package after dropping on "
                 + "the icon onto 'Uninstall' button in the target bar")
         LAUNCHER_ITEM_UNINSTALL_COMPLETED(469),
-
         @UiEvent(doc = "User cancelled uninstalling the package after dropping on "
                 + "the icon onto 'Uninstall' button in the target bar")
         LAUNCHER_ITEM_UNINSTALL_CANCELLED(470),
-
         @UiEvent(doc = "User tapped or long pressed on the task icon(aka package icon) "
                 + "from overview to open task menu.")
         LAUNCHER_TASK_ICON_TAP_OR_LONGPRESS(517),
-
         @UiEvent(doc = "User opened package specific widgets list by tapping on widgets system "
                 + "shortcut inside popup context menu.")
         LAUNCHER_SYSTEM_SHORTCUT_WIDGETS_TAP(514),
-
         @UiEvent(doc = "User tapped on app info system shortcut.")
         LAUNCHER_SYSTEM_SHORTCUT_APP_INFO_TAP(515),
 
@@ -221,22 +186,14 @@ public class StatsLogManager implements ResourceBasedOverride {
         @Deprecated
         @UiEvent(doc = "User tapped on split screen icon on a task menu.")
         LAUNCHER_SYSTEM_SHORTCUT_SPLIT_SCREEN_TAP(518),
-
         @UiEvent(doc = "User tapped on free form icon on a task menu.")
         LAUNCHER_SYSTEM_SHORTCUT_FREE_FORM_TAP(519),
 
         @UiEvent(doc = "User tapped on desktop icon on a task menu.")
         LAUNCHER_SYSTEM_SHORTCUT_DESKTOP_TAP(1706),
 
-        @UiEvent(doc = "User tapped on external display icon on a task menu,")
-        LAUNCHER_SYSTEM_SHORTCUT_EXTERNAL_DISPLAY_TAP(1957),
-
-        @UiEvent(doc = "User tapped on close app on a task menu,")
-        LAUNCHER_SYSTEM_SHORTCUT_CLOSE_APP_TAP(2081),
-
         @UiEvent(doc = "User tapped on pause app system shortcut.")
         LAUNCHER_SYSTEM_SHORTCUT_PAUSE_TAP(521),
-
         @UiEvent(doc = "User tapped on pin system shortcut.")
         LAUNCHER_SYSTEM_SHORTCUT_PIN_TAP(522),
 
@@ -245,74 +202,50 @@ public class StatsLogManager implements ResourceBasedOverride {
 
         @UiEvent(doc = "User is shown All Apps education view.")
         LAUNCHER_ALL_APPS_EDU_SHOWN(523),
-
         @UiEvent(doc = "User opened a folder.")
         LAUNCHER_FOLDER_OPEN(551),
-
         @UiEvent(doc = "Hotseat education half sheet seen")
         LAUNCHER_HOTSEAT_EDU_SEEN(479),
-
         @UiEvent(doc = "Hotseat migration accepted")
         LAUNCHER_HOTSEAT_EDU_ACCEPT(480),
-
         @UiEvent(doc = "Hotseat migration denied")
         LAUNCHER_HOTSEAT_EDU_DENY(481),
-
         @UiEvent(doc = "Hotseat education tip shown")
         LAUNCHER_HOTSEAT_EDU_ONLY_TIP(482),
-
         /**
-         * @deprecated LauncherUiChanged.rank field is repurposed to store all apps rank, so no
-         * separate event is required.
+         * @deprecated LauncherUiChanged.rank field is repurposed to store all apps
+         *             rank, so no
+         *             separate event is required.
          */
         @Deprecated
         @UiEvent(doc = "App launch ranking logged for all apps predictions")
         LAUNCHER_ALL_APPS_RANKED(552),
-
         @UiEvent(doc = "App launch ranking logged for hotseat predictions)")
         LAUNCHER_HOTSEAT_RANKED(553),
         @UiEvent(doc = "Launcher is now in background. e.g., Screen off event")
         LAUNCHER_ONSTOP(562),
-
         @UiEvent(doc = "Launcher is now in foreground. e.g., Screen on event, back button")
         LAUNCHER_ONRESUME(563),
-
         @UiEvent(doc = "User swipes or fling in LEFT direction on workspace.")
         LAUNCHER_SWIPELEFT(564),
-
         @UiEvent(doc = "User swipes or fling in RIGHT direction on workspace.")
         LAUNCHER_SWIPERIGHT(565),
-
         @UiEvent(doc = "User swipes or fling in UP direction in unknown way.")
         LAUNCHER_UNKNOWN_SWIPEUP(566),
-
         @UiEvent(doc = "User swipes or fling in DOWN direction in unknown way.")
         LAUNCHER_UNKNOWN_SWIPEDOWN(567),
-
         @UiEvent(doc = "User swipes or fling in UP direction to open apps drawer.")
         LAUNCHER_ALLAPPS_OPEN_UP(568),
-
         @UiEvent(doc = "User swipes or fling in DOWN direction to close apps drawer.")
         LAUNCHER_ALLAPPS_CLOSE_DOWN(569),
-
         @UiEvent(doc = "User tap outside apps drawer sheet to close apps drawer.")
         LAUNCHER_ALLAPPS_CLOSE_TAP_OUTSIDE(941),
-
         @UiEvent(doc = "User swipes or fling in UP direction and hold from the bottom bazel area")
         LAUNCHER_OVERVIEW_GESTURE(570),
-
         @UiEvent(doc = "User swipes or fling in LEFT direction on the bottom bazel area.")
         LAUNCHER_QUICKSWITCH_LEFT(571),
-
         @UiEvent(doc = "User swipes or fling in RIGHT direction on the bottom bazel area.")
         LAUNCHER_QUICKSWITCH_RIGHT(572),
-
-        @UiEvent(doc = "User swipes or fling on the bottom bazel area to enter Desktop mode.")
-        LAUNCHER_QUICKSWITCH_ENTER_DESKTOP_MODE(2025),
-
-        @UiEvent(doc = "User swipes or fling on the bottom bazel area to exit Desktop mode.")
-        LAUNCHER_QUICKSWITCH_EXIT_DESKTOP_MODE(2026),
-
         @UiEvent(doc = "User swipes or fling in DOWN direction on the bottom bazel area.")
         LAUNCHER_SWIPEDOWN_NAVBAR(573),
 
@@ -330,67 +263,46 @@ public class StatsLogManager implements ResourceBasedOverride {
 
         @UiEvent(doc = "User swipes or fling in UP direction from bottom bazel area.")
         LAUNCHER_HOME_GESTURE(574),
-
         @UiEvent(doc = "User's workspace layout information is snapshot in the background.")
         LAUNCHER_WORKSPACE_SNAPSHOT(579),
-
         @UiEvent(doc = "User tapped on the screenshot button on overview)")
         LAUNCHER_OVERVIEW_ACTIONS_SCREENSHOT(580),
-
         @UiEvent(doc = "User tapped on the select button on overview)")
         LAUNCHER_OVERVIEW_ACTIONS_SELECT(581),
-
         @UiEvent(doc = "User tapped on the share button on overview")
         LAUNCHER_OVERVIEW_ACTIONS_SHARE(582),
-
         @UiEvent(doc = "User tapped on the split screen button on overview")
         LAUNCHER_OVERVIEW_ACTIONS_SPLIT(895),
-
         @UiEvent(doc = "User tapped on the close button in select mode")
         LAUNCHER_SELECT_MODE_CLOSE(583),
-
         @UiEvent(doc = "User tapped on the highlight items in select mode")
         LAUNCHER_SELECT_MODE_ITEM(584),
-
         @UiEvent(doc = "Notification dot on app icon enabled.")
         LAUNCHER_NOTIFICATION_DOT_ENABLED(611),
-
         @UiEvent(doc = "Notification dot on app icon disabled.")
         LAUNCHER_NOTIFICATION_DOT_DISABLED(612),
-
         @UiEvent(doc = "For new apps, add app icons to home screen enabled.")
         LAUNCHER_ADD_NEW_APPS_TO_HOME_SCREEN_ENABLED(613),
-
         @UiEvent(doc = "For new apps, add app icons to home screen disabled.")
         LAUNCHER_ADD_NEW_APPS_TO_HOME_SCREEN_DISABLED(614),
-
         @UiEvent(doc = "Home screen rotation is enabled when phone is rotated.")
         LAUNCHER_HOME_SCREEN_ROTATION_ENABLED(615),
-
         @UiEvent(doc = "Home screen rotation is disabled when phone is rotated.")
         LAUNCHER_HOME_SCREEN_ROTATION_DISABLED(616),
-
         @UiEvent(doc = "Suggestions in all apps list enabled.")
         LAUNCHER_ALL_APPS_SUGGESTIONS_ENABLED(619),
-
         @UiEvent(doc = "Suggestions in all apps list disabled.")
         LAUNCHER_ALL_APPS_SUGGESTIONS_DISABLED(620),
-
         @UiEvent(doc = "Suggestions on home screen is enabled.")
         LAUNCHER_HOME_SCREEN_SUGGESTIONS_ENABLED(621),
-
         @UiEvent(doc = "Suggestions on home screen is disabled.")
         LAUNCHER_HOME_SCREEN_SUGGESTIONS_DISABLED(622),
-
         @UiEvent(doc = "System navigation is 3 button mode.")
         LAUNCHER_NAVIGATION_MODE_3_BUTTON(623),
-
         @UiEvent(doc = "System navigation mode is 2 button mode.")
         LAUNCHER_NAVIGATION_MODE_2_BUTTON(624),
-
         @UiEvent(doc = "System navigation mode is 0 button mode/gesture navigation mode .")
         LAUNCHER_NAVIGATION_MODE_GESTURE_BUTTON(625),
-
         @UiEvent(doc = "User tapped on image content in Overview Select mode.")
         LAUNCHER_SELECT_MODE_IMAGE(627),
 
@@ -402,170 +314,107 @@ public class StatsLogManager implements ResourceBasedOverride {
 
         @UiEvent(doc = "Activity to add external item was started")
         LAUNCHER_ADD_EXTERNAL_ITEM_START(641),
-
         @UiEvent(doc = "Activity to add external item was cancelled")
         LAUNCHER_ADD_EXTERNAL_ITEM_CANCELLED(642),
-
         @UiEvent(doc = "Activity to add external item was backed out")
         LAUNCHER_ADD_EXTERNAL_ITEM_BACK(643),
-
         @UiEvent(doc = "Item was placed automatically in external item addition flow")
         LAUNCHER_ADD_EXTERNAL_ITEM_PLACED_AUTOMATICALLY(644),
-
         @UiEvent(doc = "Item was dragged in external item addition flow")
         LAUNCHER_ADD_EXTERNAL_ITEM_DRAGGED(645),
-
         @UiEvent(doc = "A folder was replaced by a single item")
         LAUNCHER_FOLDER_CONVERTED_TO_ICON(646),
-
         @UiEvent(doc = "A hotseat prediction item was pinned")
         LAUNCHER_HOTSEAT_PREDICTION_PINNED(647),
-
         @UiEvent(doc = "Undo event was tapped.")
         LAUNCHER_UNDO(648),
-
         @UiEvent(doc = "Task switcher clear all target was tapped.")
         LAUNCHER_TASK_CLEAR_ALL(649),
-
         @UiEvent(doc = "Task preview was long pressed.")
         LAUNCHER_TASK_PREVIEW_LONGPRESS(650),
-
         @UiEvent(doc = "User swiped down on workspace (triggering noti shade to open).")
         LAUNCHER_SWIPE_DOWN_WORKSPACE_NOTISHADE_OPEN(651),
-
         @UiEvent(doc = "Notification dismissed by swiping right.")
         LAUNCHER_NOTIFICATION_DISMISSED(652),
-
-        @UiEvent(doc = "Current grid size is changed to 2x2")
-        LAUNCHER_GRID_SIZE_2_BY_2(2181),
-
-        @UiEvent(doc = "Current grid size is changed to 3x3")
-        LAUNCHER_GRID_SIZE_3_BY_3(2182),
-
-        @UiEvent(doc = "Current grid size is changed to 4x4")
-        LAUNCHER_GRID_SIZE_4_BY_4(2183),
-
-        @UiEvent(doc = "Current grid size is changed to 4x5")
-        LAUNCHER_GRID_SIZE_4_BY_5(2184),
-
-        @UiEvent(doc = "Current grid size is changed to 4x6")
-        LAUNCHER_GRID_SIZE_4_BY_6(2185),
-
-        @UiEvent(doc = "Current grid size is changed to 4x7")
-        LAUNCHER_GRID_SIZE_4_BY_7(2189),
-
-        @UiEvent(doc = "Current grid size is changed to 5x5")
-        LAUNCHER_GRID_SIZE_5_BY_5(2186),
-
-        @UiEvent(doc = "Current grid size is changed to 5x6")
-        LAUNCHER_GRID_SIZE_5_BY_6(2187),
-
-        @UiEvent(doc = "Current grid size is changed to 6x5")
-        LAUNCHER_GRID_SIZE_6_BY_5(2188),
-
+        @UiEvent(doc = "Current grid size is changed to 6.")
+        LAUNCHER_GRID_SIZE_6(930),
+        @UiEvent(doc = "Current grid size is changed to 5.")
+        LAUNCHER_GRID_SIZE_5(662),
+        @UiEvent(doc = "Current grid size is changed to 4.")
+        LAUNCHER_GRID_SIZE_4(663),
+        @UiEvent(doc = "Current grid size is changed to 3.")
+        LAUNCHER_GRID_SIZE_3(664),
+        @UiEvent(doc = "Current grid size is changed to 2.")
+        LAUNCHER_GRID_SIZE_2(665),
         @UiEvent(doc = "Launcher entered into AllApps state.")
         LAUNCHER_ALLAPPS_ENTRY(692),
-
         @UiEvent(doc = "Launcher exited from AllApps state.")
         LAUNCHER_ALLAPPS_EXIT(693),
-
         @UiEvent(doc = "User closed the AllApps keyboard.")
         LAUNCHER_ALLAPPS_KEYBOARD_CLOSED(694),
-
         @UiEvent(doc = "User switched to AllApps Main/Personal tab by swiping left.")
         LAUNCHER_ALLAPPS_SWIPE_TO_PERSONAL_TAB(695),
-
         @UiEvent(doc = "User switched to AllApps Work tab by swiping right.")
         LAUNCHER_ALLAPPS_SWIPE_TO_WORK_TAB(696),
-
         @UiEvent(doc = "Default event when dedicated UI event is not available for the user action"
                 + " on slice .")
         LAUNCHER_SLICE_DEFAULT_ACTION(700),
-
         @UiEvent(doc = "User toggled-on a Slice item.")
         LAUNCHER_SLICE_TOGGLE_ON(701),
-
         @UiEvent(doc = "User toggled-off a Slice item.")
         LAUNCHER_SLICE_TOGGLE_OFF(702),
-
         @UiEvent(doc = "User acted on a Slice item with a button.")
         LAUNCHER_SLICE_BUTTON_ACTION(703),
-
         @UiEvent(doc = "User acted on a Slice item with a slider.")
         LAUNCHER_SLICE_SLIDER_ACTION(704),
-
         @UiEvent(doc = "User tapped on the entire row of a Slice.")
         LAUNCHER_SLICE_CONTENT_ACTION(705),
-
         @UiEvent(doc = "User tapped on the see more button of a Slice.")
         LAUNCHER_SLICE_SEE_MORE_ACTION(706),
-
         @UiEvent(doc = "User selected from a selection row of Slice.")
         LAUNCHER_SLICE_SELECTION_ACTION(707),
-
         @UiEvent(doc = "IME is used for selecting the focused item on the AllApps screen.")
         LAUNCHER_ALLAPPS_FOCUSED_ITEM_SELECTED_WITH_IME(718),
-
         @UiEvent(doc = "User long-pressed on an AllApps item.")
         LAUNCHER_ALLAPPS_ITEM_LONG_PRESSED(719),
-
         @UiEvent(doc = "Launcher entered into AllApps state with device search enabled.")
         LAUNCHER_ALLAPPS_ENTRY_WITH_DEVICE_SEARCH(720),
-
         @UiEvent(doc = "User switched to AllApps Main/Personal tab by tapping on it.")
         LAUNCHER_ALLAPPS_TAP_ON_PERSONAL_TAB(721),
-
         @UiEvent(doc = "User switched to AllApps Work tab by tapping on it.")
         LAUNCHER_ALLAPPS_TAP_ON_WORK_TAB(722),
-
         @UiEvent(doc = "All apps vertical fling started.")
         LAUNCHER_ALLAPPS_VERTICAL_SWIPE_BEGIN(724),
-
         @UiEvent(doc = "All apps vertical fling ended.")
         LAUNCHER_ALLAPPS_VERTICAL_SWIPE_END(725),
-
         @UiEvent(doc = "Show URL indicator for Overview Sharing")
         LAUNCHER_OVERVIEW_SHARING_SHOW_URL_INDICATOR(764),
-
         @UiEvent(doc = "Show image indicator for Overview Sharing")
         LAUNCHER_OVERVIEW_SHARING_SHOW_IMAGE_INDICATOR(765),
-
         @UiEvent(doc = "User taps URL indicator in Overview")
         LAUNCHER_OVERVIEW_SHARING_URL_INDICATOR_TAP(766),
-
         @UiEvent(doc = "User taps image indicator in Overview")
         LAUNCHER_OVERVIEW_SHARING_IMAGE_INDICATOR_TAP(767),
-
         @UiEvent(doc = "User long presses an image in Overview")
         LAUNCHER_OVERVIEW_SHARING_IMAGE_LONG_PRESS(768),
-
         @UiEvent(doc = "User drags a URL in Overview")
         LAUNCHER_OVERVIEW_SHARING_URL_DRAG(769),
-
         @UiEvent(doc = "User drags an image in Overview")
         LAUNCHER_OVERVIEW_SHARING_IMAGE_DRAG(770),
-
         @UiEvent(doc = "User drops URL to a direct share target")
         LAUNCHER_OVERVIEW_SHARING_DROP_URL_TO_TARGET(771),
-
         @UiEvent(doc = "User drops an image to a direct share target")
         LAUNCHER_OVERVIEW_SHARING_DROP_IMAGE_TO_TARGET(772),
-
         @UiEvent(doc = "User drops URL to the More button")
         LAUNCHER_OVERVIEW_SHARING_DROP_URL_TO_MORE(773),
-
         @UiEvent(doc = "User drops an image to the More button")
         LAUNCHER_OVERVIEW_SHARING_DROP_IMAGE_TO_MORE(774),
-
         @UiEvent(doc = "User taps a share target to share URL")
         LAUNCHER_OVERVIEW_SHARING_TAP_TARGET_TO_SHARE_URL(775),
-
         @UiEvent(doc = "User taps a share target to share an image")
         LAUNCHER_OVERVIEW_SHARING_TAP_TARGET_TO_SHARE_IMAGE(776),
-
         @UiEvent(doc = "User taps the More button to share URL")
         LAUNCHER_OVERVIEW_SHARING_TAP_MORE_TO_SHARE_URL(777),
-
         @UiEvent(doc = "User taps the More button to share an image")
         LAUNCHER_OVERVIEW_SHARING_TAP_MORE_TO_SHARE_IMAGE(778),
 
@@ -586,100 +435,69 @@ public class StatsLogManager implements ResourceBasedOverride {
 
         @UiEvent(doc = "User started resizing a widget on their home screen.")
         LAUNCHER_WIDGET_RESIZE_STARTED(820),
-
         @UiEvent(doc = "User finished resizing a widget on their home screen.")
         LAUNCHER_WIDGET_RESIZE_COMPLETED(824),
-
         @UiEvent(doc = "User reconfigured a widget on their home screen.")
         LAUNCHER_WIDGET_RECONFIGURED(821),
-
         @UiEvent(doc = "User enabled themed icons option in wallpaper & style settings.")
         LAUNCHER_THEMED_ICON_ENABLED(836),
-
         @UiEvent(doc = "User disabled themed icons option in wallpaper & style settings.")
         LAUNCHER_THEMED_ICON_DISABLED(837),
-
         @UiEvent(doc = "User tapped on 'Turn on work apps' button in all apps window.")
         LAUNCHER_TURN_ON_WORK_APPS_TAP(838),
-
         @UiEvent(doc = "User tapped on 'Turn off work apps' button in all apps window.")
         LAUNCHER_TURN_OFF_WORK_APPS_TAP(839),
-
         @UiEvent(doc = "Launcher item drop failed since there was not enough room on the screen.")
         LAUNCHER_ITEM_DROP_FAILED_INSUFFICIENT_SPACE(872),
-
         @UiEvent(doc = "User clicks on the search icon on header to launch search in app.")
         LAUNCHER_ALLAPPS_SEARCHINAPP_LAUNCH(913),
-
         @UiEvent(doc = "User is shown the back gesture navigation tutorial step.")
         LAUNCHER_GESTURE_TUTORIAL_BACK_STEP_SHOWN(959),
-
         @UiEvent(doc = "User is shown the home gesture navigation tutorial step.")
         LAUNCHER_GESTURE_TUTORIAL_HOME_STEP_SHOWN(960),
-
         @UiEvent(doc = "User is shown the overview gesture navigation tutorial step.")
         LAUNCHER_GESTURE_TUTORIAL_OVERVIEW_STEP_SHOWN(961),
-
         @UiEvent(doc = "User completed the back gesture navigation tutorial step.")
         LAUNCHER_GESTURE_TUTORIAL_BACK_STEP_COMPLETED(962),
-
         @UiEvent(doc = "User completed the home gesture navigation tutorial step.")
         LAUNCHER_GESTURE_TUTORIAL_HOME_STEP_COMPLETED(963),
-
         @UiEvent(doc = "User completed the overview gesture navigation tutorial step.")
         LAUNCHER_GESTURE_TUTORIAL_OVERVIEW_STEP_COMPLETED(964),
-
         @UiEvent(doc = "User skips the gesture navigation tutorial.")
         LAUNCHER_GESTURE_TUTORIAL_SKIPPED(965),
-
         @UiEvent(doc = "User scrolled on one of the all apps surfaces such as A-Z list, search "
                 + "result page etc.")
         LAUNCHER_ALLAPPS_SCROLLED(985),
-
         @UiEvent(doc = "User scrolled up on the all apps personal A-Z list.")
         LAUNCHER_ALLAPPS_PERSONAL_SCROLLED_UP(1287),
-
         @UiEvent(doc = "User scrolled down on the all apps personal A-Z list.")
         LAUNCHER_ALLAPPS_PERSONAL_SCROLLED_DOWN(1288),
-
         @UiEvent(doc = "User scrolled on one of the all apps surfaces such as A-Z list, search "
                 + "result page etc and we don't know the direction since user came back to "
                 + "original position from which they scrolled.")
         LAUNCHER_ALLAPPS_SCROLLED_UNKNOWN_DIRECTION(1231),
-
         @UiEvent(doc = "User tapped taskbar home button")
         LAUNCHER_TASKBAR_HOME_BUTTON_TAP(1003),
-
         @UiEvent(doc = "User tapped taskbar back button")
         LAUNCHER_TASKBAR_BACK_BUTTON_TAP(1004),
-
         @UiEvent(doc = "User tapped taskbar overview/recents button")
         LAUNCHER_TASKBAR_OVERVIEW_BUTTON_TAP(1005),
-
         @UiEvent(doc = "User tapped taskbar IME switcher button")
         LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_TAP(1006),
-
         @UiEvent(doc = "User tapped taskbar a11y button")
         LAUNCHER_TASKBAR_A11Y_BUTTON_TAP(1007),
-
         @UiEvent(doc = "User tapped taskbar home button")
         LAUNCHER_TASKBAR_HOME_BUTTON_LONGPRESS(1008),
-
         @UiEvent(doc = "User tapped taskbar back button")
         LAUNCHER_TASKBAR_BACK_BUTTON_LONGPRESS(1009),
-
         @UiEvent(doc = "User tapped taskbar overview/recents button")
         LAUNCHER_TASKBAR_OVERVIEW_BUTTON_LONGPRESS(1010),
-
         @UiEvent(doc = "User tapped taskbar a11y button")
         LAUNCHER_TASKBAR_A11Y_BUTTON_LONGPRESS(1011),
-
         @UiEvent(doc = "Show an 'Undo' snackbar when users dismiss a predicted hotseat item")
         LAUNCHER_DISMISS_PREDICTION_UNDO(1035),
-
         @UiEvent(doc = "User clicked on IME quicksearch button.")
         LAUNCHER_ALLAPPS_QUICK_SEARCH_WITH_IME(1047),
-
         @UiEvent(doc = "User tapped taskbar All Apps button.")
         LAUNCHER_TASKBAR_ALLAPPS_BUTTON_TAP(1057),
 
@@ -688,54 +506,38 @@ public class StatsLogManager implements ResourceBasedOverride {
 
         @UiEvent(doc = "User tapped on Share app system shortcut.")
         LAUNCHER_SYSTEM_SHORTCUT_APP_SHARE_TAP(1075),
-
         @UiEvent(doc = "User has invoked split to right half from an app icon menu")
         LAUNCHER_APP_ICON_MENU_SPLIT_RIGHT_BOTTOM(1199),
-
         @UiEvent(doc = "User has invoked split to left half from an app icon menu")
         LAUNCHER_APP_ICON_MENU_SPLIT_LEFT_TOP(1200),
-
         @UiEvent(doc = "Number of apps in A-Z list (personal and work profile)")
         LAUNCHER_ALLAPPS_COUNT(1225),
-
         @UiEvent(doc = "User has invoked split to right half with a keyboard shortcut.")
         LAUNCHER_KEYBOARD_SHORTCUT_SPLIT_RIGHT_BOTTOM(1232),
-
         @UiEvent(doc = "User has invoked split to left half with a keyboard shortcut.")
         LAUNCHER_KEYBOARD_SHORTCUT_SPLIT_LEFT_TOP(1233),
-
         @UiEvent(doc = "User has invoked split to right half from desktop mode.")
         LAUNCHER_DESKTOP_MODE_SPLIT_RIGHT_BOTTOM(1412),
-
         @UiEvent(doc = "User has invoked split to left half from desktop mode.")
         LAUNCHER_DESKTOP_MODE_SPLIT_LEFT_TOP(1464),
-
         @UiEvent(doc = "User has collapsed the work FAB button by scrolling down in the all apps"
                 + " work A-Z list.")
         LAUNCHER_WORK_FAB_BUTTON_COLLAPSE(1276),
-
         @UiEvent(doc = "User has collapsed the work FAB button by scrolling up in the all apps"
                 + " work A-Z list.")
         LAUNCHER_WORK_FAB_BUTTON_EXTEND(1277),
-
         @UiEvent(doc = "User scrolled down on the search result page.")
         LAUNCHER_ALLAPPS_SEARCH_SCROLLED_DOWN(1285),
-
         @UiEvent(doc = "User scrolled up on the search result page.")
         LAUNCHER_ALLAPPS_SEARCH_SCROLLED_UP(1286),
-
         @UiEvent(doc = "User or automatic timeout has hidden transient taskbar.")
         LAUNCHER_TRANSIENT_TASKBAR_HIDE(1330),
-
         @UiEvent(doc = "User has swiped upwards from the gesture handle to show transient taskbar.")
         LAUNCHER_TRANSIENT_TASKBAR_SHOW(1331),
-
         @UiEvent(doc = "User has clicked an app pair and launched directly into split screen.")
         LAUNCHER_APP_PAIR_LAUNCH(1374),
-
         @UiEvent(doc = "User saved an app pair.")
         LAUNCHER_APP_PAIR_SAVE(1456),
-
         @UiEvent(doc = "App launched through pending intent")
         LAUNCHER_APP_LAUNCH_PENDING_INTENT(1394),
 
@@ -826,82 +628,6 @@ public class StatsLogManager implements ResourceBasedOverride {
         @UiEvent(doc = "User launches Overview from meta+tab keyboard shortcut")
         LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_KEYBOARD_SHORTCUT(1765),
 
-        @UiEvent(doc = "User long pressed on the taskbar IME switcher button")
-        LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_LONGPRESS(1798),
-
-        @UiEvent(doc = "Failed to launch assistant due to Google assistant not available")
-        LAUNCHER_LAUNCH_ASSISTANT_FAILED_NOT_AVAILABLE(1465),
-
-        @UiEvent(doc = "Failed to launch assistant due to service error")
-        LAUNCHER_LAUNCH_ASSISTANT_FAILED_SERVICE_ERROR(1466),
-
-        @UiEvent(doc = "User launched assistant by long-pressing nav handle")
-        LAUNCHER_LAUNCH_ASSISTANT_SUCCESSFUL_NAV_HANDLE(1467),
-
-        @UiEvent(doc = "Failed to launch due to Contextual Search not available")
-        LAUNCHER_LAUNCH_OMNI_FAILED_NOT_AVAILABLE(1471),
-
-        @UiEvent(doc = "Failed to launch due to Contextual Search setting disabled")
-        LAUNCHER_LAUNCH_OMNI_FAILED_SETTING_DISABLED(1632),
-
-        @UiEvent(doc = "User launched Contextual Search by long-pressing home in 3-button mode")
-        LAUNCHER_LAUNCH_OMNI_SUCCESSFUL_HOME(1481),
-
-        @UiEvent(doc = "User launched Contextual Search by using accessibility System Action")
-        LAUNCHER_LAUNCH_OMNI_SUCCESSFUL_SYSTEM_ACTION(1492),
-
-        @UiEvent(doc = "User launched Contextual Search by long pressing the meta key")
-        LAUNCHER_LAUNCH_OMNI_SUCCESSFUL_META(1606),
-
-        @UiEvent(doc = "Contextual Search invocation was attempted over the notification shade")
-        LAUNCHER_LAUNCH_OMNI_ATTEMPTED_OVER_NOTIFICATION_SHADE(1485),
-
-        @UiEvent(doc = "The Contextual Search all entrypoints toggle value in Settings")
-        LAUNCHER_SETTINGS_OMNI_ALL_ENTRYPOINTS_TOGGLE_VALUE(1633),
-
-        @UiEvent(doc = "Contextual Search invocation was attempted over the keyguard")
-        LAUNCHER_LAUNCH_OMNI_ATTEMPTED_OVER_KEYGUARD(1501),
-
-        @UiEvent(doc = "Contextual Search invocation was attempted while splitscreen is active")
-        LAUNCHER_LAUNCH_OMNI_ATTEMPTED_SPLITSCREEN(1505),
-
-        @UiEvent(doc = "User long press nav handle and a long press runnable was created.")
-        LAUNCHER_OMNI_GET_LONG_PRESS_RUNNABLE(1545),
-
-        @UiEvent(doc = "User tapped on \"change aspect ratio\" system shortcut.")
-        LAUNCHER_ASPECT_RATIO_SETTINGS_SYSTEM_SHORTCUT_TAP(2219),
-
-        // One Grid Flags
-        @UiEvent(doc = "User sets the device in Fixed Landscape")
-        FIXED_LANDSCAPE_TOGGLE_ENABLE(2014),
-
-        @UiEvent(doc = "User sets the device in Fixed Landscape")
-        FIXED_LANDSCAPE_TOGGLE_DISABLED(2020),
-
-        @UiEvent(doc = "Work utility view expand animation started")
-        LAUNCHER_WORK_UTILITY_VIEW_EXPAND_ANIMATION_BEGIN(2075),
-
-        @UiEvent(doc = "Work utility view expand animation ended")
-        LAUNCHER_WORK_UTILITY_VIEW_EXPAND_ANIMATION_END(2076),
-
-        @UiEvent(doc = "Work utility view shrink animation started")
-        LAUNCHER_WORK_UTILITY_VIEW_SHRINK_ANIMATION_BEGIN(2077),
-
-        @UiEvent(doc = "Work utility view shrink animation ended")
-        LAUNCHER_WORK_UTILITY_VIEW_SHRINK_ANIMATION_END(2078),
-
-        @UiEvent(doc = "Standard grid migration occurred")
-        LAUNCHER_STANDARD_GRID_MIGRATION(2200),
-
-        @UiEvent(doc = "Row shift grid migration occurred")
-        LAUNCHER_ROW_SHIFT_GRID_MIGRATION(2201),
-
-        @UiEvent(doc = "Do standard migration when upgrading to one grid")
-        LAUNCHER_STANDARD_ONE_GRID_MIGRATION(2205),
-
-        @UiEvent(doc = "Do row shift migration when upgrading to one grid")
-        LAUNCHER_ROW_SHIFT_ONE_GRID_MIGRATION(2206),
-
         // ADD MORE
         ;
 
@@ -918,28 +644,17 @@ public class StatsLogManager implements ResourceBasedOverride {
 
     /** Launcher's latency events. */
     public enum LauncherLatencyEvent implements EventEnum {
-        // Details of below 6 events with prefix of "LAUNCHER_LATENCY_STARTUP_" are discussed in
+        // Details of below 6 events with prefix of "LAUNCHER_LATENCY_STARTUP_" are
+        // discussed in
         // go/launcher-startup-latency
         @UiEvent(doc = "The total duration of launcher startup latency.")
         LAUNCHER_LATENCY_STARTUP_TOTAL_DURATION(1362),
-
         @UiEvent(doc = "The duration of launcher activity's onCreate().")
         LAUNCHER_LATENCY_STARTUP_ACTIVITY_ON_CREATE(1363),
-
-        @UiEvent(doc =
-                "The duration to inflate launcher root view in launcher activity's onCreate().")
+        @UiEvent(doc = "The duration to inflate launcher root view in launcher activity's onCreate().")
         LAUNCHER_LATENCY_STARTUP_VIEW_INFLATION(1364),
-
         @UiEvent(doc = "The duration of asynchronous loading workspace")
         LAUNCHER_LATENCY_STARTUP_WORKSPACE_LOADER_ASYNC(1367),
-
-        @UiEvent(doc = "Time passed between Contextual Search runnable creation and execution. This"
-                + " ensures that Recent animations have finished before Contextual Search starts.")
-        LAUNCHER_LATENCY_OMNI_RUNNABLE(1546),
-
-        @UiEvent(doc = "Time passed between nav handle touch down and cancellation without "
-                + "triggering Contextual Search")
-        LAUNCHER_LATENCY_CONTEXTUAL_SEARCH_LPNH_ABANDON(2171),
         ;
 
         private final int mId;
@@ -958,10 +673,9 @@ public class StatsLogManager implements ResourceBasedOverride {
      * Launcher specific ranking related events.
      */
     public enum LauncherRankingEvent implements EventEnum {
-
         UNKNOWN(0);
-        // ADD MORE
 
+        // ADD MORE
         private final int mId;
 
         LauncherRankingEvent(int id) {
@@ -977,7 +691,6 @@ public class StatsLogManager implements ResourceBasedOverride {
      * Helps to construct and log launcher event.
      */
     public interface StatsLogger {
-
         /**
          * Sets log fields from provided {@link ItemInfo}.
          */
@@ -985,7 +698,6 @@ public class StatsLogManager implements ResourceBasedOverride {
             return this;
         }
 
-
         /**
          * Sets {@link InstanceId} of log message.
          */
@@ -1038,7 +750,8 @@ public class StatsLogManager implements ResourceBasedOverride {
         /**
          * Sets the final value for container related fields of log message.
          *
-         * By default container related fields are derived from {@link ItemInfo}, this method would
+         * By default container related fields are derived from {@link ItemInfo}, this
+         * method would
          * override those values.
          */
         default StatsLogger withContainerInfo(ContainerInfo containerInfo) {
@@ -1105,7 +818,6 @@ public class StatsLogManager implements ResourceBasedOverride {
      * Helps to construct and log latency event.
      */
     public interface StatsLatencyLogger {
-
         /**
          * Should be in sync with:
          * google3/wireless/android/sysui/aster/asterstats/launcher_event_processed.proto
@@ -1126,6 +838,7 @@ public class StatsLogManager implements ResourceBasedOverride {
             // Tracking warm startup latency:
             // https://developer.android.com/topic/performance/vitals/launch-time#warm
             WARM(10);
+
             private final int mId;
 
             LatencyType(int id) {
@@ -1144,7 +857,6 @@ public class StatsLogManager implements ResourceBasedOverride {
             return this;
         }
 
-
         /**
          * Sets latency of the event.
          */
@@ -1173,7 +885,6 @@ public class StatsLogManager implements ResourceBasedOverride {
             return this;
         }
 
-
         /** Sets cardinality of the event. */
         default StatsLatencyLogger withCardinality(int cardinality) {
             return this;
@@ -1197,11 +908,11 @@ public class StatsLogManager implements ResourceBasedOverride {
      * Helps to construct and log impression event.
      */
     public interface StatsImpressionLogger {
-
         enum State {
             UNKNOWN(0),
             ALLAPPS(1),
             SEARCHBOX_WIDGET(2);
+
             private final int mLauncherState;
 
             State(int id) {
@@ -1242,7 +953,8 @@ public class StatsLogManager implements ResourceBasedOverride {
         }
 
         /**
-         * Sets boolean for each of {@link com.android.app.search.ResultType} that indicates
+         * Sets boolean for each of {@link com.android.app.search.ResultType} that
+         * indicates
          * if this result is above keyboard or not for the impression event.
          */
         default StatsImpressionLogger withAboveKeyboard(boolean aboveKeyboard) {
@@ -1258,7 +970,8 @@ public class StatsLogManager implements ResourceBasedOverride {
         }
 
         /**
-         * Sets result source that indicates the origin of the result for the impression event.
+         * Sets result source that indicates the origin of the result for the impression
+         * event.
          */
         default StatsImpressionLogger withResultSource(int resultSource) {
             return this;
@@ -1332,7 +1045,8 @@ public class StatsLogManager implements ResourceBasedOverride {
     }
 
     /**
-     * Sets InstanceId to every new {@link StatsLogger} object returned by {@link #logger()} when
+     * Sets InstanceId to every new {@link StatsLogger} object returned by
+     * {@link #logger()} when
      * not-null.
      */
     public StatsLogManager withDefaultInstanceId(@Nullable InstanceId instanceId) {
@@ -1347,4 +1061,4 @@ public class StatsLogManager implements ResourceBasedOverride {
         return Overrides.getObject(
                 StatsLogManager.class, context, R.string.stats_log_manager_class);
     }
-}
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
index 244ec01d42..365caf1285 100644
--- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
+++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
@@ -15,8 +15,6 @@
  */
 package com.android.launcher3.model;
 
-import static com.android.launcher3.LauncherSettings.Favorites.DESKTOP_ICON_FLAG;
-
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.LauncherActivityInfo;
@@ -43,7 +41,6 @@ import com.android.launcher3.model.data.WorkspaceItemFactory;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.pm.InstallSessionHelper;
 import com.android.launcher3.pm.PackageInstallInfo;
-import com.android.launcher3.util.ApplicationInfoWrapper;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.PackageManagerHelper;
 
@@ -66,6 +63,13 @@ public class AddWorkspaceItemsTask implements ModelUpdateTask {
 
     /**
      * @param itemList items to add on the workspace
+     */
+    public AddWorkspaceItemsTask(@NonNull final List> itemList) {
+        this(itemList, new WorkspaceItemSpaceFinder());
+    }
+
+    /**
+     * @param itemList        items to add on the workspace
      * @param itemSpaceFinder inject WorkspaceItemSpaceFinder dependency for testing
      */
     public AddWorkspaceItemsTask(@NonNull final List> itemList,
@@ -74,7 +78,6 @@ public class AddWorkspaceItemsTask implements ModelUpdateTask {
         mItemSpaceFinder = itemSpaceFinder;
     }
 
-
     @Override
     public void execute(@NonNull ModelTaskController taskController, @NonNull BgDataModel dataModel,
             @NonNull AllAppsList apps) {
@@ -84,10 +87,10 @@ public class AddWorkspaceItemsTask implements ModelUpdateTask {
 
         final ArrayList addedItemsFinal = new ArrayList<>();
         final IntArray addedWorkspaceScreensFinal = new IntArray();
-        final Context context = taskController.getContext();
+        final Context context = taskController.getApp().getContext();
 
         synchronized (dataModel) {
-            IntArray workspaceScreens = dataModel.collectWorkspaceScreens(context);
+            IntArray workspaceScreens = dataModel.collectWorkspaceScreens();
 
             List filteredItems = new ArrayList<>();
             for (Pair entry : mItemList) {
@@ -98,12 +101,6 @@ public class AddWorkspaceItemsTask implements ModelUpdateTask {
                         continue;
                     }
 
-                    // b/139663018 Short-circuit this logic if the icon is a system app
-                    if (new ApplicationInfoWrapper(context,
-                            Objects.requireNonNull(item.getIntent())).isSystem()) {
-                        continue;
-                    }
-
                     if (item instanceof ItemInfoWithIcon
                             && ((ItemInfoWithIcon) item).isArchived()) {
                         continue;
@@ -120,14 +117,13 @@ public class AddWorkspaceItemsTask implements ModelUpdateTask {
                 }
             }
 
-            InstallSessionHelper packageInstaller =
-                    InstallSessionHelper.INSTANCE.get(context);
+            InstallSessionHelper packageInstaller = InstallSessionHelper.INSTANCE.get(context);
             LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
 
             for (ItemInfo item : filteredItems) {
                 // Find appropriate space for the item.
-                int[] coords = mItemSpaceFinder.findSpaceForItem(
-                        workspaceScreens, addedWorkspaceScreensFinal, item.spanX, item.spanY, context);
+                int[] coords = mItemSpaceFinder.findSpaceForItem(taskController.getApp(), dataModel,
+                        workspaceScreens, addedWorkspaceScreensFinal, item.spanX, item.spanY);
                 int screenId = coords[0];
 
                 ItemInfo itemInfo;
@@ -186,11 +182,12 @@ public class AddWorkspaceItemsTask implements ModelUpdateTask {
                             continue;
                         }
 
-                        IconCache cache = taskController.getIconCache();
+                        IconCache cache = taskController.getApp().getIconCache();
                         WorkspaceItemInfo wii = (WorkspaceItemInfo) itemInfo;
                         wii.title = "";
                         wii.bitmap = cache.getDefaultIcon(item.user);
-                        cache.getTitleAndIcon(wii, DESKTOP_ICON_FLAG);
+                        cache.getTitleAndIcon(wii,
+                                ((WorkspaceItemInfo) itemInfo).usingLowResIcon());
                     }
                 }
 
@@ -232,7 +229,8 @@ public class AddWorkspaceItemsTask implements ModelUpdateTask {
     }
 
     /**
-     * Returns true if the shortcuts already exists on the workspace. This must be called after
+     * Returns true if the shortcuts already exists on the workspace. This must be
+     * called after
      * the workspace has been loaded. We identify a shortcut by its intent.
      */
     protected boolean shortcutExists(@NonNull final BgDataModel dataModel,
diff --git a/src/com/android/launcher3/model/AllAppsList.java b/src/com/android/launcher3/model/AllAppsList.java
index fe0437d3d7..9191477c75 100644
--- a/src/com/android/launcher3/model/AllAppsList.java
+++ b/src/com/android/launcher3/model/AllAppsList.java
@@ -18,7 +18,6 @@
 
 package com.android.launcher3.model;
 
-import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG;
 import static com.android.launcher3.model.data.AppInfo.COMPONENT_KEY_COMPARATOR;
 import static com.android.launcher3.model.data.AppInfo.EMPTY_ARRAY;
 
@@ -36,7 +35,6 @@ import androidx.annotation.Nullable;
 
 import com.android.launcher3.AppFilter;
 import com.android.launcher3.compat.AlphabeticIndexCompat;
-import com.android.launcher3.dagger.LauncherAppSingleton;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.model.BgDataModel.Callbacks;
 import com.android.launcher3.model.data.AppInfo;
@@ -44,7 +42,6 @@ import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.pm.PackageInstallInfo;
 import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.util.ApiWrapper;
-import com.android.launcher3.util.ApplicationInfoWrapper;
 import com.android.launcher3.util.FlagOp;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.SafeCloseable;
@@ -56,13 +53,11 @@ import java.util.List;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 
-import javax.inject.Inject;
-
 
 /**
  * Stores the list of all applications for the all apps view.
  */
-@LauncherAppSingleton
+@SuppressWarnings("NewApi")
 public class AllAppsList {
 
     private static final String TAG = "AllAppsList";
@@ -75,10 +70,10 @@ public class AllAppsList {
     public final ArrayList data = new ArrayList<>(DEFAULT_APPLICATIONS_NUMBER);
 
     @NonNull
-    private final IconCache mIconCache;
+    private IconCache mIconCache;
 
     @NonNull
-    private final AppFilter mAppFilter;
+    private AppFilter mAppFilter;
 
     private boolean mDataChanged = false;
     private Consumer mRemoveListener = NO_OP_CONSUMER;
@@ -97,7 +92,6 @@ public class AllAppsList {
     /**
      * Boring constructor.
      */
-    @Inject
     public AllAppsList(IconCache iconCache, AppFilter appFilter) {
         mIconCache = iconCache;
         mAppFilter = appFilter;
@@ -158,7 +152,7 @@ public class AllAppsList {
             return;
         }
         if (loadIcon) {
-            mIconCache.getTitleAndIcon(info, activityInfo, DEFAULT_LOOKUP_FLAG);
+            mIconCache.getTitleAndIcon(info, activityInfo, false /* useLowResIcon */);
             info.sectionName = mIndex.computeSectionName(info.title);
         } else {
             info.title = "";
@@ -177,14 +171,14 @@ public class AllAppsList {
     public AppInfo addPromiseApp(
             Context context, PackageInstallInfo installInfo, boolean loadIcon) {
         // only if not yet installed
-        if (new ApplicationInfoWrapper(context, installInfo.packageName, installInfo.user)
-                .isInstalled()) {
+        if (PackageManagerHelper.INSTANCE.get(context)
+                .isAppInstalled(installInfo.packageName, installInfo.user)) {
             return null;
         }
         AppInfo promiseAppInfo = new AppInfo(installInfo);
 
         if (loadIcon) {
-            mIconCache.getTitleAndIcon(promiseAppInfo, promiseAppInfo.getMatchingLookupFlag());
+            mIconCache.getTitleAndIcon(promiseAppInfo, promiseAppInfo.usingLowResIcon());
             promiseAppInfo.sectionName = mIndex.computeSectionName(promiseAppInfo.title);
         } else {
             promiseAppInfo.title = "";
@@ -231,8 +225,7 @@ public class AllAppsList {
                     if (DEBUG) {
                         Log.w(TAG, "updatePromiseInstallInfo: removing app due to install"
                                 + " failure and appInfo not startable."
-                                + " package=" + appInfo.getTargetPackage()
-                                + ", user=" + user);
+                                + " package=" + appInfo.getTargetPackage());
                     }
                     removeApp(i);
                 }
@@ -328,8 +321,7 @@ public class AllAppsList {
                     if (!findActivity(matches, applicationInfo.componentName)) {
                         if (DEBUG) {
                             Log.w(TAG, "Changing shortcut target due to app component name change."
-                                    + " component=" + applicationInfo.componentName
-                                    + ", user=" + user);
+                                    + " package=" + packageName);
                         }
                         removeApp(i);
                     }
@@ -345,7 +337,7 @@ public class AllAppsList {
                 } else {
                     Intent launchIntent = AppInfo.makeLaunchIntent(info);
 
-                    mIconCache.getTitleAndIcon(applicationInfo, info, DEFAULT_LOOKUP_FLAG);
+                    mIconCache.getTitleAndIcon(applicationInfo, info, false /* useLowResIcon */);
                     applicationInfo.sectionName = mIndex.computeSectionName(applicationInfo.title);
                     applicationInfo.intent = launchIntent;
                     AppInfo.updateRuntimeFlagsForActivityTarget(applicationInfo, info,
@@ -356,9 +348,8 @@ public class AllAppsList {
         } else {
             // Remove all data for this package.
             if (DEBUG) {
-                Log.w(TAG, "updatePackage: no Activities matched updated package,"
-                        + " removing any AppInfo with package=" + packageName
-                        + ", user=" + user);
+                Log.w(TAG, "updatePromiseInstallInfo: no Activities matched updated package,"
+                        + " removing all apps from package=" + packageName);
             }
             for (int i = data.size() - 1; i >= 0; i--) {
                 final AppInfo applicationInfo = data.get(i);
diff --git a/src/com/android/launcher3/model/BaseLauncherBinder.java b/src/com/android/launcher3/model/BaseLauncherBinder.java
index 5052891bb5..6a691723f8 100644
--- a/src/com/android/launcher3/model/BaseLauncherBinder.java
+++ b/src/com/android/launcher3/model/BaseLauncherBinder.java
@@ -19,14 +19,12 @@ package com.android.launcher3.model;
 import static com.android.launcher3.BuildConfigs.WIDGETS_ENABLED;
 import static com.android.launcher3.Flags.enableSmartspaceRemovalToggle;
 import static com.android.launcher3.Flags.enableWorkspaceInflation;
-import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
 import static com.android.launcher3.model.ItemInstallQueue.FLAG_LOADER_RUNNING;
-import static com.android.launcher3.model.ModelUtils.WIDGET_FILTER;
-import static com.android.launcher3.model.ModelUtils.currentScreenContentFilter;
+import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
-import android.content.Context;
+import android.os.Process;
 import android.os.Trace;
 import android.util.Log;
 import android.util.Pair;
@@ -35,47 +33,43 @@ import android.view.View;
 import androidx.annotation.NonNull;
 
 import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.LauncherModel;
+import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherModel.CallbackTask;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.celllayout.CellPosMapper;
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.dagger.ApplicationContext;
 import com.android.launcher3.model.BgDataModel.Callbacks;
 import com.android.launcher3.model.BgDataModel.FixedContainerItems;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.IntSet;
-import com.android.launcher3.util.IntSparseArrayMap;
 import com.android.launcher3.util.ItemInflater;
 import com.android.launcher3.util.LooperExecutor;
 import com.android.launcher3.util.LooperIdleLock;
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.RunnableList;
-import com.android.launcher3.widget.model.WidgetsListBaseEntriesBuilder;
 import com.android.launcher3.widget.model.WidgetsListBaseEntry;
 
-import dagger.assisted.Assisted;
-import dagger.assisted.AssistedFactory;
-import dagger.assisted.AssistedInject;
-
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.Executor;
-import java.util.function.Predicate;
 import java.util.stream.Collectors;
 
 /**
- * Binds the results of {@link com.android.launcher3.model.LoaderTask} to the Callbacks objects.
+ * Binds the results of {@link com.android.launcher3.model.LoaderTask} to the
+ * Callbacks objects.
  */
 public class BaseLauncherBinder {
 
@@ -84,9 +78,7 @@ public class BaseLauncherBinder {
 
     protected final LooperExecutor mUiExecutor;
 
-    private final Context mContext;
-    private final InvariantDeviceProfile mIDP;
-    private final LauncherModel mModel;
+    protected final LauncherAppState mApp;
     protected final BgDataModel mBgDataModel;
     private final AllAppsList mBgAllAppsList;
 
@@ -94,18 +86,10 @@ public class BaseLauncherBinder {
 
     private int mMyBindingId;
 
-    @AssistedInject
-    public BaseLauncherBinder(
-            @ApplicationContext Context context,
-            InvariantDeviceProfile idp,
-            LauncherModel model,
-            BgDataModel dataModel,
-            AllAppsList allAppsList,
-            @Assisted Callbacks[] callbacksList) {
+    public BaseLauncherBinder(LauncherAppState app, BgDataModel dataModel,
+            AllAppsList allAppsList, Callbacks[] callbacksList) {
         mUiExecutor = MAIN_EXECUTOR;
-        mContext = context;
-        mIDP = idp;
-        mModel = model;
+        mApp = app;
         mBgDataModel = dataModel;
         mBgAllAppsList = allAppsList;
         mCallbacksList = callbacksList;
@@ -117,32 +101,68 @@ public class BaseLauncherBinder {
     public void bindWorkspace(boolean incrementBindId, boolean isBindSync) {
         Trace.beginSection("BaseLauncherBinder#bindWorkspace");
         try {
-            // Save a copy of all the bg-thread collections
-            IntSparseArrayMap itemsIdMap;
-            final IntArray orderedScreenIds = new IntArray();
-            ArrayList extraItems = new ArrayList<>();
-            final int workspaceItemCount;
-            synchronized (mBgDataModel) {
-                itemsIdMap = mBgDataModel.itemsIdMap.clone();
-                orderedScreenIds.addAll(mBgDataModel.collectWorkspaceScreens(mContext));
-                mBgDataModel.extraItems.forEach(extraItems::add);
-                if (incrementBindId) {
-                    mBgDataModel.lastBindId++;
-                    mBgDataModel.lastLoadId = mModel.getLastLoadId();
-                }
-                mMyBindingId = mBgDataModel.lastBindId;
-                workspaceItemCount = mBgDataModel.itemsIdMap.size();
-            }
-
-            for (Callbacks cb : mCallbacksList) {
-                new UnifiedWorkspaceBinder(cb, itemsIdMap, extraItems, orderedScreenIds)
-                        .bind(isBindSync, workspaceItemCount);
+            if (FeatureFlags.ENABLE_WORKSPACE_LOADING_OPTIMIZATION.get()) {
+                DisjointWorkspaceBinder workspaceBinder = initWorkspaceBinder(incrementBindId,
+                        mBgDataModel.collectWorkspaceScreens());
+                workspaceBinder.bindCurrentWorkspacePages(isBindSync);
+                workspaceBinder.bindOtherWorkspacePages();
+            } else {
+                bindWorkspaceAllAtOnce(incrementBindId, isBindSync);
             }
         } finally {
             Trace.endSection();
         }
     }
 
+    /**
+     * Initializes the WorkspaceBinder for binding.
+     *
+     * @param incrementBindId this is used to stop previously started binding tasks
+     *                        that are
+     *                        obsolete but still queued.
+     * @param workspacePages  this allows the Launcher to add the correct workspace
+     *                        screens.
+     */
+    public DisjointWorkspaceBinder initWorkspaceBinder(boolean incrementBindId,
+            IntArray workspacePages) {
+
+        synchronized (mBgDataModel) {
+            if (incrementBindId) {
+                mBgDataModel.lastBindId++;
+                mBgDataModel.lastLoadId = mApp.getModel().getLastLoadId();
+            }
+            mMyBindingId = mBgDataModel.lastBindId;
+            return new DisjointWorkspaceBinder(workspacePages);
+        }
+    }
+
+    private void bindWorkspaceAllAtOnce(boolean incrementBindId, boolean isBindSync) {
+        // Save a copy of all the bg-thread collections
+        ArrayList workspaceItems = new ArrayList<>();
+        ArrayList appWidgets = new ArrayList<>();
+        final IntArray orderedScreenIds = new IntArray();
+        ArrayList extraItems = new ArrayList<>();
+        final int workspaceItemCount;
+        synchronized (mBgDataModel) {
+            workspaceItems.addAll(mBgDataModel.workspaceItems);
+            appWidgets.addAll(mBgDataModel.appWidgets);
+            orderedScreenIds.addAll(mBgDataModel.collectWorkspaceScreens());
+            mBgDataModel.extraItems.forEach(extraItems::add);
+            if (incrementBindId) {
+                mBgDataModel.lastBindId++;
+                mBgDataModel.lastLoadId = mApp.getModel().getLastLoadId();
+            }
+            mMyBindingId = mBgDataModel.lastBindId;
+            workspaceItemCount = mBgDataModel.itemsIdMap.size();
+        }
+
+        for (Callbacks cb : mCallbacksList) {
+            new UnifiedWorkspaceBinder(cb, mUiExecutor, mApp, mBgDataModel, mMyBindingId,
+                    workspaceItems, appWidgets, extraItems, orderedScreenIds)
+                    .bind(isBindSync, workspaceItemCount);
+        }
+    }
+
     /**
      * BindDeepShortcuts is abstract because it is a no-op for the go launcher.
      */
@@ -167,7 +187,8 @@ public class BaseLauncherBinder {
         Map packageUserKeytoUidMap = Arrays.stream(apps).collect(
                 Collectors.toMap(
                         appInfo -> new PackageUserKey(appInfo.componentName.getPackageName(),
-                                appInfo.user), appInfo -> appInfo.uid, (a, b) -> a));
+                                appInfo.user),
+                        appInfo -> appInfo.uid, (a, b) -> a));
         executeCallbacksTask(c -> c.bindAllApplications(apps, flags, packageUserKeytoUidMap),
                 mUiExecutor);
     }
@@ -179,8 +200,8 @@ public class BaseLauncherBinder {
         if (!WIDGETS_ENABLED) {
             return;
         }
-        List widgets = new WidgetsListBaseEntriesBuilder(mContext)
-                .build(mBgDataModel.widgetsModel.getWidgetsByPackageItemForPicker());
+        final List widgets =
+                mBgDataModel.widgetsModel.getWidgetsListForPicker(mApp.getContext());
         executeCallbacksTask(c -> c.bindAllWidgets(widgets), mUiExecutor);
     }
 
@@ -195,11 +216,13 @@ public class BaseLauncherBinder {
     }
 
     /**
-     * Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to right)
+     * Sorts the set of items by hotseat, workspace (spatially from top to bottom,
+     * left to right)
      */
-    protected void sortWorkspaceItemsSpatially(ArrayList workspaceItems) {
-        final int screenCols = mIDP.numColumns;
-        final int screenCellCount = mIDP.numColumns * mIDP.numRows;
+    protected void sortWorkspaceItemsSpatially(InvariantDeviceProfile profile,
+            ArrayList workspaceItems) {
+        final int screenCols = profile.numColumns;
+        final int screenCellCount = profile.numColumns * profile.numRows;
         Collections.sort(workspaceItems, (lhs, rhs) -> {
             if (lhs.container == rhs.container) {
                 // Within containers, order by their spatial position in that container
@@ -246,7 +269,8 @@ public class BaseLauncherBinder {
      */
     public LooperIdleLock newIdleLock(Object lock) {
         LooperIdleLock idleLock = new LooperIdleLock(lock, mUiExecutor.getLooper());
-        // If we are not binding or if the main looper is already idle, there is no reason to wait
+        // If we are not binding or if the main looper is already idle, there is no
+        // reason to wait
         if (mUiExecutor.getLooper().getQueue().isIdle()) {
             idleLock.queueIdle();
         }
@@ -255,45 +279,56 @@ public class BaseLauncherBinder {
 
     private class UnifiedWorkspaceBinder {
 
+        private final Executor mUiExecutor;
         private final Callbacks mCallbacks;
 
-        private final IntSparseArrayMap mItemIdMap;
+        private final LauncherAppState mApp;
+        private final BgDataModel mBgDataModel;
+
+        private final int mMyBindingId;
+        private final ArrayList mWorkspaceItems;
+        private final ArrayList mAppWidgets;
         private final IntArray mOrderedScreenIds;
         private final ArrayList mExtraItems;
 
-        UnifiedWorkspaceBinder(
-                Callbacks callbacks,
-                IntSparseArrayMap itemIdMap,
+        UnifiedWorkspaceBinder(Callbacks callbacks,
+                Executor uiExecutor,
+                LauncherAppState app,
+                BgDataModel bgDataModel,
+                int myBindingId,
+                ArrayList workspaceItems,
+                ArrayList appWidgets,
                 ArrayList extraItems,
                 IntArray orderedScreenIds) {
             mCallbacks = callbacks;
-            mItemIdMap = itemIdMap;
+            mUiExecutor = uiExecutor;
+            mApp = app;
+            mBgDataModel = bgDataModel;
+            mMyBindingId = myBindingId;
+            mWorkspaceItems = workspaceItems;
+            mAppWidgets = appWidgets;
             mExtraItems = extraItems;
             mOrderedScreenIds = orderedScreenIds;
         }
 
         private void bind(boolean isBindSync, int workspaceItemCount) {
-            final IntSet currentScreenIds =
-                    mCallbacks.getPagesToBindSynchronously(mOrderedScreenIds);
+            final IntSet currentScreenIds = mCallbacks.getPagesToBindSynchronously(mOrderedScreenIds);
             Objects.requireNonNull(currentScreenIds, "Null screen ids provided by " + mCallbacks);
 
-            // Separate the items that are on the current screen, and all the other remaining items
+            // Separate the items that are on the current screen, and all the other
+            // remaining items
             ArrayList currentWorkspaceItems = new ArrayList<>();
             ArrayList otherWorkspaceItems = new ArrayList<>();
             ArrayList currentAppWidgets = new ArrayList<>();
             ArrayList otherAppWidgets = new ArrayList<>();
 
-            Predicate currentScreenCheck = currentScreenContentFilter(currentScreenIds);
-            mItemIdMap.forEach(item -> {
-                if (currentScreenCheck.test(item)) {
-                    (WIDGET_FILTER.test(item) ? currentAppWidgets : currentWorkspaceItems)
-                            .add(item);
-                } else if (item.container == CONTAINER_DESKTOP) {
-                    (WIDGET_FILTER.test(item) ? otherAppWidgets : otherWorkspaceItems).add(item);
-                }
-            });
-            sortWorkspaceItemsSpatially(currentWorkspaceItems);
-            sortWorkspaceItemsSpatially(otherWorkspaceItems);
+            filterCurrentWorkspaceItems(currentScreenIds, mWorkspaceItems, currentWorkspaceItems,
+                    otherWorkspaceItems);
+            filterCurrentWorkspaceItems(currentScreenIds, mAppWidgets, currentAppWidgets,
+                    otherAppWidgets);
+            final InvariantDeviceProfile idp = mApp.getInvariantDeviceProfile();
+            sortWorkspaceItemsSpatially(idp, currentWorkspaceItems);
+            sortWorkspaceItemsSpatially(idp, otherWorkspaceItems);
 
             // Tell the workspace that we're about to start binding items
             executeCallbacksTask(c -> {
@@ -318,17 +353,16 @@ public class BaseLauncherBinder {
                 bindItemsInChunks(currentWorkspaceItems, ITEMS_CHUNK, mUiExecutor);
                 bindItemsInChunks(currentAppWidgets, 1, mUiExecutor);
             }
-            mExtraItems.forEach(item ->
-                    executeCallbacksTask(c -> c.bindExtraContainerItems(item), mUiExecutor));
+            if (!FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
+                mExtraItems.forEach(item -> executeCallbacksTask(c -> c.bindExtraContainerItems(item), mUiExecutor));
+            }
 
             RunnableList pendingTasks = new RunnableList();
             Executor pendingExecutor = pendingTasks::add;
 
             RunnableList onCompleteSignal = new RunnableList();
-            onCompleteSignal.add(() -> Log.d(TAG, "Calling onCompleteSignal"));
 
             if (enableWorkspaceInflation() && inflater != null) {
-                Log.d(TAG, "Starting async inflation");
                 MODEL_EXECUTOR.execute(() ->  {
                     inflateAsyncAndBind(otherWorkspaceItems, inflater, pendingExecutor);
                     inflateAsyncAndBind(otherAppWidgets, inflater, pendingExecutor);
@@ -339,15 +373,20 @@ public class BaseLauncherBinder {
                     MAIN_EXECUTOR.execute(onCompleteSignal::executeAllAndDestroy);
                 });
             } else {
-                Log.d(TAG, "Starting sync inflation");
                 bindItemsInChunks(otherWorkspaceItems, ITEMS_CHUNK, pendingExecutor);
                 bindItemsInChunks(otherAppWidgets, 1, pendingExecutor);
                 setupPendingBind(currentScreenIds, pendingExecutor);
                 onCompleteSignal.executeAllAndDestroy();
             }
 
-            executeCallbacksTask(c -> c.onInitialBindComplete(currentScreenIds, pendingTasks,
-                    onCompleteSignal, workspaceItemCount, isBindSync), mUiExecutor);
+            executeCallbacksTask(
+                    c -> {
+                        if (!enableWorkspaceInflation()) {
+                            MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+                        }
+                        c.onInitialBindComplete(currentScreenIds, pendingTasks, onCompleteSignal,
+                                workspaceItemCount, isBindSync);
+                    }, mUiExecutor);
         }
 
         private void setupPendingBind(
@@ -357,8 +396,12 @@ public class BaseLauncherBinder {
             executeCallbacksTask(c -> c.bindStringCache(cacheClone), pendingExecutor);
 
             executeCallbacksTask(c -> c.finishBindingItems(currentScreenIds), pendingExecutor);
-            pendingExecutor.execute(() -> ItemInstallQueue.INSTANCE.get(mContext)
-                    .resumeModelPush(FLAG_LOADER_RUNNING));
+            pendingExecutor.execute(
+                    () -> {
+                        MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
+                        ItemInstallQueue.INSTANCE.get(mApp.getContext())
+                                .resumeModelPush(FLAG_LOADER_RUNNING);
+                    });
         }
 
         /**
@@ -372,12 +415,11 @@ public class BaseLauncherBinder {
                 return;
             }
 
-            ModelWriter writer = mModel.getWriter(
-                    false /* verifyChanges */, CellPosMapper.DEFAULT, null);
-            List> bindItems = items.stream()
-                    .map(i -> Pair.create(i, inflater.inflateItem(i, writer, null)))
-                    .collect(Collectors.toList());
-            executeCallbacksTask(c -> c.bindInflatedItems(bindItems), executor);
+            ModelWriter writer = mApp.getModel()
+                    .getWriter(false /* verifyChanges */, CellPosMapper.DEFAULT, null);
+            List> finalBindItems = items.stream().map(i ->
+                    Pair.create(i, inflater.inflateItem(i, writer, null))).collect(Collectors.toList());
+            executeCallbacksTask(c -> c.bindInflatedItems(finalBindItems), executor);
         }
 
         private void bindItemsInChunks(
@@ -404,8 +446,128 @@ public class BaseLauncherBinder {
         }
     }
 
-    @AssistedFactory
-    public interface BaseLauncherBinderFactory {
-        BaseLauncherBinder createBinder(Callbacks[] callbacks);
+    private class DisjointWorkspaceBinder {
+        private final IntArray mOrderedScreenIds;
+        private final IntSet mCurrentScreenIds = new IntSet();
+        private final Set mBoundItemIds = new HashSet<>();
+
+        protected DisjointWorkspaceBinder(IntArray orderedScreenIds) {
+            mOrderedScreenIds = orderedScreenIds;
+
+            for (Callbacks cb : mCallbacksList) {
+                mCurrentScreenIds.addAll(cb.getPagesToBindSynchronously(orderedScreenIds));
+            }
+            if (mCurrentScreenIds.size() == 0) {
+                mCurrentScreenIds.add(Workspace.FIRST_SCREEN_ID);
+            }
+        }
+
+        /**
+         * Binds the currently loaded items in the Data Model. Also signals to the
+         * Callbacks[]
+         * that these items have been bound and their respective screens are ready to be
+         * shown.
+         *
+         * If this method is called after all the items on the workspace screen have
+         * already been
+         * loaded, it will bind all workspace items immediately, and
+         * bindOtherWorkspacePages() will
+         * not bind any items.
+         */
+        protected void bindCurrentWorkspacePages(boolean isBindSync) {
+            // Save a copy of all the bg-thread collections
+            ArrayList workspaceItems;
+            ArrayList appWidgets;
+            ArrayList fciList = new ArrayList<>();
+            final int workspaceItemCount;
+            synchronized (mBgDataModel) {
+                workspaceItems = new ArrayList<>(mBgDataModel.workspaceItems);
+                appWidgets = new ArrayList<>(mBgDataModel.appWidgets);
+                if (!FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
+                    mBgDataModel.extraItems.forEach(fciList::add);
+                }
+                workspaceItemCount = mBgDataModel.itemsIdMap.size();
+            }
+
+            workspaceItems.forEach(it -> mBoundItemIds.add(it.id));
+            appWidgets.forEach(it -> mBoundItemIds.add(it.id));
+            if (!FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
+                fciList.forEach(item -> executeCallbacksTask(c -> c.bindExtraContainerItems(item), mUiExecutor));
+            }
+
+            sortWorkspaceItemsSpatially(mApp.getInvariantDeviceProfile(), workspaceItems);
+
+            // Tell the workspace that we're about to start binding items
+            executeCallbacksTask(c -> {
+                c.clearPendingBinds();
+                c.startBinding();
+            }, mUiExecutor);
+
+            // Bind workspace screens
+            executeCallbacksTask(c -> c.bindScreens(mOrderedScreenIds), mUiExecutor);
+
+            bindWorkspaceItems(workspaceItems);
+            bindAppWidgets(appWidgets);
+            executeCallbacksTask(c -> {
+                MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+
+                RunnableList onCompleteSignal = new RunnableList();
+                onCompleteSignal.executeAllAndDestroy();
+                c.onInitialBindComplete(mCurrentScreenIds, new RunnableList(), onCompleteSignal,
+                        workspaceItemCount, isBindSync);
+            }, mUiExecutor);
+        }
+
+        protected void bindOtherWorkspacePages() {
+            // Save a copy of all the bg-thread collections
+            ArrayList workspaceItems;
+            ArrayList appWidgets;
+
+            synchronized (mBgDataModel) {
+                workspaceItems = new ArrayList<>(mBgDataModel.workspaceItems);
+                appWidgets = new ArrayList<>(mBgDataModel.appWidgets);
+            }
+
+            workspaceItems.removeIf(it -> mBoundItemIds.contains(it.id));
+            appWidgets.removeIf(it -> mBoundItemIds.contains(it.id));
+
+            sortWorkspaceItemsSpatially(mApp.getInvariantDeviceProfile(), workspaceItems);
+
+            bindWorkspaceItems(workspaceItems);
+            bindAppWidgets(appWidgets);
+
+            executeCallbacksTask(c -> c.finishBindingItems(mCurrentScreenIds), mUiExecutor);
+            mUiExecutor.execute(() -> {
+                MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
+                ItemInstallQueue.INSTANCE.get(mApp.getContext())
+                        .resumeModelPush(FLAG_LOADER_RUNNING);
+            });
+
+            StringCache cacheClone = mBgDataModel.stringCache.clone();
+            executeCallbacksTask(c -> c.bindStringCache(cacheClone), mUiExecutor);
+        }
+
+        private void bindWorkspaceItems(final ArrayList workspaceItems) {
+            // Bind the workspace items
+            int count = workspaceItems.size();
+            for (int i = 0; i < count; i += ITEMS_CHUNK) {
+                final int start = i;
+                final int chunkSize = (i + ITEMS_CHUNK <= count) ? ITEMS_CHUNK : (count - i);
+                executeCallbacksTask(
+                        c -> c.bindItems(workspaceItems.subList(start, start + chunkSize), false),
+                        mUiExecutor);
+            }
+        }
+
+        private void bindAppWidgets(List appWidgets) {
+            // Bind the widgets, one at a time
+            int count = appWidgets.size();
+            for (int i = 0; i < count; i++) {
+                final ItemInfo widget = appWidgets.get(i);
+                executeCallbacksTask(
+                        c -> c.bindItems(Collections.singletonList(widget), false),
+                        mUiExecutor);
+            }
+        }
     }
 }
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index 837a25659c..ee3fe5ec76 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -20,11 +20,6 @@ import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_GET_KEY_FIELDS_
 import static com.android.launcher3.BuildConfigs.QSB_ON_FIRST_SCREEN;
 import static com.android.launcher3.BuildConfigs.WIDGETS_ENABLED;
 import static com.android.launcher3.Flags.enableSmartspaceRemovalToggle;
-import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
-import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
-import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR;
-import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
-import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER;
 import static com.android.launcher3.Utilities.SHOULD_SHOW_FIRST_PAGE_WIDGET;
 import static com.android.launcher3.shortcuts.ShortcutRequest.PINNED;
 
@@ -36,6 +31,7 @@ import android.content.pm.LauncherApps;
 import android.content.pm.ShortcutInfo;
 import android.os.UserHandle;
 import android.text.TextUtils;
+import android.util.ArraySet;
 import android.util.Log;
 import android.util.Pair;
 import android.view.View;
@@ -43,16 +39,16 @@ import android.view.View;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
-import app.lawnchair.preferences2.PreferenceManager2;
-import com.android.launcher3.BuildConfig;
-import com.android.launcher3.BuildConfigs;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.dagger.LauncherAppSingleton;
-import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.AppPairInfo;
 import com.android.launcher3.model.data.CollectionInfo;
+import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.shortcuts.ShortcutKey;
@@ -67,13 +63,13 @@ import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.RunnableList;
 import com.android.launcher3.widget.model.WidgetsListBaseEntry;
 
-import com.patrykmichalik.opto.core.PreferenceExtensionsKt;
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -83,17 +79,11 @@ import java.util.function.Predicate;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
-import javax.inject.Inject;
-
 import app.lawnchair.LawnchairApp;
 
 /**
  * All the data stored in-memory and managed by the LauncherModel
- *
- * All the static data should be accessed on the background thread, A lock should be acquired on
- * this object when accessing any data from this model.
  */
-@LauncherAppSingleton
 public class BgDataModel {
 
     private static final String TAG = "BgDataModel";
@@ -104,6 +94,23 @@ public class BgDataModel {
      */
     public final IntSparseArrayMap itemsIdMap = new IntSparseArrayMap<>();
 
+    /**
+     * List of all the folders and shortcuts directly on the home screen (no widgets
+     * or shortcuts within folders).
+     */
+    public final ArrayList workspaceItems = new ArrayList<>();
+
+    /**
+     * All LauncherAppWidgetInfo created by LauncherModel.
+     */
+    public final ArrayList appWidgets = new ArrayList<>();
+
+    /**
+     * Map of id to CollectionInfos of all the folders or app pairs created by
+     * LauncherModel
+     */
+    public final IntSparseArrayMap collections = new IntSparseArrayMap<>();
+
     /**
      * Extra container based items
      */
@@ -117,7 +124,7 @@ public class BgDataModel {
     /**
      * Entire list of widgets.
      */
-    public final WidgetsModel widgetsModel;
+    public final WidgetsModel widgetsModel = new WidgetsModel();
 
     /**
      * Cache for strings used in launcher
@@ -136,34 +143,30 @@ public class BgDataModel {
     public boolean isFirstPagePinnedItemEnabled = QSB_ON_FIRST_SCREEN
             && !enableSmartspaceRemovalToggle();
 
-    @Inject
-    public BgDataModel(WidgetsModel widgetsModel) {
-        this.widgetsModel = widgetsModel;
-    }
-
     /**
      * Clears all the data
      */
     public synchronized void clear() {
+        workspaceItems.clear();
+        appWidgets.clear();
+        collections.clear();
         itemsIdMap.clear();
         deepShortcutMap.clear();
         extraItems.clear();
     }
 
     /**
-     * Creates an array of valid workspace screens based on current items in the model.
+     * Creates an array of valid workspace screens based on current items in the
+     * model.
      */
-    public synchronized IntArray collectWorkspaceScreens(Context context) {
+    public synchronized IntArray collectWorkspaceScreens() {
         IntSet screenSet = new IntSet();
-        for (ItemInfo item: itemsIdMap) {
-            if (item.container == CONTAINER_DESKTOP) {
+        for (ItemInfo item : itemsIdMap) {
+            if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
                 screenSet.add(item.screenId);
             }
         }
-
-        boolean smartspaceEnabled = PreferenceExtensionsKt.firstBlocking(
-            PreferenceManager2.INSTANCE.get(context).getEnableSmartspace());
-        if (smartspaceEnabled || screenSet.isEmpty()) {
+        if (FeatureFlags.topQsbOnFirstScreenEnabled(LawnchairApp.getInstance()) || screenSet.isEmpty()) {
             screenSet.add(Workspace.FIRST_SCREEN_ID);
         }
         return screenSet.getArray();
@@ -172,14 +175,26 @@ public class BgDataModel {
     public synchronized void dump(String prefix, FileDescriptor fd, PrintWriter writer,
             String[] args) {
         writer.println(prefix + "Data Model:");
-        writer.println(prefix + " ---- items id map ");
-        for (int i = 0; i < itemsIdMap.size(); i++) {
-            writer.println(prefix + '\t' + itemsIdMap.valueAt(i).toString());
+        writer.println(prefix + " ---- workspace items ");
+        for (int i = 0; i < workspaceItems.size(); i++) {
+            writer.println(prefix + '\t' + workspaceItems.get(i).toString());
+        }
+        writer.println(prefix + " ---- appwidget items ");
+        for (int i = 0; i < appWidgets.size(); i++) {
+            writer.println(prefix + '\t' + appWidgets.get(i).toString());
+        }
+        writer.println(prefix + " ---- collection items ");
+        for (int i = 0; i < collections.size(); i++) {
+            writer.println(prefix + '\t' + collections.valueAt(i).toString());
         }
         writer.println(prefix + " ---- extra items ");
         for (int i = 0; i < extraItems.size(); i++) {
             writer.println(prefix + '\t' + extraItems.valueAt(i).toString());
         }
+        writer.println(prefix + " ---- items id map ");
+        for (int i = 0; i < itemsIdMap.size(); i++) {
+            writer.println(prefix + '\t' + itemsIdMap.valueAt(i).toString());
+        }
 
         if (args.length > 0 && TextUtils.equals(args[0], "--all")) {
             writer.println(prefix + "shortcut counts ");
@@ -194,42 +209,100 @@ public class BgDataModel {
         removeItem(context, Arrays.asList(items));
     }
 
-    public synchronized void removeItem(Context context, List items) {
-        if (BuildConfigs.IS_STUDIO_BUILD) {
-            items.stream()
-                    .filter(item -> item.itemType == ITEM_TYPE_FOLDER
-                            || item.itemType == ITEM_TYPE_APP_PAIR)
-                    .forEach(item -> itemsIdMap.stream()
-                            .filter(info -> info.container == item.id)
-                            // We are deleting a collection which still contains items that
-                            // think they are contained by that collection.
-                            .forEach(info -> Log.e(TAG,
-                                    "deleting a collection (" + item + ") which still contains"
-                                            + " items (" + info + ")")));
+    public synchronized void removeItem(Context context, Iterable items) {
+        ArraySet updatedDeepShortcuts = new ArraySet<>();
+        for (ItemInfo item : items) {
+            switch (item.itemType) {
+                case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
+                case LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR:
+                    collections.remove(item.id);
+                    if (FeatureFlags.IS_STUDIO_BUILD) {
+                        for (ItemInfo info : itemsIdMap) {
+                            if (info.container == item.id) {
+                                // We are deleting a collection which still contains items that
+                                // think they are contained by that collection.
+                                String msg = "deleting a collection (" + item + ") which still "
+                                        + "contains items (" + info + ")";
+                                Log.e(TAG, msg);
+                            }
+                        }
+                    }
+                    workspaceItems.remove(item);
+                    break;
+                case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: {
+                    updatedDeepShortcuts.add(item.user);
+                    // Fall through.
+                }
+                case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
+                    workspaceItems.remove(item);
+                    break;
+                case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
+                case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
+                    appWidgets.remove(item);
+                    break;
+            }
+            itemsIdMap.remove(item.id);
         }
-
-        items.forEach(item -> itemsIdMap.remove(item.id));
-        items.stream().map(info -> info.user).distinct().forEach(
-                user -> updateShortcutPinnedState(context, user));
+        updatedDeepShortcuts.forEach(user -> updateShortcutPinnedState(context, user));
     }
 
     public synchronized void addItem(Context context, ItemInfo item, boolean newItem) {
-        itemsIdMap.put(item.id, item);
-        if (newItem && item.itemType == ITEM_TYPE_DEEP_SHORTCUT) {
-            updateShortcutPinnedState(context, item.user);
+        addItem(context, item, newItem, null);
+    }
+
+    public synchronized void addItem(
+            Context context, ItemInfo item, boolean newItem, @Nullable LoaderMemoryLogger logger) {
+        if (logger != null) {
+            logger.addLog(
+                    Log.DEBUG,
+                    TAG,
+                    String.format("Adding item to ID map: %s", item.toString()),
+                    /* stackTrace= */ null);
         }
-        if (BuildConfigs.IS_DEBUG_DEVICE
-                && newItem
-                && item.container != CONTAINER_DESKTOP
-                && item.container != CONTAINER_HOTSEAT
-                && !(itemsIdMap.get(item.container) instanceof CollectionInfo)) {
-            // Adding an item to a nonexistent collection.
-            Log.e(TAG, "attempted to add item: " + item + " to a nonexistent app collection");
+        itemsIdMap.put(item.id, item);
+        switch (item.itemType) {
+            case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
+                collections.put(item.id, (FolderInfo) item);
+                workspaceItems.add(item);
+                break;
+            case LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR:
+                collections.put(item.id, (AppPairInfo) item);
+                // Fall through here. App pairs are both containers (like folders) and
+                // containable
+                // items (can be placed in folders). So we need to add app pairs to the folders
+                // array (above) but also verify the existence of their container, like regular
+                // apps (below).
+            case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
+            case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
+                if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
+                        item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+                    workspaceItems.add(item);
+                } else {
+                    if (newItem) {
+                        if (!collections.containsKey(item.container)) {
+                            // Adding an item to a nonexistent collection.
+                            String msg = "attempted to add item: " + item + " to a nonexistent app"
+                                    + " collection";
+                            Log.e(TAG, msg);
+                        }
+                    } else {
+                        findOrMakeFolder(item.container).add(item);
+                    }
+                }
+                break;
+            case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
+            case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
+                appWidgets.add((LauncherAppWidgetInfo) item);
+                break;
+        }
+        if (newItem && item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
+            updateShortcutPinnedState(context, item.user);
         }
     }
 
     /**
-     * Updates the deep shortcuts state in system to match out internal model, pinning any missing
+     * Updates the deep shortucts state in system to match out internal model,
+     * pinning any missing
      * shortcuts and unpinning any extra shortcuts.
      */
     public void updateShortcutPinnedState(Context context) {
@@ -239,7 +312,8 @@ public class BgDataModel {
     }
 
     /**
-     * Updates the deep shortucts state in system to match out internal model, pinning any missing
+     * Updates the deep shortucts state in system to match out internal model,
+     * pinning any missing
      * shortcuts and unpinning any extra shortcuts.
      */
     public synchronized void updateShortcutPinnedState(Context context, UserHandle user) {
@@ -263,12 +337,12 @@ public class BgDataModel {
         forAllWorkspaceItemInfos(user, itemStream::accept);
         // Map of packageName to shortcutIds that are currently in our model
         Map> modelMap = Stream.concat(
-                    // Model shortcuts
-                    itemStream.build()
-                        .filter(wi -> wi.itemType == ITEM_TYPE_DEEP_SHORTCUT)
+                // Model shortcuts
+                itemStream.build()
+                        .filter(wi -> wi.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT)
                         .map(ShortcutKey::fromItemInfo),
-                    // Pending shortcuts
-                    ItemInstallQueue.INSTANCE.get(context).getPendingShortcuts(user))
+                // Pending shortcuts
+                ItemInstallQueue.INSTANCE.get(context).getPendingShortcuts(user))
                 .collect(groupingBy(ShortcutKey::getPackageName,
                         mapping(ShortcutKey::getId, Collectors.toSet())));
 
@@ -285,8 +359,6 @@ public class BgDataModel {
                     || !systemShortcuts.containsAll(modelShortcuts)) {
                 // Update system state for this package
                 try {
-                    FileLog.d(TAG, "updateShortcutPinnedState:"
-                            + " Pinning Shortcuts: " + entry.getKey() + ": " + modelShortcuts);
                     context.getSystemService(LauncherApps.class).pinShortcuts(
                             entry.getKey(), new ArrayList<>(modelShortcuts), user);
                 } catch (SecurityException | IllegalStateException e) {
@@ -299,9 +371,6 @@ public class BgDataModel {
         systemMap.keySet().forEach(packageName -> {
             // Update system state
             try {
-                FileLog.d(TAG, "updateShortcutPinnedState:"
-                        + " Unpinning extra Shortcuts for package: " + packageName
-                        + ": " + systemMap.get(packageName));
                 context.getSystemService(LauncherApps.class).pinShortcuts(
                         packageName, Collections.emptyList(), user);
             } catch (SecurityException | IllegalStateException e) {
@@ -311,7 +380,31 @@ public class BgDataModel {
     }
 
     /**
-     * Clear all the deep shortcut counts for the given package, and re-add the new shortcut counts.
+     * Return an existing FolderInfo object if we have encountered this ID
+     * previously,
+     * or make a new one.
+     */
+    public synchronized CollectionInfo findOrMakeFolder(int id) {
+        // See if a placeholder was created for us already
+        CollectionInfo collectionInfo = collections.get(id);
+        if (collectionInfo == null) {
+            // No placeholder -- create a new blank folder instance. At this point, we don't
+            // know
+            // if the desired container is supposed to be a folder or an app pair. In the
+            // case that
+            // it is an app pair, the blank folder will be replaced by a blank app pair when
+            // the app
+            // pair is getting processed, in
+            // WorkspaceItemProcessor.processFolderOrAppPair().
+            collectionInfo = new FolderInfo();
+            collections.put(id, collectionInfo);
+        }
+        return collectionInfo;
+    }
+
+    /**
+     * Clear all the deep shortcut counts for the given package, and re-add the new
+     * shortcut counts.
      */
     public synchronized void updateDeepShortcutCounts(
             String packageName, UserHandle user, List shortcuts) {
@@ -332,8 +425,7 @@ public class BgDataModel {
                     && (shortcut.isDeclaredInManifest() || shortcut.isDynamic())
                     && shortcut.getActivity() != null;
             if (shouldShowInContainer) {
-                ComponentKey targetComponent
-                        = new ComponentKey(shortcut.getActivity(), shortcut.getUserHandle());
+                ComponentKey targetComponent = new ComponentKey(shortcut.getActivity(), shortcut.getUserHandle());
 
                 Integer previousCount = deepShortcutMap.get(targetComponent);
                 deepShortcutMap.put(targetComponent, previousCount == null ? 1 : previousCount + 1);
@@ -342,9 +434,21 @@ public class BgDataModel {
     }
 
     /**
-     * Calls the provided {@code op} for all workspaceItems in the in-memory model (both persisted
+     * Returns a list containing all workspace items including widgets.
+     */
+    public synchronized ArrayList getAllWorkspaceItems() {
+        ArrayList items = new ArrayList<>(workspaceItems.size() + appWidgets.size());
+        items.addAll(workspaceItems);
+        items.addAll(appWidgets);
+        return items;
+    }
+
+    /**
+     * Calls the provided {@code op} for all workspaceItems in the in-memory model
+     * (both persisted
      * items and dynamic/predicted items for the provided {@code userHandle}.
-     * Note the call is not synchronized over the model, that should be handled by the called.
+     * Note the call is not synchronized over the model, that should be handled by
+     * the called.
      */
     public void forAllWorkspaceItemInfos(UserHandle userHandle, Consumer op) {
         for (ItemInfo info : itemsIdMap) {
@@ -390,7 +494,6 @@ public class BgDataModel {
 
     }
 
-
     public interface Callbacks {
         // If the launcher has permission to access deep shortcuts.
         int FLAG_HAS_SHORTCUT_PERMISSION = 1 << 0;
@@ -406,6 +509,7 @@ public class BgDataModel {
         /**
          * Returns an IntSet of page ids to bind first, synchronously if possible
          * or an empty IntSet
+         * 
          * @param orderedScreenIds All the page ids to be bound
          */
         @NonNull
@@ -413,45 +517,69 @@ public class BgDataModel {
             return new IntSet();
         }
 
-        default void clearPendingBinds() { }
-        default void startBinding() { }
+        default void clearPendingBinds() {
+        }
+
+        default void startBinding() {
+        }
 
         @Nullable
         default ItemInflater getItemInflater() {
             return null;
         }
 
-        default void bindItems(@NonNull List shortcuts, boolean forceAnimateIcons) { }
-        /** Alternate method to bind preinflated views */
-        default void bindInflatedItems(@NonNull List> items) { }
+        default void bindItems(@NonNull List shortcuts, boolean forceAnimateIcons) {
+        }
+
+        /** Alternate method to bind preinflated views */
+        default void bindInflatedItems(@NonNull List> items) {
+        }
+
+        default void bindScreens(IntArray orderedScreenIds) {
+        }
+
+        default void setIsFirstPagePinnedItemEnabled(boolean isFirstPagePinnedItemEnabled) {
+        }
+
+        default void finishBindingItems(IntSet pagesBoundFirst) {
+        }
+
+        default void preAddApps() {
+        }
 
-        default void bindScreens(IntArray orderedScreenIds) { }
-        default void setIsFirstPagePinnedItemEnabled(boolean isFirstPagePinnedItemEnabled) { }
-        default void finishBindingItems(IntSet pagesBoundFirst) { }
-        default void preAddApps() { }
         default void bindAppsAdded(IntArray newScreens,
-                ArrayList addNotAnimated, ArrayList addAnimated) { }
+                ArrayList addNotAnimated, ArrayList addAnimated) {
+        }
 
         /**
          * Called when some persistent property of an item is modified
          */
-        default void bindItemsModified(List items) { }
+        default void bindItemsModified(List items) {
+        }
 
         /**
          * Binds updated incremental download progress
          */
-        default void bindIncrementalDownloadProgressUpdated(AppInfo app) { }
+        default void bindIncrementalDownloadProgressUpdated(AppInfo app) {
+        }
 
-        /** Called when a runtime property of the ItemInfo is updated due to some system event */
-        default void bindItemsUpdated(Set updates) { }
-        default void bindWorkspaceComponentsRemoved(Predicate matcher) { }
+        default void bindWorkspaceItemsChanged(List updated) {
+        }
 
-        /**
-         * Binds the app widgets to the providers that share widgets with the UI.
-         */
-        default void bindAllWidgets(@NonNull List widgets) { }
+        default void bindWidgetsRestored(ArrayList widgets) {
+        }
 
-        default void bindSmartspaceWidget() { }
+        default void bindRestoreItemsChange(HashSet updates) {
+        }
+
+        default void bindWorkspaceComponentsRemoved(Predicate matcher) {
+        }
+
+        default void bindAllWidgets(List widgets) {
+        }
+
+        default void bindSmartspaceWidget() {
+        }
 
         /** Called when workspace has been bound. */
         default void onInitialBindComplete(@NonNull IntSet boundPages,
@@ -461,12 +589,14 @@ public class BgDataModel {
             pendingTasks.executeAllAndDestroy();
         }
 
-        default void bindDeepShortcutMap(HashMap deepShortcutMap) { }
+        default void bindDeepShortcutMap(HashMap deepShortcutMap) {
+        }
 
         /**
          * Binds extra item provided any external source
          */
-        default void bindExtraContainerItems(FixedContainerItems item) { }
+        default void bindExtraContainerItems(FixedContainerItems item) {
+        }
 
         default void bindAllApplications(AppInfo[] apps, int flags,
                 Map packageUserKeytoUidMap) {
@@ -475,6 +605,7 @@ public class BgDataModel {
         /**
          * Binds the cache of string resources
          */
-        default void bindStringCache(StringCache cache) { }
+        default void bindStringCache(StringCache cache) {
+        }
     }
 }
diff --git a/src/com/android/launcher3/model/CacheDataUpdatedTask.java b/src/com/android/launcher3/model/CacheDataUpdatedTask.java
index f740b49694..66b4fd9560 100644
--- a/src/com/android/launcher3/model/CacheDataUpdatedTask.java
+++ b/src/com/android/launcher3/model/CacheDataUpdatedTask.java
@@ -15,9 +15,6 @@
  */
 package com.android.launcher3.model;
 
-import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG;
-import static com.android.launcher3.model.ModelUtils.WIDGET_FILTER;
-
 import android.content.ComponentName;
 import android.os.UserHandle;
 
@@ -26,8 +23,6 @@ import androidx.annotation.NonNull;
 import com.android.launcher3.LauncherModel.ModelUpdateTask;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 
 import java.util.ArrayList;
@@ -59,8 +54,8 @@ public class CacheDataUpdatedTask implements ModelUpdateTask {
     @Override
     public void execute(@NonNull ModelTaskController taskController, @NonNull BgDataModel dataModel,
             @NonNull AllAppsList apps) {
-        IconCache iconCache = taskController.getIconCache();
-        ArrayList updatedItems = new ArrayList<>();
+        IconCache iconCache = taskController.getApp().getIconCache();
+        ArrayList updatedShortcuts = new ArrayList<>();
 
         synchronized (dataModel) {
             dataModel.forAllWorkspaceItemInfos(mUser, si -> {
@@ -68,26 +63,13 @@ public class CacheDataUpdatedTask implements ModelUpdateTask {
                 if (si.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
                         && isValidShortcut(si) && cn != null
                         && mPackages.contains(cn.getPackageName())) {
-                    iconCache.getTitleAndIcon(si, si.getMatchingLookupFlag());
-                    updatedItems.add(si);
+                    iconCache.getTitleAndIcon(si, si.usingLowResIcon());
+                    updatedShortcuts.add(si);
                 }
             });
-
-            dataModel.itemsIdMap.stream()
-                    .filter(WIDGET_FILTER)
-                    .filter(item -> mUser.equals(item.user))
-                    .map(item -> (LauncherAppWidgetInfo) item)
-                    .filter(widget -> mPackages.contains(widget.providerName.getPackageName())
-                            && widget.pendingItemInfo != null)
-                    .forEach(widget -> {
-                        iconCache.getTitleAndIconForApp(
-                                widget.pendingItemInfo, DEFAULT_LOOKUP_FLAG);
-                        updatedItems.add(widget);
-                    });
-
             apps.updateIconsAndLabels(mPackages, mUser);
         }
-        taskController.bindUpdatedWorkspaceItems(updatedItems);
+        taskController.bindUpdatedWorkspaceItems(updatedShortcuts);
         taskController.bindApplicationsIfNeeded();
     }
 
diff --git a/src/com/android/launcher3/model/DatabaseHelper.java b/src/com/android/launcher3/model/DatabaseHelper.java
index 050df5f56b..6842c74482 100644
--- a/src/com/android/launcher3/model/DatabaseHelper.java
+++ b/src/com/android/launcher3/model/DatabaseHelper.java
@@ -35,7 +35,6 @@ import android.util.Log;
 
 import androidx.annotation.NonNull;
 
-import app.lawnchair.preferences2.PreferenceManager2;
 import com.android.launcher3.AutoInstallsLayout;
 import com.android.launcher3.AutoInstallsLayout.LayoutParserCallback;
 import com.android.launcher3.LauncherSettings;
@@ -53,12 +52,10 @@ import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.widget.LauncherWidgetHolder;
 
-import com.patrykmichalik.opto.core.PreferenceExtensionsKt;
 import java.io.File;
 import java.net.URISyntaxException;
 import java.util.Arrays;
 import java.util.Locale;
-import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.ToLongFunction;
 import java.util.stream.Collectors;
 
@@ -84,8 +81,8 @@ public class DatabaseHelper extends NoLocaleSQLiteHelper implements
     private final Context mContext;
     private final ToLongFunction mUserSerialProvider;
     private final Runnable mOnEmptyDbCreateCallback;
-    private final AtomicInteger mMaxItemId = new AtomicInteger(-1);
 
+    private int mMaxItemId = -1;
     public boolean mHotseatRestoreTableExists;
 
     /**
@@ -103,7 +100,9 @@ public class DatabaseHelper extends NoLocaleSQLiteHelper implements
         // In the case where neither onCreate nor onUpgrade gets called, we read the
         // maxId from
         // the DB here
-        mMaxItemId.compareAndSet(-1, initializeMaxItemId(getWritableDatabase()));
+        if (mMaxItemId == -1) {
+            mMaxItemId = initializeMaxItemId(getWritableDatabase());
+        }
     }
 
     @Override
@@ -111,12 +110,12 @@ public class DatabaseHelper extends NoLocaleSQLiteHelper implements
         if (LOGD)
             Log.d(TAG, "creating new launcher database");
 
-        mMaxItemId.set(1);
+        mMaxItemId = 1;
 
         addTableToDb(db, getDefaultUserSerial(), false /* optional */);
 
         // Fresh and clean launcher DB.
-        mMaxItemId.set(initializeMaxItemId(db));
+        mMaxItemId = initializeMaxItemId(db);
         mOnEmptyDbCreateCallback.run();
     }
 
@@ -265,7 +264,7 @@ public class DatabaseHelper extends NoLocaleSQLiteHelper implements
                         Favorites.SCREEN, IntArray.wrap(-777, -778)), null);
             }
             case 30: {
-                if (PreferenceExtensionsKt.firstBlocking(PreferenceManager2.INSTANCE.get(mContext).getEnableSmartspace())) {
+                if (FeatureFlags.topQsbOnFirstScreenEnabled(mContext)) {
                     // Clean up first row in screen 0 as it might contain junk data.
                     Log.d(TAG, "Cleaning up first row");
                     db.delete(Favorites.TABLE_NAME,
@@ -462,10 +461,11 @@ public class DatabaseHelper extends NoLocaleSQLiteHelper implements
     // after that point
     @Override
     public int generateNewItemId() {
-        if (mMaxItemId.get() < 0) {
+        if (mMaxItemId < 0) {
             throw new RuntimeException("Error: max item id was not initialized");
         }
-        return mMaxItemId.incrementAndGet();
+        mMaxItemId += 1;
+        return mMaxItemId;
     }
 
     /**
@@ -494,7 +494,7 @@ public class DatabaseHelper extends NoLocaleSQLiteHelper implements
 
     public void checkId(ContentValues values) {
         int id = values.getAsInteger(Favorites._ID);
-        mMaxItemId.accumulateAndGet(id, Math::max);
+        mMaxItemId = Math.max(id, mMaxItemId);
     }
 
     private int initializeMaxItemId(SQLiteDatabase db) {
@@ -519,7 +519,7 @@ public class DatabaseHelper extends NoLocaleSQLiteHelper implements
         int count = loader.loadLayout(db);
 
         // Ensure that the max ids are initialized
-        mMaxItemId.set(initializeMaxItemId(db));
+        mMaxItemId = initializeMaxItemId(db);
         return count;
     }
 
diff --git a/src/com/android/launcher3/model/DeviceGridState.java b/src/com/android/launcher3/model/DeviceGridState.java
index 1caed28d22..6d6e00842d 100644
--- a/src/com/android/launcher3/model/DeviceGridState.java
+++ b/src/com/android/launcher3/model/DeviceGridState.java
@@ -17,25 +17,33 @@
 package com.android.launcher3.model;
 
 import static com.android.launcher3.InvariantDeviceProfile.DeviceType;
+import static com.android.launcher3.InvariantDeviceProfile.TYPE_PHONE;
 import static com.android.launcher3.LauncherPrefs.DB_FILE;
 import static com.android.launcher3.LauncherPrefs.DEVICE_TYPE;
-import static com.android.launcher3.LauncherPrefs.GRID_TYPE;
 import static com.android.launcher3.LauncherPrefs.HOTSEAT_COUNT;
 import static com.android.launcher3.LauncherPrefs.WORKSPACE_SIZE;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_GRID_SIZE_2;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_GRID_SIZE_3;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_GRID_SIZE_4;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_GRID_SIZE_5;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_GRID_SIZE_6;
 
 import android.annotation.SuppressLint;
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.text.TextUtils;
 
-import app.lawnchair.LawnchairProto;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherPrefs;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.logging.StatsLogManager.LauncherEvent;
+import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
 
 import java.util.Locale;
 import java.util.Objects;
 
+import app.lawnchair.LawnchairProto;
+
 /**
  * Utility class representing persisted grid properties.
  */
@@ -45,21 +53,17 @@ public class DeviceGridState implements Comparable {
     public static final String KEY_HOTSEAT_COUNT = "migration_src_hotseat_count";
     public static final String KEY_DEVICE_TYPE = "migration_src_device_type";
     public static final String KEY_DB_FILE = "migration_src_db_file";
-    public static final String KEY_GRID_TYPE = "migration_src_grid_type";
 
     private final String mGridSizeString;
     private final int mNumHotseat;
     private final @DeviceType int mDeviceType;
     private String mDbFile;
-    private int mGridType;
 
-    public DeviceGridState(int columns, int row, int numHotseat, int deviceType, String dbFile,
-            int gridType) {
+    public DeviceGridState(int columns, int row, int numHotseat, int deviceType, String dbFile) {
         mGridSizeString = String.format(Locale.ENGLISH, "%d,%d", columns, row);
         mNumHotseat = numHotseat;
         mDeviceType = deviceType;
         mDbFile = dbFile;
-        mGridType = gridType;
     }
 
     public DeviceGridState(InvariantDeviceProfile idp) {
@@ -67,10 +71,16 @@ public class DeviceGridState implements Comparable {
         mNumHotseat = idp.numDatabaseHotseatIcons;
         mDeviceType = idp.deviceType;
         mDbFile = idp.dbFile;
-        mGridType = idp.gridType;
     }
 
-    // Lawnchair@2038c6722c1ebd977290492881023675e1cddecd: Backup creation
+    public DeviceGridState(Context context) {
+        LauncherPrefs lp = LauncherPrefs.get(context);
+        mGridSizeString = lp.get(WORKSPACE_SIZE);
+        mNumHotseat = lp.get(HOTSEAT_COUNT);
+        mDeviceType = lp.get(DEVICE_TYPE);
+        mDbFile = lp.get(DB_FILE);
+    }
+
     @SuppressLint("WrongConstant")
     public DeviceGridState(LawnchairProto.GridState protoGridState) {
         mGridSizeString = protoGridState.getGridSize();
@@ -78,18 +88,6 @@ public class DeviceGridState implements Comparable {
         mDeviceType = protoGridState.getDeviceType();
     }
 
-    public DeviceGridState(Context context) {
-        this(LauncherPrefs.get(context));
-    }
-
-    public DeviceGridState(LauncherPrefs lp) {
-        mGridSizeString = lp.get(WORKSPACE_SIZE);
-        mNumHotseat = lp.get(HOTSEAT_COUNT);
-        mDeviceType = lp.get(DEVICE_TYPE);
-        mDbFile = lp.get(DB_FILE);
-        mGridType = lp.get(GRID_TYPE);
-    }
-
     /**
      * Returns the device type for the grid
      */
@@ -111,29 +109,23 @@ public class DeviceGridState implements Comparable {
         return mNumHotseat;
     }
 
-    /**
-     * Returns the grid type.
-     */
-    public int getGridType() {
-        return mGridType;
-    }
-
     /**
      * Stores the device state to shared preferences
      */
     public void writeToPrefs(Context context) {
+        if (context instanceof SandboxContext) {
+            return;
+        }
         LauncherPrefs.get(context).put(
                 WORKSPACE_SIZE.to(mGridSizeString),
                 HOTSEAT_COUNT.to(mNumHotseat),
                 DEVICE_TYPE.to(mDeviceType),
-                DB_FILE.to(mDbFile),
-                GRID_TYPE.to(mGridType));
-
+                DB_FILE.to(mDbFile));
     }
 
 
     public void writeToPrefs(Context context, boolean commit) {
-        SharedPreferences.Editor editor = LauncherPrefs.getPrefs(context).edit()
+        SharedPreferences.Editor editor = Utilities.getPrefs(context).edit()
                 .putString(KEY_WORKSPACE_SIZE, mGridSizeString)
                 .putInt(KEY_HOTSEAT_COUNT, mNumHotseat)
                 .putInt(KEY_DEVICE_TYPE, mDeviceType);
@@ -157,25 +149,17 @@ public class DeviceGridState implements Comparable {
      */
     public LauncherEvent getWorkspaceSizeEvent() {
         if (!TextUtils.isEmpty(mGridSizeString)) {
-            switch (mGridSizeString) {
-                case "2,2":
-                    return LauncherEvent.LAUNCHER_GRID_SIZE_2_BY_2;
-                case "3,3":
-                    return LauncherEvent.LAUNCHER_GRID_SIZE_3_BY_3;
-                case "4,4":
-                    return LauncherEvent.LAUNCHER_GRID_SIZE_4_BY_4;
-                case "4,5":
-                    return LauncherEvent.LAUNCHER_GRID_SIZE_4_BY_5;
-                case "4,6":
-                    return LauncherEvent.LAUNCHER_GRID_SIZE_4_BY_6;
-                case "4,7":
-                    return LauncherEvent.LAUNCHER_GRID_SIZE_4_BY_7;
-                case "5,5":
-                    return LauncherEvent.LAUNCHER_GRID_SIZE_5_BY_5;
-                case "5,6":
-                    return LauncherEvent.LAUNCHER_GRID_SIZE_5_BY_6;
-                case "6,5":
-                    return LauncherEvent.LAUNCHER_GRID_SIZE_6_BY_5;
+            switch (getColumns()) {
+                case 6:
+                    return LAUNCHER_GRID_SIZE_6;
+                case 5:
+                    return LAUNCHER_GRID_SIZE_5;
+                case 4:
+                    return LAUNCHER_GRID_SIZE_4;
+                case 3:
+                    return LAUNCHER_GRID_SIZE_3;
+                case 2:
+                    return LAUNCHER_GRID_SIZE_2;
             }
         }
         return null;
@@ -192,7 +176,8 @@ public class DeviceGridState implements Comparable {
     }
 
     /**
-     * Returns true if the database from another DeviceGridState can be loaded into the current
+     * Returns true if the database from another DeviceGridState can be loaded into
+     * the current
      * DeviceGridState without migration, or false otherwise.
      */
     public boolean isCompatible(DeviceGridState other) {
@@ -206,16 +191,10 @@ public class DeviceGridState implements Comparable {
     }
 
     public Integer getColumns() {
-        if (TextUtils.isEmpty(mGridSizeString)) {
-            return -1;
-        }
         return Integer.parseInt(String.valueOf(mGridSizeString.split(",")[0]));
     }
 
     public Integer getRows() {
-        if (TextUtils.isEmpty(mGridSizeString)) {
-            return -1;
-        }
         return Integer.parseInt(String.valueOf(mGridSizeString.split(",")[1]));
     }
 
diff --git a/src/com/android/launcher3/model/FirstScreenBroadcast.java b/src/com/android/launcher3/model/FirstScreenBroadcast.java
index 829e0fe08b..f443f8142e 100644
--- a/src/com/android/launcher3/model/FirstScreenBroadcast.java
+++ b/src/com/android/launcher3/model/FirstScreenBroadcast.java
@@ -45,9 +45,9 @@ import com.android.launcher3.util.PackageUserKey;
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
 
@@ -76,9 +76,9 @@ public class FirstScreenBroadcast {
 
     private static final String VERIFICATION_TOKEN_EXTRA = "verificationToken";
 
-    private final Map mSessionInfoForPackage;
+    private final HashMap mSessionInfoForPackage;
 
-    public FirstScreenBroadcast(Map sessionInfoForPackage) {
+    public FirstScreenBroadcast(HashMap sessionInfoForPackage) {
         mSessionInfoForPackage = sessionInfoForPackage;
     }
 
diff --git a/src/com/android/launcher3/model/FirstScreenBroadcastHelper.kt b/src/com/android/launcher3/model/FirstScreenBroadcastHelper.kt
index 6ad52ea92b..aa62c32e33 100644
--- a/src/com/android/launcher3/model/FirstScreenBroadcastHelper.kt
+++ b/src/com/android/launcher3/model/FirstScreenBroadcastHelper.kt
@@ -30,6 +30,7 @@ import com.android.launcher3.model.data.CollectionInfo
 import com.android.launcher3.model.data.ItemInfo
 import com.android.launcher3.model.data.LauncherAppWidgetInfo
 import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.pm.InstallSessionHelper
 import com.android.launcher3.util.Executors
 import com.android.launcher3.util.PackageManagerHelper
 import com.android.launcher3.util.PackageUserKey
@@ -79,22 +80,21 @@ object FirstScreenBroadcastHelper {
         packageManagerHelper: PackageManagerHelper,
         firstScreenItems: List,
         userKeyToSessionMap: Map,
-        allWidgets: List,
+        allWidgets: List
     ): List {
 
         // installers for installing items
-        val pendingItemInstallerMap: Map> =
+        val pendingItemInstallerMap: Map> =
             createPendingItemsMap(userKeyToSessionMap)
-
         val installingPackages = pendingItemInstallerMap.values.flatten().toSet()
 
         // installers for installed items on first screen
-        val installedItemInstallerMap: Map> =
+        val installedItemInstallerMap: Map> =
             createInstalledItemsMap(firstScreenItems, installingPackages, packageManagerHelper)
 
         // installers for widgets on all screens
-        val allInstalledWidgetsMap: Map> =
-            createInstalledItemsMap(allWidgets, installingPackages, packageManagerHelper)
+        val allInstalledWidgetsMap: Map> =
+            createAllInstalledWidgetsMap(allWidgets, installingPackages, packageManagerHelper)
 
         val allInstallers: Set =
             pendingItemInstallerMap.keys +
@@ -131,39 +131,39 @@ object FirstScreenBroadcastHelper {
                             context,
                             0 /* requestCode */,
                             Intent(),
-                            PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE,
-                        ),
+                            PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE
+                        )
                     )
                     .putStringArrayListExtra(
                         PENDING_COLLECTION_ITEM_EXTRA,
-                        ArrayList(model.pendingCollectionItems),
+                        ArrayList(model.pendingCollectionItems)
                     )
                     .putStringArrayListExtra(
                         PENDING_WORKSPACE_ITEM_EXTRA,
-                        ArrayList(model.pendingWorkspaceItems),
+                        ArrayList(model.pendingWorkspaceItems)
                     )
                     .putStringArrayListExtra(
                         PENDING_HOTSEAT_ITEM_EXTRA,
-                        ArrayList(model.pendingHotseatItems),
+                        ArrayList(model.pendingHotseatItems)
                     )
                     .putStringArrayListExtra(
                         PENDING_WIDGET_ITEM_EXTRA,
-                        ArrayList(model.pendingWidgetItems),
+                        ArrayList(model.pendingWidgetItems)
                     )
                     .putStringArrayListExtra(
                         INSTALLED_WORKSPACE_ITEMS_EXTRA,
-                        ArrayList(model.installedWorkspaceItems),
+                        ArrayList(model.installedWorkspaceItems)
                     )
                     .putStringArrayListExtra(
                         INSTALLED_HOTSEAT_ITEMS_EXTRA,
-                        ArrayList(model.installedHotseatItems),
+                        ArrayList(model.installedHotseatItems)
                     )
                     .putStringArrayListExtra(
                         ALL_INSTALLED_WIDGETS_ITEM_EXTRA,
                         ArrayList(
                             model.firstScreenInstalledWidgets +
                                 model.secondaryScreenInstalledWidgets
-                        ),
+                        )
                     )
             context.sendBroadcast(intent)
         }
@@ -172,46 +172,66 @@ object FirstScreenBroadcastHelper {
     /** Maps Installer packages to Set of app packages from install sessions */
     private fun createPendingItemsMap(
         userKeyToSessionMap: Map
-    ): Map> {
+    ): Map> {
         val myUser = Process.myUserHandle()
-        return userKeyToSessionMap.values
-            .filter {
-                it.user == myUser &&
-                    !it.installerPackageName.isNullOrEmpty() &&
-                    !it.appPackageName.isNullOrEmpty()
-            }
-            .groupBy(
-                keySelector = { it.installerPackageName },
-                valueTransform = { it.appPackageName },
-            )
-            .mapValues { it.value.filterNotNull().toSet() } as Map>
+        val result = mutableMapOf>()
+        userKeyToSessionMap.forEach { entry ->
+            if (!myUser.equals(InstallSessionHelper.getUserHandle(entry.value))) return@forEach
+            val installer = entry.value.installerPackageName
+            val appPackage = entry.value.appPackageName
+            if (installer.isNullOrEmpty() || appPackage.isNullOrEmpty()) return@forEach
+            result.getOrPut(installer) { mutableSetOf() }.add(appPackage)
+        }
+        return result
     }
 
-    /** Maps Installer packages to Set of ItemInfos. Filter out installing packages. */
+    /**
+     * Maps Installer packages to Set of ItemInfo from first screen. Filter out installing packages.
+     */
     private fun createInstalledItemsMap(
-        allItems: Iterable,
+        firstScreenItems: List,
         installingPackages: Set,
-        packageManagerHelper: PackageManagerHelper,
-    ): Map> =
-        allItems
-            .sortedBy { it.screenId }
-            .groupByTo(mutableMapOf()) {
-                getPackageName(it)?.let { pkg ->
-                    if (installingPackages.contains(pkg)) {
-                        null
-                    } else {
-                        packageManagerHelper.getAppInstallerPackage(pkg)
-                    }
-                }
+        packageManagerHelper: PackageManagerHelper
+    ): Map> {
+        val result = mutableMapOf>()
+        firstScreenItems.forEach { item ->
+            val appPackage = getPackageName(item) ?: return@forEach
+            if (installingPackages.contains(appPackage)) return@forEach
+            val installer = packageManagerHelper.getAppInstallerPackage(appPackage)
+            if (installer.isNullOrEmpty()) return@forEach
+            result.getOrPut(installer) { mutableSetOf() }.add(item)
+        }
+        return result
+    }
+
+    /**
+     * Maps Installer packages to Set of AppWidget packages installed on all screens. Filter out
+     * installing packages.
+     */
+    private fun createAllInstalledWidgetsMap(
+        allWidgets: List,
+        installingPackages: Set,
+        packageManagerHelper: PackageManagerHelper
+    ): Map> {
+        val result = mutableMapOf>()
+        allWidgets
+            .sortedBy { widget -> widget.screenId }
+            .forEach { widget ->
+                val appPackage = getPackageName(widget) ?: return@forEach
+                if (installingPackages.contains(appPackage)) return@forEach
+                val installer = packageManagerHelper.getAppInstallerPackage(appPackage)
+                if (installer.isNullOrEmpty()) return@forEach
+                result.getOrPut(installer) { mutableSetOf() }.add(widget)
             }
-            .apply { remove(null) } as Map>
+        return result
+    }
 
     /**
      * Add first screen Pending Items from Map to [FirstScreenBroadcastModel] for given installer
      */
     private fun FirstScreenBroadcastModel.addPendingItems(
         installingItems: Set?,
-        firstScreenItems: List,
+        firstScreenItems: List
     ) {
         if (installingItems == null) return
         for (info in firstScreenItems) {
@@ -231,7 +251,7 @@ object FirstScreenBroadcastHelper {
      */
     private fun FirstScreenBroadcastModel.addInstalledItems(
         installer: String,
-        installedItemInstallerMap: Map>,
+        installedItemInstallerMap: Map>,
     ) {
         installedItemInstallerMap[installer]?.forEach { info ->
             val packageName: String = getPackageName(info) ?: return@forEach
@@ -245,7 +265,7 @@ object FirstScreenBroadcastHelper {
     /** Add Widgets on every screen from Map to [FirstScreenBroadcastModel] for given installer */
     private fun FirstScreenBroadcastModel.addAllScreenWidgets(
         installer: String,
-        allInstalledWidgetsMap: Map>,
+        allInstalledWidgetsMap: Map>
     ) {
         allInstalledWidgetsMap[installer]?.forEach { widget ->
             val packageName: String = getPackageName(widget) ?: return@forEach
@@ -259,7 +279,7 @@ object FirstScreenBroadcastHelper {
 
     private fun FirstScreenBroadcastModel.addCollectionItems(
         info: ItemInfo,
-        installingPackages: Set,
+        installingPackages: Set
     ) {
         if (info !is CollectionInfo) return
         pendingCollectionItems.addAll(
@@ -316,7 +336,7 @@ object FirstScreenBroadcastHelper {
             Log.d(
                 TAG,
                 "Sending First Screen Broadcast for installer=$installerPackage" +
-                    ", total packages=${getTotalItemCount()}",
+                    ", total packages=${getTotalItemCount()}"
             )
             pendingCollectionItems.forEach {
                 Log.d(TAG, "$installerPackage:Pending Collection item:$it")
@@ -341,7 +361,15 @@ object FirstScreenBroadcastHelper {
         }
     }
 
-    private fun getPackageName(info: ItemInfo): String? = info.targetComponent?.packageName
+    private fun getPackageName(info: ItemInfo): String? {
+        var packageName: String? = null
+        if (info is LauncherAppWidgetInfo) {
+            info.providerName?.let { packageName = info.providerName.packageName }
+        } else if (info.targetComponent != null) {
+            packageName = info.targetComponent?.packageName
+        }
+        return packageName
+    }
 
     /**
      * Clone the provided list on UI thread. This is used for [FolderInfo.getContents] which is
diff --git a/src/com/android/launcher3/model/GridSizeMigrationUtil.java b/src/com/android/launcher3/model/GridSizeMigrationUtil.java
new file mode 100644
index 0000000000..d23b1ef64b
--- /dev/null
+++ b/src/com/android/launcher3/model/GridSizeMigrationUtil.java
@@ -0,0 +1,815 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.model;
+
+import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
+import static com.android.launcher3.LauncherSettings.Favorites.TMP_TABLE;
+import static com.android.launcher3.provider.LauncherDbUtils.copyTable;
+import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
+
+import android.content.ComponentName;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.database.sqlite.SQLiteDatabase;
+import android.graphics.Point;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.launcher3.Flags;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.pm.InstallSessionHelper;
+import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
+import com.android.launcher3.util.ContentWriter;
+import com.android.launcher3.util.GridOccupancy;
+import com.android.launcher3.util.IntArray;
+import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.widget.WidgetManagerHelper;
+
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * This class takes care of shrinking the workspace (by maximum of one row and
+ * one column), as a
+ * result of restoring from a larger device or device density change.
+ */
+public class GridSizeMigrationUtil {
+
+    private static final String TAG = "GridSizeMigrationUtil";
+    private static final boolean DEBUG = true;
+
+    private GridSizeMigrationUtil() {
+        // Util class should not be instantiated
+    }
+
+    /**
+     * Check given a new IDP, if migration is necessary.
+     */
+    public static boolean needsToMigrate(Context context, InvariantDeviceProfile idp) {
+        return needsToMigrate(new DeviceGridState(context), new DeviceGridState(idp));
+    }
+
+    private static boolean needsToMigrate(
+            DeviceGridState srcDeviceState, DeviceGridState destDeviceState) {
+        boolean needsToMigrate = !destDeviceState.isCompatible(srcDeviceState);
+        if (needsToMigrate) {
+            Log.i(TAG, "Migration is needed. destDeviceState: " + destDeviceState
+                    + ", srcDeviceState: " + srcDeviceState);
+        }
+        return needsToMigrate;
+    }
+
+    @VisibleForTesting
+    public static List readAllEntries(SQLiteDatabase db, String tableName,
+            Context context) {
+        DbReader dbReader = new DbReader(db, tableName, context, getValidPackages(context));
+        List result = dbReader.loadAllWorkspaceEntries();
+        result.addAll(dbReader.loadHotseatEntries());
+        return result;
+    }
+
+    /**
+     * When migrating the grid, we copy the table
+     * {@link LauncherSettings.Favorites#TABLE_NAME} from {@code source} into
+     * {@link LauncherSettings.Favorites#TMP_TABLE}, run the grid size migration
+     * algorithm
+     * to migrate the later to the former, and load the workspace from the default
+     * {@link LauncherSettings.Favorites#TABLE_NAME}.
+     *
+     * @return false if the migration failed.
+     */
+    public static boolean migrateGridIfNeeded(
+            @NonNull Context context,
+            @NonNull DeviceGridState srcDeviceState,
+            @NonNull DeviceGridState destDeviceState,
+            @NonNull DatabaseHelper target,
+            @NonNull SQLiteDatabase source) {
+        if (!needsToMigrate(srcDeviceState, destDeviceState)) {
+            return true;
+        }
+
+        if (Flags.enableGridMigrationFix()
+                && srcDeviceState.getColumns().equals(destDeviceState.getColumns())
+                && srcDeviceState.getRows() < destDeviceState.getRows()) {
+            // Only use this strategy when comparing the previous grid to the new grid and
+            // the
+            // columns are the same and the destination has more rows
+            copyTable(source, TABLE_NAME, target.getWritableDatabase(), TABLE_NAME, context);
+            destDeviceState.writeToPrefs(context);
+            return true;
+        }
+        copyTable(source, TABLE_NAME, target.getWritableDatabase(), TMP_TABLE, context);
+
+        HashSet validPackages = getValidPackages(context);
+        long migrationStartTime = System.currentTimeMillis();
+        try (SQLiteTransaction t = new SQLiteTransaction(target.getWritableDatabase())) {
+            DbReader srcReader = new DbReader(t.getDb(), TMP_TABLE, context, validPackages);
+            DbReader destReader = new DbReader(t.getDb(), TABLE_NAME, context, validPackages);
+
+            Point targetSize = new Point(destDeviceState.getColumns(), destDeviceState.getRows());
+            migrate(target, srcReader, destReader, destDeviceState.getNumHotseat(),
+                    targetSize, srcDeviceState, destDeviceState);
+            dropTable(t.getDb(), TMP_TABLE);
+            t.commit();
+            return true;
+        } catch (Exception e) {
+            Log.e(TAG, "Error during grid migration", e);
+
+            return false;
+        } finally {
+            Log.v(TAG, "Workspace migration completed in "
+                    + (System.currentTimeMillis() - migrationStartTime));
+
+            // Save current configuration, so that the migration does not run again.
+            destDeviceState.writeToPrefs(context);
+        }
+    }
+
+    public static boolean migrate(
+            @NonNull DatabaseHelper helper,
+            @NonNull final DbReader srcReader, @NonNull final DbReader destReader,
+            final int destHotseatSize, @NonNull final Point targetSize,
+            @NonNull final DeviceGridState srcDeviceState,
+            @NonNull final DeviceGridState destDeviceState) {
+
+        srcReader.loadAllWorkspaceEntries();
+
+        // Handle workspace migration
+        final List workspaceToBeAdded = new ArrayList<>();
+        final IntArray toBeRemoved = new IntArray();
+
+        // Process each screen separately
+        for (int screenId : srcReader.mWorkspaceEntriesByScreenId.keySet()) {
+            List srcScreenItems = srcReader.mWorkspaceEntriesByScreenId.get(screenId);
+            List destScreenItems = destReader.mWorkspaceEntriesByScreenId.get(screenId);
+
+            if (destScreenItems == null) {
+                destScreenItems = new ArrayList<>();
+            }
+
+            List screenItemsToAdd = new ArrayList<>();
+            IntArray screenItemsToRemove = new IntArray();
+
+            calcDiff(srcScreenItems, destScreenItems, screenItemsToAdd, screenItemsToRemove);
+
+            workspaceToBeAdded.addAll(screenItemsToAdd);
+            toBeRemoved.addAll(screenItemsToRemove);
+
+            solveGridPlacement(helper, srcReader, destReader, screenId,
+                    targetSize.x, targetSize.y, screenItemsToAdd,
+                    srcDeviceState.getColumns(), srcDeviceState.getRows());
+        }
+
+        // Remove entries that are no longer needed
+        if (!toBeRemoved.isEmpty()) {
+            removeEntryFromDb(helper.getWritableDatabase(), destReader.mTableName, toBeRemoved);
+        }
+
+        // Handle hotseat migration
+        List srcHotseatEntries = srcReader.loadHotseatEntries();
+        List destHotseatEntries = destReader.loadHotseatEntries();
+        final ArrayList hotseatToBeAdded = new ArrayList<>();
+        final IntArray hotseatToBeRemoved = new IntArray();
+
+        calcDiff(srcHotseatEntries, destHotseatEntries, hotseatToBeAdded, hotseatToBeRemoved);
+
+        if (!hotseatToBeRemoved.isEmpty()) {
+            removeEntryFromDb(helper.getWritableDatabase(), destReader.mTableName, hotseatToBeRemoved);
+        }
+
+        // Migrate hotseat
+        solveHotseatPlacement(helper, destHotseatSize,
+                srcReader, destReader, destHotseatEntries, hotseatToBeAdded);
+
+        return workspaceToBeAdded.isEmpty() && hotseatToBeAdded.isEmpty();
+    }
+
+    /**
+     * Calculate the differences between {@code src} (denoted by A) and {@code dest}
+     * (denoted by B).
+     * All DbEntry in A - B will be added to {@code toBeAdded}
+     * All DbEntry.id in B - A will be added to {@code toBeRemoved}
+     */
+    private static void calcDiff(@NonNull final List src,
+            @NonNull final List dest, @NonNull final List toBeAdded,
+            @NonNull final IntArray toBeRemoved) {
+        src.forEach(entry -> {
+            if (!dest.contains(entry)) {
+                toBeAdded.add(entry);
+            }
+        });
+        dest.forEach(entry -> {
+            if (!src.contains(entry)) {
+                toBeRemoved.add(entry.id);
+                if (entry.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
+                    entry.mFolderItems.values().forEach(ids -> ids.forEach(toBeRemoved::add));
+                }
+            }
+        });
+    }
+
+    private static void insertEntryInDb(DatabaseHelper helper, DbEntry entry,
+            String srcTableName, String destTableName) {
+        int id = copyEntryAndUpdate(helper, entry, srcTableName, destTableName);
+
+        if (entry.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER
+                || entry.itemType == LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR) {
+            for (Set itemIds : entry.mFolderItems.values()) {
+                for (int itemId : itemIds) {
+                    copyEntryAndUpdate(helper, itemId, id, srcTableName, destTableName);
+                }
+            }
+        }
+    }
+
+    private static int copyEntryAndUpdate(DatabaseHelper helper,
+            DbEntry entry, String srcTableName, String destTableName) {
+        return copyEntryAndUpdate(helper, entry, -1, -1, srcTableName, destTableName);
+    }
+
+    private static int copyEntryAndUpdate(DatabaseHelper helper,
+            int id, int folderId, String srcTableName, String destTableName) {
+        return copyEntryAndUpdate(helper, null, id, folderId, srcTableName, destTableName);
+    }
+
+    private static int copyEntryAndUpdate(DatabaseHelper helper, DbEntry entry,
+            int id, int folderId, String srcTableName, String destTableName) {
+        int newId = -1;
+        Cursor c = helper.getWritableDatabase().query(srcTableName, null,
+                LauncherSettings.Favorites._ID + " = '" + (entry != null ? entry.id : id) + "'",
+                null, null, null, null);
+        while (c.moveToNext()) {
+            ContentValues values = new ContentValues();
+            DatabaseUtils.cursorRowToContentValues(c, values);
+            if (entry != null) {
+                entry.updateContentValues(values);
+            } else {
+                values.put(LauncherSettings.Favorites.CONTAINER, folderId);
+            }
+            newId = helper.generateNewItemId();
+            values.put(LauncherSettings.Favorites._ID, newId);
+            helper.getWritableDatabase().insert(destTableName, null, values);
+        }
+        c.close();
+        return newId;
+    }
+
+    private static void removeEntryFromDb(SQLiteDatabase db, String tableName, IntArray entryIds) {
+        db.delete(tableName,
+                Utilities.createDbSelectionQuery(LauncherSettings.Favorites._ID, entryIds), null);
+    }
+
+    private static HashSet getValidPackages(Context context) {
+        // Initialize list of valid packages. This contain all the packages which are
+        // already on
+        // the device and packages which are being installed. Any item which doesn't
+        // belong to
+        // this set is removed.
+        // Since the loader removes such items anyway, removing these items here doesn't
+        // cause
+        // any extra data loss and gives us more free space on the grid for better
+        // migration.
+        HashSet validPackages = new HashSet<>();
+        for (PackageInfo info : context.getPackageManager()
+                .getInstalledPackages(PackageManager.GET_UNINSTALLED_PACKAGES)) {
+            validPackages.add(info.packageName);
+        }
+        InstallSessionHelper.INSTANCE.get(context)
+                .getActiveSessions().keySet()
+                .forEach(packageUserKey -> validPackages.add(packageUserKey.mPackageName));
+        return validPackages;
+    }
+
+    private static void solveGridPlacement(@NonNull final DatabaseHelper helper,
+                                           @NonNull final DbReader srcReader, @NonNull final DbReader destReader,
+                                           final int screenId, final int trgX, final int trgY,
+                                           @NonNull final List sortedItemsToPlace,
+                                           final int oldGridWidth, final int oldGridHeight) {
+
+        final GridOccupancy occupied = new GridOccupancy(trgX, trgY);
+        final Point trg = new Point(trgX, trgY);
+        final Point next = new Point(0, screenId == 0 && FeatureFlags.topQsbOnFirstScreenEnabled(srcReader.mContext)
+                ? 1
+                /* smartspace */ : 0);
+        List existedEntries = destReader.mWorkspaceEntriesByScreenId.get(screenId);
+        if (existedEntries != null) {
+            for (DbEntry entry : existedEntries) {
+                occupied.markCells(entry, true);
+            }
+        }
+        Iterator iterator = sortedItemsToPlace.iterator();
+        while (iterator.hasNext()) {
+            final DbEntry entry = iterator.next();
+            if (entry.minSpanX > trgX || entry.minSpanY > trgY) {
+                iterator.remove();
+                continue;
+            }
+            if (findPlacementForEntry(entry, next, trg, occupied, screenId, oldGridWidth, oldGridHeight)) {
+                insertEntryInDb(helper, entry, srcReader.mTableName, destReader.mTableName);
+                iterator.remove();
+            }
+        }
+    }
+
+    /**
+     * Search for the next possible placement of an icon. (mNextStartX, mNextStartY)
+     * serves as
+     * a memoization of last placement, we can start our search for next placement
+     * from there
+     * to speed up the search.
+     */
+    private static boolean findPlacementForEntry(@NonNull final DbEntry entry,
+                                                 @NonNull final Point next, @NonNull final Point trg,
+                                                 @NonNull final GridOccupancy occupied, final int screenId,
+                                                 final int oldGridWidth, final int oldGridHeight) {
+
+        // Calculate the new position maintaining relative distances
+        int newX = calculateNewPosition(entry.cellX, oldGridWidth, trg.x);
+        int newY = calculateNewPosition(entry.cellY, oldGridHeight, trg.y);
+
+        // Try to place the item at the calculated position first
+        if (tryPlacement(entry, newX, newY, occupied, screenId)) {
+            next.set(newX + entry.spanX, newY);
+            return true;
+        }
+
+        // If the target position is occupied, search nearby
+        for (int dy = -1; dy <= 1; dy++) {
+            for (int dx = -1; dx <= 1; dx++) {
+                if (dx == 0 && dy == 0) continue; // Skip the already tried position
+
+                int x = newX + dx;
+                int y = newY + dy;
+
+                if (x >= 0 && x < trg.x && y >= 0 && y < trg.y) {
+                    if (tryPlacement(entry, x, y, occupied, screenId)) {
+                        next.set(x + entry.spanX, y);
+                        return true;
+                    }
+                }
+            }
+        }
+
+        // If still not placed, search the entire grid
+        for (int y = 0; y < trg.y; y++) {
+            for (int x = 0; x < trg.x; x++) {
+                if (tryPlacement(entry, x, y, occupied, screenId)) {
+                    next.set(x + entry.spanX, y);
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    private static int calculateNewPosition(int oldPos, int oldGridSize, int newGridSize) {
+        if (oldPos == 0) return 0; // Keep items at the start in place
+        if (oldPos == oldGridSize - 1) return newGridSize - 1; // Keep items at the end in place
+
+        // Calculate the relative position and map it to the new grid size
+        float relativePos = (float) oldPos / (oldGridSize - 1);
+        return Math.round(relativePos * (newGridSize - 1));
+    }
+
+    private static boolean tryPlacement(DbEntry entry, int x, int y, GridOccupancy occupied, int screenId) {
+        boolean fits = occupied.isRegionVacant(x, y, entry.spanX, entry.spanY);
+        boolean minFits = occupied.isRegionVacant(x, y, entry.minSpanX, entry.minSpanY);
+
+        if (fits || minFits) {
+            entry.screenId = screenId;
+            entry.cellX = x;
+            entry.cellY = y;
+
+            if (minFits && !fits) {
+                entry.spanX = entry.minSpanX;
+                entry.spanY = entry.minSpanY;
+            }
+
+            occupied.markCells(entry, true);
+            return true;
+        }
+
+        return false;
+    }
+
+    private static void solveHotseatPlacement(
+            @NonNull final DatabaseHelper helper, final int hotseatSize,
+            @NonNull final DbReader srcReader, @NonNull final DbReader destReader,
+            @NonNull final List placedHotseatItems,
+            @NonNull final List itemsToPlace) {
+
+        final boolean[] occupied = new boolean[hotseatSize];
+        for (DbEntry entry : placedHotseatItems) {
+            occupied[entry.screenId] = true;
+        }
+
+        for (int i = 0; i < occupied.length; i++) {
+            if (!occupied[i] && !itemsToPlace.isEmpty()) {
+                DbEntry entry = itemsToPlace.remove(0);
+                entry.screenId = i;
+                // These values does not affect the item position, but we should set them
+                // to something other than -1.
+                entry.cellX = i;
+                entry.cellY = 0;
+                insertEntryInDb(helper, entry, srcReader.mTableName, destReader.mTableName);
+                occupied[entry.screenId] = true;
+            }
+        }
+    }
+
+    @VisibleForTesting
+    public static class DbReader {
+
+        private final SQLiteDatabase mDb;
+        private final String mTableName;
+        private final Context mContext;
+        private final Set mValidPackages;
+        private int mLastScreenId = -1;
+
+        private final Map> mWorkspaceEntriesByScreenId = new ArrayMap<>();
+
+        public DbReader(SQLiteDatabase db, String tableName, Context context,
+                Set validPackages) {
+            mDb = db;
+            mTableName = tableName;
+            mContext = context;
+            mValidPackages = validPackages;
+        }
+
+        protected List loadHotseatEntries() {
+            final List hotseatEntries = new ArrayList<>();
+            Cursor c = queryWorkspace(
+                    new String[] {
+                            LauncherSettings.Favorites._ID, // 0
+                            LauncherSettings.Favorites.ITEM_TYPE, // 1
+                            LauncherSettings.Favorites.INTENT, // 2
+                            LauncherSettings.Favorites.SCREEN }, // 3
+                    LauncherSettings.Favorites.CONTAINER + " = "
+                            + LauncherSettings.Favorites.CONTAINER_HOTSEAT);
+
+            final int indexId = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
+            final int indexItemType = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
+            final int indexIntent = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
+            final int indexScreen = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
+
+            IntArray entriesToRemove = new IntArray();
+            while (c.moveToNext()) {
+                DbEntry entry = new DbEntry();
+                entry.id = c.getInt(indexId);
+                entry.itemType = c.getInt(indexItemType);
+                entry.screenId = c.getInt(indexScreen);
+
+                try {
+                    // calculate weight
+                    switch (entry.itemType) {
+                        case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
+                        case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: {
+                            entry.mIntent = c.getString(indexIntent);
+                            verifyIntent(c.getString(indexIntent));
+                            break;
+                        }
+                        case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: {
+                            int total = getFolderItemsCount(entry);
+                            if (total == 0) {
+                                throw new Exception("Folder is empty");
+                            }
+                            break;
+                        }
+                        case LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR: {
+                            int total = getFolderItemsCount(entry);
+                            if (total != 2) {
+                                throw new Exception("App pair contains fewer or more than 2 items");
+                            }
+                            break;
+                        }
+                        default:
+                            throw new Exception("Invalid item type");
+                    }
+                } catch (Exception e) {
+                    if (DEBUG) {
+                        Log.d(TAG, "Removing item " + entry.id, e);
+                    }
+                    entriesToRemove.add(entry.id);
+                    continue;
+                }
+                hotseatEntries.add(entry);
+            }
+            removeEntryFromDb(mDb, mTableName, entriesToRemove);
+            c.close();
+            return hotseatEntries;
+        }
+
+        protected List loadAllWorkspaceEntries() {
+            final List workspaceEntries = new ArrayList<>();
+            Cursor c = queryWorkspace(
+                    new String[] {
+                            LauncherSettings.Favorites._ID, // 0
+                            LauncherSettings.Favorites.ITEM_TYPE, // 1
+                            LauncherSettings.Favorites.SCREEN, // 2
+                            LauncherSettings.Favorites.CELLX, // 3
+                            LauncherSettings.Favorites.CELLY, // 4
+                            LauncherSettings.Favorites.SPANX, // 5
+                            LauncherSettings.Favorites.SPANY, // 6
+                            LauncherSettings.Favorites.INTENT, // 7
+                            LauncherSettings.Favorites.APPWIDGET_PROVIDER, // 8
+                            LauncherSettings.Favorites.APPWIDGET_ID }, // 9
+                    LauncherSettings.Favorites.CONTAINER + " = "
+                            + LauncherSettings.Favorites.CONTAINER_DESKTOP);
+            final int indexId = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
+            final int indexItemType = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
+            final int indexScreen = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
+            final int indexCellX = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
+            final int indexCellY = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
+            final int indexSpanX = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANX);
+            final int indexSpanY = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANY);
+            final int indexIntent = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
+            final int indexAppWidgetProvider = c.getColumnIndexOrThrow(
+                    LauncherSettings.Favorites.APPWIDGET_PROVIDER);
+            final int indexAppWidgetId = c.getColumnIndexOrThrow(
+                    LauncherSettings.Favorites.APPWIDGET_ID);
+
+            IntArray entriesToRemove = new IntArray();
+            WidgetManagerHelper widgetManagerHelper = new WidgetManagerHelper(mContext);
+            while (c.moveToNext()) {
+                DbEntry entry = new DbEntry();
+                entry.id = c.getInt(indexId);
+                entry.itemType = c.getInt(indexItemType);
+                entry.screenId = c.getInt(indexScreen);
+                mLastScreenId = Math.max(mLastScreenId, entry.screenId);
+                entry.cellX = c.getInt(indexCellX);
+                entry.cellY = c.getInt(indexCellY);
+                entry.spanX = c.getInt(indexSpanX);
+                entry.spanY = c.getInt(indexSpanY);
+
+                try {
+                    // calculate weight
+                    switch (entry.itemType) {
+                        case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
+                        case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: {
+                            entry.mIntent = c.getString(indexIntent);
+                            verifyIntent(entry.mIntent);
+                            break;
+                        }
+                        case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: {
+                            entry.mProvider = c.getString(indexAppWidgetProvider);
+                            ComponentName cn = ComponentName.unflattenFromString(entry.mProvider);
+                            verifyPackage(cn.getPackageName());
+
+                            int widgetId = c.getInt(indexAppWidgetId);
+                            LauncherAppWidgetProviderInfo pInfo = widgetManagerHelper
+                                    .getLauncherAppWidgetInfo(widgetId, cn);
+                            Point spans = null;
+                            if (pInfo != null) {
+                                spans = pInfo.getMinSpans();
+                            }
+                            if (spans != null) {
+                                entry.minSpanX = spans.x > 0 ? spans.x : entry.spanX;
+                                entry.minSpanY = spans.y > 0 ? spans.y : entry.spanY;
+                            } else {
+                                // Assume that the widget be resized down to 2x2
+                                entry.minSpanX = entry.minSpanY = 2;
+                            }
+
+                            break;
+                        }
+                        case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: {
+                            int total = getFolderItemsCount(entry);
+                            if (total == 0) {
+                                throw new Exception("Folder is empty");
+                            }
+                            break;
+                        }
+                        case LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR: {
+                            int total = getFolderItemsCount(entry);
+                            if (total != 2) {
+                                throw new Exception("App pair contains fewer or more than 2 items");
+                            }
+                            break;
+                        }
+                        default:
+                            throw new Exception("Invalid item type");
+                    }
+                } catch (Exception e) {
+                    if (DEBUG) {
+                        Log.d(TAG, "Removing item " + entry.id, e);
+                    }
+                    entriesToRemove.add(entry.id);
+                    continue;
+                }
+                workspaceEntries.add(entry);
+                if (!mWorkspaceEntriesByScreenId.containsKey(entry.screenId)) {
+                    mWorkspaceEntriesByScreenId.put(entry.screenId, new ArrayList<>());
+                }
+                mWorkspaceEntriesByScreenId.get(entry.screenId).add(entry);
+            }
+            removeEntryFromDb(mDb, mTableName, entriesToRemove);
+            c.close();
+            return workspaceEntries;
+        }
+
+        private int getFolderItemsCount(DbEntry entry) {
+            Cursor c = queryWorkspace(
+                    new String[] { LauncherSettings.Favorites._ID, LauncherSettings.Favorites.INTENT },
+                    LauncherSettings.Favorites.CONTAINER + " = " + entry.id);
+
+            int total = 0;
+            while (c.moveToNext()) {
+                try {
+                    int id = c.getInt(0);
+                    String intent = c.getString(1);
+                    verifyIntent(intent);
+                    total++;
+                    if (!entry.mFolderItems.containsKey(intent)) {
+                        entry.mFolderItems.put(intent, new HashSet<>());
+                    }
+                    entry.mFolderItems.get(intent).add(id);
+                } catch (Exception e) {
+                    removeEntryFromDb(mDb, mTableName, IntArray.wrap(c.getInt(0)));
+                }
+            }
+            c.close();
+            return total;
+        }
+
+        private Cursor queryWorkspace(String[] columns, String where) {
+            return mDb.query(mTableName, columns, where, null, null, null, null);
+        }
+
+        /** Verifies if the mIntent should be restored. */
+        private void verifyIntent(String intentStr)
+                throws Exception {
+            Intent intent = Intent.parseUri(intentStr, 0);
+            if (intent.getComponent() != null) {
+                verifyPackage(intent.getComponent().getPackageName());
+            } else if (intent.getPackage() != null) {
+                // Only verify package if the component was null.
+                verifyPackage(intent.getPackage());
+            }
+        }
+
+        /** Verifies if the package should be restored */
+        private void verifyPackage(String packageName)
+                throws Exception {
+            if (!mValidPackages.contains(packageName)) {
+                // TODO(b/151468819): Handle promise app icon restoration during grid migration.
+                throw new Exception("Package not available");
+            }
+        }
+    }
+
+    public static class DbEntry extends ItemInfo implements Comparable {
+
+        private String mIntent;
+        private String mProvider;
+        private Map> mFolderItems = new HashMap<>();
+
+        /**
+         * Id of the specific widget.
+         */
+        public int appWidgetId = NO_ID;
+
+        /** Comparator according to the reading order */
+        @Override
+        public int compareTo(DbEntry another) {
+            if (screenId != another.screenId) {
+                return Integer.compare(screenId, another.screenId);
+            }
+            if (cellY != another.cellY) {
+                return Integer.compare(cellY, another.cellY);
+            }
+            return Integer.compare(cellX, another.cellX);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o)
+                return true;
+            if (o == null || getClass() != o.getClass())
+                return false;
+            DbEntry entry = (DbEntry) o;
+            return Objects.equals(getEntryMigrationId(), entry.getEntryMigrationId());
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(getEntryMigrationId());
+        }
+
+        public void updateContentValues(ContentValues values) {
+            values.put(LauncherSettings.Favorites.SCREEN, screenId);
+            values.put(LauncherSettings.Favorites.CELLX, cellX);
+            values.put(LauncherSettings.Favorites.CELLY, cellY);
+            values.put(LauncherSettings.Favorites.SPANX, spanX);
+            values.put(LauncherSettings.Favorites.SPANY, spanY);
+        }
+
+        @Override
+        public void writeToValues(@NonNull ContentWriter writer) {
+            super.writeToValues(writer);
+            writer.put(LauncherSettings.Favorites.APPWIDGET_ID, appWidgetId);
+        }
+
+        @Override
+        public void readFromValues(@NonNull ContentValues values) {
+            super.readFromValues(values);
+            appWidgetId = values.getAsInteger(LauncherSettings.Favorites.APPWIDGET_ID);
+        }
+
+        /**
+         * This id is not used in the DB is only used while doing the migration and it
+         * identifies
+         * an entry on each workspace. For example two calculator icons would have the
+         * same
+         * migration id even thought they have different database ids.
+         */
+        public String getEntryMigrationId() {
+            switch (itemType) {
+                case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
+                case LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR:
+                    return getFolderMigrationId();
+                case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
+                    // mProvider is the app the widget belongs to and appWidgetId it's the unique
+                    // is of the widget, we need both because if you remove a widget and then add it
+                    // again, then it can change and the WidgetProvider would not know the widget.
+                    return mProvider + appWidgetId;
+                case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
+                    final String intentStr = cleanIntentString(mIntent);
+                    try {
+                        Intent i = Intent.parseUri(intentStr, 0);
+                        return Objects.requireNonNull(i.getComponent()).toString();
+                    } catch (Exception e) {
+                        return intentStr;
+                    }
+                default:
+                    return cleanIntentString(mIntent);
+            }
+        }
+
+        /**
+         * This method should return an id that should be the same for two folders
+         * containing the
+         * same elements.
+         */
+        @NonNull
+        private String getFolderMigrationId() {
+            return mFolderItems.keySet().stream()
+                    .map(intentString -> mFolderItems.get(intentString).size()
+                            + cleanIntentString(intentString))
+                    .sorted()
+                    .collect(Collectors.joining(","));
+        }
+
+        /**
+         * This is needed because sourceBounds can change and make the id of two equal
+         * items
+         * different.
+         */
+        @NonNull
+        private String cleanIntentString(@NonNull String intentStr) {
+            try {
+                Intent i = Intent.parseUri(intentStr, 0);
+                i.setSourceBounds(null);
+                return i.toURI();
+            } catch (URISyntaxException e) {
+                Log.e(TAG, "Unable to parse Intent string", e);
+                return intentStr;
+            }
+
+        }
+    }
+}
diff --git a/src/com/android/launcher3/model/ItemInstallQueue.java b/src/com/android/launcher3/model/ItemInstallQueue.java
index 467a524775..6dc38d5aa5 100644
--- a/src/com/android/launcher3/model/ItemInstallQueue.java
+++ b/src/com/android/launcher3/model/ItemInstallQueue.java
@@ -18,7 +18,6 @@ package com.android.launcher3.model;
 
 import static android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID;
 
-import static com.android.launcher3.LauncherSettings.Favorites.DESKTOP_ICON_FLAG;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
@@ -46,18 +45,16 @@ import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings.Favorites;
-import com.android.launcher3.dagger.ApplicationContext;
-import com.android.launcher3.dagger.LauncherAppSingleton;
-import com.android.launcher3.dagger.LauncherBaseAppComponent;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.shortcuts.ShortcutRequest;
-import com.android.launcher3.util.DaggerSingletonObject;
+import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.PersistedItemArray;
 import com.android.launcher3.util.Preconditions;
+import com.android.launcher3.util.SafeCloseable;
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
 
 import java.util.HashSet;
@@ -65,13 +62,10 @@ import java.util.List;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
-import javax.inject.Inject;
-
 /**
  * Class to maintain a queue of pending items to be added to the workspace.
  */
-@LauncherAppSingleton
-public class ItemInstallQueue {
+public class ItemInstallQueue implements SafeCloseable {
 
     private static final String LOG = "ItemInstallQueue";
 
@@ -87,8 +81,9 @@ public class ItemInstallQueue {
     public static final int NEW_SHORTCUT_BOUNCE_DURATION = 450;
     public static final int NEW_SHORTCUT_STAGGER_DELAY = 85;
 
-    public static DaggerSingletonObject INSTANCE =
-            new DaggerSingletonObject<>(LauncherBaseAppComponent::getItemInstallQueue);
+    public static MainThreadInitializedObject INSTANCE =
+            new MainThreadInitializedObject<>(ItemInstallQueue::new);
+
     private final PersistedItemArray mStorage =
             new PersistedItemArray<>(APPS_PENDING_INSTALL);
     private final Context mContext;
@@ -100,11 +95,13 @@ public class ItemInstallQueue {
     // Only accessed on worker thread
     private List mItems;
 
-    @Inject
-    public ItemInstallQueue(@ApplicationContext Context context) {
+    private ItemInstallQueue(Context context) {
         mContext = context;
     }
 
+    @Override
+    public void close() {}
+
     @WorkerThread
     private void ensureQueueLoaded() {
         Preconditions.assertWorkerThread();
@@ -124,7 +121,7 @@ public class ItemInstallQueue {
 
     @WorkerThread
     private void flushQueueInBackground() {
-        Launcher launcher = Launcher.ACTIVITY_TRACKER.getCreatedContext();
+        Launcher launcher = Launcher.ACTIVITY_TRACKER.getCreatedActivity();
         if (launcher == null) {
             // Launcher not loaded
             return;
@@ -195,18 +192,22 @@ public class ItemInstallQueue {
     }
 
     private void queuePendingShortcutInfo(PendingInstallShortcutInfo info) {
+        final Exception stackTrace = new Exception();
 
         // Queue the item up for adding if launcher has not loaded properly yet
         MODEL_EXECUTOR.post(() -> {
             Pair itemInfo = info.getItemInfo(mContext);
             if (itemInfo == null) {
                 FileLog.d(LOG,
-                        "Adding PendingInstallShortcutInfo with no attached info to queue.");
+                        "Adding PendingInstallShortcutInfo with no attached info to queue.",
+                        stackTrace);
             } else {
                 FileLog.d(LOG,
-                        "Adding PendingInstallShortcutInfo to queue."
-                                + " Attached info: " + itemInfo.first);
+                        "Adding PendingInstallShortcutInfo to queue. Attached info: "
+                                + itemInfo.first,
+                        stackTrace);
             }
+
             addToQueue(info);
         });
         flushInstallQueue();
@@ -313,8 +314,7 @@ public class ItemInstallQueue {
                         }
                     }
                     LauncherAppState.getInstance(context).getIconCache()
-                            .getTitleAndIcon(si, () -> lai,
-                                    DESKTOP_ICON_FLAG.withUsePackageIcon(usePackageIcon));
+                            .getTitleAndIcon(si, () -> lai, usePackageIcon, false);
                     return Pair.create(si, null);
                 }
                 case ITEM_TYPE_DEEP_SHORTCUT: {
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index 4ad4d2a8e0..79dd80c959 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -16,14 +16,8 @@
 
 package com.android.launcher3.model;
 
-import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
-import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
-import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
-import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR;
-import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
+import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
 import static com.android.launcher3.Utilities.SHOULD_SHOW_FIRST_PAGE_WIDGET;
-import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG;
-import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_ARCHIVED;
 
 import android.content.ComponentName;
 import android.content.ContentValues;
@@ -43,22 +37,17 @@ import android.util.LongSparseArray;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
-import com.android.launcher3.Flags;
 import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.LauncherModel;
-import com.android.launcher3.LauncherPrefs;
+import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.backuprestore.LauncherRestoreEventLogger;
 import com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RestoreError;
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.dagger.ApplicationContext;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.model.data.AppInfo;
-import com.android.launcher3.model.data.CollectionInfo;
-import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.IconRequestInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -69,17 +58,13 @@ import com.android.launcher3.util.ContentWriter;
 import com.android.launcher3.util.GridOccupancy;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.IntSparseArrayMap;
+import com.patrykmichalik.opto.core.PreferenceExtensionsKt;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.UserIconInfo;
 
-import dagger.assisted.Assisted;
-import dagger.assisted.AssistedFactory;
-import dagger.assisted.AssistedInject;
-
 import java.net.URISyntaxException;
 import java.security.InvalidParameterException;
 
-import com.patrykmichalik.opto.core.PreferenceExtensionsKt;
 import app.lawnchair.LawnchairApp;
 import app.lawnchair.preferences2.PreferenceManager2;
 
@@ -92,8 +77,8 @@ public class LoaderCursor extends CursorWrapper {
 
     private final LongSparseArray allUsers;
 
+    private final LauncherAppState mApp;
     private final Context mContext;
-    private final LauncherModel mModel;
     private final PackageManagerHelper mPmHelper;
     private final IconCache mIconCache;
     private final InvariantDeviceProfile mIDP;
@@ -103,11 +88,6 @@ public class LoaderCursor extends CursorWrapper {
     private final IntArray mRestoredRows = new IntArray();
     private final IntSparseArrayMap mOccupied = new IntSparseArrayMap<>();
 
-    // CollectionInfo objects, which have not yet been loaded from the DB, but are expected to
-    // found eventually as the loading progresses
-    private final IntSparseArrayMap mPendingCollectionInfo =
-            new IntSparseArrayMap<>();
-
     private final int mIconIndex;
     public final int mTitleIndex;
 
@@ -143,24 +123,17 @@ public class LoaderCursor extends CursorWrapper {
     public int restoreFlag;
     private PreferenceManager2 preferenceManager2;
 
-    @AssistedInject
-    public LoaderCursor(
-            @Assisted Cursor cursor,
-            @Assisted UserManagerState userManagerState,
-            @Assisted @Nullable LauncherRestoreEventLogger restoreEventLogger,
-            @ApplicationContext Context context,
-            IconCache iconCache,
-            InvariantDeviceProfile idp,
-            LauncherModel model,
-            PackageManagerHelper pmHelper) {
+    public LoaderCursor(Cursor cursor, LauncherAppState app, UserManagerState userManagerState,
+            PackageManagerHelper pmHelper,
+            @Nullable LauncherRestoreEventLogger restoreEventLogger) {
         super(cursor);
-        mContext = context;
-        mIconCache = iconCache;
-        mIDP = idp;
-        mModel = model;
-        mPmHelper = pmHelper;
 
+        mApp = app;
         allUsers = userManagerState.allUsers;
+        mContext = app.getContext();
+        mIconCache = app.getIconCache();
+        mPmHelper = pmHelper;
+        mIDP = app.getInvariantDeviceProfile();
         mRestoreEventLogger = restoreEventLogger;
 
         preferenceManager2 = PreferenceManager2.getInstance(mContext);
@@ -209,8 +182,7 @@ public class LoaderCursor extends CursorWrapper {
     public Intent parseIntent() {
         String intentDescription = getString(mIntentIndex);
         try {
-            return TextUtils.isEmpty(intentDescription) ?
-                    null : Intent.parseUri(intentDescription, 0);
+            return TextUtils.isEmpty(intentDescription) ? null : Intent.parseUri(intentDescription, 0);
         } catch (URISyntaxException e) {
             Log.e(TAG, "Error parsing Intent");
             return null;
@@ -226,7 +198,7 @@ public class LoaderCursor extends CursorWrapper {
         info.itemType = itemType;
         info.title = getTitle();
         // the fallback icon
-        if (!loadIconFromDb(info)) {
+        if (!loadIcon(info)) {
             info.bitmap = mIconCache.getDefaultIcon(info.user);
         }
 
@@ -236,17 +208,19 @@ public class LoaderCursor extends CursorWrapper {
     }
 
     /**
-     * Loads the icon from the cursor and updates the {@param info} if the icon is an app resource.
+     * Loads the icon from the cursor and updates the {@param info} if the icon is
+     * an app resource.
      */
-    protected boolean loadIconFromDb(WorkspaceItemInfo info) {
-        return createIconRequestInfo(info, false).loadIconFromDbBlob(mContext);
+    protected boolean loadIcon(WorkspaceItemInfo info) {
+        return createIconRequestInfo(info, false).loadWorkspaceIcon(mContext);
     }
 
     public IconRequestInfo createIconRequestInfo(
             WorkspaceItemInfo wai, boolean useLowResIcon) {
         byte[] iconBlob = itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT || restoreFlag != 0
-                || (wai.isInactiveArchive() && Flags.restoreArchivedAppIconsFromDb())
-                ? getIconBlob() : null;
+                ? getIconBlob()
+                : null;
+
         return new IconRequestInfo<>(wai, mActivityInfo, iconBlob, useLowResIcon);
     }
 
@@ -328,21 +302,21 @@ public class LoaderCursor extends CursorWrapper {
     }
 
     /**
-     * Make an WorkspaceItemInfo object for a restored application or shortcut item that points
+     * Make an WorkspaceItemInfo object for a restored application or shortcut item
+     * that points
      * to a package that is not yet installed on the system.
      */
-    public WorkspaceItemInfo getRestoredItemInfo(Intent intent, boolean isArchived) {
+    public WorkspaceItemInfo getRestoredItemInfo(Intent intent) {
         final WorkspaceItemInfo info = new WorkspaceItemInfo();
         info.user = user;
         info.intent = intent;
 
         // the fallback icon
-        if (!loadIconFromDb(info)) {
-            FileLog.d(TAG, "loadIconFromDb failed, getting from cache - intent=" + intent);
-            mIconCache.getTitleAndIcon(info, DEFAULT_LOOKUP_FLAG);
+        if (!loadIcon(info)) {
+            mIconCache.getTitleAndIcon(info, false /* useLowResIcon */);
         }
 
-        if (hasRestoreFlag(WorkspaceItemInfo.FLAG_RESTORED_ICON) || isArchived) {
+        if (hasRestoreFlag(WorkspaceItemInfo.FLAG_RESTORED_ICON)) {
             String title = getTitle();
             if (!TextUtils.isEmpty(title)) {
                 info.title = Utilities.trim(title);
@@ -358,7 +332,6 @@ public class LoaderCursor extends CursorWrapper {
         info.contentDescription = mPM.getUserBadgedLabel(info.title, info.user);
         info.itemType = itemType;
         info.status = restoreFlag;
-        if (isArchived) info.runtimeStatusFlags |= FLAG_ARCHIVED;
         return info;
     }
 
@@ -402,11 +375,19 @@ public class LoaderCursor extends CursorWrapper {
         info.intent = newIntent;
         UserCache userCache = UserCache.getInstance(mContext);
         UserIconInfo userIconInfo = userCache.getUserInfo(user);
+
+        if (loadIcon) {
+            mIconCache.getTitleAndIcon(info, mActivityInfo, useLowResIcon);
+            if (mIconCache.isDefaultIcon(info.bitmap, user)) {
+                loadIcon(info);
+            }
+        }
+
         if (mActivityInfo != null) {
             AppInfo.updateRuntimeFlagsForActivityTarget(info, mActivityInfo, userIconInfo,
                     ApiWrapper.INSTANCE.get(mContext), mPmHelper);
         }
-        loadWorkspaceTitleAndIcon(useLowResIcon, loadIcon, info);
+
         // from the db
         if (TextUtils.isEmpty(info.title)) {
             if (loadIcon) {
@@ -425,40 +406,13 @@ public class LoaderCursor extends CursorWrapper {
         return info;
     }
 
-    @VisibleForTesting
-    void loadWorkspaceTitleAndIcon(
-            boolean useLowResIcon,
-            boolean loadIconFromCache,
-            WorkspaceItemInfo info
-    ) {
-        boolean isPreArchived = Flags.enableSupportForArchiving()
-                && Flags.restoreArchivedAppIconsFromDb()
-                && info.isInactiveArchive()
-                && LauncherPrefs.get(mContext).get(LauncherPrefs.IS_FIRST_LOAD_AFTER_RESTORE);
-        boolean preArchivedIconNotFound = isPreArchived && !loadIconFromDb(info);
-        if (preArchivedIconNotFound) {
-            Log.d(TAG, "loadIconFromDb failed for pre-archived icon, loading from cache."
-                    + " Component=" + info.getTargetComponent());
-            mIconCache.getTitleAndIcon(info, mActivityInfo,
-                    DEFAULT_LOOKUP_FLAG.withUseLowRes(useLowResIcon));
-        } else if (loadIconFromCache && !info.isInactiveArchive()) {
-            mIconCache.getTitleAndIcon(info, mActivityInfo,
-                    DEFAULT_LOOKUP_FLAG.withUseLowRes(useLowResIcon));
-            if (mIconCache.isDefaultIcon(info.bitmap, user)) {
-                Log.d(TAG, "Default Icon found in cache, trying DB instead. "
-                        + " Component=" + info.getTargetComponent());
-                loadIconFromDb(info);
-            }
-        }
-    }
-
     /**
      * Returns a {@link ContentWriter} which can be used to update the current item.
      */
     public ContentWriter updater() {
-       return new ContentWriter(mContext, new ContentWriter.CommitParams(
-               mModel.getModelDbController(),
-               BaseColumns._ID + "= ?", new String[]{Integer.toString(id)}));
+        return new ContentWriter(mContext, new ContentWriter.CommitParams(
+                mApp.getModel().getModelDbController(),
+                BaseColumns._ID + "= ?", new String[] { Integer.toString(id) }));
     }
 
     /**
@@ -474,12 +428,13 @@ public class LoaderCursor extends CursorWrapper {
 
     /**
      * Removes any items marked for removal.
+     * 
      * @return true is any item was removed.
      */
     public boolean commitDeleted() {
         if (mItemsToRemove.size() > 0) {
             // Remove dead items
-            mModel.getModelDbController().delete(
+            mApp.getModel().getModelDbController().delete(TABLE_NAME,
                     Utilities.createDbSelectionQuery(Favorites._ID, mItemsToRemove), null);
             return true;
         }
@@ -505,7 +460,7 @@ public class LoaderCursor extends CursorWrapper {
             // Update restored items that no longer require special handling
             ContentValues values = new ContentValues();
             values.put(Favorites.RESTORED, 0);
-            mModel.getModelDbController().update(values,
+            mApp.getModel().getModelDbController().update(TABLE_NAME, values,
                     Utilities.createDbSelectionQuery(Favorites._ID, mRestoredRows), null);
         }
         if (mRestoreEventLogger != null) {
@@ -536,30 +491,13 @@ public class LoaderCursor extends CursorWrapper {
         info.cellY = getInt(mCellYIndex);
     }
 
-    /**
-     * Return an existing FolderInfo object if we have encountered this ID previously,
-     * or make a new one.
-     */
-    public CollectionInfo findOrMakeFolder(int id, BgDataModel dataModel) {
-        // See if a placeholder was created for us already
-        ItemInfo info = dataModel.itemsIdMap.get(id);
-        if (info instanceof CollectionInfo c) return c;
-
-        CollectionInfo pending = mPendingCollectionInfo.get(id);
-        if (pending != null) return pending;
-
-        // No placeholder -- create a new blank folder instance. At this point, we don't know
-        // if the desired container is supposed to be a folder or an app pair. In the case that
-        // it is an app pair, the blank folder will be replaced by a blank app pair when the app
-        // pair is getting processed, in WorkspaceItemProcessor.processFolderOrAppPair().
-        pending = new FolderInfo();
-        pending.id = id;
-        mPendingCollectionInfo.put(id, pending);
-        return pending;
+    public void checkAndAddItem(ItemInfo info, BgDataModel dataModel) {
+        checkAndAddItem(info, dataModel, null);
     }
 
     /**
-     * Adds the {@param info} to {@param dataModel} if it does not overlap with any other item,
+     * Adds the {@param info} to {@param dataModel} if it does not overlap with any
+     * other item,
      * otherwise marks it for deletion.
      */
     public void checkAndAddItem(
@@ -570,37 +508,23 @@ public class LoaderCursor extends CursorWrapper {
             ShortcutKey.fromItemInfo(info);
         }
         if (checkItemPlacement(info, dataModel.isFirstPagePinnedItemEnabled)) {
-            if (logger != null) {
-                logger.addLog(
-                        Log.DEBUG,
-                        TAG,
-                        String.format("Adding item to ID map: %s", info),
-                        /* stackTrace= */ null);
-            }
-            dataModel.addItem(mContext, info, false);
-            if ((info.itemType == ITEM_TYPE_APP_PAIR
-                    || info.itemType == ITEM_TYPE_DEEP_SHORTCUT
-                    || info.itemType == ITEM_TYPE_APPLICATION)
-                    && info.container != CONTAINER_DESKTOP
-                    && info.container != CONTAINER_HOTSEAT) {
-                findOrMakeFolder(info.container, dataModel).add(info);
-            }
+            dataModel.addItem(mContext, info, false, logger);
             if (mRestoreEventLogger != null) {
                 mRestoreEventLogger.logSingleFavoritesItemRestored(itemType);
             }
         } else {
-            markDeleted("Item position overlap", RestoreError.OVERLAPPING_ITEM);
+            markDeleted("Item position overlap", RestoreError.INVALID_LOCATION);
         }
     }
 
     /**
-     * check & update map of what's occupied; used to discard overlapping/invalid items
+     * check & update map of what's occupied; used to discard overlapping/invalid
+     * items
      */
     protected boolean checkItemPlacement(ItemInfo item, boolean isFirstPagePinnedItemEnabled) {
         int containerIndex = item.screenId;
         if (item.container == Favorites.CONTAINER_HOTSEAT) {
-            final GridOccupancy hotseatOccupancy =
-                    mOccupied.get(Favorites.CONTAINER_HOTSEAT);
+            final GridOccupancy hotseatOccupancy = mOccupied.get(Favorites.CONTAINER_HOTSEAT);
 
             if (item.screenId >= mIDP.numDatabaseHotseatIcons) {
                 Log.e(TAG, "Error loading shortcut " + item
@@ -644,8 +568,9 @@ public class LoaderCursor extends CursorWrapper {
 
         if (!mOccupied.containsKey(item.screenId)) {
             GridOccupancy screen = new GridOccupancy(countX + 1, countY + 1);
-            if (item.screenId == Workspace.FIRST_SCREEN_ID && PreferenceExtensionsKt.firstBlocking(preferenceManager2.getEnableSmartspace())) {
-                // Mark the first X columns (X is width of the search container) in the first row as
+            if (item.screenId == Workspace.FIRST_SCREEN_ID && FeatureFlags.topQsbOnFirstScreenEnabled(mContext)) {
+                // Mark the first X columns (X is width of the search container) in the first
+                // row as
                 // occupied (if the feature is enabled) in order to account for the search
                 // container.
                 int spanX = mIDP.numSearchContainerColumns;
@@ -668,12 +593,4 @@ public class LoaderCursor extends CursorWrapper {
             return PreferenceExtensionsKt.firstBlocking(preferenceManager2.getAllowWidgetOverlap());
         }
     }
-
-    @AssistedFactory
-    public interface LoaderCursorFactory {
-
-        LoaderCursor createLoaderCursor(Cursor cursor,
-                UserManagerState userManagerState,
-                @Nullable LauncherRestoreEventLogger restoreEventLogger);
-    }
 }
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 970e05317f..ab85ae62be 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -22,20 +22,16 @@ import static com.android.launcher3.Flags.enableSmartspaceAsAWidget;
 import static com.android.launcher3.Flags.enableSmartspaceRemovalToggle;
 import static com.android.launcher3.LauncherPrefs.IS_FIRST_LOAD_AFTER_RESTORE;
 import static com.android.launcher3.LauncherPrefs.SHOULD_SHOW_SMARTSPACE;
-import static com.android.launcher3.LauncherSettings.Favorites.DESKTOP_ICON_FLAG;
-import static com.android.launcher3.icons.CacheableShortcutInfo.convertShortcutsToCacheableShortcuts;
-import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG;
+import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
 import static com.android.launcher3.folder.FolderGridOrganizer.createFolderGridOrganizer;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_HAS_SHORTCUT_PERMISSION;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_CHANGE_PERMISSION;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_WORK_PROFILE_QUIET_MODE_ENABLED;
-import static com.android.launcher3.model.ModelUtils.WIDGET_FILTER;
-import static com.android.launcher3.model.ModelUtils.currentScreenContentFilter;
+import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
 import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
-import static com.android.launcher3.util.LooperExecutor.CALLER_LOADER_TASK;
 import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission;
 
 import android.appwidget.AppWidgetProviderInfo;
@@ -53,6 +49,7 @@ import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
+import android.util.ArrayMap;
 import android.util.Log;
 import android.util.LongSparseArray;
 
@@ -62,29 +59,27 @@ import androidx.annotation.VisibleForTesting;
 import androidx.annotation.WorkerThread;
 
 import com.android.launcher3.Flags;
-import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherModel;
 import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.backuprestore.LauncherRestoreEventLogger;
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.dagger.ApplicationContext;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderGridOrganizer;
 import com.android.launcher3.folder.FolderNameInfos;
 import com.android.launcher3.folder.FolderNameProvider;
-import com.android.launcher3.icons.CacheableShortcutCachingLogic;
-import com.android.launcher3.icons.CacheableShortcutInfo;
+import com.android.launcher3.icons.ComponentWithLabelAndIcon;
+import com.android.launcher3.icons.ComponentWithLabelAndIcon.ComponentWithIconCachingLogic;
 import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.icons.cache.CachedObject;
-import com.android.launcher3.icons.cache.CachedObjectCachingLogic;
+import com.android.launcher3.icons.LauncherActivityCachingLogic;
+import com.android.launcher3.icons.ShortcutCachingLogic;
 import com.android.launcher3.icons.cache.IconCacheUpdateHandler;
-import com.android.launcher3.icons.cache.LauncherActivityCachingLogic;
 import com.android.launcher3.logging.FileLog;
-import com.android.launcher3.model.LoaderCursor.LoaderCursorFactory;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.AppPairInfo;
+import com.android.launcher3.model.data.CollectionInfo;
 import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.IconRequestInfo;
 import com.android.launcher3.model.data.ItemInfo;
@@ -107,32 +102,24 @@ import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.TraceHelper;
 import com.android.launcher3.widget.WidgetInflater;
 
-import dagger.assisted.Assisted;
-import dagger.assisted.AssistedFactory;
-import dagger.assisted.AssistedInject;
-
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
-import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.CancellationException;
 import java.util.stream.Collectors;
 
 import app.lawnchair.preferences.PreferenceManager;
 
-import javax.inject.Named;
-
 /**
  * Runnable for the thread that loads the contents of the launcher:
- *   - workspace icons
- *   - widgets
- *   - all apps icons
- *   - deep shortcuts within apps
+ * - workspace icons
+ * - widgets
+ * - all apps icons
+ * - deep shortcuts within apps
  */
 @SuppressWarnings("NewApi")
 public class LoaderTask implements Runnable {
@@ -141,14 +128,10 @@ public class LoaderTask implements Runnable {
 
     private static final boolean DEBUG = true;
 
-    private final Context mContext;
-    private final LauncherModel mModel;
-    private final InvariantDeviceProfile mIDP;
-    private final boolean mIsSafeModeEnabled;
+    @NonNull
+    protected final LauncherAppState mApp;
     private final AllAppsList mBgAllAppsList;
     protected final BgDataModel mBgDataModel;
-    private final LoaderCursorFactory mLoaderCursorFactory;
-
     private final ModelDelegate mModelDelegate;
     private boolean mIsRestoreFromBackup;
 
@@ -166,47 +149,36 @@ public class LoaderTask implements Runnable {
     private final IconCache mIconCache;
 
     private final UserManagerState mUserManagerState;
+    protected final Map mWidgetProvidersMap = new ArrayMap<>();
     private Map mShortcutKeyToPinnedShortcuts;
     private HashMap mInstallingPkgsCached;
 
-    private List> mWorkspaceIconRequestInfos = new ArrayList<>();
-
     private boolean mStopped;
 
     private final Set mPendingPackages = new HashSet<>();
     private boolean mItemsDeleted = false;
     private String mDbName;
 
-    @AssistedInject
-    LoaderTask(
-            @ApplicationContext Context context,
-            InvariantDeviceProfile idp,
-            LauncherModel model,
-            UserCache userCache,
-            PackageManagerHelper pmHelper,
-            InstallSessionHelper sessionHelper,
-            IconCache iconCache,
-            AllAppsList bgAllAppsList,
-            BgDataModel bgModel,
-            LoaderCursorFactory loaderCursorFactory,
-            @Named("SAFE_MODE") boolean isSafeModeEnabled,
-            @Assisted @NonNull BaseLauncherBinder launcherBinder,
-            @Assisted UserManagerState userManagerState) {
-        mContext = context;
-        mIDP = idp;
-        mModel = model;
-        mIsSafeModeEnabled = isSafeModeEnabled;
+    public LoaderTask(@NonNull LauncherAppState app, AllAppsList bgAllAppsList, BgDataModel bgModel,
+            ModelDelegate modelDelegate, @NonNull BaseLauncherBinder launcherBinder) {
+        this(app, bgAllAppsList, bgModel, modelDelegate, launcherBinder, new UserManagerState());
+    }
+
+    @VisibleForTesting
+    LoaderTask(@NonNull LauncherAppState app, AllAppsList bgAllAppsList, BgDataModel bgModel,
+            ModelDelegate modelDelegate, @NonNull BaseLauncherBinder launcherBinder,
+            UserManagerState userManagerState) {
+        mApp = app;
         mBgAllAppsList = bgAllAppsList;
         mBgDataModel = bgModel;
-        mModelDelegate = model.getModelDelegate();
+        mModelDelegate = modelDelegate;
         mLauncherBinder = launcherBinder;
-        mLoaderCursorFactory = loaderCursorFactory;
-        mLauncherApps = mContext.getSystemService(LauncherApps.class);
-        mUserManager = mContext.getSystemService(UserManager.class);
-        mUserCache = userCache;
-        mPmHelper = pmHelper;
-        mSessionHelper = sessionHelper;
-        mIconCache = iconCache;
+        mLauncherApps = mApp.getContext().getSystemService(LauncherApps.class);
+        mUserManager = mApp.getContext().getSystemService(UserManager.class);
+        mUserCache = UserCache.INSTANCE.get(mApp.getContext());
+        mPmHelper = PackageManagerHelper.INSTANCE.get(mApp.getContext());
+        mSessionHelper = InstallSessionHelper.INSTANCE.get(mApp.getContext());
+        mIconCache = mApp.getIconCache();
         mUserManagerState = userManagerState;
         mInstallingPkgsCached = null;
     }
@@ -229,34 +201,29 @@ public class LoaderTask implements Runnable {
 
     private void sendFirstScreenActiveInstallsBroadcast() {
         // Screen set is never empty
-        IntArray allScreens = mBgDataModel.collectWorkspaceScreens(mContext);
+        IntArray allScreens = mBgDataModel.collectWorkspaceScreens();
         final int firstScreen = allScreens.get(0);
         IntSet firstScreens = IntSet.wrap(firstScreen);
 
-        List firstScreenItems =
-                mBgDataModel.itemsIdMap.stream()
-                        .filter(currentScreenContentFilter(firstScreens))
-                        .toList();
-        final int disableArchivingLauncherBroadcast = Settings.Secure.getInt(
-                mContext.getContentResolver(),
-                "disable_launcher_broadcast_installed_apps",
-                /* default */ 0);
-        boolean shouldAttachArchivingExtras = mIsRestoreFromBackup
-                && disableArchivingLauncherBroadcast == 0
-                && Flags.enableFirstScreenBroadcastArchivingExtras();
-        if (shouldAttachArchivingExtras) {
-            List broadcastModels =
-                    FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast(
+        ArrayList allItems = mBgDataModel.getAllWorkspaceItems();
+        ArrayList firstScreenItems = new ArrayList<>();
+        filterCurrentWorkspaceItems(firstScreens, allItems, firstScreenItems,
+                new ArrayList<>() /* otherScreenItems are ignored */);
+        final int launcherBroadcastInstalledApps = Settings.Secure.getInt(
+                mApp.getContext().getContentResolver(),
+                "launcher_broadcast_installed_apps",
+                /* def= */ 0);
+        if (launcherBroadcastInstalledApps == 1 && mIsRestoreFromBackup) {
+            List broadcastModels = FirstScreenBroadcastHelper
+                    .createModelsForFirstScreenBroadcast(
                             mPmHelper,
                             firstScreenItems,
                             mInstallingPkgsCached,
-                            mBgDataModel.itemsIdMap.stream().filter(WIDGET_FILTER).toList()
-                    );
+                            mBgDataModel.appWidgets);
             logASplit("Sending first screen broadcast with additional archiving Extras");
-            FirstScreenBroadcastHelper.sendBroadcastsForModels(mContext, broadcastModels);
+            FirstScreenBroadcastHelper.sendBroadcastsForModels(mApp.getContext(), broadcastModels);
         } else {
-            logASplit("Sending first screen broadcast");
-            mFirstScreenBroadcast.sendBroadcasts(mContext, firstScreenItems);
+            mFirstScreenBroadcast.sendBroadcasts(mApp.getContext(), firstScreenItems);
         }
     }
 
@@ -269,44 +236,45 @@ public class LoaderTask implements Runnable {
         }
 
         TraceHelper.INSTANCE.beginSection(TAG);
-        MODEL_EXECUTOR.elevatePriority(CALLER_LOADER_TASK);
         LoaderMemoryLogger memoryLogger = new LoaderMemoryLogger();
-        mIsRestoreFromBackup =
-                LauncherPrefs.get(mContext).get(IS_FIRST_LOAD_AFTER_RESTORE);
+        mIsRestoreFromBackup = (Boolean) LauncherPrefs.get(mApp.getContext()).get(IS_FIRST_LOAD_AFTER_RESTORE);
         LauncherRestoreEventLogger restoreEventLogger = null;
         if (enableLauncherBrMetricsFixed()) {
-            restoreEventLogger = LauncherRestoreEventLogger.Companion.newInstance(mContext);
+            restoreEventLogger = LauncherRestoreEventLogger.Companion
+                    .newInstance(mApp.getContext());
         }
-        try (LauncherModel.LoaderTransaction transaction = mModel.beginLoader(this)) {
-            List allShortcuts = new ArrayList<>();
-            loadWorkspace(allShortcuts, "", new HashMap<>(), memoryLogger, restoreEventLogger);
+        try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) {
 
-            // Sanitize data re-syncs widgets/shortcuts based on the workspace loaded from db.
-            // sanitizeData should not be invoked if the workspace is loaded from a db different
+            List allShortcuts = new ArrayList<>();
+            loadWorkspace(allShortcuts, "", memoryLogger, restoreEventLogger);
+
+            // Sanitize data re-syncs widgets/shortcuts based on the workspace loaded from
+            // db.
+            // sanitizeData should not be invoked if the workspace is loaded from a db
+            // different
             // from the main db as defined in the invariant device profile.
             // (e.g. both grid preview and minimal device mode uses a different db)
-            // TODO(b/384731096): Write Unit Test to make sure sanitizeWidgetsShortcutsAndPackages
-            //  actually re-pins shortcuts that are in model but not in ShortcutManager, if possible
-            //  after a simulated restore.
-            if (Objects.equals(mIDP.dbFile, mDbName)) {
+            if (Objects.equals(mApp.getInvariantDeviceProfile().dbFile, mDbName)) {
                 verifyNotStopped();
                 sanitizeFolders(mItemsDeleted);
                 sanitizeAppPairs();
                 sanitizeWidgetsShortcutsAndPackages();
-                logASplit("sanitizeData finished");
+                logASplit("sanitizeData");
             }
 
             verifyNotStopped();
             mLauncherBinder.bindWorkspace(true /* incrementBindId */, /* isBindSync= */ false);
-            logASplit("bindWorkspace finished");
+            logASplit("bindWorkspace");
 
             mModelDelegate.workspaceLoadComplete();
-            // Notify the installer packages of packages with active installs on the first screen.
+            // Notify the installer packages of packages with active installs on the first
+            // screen.
             sendFirstScreenActiveInstallsBroadcast();
+            logASplit("sendFirstScreenBroadcast");
 
             // Take a break
             waitForIdle();
-            logASplit("step 1 loading workspace complete");
+            logASplit("step 1 complete");
             verifyNotStopped();
 
             // second step
@@ -317,77 +285,86 @@ public class LoaderTask implements Runnable {
             } finally {
                 Trace.endSection();
             }
-            logASplit("loadAllApps finished");
+            logASplit("loadAllApps");
 
+            if (FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
+                mModelDelegate.loadAndBindAllAppsItems(mUserManagerState,
+                        mLauncherBinder.mCallbacksList, mShortcutKeyToPinnedShortcuts);
+                logASplit("allAppsDelegateItems");
+            }
             verifyNotStopped();
             mLauncherBinder.bindAllApps();
-            logASplit("bindAllApps finished");
+            logASplit("bindAllApps");
 
             verifyNotStopped();
             IconCacheUpdateHandler updateHandler = mIconCache.getUpdateHandler();
             setIgnorePackages(updateHandler);
             updateHandler.updateIcons(allActivityList,
-                    LauncherActivityCachingLogic.INSTANCE,
-                    mModel::onPackageIconsUpdated);
-            logASplit("update AllApps icon cache finished");
+                    LauncherActivityCachingLogic.newInstance(mApp.getContext()),
+                    mApp.getModel()::onPackageIconsUpdated);
+            logASplit("update icon cache");
 
             verifyNotStopped();
-            logASplit("saving all shortcuts in icon cache");
-            updateHandler.updateIcons(allShortcuts, CacheableShortcutCachingLogic.INSTANCE,
-                    mModel::onPackageIconsUpdated);
+            logASplit("save shortcuts in icon cache");
+            updateHandler.updateIcons(allShortcuts, new ShortcutCachingLogic(),
+                    mApp.getModel()::onPackageIconsUpdated);
 
             // Take a break
             waitForIdle();
-            logASplit("step 2 loading AllApps complete");
+            logASplit("step 2 complete");
             verifyNotStopped();
 
             // third step
             List allDeepShortcuts = loadDeepShortcuts();
-            logASplit("loadDeepShortcuts finished");
+            logASplit("loadDeepShortcuts");
 
             verifyNotStopped();
             mLauncherBinder.bindDeepShortcuts();
-            logASplit("bindDeepShortcuts finished");
+            logASplit("bindDeepShortcuts");
 
             verifyNotStopped();
-            logASplit("saving deep shortcuts in icon cache");
-            updateHandler.updateIcons(
-                    convertShortcutsToCacheableShortcuts(allDeepShortcuts, allActivityList),
-                    CacheableShortcutCachingLogic.INSTANCE,
-                    (pkgs, user) -> { });
+            logASplit("save deep shortcuts in icon cache");
+            updateHandler.updateIcons(allDeepShortcuts,
+                    new ShortcutCachingLogic(), (pkgs, user) -> {
+                    });
 
             // Take a break
             waitForIdle();
-            logASplit("step 3 loading all shortcuts complete");
+            logASplit("step 3 complete");
             verifyNotStopped();
 
             // fourth step
-            WidgetsModel widgetsModel = mBgDataModel.widgetsModel;
-            List allWidgetsList = widgetsModel.update(/*packageUser=*/null);
-            logASplit("load widgets finished");
+            List allWidgetsList = mBgDataModel.widgetsModel.update(mApp, null);
+            logASplit("load widgets");
 
             verifyNotStopped();
             mLauncherBinder.bindWidgets();
-            logASplit("bindWidgets finished");
+            logASplit("bindWidgets");
             verifyNotStopped();
-            LauncherPrefs prefs = LauncherPrefs.get(mContext);
+            LauncherPrefs prefs = LauncherPrefs.get(mApp.getContext());
 
             if (enableSmartspaceAsAWidget() && prefs.get(SHOULD_SHOW_SMARTSPACE)) {
                 mLauncherBinder.bindSmartspaceWidget();
                 // Turn off pref.
                 prefs.putSync(SHOULD_SHOW_SMARTSPACE.to(false));
-                logASplit("bindSmartspaceWidget finished");
+                logASplit("bindSmartspaceWidget");
                 verifyNotStopped();
             } else if (!enableSmartspaceAsAWidget() && WIDGET_ON_FIRST_SCREEN
-                    && !prefs.get(LauncherPrefs.SHOULD_SHOW_SMARTSPACE)) {
+                    && !prefs.get(SHOULD_SHOW_SMARTSPACE)) {
                 // Turn on pref.
                 prefs.putSync(SHOULD_SHOW_SMARTSPACE.to(true));
             }
 
-            logASplit("saving all widgets in icon cache");
+            if (FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
+                mModelDelegate.loadAndBindOtherItems(mLauncherBinder.mCallbacksList);
+                logASplit("otherDelegateItems");
+                verifyNotStopped();
+            }
+
             updateHandler.updateIcons(allWidgetsList,
-                    CachedObjectCachingLogic.INSTANCE,
-                    mModel::onWidgetLabelsUpdated);
+                    new ComponentWithIconCachingLogic(mApp.getContext(), true),
+                    mApp.getModel()::onWidgetLabelsUpdated);
+            logASplit("save widgets in icon cache");
 
             // fifth step
             loadFolderNames();
@@ -401,131 +378,116 @@ public class LoaderTask implements Runnable {
             memoryLogger.clearLogs();
             if (mIsRestoreFromBackup) {
                 mIsRestoreFromBackup = false;
-                LauncherPrefs.get(mContext).putSync(IS_FIRST_LOAD_AFTER_RESTORE.to(false));
+                LauncherPrefs.get(mApp.getContext()).putSync(IS_FIRST_LOAD_AFTER_RESTORE.to(false));
                 if (restoreEventLogger != null) {
                     restoreEventLogger.reportLauncherRestoreResults();
                 }
             }
         } catch (CancellationException e) {
             // Loader stopped, ignore
-            FileLog.w(TAG, "LoaderTask cancelled");
+            logASplit("Cancelled");
         } catch (Exception e) {
             memoryLogger.printLogs();
             throw e;
         }
-        MODEL_EXECUTOR.restorePriority(CALLER_LOADER_TASK);
         TraceHelper.INSTANCE.endSection();
     }
 
     public synchronized void stopLocked() {
-        FileLog.w(TAG, "stopLocked: Loader stopping");
         mStopped = true;
         this.notify();
     }
 
-    public void loadWorkspaceForPreview(String selection,
-            Map widgetProviderInfoMap) {
-        loadWorkspace(new ArrayList<>(), selection, widgetProviderInfoMap, null, null);
-    }
-
-    private void loadWorkspace(
-            List allDeepShortcuts,
+    protected void loadWorkspace(
+            List allDeepShortcuts,
             String selection,
-            Map widgetProviderInfoMap,
-            @Nullable LoaderMemoryLogger memoryLogger,
-            @Nullable LauncherRestoreEventLogger restoreEventLogger
-    ) {
+            LoaderMemoryLogger memoryLogger,
+            @Nullable LauncherRestoreEventLogger restoreEventLogger) {
         Trace.beginSection("LoadWorkspace");
         try {
-            loadWorkspaceImpl(allDeepShortcuts, selection, widgetProviderInfoMap,
-                    memoryLogger, restoreEventLogger);
+            loadWorkspaceImpl(allDeepShortcuts, selection, memoryLogger, restoreEventLogger);
         } finally {
             Trace.endSection();
         }
-        logASplit("loadWorkspace finished");
+        logASplit("loadWorkspace");
 
-        mBgDataModel.isFirstPagePinnedItemEnabled = FeatureFlags.QSB_ON_FIRST_SCREEN
-                && (!enableSmartspaceRemovalToggle()
-                || LauncherPrefs.getPrefs(mContext).getBoolean(SMARTSPACE_ON_HOME_SCREEN, true));
+        if (FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
+            verifyNotStopped();
+            mModelDelegate.loadAndBindWorkspaceItems(mUserManagerState,
+                    mLauncherBinder.mCallbacksList, mShortcutKeyToPinnedShortcuts);
+            mModelDelegate.markActive();
+            logASplit("workspaceDelegateItems");
+        }
+        mBgDataModel.isFirstPagePinnedItemEnabled = com.android.launcher3.config.FeatureFlags.topQsbOnFirstScreenEnabled(mApp.getContext())
+                && (!enableSmartspaceRemovalToggle() || LauncherPrefs.getPrefs(
+                        mApp.getContext()).getBoolean(SMARTSPACE_ON_HOME_SCREEN, true));
     }
 
     private void loadWorkspaceImpl(
-            List allDeepShortcuts,
+            List allDeepShortcuts,
             String selection,
-            Map widgetProviderInfoMap,
             @Nullable LoaderMemoryLogger memoryLogger,
             @Nullable LauncherRestoreEventLogger restoreEventLogger) {
+        final Context context = mApp.getContext();
         final boolean isSdCardReady = Utilities.isBootCompleted();
-        final WidgetInflater widgetInflater = new WidgetInflater(mContext, mIsSafeModeEnabled);
+        final WidgetInflater widgetInflater = new WidgetInflater(context);
 
-        ModelDbController dbController = mModel.getModelDbController();
-        if (Flags.gridMigrationRefactor()) {
-            try {
-                dbController.attemptMigrateDb(restoreEventLogger, mModelDelegate);
-            } catch (Exception e) {
-                FileLog.e(TAG, "Failed to migrate grid", e);
-            }
-        } else {
-            dbController.tryMigrateDB(restoreEventLogger, mModelDelegate);
-        }
-        Log.d(TAG, "loadWorkspace: loading default favorites if necessary");
+        ModelDbController dbController = mApp.getModel().getModelDbController();
+        dbController.tryMigrateDB(restoreEventLogger);
+        Log.d(TAG, "loadWorkspace: loading default favorites");
         dbController.loadDefaultFavoritesIfNecessary();
 
         synchronized (mBgDataModel) {
             mBgDataModel.clear();
             mPendingPackages.clear();
 
-            final HashMap installingPkgs =
-                    mSessionHelper.getActiveSessions();
+            final HashMap installingPkgs = mSessionHelper.getActiveSessions();
             if (Flags.enableSupportForArchiving()) {
                 mInstallingPkgsCached = installingPkgs;
             }
-            installingPkgs.forEach(mIconCache::updateSessionCache);
+            installingPkgs.forEach(mApp.getIconCache()::updateSessionCache);
             if (Utilities.ATLEAST_U) { 
                 FileLog.d(TAG, "loadWorkspace: Packages with active install sessions: "
                     + installingPkgs.keySet().stream().map(info -> info.mPackageName).toList());
             }
-            
             mFirstScreenBroadcast = new FirstScreenBroadcast(installingPkgs);
 
             mShortcutKeyToPinnedShortcuts = new HashMap<>();
-            final LoaderCursor c = mLoaderCursorFactory.createLoaderCursor(
-                    dbController.query(null, selection, null, null),
-                    mUserManagerState,
+            final LoaderCursor c = new LoaderCursor(
+                    dbController.query(TABLE_NAME, null, selection, null, null),
+                    mApp, mUserManagerState, mPmHelper,
                     mIsRestoreFromBackup ? restoreEventLogger : null);
             final Bundle extras = c.getExtras();
             mDbName = extras == null ? null : extras.getString(ModelDbController.EXTRA_DB_NAME);
             try {
                 final LongSparseArray unlockedUsers = new LongSparseArray<>();
-                queryPinnedShortcutsForUnlockedUsers(mContext, unlockedUsers);
+                queryPinnedShortcutsForUnlockedUsers(context, unlockedUsers);
+
+                List> iconRequestInfos = new ArrayList<>();
 
-                mWorkspaceIconRequestInfos = new ArrayList<>();
                 WorkspaceItemProcessor itemProcessor = new WorkspaceItemProcessor(c, memoryLogger,
                         mUserCache, mUserManagerState, mLauncherApps, mPendingPackages,
-                        mShortcutKeyToPinnedShortcuts, mContext, mIDP, mIconCache,
-                        mIsSafeModeEnabled, mBgDataModel,
-                        widgetProviderInfoMap, installingPkgs, isSdCardReady,
-                        widgetInflater, mPmHelper, mWorkspaceIconRequestInfos, unlockedUsers,
+                        mShortcutKeyToPinnedShortcuts, mApp, mBgDataModel,
+                        mWidgetProvidersMap, installingPkgs, isSdCardReady,
+                        widgetInflater, mPmHelper, iconRequestInfos, unlockedUsers,
                         allDeepShortcuts);
 
-                if (mStopped) {
-                    Log.w(TAG, "loadWorkspaceImpl: Loader stopped, skipping item processing");
-                } else {
-                    while (!mStopped && c.moveToNext()) {
-                        itemProcessor.processItem();
-                    }
+                while (!mStopped && c.moveToNext()) {
+                    itemProcessor.processItem();
                 }
-                tryLoadWorkspaceIconsInBulk(mWorkspaceIconRequestInfos);
+                tryLoadWorkspaceIconsInBulk(iconRequestInfos);
             } finally {
                 IOUtils.closeSilently(c);
             }
 
-            mModelDelegate.loadAndBindWorkspaceItems(mUserManagerState,
-                    mLauncherBinder.mCallbacksList, mShortcutKeyToPinnedShortcuts);
-            mModelDelegate.loadAndBindAllAppsItems(mUserManagerState,
-                    mLauncherBinder.mCallbacksList, mShortcutKeyToPinnedShortcuts);
-            mModelDelegate.loadAndBindOtherItems(mLauncherBinder.mCallbacksList);
-            mModelDelegate.markActive();
+            if (!FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
+                mModelDelegate.loadAndBindWorkspaceItems(mUserManagerState,
+                        mLauncherBinder.mCallbacksList, mShortcutKeyToPinnedShortcuts);
+                mModelDelegate.loadAndBindAllAppsItems(mUserManagerState,
+                        mLauncherBinder.mCallbacksList, mShortcutKeyToPinnedShortcuts);
+                mModelDelegate.loadAndBindOtherItems(mLauncherBinder.mCallbacksList);
+                mModelDelegate.markActive();
+            }
 
             // Break early if we've stopped loading
             if (mStopped) {
@@ -544,26 +506,32 @@ public class LoaderTask implements Runnable {
     }
 
     /**
-     * After all items have been processed and added to the BgDataModel, this method sorts and
+     * After all items have been processed and added to the BgDataModel, this method
+     * sorts and
      * requests high-res icons for the items that are part of an app pair.
      */
     private void processAppPairItems() {
-        mBgDataModel.itemsIdMap.stream()
-                .filter(item -> item instanceof AppPairInfo)
-                .forEach(item -> {
-                    AppPairInfo appPair = (AppPairInfo) item;
-                    appPair.getContents().sort(Folder.ITEM_POS_COMPARATOR);
-                    appPair.fetchHiResIconsIfNeeded(mIconCache);
-                });
+        for (CollectionInfo collection : mBgDataModel.collections) {
+            if (!(collection instanceof AppPairInfo appPair)) {
+                continue;
+            }
+
+            appPair.getContents().sort(Folder.ITEM_POS_COMPARATOR);
+            appPair.fetchHiResIconsIfNeeded(mIconCache);
+        }
     }
 
     /**
-     * Initialized the UserManagerState, and determines which users are unlocked. Additionally, if
-     * the user is unlocked, it queries LauncherAppsService for pinned shortcuts and stores the
-     * result in a class variable to be used in other methods while processing workspace items.
+     * Initialized the UserManagerState, and determines which users are unlocked.
+     * Additionally, if
+     * the user is unlocked, it queries LauncherAppsService for pinned shortcuts and
+     * stores the
+     * result in a class variable to be used in other methods while processing
+     * workspace items.
      *
-     * @param context used to query LauncherAppsService
-     * @param unlockedUsers this param is changed, and the updated value is used outside this method
+     * @param context       used to query LauncherAppsService
+     * @param unlockedUsers this param is changed, and the updated value is used
+     *                      outside this method
      */
     @WorkerThread
     private void queryPinnedShortcutsForUnlockedUsers(Context context,
@@ -601,17 +569,19 @@ public class LoaderTask implements Runnable {
     }
 
     /**
-     * After all items have been processed and added to the BgDataModel, this method can correctly
-     * rank items inside folders and load the correct miniature preview icons to be shown when the
+     * After all items have been processed and added to the BgDataModel, this method
+     * can correctly
+     * rank items inside folders and load the correct miniature preview icons to be
+     * shown when the
      * folder is collapsed.
      */
     @WorkerThread
     private void processFolderItems() {
         // Sort the folder items, update ranks, and make sure all preview items are high res.
-        List verifiers = mIDP.supportedProfiles
-                .stream().map(FolderGridOrganizer::createFolderGridOrganizer).toList();
-        for (ItemInfo itemInfo : mBgDataModel.itemsIdMap) {
-            if (!(itemInfo instanceof FolderInfo folder)) {
+        List verifiers = mApp.getInvariantDeviceProfile().supportedProfiles
+                .stream().map(FolderGridOrganizer::createFolderGridOrganizer).collect(Collectors.toList());
+        for (CollectionInfo collection : mBgDataModel.collections) {
+            if (!(collection instanceof FolderInfo folder)) {
                 continue;
             }
 
@@ -627,10 +597,10 @@ public class LoaderTask implements Runnable {
                 info.rank = rank;
 
                 if (info instanceof WorkspaceItemInfo wii
-                        && wii.getMatchingLookupFlag().isVisuallyLessThan(DESKTOP_ICON_FLAG)
+                        && wii.usingLowResIcon()
                         && wii.itemType == Favorites.ITEM_TYPE_APPLICATION
                         && verifiers.stream().anyMatch(it -> it.isItemInPreview(info.rank))) {
-                    mIconCache.getTitleAndIcon(wii, DESKTOP_ICON_FLAG);
+                    mIconCache.getTitleAndIcon(wii, false);
                 } else if (info instanceof AppPairInfo api) {
                     api.fetchHiResIconsIfNeeded(mIconCache);
                 }
@@ -646,9 +616,7 @@ public class LoaderTask implements Runnable {
             for (IconRequestInfo iconRequestInfo : iconRequestInfos) {
                 WorkspaceItemInfo wai = iconRequestInfo.itemInfo;
                 if (mIconCache.isDefaultIcon(wai.bitmap, wai.user)) {
-                    logASplit("tryLoadWorkspaceIconsInBulk: default icon found for "
-                            + wai.getTargetComponent() + ", will attempt to load from iconBlob");
-                    iconRequestInfo.loadIconFromDbBlob(mContext);
+                    iconRequestInfo.loadWorkspaceIcon(mApp.getContext());
                 }
             }
         } finally {
@@ -680,19 +648,23 @@ public class LoaderTask implements Runnable {
     private void sanitizeFolders(boolean itemsDeleted) {
         if (itemsDeleted) {
             // Remove any empty folder
-            IntArray deletedFolderIds = mModel.getModelDbController().deleteEmptyFolders();
+            IntArray deletedFolderIds = mApp.getModel().getModelDbController().deleteEmptyFolders();
             synchronized (mBgDataModel) {
                 for (int folderId : deletedFolderIds) {
+                    mBgDataModel.workspaceItems.remove(mBgDataModel.collections.get(folderId));
+                    mBgDataModel.collections.remove(folderId);
                     mBgDataModel.itemsIdMap.remove(folderId);
                 }
             }
         }
     }
 
-    /** Cleans up app pairs if they don't have the right number of member apps (2). */
+    /**
+     * Cleans up app pairs if they don't have the right number of member apps (2).
+     */
     private void sanitizeAppPairs() {
-        IntArray deletedAppPairIds = mModel.getModelDbController().deleteBadAppPairs();
-        IntArray deletedAppIds = mModel.getModelDbController().deleteUnparentedApps();
+        IntArray deletedAppPairIds = mApp.getModel().getModelDbController().deleteBadAppPairs();
+        IntArray deletedAppIds = mApp.getModel().getModelDbController().deleteUnparentedApps();
 
         IntArray deleted = new IntArray();
         deleted.addAll(deletedAppPairIds);
@@ -700,21 +672,25 @@ public class LoaderTask implements Runnable {
 
         synchronized (mBgDataModel) {
             for (int id : deleted) {
+                mBgDataModel.workspaceItems.remove(mBgDataModel.collections.get(id));
+                mBgDataModel.collections.remove(id);
                 mBgDataModel.itemsIdMap.remove(id);
             }
         }
     }
 
     private void sanitizeWidgetsShortcutsAndPackages() {
+        Context context = mApp.getContext();
+
         // Remove any ghost widgets
-        mModel.getModelDbController().removeGhostWidgets();
+        mApp.getModel().getModelDbController().removeGhostWidgets();
 
         // Update pinned state of model shortcuts
-        mBgDataModel.updateShortcutPinnedState(mContext);
+        mBgDataModel.updateShortcutPinnedState(context);
 
         if (!Utilities.isBootCompleted() && !mPendingPackages.isEmpty()) {
-            mContext.registerReceiver(
-                    new SdCardAvailableReceiver(mContext, mModel, mPendingPackages),
+            context.registerReceiver(
+                    new SdCardAvailableReceiver(mApp, mPendingPackages),
                     new IntentFilter(Intent.ACTION_BOOT_COMPLETED),
                     null,
                     MODEL_EXECUTOR.getHandler());
@@ -727,10 +703,10 @@ public class LoaderTask implements Runnable {
         // Clear the list of apps
         mBgAllAppsList.clear();
 
-        var pref = PreferenceManager.getInstance(mContext);
+        var pref = PreferenceManager.getInstance(mApp.getContext());
         var enableBulkLoading = pref.getAllAppBulkIconLoading().get();
-        
-        List> allAppsItemRequestInfos = new ArrayList<>();
+
+        List> iconRequestInfos = new ArrayList<>();
         boolean isWorkProfileQuiet = false;
         boolean isPrivateProfileQuiet = false;
         for (UserHandle user : profiles) {
@@ -754,7 +730,7 @@ public class LoaderTask implements Runnable {
             for (int i = 0; i < apps.size(); i++) {
                 LauncherActivityInfo app = apps.get(i);
                 AppInfo appInfo = new AppInfo(app, mUserCache.getUserInfo(user),
-                        ApiWrapper.INSTANCE.get(mContext), mPmHelper, quietMode);
+                        ApiWrapper.INSTANCE.get(mApp.getContext()), mPmHelper, quietMode);
                 try {
                     if (Flags.enableSupportForArchiving()) {
                         if (app.getApplicationInfo().isArchived) {
@@ -773,54 +749,39 @@ public class LoaderTask implements Runnable {
                         }
                     }
                 } catch (Throwable e) {
-                    // LC-Ignored: Ignore any exceptions while trying to get the session info.
+                    // Ignore any exceptions while trying to get the session info.
                 }
 
-                IconRequestInfo iconRequestInfo = getAppInfoIconRequestInfo(
-                        appInfo, app, mWorkspaceIconRequestInfos, mIsRestoreFromBackup);
-                allAppsItemRequestInfos.add(iconRequestInfo);
-                mBgAllAppsList.add(appInfo, app, false);
+                iconRequestInfos.add(new IconRequestInfo<>(
+                        appInfo, app, /* useLowResIcon= */ false));
+                mBgAllAppsList.add(
+                        appInfo, app, !enableBulkLoading);
             }
             allActivityList.addAll(apps);
         }
 
         if (FeatureFlags.PROMISE_APPS_IN_ALL_APPS.get()) {
             // get all active sessions and add them to the all apps list
-            for (PackageInstaller.SessionInfo info :
-                    mSessionHelper.getAllVerifiedSessions()) {
+            for (PackageInstaller.SessionInfo info : mSessionHelper.getAllVerifiedSessions()) {
                 AppInfo promiseAppInfo = mBgAllAppsList.addPromiseApp(
-                        mContext,
+                        mApp.getContext(),
                         PackageInstallInfo.fromInstallingState(info),
-                        false);
+                        !enableBulkLoading);
 
                 if (promiseAppInfo != null) {
-                    allAppsItemRequestInfos.add(new IconRequestInfo<>(
+                    iconRequestInfos.add(new IconRequestInfo<>(
                             promiseAppInfo,
                             /* launcherActivityInfo= */ null,
-                            promiseAppInfo.getMatchingLookupFlag().useLowRes()));
+                            promiseAppInfo.usingLowResIcon()));
                 }
             }
         }
 
         if (enableBulkLoading) {
             Trace.beginSection("LoadAllAppsIconsInBulk");
-
             try {
-                mIconCache.getTitlesAndIconsInBulk(allAppsItemRequestInfos);
-                if (Flags.restoreArchivedAppIconsFromDb()) {
-                    for (IconRequestInfo iconRequestInfo : allAppsItemRequestInfos) {
-                        AppInfo appInfo = iconRequestInfo.itemInfo;
-                        if (mIconCache.isDefaultIcon(appInfo.bitmap, appInfo.user)) {
-                            logASplit("LoadAllAppsIconsInBulk: default icon found for "
-                                + appInfo.getTargetComponent()
-                                + ", will attempt to load from iconBlob: "
-                                + Arrays.toString(iconRequestInfo.iconBlob));
-                            iconRequestInfo.loadIconFromDbBlob(mContext);
-                        }
-                    }
-                }
-                allAppsItemRequestInfos.forEach(iconRequestInfo ->
-                    mBgAllAppsList.updateSectionName(iconRequestInfo.itemInfo));
+                mIconCache.getTitlesAndIconsInBulk(iconRequestInfos);
+                iconRequestInfos.forEach(iconRequestInfo -> mBgAllAppsList.updateSectionName(iconRequestInfo.itemInfo));
             } finally {
                 Trace.endSection();
             }
@@ -834,59 +795,15 @@ public class LoaderTask implements Runnable {
                     mUserManagerState.isAnyProfileQuietModeEnabled());
         }
         mBgAllAppsList.setFlags(FLAG_HAS_SHORTCUT_PERMISSION,
-                hasShortcutsPermission(mContext));
+                hasShortcutsPermission(mApp.getContext()));
         mBgAllAppsList.setFlags(FLAG_QUIET_MODE_CHANGE_PERMISSION,
-                mContext.checkSelfPermission("android.permission.MODIFY_QUIET_MODE")
-                        == PackageManager.PERMISSION_GRANTED);
+                mApp.getContext().checkSelfPermission(
+                        "android.permission.MODIFY_QUIET_MODE") == PackageManager.PERMISSION_GRANTED);
 
         mBgAllAppsList.getAndResetChangeFlag();
         return allActivityList;
     }
 
-    @NonNull
-    @VisibleForTesting
-    IconRequestInfo getAppInfoIconRequestInfo(
-            AppInfo appInfo,
-            LauncherActivityInfo activityInfo,
-            List> workspaceRequestInfos,
-            boolean isRestoreFromBackup
-    ) {
-        if (Flags.restoreArchivedAppIconsFromDb() && isRestoreFromBackup) {
-            Optional> workspaceIconRequest =
-                    workspaceRequestInfos.stream()
-                            .filter(request -> appInfo.getTargetComponent().equals(
-                                    request.itemInfo.getTargetComponent()))
-                            .findFirst();
-
-            if (workspaceIconRequest.isPresent() && activityInfo.getApplicationInfo().isArchived) {
-                logASplit("getAppInfoIconRequestInfo:"
-                            + " matching archived info found, loading icon blob into icon request."
-                            + " Component=" + appInfo.getTargetComponent());
-                IconRequestInfo iconRequestInfo = new IconRequestInfo<>(
-                        appInfo,
-                        activityInfo,
-                        workspaceIconRequest.get().iconBlob,
-                        false /* useLowResIcon= */
-                );
-                if (!iconRequestInfo.loadIconFromDbBlob(mContext)) {
-                    Log.d(TAG, "AppInfo Icon failed to load from blob, using cache.");
-                    mIconCache.getTitleAndIcon(
-                            appInfo,
-                            iconRequestInfo.launcherActivityInfo,
-                            DEFAULT_LOOKUP_FLAG
-                    );
-                }
-                return iconRequestInfo;
-            } else {
-                Log.d(TAG, "App not archived or workspace info not found"
-                        + ", creating IconRequestInfo without icon blob."
-                        + " Component:" + appInfo.getTargetComponent()
-                        + ", isArchived: " + activityInfo.getApplicationInfo().isArchived);
-            }
-        }
-        return new IconRequestInfo<>(appInfo, activityInfo, false /* useLowResIcon= */);
-    }
-
     private List loadDeepShortcuts() {
         List allShortcuts = new ArrayList<>();
         mBgDataModel.deepShortcutMap.clear();
@@ -894,7 +811,7 @@ public class LoaderTask implements Runnable {
         if (mBgAllAppsList.hasShortcutHostPermission()) {
             for (UserHandle user : mUserCache.getUserProfiles()) {
                 if (mUserManager.isUserUnlocked(user)) {
-                    List shortcuts = new ShortcutRequest(mContext, user)
+                    List shortcuts = new ShortcutRequest(mApp.getContext(), user)
                             .query(ShortcutRequest.ALL);
                     allShortcuts.addAll(shortcuts);
                     mBgDataModel.updateDeepShortcutCounts(null, user, shortcuts);
@@ -905,20 +822,19 @@ public class LoaderTask implements Runnable {
     }
 
     private void loadFolderNames() {
-        FolderNameProvider provider = FolderNameProvider.newInstance(mContext,
-                mBgAllAppsList.data, FolderNameProvider.getCollectionForSuggestions(mBgDataModel));
+        FolderNameProvider provider = FolderNameProvider.newInstance(mApp.getContext(),
+                mBgAllAppsList.data, mBgDataModel.collections);
 
         synchronized (mBgDataModel) {
-            mBgDataModel.itemsIdMap.stream()
-                    .filter(item ->
-                            item instanceof FolderInfo fi && fi.suggestedFolderNames == null)
-                    .forEach(info -> {
-                        FolderInfo fi = (FolderInfo) info;
-                        FolderNameInfos suggestionInfos = new FolderNameInfos();
-                        provider.getSuggestedFolderName(mContext, fi.getAppContents(),
-                                suggestionInfos);
-                        fi.suggestedFolderNames = suggestionInfos;
-                    });
+            for (int i = 0; i < mBgDataModel.collections.size(); i++) {
+                FolderNameInfos suggestionInfos = new FolderNameInfos();
+                CollectionInfo info = mBgDataModel.collections.valueAt(i);
+                if (info instanceof FolderInfo fi && fi.suggestedFolderNames == null) {
+                    provider.getSuggestedFolderName(mApp.getContext(), fi.getAppContents(),
+                            suggestionInfos);
+                    fi.suggestedFolderNames = suggestionInfos;
+                }
+            }
         }
     }
 
@@ -932,10 +848,4 @@ public class LoaderTask implements Runnable {
             Log.d(TAG, label);
         }
     }
-
-    @AssistedFactory
-    public interface LoaderTaskFactory {
-
-        LoaderTask newLoaderTask(BaseLauncherBinder binder, UserManagerState userState);
-    }
 }
diff --git a/src/com/android/launcher3/model/ModelDbController.java b/src/com/android/launcher3/model/ModelDbController.java
index 8457590c37..2d409bd0d6 100644
--- a/src/com/android/launcher3/model/ModelDbController.java
+++ b/src/com/android/launcher3/model/ModelDbController.java
@@ -16,33 +16,48 @@
 package com.android.launcher3.model;
 
 import static android.provider.BaseColumns._ID;
+import static android.util.Base64.NO_PADDING;
+import static android.util.Base64.NO_WRAP;
 
-import static com.android.launcher3.LauncherPrefs.DB_FILE;
-import static com.android.launcher3.LauncherPrefs.NO_DB_FILES_RESTORED;
+import static com.android.launcher3.DefaultLayoutParser.RES_PARTNER_DEFAULT_LAYOUT;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR;
 import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
 import static com.android.launcher3.LauncherSettings.Favorites.addTableToDb;
+import static com.android.launcher3.LauncherSettings.Settings.LAYOUT_DIGEST_KEY;
+import static com.android.launcher3.LauncherSettings.Settings.LAYOUT_DIGEST_LABEL;
+import static com.android.launcher3.LauncherSettings.Settings.LAYOUT_DIGEST_TAG;
 import static com.android.launcher3.provider.LauncherDbUtils.tableExists;
 
+import android.app.blob.BlobHandle;
+import android.app.blob.BlobStoreManager;
+import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
+import android.content.res.Resources;
 import android.database.Cursor;
 import android.database.SQLException;
 import android.database.sqlite.SQLiteDatabase;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
 import android.os.Process;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.provider.Settings;
 import android.text.TextUtils;
+import android.util.Base64;
 import android.util.Log;
+import android.util.Xml;
 
 import androidx.annotation.Nullable;
 import androidx.annotation.WorkerThread;
 
 import com.android.launcher3.AutoInstallsLayout;
+import com.android.launcher3.AutoInstallsLayout.SourceResources;
 import com.android.launcher3.ConstantItem;
 import com.android.launcher3.DefaultLayoutParser;
 import com.android.launcher3.EncryptionType;
@@ -56,94 +71,52 @@ import com.android.launcher3.Utilities;
 import com.android.launcher3.graphics.LauncherPreviewRenderer;
 import com.android.launcher3.backuprestore.LauncherRestoreEventLogger;
 import com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RestoreError;
-import com.android.launcher3.dagger.ApplicationContext;
-import com.android.launcher3.dagger.LauncherAppSingleton;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.provider.LauncherDbUtils;
 import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
 import com.android.launcher3.provider.RestoreDbTask;
+import com.android.launcher3.util.IOUtils;
 import com.android.launcher3.util.IntArray;
-import com.android.launcher3.util.SandboxContext;
+import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
+import com.android.launcher3.util.Partner;
 import com.android.launcher3.widget.LauncherWidgetHolder;
 
-import java.io.File;
-import java.util.List;
-import java.util.stream.Collectors;
+import org.xmlpull.v1.XmlPullParser;
 
-import javax.inject.Inject;
+import java.io.InputStream;
+import java.io.StringReader;
 
 import app.lawnchair.LawnchairApp;
 import app.lawnchair.LawnchairAppKt;
 
 /**
- * Utility class which maintains an instance of Launcher database and provides utility methods
+ * Utility class which maintains an instance of Launcher database and provides
+ * utility methods
  * around it.
  */
-@LauncherAppSingleton
 public class ModelDbController {
-    private static final String TAG = "ModelDbController";
+    private static final String TAG = "LauncherProvider";
 
     private static final String EMPTY_DATABASE_CREATED = "EMPTY_DATABASE_CREATED";
     public static final String EXTRA_DB_NAME = "db_name";
-    public static final String DATA_TYPE_DB_FILE = "database_file";
 
     protected DatabaseHelper mOpenHelper;
 
     private final Context mContext;
-    private InvariantDeviceProfile mIdp;
-    private LauncherPrefs mPrefs;
-    private UserCache mUserCache;
-    private LayoutParserFactory mLayoutParserFactory;
 
-    @Inject
-    public ModelDbController(
-            @ApplicationContext Context context,
-            InvariantDeviceProfile idp,
-            LauncherPrefs prefs,
-            UserCache userCache,
-            LayoutParserFactory layoutParserFactory) {
-        mContext = context;
-        mIdp = idp;
-        mPrefs = prefs;
-        mUserCache = userCache;
-        mLayoutParserFactory = layoutParserFactory;
-    }
-
-    // Lawnchair: ModelDbController
     public ModelDbController(Context context) {
         mContext = context;
     }
 
-    private void printDBs(String prefix) {
-        try {
-            File directory = new File(mContext.getDatabasePath(mIdp.dbFile).getParent());
-            if (directory.exists()) {
-                for (File file : directory.listFiles()) {
-                    Log.d("b/353505773", prefix + "Database file: " + file.getName());
-                }
-            } else {
-                Log.d("b/353505773", prefix + "No files found in the database directory");
-            }
-        } catch (Exception e) {
-            Log.e("b/353505773", prefix + e.getMessage());
-        }
-    }
-
     private synchronized void createDbIfNotExists() {
         if (mOpenHelper == null) {
-            String dbFile = mPrefs.get(DB_FILE);
-            if (dbFile.isEmpty()) {
-                dbFile = mIdp.dbFile;
-            }
-            mOpenHelper = createDatabaseHelper(false /* forMigration */, dbFile);
-            printDBs("before: ");
+            mOpenHelper = createDatabaseHelper(false /* forMigration */);
             RestoreDbTask.restoreIfNeeded(mContext, this);
-            printDBs("after: ");
         }
     }
 
-    protected DatabaseHelper createDatabaseHelper(boolean forMigration, String dbFile) {
+    protected DatabaseHelper createDatabaseHelper(boolean forMigration) {
         boolean isSandbox = mContext instanceof SandboxContext;
         String dbName = isSandbox ? null : InvariantDeviceProfile.INSTANCE.get(mContext).dbFile;
 
@@ -154,14 +127,15 @@ public class ModelDbController {
                 app.migrateDbName(dbName);
             }
         } catch (Throwable t) {
-            // LC-Ignored
+            // ignore
         }
 
         // Set the flag for empty DB
-        Runnable onEmptyDbCreateCallback = forMigration ? () -> { }
-                : () -> mPrefs.putSync(getEmptyDbCreatedKey(dbFile).to(true));
+        Runnable onEmptyDbCreateCallback = forMigration ? () -> {
+        }
+                : () -> LauncherPrefs.get(mContext).putSync(getEmptyDbCreatedKey(dbName).to(true));
 
-        DatabaseHelper databaseHelper = new DatabaseHelper(mContext, dbFile,
+        DatabaseHelper databaseHelper = new DatabaseHelper(mContext, dbName,
                 this::getSerialNumberForUser, onEmptyDbCreateCallback);
         // Table creation sometimes fails silently, which leads to a crash loop.
         // This way, we will try to create a table every time after crash, so the device
@@ -184,12 +158,12 @@ public class ModelDbController {
      * Refer {@link SQLiteDatabase#query}
      */
     @WorkerThread
-    public Cursor query(String[] projection, String selection,
+    public Cursor query(String table, String[] projection, String selection,
             String[] selectionArgs, String sortOrder) {
         createDbIfNotExists();
         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
         Cursor result = db.query(
-                TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder);
+                table, projection, selection, selectionArgs, null, null, sortOrder);
 
         final Bundle extra = new Bundle();
         extra.putString(EXTRA_DB_NAME, mOpenHelper.getDatabaseName());
@@ -201,12 +175,12 @@ public class ModelDbController {
      * Refer {@link SQLiteDatabase#insert(String, String, ContentValues)}
      */
     @WorkerThread
-    public int insert(ContentValues initialValues) {
+    public int insert(String table, ContentValues initialValues) {
         createDbIfNotExists();
 
         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
         addModifiedTime(initialValues);
-        int rowId = mOpenHelper.dbInsertAndCheck(db, TABLE_NAME, initialValues);
+        int rowId = mOpenHelper.dbInsertAndCheck(db, table, initialValues);
         if (rowId >= 0) {
             onAddOrDeleteOp(db);
         }
@@ -217,11 +191,11 @@ public class ModelDbController {
      * Refer {@link SQLiteDatabase#delete(String, String, String[])}
      */
     @WorkerThread
-    public int delete(String selection, String[] selectionArgs) {
+    public int delete(String table, String selection, String[] selectionArgs) {
         createDbIfNotExists();
         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
 
-        int count = db.delete(TABLE_NAME, selection, selectionArgs);
+        int count = db.delete(table, selection, selectionArgs);
         if (count > 0) {
             onAddOrDeleteOp(db);
         }
@@ -232,12 +206,14 @@ public class ModelDbController {
      * Refer {@link SQLiteDatabase#update(String, ContentValues, String, String[])}
      */
     @WorkerThread
-    public int update(ContentValues values, String selection, String[] selectionArgs) {
+    public int update(String table, ContentValues values,
+            String selection, String[] selectionArgs) {
         createDbIfNotExists();
 
         addModifiedTime(values);
         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
-        return db.update(TABLE_NAME, values, selection, selectionArgs);
+        int count = db.update(table, values, selection, selectionArgs);
+        return count;
     }
 
     /**
@@ -274,11 +250,12 @@ public class ModelDbController {
     public void createEmptyDB() {
         createDbIfNotExists();
         mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
-        mPrefs.putSync(getEmptyDbCreatedKey().to(true));
+        LauncherPrefs.get(mContext).putSync(getEmptyDbCreatedKey().to(true));
     }
 
     /**
-     * Removes any widget which are present in the framework, but not in out internal DB
+     * Removes any widget which are present in the framework, but not in out
+     * internal DB
      */
     @WorkerThread
     public void removeGhostWidgets() {
@@ -305,175 +282,61 @@ public class ModelDbController {
                 mOpenHelper.getReadableDatabase(), Favorites.HYBRID_HOTSEAT_BACKUP_TABLE);
     }
 
-    /**
-     * Resets the launcher DB if we should reset it.
-     */
-    public void resetLauncherDb(@Nullable LauncherRestoreEventLogger restoreEventLogger) {
-        if (restoreEventLogger != null) {
-            sendMetricsForFailedMigration(restoreEventLogger, getDb());
-        }
-        FileLog.d(TAG, "resetLauncherDb: Migration failed: resetting launcher database");
-        createEmptyDB();
-        mPrefs.putSync(getEmptyDbCreatedKey(mOpenHelper.getDatabaseName()).to(true));
-
-        // Write the grid state to avoid another migration
-        new DeviceGridState(mIdp).writeToPrefs(mContext);
-    }
-
-    /**
-     * Determines if we should reset the DB.
-     */
-    private boolean shouldResetDb() {
-        if (isThereExistingDb()) {
-            return true;
-        }
-        if (!isGridMigrationNecessary()) {
-            return false;
-        }
-        if (isCurrentDbSameAsTarget()) {
-            return true;
-        }
-        return false;
-    }
-
-    private boolean isThereExistingDb() {
-        if (mPrefs.get(getEmptyDbCreatedKey())) {
-            // If we already have a new DB, ignore migration
-            FileLog.d(TAG, "isThereExistingDb: new DB already created, skipping migration");
-            return true;
-        }
-        return false;
-    }
-
-    private boolean isGridMigrationNecessary() {
-        if (GridSizeMigrationDBController.needsToMigrate(mContext, mIdp)) {
-            return true;
-        }
-        FileLog.d(TAG, "isGridMigrationNecessary: no grid migration needed");
-        return false;
-    }
-
-    private boolean isCurrentDbSameAsTarget() {
-        String targetDbName = new DeviceGridState(mIdp).getDbFile();
-        if (TextUtils.equals(targetDbName, mOpenHelper.getDatabaseName())) {
-            FileLog.e(TAG, "isCurrentDbSameAsTarget: target db is same as current"
-                    + " current db: " + mOpenHelper.getDatabaseName()
-                    + " target db: " + targetDbName);
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Migrates the DB. If the migration failed, it clears the DB.
-     */
-    public void attemptMigrateDb(LauncherRestoreEventLogger restoreEventLogger,
-            ModelDelegate modelDelegate) throws Exception {
-        createDbIfNotExists();
-        if (shouldResetDb()) {
-            resetLauncherDb(restoreEventLogger);
-            return;
-        }
-
-        DatabaseHelper oldHelper = mOpenHelper;
-
-        // We save the existing db's before creating the destination db helper so we know what logic
-        // to run in grid migration based on if that grid already existed before migration or not.
-        List existingDBs = LauncherFiles.GRID_DB_FILES.stream()
-                .filter(dbName -> mContext.getDatabasePath(dbName).exists())
-                .collect(Collectors.toList());
-
-        mOpenHelper = createDatabaseHelper(true, new DeviceGridState(mIdp).getDbFile());
-        try {
-            // This is the current grid we have, given by the mContext
-            DeviceGridState srcDeviceState = new DeviceGridState(mContext);
-            // This is the state we want to migrate to that is given by the idp
-            DeviceGridState destDeviceState = new DeviceGridState(mIdp);
-
-            boolean isDestNewDb = !existingDBs.contains(destDeviceState.getDbFile());
-            GridSizeMigrationLogic gridSizeMigrationLogic = new GridSizeMigrationLogic();
-            gridSizeMigrationLogic.migrateGrid(mContext, srcDeviceState, destDeviceState,
-                    mOpenHelper, oldHelper.getWritableDatabase(), isDestNewDb, modelDelegate);
-        } catch (Exception e) {
-            resetLauncherDb(restoreEventLogger);
-            throw new Exception("attemptMigrateDb: Failed to migrate grid", e);
-        } finally {
-            if (mOpenHelper != oldHelper) {
-                oldHelper.close();
-            }
-        }
-    }
-
     /**
      * Migrates the DB if needed. If the migration failed, it clears the DB.
      */
-    public void tryMigrateDB(@Nullable LauncherRestoreEventLogger restoreEventLogger,
-            ModelDelegate modelDelegate) {
-        if (!migrateGridIfNeeded(modelDelegate)) {
+    public void tryMigrateDB(@Nullable LauncherRestoreEventLogger restoreEventLogger) {
+
+        if (!migrateGridIfNeeded()) {
             if (restoreEventLogger != null) {
-                if (mPrefs.get(NO_DB_FILES_RESTORED)) {
-                    restoreEventLogger.logLauncherItemsRestoreFailed(DATA_TYPE_DB_FILE, 1,
-                            RestoreError.DATABASE_FILE_NOT_RESTORED);
-                    mPrefs.put(NO_DB_FILES_RESTORED, false);
-                    FileLog.d(TAG, "There is no data to migrate: resetting launcher database");
-                } else {
-                    restoreEventLogger.logLauncherItemsRestored(DATA_TYPE_DB_FILE, 1);
-                    sendMetricsForFailedMigration(restoreEventLogger, getDb());
-                }
+                sendMetricsForFailedMigration(restoreEventLogger, getDb());
             }
-            FileLog.d(TAG, "tryMigrateDB: Migration failed: resetting launcher database");
+            FileLog.d(TAG, "Migration failed: resetting launcher database");
             createEmptyDB();
-            mPrefs.putSync(getEmptyDbCreatedKey(mOpenHelper.getDatabaseName()).to(true));
+            LauncherPrefs.get(mContext).putSync(
+                    getEmptyDbCreatedKey(mOpenHelper.getDatabaseName()).to(true));
 
             // Write the grid state to avoid another migration
-            new DeviceGridState(mIdp).writeToPrefs(mContext);
-        } else if (restoreEventLogger != null) {
-            restoreEventLogger.logLauncherItemsRestored(DATA_TYPE_DB_FILE, 1);
+            new DeviceGridState(LauncherAppState.getIDP(mContext)).writeToPrefs(mContext);
         }
     }
 
     /**
      * Migrates the DB if needed, and returns false if the migration failed
      * and DB needs to be cleared.
+     * 
      * @return true if migration was success or ignored, false if migration failed
-     * and the DB should be reset.
+     *         and the DB should be reset.
      */
-    private boolean migrateGridIfNeeded(ModelDelegate modelDelegate) {
+    private boolean migrateGridIfNeeded() {
         createDbIfNotExists();
-        if (mPrefs.get(getEmptyDbCreatedKey())) {
+        if (LauncherPrefs.get(mContext).get(getEmptyDbCreatedKey())) {
             // If we have already create a new DB, ignore migration
-            FileLog.d(TAG, "migrateGridIfNeeded: new DB already created, skipping migration");
+            Log.d(TAG, "migrateGridIfNeeded: new DB already created, skipping migration");
             return false;
         }
-        if (!GridSizeMigrationDBController.needsToMigrate(mContext, mIdp)) {
-            FileLog.d(TAG, "migrateGridIfNeeded: no grid migration needed");
+        InvariantDeviceProfile idp = LauncherAppState.getIDP(mContext);
+        if (!GridSizeMigrationUtil.needsToMigrate(mContext, idp)) {
+            Log.d(TAG, "migrateGridIfNeeded: no grid migration needed");
             return true;
         }
-        String targetDbName = new DeviceGridState(mIdp).getDbFile();
+        String targetDbName = new DeviceGridState(idp).getDbFile();
         if (TextUtils.equals(targetDbName, mOpenHelper.getDatabaseName())) {
-            FileLog.e(TAG, "migrateGridIfNeeded: target db is same as current"
-                    + " current db: " + mOpenHelper.getDatabaseName()
-                    + " target db: " + targetDbName);
+            Log.e(TAG, "migrateGridIfNeeded: target db is same as current: " + targetDbName);
             return false;
         }
         DatabaseHelper oldHelper = mOpenHelper;
-        // We save the existing db's before creating the destination db helper so we know what logic
-        // to run in grid migration based on if that grid already existed before migration or not.
-        List existingDBs = LauncherFiles.GRID_DB_FILES.stream()
-                .filter(dbName -> mContext.getDatabasePath(dbName).exists())
-                .collect(Collectors.toList());
-        mOpenHelper = createDatabaseHelper(true /* forMigration */, targetDbName);
+        mOpenHelper = (mContext instanceof SandboxContext) ? oldHelper
+                : createDatabaseHelper(true /* forMigration */);
         try {
             // This is the current grid we have, given by the mContext
             DeviceGridState srcDeviceState = new DeviceGridState(mContext);
             // This is the state we want to migrate to that is given by the idp
-            DeviceGridState destDeviceState = new DeviceGridState(mIdp);
-            boolean isDestNewDb = !existingDBs.contains(destDeviceState.getDbFile());
-            return GridSizeMigrationDBController.migrateGridIfNeeded(mContext, srcDeviceState,
-                    destDeviceState, mOpenHelper, oldHelper.getWritableDatabase(), isDestNewDb,
-                    modelDelegate);
+            DeviceGridState destDeviceState = new DeviceGridState(idp);
+            return GridSizeMigrationUtil.migrateGridIfNeeded(mContext, srcDeviceState,
+                    destDeviceState, mOpenHelper, oldHelper.getWritableDatabase());
         } catch (Exception e) {
-            FileLog.e(TAG, "migrateGridIfNeeded: Failed to migrate grid", e);
+            FileLog.e(TAG, "Failed to migrate grid", e);
             return false;
         } finally {
             if (mOpenHelper != oldHelper) {
@@ -483,22 +346,22 @@ public class ModelDbController {
     }
 
     /**
-     * In case of migration failure, report metrics for the count of each itemType in the DB.
+     * In case of migration failure, report metrics for the count of each itemType
+     * in the DB.
+     * 
      * @param restoreEventLogger logger used to report Launcher restore metrics
      */
     private void sendMetricsForFailedMigration(LauncherRestoreEventLogger restoreEventLogger,
             SQLiteDatabase db) {
         try (Cursor cursor = db.rawQuery(
                 "SELECT itemType, COUNT(*) AS count FROM favorites GROUP BY itemType",
-                null
-        )) {
+                null)) {
             if (cursor.moveToFirst()) {
                 do {
                     restoreEventLogger.logFavoritesItemsRestoreFailed(
                             cursor.getInt(cursor.getColumnIndexOrThrow(ITEM_TYPE)),
                             cursor.getInt(cursor.getColumnIndexOrThrow("count")),
-                            RestoreError.GRID_MIGRATION_FAILURE
-                    );
+                            RestoreError.GRID_MIGRATION_FAILURE);
                 } while (cursor.moveToNext());
             }
         } catch (Exception e) {
@@ -520,6 +383,7 @@ public class ModelDbController {
 
     /**
      * Deletes any empty folder from the DB.
+     * 
      * @return Ids of deleted folders.
      */
     @WorkerThread
@@ -531,7 +395,7 @@ public class ModelDbController {
             // Select folders whose id do not match any container value.
             String selection = LauncherSettings.Favorites.ITEM_TYPE + " = "
                     + LauncherSettings.Favorites.ITEM_TYPE_FOLDER + " AND "
-                    + LauncherSettings.Favorites._ID +  " NOT IN (SELECT "
+                    + LauncherSettings.Favorites._ID + " NOT IN (SELECT "
                     + LauncherSettings.Favorites.CONTAINER + " FROM "
                     + Favorites.TABLE_NAME + ")";
 
@@ -551,6 +415,7 @@ public class ModelDbController {
 
     /**
      * Deletes any app pair that doesn't contain 2 member apps from the DB.
+     * 
      * @return Ids of deleted app pairs.
      */
     @WorkerThread
@@ -559,13 +424,13 @@ public class ModelDbController {
 
         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
         try (SQLiteTransaction t = new SQLiteTransaction(db)) {
-            // Select all entries with ITEM_TYPE = ITEM_TYPE_APP_PAIR whose id does not appear
+            // Select all entries with ITEM_TYPE = ITEM_TYPE_APP_PAIR whose id does not
+            // appear
             // exactly twice in the CONTAINER column.
-            String selection =
-                    ITEM_TYPE + " = " + ITEM_TYPE_APP_PAIR
-                            + " AND " + _ID +  " NOT IN"
-                            + " (SELECT " + CONTAINER + " FROM " + TABLE_NAME
-                            + " GROUP BY " + CONTAINER + " HAVING COUNT(*) = 2)";
+            String selection = ITEM_TYPE + " = " + ITEM_TYPE_APP_PAIR
+                    + " AND " + _ID + " NOT IN"
+                    + " (SELECT " + CONTAINER + " FROM " + TABLE_NAME
+                    + " GROUP BY " + CONTAINER + " HAVING COUNT(*) = 2)";
 
             IntArray appPairIds = LauncherDbUtils.queryIntArray(false, db, TABLE_NAME,
                     _ID, selection, null, null);
@@ -583,6 +448,7 @@ public class ModelDbController {
 
     /**
      * Deletes any app with a container id that doesn't exist.
+     * 
      * @return Ids of deleted apps.
      */
     @WorkerThread
@@ -592,10 +458,9 @@ public class ModelDbController {
         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
         try (SQLiteTransaction t = new SQLiteTransaction(db)) {
             // Select all entries whose container id does not appear in the database.
-            String selection =
-                    CONTAINER + " >= 0"
-                            + " AND " + CONTAINER + " NOT IN"
-                            + " (SELECT " + _ID + " FROM " + TABLE_NAME + ")";
+            String selection = CONTAINER + " >= 0"
+                    + " AND " + CONTAINER + " NOT IN"
+                    + " (SELECT " + _ID + " FROM " + TABLE_NAME + ")";
 
             IntArray appIds = LauncherDbUtils.queryIntArray(false, db, TABLE_NAME,
                     _ID, selection, null, null);
@@ -616,15 +481,15 @@ public class ModelDbController {
     }
 
     private void clearFlagEmptyDbCreated() {
-        mPrefs.removeSync(getEmptyDbCreatedKey());
+        LauncherPrefs.get(mContext).removeSync(getEmptyDbCreatedKey());
     }
 
     /**
      * Loads the default workspace based on the following priority scheme:
-     *   1) From the app restrictions
-     *   2) From a package provided by play store
-     *   3) From a partner configuration APK, already in the system image
-     *   4) The default configuration for the particular device
+     * 1) From the app restrictions
+     * 2) From a package provided by play store
+     * 3) From a partner configuration APK, already in the system image
+     * 4) The default configuration for the particular device
      */
     @WorkerThread
     public synchronized void loadDefaultFavoritesIfNecessary() {
@@ -634,20 +499,33 @@ public class ModelDbController {
             LawnchairAppKt.getLawnchairApp(mContext).cleanUpDatabases();
         }
 
-        if (mPrefs.get(getEmptyDbCreatedKey())) {
+        if (LauncherPrefs.get(mContext).get(getEmptyDbCreatedKey())) {
             Log.d(TAG, "loading default workspace");
 
             LauncherWidgetHolder widgetHolder = mOpenHelper.newLauncherWidgetHolder();
             try {
-                AutoInstallsLayout loader =
-                        mLayoutParserFactory.createExternalLayoutParser(widgetHolder, mOpenHelper);
+                AutoInstallsLayout loader = createWorkspaceLoaderFromAppRestriction(widgetHolder);
+                if (loader == null) {
+                    loader = AutoInstallsLayout.get(mContext, widgetHolder, mOpenHelper);
+                }
+                if (loader == null) {
+                    final Partner partner = Partner.get(mContext.getPackageManager());
+                    if (partner != null) {
+                        int workspaceResId = partner.getXmlResId(RES_PARTNER_DEFAULT_LAYOUT);
+                        if (workspaceResId != 0) {
+                            loader = new DefaultLayoutParser(mContext, widgetHolder,
+                                    mOpenHelper, partner.getResources(), workspaceResId);
+                        }
+                    }
+                }
 
                 final boolean usingExternallyProvidedLayout = loader != null;
                 if (loader == null) {
                     loader = getDefaultLayoutParser(widgetHolder);
                 }
 
-                // There might be some partially restored DB items, due to buggy restore logic in
+                // There might be some partially restored DB items, due to buggy restore logic
+                // in
                 // previous versions of launcher.
                 mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
                 // Populate favorites table with initial favorites
@@ -665,6 +543,64 @@ public class ModelDbController {
         }
     }
 
+    /**
+     * Creates workspace loader from an XML resource listed in the app restrictions.
+     *
+     * @return the loader if the restrictions are set and the resource exists; null
+     *         otherwise.
+     */
+    private AutoInstallsLayout createWorkspaceLoaderFromAppRestriction(
+            LauncherWidgetHolder widgetHolder) {
+        ContentResolver cr = mContext.getContentResolver();
+        String blobHandlerDigest = Settings.Secure.getString(cr, LAYOUT_DIGEST_KEY);
+        if (!TextUtils.isEmpty(blobHandlerDigest)) {
+            BlobStoreManager blobManager = mContext.getSystemService(BlobStoreManager.class);
+            try (InputStream in = new ParcelFileDescriptor.AutoCloseInputStream(
+                    blobManager.openBlob(BlobHandle.createWithSha256(
+                            Base64.decode(blobHandlerDigest, NO_WRAP | NO_PADDING),
+                            LAYOUT_DIGEST_LABEL, 0, LAYOUT_DIGEST_TAG)))) {
+                return getAutoInstallsLayoutFromIS(in, widgetHolder, new SourceResources() {
+                });
+            } catch (Exception e) {
+                Log.e(TAG, "Error getting layout from blob handle", e);
+                return null;
+            }
+        }
+
+        String authority = Settings.Secure.getString(cr, "launcher3.layout.provider");
+        if (TextUtils.isEmpty(authority)) {
+            return null;
+        }
+
+        PackageManager pm = mContext.getPackageManager();
+        ProviderInfo pi = pm.resolveContentProvider(authority, 0);
+        if (pi == null) {
+            Log.e(TAG, "No provider found for authority " + authority);
+            return null;
+        }
+        Uri uri = getLayoutUri(authority, mContext);
+        try (InputStream in = cr.openInputStream(uri)) {
+            Log.d(TAG, "Loading layout from " + authority);
+
+            Resources res = pm.getResourcesForApplication(pi.applicationInfo);
+            return getAutoInstallsLayoutFromIS(in, widgetHolder, SourceResources.wrap(res));
+        } catch (Exception e) {
+            Log.e(TAG, "Error getting layout stream from: " + authority, e);
+            return null;
+        }
+    }
+
+    private AutoInstallsLayout getAutoInstallsLayoutFromIS(InputStream in,
+            LauncherWidgetHolder widgetHolder, SourceResources res) throws Exception {
+        // Read the full xml so that we fail early in case of any IO error.
+        String layout = new String(IOUtils.toByteArray(in));
+        XmlPullParser parser = Xml.newPullParser();
+        parser.setInput(new StringReader(layout));
+
+        return new AutoInstallsLayout(mContext, widgetHolder, mOpenHelper, res,
+                () -> parser, AutoInstallsLayout.TAG_WORKSPACE);
+    }
+
     public static Uri getLayoutUri(String authority, Context ctx) {
         InvariantDeviceProfile grid = LauncherAppState.getIDP(ctx);
         return new Uri.Builder().scheme("content").authority(authority).path("launcher_layout")
@@ -676,9 +612,11 @@ public class ModelDbController {
     }
 
     private DefaultLayoutParser getDefaultLayoutParser(LauncherWidgetHolder widgetHolder) {
-        int defaultLayout = mIdp.demoModeLayoutId != 0
+        InvariantDeviceProfile idp = LauncherAppState.getIDP(mContext);
+        int defaultLayout = idp.demoModeLayoutId != 0
                 && mContext.getSystemService(UserManager.class).isDemoUser()
-                ? mIdp.demoModeLayoutId : mIdp.defaultLayoutId;
+                        ? idp.demoModeLayoutId
+                        : idp.defaultLayoutId;
 
         return new DefaultLayoutParser(mContext, widgetHolder,
                 mOpenHelper, mContext.getResources(), defaultLayout);
@@ -690,13 +628,20 @@ public class ModelDbController {
 
     /**
      * Re-composite given key in respect to database. If the current db is
-     * {@link LauncherFiles#LAUNCHER_DB}, return the key as-is. Otherwise append the db name to
-     * given key. e.g. consider key="EMPTY_DATABASE_CREATED", dbName="minimal.db", the returning
+     * {@link LauncherFiles#LAUNCHER_DB}, return the key as-is. Otherwise append the
+     * db name to
+     * given key. e.g. consider key="EMPTY_DATABASE_CREATED", dbName="minimal.db",
+     * the returning
      * string will be "EMPTY_DATABASE_CREATED@minimal.db".
      */
     private ConstantItem getEmptyDbCreatedKey(String dbName) {
+        if (mContext instanceof SandboxContext) {
+            return LauncherPrefs.nonRestorableItem(EMPTY_DATABASE_CREATED,
+                    false /* default value */, EncryptionType.ENCRYPTED);
+        }
         String key = TextUtils.equals(dbName, LauncherFiles.LAUNCHER_DB)
-                ? EMPTY_DATABASE_CREATED : EMPTY_DATABASE_CREATED + "@" + dbName;
+                ? EMPTY_DATABASE_CREATED
+                : EMPTY_DATABASE_CREATED + "@" + dbName;
         return LauncherPrefs.backedUpItem(key, false /* default value */, EncryptionType.ENCRYPTED);
     }
 
@@ -704,6 +649,6 @@ public class ModelDbController {
      * Returns the serial number for the provided user
      */
     public long getSerialNumberForUser(UserHandle user) {
-        return mUserCache.getSerialNumberForUser(user);
+        return UserCache.INSTANCE.get(mContext).getSerialNumberForUser(user);
     }
 }
diff --git a/src/com/android/launcher3/model/ModelDelegate.java b/src/com/android/launcher3/model/ModelDelegate.java
index 52a2188ca2..2264d35df1 100644
--- a/src/com/android/launcher3/model/ModelDelegate.java
+++ b/src/com/android/launcher3/model/ModelDelegate.java
@@ -23,45 +23,62 @@ import android.content.pm.ShortcutInfo;
 import androidx.annotation.NonNull;
 import androidx.annotation.WorkerThread;
 
-import com.android.launcher3.LauncherModel;
-import com.android.launcher3.dagger.ApplicationContext;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.R;
 import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.util.PackageManagerHelper;
+import com.android.launcher3.util.ResourceBasedOverride;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.Map;
 
-import javax.inject.Inject;
-
 /**
  * Class to extend LauncherModel functionality to provide extra data
  */
-public class ModelDelegate {
+public class ModelDelegate implements ResourceBasedOverride {
+
+    /**
+     * Creates and initializes a new instance of the delegate
+     */
+    public static ModelDelegate newInstance(
+            Context context, LauncherAppState app, PackageManagerHelper pmHelper,
+            AllAppsList appsList, BgDataModel dataModel, boolean isPrimaryInstance) {
+        ModelDelegate delegate = Overrides.getObject(
+                ModelDelegate.class, context, R.string.model_delegate_class);
+        delegate.init(app, pmHelper, appsList, dataModel, isPrimaryInstance);
+        return delegate;
+    }
 
     protected final Context mContext;
-    protected LauncherModel mModel;
+    protected PackageManagerHelper mPmHelper;
+    protected LauncherAppState mApp;
     protected AllAppsList mAppsList;
     protected BgDataModel mDataModel;
+    protected boolean mIsPrimaryInstance;
 
-    @Inject
-    public ModelDelegate(@ApplicationContext Context context) {
+    public ModelDelegate(Context context) {
         mContext = context;
     }
 
     /**
      * Initializes the object with the given params.
      */
-    public void init(LauncherModel model, AllAppsList appsList, BgDataModel dataModel) {
-        this.mModel = model;
+    private void init(LauncherAppState app, PackageManagerHelper pmHelper, AllAppsList appsList,
+            BgDataModel dataModel, boolean isPrimaryInstance) {
+        this.mApp = app;
+        this.mPmHelper = pmHelper;
         this.mAppsList = appsList;
         this.mDataModel = dataModel;
+        this.mIsPrimaryInstance = isPrimaryInstance;
     }
 
     /** Called periodically to validate and update any data */
     @WorkerThread
     public void validateData() {
-        if (hasShortcutsPermission(mContext) != mAppsList.hasShortcutHostPermission()) {
-            mModel.forceReload();
+        if (hasShortcutsPermission(mApp.getContext())
+                != mAppsList.hasShortcutHostPermission()) {
+            mApp.getModel().forceReload();
         }
     }
 
@@ -106,11 +123,6 @@ public class ModelDelegate {
     @WorkerThread
     public void modelLoadComplete() { }
 
-    /** Called when grid migration has completed as part of grid size refactor. */
-    @WorkerThread
-    public void gridMigrationComplete(
-            @NonNull DeviceGridState src, @NonNull DeviceGridState dest) { }
-
     /**
      * Called when the delegate is no loner needed
      */
diff --git a/src/com/android/launcher3/model/ModelLauncherCallbacks.kt b/src/com/android/launcher3/model/ModelLauncherCallbacks.kt
index 8b6c369e66..b12b2bc9d2 100644
--- a/src/com/android/launcher3/model/ModelLauncherCallbacks.kt
+++ b/src/com/android/launcher3/model/ModelLauncherCallbacks.kt
@@ -17,12 +17,10 @@
 package com.android.launcher3.model
 
 import android.content.pm.LauncherApps
-import android.content.pm.PackageInstaller.SessionInfo
 import android.content.pm.ShortcutInfo
 import android.os.UserHandle
 import android.text.TextUtils
 import com.android.launcher3.LauncherModel.ModelUpdateTask
-import com.android.launcher3.config.FeatureFlags
 import com.android.launcher3.logging.FileLog
 import com.android.launcher3.model.PackageUpdatedTask.OP_ADD
 import com.android.launcher3.model.PackageUpdatedTask.OP_REMOVE
@@ -30,9 +28,6 @@ import com.android.launcher3.model.PackageUpdatedTask.OP_SUSPEND
 import com.android.launcher3.model.PackageUpdatedTask.OP_UNAVAILABLE
 import com.android.launcher3.model.PackageUpdatedTask.OP_UNSUSPEND
 import com.android.launcher3.model.PackageUpdatedTask.OP_UPDATE
-import com.android.launcher3.pm.InstallSessionTracker
-import com.android.launcher3.pm.PackageInstallInfo
-import com.android.launcher3.util.PackageUserKey
 import java.util.function.Consumer
 
 /**
@@ -40,10 +35,9 @@ import java.util.function.Consumer
  * model tasks
  */
 class ModelLauncherCallbacks(private var taskExecutor: Consumer) :
-    LauncherApps.Callback(), InstallSessionTracker.Callback {
+    LauncherApps.Callback() {
 
     override fun onPackageAdded(packageName: String, user: UserHandle) {
-        FileLog.d(TAG, "onPackageAdded triggered for packageName=$packageName, user=$user")
         taskExecutor.accept(PackageUpdatedTask(OP_ADD, user, packageName))
     }
 
@@ -54,20 +48,20 @@ class ModelLauncherCallbacks(private var taskExecutor: Consumer
     override fun onPackageLoadingProgressChanged(
         packageName: String,
         user: UserHandle,
-        progress: Float,
+        progress: Float
     ) {
         taskExecutor.accept(PackageIncrementalDownloadUpdatedTask(packageName, user, progress))
     }
 
     override fun onPackageRemoved(packageName: String, user: UserHandle) {
-        FileLog.d(TAG, "onPackageRemoved triggered for packageName=$packageName, user=$user")
+        FileLog.d(TAG, "package removed received $packageName")
         taskExecutor.accept(PackageUpdatedTask(OP_REMOVE, user, packageName))
     }
 
     override fun onPackagesAvailable(
         vararg packageNames: String,
         user: UserHandle,
-        replacing: Boolean,
+        replacing: Boolean
     ) {
         taskExecutor.accept(PackageUpdatedTask(OP_UPDATE, user, *packageNames))
     }
@@ -79,7 +73,7 @@ class ModelLauncherCallbacks(private var taskExecutor: Consumer
     override fun onPackagesUnavailable(
         packageNames: Array,
         user: UserHandle,
-        replacing: Boolean,
+        replacing: Boolean
     ) {
         if (!replacing) {
             taskExecutor.accept(PackageUpdatedTask(OP_UNAVAILABLE, user, *packageNames))
@@ -93,7 +87,7 @@ class ModelLauncherCallbacks(private var taskExecutor: Consumer
     override fun onShortcutsChanged(
         packageName: String,
         shortcuts: MutableList,
-        user: UserHandle,
+        user: UserHandle
     ) {
         taskExecutor.accept(ShortcutsChangedTask(packageName, shortcuts, user, true))
     }
@@ -103,37 +97,6 @@ class ModelLauncherCallbacks(private var taskExecutor: Consumer
         taskExecutor.accept(PackageUpdatedTask(OP_REMOVE, user, *packages.toTypedArray()))
     }
 
-    override fun onSessionFailure(packageName: String, user: UserHandle) {
-        taskExecutor.accept(SessionFailureTask(packageName, user))
-    }
-
-    override fun onPackageStateChanged(installInfo: PackageInstallInfo) {
-        taskExecutor.accept(PackageInstallStateChangedTask(installInfo))
-    }
-
-    override fun onUpdateSessionDisplay(key: PackageUserKey, info: SessionInfo) {
-        /** Updates the icons and label of all pending icons for the provided package name. */
-        taskExecutor.accept { controller, _, _ ->
-            controller.iconCache.updateSessionCache(key, info)
-        }
-        taskExecutor.accept(
-            CacheDataUpdatedTask(
-                CacheDataUpdatedTask.OP_SESSION_UPDATE,
-                key.mUser,
-                hashSetOf(key.mPackageName),
-            )
-        )
-    }
-
-    override fun onInstallSessionCreated(sessionInfo: PackageInstallInfo) {
-        if (FeatureFlags.PROMISE_APPS_IN_ALL_APPS.get()) {
-            taskExecutor.accept { taskController, _, apps ->
-                apps.addPromiseApp(taskController.context, sessionInfo)
-                taskController.bindApplicationsIfNeeded()
-            }
-        }
-    }
-
     companion object {
         private const val TAG = "LauncherAppsCallbackImpl"
     }
diff --git a/src/com/android/launcher3/model/ModelTaskController.kt b/src/com/android/launcher3/model/ModelTaskController.kt
index 95794b8539..7693217676 100644
--- a/src/com/android/launcher3/model/ModelTaskController.kt
+++ b/src/com/android/launcher3/model/ModelTaskController.kt
@@ -16,35 +16,28 @@
 
 package com.android.launcher3.model
 
-import android.content.Context
+import com.android.launcher3.LauncherAppState
 import com.android.launcher3.LauncherModel
 import com.android.launcher3.LauncherModel.CallbackTask
 import com.android.launcher3.celllayout.CellPosMapper
-import com.android.launcher3.dagger.ApplicationContext
-import com.android.launcher3.icons.IconCache
 import com.android.launcher3.model.BgDataModel.FixedContainerItems
 import com.android.launcher3.model.data.ItemInfo
-import com.android.launcher3.util.Executors.MAIN_EXECUTOR
+import com.android.launcher3.model.data.WorkspaceItemInfo
 import com.android.launcher3.util.PackageUserKey
-import com.android.launcher3.widget.model.WidgetsListBaseEntriesBuilder
 import java.util.Objects
+import java.util.concurrent.Executor
 import java.util.function.Predicate
 import java.util.stream.Collectors
-import javax.inject.Inject
 
 /** Class with utility methods and properties for running a LauncherModel Task */
-class ModelTaskController
-@Inject
-constructor(
-    @ApplicationContext val context: Context,
-    val iconCache: IconCache,
+class ModelTaskController(
+    val app: LauncherAppState,
     val dataModel: BgDataModel,
     val allAppsList: AllAppsList,
-    val model: LauncherModel,
+    private val model: LauncherModel,
+    private val uiExecutor: Executor
 ) {
 
-    private val uiExecutor = MAIN_EXECUTOR
-
     /** Schedules a {@param task} to be executed on the current callbacks. */
     fun scheduleCallbackTask(task: CallbackTask) {
         for (cb in model.callbacks) {
@@ -58,17 +51,17 @@ constructor(
      */
     fun getModelWriter() = model.getWriter(false /* verifyChanges */, CellPosMapper.DEFAULT, null)
 
-    fun bindUpdatedWorkspaceItems(allUpdates: Collection) {
+    fun bindUpdatedWorkspaceItems(allUpdates: List) {
         // Bind workspace items
-        val workspaceUpdates = allUpdates.filter { it.id != ItemInfo.NO_ID }.toSet()
+        val workspaceUpdates: MutableList = allUpdates.stream().filter { info -> info.id != ItemInfo.NO_ID }.collect(Collectors.toList())
         if (workspaceUpdates.isNotEmpty()) {
-            scheduleCallbackTask { it.bindItemsUpdated(workspaceUpdates) }
+            scheduleCallbackTask { it.bindWorkspaceItemsChanged(workspaceUpdates) }
         }
 
         // Bind extra items if any
         allUpdates
             .stream()
-            .mapToInt { it.container }
+            .mapToInt { info: WorkspaceItemInfo -> info.container }
             .distinct()
             .mapToObj { dataModel.extraItems.get(it) }
             .filter { Objects.nonNull(it) }
@@ -85,10 +78,8 @@ constructor(
     }
 
     fun bindUpdatedWidgets(dataModel: BgDataModel) {
-        val allWidgets =
-            WidgetsListBaseEntriesBuilder(context)
-                .build(dataModel.widgetsModel.widgetsByPackageItemForPicker)
-        scheduleCallbackTask { it.bindAllWidgets(allWidgets) }
+        val widgets = dataModel.widgetsModel.getWidgetsListForPicker(app.context)
+        scheduleCallbackTask { it.bindAllWidgets(widgets) }
     }
 
     fun deleteAndBindComponentsRemoved(matcher: Predicate, reason: String?) {
@@ -105,7 +96,7 @@ constructor(
             val packageUserKeyToUidMap =
                 apps.associateBy(
                     keySelector = { PackageUserKey(it.componentName!!.packageName, it.user) },
-                    valueTransform = { it.uid },
+                    valueTransform = { it.uid }
                 )
             scheduleCallbackTask { it.bindAllApplications(apps, flags, packageUserKeyToUidMap) }
         }
diff --git a/src/com/android/launcher3/model/ModelUtils.java b/src/com/android/launcher3/model/ModelUtils.java
index da79982e00..9e72e2823e 100644
--- a/src/com/android/launcher3/model/ModelUtils.java
+++ b/src/com/android/launcher3/model/ModelUtils.java
@@ -15,15 +15,15 @@
  */
 package com.android.launcher3.model;
 
-import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
-import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
-import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
-import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
-
+import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.IntSet;
 
-import java.util.function.Predicate;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.IntStream;
 
 /**
  * Utils class for {@link com.android.launcher3.LauncherModel}.
@@ -31,17 +31,54 @@ import java.util.function.Predicate;
 public class ModelUtils {
 
     /**
-     * Returns a filter for items on hotseat or current screens
+     * Filters the set of items who are directly or indirectly (via another container) on the
+     * specified screen.
      */
-    public static Predicate currentScreenContentFilter(IntSet currentScreenIds) {
-        return item -> item.container == CONTAINER_HOTSEAT
-                || (item.container == CONTAINER_DESKTOP
-                        && currentScreenIds.contains(item.screenId));
+    public static  void filterCurrentWorkspaceItems(
+            final IntSet currentScreenIds,
+            List allWorkspaceItems,
+            List currentScreenItems,
+            List otherScreenItems) {
+        // Purge any null ItemInfos
+        allWorkspaceItems.removeIf(Objects::isNull);
+        // Order the set of items by their containers first, this allows use to walk through the
+        // list sequentially, build up a list of containers that are in the specified screen,
+        // as well as all items in those containers.
+        IntSet itemsOnScreen = new IntSet();
+        Collections.sort(allWorkspaceItems,
+                (lhs, rhs) -> Integer.compare(lhs.container, rhs.container));
+        for (T info : allWorkspaceItems) {
+            if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+                if (currentScreenIds.contains(info.screenId)) {
+                    currentScreenItems.add(info);
+                    itemsOnScreen.add(info.id);
+                } else {
+                    otherScreenItems.add(info);
+                }
+            } else if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+                currentScreenItems.add(info);
+                itemsOnScreen.add(info.id);
+            } else {
+                if (itemsOnScreen.contains(info.container)) {
+                    currentScreenItems.add(info);
+                    itemsOnScreen.add(info.id);
+                } else {
+                    otherScreenItems.add(info);
+                }
+            }
+        }
     }
 
     /**
-     * Returns a filter for widget items
+     * Iterates though current workspace items and returns available hotseat ranks for prediction.
      */
-    public static final Predicate WIDGET_FILTER = item ->
-            item.itemType == ITEM_TYPE_APPWIDGET || item.itemType == ITEM_TYPE_CUSTOM_APPWIDGET;
+    public static IntArray getMissingHotseatRanks(List items, int len) {
+        IntSet seen = new IntSet();
+        items.stream().filter(
+                info -> info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT)
+                .forEach(i -> seen.add(i.screenId));
+        IntArray result = new IntArray(len);
+        IntStream.range(0, len).filter(i -> !seen.contains(i)).forEach(result::add);
+        return result;
+    }
 }
diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java
index 7b588bbdba..bbf555443c 100644
--- a/src/com/android/launcher3/model/ModelWriter.java
+++ b/src/com/android/launcher3/model/ModelWriter.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3.model;
 
+import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
 import static com.android.launcher3.provider.LauncherDbUtils.itemIdMatch;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
@@ -153,6 +154,35 @@ public class ModelWriter {
             throw e;
         }
     }
+    
+    /**
+     * Clears all views from the home screen.
+     */
+    public boolean clearAllHomeScreenViewsByType(int type) {
+        final ArrayList itemsToRemove = new ArrayList<>();
+
+        for (ItemInfo item : mBgDataModel.itemsIdMap) {
+            if (item.container == type) {
+                itemsToRemove.add(item);
+            }
+        }
+
+        if (itemsToRemove.isEmpty()) {
+            return false;
+        }
+
+        enqueueDeleteRunnable(newModelTask(() -> {
+            final ModelDbController db = mModel.getModelDbController();
+
+            for (ItemInfo item : itemsToRemove) {
+                db.delete(TABLE_NAME, itemIdMatch(item.id), null);
+                mBgDataModel.removeItem(mContext, item);
+            }
+        }));
+
+        mModel.forceReload();
+        return true;
+    }
 
     /**
      * Move an item in the DB to a new 
@@ -229,7 +259,7 @@ public class ModelWriter {
         }).executeOnModelThread();
     }
 
-    public void notifyItemModified(ItemInfo item) {
+    private void notifyItemModified(ItemInfo item) {
         notifyOtherCallbacks(c -> c.bindItemsModified(Collections.singletonList(item)));
     }
 
@@ -255,7 +285,7 @@ public class ModelWriter {
             item.onAddToDatabase(writer);
             writer.put(Favorites._ID, item.id);
 
-            mModel.getModelDbController().insert(writer.getValues(mContext));
+            mModel.getModelDbController().insert(Favorites.TABLE_NAME, writer.getValues(mContext));
             synchronized (mBgDataModel) {
                 checkItemInfoLocked(item.id, item, stackTrace);
                 mBgDataModel.addItem(mContext, item, true);
@@ -295,7 +325,7 @@ public class ModelWriter {
         notifyDelete(items);
         enqueueDeleteRunnable(newModelTask(() -> {
             for (ItemInfo item : items) {
-                mModel.getModelDbController().delete(itemIdMatch(item.id), null);
+                mModel.getModelDbController().delete(TABLE_NAME, itemIdMatch(item.id), null);
                 mBgDataModel.removeItem(mContext, item);
                 verifier.verifyModel();
             }
@@ -310,12 +340,12 @@ public class ModelWriter {
         notifyDelete(Collections.singleton(info));
 
         enqueueDeleteRunnable(newModelTask(() -> {
-            mModel.getModelDbController().delete(
+            mModel.getModelDbController().delete(Favorites.TABLE_NAME,
                     Favorites.CONTAINER + "=" + info.id, null);
             mBgDataModel.removeItem(mContext, info.getContents());
             info.getContents().clear();
 
-            mModel.getModelDbController().delete(
+            mModel.getModelDbController().delete(Favorites.TABLE_NAME,
                     Favorites._ID + "=" + info.id, null);
             mBgDataModel.removeItem(mContext, info);
             verifier.verifyModel();
@@ -421,7 +451,7 @@ public class ModelWriter {
         @Override
         public void runImpl() {
             mModel.getModelDbController().update(
-                    mWriter.get().getValues(mContext), itemIdMatch(mItemId), null);
+                    TABLE_NAME, mWriter.get().getValues(mContext), itemIdMatch(mItemId), null);
             updateItemArrays(mItem, mItemId);
         }
     }
@@ -443,7 +473,7 @@ public class ModelWriter {
                     ItemInfo item = mItems.get(i);
                     final int itemId = item.id;
                     mModel.getModelDbController().update(
-                            mValues.get(i), itemIdMatch(itemId), null);
+                            TABLE_NAME, mValues.get(i), itemIdMatch(itemId), null);
                     updateItemArrays(item, itemId);
                 }
                 t.commit();
@@ -469,14 +499,37 @@ public class ModelWriter {
                 if (item.container != Favorites.CONTAINER_DESKTOP &&
                         item.container != Favorites.CONTAINER_HOTSEAT) {
                     // Item is in a collection, make sure this collection exists
-                    if (!(mBgDataModel.itemsIdMap.get(item.container) instanceof CollectionInfo)) {
+                    if (!mBgDataModel.collections.containsKey(item.container)) {
                         // An items container is being set to a that of an item which is not in
-                        // the list of collections.
+                        // the list of Folders.
                         String msg = "item: " + item + " container being set to: " +
                                 item.container + ", not in the list of collections";
                         Log.e(TAG, msg);
                     }
                 }
+
+                // Items are added/removed from the corresponding FolderInfo elsewhere, such
+                // as in Workspace.onDrop. Here, we just add/remove them from the list of items
+                // that are on the desktop, as appropriate
+                ItemInfo modelItem = mBgDataModel.itemsIdMap.get(itemId);
+                if (modelItem != null &&
+                        (modelItem.container == Favorites.CONTAINER_DESKTOP ||
+                                modelItem.container == Favorites.CONTAINER_HOTSEAT)) {
+                    switch (modelItem.itemType) {
+                        case Favorites.ITEM_TYPE_APPLICATION:
+                        case Favorites.ITEM_TYPE_DEEP_SHORTCUT:
+                        case Favorites.ITEM_TYPE_FOLDER:
+                        case Favorites.ITEM_TYPE_APP_PAIR:
+                            if (!mBgDataModel.workspaceItems.contains(modelItem)) {
+                                mBgDataModel.workspaceItems.add(modelItem);
+                            }
+                            break;
+                        default:
+                            break;
+                    }
+                } else {
+                    mBgDataModel.workspaceItems.remove(modelItem);
+                }
                 mVerifier.verifyModel();
             }
         }
diff --git a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
index a3561eda4d..d2382132b3 100644
--- a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
+++ b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
@@ -15,8 +15,6 @@
  */
 package com.android.launcher3.model;
 
-import static com.android.launcher3.model.ModelUtils.WIDGET_FILTER;
-
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
@@ -52,11 +50,11 @@ public class PackageInstallStateChangedTask implements ModelUpdateTask {
             try {
                 // For instant apps we do not get package-add. Use setting events to update
                 // any pinned icons.
-                Context context = taskController.getContext();
+                Context context = taskController.getApp().getContext();
                 ApplicationInfo ai = context
                         .getPackageManager().getApplicationInfo(mInstallInfo.packageName, 0);
                 if (InstantAppResolver.newInstance(context).isInstantApp(ai)) {
-                    taskController.getModel().newModelCallbacks()
+                    taskController.getApp().getModel().newModelCallbacks()
                             .onPackageAdded(ai.packageName, mInstallInfo.user);
                 }
             } catch (PackageManager.NameNotFoundException e) {
@@ -87,19 +85,16 @@ public class PackageInstallStateChangedTask implements ModelUpdateTask {
                 }
             });
 
-            dataModel.itemsIdMap.stream()
-                    .filter(WIDGET_FILTER)
-                    .filter(item -> mInstallInfo.user.equals(item.user))
-                    .map(item -> (LauncherAppWidgetInfo) item)
-                    .filter(widget -> widget.providerName.getPackageName()
-                            .equals(mInstallInfo.packageName))
-                    .forEach(widget -> {
-                        widget.installProgress = mInstallInfo.progress;
-                        updates.add(widget);
-                    });
+            for (LauncherAppWidgetInfo widget : dataModel.appWidgets) {
+                if (widget.providerName.getPackageName().equals(mInstallInfo.packageName)) {
+                    widget.installProgress = mInstallInfo.progress;
+                    updates.add(widget);
+                }
+            }
 
             if (!updates.isEmpty()) {
-                taskController.bindUpdatedWorkspaceItems(updates);
+                taskController.scheduleCallbackTask(
+                        callbacks -> callbacks.bindRestoreItemsChange(updates));
             }
         }
     }
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index 10666c8a9c..999aba4517 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -15,13 +15,10 @@
  */
 package com.android.launcher3.model;
 
-import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_WORK_PROFILE_QUIET_MODE_ENABLED;
-import static com.android.launcher3.model.ModelUtils.WIDGET_FILTER;
 import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_ARCHIVED;
-import static com.android.launcher3.model.data.LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
 import static com.android.launcher3.model.data.WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON;
 import static com.android.launcher3.model.data.WorkspaceItemInfo.FLAG_RESTORED_ICON;
 
@@ -39,7 +36,9 @@ import android.util.Log;
 import androidx.annotation.NonNull;
 
 import com.android.launcher3.Flags;
+import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherModel.ModelUpdateTask;
+import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.R;
 import com.android.launcher3.config.FeatureFlags;
@@ -52,7 +51,6 @@ import com.android.launcher3.pm.PackageInstallInfo;
 import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.shortcuts.ShortcutRequest;
 import com.android.launcher3.util.ApiWrapper;
-import com.android.launcher3.util.ApplicationInfoWrapper;
 import com.android.launcher3.util.FlagOp;
 import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.ItemInfoMatcher;
@@ -104,7 +102,7 @@ public class PackageUpdatedTask implements ModelUpdateTask {
     private final String[] mPackages;
 
     public PackageUpdatedTask(final int op, @NonNull final UserHandle user,
-            @NonNull final String... packages) {
+                              @NonNull final String... packages) {
         mOp = op;
         mUser = user;
         mPackages = packages;
@@ -112,12 +110,13 @@ public class PackageUpdatedTask implements ModelUpdateTask {
 
     @Override
     public void execute(@NonNull ModelTaskController taskController, @NonNull BgDataModel dataModel,
-            @NonNull AllAppsList appsList) {
-        final Context context = taskController.getContext();
-        final IconCache iconCache = taskController.getIconCache();
+                        @NonNull AllAppsList appsList) {
+        final LauncherAppState app = taskController.getApp();
+        final Context context = app.getContext();
+        final IconCache iconCache = app.getIconCache();
 
         final String[] packages = mPackages;
-        final int packageCount = packages.length;
+        final int N = packages.length;
         final FlagOp flagOp;
         final HashSet packageSet = new HashSet<>(Arrays.asList(packages));
         final Predicate matcher = mOp == OP_USER_AVAILABILITY_CHANGE
@@ -129,12 +128,11 @@ public class PackageUpdatedTask implements ModelUpdateTask {
 
         if (DEBUG) {
             Log.d(TAG, "Package updated: mOp=" + getOpString()
-                    + " packages=" + Arrays.toString(packages)
-                    + ", user=" + mUser);
+                    + " packages=" + Arrays.toString(packages));
         }
         switch (mOp) {
             case OP_ADD: {
-                for (int i = 0; i < packageCount; i++) {
+                for (int i = 0; i < N; i++) {
                     iconCache.updateIconsForPkg(packages[i], mUser);
                     if (FeatureFlags.PROMISE_APPS_IN_ALL_APPS.get()) {
                         if (DEBUG) {
@@ -157,7 +155,7 @@ public class PackageUpdatedTask implements ModelUpdateTask {
                             + " Look for earlier AllAppsList logs to find more information.");
                     removedComponents.add(a.componentName);
                 })) {
-                    for (int i = 0; i < packageCount; i++) {
+                    for (int i = 0; i < N; i++) {
                         iconCache.updateIconsForPkg(packages[i], mUser);
                         activitiesLists.put(packages[i],
                                 appsList.updatePackage(context, packages[i], mUser));
@@ -167,7 +165,7 @@ public class PackageUpdatedTask implements ModelUpdateTask {
                 flagOp = FlagOp.NO_OP.removeFlag(WorkspaceItemInfo.FLAG_DISABLED_NOT_AVAILABLE);
                 break;
             case OP_REMOVE: {
-                for (int i = 0; i < packageCount; i++) {
+                for (int i = 0; i < N; i++) {
                     iconCache.removeIconsForPkg(packages[i], mUser);
                     PreferenceManager pm = PreferenceManager.getInstance(context);
                     if (packages[i].equals(pm.getIconPackPackage().get())) {
@@ -184,8 +182,7 @@ public class PackageUpdatedTask implements ModelUpdateTask {
                                     new Intent(context.getResources().getString(R.string.icon_packs_intent_name)),
                                     PackageManager.GET_RESOLVED_FILTER)
                             .stream().map(it -> it.activityInfo.packageName)
-                            .noneMatch(it -> new ApplicationInfoWrapper(context, it, mUser)
-                                .isInstalled());
+                            .noneMatch(it -> packageManagerHelper.isAppInstalled(it, mUser));
                     if (isThemedIconsAvailable) {
                         pm.getThemedIcons().set(false);
                     }
@@ -193,7 +190,7 @@ public class PackageUpdatedTask implements ModelUpdateTask {
                 // Fall through
             }
             case OP_UNAVAILABLE:
-                for (int i = 0; i < packageCount; i++) {
+                for (int i = 0; i < N; i++) {
                     if (DEBUG) {
                         Log.d(TAG, getOpString() + ": removing package=" + packages[i]);
                     }
@@ -242,58 +239,50 @@ public class PackageUpdatedTask implements ModelUpdateTask {
 
         // Update shortcut infos
         if (mOp == OP_ADD || flagOp != FlagOp.NO_OP) {
-            final ArrayList updatedWorkspaceItems = new ArrayList<>();
+            final ArrayList updatedWorkspaceItems = new ArrayList<>();
+            final ArrayList widgets = new ArrayList<>();
 
             // For system apps, package manager send OP_UPDATE when an app is enabled.
             final boolean isNewApkAvailable = mOp == OP_ADD || mOp == OP_UPDATE;
             synchronized (dataModel) {
-                dataModel.forAllWorkspaceItemInfos(mUser, itemInfo -> {
+                dataModel.forAllWorkspaceItemInfos(mUser, si -> {
 
                     boolean infoUpdated = false;
                     boolean shortcutUpdated = false;
 
-                    ComponentName cn = itemInfo.getTargetComponent();
-                    if (cn != null && matcher.test(itemInfo)) {
+                    ComponentName cn = si.getTargetComponent();
+                    if (cn != null && matcher.test(si)) {
                         String packageName = cn.getPackageName();
 
-                        if (itemInfo.hasStatusFlag(WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI)) {
-                            forceKeepShortcuts.add(itemInfo.id);
+                        if (si.hasStatusFlag(WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI)) {
+                            forceKeepShortcuts.add(si.id);
                             if (mOp == OP_REMOVE) {
                                 return;
                             }
                         }
 
-                        if (itemInfo.isPromise() && isNewApkAvailable) {
+                        if (si.isPromise() && isNewApkAvailable) {
                             boolean isTargetValid = !cn.getClassName().equals(
                                     IconCache.EMPTY_CLASS_NAME);
-                            if (itemInfo.itemType == ITEM_TYPE_DEEP_SHORTCUT) {
-                                int requestQuery = ShortcutRequest.PINNED;
-                                if (Flags.restoreArchivedShortcuts()) {
-                                    // Avoid race condition where shortcut service has no record of
-                                    // unarchived shortcut being pinned after restore.
-                                    // Launcher should be source-of-truth for if shortcut is pinned.
-                                    requestQuery = ShortcutRequest.ALL;
-                                }
+                            if (si.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
                                 List shortcut =
                                         new ShortcutRequest(context, mUser)
                                                 .forPackage(cn.getPackageName(),
-                                                        itemInfo.getDeepShortcutId())
-                                                .query(requestQuery);
+                                                        si.getDeepShortcutId())
+                                                .query(ShortcutRequest.PINNED);
                                 if (shortcut.isEmpty()) {
                                     isTargetValid = false;
                                     if (DEBUG) {
-                                        Log.d(TAG, "Shortcut not found for updated"
-                                                + " package=" + itemInfo.getTargetPackage()
-                                                + ", isArchived=" + itemInfo.isArchived());
+                                        Log.d(TAG, "Pinned Shortcut not found for updated"
+                                                + " package=" + si.getTargetPackage());
                                     }
                                 } else {
                                     if (DEBUG) {
-                                        Log.d(TAG, "Found shortcut for updated"
-                                                + " package=" + itemInfo.getTargetPackage()
-                                                + ", isTargetValid=" + isTargetValid
-                                                + ", isArchived=" + itemInfo.isArchived());
+                                        Log.d(TAG, "Found pinned shortcut for updated"
+                                                + " package=" + si.getTargetPackage()
+                                                + ", isTargetValid=" + isTargetValid);
                                     }
-                                    itemInfo.updateFromDeepShortcutInfo(shortcut.get(0), context);
+                                    si.updateFromDeepShortcutInfo(shortcut.get(0), context);
                                     infoUpdated = true;
                                 }
                             } else if (isTargetValid) {
@@ -301,39 +290,39 @@ public class PackageUpdatedTask implements ModelUpdateTask {
                                         .isActivityEnabled(cn, mUser);
                             }
 
-                            if (!isTargetValid && (itemInfo.hasStatusFlag(
+                            if (!isTargetValid && (si.hasStatusFlag(
                                     FLAG_RESTORED_ICON | FLAG_AUTOINSTALL_ICON)
-                                    || itemInfo.isArchived())) {
-                                if (updateWorkspaceItemIntent(context, itemInfo, packageName)) {
+                                    || si.isArchived())) {
+                                if (updateWorkspaceItemIntent(context, si, packageName)) {
                                     infoUpdated = true;
-                                } else if (shouldRemoveRestoredShortcut(itemInfo)) {
-                                    removedShortcuts.add(itemInfo.id);
+                                } else if (si.hasPromiseIconUi()) {
+                                    removedShortcuts.add(si.id);
                                     if (DEBUG) {
                                         FileLog.w(TAG, "Removing restored shortcut promise icon"
                                                 + " that no longer points to valid component."
-                                                + " id=" + itemInfo.id
-                                                + ", package=" + itemInfo.getTargetPackage()
-                                                + ", status=" + itemInfo.status
-                                                + ", isArchived=" + itemInfo.isArchived());
+                                                + " id=" + si.id
+                                                + ", package=" + si.getTargetPackage()
+                                                + ", status=" + si.status
+                                                + ", isArchived=" + si.isArchived());
                                     }
                                     return;
                                 }
                             } else if (!isTargetValid) {
-                                removedShortcuts.add(itemInfo.id);
+                                removedShortcuts.add(si.id);
                                 if (DEBUG) {
                                     FileLog.w(TAG, "Removing shortcut that no longer points to"
                                             + " valid component."
-                                            + " id=" + itemInfo.id
-                                            + " package=" + itemInfo.getTargetPackage()
-                                            + " status=" + itemInfo.status);
+                                            + " id=" + si.id
+                                            + " package=" + si.getTargetPackage()
+                                            + " status=" + si.status);
                                 }
                                 return;
                             } else {
-                                itemInfo.status = WorkspaceItemInfo.DEFAULT;
+                                si.status = WorkspaceItemInfo.DEFAULT;
                                 infoUpdated = true;
                             }
                         } else if (isNewApkAvailable && removedComponents.contains(cn)) {
-                            if (updateWorkspaceItemIntent(context, itemInfo, packageName)) {
+                            if (updateWorkspaceItemIntent(context, si, packageName)) {
                                 infoUpdated = true;
                             }
                         }
@@ -343,72 +332,70 @@ public class PackageUpdatedTask implements ModelUpdateTask {
                                     packageName);
                             // TODO: See if we can migrate this to
                             //  AppInfo#updateRuntimeFlagsForActivityTarget
-                            itemInfo.setProgressLevel(
+                            si.setProgressLevel(
                                     activities == null || activities.isEmpty()
                                             ? 100
                                             : PackageManagerHelper.getLoadingProgress(
-                                                    activities.get(0)),
+                                            activities.get(0)),
                                     PackageInstallInfo.STATUS_INSTALLED_DOWNLOADING);
                             // In case an app is archived, we need to make sure that archived state
                             // in WorkspaceItemInfo is refreshed.
                             try {
                                 if (Flags.enableSupportForArchiving() && !activities.isEmpty()) {
-                                boolean newArchivalState = activities.get(0)
-                                        .getActivityInfo().isArchived;
-                                if (newArchivalState != itemInfo.isArchived()) {
-                                    itemInfo.runtimeStatusFlags ^= FLAG_ARCHIVED;
+                                    boolean newArchivalState = activities.get(
+                                            0).getActivityInfo().isArchived;
+                                    if (newArchivalState != si.isArchived()) {
+                                        si.runtimeStatusFlags ^= FLAG_ARCHIVED;
                                         infoUpdated = true;
                                     }
                                 }
                             } catch (Throwable t) {
-                                // LC-Ignored
+                                // ignore
                             }
 
-                            if (itemInfo.itemType == Favorites.ITEM_TYPE_APPLICATION) {
+                            if (si.itemType == Favorites.ITEM_TYPE_APPLICATION) {
                                 if (activities != null && !activities.isEmpty()) {
-                                    itemInfo.setNonResizeable(ApiWrapper.INSTANCE.get(context)
+                                    si.setNonResizeable(ApiWrapper.INSTANCE.get(context)
                                             .isNonResizeableActivity(activities.get(0)));
                                 }
-                                iconCache.getTitleAndIcon(
-                                        itemInfo, itemInfo.getMatchingLookupFlag());
+                                iconCache.getTitleAndIcon(si, si.usingLowResIcon());
                                 infoUpdated = true;
                             }
                         }
 
-                        int oldRuntimeFlags = itemInfo.runtimeStatusFlags;
-                        itemInfo.runtimeStatusFlags = flagOp.apply(itemInfo.runtimeStatusFlags);
-                        if (itemInfo.runtimeStatusFlags != oldRuntimeFlags) {
+                        int oldRuntimeFlags = si.runtimeStatusFlags;
+                        si.runtimeStatusFlags = flagOp.apply(si.runtimeStatusFlags);
+                        if (si.runtimeStatusFlags != oldRuntimeFlags) {
                             shortcutUpdated = true;
                         }
                     }
 
                     if (infoUpdated || shortcutUpdated) {
-                        updatedWorkspaceItems.add(itemInfo);
+                        updatedWorkspaceItems.add(si);
                     }
-                    if (infoUpdated && itemInfo.id != ItemInfo.NO_ID) {
-                        taskController.getModelWriter().updateItemInDatabase(itemInfo);
+                    if (infoUpdated && si.id != ItemInfo.NO_ID) {
+                        taskController.getModelWriter().updateItemInDatabase(si);
                     }
                 });
 
-                dataModel.itemsIdMap.stream()
-                        .filter(WIDGET_FILTER)
-                        .filter(item -> mUser.equals(item.user))
-                        .map(item -> (LauncherAppWidgetInfo) item)
-                        .filter(widget -> widget.hasRestoreFlag(FLAG_PROVIDER_NOT_READY)
-                                && packageSet.contains(widget.providerName.getPackageName()))
-                        .forEach(widgetInfo -> {
-                            widgetInfo.restoreStatus &=
-                                    ~FLAG_PROVIDER_NOT_READY
-                                            & ~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
+                for (LauncherAppWidgetInfo widgetInfo : dataModel.appWidgets) {
+                    if (mUser.equals(widgetInfo.user)
+                            && widgetInfo.hasRestoreFlag(
+                            LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)
+                            && packageSet.contains(widgetInfo.providerName.getPackageName())) {
+                        widgetInfo.restoreStatus &=
+                                ~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY
+                                        & ~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
 
-                            // adding this flag ensures that launcher shows 'click to setup'
-                            // if the widget has a config activity. In case there is no config
-                            // activity, it will be marked as 'restored' during bind.
-                            widgetInfo.restoreStatus |= LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
-                            widgetInfo.installProgress = 100;
-                            updatedWorkspaceItems.add(widgetInfo);
-                            taskController.getModelWriter().updateItemInDatabase(widgetInfo);
-                        });
+                        // adding this flag ensures that launcher shows 'click to setup'
+                        // if the widget has a config activity. In case there is no config
+                        // activity, it will be marked as 'restored' during bind.
+                        widgetInfo.restoreStatus |= LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
+
+                        widgets.add(widgetInfo);
+                        taskController.getModelWriter().updateItemInDatabase(widgetInfo);
+                    }
+                }
             }
 
             taskController.bindUpdatedWorkspaceItems(updatedWorkspaceItems);
@@ -418,6 +405,10 @@ public class PackageUpdatedTask implements ModelUpdateTask {
                         "removing shortcuts with invalid target components."
                                 + " ids=" + removedShortcuts);
             }
+
+            if (!widgets.isEmpty()) {
+                taskController.scheduleCallbackTask(c -> c.bindWidgetsRestored(widgets));
+            }
         }
 
         final HashSet removedPackages = new HashSet<>();
@@ -433,7 +424,7 @@ public class PackageUpdatedTask implements ModelUpdateTask {
         } else if (mOp == OP_UPDATE) {
             // Mark disabled packages in the broadcast to be removed
             final LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
-            for (int i = 0; i < packageCount; i++) {
+            for (int i=0; i "NONE";
diff --git a/src/com/android/launcher3/model/SdCardAvailableReceiver.java b/src/com/android/launcher3/model/SdCardAvailableReceiver.java
index 9ae8092345..529331675b 100644
--- a/src/com/android/launcher3/model/SdCardAvailableReceiver.java
+++ b/src/com/android/launcher3/model/SdCardAvailableReceiver.java
@@ -22,8 +22,9 @@ import android.content.Intent;
 import android.content.pm.LauncherApps;
 import android.os.UserHandle;
 
+import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherModel;
-import com.android.launcher3.util.ApplicationInfoWrapper;
+import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.PackageUserKey;
 
 import java.util.ArrayList;
@@ -42,16 +43,16 @@ public class SdCardAvailableReceiver extends BroadcastReceiver {
     private final Context mContext;
     private final Set mPackages;
 
-    public SdCardAvailableReceiver(
-            Context context, LauncherModel model, Set packages) {
-        mContext = context;
-        mModel = model;
+    public SdCardAvailableReceiver(LauncherAppState app, Set packages) {
+        mModel = app.getModel();
+        mContext = app.getContext();
         mPackages = packages;
     }
 
     @Override
     public void onReceive(Context context, Intent intent) {
         final LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
+        final PackageManagerHelper pmHelper = PackageManagerHelper.INSTANCE.get(context);
         for (PackageUserKey puk : mPackages) {
             UserHandle user = puk.mUser;
 
@@ -59,7 +60,7 @@ public class SdCardAvailableReceiver extends BroadcastReceiver {
             final ArrayList packagesUnavailable = new ArrayList<>();
 
             if (!launcherApps.isPackageEnabled(puk.mPackageName, user)) {
-                if (new ApplicationInfoWrapper(context, puk.mPackageName, user).isOnSdCard()) {
+                if (pmHelper.isAppOnSdcard(puk.mPackageName, user)) {
                     packagesUnavailable.add(puk.mPackageName);
                 } else {
                     packagesRemoved.add(puk.mPackageName);
diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.java b/src/com/android/launcher3/model/ShortcutsChangedTask.java
new file mode 100644
index 0000000000..1916d23b67
--- /dev/null
+++ b/src/com/android/launcher3/model/ShortcutsChangedTask.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.model;
+
+import android.content.Context;
+import android.content.pm.ShortcutInfo;
+import android.os.UserHandle;
+
+import androidx.annotation.NonNull;
+
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel.ModelUpdateTask;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.shortcuts.ShortcutRequest;
+import com.android.launcher3.util.ItemInfoMatcher;
+import com.android.launcher3.util.PackageManagerHelper;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Handles changes due to shortcut manager updates (deep shortcut changes)
+ */
+public class ShortcutsChangedTask implements ModelUpdateTask {
+
+    @NonNull
+    private final String mPackageName;
+
+    @NonNull
+    private final List mShortcuts;
+
+    @NonNull
+    private final UserHandle mUser;
+
+    private final boolean mUpdateIdMap;
+
+    public ShortcutsChangedTask(@NonNull final String packageName,
+            @NonNull final List shortcuts, @NonNull final UserHandle user,
+            final boolean updateIdMap) {
+        mPackageName = packageName;
+        mShortcuts = shortcuts;
+        mUser = user;
+        mUpdateIdMap = updateIdMap;
+    }
+
+    @Override
+    public void execute(@NonNull ModelTaskController taskController, @NonNull BgDataModel dataModel,
+            @NonNull AllAppsList apps) {
+        final LauncherAppState app = taskController.getApp();
+        final Context context = app.getContext();
+        // Find WorkspaceItemInfo's that have changed on the workspace.
+        ArrayList matchingWorkspaceItems = new ArrayList<>();
+
+        synchronized (dataModel) {
+            dataModel.forAllWorkspaceItemInfos(mUser, si -> {
+                if ((si.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT)
+                        && mPackageName.equals(si.getIntent().getPackage())) {
+                    matchingWorkspaceItems.add(si);
+                }
+            });
+        }
+
+        if (!matchingWorkspaceItems.isEmpty()) {
+            if (mShortcuts.isEmpty()) {
+                PackageManagerHelper packageManagerHelper =
+                        PackageManagerHelper.INSTANCE.get(context);
+                // Verify that the app is indeed installed.
+                if (!packageManagerHelper.isAppInstalled(mPackageName, mUser)
+                        && !packageManagerHelper.isAppArchivedForUser(mPackageName, mUser)) {
+                    // App is not installed or archived, ignoring package events
+                    return;
+                }
+            }
+            // Update the workspace to reflect the changes to updated shortcuts residing on it.
+            List allLauncherKnownIds = matchingWorkspaceItems.stream()
+                    .map(WorkspaceItemInfo::getDeepShortcutId)
+                    .distinct()
+                    .collect(Collectors.toList());
+            List shortcuts = new ShortcutRequest(context, mUser)
+                    .forPackage(mPackageName, allLauncherKnownIds)
+                    .query(ShortcutRequest.ALL);
+
+            Set nonPinnedIds = new HashSet<>(allLauncherKnownIds);
+            ArrayList updatedWorkspaceItemInfos = new ArrayList<>();
+            for (ShortcutInfo fullDetails : shortcuts) {
+                if (!fullDetails.isPinned()) {
+                    continue;
+                }
+
+                String sid = fullDetails.getId();
+                nonPinnedIds.remove(sid);
+                matchingWorkspaceItems
+                        .stream()
+                        .filter(itemInfo -> sid.equals(itemInfo.getDeepShortcutId()))
+                        .forEach(workspaceItemInfo -> {
+                            workspaceItemInfo.updateFromDeepShortcutInfo(fullDetails, context);
+                            app.getIconCache().getShortcutIcon(workspaceItemInfo, fullDetails);
+                            updatedWorkspaceItemInfos.add(workspaceItemInfo);
+                        });
+            }
+
+            taskController.bindUpdatedWorkspaceItems(updatedWorkspaceItemInfos);
+            if (!nonPinnedIds.isEmpty()) {
+                taskController.deleteAndBindComponentsRemoved(ItemInfoMatcher.ofShortcutKeys(
+                        nonPinnedIds.stream()
+                                .map(id -> new ShortcutKey(mPackageName, mUser, id))
+                                .collect(Collectors.toSet())),
+                        "removed because the shortcut is no longer available in shortcut service");
+            }
+        }
+
+        if (mUpdateIdMap) {
+            // Update the deep shortcut map if the list of ids has changed for an activity.
+            dataModel.updateDeepShortcutCounts(mPackageName, mUser, mShortcuts);
+            taskController.bindDeepShortcuts(dataModel);
+        }
+    }
+}
diff --git a/src/com/android/launcher3/model/UserLockStateChangedTask.java b/src/com/android/launcher3/model/UserLockStateChangedTask.java
index 8b12115273..722d7bbf2b 100644
--- a/src/com/android/launcher3/model/UserLockStateChangedTask.java
+++ b/src/com/android/launcher3/model/UserLockStateChangedTask.java
@@ -24,6 +24,7 @@ import android.util.Log;
 
 import androidx.annotation.NonNull;
 
+import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherModel.ModelUpdateTask;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -55,7 +56,8 @@ public class UserLockStateChangedTask implements ModelUpdateTask {
     @Override
     public void execute(@NonNull ModelTaskController taskController, @NonNull BgDataModel dataModel,
             @NonNull AllAppsList apps) {
-        Context context = taskController.getContext();
+        LauncherAppState app = taskController.getApp();
+        Context context = app.getContext();
 
         HashMap pinnedShortcuts = new HashMap<>();
         if (mIsUserUnlocked) {
@@ -91,7 +93,7 @@ public class UserLockStateChangedTask implements ModelUpdateTask {
                         }
                         si.runtimeStatusFlags &= ~FLAG_DISABLED_LOCKED_USER;
                         si.updateFromDeepShortcutInfo(shortcut, context);
-                        taskController.getIconCache().getShortcutIcon(si, shortcut);
+                        app.getIconCache().getShortcutIcon(si, shortcut);
                     } else {
                         si.runtimeStatusFlags |= FLAG_DISABLED_LOCKED_USER;
                     }
diff --git a/src/com/android/launcher3/model/WidgetItem.java b/src/com/android/launcher3/model/WidgetItem.java
index 21ab4f7a28..846bf7fd74 100644
--- a/src/com/android/launcher3/model/WidgetItem.java
+++ b/src/com/android/launcher3/model/WidgetItem.java
@@ -1,10 +1,22 @@
 package com.android.launcher3.model;
 
+import static android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN;
+import static android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD;
+import static android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX;
+
 import static com.android.launcher3.Utilities.ATLEAST_S;
 
+import android.annotation.SuppressLint;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.util.SparseArray;
+import android.widget.RemoteViews;
 
+import androidx.core.os.BuildCompat;
+
+import com.android.launcher3.Flags;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.icons.BitmapInfo;
@@ -12,6 +24,7 @@ import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.pm.ShortcutConfigActivityInfo;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.widget.WidgetManagerHelper;
 
 /**
  * An wrapper over various items displayed in a widget picker,
@@ -27,9 +40,11 @@ public class WidgetItem extends ComponentKey {
     public final String label;
     public final CharSequence description;
     public final int spanX, spanY;
+    public final SparseArray generatedPreviews;
 
     public WidgetItem(LauncherAppWidgetProviderInfo info,
-            InvariantDeviceProfile idp, IconCache iconCache, Context context) {
+            InvariantDeviceProfile idp, IconCache iconCache, Context context,
+            WidgetManagerHelper helper) {
         super(info.provider, info.getProfile());
 
         label = iconCache.getTitleNoCache(info);
@@ -39,16 +54,38 @@ public class WidgetItem extends ComponentKey {
 
         spanX = Math.min(info.spanX, idp.numColumns);
         spanY = Math.min(info.spanY, idp.numRows);
+
+        if (BuildCompat.isAtLeastV()) {
+            generatedPreviews = new SparseArray<>(3);
+            for (int widgetCategory : new int[] {
+                    WIDGET_CATEGORY_HOME_SCREEN,
+                    WIDGET_CATEGORY_KEYGUARD,
+                    WIDGET_CATEGORY_SEARCHBOX,
+            }) {
+                if ((widgetCategory & widgetInfo.generatedPreviewCategories) != 0) {
+                    generatedPreviews.put(widgetCategory,
+                            helper.loadGeneratedPreview(widgetInfo, widgetCategory));
+                }
+            }
+        } else {
+            generatedPreviews = null;
+        }
     }
 
-    public WidgetItem(ShortcutConfigActivityInfo info, IconCache iconCache) {
+    public WidgetItem(LauncherAppWidgetProviderInfo info,
+            InvariantDeviceProfile idp, IconCache iconCache, Context context) {
+        this(info, idp, iconCache, context, new WidgetManagerHelper(context));
+    }
+
+    public WidgetItem(ShortcutConfigActivityInfo info, IconCache iconCache, PackageManager pm) {
         super(info.getComponent(), info.getUser());
         label = info.isPersistable() ? iconCache.getTitleNoCache(info) :
-                Utilities.trim(info.getLabel());
+                Utilities.trim(info.getLabel(pm));
         description = null;
         widgetInfo = null;
         activityInfo = info;
         spanX = spanY = 1;
+        generatedPreviews = null;
     }
 
     /**
@@ -67,8 +104,26 @@ public class WidgetItem extends ComponentKey {
         return false;
     }
 
+    /** Returns whether this {@link WidgetItem} has a preview layout that can be used. */
+    @SuppressLint("NewApi") // Already added API check.
+    public boolean hasPreviewLayout() {
+        return ATLEAST_S && widgetInfo != null && widgetInfo.previewLayout != Resources.ID_NULL;
+    }
+
     /** Returns whether this {@link WidgetItem} is for a shortcut rather than an app widget. */
     public boolean isShortcut() {
         return activityInfo != null;
     }
+
+    /**
+     * Returns whether this {@link WidgetItem} has a generated preview for the given widget
+     * category.
+     */
+    public boolean hasGeneratedPreview(int widgetCategory) {
+        if (!Utilities.ATLEAST_V || generatedPreviews == null) {
+            return false;
+        }
+        return generatedPreviews.contains(widgetCategory)
+                && generatedPreviews.get(widgetCategory) != null;
+    }
 }
diff --git a/src/com/android/launcher3/model/WidgetsModel.java b/src/com/android/launcher3/model/WidgetsModel.java
index aaf36df2b6..34fdc1c17c 100644
--- a/src/com/android/launcher3/model/WidgetsModel.java
+++ b/src/com/android/launcher3/model/WidgetsModel.java
@@ -4,7 +4,6 @@ package com.android.launcher3.model;
 import static android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_HIDE_FROM_PICKER;
 
 import static com.android.launcher3.BuildConfigs.WIDGETS_ENABLED;
-import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG;
 import static com.android.launcher3.pm.ShortcutConfigActivityInfo.queryList;
 import static com.android.launcher3.widget.WidgetSections.NO_CATEGORY;
 
@@ -15,6 +14,7 @@ import static java.util.stream.Collectors.toList;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.os.UserHandle;
 import android.util.Log;
 import android.util.Pair;
@@ -27,10 +27,10 @@ import com.android.launcher3.BuildConfig;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.compat.AlphabeticIndexCompat;
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.dagger.ApplicationContext;
+import com.android.launcher3.icons.ComponentWithLabelAndIcon;
 import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.icons.cache.CachedObject;
 import com.android.launcher3.model.data.PackageItemInfo;
 import com.android.launcher3.pm.ShortcutConfigActivityInfo;
 import com.android.launcher3.util.ComponentKey;
@@ -40,6 +40,9 @@ import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.widget.WidgetManagerHelper;
 import com.android.launcher3.widget.WidgetSections;
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
+import com.android.launcher3.widget.model.WidgetsListContentEntry;
+import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
 import com.android.wm.shell.Flags;
 
 import java.util.ArrayList;
@@ -52,13 +55,9 @@ import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
-import java.util.function.Function;
 import java.util.function.Predicate;
-import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
-import javax.inject.Inject;
-
 import app.lawnchair.preferences2.PreferenceManager2;
 
 /**
@@ -72,119 +71,124 @@ public class WidgetsModel {
     private static final boolean DEBUG = false;
 
     /* Map of widgets and shortcuts that are tracked per package. */
-    private final Map> mWidgetsByPackageItem = new HashMap<>();
-    @Nullable private WidgetValidityCheckForPicker mWidgetValidityCheckForPicker = null;
+    private final Map> mWidgetsList = new HashMap<>();
 
-    private static Context mContext = null;
-    private final InvariantDeviceProfile mIdp;
-    private final IconCache mIconCache;
-    private final AppFilter mAppFilter;
+    /**
+     * Returns a list of {@link WidgetsListBaseEntry} filtered using given widget item filter. All
+     * {@link WidgetItem}s in a single row are sorted (based on label and user), but the overall
+     * list of {@link WidgetsListBaseEntry}s is not sorted.
+     *
+     * @see com.android.launcher3.widget.picker.WidgetsListAdapter#setWidgets(List)
+     */
+    public synchronized ArrayList getFilteredWidgetsListForPicker(
+            Context context,
+            Predicate widgetItemFilter) {
+        if (!WIDGETS_ENABLED) {
+            return new ArrayList<>();
+        }
+        ArrayList result = new ArrayList<>();
+        AlphabeticIndexCompat indexer = new AlphabeticIndexCompat(context);
 
-    @Inject
-    public WidgetsModel(
-            @ApplicationContext Context context,
-            InvariantDeviceProfile idp,
-            IconCache iconCache,
-            AppFilter appFilter) {
-        mContext = context;
-        mIdp = idp;
-        mIconCache = iconCache;
-        mAppFilter = appFilter;
-    }
-
-    public WidgetsModel(Context context) {
-        this(context,
-                LauncherAppState.getIDP(context),
-                LauncherAppState.getInstance(context).getIconCache(), new AppFilter(context));
+        for (Map.Entry> entry : mWidgetsList.entrySet()) {
+            PackageItemInfo pkgItem = entry.getKey();
+            Stream widgetItems = entry.getValue()
+                    .stream()
+                    .filter(widgetItemFilter);
+            List widgetItemsList = widgetItems.collect(toList());
+            if (!widgetItemsList.isEmpty()) {
+                String sectionName = (pkgItem.title == null) ? "" :
+                        indexer.computeSectionName(pkgItem.title);
+                result.add(WidgetsListHeaderEntry.create(pkgItem, sectionName, widgetItemsList));
+                result.add(new WidgetsListContentEntry(pkgItem, sectionName, widgetItemsList));
+            }
+        }
+        return result;
     }
 
     /**
-     * Returns all widgets keyed by their component key.
+     * Returns a list of {@link WidgetsListBaseEntry}. All {@link WidgetItem} in a single row
+     * are sorted (based on label and user), but the overall list of
+     * {@link WidgetsListBaseEntry}s is not sorted.
+     *
+     * @see com.android.launcher3.widget.picker.WidgetsListAdapter#setWidgets(List)
      */
-    public synchronized Map getWidgetsByComponentKey() {
+    public synchronized ArrayList getWidgetsListForPicker(Context context) {
+        // return all items
+        return getFilteredWidgetsListForPicker(context, /*widgetItemFilter=*/ item -> true);
+    }
+
+    /** Returns a mapping of packages to their widgets without static shortcuts. */
+    public synchronized Map> getAllWidgetsWithoutShortcuts() {
         if (!WIDGETS_ENABLED) {
             return Collections.emptyMap();
         }
-        return mWidgetsByPackageItem.values().stream()
-                .flatMap(Collection::stream).distinct()
-                .collect(Collectors.toMap(
-                        widget -> new ComponentKey(widget.componentName, widget.user),
-                        Function.identity()
-                ));
+        Map> packagesToWidgets = new HashMap<>();
+        mWidgetsList.forEach((packageItemInfo, widgetsAndShortcuts) -> {
+            List widgets = widgetsAndShortcuts.stream()
+                    .filter(item -> item.widgetInfo != null)
+                    .collect(toList());
+            if (widgets.size() > 0) {
+                packagesToWidgets.put(
+                        new PackageUserKey(packageItemInfo.packageName, packageItemInfo.user),
+                        widgets);
+            }
+        });
+        return packagesToWidgets;
     }
 
     /**
-     * Returns widgets (eligible for display in picker) keyed by their component key.
+     * Returns a map of widget component keys to corresponding widget items. Excludes the
+     * shortcuts.
      */
-    public synchronized Map getWidgetsByComponentKeyForPicker() {
-        if (!WIDGETS_ENABLED || mWidgetValidityCheckForPicker == null) {
+    public synchronized Map getAllWidgetComponentsWithoutShortcuts() {
+        if (!WIDGETS_ENABLED) {
             return Collections.emptyMap();
         }
-
-        return mWidgetsByPackageItem.values().stream()
-                .flatMap(Collection::stream).distinct()
-                .filter(widgetItem -> mWidgetValidityCheckForPicker.test(widgetItem))
-                .collect(Collectors.toMap(
-                        widget -> new ComponentKey(widget.componentName, widget.user),
-                        Function.identity()
-                ));
-    }
-
-    /**
-     * Returns widgets (displayable in the widget picker) grouped by the package item that
-     * they should belong to.
-     */
-    public synchronized Map> getWidgetsByPackageItemForPicker() {
-        if (!WIDGETS_ENABLED || mWidgetValidityCheckForPicker == null) {
-            return Collections.emptyMap();
-        }
-
-        return mWidgetsByPackageItem.entrySet().stream()
-                .collect(
-                        Collectors.toMap(
-                                Entry::getKey,
-                                entry -> entry.getValue().stream()
-                                        .filter(widgetItem ->
-                                                mWidgetValidityCheckForPicker.test(widgetItem))
-                                        .collect(toList())
-                        )
-                )
-                .entrySet().stream()
-                .filter(entry -> !entry.getValue().isEmpty())
-                .collect(Collectors.toMap(Entry::getKey, Entry::getValue));
+        Map widgetsMap = new HashMap<>();
+        mWidgetsList.forEach((packageItemInfo, widgetsAndShortcuts) ->
+                widgetsAndShortcuts.stream().filter(item -> item.widgetInfo != null).forEach(
+                        item -> widgetsMap.put(new ComponentKey(item.componentName, item.user),
+                                item)));
+        return widgetsMap;
     }
 
     /**
      * @param packageUser If null, all widgets and shortcuts are updated and returned, otherwise
      *                    only widgets and shortcuts associated with the package/user are.
      */
-    public List update(@Nullable PackageUserKey packageUser) {
+    public List update(
+            LauncherAppState app, @Nullable PackageUserKey packageUser) {
         if (!WIDGETS_ENABLED) {
-            return new ArrayList<>();
+            return Collections.emptyList();
         }
         Preconditions.assertWorkerThread();
 
+        Context context = app.getContext();
         final ArrayList widgetsAndShortcuts = new ArrayList<>();
-        List updatedItems = new ArrayList<>();
+        List updatedItems = new ArrayList<>();
         try {
+            InvariantDeviceProfile idp = app.getInvariantDeviceProfile();
+            PackageManager pm = app.getContext().getPackageManager();
+
             // Widgets
-            WidgetManagerHelper widgetManager = new WidgetManagerHelper(mContext);
+            WidgetManagerHelper widgetManager = new WidgetManagerHelper(context);
             for (AppWidgetProviderInfo widgetInfo : widgetManager.getAllProviders(packageUser)) {
                 LauncherAppWidgetProviderInfo launcherWidgetInfo =
-                        LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, widgetInfo);
+                        LauncherAppWidgetProviderInfo.fromProviderInfo(context, widgetInfo);
 
                 widgetsAndShortcuts.add(new WidgetItem(
-                        launcherWidgetInfo, mIdp, mIconCache, mContext));
+                        launcherWidgetInfo, idp, app.getIconCache(), app.getContext(),
+                        widgetManager));
                 updatedItems.add(launcherWidgetInfo);
             }
 
             // Shortcuts
             for (ShortcutConfigActivityInfo info :
-                    queryList(mContext, packageUser)) {
-                widgetsAndShortcuts.add(new WidgetItem(info, mIconCache));
+                    queryList(context, packageUser)) {
+                widgetsAndShortcuts.add(new WidgetItem(info, app.getIconCache(), pm));
                 updatedItems.add(info);
             }
-            setWidgetsAndShortcuts(widgetsAndShortcuts, packageUser);
+            setWidgetsAndShortcuts(widgetsAndShortcuts, app, packageUser);
         } catch (Exception e) {
             if (!FeatureFlags.IS_STUDIO_BUILD && Utilities.isBinderSizeError(e)) {
                 // the returned value may be incomplete and will not be refreshed until the next
@@ -199,45 +203,46 @@ public class WidgetsModel {
         return updatedItems;
     }
 
-    private synchronized void setWidgetsAndShortcuts(
-            ArrayList rawWidgetsShortcuts, @Nullable PackageUserKey packageUser) {
+    private synchronized void setWidgetsAndShortcuts(ArrayList rawWidgetsShortcuts,
+            LauncherAppState app, @Nullable PackageUserKey packageUser) {
         if (DEBUG) {
             Log.d(TAG, "addWidgetsAndShortcuts, widgetsShortcuts#=" + rawWidgetsShortcuts.size());
         }
 
-        // Refresh the validity checker with latest app state.
-        mWidgetValidityCheckForPicker = new WidgetValidityCheckForPicker(mIdp, mAppFilter);
-
         // Temporary cache for {@link PackageItemInfos} to avoid having to go through
         // {@link mPackageItemInfos} to locate the key to be used for {@link #mWidgetsList}
         PackageItemInfoCache packageItemInfoCache = new PackageItemInfoCache();
 
         if (packageUser == null) {
             // Clear the list if this is an update on all widgets and shortcuts.
-            mWidgetsByPackageItem.clear();
+            mWidgetsList.clear();
         } else {
             // Otherwise, only clear the widgets and shortcuts for the changed package.
-            mWidgetsByPackageItem.remove(packageItemInfoCache.getOrCreate(packageUser));
+            mWidgetsList.remove(packageItemInfoCache.getOrCreate(packageUser));
         }
 
         // add and update.
-        mWidgetsByPackageItem.putAll(rawWidgetsShortcuts.stream()
+        mWidgetsList.putAll(rawWidgetsShortcuts.stream()
+                .filter(new WidgetValidityCheck(app))
                 .filter(new WidgetFlagCheck())
-                .flatMap(widgetItem -> getPackageUserKeys(mContext, widgetItem).stream()
+                .flatMap(widgetItem -> getPackageUserKeys(app.getContext(), widgetItem).stream()
                         .map(key -> new Pair<>(packageItemInfoCache.getOrCreate(key), widgetItem)))
                 .collect(groupingBy(pair -> pair.first, mapping(pair -> pair.second, toList()))));
 
         // Update each package entry
+        IconCache iconCache = app.getIconCache();
         for (PackageItemInfo p : packageItemInfoCache.values()) {
-            mIconCache.getTitleAndIconForApp(p, DEFAULT_LOOKUP_FLAG.withUseLowRes());
+            iconCache.getTitleAndIconForApp(p, true /* userLowResIcon */);
         }
     }
 
-    public void onPackageIconsUpdated(Set packageNames, UserHandle user) {
+    public void onPackageIconsUpdated(Set packageNames, UserHandle user,
+            LauncherAppState app) {
         if (!WIDGETS_ENABLED) {
             return;
         }
-        for (Entry> entry : mWidgetsByPackageItem.entrySet()) {
+        WidgetManagerHelper widgetManager = new WidgetManagerHelper(app.getContext());
+        for (Entry> entry : mWidgetsList.entrySet()) {
             if (packageNames.contains(entry.getKey().packageName)) {
                 List items = entry.getValue();
                 int count = items.size();
@@ -245,10 +250,12 @@ public class WidgetsModel {
                     WidgetItem item = items.get(i);
                     if (item.user.equals(user)) {
                         if (item.activityInfo != null) {
-                            items.set(i, new WidgetItem(item.activityInfo, mIconCache));
+                            items.set(i, new WidgetItem(item.activityInfo, app.getIconCache(),
+                                    app.getContext().getPackageManager()));
                         } else {
-                            items.set(i, new WidgetItem(
-                                    item.widgetInfo, mIdp, mIconCache, mContext));
+                            items.set(i, new WidgetItem(item.widgetInfo,
+                                    app.getInvariantDeviceProfile(), app.getIconCache(),
+                                    app.getContext(), widgetManager));
                         }
                     }
                 }
@@ -256,6 +263,50 @@ public class WidgetsModel {
         }
     }
 
+    private PackageItemInfo createPackageItemInfo(
+            ComponentName providerName,
+            UserHandle user,
+            int category
+    ) {
+        if (category == NO_CATEGORY) {
+            return new PackageItemInfo(providerName.getPackageName(), user);
+        } else {
+            return new PackageItemInfo("" , category, user);
+        }
+    }
+
+    private IntSet getCategories(ComponentName providerName, Context context) {
+        IntSet categories = WidgetSections.getWidgetsToCategory(context).get(providerName);
+        if (categories != null) {
+            return categories;
+        }
+        categories = new IntSet();
+        categories.add(NO_CATEGORY);
+        return categories;
+    }
+
+    public WidgetItem getWidgetProviderInfoByProviderName(
+            ComponentName providerName, UserHandle user, Context context) {
+        if (!WIDGETS_ENABLED) {
+            return null;
+        }
+        IntSet categories = getCategories(providerName, context);
+
+        // Checking if we have a provider in any of the categories.
+        for (Integer category: categories) {
+            PackageItemInfo key = createPackageItemInfo(providerName, user, category);
+            List widgets = mWidgetsList.get(key);
+            if (widgets != null) {
+                return widgets.stream().filter(
+                                item -> item.componentName.equals(providerName)
+                        )
+                        .findFirst()
+                        .orElse(null);
+            }
+        }
+        return null;
+    }
+
     /** Returns {@link PackageItemInfo} of a pending widget. */
     public static PackageItemInfo newPendingItemInfo(Context context, ComponentName provider,
             UserHandle user) {
@@ -293,19 +344,16 @@ public class WidgetsModel {
         return packageUserKeys;
     }
 
-    /**
-     * Checks if widgets are eligible for displaying in widget picker / tray.
-     */
-    private static class WidgetValidityCheckForPicker implements Predicate {
+    private static class WidgetValidityCheck implements Predicate {
 
         private final InvariantDeviceProfile mIdp;
         private final AppFilter mAppFilter;
         private PreferenceManager2 prefs;
 
-        WidgetValidityCheckForPicker(InvariantDeviceProfile idp, AppFilter appFilter) {
-            mIdp = idp;
-            mAppFilter = appFilter;
-            prefs = PreferenceManager2.getInstance(mContext);
+        WidgetValidityCheck(LauncherAppState app) {
+            mIdp = app.getInvariantDeviceProfile();
+            mAppFilter = new AppFilter(app.getContext());
+            prefs = PreferenceManager2.getInstance(app.getContext());
         }
 
         @Override
@@ -341,10 +389,6 @@ public class WidgetsModel {
         }
     }
 
-    /**
-     * Checks if certain widgets that are available behind flag can be used across all surfaces in
-     * launcher.
-     */
     private static class WidgetFlagCheck implements Predicate {
 
         private static final String BUBBLES_SHORTCUT_WIDGET =
@@ -368,7 +412,7 @@ public class WidgetsModel {
             if (pInfo == null) {
                 pInfo = new PackageItemInfo(key.mPackageName, key.mWidgetCategory, key.mUser);
                 pInfo.user = key.mUser;
-                mMap.put(key, pInfo);
+                mMap.put(key,  pInfo);
             }
             return pInfo;
         }
diff --git a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
index 7c22fbaee7..1926f9650f 100644
--- a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
+++ b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
@@ -18,10 +18,8 @@ package com.android.launcher3.model
 import android.annotation.SuppressLint
 import android.appwidget.AppWidgetProviderInfo
 import android.content.ComponentName
-import android.content.Context
 import android.content.Intent
 import android.content.pm.LauncherApps
-import android.content.pm.LauncherApps.ShortcutQuery
 import android.content.pm.PackageInstaller
 import android.content.pm.ShortcutInfo
 import android.graphics.Point
@@ -30,12 +28,11 @@ import android.util.Log
 import android.util.LongSparseArray
 import com.android.launcher3.Flags
 import com.android.launcher3.InvariantDeviceProfile
+import com.android.launcher3.LauncherAppState
 import com.android.launcher3.LauncherSettings.Favorites
-import com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RestoreError
 import com.android.launcher3.Utilities
-import com.android.launcher3.icons.CacheableShortcutInfo
-import com.android.launcher3.icons.IconCache
-import com.android.launcher3.icons.cache.CacheLookupFlag.Companion.DEFAULT_LOOKUP_FLAG
+import com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RestoreError
+import com.android.launcher3.config.FeatureFlags
 import com.android.launcher3.logging.FileLog
 import com.android.launcher3.model.data.AppInfo
 import com.android.launcher3.model.data.AppPairInfo
@@ -47,9 +44,7 @@ import com.android.launcher3.model.data.WorkspaceItemInfo
 import com.android.launcher3.pm.PackageInstallInfo
 import com.android.launcher3.pm.UserCache
 import com.android.launcher3.shortcuts.ShortcutKey
-import com.android.launcher3.shortcuts.ShortcutRequest
 import com.android.launcher3.util.ApiWrapper
-import com.android.launcher3.util.ApplicationInfoWrapper
 import com.android.launcher3.util.ComponentKey
 import com.android.launcher3.util.PackageManagerHelper
 import com.android.launcher3.util.PackageUserKey
@@ -72,10 +67,7 @@ class WorkspaceItemProcessor(
     private val launcherApps: LauncherApps,
     private val pendingPackages: MutableSet,
     private val shortcutKeyToPinnedShortcuts: Map,
-    private val context: Context,
-    private val idp: InvariantDeviceProfile,
-    private val iconCache: IconCache,
-    private val isSafeMode: Boolean,
+    private val app: LauncherAppState,
     private val bgDataModel: BgDataModel,
     private val widgetProvidersMap: MutableMap,
     private val installingPkgs: HashMap,
@@ -84,10 +76,12 @@ class WorkspaceItemProcessor(
     private val pmHelper: PackageManagerHelper,
     private val iconRequestInfos: MutableList>,
     private val unlockedUsers: LongSparseArray,
-    private val allDeepShortcuts: MutableList,
+    private val allDeepShortcuts: MutableList
 ) {
 
+    private val isSafeMode = app.isSafeModeEnabled
     private val tempPackageKey = PackageUserKey(null, null)
+    private val iconCache = app.iconCache
 
     /**
      * This is the entry point for processing 1 workspace item. This method is like the midfielder
@@ -103,7 +97,7 @@ class WorkspaceItemProcessor(
                 // User has been deleted, remove the item.
                 c.markDeleted(
                     "User has been deleted for item id=${c.id}",
-                    RestoreError.PROFILE_DELETED,
+                    RestoreError.PROFILE_DELETED
                 )
                 return
             }
@@ -146,7 +140,7 @@ class WorkspaceItemProcessor(
         var allowMissingTarget = false
         var intent = c.parseIntent()
         if (intent == null) {
-            c.markDeleted("Null intent from db for item id=${c.id}", RestoreError.APP_NO_DB_INTENT)
+            c.markDeleted("Null intent from db for item id=${c.id}", RestoreError.MISSING_INFO)
             return
         }
         var disabledState =
@@ -156,13 +150,9 @@ class WorkspaceItemProcessor(
         val cn = intent.component
         val targetPkg = cn?.packageName ?: intent.getPackage()
         if (targetPkg.isNullOrEmpty()) {
-            c.markDeleted(
-                "No target package for item id=${c.id}",
-                RestoreError.APP_NO_TARGET_PACKAGE,
-            )
+            c.markDeleted("No target package for item id=${c.id}", RestoreError.MISSING_INFO)
             return
         }
-        val appInfoWrapper = ApplicationInfoWrapper(context, targetPkg, c.user)
         var validTarget = launcherApps.isPackageEnabled(targetPkg, c.user)
 
         // If it's a deep shortcut, we'll use pinned shortcuts to restore it
@@ -178,7 +168,7 @@ class WorkspaceItemProcessor(
                 FileLog.d(
                     TAG,
                     "Activity not enabled for id=${c.id}, component=$cn, user=${c.user}." +
-                        " Will attempt to find fallback Activity for targetPkg=$targetPkg.",
+                        " Will attempt to find fallback Activity for targetPkg=$targetPkg."
                 )
                 intent = pmHelper.getAppLaunchIntent(targetPkg, c.user)
                 if (intent != null) {
@@ -188,7 +178,7 @@ class WorkspaceItemProcessor(
                     c.markDeleted(
                         "No Activities found for id=${c.id}, targetPkg=$targetPkg, component=$cn." +
                             " Unable to create launch Intent.",
-                        RestoreError.APP_NO_LAUNCH_INTENT,
+                        RestoreError.MISSING_INFO
                     )
                     return
                 }
@@ -197,54 +187,39 @@ class WorkspaceItemProcessor(
         if (intent.`package` == null) {
             intent.`package` = targetPkg
         }
-
-        val isPreArchivedShortcut =
-            Flags.restoreArchivedShortcuts() &&
-                appInfoWrapper.isArchived() &&
-                c.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT &&
-                c.restoreFlag != 0
-
         // else if cn == null => can't infer much, leave it
         // else if !validPkg => could be restored icon or missing sd-card
         when {
-            !TextUtils.isEmpty(targetPkg) && (!validTarget || isPreArchivedShortcut) -> {
+            !TextUtils.isEmpty(targetPkg) && !validTarget -> {
                 // Points to a valid app (superset of cn != null) but the apk
                 // is not available.
                 when {
-                    c.restoreFlag != 0 || isPreArchivedShortcut -> {
+                    c.restoreFlag != 0 -> {
                         // Package is not yet available but might be
                         // installed later.
-                        FileLog.d(
-                            TAG,
-                            "package not yet restored: $targetPkg, itemType=${c.itemType}" +
-                                ", isPreArchivedShortcut=$isPreArchivedShortcut" +
-                                ", restoreFlag=${c.restoreFlag}",
-                        )
+                        FileLog.d(TAG, "package not yet restored: $targetPkg")
                         tempPackageKey.update(targetPkg, c.user)
                         when {
                             c.hasRestoreFlag(WorkspaceItemInfo.FLAG_RESTORE_STARTED) -> {
                                 // Restore has started once.
                             }
-                            installingPkgs.containsKey(tempPackageKey) || isPreArchivedShortcut -> {
+                            installingPkgs.containsKey(tempPackageKey) -> {
                                 // App restore has started. Update the flag
                                 c.restoreFlag =
                                     c.restoreFlag or WorkspaceItemInfo.FLAG_RESTORE_STARTED
-                                FileLog.d(
-                                    TAG,
-                                    "restore started for installing app: $targetPkg, itemType=${c.itemType}",
-                                )
+                                FileLog.d(TAG, "restore started for installing app: $targetPkg")
                                 c.updater().put(Favorites.RESTORED, c.restoreFlag).commit()
                             }
                             else -> {
                                 c.markDeleted(
                                     "removing app that is not restored and not installing. package: $targetPkg",
-                                    RestoreError.APP_NOT_RESTORED_OR_INSTALLING,
+                                    RestoreError.APP_NOT_INSTALLED
                                 )
                                 return
                             }
                         }
                     }
-                    appInfoWrapper.isOnSdCard() -> {
+                    pmHelper.isAppOnSdcard(targetPkg, c.user) -> {
                         // Package is present but not available.
                         disabledState =
                             disabledState or WorkspaceItemInfo.FLAG_DISABLED_NOT_AVAILABLE
@@ -263,7 +238,7 @@ class WorkspaceItemProcessor(
                         // Do not wait for external media load anymore.
                         c.markDeleted(
                             "Invalid package removed: $targetPkg",
-                            RestoreError.APP_NOT_INSTALLED_EXTERNAL_MEDIA,
+                            RestoreError.APP_NOT_INSTALLED
                         )
                         return
                     }
@@ -271,18 +246,9 @@ class WorkspaceItemProcessor(
             }
         }
         if (c.restoreFlag and WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI != 0) {
-            FileLog.d(
-                TAG,
-                "restore flag set AND WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI != 0, setting valid target to false: $targetPkg, itemType=${c.itemType}, restoreFlag=${c.restoreFlag}",
-            )
             validTarget = false
         }
-        if (validTarget && !isPreArchivedShortcut) {
-            FileLog.d(
-                TAG,
-                "valid target true, marking restored: $targetPkg," +
-                    " itemType=${c.itemType}, restoreFlag=${c.restoreFlag}",
-            )
+        if (validTarget) {
             // The shortcut points to a valid target (either no target
             // or something which is ready to be used)
             c.markRestored()
@@ -292,34 +258,32 @@ class WorkspaceItemProcessor(
         when {
             c.restoreFlag != 0 -> {
                 // Already verified above that user is same as default user
-                info = c.getRestoredItemInfo(intent, isPreArchivedShortcut)
+                info = c.getRestoredItemInfo(intent)
             }
             c.itemType == Favorites.ITEM_TYPE_APPLICATION ->
                 info = c.getAppShortcutInfo(intent, allowMissingTarget, useLowResIcon, false)
             c.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT -> {
                 val key = ShortcutKey.fromIntent(intent, c.user)
                 if (unlockedUsers[c.serialNumber]) {
-                    val pinnedShortcut =
-                        shortcutKeyToPinnedShortcuts[key] ?: retryDeepShortcutById(key)
+                    val pinnedShortcut = shortcutKeyToPinnedShortcuts[key]
                     if (pinnedShortcut == null) {
                         // The shortcut is no longer valid.
                         c.markDeleted(
                             "Pinned shortcut not found from request. package=${key.packageName}, user=${c.user}",
-                            RestoreError.SHORTCUT_NOT_FOUND,
+                            RestoreError.SHORTCUT_NOT_FOUND
                         )
                         return
                     }
-                    info = WorkspaceItemInfo(pinnedShortcut, context)
+                    info = WorkspaceItemInfo(pinnedShortcut, app.context)
                     // If the pinned deep shortcut is no longer published,
                     // use the last saved icon instead of the default.
-                    val csi = CacheableShortcutInfo(pinnedShortcut, appInfoWrapper)
-                    iconCache.getShortcutIcon(info, csi, c::loadIconFromDb)
-                    if (appInfoWrapper.isSuspended()) {
+                    iconCache.getShortcutIcon(info, pinnedShortcut, c::loadIcon)
+                    if (pmHelper.isAppSuspended(pinnedShortcut.getPackage(), info.user)) {
                         info.runtimeStatusFlags =
                             info.runtimeStatusFlags or ItemInfoWithIcon.FLAG_DISABLED_SUSPENDED
                     }
                     intent = info.getIntent()
-                    allDeepShortcuts.add(csi)
+                    allDeepShortcuts.add(pinnedShortcut)
                 } else {
                     // Create a shortcut info in disabled mode for now.
                     info = c.loadSimpleWorkspaceItem()
@@ -331,7 +295,7 @@ class WorkspaceItemProcessor(
                 info = c.loadSimpleWorkspaceItem()
 
                 // Shortcuts are only available on the primary profile
-                if (appInfoWrapper.isSuspended()) {
+                if (!TextUtils.isEmpty(targetPkg) && pmHelper.isAppSuspended(targetPkg, c.user)) {
                     disabledState = disabledState or ItemInfoWithIcon.FLAG_DISABLED_SUSPENDED
                 }
                 info.options = c.options
@@ -362,7 +326,7 @@ class WorkspaceItemProcessor(
             info.spanX = 1
             info.spanY = 1
             info.runtimeStatusFlags = info.runtimeStatusFlags or disabledState
-            if (isSafeMode && !appInfoWrapper.isSystem()) {
+            if (isSafeMode && !PackageManagerHelper.isSystemApp(app.context, intent)) {
                 info.runtimeStatusFlags =
                     info.runtimeStatusFlags or ItemInfoWithIcon.FLAG_DISABLED_SAFEMODE
             }
@@ -372,8 +336,8 @@ class WorkspaceItemProcessor(
                     info,
                     activityInfo,
                     userCache.getUserInfo(c.user),
-                    ApiWrapper.INSTANCE[context],
-                    pmHelper,
+                    ApiWrapper.INSTANCE[app.context],
+                    pmHelper
                 )
             }
             try {
@@ -409,25 +373,6 @@ class WorkspaceItemProcessor(
         }
     }
 
-    /**
-     * It is possible that the data was cleared from ShortcutManager after it was restored. In that
-     * instance, the Launcher would have a valid Shortcut id, but ShortcutManager wouldn't recognize
-     * it as valid. Here we retry by querying ShortcutManager by package name and shortcut id.
-     */
-    private fun retryDeepShortcutById(key: ShortcutKey): ShortcutInfo? {
-        FileLog.d(TAG, "retryDeepShortcutById: package=${key.packageName}, shortcutId=${key.id}")
-        return launcherApps
-            .getShortcuts(
-                ShortcutQuery().apply {
-                    setPackage(key.packageName)
-                    setShortcutIds(listOf(key.id))
-                    setQueryFlags(ShortcutRequest.ALL)
-                },
-                key.user,
-            )
-            ?.firstOrNull()
-    }
-
     /**
      * Loads CollectionInfo information from the database and formats it. This function runs while
      * LoaderTask is still active; some of the processing for folder content items is done after all
@@ -435,14 +380,24 @@ class WorkspaceItemProcessor(
      * stored in the BgDataModel.
      */
     private fun processFolderOrAppPair() {
-        var collection = c.findOrMakeFolder(c.id, bgDataModel)
+        var collection = bgDataModel.findOrMakeFolder(c.id)
         // If we generated a placeholder Folder before this point, it may need to be replaced with
         // an app pair.
         if (c.itemType == Favorites.ITEM_TYPE_APP_PAIR && collection is FolderInfo) {
+            if (!FeatureFlags.enableAppPairs()) {
+                // If app pairs are not enabled, stop loading.
+                Log.e(TAG, "app pairs flag is off, did not load app pair")
+                return
+            }
+
+            val folderInfo: FolderInfo = collection
             val newAppPair = AppPairInfo()
             // Move the placeholder's contents over to the new app pair.
-            collection.getContents().forEach(newAppPair::add)
+            folderInfo.getContents().forEach(newAppPair::add)
             collection = newAppPair
+            // Remove the placeholder and add the app pair into the data model.
+            bgDataModel.collections.remove(c.id)
+            bgDataModel.collections.put(c.id, collection)
         }
 
         c.applyCommonProperties(collection)
@@ -494,7 +449,7 @@ class WorkspaceItemProcessor(
                     ", id=${c.id}," +
                     ", appWidgetId=${c.appWidgetId}," +
                     ", component=${component}",
-                RestoreError.INVALID_WIDGET_SIZE,
+                RestoreError.INVALID_LOCATION
             )
             return
         }
@@ -505,7 +460,7 @@ class WorkspaceItemProcessor(
                     ", appWidgetId=${c.appWidgetId}," +
                     ", component=${component}," +
                     ", container=${c.container}",
-                RestoreError.INVALID_WIDGET_CONTAINER,
+                RestoreError.INVALID_LOCATION
             )
             return
         }
@@ -519,7 +474,7 @@ class WorkspaceItemProcessor(
             TAG,
             "processWidget: id=${c.id}" +
                 ", appWidgetId=${c.appWidgetId}" +
-                ", inflationResult=$inflationResult",
+                ", inflationResult=$inflationResult"
         )
         when (inflationResult.type) {
             WidgetInflater.TYPE_DELETE -> {
@@ -529,14 +484,14 @@ class WorkspaceItemProcessor(
             WidgetInflater.TYPE_PENDING -> {
                 tempPackageKey.update(component.packageName, c.user)
                 val si = installingPkgs[tempPackageKey]
-                val isArchived =
-                    ApplicationInfoWrapper(context, component.packageName, c.user).isArchived()
+
                 if (
                     !c.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_RESTORE_STARTED) &&
                         !isSafeMode &&
                         (si == null) &&
                         (lapi == null) &&
-                        !isArchived
+                        !(Flags.enableSupportForArchiving() &&
+                            pmHelper.isAppArchived(component.packageName))
                 ) {
                     // Restore never started
                     c.markDeleted(
@@ -545,7 +500,7 @@ class WorkspaceItemProcessor(
                             ", appWidgetId=${c.appWidgetId}" +
                             ", component=${component}" +
                             ", restoreFlag:=${c.restoreFlag}",
-                        RestoreError.UNRESTORED_PENDING_WIDGET,
+                        RestoreError.APP_NOT_INSTALLED
                     )
                     return
                 } else if (
@@ -559,25 +514,19 @@ class WorkspaceItemProcessor(
                     if (si == null) 0 else (si.getProgress() * 100).toInt()
                 appWidgetInfo.pendingItemInfo =
                     WidgetsModel.newPendingItemInfo(
-                        context,
+                        app.context,
                         appWidgetInfo.providerName,
-                        appWidgetInfo.user,
+                        appWidgetInfo.user
                     )
-                val iconLookupFlag =
-                    if (isArchived && Flags.restoreArchivedAppIconsFromDb()) {
-                        DEFAULT_LOOKUP_FLAG.withSkipAddToMemCache()
-                    } else {
-                        DEFAULT_LOOKUP_FLAG
-                    }
-                iconCache.getTitleAndIconForApp(appWidgetInfo.pendingItemInfo, iconLookupFlag)
+                iconCache.getTitleAndIconForApp(appWidgetInfo.pendingItemInfo, false)
             }
             WidgetInflater.TYPE_REAL ->
                 WidgetSizes.updateWidgetSizeRangesAsync(
                     appWidgetInfo.appWidgetId,
                     lapi,
-                    context,
+                    app.context,
                     appWidgetInfo.spanX,
-                    appWidgetInfo.spanY,
+                    appWidgetInfo.spanY
                 )
         }
 
@@ -596,12 +545,12 @@ class WorkspaceItemProcessor(
                     " processWidget: Widget ${lapi.component} minSizes not met: span=${appWidgetInfo.spanX}x${appWidgetInfo.spanY} minSpan=${lapi.minSpanX}x${lapi.minSpanY}," +
                         " id: ${c.id}," +
                         " appWidgetId: ${c.appWidgetId}," +
-                        " component=${component}",
+                        " component=${component}"
                 )
-                logWidgetInfo(idp, lapi)
+                logWidgetInfo(app.invariantDeviceProfile, lapi)
             }
         }
-        c.checkAndAddItem(appWidgetInfo, bgDataModel, memoryLogger)
+        c.checkAndAddItem(appWidgetInfo, bgDataModel)
     }
 
     companion object {
@@ -609,7 +558,7 @@ class WorkspaceItemProcessor(
 
         private fun logWidgetInfo(
             idp: InvariantDeviceProfile,
-            widgetProviderInfo: LauncherAppWidgetProviderInfo,
+            widgetProviderInfo: LauncherAppWidgetProviderInfo
         ) {
             val cellSize = Point()
             for (deviceProfile in idp.supportedProfiles) {
@@ -620,7 +569,7 @@ class WorkspaceItemProcessor(
                         " available height: ${deviceProfile.availableHeightPx}," +
                         " cellLayoutBorderSpacePx Horizontal: ${deviceProfile.cellLayoutBorderSpacePx.x}," +
                         " cellLayoutBorderSpacePx Vertical: ${deviceProfile.cellLayoutBorderSpacePx.y}," +
-                        " cellSize: $cellSize",
+                        " cellSize: $cellSize"
                 )
             }
             val widgetDimension = StringBuilder()
@@ -638,19 +587,21 @@ class WorkspaceItemProcessor(
                 .append("defaultHeight: ")
                 .append(widgetProviderInfo.minHeight)
                 .append("\n")
-            widgetDimension
-                .append("targetCellWidth: ")
-                .append(widgetProviderInfo.targetCellWidth)
-                .append("\n")
-                .append("targetCellHeight: ")
-                .append(widgetProviderInfo.targetCellHeight)
-                .append("\n")
-                .append("maxResizeWidth: ")
-                .append(widgetProviderInfo.maxResizeWidth)
-                .append("\n")
-                .append("maxResizeHeight: ")
-                .append(widgetProviderInfo.maxResizeHeight)
-                .append("\n")
+            if (Utilities.ATLEAST_S) {
+                widgetDimension
+                    .append("targetCellWidth: ")
+                    .append(widgetProviderInfo.targetCellWidth)
+                    .append("\n")
+                    .append("targetCellHeight: ")
+                    .append(widgetProviderInfo.targetCellHeight)
+                    .append("\n")
+                    .append("maxResizeWidth: ")
+                    .append(widgetProviderInfo.maxResizeWidth)
+                    .append("\n")
+                    .append("maxResizeHeight: ")
+                    .append(widgetProviderInfo.maxResizeHeight)
+                    .append("\n")
+            }
             FileLog.d(TAG, widgetDimension.toString())
         }
     }
diff --git a/src/com/android/launcher3/model/WorkspaceItemSpaceFinder.java b/src/com/android/launcher3/model/WorkspaceItemSpaceFinder.java
index b9ae2f3128..77f2cfe085 100644
--- a/src/com/android/launcher3/model/WorkspaceItemSpaceFinder.java
+++ b/src/com/android/launcher3/model/WorkspaceItemSpaceFinder.java
@@ -18,14 +18,10 @@ package com.android.launcher3.model;
 import static com.android.launcher3.Utilities.SHOULD_SHOW_FIRST_PAGE_WIDGET;
 import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID;
 
-import android.content.Context;
 import android.util.LongSparseArray;
 
-import app.lawnchair.preferences2.PreferenceManager2;
-import com.android.launcher3.BuildConfig;
-import com.android.launcher3.BuildConfigs;
 import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.LauncherModel;
+import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.model.data.ItemInfo;
@@ -33,40 +29,25 @@ import com.android.launcher3.util.GridOccupancy;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.IntSet;
 
-import com.patrykmichalik.opto.core.PreferenceExtensionsKt;
 import java.util.ArrayList;
 
-import javax.inject.Inject;
-
 /**
  * Utility class to help find space for new workspace items
  */
 public class WorkspaceItemSpaceFinder {
 
-    private BgDataModel mDataModel;
-    private InvariantDeviceProfile mIDP;
-    private LauncherModel mModel;
-
-    @Inject
-    public WorkspaceItemSpaceFinder(
-            BgDataModel dataModel, InvariantDeviceProfile idp, LauncherModel model) {
-        mDataModel = dataModel;
-        mIDP = idp;
-        mModel = model;
-    }
-
     /**
      * Find a position on the screen for the given size or adds a new screen.
      *
      * @return screenId and the coordinates for the item in an int array of size 3.
      */
-    public int[] findSpaceForItem(
-            IntArray workspaceScreens, IntArray addedWorkspaceScreensFinal, int spanX, int spanY, Context context) {
+    public int[] findSpaceForItem(LauncherAppState app, BgDataModel dataModel,
+            IntArray workspaceScreens, IntArray addedWorkspaceScreensFinal, int spanX, int spanY) {
         LongSparseArray> screenItems = new LongSparseArray<>();
 
         // Use sBgItemsIdMap as all the items are already loaded.
-        synchronized (mDataModel) {
-            for (ItemInfo info : mDataModel.itemsIdMap) {
+        synchronized (dataModel) {
+            for (ItemInfo info : dataModel.itemsIdMap) {
                 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
                     ArrayList items = screenItems.get(info.screenId);
                     if (items == null) {
@@ -86,16 +67,14 @@ public class WorkspaceItemSpaceFinder {
         int screenCount = workspaceScreens.size();
         // First check the preferred screen.
         IntSet screensToExclude = new IntSet();
-        
-        boolean smartspaceEnabled = PreferenceExtensionsKt.firstBlocking(PreferenceManager2.INSTANCE.get(context).getEnableSmartspace());
-        if (smartspaceEnabled) {
+        if (FeatureFlags.topQsbOnFirstScreenEnabled(app.getContext())) {
             screensToExclude.add(FIRST_SCREEN_ID);
         }
 
         for (int screen = 0; screen < screenCount; screen++) {
             screenId = workspaceScreens.get(screen);
             if (!screensToExclude.contains(screenId) && findNextAvailableIconSpaceInScreen(
-                    screenItems.get(screenId), coordinates, spanX, spanY)) {
+                    app, screenItems.get(screenId), coordinates, spanX, spanY)) {
                 // We found a space for it
                 found = true;
                 break;
@@ -104,7 +83,7 @@ public class WorkspaceItemSpaceFinder {
 
         if (!found) {
             // Still no position found. Add a new screen to the end.
-            screenId = mModel.getModelDbController().getNewScreenId();
+            screenId = app.getModel().getModelDbController().getNewScreenId();
 
             // Save the screen id for binding in the workspace
             workspaceScreens.add(screenId);
@@ -112,7 +91,7 @@ public class WorkspaceItemSpaceFinder {
 
             // If we still can't find an empty space, then God help us all!!!
             if (!findNextAvailableIconSpaceInScreen(
-                    screenItems.get(screenId), coordinates, spanX, spanY)) {
+                    app, screenItems.get(screenId), coordinates, spanX, spanY)) {
                 throw new RuntimeException("Can't find space to add the item");
             }
         }
@@ -120,8 +99,11 @@ public class WorkspaceItemSpaceFinder {
     }
 
     private boolean findNextAvailableIconSpaceInScreen(
-            ArrayList occupiedPos, int[] xy, int spanX, int spanY) {
-        GridOccupancy occupied = new GridOccupancy(mIDP.numColumns, mIDP.numRows);
+            LauncherAppState app, ArrayList occupiedPos,
+            int[] xy, int spanX, int spanY) {
+        InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
+
+        GridOccupancy occupied = new GridOccupancy(profile.numColumns, profile.numRows);
         if (occupiedPos != null) {
             for (ItemInfo r : occupiedPos) {
                 occupied.markCells(r, true);
diff --git a/src/com/android/launcher3/model/data/AppInfo.java b/src/com/android/launcher3/model/data/AppInfo.java
index f23639e65e..8d65e76144 100644
--- a/src/com/android/launcher3/model/data/AppInfo.java
+++ b/src/com/android/launcher3/model/data/AppInfo.java
@@ -21,6 +21,7 @@ import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_ALL_APP
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.LauncherActivityInfo;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -36,7 +37,6 @@ import com.android.launcher3.pm.PackageInstallInfo;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.util.ApiWrapper;
-import com.android.launcher3.util.ApplicationInfoWrapper;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.UserIconInfo;
 
@@ -71,7 +71,8 @@ public class AppInfo extends ItemInfoWithIcon implements WorkspaceItemFactory {
 
     /**
      * The uid of the application.
-     * The kernel user-ID that has been assigned to this application. Currently this is not a unique
+     * The kernel user-ID that has been assigned to this application. Currently this
+     * is not a unique
      * ID (multiple applications can have the same uid).
      */
     public int uid = -1;
@@ -129,10 +130,10 @@ public class AppInfo extends ItemInfoWithIcon implements WorkspaceItemFactory {
     public AppInfo(@NonNull PackageInstallInfo installInfo) {
         componentName = installInfo.componentName;
         intent = new Intent(Intent.ACTION_MAIN)
-            .addCategory(Intent.CATEGORY_LAUNCHER)
-            .setComponent(componentName)
-            .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
-                | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+                .addCategory(Intent.CATEGORY_LAUNCHER)
+                .setComponent(componentName)
+                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                        | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
         setProgressLevel(installInfo);
         user = installInfo.user;
     }
@@ -153,7 +154,8 @@ public class AppInfo extends ItemInfoWithIcon implements WorkspaceItemFactory {
         if ((runtimeStatusFlags & FLAG_INSTALL_SESSION_ACTIVE) != 0) {
             // We need to update the component name when the apk is installed
             workspaceItemInfo.status |= WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON;
-            // Since the user is manually placing it on homescreen, it should not be auto-removed
+            // Since the user is manually placing it on homescreen, it should not be
+            // auto-removed
             // later
             workspaceItemInfo.status |= WorkspaceItemInfo.FLAG_RESTORE_STARTED;
             workspaceItemInfo.status |= FLAG_INSTALL_SESSION_ACTIVE;
@@ -184,7 +186,8 @@ public class AppInfo extends ItemInfoWithIcon implements WorkspaceItemFactory {
     }
 
     /**
-     * Updates the runtime status flags for the given info based on the state of the specified
+     * Updates the runtime status flags for the given info based on the state of the
+     * specified
      * activity.
      */
     public static boolean updateRuntimeFlagsForActivityTarget(
@@ -192,8 +195,8 @@ public class AppInfo extends ItemInfoWithIcon implements WorkspaceItemFactory {
             ApiWrapper apiWrapper, PackageManagerHelper pmHelper) {
         final int oldProgressLevel = info.getProgressLevel();
         final int oldRuntimeStatusFlags = info.runtimeStatusFlags;
-        ApplicationInfoWrapper appInfo = new ApplicationInfoWrapper(lai.getApplicationInfo());
-        if (appInfo.isSuspended()) {
+        ApplicationInfo appInfo = lai.getApplicationInfo();
+        if (PackageManagerHelper.isAppSuspended(appInfo)) {
             info.runtimeStatusFlags |= FLAG_DISABLED_SUSPENDED;
         } else {
             info.runtimeStatusFlags &= ~FLAG_DISABLED_SUSPENDED;
@@ -206,10 +209,12 @@ public class AppInfo extends ItemInfoWithIcon implements WorkspaceItemFactory {
                     info.runtimeStatusFlags &= ~FLAG_ARCHIVED;
                 }
             } catch (Throwable t) {
-                // LC-Ignored
+                // Ignore
             }
         }
-        info.runtimeStatusFlags |= appInfo.isSystem() ? FLAG_SYSTEM_YES : FLAG_SYSTEM_NO;
+        info.runtimeStatusFlags |= (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0
+                ? FLAG_SYSTEM_NO
+                : FLAG_SYSTEM_YES;
 
         if (Flags.privateSpaceRestrictAccessibilityDrag()) {
             if (userIconInfo.isPrivate()) {
@@ -224,7 +229,8 @@ public class AppInfo extends ItemInfoWithIcon implements WorkspaceItemFactory {
                 PackageManagerHelper.getLoadingProgress(lai),
                 PackageInstallInfo.STATUS_INSTALLED_DOWNLOADING);
         info.setNonResizeable(apiWrapper.isNonResizeableActivity(lai));
-        info.setSupportsMultiInstance(apiWrapper.supportsMultiInstance(lai));
+        info.setSupportsMultiInstance(
+                pmHelper.supportsMultiInstance(lai.getComponentName()));
         return (oldProgressLevel != info.getProgressLevel())
                 || (oldRuntimeStatusFlags != info.runtimeStatusFlags);
     }
diff --git a/src/com/android/launcher3/model/data/AppPairInfo.kt b/src/com/android/launcher3/model/data/AppPairInfo.kt
index c0fe4fded8..2eb6154476 100644
--- a/src/com/android/launcher3/model/data/AppPairInfo.kt
+++ b/src/com/android/launcher3/model/data/AppPairInfo.kt
@@ -18,12 +18,10 @@ package com.android.launcher3.model.data
 
 import android.content.Context
 import com.android.launcher3.LauncherSettings
-import com.android.launcher3.LauncherSettings.Favorites.DESKTOP_ICON_FLAG
 import com.android.launcher3.R
 import com.android.launcher3.icons.IconCache
 import com.android.launcher3.logger.LauncherAtom
 import com.android.launcher3.views.ActivityContext
-import java.util.stream.Collectors
 
 /** A type of app collection that launches multiple apps into split screen. */
 class AppPairInfo() : CollectionInfo() {
@@ -34,8 +32,9 @@ class AppPairInfo() : CollectionInfo() {
     }
 
     /** Convenience constructor, calls primary constructor and init block */
-    constructor(apps: List) : this() {
-        apps.forEach(this::add)
+    constructor(app1: WorkspaceItemInfo, app2: WorkspaceItemInfo) : this() {
+        add(app1)
+        add(app2)
     }
 
     /** Creates a new AppPairInfo that is a copy of the provided one. */
@@ -55,7 +54,7 @@ class AppPairInfo() : CollectionInfo() {
 
     /** Returns the app pair's member apps as an ArrayList of [ItemInfo]. */
     override fun getContents(): ArrayList =
-        ArrayList(contents.stream().map { it as ItemInfo }.collect(Collectors.toList()))
+        ArrayList(contents.stream().map { it as ItemInfo }.toList())
 
     /** Returns the app pair's member apps as an ArrayList of [WorkspaceItemInfo]. */
     override fun getAppContents(): ArrayList = contents
@@ -74,17 +73,16 @@ class AppPairInfo() : CollectionInfo() {
         val isTablet =
             (ActivityContext.lookupContext(context) as ActivityContext).getDeviceProfile().isTablet
         return Pair(
-            isTablet || !getFirstApp().isNonResizeable,
-            isTablet || !getSecondApp().isNonResizeable,
+            isTablet || !getFirstApp().isNonResizeable(),
+            isTablet || !getSecondApp().isNonResizeable()
         )
     }
 
     /** Fetches high-res icons for member apps if needed. */
     fun fetchHiResIconsIfNeeded(iconCache: IconCache) {
-        getAppContents()
-            .stream()
-            .filter { it.matchingLookupFlag.isVisuallyLessThan(DESKTOP_ICON_FLAG) }
-            .forEach { member -> iconCache.getTitleAndIcon(member, DESKTOP_ICON_FLAG) }
+        getAppContents().stream().filter(ItemInfoWithIcon::usingLowResIcon).forEach { member ->
+            iconCache.getTitleAndIcon(member, false)
+        }
     }
 
     /**
@@ -107,10 +105,10 @@ class AppPairInfo() : CollectionInfo() {
     }
 
     /** Generates an ItemInfo for logging. */
-    override fun buildProto(cInfo: CollectionInfo?, context: Context): LauncherAtom.ItemInfo {
+    override fun buildProto(cInfo: CollectionInfo?): LauncherAtom.ItemInfo {
         val appPairIcon = LauncherAtom.FolderIcon.newBuilder().setCardinality(contents.size)
         appPairIcon.setLabelInfo(title.toString())
-        return getDefaultItemInfoBuilder(context)
+        return getDefaultItemInfoBuilder()
             .setFolderIcon(appPairIcon)
             .setRank(rank)
             .setContainerInfo(getContainerInfo())
diff --git a/src/com/android/launcher3/model/data/CollectionInfo.kt b/src/com/android/launcher3/model/data/CollectionInfo.kt
index 12ba164713..4f5e12fd30 100644
--- a/src/com/android/launcher3/model/data/CollectionInfo.kt
+++ b/src/com/android/launcher3/model/data/CollectionInfo.kt
@@ -17,6 +17,7 @@
 package com.android.launcher3.model.data
 
 import com.android.launcher3.LauncherSettings
+import com.android.launcher3.logger.LauncherAtom
 import com.android.launcher3.util.ContentWriter
 import java.util.function.Predicate
 
@@ -41,4 +42,9 @@ abstract class CollectionInfo : ItemInfo() {
         super.onAddToDatabase(writer)
         writer.put(LauncherSettings.Favorites.TITLE, title)
     }
+
+    /** Returns the collection wrapped as {@link LauncherAtom.ItemInfo} for logging. */
+    override fun buildProto(): LauncherAtom.ItemInfo {
+        return buildProto(null)
+    }
 }
diff --git a/src/com/android/launcher3/model/data/FolderInfo.java b/src/com/android/launcher3/model/data/FolderInfo.java
index 4c792a7672..9bc5cb81b3 100644
--- a/src/com/android/launcher3/model/data/FolderInfo.java
+++ b/src/com/android/launcher3/model/data/FolderInfo.java
@@ -20,19 +20,16 @@ import static android.text.TextUtils.isEmpty;
 
 import static androidx.core.util.Preconditions.checkNotNull;
 
-import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
-import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR;
-import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
 import static com.android.launcher3.logger.LauncherAtom.Attribute.EMPTY_LABEL;
 import static com.android.launcher3.logger.LauncherAtom.Attribute.MANUAL_LABEL;
 import static com.android.launcher3.logger.LauncherAtom.Attribute.SUGGESTED_LABEL;
 
-import android.content.Context;
-
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderNameInfos;
 import com.android.launcher3.logger.LauncherAtom;
 import com.android.launcher3.logger.LauncherAtom.Attribute;
@@ -43,6 +40,8 @@ import com.android.launcher3.model.ModelWriter;
 import com.android.launcher3.util.ContentWriter;
 
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 import java.util.OptionalInt;
 import java.util.stream.IntStream;
 
@@ -51,6 +50,18 @@ import java.util.stream.IntStream;
  */
 public class FolderInfo extends CollectionInfo {
 
+    public static final int NO_FLAGS = 0x00000000;
+
+    /**
+     * The folder is locked in sorted mode
+     */
+    public static final int FLAG_ITEMS_SORTED = 0x00000001;
+
+    /**
+     * It is a work folder
+     */
+    public static final int FLAG_WORK_FOLDER = 0x00000002;
+
     /**
      * The multi-page animation has run for this folder
      */
@@ -62,7 +73,8 @@ public class FolderInfo extends CollectionInfo {
      * Different states of folder label.
      */
     public enum LabelState {
-        // Folder's label is not yet assigned( i.e., title == null). Eligible for auto-labeling.
+        // Folder's label is not yet assigned( i.e., title == null). Eligible for
+        // auto-labeling.
         UNLABELED(Attribute.UNLABELED),
 
         // Folder's label is empty(i.e., title == ""). Not eligible for auto-labeling.
@@ -82,6 +94,8 @@ public class FolderInfo extends CollectionInfo {
         }
     }
 
+    public static final String EXTRA_FOLDER_SUGGESTIONS = "suggest";
+
     public int options;
 
     public FolderNameInfos suggestedFolderNames;
@@ -91,20 +105,65 @@ public class FolderInfo extends CollectionInfo {
      */
     private final ArrayList contents = new ArrayList<>();
 
+    private ArrayList mListeners = new ArrayList<>();
+
     public FolderInfo() {
         itemType = LauncherSettings.Favorites.ITEM_TYPE_FOLDER;
     }
 
+    /** Adds a app or shortcut to the contents ArrayList without animation. */
     @Override
     public void add(@NonNull ItemInfo item) {
-        if (!willAcceptItemType(item.itemType)) {
-            throw new RuntimeException("tried to add an illegal type into a folder");
-        }
-        getContents().add(item);
+        add(item, false /* animate */);
     }
 
     /**
-     * Returns the folder's contents as an unsorted ArrayList of {@link ItemInfo}. Includes
+     * Add an app or shortcut
+     *
+     * @param item
+     */
+    public void add(ItemInfo item, boolean animate) {
+        add(item, getContents().size(), animate);
+    }
+
+    /**
+     * Add an app or shortcut for a specified rank.
+     */
+    public void add(ItemInfo item, int rank, boolean animate) {
+        if (!Folder.willAccept(item)) {
+            throw new RuntimeException("tried to add an illegal type into a folder");
+        }
+
+        rank = Utilities.boundToRange(rank, 0, getContents().size());
+        getContents().add(rank, item);
+        for (int i = 0; i < mListeners.size(); i++) {
+            mListeners.get(i).onAdd(item, rank);
+        }
+        itemsChanged(animate);
+    }
+
+    /**
+     * Remove an app or shortcut. Does not change the DB.
+     *
+     * @param item
+     */
+    public void remove(ItemInfo item, boolean animate) {
+        removeAll(Collections.singletonList(item), animate);
+    }
+
+    /**
+     * Remove all matching app or shortcut. Does not change the DB.
+     */
+    public void removeAll(List items, boolean animate) {
+        contents.removeAll(items);
+        for (int i = 0; i < mListeners.size(); i++) {
+            mListeners.get(i).onRemove(items);
+        }
+        itemsChanged(animate);
+    }
+
+    /**
+     * Returns the folder's contents as an ArrayList of {@link ItemInfo}. Includes
      * {@link WorkspaceItemInfo} and {@link AppPairInfo}s.
      */
     @NonNull
@@ -114,12 +173,14 @@ public class FolderInfo extends CollectionInfo {
     }
 
     /**
-     * Returns the folder's contents as an ArrayList of {@link WorkspaceItemInfo}. Note: Does not
-     * return any {@link AppPairInfo}s contained in the folder, instead collects *their* contents
+     * Returns the folder's contents as an ArrayList of {@link WorkspaceItemInfo}.
+     * Note: Does not
+     * return any {@link AppPairInfo}s contained in the folder, instead collects
+     * *their* contents
      * and adds them to the ArrayList.
      */
     @Override
-    public ArrayList getAppContents()  {
+    public ArrayList getAppContents() {
         ArrayList workspaceItemInfos = new ArrayList<>();
         for (ItemInfo item : contents) {
             if (item instanceof WorkspaceItemInfo wii) {
@@ -137,14 +198,39 @@ public class FolderInfo extends CollectionInfo {
         writer.put(LauncherSettings.Favorites.OPTIONS, options);
     }
 
+    public void addListener(FolderListener listener) {
+        mListeners.add(listener);
+    }
+
+    public void removeListener(FolderListener listener) {
+        mListeners.remove(listener);
+    }
+
+    public void itemsChanged(boolean animate) {
+        for (int i = 0; i < mListeners.size(); i++) {
+            mListeners.get(i).onItemsChanged(animate);
+        }
+    }
+
+    public interface FolderListener {
+        void onAdd(ItemInfo item, int rank);
+
+        void onRemove(List item);
+
+        void onItemsChanged(boolean animate);
+
+        void onTitleChanged(CharSequence title);
+
+    }
+
     public boolean hasOption(int optionFlag) {
         return (options & optionFlag) != 0;
     }
 
     /**
-     * @param option flag to set or clear
+     * @param option    flag to set or clear
      * @param isEnabled whether to set or clear the flag
-     * @param writer if not null, save changes to the db.
+     * @param writer    if not null, save changes to the db.
      */
     public void setOption(int option, boolean isEnabled, ModelWriter writer) {
         int oldOptions = options;
@@ -165,13 +251,13 @@ public class FolderInfo extends CollectionInfo {
 
     @NonNull
     @Override
-    public LauncherAtom.ItemInfo buildProto(@Nullable CollectionInfo cInfo, Context context) {
+    public LauncherAtom.ItemInfo buildProto(@Nullable CollectionInfo cInfo) {
         FolderIcon.Builder folderIcon = FolderIcon.newBuilder()
                 .setCardinality(getContents().size());
         if (LabelState.SUGGESTED.equals(getLabelState())) {
             folderIcon.setLabelInfo(title.toString());
         }
-        return getDefaultItemInfoBuilder(context)
+        return getDefaultItemInfoBuilder()
                 .setFolderIcon(folderIcon)
                 .setRank(rank)
                 .addItemAttributes(getLabelState().mLogAttribute)
@@ -179,9 +265,11 @@ public class FolderInfo extends CollectionInfo {
                 .build();
     }
 
+    @Override
     public void setTitle(@Nullable CharSequence title, ModelWriter modelWriter) {
         // Updating label from null to empty is considered as false touch.
-        // Retaining null title(ie., UNLABELED state) allows auto-labeling when new items added.
+        // Retaining null title(ie., UNLABELED state) allows auto-labeling when new
+        // items added.
         if (isEmpty(title) && this.title == null) {
             return;
         }
@@ -192,11 +280,10 @@ public class FolderInfo extends CollectionInfo {
         }
 
         this.title = title;
-        LabelState newLabelState =
-                title == null ? LabelState.UNLABELED
-                        : title.length() == 0 ? LabelState.EMPTY :
-                                getAcceptedSuggestionIndex().isPresent() ? LabelState.SUGGESTED
-                                        : LabelState.MANUAL;
+        LabelState newLabelState = title == null ? LabelState.UNLABELED
+                : title.length() == 0 ? LabelState.EMPTY
+                        : getAcceptedSuggestionIndex().isPresent() ? LabelState.SUGGESTED
+                                : LabelState.MANUAL;
 
         if (newLabelState.equals(LabelState.MANUAL)) {
             options |= FLAG_MANUAL_FOLDER_NAME;
@@ -206,6 +293,10 @@ public class FolderInfo extends CollectionInfo {
         if (modelWriter != null) {
             modelWriter.updateItemInDatabase(this);
         }
+
+        for (int i = 0; i < mListeners.size(); i++) {
+            mListeners.get(i).onTitleChanged(title);
+        }
     }
 
     /**
@@ -213,8 +304,8 @@ public class FolderInfo extends CollectionInfo {
      */
     public LabelState getLabelState() {
         return title == null ? LabelState.UNLABELED
-                : title.length() == 0 ? LabelState.EMPTY :
-                        hasOption(FLAG_MANUAL_FOLDER_NAME) ? LabelState.MANUAL
+                : title.length() == 0 ? LabelState.EMPTY
+                        : hasOption(FLAG_MANUAL_FOLDER_NAME) ? LabelState.MANUAL
                                 : LabelState.SUGGESTED;
     }
 
@@ -247,7 +338,7 @@ public class FolderInfo extends CollectionInfo {
         return IntStream.range(0, labels.length)
                 .filter(index -> !isEmpty(labels[index])
                         && newLabel.equalsIgnoreCase(
-                        labels[index].toString()))
+                                labels[index].toString()))
                 .sequential()
                 .findFirst();
     }
@@ -256,7 +347,7 @@ public class FolderInfo extends CollectionInfo {
      * Returns {@link FromState} based on current {@link #title}.
      */
     public LauncherAtom.FromState getFromLabelState() {
-        switch (getLabelState()){
+        switch (getLabelState()) {
             case EMPTY:
                 return LauncherAtom.FromState.FROM_EMPTY;
             case MANUAL:
@@ -314,13 +405,4 @@ public class FolderInfo extends CollectionInfo {
         }
         return LauncherAtom.ToState.TO_STATE_UNSPECIFIED;
     }
-
-    /**
-     * Checks if {@code itemType} is a type that can be placed in folders.
-     */
-    public static boolean willAcceptItemType(int itemType) {
-        return itemType == ITEM_TYPE_APPLICATION
-                || itemType == ITEM_TYPE_DEEP_SHORTCUT
-                || itemType == ITEM_TYPE_APP_PAIR;
-    }
 }
diff --git a/src/com/android/launcher3/model/data/IconRequestInfo.java b/src/com/android/launcher3/model/data/IconRequestInfo.java
index 42af018b61..e77e527901 100644
--- a/src/com/android/launcher3/model/data/IconRequestInfo.java
+++ b/src/com/android/launcher3/model/data/IconRequestInfo.java
@@ -64,25 +64,23 @@ public class IconRequestInfo {
     }
 
     /**
-     * Loads this request's item info's title and icon from given iconBlob from Launcher.db.
-     * This method should only be used on {@link IconRequestInfo} for {@link WorkspaceItemInfo}
-     *  or {@link AppInfo}.
+     * Loads this request's item info's title. This method should only be used on IconRequestInfos
+     * for WorkspaceItemInfos.
      */
-    public boolean loadIconFromDbBlob(Context context) {
-        if (!(itemInfo instanceof WorkspaceItemInfo) && !(itemInfo instanceof AppInfo)) {
+    public boolean loadWorkspaceIcon(Context context) {
+        if (!(itemInfo instanceof WorkspaceItemInfo)) {
             throw new IllegalStateException(
-                    "loadIconFromDb should only be used for either WorkspaceItemInfo or AppInfo: "
-                            + itemInfo);
+                    "loadWorkspaceIcon should only be use for a WorkspaceItemInfos: " + itemInfo);
         }
 
         try (LauncherIcons li = LauncherIcons.obtain(context)) {
-            ItemInfoWithIcon info = itemInfo;
+            WorkspaceItemInfo info = (WorkspaceItemInfo) itemInfo;
+            // Failed to load from resource, try loading from DB.
             if (iconBlob == null) {
-                Log.d(TAG, "loadIconFromDb: icon blob null, returning. Component="
-                        + info.getTargetComponent());
                 return false;
             }
-            info.bitmap = li.createIconBitmap(decodeByteArray(iconBlob, 0, iconBlob.length));
+            info.bitmap = li.createIconBitmap(decodeByteArray(
+                    iconBlob, 0, iconBlob.length));
             return true;
         } catch (Exception e) {
             Log.e(TAG, "Failed to decode byte array for info " + itemInfo, e);
diff --git a/src/com/android/launcher3/model/data/ItemInfo.java b/src/com/android/launcher3/model/data/ItemInfo.java
index 3a56fd73ee..0ae7b546f6 100644
--- a/src/com/android/launcher3/model/data/ItemInfo.java
+++ b/src/com/android/launcher3/model/data/ItemInfo.java
@@ -36,7 +36,6 @@ import static com.android.launcher3.shortcuts.ShortcutKey.EXTRA_SHORTCUT_ID;
 
 import android.content.ComponentName;
 import android.content.ContentValues;
-import android.content.Context;
 import android.content.Intent;
 import android.net.Uri;
 import android.os.Process;
@@ -61,6 +60,7 @@ import com.android.launcher3.logger.LauncherAtom.ShortcutsContainer;
 import com.android.launcher3.logger.LauncherAtom.TaskSwitcherContainer;
 import com.android.launcher3.logger.LauncherAtom.WallpapersContainer;
 import com.android.launcher3.logger.LauncherAtomExtensions.ExtendedContainers;
+import com.android.launcher3.model.ModelWriter;
 import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.ContentWriter;
@@ -254,8 +254,8 @@ public class ItemInfo {
         return component != null
                 ? component.getPackageName()
                 : intent != null
-                        ? intent.getPackage()
-                        : null;
+                ? intent.getPackage()
+                : null;
     }
 
     public void writeToValues(@NonNull final ContentWriter writer) {
@@ -354,16 +354,16 @@ public class ItemInfo {
      * Creates {@link LauncherAtom.ItemInfo} with important fields and parent container info.
      */
     @NonNull
-    public LauncherAtom.ItemInfo buildProto(Context context) {
-        return buildProto(null, context);
+    public LauncherAtom.ItemInfo buildProto() {
+        return buildProto(null);
     }
 
     /**
      * Creates {@link LauncherAtom.ItemInfo} with important fields and parent container info.
      */
     @NonNull
-    public LauncherAtom.ItemInfo buildProto(@Nullable final CollectionInfo cInfo, Context context) {
-        LauncherAtom.ItemInfo.Builder itemBuilder = getDefaultItemInfoBuilder(context);
+    public LauncherAtom.ItemInfo buildProto(@Nullable final CollectionInfo cInfo) {
+        LauncherAtom.ItemInfo.Builder itemBuilder = getDefaultItemInfoBuilder();
         Optional nullableComponent = Optional.ofNullable(getTargetComponent());
         switch (itemType) {
             case ITEM_TYPE_APPLICATION:
@@ -436,13 +436,13 @@ public class ItemInfo {
     }
 
     @NonNull
-    protected LauncherAtom.ItemInfo.Builder getDefaultItemInfoBuilder(Context context) {
+    protected LauncherAtom.ItemInfo.Builder getDefaultItemInfoBuilder() {
         LauncherAtom.ItemInfo.Builder itemBuilder = LauncherAtom.ItemInfo.newBuilder();
         if (LawnchairApp.isRecentsEnabled()) {
-            itemBuilder.setIsKidsMode(
-                SettingsCache.INSTANCE.get(context).getValue(NAV_BAR_KIDS_MODE, 0));
+            SettingsCache.INSTANCE
+                    .executeIfCreated(cache -> itemBuilder.setIsKidsMode(cache.getValue(NAV_BAR_KIDS_MODE, 0)));
         }
-        itemBuilder.setUserType(getUserType(UserCache.INSTANCE.get(context).getUserInfo(user)));
+        UserCache.INSTANCE.executeIfCreated(cache -> itemBuilder.setUserType(getUserType(cache.getUserInfo(user))));
         itemBuilder.setRank(rank);
         itemBuilder.addAllItemAttributes(mAttributeList);
         return itemBuilder;
@@ -460,7 +460,7 @@ public class ItemInfo {
                         .build();
             case CONTAINER_HOTSEAT_PREDICTION:
                 return ContainerInfo.newBuilder().setPredictedHotseatContainer(
-                        LauncherAtom.PredictedHotseatContainer.newBuilder().setIndex(screenId))
+                                LauncherAtom.PredictedHotseatContainer.newBuilder().setIndex(screenId))
                         .build();
             case CONTAINER_DESKTOP:
                 return ContainerInfo.newBuilder()
@@ -511,8 +511,7 @@ public class ItemInfo {
     }
 
     /**
-     * Returns non-AOSP container wrapped by {@link ExtendedContainers} object. Should be overridden
-     * by build variants.
+     * Sets extra container info wrapped by {@link ExtendedContainers} object.
      */
     public void setExtendedContainers(@NonNull ExtendedContainers extendedContainers) {
         mExtendedContainers = extendedContainers;
@@ -539,6 +538,14 @@ public class ItemInfo {
         return itemInfo;
     }
 
+    /**
+     * Sets the title of the item and writes to DB model if needed.
+     */
+    public void setTitle(@Nullable final CharSequence title,
+                         @Nullable final ModelWriter modelWriter) {
+        this.title = title;
+    }
+
     /**
      * Returns a string ID that is stable for a user session, but may not be persisted
      */
@@ -562,4 +569,4 @@ public class ItemInfo {
             return SysUiStatsLog.LAUNCHER_UICHANGED__USER_TYPE__TYPE_UNKNOWN;
         }
     }
-}
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
index e0e7523e0d..1641d502f1 100644
--- a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
+++ b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
@@ -16,23 +16,15 @@
 
 package com.android.launcher3.model.data;
 
-import static com.android.launcher3.icons.BitmapInfo.FLAG_NO_BADGE;
-import static com.android.launcher3.icons.BitmapInfo.FLAG_THEMED;
-
 import android.content.Context;
 import android.content.Intent;
 import android.os.Process;
 
-import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.Flags;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.graphics.ThemeManager;
 import com.android.launcher3.icons.BitmapInfo;
-import com.android.launcher3.icons.BitmapInfo.DrawableCreationFlags;
 import com.android.launcher3.icons.FastBitmapDrawable;
-import com.android.launcher3.icons.cache.CacheLookupFlag;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.pm.PackageInstallInfo;
 import com.android.launcher3.util.ApiWrapper;
@@ -49,7 +41,6 @@ public abstract class ItemInfoWithIcon extends ItemInfo {
     /**
      * The bitmap for the application icon
      */
-    @NonNull
     public BitmapInfo bitmap = BitmapInfo.LOW_RES_INFO;
 
     /**
@@ -194,10 +185,10 @@ public abstract class ItemInfoWithIcon extends ItemInfo {
     }
 
     /**
-     * Returns the lookup flag to match this current state of this info
+     * Indicates whether we're using a low res icon
      */
-    public CacheLookupFlag getMatchingLookupFlag() {
-        return bitmap.getMatchingLookupFlag();
+    public boolean usingLowResIcon() {
+        return bitmap.isLowRes();
     }
 
     /**
@@ -322,19 +313,21 @@ public abstract class ItemInfoWithIcon extends ItemInfo {
      * Returns a FastBitmapDrawable with the icon.
      */
     public FastBitmapDrawable newIcon(Context context) {
-        return newIcon(context, 0);
+        return newIcon(context, PreferenceManager.getInstance(context).getThemedIcons().get());
     }
 
     /**
      * Returns a FastBitmapDrawable with the icon and context theme applied
      */
-    public FastBitmapDrawable newIcon(Context context, @DrawableCreationFlags int creationFlags) {
-        var shouldTheme = PreferenceManager.getInstance(context).getThemedIcons().get();
-        if (!shouldTheme) {
-            creationFlags &= ~FLAG_THEMED;
-        }
-        FastBitmapDrawable drawable = bitmap.newIcon(
-                context, creationFlags, Utilities.getIconShapeOrNull(context));
+    public FastBitmapDrawable newIcon(Context context, boolean applyTheme) {
+        return newIcon(context, applyTheme ? BitmapInfo.FLAG_THEMED : BitmapInfo.FLAG_NO_BADGE);
+    }
+
+    /**
+     * Returns a FastBitmapDrawable with the icon and context theme applied
+     */
+    public FastBitmapDrawable newIcon(Context context, @BitmapInfo.DrawableCreationFlags int creationFlags) {
+        FastBitmapDrawable drawable = (creationFlags & BitmapInfo.FLAG_THEMED) != 0 ? bitmap.newThemedIcon(context) : bitmap.newIcon(context, creationFlags);
         drawable.setIsDisabled(isDisabled());
         return drawable;
     }
diff --git a/src/com/android/launcher3/model/data/LauncherAppWidgetInfo.java b/src/com/android/launcher3/model/data/LauncherAppWidgetInfo.java
index 4099d043d1..04a14d27b5 100644
--- a/src/com/android/launcher3/model/data/LauncherAppWidgetInfo.java
+++ b/src/com/android/launcher3/model/data/LauncherAppWidgetInfo.java
@@ -25,7 +25,6 @@ import static com.android.launcher3.Utilities.ATLEAST_S;
 
 import android.appwidget.AppWidgetHostView;
 import android.content.ComponentName;
-import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
 import android.os.Process;
@@ -234,7 +233,7 @@ public class LauncherAppWidgetInfo extends ItemInfo {
         if (providerInfo.isConfigurationOptional()) {
             widgetFeatures |= FEATURE_OPTIONAL_CONFIGURATION;
         }
-        if (providerInfo.previewLayout != Resources.ID_NULL) {
+        if (ATLEAST_S && providerInfo.previewLayout != Resources.ID_NULL) {
             widgetFeatures |= FEATURE_PREVIEW_LAYOUT;
         }
         if (ATLEAST_S && (providerInfo.targetCellWidth > 0 || providerInfo.targetCellHeight > 0)) {
@@ -272,9 +271,8 @@ public class LauncherAppWidgetInfo extends ItemInfo {
 
     @NonNull
     @Override
-    public LauncherAtom.ItemInfo buildProto(
-            @Nullable CollectionInfo collectionInfo, Context context) {
-        LauncherAtom.ItemInfo info = super.buildProto(collectionInfo, context);
+    public LauncherAtom.ItemInfo buildProto(@Nullable CollectionInfo collectionInfo) {
+        LauncherAtom.ItemInfo info = super.buildProto(collectionInfo);
         return info.toBuilder()
                 .setWidget(info.getWidget().toBuilder().setWidgetFeatures(widgetFeatures))
                 .addItemAttributes(getAttribute(sourceContainer))
diff --git a/src/com/android/launcher3/model/data/SearchActionItemInfo.java b/src/com/android/launcher3/model/data/SearchActionItemInfo.java
index ff638bdb3f..86f3574445 100644
--- a/src/com/android/launcher3/model/data/SearchActionItemInfo.java
+++ b/src/com/android/launcher3/model/data/SearchActionItemInfo.java
@@ -16,12 +16,9 @@
 package com.android.launcher3.model.data;
 
 import static com.android.launcher3.LauncherSettings.Favorites.EXTENDED_CONTAINERS;
-import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG;
 
 import android.app.PendingIntent;
-import android.content.Context;
 import android.content.Intent;
-import android.content.pm.ShortcutInfo;
 import android.graphics.drawable.Icon;
 import android.os.Process;
 import android.os.UserHandle;
@@ -138,14 +135,14 @@ public class SearchActionItemInfo extends ItemInfoWithIcon {
         return new SearchActionItemInfo(this);
     }
 
-    public ItemInfo buildProto(FolderInfo fInfo, Context context) {
+    public ItemInfo buildProto(FolderInfo fInfo) {
         SearchActionItem.Builder itemBuilder = SearchActionItem.newBuilder()
                 .setPackageName(mFallbackPackageName);
 
         if (!mIsPersonalTitle) {
             itemBuilder.setTitle(title.toString());
         }
-        return getDefaultItemInfoBuilder(context)
+        return getDefaultItemInfoBuilder()
                 .setSearchActionItem(itemBuilder)
                 .setContainerInfo(getContainerInfo())
                 .build();
@@ -174,12 +171,15 @@ public class SearchActionItemInfo extends ItemInfoWithIcon {
         model.enqueueModelUpdateTask(new LauncherModel.ModelUpdateTask() {
             @Override
             public void execute(ModelTaskController app, BgDataModel dataModel, AllAppsList apps) {
-                PackageItemInfo pkgInfo = new PackageItemInfo(getIntentPackageName(), user);
-                app.getIconCache().getTitleAndIconForApp(pkgInfo, DEFAULT_LOOKUP_FLAG);
-                
-                // TODO: ShortcutInfo
-                //ShortcutInfo si = getShortcutInfo();
-                model.updateAndBindWorkspaceItem(info, null /* shortcutInfo */);
+
+                model.updateAndBindWorkspaceItem(() -> {
+                    PackageItemInfo pkgInfo = new PackageItemInfo(getIntentPackageName(), user);
+                    app.getApp().getIconCache().getTitleAndIconForApp(pkgInfo, false);
+                    try (LauncherIcons li = LauncherIcons.obtain(app.getApp().getContext())) {
+                        info.bitmap = li.badgeBitmap(info.bitmap.icon, pkgInfo.bitmap);
+                    }
+                    return info;
+                });
             }
         });
         return info;
diff --git a/src/com/android/launcher3/model/data/WorkspaceItemInfo.java b/src/com/android/launcher3/model/data/WorkspaceItemInfo.java
index afae2b192f..c6dde71640 100644
--- a/src/com/android/launcher3/model/data/WorkspaceItemInfo.java
+++ b/src/com/android/launcher3/model/data/WorkspaceItemInfo.java
@@ -21,13 +21,10 @@ import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ShortcutInfo;
-import android.os.Build.VERSION;
-import android.os.Build.VERSION_CODES;
 import android.text.TextUtils;
 import android.util.Log;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 
 import com.android.launcher3.Flags;
 import com.android.launcher3.LauncherSettings;
@@ -38,7 +35,6 @@ import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.util.ApiWrapper;
 import com.android.launcher3.util.ContentWriter;
-import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
 
 import java.util.Arrays;
 
@@ -50,17 +46,21 @@ public class WorkspaceItemInfo extends ItemInfoWithIcon {
     public static final int DEFAULT = 0;
 
     /**
-     * The shortcut was restored from a backup and it not ready to be used. This is automatically
+     * The shortcut was restored from a backup and it not ready to be used. This is
+     * automatically
      * set during backup/restore
      */
     public static final int FLAG_RESTORED_ICON = 1;
 
     /**
-     * The icon was added as an auto-install app, and is not ready to be used. This flag can't
-     * be present along with {@link #FLAG_RESTORED_ICON}, and is set during default layout
+     * The icon was added as an auto-install app, and is not ready to be used. This
+     * flag can't
+     * be present along with {@link #FLAG_RESTORED_ICON}, and is set during default
+     * layout
      * parsing.
      *
-     * OR this icon was added due to it being an active install session created by the user.
+     * OR this icon was added due to it being an active install session created by
+     * the user.
      */
     public static final int FLAG_AUTOINSTALL_ICON = 1 << 1;
 
@@ -94,16 +94,15 @@ public class WorkspaceItemInfo extends ItemInfoWithIcon {
     public int status;
 
     /**
-     * A set of person's Id associated with the WorkspaceItemInfo, this is only used if the item
+     * A set of person's Id associated with the WorkspaceItemInfo, this is only used
+     * if the item
      * represents a deep shortcut.
      */
-    @NonNull private String[] personKeys = Utilities.EMPTY_STRING_ARRAY;
+    @NonNull
+    private String[] personKeys = Utilities.EMPTY_STRING_ARRAY;
 
     public int options;
 
-    @Nullable
-    private ShortcutInfo mShortcutInfo = null;
-
     public WorkspaceItemInfo() {
         itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
     }
@@ -116,7 +115,7 @@ public class WorkspaceItemInfo extends ItemInfoWithIcon {
         personKeys = info.personKeys.clone();
     }
 
-    /** TODO: Remove this.  It's only called by ApplicationInfo.makeWorkspaceItem. */
+    /** TODO: Remove this. It's only called by ApplicationInfo.makeWorkspaceItem. */
     public WorkspaceItemInfo(AppInfo info) {
         super(info);
         title = Utilities.trim(info.title);
@@ -145,7 +144,7 @@ public class WorkspaceItemInfo extends ItemInfoWithIcon {
                 .put(Favorites.OPTIONS, options)
                 .put(Favorites.RESTORED, status);
 
-        if (!getMatchingLookupFlag().useLowRes()) {
+        if (!usingLowResIcon()) {
             writer.putIcon(bitmap, user);
         }
     }
@@ -160,7 +159,6 @@ public class WorkspaceItemInfo extends ItemInfoWithIcon {
         return (status & flag) != 0;
     }
 
-
     public final boolean isPromise() {
         return hasStatusFlag(FLAG_RESTORED_ICON | FLAG_AUTOINSTALL_ICON)
                 // For archived apps, promise icons are always ready to be displayed.
@@ -168,7 +166,8 @@ public class WorkspaceItemInfo extends ItemInfoWithIcon {
     }
 
     /**
-     * Returns true if the workspace item supports promise icon UI. There are a few cases where they
+     * Returns true if the workspace item supports promise icon UI. There are a few
+     * cases where they
      * are supported:
      * 1. Icons to be restored via backup/restore.
      * 2. Icons added as an auto-install app.
@@ -181,10 +180,8 @@ public class WorkspaceItemInfo extends ItemInfoWithIcon {
 
     public void updateFromDeepShortcutInfo(@NonNull final ShortcutInfo shortcutInfo,
             @NonNull final Context context) {
-        if (BubbleAnythingFlagHelper.enableCreateAnyBubble()) {
-            mShortcutInfo = shortcutInfo;
-        }
-        // {@link ShortcutInfo#getActivity} can change during an update. Recreate the intent
+        // {@link ShortcutInfo#getActivity} can change during an update. Recreate the
+        // intent
         intent = ShortcutKey.makeIntent(shortcutInfo);
         title = shortcutInfo.getShortLabel();
 
@@ -192,13 +189,7 @@ public class WorkspaceItemInfo extends ItemInfoWithIcon {
         if (TextUtils.isEmpty(label)) {
             label = shortcutInfo.getShortLabel();
         }
-        try {
-            contentDescription = context.getPackageManager().getUserBadgedLabel(label, user);
-        } catch (SecurityException e) {
-            contentDescription = null;
-            Log.e(TAG, "Failed to get content description", e);
-        }
-
+        contentDescription = context.getPackageManager().getUserBadgedLabel(label, user);
         if (shortcutInfo.isEnabled()) {
             runtimeStatusFlags &= ~FLAG_DISABLED_BY_PUBLISHER;
         } else {
@@ -227,13 +218,9 @@ public class WorkspaceItemInfo extends ItemInfoWithIcon {
         }
     }
 
-    @Nullable
-    public ShortcutInfo getDeepShortcutInfo() {
-        return mShortcutInfo;
-    }
-
     /**
-     * {@code true} if the shortcut is disabled due to its app being a lower version.
+     * {@code true} if the shortcut is disabled due to its app being a lower
+     * version.
      */
     public boolean isDisabledVersionLower() {
         return (runtimeStatusFlags & FLAG_DISABLED_VERSION_LOWER) != 0;
@@ -242,7 +229,8 @@ public class WorkspaceItemInfo extends ItemInfoWithIcon {
     /** Returns the WorkspaceItemInfo id associated with the deep shortcut. */
     public String getDeepShortcutId() {
         return itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT
-                ? getIntent().getStringExtra(ShortcutKey.EXTRA_SHORTCUT_ID) : null;
+                ? getIntent().getStringExtra(ShortcutKey.EXTRA_SHORTCUT_ID)
+                : null;
     }
 
     @NonNull
@@ -255,8 +243,10 @@ public class WorkspaceItemInfo extends ItemInfoWithIcon {
         ComponentName cn = super.getTargetComponent();
         if (cn == null && hasStatusFlag(
                 FLAG_SUPPORTS_WEB_UI | FLAG_AUTOINSTALL_ICON | FLAG_RESTORED_ICON)) {
-            // Legacy shortcuts and promise icons with web UI may not have a componentName but just
-            // a packageName. In that case create a empty componentName instead of adding additional
+            // Legacy shortcuts and promise icons with web UI may not have a componentName
+            // but just
+            // a packageName. In that case create a empty componentName instead of adding
+            // additional
             // check everywhere.
             String pkg = intent.getPackage();
             return pkg == null ? null : new ComponentName(pkg, IconCache.EMPTY_CLASS_NAME);
diff --git a/src/com/android/launcher3/notification/NotificationKeyData.java b/src/com/android/launcher3/notification/NotificationKeyData.java
index f3ad625c9e..e38824cb4b 100644
--- a/src/com/android/launcher3/notification/NotificationKeyData.java
+++ b/src/com/android/launcher3/notification/NotificationKeyData.java
@@ -18,8 +18,6 @@ package com.android.launcher3.notification;
 
 import android.app.Notification;
 import android.app.Person;
-import android.os.Build.VERSION;
-import android.os.Build.VERSION_CODES;
 import android.service.notification.StatusBarNotification;
 
 import androidx.annotation.NonNull;
@@ -65,12 +63,8 @@ public class NotificationKeyData {
         if (people == null || people.isEmpty()) {
             return Utilities.EMPTY_STRING_ARRAY;
         }
-        if (Utilities.ATLEAST_P) {
-            return people.stream().filter(person -> person.getKey() != null)
-                    .map(Person::getKey).sorted().toArray(String[]::new);
-        } else {
-            return Utilities.EMPTY_STRING_ARRAY;
-        }
+        return people.stream().filter(person -> person.getKey() != null)
+                .map(Person::getKey).sorted().toArray(String[]::new);
     }
 
     @Override
diff --git a/src/com/android/launcher3/notification/NotificationListener.java b/src/com/android/launcher3/notification/NotificationListener.java
index 012de3a804..e990e2ce18 100644
--- a/src/com/android/launcher3/notification/NotificationListener.java
+++ b/src/com/android/launcher3/notification/NotificationListener.java
@@ -180,9 +180,6 @@ public class NotificationListener extends NotificationListenerService {
                     for (NotificationsChangedListener listener : sNotificationsChangedListeners) {
                         listener.onNotificationPosted(msg.first, msg.second);
                     }
-                    Log.i(TAG, "received notification posted event - " + msg.first);
-                } else {
-                    Log.i(TAG, "received notification posted event, but there are no listeners");
                 }
                 break;
             case MSG_NOTIFICATION_REMOVED:
@@ -191,9 +188,6 @@ public class NotificationListener extends NotificationListenerService {
                     for (NotificationsChangedListener listener : sNotificationsChangedListeners) {
                         listener.onNotificationRemoved(msg.first, msg.second);
                     }
-                    Log.i(TAG, "received notification removed event - " + msg.first);
-                } else {
-                    Log.i(TAG, "received notification removed event, but there are no listeners");
                 }
                 break;
             case MSG_NOTIFICATION_FULL_REFRESH:
@@ -202,11 +196,6 @@ public class NotificationListener extends NotificationListenerService {
                         listener.onNotificationFullRefresh(
                                 (List) message.obj);
                     }
-                    ((List) message.obj).forEach(sbn -> Log.i(TAG,
-                            "Handling notification state refresh for " + sbn.getPackageName() + "#"
-                                    + sbn.getUserId()));
-                } else {
-                    Log.i(TAG, "received notification refresh event, but there are no listeners");
                 }
                 break;
         }
@@ -230,7 +219,6 @@ public class NotificationListener extends NotificationListenerService {
     @Override
     public void onListenerConnected() {
         super.onListenerConnected();
-        Log.i(TAG, "onListenerConnected");
         sIsConnected = true;
 
         // Register an observer to rebind the notification listener when dots are re-enabled.
@@ -257,7 +245,6 @@ public class NotificationListener extends NotificationListenerService {
     @Override
     public void onListenerDisconnected() {
         super.onListenerDisconnected();
-        Log.i(TAG, "onListenerDisconnected");
         sIsConnected = false;
         mSettingsCache.unregister(NOTIFICATION_BADGING_URI, mNotificationSettingsChangedListener);
         onNotificationFullRefresh();
diff --git a/src/com/android/launcher3/pageindicators/PageIndicator.java b/src/com/android/launcher3/pageindicators/PageIndicator.java
index a6f76c4d44..0640bf3672 100644
--- a/src/com/android/launcher3/pageindicators/PageIndicator.java
+++ b/src/com/android/launcher3/pageindicators/PageIndicator.java
@@ -15,8 +15,6 @@
  */
 package com.android.launcher3.pageindicators;
 
-import java.util.function.Consumer;
-
 /**
  * Base class for a page indicator.
  */
@@ -28,14 +26,6 @@ public interface PageIndicator {
 
     void setMarkersCount(int numMarkers);
 
-    /**
-     * This is only going to be used by the FolderPagedView's PageIndicator. A refactor is planned
-     * to separate the two purposes of this class, but in the meantime, this indicator will serve to
-     * let the folder snap to the page of its click, and also tell the PageIndicator not to draw
-     * arrows if the click listener is null (at least until after this is refactored).
-     */
-    void setArrowClickListener(Consumer listener);
-
     /**
      * Sets a flag indicating whether to pause scroll.
      * 

Should be set to {@code true} while the screen is binding or new data is being applied, diff --git a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java index cf49e2e693..8f85ffc9a3 100644 --- a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java +++ b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java @@ -16,7 +16,6 @@ package com.android.launcher3.pageindicators; -import static com.android.launcher3.Flags.enableLauncherVisualRefresh; import static com.android.launcher3.config.FeatureFlags.FOLDABLE_SINGLE_PAGE; import android.animation.Animator; @@ -32,13 +31,11 @@ import android.graphics.Paint; import android.graphics.Paint.Style; import android.graphics.Rect; import android.graphics.RectF; -import android.graphics.drawable.VectorDrawable; import android.os.Handler; import android.os.Looper; import android.util.AttributeSet; import android.util.FloatProperty; import android.util.IntProperty; -import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewOutlineProvider; @@ -46,29 +43,24 @@ import android.view.animation.Interpolator; import android.view.animation.OvershootInterpolator; import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; import com.android.launcher3.Insettable; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.config.FeatureFlags; -import java.util.function.Consumer; - import app.lawnchair.theme.color.tokens.ColorTokens; /** - * {@link PageIndicator} which shows dots per page. The active page is shown with the current + * {@link PageIndicator} which shows dots per page. The active page is shown + * with the current * accent color. - *

- * TODO(b/402258632): Split PageIndicatorDots into 2 different classes: FolderPageIndicator & - * WorkspacePageIndicator. A lot of the functionality in this class is only used by one UI purpose. */ public class PageIndicatorDots extends View implements Insettable, PageIndicator { private static final float SHIFT_PER_ANIMATION = 0.5f; - private static final float SHIFT_THRESHOLD = (enableLauncherVisualRefresh() ? 0.5f : 0.2f); - private static final long ANIMATION_DURATION = (enableLauncherVisualRefresh() ? 200 : 150); + private static final float SHIFT_THRESHOLD = 0.1f; + private static final long ANIMATION_DURATION = 150; private static final int PAGINATION_FADE_DELAY = ViewConfiguration.getScrollDefaultDelay(); private static final int PAGINATION_FADE_IN_DURATION = 83; private static final int PAGINATION_FADE_OUT_DURATION = 167; @@ -77,66 +69,52 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator private static final int ENTER_ANIMATION_STAGGERED_DELAY = 150; private static final int ENTER_ANIMATION_DURATION = 400; - private static final int LARGE_HEIGHT_MULTIPLIER = 12; - private static final int SMALL_HEIGHT_MULTIPLIER = 4; - private static final int LARGE_WIDTH_MULTIPLIER = 5; - private static final int SMALL_WIDTH_MULTIPLIER = 3; - private static final float ARROW_TOUCH_BOX_FACTOR = 5f; - private static final int PAGE_INDICATOR_ALPHA = 255; private static final int DOT_ALPHA = 128; private static final float DOT_ALPHA_FRACTION = 0.5f; - private static final int DOT_GAP_FACTOR = 4; + private final int DOT_GAP_FACTOR = FeatureFlags.showDotPagination(getContext()) ? 4 : 3; private static final int VISIBLE_ALPHA = 255; private static final int INVISIBLE_ALPHA = 0; private Paint mPaginationPaint; - private Consumer mOnArrowClickListener; // This value approximately overshoots to 1.5 times the original size. private static final float ENTER_ANIMATION_OVERSHOOT_TENSION = 4.9f; - // This is used to optimize the onDraw method by not constructing a new RectF each draw. private static final RectF sTempRect = new RectF(); - private static final RectF sLastActiveRect = new RectF(); - private static final FloatProperty CURRENT_POSITION = - new FloatProperty("current_position") { - @Override - public Float get(PageIndicatorDots obj) { - return obj.mCurrentPosition; - } + private static final FloatProperty CURRENT_POSITION = new FloatProperty( + "current_position") { + @Override + public Float get(PageIndicatorDots obj) { + return obj.mCurrentPosition; + } - @Override - public void setValue(PageIndicatorDots obj, float pos) { - obj.mCurrentPosition = pos; - obj.invalidate(); - obj.invalidateOutline(); - } - }; + @Override + public void setValue(PageIndicatorDots obj, float pos) { + obj.mCurrentPosition = pos; + obj.invalidate(); + obj.invalidateOutline(); + } + }; - private static final IntProperty PAGINATION_ALPHA = - new IntProperty("pagination_alpha") { - @Override - public Integer get(PageIndicatorDots obj) { - return obj.mPaginationPaint.getAlpha(); - } + private static final IntProperty PAGINATION_ALPHA = new IntProperty( + "pagination_alpha") { + @Override + public Integer get(PageIndicatorDots obj) { + return obj.mPaginationPaint.getAlpha(); + } - @Override - public void setValue(PageIndicatorDots obj, int alpha) { - obj.mPaginationPaint.setAlpha(alpha); - obj.invalidate(); - } - }; + @Override + public void setValue(PageIndicatorDots obj, int alpha) { + obj.mPaginationPaint.setAlpha(alpha); + obj.invalidate(); + } + }; private final Handler mDelayedPaginationFadeHandler = new Handler(Looper.getMainLooper()); private final float mDotRadius; - private final float mGapWidth; private final float mCircleGap; private final boolean mIsRtl; - private final VectorDrawable mArrowRight; - private final VectorDrawable mArrowLeft; - private final Rect mArrowRightBounds = new Rect(); - private final Rect mArrowLeftBounds = new Rect(); private int mNumPages; private int mActivePage; @@ -147,25 +125,22 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator /** * The current position of the active dot including the animation progress. * For ex: - * 0.0 => Active dot is at position 0 + * 0.0 => Active dot is at position 0 * 0.33 => Active dot is at position 0 and is moving towards 1 * 0.50 => Active dot is at position [0, 1] * 0.77 => Active dot has left position 0 and is collapsing towards position 1 - * 1.0 => Active dot is at position 1 + * 1.0 => Active dot is at position 1 */ private float mCurrentPosition; - private int mLastPosition; private float mFinalPosition; private boolean mIsScrollPaused; - @VisibleForTesting - boolean mIsTwoPanels; + private boolean mIsTwoPanels; private ObjectAnimator mAnimator; private @Nullable ObjectAnimator mAlphaAnimator; private float[] mEntryAnimationRadiusFactors; - private final Runnable mHidePaginationRunnable = - () -> animatePaginationToAlpha(INVISIBLE_ALPHA); + private final Runnable mHidePaginationRunnable = () -> animatePaginationToAlpha(INVISIBLE_ALPHA); public PageIndicatorDots(Context context) { this(context, null); @@ -181,20 +156,18 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator mPaginationPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaginationPaint.setStyle(Style.FILL); mPaginationPaint.setColor(ColorTokens.FolderPaginationColor.resolveColor(context)); - mDotRadius = getResources().getDimension(R.dimen.page_indicator_dot_size) / 2; - mGapWidth = getResources().getDimension(R.dimen.page_indicator_gap_width); - mCircleGap = (enableLauncherVisualRefresh()) - ? mDotRadius * 2 + mGapWidth - : DOT_GAP_FACTOR * mDotRadius; + mDotRadius = (FeatureFlags.showDotPagination(context) + ? getResources().getDimension(R.dimen.page_indicator_dot_size_v2) + : getResources().getDimension(R.dimen.page_indicator_dot_size)) + / 2; + mCircleGap = DOT_GAP_FACTOR * mDotRadius; setOutlineProvider(new MyOutlineProver()); mIsRtl = Utilities.isRtl(getResources()); - mArrowRight = (VectorDrawable) getResources().getDrawable(R.drawable.ic_chevron_end); - mArrowLeft = (VectorDrawable) getResources().getDrawable(R.drawable.ic_chevron_start); } @Override public void setScroll(int currentScroll, int totalScroll) { - if (currentScroll == 0 && totalScroll == 0) { + if (FeatureFlags.showDotPagination(getContext()) && currentScroll == 0 && totalScroll == 0) { CURRENT_POSITION.set(this, (float) mActivePage); return; } @@ -218,47 +191,36 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator mTotalScroll = totalScroll; - if (enableLauncherVisualRefresh()) { - float scrollPerPage = (float) totalScroll / (mNumPages - 1); - float position = currentScroll / scrollPerPage; - animateToPosition(Math.round(position)); + int scrollPerPage = totalScroll / (mNumPages - 1); + int pageToLeft = scrollPerPage == 0 ? 0 : currentScroll / scrollPerPage; + int pageToLeftScroll = pageToLeft * scrollPerPage; + int pageToRightScroll = pageToLeftScroll + scrollPerPage; - float delta = Math.abs((int) position - position); - if (mShouldAutoHide && (delta < 0.1 || delta > 0.9)) { + float scrollThreshold = SHIFT_THRESHOLD * scrollPerPage; + if (currentScroll < pageToLeftScroll + scrollThreshold) { + // scroll is within the left page's threshold + animateToPosition(pageToLeft); + if (mShouldAutoHide) { + hideAfterDelay(); + } + } else if (currentScroll > pageToRightScroll - scrollThreshold) { + // scroll is far enough from left page to go to the right page + animateToPosition(pageToLeft + 1); + if (mShouldAutoHide) { hideAfterDelay(); } } else { - int scrollPerPage = totalScroll / (mNumPages - 1); - int pageToLeft = scrollPerPage == 0 ? 0 : currentScroll / scrollPerPage; - int pageToLeftScroll = pageToLeft * scrollPerPage; - int pageToRightScroll = pageToLeftScroll + scrollPerPage; - - float scrollThreshold = SHIFT_THRESHOLD * scrollPerPage; - if (currentScroll < pageToLeftScroll + scrollThreshold) { - // scroll is within the left page's threshold - animateToPosition(pageToLeft); - if (mShouldAutoHide) { - hideAfterDelay(); - } - } else if (currentScroll > pageToRightScroll - scrollThreshold) { - // scroll is far enough from left page to go to the right page - animateToPosition(pageToLeft + 1); - if (mShouldAutoHide) { - hideAfterDelay(); - } - } else { - // scroll is between left and right page - animateToPosition(pageToLeft + SHIFT_PER_ANIMATION); - if (mShouldAutoHide) { - mDelayedPaginationFadeHandler.removeCallbacksAndMessages(null); - } + // scroll is between left and right page + animateToPosition(pageToLeft + SHIFT_PER_ANIMATION); + if (mShouldAutoHide) { + mDelayedPaginationFadeHandler.removeCallbacksAndMessages(null); } } } @Override public void setShouldAutoHide(boolean shouldAutoHide) { - mShouldAutoHide = shouldAutoHide; + mShouldAutoHide = shouldAutoHide && FeatureFlags.showDotPagination(getContext()); if (shouldAutoHide && mPaginationPaint.getAlpha() > INVISIBLE_ALPHA) { hideAfterDelay(); } else if (!shouldAutoHide) { @@ -278,7 +240,8 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator private void animatePaginationToAlpha(int alpha) { if (alpha == mToAlpha) { - // Ignore the new animation if it is going to the same alpha as the current animation. + // Ignore the new animation if it is going to the same alpha as the current + // animation. return; } @@ -324,23 +287,15 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator private void animateToPosition(float position) { mFinalPosition = position; - if (!enableLauncherVisualRefresh() - && Math.abs(mCurrentPosition - mFinalPosition) < SHIFT_THRESHOLD) { + if (Math.abs(mCurrentPosition - mFinalPosition) < SHIFT_THRESHOLD) { mCurrentPosition = mFinalPosition; } - if (mAnimator == null && Float.compare(mCurrentPosition, position) != 0) { - float positionForThisAnim = enableLauncherVisualRefresh() - ? position - : (mCurrentPosition > mFinalPosition - ? mCurrentPosition - SHIFT_PER_ANIMATION - : mCurrentPosition + SHIFT_PER_ANIMATION); + if (mAnimator == null && Float.compare(mCurrentPosition, mFinalPosition) != 0) { + float positionForThisAnim = mCurrentPosition > mFinalPosition ? mCurrentPosition - SHIFT_PER_ANIMATION + : mCurrentPosition + SHIFT_PER_ANIMATION; mAnimator = ObjectAnimator.ofFloat(this, CURRENT_POSITION, positionForThisAnim); mAnimator.addListener(new AnimationCycleListener()); mAnimator.setDuration(ANIMATION_DURATION); - if (enableLauncherVisualRefresh()) { - mLastPosition = (int) mCurrentPosition; - mAnimator.setInterpolator(new OvershootInterpolator()); - } mAnimator.start(); } } @@ -363,7 +318,6 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator invalidate(); } - // TODO(b/394355070): Verify Folder Entry Animation works correctly with visual updates public void playEntryAnimation() { int count = mEntryAnimationRadiusFactors.length; if (count == 0) { @@ -403,7 +357,8 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator @Override public void setActiveMarker(int activePage) { - // In unfolded foldables, every page has two CellLayouts, so we need to halve the active + // In unfolded foldables, every page has two CellLayouts, so we need to halve + // the active // page for it to be accurate. if (mIsTwoPanels && !FOLDABLE_SINGLE_PAGE.get()) { activePage = activePage / 2; @@ -427,11 +382,6 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator requestLayout(); } - @Override - public void setArrowClickListener(Consumer listener) { - mOnArrowClickListener = listener; - } - @Override public void setPauseScroll(boolean pause, boolean isTwoPanels) { mIsTwoPanels = isTwoPanels; @@ -446,17 +396,13 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - // TODO(b/394355070): Verify Folder Entry Animation works correctly with visual updates - // Add extra spacing of mDotRadius on all sides so than entry animation could be run - // and so the hitboxes of arrows can be clicked easier. - int width = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY ? - MeasureSpec.getSize(widthMeasureSpec) - : (int) ((mNumPages * ((enableLauncherVisualRefresh()) - ? LARGE_WIDTH_MULTIPLIER : SMALL_WIDTH_MULTIPLIER) + 2) * mDotRadius); + // Add extra spacing of mDotRadius on all sides so than entry animation could be + // run. + int width = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY ? MeasureSpec.getSize(widthMeasureSpec) + : (int) ((mNumPages * 3 + 2) * mDotRadius); int height = MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY ? MeasureSpec.getSize(heightMeasureSpec) - : (int) (((enableLauncherVisualRefresh()) - ? LARGE_HEIGHT_MULTIPLIER : SMALL_HEIGHT_MULTIPLIER) * mDotRadius); + : (int) (4 * mDotRadius); setMeasuredDimension(width, height); } @@ -471,212 +417,45 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator return; } + // Draw all page indicators; float circleGap = mCircleGap; - float x = ((float) getWidth() / 2) - (mCircleGap * ((float) mNumPages - 1) / 2); + float startX = ((float) getWidth() / 2) + - (mCircleGap * (((float) mNumPages - 1) / 2)) + - mDotRadius; + + float x = startX + mDotRadius; float y = getHeight() / 2; if (mEntryAnimationRadiusFactors != null) { - if (enableLauncherVisualRefresh()) { - x -= mDotRadius; - if (mIsRtl) { - x = getWidth() - x; - circleGap = -circleGap; - } - sTempRect.top = y - mDotRadius; - sTempRect.bottom = y + mDotRadius; - - for (int i = 0; i < mEntryAnimationRadiusFactors.length; i++) { - if (i == mActivePage) { - if (mIsRtl) { - sTempRect.left = x - (mDotRadius * 3); - sTempRect.right = x + mDotRadius; - x += circleGap - (mDotRadius * 2); - } else { - sTempRect.left = x - mDotRadius; - sTempRect.right = x + (mDotRadius * 3); - x += circleGap + (mDotRadius * 2); - } - scale(sTempRect, mEntryAnimationRadiusFactors[i]); - float scaledRadius = mDotRadius * mEntryAnimationRadiusFactors[i]; - mPaginationPaint.setAlpha(PAGE_INDICATOR_ALPHA); - canvas.drawRoundRect(sTempRect, scaledRadius, scaledRadius, - mPaginationPaint); - } else { - mPaginationPaint.setAlpha(DOT_ALPHA); - canvas.drawCircle(x, y, mDotRadius * mEntryAnimationRadiusFactors[i], - mPaginationPaint); - x += circleGap; - } - } - } else { - // During entry animation, only draw the circles - - if (mIsRtl) { - x = getWidth() - x; - circleGap = -circleGap; - } - for (int i = 0; i < mEntryAnimationRadiusFactors.length; i++) { - mPaginationPaint.setAlpha(i == mActivePage ? PAGE_INDICATOR_ALPHA : DOT_ALPHA); - canvas.drawCircle(x, y, mDotRadius * mEntryAnimationRadiusFactors[i], - mPaginationPaint); - x += circleGap; - } + // During entry animation, only draw the circles + if (mIsRtl) { + x = getWidth() - x; + circleGap = -circleGap; + } + for (int i = 0; i < mEntryAnimationRadiusFactors.length; i++) { + mPaginationPaint.setAlpha(i == mActivePage ? PAGE_INDICATOR_ALPHA : DOT_ALPHA); + canvas.drawCircle(x, y, mDotRadius * mEntryAnimationRadiusFactors[i], + mPaginationPaint); + x += circleGap; } } else { - // Save the current alpha value, so we can reset to it again after drawing the dots int alpha = mPaginationPaint.getAlpha(); - if (enableLauncherVisualRefresh()) { - int nonActiveAlpha = (int) (alpha * DOT_ALPHA_FRACTION); - - float diameter = 2 * mDotRadius; - sTempRect.top = y - mDotRadius; - sTempRect.bottom = y + mDotRadius; - sTempRect.left = x - diameter; - - float currentPosition = mCurrentPosition; - float lastPosition = mLastPosition; - - if (mIsRtl) { - currentPosition = mNumPages - currentPosition - 1; - lastPosition = mNumPages - lastPosition - 1; - } - float posDif = Math.abs(lastPosition - currentPosition); - float boundedPosition = (posDif > 1) - ? Math.round(currentPosition) - : currentPosition; - float bounceProgress = (posDif > 1) ? posDif - 1 : 0; - float bounceAdjustment = Math.abs(currentPosition - boundedPosition) * diameter; - - if (mOnArrowClickListener != null && boundedPosition >= 1) { - // Here we draw the Left Arrow - mArrowLeft.setAlpha(alpha); - int size = (int) (mGapWidth * 4); - mArrowLeftBounds.left = (int) (sTempRect.left - mGapWidth - size); - mArrowLeftBounds.top = (int) (y - size / 2); - mArrowLeftBounds.right = (int) (sTempRect.left - mGapWidth); - mArrowLeftBounds.bottom = (int) (y + size / 2); - mArrowLeft.setBounds(mArrowLeftBounds); - mArrowLeft.draw(canvas); - } - - // Here we draw the dots, one at a time from the left-most dot to the right-most dot - // 1.0 => 000000 000000111111 000000 - // 1.3 => 000000 0000001111 11000000 - // 1.6 => 000000 00000011 1111000000 - // 2.0 => 000000 000000 111111000000 - for (int i = 0; i < mNumPages; i++) { - mPaginationPaint.setAlpha(nonActiveAlpha); - float delta = Math.abs(boundedPosition - i); - if (delta <= SHIFT_THRESHOLD) { - mPaginationPaint.setAlpha(alpha); - } - - // If boundedPosition is 3.3, both 3 and 4 should enter this condition. - // If boundedPosition is 3, only 3 should enter this condition. - if (delta < 1) { - sTempRect.right = sTempRect.left + diameter + ((1 - delta) * diameter); - - // While the animation is shifting the active pagination dots size from - // the previously active one, to the newly active dot, there is no bounce - // adjustment. The bounce happens in the "Overshoot" phase of the animation. - // lastPosition is used to determine when the currentPosition is just - // leaving the page, or if it is in the overshoot phase. - if (boundedPosition == i && bounceProgress != 0) { - if (lastPosition < currentPosition) { - sTempRect.left -= bounceAdjustment; - } else { - sTempRect.right += bounceAdjustment; - } - } - } else { - sTempRect.right = sTempRect.left + diameter; - - if (lastPosition == i && bounceProgress != 0) { - if (lastPosition > currentPosition) { - sTempRect.left += bounceAdjustment; - } else { - sTempRect.right -= bounceAdjustment; - } - } - } - if (Math.round(mCurrentPosition) == i) { - sLastActiveRect.set(sTempRect); - if (mCurrentPosition == 0) { - // The outline is calculated before onDraw is called. If the user has - // paginated, closed the folder, and opened the folder again, the - // first drawn outline will use stale bounds. - // Invalidation is cheap, and is only needed when scroll is 0. - invalidateOutline(); - } - } - canvas.drawRoundRect(sTempRect, mDotRadius, mDotRadius, mPaginationPaint); - - // TODO(b/394355070) Verify RTL experience works correctly with visual updates - sTempRect.left = sTempRect.right + mGapWidth; - } - - if (mOnArrowClickListener != null && boundedPosition <= mNumPages - 2) { - // Here we draw the Right Arrow - mArrowRight.setAlpha(alpha); - int size = (int) (mGapWidth * 4); - mArrowRightBounds.left = (int) sTempRect.left; - mArrowRightBounds.top = (int) (y - size / 2); - mArrowRightBounds.right = (int) (int) (sTempRect.left + size); - mArrowRightBounds.bottom = (int) (y + size / 2); - mArrowRight.setBounds(mArrowRightBounds); - mArrowRight.draw(canvas); - } - } else { - // Here we draw the dots - mPaginationPaint.setAlpha((int) (alpha * DOT_ALPHA_FRACTION)); - for (int i = 0; i < mNumPages; i++) { - canvas.drawCircle(x, y, mDotRadius, mPaginationPaint); - x += circleGap; - } - - // Here we draw the current page indicator - mPaginationPaint.setAlpha(alpha); - canvas.drawRoundRect(getActiveRect(), mDotRadius, mDotRadius, mPaginationPaint); + // Here we draw the dots + mPaginationPaint.setAlpha(FeatureFlags.showDotPagination(getContext()) + ? ((int) (alpha * DOT_ALPHA_FRACTION)) + : DOT_ALPHA); + for (int i = 0; i < mNumPages; i++) { + canvas.drawCircle(x, y, mDotRadius, mPaginationPaint); + x += circleGap; } - // Reset the alpha so it doesn't become progressively more transparent each onDraw call - mPaginationPaint.setAlpha(alpha); + // Here we draw the current page indicator + mPaginationPaint.setAlpha(FeatureFlags.showDotPagination(getContext()) ? alpha : PAGE_INDICATOR_ALPHA); + canvas.drawRoundRect(getActiveRect(), mDotRadius, mDotRadius, mPaginationPaint); } } - @Override - public boolean onTouchEvent(MotionEvent ev) { - if (mOnArrowClickListener == null) { - // No - Op. Don't care about touch events - } else if ((mIsRtl && withinExpandedBounds(mArrowRightBounds, ev)) - || (!mIsRtl && withinExpandedBounds(mArrowLeftBounds, ev))) { - mOnArrowClickListener.accept(Direction.START); - } else if ((mIsRtl && withinExpandedBounds(mArrowLeftBounds, ev)) - || (!mIsRtl && withinExpandedBounds(mArrowRightBounds, ev))) { - mOnArrowClickListener.accept(Direction.END); - } - return super.onTouchEvent(ev); - } - - // For larger Touch box - private boolean withinExpandedBounds(Rect rect, MotionEvent ev) { - RectF scaledRect = new RectF(rect); - scale(scaledRect, ARROW_TOUCH_BOX_FACTOR); - return scaledRect.contains(ev.getX(), ev.getY()); - } - - private static void scale(RectF rect, float factor) { - float horizontalAdjustment = rect.width() * (factor - 1) / 2; - float verticalAdjustment = rect.height() * (factor - 1) / 2; - - rect.top -= verticalAdjustment; - rect.bottom += verticalAdjustment; - - rect.left -= horizontalAdjustment; - rect.right += horizontalAdjustment; - } - private RectF getActiveRect() { float startCircle = (int) mCurrentPosition; float delta = mCurrentPosition - startCircle; @@ -709,35 +488,18 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator return sTempRect; } - @VisibleForTesting - int getActivePage() { - return mActivePage; - } - - @VisibleForTesting - int getNumPages() { - return mNumPages; - } - - @VisibleForTesting - float getCurrentPosition() { - return mCurrentPosition; - } - private class MyOutlineProver extends ViewOutlineProvider { @Override public void getOutline(View view, Outline outline) { if (mEntryAnimationRadiusFactors == null) { - RectF activeRect = enableLauncherVisualRefresh() - ? sLastActiveRect : getActiveRect(); + RectF activeRect = getActiveRect(); outline.setRoundRect( (int) activeRect.left, (int) activeRect.top, (int) activeRect.right, (int) activeRect.bottom, - mDotRadius - ); + mDotRadius); } } } @@ -757,7 +519,7 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator @Override public void onAnimationEnd(Animator animation) { if (!mCancelled) { - if (mShouldAutoHide) { + if (mShouldAutoHide && FeatureFlags.showDotPagination(getContext())) { hideAfterDelay(); } mAnimator = null; @@ -767,7 +529,8 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator } /** - * We need to override setInsets to prevent InsettableFrameLayout from applying different + * We need to override setInsets to prevent InsettableFrameLayout from applying + * different * margins on the pagination. */ @Override diff --git a/src/com/android/launcher3/pageindicators/WorkspacePageIndicator.java b/src/com/android/launcher3/pageindicators/WorkspacePageIndicator.java new file mode 100644 index 0000000000..bde4e525a1 --- /dev/null +++ b/src/com/android/launcher3/pageindicators/WorkspacePageIndicator.java @@ -0,0 +1,265 @@ +package com.android.launcher3.pageindicators; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Rect; +import android.os.Handler; +import android.os.Looper; +import android.util.AttributeSet; +import android.util.Property; +import android.view.View; +import android.view.ViewConfiguration; + +import com.android.launcher3.Insettable; +import com.android.launcher3.Launcher; +import com.android.launcher3.R; +import com.android.launcher3.Utilities; +import com.android.launcher3.util.Themes; + +/** + * A PageIndicator that briefly shows a fraction of a line when moving between pages + * + * The fraction is 1 / number of pages and the position is based on the progress of the page scroll. + */ +public class WorkspacePageIndicator extends View implements Insettable, PageIndicator { + + private static final int LINE_ANIMATE_DURATION = ViewConfiguration.getScrollBarFadeDuration(); + private static final int LINE_FADE_DELAY = ViewConfiguration.getScrollDefaultDelay(); + public static final int WHITE_ALPHA = (int) (0.70f * 255); + public static final int BLACK_ALPHA = (int) (0.65f * 255); + + private static final int LINE_ALPHA_ANIMATOR_INDEX = 0; + private static final int NUM_PAGES_ANIMATOR_INDEX = 1; + private static final int TOTAL_SCROLL_ANIMATOR_INDEX = 2; + private static final int ANIMATOR_COUNT = 3; + + private ValueAnimator[] mAnimators = new ValueAnimator[ANIMATOR_COUNT]; + + private final Handler mDelayedLineFadeHandler = new Handler(Looper.getMainLooper()); + private final Launcher mLauncher; + + private boolean mShouldAutoHide = true; + + // The alpha of the line when it is showing. + private int mActiveAlpha = 0; + // The alpha that the line is being animated to or already at (either 0 or mActiveAlpha). + private int mToAlpha; + // A float value representing the number of pages, to allow for an animation when it changes. + private float mNumPagesFloat; + private int mCurrentScroll; + private int mTotalScroll; + private Paint mLinePaint; + private final int mLineHeight; + + private static final Property PAINT_ALPHA + = new Property(Integer.class, "paint_alpha") { + @Override + public Integer get(WorkspacePageIndicator obj) { + return obj.mLinePaint.getAlpha(); + } + + @Override + public void set(WorkspacePageIndicator obj, Integer alpha) { + obj.mLinePaint.setAlpha(alpha); + obj.invalidate(); + } + }; + + private static final Property NUM_PAGES + = new Property(Float.class, "num_pages") { + @Override + public Float get(WorkspacePageIndicator obj) { + return obj.mNumPagesFloat; + } + + @Override + public void set(WorkspacePageIndicator obj, Float numPages) { + obj.mNumPagesFloat = numPages; + obj.invalidate(); + } + }; + + private static final Property TOTAL_SCROLL + = new Property(Integer.class, "total_scroll") { + @Override + public Integer get(WorkspacePageIndicator obj) { + return obj.mTotalScroll; + } + + @Override + public void set(WorkspacePageIndicator obj, Integer totalScroll) { + obj.mTotalScroll = totalScroll; + obj.invalidate(); + } + }; + + private Runnable mHideLineRunnable = () -> animateLineToAlpha(0); + + public WorkspacePageIndicator(Context context) { + this(context, null); + } + + public WorkspacePageIndicator(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public WorkspacePageIndicator(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + Resources res = context.getResources(); + mLinePaint = new Paint(); + mLinePaint.setAlpha(0); + + mLauncher = Launcher.getLauncher(context); + mLineHeight = res.getDimensionPixelSize(R.dimen.workspace_page_indicator_line_height); + + boolean darkText = Themes.getAttrBoolean(mLauncher, R.attr.isWorkspaceDarkText); + mActiveAlpha = darkText ? BLACK_ALPHA : WHITE_ALPHA; + mLinePaint.setColor(darkText ? Color.BLACK : Color.WHITE); + } + + @Override + protected void onDraw(Canvas canvas) { + if (mTotalScroll == 0 || mNumPagesFloat == 0) { + return; + } + + // Compute and draw line rect. + float progress = Utilities.boundToRange(((float) mCurrentScroll) / mTotalScroll, 0f, 1f); + int availableWidth = getWidth(); + int lineWidth = (int) (availableWidth / mNumPagesFloat); + int lineLeft = (int) (progress * (availableWidth - lineWidth)); + int lineRight = lineLeft + lineWidth; + + canvas.drawRoundRect(lineLeft, getHeight() / 2 - mLineHeight / 2, lineRight, + getHeight() / 2 + mLineHeight / 2, mLineHeight, mLineHeight, mLinePaint); + } + + @Override + public void setScroll(int currentScroll, int totalScroll) { + if (getAlpha() == 0) { + return; + } + animateLineToAlpha(mActiveAlpha); + + mCurrentScroll = currentScroll; + if (mTotalScroll == 0) { + mTotalScroll = totalScroll; + } else if (mTotalScroll != totalScroll) { + animateToTotalScroll(totalScroll); + } else { + invalidate(); + } + + if (mShouldAutoHide) { + hideAfterDelay(); + } + } + + private void hideAfterDelay() { + mDelayedLineFadeHandler.removeCallbacksAndMessages(null); + mDelayedLineFadeHandler.postDelayed(mHideLineRunnable, LINE_FADE_DELAY); + } + + @Override + public void setActiveMarker(int activePage) { } + + @Override + public void setMarkersCount(int numMarkers) { + if (Float.compare(numMarkers, mNumPagesFloat) != 0) { + setupAndRunAnimation(ObjectAnimator.ofFloat(this, NUM_PAGES, numMarkers), + NUM_PAGES_ANIMATOR_INDEX); + } else { + if (mAnimators[NUM_PAGES_ANIMATOR_INDEX] != null) { + mAnimators[NUM_PAGES_ANIMATOR_INDEX].cancel(); + mAnimators[NUM_PAGES_ANIMATOR_INDEX] = null; + } + } + } + + @Override + public void setShouldAutoHide(boolean shouldAutoHide) { + mShouldAutoHide = shouldAutoHide; + if (shouldAutoHide && mLinePaint.getAlpha() > 0) { + hideAfterDelay(); + } else if (!shouldAutoHide) { + mDelayedLineFadeHandler.removeCallbacksAndMessages(null); + } + } + + private void animateLineToAlpha(int alpha) { + if (alpha == mToAlpha) { + // Ignore the new animation if it is going to the same alpha as the current animation. + return; + } + mToAlpha = alpha; + setupAndRunAnimation(ObjectAnimator.ofInt(this, PAINT_ALPHA, alpha), + LINE_ALPHA_ANIMATOR_INDEX); + } + + private void animateToTotalScroll(int totalScroll) { + setupAndRunAnimation(ObjectAnimator.ofInt(this, TOTAL_SCROLL, totalScroll), + TOTAL_SCROLL_ANIMATOR_INDEX); + } + + /** + * Starts the given animator and stores it in the provided index in {@link #mAnimators} until + * the animation ends. + * + * If an animator is already at the index (i.e. it is already playing), it is canceled and + * replaced with the new animator. + */ + private void setupAndRunAnimation(ValueAnimator animator, final int animatorIndex) { + if (mAnimators[animatorIndex] != null) { + mAnimators[animatorIndex].cancel(); + } + mAnimators[animatorIndex] = animator; + mAnimators[animatorIndex].addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mAnimators[animatorIndex] = null; + } + }); + mAnimators[animatorIndex].setDuration(LINE_ANIMATE_DURATION); + mAnimators[animatorIndex].start(); + } + + /** + * Pauses all currently running animations. + */ + @Override + public void pauseAnimations() { + for (int i = 0; i < ANIMATOR_COUNT; i++) { + if (mAnimators[i] != null) { + mAnimators[i].pause(); + } + } + } + + /** + * Force-ends all currently running or paused animations. + */ + @Override + public void skipAnimationsToEnd() { + for (int i = 0; i < ANIMATOR_COUNT; i++) { + if (mAnimators[i] != null) { + mAnimators[i].end(); + } + } + } + + /** + * We need to override setInsets to prevent InsettableFrameLayout from applying different + * margins on the page indicator. + */ + @Override + public void setInsets(Rect insets) { + } +} diff --git a/src/com/android/launcher3/pm/InstallSessionHelper.java b/src/com/android/launcher3/pm/InstallSessionHelper.java index 46ba993bea..acd2bfa416 100644 --- a/src/com/android/launcher3/pm/InstallSessionHelper.java +++ b/src/com/android/launcher3/pm/InstallSessionHelper.java @@ -19,6 +19,7 @@ package com.android.launcher3.pm; import static com.android.launcher3.Utilities.ATLEAST_V; import android.content.Context; +import android.content.pm.ApplicationInfo; import android.content.pm.LauncherApps; import android.content.pm.PackageInstaller; import android.content.pm.PackageInstaller.SessionInfo; @@ -34,18 +35,16 @@ import androidx.annotation.WorkerThread; import com.android.launcher3.Flags; import com.android.launcher3.LauncherPrefs; import com.android.launcher3.SessionCommitReceiver; -import com.android.launcher3.dagger.ApplicationContext; -import com.android.launcher3.dagger.LauncherAppSingleton; -import com.android.launcher3.dagger.LauncherBaseAppComponent; import com.android.launcher3.Utilities; import com.android.launcher3.logging.FileLog; import com.android.launcher3.model.ItemInstallQueue; -import com.android.launcher3.util.ApplicationInfoWrapper; -import com.android.launcher3.util.DaggerSingletonObject; import com.android.launcher3.util.IntArray; import com.android.launcher3.util.IntSet; +import com.android.launcher3.util.MainThreadInitializedObject; +import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.util.Preconditions; +import com.android.launcher3.util.SafeCloseable; import java.util.ArrayList; import java.util.HashMap; @@ -53,14 +52,11 @@ import java.util.Iterator; import java.util.List; import java.util.Objects; -import javax.inject.Inject; - /** * Utility class to tracking install sessions */ @SuppressWarnings("NewApi") -@LauncherAppSingleton -public class InstallSessionHelper { +public class InstallSessionHelper implements SafeCloseable { @NonNull private static final String LOG = "InstallSessionHelper"; @@ -73,8 +69,8 @@ public class InstallSessionHelper { private static final boolean DEBUG = false; @NonNull - public static final DaggerSingletonObject INSTANCE = - new DaggerSingletonObject<>(LauncherBaseAppComponent::getInstallSessionHelper); + public static final MainThreadInitializedObject INSTANCE = + new MainThreadInitializedObject<>(InstallSessionHelper::new); @Nullable private final LauncherApps mLauncherApps; @@ -91,13 +87,15 @@ public class InstallSessionHelper { @Nullable private IntSet mPromiseIconIds; - @Inject - public InstallSessionHelper(@NonNull @ApplicationContext final Context context) { + public InstallSessionHelper(@NonNull final Context context) { mInstaller = context.getPackageManager().getPackageInstaller(); mAppContext = context.getApplicationContext(); mLauncherApps = context.getSystemService(LauncherApps.class); } + @Override + public void close() { } + @WorkerThread @NonNull private IntSet getPromiseIconIds() { @@ -177,7 +175,8 @@ public class InstallSessionHelper { synchronized (mSessionVerifiedMap) { if (!mSessionVerifiedMap.containsKey(pkg)) { boolean hasSystemFlag = DEBUG || mAppContext.getPackageName().equals(pkg) - || new ApplicationInfoWrapper(mAppContext, pkg, user).isSystem(); + || PackageManagerHelper.INSTANCE.get(mAppContext) + .getApplicationInfo(pkg, user, ApplicationInfo.FLAG_SYSTEM) != null; mSessionVerifiedMap.put(pkg, hasSystemFlag); } } @@ -224,7 +223,7 @@ public class InstallSessionHelper { @WorkerThread void tryQueuePromiseAppIcon(@Nullable final PackageInstaller.SessionInfo sessionInfo) { if (sessionInfo != null - && SessionCommitReceiver.isEnabled(mAppContext, getUserHandle(sessionInfo)) + && SessionCommitReceiver.isEnabled(mAppContext) && verifySessionInfo(sessionInfo) && !promiseIconAddedForId(sessionInfo.getSessionId())) { // In case of unarchival, we do not want to add a workspace promise icon if one is @@ -255,8 +254,8 @@ public class InstallSessionHelper { && sessionInfo.getInstallReason() == PackageManager.INSTALL_REASON_USER && sessionInfo.getAppIcon() != null && !TextUtils.isEmpty(sessionInfo.getAppLabel()) - && !new ApplicationInfoWrapper(mAppContext, sessionInfo.getAppPackageName(), - getUserHandle(sessionInfo)).isInstalled(); + && !PackageManagerHelper.INSTANCE.get(mAppContext).isAppInstalled( + sessionInfo.getAppPackageName(), getUserHandle(sessionInfo)); } public InstallSessionTracker registerInstallTracker( diff --git a/src/com/android/launcher3/pm/InstallSessionTracker.java b/src/com/android/launcher3/pm/InstallSessionTracker.java index 8569c58cdd..1e00ca8b05 100644 --- a/src/com/android/launcher3/pm/InstallSessionTracker.java +++ b/src/com/android/launcher3/pm/InstallSessionTracker.java @@ -25,7 +25,6 @@ import android.content.pm.PackageInstaller; import android.content.pm.PackageInstaller.SessionInfo; import android.os.Build; import android.os.UserHandle; -import android.util.Log; import android.util.SparseArray; import androidx.annotation.NonNull; @@ -35,17 +34,13 @@ import androidx.annotation.WorkerThread; import com.android.launcher3.Flags; import com.android.launcher3.Utilities; import com.android.launcher3.util.PackageUserKey; -import com.android.launcher3.util.SafeCloseable; import java.lang.ref.WeakReference; import java.util.Objects; @SuppressWarnings("NewApi") @WorkerThread -public class InstallSessionTracker extends PackageInstaller.SessionCallback implements - SafeCloseable { - - public static final String TAG = "InstallSessionTracker"; +public class InstallSessionTracker extends PackageInstaller.SessionCallback { // Lazily initialized private SparseArray mActiveSessions = null; @@ -81,11 +76,6 @@ public class InstallSessionTracker extends PackageInstaller.SessionCallback impl } SessionInfo sessionInfo = pushSessionDisplayToLauncher(sessionId, helper, callback); if (sessionInfo != null) { - Log.d(TAG, "onCreated: Install session created for" - + " appPackageName=" + sessionInfo.getAppPackageName() - + ", sessionId=" + sessionInfo.getSessionId() - + ", appIcon=" + sessionInfo.getAppIcon() - + ", appLabel=" + sessionInfo.getAppLabel()); callback.onInstallSessionCreated(PackageInstallInfo.fromInstallingState(sessionInfo)); } @@ -113,10 +103,6 @@ public class InstallSessionTracker extends PackageInstaller.SessionCallback impl activeSessions.remove(sessionId); if (key != null && key.mPackageName != null) { - Log.d(TAG, "onFinished: active install session finished for" - + " appPackageName=" + key.mPackageName - + ", sessionId=" + sessionId - + ", success=" + success); String packageName = key.mPackageName; PackageInstallInfo info = PackageInstallInfo.fromState( success ? STATUS_INSTALLED : STATUS_FAILED, @@ -156,11 +142,6 @@ public class InstallSessionTracker extends PackageInstaller.SessionCallback impl } SessionInfo sessionInfo = pushSessionDisplayToLauncher(sessionId, helper, callback); if (sessionInfo != null) { - Log.d(TAG, "onBadgingChanged: badging info changed for" - + " appPackageName=" + sessionInfo.getAppPackageName() - + ", sessionId=" + sessionInfo.getSessionId() - + ", appIcon=" + sessionInfo.getAppIcon() - + ", appLabel=" + sessionInfo.getAppLabel()); helper.tryQueuePromiseAppIcon(sessionInfo); } } @@ -199,8 +180,7 @@ public class InstallSessionTracker extends PackageInstaller.SessionCallback impl } } - @Override - public void close() { + public void unregister() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { mInstaller.unregisterSessionCallback(this); } else { diff --git a/src/com/android/launcher3/pm/PinRequestHelper.java b/src/com/android/launcher3/pm/PinRequestHelper.java index 2ed6591a35..667136ae00 100644 --- a/src/com/android/launcher3/pm/PinRequestHelper.java +++ b/src/com/android/launcher3/pm/PinRequestHelper.java @@ -32,8 +32,7 @@ import android.os.SystemClock; import androidx.annotation.Nullable; import com.android.launcher3.LauncherAppState; -import com.android.launcher3.icons.CacheableShortcutCachingLogic; -import com.android.launcher3.icons.CacheableShortcutInfo; +import com.android.launcher3.icons.ShortcutCachingLogic; import com.android.launcher3.model.data.WorkspaceItemInfo; public class PinRequestHelper { @@ -78,10 +77,8 @@ public class PinRequestHelper { WorkspaceItemInfo info = new WorkspaceItemInfo(si, context); // Apply the unbadged icon synchronously using the caching logic directly and // fetch the actual icon asynchronously. - LauncherAppState app = LauncherAppState.getInstance(context); - info.bitmap = CacheableShortcutCachingLogic.INSTANCE.loadIcon( - context, app.getIconCache(), new CacheableShortcutInfo(si, context)); - app.getModel().updateAndBindWorkspaceItem(info, si); + info.bitmap = new ShortcutCachingLogic().loadIcon(context, si); + LauncherAppState.getInstance(context).getModel().updateAndBindWorkspaceItem(info, si); return info; } else { return null; diff --git a/src/com/android/launcher3/pm/ShortcutConfigActivityInfo.java b/src/com/android/launcher3/pm/ShortcutConfigActivityInfo.java index 409174e068..351ebce087 100644 --- a/src/com/android/launcher3/pm/ShortcutConfigActivityInfo.java +++ b/src/com/android/launcher3/pm/ShortcutConfigActivityInfo.java @@ -26,9 +26,9 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentSender; -import android.content.pm.ApplicationInfo; import android.content.pm.LauncherActivityInfo; import android.content.pm.LauncherApps; +import android.content.pm.PackageManager; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Process; @@ -40,10 +40,9 @@ import androidx.annotation.Nullable; import com.android.launcher3.LauncherSettings; import com.android.launcher3.R; -import com.android.launcher3.icons.cache.BaseIconCache; -import com.android.launcher3.icons.cache.CachedObject; +import com.android.launcher3.icons.ComponentWithLabelAndIcon; +import com.android.launcher3.icons.IconCache; import com.android.launcher3.model.data.WorkspaceItemInfo; -import com.android.launcher3.util.ApplicationInfoWrapper; import com.android.launcher3.util.PackageUserKey; import java.util.ArrayList; @@ -53,26 +52,16 @@ import java.util.List; /** * Wrapper class for representing a shortcut configure activity. */ -public abstract class ShortcutConfigActivityInfo implements CachedObject { +public abstract class ShortcutConfigActivityInfo implements ComponentWithLabelAndIcon { private static final String TAG = "SCActivityInfo"; private final ComponentName mCn; private final UserHandle mUser; - private final ApplicationInfoWrapper mInfoWrapper; - protected ShortcutConfigActivityInfo( - ComponentName cn, UserHandle user, ApplicationInfoWrapper infoWrapper) { + protected ShortcutConfigActivityInfo(ComponentName cn, UserHandle user) { mCn = cn; mUser = user; - mInfoWrapper = infoWrapper; - } - - protected ShortcutConfigActivityInfo( - ComponentName cn, UserHandle user, Context context) { - mCn = cn; - mUser = user; - mInfoWrapper = new ApplicationInfoWrapper(context, cn.getPackageName(), user); } @Override @@ -90,7 +79,7 @@ public abstract class ShortcutConfigActivityInfo implements CachedObject { } @Override - public abstract Drawable getFullResIcon(BaseIconCache cache); + public abstract Drawable getFullResIcon(IconCache cache); /** * Return a WorkspaceItemInfo, if it can be created directly on drop, without requiring any @@ -100,12 +89,6 @@ public abstract class ShortcutConfigActivityInfo implements CachedObject { return null; } - @Nullable - @Override - public ApplicationInfo getApplicationInfo() { - return mInfoWrapper.getInfo(); - } - public boolean startConfigActivity(Activity activity, int requestCode) { Intent intent = new Intent(Intent.ACTION_CREATE_SHORTCUT) .setComponent(getComponent()); @@ -124,7 +107,7 @@ public abstract class ShortcutConfigActivityInfo implements CachedObject { } /** - * Returns true if various properties ({@link #getLabel()}, + * Returns true if various properties ({@link #getLabel(PackageManager)}, * {@link #getFullResIcon}) can be safely persisted. */ public boolean isPersistable() { @@ -137,19 +120,18 @@ public abstract class ShortcutConfigActivityInfo implements CachedObject { private final LauncherActivityInfo mInfo; public ShortcutConfigActivityInfoVO(LauncherActivityInfo info) { - super(info.getComponentName(), info.getUser(), - new ApplicationInfoWrapper(info.getApplicationInfo())); + super(info.getComponentName(), info.getUser()); mInfo = info; } @Override - public CharSequence getLabel() { + public CharSequence getLabel(PackageManager pm) { return mInfo.getLabel(); } @Override - public Drawable getFullResIcon(BaseIconCache cache) { - return cache.getFullResIcon(mInfo.getActivityInfo()); + public Drawable getFullResIcon(IconCache cache) { + return cache.getFullResIcon(mInfo); } @Override diff --git a/src/com/android/launcher3/pm/UserCache.java b/src/com/android/launcher3/pm/UserCache.java index 98a3882132..fec30b1747 100644 --- a/src/com/android/launcher3/pm/UserCache.java +++ b/src/com/android/launcher3/pm/UserCache.java @@ -32,15 +32,11 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.annotation.WorkerThread; -import com.android.launcher3.dagger.ApplicationContext; -import com.android.launcher3.dagger.LauncherAppSingleton; -import com.android.launcher3.dagger.LauncherBaseAppComponent; import com.android.launcher3.icons.BitmapInfo; import com.android.launcher3.icons.UserBadgeDrawable; import com.android.launcher3.util.ApiWrapper; -import com.android.launcher3.util.DaggerSingletonObject; -import com.android.launcher3.util.DaggerSingletonTracker; import com.android.launcher3.util.FlagOp; +import com.android.launcher3.util.MainThreadInitializedObject; import com.android.launcher3.util.SafeCloseable; import com.android.launcher3.util.SimpleBroadcastReceiver; import com.android.launcher3.util.UserIconInfo; @@ -51,29 +47,29 @@ import java.util.List; import java.util.Map; import java.util.function.BiConsumer; -import javax.inject.Inject; - /** * Class which manages a local cache of user handles to avoid system rpc */ -@LauncherAppSingleton -public class UserCache { - - public static DaggerSingletonObject INSTANCE = - new DaggerSingletonObject<>(LauncherBaseAppComponent::getUserCache); +public class UserCache implements SafeCloseable { public static final String ACTION_PROFILE_ADDED = ATLEAST_U - ? Intent.ACTION_PROFILE_ADDED : Intent.ACTION_MANAGED_PROFILE_ADDED; + ? Intent.ACTION_PROFILE_ADDED + : Intent.ACTION_MANAGED_PROFILE_ADDED; public static final String ACTION_PROFILE_REMOVED = ATLEAST_U - ? Intent.ACTION_PROFILE_REMOVED : Intent.ACTION_MANAGED_PROFILE_REMOVED; + ? Intent.ACTION_PROFILE_REMOVED + : Intent.ACTION_MANAGED_PROFILE_REMOVED; public static final String ACTION_PROFILE_UNLOCKED = ATLEAST_U - ? Intent.ACTION_PROFILE_ACCESSIBLE : Intent.ACTION_MANAGED_PROFILE_UNLOCKED; + ? Intent.ACTION_PROFILE_ACCESSIBLE + : Intent.ACTION_MANAGED_PROFILE_UNLOCKED; public static final String ACTION_PROFILE_LOCKED = ATLEAST_U - ? Intent.ACTION_PROFILE_INACCESSIBLE : Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE; + ? Intent.ACTION_PROFILE_INACCESSIBLE + : Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE; public static final String ACTION_PROFILE_AVAILABLE = "android.intent.action.PROFILE_AVAILABLE"; - public static final String ACTION_PROFILE_UNAVAILABLE = - "android.intent.action.PROFILE_UNAVAILABLE"; + public static final String ACTION_PROFILE_UNAVAILABLE = "android.intent.action.PROFILE_UNAVAILABLE"; + + public static final MainThreadInitializedObject INSTANCE = new MainThreadInitializedObject<>( + UserCache::new); /** Returns an instance of UserCache bound to the context provided. */ public static UserCache getInstance(Context context) { @@ -81,8 +77,9 @@ public class UserCache { } private final List> mUserEventListeners = new ArrayList<>(); - private final SimpleBroadcastReceiver mUserChangeReceiver; - private final ApiWrapper mApiWrapper; + private final SimpleBroadcastReceiver mUserChangeReceiver = new SimpleBroadcastReceiver(this::onUsersChanged); + + private final Context mContext; @NonNull private Map mUserToSerialMap; @@ -90,23 +87,20 @@ public class UserCache { @NonNull private Map> mUserToPreInstallAppMap; - @Inject - public UserCache( - @ApplicationContext Context context, - DaggerSingletonTracker tracker, - ApiWrapper apiWrapper - ) { - mApiWrapper = apiWrapper; - mUserChangeReceiver = new SimpleBroadcastReceiver(context, - MODEL_EXECUTOR, this::onUsersChanged); + private UserCache(Context context) { + mContext = context; mUserToSerialMap = Collections.emptyMap(); MODEL_EXECUTOR.execute(this::initAsync); - tracker.addCloseable(() -> mUserChangeReceiver.unregisterReceiverSafely()); + } + + @Override + public void close() { + MODEL_EXECUTOR.execute(() -> mUserChangeReceiver.unregisterReceiverSafely(mContext)); } @WorkerThread private void initAsync() { - mUserChangeReceiver.register( + mUserChangeReceiver.register(mContext, Intent.ACTION_MANAGED_PROFILE_AVAILABLE, Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE, Intent.ACTION_MANAGED_PROFILE_REMOVED, @@ -132,7 +126,7 @@ public class UserCache { @WorkerThread private void updateCache() { - mUserToSerialMap = mApiWrapper.queryAllUsers(); + mUserToSerialMap = ApiWrapper.INSTANCE.get(mContext).queryAllUsers(); mUserToPreInstallAppMap = fetchPreInstallApps(); } @@ -142,7 +136,7 @@ public class UserCache { mUserToSerialMap.forEach((userHandle, userIconInfo) -> { // Fetch only for private profile, as other profiles have no usages yet. List preInstallApp = userIconInfo.isPrivate() - ? mApiWrapper.getPreInstalledSystemPackages(userHandle) + ? ApiWrapper.INSTANCE.get(mContext).getPreInstalledSystemPackages(userHandle) : new ArrayList<>(); userToPreInstallApp.put(userHandle, preInstallApp); }); @@ -191,11 +185,6 @@ public class UserCache { mUserToSerialMap.put(userHandle, info); } - @VisibleForTesting - public void putToPreInstallCache(UserHandle userHandle, List preInstalledApps) { - mUserToPreInstallAppMap.put(userHandle, preInstalledApps); - } - /** * @see UserManager#getUserProfiles() */ @@ -213,12 +202,13 @@ public class UserCache { } /** - * Get a non-themed {@link UserBadgeDrawable} based on the provided {@link UserHandle}. + * Get a non-themed {@link UserBadgeDrawable} based on the provided + * {@link UserHandle}. */ @Nullable public static UserBadgeDrawable getBadgeDrawable(Context context, UserHandle userHandle) { return (UserBadgeDrawable) BitmapInfo.LOW_RES_INFO.withFlags(UserCache.getInstance(context) - .getUserInfo(userHandle).applyBitmapInfoFlags(FlagOp.NO_OP)) - .getBadgeDrawable(context, false /* isThemed */, null); + .getUserInfo(userHandle).applyBitmapInfoFlags(FlagOp.NO_OP)) + .getBadgeDrawable(context, false /* isThemed */); } } diff --git a/src/com/android/launcher3/popup/ArrowPopup.java b/src/com/android/launcher3/popup/ArrowPopup.java index 7dbf952341..51caf99ffb 100644 --- a/src/com/android/launcher3/popup/ArrowPopup.java +++ b/src/com/android/launcher3/popup/ArrowPopup.java @@ -16,6 +16,10 @@ package com.android.launcher3.popup; +import static androidx.core.content.ContextCompat.getColorStateList; + +import static com.android.app.animation.Interpolators.ACCELERATED_EASE; +import static com.android.app.animation.Interpolators.DECELERATED_EASE; import static com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE; import static com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE; import static com.android.app.animation.Interpolators.LINEAR; @@ -47,6 +51,7 @@ import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.InsettableFrameLayout; import com.android.launcher3.R; import com.android.launcher3.Utilities; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.shortcuts.DeepShortcutView; import com.android.launcher3.util.RunnableList; @@ -58,7 +63,8 @@ import app.lawnchair.theme.color.tokens.ColorTokens; import app.lawnchair.theme.drawable.DrawableTokens; /** - * A container for shortcuts to deep links and notifications associated with an app. + * A container for shortcuts to deep links and notifications associated with an + * app. * * @param The activity on with the popup shows */ @@ -85,7 +91,7 @@ public abstract class ArrowPopup private static final int OPEN_CHILD_FADE_DURATION_U = 83; private static final int OPEN_OVERSHOOT_DURATION_U = 200; - private static final int CLOSE_DURATION_U = 233; + private static final int CLOSE_DURATION_U = 233; private static final int CLOSE_FADE_START_DELAY_U = 150; private static final int CLOSE_FADE_DURATION_U = 83; private static final int CLOSE_CHILD_FADE_START_DELAY_U = 150; @@ -121,12 +127,12 @@ public abstract class ArrowPopup private RunnableList mOnCloseCallbacks = new RunnableList(); - // The rect string of the view that the arrow is attached to, in screen reference frame. + // The rect string of the view that the arrow is attached to, in screen + // reference frame. protected int mArrowColor; protected final float mElevation; - // Tag for Views that have children that will need to be iterated to add styling. private final String mIterateChildrenTag; public final int[] mColors; @@ -144,7 +150,6 @@ public abstract class ArrowPopup // Initialize arrow view final Resources resources = getResources(); - mArrowColor = ColorTokens.PopupArrow.resolveColor(context); mChildContainerMargin = resources.getDimensionPixelSize(R.dimen.popup_margin); mArrowWidth = resources.getDimensionPixelSize(R.dimen.popup_arrow_width); mArrowHeight = resources.getDimensionPixelSize(R.dimen.popup_arrow_height); @@ -158,24 +163,23 @@ public abstract class ArrowPopup int smallerRadius = resources.getDimensionPixelSize(R.dimen.popup_smaller_radius); mRoundedTop = new GradientDrawable(); mRoundedTop.setColor(popupPrimaryColor); - mRoundedTop.setCornerRadii(new float[]{mOutlineRadius, mOutlineRadius, mOutlineRadius, - mOutlineRadius, smallerRadius, smallerRadius, smallerRadius, smallerRadius}); + mRoundedTop.setCornerRadii(new float[] { mOutlineRadius, mOutlineRadius, mOutlineRadius, + mOutlineRadius, smallerRadius, smallerRadius, smallerRadius, smallerRadius }); mRoundedBottom = new GradientDrawable(); mRoundedBottom.setColor(popupPrimaryColor); - mRoundedBottom.setCornerRadii(new float[]{smallerRadius, smallerRadius, smallerRadius, - smallerRadius, mOutlineRadius, mOutlineRadius, mOutlineRadius, mOutlineRadius}); + mRoundedBottom.setCornerRadii(new float[] { smallerRadius, smallerRadius, smallerRadius, + smallerRadius, mOutlineRadius, mOutlineRadius, mOutlineRadius, mOutlineRadius }); mIterateChildrenTag = getContext().getString(R.string.popup_container_iterate_children); - if (mActivityContext.canUseMultipleShadesForPopup()) { - mColors = new int[]{ - ColorTokens.PopupShadeFirst.resolveColor(context), - ColorTokens.PopupShadeSecond.resolveColor(context), - ColorTokens.PopupShadeThird.resolveColor(context) - }; + if (!FeatureFlags.showMaterialUPopup(getContext()) && mActivityContext.canUseMultipleShadesForPopup()) { + mColors = new int[] { + ColorTokens.PopupShadeFirst.resolveColor(context), + ColorTokens.PopupShadeSecond.resolveColor(context), + ColorTokens.PopupShadeThird.resolveColor(context) }; } else { - mColors = new int[]{ColorTokens.PopupArrow.resolveColor(context)}; + mColors = new int[]{ ColorTokens.PopupShadeFirst.resolveColor(context)}; } } @@ -222,7 +226,8 @@ public abstract class ArrowPopup } /** - * @param backgroundColor When Color.TRANSPARENT, we get color from {@link #mColors}. + * @param backgroundColor When Color.TRANSPARENT, we get color from + * {@link #mColors}. * Otherwise, we will use this color for all child views. */ protected void assignMarginsAndBackgrounds(ViewGroup viewGroup, int backgroundColor) { @@ -241,13 +246,14 @@ public abstract class ArrowPopup } } + int numVisibleChild = 0; int numVisibleShortcut = 0; View lastView = null; AnimatorSet colorAnimator = new AnimatorSet(); for (int i = 0; i < count; i++) { View view = viewGroup.getChildAt(i); if (view.getVisibility() == VISIBLE) { - if (lastView != null && (isShortcutContainer(lastView))) { + if (lastView != null) { MarginLayoutParams mlp = (MarginLayoutParams) lastView.getLayoutParams(); mlp.bottomMargin = mChildContainerMargin; } @@ -255,19 +261,31 @@ public abstract class ArrowPopup MarginLayoutParams mlp = (MarginLayoutParams) lastView.getLayoutParams(); mlp.bottomMargin = 0; - if (colors != null && isShortcutContainer(view)) { - setChildColor(view, colors[0], colorAnimator); - mArrowColor = colors[0]; + if (colors != null) { + if (!FeatureFlags.showMaterialUPopup(getContext())) { + backgroundColor = colors[numVisibleChild % colors.length]; + } + + if (FeatureFlags.showMaterialUPopup(getContext()) && isShortcutContainer(view)) { + setChildColor(view, colors[0], colorAnimator); + mArrowColor = colors[0]; + } + } + + // Arrow color matches the first child or the last child. + if (!FeatureFlags.showMaterialUPopup(getContext()) + && (mIsAboveIcon || (numVisibleChild == 0 && viewGroup == this))) { + mArrowColor = backgroundColor; } if (view instanceof ViewGroup && isShortcutContainer(view)) { assignMarginsAndBackgrounds((ViewGroup) view, backgroundColor); + numVisibleChild++; continue; } if (isShortcutOrWrapper(view)) { if (totalVisibleShortcuts == 1) { - // Lawnchair-TODO-High: view.setBackgroundResource is use instead view.setBackground(DrawableTokens.SingleItemPrimary.resolve(getContext())); } else if (totalVisibleShortcuts > 1) { if (numVisibleShortcut == 0) { @@ -282,6 +300,7 @@ public abstract class ArrowPopup } setChildColor(view, backgroundColor, colorAnimator); + numVisibleChild++; } } @@ -358,7 +377,8 @@ public abstract class ArrowPopup if (Gravity.isVertical(mGravity)) { // This is only true if there wasn't room for the container next to the icon, - // so we centered it instead. In that case we don't want to showDefaultOptions the arrow. + // so we centered it instead. In that case we don't want to showDefaultOptions + // the arrow. mArrow.setVisibility(INVISIBLE); } else { updateArrowColor(); @@ -394,9 +414,11 @@ public abstract class ArrowPopup protected abstract void getTargetObjectLocation(Rect outPos); /** - * Orients this container above or below the given icon, aligning with the left or right. + * Orients this container above or below the given icon, aligning with the left + * or right. * - * These are the preferred orientations, in order (RTL prefers right-aligned over left): + * These are the preferred orientations, in order (RTL prefers right-aligned + * over left): * - Above and left-aligned * - Above and right-aligned * - Below and left-aligned @@ -412,15 +434,20 @@ public abstract class ArrowPopup /** * @see #orientAboutObject() * - * @param allowAlignLeft Set to false if we already tried aligning left and didn't have room. - * @param allowAlignRight Set to false if we already tried aligning right and didn't have room. - * TODO: Can we test this with all permutations of widths/heights and icon locations + RTL? + * @param allowAlignLeft Set to false if we already tried aligning left and + * didn't have room. + * @param allowAlignRight Set to false if we already tried aligning right and + * didn't have room. + * TODO: Can we test this with all permutations of + * widths/heights and icon locations + RTL? */ private void orientAboutObject(boolean allowAlignLeft, boolean allowAlignRight) { measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); - int extraVerticalSpace = mArrowHeight + mArrowOffsetVertical + getExtraVerticalOffset(); - // The margins are added after we call this method, so we need to account for them here. + int extraVerticalSpace = mArrowHeight + mArrowOffsetVertical + + getResources().getDimensionPixelSize(R.dimen.popup_vertical_padding); + // The margins are added after we call this method, so we need to account for + // them here. int numVisibleChildren = 0; for (int i = getChildCount() - 1; i >= 0; --i) { if (getChildAt(i).getVisibility() == VISIBLE) { @@ -441,18 +468,20 @@ public abstract class ArrowPopup mIsLeftAligned = !mIsRtl ? allowAlignLeft : !allowAlignRight; int x = mIsLeftAligned ? leftAlignedX : rightAlignedX; - // Offset x so that the arrow and shortcut icons are center-aligned with the original icon. + // Offset x so that the arrow and shortcut icons are center-aligned with the + // original icon. int iconWidth = mTempRect.width(); int xOffset = iconWidth / 2 - mArrowOffsetHorizontal - mArrowWidth / 2; x += mIsLeftAligned ? xOffset : -xOffset; - // Check whether we can still align as we originally wanted, now that we've calculated x. + // Check whether we can still align as we originally wanted, now that we've + // calculated x. if (!allowAlignLeft && !allowAlignRight) { - // We've already tried both ways and couldn't make it fit. onLayout() will set the + // We've already tried both ways and couldn't make it fit. onLayout() will set + // the // gravity to CENTER_HORIZONTAL, but continue below to update y. } else { - boolean canBeLeftAligned = x + width + insets.left - < dragLayer.getWidth() - insets.right; + boolean canBeLeftAligned = x + width + insets.left < dragLayer.getWidth() - insets.right; boolean canBeRightAligned = x > insets.left; boolean alignmentStillValid = mIsLeftAligned && canBeLeftAligned || !mIsLeftAligned && canBeRightAligned; @@ -479,9 +508,11 @@ public abstract class ArrowPopup mGravity = 0; if ((insets.top + y + height) > (dragLayer.getBottom() - insets.bottom)) { - // The container is opening off the screen, so just center it in the drag layer instead. + // The container is opening off the screen, so just center it in the drag layer + // instead. mGravity = Gravity.CENTER_VERTICAL; - // Put the container next to the icon, preferring the right side in ltr (left in rtl). + // Put the container next to the icon, preferring the right side in ltr (left in + // rtl). int rightSide = leftAlignedX + iconWidth - insets.left; int leftSide = rightAlignedX - iconWidth - insets.left; if (!mIsRtl) { @@ -513,10 +544,8 @@ public abstract class ArrowPopup FrameLayout.LayoutParams arrowLp = (FrameLayout.LayoutParams) mArrow.getLayoutParams(); if (mIsAboveIcon) { arrowLp.gravity = lp.gravity = Gravity.BOTTOM; - lp.bottomMargin = - getPopupContainer().getHeight() - y - getMeasuredHeight() - insets.top; - arrowLp.bottomMargin = - lp.bottomMargin - arrowLp.height - mArrowOffsetVertical - insets.bottom; + lp.bottomMargin = getPopupContainer().getHeight() - y - getMeasuredHeight() - insets.top; + arrowLp.bottomMargin = lp.bottomMargin - arrowLp.height - mArrowOffsetVertical - insets.bottom; } else { arrowLp.gravity = lp.gravity = Gravity.TOP; lp.topMargin = y + insets.top; @@ -558,14 +587,23 @@ public abstract class ArrowPopup protected void animateOpen() { setVisibility(View.VISIBLE); - mOpenCloseAnimator = getOpenCloseAnimator( + mOpenCloseAnimator = FeatureFlags.showMaterialUPopup(getContext()) + ? getMaterialUOpenCloseAnimator( true, OPEN_DURATION_U, OPEN_FADE_START_DELAY_U, OPEN_FADE_DURATION_U, OPEN_CHILD_FADE_START_DELAY_U, OPEN_CHILD_FADE_DURATION_U, - EMPHASIZED_DECELERATE); + EMPHASIZED_DECELERATE) + : getOpenCloseAnimator( + true, + mOpenDuration, + mOpenFadeStartDelay, + mOpenFadeDuration, + mOpenChildFadeStartDelay, + mOpenChildFadeDuration, + DECELERATED_EASE); onCreateOpenAnimation(mOpenCloseAnimator); mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() { @@ -579,6 +617,62 @@ public abstract class ArrowPopup mOpenCloseAnimator.start(); } + public int getExtraVerticalOffset() { + return getResources().getDimensionPixelSize(R.dimen.popup_vertical_padding); + } + + /** + * Sets X and Y pivots for the view animation considering arrow position. + */ + protected void setPivotForOpenCloseAnimation() { + int arrowCenter = mArrowOffsetHorizontal + mArrowWidth / 2; + if (mIsArrowRotated) { + setPivotX(mIsLeftAligned ? 0f : getMeasuredWidth()); + setPivotY(arrowCenter); + } else { + setPivotX(mIsLeftAligned ? arrowCenter : getMeasuredWidth() - arrowCenter); + setPivotY(mIsAboveIcon ? getMeasuredHeight() : 0f); + } + } + + private AnimatorSet getOpenCloseAnimator(boolean isOpening, int totalDuration, + int fadeStartDelay, int fadeDuration, int childFadeStartDelay, + int childFadeDuration, Interpolator interpolator) { + final AnimatorSet animatorSet = new AnimatorSet(); + float[] alphaValues = isOpening ? new float[] { 0, 1 } : new float[] { 1, 0 }; + float[] scaleValues = isOpening ? new float[] { 0.5f, 1 } : new float[] { 1, 0.5f }; + + ValueAnimator fade = ValueAnimator.ofFloat(alphaValues); + fade.setStartDelay(fadeStartDelay); + fade.setDuration(fadeDuration); + fade.setInterpolator(LINEAR); + fade.addUpdateListener(anim -> { + float alpha = (float) anim.getAnimatedValue(); + mArrow.setAlpha(alpha); + setAlpha(alpha); + }); + animatorSet.play(fade); + + setPivotX(mIsLeftAligned ? 0 : getMeasuredWidth()); + setPivotY(mIsAboveIcon ? getMeasuredHeight() : 0); + Animator scale = ObjectAnimator.ofFloat(this, View.SCALE_Y, scaleValues); + scale.setDuration(totalDuration); + scale.setInterpolator(interpolator); + animatorSet.play(scale); + + if (shouldScaleArrow) { + Animator arrowScaleAnimator = ObjectAnimator.ofFloat(mArrow, View.SCALE_Y, + scaleValues); + arrowScaleAnimator.setDuration(totalDuration); + arrowScaleAnimator.setInterpolator(interpolator); + animatorSet.play(arrowScaleAnimator); + } + + fadeInChildViews(this, alphaValues, childFadeStartDelay, childFadeDuration, animatorSet); + + return animatorSet; + } + private void fadeInChildViews(ViewGroup group, float[] alphaValues, long startDelay, long duration, AnimatorSet out) { for (int i = group.getChildCount() - 1; i >= 0; --i) { @@ -611,14 +705,22 @@ public abstract class ArrowPopup } mIsOpen = false; - mOpenCloseAnimator = getOpenCloseAnimator( + mOpenCloseAnimator = FeatureFlags.showMaterialUPopup(getContext()) + ? getMaterialUOpenCloseAnimator( false, CLOSE_DURATION_U, CLOSE_FADE_START_DELAY_U, CLOSE_FADE_DURATION_U, CLOSE_CHILD_FADE_START_DELAY_U, CLOSE_CHILD_FADE_DURATION_U, - EMPHASIZED_ACCELERATE); + EMPHASIZED_ACCELERATE) + : getOpenCloseAnimator(false, + mCloseDuration, + mCloseFadeStartDelay, + mCloseFadeDuration, + mCloseChildFadeStartDelay, + mCloseChildFadeDuration, + ACCELERATED_EASE); onCreateCloseAnimation(mOpenCloseAnimator); mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() { @@ -635,14 +737,10 @@ public abstract class ArrowPopup mOpenCloseAnimator.start(); } - public int getExtraVerticalOffset() { - return getResources().getDimensionPixelSize(R.dimen.popup_vertical_padding); - } + protected AnimatorSet getMaterialUOpenCloseAnimator(boolean isOpening, int scaleDuration, + int fadeStartDelay, int fadeDuration, int childFadeStartDelay, int childFadeDuration, + Interpolator interpolator) { - /** - * Sets X and Y pivots for the view animation considering arrow position. - */ - protected void setPivotForOpenCloseAnimation() { int arrowCenter = mArrowOffsetHorizontal + mArrowWidth / 2; if (mIsArrowRotated) { setPivotX(mIsLeftAligned ? 0f : getMeasuredWidth()); @@ -651,17 +749,9 @@ public abstract class ArrowPopup setPivotX(mIsLeftAligned ? arrowCenter : getMeasuredWidth() - arrowCenter); setPivotY(mIsAboveIcon ? getMeasuredHeight() : 0f); } - } - - protected AnimatorSet getOpenCloseAnimator(boolean isOpening, int scaleDuration, - int fadeStartDelay, int fadeDuration, int childFadeStartDelay, int childFadeDuration, - Interpolator interpolator) { - - setPivotForOpenCloseAnimation(); - - float[] alphaValues = isOpening ? new float[] {0, 1} : new float[] {1, 0}; - float[] scaleValues = isOpening ? new float[] {0.5f, 1.02f} : new float[] {1f, 0.5f}; + float[] alphaValues = isOpening ? new float[] { 0, 1 } : new float[] { 1, 0 }; + float[] scaleValues = isOpening ? new float[] { 0.5f, 1.02f } : new float[] { 1f, 0.5f }; Animator alpha = getAnimatorOfFloat(this, View.ALPHA, fadeDuration, fadeStartDelay, LINEAR, alphaValues); Animator arrowAlpha = getAnimatorOfFloat(mArrow, View.ALPHA, fadeDuration, fadeStartDelay, @@ -673,7 +763,7 @@ public abstract class ArrowPopup final AnimatorSet animatorSet = new AnimatorSet(); if (isOpening) { - float[] scaleValuesOvershoot = new float[] {1.02f, 1f}; + float[] scaleValuesOvershoot = new float[] { 1.02f, 1f }; PathInterpolator overshootInterpolator = new PathInterpolator(0.3f, 0, 0.33f, 1f); Animator overshootY = getAnimatorOfFloat(this, View.SCALE_Y, OPEN_OVERSHOOT_DURATION_U, scaleDuration, overshootInterpolator, @@ -692,7 +782,7 @@ public abstract class ArrowPopup } private Animator getAnimatorOfFloat(View view, Property property, - int duration, int startDelay, Interpolator interpolator, float... values) { + int duration, int startDelay, Interpolator interpolator, float... values) { Animator animator = ObjectAnimator.ofFloat(view, property, values); animator.setDuration(duration); animator.setInterpolator(interpolator); @@ -701,14 +791,18 @@ public abstract class ArrowPopup } /** - * Called when creating the open transition allowing subclass can add additional animations. + * Called when creating the open transition allowing subclass can add additional + * animations. */ - protected void onCreateOpenAnimation(AnimatorSet anim) { } + protected void onCreateOpenAnimation(AnimatorSet anim) { + } /** - * Called when creating the close transition allowing subclass can add additional animations. + * Called when creating the close transition allowing subclass can add + * additional animations. */ - protected void onCreateCloseAnimation(AnimatorSet anim) { } + protected void onCreateCloseAnimation(AnimatorSet anim) { + } /** * Closes the popup without animation. diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java index 429559fe6f..be56626274 100644 --- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java +++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java @@ -16,18 +16,12 @@ package com.android.launcher3.popup; -import static android.multiuser.Flags.enableMovingContentIntoPrivateSpace; - import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_SHORTCUTS; -import static com.android.launcher3.Utilities.ATLEAST_BAKLAVA; import static com.android.launcher3.Utilities.squaredHypot; import static com.android.launcher3.Utilities.squaredTouchSlop; -import static com.android.launcher3.allapps.AlphabeticalAppsList.PRIVATE_SPACE_PACKAGE; import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_NOT_PINNABLE; import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS; -import static com.android.launcher3.shortcuts.DeepShortcutTextView.GOOGLE_SANS_FLEX_LABEL_LARGE; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; -import static com.android.wm.shell.Flags.enableGsf; import android.animation.AnimatorSet; import android.animation.LayoutTransition; @@ -35,7 +29,6 @@ import android.content.Context; import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; -import android.graphics.Typeface; import android.os.Handler; import android.os.Looper; import android.util.AttributeSet; @@ -47,6 +40,7 @@ import android.widget.ImageView; import androidx.annotation.LayoutRes; import com.android.launcher3.AbstractFloatingView; +import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.BubbleTextView; import com.android.launcher3.DragSource; import com.android.launcher3.DropTarget; @@ -73,7 +67,6 @@ import com.android.launcher3.views.ActivityContext; import com.android.launcher3.views.BaseDragLayer; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -216,29 +209,7 @@ public class PopupContainerWithArrow container = (PopupContainerWithArrow) launcher.getLayoutInflater().inflate( R.layout.popup_container, launcher.getDragLayer(), false); container.configureForLauncher(launcher, item); - - /* LC-Note: Fix for missing flags and account for NCDFE */ - boolean shouldHideSystemShortcuts; - if (ATLEAST_BAKLAVA) { - boolean enableMovingContentIntoPrivateSpace = false; - try { - /* LC-Note: Some devices (Android 16 QPR) doesn't have or expose this flag to user. - * Let's assume no, because (the flags) enableMovingContentIntoPrivateSpace seems - * to be False for R8 by default. - * */ - enableMovingContentIntoPrivateSpace = enableMovingContentIntoPrivateSpace(); - } catch (NoClassDefFoundError e) { - /* LC-Ignored: we already set it false by default. */ - } - - shouldHideSystemShortcuts = enableMovingContentIntoPrivateSpace - && Objects.equals(item.getTargetPackage(), PRIVATE_SPACE_PACKAGE); - } else { - shouldHideSystemShortcuts = false; - } - - container.populateAndShowRows(icon, deepShortcutCount, - shouldHideSystemShortcuts ? Collections.emptyList() : systemShortcuts); + container.populateAndShowRows(icon, deepShortcutCount, systemShortcuts); launcher.refreshAndBindWidgetsForPackageUser(PackageUserKey.fromItemInfo(item)); container.requestFocus(); return container; @@ -264,21 +235,7 @@ public class PopupContainerWithArrow * @param systemShortcuts List of SystemShortcuts to add to container */ public void populateAndShowRows(final BubbleTextView originalIcon, - int deepShortcutCount, List systemShortcuts) { - populateAndShowRows(originalIcon, (ItemInfo) originalIcon.getTag(), deepShortcutCount, - systemShortcuts); - } - - /** - * Populate and show shortcuts for the Launcher U app shortcut design. - * Will inflate the container and shortcut View instances for the popup container. - * @param originalIcon App icon that the popup is shown for - * @param itemInfo The info that is used to load app shortcuts - * @param deepShortcutCount Number of DeepShortcutView instances to add to container - * @param systemShortcuts List of SystemShortcuts to add to container - */ - public void populateAndShowRows(final BubbleTextView originalIcon, ItemInfo itemInfo, - int deepShortcutCount, List systemShortcuts) { + int deepShortcutCount, List systemShortcuts) { mOriginalIcon = originalIcon; mContainerWidth = getResources().getDimensionPixelSize(R.dimen.bg_popup_item_width); @@ -291,7 +248,7 @@ public class PopupContainerWithArrow R.layout.system_shortcut); } show(); - loadAppShortcuts(itemInfo); + loadAppShortcuts((ItemInfo) originalIcon.getTag()); } /** @@ -317,7 +274,7 @@ public class PopupContainerWithArrow * @param systemShortcuts List of SystemShortcuts */ private void addAllShortcuts(int deepShortcutCount, - List systemShortcuts) { + List systemShortcuts) { if (deepShortcutCount + systemShortcuts.size() <= SHORTCUT_COLLAPSE_THRESHOLD) { // add all system shortcuts including widgets shortcut to same container addSystemShortcuts(systemShortcuts, @@ -385,7 +342,7 @@ public class PopupContainerWithArrow * @param systemShortcutLayout Layout Resource for the individual shortcut Views */ private void addSystemShortcuts(List systemShortcuts, - @LayoutRes int systemShortcutContainerLayout, @LayoutRes int systemShortcutLayout) { + @LayoutRes int systemShortcutContainerLayout, @LayoutRes int systemShortcutLayout) { if (systemShortcuts.size() == 0) { return; @@ -500,15 +457,11 @@ public class PopupContainerWithArrow * @return The view inflated for the SystemShortcut */ protected View initializeSystemShortcut(int resId, ViewGroup container, SystemShortcut info, - boolean shouldAppendSpacer) { + boolean shouldAppendSpacer) { View view = inflateAndAdd(resId, container); if (view instanceof DeepShortcutView) { // System shortcut takes entire row with icon and text final DeepShortcutView shortcutView = (DeepShortcutView) view; - if (enableGsf()) { - shortcutView.getBubbleText().setTypeface( - Typeface.create(GOOGLE_SANS_FLEX_LABEL_LARGE, Typeface.NORMAL)); - } info.setIconAndLabelFor(shortcutView.getIconView(), shortcutView.getBubbleText()); } else if (view instanceof ImageView) { // System shortcut is just an icon @@ -629,7 +582,7 @@ public class PopupContainerWithArrow /** * Dismisses the popup if it is no longer valid */ - public static void dismissInvalidPopup(T activity) { + public static void dismissInvalidPopup(BaseDraggingActivity activity) { PopupContainerWithArrow popup = getOpen(activity); if (popup != null && (!popup.mOriginalIcon.isAttachedToWindow() || !ShortcutUtil.supportsShortcuts((ItemInfo) popup.mOriginalIcon.getTag()))) { diff --git a/src/com/android/launcher3/popup/PopupDataProvider.java b/src/com/android/launcher3/popup/PopupDataProvider.java index 95110329d3..fb463f7d24 100644 --- a/src/com/android/launcher3/popup/PopupDataProvider.java +++ b/src/com/android/launcher3/popup/PopupDataProvider.java @@ -23,27 +23,29 @@ import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.android.launcher3.BubbleTextView; -import com.android.launcher3.allapps.ActivityAllAppsContainerView; import com.android.launcher3.dot.DotInfo; -import com.android.launcher3.folder.Folder; -import com.android.launcher3.folder.FolderIcon; -import com.android.launcher3.model.data.FolderInfo; +import com.android.launcher3.model.WidgetItem; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.notification.NotificationKeyData; import com.android.launcher3.notification.NotificationListener; import com.android.launcher3.util.ComponentKey; -import com.android.launcher3.util.LauncherBindableItemsContainer.ItemOperator; import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.util.ShortcutUtil; -import com.android.launcher3.views.ActivityContext; +import com.android.launcher3.widget.PendingAddWidgetInfo; +import com.android.launcher3.widget.model.WidgetsListBaseEntry; +import com.android.launcher3.widget.model.WidgetsListContentEntry; +import com.android.launcher3.widget.picker.WidgetRecommendationCategory; import java.io.PrintWriter; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Function; import java.util.function.Predicate; +import java.util.stream.Collectors; /** * Provides data for the popup menu that appears after long-clicking on apps. @@ -53,45 +55,26 @@ public class PopupDataProvider implements NotificationListener.NotificationsChan private static final boolean LOGD = false; private static final String TAG = "PopupDataProvider"; - private final ActivityContext mContext; - - /** Maps packages to their DotInfo's . */ - private final Map mPackageUserToDotInfos = new HashMap<>(); + private final Consumer> mNotificationDotsChangeListener; /** Maps launcher activity components to a count of how many shortcuts they have. */ private HashMap mDeepShortcutMap = new HashMap<>(); + /** Maps packages to their DotInfo's . */ + private Map mPackageUserToDotInfos = new HashMap<>(); - public PopupDataProvider(ActivityContext context) { - mContext = context; + /** All installed widgets. */ + private List mAllWidgets = List.of(); + /** Widgets that can be recommended to the users. */ + private List mRecommendedWidgets = List.of(); + + private PopupDataChangeListener mChangeListener = PopupDataChangeListener.INSTANCE; + + public PopupDataProvider(Consumer> notificationDotsChangeListener) { + mNotificationDotsChangeListener = notificationDotsChangeListener; } private void updateNotificationDots(Predicate updatedDots) { - final PackageUserKey packageUserKey = new PackageUserKey(null, null); - Predicate matcher = info -> !packageUserKey.updateFromItemInfo(info) - || updatedDots.test(packageUserKey); - - ItemOperator op = (info, v) -> { - if (v instanceof BubbleTextView && info != null && matcher.test(info)) { - ((BubbleTextView) v).applyDotState(info, true /* animate */); - } else if (v instanceof FolderIcon icon - && info instanceof FolderInfo fi && fi.anyMatch(matcher)) { - icon.updateDotInfo(); - } - - // process all the shortcuts - return false; - }; - - mContext.getContent().mapOverItems(op); - Folder folder = Folder.getOpen(mContext); - if (folder != null) { - folder.mapOverItems(op); - } - - ActivityAllAppsContainerView appsView = mContext.getAppsView(); - if (appsView != null) { - appsView.getAppsStore().updateNotificationDots(updatedDots); - } + mNotificationDotsChangeListener.accept(updatedDots); } @Override @@ -200,8 +183,102 @@ public class PopupDataProvider implements NotificationListener.NotificationsChan })) ? dotInfo : null; } + /** + * Sets a list of recommended widgets ordered by their order of appearance in the widgets + * recommendation UI. + */ + public void setRecommendedWidgets(List recommendedWidgets) { + mRecommendedWidgets = recommendedWidgets; + mChangeListener.onRecommendedWidgetsBound(); + } + + public void setAllWidgets(List allWidgets) { + mAllWidgets = allWidgets; + mChangeListener.onWidgetsBound(); + } + + public void setChangeListener(PopupDataChangeListener listener) { + mChangeListener = listener == null ? PopupDataChangeListener.INSTANCE : listener; + } + + public List getAllWidgets() { + return mAllWidgets; + } + + /** Returns a list of recommended widgets. */ + public List getRecommendedWidgets() { + HashMap allWidgetItems = new HashMap<>(); + mAllWidgets.stream() + .filter(entry -> entry instanceof WidgetsListContentEntry) + .forEach(entry -> ((WidgetsListContentEntry) entry).mWidgets + .forEach(widget -> allWidgetItems.put( + new ComponentKey(widget.componentName, widget.user), widget))); + return mRecommendedWidgets.stream() + .map(recommendedWidget -> allWidgetItems.get( + new ComponentKey(recommendedWidget.getTargetComponent(), + recommendedWidget.user))) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + /** Returns the recommended widgets mapped by their category. */ + @NonNull + public Map> getCategorizedRecommendedWidgets() { + Map allWidgetItems = mAllWidgets.stream() + .filter(entry -> entry instanceof WidgetsListContentEntry) + .flatMap(entry -> entry.mWidgets.stream()) + .distinct() + .collect(Collectors.toMap( + widget -> new ComponentKey(widget.componentName, widget.user), + Function.identity() + )); + return mRecommendedWidgets.stream() + .filter(itemInfo -> itemInfo instanceof PendingAddWidgetInfo + && ((PendingAddWidgetInfo) itemInfo).recommendationCategory != null) + .collect(Collectors.groupingBy( + it -> ((PendingAddWidgetInfo) it).recommendationCategory, + Collectors.collectingAndThen( + Collectors.toList(), + list -> list.stream() + .map(it -> allWidgetItems.get( + new ComponentKey(it.getTargetComponent(), + it.user))) + .filter(Objects::nonNull) + .collect(Collectors.toList()) + ) + )); + } + + public List getWidgetsForPackageUser(PackageUserKey packageUserKey) { + return mAllWidgets.stream() + .filter(row -> row instanceof WidgetsListContentEntry + && row.mPkgItem.packageName.equals(packageUserKey.mPackageName)) + .flatMap(row -> ((WidgetsListContentEntry) row).mWidgets.stream()) + .filter(widget -> packageUserKey.mUser.equals(widget.user)) + .collect(Collectors.toList()); + } + + /** Gets the WidgetsListContentEntry for the currently selected header. */ + public WidgetsListContentEntry getSelectedAppWidgets(PackageUserKey packageUserKey) { + return (WidgetsListContentEntry) mAllWidgets.stream() + .filter(row -> row instanceof WidgetsListContentEntry + && PackageUserKey.fromPackageItemInfo(row.mPkgItem).equals(packageUserKey)) + .findAny() + .orElse(null); + } + public void dump(String prefix, PrintWriter writer) { writer.println(prefix + "PopupDataProvider:"); writer.println(prefix + "\tmPackageUserToDotInfos:" + mPackageUserToDotInfos); } + + public interface PopupDataChangeListener { + + PopupDataChangeListener INSTANCE = new PopupDataChangeListener() { }; + + default void onWidgetsBound() { } + + /** A callback to get notified when recommended widgets are bound. */ + default void onRecommendedWidgetsBound() { } + } } diff --git a/src/com/android/launcher3/popup/PopupLiveUpdateHandler.java b/src/com/android/launcher3/popup/PopupLiveUpdateHandler.java index 1fd355772b..4c94f9401f 100644 --- a/src/com/android/launcher3/popup/PopupLiveUpdateHandler.java +++ b/src/com/android/launcher3/popup/PopupLiveUpdateHandler.java @@ -19,8 +19,6 @@ import android.content.Context; import android.view.View; import com.android.launcher3.views.ActivityContext; -import com.android.launcher3.widget.picker.model.WidgetPickerDataProvider; -import com.android.launcher3.widget.picker.model.WidgetPickerDataProvider.WidgetPickerDataChangeListener; /** * Utility class to handle updates while the popup is visible (like widgets and @@ -29,7 +27,7 @@ import com.android.launcher3.widget.picker.model.WidgetPickerDataProvider.Widget * @param The activity on which the popup shows */ public abstract class PopupLiveUpdateHandler implements - WidgetPickerDataChangeListener, View.OnAttachStateChangeListener { + PopupDataProvider.PopupDataChangeListener, View.OnAttachStateChangeListener { protected final T mContext; protected final PopupContainerWithArrow mPopupContainerWithArrow; @@ -42,25 +40,19 @@ public abstract class PopupLiveUpdateHandler Runnable createUpdateRunnable( final T context, final ItemInfo originalInfo, - final Handler uiHandler, - final PopupContainerWithArrow container, - final List shortcutViews - ) { + final Handler uiHandler, final PopupContainerWithArrow container, + final List shortcutViews) { final ComponentName activity = originalInfo.getTargetComponent(); final UserHandle user = originalInfo.user; - final String targetPackage = originalInfo.getTargetPackage(); return () -> { - ApplicationInfoWrapper infoWrapper = - new ApplicationInfoWrapper(context, targetPackage, user); List shortcuts = new ShortcutRequest(context, user) .withContainer(activity) .query(ShortcutRequest.PUBLISHED); @@ -128,7 +121,7 @@ public class PopupPopulator { for (int i = 0; i < shortcuts.size() && i < shortcutViews.size(); i++) { final ShortcutInfo shortcut = shortcuts.get(i); final WorkspaceItemInfo si = new WorkspaceItemInfo(shortcut, context); - cache.getShortcutIcon(si, new CacheableShortcutInfo(shortcut, infoWrapper)); + cache.getShortcutIcon(si, shortcut); si.rank = i; si.container = CONTAINER_SHORTCUTS; diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java index 32f3c43687..9d49ca4aa6 100644 --- a/src/com/android/launcher3/popup/SystemShortcut.java +++ b/src/com/android/launcher3/popup/SystemShortcut.java @@ -1,22 +1,19 @@ package com.android.launcher3.popup; -import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_DISMISS_PREDICTION_UNDO; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_PRIVATE_SPACE_INSTALL_SYSTEM_SHORTCUT_TAP; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_PRIVATE_SPACE_UNINSTALL_SYSTEM_SHORTCUT_TAP; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_APP_INFO_TAP; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_DONT_SUGGEST_APP_TAP; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_WIDGETS_TAP; -import static com.android.launcher3.widget.picker.model.data.WidgetPickerDataUtils.findAllWidgetsForPackageUser; +import android.app.ActivityOptions; import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.pm.ShortcutInfo; import android.graphics.Rect; import android.net.Uri; import android.os.Process; import android.os.UserHandle; -import android.util.Log; import android.view.View; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.ImageView; @@ -28,42 +25,41 @@ import androidx.annotation.Nullable; import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.AbstractFloatingViewHelper; import com.android.launcher3.Flags; -import com.android.launcher3.LauncherSettings; import com.android.launcher3.R; import com.android.launcher3.SecondaryDropTarget; import com.android.launcher3.Utilities; import com.android.launcher3.allapps.PrivateProfileManager; +import com.android.launcher3.model.WidgetItem; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.pm.UserCache; -import com.android.launcher3.util.ActivityOptionsWrapper; import com.android.launcher3.util.ApiWrapper; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.InstantAppResolver; import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.views.ActivityContext; -import com.android.launcher3.views.Snackbar; import com.android.launcher3.widget.WidgetsBottomSheet; -import com.android.launcher3.widget.picker.model.data.WidgetPickerData; +import com.patrykmichalik.opto.core.PreferenceExtensionsKt; import java.net.URISyntaxException; import java.util.Arrays; +import java.util.List; -import com.patrykmichalik.opto.core.PreferenceExtensionsKt; import app.lawnchair.preferences2.PreferenceManager2; /** - * Represents a system shortcut for a given app. The shortcut should have a label and icon, and an + * Represents a system shortcut for a given app. The shortcut should have a + * label and icon, and an * onClickListener that depends on the item that the shortcut services. * - * Example system shortcuts, defined as inner classes, include Widgets and AppInfo. + * Example system shortcuts, defined as inner classes, include Widgets and + * AppInfo. * * @param extends {@link ActivityContext} */ public abstract class SystemShortcut extends ItemInfo implements View.OnClickListener { - private static final String TAG = "SystemShortcut"; private final int mIconResId; protected final int mLabelResId; @@ -118,12 +114,11 @@ public abstract class SystemShortcut extends ItemInfo } public static final Factory WIDGETS = (context, itemInfo, originalView) -> { - final PackageUserKey packageUserKey = PackageUserKey.fromItemInfo(itemInfo); - if (packageUserKey == null) return null; - - final WidgetPickerData data = context.getWidgetPickerDataProvider().get(); - if (findAllWidgetsForPackageUser(data, packageUserKey).isEmpty()) { - // hides widget picker shortcut if there are no widgets for the package. + if (itemInfo.getTargetComponent() == null) + return null; + final List widgets = context.getPopupDataProvider().getWidgetsForPackageUser(new PackageUserKey( + itemInfo.getTargetComponent().getPackageName(), itemInfo.user)); + if (widgets.isEmpty()) { return null; } return new Widgets(context, itemInfo, originalView); @@ -138,9 +133,8 @@ public abstract class SystemShortcut extends ItemInfo @Override public void onClick(View view) { AbstractFloatingView.closeAllOpenViews(mTarget); - WidgetsBottomSheet widgetsBottomSheet = - (WidgetsBottomSheet) mTarget.getLayoutInflater().inflate( - R.layout.widgets_bottom_sheet, mTarget.getDragLayer(), false); + WidgetsBottomSheet widgetsBottomSheet = (WidgetsBottomSheet) mTarget.getLayoutInflater().inflate( + R.layout.widgets_bottom_sheet, mTarget.getDragLayer(), false); widgetsBottomSheet.populateAndShow(mItemInfo); mTarget.getStatsLogManager().logger().withItemInfo(mItemInfo) .log(LAUNCHER_SYSTEM_SHORTCUT_WIDGETS_TAP); @@ -160,13 +154,16 @@ public abstract class SystemShortcut extends ItemInfo } /** - * Constructor used by overview for staged split to provide custom A11y information. + * Constructor used by overview for staged split to provide custom A11y + * information. * * Future improvements considerations: - * Have the logic in {@link #createAccessibilityAction(Context)} be moved to super + * Have the logic in {@link #createAccessibilityAction(Context)} be moved to + * super * call in {@link SystemShortcut#createAccessibilityAction(Context)} by having * SystemShortcut be aware of TaskContainers and staged split. - * That way it could directly create the correct node info for any shortcut that supports + * That way it could directly create the correct node info for any shortcut that + * supports * split, but then we'll need custom resIDs for each pair of shortcuts. */ public AppInfo(T target, ItemInfo itemInfo, View originalView, @@ -191,12 +188,10 @@ public abstract class SystemShortcut extends ItemInfo @Override public void onClick(View view) { + dismissTaskMenuView(); Rect sourceBounds = Utilities.getViewBounds(view); - ActivityOptionsWrapper options = mTarget.getActivityLaunchOptions(view, mItemInfo); - // Dismiss the taskMenu when the app launch animation is complete - options.onEndCallback.add(this::dismissTaskMenuView); PackageManagerHelper.startDetailsActivityForInfo(view.getContext(), mItemInfo, - sourceBounds, options.toBundle()); + sourceBounds, ActivityOptions.makeBasic().toBundle()); mTarget.getStatsLogManager().logger().withItemInfo(mItemInfo) .log(LAUNCHER_SYSTEM_SHORTCUT_APP_INFO_TAP); } @@ -215,46 +210,43 @@ public abstract class SystemShortcut extends ItemInfo } } - public static final Factory PRIVATE_PROFILE_INSTALL = - (context, itemInfo, originalView) -> { - if (originalView == null) { - return null; - } - if (itemInfo.getTargetComponent() == null - || !(itemInfo instanceof com.android.launcher3.model.data.AppInfo) - || !itemInfo.getContainerInfo().hasAllAppsContainer() - || !Process.myUserHandle().equals(itemInfo.user)) { - return null; - } + public static final Factory PRIVATE_PROFILE_INSTALL = (context, itemInfo, originalView) -> { + if (originalView == null) { + return null; + } + if (itemInfo.getTargetComponent() == null + || !(itemInfo instanceof com.android.launcher3.model.data.AppInfo) + || !itemInfo.getContainerInfo().hasAllAppsContainer() + || !Process.myUserHandle().equals(itemInfo.user)) { + return null; + } - PrivateProfileManager privateProfileManager = - context.getAppsView().getPrivateProfileManager(); - if (privateProfileManager == null || !privateProfileManager.isEnabled()) { - return null; - } + PrivateProfileManager privateProfileManager = context.getAppsView().getPrivateProfileManager(); + if (privateProfileManager == null || !privateProfileManager.isEnabled()) { + return null; + } - UserHandle privateProfileUser = privateProfileManager.getProfileUser(); - if (privateProfileUser == null) { - return null; - } - // Do not show shortcut if an app is already installed to the space - ComponentName targetComponent = itemInfo.getTargetComponent(); - if (context.getAppsView().getAppsStore().getApp( - new ComponentKey(targetComponent, privateProfileUser)) != null) { - return null; - } + UserHandle privateProfileUser = privateProfileManager.getProfileUser(); + if (privateProfileUser == null) { + return null; + } + // Do not show shortcut if an app is already installed to the space + ComponentName targetComponent = itemInfo.getTargetComponent(); + if (context.getAppsView().getAppsStore().getApp( + new ComponentKey(targetComponent, privateProfileUser)) != null) { + return null; + } - // Do not show shortcut for settings - String[] packagesToSkip = - originalView.getContext().getResources() - .getStringArray(R.array.skip_private_profile_shortcut_packages); - if (Arrays.asList(packagesToSkip).contains(targetComponent.getPackageName())) { - return null; - } + // Do not show shortcut for settings + String[] packagesToSkip = originalView.getContext().getResources() + .getStringArray(R.array.skip_private_profile_shortcut_packages); + if (Arrays.asList(packagesToSkip).contains(targetComponent.getPackageName())) { + return null; + } - return new InstallToPrivateProfile<>( - context, itemInfo, originalView, privateProfileUser); - }; + return new InstallToPrivateProfile<>( + context, itemInfo, originalView, privateProfileUser); + }; static class InstallToPrivateProfile extends SystemShortcut { UserHandle mSpaceUser; @@ -273,9 +265,8 @@ public abstract class SystemShortcut extends ItemInfo @Override public void onClick(View view) { - Intent intent = - ApiWrapper.INSTANCE.get(view.getContext()).getAppMarketActivityIntent( - mItemInfo.getTargetComponent().getPackageName(), mSpaceUser); + Intent intent = ApiWrapper.INSTANCE.get(view.getContext()).getAppMarketActivityIntent( + mItemInfo.getTargetComponent().getPackageName(), mSpaceUser); mTarget.startActivitySafely(view, intent, mItemInfo); AbstractFloatingView.closeAllOpenViews(mTarget); mTarget.getStatsLogManager() @@ -285,27 +276,25 @@ public abstract class SystemShortcut extends ItemInfo } } - public static final Factory INSTALL = - (activity, itemInfo, originalView) -> { - if (originalView == null) { - return null; - } - boolean supportsWebUI = (itemInfo instanceof WorkspaceItemInfo) - && ((WorkspaceItemInfo) itemInfo).hasStatusFlag( + public static final Factory INSTALL = (activity, itemInfo, originalView) -> { + if (originalView == null) { + return null; + } + boolean supportsWebUI = (itemInfo instanceof WorkspaceItemInfo) + && ((WorkspaceItemInfo) itemInfo).hasStatusFlag( WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI); - boolean isInstantApp = false; - if (itemInfo instanceof com.android.launcher3.model.data.AppInfo) { - com.android.launcher3.model.data.AppInfo - appInfo = (com.android.launcher3.model.data.AppInfo) itemInfo; - isInstantApp = InstantAppResolver.newInstance( - originalView.getContext()).isInstantApp(appInfo); - } - boolean enabled = supportsWebUI || isInstantApp; - if (!enabled) { - return null; - } - return new Install(activity, itemInfo, originalView); - }; + boolean isInstantApp = false; + if (itemInfo instanceof com.android.launcher3.model.data.AppInfo) { + com.android.launcher3.model.data.AppInfo appInfo = (com.android.launcher3.model.data.AppInfo) itemInfo; + isInstantApp = InstantAppResolver.newInstance( + originalView.getContext()).isInstantApp(appInfo); + } + boolean enabled = supportsWebUI || isInstantApp; + if (!enabled) { + return null; + } + return new Install(activity, itemInfo, originalView); + }; public static class Install extends SystemShortcut { @@ -323,13 +312,12 @@ public abstract class SystemShortcut extends ItemInfo } } - public static final Factory DONT_SUGGEST_APP = - (activity, itemInfo, originalView) -> { - if (!itemInfo.isPredictedItem()) { - return null; - } - return new DontSuggestApp<>(activity, itemInfo, originalView); - }; + public static final Factory DONT_SUGGEST_APP = (activity, itemInfo, originalView) -> { + if (!itemInfo.isPredictedItem()) { + return null; + } + return new DontSuggestApp<>(activity, itemInfo, originalView); + }; private static class DontSuggestApp extends SystemShortcut { DontSuggestApp(T target, ItemInfo itemInfo, View originalView) { @@ -343,42 +331,34 @@ public abstract class SystemShortcut extends ItemInfo mTarget.getStatsLogManager().logger() .withItemInfo(mItemInfo) .log(LAUNCHER_SYSTEM_SHORTCUT_DONT_SUGGEST_APP_TAP); - if (Flags.enableDismissPredictionUndo()) { - Snackbar.show(mTarget, - view.getContext().getString(R.string.item_removed), R.string.undo, - () -> { }, () -> - mTarget.getStatsLogManager().logger() - .withItemInfo(mItemInfo) - .log(LAUNCHER_DISMISS_PREDICTION_UNDO)); - } } } - public static final Factory UNINSTALL_APP = - (activityContext, itemInfo, originalView) -> { - if (originalView == null) { - return null; - } - if (!Flags.enablePrivateSpace()) { - return null; - } - if (!UserCache.INSTANCE.get(originalView.getContext()).getUserInfo( - itemInfo.user).isPrivate()) { - // If app is not Private Space app. - return null; - } - ComponentName cn = SecondaryDropTarget.getUninstallTarget(originalView.getContext(), - itemInfo); - if (cn == null) { - // If component name is null, don't show uninstall shortcut. - // System apps will have component name as null. - return null; - } - return new UninstallApp(activityContext, itemInfo, originalView, cn); - }; + public static final Factory UNINSTALL_APP = (activityContext, itemInfo, originalView) -> { + if (originalView == null) { + return null; + } + if (!Flags.enablePrivateSpace()) { + return null; + } + if (!UserCache.INSTANCE.get(originalView.getContext()).getUserInfo( + itemInfo.user).isPrivate()) { + // If app is not Private Space app. + return null; + } + ComponentName cn = SecondaryDropTarget.getUninstallTarget(originalView.getContext(), + itemInfo); + if (cn == null) { + // If component name is null, don't show uninstall shortcut. + // System apps will have component name as null. + return null; + } + return new UninstallApp(activityContext, itemInfo, originalView, cn); + }; private static class UninstallApp extends SystemShortcut { - @NonNull ComponentName mComponentName; + @NonNull + ComponentName mComponentName; UninstallApp(T target, ItemInfo itemInfo, @NonNull View originalView, @NonNull ComponentName cn) { @@ -404,63 +384,4 @@ public abstract class SystemShortcut extends ItemInfo mAbstractFloatingViewHelper.closeOpenViews(mTarget, true, AbstractFloatingView.TYPE_ALL & ~AbstractFloatingView.TYPE_REBIND_SAFE); } - - public static final Factory BUBBLE_SHORTCUT = - (activity, itemInfo, originalView) -> { - if ((itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) - && (itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) - && !(itemInfo instanceof WorkspaceItemInfo)) { - return null; - } - return new BubbleShortcut<>(activity, itemInfo, originalView); - }; - - public interface BubbleActivityStarter { - /** Tell SysUI to show the provided shortcut in a bubble. */ - void showShortcutBubble(ShortcutInfo info); - - /** Tell SysUI to show the provided intent in a bubble. */ - void showAppBubble(Intent intent, UserHandle user); - } - - public static class BubbleShortcut extends SystemShortcut { - - private BubbleActivityStarter mStarter; - - public BubbleShortcut(T target, ItemInfo itemInfo, View originalView) { - super(R.drawable.ic_bubble_button, R.string.bubble, target, - itemInfo, originalView); - if (target instanceof BubbleActivityStarter) { - mStarter = (BubbleActivityStarter) target; - } - } - - @Override - public void onClick(View view) { - dismissTaskMenuView(); - if (mStarter == null) { - Log.w(TAG, "starter null!"); - return; - } - // TODO: handle GroupTask (single) items so that recent items in taskbar work - if (mItemInfo instanceof WorkspaceItemInfo) { - WorkspaceItemInfo workspaceItemInfo = (WorkspaceItemInfo) mItemInfo; - ShortcutInfo shortcutInfo = workspaceItemInfo.getDeepShortcutInfo(); - if (shortcutInfo != null) { - mStarter.showShortcutBubble(shortcutInfo); - return; - } - } - // If we're here check for an intent - Intent intent = mItemInfo.getIntent(); - if (intent != null) { - if (intent.getPackage() == null) { - intent.setPackage(mItemInfo.getTargetPackage()); - } - mStarter.showAppBubble(intent, mItemInfo.user); - } else { - Log.w(TAG, "unable to bubble, no intent: " + mItemInfo); - } - } - } } diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java index acb6c631ee..3cd0ebe05e 100644 --- a/src/com/android/launcher3/provider/RestoreDbTask.java +++ b/src/com/android/launcher3/provider/RestoreDbTask.java @@ -77,19 +77,21 @@ import com.android.launcher3.util.ContentWriter; import com.android.launcher3.util.IntArray; import com.android.launcher3.util.LogConfig; +import java.io.File; import java.io.InvalidObjectException; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; -import java.util.function.Supplier; import java.util.stream.Collectors; /** * Utility class to update DB schema after it has been restored. * - * This task is executed when Launcher starts for the first time and not immediately after restore. - * This helps keep the model consistent if the launcher updates between restore and first startup. + * This task is executed when Launcher starts for the first time and not + * immediately after restore. + * This helps keep the model consistent if the launcher updates between restore + * and first startup. */ public class RestoreDbTask { @@ -103,9 +105,9 @@ public class RestoreDbTask { public static final String APPWIDGET_OLD_IDS = "appwidget_old_ids"; public static final String APPWIDGET_IDS = "appwidget_ids"; @VisibleForTesting - public static final String[] DB_COLUMNS_TO_LOG = {"profileId", "title", "itemType", "screen", + public static final String[] DB_COLUMNS_TO_LOG = { "profileId", "title", "itemType", "screen", "container", "cellX", "cellY", "spanX", "spanY", "intent", "appWidgetProvider", - "appWidgetId", "restored"}; + "appWidgetId", "restored" }; /** * Tries to restore the backup DB if needed @@ -127,49 +129,45 @@ public class RestoreDbTask { // executed again. LauncherPrefs.get(context).removeSync(RESTORE_DEVICE); - DeviceGridState deviceGridState = new DeviceGridState(context); - FileLog.d(TAG, "restoreIfNeeded: deviceGridState from context: " + deviceGridState); - String oldPhoneFileName = deviceGridState.getDbFile(); - List previousDbs = existingDbs(context); - removeOldDBs(context, oldPhoneFileName); - // The idp before this contains data about the old phone, after this it becomes the idp - // of the current phone. - if (!Flags.oneGridSpecs()) { - FileLog.d(TAG, "Resetting IDP to default for restore dest device"); + if (Flags.enableNarrowGridRestore()) { + String oldPhoneFileName = idp.dbFile; + List previousDbs = existingDbs(); + removeOldDBs(context, oldPhoneFileName); + // The idp before this contains data about the old phone, after this it becomes + // the idp + // of the current phone. idp.reset(context); - trySettingPreviousGridAsCurrent(context, idp, oldPhoneFileName, previousDbs); + trySettingPreviousGidAsCurrent(context, idp, oldPhoneFileName, previousDbs); + } else { + idp.reinitializeAfterRestore(context); } } - /** - * Try setting the gird used in the previous phone to the new one. If the current device doesn't + * Try setting the gird used in the previous phone to the new one. If the + * current device doesn't * support the previous grid option it will not be set. */ - private static void trySettingPreviousGridAsCurrent(Context context, InvariantDeviceProfile idp, + private static void trySettingPreviousGidAsCurrent(Context context, InvariantDeviceProfile idp, String oldPhoneDbFileName, List previousDbs) { InvariantDeviceProfile.GridOption oldPhoneGridOption = idp.getGridOptionFromFileName( context, oldPhoneDbFileName); - // The grid option could be null if current phone doesn't support the previous db. + // The grid option could be null if current phone doesn't support the previous + // db. if (oldPhoneGridOption != null) { - FileLog.d(TAG, "trySettingPreviousGridAsCurrent:" - + ", oldPhoneDbFileName: " + oldPhoneDbFileName - + ", oldPhoneGridOption: " + oldPhoneGridOption - + ", previousDbs: " + previousDbs); - - /* If the user only used the default db on the previous phone and the new default db is - * bigger than or equal to the previous one, then keep the new default db */ + /* + * If the user only used the default db on the previous phone and the new + * default db is + * bigger than or equal to the previous one, then keep the new default db + */ if (previousDbs.size() == 1 && oldPhoneGridOption.numColumns <= idp.numColumns && oldPhoneGridOption.numRows <= idp.numRows) { /* Keep the user in default grid */ - FileLog.d(TAG, "Keeping default db from restore as current grid"); return; } /* * Here we are setting the previous db as the current one. */ - FileLog.d(TAG, "Setting grid from old device as current grid: " - + "oldPhoneGridOption:" + oldPhoneGridOption.name); idp.setCurrentGrid(context, oldPhoneGridOption.name); } } @@ -177,20 +175,20 @@ public class RestoreDbTask { /** * Returns a list of paths of the existing launcher dbs. */ - @VisibleForTesting - public static List existingDbs(Context context) { - // At this point idp.dbFile contains the name of the dbFile from the previous phone + private static List existingDbs() { + // At this point idp.dbFile contains the name of the dbFile from the previous + // phone return LauncherFiles.GRID_DB_FILES.stream() - .filter(dbName -> context.getDatabasePath(dbName).exists()) - .collect(Collectors.toList()); + .filter(dbName -> new File(dbName).exists()) + .collect(toList()); } /** * Only keep the last database used on the previous device. */ - @VisibleForTesting - public static void removeOldDBs(Context context, String oldPhoneDbFileName) { - // At this point idp.dbFile contains the name of the dbFile from the previous phone + private static void removeOldDBs(Context context, String oldPhoneDbFileName) { + // At this point idp.dbFile contains the name of the dbFile from the previous + // phone LauncherFiles.GRID_DB_FILES.stream() .filter(dbName -> !dbName.equals(oldPhoneDbFileName)) .forEach(dbName -> { @@ -206,11 +204,9 @@ public class RestoreDbTask { try (SQLiteTransaction t = new SQLiteTransaction(db)) { RestoreDbTask task = new RestoreDbTask(); BackupManager backupManager = new BackupManager(context); - LauncherRestoreEventLogger restoreEventLogger = - LauncherRestoreEventLogger.Companion.newInstance(context); + LauncherRestoreEventLogger restoreEventLogger = LauncherRestoreEventLogger.Companion.newInstance(context); task.sanitizeDB(context, controller, db, backupManager, restoreEventLogger); - task.restoreAppWidgetIdsIfExists(context, controller, restoreEventLogger, - () -> new AppWidgetHost(context, APPWIDGET_HOST_ID)); + task.restoreAppWidgetIdsIfExists(context, controller, restoreEventLogger); t.commit(); return true; } catch (Exception e) { @@ -221,13 +217,15 @@ public class RestoreDbTask { /** * Makes the following changes in the provider DB. - * 1. Removes all entries belonging to any profiles that were not restored. - * 2. Marks all entries as restored. The flags are updated during first load or as - * the restored apps get installed. - * 3. If the user serial for any restored profile is different than that of the previous - * device, update the entries to the new profile id. - * 4. If restored from a single display backup, remove gaps between screenIds - * 5. Override shortcuts that need to be replaced. + * 1. Removes all entries belonging to any profiles that were not restored. + * 2. Marks all entries as restored. The flags are updated during first load or + * as + * the restored apps get installed. + * 3. If the user serial for any restored profile is different than that of the + * previous + * device, update the entries to the new profile id. + * 4. If restored from a single display backup, remove gaps between screenIds + * 5. Override shortcuts that need to be replaced. * * @return number of items deleted */ @@ -287,15 +285,17 @@ public class RestoreDbTask { db.update(Favorites.TABLE_NAME, values, null, null); // Mark widgets with appropriate restore flag. - values.put(Favorites.RESTORED, LauncherAppWidgetInfo.FLAG_ID_NOT_VALID + values.put(Favorites.RESTORED, LauncherAppWidgetInfo.FLAG_ID_NOT_VALID | LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY | LauncherAppWidgetInfo.FLAG_UI_NOT_READY | (keepAllIcons ? LauncherAppWidgetInfo.FLAG_RESTORE_STARTED : 0)); db.update(Favorites.TABLE_NAME, values, "itemType = ?", - new String[]{Integer.toString(Favorites.ITEM_TYPE_APPWIDGET)}); + new String[] { Integer.toString(Favorites.ITEM_TYPE_APPWIDGET) }); - // Migrate ids. To avoid any overlap, we initially move conflicting ids to a temp - // location. Using Long.MIN_VALUE since profile ids can not be negative, so there will + // Migrate ids. To avoid any overlap, we initially move conflicting ids to a + // temp + // location. Using Long.MIN_VALUE since profile ids can not be negative, so + // there will // be no overlap. final long tempLocationOffset = Long.MIN_VALUE; SparseLongArray tempMigratedIds = new SparseLongArray(profileMapping.size()); @@ -335,7 +335,8 @@ public class RestoreDbTask { } /** - * Remove gaps between screenIds to make sure no empty pages are left in between. + * Remove gaps between screenIds to make sure no empty pages are left in + * between. * * e.g. [0, 3, 4, 6, 7] -> [0, 1, 2, 3, 4] */ @@ -361,7 +362,8 @@ public class RestoreDbTask { } /** - * Updates profile id of all entries from {@param oldProfileId} to {@param newProfileId}. + * Updates profile id of all entries from {@param oldProfileId} to + * {@param newProfileId}. */ protected void migrateProfileId(SQLiteDatabase db, long oldProfileId, long newProfileId) { FileLog.d(TAG, "Changing profile user id from " + oldProfileId + " to " + newProfileId); @@ -369,10 +371,9 @@ public class RestoreDbTask { ContentValues values = new ContentValues(); values.put(Favorites.PROFILE_ID, newProfileId); db.update(Favorites.TABLE_NAME, values, "profileId = ?", - new String[]{Long.toString(oldProfileId)}); + new String[] { Long.toString(oldProfileId) }); } - /** * Changes the default value for the column. */ @@ -384,12 +385,13 @@ public class RestoreDbTask { } /** - * Returns a list of the managed profile id(s) used in the favorites table of the provided db. + * Returns a list of the managed profile id(s) used in the favorites table of + * the provided db. */ private LongSparseArray getManagedProfileIds(SQLiteDatabase db, long defaultProfileId) { LongSparseArray ids = new LongSparseArray<>(); try (Cursor c = db.rawQuery("SELECT profileId from favorites WHERE profileId != ? " - + "GROUP BY profileId", new String[] {Long.toString(defaultProfileId)})) { + + "GROUP BY profileId", new String[] { Long.toString(defaultProfileId) })) { while (c.moveToNext()) { ids.put(c.getLong(c.getColumnIndex(Favorites.PROFILE_ID)), null); } @@ -398,7 +400,8 @@ public class RestoreDbTask { } /** - * Returns a UserHandle of a restored managed profile with the given serial number, or null + * Returns a UserHandle of a restored managed profile with the given serial + * number, or null * if none found. */ private UserHandle getUserForAncestralSerialNumber(BackupManager backupManager, @@ -422,11 +425,7 @@ public class RestoreDbTask { } public static boolean isPending(Context context) { - return isPending(LauncherPrefs.get(context)); - } - - public static boolean isPending(LauncherPrefs prefs) { - return prefs.has(RESTORE_DEVICE); + return LauncherPrefs.get(context).has(RESTORE_DEVICE); } /** @@ -442,13 +441,14 @@ public class RestoreDbTask { @WorkerThread @VisibleForTesting void restoreAppWidgetIdsIfExists(Context context, ModelDbController controller, - LauncherRestoreEventLogger restoreEventLogger, Supplier hostSupplier) { + LauncherRestoreEventLogger restoreEventLogger) { LauncherPrefs lp = LauncherPrefs.get(context); if (lp.has(APP_WIDGET_IDS, OLD_APP_WIDGET_IDS)) { + AppWidgetHost host = new AppWidgetHost(context, APPWIDGET_HOST_ID); restoreAppWidgetIds(context, controller, restoreEventLogger, IntArray.fromConcatString(lp.get(OLD_APP_WIDGET_IDS)).toArray(), IntArray.fromConcatString(lp.get(APP_WIDGET_IDS)).toArray(), - hostSupplier.get()); + host); } else { FileLog.d(TAG, "Did not receive new app widget id map during Launcher restore"); } @@ -471,7 +471,8 @@ public class RestoreDbTask { return; } if (!RestoreDbTask.isPending(context)) { - // Someone has already gone through our DB once, probably LoaderTask. Skip any further + // Someone has already gone through our DB once, probably LoaderTask. Skip any + // further // modifications of the DB. FileLog.e(TAG, "Skipping widget ID remap as DB already in use"); for (int widgetId : newWidgetIds) { @@ -502,7 +503,8 @@ public class RestoreDbTask { state = LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY; } - // b/135926478: Work profile widget restore is broken in platform. This forces us to + // b/135926478: Work profile widget restore is broken in platform. This forces + // us to // recreate the widget during loading with the correct host provider. long mainProfileId = UserCache.INSTANCE.get(context) .getSerialNumberForUser(myUserHandle()); @@ -524,8 +526,8 @@ public class RestoreDbTask { + " the database anymore"); try (Cursor cursor = controller.getDb().query( Favorites.TABLE_NAME, - new String[]{Favorites.APPWIDGET_ID}, - "appWidgetId=?", new String[]{oldWidgetId}, null, null, null)) { + new String[] { Favorites.APPWIDGET_ID }, + "appWidgetId=?", new String[] { oldWidgetId }, null, null, null)) { if (!cursor.moveToFirst()) { // The widget no long exists. FileLog.d(TAG, "Deleting widgetId: " + newWidgetIds[i] + " with old id: " @@ -533,20 +535,19 @@ public class RestoreDbTask { host.deleteAppWidgetId(newWidgetIds[i]); launcherRestoreEventLogger.logSingleFavoritesItemRestoreFailed( ITEM_TYPE_APPWIDGET, - RestoreError.WIDGET_REMOVED - ); + RestoreError.WIDGET_REMOVED); } } } } logFavoritesTable(controller.getDb(), "launcher db after remap widget ids", null, null); - LauncherAppState.INSTANCE.get(context).getModel().reloadIfActive(); + LauncherAppState.INSTANCE.executeIfCreated(app -> app.getModel().forceReload()); } private static void logDatabaseWidgetInfo(ModelDbController controller) { try (Cursor cursor = controller.getDb().query(Favorites.TABLE_NAME, - new String[]{Favorites.APPWIDGET_ID, Favorites.RESTORED, Favorites.PROFILE_ID}, + new String[] { Favorites.APPWIDGET_ID, Favorites.RESTORED, Favorites.PROFILE_ID }, Favorites.APPWIDGET_ID + "!=" + LauncherAppWidgetInfo.NO_ID, null, null, null, null)) { IntArray widgetIdList = new IntArray(); @@ -589,19 +590,18 @@ public class RestoreDbTask { protected static void maybeOverrideShortcuts(Context context, ModelDbController controller, SQLiteDatabase db, long currentUser) { - Map activityOverrides = - ApiWrapper.INSTANCE.get(context).getActivityOverrides(); + Map activityOverrides = ApiWrapper.INSTANCE.get(context).getActivityOverrides(); if (activityOverrides == null || activityOverrides.isEmpty()) { return; } try (Cursor c = db.query(Favorites.TABLE_NAME, - new String[]{Favorites._ID, Favorites.INTENT}, + new String[] { Favorites._ID, Favorites.INTENT }, String.format("%s=? AND %s=? AND ( %s )", Favorites.ITEM_TYPE, Favorites.PROFILE_ID, getTelephonyIntentSQLLiteSelection(activityOverrides.keySet())), - new String[]{String.valueOf(ITEM_TYPE_APPLICATION), String.valueOf(currentUser)}, + new String[] { String.valueOf(ITEM_TYPE_APPLICATION), String.valueOf(currentUser) }, null, null, null); - SQLiteTransaction t = new SQLiteTransaction(db)) { + SQLiteTransaction t = new SQLiteTransaction(db)) { final int idIndex = c.getColumnIndexOrThrow(Favorites._ID); final int intentIndex = c.getColumnIndexOrThrow(Favorites.INTENT); while (c.moveToNext()) { @@ -613,7 +613,7 @@ public class RestoreDbTask { controller.getSerialNumberForUser(override.getUser())); values.put(Favorites.INTENT, AppInfo.makeLaunchIntent(override).toUri(0)); db.update(Favorites.TABLE_NAME, values, String.format("%s=?", Favorites._ID), - new String[]{String.valueOf(c.getInt(idIndex))}); + new String[] { String.valueOf(c.getInt(idIndex)) }); } } t.commit(); @@ -625,16 +625,20 @@ public class RestoreDbTask { private static String getTelephonyIntentSQLLiteSelection(Collection packages) { return packages.stream().map( packageToChange -> String.format("intent LIKE '%%' || '%s' || '%%' ", - packageToChange)).collect( - Collectors.joining(" OR ")); + packageToChange)) + .collect( + Collectors.joining(" OR ")); } /** * Queries and logs the items from the Favorites table in the launcher db. - * This is to understand why items might be missing during the restore process for Launcher. - * @param database The Launcher db to query from. - * @param logHeader First line in log statement, used to explain what is being logged. - * @param where The SELECT statement to query items. + * This is to understand why items might be missing during the restore process + * for Launcher. + * + * @param database The Launcher db to query from. + * @param logHeader First line in log statement, used to explain what is being + * logged. + * @param where The SELECT statement to query items. * @param profileIds The profile ID's for each user profile. */ public static void logFavoritesTable(SQLiteDatabase database, @NonNull String logHeader, @@ -646,8 +650,7 @@ public class RestoreDbTask { /* selection args */ profileIds, /* groupBy */ null, /* having */ null, - /* orderBy */ null - )) { + /* orderBy */ null)) { if (cursor.moveToFirst()) { String[] columnNames = cursor.getColumnNames(); StringBuilder stringBuilder = new StringBuilder(logHeader + "\n"); @@ -671,12 +674,13 @@ public class RestoreDbTask { } } - /** - * Queries and reports the count of each itemType to be removed due to unrestored profiles. - * @param database The Launcher db to query from. - * @param where Query being used for to find unrestored profiles - * @param profileIds profile ids that were not restored + * Queries and reports the count of each itemType to be removed due to + * unrestored profiles. + * + * @param database The Launcher db to query from. + * @param where Query being used for to find unrestored profiles + * @param profileIds profile ids that were not restored * @param restoreEventLogger Backup/Restore Logger to report metrics */ private void reportUnrestoredProfiles(SQLiteDatabase database, String where, @@ -689,8 +693,7 @@ public class RestoreDbTask { restoreEventLogger.logFavoritesItemsRestoreFailed( cursor.getInt(cursor.getColumnIndexOrThrow(ITEM_TYPE)), cursor.getInt(cursor.getColumnIndexOrThrow("count")), - RestoreError.PROFILE_NOT_RESTORED - ); + RestoreError.PROFILE_NOT_RESTORED); } while (cursor.moveToNext()); } } catch (Exception e) { diff --git a/src/com/android/launcher3/qsb/QsbContainerView.java b/src/com/android/launcher3/qsb/QsbContainerView.java index 9719b5593c..4589f5679b 100644 --- a/src/com/android/launcher3/qsb/QsbContainerView.java +++ b/src/com/android/launcher3/qsb/QsbContainerView.java @@ -308,7 +308,7 @@ public class QsbContainerView extends FrameLayout { } public boolean isQsbEnabled() { - return FeatureFlags.QSB_ON_FIRST_SCREEN; + return FeatureFlags.topQsbOnFirstScreenEnabled(getContext()); } protected Bundle createBindOptions() { @@ -320,8 +320,7 @@ public class QsbContainerView extends FrameLayout { protected View getDefaultView(ViewGroup container, boolean showSetupIcon) { // Return a default widget with setup icon. View v = QsbWidgetHostView.getDefaultView(container); - // pE-TODO(??): Why are we using isInPreviewMode() check to prevent crash? - if (showSetupIcon && !isInPreviewMode()) { + if (showSetupIcon) { requestQsbCreate(); View setupButton = v.findViewById(R.id.btn_qsb_setup); setupButton.setVisibility(View.VISIBLE); diff --git a/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt b/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt index c01049a673..6d6b3b6497 100644 --- a/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt +++ b/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt @@ -17,16 +17,10 @@ package com.android.launcher3.recyclerview import android.content.Context -import android.util.Log -import android.view.ContextThemeWrapper -import android.view.InflateException -import androidx.annotation.VisibleForTesting -import androidx.annotation.VisibleForTesting.Companion.PROTECTED import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView.RecycledViewPool import androidx.recyclerview.widget.RecyclerView.ViewHolder import com.android.launcher3.BubbleTextView -import com.android.launcher3.BuildConfig import com.android.launcher3.allapps.BaseAllAppsAdapter import com.android.launcher3.config.FeatureFlags import com.android.launcher3.util.CancellableTask @@ -34,6 +28,7 @@ import com.android.launcher3.util.Executors.MAIN_EXECUTOR import com.android.launcher3.util.Executors.VIEW_PREINFLATION_EXECUTOR import com.android.launcher3.util.Themes import com.android.launcher3.views.ActivityContext +import com.android.launcher3.views.ActivityContext.ActivityContextDelegate const val PREINFLATE_ICONS_ROW_COUNT = 4 const val EXTRA_ICONS_COUNT = 2 @@ -43,22 +38,15 @@ const val EXTRA_ICONS_COUNT = 2 * [RecyclerView]. The view inflation will happen on background thread and inflated [ViewHolder]s * will be added to [RecycledViewPool] on main thread. */ -class AllAppsRecyclerViewPool : RecycledViewPool() where T : Context, T : ActivityContext { +class AllAppsRecyclerViewPool : RecycledViewPool() { var hasWorkProfile = false - @VisibleForTesting(otherwise = PROTECTED) - var mCancellableTask: CancellableTask>? = null - - companion object { - private const val TAG = "AllAppsRecyclerViewPool" - private const val NULL_LAYOUT_MANAGER_ERROR_STRING = - "activeRv's layoutManager should not be null" - } + private var mCancellableTask: CancellableTask>? = null /** * Preinflate app icons. If all apps RV cannot be scrolled down, we don't need to preinflate. */ - fun preInflateAllAppsViewHolders(context: T) { + fun preInflateAllAppsViewHolders(context: T) where T : Context, T : ActivityContext { val appsView = context.appsView ?: return val activeRv: RecyclerView = appsView.activeRecyclerView ?: return val preInflateCount = getPreinflateCount(context) @@ -66,22 +54,15 @@ class AllAppsRecyclerViewPool : RecycledViewPool() where T : Context, T : Act return } - if (activeRv.layoutManager == null) { - if (false) { - throw IllegalStateException(NULL_LAYOUT_MANAGER_ERROR_STRING) - } else { - Log.e(TAG, NULL_LAYOUT_MANAGER_ERROR_STRING) - } - return - } - // Create a separate context dedicated for all apps preinflation thread. The goal is to // create a separate AssetManager obj internally to avoid lock contention with // AssetManager obj that is associated with the launcher context on the main thread. val allAppsPreInflationContext = - ContextThemeWrapper(context, Themes.getActivityThemeRes(context)).apply { - applyOverrideConfiguration(context.resources.configuration) - } + ActivityContextDelegate( + context.createConfigurationContext(context.resources.configuration), + Themes.getActivityThemeRes(context), + context + ) // Because we perform onCreateViewHolder() on worker thread, we need a separate // adapter/inflator object as they are not thread-safe. Note that the adapter @@ -93,72 +74,37 @@ class AllAppsRecyclerViewPool : RecycledViewPool() where T : Context, T : Act context, context.appsView.layoutInflater.cloneInContext(allAppsPreInflationContext), null, - null, + null ) { override fun setAppsPerRow(appsPerRow: Int) = Unit - override fun getLayoutManager(): RecyclerView.LayoutManager? = null } - preInflateAllAppsViewHolders( - adapter, - BaseAllAppsAdapter.VIEW_TYPE_ICON, - activeRv, - preInflateCount, - ) { - getPreinflateCount(context) - } - } - - @VisibleForTesting(otherwise = PROTECTED) - fun preInflateAllAppsViewHolders( - adapter: RecyclerView.Adapter<*>, - viewType: Int, - activeRv: RecyclerView, - preInflationCount: Int, - preInflationCountProvider: () -> Int, - ) { - if (preInflationCount <= 0) { - return - } mCancellableTask?.cancel() var task: CancellableTask>? = null task = CancellableTask( { val list: ArrayList = ArrayList() - for (i in 0 until preInflationCount) { + for (i in 0 until preInflateCount) { if (task?.canceled == true) { break } - // If activeRv's layout manager has been reset to null on main thread, skip - // the preinflation as we cannot generate correct LayoutParams - if (activeRv.layoutManager == null) { - list.clear() - break - } - try { - list.add(adapter.createViewHolder(activeRv, viewType)) - } catch (e: InflateException) { - list.clear() - // It's still possible for UI thread to set activeRv's layout manager to - // null and we should break the loop and cancel the preinflation. - break - } + list.add( + adapter.createViewHolder(activeRv, BaseAllAppsAdapter.VIEW_TYPE_ICON) + ) } list }, MAIN_EXECUTOR, { viewHolders -> - // Run preInflationCountProvider again as the needed VH might have changed - val newPreInflationCount = preInflationCountProvider.invoke() - for (i in 0 until minOf(viewHolders.size, newPreInflationCount)) { + for (i in 0 until minOf(viewHolders.size, getPreinflateCount(context))) { putRecycledView(viewHolders[i]) } - }, + } ) mCancellableTask = task - VIEW_PREINFLATION_EXECUTOR.execute(mCancellableTask) + VIEW_PREINFLATION_EXECUTOR.submit(mCancellableTask) } /** @@ -179,12 +125,14 @@ class AllAppsRecyclerViewPool : RecycledViewPool() where T : Context, T : Act * app icons in size of one all apps pages, so that opening all apps don't need to inflate app * icons. */ - fun getPreinflateCount(context: T): Int { + fun getPreinflateCount(context: T): Int where T : Context, T : ActivityContext { var targetPreinflateCount = PREINFLATE_ICONS_ROW_COUNT * context.deviceProfile.numShownAllAppsColumns + EXTRA_ICONS_COUNT - val grid = ActivityContext.lookupContext(context).deviceProfile - targetPreinflateCount += grid.maxAllAppsRowCount * grid.numShownAllAppsColumns + if (FeatureFlags.ALL_APPS_GONE_VISIBILITY.get()) { + val grid = ActivityContext.lookupContext(context).deviceProfile + targetPreinflateCount += grid.maxAllAppsRowCount * grid.numShownAllAppsColumns + } if (hasWorkProfile) { targetPreinflateCount *= 2 } diff --git a/src/com/android/launcher3/search/StringMatcherUtility.java b/src/com/android/launcher3/search/StringMatcherUtility.java index 20b4d0b08a..7446314296 100644 --- a/src/com/android/launcher3/search/StringMatcherUtility.java +++ b/src/com/android/launcher3/search/StringMatcherUtility.java @@ -127,17 +127,17 @@ public class StringMatcherUtility { if (query == null || target == null) { return false; } - int compare = mCollator.compare(query, target); - if (compare == 0) { - return true; - } else if (compare < 0) { - // The target string can contain a modifier which would make it larger than - // the query string (even though the length is same). If the query becomes - // larger after appending a unicode character, it was originally a prefix of - // the target string and hence should match. - return mCollator.compare(query + MAX_UNICODE, target) >= 0; - } else { - return false; + switch (mCollator.compare(query, target)) { + case 0: + return true; + case -1: + // The target string can contain a modifier which would make it larger than + // the query string (even though the length is same). If the query becomes + // larger after appending a unicode character, it was originally a prefix of + // the target string and hence should match. + return mCollator.compare(query + MAX_UNICODE, target) > -1; + default: + return false; } } diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java index c4fed71f9a..529b9c2a49 100644 --- a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java +++ b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java @@ -15,9 +15,6 @@ */ package com.android.launcher3.secondarydisplay; -import static com.android.launcher3.util.WallpaperThemeManager.setWallpaperDependentTheme; -import static com.android.window.flags.Flags.enableTaskbarConnectedDisplays; - import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.content.Intent; @@ -29,11 +26,10 @@ import android.view.View.OnClickListener; import android.view.ViewAnimationUtils; import android.view.inputmethod.InputMethodManager; -import androidx.annotation.NonNull; import androidx.annotation.UiThread; import com.android.launcher3.AbstractFloatingView; -import com.android.launcher3.BaseActivity; +import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.BubbleTextView; import com.android.launcher3.DragSource; import com.android.launcher3.DropTarget; @@ -58,6 +54,7 @@ import com.android.launcher3.popup.PopupContainerWithArrow; import com.android.launcher3.popup.PopupDataProvider; import com.android.launcher3.touch.ItemClickHandler.ItemClickProxy; import com.android.launcher3.util.ComponentKey; +import com.android.launcher3.util.IntSet; import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.util.Preconditions; import com.android.launcher3.util.Themes; @@ -69,7 +66,7 @@ import java.util.Map; /** * Launcher activity for secondary displays */ -public class SecondaryDisplayLauncher extends BaseActivity +public class SecondaryDisplayLauncher extends BaseDraggingActivity implements BgDataModel.Callbacks, DragController.DragListener { private LauncherModel mModel; @@ -83,6 +80,7 @@ public class SecondaryDisplayLauncher extends BaseActivity private boolean mAppDrawerShown = false; private StringCache mStringCache; + private boolean mBindingItems = false; private SecondaryDisplayPredictions mSecondaryDisplayPredictions; private final int[] mTempXY = new int[2]; @@ -90,26 +88,50 @@ public class SecondaryDisplayLauncher extends BaseActivity @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setWallpaperDependentTheme(this); mModel = LauncherAppState.getInstance(this).getModel(); mDragController = new SecondaryDragController(this); mSecondaryDisplayPredictions = SecondaryDisplayPredictions.newInstance(this); + if (getWindow().getDecorView().isAttachedToWindow()) { + initUi(); + } + } - mDeviceProfile = InvariantDeviceProfile.INSTANCE.get(this) - .createDeviceProfileForSecondaryDisplay(this); + @Override + public void onAttachedToWindow() { + super.onAttachedToWindow(); + initUi(); + } + + @Override + public void onDetachedFromWindow() { + super.onDetachedFromWindow(); + this.getDragController().removeDragListener(this); + } + + private void initUi() { + if (mDragLayer != null) { + return; + } + InvariantDeviceProfile currentDisplayIdp = new InvariantDeviceProfile( + this, getWindow().getDecorView().getDisplay()); + + // Disable transpose layout and use multi-window mode so that the icons are + // scaled properly + mDeviceProfile = currentDisplayIdp.getDeviceProfile(this) + .toBuilder(this) + .setMultiWindowMode(true) + .setTransposeLayoutWithOrientation(false) + .build(); mDeviceProfile.autoResizeAllAppsCells(); setContentView(R.layout.secondary_launcher); mDragLayer = findViewById(R.id.drag_layer); mAppsView = findViewById(R.id.apps_view); mAppsButton = findViewById(R.id.all_apps_button); - // TODO (b/391965805): Replace this flag with DesktopExperiences flag. - if (enableTaskbarConnectedDisplays()) { - mAppsButton.setVisibility(View.INVISIBLE); - } mDragController.addDragListener(this); - mPopupDataProvider = new PopupDataProvider(this); + mPopupDataProvider = new PopupDataProvider( + mAppsView.getAppsStore()::updateNotificationDots); mModel.addCallbacksAndLoad(this); } @@ -133,7 +155,8 @@ public class SecondaryDisplayLauncher extends BaseActivity } } - // A new intent will bring the launcher to top. Hide the app drawer to reset the state. + // A new intent will bring the launcher to top. Hide the app drawer to reset the + // state. showAppDrawer(false); } @@ -152,7 +175,8 @@ public class SecondaryDisplayLauncher extends BaseActivity return; } - // Note: There should be at most one log per method call. This is enforced implicitly + // Note: There should be at most one log per method call. This is enforced + // implicitly // by using if-else statements. AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(this); if (topView != null && topView.canHandleBack()) { @@ -178,6 +202,15 @@ public class SecondaryDisplayLauncher extends BaseActivity return mAppsView; } + @Override + public View getRootView() { + return mDragLayer; + } + + @Override + protected void reapplyUi() { + } + @Override public BaseDragLayer getDragLayer() { return mDragLayer; @@ -207,7 +240,7 @@ public class SecondaryDisplayLauncher extends BaseActivity float closeR = Themes.getDialogCornerRadius(this); float startR = mAppsButton.getWidth() / 2f; - float[] buttonPos = new float[]{startR, startR}; + float[] buttonPos = new float[] { startR, startR }; mDragLayer.getDescendantCoordRelativeToSelf(mAppsButton, buttonPos); mDragLayer.mapCoordInSelfToDescendant(mAppsView, buttonPos); final Animator animator = ViewAnimationUtils.createCircularReveal(mAppsView, @@ -225,9 +258,7 @@ public class SecondaryDisplayLauncher extends BaseActivity @Override public void onAnimationEnd(Animator animation) { mAppsView.setVisibility(View.INVISIBLE); - // TODO (b/391965805): Replace this flag with DesktopExperiences flag. - mAppsButton.setVisibility( - enableTaskbarConnectedDisplays() ? View.INVISIBLE : View.VISIBLE); + mAppsButton.setVisibility(View.VISIBLE); mAppsView.getSearchUiManager().resetSearch(); } }); @@ -237,9 +268,20 @@ public class SecondaryDisplayLauncher extends BaseActivity @Override public void startBinding() { + mBindingItems = true; mDragController.cancelDrag(); } + @Override + public boolean isBindingItems() { + return mBindingItems; + } + + @Override + public void finishBindingItems(IntSet pagesBoundFirst) { + mBindingItems = false; + } + @Override public void bindDeepShortcutMap(HashMap deepShortcutMap) { mPopupDataProvider.setDeepShortcutMap(deepShortcutMap); @@ -272,8 +314,6 @@ public class SecondaryDisplayLauncher extends BaseActivity mStringCache = cache; } - @Override - @NonNull public PopupDataProvider getPopupDataProvider() { return mPopupDataProvider; } @@ -289,9 +329,12 @@ public class SecondaryDisplayLauncher extends BaseActivity } private void onIconClicked(View v) { - // Make sure that rogue clicks don't get through while allapps is launching, or after the - // view has detached (it's possible for this to happen if the view is removed mid touch). - if (v.getWindowToken() == null) return; + // Make sure that rogue clicks don't get through while allapps is launching, or + // after the + // view has detached (it's possible for this to happen if the view is removed + // mid touch). + if (v.getWindowToken() == null) + return; Object tag = v.getTag(); if (tag instanceof ItemClickProxy) { @@ -301,7 +344,7 @@ public class SecondaryDisplayLauncher extends BaseActivity Intent intent; if (item instanceof ItemInfoWithIcon && (((ItemInfoWithIcon) item).runtimeStatusFlags - & ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE) != 0) { + & ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE) != 0) { ItemInfoWithIcon appInfo = (ItemInfoWithIcon) item; intent = appInfo.getMarketIntent(this); } else { @@ -315,7 +358,8 @@ public class SecondaryDisplayLauncher extends BaseActivity } /** - * Core functionality for beginning a drag operation for an item that will be dropped within + * Core functionality for beginning a drag operation for an item that will be + * dropped within * the secondary display grid home screen */ public void beginDragShared(View child, DragSource source, DragOptions options) { @@ -412,8 +456,10 @@ public class SecondaryDisplayLauncher extends BaseActivity } @Override - public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) { } + public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) { + } @Override - public void onDragEnd() { } + public void onDragEnd() { + } } diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java b/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java index c1008faeeb..c36c9c9b59 100644 --- a/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java +++ b/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java @@ -65,7 +65,7 @@ public class SecondaryDragLayer extends BaseDragLayer @Override public void recreateControllers() { mControllers = new TouchController[]{new CloseAllAppsTouchController(), - mContainer.getDragController()}; + mActivity.getDragController()}; } /** @@ -79,10 +79,10 @@ public class SecondaryDragLayer extends BaseDragLayer mAppsView = findViewById(R.id.apps_view); // Setup workspace mWorkspace = findViewById(R.id.workspace_grid); - mPinnedAppsAdapter = new PinnedAppsAdapter(mContainer, mAppsView.getAppsStore(), + mPinnedAppsAdapter = new PinnedAppsAdapter(mActivity, mAppsView.getAppsStore(), this::onIconLongClicked); mWorkspace.setAdapter(mPinnedAppsAdapter); - mWorkspace.setNumColumns(mContainer.getDeviceProfile().inv.numColumns); + mWorkspace.setNumColumns(mActivity.getDeviceProfile().inv.numColumns); } /** @@ -112,7 +112,7 @@ public class SecondaryDragLayer extends BaseDragLayer int height = MeasureSpec.getSize(heightMeasureSpec); setMeasuredDimension(width, height); - DeviceProfile grid = mContainer.getDeviceProfile(); + DeviceProfile grid = mActivity.getDeviceProfile(); int count = getChildCount(); for (int i = 0; i < count; i++) { final View child = getChildAt(i); @@ -153,17 +153,17 @@ public class SecondaryDragLayer extends BaseDragLayer @Override public boolean onControllerInterceptTouchEvent(MotionEvent ev) { - if (!mContainer.isAppDrawerShown()) { + if (!mActivity.isAppDrawerShown()) { return false; } - if (AbstractFloatingView.getTopOpenView(mContainer) != null) { + if (AbstractFloatingView.getTopOpenView(mActivity) != null) { return false; } if (ev.getAction() == MotionEvent.ACTION_DOWN - && !isEventOverView(mContainer.getAppsView(), ev)) { - mContainer.showAppDrawer(false); + && !isEventOverView(mActivity.getAppsView(), ev)) { + mActivity.showAppDrawer(false); return true; } return false; @@ -178,7 +178,7 @@ public class SecondaryDragLayer extends BaseDragLayer if (!(v instanceof BubbleTextView)) { return false; } - if (PopupContainerWithArrow.getOpen(mContainer) != null) { + if (PopupContainerWithArrow.getOpen(mActivity) != null) { // There is already an items container open, so don't open this one. v.clearFocus(); return false; @@ -187,32 +187,32 @@ public class SecondaryDragLayer extends BaseDragLayer if (!ShortcutUtil.supportsShortcuts(item)) { return false; } - PopupDataProvider popupDataProvider = mContainer.getPopupDataProvider(); + PopupDataProvider popupDataProvider = mActivity.getPopupDataProvider(); if (popupDataProvider == null) { return false; } // order of this list will reflect in the popup List systemShortcuts = new ArrayList<>(); - systemShortcuts.add(APP_INFO.getShortcut(mContainer, item, v)); + systemShortcuts.add(APP_INFO.getShortcut(mActivity, item, v)); // Hide redundant pin shortcut for app drawer icons if drag-n-drop is enabled. - if (!FeatureFlags.SECONDARY_DRAG_N_DROP_TO_PIN.get() || !mContainer.isAppDrawerShown()) { + if (!FeatureFlags.SECONDARY_DRAG_N_DROP_TO_PIN.get() || !mActivity.isAppDrawerShown()) { systemShortcuts.add(mPinnedAppsAdapter.getSystemShortcut(item, v)); } int deepShortcutCount = popupDataProvider.getShortcutCountForItem(item); final PopupContainerWithArrow container; - container = (PopupContainerWithArrow) mContainer.getLayoutInflater().inflate( - R.layout.popup_container, mContainer.getDragLayer(), false); + container = (PopupContainerWithArrow) mActivity.getLayoutInflater().inflate( + R.layout.popup_container, mActivity.getDragLayer(), false); container.populateAndShowRows((BubbleTextView) v, deepShortcutCount, systemShortcuts); container.requestFocus(); - if (!FeatureFlags.SECONDARY_DRAG_N_DROP_TO_PIN.get() || !mContainer.isAppDrawerShown()) { + if (!FeatureFlags.SECONDARY_DRAG_N_DROP_TO_PIN.get() || !mActivity.isAppDrawerShown()) { return true; } DragOptions options = new DragOptions(); - DeviceProfile grid = mContainer.getDeviceProfile(); + DeviceProfile grid = mActivity.getDeviceProfile(); options.intrinsicIconScaleFactor = (float) grid.allAppsIconSizePx / grid.iconSizePx; options.preDragCondition = container.createPreDragCondition(false); if (options.preDragCondition == null) { @@ -229,7 +229,7 @@ public class SecondaryDragLayer extends BaseDragLayer mDragView = dragObject.dragView; if (!shouldStartDrag(0)) { mDragView.setOnScaleAnimEndCallback(() -> - mContainer.beginDragShared(v, mContainer.getAppsView(), options)); + mActivity.beginDragShared(v, mActivity.getAppsView(), options)); } } @@ -239,7 +239,7 @@ public class SecondaryDragLayer extends BaseDragLayer } }; } - mContainer.beginDragShared(v, mContainer.getAppsView(), options); + mActivity.beginDragShared(v, mActivity.getAppsView(), options); return true; } } \ No newline at end of file diff --git a/src/com/android/launcher3/settings/SettingsActivity.java b/src/com/android/launcher3/settings/SettingsActivity.java index ab8d16bb46..315231546c 100644 --- a/src/com/android/launcher3/settings/SettingsActivity.java +++ b/src/com/android/launcher3/settings/SettingsActivity.java @@ -22,14 +22,10 @@ import static androidx.preference.PreferenceFragmentCompat.ARG_PREFERENCE_ROOT; import static com.android.launcher3.BuildConfigs.IS_DEBUG_DEVICE; import static com.android.launcher3.BuildConfigs.IS_STUDIO_BUILD; -import static com.android.launcher3.BuildConfigs.NOTIFICATION_DOTS_ENABLED; -import static com.android.launcher3.InvariantDeviceProfile.TYPE_MULTI_DISPLAY; -import static com.android.launcher3.InvariantDeviceProfile.TYPE_TABLET; import static com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY; import android.app.Activity; import android.content.Intent; -import android.content.pm.ActivityInfo; import android.net.Uri; import android.os.Bundle; import android.provider.Settings; @@ -48,14 +44,12 @@ import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; import androidx.preference.PreferenceFragmentCompat.OnPreferenceStartFragmentCallback; import androidx.preference.PreferenceFragmentCompat.OnPreferenceStartScreenCallback; -import androidx.preference.PreferenceGroup; import androidx.preference.PreferenceGroup.PreferencePositionCallback; import androidx.preference.PreferenceScreen; import androidx.recyclerview.widget.RecyclerView; import com.android.launcher3.BuildConfig; -import com.android.launcher3.Flags; -import com.android.launcher3.InvariantDeviceProfile; +import com.android.launcher3.BuildConfigs; import com.android.launcher3.LauncherFiles; import com.android.launcher3.R; import com.android.launcher3.states.RotationHelper; @@ -63,23 +57,25 @@ import com.android.launcher3.util.DisplayController; import com.android.launcher3.util.SettingsCache; /** - * Settings activity for Launcher. Currently implements the following setting: Allow rotation + * Settings activity for Launcher. Currently implements the following setting: + * Allow rotation */ public class SettingsActivity extends FragmentActivity implements OnPreferenceStartFragmentCallback, OnPreferenceStartScreenCallback { @VisibleForTesting - public static final String DEVELOPER_OPTIONS_KEY = "pref_developer_options"; - - public static final String FIXED_LANDSCAPE_MODE = "pref_fixed_landscape_mode"; + static final String DEVELOPER_OPTIONS_KEY = "pref_developer_options"; private static final String NOTIFICATION_DOTS_PREFERENCE_KEY = "pref_icon_badging"; public static final String EXTRA_FRAGMENT_ARGS = ":settings:fragment_args"; + public static final String EXTRA_SHOW_FRAGMENT_ARGS = ":settings:show_fragment_args"; - // Intent extra to indicate the pref-key to highlighted when opening the settings activity + // Intent extra to indicate the pref-key to highlighted when opening the + // settings activity public static final String EXTRA_FRAGMENT_HIGHLIGHT_KEY = ":settings:fragment_args_key"; - // Intent extra to indicate the pref-key of the root screen when opening the settings activity + // Intent extra to indicate the pref-key of the root screen when opening the + // settings activity public static final String EXTRA_FRAGMENT_ROOT_KEY = ARG_PREFERENCE_ROOT; private static final int DELAY_HIGHLIGHT_DURATION_MILLIS = 600; @@ -125,7 +121,8 @@ public class SettingsActivity extends FragmentActivity private boolean startPreference(String fragment, Bundle args, String key) { if (getSupportFragmentManager().isStateSaved()) { - // Sometimes onClick can come after onPause because of being posted on the handler. + // Sometimes onClick can come after onPause because of being posted on the + // handler. // Skip starting new preferences in that case. return false; } @@ -169,17 +166,16 @@ public class SettingsActivity extends FragmentActivity public static class LauncherSettingsFragment extends PreferenceFragmentCompat implements SettingsCache.OnChangeListener { - protected boolean mDeveloperOptionsEnabled = true; + protected boolean mDeveloperOptionsEnabled = false; private boolean mRestartOnResume = false; private String mHighLightKey; - private boolean mPreferenceHighlighted = false; @Override public void onCreate(@Nullable Bundle savedInstanceState) { - if (false) { + if (BuildConfigs.IS_DEBUG_DEVICE) { Uri devUri = Settings.Global.getUriFor(DEVELOPMENT_SETTINGS_ENABLED); SettingsCache settingsCache = SettingsCache.INSTANCE.get(getContext()); mDeveloperOptionsEnabled = settingsCache.getValue(devUri); @@ -208,62 +204,11 @@ public class SettingsActivity extends FragmentActivity } } - // If the target preference is not in the current preference screen, find the parent - // preference screen that contains the target preference and set it as the preference - // screen. - if (Flags.navigateToChildPreference() - && mHighLightKey != null - && !isKeyInPreferenceGroup(mHighLightKey, screen)) { - final PreferenceScreen parentPreferenceScreen = - findParentPreference(screen, mHighLightKey); - if (parentPreferenceScreen != null && getActivity() != null) { - if (!TextUtils.isEmpty(parentPreferenceScreen.getTitle())) { - getActivity().setTitle(parentPreferenceScreen.getTitle()); - } - setPreferenceScreen(parentPreferenceScreen); - return; - } - } - if (getActivity() != null && !TextUtils.isEmpty(getPreferenceScreen().getTitle())) { getActivity().setTitle(getPreferenceScreen().getTitle()); } } - private boolean isKeyInPreferenceGroup(String targetKey, PreferenceGroup parent) { - for (int i = 0; i < parent.getPreferenceCount(); i++) { - Preference pref = parent.getPreference(i); - if (pref.getKey() != null && pref.getKey().equals(targetKey)) { - return true; - } - } - return false; - } - - /** - * Finds the parent preference screen for the given target key. - * - * @param parent the parent preference screen - * @param targetKey the key of the preference to find - * @return the parent preference screen that contains the target preference - */ - @Nullable - private PreferenceScreen findParentPreference(PreferenceScreen parent, String targetKey) { - for (int i = 0; i < parent.getPreferenceCount(); i++) { - Preference pref = parent.getPreference(i); - if (pref instanceof PreferenceScreen) { - PreferenceScreen foundKey = findParentPreference((PreferenceScreen) pref, - targetKey); - if (foundKey != null) { - return foundKey; - } - } else if (pref.getKey() != null && pref.getKey().equals(targetKey)) { - return parent; - } - } - return null; - } - @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); @@ -289,18 +234,17 @@ public class SettingsActivity extends FragmentActivity } /** - * Initializes a preference. This is called for every preference. Returning false here + * Initializes a preference. This is called for every preference. Returning + * false here * will remove that preference from the list. */ protected boolean initPreference(Preference preference) { - DisplayController.Info info = DisplayController.INSTANCE.get(getContext()).getInfo(); switch (preference.getKey()) { case NOTIFICATION_DOTS_PREFERENCE_KEY: - return NOTIFICATION_DOTS_ENABLED; + return BuildConfigs.NOTIFICATION_DOTS_ENABLED; + case ALLOW_ROTATION_PREFERENCE_KEY: - if (Flags.oneGridSpecs()) { - return false; - } + DisplayController.Info info = DisplayController.INSTANCE.get(getContext()).getInfo(); if (info.isTablet(info.realBounds)) { // Launcher supports rotation by default. No need to show this setting. return false; @@ -308,34 +252,14 @@ public class SettingsActivity extends FragmentActivity // Initialize the UI once preference.setDefaultValue(RotationHelper.getAllowRotationDefaultValue(info)); return true; + case DEVELOPER_OPTIONS_KEY: if (IS_STUDIO_BUILD) { preference.setOrder(0); } return mDeveloperOptionsEnabled; - case FIXED_LANDSCAPE_MODE: - if (!Flags.oneGridSpecs() - // adding this condition until fixing b/378972567 - || InvariantDeviceProfile.INSTANCE.get(getContext()).deviceType - == TYPE_MULTI_DISPLAY - || InvariantDeviceProfile.INSTANCE.get(getContext()).deviceType - == TYPE_TABLET) { - return false; - } - // When the setting changes rotate the screen accordingly to showcase the result - // of the setting - preference.setOnPreferenceChangeListener( - (pref, newValue) -> { - getActivity().setRequestedOrientation( - (boolean) newValue - ? ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE - : ActivityInfo.SCREEN_ORIENTATION_USER - ); - return true; - } - ); - return !info.isTablet(info.realBounds); } + return true; } diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutTextView.java b/src/com/android/launcher3/shortcuts/DeepShortcutTextView.java index 282d52f64a..4aaa7ff8bf 100644 --- a/src/com/android/launcher3/shortcuts/DeepShortcutTextView.java +++ b/src/com/android/launcher3/shortcuts/DeepShortcutTextView.java @@ -16,12 +16,9 @@ package com.android.launcher3.shortcuts; -import static com.android.wm.shell.Flags.enableGsf; - import android.content.Context; import android.graphics.Canvas; import android.graphics.Rect; -import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.text.TextUtils; import android.util.AttributeSet; @@ -34,7 +31,6 @@ import com.android.launcher3.Utilities; * A {@link BubbleTextView} that has the shortcut icon on the left and drag handle on the right. */ public class DeepShortcutTextView extends BubbleTextView { - public static final String GOOGLE_SANS_FLEX_LABEL_LARGE = "variable-label-large"; private boolean mShowLoadingState; private Drawable mLoadingStatePlaceholder; @@ -51,9 +47,6 @@ public class DeepShortcutTextView extends BubbleTextView { public DeepShortcutTextView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); showLoadingState(true); - if (enableGsf()) { - setTypeface(Typeface.create(GOOGLE_SANS_FLEX_LABEL_LARGE, Typeface.NORMAL)); - } } @Override diff --git a/src/com/android/launcher3/statemanager/BaseState.java b/src/com/android/launcher3/statemanager/BaseState.java index ea5415983c..b81729a712 100644 --- a/src/com/android/launcher3/statemanager/BaseState.java +++ b/src/com/android/launcher3/statemanager/BaseState.java @@ -15,13 +15,15 @@ */ package com.android.launcher3.statemanager; +import android.content.Context; + import com.android.launcher3.DeviceProfile; import com.android.launcher3.views.ActivityContext; /** * Interface representing a state of a StatefulContainer */ -public interface BaseState { +public interface BaseState { // Flag to indicate that Launcher is non-interactive in this state int FLAG_NON_INTERACTIVE = 1 << 0; @@ -35,7 +37,8 @@ public interface BaseState { /** * @return How long the animation to this state should take (or from this state to NORMAL). */ - int getTransitionDuration(ActivityContext context, boolean isToState); + + int getTransitionDuration(DEVICE_PROFILE_CONTEXT context, boolean isToState); /** * Returns the state to go back to from this state @@ -68,20 +71,6 @@ public interface BaseState { return false; } - /** - * For this state, whether we should show desktop exploded view in Overview. - */ - default boolean showExplodedDesktopView() { - return false; - } - - /** - * For this state, whether fullscreen and desktop quickswitch carousel are detached. - */ - default boolean detachDesktopCarousel() { - return true; - } - /** * For this state, whether member variables and other forms of data state should be preserved * or wiped when the state is reapplied. (See {@link StateManager#reapplyState()}) diff --git a/src/com/android/launcher3/statemanager/StateManager.java b/src/com/android/launcher3/statemanager/StateManager.java index a125331865..ac07c0f2cf 100644 --- a/src/com/android/launcher3/statemanager/StateManager.java +++ b/src/com/android/launcher3/statemanager/StateManager.java @@ -18,7 +18,6 @@ package com.android.launcher3.statemanager; import static android.animation.ValueAnimator.areAnimatorsEnabled; -import static com.android.launcher3.Flags.enableStateManagerProtoLog; import static com.android.launcher3.anim.AnimatorPlaybackController.callListenerCommandRecursively; import static com.android.launcher3.states.StateAnimationConfig.HANDLE_STATE_APPLY; import static com.android.launcher3.states.StateAnimationConfig.SKIP_ALL_ANIMATIONS; @@ -27,6 +26,7 @@ import android.animation.Animator; import android.animation.Animator.AnimatorListener; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; +import android.content.Context; import android.os.Handler; import android.os.Looper; import android.util.Log; @@ -39,7 +39,6 @@ import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.states.StateAnimationConfig; import com.android.launcher3.states.StateAnimationConfig.AnimationFlags; import com.android.launcher3.states.StateAnimationConfig.AnimationPropertyFlags; -import com.android.launcher3.util.StateManagerProtoLogProxy; import java.io.PrintWriter; import java.util.ArrayList; @@ -49,49 +48,50 @@ import java.util.stream.Collectors; /** * Class to manage transitions between different states for a StatefulActivity based on different * states - * @param Basestate used by the state manager - * @param container object used to manage state + * @param STATE_TYPE Basestate used by the state manager + * @param STATEFUL_CONTAINER container object used to manage state */ -public class StateManager, T extends StatefulContainer> { +public class StateManager, + STATEFUL_CONTAINER extends Context & StatefulContainer> { public static final String TAG = "StateManager"; // b/279059025, b/325463989 private static final boolean DEBUG = true; - private final AnimationState mConfig = new AnimationState<>(); + private final AnimationState mConfig = new AnimationState(); private final Handler mUiHandler; - private final T mContainer; - private final ArrayList> mListeners = new ArrayList<>(); - private final S mBaseState; + private final STATEFUL_CONTAINER mStatefulContainer; + private final ArrayList> mListeners = new ArrayList<>(); + private final STATE_TYPE mBaseState; // Animators which are run on properties also controlled by state animations. - private final AtomicAnimationFactory mAtomicAnimationFactory; + private final AtomicAnimationFactory mAtomicAnimationFactory; - private StateHandler[] mStateHandlers; - private S mState; + private StateHandler[] mStateHandlers; + private STATE_TYPE mState; - private S mLastStableState; - private S mCurrentStableState; + private STATE_TYPE mLastStableState; + private STATE_TYPE mCurrentStableState; - private S mRestState; + private STATE_TYPE mRestState; - public StateManager(T container, S baseState) { + public StateManager(STATEFUL_CONTAINER container, STATE_TYPE baseState) { mUiHandler = new Handler(Looper.getMainLooper()); - mContainer = container; + mStatefulContainer = container; mBaseState = baseState; mState = mLastStableState = mCurrentStableState = baseState; mAtomicAnimationFactory = container.createAtomicAnimationFactory(); } - public S getState() { + public STATE_TYPE getState() { return mState; } - public S getTargetState() { - return mConfig.targetState; + public STATE_TYPE getTargetState() { + return (STATE_TYPE) mConfig.targetState; } - public S getCurrentStableState() { + public STATE_TYPE getCurrentStableState() { return mCurrentStableState; } @@ -113,20 +113,20 @@ public class StateManager, T extends StatefulContainer writer.println(prefix + "\tisInTransition:" + isInTransition()); } - public StateHandler[] getStateHandlers() { + public StateHandler[] getStateHandlers() { if (mStateHandlers == null) { - ArrayList> handlers = new ArrayList<>(); - mContainer.collectStateHandlers(handlers); + ArrayList> handlers = new ArrayList<>(); + mStatefulContainer.collectStateHandlers(handlers); mStateHandlers = handlers.toArray(new StateHandler[handlers.size()]); } return mStateHandlers; } - public void addStateListener(StateListener listener) { + public void addStateListener(StateListener listener) { mListeners.add(listener); } - public void removeStateListener(StateListener listener) { + public void removeStateListener(StateListener listener) { mListeners.remove(listener); } @@ -134,14 +134,14 @@ public class StateManager, T extends StatefulContainer * Returns true if the state changes should be animated. */ public boolean shouldAnimateStateChange() { - return mContainer.shouldAnimateStateChange(); + return mStatefulContainer.shouldAnimateStateChange(); } /** * @return {@code true} if the state matches the current state and there is no active * transition to different state. */ - public boolean isInStableState(S state) { + public boolean isInStableState(STATE_TYPE state) { return mState == state && mCurrentStableState == state && (mConfig.targetState == null || mConfig.targetState == state); } @@ -154,23 +154,23 @@ public class StateManager, T extends StatefulContainer } /** - * @see #goToState(S, boolean, AnimatorListener) + * @see #goToState(STATE_TYPE, boolean, AnimatorListener) */ - public void goToState(S state) { + public void goToState(STATE_TYPE state) { goToState(state, shouldAnimateStateChange()); } /** - * @see #goToState(S, boolean, AnimatorListener) + * @see #goToState(STATE_TYPE, boolean, AnimatorListener) */ - public void goToState(S state, AnimatorListener listener) { + public void goToState(STATE_TYPE state, AnimatorListener listener) { goToState(state, shouldAnimateStateChange(), listener); } /** - * @see #goToState(S, boolean, AnimatorListener) + * @see #goToState(STATE_TYPE, boolean, AnimatorListener) */ - public void goToState(S state, boolean animated) { + public void goToState(STATE_TYPE state, boolean animated) { goToState(state, animated, 0, null); } @@ -181,21 +181,21 @@ public class StateManager, T extends StatefulContainer * true otherwise * @param listener any action to perform at the end of the transition, or null. */ - public void goToState(S state, boolean animated, AnimatorListener listener) { + public void goToState(STATE_TYPE state, boolean animated, AnimatorListener listener) { goToState(state, animated, 0, listener); } /** * Changes the Launcher state to the provided state after the given delay. */ - public void goToState(S state, long delay, AnimatorListener listener) { + public void goToState(STATE_TYPE state, long delay, AnimatorListener listener) { goToState(state, true, delay, listener); } /** * Changes the Launcher state to the provided state after the given delay. */ - public void goToState(S state, long delay) { + public void goToState(STATE_TYPE state, long delay) { goToState(state, true, delay, null); } @@ -217,7 +217,7 @@ public class StateManager, T extends StatefulContainer cancelAnimation(); } if (mConfig.currentAnimation == null) { - for (StateHandler handler : getStateHandlers()) { + for (StateHandler handler : getStateHandlers()) { handler.setState(mState); } if (wasInAnimation) { @@ -228,35 +228,32 @@ public class StateManager, T extends StatefulContainer /** Handles backProgress in predictive back gesture by passing it to state handlers. */ public void onBackProgressed( - S toState, @FloatRange(from = 0.0, to = 1.0) float backProgress) { - for (StateHandler handler : getStateHandlers()) { + STATE_TYPE toState, @FloatRange(from = 0.0, to = 1.0) float backProgress) { + for (StateHandler handler : getStateHandlers()) { handler.onBackProgressed(toState, backProgress); } } /** Handles back cancelled event in predictive back gesture by passing it to state handlers. */ - public void onBackCancelled(S toState) { - for (StateHandler handler : getStateHandlers()) { + public void onBackCancelled(STATE_TYPE toState) { + for (StateHandler handler : getStateHandlers()) { handler.onBackCancelled(toState); } } private void goToState( - S state, boolean animated, long delay, AnimatorListener listener) { - if (enableStateManagerProtoLog()) { - StateManagerProtoLogProxy.logGoToState( - mState, state, getTrimmedStackTrace("StateManager.goToState")); - } else if (DEBUG) { + STATE_TYPE state, boolean animated, long delay, AnimatorListener listener) { + if (DEBUG) { Log.d(TAG, "goToState - fromState: " + mState + ", toState: " + state + ", partial trace:\n" + getTrimmedStackTrace("StateManager.goToState")); } animated &= areAnimatorsEnabled(); - if (getState() == state) { + if (mStatefulContainer.isInState(state)) { if (mConfig.currentAnimation == null) { // Run any queued runnable if (listener != null) { - listener.onAnimationEnd(new AnimatorSet()); + listener.onAnimationEnd(null); } return; } else if ((!mConfig.isUserControlled() && animated && mConfig.targetState == state) @@ -271,13 +268,13 @@ public class StateManager, T extends StatefulContainer } // Cancel the current animation. This will reset mState to mCurrentStableState, so store it. - S fromState = mState; + STATE_TYPE fromState = mState; cancelAnimation(); if (!animated) { mAtomicAnimationFactory.cancelAllStateElementAnimation(); onStateTransitionStart(state); - for (StateHandler handler : getStateHandlers()) { + for (StateHandler handler : getStateHandlers()) { handler.setState(state); } @@ -285,7 +282,7 @@ public class StateManager, T extends StatefulContainer // Run any queued runnable if (listener != null) { - listener.onAnimationEnd(new AnimatorSet()); + listener.onAnimationEnd(null); } return; } @@ -304,13 +301,13 @@ public class StateManager, T extends StatefulContainer } } - private void goToStateAnimated(S state, S fromState, + private void goToStateAnimated(STATE_TYPE state, STATE_TYPE fromState, AnimatorListener listener) { // Since state mBaseState can be reached from multiple states, just assume that the // transition plays in reverse and use the same duration as previous state. mConfig.duration = state == mBaseState - ? fromState.getTransitionDuration(mContainer, false /* isToState */) - : state.getTransitionDuration(mContainer, true /* isToState */); + ? fromState.getTransitionDuration(mStatefulContainer, false /* isToState */) + : state.getTransitionDuration(mStatefulContainer, true /* isToState */); prepareForAtomicAnimation(fromState, state, mConfig); AnimatorSet animation = createAnimationToNewWorkspaceInternal(state).buildAnim(); if (listener != null) { @@ -324,7 +321,7 @@ public class StateManager, T extends StatefulContainer * - Setting interpolators for various animations included in the state transition. * - Setting some start values (e.g. scale) for views that are hidden but about to be shown. */ - public void prepareForAtomicAnimation(S fromState, S toState, + public void prepareForAtomicAnimation(STATE_TYPE fromState, STATE_TYPE toState, StateAnimationConfig config) { mAtomicAnimationFactory.prepareForAtomicAnimation(fromState, toState, config); } @@ -333,11 +330,8 @@ public class StateManager, T extends StatefulContainer * Creates an animation representing atomic transitions between the provided states */ public AnimatorSet createAtomicAnimation( - S fromState, S toState, StateAnimationConfig config) { - if (enableStateManagerProtoLog()) { - StateManagerProtoLogProxy.logCreateAtomicAnimation( - mState, toState, getTrimmedStackTrace("StateManager.createAtomicAnimation")); - } else if (DEBUG) { + STATE_TYPE fromState, STATE_TYPE toState, StateAnimationConfig config) { + if (DEBUG) { Log.d(TAG, "createAtomicAnimation - fromState: " + fromState + ", toState: " + toState + ", partial trace:\n" + getTrimmedStackTrace( "StateManager.createAtomicAnimation")); @@ -346,7 +340,7 @@ public class StateManager, T extends StatefulContainer PendingAnimation builder = new PendingAnimation(config.duration); prepareForAtomicAnimation(fromState, toState, config); - for (StateHandler handler : getStateHandlers()) { + for (StateHandler handler : mStatefulContainer.getStateManager().getStateHandlers()) { handler.setStateWithAnimation(toState, config, builder); } return builder.buildAnim(); @@ -360,19 +354,19 @@ public class StateManager, T extends StatefulContainer * accuracy. */ public AnimatorPlaybackController createAnimationToNewWorkspace( - S state, long duration) { + STATE_TYPE state, long duration) { return createAnimationToNewWorkspace(state, duration, 0 /* animFlags */); } public AnimatorPlaybackController createAnimationToNewWorkspace( - S state, long duration, @AnimationFlags int animFlags) { + STATE_TYPE state, long duration, @AnimationFlags int animFlags) { StateAnimationConfig config = new StateAnimationConfig(); config.duration = duration; config.animFlags = animFlags; return createAnimationToNewWorkspace(state, config); } - public AnimatorPlaybackController createAnimationToNewWorkspace(S state, + public AnimatorPlaybackController createAnimationToNewWorkspace(STATE_TYPE state, StateAnimationConfig config) { config.animProps |= StateAnimationConfig.USER_CONTROLLED; cancelAnimation(); @@ -382,10 +376,10 @@ public class StateManager, T extends StatefulContainer return mConfig.playbackController; } - private PendingAnimation createAnimationToNewWorkspaceInternal(final S state) { + private PendingAnimation createAnimationToNewWorkspaceInternal(final STATE_TYPE state) { PendingAnimation builder = new PendingAnimation(mConfig.duration); if (!mConfig.hasAnimationFlag(SKIP_ALL_ANIMATIONS)) { - for (StateHandler handler : getStateHandlers()) { + for (StateHandler handler : getStateHandlers()) { handler.setStateWithAnimation(state, mConfig, builder); } } @@ -394,7 +388,7 @@ public class StateManager, T extends StatefulContainer return builder; } - private AnimatorListener createStateAnimationListener(S state) { + private AnimatorListener createStateAnimationListener(STATE_TYPE state) { return new AnimationSuccessListener() { @Override @@ -410,13 +404,11 @@ public class StateManager, T extends StatefulContainer }; } - private void onStateTransitionStart(S state) { + private void onStateTransitionStart(STATE_TYPE state) { mState = state; - mContainer.onStateSetStart(mState); + mStatefulContainer.onStateSetStart(mState); - if (enableStateManagerProtoLog()) { - StateManagerProtoLogProxy.logOnStateTransitionStart(state); - } else if (DEBUG) { + if (DEBUG) { Log.d(TAG, "onStateTransitionStart - state: " + state); } for (int i = mListeners.size() - 1; i >= 0; i--) { @@ -424,21 +416,19 @@ public class StateManager, T extends StatefulContainer } } - private void onStateTransitionEnd(S state) { + private void onStateTransitionEnd(STATE_TYPE state) { // Only change the stable states after the transitions have finished if (state != mCurrentStableState) { mLastStableState = state.getHistoryForState(mCurrentStableState); mCurrentStableState = state; } - mContainer.onStateSetEnd(state); + mStatefulContainer.onStateSetEnd(state); if (state == mBaseState) { setRestState(null); } - if (enableStateManagerProtoLog()) { - StateManagerProtoLogProxy.logOnStateTransitionEnd(state); - } else if (DEBUG) { + if (DEBUG) { Log.d(TAG, "onStateTransitionEnd - state: " + state); } for (int i = mListeners.size() - 1; i >= 0; i--) { @@ -446,7 +436,7 @@ public class StateManager, T extends StatefulContainer } } - public S getLastState() { + public STATE_TYPE getLastState() { return mLastStableState; } @@ -466,11 +456,11 @@ public class StateManager, T extends StatefulContainer } } - public S getRestState() { + public STATE_TYPE getRestState() { return mRestState == null ? mBaseState : mRestState; } - public void setRestState(S restState) { + public void setRestState(STATE_TYPE restState) { mRestState = restState; } @@ -478,11 +468,7 @@ public class StateManager, T extends StatefulContainer * Cancels the current animation. */ public void cancelAnimation() { - if (enableStateManagerProtoLog()) { - StateManagerProtoLogProxy.logCancelAnimation( - mConfig.currentAnimation != null, - getTrimmedStackTrace("StateManager.cancelAnimation")); - } else if (DEBUG && mConfig.currentAnimation != null) { + if (DEBUG && mConfig.currentAnimation != null) { Log.d(TAG, "cancelAnimation - with ongoing animation" + ", partial trace:\n" + getTrimmedStackTrace("StateManager.cancelAnimation")); } @@ -516,7 +502,7 @@ public class StateManager, T extends StatefulContainer * @param anim The custom animation to the given state. * @param toState The state we are animating towards. */ - public void setCurrentAnimation(AnimatorSet anim, S toState) { + public void setCurrentAnimation(AnimatorSet anim, STATE_TYPE toState) { cancelAnimation(); setCurrentAnimation(anim); anim.addListener(createStateAnimationListener(toState)); @@ -689,10 +675,10 @@ public class StateManager, T extends StatefulContainer /** Handles backProgress in predictive back gesture for target state. */ default void onBackProgressed( - STATE_TYPE toState, @FloatRange(from = 0.0, to = 1.0) float backProgress) {} + STATE_TYPE toState, @FloatRange(from = 0.0, to = 1.0) float backProgress) {}; /** Handles back cancelled event in predictive back gesture for target state. */ - default void onBackCancelled(STATE_TYPE toState) {} + default void onBackCancelled(STATE_TYPE toState) {}; } public interface StateListener { diff --git a/src/com/android/launcher3/statemanager/StatefulActivity.java b/src/com/android/launcher3/statemanager/StatefulActivity.java index 445701d19d..28f2def8ab 100644 --- a/src/com/android/launcher3/statemanager/StatefulActivity.java +++ b/src/com/android/launcher3/statemanager/StatefulActivity.java @@ -23,13 +23,12 @@ import static com.android.launcher3.LauncherState.FLAG_NON_INTERACTIVE; import android.content.res.Configuration; import android.os.Bundle; import android.os.Handler; -import android.os.Trace; import android.view.LayoutInflater; import android.view.View; import androidx.annotation.CallSuper; -import com.android.launcher3.BaseActivity; +import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.LauncherRootView; import com.android.launcher3.Utilities; import com.android.launcher3.statemanager.StateManager.StateHandler; @@ -43,7 +42,7 @@ import java.util.List; * @param Type of state object */ public abstract class StatefulActivity> - extends BaseActivity implements StatefulContainer { + extends BaseDraggingActivity implements StatefulContainer { public final Handler mHandler = new Handler(); private final Runnable mHandleDeferredResume = this::handleDeferredResume; @@ -176,10 +175,8 @@ public abstract class StatefulActivity> @Override public void onConfigurationChanged(Configuration newConfig) { - Trace.beginSection("statefulActivity#onConfigurationChanged"); handleConfigurationChanged(newConfig); super.onConfigurationChanged(newConfig); - Trace.endSection(); } /** @@ -204,4 +201,9 @@ public abstract class StatefulActivity> */ protected abstract void onHandleConfigurationChanged(); + /** + * Enter staged split directly from the current running app. + * @param leftOrTop if the staged split will be positioned left or top. + */ + public void enterStageSplitFromRunningApp(boolean leftOrTop) { } } diff --git a/src/com/android/launcher3/statemanager/StatefulContainer.java b/src/com/android/launcher3/statemanager/StatefulContainer.java index 83a2fdcac5..0cf0a27685 100644 --- a/src/com/android/launcher3/statemanager/StatefulContainer.java +++ b/src/com/android/launcher3/statemanager/StatefulContainer.java @@ -20,8 +20,6 @@ package com.android.launcher3.statemanager; import static com.android.launcher3.LauncherState.FLAG_CLOSE_POPUPS; import static com.android.launcher3.statemanager.BaseState.FLAG_NON_INTERACTIVE; -import android.content.res.Configuration; - import androidx.annotation.CallSuper; import com.android.launcher3.AbstractFloatingView; @@ -56,15 +54,12 @@ public interface StatefulContainer> ext /** * Called when transition to state ends - * * @param state current state of State_Type */ - default void onStateSetEnd(STATE_TYPE state) { - } + default void onStateSetEnd(STATE_TYPE state) { } /** * Called when transition to state starts - * * @param state current state of State_Type */ @CallSuper @@ -76,7 +71,6 @@ public interface StatefulContainer> ext /** * Returns true if the activity is in the provided state - * * @param state current state of State_Type */ default boolean isInState(STATE_TYPE state) { @@ -87,8 +81,4 @@ public interface StatefulContainer> ext * Returns true if state change should transition with animation */ boolean shouldAnimateStateChange(); - - default void handleConfigurationChanged(Configuration configuration){ - //no op - } } diff --git a/src/com/android/launcher3/states/EditModeState.kt b/src/com/android/launcher3/states/EditModeState.kt index 268a37301d..6ff47aef43 100644 --- a/src/com/android/launcher3/states/EditModeState.kt +++ b/src/com/android/launcher3/states/EditModeState.kt @@ -36,7 +36,11 @@ class EditModeState(id: Int) : LauncherState(id, StatsLogManager.LAUNCHER_STATE_ FLAG_WORKSPACE_HAS_BACKGROUNDS) } - override fun getTransitionDuration(context: ActivityContext, isToState: Boolean) = 150 + override fun getTransitionDuration(context: T, isToState: Boolean): Int where + T : Context?, + T : ActivityContext? { + return 150 + } override fun getDepthUnchecked(context: T): Float where T : Context?, T : ActivityContext? { if (enableScalingRevealHomeAnimation()) { diff --git a/src/com/android/launcher3/states/HintState.java b/src/com/android/launcher3/states/HintState.java index f4e6e94e5b..61a4a395e3 100644 --- a/src/com/android/launcher3/states/HintState.java +++ b/src/com/android/launcher3/states/HintState.java @@ -24,9 +24,6 @@ import androidx.core.graphics.ColorUtils; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; -import com.android.launcher3.R; -import com.android.launcher3.util.Themes; -import com.android.launcher3.views.ActivityContext; import app.lawnchair.theme.color.tokens.ColorTokens; @@ -49,7 +46,7 @@ public class HintState extends LauncherState { } @Override - public int getTransitionDuration(ActivityContext context, boolean isToState) { + public int getTransitionDuration(Context context, boolean isToState) { return 80; } diff --git a/src/com/android/launcher3/states/RotationHelper.java b/src/com/android/launcher3/states/RotationHelper.java index 9376518ddb..fdb37f0dcd 100644 --- a/src/com/android/launcher3/states/RotationHelper.java +++ b/src/com/android/launcher3/states/RotationHelper.java @@ -18,7 +18,6 @@ package com.android.launcher3.states; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE; import static android.util.DisplayMetrics.DENSITY_DEVICE_STABLE; import static com.android.launcher3.LauncherPrefs.ALLOW_ROTATION; @@ -27,24 +26,23 @@ import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import static com.android.launcher3.util.window.WindowManagerProxy.MIN_TABLET_WIDTH; import android.content.Context; +import android.content.SharedPreferences; +import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.os.Handler; import android.os.Message; -import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.WorkerThread; import com.android.launcher3.BaseActivity; import com.android.launcher3.DeviceProfile; -import com.android.launcher3.LauncherPrefChangeListener; import com.android.launcher3.LauncherPrefs; -import com.android.launcher3.util.ContextTracker; import com.android.launcher3.util.DisplayController; /** * Utility class to manage launcher rotation */ -public class RotationHelper implements LauncherPrefChangeListener, +public class RotationHelper implements OnSharedPreferenceChangeListener, DeviceProfile.OnDeviceProfileChangeListener, DisplayController.DisplayInfoChangeListener { @@ -65,8 +63,6 @@ public class RotationHelper implements LauncherPrefChangeListener, public static final int REQUEST_ROTATE = 1; public static final int REQUEST_LOCK = 2; - private boolean mIsFixedLandscape = false; - @NonNull private final BaseActivity mActivity; private final Handler mRequestOrientationHandler; @@ -77,7 +73,7 @@ public class RotationHelper implements LauncherPrefChangeListener, /** * Rotation request made by - * {@link ContextTracker.SchedulerCallback}. + * {@link com.android.launcher3.util.ActivityTracker.SchedulerCallback}. * This supersedes any other request. */ private int mStateHandlerRequest = REQUEST_NONE; @@ -116,7 +112,7 @@ public class RotationHelper implements LauncherPrefChangeListener, } @Override - public void onPrefChanged(String s) { + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) { if (mDestroyed || mIgnoreAutoRotateSettings) return; boolean wasRotationEnabled = mHomeRotationEnabled; mHomeRotationEnabled = LauncherPrefs.get(mActivity).get(ALLOW_ROTATION); @@ -167,18 +163,6 @@ public class RotationHelper implements LauncherPrefChangeListener, notifyChange(); } - public boolean isFixedLandscape() { - return mIsFixedLandscape; - } - - /** - * If fixedLandscape is true then the Launcher become landscape until set false.. - */ - public void setFixedLandscape(boolean fixedLandscape) { - mIsFixedLandscape = fixedLandscape; - notifyChange(); - } - // Used by tests only. public void forceAllowRotationForTesting(boolean allowRotation) { if (mDestroyed) return; @@ -211,9 +195,7 @@ public class RotationHelper implements LauncherPrefChangeListener, } final int activityFlags; - if (mIsFixedLandscape) { - activityFlags = SCREEN_ORIENTATION_USER_LANDSCAPE; - } else if (mStateHandlerRequest != REQUEST_NONE) { + if (mStateHandlerRequest != REQUEST_NONE) { activityFlags = mStateHandlerRequest == REQUEST_LOCK ? SCREEN_ORIENTATION_LOCKED : SCREEN_ORIENTATION_UNSPECIFIED; } else if (mCurrentTransitionRequest != REQUEST_NONE) { @@ -231,7 +213,6 @@ public class RotationHelper implements LauncherPrefChangeListener, } if (activityFlags != mLastActivityFlags) { mLastActivityFlags = activityFlags; - Log.d("b/380940677", toString()); mRequestOrientationHandler.sendEmptyMessage(activityFlags); } } @@ -259,9 +240,9 @@ public class RotationHelper implements LauncherPrefChangeListener, return String.format("[mStateHandlerRequest=%d, mCurrentStateRequest=%d, " + "mLastActivityFlags=%d, mIgnoreAutoRotateSettings=%b, " + "mHomeRotationEnabled=%b, mForceAllowRotationForTesting=%b," - + " mDestroyed=%b, mIsFixedLandscape=%b]", + + " mDestroyed=%b]", mStateHandlerRequest, mCurrentStateRequest, mLastActivityFlags, mIgnoreAutoRotateSettings, mHomeRotationEnabled, mForceAllowRotationForTesting, - mDestroyed, mIsFixedLandscape); + mDestroyed); } } diff --git a/src/com/android/launcher3/states/SpringLoadedState.java b/src/com/android/launcher3/states/SpringLoadedState.java index 15e6c6190c..2e57ed86ab 100644 --- a/src/com/android/launcher3/states/SpringLoadedState.java +++ b/src/com/android/launcher3/states/SpringLoadedState.java @@ -24,7 +24,6 @@ import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; import com.android.launcher3.Workspace; -import com.android.launcher3.views.ActivityContext; /** * Definition for spring loaded state used during drag and drop. @@ -42,7 +41,7 @@ public class SpringLoadedState extends LauncherState { } @Override - public int getTransitionDuration(ActivityContext context, boolean isToState) { + public int getTransitionDuration(Context context, boolean isToState) { return 150; } diff --git a/src/com/android/launcher3/states/StateAnimationConfig.java b/src/com/android/launcher3/states/StateAnimationConfig.java index 0d1067dfc6..ce85945b4c 100644 --- a/src/com/android/launcher3/states/StateAnimationConfig.java +++ b/src/com/android/launcher3/states/StateAnimationConfig.java @@ -78,6 +78,7 @@ public class StateAnimationConfig { ANIM_WORKSPACE_PAGE_TRANSLATE_X, ANIM_OVERVIEW_SPLIT_SELECT_FLOATING_TASK_TRANSLATE_OFFSCREEN, ANIM_OVERVIEW_SPLIT_SELECT_INSTRUCTIONS_FADE, + ANIM_ALL_APPS_BOTTOM_SHEET_FADE, ANIM_ALL_APPS_KEYBOARD_FADE }) @Retention(RetentionPolicy.SOURCE) @@ -103,7 +104,8 @@ public class StateAnimationConfig { public static final int ANIM_WORKSPACE_PAGE_TRANSLATE_X = 15; public static final int ANIM_OVERVIEW_SPLIT_SELECT_FLOATING_TASK_TRANSLATE_OFFSCREEN = 17; public static final int ANIM_OVERVIEW_SPLIT_SELECT_INSTRUCTIONS_FADE = 18; - public static final int ANIM_ALL_APPS_KEYBOARD_FADE = 19; + public static final int ANIM_ALL_APPS_BOTTOM_SHEET_FADE = 19; + public static final int ANIM_ALL_APPS_KEYBOARD_FADE = 20; private static final int ANIM_TYPES_COUNT = 21; diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java index e5105cdc63..db2a6e0a8b 100644 --- a/src/com/android/launcher3/testing/TestInformationHandler.java +++ b/src/com/android/launcher3/testing/TestInformationHandler.java @@ -15,12 +15,12 @@ */ package com.android.launcher3.testing; -import static com.android.launcher3.Flags.enableFallbackOverviewInWindow; import static com.android.launcher3.Flags.enableGridOnlyOverview; -import static com.android.launcher3.Flags.enableLauncherOverviewInWindow; import static com.android.launcher3.allapps.AllAppsStore.DEFER_UPDATES_TEST; import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION; import static com.android.launcher3.config.FeatureFlags.FOLDABLE_SINGLE_PAGE; +import static com.android.launcher3.config.FeatureFlags.enableAppPairs; +import static com.android.launcher3.config.FeatureFlags.enableSplitContextually; import static com.android.launcher3.testing.shared.TestProtocol.TEST_INFO_RESPONSE_FIELD; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; @@ -55,7 +55,9 @@ import com.android.launcher3.ShortcutAndWidgetContainer; import com.android.launcher3.Workspace; import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.icons.ClockDrawableWrapper; +import com.android.launcher3.testing.shared.HotseatCellCenterRequest; import com.android.launcher3.testing.shared.TestProtocol; +import com.android.launcher3.testing.shared.WorkspaceCellCenterRequest; import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter; import com.android.launcher3.util.DisplayController; import com.android.launcher3.util.ResourceBasedOverride; @@ -166,14 +168,21 @@ public class TestInformationHandler implements ResourceBasedOverride { } case TestProtocol.REQUEST_TARGET_INSETS: { - return getUIProperty(Bundle::putParcelable, insets -> Insets.max( - insets.getSystemGestureInsets(), - insets.getSystemWindowInsets()), this::getWindowInsets); + return getUIProperty(Bundle::putParcelable, activity -> { + WindowInsets insets = activity.getWindow() + .getDecorView().getRootWindowInsets(); + return Insets.max( + insets.getSystemGestureInsets(), + insets.getSystemWindowInsets()); + }, this::getCurrentActivity); } case TestProtocol.REQUEST_WINDOW_INSETS: { - return getUIProperty(Bundle::putParcelable, - WindowInsets::getSystemWindowInsets, this::getWindowInsets); + return getUIProperty(Bundle::putParcelable, activity -> { + WindowInsets insets = activity.getWindow() + .getDecorView().getRootWindowInsets(); + return insets.getSystemWindowInsets(); + }, this::getCurrentActivity); } case TestProtocol.REQUEST_CELL_LAYOUT_BOARDER_HEIGHT: { @@ -183,13 +192,13 @@ public class TestInformationHandler implements ResourceBasedOverride { } case TestProtocol.REQUEST_SYSTEM_GESTURE_REGION: { - return getUIProperty(Bundle::putParcelable, windowInsets -> { - WindowInsetsCompat insets = - WindowInsetsCompat.toWindowInsetsCompat(windowInsets); + return getUIProperty(Bundle::putParcelable, activity -> { + WindowInsetsCompat insets = WindowInsetsCompat.toWindowInsetsCompat( + activity.getWindow().getDecorView().getRootWindowInsets()); return insets.getInsets(WindowInsetsCompat.Type.ime() | WindowInsetsCompat.Type.systemGestures()) .toPlatformInsets(); - }, this::getWindowInsets); + }, this::getCurrentActivity); } case TestProtocol.REQUEST_ICON_HEIGHT: { @@ -214,10 +223,6 @@ public class TestInformationHandler implements ResourceBasedOverride { ENABLE_TASKBAR_NAVBAR_UNIFICATION); return response; - case TestProtocol.REQUEST_TASKBAR_SHOWN_ON_HOME: - response.putBoolean(TEST_INFO_RESPONSE_FIELD, - DisplayController.showLockedTaskbarOnHome(mContext)); - return response; case TestProtocol.REQUEST_NUM_ALL_APPS_COLUMNS: response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, mDeviceProfile.numShownAllAppsColumns); @@ -247,13 +252,13 @@ public class TestInformationHandler implements ResourceBasedOverride { } case TestProtocol.REQUEST_GET_SPLIT_SELECTION_ACTIVE: - response.putBoolean(TEST_INFO_RESPONSE_FIELD, - Launcher.ACTIVITY_TRACKER.getCreatedContext().isSplitSelectionActive()); + response.putBoolean(TEST_INFO_RESPONSE_FIELD, enableSplitContextually() + && Launcher.ACTIVITY_TRACKER.getCreatedActivity().isSplitSelectionActive()); return response; case TestProtocol.REQUEST_ENABLE_ROTATION: MAIN_EXECUTOR.submit(() -> - Launcher.ACTIVITY_TRACKER.getCreatedContext().getRotationHelper() + Launcher.ACTIVITY_TRACKER.getCreatedActivity().getRotationHelper() .forceAllowRotationForTesting(Boolean.parseBoolean(arg))); return response; @@ -267,15 +272,15 @@ public class TestInformationHandler implements ResourceBasedOverride { }); case TestProtocol.REQUEST_WORKSPACE_CELL_CENTER: { - Rect cellPos = extra.getParcelable(TestProtocol.TEST_INFO_PARAM_CELL_SPAN); + final WorkspaceCellCenterRequest request = extra.getParcelable( + TestProtocol.TEST_INFO_REQUEST_FIELD); return getLauncherUIProperty(Bundle::putParcelable, launcher -> { final Workspace workspace = launcher.getWorkspace(); // TODO(b/216387249): allow caller selecting different pages. CellLayout cellLayout = (CellLayout) workspace.getPageAt( workspace.getCurrentPage()); final Rect cellRect = getDescendantRectRelativeToDragLayerForCell(launcher, - cellLayout, cellPos.left, cellPos.top, cellPos.width(), - cellPos.height()); + cellLayout, request.cellX, request.cellY, request.spanX, request.spanY); return new Point(cellRect.centerX(), cellRect.centerY()); }); } @@ -294,11 +299,12 @@ public class TestInformationHandler implements ResourceBasedOverride { } case TestProtocol.REQUEST_HOTSEAT_CELL_CENTER: { - int cellIndex = extra.getInt(TestProtocol.TEST_INFO_PARAM_INDEX); + final HotseatCellCenterRequest request = extra.getParcelable( + TestProtocol.TEST_INFO_REQUEST_FIELD); return getLauncherUIProperty(Bundle::putParcelable, launcher -> { final Hotseat hotseat = launcher.getHotseat(); final Rect cellRect = getDescendantRectRelativeToDragLayerForCell(launcher, - hotseat, cellIndex, /* cellY= */ 0, + hotseat, request.cellInd, /* cellY= */ 0, /* spanX= */ 1, /* spanY= */ 1); // TODO(b/234322284): return the real center point. return new Point(cellRect.left + (cellRect.right - cellRect.left) / 3, @@ -329,9 +335,8 @@ public class TestInformationHandler implements ResourceBasedOverride { return response; } - case TestProtocol.REQUEST_IS_RECENTS_WINDOW_ENABLED: { - response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD, - enableLauncherOverviewInWindow() || enableFallbackOverviewInWindow()); + case TestProtocol.REQUEST_FLAG_ENABLE_APP_PAIRS: { + response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD, enableAppPairs()); return response; } @@ -477,13 +482,12 @@ public class TestInformationHandler implements ResourceBasedOverride { } protected boolean isLauncherInitialized() { - return Launcher.ACTIVITY_TRACKER.getCreatedContext() == null + return Launcher.ACTIVITY_TRACKER.getCreatedActivity() == null || LauncherAppState.getInstance(mContext).getModel().isModelLoaded(); } - protected WindowInsets getWindowInsets(){ - return Launcher.ACTIVITY_TRACKER.getCreatedContext().getWindow().getDecorView() - .getRootWindowInsets(); + protected Activity getCurrentActivity() { + return Launcher.ACTIVITY_TRACKER.getCreatedActivity(); } /** @@ -491,13 +495,13 @@ public class TestInformationHandler implements ResourceBasedOverride { */ public static Bundle getLauncherUIProperty( BundleSetter bundleSetter, Function provider) { - return getUIProperty(bundleSetter, provider, Launcher.ACTIVITY_TRACKER::getCreatedContext); + return getUIProperty(bundleSetter, provider, Launcher.ACTIVITY_TRACKER::getCreatedActivity); } /** * Returns the result by getting a generic property on UI thread */ - protected static Bundle getUIProperty( + private static Bundle getUIProperty( BundleSetter bundleSetter, Function provider, Supplier targetSupplier) { return getFromExecutorSync(MAIN_EXECUTOR, () -> { S target = targetSupplier.get(); diff --git a/src/com/android/launcher3/testing/TestInformationProvider.java b/src/com/android/launcher3/testing/TestInformationProvider.java index 4b592e7627..17b472a473 100644 --- a/src/com/android/launcher3/testing/TestInformationProvider.java +++ b/src/com/android/launcher3/testing/TestInformationProvider.java @@ -16,40 +16,61 @@ package com.android.launcher3.testing; -import android.content.Context; +import android.content.ContentProvider; +import android.content.ContentValues; +import android.database.Cursor; +import android.net.Uri; import android.os.Bundle; import android.util.Log; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - import com.android.launcher3.Utilities; -import com.android.launcher3.util.ContentProviderProxy; -public class TestInformationProvider extends ContentProviderProxy { +public class TestInformationProvider extends ContentProvider { private static final String TAG = "TestInformationProvider"; - @Nullable @Override - public ProxyProvider getProxy(@NonNull Context context) { - if (Utilities.isRunningInTestHarness()) { - return new ProxyProvider() { - @Nullable - @Override - public Bundle call(@NonNull String method, @Nullable String arg, - @Nullable Bundle extras) { - TestInformationHandler handler = TestInformationHandler.newInstance(context); - handler.init(context); + public boolean onCreate() { + return true; + } - Bundle response = handler.call(method, arg, extras); - if (response == null) { - Log.e(TAG, "Couldn't handle method: " + method + "; current handler=" - + handler.getClass().getSimpleName()); - } - return response; - } - }; + @Override + public int update(Uri uri, ContentValues contentValues, String s, String[] strings) { + return 0; + } + + @Override + public int delete(Uri uri, String s, String[] strings) { + return 0; + } + + @Override + public Uri insert(Uri uri, ContentValues contentValues) { + return null; + } + + @Override + public String getType(Uri uri) { + return null; + } + + @Override + public Cursor query(Uri uri, String[] strings, String s, String[] strings1, String s1) { + return null; + } + + @Override + public Bundle call(String method, String arg, Bundle extras) { + if (Utilities.isRunningInTestHarness()) { + TestInformationHandler handler = TestInformationHandler.newInstance(getContext()); + handler.init(getContext()); + + Bundle response = handler.call(method, arg, extras); + if (response == null) { + Log.e(TAG, "Couldn't handle method: " + method + "; current handler=" + + handler.getClass().getSimpleName()); + } + return response; } return null; } diff --git a/src/com/android/launcher3/testing/shared/TestProtocol.java b/src/com/android/launcher3/testing/shared/TestProtocol.java index 142171e0eb..59d0de69ee 100644 --- a/src/com/android/launcher3/testing/shared/TestProtocol.java +++ b/src/com/android/launcher3/testing/shared/TestProtocol.java @@ -31,7 +31,7 @@ public final class TestProtocol { public static final String SEARCH_RESULT_COMPLETE = "SEARCH_RESULT_COMPLETE"; public static final String LAUNCHER_ACTIVITY_STOPPED_MESSAGE = "TAPL_LAUNCHER_ACTIVITY_STOPPED"; public static final String WALLPAPER_OPEN_ANIMATION_FINISHED_MESSAGE = - "TAPL_WALLPAPER_OPEN_ANIMATION_FINISHED"; + "TAPL_WALLPAPER_OPEN_ANIMATION_FINISHED"; public static final int NORMAL_STATE_ORDINAL = 0; public static final int SPRING_LOADED_STATE_ORDINAL = 1; public static final int OVERVIEW_STATE_ORDINAL = 2; @@ -76,21 +76,20 @@ public final class TestProtocol { } } + public static final String TEST_INFO_REQUEST_FIELD = "request"; public static final String TEST_INFO_RESPONSE_FIELD = "response"; - public static final String TEST_INFO_PARAM_INDEX = "index"; - public static final String TEST_INFO_PARAM_CELL_SPAN = "cell-span"; public static final String REQUEST_HOME_TO_OVERVIEW_SWIPE_HEIGHT = - "home-to-overview-swipe-height"; + "home-to-overview-swipe-height"; public static final String REQUEST_BACKGROUND_TO_OVERVIEW_SWIPE_HEIGHT = - "background-to-overview-swipe-height"; + "background-to-overview-swipe-height"; public static final String REQUEST_HOME_TO_ALL_APPS_SWIPE_HEIGHT = - "home-to-all-apps-swipe-height"; + "home-to-all-apps-swipe-height"; public static final String REQUEST_ICON_HEIGHT = - "icon-height"; + "icon-height"; public static final String REQUEST_IS_LAUNCHER_INITIALIZED = "is-launcher-initialized"; public static final String REQUEST_IS_LAUNCHER_LAUNCHER_ACTIVITY_STARTED = - "is-launcher-activity-started"; + "is-launcher-activity-started"; public static final String REQUEST_FREEZE_APP_LIST = "freeze-app-list"; public static final String REQUEST_UNFREEZE_APP_LIST = "unfreeze-app-list"; public static final String REQUEST_ENABLE_BLOCK_TIMEOUT = "enable-block-timeout"; @@ -122,22 +121,20 @@ public final class TestProtocol { public static final String REQUEST_HOTSEAT_ICON_NAMES = "get-hotseat-icon-names"; public static final String REQUEST_IS_TABLET = "is-tablet"; public static final String REQUEST_IS_PREDICTIVE_BACK_SWIPE_ENABLED = - "is-predictive-back-swipe-enabled"; + "is-predictive-back-swipe-enabled"; public static final String REQUEST_ENABLE_TASKBAR_NAVBAR_UNIFICATION = - "enable-taskbar-navbar-unification"; - public static final String REQUEST_TASKBAR_SHOWN_ON_HOME = - "taskbar-shown-on-home"; + "enable-taskbar-navbar-unification"; public static final String REQUEST_NUM_ALL_APPS_COLUMNS = "num-all-apps-columns"; public static final String REQUEST_IS_TWO_PANELS = "is-two-panel"; public static final String REQUEST_CELL_LAYOUT_BOARDER_HEIGHT = "cell-layout-boarder-height"; public static final String REQUEST_START_DRAG_THRESHOLD = "start-drag-threshold"; public static final String REQUEST_SHELL_DRAG_READY = "shell-drag-ready"; public static final String REQUEST_GET_ACTIVITIES_CREATED_COUNT = - "get-activities-created-count"; + "get-activities-created-count"; public static final String REQUEST_GET_ACTIVITIES = "get-activities"; public static final String REQUEST_HAS_TIS = "has-touch-interaction-service"; public static final String REQUEST_TASKBAR_ALL_APPS_TOP_PADDING = - "taskbar-all-apps-top-padding"; + "taskbar-all-apps-top-padding"; public static final String REQUEST_ALL_APPS_TOP_PADDING = "all-apps-top-padding"; public static final String REQUEST_ALL_APPS_BOTTOM_PADDING = "all-apps-bottom-padding"; public static final String REQUEST_REFRESH_OVERVIEW_TARGET = "refresh-overview-target"; @@ -147,17 +144,20 @@ public final class TestProtocol { public static final String REQUEST_WORKSPACE_COLUMNS_ROWS = "workspace-columns-rows"; public static final String REQUEST_WORKSPACE_CURRENT_PAGE_INDEX = - "workspace-current-page-index"; + "workspace-current-page-index"; public static final String REQUEST_HOTSEAT_CELL_CENTER = "hotseat-cell-center"; - public static final String REQUEST_GET_OVERVIEW_TASK_SIZE = "get-overivew-task-size"; - public static final String REQUEST_GET_OVERVIEW_GRID_TASK_SIZE = "get-overivew-grid-task-size"; + public static final String REQUEST_GET_FOCUSED_TASK_HEIGHT_FOR_TABLET = + "get-focused-task-height-for-tablet"; + public static final String REQUEST_GET_GRID_TASK_SIZE_RECT_FOR_TABLET = + "get-grid-task-size-rect-for-tablet"; public static final String REQUEST_GET_OVERVIEW_PAGE_SPACING = "get-overview-page-spacing"; public static final String REQUEST_GET_OVERVIEW_CURRENT_PAGE_INDEX = - "get-overview-current-page-index"; + "get-overview-current-page-index"; public static final String REQUEST_GET_SPLIT_SELECTION_ACTIVE = "get-split-selection-active"; public static final String REQUEST_ENABLE_ROTATION = "enable_rotation"; + public static final String REQUEST_ENABLE_SUGGESTION = "enable-suggestion"; public static final String REQUEST_MODEL_QUEUE_CLEARED = "model-queue-cleared"; public static boolean sDebugTracing = false; @@ -170,11 +170,16 @@ public final class TestProtocol { public static final String PERMANENT_DIAG_TAG = "TaplTarget"; public static final String ICON_MISSING = "b/282963545"; + public static final String UIOBJECT_STALE_ELEMENT = "b/319501259"; + public static final String TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE = "b/326908466"; + public static final String WIDGET_CONFIG_NULL_EXTRA_INTENT = "b/324419890"; + public static final String OVERVIEW_SELECT_TOOLTIP_MISALIGNED = "b/332485341"; + public static final String REQUEST_FLAG_ENABLE_GRID_ONLY_OVERVIEW = "enable-grid-only-overview"; - public static final String REQUEST_IS_RECENTS_WINDOW_ENABLED = "recents-window-enabled"; + public static final String REQUEST_FLAG_ENABLE_APP_PAIRS = "enable-app-pairs"; public static final String REQUEST_UNSTASH_BUBBLE_BAR_IF_STASHED = - "unstash-bubble-bar-if-stashed"; + "unstash-bubble-bar-if-stashed"; public static final String REQUEST_INJECT_FAKE_TRACKPAD = "inject-fake-trackpad"; public static final String REQUEST_EJECT_FAKE_TRACKPAD = "eject-fake-trackpad"; diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java index 4509bae5ab..3817563b0b 100644 --- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java +++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java @@ -16,7 +16,6 @@ package com.android.launcher3.touch; import static com.android.app.animation.Interpolators.scrollInterpolatorForVelocity; -import static com.android.launcher3.Flags.enableMouseInteractionChanges; import static com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS; import static com.android.launcher3.LauncherAnimUtils.TABLET_BOTTOM_SHEET_SUCCESS_TRANSITION_PROGRESS; import static com.android.launcher3.LauncherAnimUtils.newCancelListener; @@ -34,7 +33,6 @@ import static com.android.launcher3.util.window.RefreshRateTracker.getSingleFram import android.animation.Animator.AnimatorListener; import android.animation.ValueAnimator; -import android.view.InputDevice; import android.view.MotionEvent; import com.android.launcher3.Launcher; @@ -109,9 +107,7 @@ public abstract class AbstractStateChangeTouchController ignoreSlopWhenSettling = true; } else { directionsToDetectScroll = getSwipeDirection(); - boolean ignoreMouseScroll = ev.getSource() == InputDevice.SOURCE_MOUSE - && enableMouseInteractionChanges(); - if (directionsToDetectScroll == 0 || ignoreMouseScroll) { + if (directionsToDetectScroll == 0) { mNoIntercept = true; return false; } diff --git a/src/com/android/launcher3/touch/AllAppsSwipeController.java b/src/com/android/launcher3/touch/AllAppsSwipeController.java index f86fd6ac33..fe4a83b8d0 100644 --- a/src/com/android/launcher3/touch/AllAppsSwipeController.java +++ b/src/com/android/launcher3/touch/AllAppsSwipeController.java @@ -23,9 +23,12 @@ import static com.android.app.animation.Interpolators.FINAL_FRAME; import static com.android.app.animation.Interpolators.INSTANT; import static com.android.app.animation.Interpolators.LINEAR; import static com.android.app.animation.Interpolators.clampToProgress; +import static com.android.app.animation.Interpolators.mapToProgress; import static com.android.launcher3.LauncherState.ALL_APPS; import static com.android.launcher3.LauncherState.NORMAL; +import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_BOTTOM_SHEET_FADE; import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE; +import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_KEYBOARD_FADE; import static com.android.launcher3.states.StateAnimationConfig.ANIM_DEPTH; import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_FADE; import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_SCALE; @@ -34,13 +37,15 @@ import static com.android.launcher3.states.StateAnimationConfig.ANIM_SCRIM_FADE; import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS; import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_FADE; import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_SCALE; +import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_TRANSLATE; +import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW; import android.view.MotionEvent; import android.view.animation.Interpolator; import com.android.app.animation.Interpolators; import com.android.launcher3.AbstractFloatingView; -import com.android.launcher3.Flags; +import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; import com.android.launcher3.states.StateAnimationConfig; @@ -55,10 +60,10 @@ public class AllAppsSwipeController extends AbstractStateChangeTouchController { private static final float ALL_APPS_SCRIM_VISIBLE_THRESHOLD = 0.1f; private static final float ALL_APPS_STAGGERED_FADE_THRESHOLD = 0.5f; - private static final Interpolator ALL_APPS_SCRIM_RESPONDER = + public static final Interpolator ALL_APPS_SCRIM_RESPONDER = Interpolators.clampToProgress( LINEAR, ALL_APPS_SCRIM_VISIBLE_THRESHOLD, ALL_APPS_STAGGERED_FADE_THRESHOLD); - private static final Interpolator ALL_APPS_CLAMPING_RESPONDER = + public static final Interpolator ALL_APPS_CLAMPING_RESPONDER = Interpolators.clampToProgress( LINEAR, 1 - ALL_APPS_CONTENT_FADE_MAX_CLAMPING_THRESHOLD, @@ -200,7 +205,7 @@ public class AllAppsSwipeController extends AbstractStateChangeTouchController { * Applies Animation config values for transition from all apps to home. */ public static void applyAllAppsToNormalConfig(Launcher launcher, StateAnimationConfig config) { - if (launcher.getDeviceProfile().shouldShowAllAppsOnSheet()) { + if (launcher.getDeviceProfile().isTablet) { config.setInterpolator(ANIM_SCRIM_FADE, Interpolators.reverse(ALL_APPS_SCRIM_RESPONDER)); config.setInterpolator(ANIM_ALL_APPS_FADE, FINAL_FRAME); @@ -209,20 +214,6 @@ public class AllAppsSwipeController extends AbstractStateChangeTouchController { } config.setInterpolator(ANIM_WORKSPACE_SCALE, DECELERATED_EASE); config.setInterpolator(ANIM_DEPTH, DECELERATED_EASE); - if (Flags.allAppsBlur()) { - if (!config.isUserControlled()) { - config.setInterpolator(ANIM_DEPTH, EMPHASIZED_DECELERATE); - } - config.setInterpolator(ANIM_WORKSPACE_FADE, - clampToProgress(LINEAR, 1 - ALL_APPS_SCRIM_VISIBLE_THRESHOLD, 1)); - config.setInterpolator(ANIM_HOTSEAT_FADE, - clampToProgress(LINEAR, 1 - ALL_APPS_SCRIM_VISIBLE_THRESHOLD, 1)); - } else if (launcher.getDeviceProfile().isPhone) { - // On phones without blur, reveal the workspace and hotseat when leaving All Apps. - config.setInterpolator(ANIM_WORKSPACE_FADE, INSTANT); - config.setInterpolator(ANIM_HOTSEAT_FADE, INSTANT); - config.animFlags |= StateAnimationConfig.SKIP_DEPTH_CONTROLLER; - } } else { if (config.isUserControlled()) { config.setInterpolator(ANIM_DEPTH, Interpolators.reverse(BLUR_MANUAL)); @@ -256,7 +247,7 @@ public class AllAppsSwipeController extends AbstractStateChangeTouchController { */ public static void applyNormalToAllAppsAnimConfig( Launcher launcher, StateAnimationConfig config) { - if (launcher.getDeviceProfile().shouldShowAllAppsOnSheet()) { + if (launcher.getDeviceProfile().isTablet) { config.setInterpolator(ANIM_ALL_APPS_FADE, INSTANT); config.setInterpolator(ANIM_SCRIM_FADE, ALL_APPS_SCRIM_RESPONDER); if (!config.isUserControlled()) { @@ -264,18 +255,6 @@ public class AllAppsSwipeController extends AbstractStateChangeTouchController { } config.setInterpolator(ANIM_WORKSPACE_SCALE, DECELERATED_EASE); config.setInterpolator(ANIM_DEPTH, DECELERATED_EASE); - if (Flags.allAppsBlur()) { - config.setInterpolator(ANIM_DEPTH, LINEAR); - config.setInterpolator(ANIM_WORKSPACE_FADE, - clampToProgress(LINEAR, 0, ALL_APPS_SCRIM_VISIBLE_THRESHOLD)); - config.setInterpolator(ANIM_HOTSEAT_FADE, - clampToProgress(LINEAR, 0, ALL_APPS_SCRIM_VISIBLE_THRESHOLD)); - } else if (launcher.getDeviceProfile().isPhone) { - // On phones without blur, hide the workspace and hotseat when entering All Apps. - config.setInterpolator(ANIM_WORKSPACE_FADE, FINAL_FRAME); - config.setInterpolator(ANIM_HOTSEAT_FADE, FINAL_FRAME); - config.animFlags |= StateAnimationConfig.SKIP_DEPTH_CONTROLLER; - } } else { config.setInterpolator(ANIM_DEPTH, config.isUserControlled() ? BLUR_MANUAL : BLUR_ATOMIC); @@ -302,6 +281,36 @@ public class AllAppsSwipeController extends AbstractStateChangeTouchController { } } + /** + * Applies Animation config values for transition from overview to all apps. + * + * @param threshold progress at which all apps will open upon release + */ + public static void applyOverviewToAllAppsAnimConfig( + DeviceProfile deviceProfile, StateAnimationConfig config, float threshold) { + config.animProps |= StateAnimationConfig.USER_CONTROLLED; + config.animFlags = SKIP_OVERVIEW; + if (deviceProfile.isTablet) { + config.setInterpolator(ANIM_ALL_APPS_FADE, INSTANT); + config.setInterpolator(ANIM_SCRIM_FADE, ALL_APPS_SCRIM_RESPONDER); + // The fact that we end on Workspace is not very ideal, but since we do, fade it in at + // the end of the transition. Don't scale/translate it. + config.setInterpolator(ANIM_WORKSPACE_FADE, clampToProgress(LINEAR, 0.8f, 1)); + config.setInterpolator(ANIM_WORKSPACE_SCALE, INSTANT); + config.setInterpolator(ANIM_WORKSPACE_TRANSLATE, INSTANT); + } else { + // Pop the background panel, keyboard, and content in at full opacity at the threshold. + config.setInterpolator(ANIM_ALL_APPS_BOTTOM_SHEET_FADE, + thresholdInterpolator(threshold, INSTANT)); + config.setInterpolator(ANIM_ALL_APPS_KEYBOARD_FADE, + thresholdInterpolator(threshold, INSTANT)); + config.setInterpolator(ANIM_ALL_APPS_FADE, thresholdInterpolator(threshold, INSTANT)); + + config.setInterpolator(ANIM_VERTICAL_PROGRESS, + thresholdInterpolator(threshold, mapToProgress(LINEAR, threshold, 1f))); + } + } + /** Creates an interpolator that is 0 until the threshold, then follows given interpolator. */ private static Interpolator thresholdInterpolator(float threshold, Interpolator interpolator) { return progress -> progress <= threshold ? 0 : interpolator.getInterpolation(progress); diff --git a/src/com/android/launcher3/touch/BaseSwipeDetector.java b/src/com/android/launcher3/touch/BaseSwipeDetector.java index faac4a3eb6..52c358143a 100644 --- a/src/com/android/launcher3/touch/BaseSwipeDetector.java +++ b/src/com/android/launcher3/touch/BaseSwipeDetector.java @@ -17,8 +17,6 @@ package com.android.launcher3.touch; import static android.view.MotionEvent.INVALID_POINTER_ID; -import static com.android.launcher3.MotionEventsUtils.isTrackpadMotionEvent; - import android.content.Context; import android.graphics.PointF; import android.util.Log; @@ -66,7 +64,6 @@ public abstract class BaseSwipeDetector { protected PointF mSubtractDisplacement = new PointF(); @VisibleForTesting ScrollState mState = ScrollState.IDLE; private boolean mIsSettingState; - protected boolean mIsTrackpadGesture; protected boolean mIgnoreSlopWhenSettling; protected Context mContext; @@ -125,10 +122,6 @@ public abstract class BaseSwipeDetector { return mState == ScrollState.DRAGGING || mState == ScrollState.SETTLING; } - public boolean isTrackpadGesture() { - return mIsTrackpadGesture; - } - public void finishedScrolling() { setState(ScrollState.IDLE); } @@ -154,7 +147,7 @@ public abstract class BaseSwipeDetector { mLastPos.set(mDownPos); mLastDisplacement.set(0, 0); mDisplacement.set(0, 0); - mIsTrackpadGesture = isTrackpadMotionEvent(ev); + if (mState == ScrollState.SETTLING && mIgnoreSlopWhenSettling) { setState(ScrollState.DRAGGING); } diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java index 381d17accb..a448acc64c 100644 --- a/src/com/android/launcher3/touch/ItemClickHandler.java +++ b/src/com/android/launcher3/touch/ItemClickHandler.java @@ -46,6 +46,7 @@ import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherSettings; import com.android.launcher3.R; +import com.android.launcher3.Utilities; import com.android.launcher3.apppairs.AppPairIcon; import com.android.launcher3.folder.Folder; import com.android.launcher3.folder.FolderIcon; @@ -66,7 +67,10 @@ import com.android.launcher3.testing.shared.TestProtocol; import com.android.launcher3.util.ApiWrapper; import com.android.launcher3.util.ItemInfoMatcher; import com.android.launcher3.views.FloatingIconView; +import com.android.launcher3.views.Snackbar; import com.android.launcher3.widget.LauncherAppWidgetProviderInfo; +import com.android.launcher3.widget.PendingAddShortcutInfo; +import com.android.launcher3.widget.PendingAddWidgetInfo; import com.android.launcher3.widget.PendingAppWidgetHostView; import com.android.launcher3.widget.WidgetAddFlowHandler; import com.android.launcher3.widget.WidgetManagerHelper; @@ -123,6 +127,15 @@ public class ItemClickHandler { } } else if (tag instanceof ItemClickProxy) { ((ItemClickProxy) tag).onItemClicked(v); + } else if (tag instanceof PendingAddWidgetInfo) { + if (DEBUG) { + String targetPackage = ((PendingAddWidgetInfo) tag).getTargetPackage(); + Log.d(TAG, "onClick: PendingAddWidgetInfo clicked for package=" + targetPackage); + } + CharSequence msg = Utilities.wrapForTts( + launcher.getText(R.string.long_press_widget_to_add), + launcher.getString(R.string.long_accessible_way_to_add)); + Snackbar.show(launcher, msg, null); } } @@ -228,9 +241,10 @@ public class ItemClickHandler { private static void onClickPendingAppItem(View v, Launcher launcher, String packageName, boolean downloadStarted) { ItemInfo item = (ItemInfo) v.getTag(); - CompletableFuture siFuture = CompletableFuture.supplyAsync(() -> - InstallSessionHelper.INSTANCE.get(launcher) - .getActiveSessionInfo(item.user, packageName), + CompletableFuture siFuture; + siFuture = CompletableFuture.supplyAsync(() -> + InstallSessionHelper.INSTANCE.get(launcher) + .getActiveSessionInfo(item.user, packageName), UI_HELPER_EXECUTOR); Consumer marketLaunchAction = sessionInfo -> { if (sessionInfo != null) { @@ -244,8 +258,8 @@ public class ItemClickHandler { } } // Fallback to using custom market intent. - Intent intent = ApiWrapper.INSTANCE.get(launcher).getMarketSearchIntent( - packageName, item.user); + Intent intent = ApiWrapper.INSTANCE.get(launcher).getAppMarketActivityIntent( + packageName, Process.myUserHandle()); launcher.startActivitySafely(v, intent, item); }; @@ -357,7 +371,9 @@ public class ItemClickHandler { // Check for abandoned promise if ((v instanceof BubbleTextView) && shortcut.hasPromiseIconUi() && (!Flags.enableSupportForArchiving() || !shortcut.isArchived())) { - String packageName = shortcut.getTargetPackage(); + String packageName = shortcut.getIntent().getComponent() != null + ? shortcut.getIntent().getComponent().getPackageName() + : shortcut.getIntent().getPackage(); if (!TextUtils.isEmpty(packageName)) { onClickPendingAppItem( v, diff --git a/src/com/android/launcher3/touch/ItemLongClickListener.java b/src/com/android/launcher3/touch/ItemLongClickListener.java index 98ba8c7e99..89057a21fd 100644 --- a/src/com/android/launcher3/touch/ItemLongClickListener.java +++ b/src/com/android/launcher3/touch/ItemLongClickListener.java @@ -188,7 +188,7 @@ public class ItemLongClickListener { // Return early if an item is already being dragged (e.g. when long-pressing two shortcuts) if (launcher.getDragController().isDragging()) return false; // Return early if user is in the middle of selecting split-screen apps - if (launcher.isSplitSelectionActive()) { + if (FeatureFlags.enableSplitContextually() && launcher.isSplitSelectionActive()) { return false; } diff --git a/src/com/android/launcher3/touch/WorkspaceTouchListener.java b/src/com/android/launcher3/touch/WorkspaceTouchListener.java index 620e3bdfeb..5c918e73e5 100644 --- a/src/com/android/launcher3/touch/WorkspaceTouchListener.java +++ b/src/com/android/launcher3/touch/WorkspaceTouchListener.java @@ -23,7 +23,6 @@ import static android.view.MotionEvent.ACTION_MOVE; import static android.view.MotionEvent.ACTION_POINTER_UP; import static android.view.MotionEvent.ACTION_UP; -import static com.android.launcher3.Flags.enableMouseInteractionChanges; import static com.android.launcher3.LauncherState.ALL_APPS; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_CLOSE_TAP_OUTSIDE; @@ -36,7 +35,6 @@ import android.graphics.PointF; import android.graphics.Rect; import android.view.GestureDetector; import android.view.HapticFeedbackConstants; -import android.view.InputDevice; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; @@ -47,6 +45,7 @@ import com.android.launcher3.CellLayout; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; import com.android.launcher3.Workspace; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.logger.LauncherAtom; import com.android.launcher3.testing.TestLogging; @@ -144,7 +143,7 @@ public class WorkspaceTouchListener extends GestureDetector.SimpleOnGestureListe } boolean isInAllAppsBottomSheet = mLauncher.isInState(ALL_APPS) - && mLauncher.getDeviceProfile().shouldShowAllAppsOnSheet(); + && mLauncher.getDeviceProfile().isTablet; final boolean result; if (mLongPressState == STATE_COMPLETED) { @@ -208,10 +207,6 @@ public class WorkspaceTouchListener extends GestureDetector.SimpleOnGestureListe @Override public void onLongPress(MotionEvent event) { - if (event.getSource() == InputDevice.SOURCE_MOUSE && enableMouseInteractionChanges()) { - // Stop mouse long press events from showing the menu. - return; - } maybeShowMenu(); } @@ -226,7 +221,7 @@ public class WorkspaceTouchListener extends GestureDetector.SimpleOnGestureListe HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); mLauncher.getStatsLogManager().logger().log(LAUNCHER_WORKSPACE_LONGPRESS); mLauncher.showDefaultOptions(mTouchDownPoint.x, mTouchDownPoint.y); - if (mLauncher.isSplitSelectionActive()) { + if (FeatureFlags.enableSplitContextually() && mLauncher.isSplitSelectionActive()) { mLauncher.dismissSplitSelection(LAUNCHER_SPLIT_SELECTION_EXIT_INTERRUPTED); } } else { diff --git a/src/com/android/launcher3/util/ActivityOptionsWrapper.java b/src/com/android/launcher3/util/ActivityOptionsWrapper.java index 17ff2a96b7..99cc1f7b6c 100644 --- a/src/com/android/launcher3/util/ActivityOptionsWrapper.java +++ b/src/com/android/launcher3/util/ActivityOptionsWrapper.java @@ -25,7 +25,6 @@ import android.os.Bundle; public class ActivityOptionsWrapper { public final ActivityOptions options; - // Called when the app launch animation is complete public final RunnableList onEndCallback; public ActivityOptionsWrapper(ActivityOptions options, RunnableList onEndCallback) { diff --git a/src/com/android/launcher3/util/ActivityTracker.java b/src/com/android/launcher3/util/ActivityTracker.java new file mode 100644 index 0000000000..b2d0d75dc4 --- /dev/null +++ b/src/com/android/launcher3/util/ActivityTracker.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.util; + +import android.util.Log; + +import androidx.annotation.Nullable; + +import com.android.launcher3.BaseActivity; + +import java.io.PrintWriter; +import java.lang.ref.WeakReference; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * Helper class to statically track activity creation + * @param The activity type to track + */ +public final class ActivityTracker { + + private static final String TAG = "ActivityTracker"; + + private WeakReference mCurrentActivity = new WeakReference<>(null); + private CopyOnWriteArrayList> mCallbacks = new CopyOnWriteArrayList<>(); + + @Nullable + public R getCreatedActivity() { + return (R) mCurrentActivity.get(); + } + + public void onActivityDestroyed(T activity) { + if (mCurrentActivity.get() == activity) { + mCurrentActivity.clear(); + } + } + + /** + * Call {@link SchedulerCallback#init(BaseActivity, boolean)} when the + * activity is ready. If the activity is already created, this is called immediately. + * + * The tracker maintains a strong ref to the callback, so it is up to the caller to return + * {@code false} in the callback OR to unregister the callback explicitly. + * + * @param callback The callback to call init() on when the activity is ready. + */ + public void registerCallback(SchedulerCallback callback, String reasonString) { + Log.d(TAG, "Registering callback: " + callback + ", reason=" + reasonString); + T activity = mCurrentActivity.get(); + mCallbacks.add(callback); + if (activity != null) { + if (!callback.init(activity, activity.isStarted())) { + unregisterCallback(callback, "ActivityTracker.registerCallback: Intent handled"); + } + } + } + + /** + * Unregisters a registered callback. + */ + public void unregisterCallback(SchedulerCallback callback, String reasonString) { + Log.d(TAG, "Unregistering callback: " + callback + ", reason=" + reasonString); + mCallbacks.remove(callback); + } + + public boolean handleCreate(T activity) { + mCurrentActivity = new WeakReference<>(activity); + return handleIntent(activity, false /* alreadyOnHome */); + } + + public boolean handleNewIntent(T activity) { + return handleIntent(activity, activity.isStarted()); + } + + private boolean handleIntent(T activity, boolean alreadyOnHome) { + boolean handled = false; + if (!mCallbacks.isEmpty()) { + Log.d(TAG, "handleIntent: mCallbacks=" + mCallbacks); + } + for (SchedulerCallback cb : mCallbacks) { + if (!cb.init(activity, alreadyOnHome)) { + // Callback doesn't want any more updates + unregisterCallback(cb, "ActivityTracker.handleIntent: Intent handled"); + } + handled = true; + } + return handled; + } + + public void dump(String prefix, PrintWriter writer) { + writer.println(prefix + "ActivityTracker:"); + writer.println(prefix + "\tmCurrentActivity=" + mCurrentActivity.get()); + writer.println(prefix + "\tmCallbacks=" + mCallbacks); + } + + public interface SchedulerCallback { + + /** + * Called when the activity is ready. + * @param alreadyOnHome Whether the activity is already started. + * @return Whether to continue receiving callbacks (i.e. if the activity is recreated). + */ + boolean init(T activity, boolean alreadyOnHome); + } +} diff --git a/src/com/android/launcher3/util/ApiWrapper.java b/src/com/android/launcher3/util/ApiWrapper.java index d065e309ed..429ee3ac2b 100644 --- a/src/com/android/launcher3/util/ApiWrapper.java +++ b/src/com/android/launcher3/util/ApiWrapper.java @@ -17,13 +17,13 @@ package com.android.launcher3.util; import static com.android.launcher3.LauncherConstants.ActivityCodes.REQUEST_HOME_ROLE; +import static com.android.launcher3.util.MainThreadInitializedObject.forOverride; import android.app.ActivityOptions; import android.app.Person; import android.app.role.RoleManager; import android.content.Context; import android.content.Intent; -import android.content.pm.ApplicationInfo; import android.content.pm.LauncherActivityInfo; import android.content.pm.LauncherApps; import android.content.pm.LauncherUserInfo; @@ -31,45 +31,34 @@ import android.content.pm.ShortcutInfo; import android.graphics.drawable.ColorDrawable; import android.net.Uri; import android.os.Build; -import android.os.Process; import android.os.UserHandle; import android.os.UserManager; import android.util.ArrayMap; -import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.content.ContextCompat; import com.android.launcher3.BuildConfig; import com.android.launcher3.Launcher; +import com.android.launcher3.R; import com.android.launcher3.Utilities; -import com.android.launcher3.dagger.ApplicationContext; -import com.android.launcher3.dagger.LauncherAppComponent; -import com.android.launcher3.dagger.LauncherAppSingleton; import java.util.Collections; import java.util.List; import java.util.Map; -import javax.inject.Inject; - /** * A wrapper for the hidden API calls */ -@LauncherAppSingleton -public class ApiWrapper { +public class ApiWrapper implements ResourceBasedOverride, SafeCloseable { - public static final DaggerSingletonObject INSTANCE = new DaggerSingletonObject<>( - LauncherAppComponent::getApiWrapper); + public static final MainThreadInitializedObject INSTANCE = + forOverride(ApiWrapper.class, R.string.api_wrapper_class); protected final Context mContext; - private final String[] mLegacyMultiInstanceSupportedApps; - @Inject - public ApiWrapper(@ApplicationContext Context context) { + public ApiWrapper(Context context) { mContext = context; - mLegacyMultiInstanceSupportedApps = context.getResources().getStringArray( - com.android.launcher3.R.array.config_appsSupportMultiInstancesSplit); } /** @@ -113,7 +102,7 @@ public class ApiWrapper { serial); try { - if (Utilities.ATLEAST_V && launcherApps != null) { + if (Utilities.ATLEAST_U && launcherApps != null) { LauncherUserInfo userInfo = launcherApps.getLauncherUserInfo(user); if (userInfo != null) { var userType = userInfo.getUserType(); @@ -150,21 +139,6 @@ public class ApiWrapper { * Activity). */ public Intent getAppMarketActivityIntent(String packageName, UserHandle user) { - return createMarketIntent(packageName); - } - - /** - * Returns an intent which can be used to start a search for a package on app market - */ - public Intent getMarketSearchIntent(String packageName, UserHandle user) { - // If we are search for the current user, just launch the market directly as the - // system won't have the installer details either - return (Process.myUserHandle().equals(user)) - ? createMarketIntent(packageName) - : getAppMarketActivityIntent(packageName, user); - } - - private static Intent createMarketIntent(String packageName) { return new Intent(Intent.ACTION_VIEW) .setData(new Uri.Builder() .scheme("market") @@ -186,30 +160,11 @@ public class ApiWrapper { /** * Checks if an activity is flagged as non-resizeable. */ - public boolean isNonResizeableActivity(@NonNull LauncherActivityInfo lai) { - // Overridden in Quickstep + public boolean isNonResizeableActivity(LauncherActivityInfo lai) { + // Overridden in quickstep return false; } - /** - * Checks if an activity supports multi-instance. - */ - public boolean supportsMultiInstance(@NonNull LauncherActivityInfo lai) { - // Check app multi-instance properties after V - if (!Utilities.ATLEAST_V) { - return false; - } - - // Check the legacy hardcoded allowlist first - for (String pkg : mLegacyMultiInstanceSupportedApps) { - if (pkg.equals(lai.getComponentName().getPackageName())) { - return true; - } - } - - // Overridden in Quickstep - return false; - } /** * Starts an Activity which can be used to set this Launcher as the HOME app, via a consent * screen. In case the consent screen cannot be shown, or the user does not set current Launcher @@ -229,27 +184,8 @@ public class ApiWrapper { } } - /** - * Returns a hash to uniquely identify a particular version of appInfo - */ - public String getApplicationInfoHash(@NonNull ApplicationInfo appInfo) { - // The hashString in source dir changes with every install - return appInfo.sourceDir; - } - - /** - * Returns the round icon resource Id if defined by the app - */ - public int getRoundIconRes(@NonNull ApplicationInfo appInfo) { - return 0; - } - - /** - * Checks if the shortcut is using an icon with file or URI source - */ - public boolean isFileDrawable(@NonNull ShortcutInfo shortcutInfo) { - return false; - } + @Override + public void close() { } private static class NoopDrawable extends ColorDrawable { @Override @@ -262,4 +198,4 @@ public class ApiWrapper { return 1; } } -} +} \ No newline at end of file diff --git a/src/com/android/launcher3/util/BackPressHandler.java b/src/com/android/launcher3/util/BackPressHandler.java index bba871b17a..fbd4225d64 100644 --- a/src/com/android/launcher3/util/BackPressHandler.java +++ b/src/com/android/launcher3/util/BackPressHandler.java @@ -15,15 +15,12 @@ */ package com.android.launcher3.util; -import android.os.Build.VERSION_CODES; import android.window.OnBackAnimationCallback; -import androidx.annotation.RequiresApi; /** * Extension of {@link OnBackAnimationCallback} that allows a check to determine * if this callback supports handling back or not */ -@RequiresApi(api = VERSION_CODES.UPSIDE_DOWN_CAKE) public interface BackPressHandler extends OnBackAnimationCallback { boolean canHandleBack(); } diff --git a/src/com/android/launcher3/util/ContentWriter.java b/src/com/android/launcher3/util/ContentWriter.java index c2c1fee561..9910dc2e70 100644 --- a/src/com/android/launcher3/util/ContentWriter.java +++ b/src/com/android/launcher3/util/ContentWriter.java @@ -23,6 +23,7 @@ import android.os.UserHandle; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherSettings; +import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.icons.BitmapInfo; import com.android.launcher3.icons.GraphicsUtils; import com.android.launcher3.model.ModelDbController; @@ -106,7 +107,7 @@ public class ContentWriter { public int commit() { if (mCommitParams != null) { return mCommitParams.mDbController.update( - getValues(mContext), + Favorites.TABLE_NAME, getValues(mContext), mCommitParams.mWhere, mCommitParams.mSelectionArgs); } return 0; diff --git a/src/com/android/launcher3/util/DimensionUtils.kt b/src/com/android/launcher3/util/DimensionUtils.kt index 821dda789f..63e919ad3e 100644 --- a/src/com/android/launcher3/util/DimensionUtils.kt +++ b/src/com/android/launcher3/util/DimensionUtils.kt @@ -31,8 +31,7 @@ object DimensionUtils { fun getTaskbarPhoneDimensions( deviceProfile: DeviceProfile, res: Resources, - isPhoneMode: Boolean, - isGestureNav: Boolean, + isPhoneMode: Boolean ): Point { val p = Point() // Taskbar for large screen @@ -43,7 +42,7 @@ object DimensionUtils { } // Taskbar on phone using gesture nav, it will always be stashed - if (isGestureNav) { + if (deviceProfile.isGestureMode) { p.x = ViewGroup.LayoutParams.MATCH_PARENT p.y = res.getDimensionPixelSize(R.dimen.taskbar_stashed_size) return p diff --git a/src/com/android/launcher3/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java index e8d353df71..e6fac12eb6 100644 --- a/src/com/android/launcher3/util/DisplayController.java +++ b/src/com/android/launcher3/util/DisplayController.java @@ -15,17 +15,15 @@ */ package com.android.launcher3.util; -import static android.content.pm.PackageManager.FEATURE_SENSOR_HINGE_ANGLE; +import static android.content.Intent.ACTION_CONFIGURATION_CHANGED; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; -import static com.android.launcher3.Flags.enableOverviewOnConnectedDisplays; import static com.android.launcher3.InvariantDeviceProfile.TYPE_MULTI_DISPLAY; import static com.android.launcher3.InvariantDeviceProfile.TYPE_PHONE; import static com.android.launcher3.InvariantDeviceProfile.TYPE_TABLET; import static com.android.launcher3.LauncherPrefs.TASKBAR_PINNING; import static com.android.launcher3.LauncherPrefs.TASKBAR_PINNING_KEY; -import static com.android.launcher3.Utilities.ATLEAST_S; import static com.android.launcher3.Utilities.dpiFromPx; import static com.android.launcher3.LauncherPrefs.TASKBAR_PINNING_DESKTOP_MODE_KEY; import static com.android.launcher3.LauncherPrefs.TASKBAR_PINNING_IN_DESKTOP_MODE; @@ -37,68 +35,57 @@ import static com.android.launcher3.util.FlagDebugUtils.appendFlag; import static com.android.launcher3.util.window.WindowManagerProxy.MIN_TABLET_WIDTH; import android.annotation.SuppressLint; +import android.annotation.TargetApi; import android.content.ComponentCallbacks; import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import android.content.res.Configuration; import android.graphics.Point; import android.graphics.Rect; import android.hardware.display.DisplayManager; +import android.os.Build; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; -import android.util.SparseArray; import android.view.Display; import androidx.annotation.AnyThread; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import androidx.annotation.UiThread; import androidx.annotation.VisibleForTesting; import com.android.launcher3.InvariantDeviceProfile.DeviceType; -import com.android.launcher3.LauncherPrefChangeListener; import com.android.launcher3.LauncherPrefs; import com.android.launcher3.Utilities; -import com.android.launcher3.dagger.ApplicationContext; -import com.android.launcher3.dagger.LauncherAppComponent; -import com.android.launcher3.dagger.LauncherAppSingleton; import com.android.launcher3.logging.FileLog; import com.android.launcher3.util.window.CachedDisplayInfo; import com.android.launcher3.util.window.WindowManagerProxy; -import com.android.launcher3.util.window.WindowManagerProxy.DesktopVisibilityListener; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.StringJoiner; -import java.util.concurrent.CopyOnWriteArrayList; - -import javax.inject.Inject; /** - * Utility class to cache properties of default display to avoid a system RPC on every call. + * Utility class to cache properties of default display to avoid a system RPC on + * every call. */ @SuppressLint("NewApi") -@LauncherAppSingleton -public class DisplayController implements DesktopVisibilityListener { +public class DisplayController implements ComponentCallbacks, SafeCloseable { private static final String TAG = "DisplayController"; private static final boolean DEBUG = false; - private static boolean sTaskbarModePreferenceStatusForTests = false; private static boolean sTransientTaskbarStatusForTests = true; // TODO(b/254119092) remove all logs with this tag public static final String TASKBAR_NOT_DESTROYED_TAG = "b/254119092"; - public static final DaggerSingletonObject INSTANCE = - new DaggerSingletonObject<>(LauncherAppComponent::getDisplayController); + public static final MainThreadInitializedObject INSTANCE = new MainThreadInitializedObject<>( + DisplayController::new); public static final int CHANGE_ACTIVE_SCREEN = 1 << 0; public static final int CHANGE_ROTATION = 1 << 1; @@ -107,105 +94,77 @@ public class DisplayController implements DesktopVisibilityListener { public static final int CHANGE_NAVIGATION_MODE = 1 << 4; public static final int CHANGE_TASKBAR_PINNING = 1 << 5; public static final int CHANGE_DESKTOP_MODE = 1 << 6; - public static final int CHANGE_SHOW_LOCKED_TASKBAR = 1 << 7; public static final int CHANGE_ALL = CHANGE_ACTIVE_SCREEN | CHANGE_ROTATION | CHANGE_DENSITY | CHANGE_SUPPORTED_BOUNDS | CHANGE_NAVIGATION_MODE - | CHANGE_TASKBAR_PINNING | CHANGE_DESKTOP_MODE | CHANGE_SHOW_LOCKED_TASKBAR; + | CHANGE_TASKBAR_PINNING | CHANGE_DESKTOP_MODE; private static final String ACTION_OVERLAY_CHANGED = "android.intent.action.OVERLAY_CHANGED"; private static final String TARGET_OVERLAY_PACKAGE = "android"; - private final WindowManagerProxy mWMProxy; + private final Context mContext; + private final DisplayManager mDM; - private final @ApplicationContext Context mAppContext; + // Null for SDK < S + private final Context mWindowContext; - // The callback in this listener updates DeviceProfile, which other listeners might depend on + // The callback in this listener updates DeviceProfile, which other listeners + // might depend on private DisplayInfoChangeListener mPriorityListener; + private final ArrayList mListeners = new ArrayList<>(); - private final SparseArray mPerDisplayInfo = - new SparseArray<>(); - - // We will register broadcast receiver on main thread to ensure not missing changes on - // TARGET_OVERLAY_PACKAGE and ACTION_OVERLAY_CHANGED. - private final SimpleBroadcastReceiver mReceiver; + private final SimpleBroadcastReceiver mReceiver = new SimpleBroadcastReceiver(this::onIntent); + private Info mInfo; private boolean mDestroyed = false; - @Inject - protected DisplayController(@ApplicationContext Context context, - WindowManagerProxy wmProxy, - LauncherPrefs prefs, - DaggerSingletonTracker lifecycle) { - mAppContext = context; - mWMProxy = wmProxy; + private SharedPreferences.OnSharedPreferenceChangeListener mTaskbarPinningPreferenceChangeListener; + + @VisibleForTesting + protected DisplayController(Context context) { + mContext = context; + mDM = context.getSystemService(DisplayManager.class); if (enableTaskbarPinning()) { - LauncherPrefChangeListener prefListener = key -> { - Info info = getInfo(); - boolean isTaskbarPinningChanged = TASKBAR_PINNING_KEY.equals(key) - && info.mIsTaskbarPinned != prefs.get(TASKBAR_PINNING); - boolean isTaskbarPinningDesktopModeChanged = - TASKBAR_PINNING_DESKTOP_MODE_KEY.equals(key) - && info.mIsTaskbarPinnedInDesktopMode != prefs.get( - TASKBAR_PINNING_IN_DESKTOP_MODE); - if (isTaskbarPinningChanged || isTaskbarPinningDesktopModeChanged) { - notifyConfigChange(DEFAULT_DISPLAY); - } - }; - - prefs.addListener(prefListener, TASKBAR_PINNING); - prefs.addListener(prefListener, TASKBAR_PINNING_IN_DESKTOP_MODE); - lifecycle.addCloseable(() -> prefs.removeListener( - prefListener, TASKBAR_PINNING, TASKBAR_PINNING_IN_DESKTOP_MODE)); + attachTaskbarPinningSharedPreferenceChangeListener(mContext); } - DisplayManager displayManager = context.getSystemService(DisplayManager.class); - Display defaultDisplay = displayManager.getDisplay(DEFAULT_DISPLAY); - PerDisplayInfo defaultPerDisplayInfo = getOrCreatePerDisplayInfo(defaultDisplay); + Display display = mDM.getDisplay(DEFAULT_DISPLAY); + if (Utilities.ATLEAST_S) { + mWindowContext = mContext.createWindowContext(display, TYPE_APPLICATION, null); + mWindowContext.registerComponentCallbacks(this); + } else { + mWindowContext = null; + mReceiver.register(mContext, ACTION_CONFIGURATION_CHANGED); + } // Initialize navigation mode change listener - mReceiver = new SimpleBroadcastReceiver(context, MAIN_EXECUTOR, this::onIntent); - mReceiver.registerPkgActions(TARGET_OVERLAY_PACKAGE, ACTION_OVERLAY_CHANGED); + mReceiver.registerPkgActions(mContext, TARGET_OVERLAY_PACKAGE, ACTION_OVERLAY_CHANGED); - wmProxy.registerDesktopVisibilityListener(this); - FileLog.i(TAG, "(CTOR) perDisplayBounds: " - + defaultPerDisplayInfo.mInfo.mPerDisplayBounds); + WindowManagerProxy wmProxy = WindowManagerProxy.INSTANCE.get(context); + Context displayInfoContext = getDisplayInfoContext(display); + mInfo = new Info(displayInfoContext, wmProxy, + wmProxy.estimateInternalDisplayBounds(displayInfoContext)); + FileLog.i(TAG, "(CTOR) perDisplayBounds: " + mInfo.mPerDisplayBounds); + } - if (enableOverviewOnConnectedDisplays()) { - final DisplayManager.DisplayListener displayListener = - new DisplayManager.DisplayListener() { - @Override - public void onDisplayAdded(int displayId) { - getOrCreatePerDisplayInfo(displayManager.getDisplay(displayId)); - } + private void attachTaskbarPinningSharedPreferenceChangeListener(Context context) { + mTaskbarPinningPreferenceChangeListener = (sharedPreferences, key) -> { + LauncherPrefs prefs = LauncherPrefs.get(mContext); + boolean isTaskbarPinningChanged = TASKBAR_PINNING_KEY.equals(key) + && mInfo.mIsTaskbarPinned != prefs.get(TASKBAR_PINNING); + boolean isTaskbarPinningDesktopModeChanged = TASKBAR_PINNING_DESKTOP_MODE_KEY.equals(key) + && mInfo.mIsTaskbarPinnedInDesktopMode != prefs.get( + TASKBAR_PINNING_IN_DESKTOP_MODE); + if (isTaskbarPinningChanged || isTaskbarPinningDesktopModeChanged) { + handleInfoChange(mWindowContext.getDisplay()); + } + }; - @Override - public void onDisplayChanged(int displayId) { - } - - @Override - public void onDisplayRemoved(int displayId) { - removePerDisplayInfo(displayId); - } - }; - displayManager.registerDisplayListener(displayListener, MAIN_EXECUTOR.getHandler()); - lifecycle.addCloseable(() -> { - displayManager.unregisterDisplayListener(displayListener); - }); - // Add any PerDisplayInfos for already-connected displays. - Arrays.stream(displayManager.getDisplays()) - .forEach((it) -> - getOrCreatePerDisplayInfo( - displayManager.getDisplay(it.getDisplayId()))); - } - - lifecycle.addCloseable(() -> { - mDestroyed = true; - defaultPerDisplayInfo.cleanup(); - mReceiver.unregisterReceiverSafely(); - wmProxy.unregisterDesktopVisibilityListener(this); - }); + LauncherPrefs.get(context).addListener( + mTaskbarPinningPreferenceChangeListener, TASKBAR_PINNING); + LauncherPrefs.get(context).addListener( + mTaskbarPinningPreferenceChangeListener, TASKBAR_PINNING_IN_DESKTOP_MODE); } /** @@ -224,6 +183,13 @@ public class DisplayController implements DesktopVisibilityListener { return INSTANCE.get(context).getInfo().isTransientTaskbar(); } + /** + * Handles info change for desktop mode. + */ + public static void handleInfoChangeForDesktopMode(Context context) { + INSTANCE.get(context).handleInfoChange(context.getDisplay()); + } + /** * Enables transient taskbar status for tests. */ @@ -232,14 +198,6 @@ public class DisplayController implements DesktopVisibilityListener { sTransientTaskbarStatusForTests = enable; } - /** - * Enables respecting taskbar mode preference during test. - */ - @VisibleForTesting - public static void enableTaskbarModePreferenceForTests(boolean enable) { - sTaskbarModePreferenceStatusForTests = enable; - } - /** * Returns whether the taskbar is pinned in gesture navigation mode. */ @@ -247,31 +205,20 @@ public class DisplayController implements DesktopVisibilityListener { return INSTANCE.get(context).getInfo().isPinnedTaskbar(); } - /** - * Returns whether the taskbar is pinned in gesture navigation mode. - */ - public static boolean isInDesktopMode(Context context) { - return INSTANCE.get(context).getInfo().isInDesktopMode(); - } - - /** - * Returns whether the taskbar is forced to be pinned when home is visible. - */ - public static boolean showLockedTaskbarOnHome(Context context) { - return INSTANCE.get(context).getInfo().showLockedTaskbarOnHome(); - } - - /** - * Returns whether desktop taskbar (pinned taskbar that shows desktop tasks) is to be used - * on the display because the display is a freeform display. - */ - public static boolean showDesktopTaskbarForFreeformDisplay(Context context) { - return INSTANCE.get(context).getInfo().showDesktopTaskbarForFreeformDisplay(); - } - @Override - public void onIsInDesktopModeChanged(int displayId, boolean isInDesktopModeAndNotInOverview) { - notifyConfigChange(displayId); + public void close() { + mDestroyed = true; + if (enableTaskbarPinning()) { + LauncherPrefs.get(mContext).removeListener( + mTaskbarPinningPreferenceChangeListener, TASKBAR_PINNING); + LauncherPrefs.get(mContext).removeListener( + mTaskbarPinningPreferenceChangeListener, TASKBAR_PINNING_IN_DESKTOP_MODE); + } + if (mWindowContext != null) { + mWindowContext.unregisterComponentCallbacks(this); + } else { + // TODO: unregister broadcast receiver + } } /** @@ -281,9 +228,10 @@ public class DisplayController implements DesktopVisibilityListener { /** * Invoked when display info has changed. + * * @param context updated context associated with the display. - * @param info updated display information. - * @param flags bitmask indicating type of change. + * @param info updated display information. + * @param flags bitmask indicating type of change. */ void onDisplayInfoChanged(Context context, Info info, int flags); } @@ -292,90 +240,79 @@ public class DisplayController implements DesktopVisibilityListener { if (mDestroyed) { return; } + boolean reconfigure = false; if (ACTION_OVERLAY_CHANGED.equals(intent.getAction())) { - Log.d(TAG, "Overlay changed, notifying listeners"); - notifyConfigChange(DEFAULT_DISPLAY); + reconfigure = true; + } else if (ACTION_CONFIGURATION_CHANGED.equals(intent.getAction())) { + Configuration config = mContext.getResources().getConfiguration(); + reconfigure = mInfo.fontScale != config.fontScale + || mInfo.densityDpi != config.densityDpi; } - } - @VisibleForTesting - public void onConfigurationChanged(Configuration config) { - onConfigurationChanged(config, DEFAULT_DISPLAY); + if (reconfigure) { + Log.d(TAG, "Configuration changed, notifying listeners"); + Display display = mDM.getDisplay(DEFAULT_DISPLAY); + if (display != null) { + handleInfoChange(display); + } + } } @UiThread - private void onConfigurationChanged(Configuration config, int displayId) { + @Override + @TargetApi(Build.VERSION_CODES.S) + public final void onConfigurationChanged(Configuration config) { Log.d(TASKBAR_NOT_DESTROYED_TAG, "DisplayController#onConfigurationChanged: " + config); - PerDisplayInfo perDisplayInfo = mPerDisplayInfo.get(displayId); - Context windowContext = perDisplayInfo.mWindowContext; - Info info = perDisplayInfo.mInfo; - if (config.densityDpi != info.densityDpi - || config.fontScale != info.fontScale - || !info.mScreenSizeDp.equals( - new PortraitSize(config.screenHeightDp, config.screenWidthDp)) - || windowContext.getDisplay().getRotation() != info.rotation - || mWMProxy.showLockedTaskbarOnHome(windowContext) - != info.showLockedTaskbarOnHome() - || mWMProxy.showDesktopTaskbarForFreeformDisplay(windowContext) - != info.showDesktopTaskbarForFreeformDisplay()) { - notifyConfigChange(displayId); + Display display = mWindowContext.getDisplay(); + if (config.densityDpi != mInfo.densityDpi + || config.fontScale != mInfo.fontScale + || display.getRotation() != mInfo.rotation + || !mInfo.mScreenSizeDp.equals( + new PortraitSize(config.screenHeightDp, config.screenWidthDp))) { + handleInfoChange(display); } } + @Override + public final void onLowMemory() { + } + public void setPriorityListener(DisplayInfoChangeListener listener) { mPriorityListener = listener; } public void addChangeListener(DisplayInfoChangeListener listener) { - addChangeListenerForDisplay(listener, DEFAULT_DISPLAY); + mListeners.add(listener); } public void removeChangeListener(DisplayInfoChangeListener listener) { - removeChangeListenerForDisplay(listener, DEFAULT_DISPLAY); - } - - public void addChangeListenerForDisplay(DisplayInfoChangeListener listener, int displayId) { - PerDisplayInfo perDisplayInfo = mPerDisplayInfo.get(displayId); - if (perDisplayInfo != null) { - perDisplayInfo.addListener(listener); - } - } - - public void removeChangeListenerForDisplay(DisplayInfoChangeListener listener, int displayId) { - PerDisplayInfo perDisplayInfo = mPerDisplayInfo.get(displayId); - if (perDisplayInfo != null) { - perDisplayInfo.removeListener(listener); - } + mListeners.remove(listener); } public Info getInfo() { - return mPerDisplayInfo.get(DEFAULT_DISPLAY).mInfo; + return mInfo; } - public @Nullable Info getInfoForDisplay(int displayId) { - if (enableOverviewOnConnectedDisplays()) { - PerDisplayInfo perDisplayInfo = mPerDisplayInfo.get(displayId); - if (perDisplayInfo != null) { - return perDisplayInfo.mInfo; - } else { - return null; - } - } else { - return getInfo(); + private Context getDisplayInfoContext(Display display) { + return Utilities.ATLEAST_S ? mWindowContext : mContext.createDisplayContext(display); + } + + @AnyThread + @VisibleForTesting + public void handleInfoChange(Display display) { + WindowManagerProxy wmProxy = WindowManagerProxy.INSTANCE.get(mContext); + Info oldInfo = mInfo; + + Context displayInfoContext = getDisplayInfoContext(display); + Info newInfo = new Info(displayInfoContext, wmProxy, oldInfo.mPerDisplayBounds); + + if (newInfo.densityDpi != oldInfo.densityDpi || newInfo.fontScale != oldInfo.fontScale + || newInfo.getNavigationMode() != oldInfo.getNavigationMode()) { + // Cache may not be valid anymore, recreate without cache + newInfo = new Info(displayInfoContext, wmProxy, + wmProxy.estimateInternalDisplayBounds(displayInfoContext)); } - } - @AnyThread - public void notifyConfigChange() { - notifyConfigChange(DEFAULT_DISPLAY); - } - - @AnyThread - public void notifyConfigChange(int displayId) { - notifyConfigChangeForDisplay(displayId); - } - - private int calculateChange(Info oldInfo, Info newInfo) { int change = 0; if (!newInfo.normalizedDisplayInfo.equals(oldInfo.normalizedDisplayInfo)) { change |= CHANGE_ACTIVE_SCREEN; @@ -396,88 +333,33 @@ public class DisplayController implements DesktopVisibilityListener { "(CHANGE_SUPPORTED_BOUNDS) perDisplayBounds: " + newInfo.mPerDisplayBounds); } if ((newInfo.mIsTaskbarPinned != oldInfo.mIsTaskbarPinned) - || (newInfo.mIsTaskbarPinnedInDesktopMode - != oldInfo.mIsTaskbarPinnedInDesktopMode) - || newInfo.isPinnedTaskbar() != oldInfo.isPinnedTaskbar()) { + || (newInfo.mIsTaskbarPinnedInDesktopMode != oldInfo.mIsTaskbarPinnedInDesktopMode)) { change |= CHANGE_TASKBAR_PINNING; } if (newInfo.mIsInDesktopMode != oldInfo.mIsInDesktopMode) { change |= CHANGE_DESKTOP_MODE; } - if (newInfo.mShowLockedTaskbarOnHome != oldInfo.mShowLockedTaskbarOnHome) { - change |= CHANGE_SHOW_LOCKED_TASKBAR; - } if (DEBUG) { Log.d(TAG, "handleInfoChange - change: " + getChangeFlagsString(change)); } - return change; - } - private Info getNewInfo(Info oldInfo, Context displayInfoContext) { - Info newInfo = new Info(displayInfoContext, mWMProxy, oldInfo.mPerDisplayBounds); - - if (newInfo.densityDpi != oldInfo.densityDpi || newInfo.fontScale != oldInfo.fontScale - || newInfo.getNavigationMode() != oldInfo.getNavigationMode()) { - // Cache may not be valid anymore, recreate without cache - newInfo = new Info(displayInfoContext, mWMProxy, - mWMProxy.estimateInternalDisplayBounds(displayInfoContext)); - } - return newInfo; - } - - @AnyThread - public void notifyConfigChangeForDisplay(int displayId) { - PerDisplayInfo perDisplayInfo = mPerDisplayInfo.get(displayId); - if (perDisplayInfo == null) return; - Info oldInfo = perDisplayInfo.mInfo; - final Info newInfo = getNewInfo(oldInfo, perDisplayInfo.mWindowContext); - final int flags = calculateChange(oldInfo, newInfo); - if (flags != 0) { - MAIN_EXECUTOR.execute(() -> { - perDisplayInfo.mInfo = newInfo; - if (displayId == DEFAULT_DISPLAY && mPriorityListener != null) { - mPriorityListener.onDisplayInfoChanged(perDisplayInfo.mWindowContext, newInfo, - flags); - } - perDisplayInfo.notifyListeners(newInfo, flags); - }); + if (change != 0) { + mInfo = newInfo; + final int flags = change; + MAIN_EXECUTOR.execute(() -> notifyChange(displayInfoContext, flags)); } } - private PerDisplayInfo getOrCreatePerDisplayInfo(Display display) { - int displayId = display.getDisplayId(); - PerDisplayInfo perDisplayInfo = mPerDisplayInfo.get(displayId); - if (perDisplayInfo != null) { - return perDisplayInfo; + private void notifyChange(Context context, int flags) { + if (mPriorityListener != null) { + mPriorityListener.onDisplayInfoChanged(context, mInfo, flags); } - if (DEBUG) { - Log.d(TAG, - String.format("getOrCreatePerDisplayInfo - no cached value found for %d", - displayId)); - } - Context windowContext; - if (ATLEAST_S) { - windowContext = mAppContext.createWindowContext(display, TYPE_APPLICATION, null); - } else { - windowContext = mAppContext.createDisplayContext(display); - } - Info info = new Info(windowContext, mWMProxy, - mWMProxy.estimateInternalDisplayBounds(windowContext)); - perDisplayInfo = new PerDisplayInfo(displayId, windowContext, info); - mPerDisplayInfo.put(displayId, perDisplayInfo); - return perDisplayInfo; - } - /** - * Clean up resources for the given display id. - * @param displayId The display id - */ - void removePerDisplayInfo(int displayId) { - PerDisplayInfo info = mPerDisplayInfo.get(displayId); - if (info == null) return; - info.cleanup(); - mPerDisplayInfo.remove(displayId); + int count = mListeners.size(); + for (int i = 0; i < count; i++) { + mListeners.get(i).onDisplayInfoChanged(context, mInfo, flags); + } } public static class Info { @@ -497,21 +379,13 @@ public class DisplayController implements DesktopVisibilityListener { // WindowBounds public final WindowBounds realBounds; public final Set supportedBounds = new ArraySet<>(); - private final ArrayMap> mPerDisplayBounds = - new ArrayMap<>(); + private final ArrayMap> mPerDisplayBounds = new ArrayMap<>(); private final boolean mIsTaskbarPinned; private final boolean mIsTaskbarPinnedInDesktopMode; private final boolean mIsInDesktopMode; - private final boolean mShowLockedTaskbarOnHome; - private final boolean mIsHomeVisible; - - private final boolean mShowDesktopTaskbarForFreeformDisplay; - - private boolean mIsFoldable; - public Info(Context displayInfoContext) { /* don't need system overrides for external displays */ this(displayInfoContext, new WindowManagerProxy(), new ArrayMap<>()); @@ -532,10 +406,6 @@ public class DisplayController implements DesktopVisibilityListener { densityDpi = config.densityDpi; mScreenSizeDp = new PortraitSize(config.screenHeightDp, config.screenWidthDp); navigationMode = wmProxy.getNavigationMode(displayInfoContext); - - // LC: Hacky stuff but it work! - mIsFoldable = Utilities.ATLEAST_R && displayInfoContext.getPackageManager() - .hasSystemFeature(FEATURE_SENSOR_HINGE_ANGLE); mPerDisplayBounds.putAll(perDisplayBoundsCache); List cachedValue = getCurrentBounds(); @@ -576,11 +446,7 @@ public class DisplayController implements DesktopVisibilityListener { mIsTaskbarPinned = LauncherPrefs.get(displayInfoContext).get(TASKBAR_PINNING); mIsTaskbarPinnedInDesktopMode = LauncherPrefs.get(displayInfoContext).get( TASKBAR_PINNING_IN_DESKTOP_MODE); - mIsInDesktopMode = wmProxy.isInDesktopMode(DEFAULT_DISPLAY); - mShowLockedTaskbarOnHome = wmProxy.showLockedTaskbarOnHome(displayInfoContext); - mShowDesktopTaskbarForFreeformDisplay = wmProxy.showDesktopTaskbarForFreeformDisplay( - displayInfoContext); - mIsHomeVisible = wmProxy.isHomeVisible(displayInfoContext); + mIsInDesktopMode = wmProxy.isInDesktopMode(); } /** @@ -590,22 +456,13 @@ public class DisplayController implements DesktopVisibilityListener { if (navigationMode != NavigationMode.NO_BUTTON) { return false; } - if (Utilities.isRunningInTestHarness() && !sTaskbarModePreferenceStatusForTests) { + if (Utilities.isRunningInTestHarness()) { // TODO(b/258604917): Once ENABLE_TASKBAR_PINNING is enabled, remove usage of - // sTransientTaskbarStatusForTests and update test to directly - // toggle shared preference to switch transient taskbar on/off. + // sTransientTaskbarStatusForTests and update test to directly + // toggle shared preference to switch transient taskbar on/off. return sTransientTaskbarStatusForTests; } if (enableTaskbarPinning()) { - // If "freeform" display taskbar is enabled, ensure the taskbar is pinned. - if (mShowDesktopTaskbarForFreeformDisplay) { - return false; - } - - // If Launcher is visible on the freeform display, ensure the taskbar is pinned. - if (mShowLockedTaskbarOnHome && mIsHomeVisible) { - return false; - } if (mIsInDesktopMode) { return !mIsTaskbarPinnedInDesktopMode; } @@ -621,9 +478,6 @@ public class DisplayController implements DesktopVisibilityListener { return navigationMode == NavigationMode.NO_BUTTON && !isTransientTaskbar(); } - /** - * Returns whether the taskbar is in desktop mode. - */ public boolean isInDesktopMode() { return mIsInDesktopMode; } @@ -654,8 +508,9 @@ public class DisplayController implements DesktopVisibilityListener { return Collections.unmodifiableSet(mPerDisplayBounds.keySet()); } - /** Returns all {@link WindowBounds}s for the current display. */ - @Nullable + /** + * Returns all {@link WindowBounds}s for the current display. + */ public List getCurrentBounds() { return mPerDisplayBounds.get(normalizedDisplayInfo); } @@ -671,43 +526,20 @@ public class DisplayController implements DesktopVisibilityListener { int type = supportedBounds.stream() .mapToInt(bounds -> isTablet(bounds) ? flagTablet : flagPhone) .reduce(0, (a, b) -> a | b); - if (type == (flagPhone | flagTablet)) { - Log.d("LC-DisplayController", "Device has multiple display (Phone|Tablet) based on bounds"); + // device has profiles supporting both phone and tablet modes return TYPE_MULTI_DISPLAY; - } - if (type == flagTablet) { - // LC: Hacky stuff but it work! - if (mIsFoldable) { - Log.d("LC-DisplayController", "Device is Foldable (Tablet && Hinge Detected)"); - return TYPE_MULTI_DISPLAY; - } - Log.d("LC-DisplayController", "Device has tablet profile (Tablet)"); + } else if (type == flagTablet) { return TYPE_TABLET; } else { - Log.d("LC-DisplayController", "Device has phone profile (Phone)"); return TYPE_PHONE; } } - - /** - * Returns whether the taskbar is forced to be pinned when home is visible. - */ - public boolean showLockedTaskbarOnHome() { - return mShowLockedTaskbarOnHome; - } - - /** - * Returns whether the taskbar should be pinned, and showing desktop tasks, because the - * display is a "freeform" display. - */ - public boolean showDesktopTaskbarForFreeformDisplay() { - return mShowDesktopTaskbarForFreeformDisplay; - } } /** * Returns the given binary flags as a human-readable string. + * * @see #CHANGE_ALL */ public String getChangeFlagsString(int change) { @@ -719,7 +551,6 @@ public class DisplayController implements DesktopVisibilityListener { appendFlag(result, change, CHANGE_NAVIGATION_MODE, "CHANGE_NAVIGATION_MODE"); appendFlag(result, change, CHANGE_TASKBAR_PINNING, "CHANGE_TASKBAR_VARIANT"); appendFlag(result, change, CHANGE_DESKTOP_MODE, "CHANGE_DESKTOP_MODE"); - appendFlag(result, change, CHANGE_SHOW_LOCKED_TASKBAR, "CHANGE_SHOW_LOCKED_TASKBAR"); return result.toString(); } @@ -727,29 +558,20 @@ public class DisplayController implements DesktopVisibilityListener { * Dumps the current state information */ public void dump(PrintWriter pw) { - int count = mPerDisplayInfo.size(); - for (int i = 0; i < count; ++i) { - int displayId = mPerDisplayInfo.keyAt(i); - Info info = getInfoForDisplay(displayId); - if (info == null) { - continue; - } - pw.println(String.format(Locale.ENGLISH, "DisplayController.Info (displayId=%d):", - displayId)); - pw.println(" normalizedDisplayInfo=" + info.normalizedDisplayInfo); - pw.println(" rotation=" + info.rotation); - pw.println(" fontScale=" + info.fontScale); - pw.println(" densityDpi=" + info.densityDpi); - pw.println(" navigationMode=" + info.getNavigationMode().name()); - pw.println(" isTaskbarPinned=" + info.mIsTaskbarPinned); - pw.println(" isTaskbarPinnedInDesktopMode=" + info.mIsTaskbarPinnedInDesktopMode); - pw.println(" isInDesktopMode=" + info.mIsInDesktopMode); - pw.println(" showLockedTaskbarOnHome=" + info.showLockedTaskbarOnHome()); - pw.println(" currentSize=" + info.currentSize); - info.mPerDisplayBounds.forEach((key, value) -> pw.println( - " perDisplayBounds - " + key + ": " + value)); - pw.println(" isTransientTaskbar=" + info.isTransientTaskbar()); - } + Info info = mInfo; + pw.println("DisplayController.Info:"); + pw.println(" normalizedDisplayInfo=" + info.normalizedDisplayInfo); + pw.println(" rotation=" + info.rotation); + pw.println(" fontScale=" + info.fontScale); + pw.println(" densityDpi=" + info.densityDpi); + pw.println(" navigationMode=" + info.getNavigationMode().name()); + pw.println(" isTaskbarPinned=" + info.mIsTaskbarPinned); + pw.println(" isTaskbarPinnedInDesktopMode=" + info.mIsTaskbarPinnedInDesktopMode); + pw.println(" isInDesktopMode=" + info.mIsInDesktopMode); + pw.println(" currentSize=" + info.currentSize); + info.mPerDisplayBounds.forEach((key, value) -> pw.println( + " perDisplayBounds - " + key + ": " + value)); + pw.println(" isTransientTaskbar=" + info.isTransientTaskbar()); } /** @@ -765,8 +587,10 @@ public class DisplayController implements DesktopVisibilityListener { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; PortraitSize that = (PortraitSize) o; return width == that.width && height == that.height; } @@ -777,47 +601,4 @@ public class DisplayController implements DesktopVisibilityListener { } } - private class PerDisplayInfo implements ComponentCallbacks { - final int mDisplayId; - final CopyOnWriteArrayList mListeners = - new CopyOnWriteArrayList<>(); - final Context mWindowContext; - Info mInfo; - - PerDisplayInfo(int displayId, Context windowContext, Info info) { - this.mDisplayId = displayId; - this.mWindowContext = windowContext; - this.mInfo = info; - windowContext.registerComponentCallbacks(this); - } - - void addListener(DisplayInfoChangeListener listener) { - mListeners.add(listener); - } - - void removeListener(DisplayInfoChangeListener listener) { - mListeners.remove(listener); - } - - void notifyListeners(Info info, int flags) { - int count = mListeners.size(); - for (int i = 0; i < count; ++i) { - mListeners.get(i).onDisplayInfoChanged(mWindowContext, info, flags); - } - } - - @Override - public void onConfigurationChanged(@NonNull Configuration newConfig) { - DisplayController.this.onConfigurationChanged(newConfig, mDisplayId); - } - - @Override - public void onLowMemory() {} - - void cleanup() { - mWindowContext.unregisterComponentCallbacks(this); - mListeners.clear(); - } - } - } diff --git a/src/com/android/launcher3/util/DynamicResource.java b/src/com/android/launcher3/util/DynamicResource.java index fe9fd7c566..fbdb5c2aa8 100644 --- a/src/com/android/launcher3/util/DynamicResource.java +++ b/src/com/android/launcher3/util/DynamicResource.java @@ -22,39 +22,35 @@ import androidx.annotation.DimenRes; import androidx.annotation.FractionRes; import androidx.annotation.IntegerRes; -import com.android.launcher3.dagger.ApplicationContext; -import com.android.launcher3.dagger.LauncherAppSingleton; -import com.android.launcher3.dagger.LauncherBaseAppComponent; import com.android.systemui.plugins.PluginListener; import com.android.systemui.plugins.ResourceProvider; -import javax.inject.Inject; - /** * Utility class to support customizing resource values using plugins * * To load resources, call - * DynamicResource.provider(context).getInt(resId) or any other supported methods + * DynamicResource.provider(context).getInt(resId) or any other supported methods * * To allow customization for a particular resource, add them to dynamic_resources.xml */ -@LauncherAppSingleton public class DynamicResource implements - ResourceProvider, PluginListener { + ResourceProvider, PluginListener, SafeCloseable { - private static final DaggerSingletonObject INSTANCE = - new DaggerSingletonObject<>(LauncherBaseAppComponent::getDynamicResource); + private static final MainThreadInitializedObject INSTANCE = + new MainThreadInitializedObject<>(DynamicResource::new); private final Context mContext; private ResourceProvider mPlugin; - @Inject - public DynamicResource(@ApplicationContext Context context, - PluginManagerWrapper pluginManagerWrapper, DaggerSingletonTracker tracker) { + private DynamicResource(Context context) { mContext = context; - pluginManagerWrapper.addPluginListener(this, + PluginManagerWrapper.INSTANCE.get(context).addPluginListener(this, ResourceProvider.class, false /* allowedMultiple */); - tracker.addCloseable(() -> pluginManagerWrapper.removePluginListener(this)); + } + + @Override + public void close() { + PluginManagerWrapper.INSTANCE.get(mContext).removePluginListener(this); } @Override diff --git a/src/com/android/launcher3/util/EdgeEffectCompat.java b/src/com/android/launcher3/util/EdgeEffectCompat.java index 58a45499e4..2dade2e508 100644 --- a/src/com/android/launcher3/util/EdgeEffectCompat.java +++ b/src/com/android/launcher3/util/EdgeEffectCompat.java @@ -33,6 +33,21 @@ public class EdgeEffectCompat extends EdgeEffect { super(context); } + @Override + public float getDistance() { + return Utilities.ATLEAST_S ? super.getDistance() : 0; + } + + @Override + public float onPullDistance(float deltaDistance, float displacement) { + if (Utilities.ATLEAST_S) { + return super.onPullDistance(deltaDistance, displacement); + } else { + onPull(deltaDistance, displacement); + return deltaDistance; + } + } + public float onPullDistance(float deltaDistance, float displacement, MotionEvent ev) { return onPullDistance(deltaDistance, displacement); } diff --git a/src/com/android/launcher3/util/Executors.java b/src/com/android/launcher3/util/Executors.java index 296fc8ad27..c622b71108 100644 --- a/src/com/android/launcher3/util/Executors.java +++ b/src/com/android/launcher3/util/Executors.java @@ -16,8 +16,8 @@ package com.android.launcher3.util; import static android.os.Process.THREAD_PRIORITY_BACKGROUND; -import static android.os.Process.THREAD_PRIORITY_FOREGROUND; +import android.os.HandlerThread; import android.os.Looper; import android.os.Process; @@ -51,20 +51,21 @@ public class Executors { /** * An {@link LooperExecutor} to be used with async task where order is important. */ - public static final LooperExecutor ORDERED_BG_EXECUTOR = - new LooperExecutor("BackgroundExecutor", THREAD_PRIORITY_BACKGROUND); + public static final LooperExecutor ORDERED_BG_EXECUTOR = new LooperExecutor( + createAndStartNewLooper("BackgroundExecutor", THREAD_PRIORITY_BACKGROUND)); /** * Returns the executor for running tasks on the main thread. */ public static final LooperExecutor MAIN_EXECUTOR = - new LooperExecutor(Looper.getMainLooper(), THREAD_PRIORITY_FOREGROUND); + new LooperExecutor(Looper.getMainLooper()); /** * A background executor for using time sensitive actions where user is waiting for response. */ public static final LooperExecutor UI_HELPER_EXECUTOR = - new LooperExecutor("UiThreadHelper", Process.THREAD_PRIORITY_FOREGROUND); + new LooperExecutor( + createAndStartNewLooper("UiThreadHelper", Process.THREAD_PRIORITY_FOREGROUND)); /** A background executor to preinflate views. */ @@ -73,10 +74,27 @@ public class Executors { new SimpleThreadFactory( "preinflate-allapps-icons", THREAD_PRIORITY_BACKGROUND)); + /** + * Utility method to get a started handler thread statically + */ + public static Looper createAndStartNewLooper(String name) { + return createAndStartNewLooper(name, Process.THREAD_PRIORITY_DEFAULT); + } + + /** + * Utility method to get a started handler thread statically with the provided priority + */ + public static Looper createAndStartNewLooper(String name, int priority) { + HandlerThread thread = new HandlerThread(name, priority); + thread.start(); + return thread.getLooper(); + } + /** * Executor used for running Launcher model related tasks (eg loading icons or updated db) */ - public static final LooperExecutor MODEL_EXECUTOR = new LooperExecutor("launcher-loader"); + public static final LooperExecutor MODEL_EXECUTOR = + new LooperExecutor(createAndStartNewLooper("launcher-loader")); /** * Returns and caches a single thread executor for a given package. @@ -84,7 +102,9 @@ public class Executors { * @param packageName Package associated with the executor. */ public static LooperExecutor getPackageExecutor(String packageName) { - return PACKAGE_EXECUTORS.computeIfAbsent(packageName, LooperExecutor::new); + return PACKAGE_EXECUTORS.computeIfAbsent( + packageName, p -> new LooperExecutor( + createAndStartNewLooper(p, Process.THREAD_PRIORITY_DEFAULT))); } /** diff --git a/src/com/android/launcher3/util/IntSparseArrayMap.java b/src/com/android/launcher3/util/IntSparseArrayMap.java index 70f74e36f5..9d5391be0e 100644 --- a/src/com/android/launcher3/util/IntSparseArrayMap.java +++ b/src/com/android/launcher3/util/IntSparseArrayMap.java @@ -19,8 +19,6 @@ package com.android.launcher3.util; import android.util.SparseArray; import java.util.Iterator; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; /** * Extension of {@link SparseArray} with some utility methods. @@ -45,10 +43,6 @@ public class IntSparseArrayMap extends SparseArray implements Iterable return new ValueIterator(); } - public Stream stream() { - return StreamSupport.stream(spliterator(), false); - } - @Thunk class ValueIterator implements Iterator { private int mNextIndex = 0; diff --git a/src/com/android/launcher3/util/ItemInflater.kt b/src/com/android/launcher3/util/ItemInflater.kt index acb3c4ee12..ebf4656c61 100644 --- a/src/com/android/launcher3/util/ItemInflater.kt +++ b/src/com/android/launcher3/util/ItemInflater.kt @@ -24,7 +24,6 @@ import android.view.View.OnClickListener import android.view.View.OnFocusChangeListener import android.view.ViewGroup import com.android.launcher3.BubbleTextView -import com.android.launcher3.LauncherAppState import com.android.launcher3.LauncherSettings.Favorites import com.android.launcher3.R import com.android.launcher3.apppairs.AppPairIcon @@ -47,11 +46,10 @@ class ItemInflater( private val widgetHolder: LauncherWidgetHolder, private val clickListener: OnClickListener, private val focusListener: OnFocusChangeListener, - private val defaultParent: ViewGroup, + private val defaultParent: ViewGroup ) where T : Context, T : ActivityContext { - private val widgetInflater = - WidgetInflater(context, LauncherAppState.getInstance(context).isSafeModeEnabled) + private val widgetInflater = WidgetInflater(context) @JvmOverloads fun inflateItem(item: ItemInfo, writer: ModelWriter, nullableParent: ViewGroup? = null): View? { @@ -77,7 +75,7 @@ class ItemInflater( R.layout.folder_icon, context, parent, - item as FolderInfo, + item as FolderInfo ) Favorites.ITEM_TYPE_APP_PAIR -> return AppPairIcon.inflateIcon( @@ -85,7 +83,7 @@ class ItemInflater( context, parent, item as AppPairInfo, - BubbleTextView.DISPLAY_WORKSPACE, + BubbleTextView.DISPLAY_WORKSPACE ) Favorites.ITEM_TYPE_APPWIDGET, Favorites.ITEM_TYPE_CUSTOM_APPWIDGET -> diff --git a/src/com/android/launcher3/util/LauncherBindableItemsContainer.java b/src/com/android/launcher3/util/LauncherBindableItemsContainer.java new file mode 100644 index 0000000000..02779ce596 --- /dev/null +++ b/src/com/android/launcher3/util/LauncherBindableItemsContainer.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.util; + +import android.graphics.drawable.Drawable; +import android.view.View; + +import com.android.launcher3.BubbleTextView; +import com.android.launcher3.apppairs.AppPairIcon; +import com.android.launcher3.folder.Folder; +import com.android.launcher3.folder.FolderIcon; +import com.android.launcher3.graphics.PreloadIconDrawable; +import com.android.launcher3.model.data.AppPairInfo; +import com.android.launcher3.model.data.FolderInfo; +import com.android.launcher3.model.data.ItemInfo; +import com.android.launcher3.model.data.LauncherAppWidgetInfo; +import com.android.launcher3.model.data.WorkspaceItemInfo; +import com.android.launcher3.views.ActivityContext; +import com.android.launcher3.widget.PendingAppWidgetHostView; + +import java.util.HashSet; +import java.util.List; + +/** + * Interface representing a container which can bind Launcher items with some utility methods + */ +public interface LauncherBindableItemsContainer { + + /** + * Called to update workspace items as a result of + * {@link com.android.launcher3.model.BgDataModel.Callbacks#bindWorkspaceItemsChanged(List)} + */ + default void updateWorkspaceItems(List shortcuts, ActivityContext context) { + final HashSet updates = new HashSet<>(shortcuts); + ItemOperator op = (info, v) -> { + if (v instanceof BubbleTextView && updates.contains(info)) { + WorkspaceItemInfo si = (WorkspaceItemInfo) info; + BubbleTextView shortcut = (BubbleTextView) v; + Drawable oldIcon = shortcut.getIcon(); + boolean oldPromiseState = (oldIcon instanceof PreloadIconDrawable) + && ((PreloadIconDrawable) oldIcon).hasNotCompleted(); + shortcut.applyFromWorkspaceItem( + si, + si.isPromise() != oldPromiseState + && oldIcon instanceof PreloadIconDrawable + ? (PreloadIconDrawable) oldIcon + : null); + } else if (info instanceof FolderInfo && v instanceof FolderIcon) { + ((FolderIcon) v).updatePreviewItems(updates::contains); + } else if (info instanceof AppPairInfo && v instanceof AppPairIcon appPairIcon) { + appPairIcon.maybeRedrawForWorkspaceUpdate(updates::contains); + } + + // Iterate all items + return false; + }; + + mapOverItems(op); + Folder openFolder = Folder.getOpen(context); + if (openFolder != null) { + openFolder.iterateOverItems(op); + } + } + + /** + * Called to update restored items as a result of + * {@link com.android.launcher3.model.BgDataModel.Callbacks#bindRestoreItemsChange(HashSet)}} + */ + default void updateRestoreItems(final HashSet updates, ActivityContext context) { + ItemOperator op = (info, v) -> { + if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView + && updates.contains(info)) { + ((BubbleTextView) v).applyLoadingState(null); + } else if (v instanceof PendingAppWidgetHostView + && info instanceof LauncherAppWidgetInfo + && updates.contains(info)) { + ((PendingAppWidgetHostView) v).applyState(); + } else if (v instanceof FolderIcon && info instanceof FolderInfo) { + ((FolderIcon) v).updatePreviewItems(updates::contains); + } else if (info instanceof AppPairInfo && v instanceof AppPairIcon appPairIcon) { + appPairIcon.maybeRedrawForWorkspaceUpdate(updates::contains); + } + // process all the shortcuts + return false; + }; + + mapOverItems(op); + Folder folder = Folder.getOpen(context); + if (folder != null) { + folder.iterateOverItems(op); + } + } + + /** + * Map the operator over the shortcuts and widgets. + * + * @param op the operator to map over the shortcuts + */ + void mapOverItems(ItemOperator op); + + interface ItemOperator { + /** + * Process the next itemInfo, possibly with side-effect on the next item. + * + * @param info info for the shortcut + * @param view view for the shortcut + * @return true if done, false to continue the map + */ + boolean evaluate(ItemInfo info, View view); + } +} diff --git a/src/com/android/launcher3/util/LockedUserState.kt b/src/com/android/launcher3/util/LockedUserState.kt index 742a32798f..94f9e4fb69 100644 --- a/src/com/android/launcher3/util/LockedUserState.kt +++ b/src/com/android/launcher3/util/LockedUserState.kt @@ -20,35 +20,20 @@ import android.content.Intent import android.os.Process import android.os.UserManager import androidx.annotation.VisibleForTesting -import com.android.launcher3.dagger.ApplicationContext -import com.android.launcher3.dagger.LauncherAppComponent -import com.android.launcher3.dagger.LauncherAppSingleton -import com.android.launcher3.util.Executors.MAIN_EXECUTOR -import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR -import javax.inject.Inject -@LauncherAppSingleton -class LockedUserState -@Inject -constructor(@ApplicationContext private val context: Context, lifeCycle: DaggerSingletonTracker) { +class LockedUserState(private val mContext: Context) : SafeCloseable { val isUserUnlockedAtLauncherStartup: Boolean - var isUserUnlocked = false - private set(value) { - field = value - if (value) { - notifyUserUnlocked() - } - } - + var isUserUnlocked: Boolean + private set private val mUserUnlockedActions: RunnableList = RunnableList() @VisibleForTesting - val userUnlockedReceiver = - SimpleBroadcastReceiver(context, UI_HELPER_EXECUTOR) { - if (Intent.ACTION_USER_UNLOCKED == it.action) { - isUserUnlocked = true - } + val mUserUnlockedReceiver = SimpleBroadcastReceiver { + if (Intent.ACTION_USER_UNLOCKED == it.action) { + isUserUnlocked = true + notifyUserUnlocked() } + } init { // 1) when user reboots devices, launcher process starts at lock screen and both @@ -57,29 +42,30 @@ constructor(@ApplicationContext private val context: Context, lifeCycle: DaggerS // yet isUserUnlockedAtLauncherStartup will remains as false. // 2) when launcher process restarts after user has unlocked screen, both variable are // init as true and will not change. - isUserUnlocked = checkIsUserUnlocked() + isUserUnlocked = + mContext + .getSystemService(UserManager::class.java)!! + .isUserUnlocked(Process.myUserHandle()) isUserUnlockedAtLauncherStartup = isUserUnlocked - if (!isUserUnlocked) { - userUnlockedReceiver.register( - { - // If user is unlocked while registering broadcast receiver, we should update - // [isUserUnlocked], which will call [notifyUserUnlocked] in setter - if (checkIsUserUnlocked()) { - MAIN_EXECUTOR.execute { isUserUnlocked = true } - } - }, - Intent.ACTION_USER_UNLOCKED, - ) + if (isUserUnlocked) { + notifyUserUnlocked() + } else { + mUserUnlockedReceiver.register(mContext, Intent.ACTION_USER_UNLOCKED) } - lifeCycle.addCloseable { userUnlockedReceiver.unregisterReceiverSafely() } } - private fun checkIsUserUnlocked() = - context.getSystemService(UserManager::class.java)!!.isUserUnlocked(Process.myUserHandle()) - private fun notifyUserUnlocked() { mUserUnlockedActions.executeAllAndDestroy() - userUnlockedReceiver.unregisterReceiverSafely() + Executors.THREAD_POOL_EXECUTOR.execute { + mUserUnlockedReceiver.unregisterReceiverSafely(mContext) + } + } + + /** Stops the receiver from listening for ACTION_USER_UNLOCK broadcasts. */ + override fun close() { + Executors.THREAD_POOL_EXECUTOR.execute { + mUserUnlockedReceiver.unregisterReceiverSafely(mContext) + } } /** @@ -90,15 +76,10 @@ constructor(@ApplicationContext private val context: Context, lifeCycle: DaggerS mUserUnlockedActions.add(action) } - /** Removes a previously queued `Runnable` to be run when the user is unlocked. */ - fun removeOnUserUnlockedRunnable(action: Runnable) { - mUserUnlockedActions.remove(action) - } - companion object { @VisibleForTesting @JvmField - val INSTANCE = DaggerSingletonObject(LauncherAppComponent::getLockedUserState) + val INSTANCE = MainThreadInitializedObject { LockedUserState(it) } @JvmStatic fun get(context: Context): LockedUserState = INSTANCE.get(context) } diff --git a/src/com/android/launcher3/util/LogConfig.java b/src/com/android/launcher3/util/LogConfig.java index 72e3e79a84..3d4b409cf8 100644 --- a/src/com/android/launcher3/util/LogConfig.java +++ b/src/com/android/launcher3/util/LogConfig.java @@ -52,9 +52,9 @@ public class LogConfig { public static final String WEB_APP_SEARCH_LOGGING = "WebAppSearchLogging"; /** - * When turned on, we enable quick launch related logging. + * When turned on, we enable quick launch v2 related logging. */ - public static final String QUICK_LAUNCH = "QuickLaunch"; + public static final String QUICK_LAUNCH_V2 = "QuickLaunchV2"; /** * When turned on, we enable Gms Play related logging. @@ -76,5 +76,4 @@ public class LogConfig { * When turned on, we enable zero state web data loader related logging. */ public static final String ZERO_WEB_DATA_LOADER = "ZeroStateWebDataLoaderLog"; - public static final String SEARCH_TARGET_UTIL_LOG = "SearchTargetUtilLog"; } diff --git a/src/com/android/launcher3/util/LooperExecutor.java b/src/com/android/launcher3/util/LooperExecutor.java new file mode 100644 index 0000000000..3a8a13c8bb --- /dev/null +++ b/src/com/android/launcher3/util/LooperExecutor.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ +package com.android.launcher3.util; + +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Process; + +import java.util.List; +import java.util.concurrent.AbstractExecutorService; +import java.util.concurrent.TimeUnit; + +/** + * Extension of {@link AbstractExecutorService} which executed on a provided looper. + */ +public class LooperExecutor extends AbstractExecutorService { + + private final Handler mHandler; + + public LooperExecutor(Looper looper) { + mHandler = new Handler(looper); + } + + public Handler getHandler() { + return mHandler; + } + + @Override + public void execute(Runnable runnable) { + if (getHandler().getLooper() == Looper.myLooper()) { + runnable.run(); + } else { + getHandler().post(runnable); + } + } + + /** + * Same as execute, but never runs the action inline. + */ + public void post(Runnable runnable) { + getHandler().post(runnable); + } + + /** + * Not supported and throws an exception when used. + */ + @Override + @Deprecated + public void shutdown() { + throw new UnsupportedOperationException(); + } + + /** + * Not supported and throws an exception when used. + */ + @Override + @Deprecated + public List shutdownNow() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isShutdown() { + return false; + } + + @Override + public boolean isTerminated() { + return false; + } + + /** + * Not supported and throws an exception when used. + */ + @Override + @Deprecated + public boolean awaitTermination(long l, TimeUnit timeUnit) { + throw new UnsupportedOperationException(); + } + + /** + * Returns the thread for this executor + */ + public Thread getThread() { + return getHandler().getLooper().getThread(); + } + + /** + * Returns the looper for this executor + */ + public Looper getLooper() { + return getHandler().getLooper(); + } + + /** + * Set the priority of a thread, based on Linux priorities. + * @param priority Linux priority level, from -20 for highest scheduling priority + * to 19 for lowest scheduling priority. + * @see Process#setThreadPriority(int, int) + */ + public void setThreadPriority(int priority) { + Process.setThreadPriority(((HandlerThread) getThread()).getThreadId(), priority); + } +} diff --git a/src/com/android/launcher3/util/MainThreadInitializedObject.java b/src/com/android/launcher3/util/MainThreadInitializedObject.java index cd8529c640..d2b1f4f328 100644 --- a/src/com/android/launcher3/util/MainThreadInitializedObject.java +++ b/src/com/android/launcher3/util/MainThreadInitializedObject.java @@ -12,20 +12,33 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. + * + * Modifications copyright 2021, Lawnchair */ package com.android.launcher3.util; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import android.content.Context; +import android.content.ContextWrapper; import android.os.Looper; +import android.util.Log; +import androidx.annotation.UiThread; +import androidx.annotation.VisibleForTesting; + +import com.android.launcher3.util.ResourceBasedOverride.Overrides; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; import java.util.concurrent.ExecutionException; +import java.util.function.Consumer; /** * Utility class for defining singletons which are initiated on main thread. - *

- * Lawnchair: This class has moved to Launcher3 Go variant. */ public class MainThreadInitializedObject { @@ -38,7 +51,7 @@ public class MainThreadInitializedObject { public T get(Context context) { Context app = context.getApplicationContext(); - if (app instanceof ObjectSandbox sc) { + if (app instanceof SandboxApplication sc) { return sc.getObject(this); } @@ -56,18 +69,127 @@ public class MainThreadInitializedObject { return mValue; } + /** + * Executes the callback is the value is already created + * + * @return true if the callback was executed, false otherwise + */ + public boolean executeIfCreated(Consumer callback) { + T v = mValue; + if (v != null) { + callback.accept(v); + return true; + } else { + return false; + } + } + + @VisibleForTesting + public void initializeForTesting(T value) { + mValue = value; + } + + protected void onPostInit(Context context) { + } + + public T getNoCreate() { + return mValue; + } + + /** + * Initializes a provider based on resource overrides + */ + public static MainThreadInitializedObject forOverride( + Class clazz, int resourceId) { + return new MainThreadInitializedObject<>(c -> Overrides.getObject(clazz, c, resourceId)); + } + public interface ObjectProvider { T get(Context context); } - /** Sandbox for isolating {@link MainThreadInitializedObject} instances from Launcher. */ - public interface ObjectSandbox { + public interface SandboxApplication { /** - * Find a cached object from mObjectMap if we have already created one. If not, generate + * Find a cached object from mObjectMap if we have already created one. If not, + * generate * an object using the provider. */ T getObject(MainThreadInitializedObject object); + + @UiThread + default T createObject(MainThreadInitializedObject object) { + return object.mProvider.get((Context) this); + } + } + + /** + * Abstract Context which allows custom implementations for + * {@link MainThreadInitializedObject} providers + */ + public static class SandboxContext extends ContextWrapper implements SandboxApplication { + + private static final String TAG = "SandboxContext"; + + public final Map mObjectMap = new HashMap<>(); + public final ArrayList mOrderedObjects = new ArrayList<>(); + + private final Object mDestroyLock = new Object(); + private boolean mDestroyed = false; + + public SandboxContext(Context base) { + super(base); + } + + @Override + public Context getApplicationContext() { + return this; + } + + public void onDestroy() { + synchronized (mDestroyLock) { + // Destroy in reverse order + for (int i = mOrderedObjects.size() - 1; i >= 0; i--) { + mOrderedObjects.get(i).close(); + } + mDestroyed = true; + } + } + + @Override + public T getObject(MainThreadInitializedObject object) { + synchronized (mDestroyLock) { + if (mDestroyed) { + Log.e(TAG, "Static object access with a destroyed context"); + } + T t = (T) mObjectMap.get(object); + if (t != null) { + return t; + } + if (Looper.myLooper() == Looper.getMainLooper()) { + t = createObject(object); + mObjectMap.put(object, t); + mOrderedObjects.add(t); + return t; + } + } + + try { + return MAIN_EXECUTOR.submit(() -> getObject(object)).get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } + + /** + * Put a value into mObjectMap, can be used to put mocked + * MainThreadInitializedObject + * instances into SandboxContext. + */ + public void putObject( + MainThreadInitializedObject object, T value) { + mObjectMap.put(object, value); + } } } diff --git a/src/com/android/launcher3/util/MultiTranslateDelegate.java b/src/com/android/launcher3/util/MultiTranslateDelegate.java index ce006c4184..84ef445dcf 100644 --- a/src/com/android/launcher3/util/MultiTranslateDelegate.java +++ b/src/com/android/launcher3/util/MultiTranslateDelegate.java @@ -37,8 +37,6 @@ public class MultiTranslateDelegate { public static final int INDEX_TASKBAR_ALIGNMENT_ANIM = 3; public static final int INDEX_TASKBAR_REVEAL_ANIM = 4; public static final int INDEX_TASKBAR_PINNING_ANIM = 5; - public static final int INDEX_NAV_BAR_ANIM = 6; - public static final int INDEX_BUBBLE_BAR_ANIM = 7; // Affect all items inside of a MultipageCellLayout public static final int INDEX_CELLAYOUT_MULTIPAGE_SPACING = 3; @@ -49,7 +47,7 @@ public class MultiTranslateDelegate { // Specific for hotseat items when adjusting for bubbles public static final int INDEX_BUBBLE_ADJUSTMENT_ANIM = 3; - public static final int COUNT = 8; + public static final int COUNT = 6; private final MultiPropertyFactory mTranslationX; private final MultiPropertyFactory mTranslationY; diff --git a/src/com/android/launcher3/util/OnboardingPrefs.kt b/src/com/android/launcher3/util/OnboardingPrefs.kt index 771594ea9d..ac6e97c5b2 100644 --- a/src/com/android/launcher3/util/OnboardingPrefs.kt +++ b/src/com/android/launcher3/util/OnboardingPrefs.kt @@ -16,7 +16,6 @@ package com.android.launcher3.util import android.content.Context -import androidx.annotation.VisibleForTesting import com.android.launcher3.LauncherPrefs import com.android.launcher3.LauncherPrefs.Companion.backedUpItem @@ -27,7 +26,7 @@ object OnboardingPrefs { val sharedPrefKey: String, val maxCount: Int, ) { - @VisibleForTesting val prefItem = backedUpItem(sharedPrefKey, 0) + private val prefItem = backedUpItem(sharedPrefKey, 0) /** @return The number of times we have seen the given event. */ fun get(c: Context): Int { diff --git a/src/com/android/launcher3/util/OverlayEdgeEffect.java b/src/com/android/launcher3/util/OverlayEdgeEffect.java index 20900409da..0b6066bb01 100644 --- a/src/com/android/launcher3/util/OverlayEdgeEffect.java +++ b/src/com/android/launcher3/util/OverlayEdgeEffect.java @@ -47,7 +47,6 @@ public class OverlayEdgeEffect extends EdgeEffectCompat { return mDistance; } - @Override public float onPullDistance(float deltaDistance, float displacement) { // Fallback implementation, will never actually get called if (BuildConfigs.IS_DEBUG_DEVICE) { diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java index 1e2607fc9a..38333339f6 100644 --- a/src/com/android/launcher3/util/PackageManagerHelper.java +++ b/src/com/android/launcher3/util/PackageManagerHelper.java @@ -16,7 +16,8 @@ package com.android.launcher3.util; -import static com.android.launcher3.Utilities.ATLEAST_R; +import static android.view.WindowManager.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI; + import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE; import android.annotation.SuppressLint; @@ -27,12 +28,12 @@ import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.LauncherActivityInfo; import android.content.pm.LauncherApps; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ResolveInfo; import android.graphics.Rect; import android.net.Uri; -import android.os.Build.VERSION; -import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.os.Process; import android.os.UserHandle; @@ -43,11 +44,10 @@ import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.launcher3.Flags; import com.android.launcher3.PendingAddItemInfo; import com.android.launcher3.R; -import com.android.launcher3.dagger.ApplicationContext; -import com.android.launcher3.dagger.LauncherAppSingleton; -import com.android.launcher3.dagger.LauncherBaseAppComponent; +import com.android.launcher3.Utilities; import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.ItemInfoWithIcon; @@ -58,19 +58,16 @@ import java.net.URISyntaxException; import java.util.List; import java.util.Objects; -import javax.inject.Inject; - /** * Utility methods using package manager */ -@LauncherAppSingleton -public class PackageManagerHelper { +public class PackageManagerHelper implements SafeCloseable { private static final String TAG = "PackageManagerHelper"; @NonNull - public static DaggerSingletonObject INSTANCE = - new DaggerSingletonObject<>(LauncherBaseAppComponent::getPackageManagerHelper); + public static final MainThreadInitializedObject INSTANCE = new MainThreadInitializedObject<>( + PackageManagerHelper::new); @NonNull private final Context mContext; @@ -81,29 +78,111 @@ public class PackageManagerHelper { @NonNull private final LauncherApps mLauncherApps; - @Inject - public PackageManagerHelper(@ApplicationContext final Context context) { + private final String[] mLegacyMultiInstanceSupportedApps; + + public PackageManagerHelper(@NonNull final Context context) { mContext = context; mPm = context.getPackageManager(); mLauncherApps = Objects.requireNonNull(context.getSystemService(LauncherApps.class)); + mLegacyMultiInstanceSupportedApps = mContext.getResources().getStringArray( + R.array.config_appsSupportMultiInstancesSplit); + } + + @Override + public void close() { + } + + /** + * Returns true if the app can possibly be on the SDCard. This is just a + * workaround and doesn't + * guarantee that the app is on SD card. + */ + public boolean isAppOnSdcard(@NonNull final String packageName, + @NonNull final UserHandle user) { + final ApplicationInfo info = getApplicationInfo( + packageName, user, PackageManager.MATCH_UNINSTALLED_PACKAGES); + return info != null && (info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0; + } + + /** + * Returns whether the target app is suspended for a given user as per + * {@link android.app.admin.DevicePolicyManager#isPackageSuspended}. + */ + public boolean isAppSuspended(@NonNull final String packageName, + @NonNull final UserHandle user) { + final ApplicationInfo info = getApplicationInfo(packageName, user, 0); + return info != null && isAppSuspended(info); + } + + /** + * Returns whether the target app is installed for a given user + */ + public boolean isAppInstalled(@NonNull final String packageName, + @NonNull final UserHandle user) { + final ApplicationInfo info = getApplicationInfo(packageName, user, 0); + return info != null; + } + + /** + * Returns whether the target app is archived for a given user + */ + @SuppressWarnings("NewApi") + public boolean isAppArchivedForUser(@NonNull final String packageName, + @NonNull final UserHandle user) { + if (!Flags.enableSupportForArchiving()) { + return false; + } + final ApplicationInfo info = getApplicationInfo( + // LauncherApps does not support long flags currently. Since archived apps are + // subset of uninstalled apps, this filter also includes archived apps. + packageName, user, PackageManager.MATCH_UNINSTALLED_PACKAGES); + return info != null && info.isArchived; + } + + /** + * Returns whether the target app is in archived state + */ + @SuppressWarnings("NewApi") + public boolean isAppArchived(@NonNull final String packageName) { + final ApplicationInfo info; + try { + info = mPm.getPackageInfo(packageName, + PackageManager.PackageInfoFlags.of( + PackageManager.MATCH_ARCHIVED_PACKAGES)).applicationInfo; + return info.isArchived; + } catch (Throwable e) { + Log.e(TAG, "Failed to get applicationInfo for package: " + packageName, e); + return false; + } } /** * Returns the installing app package for the given package */ + @SuppressLint("NewApi") public String getAppInstallerPackage(@NonNull final String packageName) { try { - if (ATLEAST_R) { - return mPm.getInstallSourceInfo(packageName).getInstallingPackageName(); - } else { - return null; - } + return mPm.getInstallSourceInfo(packageName).getInstallingPackageName(); } catch (NameNotFoundException e) { Log.e(TAG, "Failed to get installer package for app package:" + packageName, e); return null; } } + /** + * Returns the application info for the provided package or null + */ + @Nullable + public ApplicationInfo getApplicationInfo(@NonNull final String packageName, + @NonNull final UserHandle user, final int flags) { + try { + ApplicationInfo info = mLauncherApps.getApplicationInfo(packageName, flags, user); + return !isPackageInstalledOrArchived(info) || !info.enabled ? null : info; + } catch (PackageManager.NameNotFoundException e) { + return null; + } + } + /** * Returns the preferred launch activity intent for a given package. */ @@ -123,6 +202,25 @@ public class PackageManagerHelper { return activities.isEmpty() ? null : activities.get(0); } + /** + * Returns whether an application is suspended as per + * {@link android.app.admin.DevicePolicyManager#isPackageSuspended}. + */ + public static boolean isAppSuspended(ApplicationInfo info) { + return (info.flags & ApplicationInfo.FLAG_SUSPENDED) != 0; + } + + public Intent getMarketIntent(String packageName) { + return new Intent(Intent.ACTION_VIEW) + .setData(new Uri.Builder() + .scheme("market") + .authority("details") + .appendQueryParameter("id", packageName) + .build()) + .putExtra(Intent.EXTRA_REFERRER, new Uri.Builder().scheme("android-app") + .authority(mContext.getPackageName()).build()); + } + /** * Creates a new market search intent. */ @@ -131,7 +229,7 @@ public class PackageManagerHelper { Intent intent = Intent.parseUri(context.getString(R.string.market_search_intent), 0); if (!TextUtils.isEmpty(query)) { intent.setData( - intent.getData().buildUpon().appendQueryParameter("q", query).build()); + intent.getData().buildUpon().appendQueryParameter("q", query).build()); } return intent; } catch (URISyntaxException e) { @@ -139,27 +237,25 @@ public class PackageManagerHelper { } } - // Lawnchair public static Intent getStyleWallpapersIntent(Context context) { return getStyleWallpapersAltIntent(context); } - // Lawnchair public static Intent getStyleWallpapersAltIntent(Context context) { return new Intent(Intent.ACTION_SET_WALLPAPER).setComponent( - new ComponentName(context.getString(R.string.wallpaper_picker_package_alt), - "com.android.customization.picker.CustomizationPickerActivity")); + new ComponentName(context.getString(R.string.wallpaper_picker_package_alt), + "com.android.customization.picker.CustomizationPickerActivity")); } /** * Starts the details activity for {@code info} */ public static void startDetailsActivityForInfo(Context context, ItemInfo info, - Rect sourceBounds, Bundle opts) { + Rect sourceBounds, Bundle opts) { if (info instanceof ItemInfoWithIcon appInfo && (appInfo.runtimeStatusFlags & FLAG_INSTALL_SESSION_ACTIVE) != 0) { context.startActivity(ApiWrapper.INSTANCE.get(context).getAppMarketActivityIntent( - appInfo.getTargetComponent().getPackageName(), Process.myUserHandle()), opts); + appInfo.getTargetComponent().getPackageName(), Process.myUserHandle())); return; } ComponentName componentName = null; @@ -183,12 +279,50 @@ public class PackageManagerHelper { } } + public static boolean isSystemApp(@NonNull final Context context, + @NonNull final Intent intent) { + PackageManager pm = context.getPackageManager(); + // Get the package name for intent + String packageName = null; + if (intent != null) { + ComponentName cn = intent.getComponent(); + if (cn == null) { + ResolveInfo info = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY); + if ((info != null) && (info.activityInfo != null)) { + packageName = info.activityInfo.packageName; + } + } else { + packageName = cn.getPackageName(); + } + } + return isSystemApp(context, packageName); + } + + public static boolean isSystemApp(Context context, String packageName) { + PackageManager pm = context.getPackageManager(); + // Check if the provided package is a system app. + if (packageName != null) { + try { + PackageInfo info = pm.getPackageInfo(packageName, 0); + return (info != null) && (info.applicationInfo != null) && + ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0); + } catch (NameNotFoundException e) { + return false; + } + } else { + return false; + } + } + /** - * Returns true if the intent is a valid launch intent for a launcher activity of an app. - * This is used to identify shortcuts which are different from the ones exposed by the + * Returns true if the intent is a valid launch intent for a launcher activity + * of an app. + * This is used to identify shortcuts which are different from the ones exposed + * by the * applications' manifest file. * - * @param launchIntent The intent that will be launched when the shortcut is clicked. + * @param launchIntent The intent that will be launched when the shortcut is + * clicked. */ public static boolean isLauncherAppTarget(Intent launchIntent) { if (launchIntent != null @@ -221,12 +355,58 @@ public class PackageManagerHelper { /** Returns the incremental download progress for the given shortcut's app. */ public static int getLoadingProgress(LauncherActivityInfo info) { - return (int) (100 * info.getLoadingProgress()); + if (Utilities.ATLEAST_S) { + return (int) (100 * info.getLoadingProgress()); + } + return 100; + } + + /** Returns true in case app is installed on the device or in archived state. */ + @SuppressWarnings("NewApi") + private boolean isPackageInstalledOrArchived(ApplicationInfo info) { + return (info.flags & ApplicationInfo.FLAG_INSTALLED) != 0 + || (Flags.enableSupportForArchiving() && info.isArchived); } /** - * Returns whether two apps should be considered the same for multi-instance purposes, which - * requires additional checks to ensure they can be started as multiple instances. + * Returns whether the given component or its application has the multi-instance + * property set. + */ + public boolean supportsMultiInstance(@NonNull ComponentName component) { + // Check the legacy hardcoded allowlist first + for (String pkg : mLegacyMultiInstanceSupportedApps) { + if (pkg.equals(component.getPackageName())) { + return true; + } + } + + // Check app multi-instance properties after V + if (!Utilities.ATLEAST_V) { + return false; + } + + try { + // Check if the component has the multi-instance property + return mPm.getProperty(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, component) + .getBoolean(); + } catch (PackageManager.NameNotFoundException e1) { + try { + // Check if the application has the multi-instance property + return mPm.getProperty(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, + component.getPackageName()) + .getBoolean(); + } catch (PackageManager.NameNotFoundException e2) { + // Fall through + } + } + return false; + } + + /** + * Returns whether two apps should be considered the same for multi-instance + * purposes, which + * requires additional checks to ensure they can be started as multiple + * instances. */ public static boolean isSameAppForMultiInstance(@NonNull ItemInfo app1, @NonNull ItemInfo app2) { diff --git a/src/com/android/launcher3/util/PluginManagerWrapper.java b/src/com/android/launcher3/util/PluginManagerWrapper.java index 5b28570acb..b27aa120b9 100644 --- a/src/com/android/launcher3/util/PluginManagerWrapper.java +++ b/src/com/android/launcher3/util/PluginManagerWrapper.java @@ -15,39 +15,32 @@ */ package com.android.launcher3.util; -import androidx.annotation.AnyThread; +import static com.android.launcher3.util.MainThreadInitializedObject.forOverride; -import com.android.launcher3.dagger.LauncherAppSingleton; -import com.android.launcher3.dagger.LauncherBaseAppComponent; +import com.android.launcher3.R; import com.android.systemui.plugins.Plugin; import com.android.systemui.plugins.PluginListener; import java.io.PrintWriter; -import javax.inject.Inject; +public class PluginManagerWrapper implements ResourceBasedOverride, SafeCloseable { -@LauncherAppSingleton -public class PluginManagerWrapper{ + public static final MainThreadInitializedObject INSTANCE = + forOverride(PluginManagerWrapper.class, R.string.plugin_manager_wrapper_class); - public static final DaggerSingletonObject INSTANCE = - new DaggerSingletonObject<>(LauncherBaseAppComponent::getPluginManagerWrapper); - - @Inject - public PluginManagerWrapper() { } - - @AnyThread public void addPluginListener( PluginListener listener, Class pluginClass) { addPluginListener(listener, pluginClass, false); } - @AnyThread public void addPluginListener( PluginListener listener, Class pluginClass, boolean allowMultiple) { } - @AnyThread public void removePluginListener(PluginListener listener) { } + @Override + public void close() { } + public void dump(PrintWriter pw) { } } diff --git a/src/com/android/launcher3/util/ScreenOnTracker.java b/src/com/android/launcher3/util/ScreenOnTracker.java index 8ffe9eac51..e16e4778e9 100644 --- a/src/com/android/launcher3/util/ScreenOnTracker.java +++ b/src/com/android/launcher3/util/ScreenOnTracker.java @@ -19,62 +19,38 @@ import static android.content.Intent.ACTION_SCREEN_OFF; import static android.content.Intent.ACTION_SCREEN_ON; import static android.content.Intent.ACTION_USER_PRESENT; -import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; - import android.content.Context; import android.content.Intent; -import androidx.annotation.VisibleForTesting; - -import com.android.launcher3.dagger.ApplicationContext; -import com.android.launcher3.dagger.LauncherAppSingleton; -import com.android.launcher3.dagger.LauncherBaseAppComponent; - import java.util.concurrent.CopyOnWriteArrayList; -import javax.inject.Inject; - /** * Utility class for tracking if the screen is currently on or off */ -@LauncherAppSingleton public class ScreenOnTracker implements SafeCloseable { - public static final DaggerSingletonObject INSTANCE = - new DaggerSingletonObject<>(LauncherBaseAppComponent::getScreenOnTracker); + public static final MainThreadInitializedObject INSTANCE = + new MainThreadInitializedObject<>(ScreenOnTracker::new); - private final SimpleBroadcastReceiver mReceiver; + private final SimpleBroadcastReceiver mReceiver = new SimpleBroadcastReceiver(this::onReceive); private final CopyOnWriteArrayList mListeners = new CopyOnWriteArrayList<>(); + private final Context mContext; private boolean mIsScreenOn; - @Inject - ScreenOnTracker(@ApplicationContext Context context, DaggerSingletonTracker tracker) { + private ScreenOnTracker(Context context) { // Assume that the screen is on to begin with - mReceiver = new SimpleBroadcastReceiver(context, UI_HELPER_EXECUTOR, this::onReceive); - init(tracker); - } - - @VisibleForTesting - ScreenOnTracker(@ApplicationContext Context context, SimpleBroadcastReceiver receiver, - DaggerSingletonTracker tracker) { - mReceiver = receiver; - init(tracker); - } - - private void init(DaggerSingletonTracker tracker) { + mContext = context; mIsScreenOn = true; - mReceiver.register(ACTION_SCREEN_ON, ACTION_SCREEN_OFF, ACTION_USER_PRESENT); - tracker.addCloseable(this); + mReceiver.register(context, ACTION_SCREEN_ON, ACTION_SCREEN_OFF, ACTION_USER_PRESENT); } @Override public void close() { - mReceiver.unregisterReceiverSafely(); + mReceiver.unregisterReceiverSafely(mContext); } - @VisibleForTesting - void onReceive(Intent intent) { + private void onReceive(Intent intent) { String action = intent.getAction(); if (ACTION_SCREEN_ON.equals(action)) { mIsScreenOn = true; diff --git a/src/com/android/launcher3/util/SettingsCache.java b/src/com/android/launcher3/util/SettingsCache.java index a4577ecc26..ccd154a570 100644 --- a/src/com/android/launcher3/util/SettingsCache.java +++ b/src/com/android/launcher3/util/SettingsCache.java @@ -18,30 +18,18 @@ package com.android.launcher3.util; import static android.provider.Settings.System.ACCELEROMETER_ROTATION; -import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; - import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; import android.net.Uri; import android.os.Handler; -import android.os.Looper; import android.provider.Settings; -import android.util.Log; -import androidx.annotation.UiThread; - -import com.android.launcher3.dagger.ApplicationContext; -import com.android.launcher3.dagger.LauncherAppSingleton; -import com.android.launcher3.dagger.LauncherBaseAppComponent; - +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; -import java.util.function.Function; - -import javax.inject.Inject; /** * ContentObserver over Settings keys that also has a caching layer. @@ -57,8 +45,7 @@ import javax.inject.Inject; * * Cache will also be updated if a key queried is missing (even if it has no listeners registered). */ -@LauncherAppSingleton -public class SettingsCache extends ContentObserver { +public class SettingsCache extends ContentObserver implements SafeCloseable { /** Hidden field Settings.Secure.NOTIFICATION_BADGING */ public static final Uri NOTIFICATION_BADGING_URI = @@ -80,31 +67,27 @@ public class SettingsCache extends ContentObserver { private static final String SYSTEM_URI_PREFIX = Settings.System.CONTENT_URI.toString(); private static final String GLOBAL_URI_PREFIX = Settings.Global.CONTENT_URI.toString(); - private final Function> mListenerMapper = uri -> { - registerUriAsync(uri); - return new CopyOnWriteArrayList<>(); - }; - /** * Caches the last seen value for registered keys. */ - private final Map mKeyCache = new ConcurrentHashMap<>(); - private final Map> mListenerMap = - new ConcurrentHashMap<>(); + private Map mKeyCache = new ConcurrentHashMap<>(); + private final Map> mListenerMap = new HashMap<>(); protected final ContentResolver mResolver; /** * Singleton instance */ - public static final DaggerSingletonObject INSTANCE = - new DaggerSingletonObject<>(LauncherBaseAppComponent::getSettingsCache); + public static MainThreadInitializedObject INSTANCE = + new MainThreadInitializedObject<>(SettingsCache::new); - @Inject - SettingsCache(@ApplicationContext Context context, DaggerSingletonTracker tracker) { - super(new Handler(Looper.getMainLooper())); + private SettingsCache(Context context) { + super(new Handler()); mResolver = context.getContentResolver(); - tracker.addCloseable(() -> - UI_HELPER_EXECUTOR.execute(() -> mResolver.unregisterContentObserver(this))); + } + + @Override + public void close() { + mResolver.unregisterContentObserver(this); } @Override @@ -112,12 +95,11 @@ public class SettingsCache extends ContentObserver { // We use default of 1, but if we're getting an onChange call, can assume a non-default // value will exist boolean newVal = updateValue(uri, 1 /* Effectively Unused */); - List listeners = mListenerMap.get(uri); - if (listeners == null) { + if (!mListenerMap.containsKey(uri)) { return; } - for (OnChangeListener listener : listeners) { + for (OnChangeListener listener : mListenerMap.get(uri)) { listener.onSettingsChanged(newVal); } } @@ -138,26 +120,23 @@ public class SettingsCache extends ContentObserver { if (mKeyCache.containsKey(keySetting)) { return mKeyCache.get(keySetting); } else { - try { - return updateValue(keySetting, defaultValue); - } catch (SecurityException e) { - Log.d("LC_SettingsCache", "Key not readable, assume false for " + keySetting.toString()); - return false; - } + return updateValue(keySetting, defaultValue); } } - private void registerUriAsync(Uri uri) { - UI_HELPER_EXECUTOR.execute(() -> mResolver.registerContentObserver(uri, false, this)); - } - /** * Does not de-dupe if you add same listeners for the same key multiple times. * Unregister once complete using {@link #unregister(Uri, OnChangeListener)} */ - @UiThread public void register(Uri uri, OnChangeListener changeListener) { - mListenerMap.computeIfAbsent(uri, mListenerMapper).add(changeListener); + if (mListenerMap.containsKey(uri)) { + mListenerMap.get(uri).add(changeListener); + } else { + CopyOnWriteArrayList l = new CopyOnWriteArrayList<>(); + l.add(changeListener); + mListenerMap.put(uri, l); + mResolver.registerContentObserver(uri, false, this); + } } private boolean updateValue(Uri keyUri, int defaultValue) { diff --git a/src/com/android/launcher3/util/ShortcutUtil.java b/src/com/android/launcher3/util/ShortcutUtil.java index 12746d8e8a..2130dbfd8d 100644 --- a/src/com/android/launcher3/util/ShortcutUtil.java +++ b/src/com/android/launcher3/util/ShortcutUtil.java @@ -54,6 +54,14 @@ public class ShortcutUtil { ? ((WorkspaceItemInfo) info).getPersonKeys() : Utilities.EMPTY_STRING_ARRAY; } + /** + * Returns true if the item is a deep shortcut. + */ + public static boolean isDeepShortcut(ItemInfo info) { + return info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT + && info instanceof WorkspaceItemInfo; + } + private static boolean isActive(ItemInfo info) { boolean isLoading = info instanceof WorkspaceItemInfo && ((WorkspaceItemInfo) info).hasPromiseIconUi(); diff --git a/src/com/android/launcher3/util/SimpleBroadcastReceiver.java b/src/com/android/launcher3/util/SimpleBroadcastReceiver.java index 2fb658510e..5ccd4d00ea 100644 --- a/src/com/android/launcher3/util/SimpleBroadcastReceiver.java +++ b/src/com/android/launcher3/util/SimpleBroadcastReceiver.java @@ -19,38 +19,20 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.os.Handler; -import android.os.Looper; import android.os.PatternMatcher; import android.text.TextUtils; -import androidx.annotation.AnyThread; -import androidx.annotation.NonNull; import androidx.annotation.Nullable; - import androidx.core.content.ContextCompat; + import java.util.function.Consumer; public class SimpleBroadcastReceiver extends BroadcastReceiver { - public static final String TAG = "SimpleBroadcastReceiver"; - // Keeps a strong reference to the context. - private final Context mContext; private final Consumer mIntentConsumer; - // Handler to register/unregister broadcast receiver - private final Handler mHandler; - - public SimpleBroadcastReceiver(@NonNull Context context, LooperExecutor looperExecutor, - Consumer intentConsumer) { - this(context, looperExecutor.getHandler(), intentConsumer); - } - - public SimpleBroadcastReceiver(@NonNull Context context, Handler handler, - Consumer intentConsumer) { - mContext = context; + public SimpleBroadcastReceiver(Consumer intentConsumer) { mIntentConsumer = intentConsumer; - mHandler = handler; } @Override @@ -58,135 +40,18 @@ public class SimpleBroadcastReceiver extends BroadcastReceiver { mIntentConsumer.accept(intent); } - /** Calls {@link #register(Runnable, String...)} with null completionCallback. */ - @AnyThread - public void register(String... actions) { - register(null, actions); + /** + * Helper method to register multiple actions + */ + public void register(Context context, String... actions) { + ContextCompat.registerReceiver(context, this, getFilter(actions), ContextCompat.RECEIVER_NOT_EXPORTED); } /** - * Calls {@link #register(Runnable, int, String...)} with null completionCallback. + * Helper method to register multiple actions associated with a paction */ - @AnyThread - public void register(int flags, String... actions) { - register(null, flags, actions); - } - - /** - * Register broadcast receiver. If this method is called on the same looper with mHandler's - * looper, then register will be called synchronously. Otherwise asynchronously. This ensures - * register happens on {@link #mHandler}'s looper. - * - * @param completionCallback callback that will be triggered after registration is completed, - * caller usually pass this callback to check if states has changed - * while registerReceiver() is executed on a binder call. - */ - @AnyThread - public void register(@Nullable Runnable completionCallback, String... actions) { - if (Looper.myLooper() == mHandler.getLooper()) { - registerInternal(mContext, completionCallback, actions); - } else { - mHandler.post(() -> registerInternal(mContext, completionCallback, actions)); - } - } - - /** Register broadcast receiver and run completion callback if passed. */ - @AnyThread - private void registerInternal( - @NonNull Context context, @Nullable Runnable completionCallback, String... actions) { - ContextCompat.registerReceiver(context, this, getFilter(actions), - ContextCompat.RECEIVER_NOT_EXPORTED); - if (completionCallback != null) { - completionCallback.run(); - } - } - - /** - * Same as {@link #register(Runnable, String...)} above but with additional flags - * params utilizine the original {@link Context}. - */ - @AnyThread - public void register(@Nullable Runnable completionCallback, int flags, String... actions) { - if (Looper.myLooper() == mHandler.getLooper()) { - registerInternal(mContext, completionCallback, flags, actions); - } else { - mHandler.post(() -> registerInternal(mContext, completionCallback, flags, actions)); - } - } - - /** Register broadcast receiver and run completion callback if passed. */ - @AnyThread - private void registerInternal( - @NonNull Context context, @Nullable Runnable completionCallback, int flags, - String... actions) { - context.registerReceiver(this, getFilter(actions), flags); - if (completionCallback != null) { - completionCallback.run(); - } - } - - /** - * Same as {@link #register(Runnable, int, String...)} above but with additional permission - * params utilizine the original {@link Context}. - */ - @AnyThread - public void register(@Nullable Runnable completionCallback, - String broadcastPermission, int flags, String... actions) { - if (Looper.myLooper() == mHandler.getLooper()) { - registerInternal(mContext, completionCallback, broadcastPermission, flags, actions); - } else { - mHandler.post(() -> registerInternal(mContext, completionCallback, broadcastPermission, - flags, actions)); - } - } - - /** Register broadcast receiver with permission and run completion callback if passed. */ - @AnyThread - private void registerInternal( - @NonNull Context context, @Nullable Runnable completionCallback, - String broadcastPermission, int flags, String... actions) { - context.registerReceiver(this, getFilter(actions), broadcastPermission, null, flags); - if (completionCallback != null) { - completionCallback.run(); - } - } - - /** Same as {@link #register(Runnable, String...)} above but with pkg name. */ - @AnyThread - public void registerPkgActions(@Nullable String pkg, String... actions) { - if (Looper.myLooper() == mHandler.getLooper()) { - mContext.registerReceiver(this, getPackageFilter(pkg, actions)); - } else { - mHandler.post(() -> { - mContext.registerReceiver(this, getPackageFilter(pkg, actions)); - }); - } - } - - /** - * Unregister broadcast receiver. If this method is called on the same looper with mHandler's - * looper, then unregister will be called synchronously. Otherwise asynchronously. This ensures - * unregister happens on {@link #mHandler}'s looper. - */ - @AnyThread - public void unregisterReceiverSafely() { - if (Looper.myLooper() == mHandler.getLooper()) { - unregisterReceiverSafelyInternal(mContext); - } else { - mHandler.post(() -> { - unregisterReceiverSafelyInternal(mContext); - }); - } - } - - /** Unregister broadcast receiver ignoring any errors. */ - @AnyThread - private void unregisterReceiverSafelyInternal(@NonNull Context context) { - try { - context.unregisterReceiver(this); - } catch (IllegalArgumentException e) { - // It was probably never registered or already unregistered. Ignore. - } + public void registerPkgActions(Context context, @Nullable String pkg, String... actions) { + ContextCompat.registerReceiver(context, this, getPackageFilter(pkg, actions), ContextCompat.RECEIVER_NOT_EXPORTED); } /** @@ -208,4 +73,15 @@ public class SimpleBroadcastReceiver extends BroadcastReceiver { } return filter; } + + /** + * Unregisters the receiver ignoring any errors + */ + public void unregisterReceiverSafely(Context context) { + try { + context.unregisterReceiver(this); + } catch (IllegalArgumentException e) { + // It was probably never registered or already unregistered. Ignore. + } + } } diff --git a/src/com/android/launcher3/util/SplitConfigurationOptions.java b/src/com/android/launcher3/util/SplitConfigurationOptions.java index e1ef77a00a..95624b1cb3 100644 --- a/src/com/android/launcher3/util/SplitConfigurationOptions.java +++ b/src/com/android/launcher3/util/SplitConfigurationOptions.java @@ -73,27 +73,7 @@ public final class SplitConfigurationOptions { */ public static final int STAGE_TYPE_SIDE = 1; - /** - * Position independent stage identifier for a given Stage - */ - public static final int STAGE_TYPE_A = 2; - /** - * Position independent stage identifier for a given Stage - */ - public static final int STAGE_TYPE_B = 3; - /** - * Position independent stage identifier for a given Stage - */ - public static final int STAGE_TYPE_C = 4; - - @IntDef({ - STAGE_TYPE_UNDEFINED, - STAGE_TYPE_MAIN, - STAGE_TYPE_SIDE, - STAGE_TYPE_A, - STAGE_TYPE_B, - STAGE_TYPE_C - }) + @IntDef({STAGE_TYPE_UNDEFINED, STAGE_TYPE_MAIN, STAGE_TYPE_SIDE}) public @interface StageType {} /////////////////////////////////// @@ -127,10 +107,10 @@ public final class SplitConfigurationOptions { /** This rect represents the actual gap between the two apps */ public final Rect visualDividerBounds; // This class is orientation-agnostic, so we compute both for later use - private final float topTaskPercent; - private final float leftTaskPercent; - private final float dividerWidthPercent; - private final float dividerHeightPercent; + public final float topTaskPercent; + public final float leftTaskPercent; + public final float dividerWidthPercent; + public final float dividerHeightPercent; public final int snapPosition; /** @@ -190,39 +170,6 @@ public final class SplitConfigurationOptions { dividerHeightPercent = visualDividerBounds.height() / totalHeight; } - /** - * Returns the percentage size of the left/top task (compared to the full width/height of - * the split pair). E.g. if the left task is 4 units wide, the divider is 2 units, and the - * right task is 4 units, this method will return 0.4f. - */ - public float getLeftTopTaskPercent() { - // topTaskPercent and leftTaskPercent are defined at creation time, and are not updated - // on device rotate, so we have to check appsStackedVertically to return the right - // creation-time measurements. - return appsStackedVertically ? topTaskPercent : leftTaskPercent; - } - - /** - * Returns the percentage size of the divider's thickness (compared to the full width/height - * of the split pair). E.g. if the left task is 4 units wide, the divider is 2 units, and - * the right task is 4 units, this method will return 0.2f. - */ - public float getDividerPercent() { - // dividerHeightPercent and dividerWidthPercent are defined at creation time, and are - // not updated on device rotate, so we have to check appsStackedVertically to return - // the right creation-time measurements. - return appsStackedVertically ? dividerHeightPercent : dividerWidthPercent; - } - - /** - * Returns the percentage size of the right/bottom task (compared to the full width/height - * of the split pair). E.g. if the left task is 4 units wide, the divider is 2 units, and - * the right task is 4 units, this method will return 0.4f. - */ - public float getRightBottomTaskPercent() { - return 1 - (getLeftTopTaskPercent() + getDividerPercent()); - } - @Override public String toString() { return "LeftTop: " + leftTopBounds + ", taskId: " + leftTopTaskId + "\n" @@ -239,6 +186,12 @@ public final class SplitConfigurationOptions { public int stagePosition = STAGE_POSITION_UNDEFINED; @StageType public int stageType = STAGE_TYPE_UNDEFINED; + + @Override + public String toString() { + return "SplitStageInfo { taskId=" + taskId + + ", stagePosition=" + stagePosition + ", stageType=" + stageType + " }"; + } } public static StatsLogManager.EventEnum getLogEventForPosition(@StagePosition int position) { @@ -264,7 +217,7 @@ public final class SplitConfigurationOptions { private Drawable drawable; public final Intent intent; public final SplitPositionOption position; - private ItemInfo itemInfo; + public final ItemInfo itemInfo; public final StatsLogManager.EventEnum splitEvent; /** Represents the taskId of the first app to start in split screen */ public int alreadyRunningTaskId = INVALID_TASK_ID; @@ -292,9 +245,5 @@ public final class SplitConfigurationOptions { public View getView() { return view; } - - public ItemInfo getItemInfo() { - return itemInfo; - } } } diff --git a/src/com/android/launcher3/util/StableViewInfo.kt b/src/com/android/launcher3/util/StableViewInfo.kt index 29dcd59711..34e3fc663f 100644 --- a/src/com/android/launcher3/util/StableViewInfo.kt +++ b/src/com/android/launcher3/util/StableViewInfo.kt @@ -25,9 +25,9 @@ data class StableViewInfo(val itemId: Int, val containerId: Int, val stableId: A fun matches(info: ItemInfo?) = info != null && - itemId == info.id && - containerId == info.container && - stableId == info.stableId + itemId == info.id && + containerId == info.container && + stableId == info.stableId companion object { @@ -53,4 +53,4 @@ data class StableViewInfo(val itemId: Int, val containerId: Int, val stableId: A fun fromLaunchCookies(launchCookies: List) = launchCookies.firstNotNullOfOrNull { ObjectWrapper.unwrap(it) } } -} +} \ No newline at end of file diff --git a/src/com/android/launcher3/util/SystemUiController.java b/src/com/android/launcher3/util/SystemUiController.java index 368b267ab6..df54fd7db9 100644 --- a/src/com/android/launcher3/util/SystemUiController.java +++ b/src/com/android/launcher3/util/SystemUiController.java @@ -17,6 +17,7 @@ package com.android.launcher3.util; import android.view.View; +import android.view.Window; import androidx.annotation.IntDef; @@ -53,11 +54,11 @@ public class SystemUiController { }) public @interface SystemUiControllerFlags {} - private final View mView; + private final Window mWindow; private final int[] mStates = new int[5]; - public SystemUiController(View view) { - mView = view; + public SystemUiController(Window window) { + mWindow = window; } public void updateUiState(int uiState, boolean isLight) { @@ -71,14 +72,14 @@ public class SystemUiController { } mStates[uiState] = flags; - int oldFlags = mView.getSystemUiVisibility(); + int oldFlags = mWindow.getDecorView().getSystemUiVisibility(); // Apply the state flags in priority order int newFlags = oldFlags; for (int stateFlag : mStates) { newFlags = getSysUiVisibilityFlags(stateFlag, newFlags); } if (newFlags != oldFlags) { - mView.setSystemUiVisibility(newFlags); + mWindow.getDecorView().setSystemUiVisibility(newFlags); } } @@ -87,7 +88,7 @@ public class SystemUiController { */ public int getBaseSysuiVisibility() { return getSysUiVisibilityFlags( - mStates[UI_STATE_BASE_WINDOW], mView.getSystemUiVisibility()); + mStates[UI_STATE_BASE_WINDOW], mWindow.getDecorView().getSystemUiVisibility()); } private int getSysUiVisibilityFlags(int stateFlag, int currentVisibility) { diff --git a/src/com/android/launcher3/util/Themes.java b/src/com/android/launcher3/util/Themes.java index 2723593ec5..4a1d1ee341 100644 --- a/src/com/android/launcher3/util/Themes.java +++ b/src/com/android/launcher3/util/Themes.java @@ -19,6 +19,8 @@ package com.android.launcher3.util; import static app.lawnchair.wallpaper.WallpaperColorsCompat.HINT_SUPPORTS_DARK_TEXT; import static app.lawnchair.wallpaper.WallpaperColorsCompat.HINT_SUPPORTS_DARK_THEME; +import static com.android.launcher3.LauncherPrefs.THEMED_ICONS; + import android.content.Context; import android.content.res.TypedArray; import android.graphics.Color; @@ -31,12 +33,12 @@ import android.view.ContextThemeWrapper; import androidx.annotation.ColorInt; +import com.android.launcher3.LauncherPrefs; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.icons.GraphicsUtils; -import com.android.launcher3.views.ActivityContext; - import com.patrykmichalik.opto.core.PreferenceExtensionsKt; + import app.lawnchair.preferences.PreferenceManager; import app.lawnchair.preferences2.PreferenceManager2; import app.lawnchair.theme.color.ColorMode; @@ -44,6 +46,7 @@ import app.lawnchair.theme.color.tokens.ColorTokens; import app.lawnchair.wallpaper.WallpaperColorsCompat; import app.lawnchair.wallpaper.WallpaperManagerCompat; import app.lawnchair.ui.theme.ColorKt; +import com.android.launcher3.views.ActivityContext; /** * Various utility methods associated with theming. @@ -51,7 +54,12 @@ import app.lawnchair.ui.theme.ColorKt; @SuppressWarnings("NewApi") public class Themes { - /** Gets the WallpaperColorHints and then uses those to get the correct activity theme res. */ + public static final String KEY_THEMED_ICONS = "themed_icons"; + + /** + * Gets the WallpaperColorHints and then uses those to get the correct activity + * theme res. + */ public static int getActivityThemeRes(Context context) { WallpaperColorsCompat colors = WallpaperManagerCompat.INSTANCE.get(context).getWallpaperColors(); final int colorHints = colors != null ? colors.getColorHints() : 0; @@ -102,7 +110,7 @@ public class Themes { public static String getDefaultBodyFont(Context context) { TypedArray ta = context.obtainStyledAttributes(android.R.style.TextAppearance_DeviceDefault, - new int[]{android.R.attr.fontFamily}); + new int[] { android.R.attr.fontFamily }); String value = ta.getString(0); ta.recycle(); return value; @@ -113,7 +121,7 @@ public class Themes { } public static float getDimension(Context context, int attr, float defaultValue) { - TypedArray ta = context.obtainStyledAttributes(new int[]{attr}); + TypedArray ta = context.obtainStyledAttributes(new int[] { attr }); float value = ta.getDimension(0, defaultValue); ta.recycle(); return value; @@ -138,36 +146,38 @@ public class Themes { } public static boolean getAttrBoolean(Context context, int attr) { - TypedArray ta = context.obtainStyledAttributes(new int[]{attr}); + TypedArray ta = context.obtainStyledAttributes(new int[] { attr }); boolean value = ta.getBoolean(0, false); ta.recycle(); return value; } public static Drawable getAttrDrawable(Context context, int attr) { - TypedArray ta = context.obtainStyledAttributes(new int[]{attr}); + TypedArray ta = context.obtainStyledAttributes(new int[] { attr }); Drawable value = ta.getDrawable(0); ta.recycle(); return value; } public static int getAttrInteger(Context context, int attr) { - TypedArray ta = context.obtainStyledAttributes(new int[]{attr}); + TypedArray ta = context.obtainStyledAttributes(new int[] { attr }); int value = ta.getInteger(0, 0); ta.recycle(); return value; } /** - * Scales a color matrix such that, when applied to color R G B A, it produces R' G' B' A' where + * Scales a color matrix such that, when applied to color R G B A, it produces + * R' G' B' A' where * R' = r * R * G' = g * G * B' = b * B * A' = a * A * - * The matrix will, for instance, turn white into r g b a, and black will remain black. + * The matrix will, for instance, turn white into r g b a, and black will remain + * black. * - * @param color The color r g b a + * @param color The color r g b a * @param target The ColorMatrix to scale */ public static void setColorScaleOnMatrix(int color, ColorMatrix target) { @@ -176,15 +186,18 @@ public class Themes { } /** - * Changes a color matrix such that, when applied to srcColor, it produces dstColor. + * Changes a color matrix such that, when applied to srcColor, it produces + * dstColor. * - * Note that values on the last column of target ColorMatrix can be negative, and may result in - * negative values when applied on a color. Such negative values will be automatically shifted + * Note that values on the last column of target ColorMatrix can be negative, + * and may result in + * negative values when applied on a color. Such negative values will be + * automatically shifted * up to 0 by the framework. * * @param srcColor The color to start from * @param dstColor The color to create by applying target on srcColor - * @param target The ColorMatrix to transform the color + * @param target The ColorMatrix to transform the color */ public static void setColorChangeOnMatrix(int srcColor, int dstColor, ColorMatrix target) { target.reset(); @@ -195,7 +208,8 @@ public class Themes { } /** - * Creates a map for attribute-name to value for all the values in {@param attrs} which can be + * Creates a map for attribute-name to value for all the values in + * {@param attrs} which can be * held in memory for later use. */ public static SparseArray createValueMap(Context context, AttributeSet attrSet, @@ -219,7 +233,10 @@ public class Themes { return result; } - /** Returns the desired navigation bar scrim color depending on the {@code DeviceProfile}. */ + /** + * Returns the desired navigation bar scrim color depending on the + * {@code DeviceProfile}. + */ @ColorInt public static int getNavBarScrimColor(T context) { return context.getDeviceProfile().isTaskbarPresent diff --git a/src/com/android/launcher3/util/VibratorWrapper.java b/src/com/android/launcher3/util/VibratorWrapper.java index 5caa18abc1..e1752ca55a 100644 --- a/src/com/android/launcher3/util/VibratorWrapper.java +++ b/src/com/android/launcher3/util/VibratorWrapper.java @@ -20,88 +20,173 @@ import static android.os.VibrationEffect.createOneShot; import static android.os.VibrationEffect.createPredefined; import static android.provider.Settings.System.HAPTIC_FEEDBACK_ENABLED; -import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import android.annotation.SuppressLint; import android.content.Context; import android.media.AudioAttributes; import android.net.Uri; +import android.os.SystemClock; import android.os.VibrationEffect; import android.os.Vibrator; import android.provider.Settings; -import androidx.annotation.VisibleForTesting; - -import com.android.launcher3.dagger.ApplicationContext; -import com.android.launcher3.dagger.LauncherAppSingleton; -import com.android.launcher3.dagger.LauncherBaseAppComponent; - -import javax.inject.Inject; +import androidx.annotation.Nullable; import com.android.launcher3.Utilities; /** - * Wrapper around {@link Vibrator} to easily perform haptic feedback where necessary. + * Wrapper around {@link Vibrator} to easily perform haptic feedback where + * necessary. */ -@LauncherAppSingleton -public class VibratorWrapper { +public class VibratorWrapper implements SafeCloseable { - public static final DaggerSingletonObject INSTANCE = - new DaggerSingletonObject<>(LauncherBaseAppComponent::getVibratorWrapper); + public static final MainThreadInitializedObject INSTANCE = new MainThreadInitializedObject<>( + VibratorWrapper::new); public static final AudioAttributes VIBRATION_ATTRS = new AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) .build(); - public static final VibrationEffect EFFECT_CLICK = - Utilities.ATLEAST_Q ? createPredefined(VibrationEffect.EFFECT_CLICK) : createOneShot(1000L, VibrationEffect.DEFAULT_AMPLITUDE); - @VisibleForTesting - static final Uri HAPTIC_FEEDBACK_URI = Settings.System.getUriFor(HAPTIC_FEEDBACK_ENABLED); + public static final VibrationEffect EFFECT_CLICK = Utilities.ATLEAST_Q ? createPredefined(VibrationEffect.EFFECT_CLICK) : createOneShot(1000L, VibrationEffect.DEFAULT_AMPLITUDE); + private static final Uri HAPTIC_FEEDBACK_URI = Settings.System.getUriFor(HAPTIC_FEEDBACK_ENABLED); - @VisibleForTesting static final float LOW_TICK_SCALE = 0.9f; + private static final float LOW_TICK_SCALE = 0.9f; + private static final float DRAG_TEXTURE_SCALE = 0.03f; + private static final float DRAG_COMMIT_SCALE = 0.5f; + private static final float DRAG_BUMP_SCALE = 0.4f; + private static final int DRAG_TEXTURE_EFFECT_SIZE = 200; + + @Nullable + private final VibrationEffect mDragEffect; + @Nullable + private final VibrationEffect mCommitEffect; + @Nullable + private final VibrationEffect mBumpEffect; + + private long mLastDragTime; + private final int mThresholdUntilNextDragCallMillis; /** * Haptic when entering overview. */ public static final VibrationEffect OVERVIEW_HAPTIC = EFFECT_CLICK; + private final Context mContext; private final Vibrator mVibrator; private final boolean mHasVibrator; - - @VisibleForTesting - final SettingsCache.OnChangeListener mHapticChangeListener = - isEnabled -> mIsHapticFeedbackEnabled = isEnabled; + private final SettingsCache.OnChangeListener mHapticChangeListener = isEnabled -> mIsHapticFeedbackEnabled = isEnabled; private boolean mIsHapticFeedbackEnabled; - @Inject - public VibratorWrapper(@ApplicationContext Context context, SettingsCache settingsCache, - DaggerSingletonTracker tracker) { - + private VibratorWrapper(Context context) { + mContext = context; mVibrator = context.getSystemService(Vibrator.class); mHasVibrator = mVibrator.hasVibrator(); if (mHasVibrator) { - MAIN_EXECUTOR.execute( - () -> settingsCache.register(HAPTIC_FEEDBACK_URI, mHapticChangeListener)); - mIsHapticFeedbackEnabled = settingsCache.getValue(HAPTIC_FEEDBACK_URI, 0); - tracker.addCloseable( - () -> settingsCache.unregister(HAPTIC_FEEDBACK_URI, mHapticChangeListener)); + SettingsCache cache = SettingsCache.INSTANCE.get(mContext); + cache.register(HAPTIC_FEEDBACK_URI, mHapticChangeListener); + mIsHapticFeedbackEnabled = cache.getValue(HAPTIC_FEEDBACK_URI, 0); } else { mIsHapticFeedbackEnabled = false; } + + if (Utilities.ATLEAST_S && mVibrator.areAllPrimitivesSupported( + PRIMITIVE_LOW_TICK)) { + + // Drag texture, Commit, and Bump should only be used for premium phones. + // Before using these haptics make sure check if the device can use it + VibrationEffect.Composition dragEffect = VibrationEffect.startComposition(); + for (int i = 0; i < DRAG_TEXTURE_EFFECT_SIZE; i++) { + dragEffect.addPrimitive( + PRIMITIVE_LOW_TICK, DRAG_TEXTURE_SCALE); + } + mDragEffect = dragEffect.compose(); + mCommitEffect = VibrationEffect.startComposition().addPrimitive( + VibrationEffect.Composition.PRIMITIVE_TICK, DRAG_COMMIT_SCALE).compose(); + mBumpEffect = VibrationEffect.startComposition().addPrimitive( + PRIMITIVE_LOW_TICK, DRAG_BUMP_SCALE).compose(); + int primitiveDuration = mVibrator.getPrimitiveDurations( + PRIMITIVE_LOW_TICK)[0]; + + mThresholdUntilNextDragCallMillis = DRAG_TEXTURE_EFFECT_SIZE * primitiveDuration + 100; + } else { + mDragEffect = null; + mCommitEffect = null; + mBumpEffect = null; + mThresholdUntilNextDragCallMillis = 0; + } + } + + @Override + public void close() { + if (mHasVibrator) { + SettingsCache.INSTANCE.get(mContext) + .unregister(HAPTIC_FEEDBACK_URI, mHapticChangeListener); + } } /** - * This should be used to cancel a haptic in case where the haptic shouldn't be vibrating. For - * example, when no animation is happening but a vibrator happens to be vibrating still. + * This is called when the user swipes to/from all apps. This is meant to be + * used in between + * long animation progresses so that it gives a dragging texture effect. For a + * better + * experience, this should be used in combination with vibrateForDragCommit(). + */ + public void vibrateForDragTexture() { + if (mDragEffect == null) { + return; + } + long currentTime = SystemClock.elapsedRealtime(); + long elapsedTimeSinceDrag = currentTime - mLastDragTime; + if (elapsedTimeSinceDrag >= mThresholdUntilNextDragCallMillis) { + vibrate(mDragEffect); + mLastDragTime = currentTime; + } + } + + /** + * This is used when user reaches the commit threshold when swiping to/from from + * all apps. + */ + public void vibrateForDragCommit() { + if (mCommitEffect != null) { + vibrate(mCommitEffect); + } + // resetting dragTexture timestamp to be able to play dragTexture again + mLastDragTime = 0; + } + + /** + * The bump haptic is used to be called at the end of a swipe and only if it the + * gesture is a + * FLING going to/from all apps. Client can just call this method elsewhere just + * for the + * effect. + */ + public void vibrateForDragBump() { + if (mBumpEffect != null) { + vibrate(mBumpEffect); + } + } + + /** + * This should be used to cancel a haptic in case where the haptic shouldn't be + * vibrating. For + * example, when no animation is happening but a vibrator happens to be + * vibrating still. */ public void cancelVibrate() { UI_HELPER_EXECUTOR.execute(mVibrator::cancel); + // reset dragTexture timestamp to be able to play dragTexture again whenever + // cancelled + mLastDragTime = 0; } - /** Vibrates with the given effect if haptic feedback is available and enabled. */ + /** + * Vibrates with the given effect if haptic feedback is available and enabled. + */ public void vibrate(VibrationEffect vibrationEffect) { if (mHasVibrator && mIsHapticFeedbackEnabled) { UI_HELPER_EXECUTOR.execute(() -> mVibrator.vibrate(vibrationEffect, VIBRATION_ATTRS)); @@ -109,7 +194,8 @@ public class VibratorWrapper { } /** - * Vibrates with a single primitive, if supported, or use a fallback effect instead. This only + * Vibrates with a single primitive, if supported, or use a fallback effect + * instead. This only * vibrates if haptic feedback is available and enabled. */ @SuppressLint("NewApi") diff --git a/src/com/android/launcher3/util/ViewCache.java b/src/com/android/launcher3/util/ViewCache.java index b98e977dd4..98e6822542 100644 --- a/src/com/android/launcher3/util/ViewCache.java +++ b/src/com/android/launcher3/util/ViewCache.java @@ -21,8 +21,6 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import androidx.annotation.VisibleForTesting; - import com.android.launcher3.R; /** @@ -69,8 +67,7 @@ public class ViewCache { } } - @VisibleForTesting - static class CacheEntry { + private static class CacheEntry { final int mMaxSize; final View[] mViews; diff --git a/src/com/android/launcher3/util/ViewOnDrawExecutor.java b/src/com/android/launcher3/util/ViewOnDrawExecutor.java index dad76299b7..26bfd36dcf 100644 --- a/src/com/android/launcher3/util/ViewOnDrawExecutor.java +++ b/src/com/android/launcher3/util/ViewOnDrawExecutor.java @@ -16,7 +16,6 @@ package com.android.launcher3.util; -import android.util.Log; import android.view.View; import android.view.View.OnAttachStateChangeListener; import android.view.ViewTreeObserver.OnDrawListener; @@ -33,7 +32,6 @@ import java.util.function.Consumer; public class ViewOnDrawExecutor implements OnDrawListener, Runnable, OnAttachStateChangeListener { - private static final String TAG = "ViewOnDrawExecutor"; private final RunnableList mTasks; private final Consumer mOnClearCallback; private View mAttachedView; @@ -90,10 +88,7 @@ public class ViewOnDrawExecutor implements OnDrawListener, Runnable, * Executes all tasks immediately */ public void markCompleted() { - if (mCancelled) { - Log.d(TAG, "markCompleted ignored: cancelled"); - } else { - Log.d(TAG, "markCompleted: executing tasks"); + if (!mCancelled) { mTasks.executeAllAndDestroy(); } mCompleted = true; @@ -106,7 +101,6 @@ public class ViewOnDrawExecutor implements OnDrawListener, Runnable, } public void cancel() { - Log.d(TAG, "Cancelling tasks"); mCancelled = true; markCompleted(); } diff --git a/src/com/android/launcher3/util/ViewPool.java b/src/com/android/launcher3/util/ViewPool.java index 16270576da..e413d7ff9c 100644 --- a/src/com/android/launcher3/util/ViewPool.java +++ b/src/com/android/launcher3/util/ViewPool.java @@ -17,7 +17,6 @@ package com.android.launcher3.util; import android.content.Context; import android.os.Handler; -import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -25,7 +24,6 @@ import android.view.ViewGroup; import androidx.annotation.AnyThread; import androidx.annotation.Nullable; import androidx.annotation.UiThread; -import androidx.annotation.VisibleForTesting; import com.android.launcher3.util.ViewPool.Reusable; @@ -34,7 +32,6 @@ import com.android.launcher3.util.ViewPool.Reusable; * During initialization, views are inflated on the background thread. */ public class ViewPool { - private static final String TAG = ViewPool.class.getSimpleName(); private final Object[] mPool; @@ -44,21 +41,11 @@ public class ViewPool { private int mCurrentSize = 0; - @Nullable - private Thread mViewPoolInitThread; - public ViewPool(Context context, @Nullable ViewGroup parent, int layoutId, int maxSize, int initialSize) { - this(LayoutInflater.from(context).cloneInContext(context), - parent, layoutId, maxSize, initialSize); - } - - @VisibleForTesting - ViewPool(LayoutInflater inflater, @Nullable ViewGroup parent, - int layoutId, int maxSize, int initialSize) { mLayoutId = layoutId; mParent = parent; - mInflater = inflater; + mInflater = LayoutInflater.from(context); mPool = new Object[maxSize]; if (initialSize > 0) { @@ -77,15 +64,12 @@ public class ViewPool { // Inflate views on a non looper thread. This allows us to catch errors like calling // "new Handler()" in constructor easily. - mViewPoolInitThread = new Thread(() -> { + new Thread(() -> { for (int i = 0; i < initialSize; i++) { T view = inflateNewView(inflater); handler.post(() -> addToPool(view)); } - Log.d(TAG, "initPool complete"); - mViewPoolInitThread = null; - }, "ViewPool-init"); - mViewPoolInitThread.start(); + }, "ViewPool-init").start(); } @UiThread @@ -122,12 +106,6 @@ public class ViewPool { return (T) inflater.inflate(mLayoutId, mParent, false); } - public void killOngoingInitializations() throws InterruptedException { - if (mViewPoolInitThread != null) { - mViewPoolInitThread.join(); - } - } - /** * Interface to indicate that a view is reusable */ diff --git a/src/com/android/launcher3/util/WallpaperColorHints.kt b/src/com/android/launcher3/util/WallpaperColorHints.kt index 29fe31ae3d..1361c1ed21 100644 --- a/src/com/android/launcher3/util/WallpaperColorHints.kt +++ b/src/com/android/launcher3/util/WallpaperColorHints.kt @@ -23,50 +23,47 @@ import android.app.WallpaperManager.OnColorsChangedListener import android.content.Context import androidx.annotation.MainThread import androidx.annotation.VisibleForTesting -import com.android.launcher3.dagger.ApplicationContext -import com.android.launcher3.dagger.LauncherAppComponent -import com.android.launcher3.dagger.LauncherAppSingleton +import com.android.launcher3.Utilities import com.android.launcher3.util.Executors.MAIN_EXECUTOR import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR -import javax.inject.Inject /** * This class caches the system's wallpaper color hints for use by other classes as a performance * enhancer. It also centralizes all the WallpaperManager color hint code in one location. */ -@LauncherAppSingleton -class WallpaperColorHints -@Inject -constructor(@ApplicationContext private val context: Context, tracker: DaggerSingletonTracker) { +class WallpaperColorHints(private val context: Context) : SafeCloseable { var hints: Int = 0 private set - private val wallpaperManager get() = context.getSystemService(WallpaperManager::class.java)!! - private val onColorHintsChangedListeners = mutableListOf() + private val onClose: SafeCloseable init { - hints = wallpaperManager.getWallpaperColors(FLAG_SYSTEM)?.colorHints ?: 0 - val onColorsChangedListener = OnColorsChangedListener { colors, which -> - onColorsChanged(colors, which) - } - UI_HELPER_EXECUTOR.execute { - wallpaperManager.addOnColorsChangedListener( - onColorsChangedListener, - MAIN_EXECUTOR.handler, - ) - } - tracker.addCloseable { - UI_HELPER_EXECUTOR.execute { - wallpaperManager.removeOnColorsChangedListener(onColorsChangedListener) + if (Utilities.ATLEAST_S) { + hints = wallpaperManager.getWallpaperColors(FLAG_SYSTEM)?.colorHints ?: 0 + val onColorsChangedListener = OnColorsChangedListener { colors, which -> + onColorsChanged(colors, which) } + UI_HELPER_EXECUTOR.execute { + wallpaperManager.addOnColorsChangedListener( + onColorsChangedListener, + MAIN_EXECUTOR.handler + ) + } + onClose = SafeCloseable { + UI_HELPER_EXECUTOR.execute { + wallpaperManager.removeOnColorsChangedListener(onColorsChangedListener) + } + } + } else { + onClose = SafeCloseable {} } } @MainThread private fun onColorsChanged(colors: WallpaperColors?, which: Int) { - if ((which and FLAG_SYSTEM) != 0) { + if ((which and FLAG_SYSTEM) != 0 && Utilities.ATLEAST_S) { val newHints = colors?.colorHints ?: 0 if (newHints != hints) { hints = newHints @@ -75,6 +72,8 @@ constructor(@ApplicationContext private val context: Context, tracker: DaggerSin } } + override fun close() = onClose.close() + fun registerOnColorHintsChangedListener(listener: OnColorHintListener) { onColorHintsChangedListeners.add(listener) } @@ -86,8 +85,7 @@ constructor(@ApplicationContext private val context: Context, tracker: DaggerSin companion object { @VisibleForTesting @JvmField - val INSTANCE = DaggerSingletonObject(LauncherAppComponent::getWallpaperColorHints) - + val INSTANCE = MainThreadInitializedObject { WallpaperColorHints(it) } @JvmStatic fun get(context: Context): WallpaperColorHints = INSTANCE.get(context) } } diff --git a/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java b/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java index db7d647520..a16eba70e7 100644 --- a/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java +++ b/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java @@ -29,10 +29,12 @@ public class WallpaperOffsetInterpolator { private static final String TAG = "WPOffsetInterpolator"; private static final int ANIMATION_DURATION = 250; - // Don't use all the wallpaper for parallax until you have at least this many pages + // Don't use all the wallpaper for parallax until you have at least this many + // pages private static final int MIN_PARALLAX_PAGE_SPAN = 4; - private final SimpleBroadcastReceiver mWallpaperChangeReceiver; + private final SimpleBroadcastReceiver mWallpaperChangeReceiver = new SimpleBroadcastReceiver( + i -> onWallpaperChanged()); private final Workspace mWorkspace; private final boolean mIsRtl; private final Handler mHandler; @@ -48,8 +50,6 @@ public class WallpaperOffsetInterpolator { public WallpaperOffsetInterpolator(Workspace workspace) { mWorkspace = workspace; - mWallpaperChangeReceiver = new SimpleBroadcastReceiver( - workspace.getContext(), UI_HELPER_EXECUTOR, i -> onWallpaperChanged()); mIsRtl = Utilities.isRtl(workspace.getResources()); mHandler = new OffsetHandler(workspace.getContext()); prefs = PreferenceManager.getInstance(workspace.getContext()); @@ -69,36 +69,42 @@ public class WallpaperOffsetInterpolator { /** * Computes the wallpaper offset as an int ratio (out[0] / out[1]) * - * TODO: do different behavior if it's a live wallpaper? + * TODO: do different behavior if it's a live wallpaper? */ private void wallpaperOffsetForScroll(int scroll, int numScrollableScreens, final int[] out) { out[1] = 1; - // To match the default wallpaper behavior in the system, we default to either the left + // To match the default wallpaper behavior in the system, we default to either + // the left // or right edge on initialization if (!prefs.getWallpaperScrolling().get() || mLockedToDefaultPage || numScrollableScreens <= 1) { out[0] = mIsRtl ? 1 : 0; return; } - // Distribute the wallpaper parallax over a minimum of MIN_PARALLAX_PAGE_SPAN workspace - // screens, not including the custom screen, and empty screens (if > MIN_PARALLAX_PAGE_SPAN) - int numScreensForWallpaperParallax = mWallpaperIsLiveWallpaper ? numScrollableScreens : - Math.max(MIN_PARALLAX_PAGE_SPAN, numScrollableScreens); + // Distribute the wallpaper parallax over a minimum of MIN_PARALLAX_PAGE_SPAN + // workspace + // screens, not including the custom screen, and empty screens (if > + // MIN_PARALLAX_PAGE_SPAN) + int numScreensForWallpaperParallax = mWallpaperIsLiveWallpaper ? numScrollableScreens + : Math.max(MIN_PARALLAX_PAGE_SPAN, numScrollableScreens); // Offset by the custom screen - // Don't confuse screens & pages in this function. In a phone UI, we often use screens & - // pages interchangeably. However, in a n-panels UI, where n > 1, the screen in this class + // Don't confuse screens & pages in this function. In a phone UI, we often use + // screens & + // pages interchangeably. However, in a n-panels UI, where n > 1, the screen in + // this class // means the scrollable screen. Each screen can consist of at most n panels. - // Each panel has at most 1 page. Take 5 pages in 2 panels UI as an example, the Workspace + // Each panel has at most 1 page. Take 5 pages in 2 panels UI as an example, the + // Workspace // looks as follow: // // S: scrollable screen, P: page, : empty - // S0 S1 S2 - // _______ _______ ________ - // |P0|P1| |P2|P3| |P4|| - // ¯¯¯¯¯¯¯ ¯¯¯¯¯¯¯ ¯¯¯¯¯¯¯¯ + // S0 S1 S2 + // _______ _______ ________ + // |P0|P1| |P2|P3| |P4|| + // ¯¯¯¯¯¯¯ ¯¯¯¯¯¯¯ ¯¯¯¯¯¯¯¯ int endIndex = getNumPagesExcludingEmpty() - 1; final int leftPageIndex = mIsRtl ? endIndex : 0; final int rightPageIndex = mIsRtl ? 0 : endIndex; @@ -112,14 +118,16 @@ public class WallpaperOffsetInterpolator { return; } - // Sometimes the left parameter of the pages is animated during a layout transition; + // Sometimes the left parameter of the pages is animated during a layout + // transition; // this parameter offsets it to keep the wallpaper from animating as well int adjustedScroll = scroll - leftPageScrollX - mWorkspace.getLayoutTransitionOffsetForPage(0); adjustedScroll = Utilities.boundToRange(adjustedScroll, 0, scrollRange); out[1] = (numScreensForWallpaperParallax - 1) * scrollRange; - // The offset is now distributed 0..1 between the left and right pages that we care about, + // The offset is now distributed 0..1 between the left and right pages that we + // care about, // so we just map that between the pages that we are using for parallax int rtlOffset = 0; if (mIsRtl) { @@ -137,11 +145,16 @@ public class WallpaperOffsetInterpolator { /** * Returns the number of screens that can be scrolled. * - *

In an usual phone UI, the number of scrollable screens is equal to the number of + *

+ * In an usual phone UI, the number of scrollable screens is equal to the number + * of * CellLayouts because each screen has exactly 1 CellLayout. * - *

In a n-panels UI, a screen shows n panels. Each panel has at most 1 CellLayout. Take - * 2-panels UI as an example: let's say there are 5 CellLayouts in the Workspace. the number of + *

+ * In a n-panels UI, a screen shows n panels. Each panel has at most 1 + * CellLayout. Take + * 2-panels UI as an example: let's say there are 5 CellLayouts in the + * Workspace. the number of * scrollable screens will be 3 = ⌈5 / 2⌉. */ private int getNumScrollableScreensExcludingEmpty() { @@ -152,8 +165,11 @@ public class WallpaperOffsetInterpolator { /** * Returns the number of non-empty pages in the Workspace. * - *

If a user starts dragging on the rightmost (or leftmost in RTL), an empty CellLayout is - * added to the Workspace. This empty CellLayout add as a hover-over target for adding a new + *

+ * If a user starts dragging on the rightmost (or leftmost in RTL), an empty + * CellLayout is + * added to the Workspace. This empty CellLayout add as a hover-over target for + * adding a new * page. To avoid janky motion effect, we ignore this empty CellLayout. */ private int getNumPagesExcludingEmpty() { @@ -203,10 +219,10 @@ public class WallpaperOffsetInterpolator { public void setWindowToken(IBinder token) { mWindowToken = token; if (mWindowToken == null && mRegistered) { - mWallpaperChangeReceiver.unregisterReceiverSafely(); + mWallpaperChangeReceiver.unregisterReceiverSafely(mWorkspace.getContext()); mRegistered = false; } else if (mWindowToken != null && !mRegistered) { - mWallpaperChangeReceiver.register(ACTION_WALLPAPER_CHANGED); + mWallpaperChangeReceiver.register(mWorkspace.getContext(), ACTION_WALLPAPER_CHANGED); onWallpaperChanged(); mRegistered = true; } @@ -214,7 +230,8 @@ public class WallpaperOffsetInterpolator { private void onWallpaperChanged() { UI_HELPER_EXECUTOR.execute(() -> { - // Updating the boolean on a background thread is fine as the assignments are atomic + // Updating the boolean on a background thread is fine as the assignments are + // atomic mWallpaperIsLiveWallpaper = WallpaperManager.getInstance(mWorkspace.getContext()) .getWallpaperInfo() != null; updateOffset(); diff --git a/src/com/android/launcher3/util/rects/Rects.kt b/src/com/android/launcher3/util/rects/Rects.kt index 2f1942a97e..1e6d7174cd 100644 --- a/src/com/android/launcher3/util/rects/Rects.kt +++ b/src/com/android/launcher3/util/rects/Rects.kt @@ -18,24 +18,6 @@ package com.android.launcher3.util.rects import android.graphics.Rect import android.view.View -import com.android.launcher3.Utilities - -/** - * Linearly interpolate between two rectangles. The result is stored in the rect the function is - * called on. - * - * @param start the starting rectangle - * @param end the ending rectangle - * @param t the interpolation factor, where 0 is the start and 1 is the end - */ -fun Rect.lerpRect(start: Rect, end: Rect, t: Float) { - set( - Utilities.mapRange(t, start.left.toFloat(), end.left.toFloat()).toInt(), - Utilities.mapRange(t, start.top.toFloat(), end.top.toFloat()).toInt(), - Utilities.mapRange(t, start.right.toFloat(), end.right.toFloat()).toInt(), - Utilities.mapRange(t, start.bottom.toFloat(), end.bottom.toFloat()).toInt(), - ) -} /** Copy the coordinates of the [view] relative to its parent into this rectangle. */ fun Rect.set(view: View) { diff --git a/src/com/android/launcher3/util/window/RefreshRateTracker.java b/src/com/android/launcher3/util/window/RefreshRateTracker.java new file mode 100644 index 0000000000..7814617b9e --- /dev/null +++ b/src/com/android/launcher3/util/window/RefreshRateTracker.java @@ -0,0 +1,83 @@ +/* + * 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. + */ +package com.android.launcher3.util.window; + +import static android.view.Display.DEFAULT_DISPLAY; + +import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; + +import android.content.Context; +import android.hardware.display.DisplayManager; +import android.hardware.display.DisplayManager.DisplayListener; +import android.view.Display; + +import androidx.annotation.WorkerThread; + +import com.android.launcher3.util.MainThreadInitializedObject; +import com.android.launcher3.util.SafeCloseable; + +/** + * Utility class to track refresh rate of the current device + */ +public class RefreshRateTracker implements DisplayListener, SafeCloseable { + + private static final MainThreadInitializedObject INSTANCE = + new MainThreadInitializedObject<>(RefreshRateTracker::new); + + private int mSingleFrameMs = 1; + + private final DisplayManager mDM; + + private RefreshRateTracker(Context context) { + mDM = context.getSystemService(DisplayManager.class); + updateSingleFrameMs(); + mDM.registerDisplayListener(this, UI_HELPER_EXECUTOR.getHandler()); + } + + /** + * Returns the single frame time in ms + */ + public static int getSingleFrameMs(Context context) { + return INSTANCE.get(context).mSingleFrameMs; + } + + @Override + public final void onDisplayAdded(int displayId) { } + + @Override + public final void onDisplayRemoved(int displayId) { } + + @WorkerThread + @Override + public final void onDisplayChanged(int displayId) { + if (displayId == DEFAULT_DISPLAY) { + updateSingleFrameMs(); + } + } + + private void updateSingleFrameMs() { + Display display = mDM.getDisplay(DEFAULT_DISPLAY); + if (display != null) { + float refreshRate = display.getRefreshRate(); + mSingleFrameMs = refreshRate > 0 ? (int) (1000 / refreshRate) : 16; + } + } + + @Override + public void close() { + mDM.unregisterDisplayListener(this); + } +} diff --git a/src/com/android/launcher3/util/window/WindowManagerProxy.java b/src/com/android/launcher3/util/window/WindowManagerProxy.java index ae2c4f3ff0..ef4ac68dec 100644 --- a/src/com/android/launcher3/util/window/WindowManagerProxy.java +++ b/src/com/android/launcher3/util/window/WindowManagerProxy.java @@ -27,10 +27,12 @@ import static com.android.launcher3.testing.shared.ResourceUtils.NAV_BAR_INTERAC import static com.android.launcher3.testing.shared.ResourceUtils.STATUS_BAR_HEIGHT; import static com.android.launcher3.testing.shared.ResourceUtils.STATUS_BAR_HEIGHT_LANDSCAPE; import static com.android.launcher3.testing.shared.ResourceUtils.STATUS_BAR_HEIGHT_PORTRAIT; +import static com.android.launcher3.util.MainThreadInitializedObject.forOverride; import static com.android.launcher3.util.RotationUtils.deltaRotation; import static com.android.launcher3.util.RotationUtils.rotateRect; import static com.android.launcher3.util.RotationUtils.rotateSize; +import android.annotation.TargetApi; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; @@ -38,6 +40,7 @@ import android.graphics.Insets; import android.graphics.Point; import android.graphics.Rect; import android.hardware.display.DisplayManager; +import android.os.Build; import android.util.ArrayMap; import android.util.Log; import android.view.Display; @@ -52,33 +55,37 @@ import androidx.annotation.VisibleForTesting; import com.android.launcher3.R; import com.android.launcher3.Utilities; -import com.android.launcher3.dagger.LauncherAppSingleton; -import com.android.launcher3.dagger.LauncherBaseAppComponent; import com.android.launcher3.testing.shared.ResourceUtils; -import com.android.launcher3.util.DaggerSingletonObject; +import com.android.launcher3.util.MainThreadInitializedObject; import com.android.launcher3.util.NavigationMode; +import com.android.launcher3.util.ResourceBasedOverride; +import com.android.launcher3.util.SafeCloseable; import com.android.launcher3.util.WindowBounds; import java.util.ArrayList; import java.util.List; -import javax.inject.Inject; - /** * Utility class for mocking some window manager behaviours */ -@LauncherAppSingleton -public class WindowManagerProxy { +public class WindowManagerProxy implements ResourceBasedOverride, SafeCloseable { private static final String TAG = "WindowManagerProxy"; public static final int MIN_TABLET_WIDTH = 600; - public static final DaggerSingletonObject INSTANCE = - new DaggerSingletonObject<>(LauncherBaseAppComponent::getWmProxy); + public static final MainThreadInitializedObject INSTANCE = forOverride(WindowManagerProxy.class, + R.string.window_manager_proxy_class); protected final boolean mTaskbarDrawnInProcess; - @Inject + /** + * Creates a new instance of proxy, applying any overrides + */ + public static WindowManagerProxy newInstance(Context context) { + return Overrides.getObject(WindowManagerProxy.class, context, + R.string.window_manager_proxy_class); + } + public WindowManagerProxy() { this(false); } @@ -95,7 +102,8 @@ public class WindowManagerProxy { } /** - * Returns a map of normalized info of internal displays to estimated window bounds + * Returns a map of normalized info of internal displays to estimated window + * bounds * for that display */ public ArrayMap> estimateInternalDisplayBounds( @@ -110,34 +118,13 @@ public class WindowManagerProxy { /** * Returns if we are in desktop mode or not. */ - public boolean isInDesktopMode(int displayId) { + public boolean isInDesktopMode() { return false; } /** - * Returns if the pinned taskbar should be shown when home is visible. - */ - public boolean showLockedTaskbarOnHome(Context displayInfoContext) { - return false; - } - - /** - * Returns whether the display is a freeform display for which taskbar should be pinned - * and showing desktop tasks. - */ - public boolean showDesktopTaskbarForFreeformDisplay(Context displayInfoContext) { - return false; - } - - /** - * Returns if the home is visible. - */ - public boolean isHomeVisible(Context context) { - return false; - } - - /** - * Returns the real bounds for the provided display after applying any insets normalization + * Returns the real bounds for the provided display after applying any insets + * normalization */ public WindowBounds getRealBounds(Context displayInfoContext, CachedDisplayInfo info) { WindowMetrics windowMetrics = displayInfoContext.getSystemService(WindowManager.class) @@ -148,7 +135,8 @@ public class WindowManagerProxy { } /** - * Returns an updated insets, accounting for various Launcher UI specific overrides like taskbar + * Returns an updated insets, accounting for various Launcher UI specific + * overrides like taskbar */ public WindowInsets normalizeWindowInsets(Context context, WindowInsets oldInsets, Rect outInsets) { @@ -171,14 +159,15 @@ public class WindowManagerProxy { int bottomNav = isLargeScreen ? 0 : (isPortrait - ? getDimenByName(systemRes, NAVBAR_HEIGHT) - : (isGesture - ? getDimenByName(systemRes, NAVBAR_HEIGHT_LANDSCAPE) - : 0)); + ? getDimenByName(systemRes, NAVBAR_HEIGHT) + : (isGesture + ? getDimenByName(systemRes, NAVBAR_HEIGHT_LANDSCAPE) + : 0)); int leftNav = navInsets.left; int rightNav = navInsets.right; if (!isLargeScreen && !isGesture && !isPortrait) { - // In 3-button landscape/seascape, Launcher should always have nav insets regardless if + // In 3-button landscape/seascape, Launcher should always have nav insets + // regardless if // it's initiated from fullscreen apps. int navBarWidth = getDimenByName(systemRes, NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE); switch (getRotation(context)) { @@ -201,8 +190,10 @@ public class WindowManagerProxy { insetsBuilder.setInsetsIgnoringVisibility( WindowInsets.Type.statusBars(), newStatusBarInsets); - // Override the tappable insets to be 0 on the bottom for gesture nav (otherwise taskbar - // would count towards it). This is used for the bottom protection in All Apps for example. + // Override the tappable insets to be 0 on the bottom for gesture nav (otherwise + // taskbar + // would count towards it). This is used for the bottom protection in All Apps + // for example. if (isGesture) { Insets oldTappableInsets = oldInsets.getInsets(WindowInsets.Type.tappableElement()); Insets newTappableInsets = Insets.of(oldTappableInsets.left, oldTappableInsets.top, @@ -222,8 +213,10 @@ public class WindowManagerProxy { } /** - * For large screen, when display cutout is at bottom left/right corner of screen, override - * display cutout's bottom inset to 0, because launcher allows drawing content over that area. + * For large screen, when display cutout is at bottom left/right corner of + * screen, override + * display cutout's bottom inset to 0, because launcher allows drawing content + * over that area. */ public void applyDisplayCutoutBottomInsetOverrideOnLargeScreen( @NonNull Context context, @@ -231,7 +224,7 @@ public class WindowManagerProxy { int screenWidthPx, @NonNull WindowInsets windowInsets, @NonNull WindowInsets.Builder insetsBuilder) { - if (!isLargeScreen) { + if (!isLargeScreen || !Utilities.ATLEAST_S) { return; } @@ -265,19 +258,25 @@ public class WindowManagerProxy { } /** - * Return true if bottom display cutouts are at bottom left/right corners, AND has width or - * height <= maxWidthAndHeightOfSmallCutoutPx. Note that display cutout rect and screenWidthPx + * Return true if bottom display cutouts are at bottom left/right corners, AND + * has width or + * height <= maxWidthAndHeightOfSmallCutoutPx. Note that display cutout rect and + * screenWidthPx * passed to this method should be in the SAME screen rotation. * - * @param cutoutRectBottom bottom display cutout rect, this is based on current screen rotation - * @param screenWidthPx screen width in px based on current screen rotation - * @param maxWidthAndHeightOfSmallCutoutPx maximum width and height pixels of cutout. + * @param cutoutRectBottom bottom display cutout rect, this is + * based on current screen rotation + * @param screenWidthPx screen width in px based on current + * screen rotation + * @param maxWidthAndHeightOfSmallCutoutPx maximum width and height pixels of + * cutout. */ @VisibleForTesting static boolean areBottomDisplayCutoutsSmallAndAtCorners( @NonNull Rect cutoutRectBottom, int screenWidthPx, int maxWidthAndHeightOfSmallCutoutPx) { - // Empty cutoutRectBottom means there is no display cutout at the bottom. We should ignore + // Empty cutoutRectBottom means there is no display cutout at the bottom. We + // should ignore // it by returning false. if (cutoutRectBottom.isEmpty()) { return false; @@ -296,10 +295,11 @@ public class WindowManagerProxy { } /** - * Returns a list of possible WindowBounds for the display keyed on the 4 surface rotations + * Returns a list of possible WindowBounds for the display keyed on the 4 + * surface rotations */ protected List estimateWindowBounds(Context context, - final CachedDisplayInfo displayInfo) { + final CachedDisplayInfo displayInfo) { int densityDpi = context.getResources().getConfiguration().densityDpi; final int rotation = displayInfo.rotation; @@ -316,9 +316,12 @@ public class WindowManagerProxy { boolean isTablet = swDp >= MIN_TABLET_WIDTH; boolean isTabletOrGesture = isTablet || isGestureNav(context); - // Use the status bar height resources because current system API to get the status bar - // height doesn't allow to do this for an arbitrary display, it returns value only - // for the current active display (see com.android.internal.policy.StatusBarUtils) + // Use the status bar height resources because current system API to get the + // status bar + // height doesn't allow to do this for an arbitrary display, it returns value + // only + // for the current active display (see + // com.android.internal.policy.StatusBarUtils) int statusBarHeightPortrait = getDimenByName(systemRes, STATUS_BAR_HEIGHT_PORTRAIT, STATUS_BAR_HEIGHT); int statusBarHeightLandscape = getDimenByName(systemRes, @@ -328,14 +331,17 @@ public class WindowManagerProxy { navBarHeightPortrait = isTablet ? (mTaskbarDrawnInProcess - ? 0 : context.getResources().getDimensionPixelSize(R.dimen.taskbar_size)) + ? 0 + : context.getResources().getDimensionPixelSize(R.dimen.taskbar_size)) : getDimenByName(systemRes, NAVBAR_HEIGHT); navBarHeightLandscape = isTablet ? (mTaskbarDrawnInProcess - ? 0 : context.getResources().getDimensionPixelSize(R.dimen.taskbar_size)) + ? 0 + : context.getResources().getDimensionPixelSize(R.dimen.taskbar_size)) : (isTabletOrGesture - ? getDimenByName(systemRes, NAVBAR_HEIGHT_LANDSCAPE) : 0); + ? getDimenByName(systemRes, NAVBAR_HEIGHT_LANDSCAPE) + : 0); navbarWidthLandscape = isTabletOrGesture ? 0 : getDimenByName(systemRes, NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE); @@ -406,16 +412,25 @@ public class WindowManagerProxy { /** * Returns a CachedDisplayInfo initialized for the current display */ + @TargetApi(Build.VERSION_CODES.S) public CachedDisplayInfo getDisplayInfo(Context displayInfoContext) { int rotation = getRotation(displayInfoContext); - WindowMetrics windowMetrics = displayInfoContext.getSystemService(WindowManager.class) - .getMaximumWindowMetrics(); - return getDisplayInfo(windowMetrics, rotation); + if (Utilities.ATLEAST_S) { + WindowMetrics windowMetrics = displayInfoContext.getSystemService(WindowManager.class) + .getMaximumWindowMetrics(); + return getDisplayInfo(windowMetrics, rotation); + } else { + Point size = new Point(); + Display display = getDisplay(displayInfoContext); + display.getRealSize(size); + return new CachedDisplayInfo(size, rotation); + } } /** * Returns a CachedDisplayInfo initialized for the current display */ + @TargetApi(Build.VERSION_CODES.S) protected CachedDisplayInfo getDisplayInfo(WindowMetrics windowMetrics, int rotation) { Point size = new Point(windowMetrics.getBounds().right, windowMetrics.getBounds().bottom); return new CachedDisplayInfo(size, rotation, @@ -423,7 +438,8 @@ public class WindowManagerProxy { } /** - * Returns bounds of the display associated with the context, or bounds of DEFAULT_DISPLAY + * Returns bounds of the display associated with the context, or bounds of + * DEFAULT_DISPLAY * if the context isn't associated with a display. */ public Rect getCurrentBounds(Context displayInfoContext) { @@ -437,7 +453,8 @@ public class WindowManagerProxy { } /** - * Returns rotation of the display associated with the context, or rotation of DEFAULT_DISPLAY + * Returns rotation of the display associated with the context, or rotation of + * DEFAULT_DISPLAY * if the context isn't associated with a display. */ public int getRotation(Context displayInfoContext) { @@ -445,7 +462,8 @@ public class WindowManagerProxy { } /** - * Returns the display associated with the context, or DEFAULT_DISPLAY if the context isn't + * Returns the display associated with the context, or DEFAULT_DISPLAY if the + * context isn't * associated with a display. */ protected Display getDisplay(Context displayInfoContext) { @@ -462,7 +480,7 @@ public class WindowManagerProxy { * Returns a DisplayCutout which represents a rotated version of the original */ protected DisplayCutout rotateCutout(DisplayCutout original, int startWidth, int startHeight, - int fromRotation, int toRotation) { + int fromRotation, int toRotation) { Rect safeCutout = getSafeInsets(original); rotateRect(safeCutout, deltaRotation(fromRotation, toRotation)); return new DisplayCutout(Insets.of(safeCutout), null, null, null, null); @@ -487,6 +505,10 @@ public class WindowManagerProxy { return Utilities.ATLEAST_S ? NavigationMode.NO_BUTTON : NavigationMode.THREE_BUTTONS; } + @Override + public void close() { + } + /** * @see DisplayCutout#getSafeInsets */ @@ -497,60 +519,4 @@ public class WindowManagerProxy { } return new Rect(); } - - /** Registers a listener for Taskbar changes in Desktop Mode. */ - public void registerDesktopVisibilityListener(DesktopVisibilityListener listener) { } - - /** Removes a previously registered listener for Taskbar changes in Desktop Mode. */ - public void unregisterDesktopVisibilityListener(DesktopVisibilityListener listener) { } - - /** A listener for when the user enters/exits Desktop Mode. */ - public interface DesktopVisibilityListener { - /** - * Called when the desktop mode state on the display whose ID is `displayId` changes. - * - * @param displayId The ID of the display for which this notification is triggering. - * @param isInDesktopModeAndNotInOverview True if a desktop is currently active on the given - * display, and Overview is currently inactive. - */ - default void onIsInDesktopModeChanged(int displayId, - boolean isInDesktopModeAndNotInOverview) { - } - - /** - * Called whenever the conditions that allow the creation of desks change. - * - * @param canCreateDesks whether it is possible to create new desks. - */ - default void onCanCreateDesksChanged(boolean canCreateDesks) { - } - - /** - * Called when a new desk is added. - * - * @param displayId The ID of the display on which the desk was added. - * @param deskId The ID of the newly added desk. - */ - default void onDeskAdded(int displayId, int deskId) {} - - /** - * Called when an existing desk is removed. - * - * @param displayId The ID of the display on which the desk was removed. - * @param deskId The ID of the desk that was removed. - */ - default void onDeskRemoved(int displayId, int deskId) {} - - /** - * Called when the active desk changes. - * - * @param displayId The ID of the display on which the desk activation change is happening. - * @param newActiveDesk The ID of the new active desk or -1 if no desk is active anymore - * (i.e. exit desktop mode). - * @param oldActiveDesk The ID of the desk that was previously active, or -1 if no desk was - * active before. - */ - default void onActiveDeskChanged(int displayId, int newActiveDesk, int oldActiveDesk) {} - } - } diff --git a/src/com/android/launcher3/views/AbstractSlideInView.java b/src/com/android/launcher3/views/AbstractSlideInView.java index 749e526427..711dbbbe7a 100644 --- a/src/com/android/launcher3/views/AbstractSlideInView.java +++ b/src/com/android/launcher3/views/AbstractSlideInView.java @@ -305,7 +305,7 @@ public abstract class AbstractSlideInView // @Override @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public void onBackStarted(BackEvent backEvent) { - super.onBackStarted(backEvent); + super.onBackStarted(); mViewToAnimateInSwipeToDismiss = shouldAnimateContentViewInBackSwipe() ? mContent : this; } @@ -313,7 +313,8 @@ public abstract class AbstractSlideInView @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public void onBackProgressed(BackEvent backEvent) { final float progress = backEvent.getProgress(); - mSwipeToDismissProgress.updateValue(progress); + float deceleratedProgress = Interpolators.BACK_GESTURE.getInterpolation(progress); + mSwipeToDismissProgress.updateValue(deceleratedProgress); } /** diff --git a/src/com/android/launcher3/views/AccessibilityActionsView.java b/src/com/android/launcher3/views/AccessibilityActionsView.java index a1e118d2a4..fcb7c282ee 100644 --- a/src/com/android/launcher3/views/AccessibilityActionsView.java +++ b/src/com/android/launcher3/views/AccessibilityActionsView.java @@ -63,7 +63,10 @@ public class AccessibilityActionsView extends View implements StateListener null; - } - /** * The all apps container, if it exists in this context. */ @@ -232,7 +220,9 @@ public interface ActivityContext extends SavedStateRegistryOwner { getOnDeviceProfileChangeListeners().remove(listener); } - ViewCache getViewCache(); + default ViewCache getViewCache() { + return new ViewCache(); + } /** * Controller for supporting item drag-and-drop @@ -241,11 +231,6 @@ public interface ActivityContext extends SavedStateRegistryOwner { return null; } - @Nullable - default SystemUiController getSystemUiController() { - return null; - } - /** * Handler for actions taken on drop targets that require launcher */ @@ -265,7 +250,8 @@ public interface ActivityContext extends SavedStateRegistryOwner { } /** - * Returns {@code true} if popups can use a range of color shades instead of a singular color. + * Returns {@code true} if popups can use a range of color shades instead of a + * singular color. */ default boolean canUseMultipleShadesForPopup() { return true; @@ -274,7 +260,15 @@ public interface ActivityContext extends SavedStateRegistryOwner { /** * Called just before logging the given item. */ - default void applyOverwritesToLogItem(LauncherAtom.ItemInfo.Builder itemInfoBuilder) { } + default void applyOverwritesToLogItem(LauncherAtom.ItemInfo.Builder itemInfoBuilder) { + } + + /** + * Returns {@code true} if items are currently being bound within this context. + */ + default boolean isBindingItems() { + return false; + } default View.OnClickListener getItemOnClickListener() { return v -> { @@ -287,20 +281,8 @@ public interface ActivityContext extends SavedStateRegistryOwner { return v -> false; } - @NonNull - default PopupDataProvider getPopupDataProvider() { - return new PopupDataProvider(this); - } - - default DotInfo getDotInfoForItem(ItemInfo info) { - return getPopupDataProvider().getDotInfoForItem(info); - } - - /** - * Returns the {@link WidgetPickerDataProvider} that can be used to read widgets for display. - */ @Nullable - default WidgetPickerDataProvider getWidgetPickerDataProvider() { + default PopupDataProvider getPopupDataProvider() { return null; } @@ -317,24 +299,29 @@ public interface ActivityContext extends SavedStateRegistryOwner { if (root == null) { return; } - Preconditions.assertUIThread(); - // Hide keyboard with WindowInsetsController if could. In case hideSoftInputFromWindow may - // get ignored by input connection being finished when the screen is off. - // - // In addition, inside IMF, the keyboards are closed asynchronously that launcher no longer - // need to post to the message queue. - final WindowInsetsController wic = root.getWindowInsetsController(); - WindowInsets insets = root.getRootWindowInsets(); - boolean isImeShown = insets != null && insets.isVisible(WindowInsets.Type.ime()); - if (wic != null) { - // Only hide the keyboard if it is actually showing. - if (isImeShown) { + if (Utilities.ATLEAST_R) { + Preconditions.assertUIThread(); + // Hide keyboard with WindowInsetsController if could. In case + // hideSoftInputFromWindow may get ignored by input connection being finished + // when the screen is off. + // + // In addition, inside IMF, the keyboards are closed asynchronously that + // launcher no + // longer need to post to the message queue. + final WindowInsetsController wic = root.getWindowInsetsController(); + WindowInsets insets = root.getRootWindowInsets(); + boolean isImeShown = insets != null && insets.isVisible(WindowInsets.Type.ime()); + if (wic != null && isImeShown) { + StatsLogManager slm = getStatsLogManager(); + slm.keyboardStateManager().setKeyboardState(HIDE); + // this method cannot be called cross threads wic.hide(WindowInsets.Type.ime()); getStatsLogManager().logger().log(LAUNCHER_ALLAPPS_KEYBOARD_CLOSED); } - // If the WindowInsetsController is not null, we end here regardless of whether we hid + // If the WindowInsetsController is not null, we end here regardless of whether + // we hid // the keyboard or not. return; } @@ -345,8 +332,7 @@ public interface ActivityContext extends SavedStateRegistryOwner { UI_HELPER_EXECUTOR.execute(() -> { if (imm.hideSoftInputFromWindow(token, 0)) { // log keyboard close event only when keyboard is actually closed - MAIN_EXECUTOR.execute(() -> - getStatsLogManager().logger().log(LAUNCHER_ALLAPPS_KEYBOARD_CLOSED)); + MAIN_EXECUTOR.execute(() -> getStatsLogManager().logger().log(LAUNCHER_ALLAPPS_KEYBOARD_CLOSED)); } }); } @@ -356,12 +342,12 @@ public interface ActivityContext extends SavedStateRegistryOwner { * Returns if the connected keyboard is a hardware keyboard. */ default boolean isHardwareKeyboard() { - return Configuration.KEYBOARD_QWERTY - == ((Context) this).getResources().getConfiguration().keyboard; + return Configuration.KEYBOARD_QWERTY == ((Context) this).getResources().getConfiguration().keyboard; } /** - * Returns if the software keyboard (including input toolbar) is hidden. Hardware + * Returns if the software keyboard (including input toolbar) is hidden. + * Hardware * keyboards do not display on screen by default. */ default boolean isSoftwareKeyboardHidden() { @@ -373,8 +359,7 @@ public interface ActivityContext extends SavedStateRegistryOwner { if (insets == null) { return false; } - WindowInsetsCompat insetsCompat = - WindowInsetsCompat.toWindowInsetsCompat(insets, dragLayer); + WindowInsetsCompat insetsCompat = WindowInsetsCompat.toWindowInsetsCompat(insets, dragLayer); return !insetsCompat.isVisible(WindowInsetsCompat.Type.ime()); } } @@ -382,10 +367,11 @@ public interface ActivityContext extends SavedStateRegistryOwner { /** * Sends a pending intent animating from a view. * - * @param v View to animate. + * @param v View to animate. * @param intent The pending intent being launched. - * @param item Item associated with the view. - * @return RunnableList for listening for animation finish if the activity was properly + * @param item Item associated with the view. + * @return RunnableList for listening for animation finish if the activity was + * properly * or started, {@code null} if the launch finished */ default RunnableList sendPendingIntentWithAnimation( @@ -410,19 +396,18 @@ public interface ActivityContext extends SavedStateRegistryOwner { /** * Safely starts an activity. * - * @param v View starting the activity. + * @param v View starting the activity. * @param intent Base intent being launched. - * @param item Item associated with the view. - * @return RunnableList for listening for animation finish if the activity was properly + * @param item Item associated with the view. + * @return RunnableList for listening for animation finish if the activity was + * properly * or started, {@code null} if the launch finished */ default RunnableList startActivitySafely( View v, Intent intent, @Nullable ItemInfo item) { Preconditions.assertUIThread(); Context context = (Context) this; - - if (LauncherAppState.getInstance(context).isSafeModeEnabled() - && !new ApplicationInfoWrapper(context, intent).isSystem()) { + if (isAppBlockedForSafeMode() && !PackageManagerHelper.isSystemApp(context, intent)) { Toast.makeText(context, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show(); return null; } @@ -435,7 +420,8 @@ public interface ActivityContext extends SavedStateRegistryOwner { } ActivityOptionsWrapper options = v != null ? getActivityLaunchOptions(v, item) : makeDefaultActivityOptions(item != null && item.animationType == DEFAULT_NO_ICON - ? SPLASH_SCREEN_STYLE_SOLID_COLOR : -1 /* SPLASH_SCREEN_STYLE_UNDEFINED */); + ? SPLASH_SCREEN_STYLE_SOLID_COLOR + : -1 /* SPLASH_SCREEN_STYLE_UNDEFINED */); UserHandle user = item == null ? null : item.user; Bundle optsBundle = options.toBundle(); // Prepare intent @@ -468,6 +454,11 @@ public interface ActivityContext extends SavedStateRegistryOwner { return null; } + /** Returns {@code true} if an app launch is blocked due to safe mode. */ + default boolean isAppBlockedForSafeMode() { + return false; + } + /** * Creates and logs a new app launch event. */ @@ -480,10 +471,9 @@ public interface ActivityContext extends SavedStateRegistryOwner { /** * Returns launch options for an Activity. * - * @param v View initiating a launch. + * @param v View initiating a launch. * @param item Item associated with the view. */ - @NonNull default ActivityOptionsWrapper getActivityLaunchOptions(View v, @Nullable ItemInfo item) { int left = 0, top = 0; int width = v.getMeasuredWidth(), height = v.getMeasuredHeight(); @@ -498,8 +488,7 @@ public interface ActivityContext extends SavedStateRegistryOwner { height = bounds.height(); } } - ActivityOptions options = - allowBGLaunch(ActivityOptions.makeClipRevealAnimation(v, left, top, width, height)); + ActivityOptions options = allowBGLaunch(ActivityOptions.makeClipRevealAnimation(v, left, top, width, height)); options.setLaunchDisplayId( (v != null && v.getDisplay() != null) ? v.getDisplay().getDisplayId() : Display.DEFAULT_DISPLAY); @@ -508,7 +497,8 @@ public interface ActivityContext extends SavedStateRegistryOwner { } /** - * Creates a default activity option and we do not want association with any launcher element. + * Creates a default activity option and we do not want association with any + * launcher element. */ default ActivityOptionsWrapper makeDefaultActivityOptions(int splashScreenStyle) { ActivityOptions options = allowBGLaunch(ActivityOptions.makeBasic()); @@ -523,9 +513,6 @@ public interface ActivityContext extends SavedStateRegistryOwner { return new CellPosMapper(dp.isVerticalBarLayout(), dp.numShownHotseatIcons); } - /** Set to manage objects that can be cleaned up along with the context */ - WeakCleanupSet getOwnerCleanupSet(); - /** Whether bubbles are enabled. */ default boolean isBubbleBarEnabled() { return false; @@ -536,13 +523,9 @@ public interface ActivityContext extends SavedStateRegistryOwner { return false; } - /** Returns the current ActivityContext as context */ - default Context asContext() { - return (Context) this; - } - /** - * Returns the ActivityContext associated with the given Context, or throws an exception if + * Returns the ActivityContext associated with the given Context, or throws an + * exception if * the Context is not associated with any ActivityContext. */ static T lookupContext(Context context) { @@ -560,10 +543,21 @@ public interface ActivityContext extends SavedStateRegistryOwner { static T lookupContextNoThrow(Context context) { if (context instanceof ActivityContext) { return (T) context; - } else if (context instanceof ContextWrapper cw) { - return lookupContextNoThrow(cw.getBaseContext()); + } else if (context instanceof ActivityContextDelegate acd) { + return (T) acd.mDelegate; + } else if (context instanceof ContextWrapper) { + return lookupContextNoThrow(((ContextWrapper) context).getBaseContext()); } else { return null; } } + + class ActivityContextDelegate extends ContextThemeWrapper { + public final ActivityContext mDelegate; + + public ActivityContextDelegate(Context base, int themeResId, ActivityContext delegate) { + super(base, themeResId); + mDelegate = delegate; + } + } } diff --git a/src/com/android/launcher3/views/ArrowTipView.java b/src/com/android/launcher3/views/ArrowTipView.java index abb0081f1f..bb4f04008b 100644 --- a/src/com/android/launcher3/views/ArrowTipView.java +++ b/src/com/android/launcher3/views/ArrowTipView.java @@ -73,7 +73,7 @@ public class ArrowTipView extends AbstractFloatingView { } }; - protected final ActivityContext mActivityContext; + private final ActivityContext mActivityContext; private final Handler mHandler = new Handler(); private boolean mIsPointingUp; private Runnable mOnClosed; @@ -103,26 +103,16 @@ public class ArrowTipView extends AbstractFloatingView { R.dimen.arrow_toast_arrow_width); mArrowMinOffset = context.getResources().getDimensionPixelSize( R.dimen.dynamic_grid_cell_border_spacing); - Context localContext = context; - TypedArray ta = localContext.obtainStyledAttributes(R.styleable.ArrowTipView); + TypedArray ta = context.obtainStyledAttributes(R.styleable.ArrowTipView); // Set style to default to avoid inflation issues with missing attributes. if (!ta.hasValue(R.styleable.ArrowTipView_arrowTipBackground) || !ta.hasValue(R.styleable.ArrowTipView_arrowTipTextColor)) { - localContext = new ContextThemeWrapper(localContext, R.style.ArrowTipStyle); + context = new ContextThemeWrapper(context, R.style.ArrowTipStyle); } - mArrowViewPaintColor = applyArrowPaintColor(ta, localContext); - init(localContext, layoutId); - } - - protected int applyArrowPaintColor(TypedArray typedArray, Context context) { - int arrowPaintColor = typedArray.getColor(R.styleable.ArrowTipView_arrowTipBackground, + mArrowViewPaintColor = ta.getColor(R.styleable.ArrowTipView_arrowTipBackground, context.getColor(R.color.arrow_tip_view_bg)); - typedArray.recycle(); - return arrowPaintColor; - } - - protected int getArrowId() { - return R.id.arrow; + ta.recycle(); + init(context, layoutId); } @Override @@ -164,7 +154,7 @@ public class ArrowTipView extends AbstractFloatingView { inflate(context, layoutId, this); setOrientation(LinearLayout.VERTICAL); - mArrowView = findViewById(getArrowId()); + mArrowView = findViewById(R.id.arrow); updateArrowTipInView(mIsPointingUp); setAlpha(0); @@ -353,34 +343,6 @@ public class ArrowTipView extends AbstractFloatingView { parent.addView(this); requestLayout(); } - return showAtLocation(arrowXCoord, yCoordDownPointingTip, yCoordUpPointingTip, - minViewMargin, parentViewWidth, parentViewHeight, shouldAutoClose); - } - - /** - * Show the ArrowTipView (tooltip) custom aligned. The tooltip is vertically flipped if it - * cannot fit on screen in the requested orientation. - * - * @param arrowXCoord The X coordinate for the arrow on the tooltip. The arrow is usually in the - * center of tooltip unless the tooltip goes beyond screen margin. - * @param yCoordDownPointingTip The Y coordinate of the pointed tip end of the tooltip when the - * tooltip is placed pointing downwards. - * @param yCoordUpPointingTip The Y coordinate of the pointed tip end of the tooltip when the - * tooltip is placed pointing upwards. - * @param minViewMargin The view margin in pixels from the tip end to the y coordinate. - * @param parentViewWidth The width in pixels of the parent view. - * @param parentViewHeight The height in pixels of the parent view. - * @param shouldAutoClose If Tooltip should be auto close. - * @return The tool tip view. {@code null} if the tip can not be shown. - */ - protected ArrowTipView showAtLocation( - @Px int arrowXCoord, - @Px int yCoordDownPointingTip, - @Px int yCoordUpPointingTip, - @Px int minViewMargin, - @Px int parentViewWidth, - @Px int parentViewHeight, - boolean shouldAutoClose) { post(() -> { // Adjust the tooltip horizontally. diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java index 331ad16278..43ffa87fb2 100644 --- a/src/com/android/launcher3/views/BaseDragLayer.java +++ b/src/com/android/launcher3/views/BaseDragLayer.java @@ -107,7 +107,7 @@ public abstract class BaseDragLayer protected final RectF mSystemGestureRegion = new RectF(); private int mTouchDispatchState = 0; - protected final T mContainer; + protected final T mActivity; private final MultiValueAlpha mMultiValueAlpha; // All the touch controllers for the view @@ -121,7 +121,7 @@ public abstract class BaseDragLayer public BaseDragLayer(Context context, AttributeSet attrs, int alphaChannelCount) { super(context, attrs); - mContainer = ActivityContext.lookupContext(context); + mActivity = ActivityContext.lookupContext(context); mMultiValueAlpha = new MultiValueAlpha(this, alphaChannelCount); } @@ -159,7 +159,7 @@ public abstract class BaseDragLayer } mTouchCompleteListener = null; } else if (action == MotionEvent.ACTION_DOWN) { - mContainer.finishAutoCancelActionMode(); + mActivity.finishAutoCancelActionMode(); } return findActiveController(ev); } @@ -173,7 +173,7 @@ public abstract class BaseDragLayer } private TouchController findControllerToHandleTouch(MotionEvent ev) { - AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mContainer); + AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity); if (topView != null && (isEventWithinSystemGestureRegion(ev) || topView.canInterceptEventsInSystemGestureRegion()) @@ -207,7 +207,7 @@ public abstract class BaseDragLayer @Override public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) { // Shortcuts can appear above folder - View topView = AbstractFloatingView.getTopOpenViewWithType(mContainer, + View topView = AbstractFloatingView.getTopOpenViewWithType(mActivity, AbstractFloatingView.TYPE_ACCESSIBLE); if (topView != null) { if (child == topView) { @@ -222,7 +222,7 @@ public abstract class BaseDragLayer @Override public void addChildrenForAccessibility(ArrayList childrenForAccessibility) { - View topView = AbstractFloatingView.getTopOpenViewWithType(mContainer, + View topView = AbstractFloatingView.getTopOpenViewWithType(mActivity, AbstractFloatingView.TYPE_ACCESSIBLE); if (topView != null) { // Only add the top view as a child for accessibility when it is open @@ -460,7 +460,7 @@ public abstract class BaseDragLayer @Override protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { - View topView = AbstractFloatingView.getTopOpenView(mContainer); + View topView = AbstractFloatingView.getTopOpenView(mActivity); if (topView != null) { return topView.requestFocus(direction, previouslyFocusedRect); } else { @@ -470,7 +470,7 @@ public abstract class BaseDragLayer @Override public void addFocusables(ArrayList views, int direction, int focusableMode) { - View topView = AbstractFloatingView.getTopOpenView(mContainer); + View topView = AbstractFloatingView.getTopOpenView(mActivity); if (topView != null) { topView.addFocusables(views, direction); } else { @@ -558,7 +558,7 @@ public abstract class BaseDragLayer Insets gestureInsets = insets.getMandatorySystemGestureInsets(); int gestureInsetBottom = gestureInsets.bottom; Insets imeInset = insets.getInsets(WindowInsets.Type.ime()); - DeviceProfile dp = mContainer.getDeviceProfile(); + DeviceProfile dp = mActivity.getDeviceProfile(); if (dp.isTaskbarPresent) { // Ignore taskbar gesture insets to avoid interfering with TouchControllers. gestureInsetBottom = ResourceUtils.getNavbarSize( diff --git a/src/com/android/launcher3/views/ClipIconView.java b/src/com/android/launcher3/views/ClipIconView.java index a295d6b52e..f90a3e4dd1 100644 --- a/src/com/android/launcher3/views/ClipIconView.java +++ b/src/com/android/launcher3/views/ClipIconView.java @@ -20,7 +20,6 @@ import static com.android.launcher3.Flags.enableAdditionalHomeAnimations; import static com.android.launcher3.Utilities.boundToRange; import static com.android.launcher3.Utilities.mapToRange; import static com.android.launcher3.anim.AnimatorListeners.forEndCallback; -import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR; import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION; import static java.lang.Math.max; @@ -45,11 +44,10 @@ import androidx.annotation.Nullable; import androidx.core.util.Consumer; import com.android.launcher3.DeviceProfile; -import com.android.launcher3.Flags; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.dragndrop.FolderAdaptiveIcon; -import com.android.launcher3.graphics.ThemeManager; +import com.android.launcher3.graphics.IconShape; /** * A view used to draw both layers of an {@link AdaptiveIconDrawable}. @@ -69,7 +67,6 @@ public class ClipIconView extends View implements ClipPathView { private boolean mIsAdaptiveIcon = false; private ValueAnimator mRevealAnimator; - private float mIconScale; private final Rect mStartRevealRect = new Rect(); private final Rect mEndRevealRect = new Rect(); @@ -175,12 +172,9 @@ public class ClipIconView extends View implements ClipPathView { mTaskCornerRadius = cornerRadius / scale; if (mIsAdaptiveIcon) { - final ThemeManager themeManager = ThemeManager.INSTANCE.get(getContext()); - mIconScale = themeManager.getIconState().getIconScale(); - if ((!isOpening || Flags.enableLauncherIconShapes()) - && progress >= shapeProgressStart) { + if (!isOpening && progress >= shapeProgressStart) { if (mRevealAnimator == null) { - mRevealAnimator = themeManager.getIconShape() + mRevealAnimator = IconShape.INSTANCE.get(getContext()).getShape() .createRevealAnimator(this, mStartRevealRect, mOutline, mTaskCornerRadius, !isOpening); mRevealAnimator.addListener(forEndCallback(() -> mRevealAnimator = null)); @@ -264,7 +258,8 @@ public class ClipIconView extends View implements ClipPathView { mStartRevealRect.set(0, 0, originalWidth, originalHeight); if (!isFolderIcon) { - Utilities.scaleRectAboutCenter(mStartRevealRect, ICON_VISIBLE_AREA_FACTOR); + Utilities.scaleRectAboutCenter(mStartRevealRect, + IconShape.INSTANCE.get(getContext()).getNormalizationScale()); } if (dp.isLandscape) { @@ -314,24 +309,17 @@ public class ClipIconView extends View implements ClipPathView { @Override public void draw(Canvas canvas) { - int count1 = canvas.save(); + int count = canvas.save(); if (mClipPath != null) { canvas.clipPath(mClipPath); } - int count2 = canvas.save(); - float iconCenterX = - (mFinalDrawableBounds.right - mFinalDrawableBounds.left) / 2f * mIconScale; - float iconCenterY = - (mFinalDrawableBounds.bottom - mFinalDrawableBounds.top) / 2f * mIconScale; - canvas.scale(mIconScale, mIconScale, iconCenterX, iconCenterY); + super.draw(canvas); if (mBackground != null) { mBackground.draw(canvas); } if (mForeground != null) { mForeground.draw(canvas); } - canvas.restoreToCount(count2); - super.draw(canvas); if (mTaskViewArtist != null) { canvas.saveLayerAlpha( 0, @@ -345,7 +333,7 @@ public class ClipIconView extends View implements ClipPathView { canvas.scale(drawScale, drawScale); mTaskViewArtist.taskViewDrawCallback.accept(canvas); } - canvas.restoreToCount(count1); + canvas.restoreToCount(count); } void recycle() { diff --git a/src/com/android/launcher3/views/ComposeInitializer.java b/src/com/android/launcher3/views/ComposeInitializer.java new file mode 100644 index 0000000000..092988591e --- /dev/null +++ b/src/com/android/launcher3/views/ComposeInitializer.java @@ -0,0 +1,229 @@ +/* + * 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.launcher3.views; + +import android.os.Build; +import android.view.View; +import android.view.ViewParent; +import android.view.ViewTreeObserver; + +import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; +import androidx.lifecycle.Lifecycle; +import androidx.lifecycle.LifecycleOwner; +import androidx.lifecycle.LifecycleRegistry; +import androidx.lifecycle.ViewTreeLifecycleOwner; +import androidx.savedstate.SavedStateRegistry; +import androidx.savedstate.SavedStateRegistryController; +import androidx.savedstate.SavedStateRegistryOwner; +import androidx.savedstate.ViewTreeSavedStateRegistryOwner; + +import com.android.launcher3.Utilities; + +/** + * An initializer to use Compose for classes implementing {@code ActivityContext}. This allows + * adding ComposeView to ViewTree outside a {@link androidx.activity.ComponentActivity}. + */ +public final class ComposeInitializer { + /** + * Performs the initialization to use Compose in the ViewTree of {@code target}. + */ + public static void initCompose(ActivityContext target) { + getContentChild(target).addOnAttachStateChangeListener( + new View.OnAttachStateChangeListener() { + + @Override + public void onViewAttachedToWindow(View v) { + ComposeInitializer.onAttachedToWindow(v); + } + + @Override + public void onViewDetachedFromWindow(View v) { + ComposeInitializer.onDetachedFromWindow(v); + } + }); + } + + /** + * Find the "content child" for {@code target}. + * + * @see "WindowRecomposer.android.kt: [View.contentChild]" + */ + private static View getContentChild(ActivityContext target) { + View self = target.getDragLayer(); + ViewParent parent = self.getParent(); + while (parent instanceof View parentView) { + if (parentView.getId() == android.R.id.content) return self; + self = parentView; + parent = self.getParent(); + } + return self; + } + + /** + * Function to be called on your window root view's [View.onAttachedToWindow] function. + */ + private static void onAttachedToWindow(View root) { + if (ViewTreeLifecycleOwner.get(root) != null) { + throw new IllegalStateException( + "View " + root + " already has a LifecycleOwner"); + } + + ViewParent parent = root.getParent(); + if (parent instanceof View && ((View) parent).getId() != android.R.id.content) { + throw new IllegalStateException( + "ComposeInitializer.onContentChildAttachedToWindow(View) must be called on " + + "the content child. Outside of activities and dialogs, this is " + + "usually the top-most View of a window."); + } + + // The lifecycle owner, which is STARTED when [root] is visible and RESUMED when [root] + // is both visible and focused. + ViewLifecycleOwner lifecycleOwner = new ViewLifecycleOwner(root); + + // We must call [ViewLifecycleOwner.onCreate] after creating the + // [SavedStateRegistryOwner] because `onCreate` might move the lifecycle state to STARTED + // which will make [SavedStateRegistryController.performRestore] throw. + lifecycleOwner.onCreate(); + + // Set the owners on the root. They will be reused by any ComposeView inside the root + // hierarchy. + ViewTreeLifecycleOwner.set(root, lifecycleOwner); + ViewTreeSavedStateRegistryOwner.set(root, lifecycleOwner); + } + + /** + * Function to be called on your window root view's [View.onDetachedFromWindow] function. + */ + private static void onDetachedFromWindow(View root) { + final LifecycleOwner lifecycleOwner = ViewTreeLifecycleOwner.get(root); + if (lifecycleOwner != null) { + ((ViewLifecycleOwner) lifecycleOwner).onDestroy(); + } + ViewTreeLifecycleOwner.set(root, null); + ViewTreeSavedStateRegistryOwner.set(root, null); + } + + /** + * A [LifecycleOwner] for a [View] that updates lifecycle state based on window state. + * + * Also a trivial implementation of [SavedStateRegistryOwner] that does not do any save or + * restore. This works for processes similar to the SystemUI process, which is always running + * and top-level windows using this initialization are created once, when the process is + * started. + * + * The implementation requires the caller to call [onCreate] and [onDestroy] when the view is + * attached to or detached from a view hierarchy. After [onCreate] and before [onDestroy] is + * called, the implementation monitors window state in the following way + * * If the window is not visible, we are in the [Lifecycle.State.CREATED] state + * * If the window is visible but not focused, we are in the [Lifecycle.State.STARTED] state + * * If the window is visible and focused, we are in the [Lifecycle.State.RESUMED] state + * + * Or in table format: + * ``` + * ┌───────────────┬───────────────────┬──────────────┬─────────────────┐ + * │ View attached │ Window Visibility │ Window Focus │ Lifecycle State │ + * ├───────────────┼───────────────────┴──────────────┼─────────────────┤ + * │ Not attached │ Any │ N/A │ + * ├───────────────┼───────────────────┬──────────────┼─────────────────┤ + * │ │ Not visible │ Any │ CREATED │ + * │ ├───────────────────┼──────────────┼─────────────────┤ + * │ Attached │ │ No focus │ STARTED │ + * │ │ Visible ├──────────────┼─────────────────┤ + * │ │ │ Has focus │ RESUMED │ + * └───────────────┴───────────────────┴──────────────┴─────────────────┘ + * ``` + */ + private static class ViewLifecycleOwner implements SavedStateRegistryOwner { + private final ViewTreeObserver.OnWindowFocusChangeListener mWindowFocusListener = + hasFocus -> updateState(); + private final LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this); + + private final SavedStateRegistryController mSavedStateRegistryController = + SavedStateRegistryController.create(this); + + private final View mView; + private final Api34Impl mApi34Impl; + + ViewLifecycleOwner(View view) { + mView = view; + if (Utilities.ATLEAST_U) { + mApi34Impl = new Api34Impl(); + } else { + mApi34Impl = null; + } + + mSavedStateRegistryController.performRestore(null); + } + + @NonNull + @Override + public Lifecycle getLifecycle() { + return mLifecycleRegistry; + } + + @NonNull + @Override + public SavedStateRegistry getSavedStateRegistry() { + return mSavedStateRegistryController.getSavedStateRegistry(); + } + + void onCreate() { + mLifecycleRegistry.setCurrentState(Lifecycle.State.CREATED); + if (Utilities.ATLEAST_U) { + mApi34Impl.addOnWindowVisibilityChangeListener(); + } + mView.getViewTreeObserver().addOnWindowFocusChangeListener( + mWindowFocusListener); + updateState(); + } + + void onDestroy() { + if (Utilities.ATLEAST_U) { + mApi34Impl.removeOnWindowVisibilityChangeListener(); + } + mView.getViewTreeObserver().removeOnWindowFocusChangeListener( + mWindowFocusListener); + mLifecycleRegistry.setCurrentState(Lifecycle.State.DESTROYED); + } + + private void updateState() { + Lifecycle.State state = + mView.getWindowVisibility() != View.VISIBLE ? Lifecycle.State.CREATED + : (!mView.hasWindowFocus() ? Lifecycle.State.STARTED + : Lifecycle.State.RESUMED); + mLifecycleRegistry.setCurrentState(state); + } + + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + private class Api34Impl { + private final ViewTreeObserver.OnWindowVisibilityChangeListener + mWindowVisibilityListener = + visibility -> updateState(); + + void addOnWindowVisibilityChangeListener() { + mView.getViewTreeObserver().addOnWindowVisibilityChangeListener( + mWindowVisibilityListener); + } + + void removeOnWindowVisibilityChangeListener() { + mView.getViewTreeObserver().removeOnWindowVisibilityChangeListener( + mWindowVisibilityListener); + } + } + } +} diff --git a/src/com/android/launcher3/views/DoubleShadowBubbleTextView.java b/src/com/android/launcher3/views/DoubleShadowBubbleTextView.java index df157c2448..6278493131 100644 --- a/src/com/android/launcher3/views/DoubleShadowBubbleTextView.java +++ b/src/com/android/launcher3/views/DoubleShadowBubbleTextView.java @@ -19,20 +19,11 @@ package com.android.launcher3.views; import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound; import android.content.Context; +import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; -import android.graphics.drawable.Drawable; -import android.os.Build; -import android.text.Spannable; -import android.text.SpannableString; -import android.text.TextUtils; -import android.text.style.ImageSpan; import android.util.AttributeSet; -import android.util.Log; - -import androidx.annotation.DrawableRes; -import androidx.annotation.NonNull; -import androidx.annotation.RequiresApi; +import android.widget.TextView; import com.android.launcher3.BubbleTextView; import com.android.launcher3.R; @@ -42,7 +33,7 @@ import com.android.launcher3.R; */ public class DoubleShadowBubbleTextView extends BubbleTextView { - public final ShadowInfo mShadowInfo; + private final ShadowInfo mShadowInfo; public DoubleShadowBubbleTextView(Context context) { this(context, null); @@ -54,68 +45,22 @@ public class DoubleShadowBubbleTextView extends BubbleTextView { public DoubleShadowBubbleTextView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); - mShadowInfo = ShadowInfo.Companion.fromContext(context, attrs, defStyle); - setShadowLayer( - mShadowInfo.getAmbientShadowBlur(), - 0, - 0, - mShadowInfo.getAmbientShadowColor() - ); - } - - @Override - public void setTextWithStartIcon(CharSequence text, @DrawableRes int drawableId) { - Drawable drawable = getContext().getDrawable(drawableId); - if (drawable == null) { - setText(text); - Log.w(TAG, "setTextWithStartIcon: start icon Drawable not found from resources" - + ", will just set text instead."); - return; - } - drawable.setTint(getCurrentTextColor()); - int textSize = Math.round(getTextSize()); - ImageSpan imageSpan; - if (!skipDoubleShadow() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - drawable = getDoubleShadowDrawable(drawable, textSize); - } - drawable.setBounds(0, 0, textSize, textSize); - imageSpan = new ImageSpan(drawable, ImageSpan.ALIGN_CENTER); - // First space will be replaced with Drawable, second space is for space before text. - SpannableString spannable = new SpannableString(" " + text); - spannable.setSpan(imageSpan, 0, 1, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); - setText(spannable); - } - - @RequiresApi(Build.VERSION_CODES.S) - private DoubleShadowIconDrawable getDoubleShadowDrawable( - @NonNull Drawable drawable, int textSize - ) { - // add some padding via inset to avoid shadow clipping - int iconInsetSize = getContext().getResources() - .getDimensionPixelSize(R.dimen.app_title_icon_shadow_inset); - return new DoubleShadowIconDrawable( - mShadowInfo, - drawable, - textSize, - iconInsetSize - ); + mShadowInfo = new ShadowInfo(context, attrs, defStyle); + setShadowLayer(mShadowInfo.ambientShadowBlur, 0, 0, mShadowInfo.ambientShadowColor); } @Override public void onDraw(Canvas canvas) { - if (shouldDrawAppContrastTile() && !TextUtils.isEmpty(getText())) { - drawAppContrastTile(canvas); - } // If text is transparent or shadow alpha is 0, don't draw any shadow - if (skipDoubleShadow()) { + if (mShadowInfo.skipDoubleShadow(this)) { super.onDraw(canvas); return; } int alpha = Color.alpha(getCurrentTextColor()); // We enhance the shadow by drawing the shadow twice - getPaint().setShadowLayer(mShadowInfo.getAmbientShadowBlur(), 0, 0, - getTextShadowColor(mShadowInfo.getAmbientShadowColor(), alpha)); + getPaint().setShadowLayer(mShadowInfo.ambientShadowBlur, 0, 0, + getTextShadowColor(mShadowInfo.ambientShadowColor, alpha)); drawWithoutDot(canvas); canvas.save(); @@ -124,10 +69,10 @@ public class DoubleShadowBubbleTextView extends BubbleTextView { getScrollY() + getHeight()); getPaint().setShadowLayer( - mShadowInfo.getKeyShadowBlur(), - mShadowInfo.getKeyShadowOffsetX(), - mShadowInfo.getKeyShadowOffsetY(), - getTextShadowColor(mShadowInfo.getKeyShadowColor(), alpha)); + mShadowInfo.keyShadowBlur, + mShadowInfo.keyShadowOffsetX, + mShadowInfo.keyShadowOffsetY, + getTextShadowColor(mShadowInfo.keyShadowColor, alpha)); drawWithoutDot(canvas); canvas.restore(); @@ -135,30 +80,55 @@ public class DoubleShadowBubbleTextView extends BubbleTextView { drawRunningAppIndicatorIfNecessary(canvas); } - private boolean skipDoubleShadow() { - int textAlpha = Color.alpha(getCurrentTextColor()); - int keyShadowAlpha = Color.alpha(mShadowInfo.getKeyShadowColor()); - int ambientShadowAlpha = Color.alpha(mShadowInfo.getAmbientShadowColor()); - if (textAlpha == 0 || (keyShadowAlpha == 0 && ambientShadowAlpha == 0)) { - getPaint().clearShadowLayer(); - return true; - } else if (ambientShadowAlpha > 0 && keyShadowAlpha == 0) { - getPaint().setShadowLayer(mShadowInfo.getAmbientShadowBlur(), 0, 0, - getTextShadowColor(mShadowInfo.getAmbientShadowColor(), textAlpha)); - return true; - } else if (keyShadowAlpha > 0 && ambientShadowAlpha == 0) { - getPaint().setShadowLayer( - mShadowInfo.getKeyShadowBlur(), - mShadowInfo.getKeyShadowOffsetX(), - mShadowInfo.getKeyShadowOffsetY(), - getTextShadowColor(mShadowInfo.getKeyShadowColor(), textAlpha)); - return true; - } else { - return false; + public static class ShadowInfo { + public final float ambientShadowBlur; + public final int ambientShadowColor; + + public final float keyShadowBlur; + public final float keyShadowOffsetX; + public final float keyShadowOffsetY; + public final int keyShadowColor; + + public ShadowInfo(Context c, AttributeSet attrs, int defStyle) { + + TypedArray a = c.obtainStyledAttributes( + attrs, R.styleable.ShadowInfo, defStyle, 0); + + ambientShadowBlur = a.getDimensionPixelSize( + R.styleable.ShadowInfo_ambientShadowBlur, 0); + ambientShadowColor = a.getColor(R.styleable.ShadowInfo_ambientShadowColor, 0); + + keyShadowBlur = a.getDimensionPixelSize(R.styleable.ShadowInfo_keyShadowBlur, 0); + keyShadowOffsetX = a.getDimensionPixelSize(R.styleable.ShadowInfo_keyShadowOffsetX, 0); + keyShadowOffsetY = a.getDimensionPixelSize(R.styleable.ShadowInfo_keyShadowOffsetY, 0); + keyShadowColor = a.getColor(R.styleable.ShadowInfo_keyShadowColor, 0); + a.recycle(); + } + + public boolean skipDoubleShadow(TextView textView) { + int textAlpha = Color.alpha(textView.getCurrentTextColor()); + int keyShadowAlpha = Color.alpha(keyShadowColor); + int ambientShadowAlpha = Color.alpha(ambientShadowColor); + if (textAlpha == 0 || (keyShadowAlpha == 0 && ambientShadowAlpha == 0)) { + textView.getPaint().clearShadowLayer(); + return true; + } else if (ambientShadowAlpha > 0 && keyShadowAlpha == 0) { + textView.getPaint().setShadowLayer(ambientShadowBlur, 0, 0, + getTextShadowColor(ambientShadowColor, textAlpha)); + return true; + } else if (keyShadowAlpha > 0 && ambientShadowAlpha == 0) { + textView.getPaint().setShadowLayer( + keyShadowBlur, + keyShadowOffsetX, + keyShadowOffsetY, + getTextShadowColor(keyShadowColor, textAlpha)); + return true; + } else { + return false; + } } } - // Multiplies the alpha of shadowColor by textAlpha. private static int getTextShadowColor(int shadowColor, int textAlpha) { return setColorAlphaBound(shadowColor, diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java index a5d7740fb2..c6642f36e9 100644 --- a/src/com/android/launcher3/views/FloatingIconView.java +++ b/src/com/android/launcher3/views/FloatingIconView.java @@ -55,7 +55,7 @@ import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.folder.FolderIcon; import com.android.launcher3.graphics.PreloadIconDrawable; import com.android.launcher3.icons.FastBitmapDrawable; -import com.android.launcher3.icons.IconNormalizer; +import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.ItemInfoWithIcon; import com.android.launcher3.popup.SystemShortcut; @@ -463,7 +463,11 @@ public class FloatingIconView extends FrameLayout implements Rect bounds = new Rect(0, 0, (int) position.width() + blurSizeOutline, (int) position.height() + blurSizeOutline); bounds.inset(blurSizeOutline / 2, blurSizeOutline / 2); - Utilities.scaleRectAboutCenter(bounds, IconNormalizer.ICON_VISIBLE_AREA_FACTOR); + + try (LauncherIcons li = LauncherIcons.obtain(l)) { + Utilities.scaleRectAboutCenter(bounds, li.getNormalizer().getScale(drawable, null, + null, null)); + } bounds.inset( (int) (-bounds.width() * AdaptiveIconDrawable.getExtraInsetFraction()), diff --git a/src/com/android/launcher3/views/FloatingIconViewCompanion.java b/src/com/android/launcher3/views/FloatingIconViewCompanion.java index fc23903662..bcf39e0e82 100644 --- a/src/com/android/launcher3/views/FloatingIconViewCompanion.java +++ b/src/com/android/launcher3/views/FloatingIconViewCompanion.java @@ -27,6 +27,7 @@ public interface FloatingIconViewCompanion { void setIconVisible(boolean visible); void setForceHideDot(boolean hide); default void setForceHideRing(boolean hide) {} + default void resetIconScale(boolean shouldReset) {} /** * Sets the visibility of icon and dot of the view @@ -36,8 +37,9 @@ public interface FloatingIconViewCompanion { ((FloatingIconViewCompanion) view).setIconVisible(visible); ((FloatingIconViewCompanion) view).setForceHideDot(!visible); ((FloatingIconViewCompanion) view).setForceHideRing(!visible); + ((FloatingIconViewCompanion) view).resetIconScale(true); } else { view.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); } } -} +} \ No newline at end of file diff --git a/src/com/android/launcher3/views/FloatingSurfaceView.java b/src/com/android/launcher3/views/FloatingSurfaceView.java index 9ffe1ba700..07cc84c133 100644 --- a/src/com/android/launcher3/views/FloatingSurfaceView.java +++ b/src/com/android/launcher3/views/FloatingSurfaceView.java @@ -15,7 +15,6 @@ */ package com.android.launcher3.views; -import static com.android.launcher3.Utilities.ATLEAST_Q; import static com.android.launcher3.views.FloatingIconView.getLocationBoundsForView; import static com.android.launcher3.views.FloatingIconViewCompanion.setPropertiesVisible; @@ -25,8 +24,6 @@ import android.graphics.Picture; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.RectF; -import android.os.Build.VERSION; -import android.os.Build.VERSION_CODES; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.SurfaceHolder; @@ -108,7 +105,9 @@ public class FloatingSurfaceView extends AbstractFloatingView implements private void removeViewImmediate() { // Cancel any pending remove Executors.MAIN_EXECUTOR.getHandler().removeCallbacks(mRemoveViewRunnable); - removeViewFromParent(); + if (isAttachedToWindow()) { + removeViewFromParent(); + } } /** @@ -162,8 +161,9 @@ public class FloatingSurfaceView extends AbstractFloatingView implements if (mContract == null) { return; } - View icon = mLauncher.getFirstHomeElementForAppClose(null /* StableViewInfo */, - mContract.componentName.getPackageName(), mContract.user); + View icon = mLauncher.getFirstMatchForAppClose(null /* StableViewInfo */, + mContract.componentName.getPackageName(), mContract.user, + false /* supportsAllAppsState */); boolean iconChanged = mIcon != icon; if (iconChanged) { @@ -202,11 +202,7 @@ public class FloatingSurfaceView extends AbstractFloatingView implements private void sendIconInfo() { if (mContract != null) { - if (ATLEAST_Q) { - mContract.sendEndPosition(mIconPosition, mLauncher, mSurfaceView.getSurfaceControl()); - } else { - mContract.sendEndPosition(mIconPosition, mLauncher, null); - } + mContract.sendEndPosition(mIconPosition, mLauncher, mSurfaceView.getSurfaceControl()); } } @@ -218,7 +214,7 @@ public class FloatingSurfaceView extends AbstractFloatingView implements @Override public void surfaceChanged(@NonNull SurfaceHolder surfaceHolder, - int format, int width, int height) { + int format, int width, int height) { drawOnSurface(); } @@ -232,11 +228,17 @@ public class FloatingSurfaceView extends AbstractFloatingView implements private void drawOnSurface() { SurfaceHolder surfaceHolder = mSurfaceView.getHolder(); + if (!surfaceHolder.getSurface().isValid()) return; - Canvas c = surfaceHolder.lockHardwareCanvas(); - if (c != null) { - mPicture.draw(c); - surfaceHolder.unlockCanvasAndPost(c); + synchronized (this) { + Canvas c = surfaceHolder.lockHardwareCanvas(); + if (c != null) { + try { + mPicture.draw(c); + } finally { + surfaceHolder.unlockCanvasAndPost(c); + } + } } } @@ -245,4 +247,4 @@ public class FloatingSurfaceView extends AbstractFloatingView implements setPropertiesVisible(mIcon, isVisible); } } -} +} \ No newline at end of file diff --git a/src/com/android/launcher3/views/OptionsPopupView.java b/src/com/android/launcher3/views/OptionsPopupView.java index ee2cab3ebe..9ab7b6452b 100644 --- a/src/com/android/launcher3/views/OptionsPopupView.java +++ b/src/com/android/launcher3/views/OptionsPopupView.java @@ -15,13 +15,7 @@ */ package com.android.launcher3.views; -import static com.android.launcher3.BuildConfigs.WIDGETS_ENABLED; import static com.android.launcher3.LauncherState.EDIT_MODE; -import static com.android.launcher3.config.FeatureFlags.MULTI_SELECT_EDIT_MODE; -import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.IGNORE; -import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALL_APPS_TAP_OR_LONGPRESS; -import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SETTINGS_BUTTON_TAP_OR_LONGPRESS; -import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WIDGETSTRAY_BUTTON_TAP_OR_LONGPRESS; import android.content.Context; import android.content.Intent; @@ -47,6 +41,7 @@ import com.android.launcher3.Launcher; import com.android.launcher3.LauncherSettings; import com.android.launcher3.R; import com.android.launcher3.Utilities; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.logging.StatsLogManager.EventEnum; import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.popup.ArrowPopup; @@ -54,11 +49,11 @@ import com.android.launcher3.shortcuts.DeepShortcutView; import com.android.launcher3.testing.TestLogging; import com.android.launcher3.testing.shared.TestProtocol; import com.android.launcher3.widget.picker.WidgetsFullSheet; +import com.patrykmichalik.opto.core.PreferenceExtensionsKt; import java.util.ArrayList; import java.util.List; -import com.patrykmichalik.opto.core.PreferenceExtensionsKt; import app.lawnchair.preferences2.PreferenceManager2; import app.lawnchair.ui.popup.LauncherOptionsPopup; @@ -74,8 +69,7 @@ public class OptionsPopupView extends Arrow private static final String EXTRA_WALLPAPER_OFFSET = "com.android.launcher3.WALLPAPER_OFFSET"; private static final String EXTRA_WALLPAPER_FLAVOR = "com.android.launcher3.WALLPAPER_FLAVOR"; // An intent extra to indicate the launch source by launcher. - private static final String EXTRA_WALLPAPER_LAUNCH_SOURCE = - "com.android.wallpaper.LAUNCH_SOURCE"; + private static final String EXTRA_WALLPAPER_LAUNCH_SOURCE = "com.android.wallpaper.LAUNCH_SOURCE"; public final ArrayMap mItemMap = new ArrayMap<>(); private RectF mTargetRect; @@ -151,13 +145,11 @@ public class OptionsPopupView extends Arrow @Override public void assignMarginsAndBackgrounds(ViewGroup viewGroup) { - assignMarginsAndBackgrounds(viewGroup, mColors[0]); - // last shortcut doesn't need bottom margin - final int count = viewGroup.getChildCount() - 1; - for (int i = 0; i < count; i++) { - // These are shortcuts and not shortcut containers, but they still need bottom margin - MarginLayoutParams mlp = (MarginLayoutParams) viewGroup.getChildAt(i).getLayoutParams(); - mlp.bottomMargin = mChildContainerMargin; + if (FeatureFlags.showMaterialUPopup(getContext())) { + assignMarginsAndBackgrounds(viewGroup, + mColors[0]); + } else { + assignMarginsAndBackgrounds(viewGroup, Color.TRANSPARENT); } } @@ -179,6 +171,7 @@ public class OptionsPopupView extends Arrow if (activityContext == null) { return null; } + OptionsPopupView popup = (OptionsPopupView) activityContext.getLayoutInflater() .inflate(R.layout.longpress_options_menu, activityContext.getDragLayer(), false); popup.mTargetRect = targetRect; @@ -209,27 +202,12 @@ public class OptionsPopupView extends Arrow OptionsPopupView::toggleHomeScreenLock, OptionsPopupView::startSystemSettings, OptionsPopupView::enterHomeGardening, - OptionsPopupView::enterAllApps, OptionsPopupView::startWallpaperPicker, OptionsPopupView::onWidgetsClicked, OptionsPopupView::startSettings ); } - /** - * Used by the options to open All Apps, uses an intent as to not tie the implementation of - * opening All Apps with OptionsPopup, instead it uses the public API to open All Apps. - */ - public static boolean enterAllApps(View view) { - Launcher launcher = Launcher.getLauncher(view.getContext()); - launcher.startActivity( - new Intent(Intent.ACTION_ALL_APPS) - .setComponent(launcher.getComponentName()) - .setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP) - ); - return true; - } - private static boolean enterHomeGardening(View view) { Launcher launcher = Launcher.getLauncher(view.getContext()); launcher.getStateManager().goToState(EDIT_MODE); diff --git a/src/com/android/launcher3/views/RecyclerViewFastScroller.java b/src/com/android/launcher3/views/RecyclerViewFastScroller.java index e5f395775a..a2241e0f07 100644 --- a/src/com/android/launcher3/views/RecyclerViewFastScroller.java +++ b/src/com/android/launcher3/views/RecyclerViewFastScroller.java @@ -20,9 +20,6 @@ import static android.view.HapticFeedbackConstants.CLOCK_TICK; import static androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_IDLE; -import static com.android.launcher3.views.RecyclerViewFastScroller.FastScrollerLocation.ALL_APPS_SCROLLER; -import static com.android.launcher3.views.RecyclerViewFastScroller.FastScrollerLocation.WIDGET_SCROLLER; - import android.animation.ObjectAnimator; import android.content.Context; import android.content.res.Resources; @@ -43,15 +40,11 @@ import android.view.ViewConfiguration; import android.view.WindowInsets; import android.widget.TextView; -import androidx.annotation.NonNull; -import androidx.constraintlayout.widget.ConstraintLayout; import androidx.recyclerview.widget.RecyclerView; import com.android.launcher3.FastScrollRecyclerView; -import com.android.launcher3.Flags; import com.android.launcher3.R; import com.android.launcher3.Utilities; -import com.android.launcher3.allapps.LetterListTextView; import com.android.launcher3.graphics.FastScrollThumbDrawable; import com.android.launcher3.util.Themes; @@ -64,19 +57,6 @@ import app.lawnchair.theme.color.tokens.ColorTokens; * The track and scrollbar that shows when you scroll the list. */ public class RecyclerViewFastScroller extends View { - - /** FastScrollerLocation describes what RecyclerView the fast scroller is dedicated to. */ - public enum FastScrollerLocation { - UNKNOWN_SCROLLER(0), - ALL_APPS_SCROLLER(1), - WIDGET_SCROLLER(2); - - public final int location; - - FastScrollerLocation(int location) { - this.location = location; - } - } private static final String TAG = "RecyclerViewFastScroller"; private static final boolean DEBUG = false; private static final int FASTSCROLL_THRESHOLD_MILLIS = 10; @@ -128,8 +108,6 @@ public class RecyclerViewFastScroller extends View { private final Point mThumbDrawOffset = new Point(); private final Paint mTrackPaint; - private final int mThumbColor; - private final int mThumbLetterScrollerColor; private float mLastTouchY; private boolean mIsDragging; @@ -163,7 +141,6 @@ public class RecyclerViewFastScroller extends View { private int mDownX; private int mDownY; private int mLastY; - private FastScrollerLocation mFastScrollerLocation; public RecyclerViewFastScroller(Context context) { this(context, null); @@ -176,16 +153,13 @@ public class RecyclerViewFastScroller extends View { public RecyclerViewFastScroller(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); - mFastScrollerLocation = FastScrollerLocation.UNKNOWN_SCROLLER; mTrackPaint = new Paint(); mTrackPaint.setColor(ColorTokens.TextColorPrimary.resolveColor(getContext())); mTrackPaint.setAlpha(MAX_TRACK_ALPHA); - mThumbColor = Themes.getColorAccent(context); - mThumbLetterScrollerColor = context.getColor(R.color.materialColorSurfaceBright); mThumbPaint = new Paint(); mThumbPaint.setAntiAlias(true); - mThumbPaint.setColor(mThumbColor); + mThumbPaint.setColor(Themes.getColorAccent(context)); mThumbPaint.setStyle(Paint.Style.FILL); Resources res = getResources(); @@ -357,26 +331,11 @@ public class RecyclerViewFastScroller extends View { if (!sectionName.equals(mPopupSectionName)) { mPopupSectionName = sectionName; mPopupView.setText(sectionName); - // AllApps haptics are taken care of by AllAppsFastScrollHelper. - if (mFastScrollerLocation != ALL_APPS_SCROLLER) { - performHapticFeedback(CLOCK_TICK); - } + performHapticFeedback(CLOCK_TICK); } animatePopupVisibility(!TextUtils.isEmpty(sectionName)); mLastTouchY = boundedY; setThumbOffsetY((int) mLastTouchY); - updateFastScrollerLetterList(y); - } - - private void updateFastScrollerLetterList(int y) { - if (!shouldUseLetterFastScroller()) { - return; - } - ConstraintLayout mLetterList = mRv.getLetterList(); - for (int i = 0; i < mLetterList.getChildCount(); i++) { - LetterListTextView currentLetter = (LetterListTextView) mLetterList.getChildAt(i); - currentLetter.animateBasedOnYPosition(y + mTouchOffsetY); - } } /** End any active fast scrolling touch handling, if applicable. */ @@ -402,35 +361,15 @@ public class RecyclerViewFastScroller extends View { mThumbDrawOffset.set(getWidth() / 2, mRv.getScrollBarTop()); // Draw the track float halfW = mWidth / 2; - boolean useLetterFastScroller = shouldUseLetterFastScroller(); - if (useLetterFastScroller) { - float translateX; - if (mIsDragging) { - // halfW * 3 is half circle. - translateX = halfW * 3; - } else { - translateX = halfW * 5; - } - canvas.translate(translateX, mThumbOffsetY); - } else { - canvas.drawRoundRect(-halfW, 0, halfW, mRv.getScrollbarTrackHeight(), - mWidth, mWidth, mTrackPaint); - canvas.translate(0, mThumbOffsetY); - } - mThumbDrawOffset.y += mThumbOffsetY; + canvas.drawRoundRect(-halfW, 0, halfW, mRv.getScrollbarTrackHeight(), + mWidth, mWidth, mTrackPaint); - /* Draw half circle */ + canvas.translate(0, mThumbOffsetY); + mThumbDrawOffset.y += mThumbOffsetY; halfW += mThumbPadding; float r = getScrollThumbRadius(); - if (useLetterFastScroller) { - mThumbPaint.setColor(mThumbLetterScrollerColor); - mThumbBounds.set(0, 0, 0, mThumbHeight); - canvas.drawCircle(-halfW, halfW, r * 2, mThumbPaint); - } else { - mThumbPaint.setColor(mThumbColor); - mThumbBounds.set(-halfW, 0, halfW, mThumbHeight); - canvas.drawRoundRect(mThumbBounds, r, r, mThumbPaint); - } + mThumbBounds.set(-halfW, 0, halfW, mThumbHeight); + canvas.drawRoundRect(mThumbBounds, r, r, mThumbPaint); mThumbBounds.roundOut(SYSTEM_GESTURE_EXCLUSION_RECT.get(0)); // swiping very close to the thumb area (not just within it's bound) // will also prevent back gesture @@ -445,11 +384,6 @@ public class RecyclerViewFastScroller extends View { canvas.restoreToCount(saveCount); } - boolean shouldUseLetterFastScroller() { - return Flags.letterFastScroller() - && getScrollerLocation() == FastScrollerLocation.ALL_APPS_SCROLLER; - } - @Override public WindowInsets onApplyWindowInsets(WindowInsets insets) { if (Utilities.ATLEAST_Q) { @@ -495,25 +429,19 @@ public class RecyclerViewFastScroller extends View { return isNearThumb(x, y); } - public FastScrollerLocation getScrollerLocation() { - return mFastScrollerLocation; - } - - public void setFastScrollerLocation(@NonNull FastScrollerLocation location) { - mFastScrollerLocation = location; + /** + * Returns whether the specified x position is near the scroll bar. + */ + public boolean isNearScrollBar(int x) { + return x >= (getWidth() - mMaxWidth) / 2 - mScrollbarLeftOffsetTouchDelegate + && x <= (getWidth() + mMaxWidth) / 2; } private void animatePopupVisibility(boolean visible) { if (mPopupVisible != visible) { mPopupVisible = visible; - if (shouldUseLetterFastScroller()) { - mRv.getLetterList().animate().alpha(visible ? 1f : 0f) - .setDuration(visible ? 200 : 150).start(); - } else { - mPopupView.animate().cancel(); - mPopupView.animate().alpha(visible ? 1f : 0f) - .setDuration(visible ? 200 : 150).start(); - } + mPopupView.animate().cancel(); + mPopupView.animate().alpha(visible ? 1f : 0f).setDuration(visible ? 200 : 150).start(); } } diff --git a/src/com/android/launcher3/views/ScrimView.java b/src/com/android/launcher3/views/ScrimView.java index 77b23c0163..3b87425cee 100644 --- a/src/com/android/launcher3/views/ScrimView.java +++ b/src/com/android/launcher3/views/ScrimView.java @@ -31,6 +31,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Px; import androidx.core.graphics.ColorUtils; +import com.android.launcher3.BaseActivity; import com.android.launcher3.Insettable; import com.android.launcher3.util.SystemUiController; @@ -56,7 +57,8 @@ public class ScrimView extends View implements Insettable { } @Override - public void setInsets(Rect insets) {} + public void setInsets(Rect insets) { + } @Override public boolean hasOverlappingRendering() { @@ -143,8 +145,7 @@ public class ScrimView extends View implements Insettable { protected SystemUiController getSystemUiController() { if (mSystemUiController == null) { - mSystemUiController = - ActivityContext.lookupContext(getContext()).getSystemUiController(); + mSystemUiController = BaseActivity.fromContext(getContext()).getSystemUiController(); } return mSystemUiController; } diff --git a/src/com/android/launcher3/views/SpringRelativeLayout.java b/src/com/android/launcher3/views/SpringRelativeLayout.java index a13152ec76..923eb19d3b 100644 --- a/src/com/android/launcher3/views/SpringRelativeLayout.java +++ b/src/com/android/launcher3/views/SpringRelativeLayout.java @@ -25,6 +25,8 @@ import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView.EdgeEffectFactory; +import com.android.launcher3.Utilities; + /** * View group to allow rendering overscroll effect in a child at the parent level */ @@ -44,8 +46,10 @@ public class SpringRelativeLayout extends RelativeLayout { public SpringRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); - mEdgeGlowTop = new EdgeEffect(context, attrs); - mEdgeGlowBottom = new EdgeEffect(context, attrs); + mEdgeGlowTop = Utilities.ATLEAST_S + ? new EdgeEffect(context, attrs) : new EdgeEffect(context); + mEdgeGlowBottom = Utilities.ATLEAST_S + ? new EdgeEffect(context, attrs) : new EdgeEffect(context); setWillNotDraw(false); } diff --git a/src/com/android/launcher3/views/StickyHeaderLayout.java b/src/com/android/launcher3/views/StickyHeaderLayout.java index 4142e1fa98..090251f257 100644 --- a/src/com/android/launcher3/views/StickyHeaderLayout.java +++ b/src/com/android/launcher3/views/StickyHeaderLayout.java @@ -120,19 +120,7 @@ public class StickyHeaderLayout extends LinearLayout implements } private float getCurrentScroll() { - float scroll; - if (mCurrentRecyclerView.getVisibility() != VISIBLE) { - // When no list is displayed, assume no scroll. - scroll = 0f; - } else if (mCurrentEmptySpaceView != null) { - // Otherwise use empty space view as reference to position. - scroll = mCurrentEmptySpaceView.getY(); - } else { - // If there is no empty space view, but the list is visible, we are scrolled away - // completely, so assume all non-sticky children should also be scrolled away. - scroll = -mHeaderHeight; - } - return mScrollOffset + scroll; + return mScrollOffset + (mCurrentEmptySpaceView == null ? 0 : mCurrentEmptySpaceView.getY()); } @Override diff --git a/src/com/android/launcher3/widget/AddItemWidgetsBottomSheet.java b/src/com/android/launcher3/widget/AddItemWidgetsBottomSheet.java index f981a5a72b..31a28e4f40 100644 --- a/src/com/android/launcher3/widget/AddItemWidgetsBottomSheet.java +++ b/src/com/android/launcher3/widget/AddItemWidgetsBottomSheet.java @@ -168,15 +168,15 @@ public class AddItemWidgetsBottomSheet extends AbstractSlideInView implements OnClickListener, OnLongClickListener, - WidgetPickerDataChangeListener, Insettable, OnDeviceProfileChangeListener { + PopupDataProvider.PopupDataChangeListener, Insettable, OnDeviceProfileChangeListener { /** The default number of cells that can fit horizontally in a widget sheet. */ public static final int DEFAULT_MAX_HORIZONTAL_SPANS = 4; @@ -113,14 +113,14 @@ public abstract class BaseWidgetSheet extends AbstractSlideInView WindowInsets windowInsets = WindowManagerProxy.INSTANCE.get(getContext()) .normalizeWindowInsets(getContext(), getRootWindowInsets(), new Rect()); mNavBarScrimHeight = getNavBarScrimHeight(windowInsets); - mActivityContext.getWidgetPickerDataProvider().setChangeListener(this); + mActivityContext.getPopupDataProvider().setChangeListener(this); mActivityContext.addOnDeviceProfileChangeListener(this); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); - mActivityContext.getWidgetPickerDataProvider().setChangeListener(null); + mActivityContext.getPopupDataProvider().setChangeListener(null); mActivityContext.removeOnDeviceProfileChangeListener(this); } @@ -134,21 +134,6 @@ public abstract class BaseWidgetSheet extends AbstractSlideInView setupNavBarColor(); } - @Override - public void setScaleY(float scaleY) { - super.setScaleY(scaleY); - try { - if (predictiveBackThreeButtonNav() && mNavBarScrimHeight > 0) { - // Call invalidate to prevent navbar scrim from scaling. The navbar scrim is drawn - // directly onto the canvas. To prevent it from being scaled with the canvas, there's a - // counter scale applied in dispatchDraw. - invalidate(); - } - } catch (Throwable t) { - // LC-Ignored - } - } - @Override public final void onClick(View v) { WidgetCell wc; @@ -160,36 +145,40 @@ public abstract class BaseWidgetSheet extends AbstractSlideInView return; } - scrollToWidgetCell(wc); + if (enableWidgetTapToAdd()) { + scrollToWidgetCell(wc); - if (mWidgetCellWithAddButton != null) { - if (mWidgetCellWithAddButton.isShowingAddButton()) { - // If there is a add button currently showing, hide it. - mWidgetCellWithAddButton.hideAddButton(/* animate= */ true); - } else { - // The last recorded widget cell to show an add button is no longer showing it, - // likely because the widget cell has been recycled or lost focus. If this is - // the cell that has been clicked, we will show it below. - mWidgetCellWithAddButton = null; + if (mWidgetCellWithAddButton != null) { + if (mWidgetCellWithAddButton.isShowingAddButton()) { + // If there is a add button currently showing, hide it. + mWidgetCellWithAddButton.hideAddButton(/* animate= */ true); + } else { + // The last recorded widget cell to show an add button is no longer showing it, + // likely because the widget cell has been recycled or lost focus. If this is + // the cell that has been clicked, we will show it below. + mWidgetCellWithAddButton = null; + } } - } - if (mWidgetCellWithAddButton != wc) { - // If click is on a cell not showing an add button, show it now. - final PendingAddItemInfo info = (PendingAddItemInfo) wc.getTag(); - if (mActivityContext instanceof Launcher) { - wc.showAddButton((view) -> addWidget(info)); - } else { - wc.showAddButton((view) -> mActivityContext.getItemOnClickListener() - .onClick(wc)); + if (mWidgetCellWithAddButton != wc) { + // If click is on a cell not showing an add button, show it now. + final PendingAddItemInfo info = (PendingAddItemInfo) wc.getTag(); + if (mActivityContext instanceof Launcher) { + wc.showAddButton((view) -> addWidget(info)); + } else { + wc.showAddButton((view) -> mActivityContext.getItemOnClickListener() + .onClick(wc)); + } } - } - mWidgetCellWithAddButton = mWidgetCellWithAddButton != wc ? wc : null; - if (mWidgetCellWithAddButton != null) { - mLastSelectedWidgetItem = mWidgetCellWithAddButton.getWidgetItem(); + mWidgetCellWithAddButton = mWidgetCellWithAddButton != wc ? wc : null; + if (mWidgetCellWithAddButton != null) { + mLastSelectedWidgetItem = mWidgetCellWithAddButton.getWidgetItem(); + } else { + mLastSelectedWidgetItem = null; + } } else { - mLastSelectedWidgetItem = null; + mActivityContext.getItemOnClickListener().onClick(wc); } } @@ -346,10 +335,8 @@ public abstract class BaseWidgetSheet extends AbstractSlideInView super.dispatchDraw(canvas); if (mNavBarScrimHeight > 0) { - float left = (getWidth() - getWidth() / getScaleX()) / 2; - float top = getHeight() / 2f + (getHeight() / 2f - mNavBarScrimHeight) / getScaleY(); - canvas.drawRect(left, top, getWidth() / getScaleX(), - top + mNavBarScrimHeight / getScaleY(), mNavBarScrimPaint); + canvas.drawRect(0, getHeight() - mNavBarScrimHeight, getWidth(), getHeight(), + mNavBarScrimPaint); } } @@ -362,21 +349,8 @@ public abstract class BaseWidgetSheet extends AbstractSlideInView * status bar, into account. */ protected void doMeasure(int widthMeasureSpec, int heightMeasureSpec) { - int widthUsed = getInsetsWidth(); - DeviceProfile deviceProfile = mActivityContext.getDeviceProfile(); - measureChildWithMargins(mContent, widthMeasureSpec, - widthUsed, heightMeasureSpec, deviceProfile.bottomSheetTopPadding); - setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), - MeasureSpec.getSize(heightMeasureSpec)); - } - - /** - * Returns the width used on left and right by the insets / padding. - */ - protected int getInsetsWidth() { int widthUsed; - DeviceProfile deviceProfile = mActivityContext.getDeviceProfile(); if (deviceProfile.isTablet) { widthUsed = Math.max(2 * getTabletHorizontalMargin(deviceProfile), 2 * (mInsets.left + mInsets.right)); @@ -387,7 +361,11 @@ public abstract class BaseWidgetSheet extends AbstractSlideInView widthUsed = Math.max(padding.left + padding.right, 2 * (mInsets.left + mInsets.right)); } - return widthUsed; + + measureChildWithMargins(mContent, widthMeasureSpec, + widthUsed, heightMeasureSpec, deviceProfile.bottomSheetTopPadding); + setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), + MeasureSpec.getSize(heightMeasureSpec)); } /** Returns the horizontal margins to be applied to the widget sheet. **/ diff --git a/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java b/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java index 819c1eebec..50556f026e 100644 --- a/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java +++ b/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java @@ -15,40 +15,33 @@ */ package com.android.launcher3.widget; -import static android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN; - import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; -import static com.android.launcher3.widget.LauncherAppWidgetProviderInfo.fromProviderInfo; -import android.appwidget.AppWidgetProviderInfo; import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; +import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.RectF; import android.graphics.drawable.Drawable; -import android.os.Build.VERSION; -import android.os.Build.VERSION_CODES; import android.os.Handler; import android.os.Process; import android.util.Log; import android.util.Size; -import android.widget.RemoteViews; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; -import androidx.core.os.BuildCompat; import com.android.launcher3.DeviceProfile; -import com.android.launcher3.Flags; import com.android.launcher3.LauncherAppState; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.icons.BitmapRenderer; import com.android.launcher3.icons.LauncherIcons; +import com.android.launcher3.icons.ShadowGenerator; import com.android.launcher3.model.WidgetItem; import com.android.launcher3.pm.ShortcutConfigActivityInfo; import com.android.launcher3.util.CancellableTask; @@ -60,19 +53,20 @@ import com.android.launcher3.widget.util.WidgetSizes; import java.util.concurrent.ExecutionException; import java.util.function.Consumer; -/** - * Utility class to generate widget previews - * - * Note that it no longer uses database, all previews are freshly generated - */ +/** Utility class to load widget previews */ public class DatabaseWidgetPreviewLoader { private static final String TAG = "WidgetPreviewLoader"; private final Context mContext; + private final float mPreviewBoxCornerRadius; public DatabaseWidgetPreviewLoader(Context context) { mContext = context; + float previewCornerRadius = RoundedCornerEnforcement.computeEnforcedRadius(context); + mPreviewBoxCornerRadius = previewCornerRadius > 0 + ? previewCornerRadius + : mContext.getResources().getDimension(R.dimen.widget_preview_corner_radius); } /** @@ -84,10 +78,10 @@ public class DatabaseWidgetPreviewLoader { public CancellableTask loadPreview( @NonNull WidgetItem item, @NonNull Size previewSize, - @NonNull Consumer callback) { + @NonNull Consumer callback) { Handler handler = getLoaderExecutor().getHandler(); - CancellableTask request = new CancellableTask<>( - () -> generatePreviewInfoBg(item, previewSize.getWidth(), previewSize.getHeight()), + CancellableTask request = new CancellableTask<>( + () -> generatePreview(item, previewSize.getWidth(), previewSize.getHeight()), MAIN_EXECUTOR, callback); Utilities.postAsyncCallback(handler, request); @@ -100,41 +94,6 @@ public class DatabaseWidgetPreviewLoader { return Executors.UI_HELPER_EXECUTOR; } - /** Generated the preview object. This method must be called on a background thread */ - @VisibleForTesting - @NonNull - public WidgetPreviewInfo generatePreviewInfoBg( - WidgetItem item, int previewWidth, int previewHeight) { - WidgetPreviewInfo result = new WidgetPreviewInfo(); - - AppWidgetProviderInfo widgetInfo = item.widgetInfo; - if (BuildCompat.isAtLeastV() && Flags.enableGeneratedPreviews() && widgetInfo != null - && ((widgetInfo.generatedPreviewCategories & WIDGET_CATEGORY_HOME_SCREEN) != 0)) { - result.remoteViews = new WidgetManagerHelper(mContext) - .loadGeneratedPreview(widgetInfo, WIDGET_CATEGORY_HOME_SCREEN); - if (result.remoteViews != null) { - result.providerInfo = widgetInfo; - } - } - - if (Utilities.ATLEAST_S) { - if (result.providerInfo == null && widgetInfo != null - && widgetInfo.previewLayout != Resources.ID_NULL) { - result.providerInfo = fromProviderInfo(mContext, widgetInfo.clone()); - // A hack to force the initial layout to be the preview layout since there is no API for - // rendering a preview layout for work profile apps yet. For non-work profile layout, a - // proper solution is to use RemoteViews(PackageName, LayoutId). - result.providerInfo.initialLayout = item.widgetInfo.previewLayout; - } - } - - if (result.providerInfo == null) { - // fallback to bitmap preview - result.previewBitmap = generatePreview(item, previewWidth, previewHeight); - } - return result; - } - /** * Returns a generated preview for a widget and if the preview should be saved in persistent * storage. @@ -225,14 +184,19 @@ public class DatabaseWidgetPreviewLoader { // Draw horizontal and vertical lines to represent individual columns. final Paint p = new Paint(Paint.ANTI_ALIAS_FLAG); - boxRect = new RectF(/* left= */ 0, /* top= */ 0, /* right= */ - previewWidthF, /* bottom= */ previewHeightF); - p.setStyle(Paint.Style.FILL); - p.setColor(Color.WHITE); - float roundedCorner = mContext.getResources().getDimension( - android.R.dimen.system_app_widget_background_radius); - c.drawRoundRect(boxRect, roundedCorner, roundedCorner, p); + if (Utilities.ATLEAST_S) { + boxRect = new RectF(/* left= */ 0, /* top= */ 0, /* right= */ + previewWidthF, /* bottom= */ previewHeightF); + + p.setStyle(Paint.Style.FILL); + p.setColor(Color.WHITE); + float roundedCorner = mContext.getResources().getDimension( + android.R.dimen.system_app_widget_background_radius); + c.drawRoundRect(boxRect, roundedCorner, roundedCorner, p); + } else { + boxRect = drawBoxWithShadow(c, previewWidthF, previewHeightF); + } p.setStyle(Paint.Style.STROKE); p.setStrokeWidth(mContext.getResources() @@ -255,8 +219,8 @@ public class DatabaseWidgetPreviewLoader { // Draw icon in the center. try { - Drawable icon = info.getFullResIcon( - LauncherAppState.getInstance(mContext).getIconCache()); + Drawable icon = LauncherAppState.getInstance(mContext).getIconCache() + .getFullResIcon(info.provider.getPackageName(), info.icon); if (icon != null) { int appIconSize = dp.iconSizePx; int iconSize = (int) Math.min(appIconSize * scale, @@ -274,6 +238,21 @@ public class DatabaseWidgetPreviewLoader { }); } + private RectF drawBoxWithShadow(Canvas c, int width, int height) { + Resources res = mContext.getResources(); + + ShadowGenerator.Builder builder = new ShadowGenerator.Builder(Color.WHITE); + builder.shadowBlur = res.getDimension(R.dimen.widget_preview_shadow_blur); + builder.radius = mPreviewBoxCornerRadius; + builder.keyShadowDistance = res.getDimension(R.dimen.widget_preview_key_shadow_distance); + + builder.bounds.set(builder.shadowBlur, builder.shadowBlur, + width - builder.shadowBlur, + height - builder.shadowBlur - builder.keyShadowDistance); + builder.drawShadow(c); + return builder.bounds; + } + private Bitmap generateShortcutPreview( ShortcutConfigActivityInfo info, int maxWidth, int maxHeight) { int iconSize = ActivityContext.lookupContext(mContext).getDeviceProfile().allAppsIconSizePx; @@ -307,15 +286,4 @@ public class DatabaseWidgetPreviewLoader { throw new RuntimeException(e); } } - - /** - * Simple class to hold preview information - */ - public static class WidgetPreviewInfo { - - public AppWidgetProviderInfo providerInfo; - public RemoteViews remoteViews; - - public Bitmap previewBitmap; - } } diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHost.java b/src/com/android/launcher3/widget/LauncherAppWidgetHost.java index feac9cbb5c..32991bdbee 100644 --- a/src/com/android/launcher3/widget/LauncherAppWidgetHost.java +++ b/src/com/android/launcher3/widget/LauncherAppWidgetHost.java @@ -16,28 +16,66 @@ package com.android.launcher3.widget; +import static com.android.launcher3.widget.LauncherWidgetHolder.APPWIDGET_HOST_ID; + import android.appwidget.AppWidgetHost; import android.appwidget.AppWidgetProviderInfo; import android.content.Context; +import android.view.accessibility.AccessibilityNodeInfo; +import android.widget.RemoteViews; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; + +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.util.Executors; +import com.android.launcher3.util.SafeCloseable; +import com.android.launcher3.widget.LauncherWidgetHolder.ProviderChangedListener; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.WeakHashMap; +import java.util.function.IntConsumer; import app.lawnchair.LawnchairAppWidgetHostView; /** - * Specific {@link AppWidgetHost} that creates our {@link LauncherAppWidgetHostView} + * Specific {@link AppWidgetHost} that creates our + * {@link LauncherAppWidgetHostView} * which correctly captures all long-press events. This ensures that users can * always pick up and move widgets. */ -class LauncherAppWidgetHost extends ListenableAppWidgetHost { +class LauncherAppWidgetHost extends AppWidgetHost { + @NonNull + private final List mProviderChangeListeners; + + @NonNull + private final Context mContext; + + @Nullable + private final IntConsumer mAppWidgetRemovedCallback; @Nullable private ListenableHostView mViewToRecycle; - LauncherAppWidgetHost(@NonNull Context context, int appWidgetId) { - super(context, appWidgetId); + public LauncherAppWidgetHost(@NonNull Context context, + @Nullable IntConsumer appWidgetRemovedCallback, + List providerChangeListeners) { + super(context, APPWIDGET_HOST_ID); + mContext = context; + mAppWidgetRemovedCallback = appWidgetRemovedCallback; + mProviderChangeListeners = providerChangeListeners; + } + + @Override + protected void onProvidersChanged() { + if (!mProviderChangeListeners.isEmpty()) { + for (LauncherWidgetHolder.ProviderChangedListener callback : new ArrayList<>(mProviderChangeListeners)) { + callback.notifyWidgetProvidersChanged(); + } + } } /** @@ -47,21 +85,46 @@ class LauncherAppWidgetHost extends ListenableAppWidgetHost { mViewToRecycle = viewToRecycle; } - @VisibleForTesting - @Nullable ListenableHostView getViewToRecycle() { - return mViewToRecycle; - } - @Override @NonNull public LauncherAppWidgetHostView onCreateView(Context context, int appWidgetId, AppWidgetProviderInfo appWidget) { - ListenableHostView result = - mViewToRecycle != null ? mViewToRecycle : new ListenableHostView(context); + ListenableHostView result = mViewToRecycle != null ? mViewToRecycle : new ListenableHostView(context); mViewToRecycle = null; return result; } + /** + * Called when the AppWidget provider for a AppWidget has been upgraded to a new + * apk. + */ + @Override + protected void onProviderChanged(int appWidgetId, @NonNull AppWidgetProviderInfo appWidget) { + LauncherAppWidgetProviderInfo info = LauncherAppWidgetProviderInfo.fromProviderInfo( + mContext, appWidget); + super.onProviderChanged(appWidgetId, info); + // The super method updates the dimensions of the providerInfo. Update the + // launcher spans accordingly. + info.initSpans(mContext, LauncherAppState.getIDP(mContext)); + } + + /** + * Called on an appWidget is removed for a widgetId + * + * @param appWidgetId TODO: make this override when SDK is updated + */ + @Override + public void onAppWidgetRemoved(int appWidgetId) { + if (mAppWidgetRemovedCallback == null) { + return; + } + // Route the call via model thread, in case it comes while a loader-bind is in + // progress + Executors.MODEL_EXECUTOR.execute( + () -> Executors.MAIN_EXECUTOR.execute( + () -> mAppWidgetRemovedCallback.accept(appWidgetId))); + } + /** * The same as super.clearViews(), except with the scope exposed */ @@ -69,4 +132,38 @@ class LauncherAppWidgetHost extends ListenableAppWidgetHost { public void clearViews() { super.clearViews(); } + + public static class ListenableHostView extends LauncherAppWidgetHostView { + + private Set mUpdateListeners = Collections.EMPTY_SET; + + ListenableHostView(Context context) { + super(context); + } + + @Override + public void updateAppWidget(RemoteViews remoteViews) { + super.updateAppWidget(remoteViews); + mUpdateListeners.forEach(Runnable::run); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(LauncherAppWidgetHostView.class.getName()); + } + + /** + * Adds a callback to be run everytime the provided app widget updates. + * + * @return a closable to remove this callback + */ + public SafeCloseable addUpdateListener(Runnable callback) { + if (mUpdateListeners == Collections.EMPTY_SET) { + mUpdateListeners = Collections.newSetFromMap(new WeakHashMap<>()); + } + mUpdateListeners.add(callback); + return () -> mUpdateListeners.remove(callback); + } + } } diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java index e3643fe9a3..48c07c9c9a 100644 --- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java +++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java @@ -70,8 +70,6 @@ public class LauncherAppWidgetHostView extends BaseLauncherAppWidgetHostView private static final String TRACE_METHOD_NAME = "appwidget load-widget "; - private static final Integer NO_LAYOUT_ID = Integer.valueOf(0); - private final CheckLongPressHelper mLongPressHelper; protected final ActivityContext mActivityContext; @@ -81,8 +79,7 @@ public class LauncherAppWidgetHostView extends BaseLauncherAppWidgetHostView private Runnable mAutoAdvanceRunnable; private long mDeferUpdatesUntilMillis = 0; - private RemoteViews mLastRemoteViews; - private boolean mReapplyOnResumeUpdates = false; + RemoteViews mLastRemoteViews; private boolean mTrackingWidgetUpdate = false; @@ -126,7 +123,7 @@ public class LauncherAppWidgetHostView extends BaseLauncherAppWidgetHostView @Override public void setAppWidget(int appWidgetId, AppWidgetProviderInfo info) { super.setAppWidget(appWidgetId, info); - if (!mTrackingWidgetUpdate && appWidgetId != -1) { + if (!mTrackingWidgetUpdate) { mTrackingWidgetUpdate = true; Log.i(TAG, "App widget created with id: " + appWidgetId); if (ATLEAST_Q) { @@ -145,11 +142,11 @@ public class LauncherAppWidgetHostView extends BaseLauncherAppWidgetHostView } mTrackingWidgetUpdate = false; } - mLastRemoteViews = remoteViews; - mReapplyOnResumeUpdates = isDeferringUpdates(); - if (mReapplyOnResumeUpdates) { + if (isDeferringUpdates()) { + mLastRemoteViews = remoteViews; return; } + mLastRemoteViews = null; super.updateAppWidget(remoteViews); @@ -157,18 +154,6 @@ public class LauncherAppWidgetHostView extends BaseLauncherAppWidgetHostView checkIfAutoAdvance(); } - @Override - public void onViewAdded(View child) { - super.onViewAdded(child); - mReapplyOnResumeUpdates |= isDeferringUpdates(); - } - - @Override - public void onViewRemoved(View child) { - super.onViewRemoved(child); - mReapplyOnResumeUpdates |= isDeferringUpdates(); - } - private boolean checkScrollableRecursively(ViewGroup viewGroup) { if (viewGroup instanceof AdapterView) { return true; @@ -185,21 +170,6 @@ public class LauncherAppWidgetHostView extends BaseLauncherAppWidgetHostView return false; } - private boolean isTaggedAsScrollable() { - // TODO: Introduce new api in AppWidgetHostView to indicate whether the widget is - // scrollable. - for (int i = 0; i < this.getChildCount(); i++) { - View child = this.getChildAt(i); - final Integer layoutId = (Integer) child.getTag(android.R.id.widget_frame); - if (layoutId != null) { - // The layout id is only set to 0 when RemoteViews is created from - // DrawInstructions. - return NO_LAYOUT_ID.equals(layoutId); - } - } - return false; - } - /** * Returns true if the application of {@link RemoteViews} through {@link #updateAppWidget} are * currently being deferred. @@ -223,16 +193,18 @@ public class LauncherAppWidgetHostView extends BaseLauncherAppWidgetHostView * {@link #updateAppWidget} and apply any deferred updates. */ public void endDeferringUpdates() { + RemoteViews remoteViews; mDeferUpdatesUntilMillis = 0; - if (mReapplyOnResumeUpdates) { - updateAppWidget(mLastRemoteViews); + remoteViews = mLastRemoteViews; + + if (remoteViews != null) { + updateAppWidget(remoteViews); } } - @Override public boolean onInterceptTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { - BaseDragLayer dragLayer = mActivityContext.getDragLayer(); + BaseDragLayer dragLayer = mActivityContext.getDragLayer(); if (mIsScrollable) { dragLayer.requestDisallowInterceptTouchEvent(true); } @@ -242,7 +214,6 @@ public class LauncherAppWidgetHostView extends BaseLauncherAppWidgetHostView return mLongPressHelper.hasPerformedLongPress(); } - @Override public boolean onTouchEvent(MotionEvent ev) { mLongPressHelper.onTouchEvent(ev); // We want to keep receiving though events to be able to cancel long press on ACTION_UP @@ -272,6 +243,16 @@ public class LauncherAppWidgetHostView extends BaseLauncherAppWidgetHostView mLongPressHelper.cancelLongPress(); } + @Override + public AppWidgetProviderInfo getAppWidgetInfo() { + AppWidgetProviderInfo info = super.getAppWidgetInfo(); + if (info != null && !(info instanceof LauncherAppWidgetProviderInfo)) { + throw new IllegalStateException("Launcher widget must have" + + " LauncherAppWidgetProviderInfo"); + } + return info; + } + @Override public void getFocusedRect(Rect r) { super.getFocusedRect(r); @@ -291,7 +272,7 @@ public class LauncherAppWidgetHostView extends BaseLauncherAppWidgetHostView @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); - mIsScrollable = isTaggedAsScrollable() || checkScrollableRecursively(this); + mIsScrollable = checkScrollableRecursively(this); } /** diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java b/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java index 10e131d639..63d0b25409 100644 --- a/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java +++ b/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java @@ -1,11 +1,10 @@ package com.android.launcher3.widget; -import static com.android.launcher3.InvariantDeviceProfile.TYPE_PHONE; +import static com.android.launcher3.Utilities.ATLEAST_S; import android.appwidget.AppWidgetProviderInfo; import android.content.ComponentName; import android.content.Context; -import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.graphics.Point; import android.graphics.Rect; @@ -13,16 +12,12 @@ import android.graphics.drawable.Drawable; import android.os.Parcel; import android.os.UserHandle; -import androidx.annotation.Nullable; - import com.android.launcher3.DeviceProfile; -import com.android.launcher3.Flags; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.LauncherAppState; import com.android.launcher3.Utilities; +import com.android.launcher3.icons.ComponentWithLabelAndIcon; import com.android.launcher3.icons.IconCache; -import com.android.launcher3.icons.cache.BaseIconCache; -import com.android.launcher3.icons.cache.CachedObject; import com.android.launcher3.model.data.LauncherAppWidgetInfo; /** @@ -31,7 +26,8 @@ import com.android.launcher3.model.data.LauncherAppWidgetInfo; * (who's implementation is owned by the launcher). This object represents a widget type / class, * as opposed to a widget instance, and so should not be confused with {@link LauncherAppWidgetInfo} */ -public class LauncherAppWidgetProviderInfo extends AppWidgetProviderInfo implements CachedObject { +public class LauncherAppWidgetProviderInfo extends AppWidgetProviderInfo + implements ComponentWithLabelAndIcon { public static final String CLS_CUSTOM_WIDGET_PREFIX = "#custom-widget-"; @@ -73,8 +69,6 @@ public class LauncherAppWidgetProviderInfo extends AppWidgetProviderInfo impleme protected boolean mIsMinSizeFulfilled; - private PackageManager mPM; - public static LauncherAppWidgetProviderInfo fromProviderInfo(Context context, AppWidgetProviderInfo info) { final LauncherAppWidgetProviderInfo launcherInfo; @@ -103,7 +97,6 @@ public class LauncherAppWidgetProviderInfo extends AppWidgetProviderInfo impleme } public void initSpans(Context context, InvariantDeviceProfile idp) { - mPM = context.getApplicationContext().getPackageManager(); int minSpanX = 0; int minSpanY = 0; int maxSpanX = idp.numColumns; @@ -111,15 +104,9 @@ public class LauncherAppWidgetProviderInfo extends AppWidgetProviderInfo impleme int spanX = 0; int spanY = 0; + Point cellSize = new Point(); for (DeviceProfile dp : idp.supportedProfiles) { - // On phones we no longer support regular landscape, only fixed landscape for this - // reason we don't need to take regular landscape into account in phones - if (Flags.oneGridSpecs() && dp.inv.deviceType == TYPE_PHONE - && dp.inv.isFixedLandscape != dp.isLandscape) { - continue; - } - dp.getCellSize(cellSize); Rect widgetPadding = dp.widgetPadding; @@ -130,13 +117,15 @@ public class LauncherAppWidgetProviderInfo extends AppWidgetProviderInfo impleme getSpanY(widgetPadding, minResizeHeight, dp.cellLayoutBorderSpacePx.y, cellSize.y)); - if (maxResizeWidth > 0) { - maxSpanX = Math.min(maxSpanX, getSpanX(widgetPadding, maxResizeWidth, - dp.cellLayoutBorderSpacePx.x, cellSize.x)); - } - if (maxResizeHeight > 0) { - maxSpanY = Math.min(maxSpanY, getSpanY(widgetPadding, maxResizeHeight, - dp.cellLayoutBorderSpacePx.y, cellSize.y)); + if (ATLEAST_S) { + if (maxResizeWidth > 0) { + maxSpanX = Math.min(maxSpanX, getSpanX(widgetPadding, maxResizeWidth, + dp.cellLayoutBorderSpacePx.x, cellSize.x)); + } + if (maxResizeHeight > 0) { + maxSpanY = Math.min(maxSpanY, getSpanY(widgetPadding, maxResizeHeight, + dp.cellLayoutBorderSpacePx.y, cellSize.y)); + } } spanX = Math.max(spanX, @@ -147,16 +136,18 @@ public class LauncherAppWidgetProviderInfo extends AppWidgetProviderInfo impleme cellSize.y)); } - // Ensures maxSpan >= minSpan - maxSpanX = Math.max(maxSpanX, minSpanX); - maxSpanY = Math.max(maxSpanY, minSpanY); + if (ATLEAST_S) { + // Ensures maxSpan >= minSpan + maxSpanX = Math.max(maxSpanX, minSpanX); + maxSpanY = Math.max(maxSpanY, minSpanY); - // Use targetCellWidth/Height if it is within the min/max ranges. - // Otherwise, use the span of minWidth/Height. - if (targetCellWidth >= minSpanX && targetCellWidth <= maxSpanX - && targetCellHeight >= minSpanY && targetCellHeight <= maxSpanY) { - spanX = targetCellWidth; - spanY = targetCellHeight; + // Use targetCellWidth/Height if it is within the min/max ranges. + // Otherwise, use the span of minWidth/Height. + if (targetCellWidth >= minSpanX && targetCellWidth <= maxSpanX + && targetCellHeight >= minSpanY && targetCellHeight <= maxSpanY) { + spanX = targetCellWidth; + spanY = targetCellHeight; + } } // If minSpanX/Y > spanX/Y, ignore the minSpanX/Y to match the behavior described in @@ -201,9 +192,8 @@ public class LauncherAppWidgetProviderInfo extends AppWidgetProviderInfo impleme (widgetSize + widgetPadding + cellSpacing) / (cellSize + cellSpacing))); } - @Override - public CharSequence getLabel() { - return super.loadLabel(mPM); + public String getLabel(PackageManager packageManager) { + return super.loadLabel(packageManager); } public Point getMinSpans() { @@ -225,7 +215,8 @@ public class LauncherAppWidgetProviderInfo extends AppWidgetProviderInfo impleme } public boolean isConfigurationOptional() { - return isReconfigurable() + return ATLEAST_S + && isReconfigurable() && (getWidgetFeatures() & WIDGET_FEATURE_CONFIGURATION_OPTIONAL) != 0; } @@ -240,13 +231,7 @@ public class LauncherAppWidgetProviderInfo extends AppWidgetProviderInfo impleme } @Override - public Drawable getFullResIcon(BaseIconCache cache) { - return cache.getFullResIcon(getActivityInfo()); + public Drawable getFullResIcon(IconCache cache) { + return cache.getFullResIcon(provider.getPackageName(), icon); } - - @Nullable - @Override - public ApplicationInfo getApplicationInfo() { - return getActivityInfo().applicationInfo; - } -} +} \ No newline at end of file diff --git a/src/com/android/launcher3/widget/LauncherWidgetHolder.java b/src/com/android/launcher3/widget/LauncherWidgetHolder.java index 550d585dfc..3d13776066 100644 --- a/src/com/android/launcher3/widget/LauncherWidgetHolder.java +++ b/src/com/android/launcher3/widget/LauncherWidgetHolder.java @@ -21,7 +21,6 @@ import static com.android.launcher3.BuildConfigs.WIDGETS_ENABLED; import static com.android.launcher3.Flags.enableWorkspaceInflation; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.widget.LauncherAppWidgetProviderInfo.fromProviderInfo; -import static com.android.launcher3.widget.ListenableAppWidgetHost.getWidgetHolderExecutor; import android.app.ActivityOptions; import android.appwidget.AppWidgetHost; @@ -39,121 +38,100 @@ import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; -import androidx.annotation.WorkerThread; import com.android.launcher3.BaseActivity; +import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.R; import com.android.launcher3.Utilities; -import com.android.launcher3.dagger.LauncherComponentProvider; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.testing.TestLogging; import com.android.launcher3.testing.shared.TestProtocol; import com.android.launcher3.util.ActivityOptionsWrapper; import com.android.launcher3.util.ResourceBasedOverride; import com.android.launcher3.util.SafeCloseable; -import com.android.launcher3.views.ActivityContext; -import com.android.launcher3.widget.ListenableAppWidgetHost.ProviderChangedListener; +import com.android.launcher3.widget.LauncherAppWidgetHost.ListenableHostView; import com.android.launcher3.widget.custom.CustomWidgetManager; -import dagger.assisted.Assisted; -import dagger.assisted.AssistedFactory; -import dagger.assisted.AssistedInject; - import java.util.ArrayList; import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Consumer; import java.util.function.IntConsumer; import app.lawnchair.LawnchairAppWidgetHostView; /** - * A wrapper for LauncherAppWidgetHost. This class is created so the AppWidgetHost could run in + * A wrapper for LauncherAppWidgetHost. This class is created so the + * AppWidgetHost could run in * background. */ public class LauncherWidgetHolder { - - private static final String TAG = "LauncherWidgetHolder"; - public static final int APPWIDGET_HOST_ID = 1024; protected static final int FLAG_LISTENING = 1; protected static final int FLAG_STATE_IS_NORMAL = 1 << 1; protected static final int FLAG_ACTIVITY_STARTED = 1 << 2; protected static final int FLAG_ACTIVITY_RESUMED = 1 << 3; - - private static final int FLAGS_SHOULD_LISTEN = - FLAG_STATE_IS_NORMAL | FLAG_ACTIVITY_STARTED | FLAG_ACTIVITY_RESUMED; - - // TODO(b/191735836): Replace with ActivityOptions.KEY_SPLASH_SCREEN_STYLE when un-hidden - private static final String KEY_SPLASH_SCREEN_STYLE = "android.activity.splashScreenStyle"; - // TODO(b/191735836): Replace with SplashScreen.SPLASH_SCREEN_STYLE_EMPTY when un-hidden - private static final int SPLASH_SCREEN_STYLE_EMPTY = 0; + private static final int FLAGS_SHOULD_LISTEN = FLAG_STATE_IS_NORMAL | FLAG_ACTIVITY_STARTED | FLAG_ACTIVITY_RESUMED; @NonNull protected final Context mContext; @NonNull - protected final ListenableAppWidgetHost mWidgetHost; + private final AppWidgetHost mWidgetHost; @NonNull protected final SparseArray mViews = new SparseArray<>(); + protected final List mProviderChangedListeners = new ArrayList<>(); - /** package visibility */ - final List mProviderChangedListeners = new ArrayList<>(); + protected int mFlags = FLAG_STATE_IS_NORMAL; - protected AtomicInteger mFlags = new AtomicInteger(FLAG_STATE_IS_NORMAL); + // TODO(b/191735836): Replace with ActivityOptions.KEY_SPLASH_SCREEN_STYLE when + // un-hidden + private static final String KEY_SPLASH_SCREEN_STYLE = "android.activity.splashScreenStyle"; + // TODO(b/191735836): Replace with SplashScreen.SPLASH_SCREEN_STYLE_EMPTY when + // un-hidden + private static final int SPLASH_SCREEN_STYLE_EMPTY = 0; - @Nullable - private Consumer mOnViewCreationCallback; - - /** package visibility */ - @Nullable IntConsumer mAppWidgetRemovedCallback; - - @AssistedInject - protected LauncherWidgetHolder(@Assisted("UI_CONTEXT") @NonNull Context context) { - this(context, APPWIDGET_HOST_ID); - } - - public LauncherWidgetHolder(@NonNull Context context, int hostId) { - this(context, new LauncherAppWidgetHost(context, hostId)); - } - - protected LauncherWidgetHolder( - @NonNull Context context, @NonNull ListenableAppWidgetHost appWidgetHost) { + protected LauncherWidgetHolder(@NonNull Context context, + @Nullable IntConsumer appWidgetRemovedCallback) { mContext = context; - mWidgetHost = appWidgetHost; - MAIN_EXECUTOR.execute(() -> mWidgetHost.getHolders().add(this)); + mWidgetHost = createHost(context, appWidgetRemovedCallback); } - /** Starts listening to the widget updates from the server side */ + protected AppWidgetHost createHost( + Context context, @Nullable IntConsumer appWidgetRemovedCallback) { + return new LauncherAppWidgetHost( + context, appWidgetRemovedCallback, mProviderChangedListeners); + } + + /** + * Starts listening to the widget updates from the server side + */ public void startListening() { if (!WIDGETS_ENABLED) { return; } - getWidgetHolderExecutor().execute(() -> { - try { - mWidgetHost.startListening(); - } catch (Exception e) { - if (!Utilities.isBinderSizeError(e)) { - Log.e(TAG, "RuntimeException 1"); - return; - } - // We're willing to let this slide. The exception is being caused by the list of - // RemoteViews which is being passed back. The startListening relationship will - // have been established by this point, and we will end up populating the - // widgets upon bind anyway. See issue 14255011 for more context. + try { + mWidgetHost.startListening(); + } catch (Exception e) { + if (!Utilities.isBinderSizeError(e)) { + return; } - // TODO: Investigate why widgetHost.startListening() always return non-empty updates - setListeningFlag(true); + // We're willing to let this slide. The exception is being caused by the list of + // RemoteViews which is being passed back. The startListening relationship will + // have been established by this point, and we will end up populating the + // widgets upon bind anyway. See issue 14255011 for more context. + } + // TODO: Investigate why widgetHost.startListening() always return non-empty + // updates + setListeningFlag(true); - MAIN_EXECUTOR.execute(this::updateDeferredView); - }); + updateDeferredView(); } - /** Update any views which have been deferred because the host was not listening */ + /** + * Update any views which have been deferred because the host was not listening. + */ protected void updateDeferredView() { // Update any views which have been deferred because the host was not listening. // We go in reverse order and inflate any deferred or cached widget @@ -181,7 +159,9 @@ public class LauncherWidgetHolder { /** * Set the NORMAL state of the widget host - * @param isNormal True if setting the host to be in normal state, false otherwise + * + * @param isNormal True if setting the host to be in normal state, false + * otherwise */ public void setStateIsNormal(boolean isNormal) { setShouldListenFlag(FLAG_STATE_IS_NORMAL, isNormal); @@ -189,6 +169,7 @@ public class LauncherWidgetHolder { /** * Delete the specified app widget from the host + * * @param appWidgetId The ID of the app widget to be deleted */ public void deleteAppWidgetId(int appWidgetId) { @@ -200,18 +181,12 @@ public class LauncherWidgetHolder { * Called when the launcher is destroyed */ public void destroy() { - try { - MAIN_EXECUTOR.submit(() -> { - clearViews(); - mWidgetHost.getHolders().remove(this); - }).get(); - } catch (Exception e) { - Log.e(TAG, "Failed to remove self from holder list", e); - } + // No-op } /** - * @return The allocated app widget id if allocation is successful, returns -1 otherwise + * @return The allocated app widget id if allocation is successful, returns -1 + * otherwise */ public int allocateAppWidgetId() { if (!WIDGETS_ENABLED) { @@ -222,40 +197,35 @@ public class LauncherWidgetHolder { } /** - * Add a listener that is triggered when the providers of the widgets are changed + * Add a listener that is triggered when the providers of the widgets are + * changed + * * @param listener The listener that notifies when the providers changed */ - public void addProviderChangeListener(@NonNull ProviderChangedListener listener) { + public void addProviderChangeListener( + @NonNull LauncherWidgetHolder.ProviderChangedListener listener) { MAIN_EXECUTOR.execute(() -> mProviderChangedListeners.add(listener)); } /** * Remove the specified listener from the host + * * @param listener The listener that is to be removed from the host */ - public void removeProviderChangeListener(ProviderChangedListener listener) { + public void removeProviderChangeListener( + LauncherWidgetHolder.ProviderChangedListener listener) { MAIN_EXECUTOR.execute(() -> mProviderChangedListeners.remove(listener)); } - /** - * Sets a callbacks for whenever a widget view is created - */ - public void setOnViewCreationCallback(@Nullable Consumer callback) { - mOnViewCreationCallback = callback; - } - - /** Sets a callback for listening app widget removals */ - public void setAppWidgetRemovedCallback(@Nullable IntConsumer callback) { - mAppWidgetRemovedCallback = callback; - } - /** * Starts the configuration activity for the widget - * @param activity The activity in which to start the configuration page - * @param widgetId The ID of the widget + * + * @param activity The activity in which to start the configuration page + * @param widgetId The ID of the widget * @param requestCode The request code */ - public void startConfigActivity(@NonNull BaseActivity activity, int widgetId, int requestCode) { + public void startConfigActivity(@NonNull BaseDraggingActivity activity, int widgetId, + int requestCode) { startConfigActivity(activity, widgetId, requestCode, 0); } @@ -267,7 +237,7 @@ public class LauncherWidgetHolder { * @param requestCode The request code * @param retryCount The number of retries attempted */ - private void startConfigActivity(@NonNull BaseActivity activity, int widgetId, + private void startConfigActivity(@NonNull BaseDraggingActivity activity, int widgetId, int requestCode, int retryCount) { if (!WIDGETS_ENABLED) { sendActionCancelled(activity, requestCode); @@ -295,11 +265,11 @@ public class LauncherWidgetHolder { } } - private void handleInvalidWidgetId(BaseActivity activity, int widgetId, int requestCode) { + private void handleInvalidWidgetId(BaseDraggingActivity activity, int widgetId, int requestCode) { handleInvalidWidgetId(activity, widgetId, requestCode, 0); } - private void handleInvalidWidgetId(BaseActivity activity, int widgetId, int requestCode, int retryCount) { + private void handleInvalidWidgetId(BaseDraggingActivity activity, int widgetId, int requestCode, int retryCount) { // Remove the invalid widget deleteAppWidgetId(widgetId); @@ -323,32 +293,37 @@ public class LauncherWidgetHolder { } /** - * Returns an {@link android.app.ActivityOptions} bundle from the {code activity} for launching - * the configuration of the {@code widgetId} app widget, or null of options cannot be produced. + * Returns an {@link android.app.ActivityOptions} bundle from the {code + * activity} for launching + * the configuration of the {@code widgetId} app widget, or default + * configuration options + * if they cannot be produced. */ - @Nullable - protected Bundle getConfigurationActivityOptions(@NonNull ActivityContext activity, + @NonNull + protected Bundle getConfigurationActivityOptions(@NonNull BaseDraggingActivity activity, int widgetId) { LauncherAppWidgetHostView view = mViews.get(widgetId); - if (view == null) { - return activity.makeDefaultActivityOptions( - -1 /* SPLASH_SCREEN_STYLE_UNDEFINED */).toBundle(); - } + if (view == null) + return getDefaultConfigurationActivityOptions(); Object tag = view.getTag(); - if (!(tag instanceof ItemInfo)) { - return activity.makeDefaultActivityOptions( - -1 /* SPLASH_SCREEN_STYLE_UNDEFINED */).toBundle(); + if (!(tag instanceof ItemInfo)) + return getDefaultConfigurationActivityOptions(); + ActivityOptionsWrapper activityOptionsWrapper = activity.getActivityLaunchOptions(view, (ItemInfo) tag); + // Must allow background activity start for U. + Utilities.allowBGLaunch(activityOptionsWrapper.options); + Bundle bundle = activityOptionsWrapper.toBundle(); + if (Utilities.ATLEAST_S) { + bundle.putInt(KEY_SPLASH_SCREEN_STYLE, SPLASH_SCREEN_STYLE_EMPTY); } - Bundle bundle = activity.getActivityLaunchOptions(view, (ItemInfo) tag).toBundle(); - bundle.putInt(KEY_SPLASH_SCREEN_STYLE, SPLASH_SCREEN_STYLE_EMPTY); return bundle; } /** * Starts the binding flow for the widget - * @param activity The activity for which to bind the widget + * + * @param activity The activity for which to bind the widget * @param appWidgetId The ID of the widget - * @param info The {@link AppWidgetProviderInfo} of the widget + * @param info The {@link AppWidgetProviderInfo} of the widget * @param requestCode The request code */ public void startBindFlow(@NonNull BaseActivity activity, @@ -367,28 +342,30 @@ public class LauncherWidgetHolder { activity.startActivityForResult(intent, requestCode); } - /** Stop the host from listening to the widget updates */ + /** + * Stop the host from listening to the widget updates + */ public void stopListening() { if (!WIDGETS_ENABLED) { return; } - getWidgetHolderExecutor().execute(() -> { - mWidgetHost.stopListening(); + try { + if (mWidgetHost != null) { + mWidgetHost.stopListening(); + } + } catch (Exception e) { + Log.e("LauncherWidgetHolder", "Error stopping widget listening", e); + } finally { setListeningFlag(false); - }); + } } - /** - * Update {@link #FLAG_LISTENING} on {@link #mFlags} after making binder calls from - * {@link #mWidgetHost}. - */ - @WorkerThread protected void setListeningFlag(final boolean isListening) { if (isListening) { - mFlags.updateAndGet(old -> old | FLAG_LISTENING); + mFlags |= FLAG_LISTENING; return; } - mFlags.updateAndGet(old -> old & ~FLAG_LISTENING); + mFlags &= ~FLAG_LISTENING; } /** @@ -401,6 +378,7 @@ public class LauncherWidgetHolder { /** * Adds a callback to be run everytime the provided app widget updates. + * * @return a closable to remove this callback */ public SafeCloseable addOnUpdateListener( @@ -408,12 +386,15 @@ public class LauncherWidgetHolder { if (createView(appWidgetId, appWidget) instanceof ListenableHostView lhv) { return lhv.addUpdateListener(callback); } - return () -> { }; + return () -> { + }; } /** - * Create a view for the specified app widget. When calling this method from a background - * thread, the returned view will not receive ongoing updates. The caller needs to reattach + * Create a view for the specified app widget. When calling this method from a + * background + * thread, the returned view will not receive ongoing updates. The caller needs + * to reattach * the view using {@link #attachViewToHostAndGetAttachedView} on UIThread * * @param appWidgetId The ID of the widget @@ -431,8 +412,8 @@ public class LauncherWidgetHolder { } LauncherAppWidgetHostView view = createViewInternal(appWidgetId, appWidget); - if (mOnViewCreationCallback != null) mOnViewCreationCallback.accept(view); - // Do not update mViews on a background thread call, as the holder is not thread safe. + // Do not update mViews on a background thread call, as the holder is not thread + // safe. if (!enableWorkspaceInflation() || Looper.myLooper() == Looper.getMainLooper()) { mViews.put(appWidgetId, view); } @@ -440,8 +421,10 @@ public class LauncherWidgetHolder { } /** - * Attaches an already inflated view to the host. If the view can't be attached, creates + * Attaches an already inflated view to the host. If the view can't be attached, + * creates * and attaches a new view. + * * @return the final attached view */ @NonNull @@ -450,8 +433,9 @@ public class LauncherWidgetHolder { // Binder can also inflate placeholder widgets in case of backup-restore. Skip // attaching such widgets - boolean isRealWidget = (!(view instanceof PendingAppWidgetHostView pw) - || pw.isDeferredWidget()) + boolean isRealWidget = ((view instanceof PendingAppWidgetHostView pw) + ? pw.isDeferredWidget() + : true) && view.getAppWidgetInfo() != null; if (isRealWidget && mViews.get(view.getAppWidgetId()) != view) { view = recycleExistingView(view); @@ -462,16 +446,18 @@ public class LauncherWidgetHolder { /** * Recycling logic: - * 1) If the final view should be a pendingView - * if the provided view is also a pendingView, return itself - * otherwise discard provided view and return a new pending view - * 2) If the recycled view is a pendingView, discard it and return a new view - * 3) Use the same for as creating a new view, but used the provided view in the host instead - * of creating a new view. This ensures that all the host callbacks are properly attached - * as a result of using the same flow. + * 1) If the final view should be a pendingView + * if the provided view is also a pendingView, return itself + * otherwise discard provided view and return a new pending view + * 2) If the recycled view is a pendingView, discard it and return a new view + * 3) Use the same for as creating a new view, but used the provided view in the + * host instead + * of creating a new view. This ensures that all the host callbacks are properly + * attached + * as a result of using the same flow. */ protected LauncherAppWidgetHostView recycleExistingView(LauncherAppWidgetHostView view) { - if ((mFlags.get() & FLAG_LISTENING) == 0) { + if ((mFlags & FLAG_LISTENING) == 0) { if (view instanceof PendingAppWidgetHostView pv && pv.isDeferredWidget()) { return view; } else { @@ -493,8 +479,9 @@ public class LauncherWidgetHolder { @NonNull protected LauncherAppWidgetHostView createViewInternal( int appWidgetId, @NonNull LauncherAppWidgetProviderInfo appWidget) { - if ((mFlags.get() & FLAG_LISTENING) == 0) { - // Since the launcher hasn't started listening to widget updates, we can't simply call + if ((mFlags & FLAG_LISTENING) == 0) { + // Since the launcher hasn't started listening to widget updates, we can't + // simply call // host.createView here because the later will make a binder call to retrieve // RemoteViews from system process. return new PendingAppWidgetHostView(mContext, this, appWidgetId, appWidget); @@ -514,7 +501,8 @@ public class LauncherWidgetHolder { throw new RuntimeException(e); } - // If the exception was thrown while fetching the remote views, let the view stay. + // If the exception was thrown while fetching the remote views, let the view + // stay. // This will ensure that if the widget posts a valid update later, the view // will update. LauncherAppWidgetHostView view = mViews.get(appWidgetId); @@ -528,13 +516,28 @@ public class LauncherWidgetHolder { } } - /** Clears all the views from the host */ + /** + * Listener for getting notifications on provider changes. + */ + public interface ProviderChangedListener { + /** + * Notify the listener that the providers have changed + */ + void notifyWidgetProvidersChanged(); + } + + /** + * Clears all the views from the host + */ public void clearViews() { - ((LauncherAppWidgetHost) mWidgetHost).clearViews(); + LauncherAppWidgetHost tempHost = (LauncherAppWidgetHost) mWidgetHost; + tempHost.clearViews(); mViews.clear(); } - /** Clears all the internal widget views */ + /** + * Clears all the internal widget views + */ public void clearWidgetViews() { clearViews(); } @@ -543,27 +546,26 @@ public class LauncherWidgetHolder { * @return True if the host is listening to the updates, false otherwise */ public boolean isListening() { - return (mFlags.get() & FLAG_LISTENING) != 0; + return (mFlags & FLAG_LISTENING) != 0; } /** - * Sets or unsets a flag the can change whether the widget host should be in the listening + * Sets or unsets a flag the can change whether the widget host should be in the + * listening * state. */ - @VisibleForTesting - void setShouldListenFlag(int flag, boolean on) { + private void setShouldListenFlag(int flag, boolean on) { if (on) { - mFlags.updateAndGet(old -> old | flag); + mFlags |= flag; } else { - mFlags.updateAndGet(old -> old & ~flag); + mFlags &= ~flag; } final boolean listening = isListening(); - int currentFlag = mFlags.get(); - if (!listening && shouldListen(currentFlag)) { + if (!listening && shouldListen(mFlags)) { // Postpone starting listening until all flags are on. startListening(); - } else if (listening && (currentFlag & FLAG_ACTIVITY_STARTED) == 0) { + } else if (listening && (mFlags & FLAG_ACTIVITY_STARTED) == 0) { // Postpone stopping listening until the activity is stopped. stopListening(); } @@ -581,19 +583,34 @@ public class LauncherWidgetHolder { * Returns the new LauncherWidgetHolder instance */ public static LauncherWidgetHolder newInstance(Context context) { - return LauncherComponentProvider.get(context).getWidgetHolderFactory().newInstance(context); + return HolderFactory.newFactory(context).newInstance(context, null); } - /** A factory that generates new instances of {@code LauncherWidgetHolder} */ - public interface WidgetHolderFactory { + /** + * A factory class that generates new instances of {@code LauncherWidgetHolder} + */ + public static class HolderFactory implements ResourceBasedOverride { - LauncherWidgetHolder newInstance(@NonNull Context context); - } + /** + * @param context The context of the caller + * @param appWidgetRemovedCallback The callback that is called when widgets are + * removed + * @return A new instance of {@code LauncherWidgetHolder} + */ + public LauncherWidgetHolder newInstance(@NonNull Context context, + @Nullable IntConsumer appWidgetRemovedCallback) { + return new LauncherWidgetHolder(context, appWidgetRemovedCallback); + } - /** A factory that generates new instances of {@code LauncherWidgetHolder} */ - @AssistedFactory - public interface WidgetHolderFactoryImpl extends WidgetHolderFactory { - - LauncherWidgetHolder newInstance(@Assisted("UI_CONTEXT") @NonNull Context context); + /** + * @param context The context of the caller + * @return A new instance of factory class for widget holders. If not specified, + * returning + * {@code HolderFactory} by default. + */ + public static HolderFactory newFactory(Context context) { + return Overrides.getObject( + HolderFactory.class, context, R.string.widget_holder_factory_class); + } } } diff --git a/src/com/android/launcher3/widget/LocalColorExtractor.java b/src/com/android/launcher3/widget/LocalColorExtractor.java index 3d6f0ef45b..2f7a3ca4ea 100644 --- a/src/com/android/launcher3/widget/LocalColorExtractor.java +++ b/src/com/android/launcher3/widget/LocalColorExtractor.java @@ -68,9 +68,4 @@ public class LocalColorExtractor implements ResourceBasedOverride { public SparseIntArray generateColorsOverride(WallpaperColors colors) { return null; } - - /** - * Updates the base context to contain the colors override - */ - public void applyColorsOverride(Context base, SparseIntArray override) { } } diff --git a/src/com/android/launcher3/widget/PendingAddWidgetInfo.java b/src/com/android/launcher3/widget/PendingAddWidgetInfo.java index 23ab0fb6a2..a9162527af 100644 --- a/src/com/android/launcher3/widget/PendingAddWidgetInfo.java +++ b/src/com/android/launcher3/widget/PendingAddWidgetInfo.java @@ -82,9 +82,8 @@ public class PendingAddWidgetInfo extends PendingAddItemInfo { @NonNull @Override - public LauncherAtom.ItemInfo buildProto( - @Nullable CollectionInfo collectionInfo, Context context) { - LauncherAtom.ItemInfo info = super.buildProto(collectionInfo, context); + public LauncherAtom.ItemInfo buildProto(@Nullable CollectionInfo collectionInfo) { + LauncherAtom.ItemInfo info = super.buildProto(collectionInfo); return info.toBuilder() .addItemAttributes(LauncherAppWidgetInfo.getAttribute(sourceContainer)) .build(); diff --git a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java index 1157117c34..bc43e41bce 100644 --- a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java +++ b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java @@ -21,7 +21,7 @@ import static android.graphics.Paint.DITHER_FLAG; import static android.graphics.Paint.FILTER_BITMAP_FLAG; import static com.android.launcher3.graphics.PreloadIconDrawable.newPendingIcon; -import static com.android.launcher3.model.data.LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY; +import static com.android.launcher3.icons.FastBitmapDrawable.getDisabledColorFilter; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import android.appwidget.AppWidgetProviderInfo; @@ -37,9 +37,6 @@ import android.graphics.RectF; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Bundle; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; import android.text.Layout; import android.text.StaticLayout; import android.text.TextPaint; @@ -63,10 +60,8 @@ import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver; import com.android.launcher3.model.data.ItemInfoWithIcon; import com.android.launcher3.model.data.LauncherAppWidgetInfo; import com.android.launcher3.model.data.PackageItemInfo; -import com.android.launcher3.util.RunnableList; import com.android.launcher3.util.SafeCloseable; import com.android.launcher3.util.Themes; -import com.android.launcher3.widget.ListenableAppWidgetHost.ProviderChangedListener; import java.util.List; @@ -88,8 +83,6 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView private final Matrix mMatrix = new Matrix(); private final RectF mPreviewBitmapRect = new RectF(); private final RectF mCanvasRect = new RectF(); - private final Handler mHandler = new Handler(Looper.getMainLooper()); - private final RunnableList mOnDetachCleanup = new RunnableList(); private final LauncherWidgetHolder mWidgetHolder; private final LauncherAppWidgetProviderInfo mAppwidget; @@ -99,6 +92,7 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView private final CharSequence mLabel; private OnClickListener mClickListener; + private SafeCloseable mOnDetachCleanup; private int mDragFlags; @@ -113,7 +107,8 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView private final Paint mPreviewPaint; private Layout mSetupTextLayout; - @Nullable private Bitmap mPreviewBitmap; + @Nullable + private Bitmap mPreviewBitmap; public PendingAppWidgetHostView(Context context, LauncherWidgetHolder widgetHolder, LauncherAppWidgetInfo info, @Nullable LauncherAppWidgetProviderInfo appWidget) { @@ -152,7 +147,8 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView } /** - * Set {@link Bitmap} of widget preview and update background drawable. When showing preview + * Set {@link Bitmap} of widget preview and update background drawable. When + * showing preview * bitmap, we shouldn't draw background. */ public void setPreviewBitmapAndUpdateBackground(@Nullable Bitmap previewBitmap) { @@ -218,15 +214,16 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView protected void onAttachedToWindow() { super.onAttachedToWindow(); - mOnDetachCleanup.executeAllAndClear(); if ((mAppwidget != null) && !mInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID) && mInfo.restoreStatus != LauncherAppWidgetInfo.RESTORE_COMPLETED) { // If the widget is not completely restored, but has a valid ID, then listen of // updates from provider app for potential restore complete. - SafeCloseable updateCleanup = mWidgetHolder.addOnUpdateListener( + if (mOnDetachCleanup != null) { + mOnDetachCleanup.close(); + } + mOnDetachCleanup = mWidgetHolder.addOnUpdateListener( mInfo.appWidgetId, mAppwidget, this::checkIfRestored); - mOnDetachCleanup.add(updateCleanup::close); checkIfRestored(); } } @@ -234,7 +231,10 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); - mOnDetachCleanup.executeAllAndClear(); + if (mOnDetachCleanup != null) { + mOnDetachCleanup.close(); + mOnDetachCleanup = null; + } } /** @@ -246,13 +246,14 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView } LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) getTag(); if (info == null) { - // This occurs when LauncherAppWidgetHostView is used to render a preview layout. + // This occurs when LauncherAppWidgetHostView is used to render a preview + // layout. return; } if (mActivityContext instanceof Launcher launcher) { // Remove and rebind the current widget (which was inflated in the wrong // orientation), but don't delete it from the database - launcher.removeItem(this, info, false /* deleteFromDb */, + launcher.removeItem(this, info, false /* deleteFromDb */, "widget removed because of configuration change"); launcher.bindAppWidget(info); } @@ -299,30 +300,43 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView mCenterDrawable.setCallback(null); mCenterDrawable = null; } - mDragFlags = FLAG_DRAW_ICON; + mDragFlags = 0; + if (info.bitmap.icon != null) { + mDragFlags = FLAG_DRAW_ICON; - // The view displays three modes, - // 1) App icon in the center - // 2) Preload icon in the center - // 3) App icon in the center with a setup icon on the top left corner. - if (mDisabledForSafeMode) { - FastBitmapDrawable disabledIcon = info.newIcon(getContext()); - disabledIcon.setIsDisabled(true); - mCenterDrawable = disabledIcon; - mSettingIconDrawable = null; - } else if (isReadyForClickSetup()) { - mCenterDrawable = info.newIcon(getContext()); - mSettingIconDrawable = getResources().getDrawable(R.drawable.ic_setting).mutate(); - updateSettingColor(info.bitmap.color); + Drawable widgetCategoryIcon = getWidgetCategoryIcon(); + // The view displays three modes, + // 1) App icon in the center + // 2) Preload icon in the center + // 3) App icon in the center with a setup icon on the top left corner. + if (mDisabledForSafeMode) { + if (widgetCategoryIcon == null) { + FastBitmapDrawable disabledIcon = info.newIcon(getContext()); + disabledIcon.setIsDisabled(true); + mCenterDrawable = disabledIcon; + } else { + widgetCategoryIcon.setColorFilter(getDisabledColorFilter()); + mCenterDrawable = widgetCategoryIcon; + } + mSettingIconDrawable = null; + } else if (isReadyForClickSetup()) { + mCenterDrawable = widgetCategoryIcon == null + ? info.newIcon(getContext()) + : widgetCategoryIcon; + mSettingIconDrawable = getResources().getDrawable(R.drawable.ic_setting).mutate(); + updateSettingColor(info.bitmap.color); - mDragFlags |= FLAG_DRAW_SETTINGS | FLAG_DRAW_LABEL; - } else { - mCenterDrawable = newPendingIcon(getContext(), info); - mSettingIconDrawable = null; - applyState(); + mDragFlags |= FLAG_DRAW_SETTINGS | FLAG_DRAW_LABEL; + } else { + mCenterDrawable = widgetCategoryIcon == null + ? newPendingIcon(getContext(), info) + : widgetCategoryIcon; + mSettingIconDrawable = null; + applyState(); + } + mCenterDrawable.setCallback(this); + mDrawableSizeChanged = true; } - mCenterDrawable.setCallback(this); - mDrawableSizeChanged = true; invalidate(); } @@ -332,7 +346,7 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView Color.colorToHSV(dominantColor, hsv); hsv[1] = Math.min(hsv[1], MIN_SATURATION); hsv[2] = 1; - mSettingIconDrawable.setColorFilter(Color.HSVToColor(hsv), PorterDuff.Mode.SRC_IN); + mSettingIconDrawable.setColorFilter(Color.HSVToColor(hsv), PorterDuff.Mode.SRC_IN); } @Override @@ -341,11 +355,6 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView } public void applyState() { - if (mCenterDrawable instanceof FastBitmapDrawable fb - && mInfo.pendingItemInfo != null - && !fb.isSameInfo(mInfo.pendingItemInfo.bitmap)) { - reapplyItemInfo(mInfo.pendingItemInfo); - } if (mCenterDrawable != null) { mCenterDrawable.setLevel(Math.max(mInfo.installProgress, 0)); } @@ -353,7 +362,8 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView @Override public void onClick(View v) { - // AppWidgetHostView blocks all click events on the root view. Instead handle click events + // AppWidgetHostView blocks all click events on the root view. Instead handle + // click events // on the content and pass it along. if (mClickListener != null) { mClickListener.onClick(this); @@ -362,17 +372,19 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView /** * A pending widget is ready for setup after the provider is installed and - * 1) Widget id is not valid: the widget id is not yet bound to the provider, probably - * because the launcher doesn't have appropriate permissions. - * Note that we would still have an allocated id as that does not - * require any permissions and can be done during view inflation. - * 2) UI is not ready: the id is valid and the bound. But the widget has a configure activity - * which needs to be called once. + * 1) Widget id is not valid: the widget id is not yet bound to the provider, + * probably + * because the launcher doesn't have appropriate permissions. + * Note that we would still have an allocated id as that does not + * require any permissions and can be done during view inflation. + * 2) UI is not ready: the id is valid and the bound. But the widget has a + * configure activity + * which needs to be called once. */ public boolean isReadyForClickSetup() { return !mInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) && (mInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_UI_NOT_READY) - || mInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)); + || mInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)); } private void updateDrawableBounds() { @@ -390,7 +402,8 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView float iconSize = ((mDragFlags & FLAG_DRAW_ICON) == 0) ? 0 : Math.max(0, Math.min(availableWidth, availableHeight)); - // Use twice the setting size factor, as the setting is drawn at a corner and the + // Use twice the setting size factor, as the setting is drawn at a corner and + // the // icon is drawn in the center. float settingIconScaleFactor = ((mDragFlags & FLAG_DRAW_SETTINGS) == 0) ? 0 : 1 + SETUP_ICON_SIZE_FACTOR * 2; @@ -482,72 +495,18 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView } /** - * Creates a runnable runnable which tries to refresh the widget if it is restored - */ - public void postProviderAvailabilityCheck() { - if (!mInfo.hasRestoreFlag(FLAG_PROVIDER_NOT_READY) && getAppWidgetInfo() == null) { - // If the info state suggests that the provider is ready, but there is no - // provider info attached on this pending view, recreate when the provider is available - DeferredWidgetRefresh restoreRunnable = new DeferredWidgetRefresh(); - mOnDetachCleanup.add(restoreRunnable::cleanup); - mHandler.post(restoreRunnable::notifyWidgetProvidersChanged); - } - } - - /** - * Used as a workaround to ensure that the AppWidgetService receives the - * PACKAGE_ADDED broadcast before updating widgets. + * Returns the widget category icon for {@link #mInfo}. * - * This class will periodically check for the availability of the WidgetProvider as a result - * of providerChanged callback from the host. When the provider is available or a timeout of - * 10-sec is reached, it reinflates the pending-widget which in-turn goes through the process - * of re-evaluating the pending state of the widget, + *

+ * If {@link #mInfo}'s category is {@code PackageItemInfo#NO_CATEGORY} or + * unknown, returns + * {@code null}. */ - private class DeferredWidgetRefresh implements Runnable, ProviderChangedListener { - private boolean mRefreshPending = true; - - DeferredWidgetRefresh() { - mWidgetHolder.addProviderChangeListener(this); - // Force refresh after 10 seconds, if we don't get the provider changed event. - // This could happen when the provider is no longer available in the app. - Message msg = Message.obtain(getHandler(), this); - msg.obj = DeferredWidgetRefresh.class; - mHandler.sendMessageDelayed(msg, 10000); - } - - /** - * Reinflate the widget if it is still attached. - */ - @Override - public void run() { - cleanup(); - if (mRefreshPending) { - reInflate(); - mRefreshPending = false; - } - } - - @Override - public void notifyWidgetProvidersChanged() { - final AppWidgetProviderInfo widgetInfo; - WidgetManagerHelper widgetHelper = new WidgetManagerHelper(getContext()); - if (mInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) { - widgetInfo = widgetHelper.findProvider(mInfo.providerName, mInfo.user); - } else { - widgetInfo = widgetHelper.getLauncherAppWidgetInfo(mInfo.appWidgetId, - mInfo.getTargetComponent()); - } - if (widgetInfo != null) { - run(); - } - } - - /** - * Removes any scheduled callbacks and change listeners, no-op if nothing is scheduled - */ - public void cleanup() { - mWidgetHolder.removeProviderChangeListener(this); - mHandler.removeCallbacks(this); + @Nullable + private Drawable getWidgetCategoryIcon() { + if (mInfo.pendingItemInfo.widgetCategory == WidgetSections.NO_CATEGORY) { + return null; } + return mInfo.pendingItemInfo.newIcon(getContext()); } } diff --git a/src/com/android/launcher3/widget/PendingItemDragHelper.java b/src/com/android/launcher3/widget/PendingItemDragHelper.java index 04d69e96af..60ee2878ac 100644 --- a/src/com/android/launcher3/widget/PendingItemDragHelper.java +++ b/src/com/android/launcher3/widget/PendingItemDragHelper.java @@ -150,7 +150,9 @@ public class PendingItemDragHelper extends DragPreviewProvider { Drawable p = new FastBitmapDrawable(new DatabaseWidgetPreviewLoader(launcher) .generateWidgetPreview( createWidgetInfo.info, maxWidth, previewSizeBeforeScale)); - p = new RoundDrawableWrapper(p, mEnforcedRoundedCornersForWidget); + if (RoundedCornerEnforcement.isRoundedCornerEnabled()) { + p = new RoundDrawableWrapper(p, mEnforcedRoundedCornersForWidget); + } preview = p; } diff --git a/src/com/android/launcher3/widget/RoundedCornerEnforcement.java b/src/com/android/launcher3/widget/RoundedCornerEnforcement.java index f3aaf2632f..086c7c9a30 100644 --- a/src/com/android/launcher3/widget/RoundedCornerEnforcement.java +++ b/src/com/android/launcher3/widget/RoundedCornerEnforcement.java @@ -16,8 +16,6 @@ package com.android.launcher3.widget; -import static com.android.launcher3.Flags.useSystemRadiusForAppWidgets; - import android.appwidget.AppWidgetHostView; import android.content.Context; import android.content.res.Resources; @@ -30,8 +28,9 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.launcher3.R; - import com.android.launcher3.Utilities; +import com.android.launcher3.config.FeatureFlags; + import java.util.ArrayList; import java.util.List; @@ -70,10 +69,15 @@ public class RoundedCornerEnforcement { /** * Check whether the app widget has opted out of the enforcement. */ - public static boolean hasAppWidgetOptedOut(@NonNull View background) { + public static boolean hasAppWidgetOptedOut(@NonNull View appWidget, @NonNull View background) { return background.getId() == android.R.id.background && background.getClipToOutline(); } + /** Check if the app widget is in the deny list. */ + public static boolean isRoundedCornerEnabled() { + return sRoundedCornerEnabled; + } + /** * Computes the rounded rectangle needed for this app widget. * @@ -105,10 +109,6 @@ public class RoundedCornerEnforcement { return res.getDimension(R.dimen.enforced_rounded_corner_max_radius); } float systemRadius = res.getDimension(android.R.dimen.system_app_widget_background_radius); - if (useSystemRadiusForAppWidgets()) { - return systemRadius; - } - float defaultRadius = res.getDimension(R.dimen.enforced_rounded_corner_max_radius); return Math.min(defaultRadius, systemRadius); } diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java index 78f0969142..e3152cdc95 100644 --- a/src/com/android/launcher3/widget/WidgetCell.java +++ b/src/com/android/launcher3/widget/WidgetCell.java @@ -16,16 +16,17 @@ package com.android.launcher3.widget; -import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK; +import static android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN; +import static com.android.launcher3.Flags.enableWidgetTapToAdd; import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_TRAY; +import static com.android.launcher3.widget.LauncherAppWidgetProviderInfo.fromProviderInfo; import static com.android.launcher3.widget.util.WidgetSizes.getWidgetItemSizePx; import android.animation.Animator; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.TimeInterpolator; -import android.appwidget.AppWidgetProviderInfo; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Rect; @@ -46,12 +47,16 @@ import android.widget.LinearLayout; import android.widget.RemoteViews; import android.widget.TextView; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.app.animation.Interpolators; import com.android.launcher3.CheckLongPressHelper; +import com.android.launcher3.Flags; +import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppState; import com.android.launcher3.R; +import com.android.launcher3.Utilities; import com.android.launcher3.anim.AnimatedPropertySetter; import com.android.launcher3.icons.FastBitmapDrawable; import com.android.launcher3.icons.RoundDrawableWrapper; @@ -60,7 +65,6 @@ import com.android.launcher3.model.data.ItemInfoWithIcon; import com.android.launcher3.model.data.PackageItemInfo; import com.android.launcher3.util.CancellableTask; import com.android.launcher3.views.ActivityContext; -import com.android.launcher3.widget.DatabaseWidgetPreviewLoader.WidgetPreviewInfo; import com.android.launcher3.widget.picker.util.WidgetPreviewContainerSize; import com.android.launcher3.widget.util.WidgetSizes; @@ -69,22 +73,22 @@ import java.util.function.Consumer; import app.lawnchair.LawnchairAppWidgetHostView; import app.lawnchair.font.FontManager; import app.lawnchair.theme.drawable.DrawableTokens; -import app.lawnchair.preferences2.PreferenceManager2; -import com.patrykmichalik.opto.core.PreferenceExtensionsKt; /** - * Represents the individual cell of the widget inside the widget tray. The preview is drawn + * Represents the individual cell of the widget inside the widget tray. The + * preview is drawn * horizontally centered, and scaled down if needed. * - * This view does not support padding. Since the image is scaled down to fit the view, padding will - * further decrease the scaling factor. Drag-n-drop uses the view bounds for showing a smooth - * transition from the view to drag view, so when adding padding support, DnD would need to + * This view does not support padding. Since the image is scaled down to fit the + * view, padding will + * further decrease the scaling factor. Drag-n-drop uses the view bounds for + * showing a smooth + * transition from the view to drag view, so when adding padding support, DnD + * would need to * consider the appropriate scaling factor. */ public class WidgetCell extends LinearLayout { - PreferenceManager2 prefs2 = PreferenceManager2.INSTANCE.get(getContext()); - private static final String TAG = "WidgetCell"; private static final boolean DEBUG = false; @@ -92,7 +96,8 @@ public class WidgetCell extends LinearLayout { private static final int ADD_BUTTON_FADE_DURATION_MS = 100; /** - * The requested scale of the preview container. It can be lower than this as well. + * The requested scale of the preview container. It can be lower than this as + * well. */ private float mPreviewContainerScale = 1f; private Size mPreviewContainerSize = new Size(0, 0); @@ -127,6 +132,7 @@ public class WidgetCell extends LinearLayout { private boolean mIsShowingAddButton = false; // Height enforced by the parent to align all widget cells displayed by it. private int mParentAlignedPreviewHeight; + public WidgetCell(Context context) { this(context, null); } @@ -158,8 +164,8 @@ public class WidgetCell extends LinearLayout { mWidgetName = findViewById(R.id.widget_name); mWidgetDims = findViewById(R.id.widget_dims); mWidgetDescription = findViewById(R.id.widget_description); - mWidgetTextContainer = findViewById(R.id.widget_text_container); mWidgetAddButton = findViewById(R.id.widget_add_button); + mWidgetTextContainer = findViewById(R.id.widget_text_container); FontManager fontManager = FontManager.INSTANCE.get(getContext()); fontManager.setCustomFont(mWidgetName, R.id.font_body_medium); @@ -168,23 +174,6 @@ public class WidgetCell extends LinearLayout { // LC: Allow customisability to the Add Button, Test: Press on any Widget on the Widget sheet. mWidgetAddButton.setBackground(DrawableTokens.WidgetAddButtonBackground.resolve(getContext())); fontManager.setCustomFont(mWidgetAddButton, R.id.font_body_medium); - - setAccessibilityDelegate(new AccessibilityDelegate() { - @Override - public void onInitializeAccessibilityNodeInfo(View host, - AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(host, info); - if (hasOnClickListeners()) { - String accessibilityLabel = getResources().getString( - mWidgetAddButton.isShown() - ? R.string.widget_cell_tap_to_hide_add_button_label - : R.string.widget_cell_tap_to_show_add_button_label); - info.addAction(new AccessibilityNodeInfo.AccessibilityAction(ACTION_CLICK, - accessibilityLabel)); - } - } - }); - mWidgetAddButton.setVisibility(INVISIBLE); } public void setRemoteViewsPreview(RemoteViews view) { @@ -196,7 +185,9 @@ public class WidgetCell extends LinearLayout { return mRemoteViewsPreview; } - /** Returns the app widget host view scale, which is a value between [0f, 1f]. */ + /** + * Returns the app widget host view scale, which is a value between [0f, 1f]. + */ public float getAppWidgetHostViewScale() { return mAppWidgetHostViewScale; } @@ -225,7 +216,9 @@ public class WidgetCell extends LinearLayout { showDescription(true); showDimensions(true); - hideAddButton(/* animate= */ false); + if (enableWidgetTapToAdd()) { + hideAddButton(/* animate= */ false); + } if (mActiveRequest != null) { mActiveRequest.cancel(); @@ -252,6 +245,19 @@ public class WidgetCell extends LinearLayout { * Applies the item to this view */ public void applyFromCellItem(WidgetItem item) { + applyFromCellItem(item, this::applyPreview, /* cachedPreview= */null); + } + + /** + * Applies the item to this view + * + * @param item item to apply + * @param callback callback when preview is loaded in case the preview is + * being loaded or cached + * @param cachedPreview previously cached preview bitmap is present + */ + public void applyFromCellItem(WidgetItem item, @NonNull Consumer callback, + @Nullable Bitmap cachedPreview) { Context context = getContext(); mItem = item; mWidgetSize = getWidgetItemSizePx(getContext(), mActivity.getDeviceProfile(), mItem); @@ -267,8 +273,10 @@ public class WidgetCell extends LinearLayout { mWidgetDescription.setVisibility(GONE); } - // Setting the content description on the WidgetCell itself ensures that it remains - // screen reader focusable when the add button is showing and the text is hidden. + // Setting the content description on the WidgetCell itself ensures that it + // remains + // screen reader focusable when the add button is showing and the text is + // hidden. setContentDescription(createContentDescription(context)); if (mWidgetAddButton != null) { mWidgetAddButton.setContentDescription(context.getString( @@ -282,28 +290,40 @@ public class WidgetCell extends LinearLayout { } if (mRemoteViewsPreview != null) { - WidgetPreviewInfo previewInfo = new WidgetPreviewInfo(); - previewInfo.providerInfo = item.widgetInfo; - previewInfo.remoteViews = mRemoteViewsPreview; - applyPreview(previewInfo); + mAppWidgetHostViewPreview = createAppWidgetHostView(context); + setAppWidgetHostViewPreview(mAppWidgetHostViewPreview, item.widgetInfo, + mRemoteViewsPreview); + } else if (Utilities.ATLEAST_V + && item.hasGeneratedPreview(WIDGET_CATEGORY_HOME_SCREEN)) { + mAppWidgetHostViewPreview = createAppWidgetHostView(context); + setAppWidgetHostViewPreview(mAppWidgetHostViewPreview, item.widgetInfo, + item.generatedPreviews.get(WIDGET_CATEGORY_HOME_SCREEN)); + } else if (item.hasPreviewLayout()) { + // If the context is a Launcher activity, DragView will show + // mAppWidgetHostViewPreview + // as a preview during drag & drop. And thus, we should use + // LauncherAppWidgetHostView, + // which supports applying local color extraction during drag & drop. + mAppWidgetHostViewPreview = isLauncherContext(context) + ? new LawnchairAppWidgetHostView(context) + : createAppWidgetHostView(context); + LauncherAppWidgetProviderInfo providerInfo = fromProviderInfo(context, item.widgetInfo.clone()); + // A hack to force the initial layout to be the preview layout since there is no + // API for + // rendering a preview layout for work profile apps yet. For non-work profile + // layout, a + // proper solution is to use RemoteViews(PackageName, LayoutId). + providerInfo.initialLayout = item.widgetInfo.previewLayout; + setAppWidgetHostViewPreview(mAppWidgetHostViewPreview, providerInfo, null); + } else if (cachedPreview != null) { + applyPreview(cachedPreview); } else { if (mActiveRequest == null) { - mActiveRequest = mWidgetPreviewLoader.loadPreview( - mItem, mWidgetSize, this::applyPreview); + mActiveRequest = mWidgetPreviewLoader.loadPreview(mItem, mWidgetSize, callback); } } } - private void applyPreview(WidgetPreviewInfo previewInfo) { - if (previewInfo.providerInfo != null) { - mAppWidgetHostViewPreview = createAppWidgetHostView(getContext()); - setAppWidgetHostViewPreview(mAppWidgetHostViewPreview, previewInfo.providerInfo, - previewInfo.remoteViews); - } else { - applyBitmapPreview(previewInfo.previewBitmap); - } - } - private void initPreviewContainerSizeAndScale() { WidgetPreviewContainerSize previewSize = WidgetPreviewContainerSize.Companion.forItem(mItem, mActivity.getDeviceProfile()); @@ -316,9 +336,8 @@ public class WidgetCell extends LinearLayout { } private String createContentDescription(Context context) { - String contentDescription = - context.getString(R.string.widget_preview_name_and_dims_content_description, - mItem.label, mItem.spanX, mItem.spanY); + String contentDescription = context.getString(R.string.widget_preview_name_and_dims_content_description, + mItem.label, mItem.spanX, mItem.spanY); if (!TextUtils.isEmpty(mItem.description)) { contentDescription += " " + mItem.description; } @@ -327,7 +346,7 @@ public class WidgetCell extends LinearLayout { private void setAppWidgetHostViewPreview( NavigableAppWidgetHostView appWidgetHostViewPreview, - AppWidgetProviderInfo providerInfo, + LauncherAppWidgetProviderInfo providerInfo, @Nullable RemoteViews remoteViews) { appWidgetHostViewPreview.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); appWidgetHostViewPreview.setAppWidget(/* appWidgetId= */ -1, providerInfo); @@ -339,11 +358,10 @@ public class WidgetCell extends LinearLayout { mWidgetSize.getWidth(), mWidgetSize.getHeight(), Gravity.CENTER); mWidgetImageContainer.addView(appWidgetHostViewPreview, /* index= */ 0, widgetHostLP); mWidgetImage.setVisibility(View.GONE); - applyBitmapPreview(null); + applyPreview(null); appWidgetHostViewPreview.addOnLayoutChangeListener( - (v, l, t, r, b, ol, ot, or, ob) -> - updateAppWidgetHostScale(appWidgetHostViewPreview)); + (v, l, t, r, b, ol, ot, or, ob) -> updateAppWidgetHostScale(appWidgetHostViewPreview)); } private void updateAppWidgetHostScale(NavigableAppWidgetHostView view) { @@ -353,7 +371,8 @@ public class WidgetCell extends LinearLayout { if (view.getChildCount() == 1) { View content = view.getChildAt(0); - // Take the content width based on the edge furthest from the center, so that when + // Take the content width based on the edge furthest from the center, so that + // when // scaling the hostView, the farthest edge is still visible. contentWidth = 2 * Math.max(contentWidth / 2 - content.getLeft(), content.getRight() - contentWidth / 2); @@ -370,7 +389,8 @@ public class WidgetCell extends LinearLayout { } view.setScaleToFit(mAppWidgetHostViewScale); - // layout based previews maybe ready at this point to inspect their inner height. + // layout based previews maybe ready at this point to inspect their inner + // height. if (mPreviewReadyListener != null) { mPreviewReadyListener.onPreviewAvailable(); mPreviewReadyListener = null; @@ -397,7 +417,7 @@ public class WidgetCell extends LinearLayout { mAnimatePreview = shouldAnimate; } - private void applyBitmapPreview(Bitmap bitmap) { + private void applyPreview(Bitmap bitmap) { if (bitmap != null) { Drawable drawable = new RoundDrawableWrapper( new FastBitmapDrawable(bitmap), mEnforcedCornerRadius); @@ -431,7 +451,8 @@ public class WidgetCell extends LinearLayout { /** * Shows or hides the long description displayed below each widget. * - * @param show a flag that shows the long description of the widget if {@code true}, hides it if + * @param show a flag that shows the long description of the widget if + * {@code true}, hides it if * {@code false}. */ public void showDescription(boolean show) { @@ -441,7 +462,8 @@ public class WidgetCell extends LinearLayout { /** * Shows or hides the dimensions displayed below each widget. * - * @param show a flag that shows the dimensions of the widget if {@code true}, hides it if + * @param show a flag that shows the dimensions of the widget if {@code true}, + * hides it if * {@code false}. */ public void showDimensions(boolean show) { @@ -449,10 +471,12 @@ public class WidgetCell extends LinearLayout { } /** - * Set whether the app icon, for the app that provides the widget, should be shown next to the + * Set whether the app icon, for the app that provides the widget, should be + * shown next to the * title text of the widget. * - * @param show true if the app icon should be shown in the title text of the cell, false hides + * @param show true if the app icon should be shown in the title text of the + * cell, false hides * it. */ public void showAppIconInWidgetTitle(boolean show) { @@ -486,8 +510,8 @@ public class WidgetCell extends LinearLayout { mLongPressHelper.cancelLongPress(); } - private static LauncherAppWidgetHostView createAppWidgetHostView(Context context) { - return new LauncherAppWidgetHostView(context) { + private static NavigableAppWidgetHostView createAppWidgetHostView(Context context) { + return new NavigableAppWidgetHostView(context) { @Override protected boolean shouldAllowDirectClick() { return false; @@ -495,17 +519,28 @@ public class WidgetCell extends LinearLayout { }; } + private static boolean isLauncherContext(Context context) { + return ActivityContext.lookupContext(context) instanceof Launcher; + } + @Override public CharSequence getAccessibilityClassName() { return WidgetCell.class.getName(); } + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK); + } + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { ViewGroup.LayoutParams containerLp = mWidgetImageContainer.getLayoutParams(); int maxWidth = MeasureSpec.getSize(widthMeasureSpec); - // mPreviewContainerScale ensures the needed scaling with respect to original widget size. + // mPreviewContainerScale ensures the needed scaling with respect to original + // widget size. mAppWidgetHostViewScale = mPreviewContainerScale; containerLp.width = mPreviewContainerSize.getWidth(); int height = mPreviewContainerSize.getHeight(); @@ -524,7 +559,8 @@ public class WidgetCell extends LinearLayout { containerLp.height = height; } - // No need to call mWidgetImageContainer.setLayoutParams as we are in measure pass + // No need to call mWidgetImageContainer.setLayoutParams as we are in measure + // pass super.onMeasure(widthMeasureSpec, heightMeasureSpec); } @@ -538,7 +574,8 @@ public class WidgetCell extends LinearLayout { } /** - * Sets the height of the preview as adjusted by the parent to have this cell's content aligned + * Sets the height of the preview as adjusted by the parent to have this cell's + * content aligned * with other cells displayed by the parent. */ public void setParentAlignedPreviewHeight(int previewHeight) { @@ -547,8 +584,10 @@ public class WidgetCell extends LinearLayout { /** * Returns the height of the preview without any empty space. - * In case of appwidget host views, it returns the height of first child. This way, if preview - * view provided by an app doesn't fill bounds, this will return actual height without white + * In case of appwidget host views, it returns the height of first child. This + * way, if preview + * view provided by an app doesn't fill bounds, this will return actual height + * without white * space. */ public int getPreviewContentHeight() { @@ -579,8 +618,10 @@ public class WidgetCell extends LinearLayout { public void loadHighResPackageIcon() { cancelIconLoadRequest(); if (mItem.bitmap.isLowRes()) { - // We use the package icon instead of the receiver one so that the overall package that - // the widget came from can be identified in the recommended widgets. This matches with + // We use the package icon instead of the receiver one so that the overall + // package that + // the widget came from can be identified in the recommended widgets. This + // matches with // the package icon headings in the all widgets list. PackageItemInfo tmpPackageItem = new PackageItemInfo( mItem.componentName.getPackageName(), @@ -590,7 +631,10 @@ public class WidgetCell extends LinearLayout { } } - /** Can be called to update the package icon shown in the label of recommended widgets. */ + /** + * Can be called to update the package icon shown in the label of recommended + * widgets. + */ private void reapplyIconInfo(ItemInfoWithIcon info) { if (mItem == null || info.bitmap.isNullOrLowRes()) { showAppIconInWidgetTitle(false); @@ -609,11 +653,12 @@ public class WidgetCell extends LinearLayout { /** * Show tap to add button. + * * @param callback Callback to be set on the button. */ public void showAddButton(View.OnClickListener callback) { - if (PreferenceExtensionsKt.firstBlocking(prefs2.getLockHomeScreen())) return; - if (mIsShowingAddButton) return; + if (mIsShowingAddButton) + return; mIsShowingAddButton = true; setupIconOrTextButton(); @@ -623,7 +668,8 @@ public class WidgetCell extends LinearLayout { } /** - * Depending on the width of the cell, set up the add button to be icon-only or icon+text. + * Depending on the width of the cell, set up the add button to be icon-only or + * icon+text. */ private void setupIconOrTextButton() { String addText = getResources().getString(R.string.widget_add_button_label); @@ -660,7 +706,8 @@ public class WidgetCell extends LinearLayout { * Hide tap to add button. */ public void hideAddButton(boolean animate) { - if (!mIsShowingAddButton) return; + if (!mIsShowingAddButton) + return; mIsShowingAddButton = false; mWidgetAddButton.setOnClickListener(null); @@ -703,7 +750,8 @@ public class WidgetCell extends LinearLayout { * Returns true if this WidgetCell is displaying the same item as info. */ public boolean matchesItem(WidgetItem info) { - if (info == null || mItem == null) return false; + if (info == null || mItem == null) + return false; if (info.widgetInfo != null && mItem.widgetInfo != null) { return info.widgetInfo.getUser().equals(mItem.widgetInfo.getUser()) && info.widgetInfo.getComponent().equals(mItem.widgetInfo.getComponent()); diff --git a/src/com/android/launcher3/widget/WidgetImageView.java b/src/com/android/launcher3/widget/WidgetImageView.java index 352c0a3ccb..f3320548bb 100644 --- a/src/com/android/launcher3/widget/WidgetImageView.java +++ b/src/com/android/launcher3/widget/WidgetImageView.java @@ -24,8 +24,6 @@ import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.View; -import com.android.launcher3.icons.RoundDrawableWrapper; - /** * View that draws a bitmap horizontally centered. If the image width is greater than the view * width, the image is scaled down appropriately. @@ -87,11 +85,6 @@ public class WidgetImageView extends View { final float scale = bitmapAspectRatio > containerAspectRatio ? myWidth / bitmapWidth : myHeight / bitmapHeight; - // When updating the scale, also update scale on drawable if it has rounding. - if (mDrawable instanceof RoundDrawableWrapper && scale <= 1) { - ((RoundDrawableWrapper) mDrawable).setCornerRadiusScale(scale); - } - final float scaledWidth = bitmapWidth * scale; final float scaledHeight = bitmapHeight * scale; diff --git a/src/com/android/launcher3/widget/WidgetInflater.kt b/src/com/android/launcher3/widget/WidgetInflater.kt index 33fb002822..c14942e921 100644 --- a/src/com/android/launcher3/widget/WidgetInflater.kt +++ b/src/com/android/launcher3/widget/WidgetInflater.kt @@ -19,40 +19,36 @@ package com.android.launcher3.widget import android.content.Context import com.android.launcher3.BuildConfigs import com.android.launcher3.Launcher +import com.android.launcher3.LauncherAppState import com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RestoreError -import com.android.launcher3.dagger.ApplicationContext import com.android.launcher3.logging.FileLog import com.android.launcher3.model.data.LauncherAppWidgetInfo import com.android.launcher3.qsb.QsbContainerView -import javax.inject.Inject -import javax.inject.Named /** Utility class for handling widget inflation taking into account all the restore state updates */ -class WidgetInflater -@Inject -constructor( - @ApplicationContext private val context: Context, - @Named("SAFE_MODE") private val isSafeModeEnabled: Boolean, -) { +class WidgetInflater(private val context: Context) { private val widgetHelper = WidgetManagerHelper(context) - fun inflateAppWidget(item: LauncherAppWidgetInfo): InflationResult { + fun inflateAppWidget( + item: LauncherAppWidgetInfo, + ): InflationResult { if (item.hasOptionFlag(LauncherAppWidgetInfo.OPTION_SEARCH_WIDGET)) { item.providerName = QsbContainerView.getSearchComponentName(context) if (item.providerName == null) { return InflationResult( TYPE_DELETE, reason = "search widget removed because search component cannot be found", - restoreErrorType = RestoreError.NO_SEARCH_WIDGET, + restoreErrorType = RestoreError.NO_SEARCH_WIDGET ) } } - if (isSafeModeEnabled) return InflationResult(TYPE_PENDING) - + if (LauncherAppState.INSTANCE.get(context).isSafeModeEnabled) { + return InflationResult(TYPE_PENDING) + } val appWidgetInfo: LauncherAppWidgetProviderInfo? var removalReason = "" - @RestoreError var logReason = RestoreError.OTHER_WIDGET_INFLATION_FAIL + @RestoreError var logReason = RestoreError.APP_NOT_INSTALLED var update = false if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) { @@ -78,7 +74,7 @@ constructor( if (appWidgetInfo == null) { if (item.appWidgetId <= LauncherAppWidgetInfo.CUSTOM_WIDGET_ID) { removalReason = "CustomWidgetManager cannot find provider from that widget id." - logReason = RestoreError.INVALID_CUSTOM_WIDGET_ID + logReason = RestoreError.MISSING_INFO } else { removalReason = ("AppWidgetManager cannot find provider for that widget id." + @@ -100,7 +96,7 @@ constructor( type = TYPE_DELETE, reason = "Removing restored widget: id=${item.appWidgetId} belongs to component ${item.providerName} user ${item.user}, as the provider is null and $removalReason", - restoreErrorType = logReason, + restoreErrorType = logReason ) } @@ -136,7 +132,7 @@ constructor( widgetHelper.bindAppWidgetIdIfAllowed( item.appWidgetId, appWidgetInfo, - options, + options ) // We tried to bind once. If we were not able to bind, we would need to @@ -193,10 +189,9 @@ constructor( data class InflationResult( val type: Int, val reason: String? = null, - @RestoreError - val restoreErrorType: String = RestoreError.UNSPECIFIED_WIDGET_INFLATION_RESULT, + @RestoreError val restoreErrorType: String = RestoreError.APP_NOT_INSTALLED, val isUpdate: Boolean = false, - val widgetInfo: LauncherAppWidgetProviderInfo? = null, + val widgetInfo: LauncherAppWidgetProviderInfo? = null ) companion object { diff --git a/src/com/android/launcher3/widget/WidgetManagerHelper.java b/src/com/android/launcher3/widget/WidgetManagerHelper.java index 8bdaa415ff..4db376bc51 100644 --- a/src/com/android/launcher3/widget/WidgetManagerHelper.java +++ b/src/com/android/launcher3/widget/WidgetManagerHelper.java @@ -32,7 +32,6 @@ import android.widget.RemoteViews; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; -import androidx.annotation.VisibleForTesting; import com.android.launcher3.Utilities; import com.android.launcher3.logging.FileLog; @@ -60,13 +59,8 @@ public class WidgetManagerHelper { final Context mContext; public WidgetManagerHelper(Context context) { - this(context, AppWidgetManager.getInstance(context)); - } - - @VisibleForTesting - public WidgetManagerHelper(Context context, AppWidgetManager appWidgetManager) { mContext = context; - mAppWidgetManager = appWidgetManager; + mAppWidgetManager = AppWidgetManager.getInstance(context); } /** @@ -169,12 +163,7 @@ public class WidgetManagerHelper { @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) public RemoteViews loadGeneratedPreview(@NonNull AppWidgetProviderInfo info, int widgetCategory) { - try { - return mAppWidgetManager.getWidgetPreview(info.provider, info.getProfile(), widgetCategory); - } catch (NoSuchMethodError | NoClassDefFoundError e) { - Log.w("LC_"+TAG, "loadGeneratedPreview: Error loading widget preview"); - return null; - } + return mAppWidgetManager.getWidgetPreview(info.provider, info.getProfile(), widgetCategory); } private static Stream allWidgetsSteam(Context context) { diff --git a/src/com/android/launcher3/widget/WidgetsBottomSheet.java b/src/com/android/launcher3/widget/WidgetsBottomSheet.java index 278104829a..ae4456a951 100644 --- a/src/com/android/launcher3/widget/WidgetsBottomSheet.java +++ b/src/com/android/launcher3/widget/WidgetsBottomSheet.java @@ -17,7 +17,6 @@ package com.android.launcher3.widget; import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_BOTTOM_WIDGETS_TRAY; -import static com.android.launcher3.widget.picker.model.data.WidgetPickerDataUtils.findAllWidgetsForPackageUser; import android.content.Context; import android.graphics.Rect; @@ -41,7 +40,6 @@ import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.model.WidgetItem; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.util.PackageUserKey; -import com.android.launcher3.widget.picker.model.data.WidgetPickerData; import com.android.launcher3.widget.util.WidgetsTableUtils; import java.util.List; @@ -136,10 +134,10 @@ public class WidgetsBottomSheet extends BaseWidgetSheet { @Override public void onWidgetsBound() { - final WidgetPickerData data = mActivityContext.getWidgetPickerDataProvider().get(); - final PackageUserKey packageUserKey = PackageUserKey.fromItemInfo(mOriginalItemInfo); - List widgets = packageUserKey != null ? findAllWidgetsForPackageUser(data, - packageUserKey) : List.of(); + List widgets = mActivityContext.getPopupDataProvider().getWidgetsForPackageUser( + new PackageUserKey( + mOriginalItemInfo.getTargetComponent().getPackageName(), + mOriginalItemInfo.user)); TableLayout widgetsTable = findViewById(R.id.widgets_table); widgetsTable.removeAllViews(); @@ -259,7 +257,4 @@ public class WidgetsBottomSheet extends BaseWidgetSheet { } } } - - @Override - public void onRecommendedWidgetsBound() {} // no op } diff --git a/src/com/android/launcher3/widget/custom/CustomAppWidgetProviderInfo.java b/src/com/android/launcher3/widget/custom/CustomAppWidgetProviderInfo.java index 82a688306a..398b1df394 100644 --- a/src/com/android/launcher3/widget/custom/CustomAppWidgetProviderInfo.java +++ b/src/com/android/launcher3/widget/custom/CustomAppWidgetProviderInfo.java @@ -18,11 +18,10 @@ package com.android.launcher3.widget.custom; import android.content.ComponentName; import android.content.Context; +import android.content.pm.PackageManager; import android.os.Parcel; import android.os.Parcelable; -import androidx.annotation.VisibleForTesting; - import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.Utilities; import com.android.launcher3.widget.LauncherAppWidgetProviderInfo; @@ -53,9 +52,6 @@ public class CustomAppWidgetProviderInfo extends LauncherAppWidgetProviderInfo } } - @VisibleForTesting - CustomAppWidgetProviderInfo() {} - @Override public void initSpans(Context context, InvariantDeviceProfile idp) { mIsMinSizeFulfilled = Math.min(spanX, minSpanX) <= idp.numColumns @@ -63,7 +59,7 @@ public class CustomAppWidgetProviderInfo extends LauncherAppWidgetProviderInfo } @Override - public CharSequence getLabel() { + public String getLabel(PackageManager packageManager) { return Utilities.trim(label); } diff --git a/src/com/android/launcher3/widget/custom/CustomWidgetManager.java b/src/com/android/launcher3/widget/custom/CustomWidgetManager.java index 20cce8fc2d..50012b36f6 100644 --- a/src/com/android/launcher3/widget/custom/CustomWidgetManager.java +++ b/src/com/android/launcher3/widget/custom/CustomWidgetManager.java @@ -18,7 +18,6 @@ package com.android.launcher3.widget.custom; import static com.android.launcher3.Flags.enableSmartspaceAsAWidget; import static com.android.launcher3.model.data.LauncherAppWidgetInfo.CUSTOM_WIDGET_ID; -import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.widget.LauncherAppWidgetProviderInfo.CLS_CUSTOM_WIDGET_PREFIX; import android.appwidget.AppWidgetManager; @@ -31,14 +30,10 @@ import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; import com.android.launcher3.R; -import com.android.launcher3.dagger.ApplicationContext; -import com.android.launcher3.dagger.LauncherAppSingleton; -import com.android.launcher3.dagger.LauncherBaseAppComponent; -import com.android.launcher3.util.DaggerSingletonObject; -import com.android.launcher3.util.DaggerSingletonTracker; +import com.android.launcher3.util.MainThreadInitializedObject; +import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.util.PluginManagerWrapper; import com.android.launcher3.util.SafeCloseable; import com.android.launcher3.widget.LauncherAppWidgetHostView; @@ -50,46 +45,31 @@ import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; -import java.util.Map; -import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Consumer; import java.util.stream.Stream; -import javax.inject.Inject; - /** * CustomWidgetManager handles custom widgets implemented as a plugin. */ -@LauncherAppSingleton -public class CustomWidgetManager implements PluginListener { +public class CustomWidgetManager implements PluginListener, SafeCloseable { - public static final DaggerSingletonObject INSTANCE = - new DaggerSingletonObject<>(LauncherBaseAppComponent::getCustomWidgetManager); + public static final MainThreadInitializedObject INSTANCE = + new MainThreadInitializedObject<>(CustomWidgetManager::new); private static final String TAG = "CustomWidgetManager"; private static final String PLUGIN_PKG = "android"; private final Context mContext; private final HashMap mPlugins; private final List mCustomWidgets; - private final List mWidgetRefreshCallbacks = new CopyOnWriteArrayList<>(); - private final @NonNull AppWidgetManager mAppWidgetManager; + private Consumer mWidgetRefreshCallback; - @Inject - CustomWidgetManager(@ApplicationContext Context context, PluginManagerWrapper pluginManager, - DaggerSingletonTracker tracker) { - this(context, pluginManager, AppWidgetManager.getInstance(context), tracker); - } - - @VisibleForTesting - CustomWidgetManager(@ApplicationContext Context context, - PluginManagerWrapper pluginManager, - @NonNull AppWidgetManager widgetManager, - DaggerSingletonTracker tracker) { + private CustomWidgetManager(Context context) { mContext = context; - mAppWidgetManager = widgetManager; mPlugins = new HashMap<>(); mCustomWidgets = new ArrayList<>(); + PluginManagerWrapper.INSTANCE.get(context) + .addPluginListener(this, CustomWidgetPlugin.class, true); - pluginManager.addPluginListener(this, CustomWidgetPlugin.class, true); if (enableSmartspaceAsAWidget()) { for (String s: context.getResources() .getStringArray(R.array.custom_widget_providers)) { @@ -97,49 +77,47 @@ public class CustomWidgetManager implements PluginListener { Class cls = Class.forName(s); CustomWidgetPlugin plugin = (CustomWidgetPlugin) cls.getDeclaredConstructor(Context.class).newInstance(context); - MAIN_EXECUTOR.execute(() -> onPluginConnected(plugin, context)); - } catch (ClassNotFoundException | InstantiationException - | IllegalAccessException + onPluginConnected(plugin, context); + } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | ClassCastException | NoSuchMethodException | InvocationTargetException e) { Log.e(TAG, "Exception found when trying to add custom widgets: " + e); } } } - tracker.addCloseable(() -> pluginManager.removePluginListener(this)); + } + + @Override + public void close() { + PluginManagerWrapper.INSTANCE.get(mContext).removePluginListener(this); } @Override public void onPluginConnected(CustomWidgetPlugin plugin, Context context) { - CustomAppWidgetProviderInfo info = getAndAddInfo(new ComponentName( - PLUGIN_PKG, CLS_CUSTOM_WIDGET_PREFIX + plugin.getClass().getName())); - if (info != null) { - plugin.updateWidgetInfo(info, mContext); - mPlugins.put(info.provider, plugin); - mWidgetRefreshCallbacks.forEach(MAIN_EXECUTOR::execute); - } + List providers = AppWidgetManager.getInstance(context) + .getInstalledProvidersForProfile(Process.myUserHandle()); + if (providers.isEmpty()) return; + Parcel parcel = Parcel.obtain(); + providers.get(0).writeToParcel(parcel, 0); + parcel.setDataPosition(0); + CustomAppWidgetProviderInfo info = newInfo(plugin, parcel); + parcel.recycle(); + mPlugins.put(info.provider, plugin); + mCustomWidgets.add(info); } @Override public void onPluginDisconnected(CustomWidgetPlugin plugin) { - // Leave the providerInfo as plugins can get disconnected/reconnected multiple times - mPlugins.values().remove(plugin); - mWidgetRefreshCallbacks.forEach(MAIN_EXECUTOR::execute); - } - - @VisibleForTesting - @NonNull - Map getPlugins() { - return mPlugins; + ComponentName cn = getWidgetProviderComponent(plugin); + mPlugins.remove(cn); + mCustomWidgets.removeIf(w -> w.getComponent().equals(cn)); } /** * Inject a callback function to refresh the widgets. - * @return a closeable to remove this callback */ - public SafeCloseable addWidgetRefreshCallback(Runnable callback) { - mWidgetRefreshCallbacks.add(callback); - return () -> mWidgetRefreshCallbacks.remove(callback); + public void setWidgetRefreshCallback(Consumer cb) { + mWidgetRefreshCallback = cb; } /** @@ -148,9 +126,8 @@ public class CustomWidgetManager implements PluginListener { public void onViewCreated(LauncherAppWidgetHostView view) { CustomAppWidgetProviderInfo info = (CustomAppWidgetProviderInfo) view.getAppWidgetInfo(); CustomWidgetPlugin plugin = mPlugins.get(info.provider); - if (plugin != null) { - plugin.onViewCreated(view); - } + if (plugin == null) return; + plugin.onViewCreated(view); } /** @@ -166,13 +143,14 @@ public class CustomWidgetManager implements PluginListener { */ @Nullable public LauncherAppWidgetProviderInfo getWidgetProvider(ComponentName cn) { - LauncherAppWidgetProviderInfo info = mCustomWidgets.stream() + return mCustomWidgets.stream() .filter(w -> w.getComponent().equals(cn)).findAny().orElse(null); - if (info == null) { - // If the info is not present, add a placeholder info since the - // plugin might get loaded later - info = getAndAddInfo(cn); - } + } + + private CustomAppWidgetProviderInfo newInfo(CustomWidgetPlugin plugin, Parcel parcel) { + CustomAppWidgetProviderInfo info = new CustomAppWidgetProviderInfo(parcel, false); + info.provider = getWidgetProviderComponent(plugin); + plugin.updateWidgetInfo(info, mContext); return info; } @@ -183,24 +161,8 @@ public class CustomWidgetManager implements PluginListener { return CUSTOM_WIDGET_ID - mCustomWidgets.indexOf(getWidgetProvider(componentName)); } - @Nullable - private CustomAppWidgetProviderInfo getAndAddInfo(ComponentName cn) { - for (CustomAppWidgetProviderInfo info : mCustomWidgets) { - if (info.provider.equals(cn)) return info; - } - - List providers = mAppWidgetManager - .getInstalledProvidersForProfile(Process.myUserHandle()); - if (providers.isEmpty()) return null; - Parcel parcel = Parcel.obtain(); - providers.get(0).writeToParcel(parcel, 0); - parcel.setDataPosition(0); - CustomAppWidgetProviderInfo info = new CustomAppWidgetProviderInfo(parcel, false); - parcel.recycle(); - - info.provider = cn; - info.initialLayout = 0; - mCustomWidgets.add(info); - return info; + private ComponentName getWidgetProviderComponent(CustomWidgetPlugin plugin) { + return new ComponentName( + PLUGIN_PKG, CLS_CUSTOM_WIDGET_PREFIX + plugin.getClass().getName()); } } diff --git a/src/com/android/launcher3/widget/model/WidgetListSpaceEntry.java b/src/com/android/launcher3/widget/model/WidgetListSpaceEntry.java index a761ecd916..5b1da5b368 100644 --- a/src/com/android/launcher3/widget/model/WidgetListSpaceEntry.java +++ b/src/com/android/launcher3/widget/model/WidgetListSpaceEntry.java @@ -33,9 +33,4 @@ public class WidgetListSpaceEntry extends WidgetsListBaseEntry { Collections.EMPTY_LIST); mPkgItem.title = ""; } - - @Override - public WidgetsListBaseEntry copy() { - return new WidgetListSpaceEntry(); - } } diff --git a/src/com/android/launcher3/widget/model/WidgetsListBaseEntry.java b/src/com/android/launcher3/widget/model/WidgetsListBaseEntry.java index 9246e45be8..0003b762c2 100644 --- a/src/com/android/launcher3/widget/model/WidgetsListBaseEntry.java +++ b/src/com/android/launcher3/widget/model/WidgetsListBaseEntry.java @@ -43,7 +43,4 @@ public abstract class WidgetsListBaseEntry { this.mWidgets = items.stream().sorted(new WidgetItemComparator()).collect(Collectors.toList()); } - - - public abstract WidgetsListBaseEntry copy(); } diff --git a/src/com/android/launcher3/widget/model/WidgetsListContentEntry.java b/src/com/android/launcher3/widget/model/WidgetsListContentEntry.java index cc1739f60a..d709196225 100644 --- a/src/com/android/launcher3/widget/model/WidgetsListContentEntry.java +++ b/src/com/android/launcher3/widget/model/WidgetsListContentEntry.java @@ -57,11 +57,6 @@ public final class WidgetsListContentEntry extends WidgetsListBaseEntry { mMaxSpanSize = maxSpanSize; } - @Override - public WidgetsListBaseEntry copy() { - return new WidgetsListContentEntry(mPkgItem, mTitleSectionName, mWidgets, mMaxSpanSize); - } - @Override public String toString() { return "Content:" + mPkgItem.packageName + ":" + mWidgets.size() + " maxSpanSize: " diff --git a/src/com/android/launcher3/widget/model/WidgetsListHeaderEntry.java b/src/com/android/launcher3/widget/model/WidgetsListHeaderEntry.java index e2ea068fb3..0d775c3532 100644 --- a/src/com/android/launcher3/widget/model/WidgetsListHeaderEntry.java +++ b/src/com/android/launcher3/widget/model/WidgetsListHeaderEntry.java @@ -83,12 +83,6 @@ public final class WidgetsListHeaderEntry extends WidgetsListBaseEntry { mIsWidgetListShown = isWidgetListShown; } - @Override - public WidgetsListBaseEntry copy() { - return new WidgetsListHeaderEntry(mPkgItem, mTitleSectionName, mWidgets, - mVisibleWidgetsCount, mIsSearchEntry, mIsWidgetListShown); - } - /** Returns {@code true} if the widgets list associated with this header is shown. */ public boolean isWidgetListShown() { return mIsWidgetListShown; diff --git a/src/com/android/launcher3/widget/picker/OWNERS b/src/com/android/launcher3/widget/picker/OWNERS index 991193f799..6aabbfa119 100644 --- a/src/com/android/launcher3/widget/picker/OWNERS +++ b/src/com/android/launcher3/widget/picker/OWNERS @@ -6,6 +6,7 @@ set noparent # # Widget Picker OWNERS +zakcohen@google.com shamalip@google.com wvk@google.com diff --git a/src/com/android/launcher3/widget/picker/WidgetRecommendationCategory.java b/src/com/android/launcher3/widget/picker/WidgetRecommendationCategory.java index a68effd101..072d1d5948 100644 --- a/src/com/android/launcher3/widget/picker/WidgetRecommendationCategory.java +++ b/src/com/android/launcher3/widget/picker/WidgetRecommendationCategory.java @@ -19,8 +19,6 @@ package com.android.launcher3.widget.picker; import androidx.annotation.Nullable; import androidx.annotation.StringRes; -import com.android.launcher3.R; - import java.util.Objects; /** @@ -28,10 +26,6 @@ import java.util.Objects; * option in the pop-up opened on long press of launcher workspace). */ public class WidgetRecommendationCategory implements Comparable { - public static WidgetRecommendationCategory DEFAULT_WIDGET_RECOMMENDATION_CATEGORY = - new WidgetRecommendationCategory( - R.string.others_widget_recommendation_category_label, /*order=*/0); - /** Resource id that holds the user friendly label for the category. */ @StringRes public final int categoryTitleRes; diff --git a/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProvider.java b/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProvider.java index 8f34fe3572..9253b374d6 100644 --- a/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProvider.java +++ b/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProvider.java @@ -24,7 +24,7 @@ import androidx.annotation.WorkerThread; import com.android.launcher3.R; import com.android.launcher3.model.WidgetItem; -import com.android.launcher3.util.ApplicationInfoWrapper; +import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.util.Preconditions; import com.android.launcher3.util.ResourceBasedOverride; @@ -35,7 +35,19 @@ import com.android.launcher3.util.ResourceBasedOverride; * own implementation. Method {@code getWidgetRecommendationCategory} is called per widget to get * the category.

*/ -public class WidgetRecommendationCategoryProvider { +public class WidgetRecommendationCategoryProvider implements ResourceBasedOverride { + private static final String TAG = "WidgetRecommendationCategoryProvider"; + + /** + * Retrieve instance of this object that can be overridden in runtime based on the build + * variant of the application. + */ + public static WidgetRecommendationCategoryProvider newInstance(Context context) { + Preconditions.assertWorkerThread(); + return Overrides.getObject( + WidgetRecommendationCategoryProvider.class, context.getApplicationContext(), + R.string.widget_recommendation_category_provider_class); + } /** * Returns a {@link WidgetRecommendationCategory} for the provided widget item that can be used @@ -50,14 +62,14 @@ public class WidgetRecommendationCategoryProvider { // via the overridden WidgetRecommendationCategoryProvider resource. Preconditions.assertWorkerThread(); - if (item.widgetInfo != null && item.widgetInfo.getComponent() != null) { - ApplicationInfo applicationInfo = new ApplicationInfoWrapper( - context, - item.widgetInfo.getComponent().getPackageName(), - item.widgetInfo.getUser()) - .getInfo(); - if (applicationInfo != null) { - return getCategoryFromApplicationCategory(applicationInfo.category); + try (PackageManagerHelper pmHelper = new PackageManagerHelper(context)) { + if (item.widgetInfo != null && item.widgetInfo.getComponent() != null) { + ApplicationInfo applicationInfo = pmHelper.getApplicationInfo( + item.widgetInfo.getComponent().getPackageName(), item.widgetInfo.getUser(), + 0 /* flags */); + if (applicationInfo != null) { + return getCategoryFromApplicationCategory(applicationInfo.category); + } } } return null; diff --git a/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java b/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java index 333db0b03e..5e19d24e93 100644 --- a/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java +++ b/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java @@ -16,8 +16,7 @@ package com.android.launcher3.widget.picker; -import static com.android.launcher3.widget.picker.WidgetRecommendationCategory.DEFAULT_WIDGET_RECOMMENDATION_CATEGORY; -import static com.android.launcher3.widget.util.WidgetsTableUtils.groupWidgetItemsUsingRowPxWithReordering; +import static com.android.launcher3.widget.util.WidgetsTableUtils.groupWidgetItemsUsingRowPxWithoutReordering; import static java.util.stream.Collectors.toList; @@ -41,7 +40,6 @@ import com.android.launcher3.model.WidgetItem; import com.android.launcher3.pageindicators.PageIndicatorDots; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; @@ -58,16 +56,9 @@ import java.util.stream.Collectors; public final class WidgetRecommendationsView extends PagedView { private @Px float mAvailableHeight = Float.MAX_VALUE; private @Px float mAvailableWidth = 0; - private int mLastUiMode = -1; private static final String INITIALLY_DISPLAYED_WIDGETS_STATE_KEY = "widgetRecommendationsView:mDisplayedWidgets"; private static final int MAX_CATEGORIES = 3; - - // Whether to show all widgets in a full page without any limitation on height - private boolean mShowFullPageViewIfLowDensity = false; - // Number of items below which a category is considered low density. - private static final int IDEAL_ITEMS_PER_CATEGORY = 2; - private TextView mRecommendationPageTitle; private final List mCategoryTitles = new ArrayList<>(); @@ -98,14 +89,6 @@ public final class WidgetRecommendationsView extends PagedView> recommendations) { - if (mShowFullPageViewIfLowDensity) { - boolean hasLessCategories = recommendations.size() < MAX_CATEGORIES; - long lowDensityCategoriesCount = recommendations.values() - .stream() - .limit(MAX_CATEGORIES) - .filter(items -> items.size() < IDEAL_ITEMS_PER_CATEGORY).count(); + /** + * Displays all the provided recommendations in a single table if they fit. + * + * @param recommendedWidgets list of widgets to be displayed in recommendation section. + * @param deviceProfile the current {@link DeviceProfile} + * @param availableHeight height in px that can be used to display the recommendations; + * recommendations that don't fit in this height won't be shown + * @param availableWidth width in px that the recommendations should display in + * @param cellPadding padding in px that should be applied to each widget in the + * recommendations + * @return number of recommendations that could fit in the available space. + */ + public int setRecommendations( + List recommendedWidgets, DeviceProfile deviceProfile, + final @Px float availableHeight, final @Px int availableWidth, + final @Px int cellPadding) { + this.mAvailableHeight = availableHeight; + this.mAvailableWidth = availableWidth; + clear(); - // If there less number of categories or if there are at least 2 categorizes with less - // widgets, prefer showing single page view. - return hasLessCategories || lowDensityCategoriesCount > 1; + Set displayedWidgets = maybeDisplayInTable(recommendedWidgets, + deviceProfile, + availableWidth, cellPadding); + + if (mDisplayedWidgets.isEmpty()) { + // Save the widgets shown for the first time user opened the picker; so that, they can + // be maintained across orientation changes. + mDisplayedWidgets = displayedWidgets; } - return false; + + updateTitleAndIndicator(/* requestedPage= */ 0); + return displayedWidgets.size(); } /** @@ -183,82 +184,54 @@ public final class WidgetRecommendationsView extends PagedView> recommendations, DeviceProfile deviceProfile, final @Px float availableHeight, - final @Px int availableWidth, final @Px int cellPadding, final int requestedPage, - final boolean forceUpdate) { - if (forceUpdate || shouldUpdate(availableWidth, availableHeight)) { - Context context = getContext(); - this.mAvailableHeight = availableHeight; - this.mAvailableWidth = availableWidth; - this.mLastUiMode = context.getResources().getConfiguration().uiMode; + final @Px int availableWidth, final @Px int cellPadding, final int requestedPage) { + this.mAvailableHeight = availableHeight; + this.mAvailableWidth = availableWidth; + Context context = getContext(); + // For purpose of recommendations section, we don't want paging dots to be halved in two + // pane display, so, we always provide isTwoPanels = "false". + mPageIndicator.setPauseScroll(/*pause=*/true, /*isTwoPanels=*/ false); + clear(); - final Map> mappedRecommendations; - if (shouldShowSinglePageView(recommendations)) { // map to single category. - mappedRecommendations = Map.of(DEFAULT_WIDGET_RECOMMENDATION_CATEGORY, - recommendations.values().stream().flatMap( - Collection::stream).toList()); - } else { - mappedRecommendations = recommendations; + int displayedCategories = 0; + Set allDisplayedWidgets = new HashSet<>(); + + // Render top MAX_CATEGORIES in separate tables. Each table becomes a page. + for (Map.Entry> entry : + new TreeMap<>(recommendations).entrySet()) { + // If none of the recommendations for the category could fit in the mAvailableHeight, we + // don't want to add that category; and we look for the next one. + Set displayedWidgetsForCategory = maybeDisplayInTable(entry.getValue(), + deviceProfile, + availableWidth, cellPadding); + if (!displayedWidgetsForCategory.isEmpty()) { + mCategoryTitles.add( + context.getResources().getString(entry.getKey().categoryTitleRes)); + displayedCategories++; + allDisplayedWidgets.addAll(displayedWidgetsForCategory); } - // For purpose of recommendations section, we don't want paging dots to be halved in two - // pane display, so, we always provide isTwoPanels = "false". - mPageIndicator.setPauseScroll(/*pause=*/true, /*isTwoPanels=*/ false); - clear(); - - int displayedCategories = 0; - Set allDisplayedWidgets = new HashSet<>(); - - // Render top MAX_CATEGORIES in separate tables. Each table becomes a page. - for (Map.Entry> entry : - new TreeMap<>(mappedRecommendations).entrySet()) { - // If none of the recommendations for the category could fit in the - // mAvailableHeight, we don't want to add that category; and we look for the next - // one. - Set displayedWidgetsForCategory = maybeDisplayInTable( - entry.getValue(), - deviceProfile, - availableWidth, cellPadding); - if (!displayedWidgetsForCategory.isEmpty()) { - mCategoryTitles.add( - context.getResources().getString(entry.getKey().categoryTitleRes)); - displayedCategories++; - allDisplayedWidgets.addAll(displayedWidgetsForCategory); - } - - if (displayedCategories == MAX_CATEGORIES) { - break; - } + if (displayedCategories == MAX_CATEGORIES) { + break; } - - if (mDisplayedWidgets.isEmpty()) { - // Save the widgets shown for the first time user opened the picker; so that, - // they can - // be maintained across orientation changes. - mDisplayedWidgets = allDisplayedWidgets; - } - - updateTitleAndIndicator(requestedPage); - // For purpose of recommendations section, we don't want paging dots to be halved in two - // pane display, so, we always provide isTwoPanels = "false". - mPageIndicator.setPauseScroll(/*pause=*/false, /*isTwoPanels=*/false); - return allDisplayedWidgets.size(); - } else { - return mDisplayedWidgets.size(); } - } - /** - * Returns if we should re-render the views. - */ - private boolean shouldUpdate(int availableWidth, float availableHeight) { - return this.mAvailableWidth != availableWidth || this.mAvailableHeight != availableHeight - || getContext().getResources().getConfiguration().uiMode != this.mLastUiMode; + if (mDisplayedWidgets.isEmpty()) { + // Save the widgets shown for the first time user opened the picker; so that, they can + // be maintained across orientation changes. + mDisplayedWidgets = allDisplayedWidgets; + } + + updateTitleAndIndicator(requestedPage); + // For purpose of recommendations section, we don't want paging dots to be halved in two + // pane display, so, we always provide isTwoPanels = "false". + mPageIndicator.setPauseScroll(/*pause=*/false, /*isTwoPanels=*/false); + return allDisplayedWidgets.size(); } private void clear() { @@ -280,28 +253,16 @@ public final class WidgetRecommendationsView extends PagedView 1) { // Since the title is outside the paging scroll, we update the title on page switch. int nextPage = getNextPage(); - updatePageTitle(nextPage); + mRecommendationPageTitle.setText(mCategoryTitles.get(nextPage)); mPageSwitchListeners.forEach(listener -> listener.accept(nextPage)); super.notifyPageSwitchListener(prevPage); } @@ -359,14 +320,14 @@ public final class WidgetRecommendationsView extends PagedView mDisplayedWidgets.contains(w.componentName)).collect(Collectors.toList()); + w -> mDisplayedWidgets.contains(w.componentName)).collect(toList()); } Context context = getContext(); LayoutInflater inflater = LayoutInflater.from(context); // Since we are limited by space, we don't sort recommendations - to show most relevant // (if possible). - List> rows = groupWidgetItemsUsingRowPxWithReordering( + List> rows = groupWidgetItemsUsingRowPxWithoutReordering( filteredRecommendedWidgets, context, deviceProfile, diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java index 7b905a7358..bca15d1da8 100644 --- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java +++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java @@ -15,22 +15,16 @@ */ package com.android.launcher3.widget.picker; -import static com.android.launcher3.Flags.enableTieredWidgetsByDefaultInPicker; +import static com.android.launcher3.Flags.enableCategorizedWidgetSuggestions; +import static com.android.launcher3.Flags.enableUnfoldedTwoPanePicker; import static com.android.launcher3.allapps.ActivityAllAppsContainerView.AdapterHolder.SEARCH; -import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WIDGETSTRAY_EXPAND_PRESS; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WIDGETSTRAY_SEARCHED; import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL; -import static com.android.launcher3.views.RecyclerViewFastScroller.FastScrollerLocation.WIDGET_SCROLLER; - -import static java.lang.Math.abs; -import static java.util.Collections.emptyList; import android.animation.Animator; import android.content.Context; import android.content.res.Resources; import android.graphics.Rect; -import android.os.Build.VERSION; -import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.os.Parcelable; import android.os.Process; @@ -42,7 +36,6 @@ import android.util.SparseArray; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; -import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.Window; import android.view.ViewParent; @@ -58,7 +51,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.Px; import androidx.annotation.VisibleForTesting; -import androidx.collection.SparseArrayCompat; import androidx.core.view.WindowInsetsCompat; import androidx.core.view.WindowInsetsControllerCompat; import androidx.recyclerview.widget.DefaultItemAnimator; @@ -66,6 +58,7 @@ import androidx.recyclerview.widget.RecyclerView; import com.android.launcher3.BaseActivity; import com.android.launcher3.DeviceProfile; +import com.android.launcher3.LauncherAppState; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.anim.PendingAnimation; @@ -79,10 +72,8 @@ import com.android.launcher3.views.StickyHeaderLayout; import com.android.launcher3.widget.BaseWidgetSheet; import com.android.launcher3.widget.WidgetCell; import com.android.launcher3.widget.model.WidgetsListBaseEntry; -import com.android.launcher3.widget.picker.model.data.WidgetPickerData; import com.android.launcher3.widget.picker.search.SearchModeListener; import com.android.launcher3.widget.picker.search.WidgetsSearchBar; -import com.android.launcher3.widget.picker.search.WidgetsSearchBar.WidgetsSearchDataProvider; import com.android.launcher3.workprofile.PersonalWorkPagedView; import com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip.OnActivePageChangedListener; @@ -102,52 +93,45 @@ import app.lawnchair.theme.drawable.DrawableTokens; */ public class WidgetsFullSheet extends BaseWidgetSheet implements OnActivePageChangedListener, - WidgetsRecyclerView.HeaderViewDimensionsProvider, SearchModeListener, - WidgetsListAdapter.ExpandButtonClickListener { + WidgetsRecyclerView.HeaderViewDimensionsProvider, SearchModeListener { private static final long FADE_IN_DURATION = 150; - // The widget recommendation table can easily take over the entire screen on devices with small - // resolution or landscape on phone. This ratio defines the max percentage of content area that + // The widget recommendation table can easily take over the entire screen on + // devices with small + // resolution or landscape on phone. This ratio defines the max percentage of + // content area that // the table can display with respect to bottom sheet's height. private static final float RECOMMENDATION_TABLE_HEIGHT_RATIO = 0.45f; - private static final String RECOMMENDATIONS_SAVED_STATE_KEY = - "widgetsFullSheet:mRecommendationsCurrentPage"; + private static final String RECOMMENDATIONS_SAVED_STATE_KEY = "widgetsFullSheet:mRecommendationsCurrentPage"; private static final String SUPER_SAVED_STATE_KEY = "widgetsFullSheet:superHierarchyState"; private final UserCache mUserCache; private final UserManagerState mUserManagerState = new UserManagerState(); private final UserHandle mCurrentUser = Process.myUserHandle(); - private final Predicate mPrimaryWidgetsFilter = - entry -> mCurrentUser.equals(entry.mPkgItem.user); + private final Predicate mPrimaryWidgetsFilter = entry -> mCurrentUser + .equals(entry.mPkgItem.user); private final Predicate mWorkWidgetsFilter; protected final boolean mHasWorkProfile; // Number of recommendations displayed protected int mRecommendedWidgetsCount; private List mRecommendedWidgets = new ArrayList<>(); - private Map> mRecommendedWidgetsMap = - new HashMap<>(); + private Map> mRecommendedWidgetsMap = new HashMap<>(); protected int mRecommendationsCurrentPage = 0; protected final SparseArray mAdapters = new SparseArray(); - // Helps with removing focus from searchbar by analyzing motion events. - private final SearchClearFocusHelper mSearchClearFocusHelper = new SearchClearFocusHelper(); - private final float mTouchSlop; // initialized in constructor + private final OnAttachStateChangeListener mBindScrollbarInSearchMode = new OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(View view) { + WidgetsRecyclerView searchRecyclerView = mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView; + if (mIsInSearchMode && searchRecyclerView != null) { + searchRecyclerView.bindFastScrollbar(mFastScroller); + } + } - private final OnAttachStateChangeListener mBindScrollbarInSearchMode = - new OnAttachStateChangeListener() { - @Override - public void onViewAttachedToWindow(View view) { - WidgetsRecyclerView searchRecyclerView = - mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView; - if (mIsInSearchMode && searchRecyclerView != null) { - searchRecyclerView.bindFastScrollbar(mFastScroller, WIDGET_SCROLLER); - } - } - - @Override - public void onViewDetachedFromWindow(View view) { - } - }; + @Override + public void onViewDetachedFromWindow(View view) { + } + }; @Px private final int mTabsHeight; @@ -158,16 +142,14 @@ public class WidgetsFullSheet extends BaseWidgetSheet private WidgetsRecyclerView mCurrentTouchEventRecyclerView; @Nullable PersonalWorkPagedView mViewPager; - protected boolean mIsInSearchMode; + private boolean mIsInSearchMode; private boolean mIsNoWidgetsViewNeeded; @Px protected int mMaxSpanPerRow; protected DeviceProfile mDeviceProfile; protected TextView mNoWidgetsView; - protected LinearLayout mSearchScrollView; - // Reference to the mSearchScrollView when it is is a sticky header. - private @Nullable StickyHeaderLayout mStickyHeaderLayout; + protected StickyHeaderLayout mSearchScrollView; protected WidgetRecommendationsView mWidgetRecommendationsView; protected LinearLayout mWidgetRecommendationsContainer; protected View mTabBar; @@ -179,7 +161,6 @@ public class WidgetsFullSheet extends BaseWidgetSheet public WidgetsFullSheet(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); - mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); mDeviceProfile = mActivityContext.getDeviceProfile(); mUserCache = UserCache.INSTANCE.get(context); mHasWorkProfile = mUserCache.getUserProfiles() @@ -233,7 +214,8 @@ public class WidgetsFullSheet extends BaseWidgetSheet R.id.widget_recommendations_container); mWidgetRecommendationsView = mSearchScrollView.findViewById( R.id.widget_recommendations_view); - // To save the currently displayed page, so that, it can be requested when rebinding + // To save the currently displayed page, so that, it can be requested when + // rebinding // recommendations with different size constraints. mWidgetRecommendationsView.addPageSwitchListener( newPage -> mRecommendationsCurrentPage = newPage); @@ -248,11 +230,7 @@ public class WidgetsFullSheet extends BaseWidgetSheet protected void setupViews() { mSearchScrollView = findViewById(R.id.search_and_recommendations_container); - if (mSearchScrollView instanceof StickyHeaderLayout) { - mStickyHeaderLayout = (StickyHeaderLayout) mSearchScrollView; - mStickyHeaderLayout.setCurrentRecyclerView( - findViewById(R.id.primary_widgets_list_view)); - } + mSearchScrollView.setCurrentRecyclerView(findViewById(R.id.primary_widgets_list_view)); mNoWidgetsView = findViewById(R.id.no_widgets_text); mFastScroller = findViewById(R.id.fast_scroller); mFastScroller.setPopupView(findViewById(R.id.fast_scroller_popup)); @@ -285,72 +263,55 @@ public class WidgetsFullSheet extends BaseWidgetSheet mSearchBar = mSearchScrollView.findViewById(R.id.widgets_search_bar); - mSearchBar.initialize(new WidgetsSearchDataProvider() { - @Override - public List getWidgets() { - if (enableTieredWidgetsByDefaultInPicker()) { - // search all - return mActivityContext.getWidgetPickerDataProvider().get().getAllWidgets(); - } else { - // Can be removed when inlining enableTieredWidgetsByDefaultInPicker flag - return getWidgetsToDisplay(); - } - } - }, /* searchModeListener= */ this); + mSearchBar.initialize( + mActivityContext.getPopupDataProvider(), /* searchModeListener= */ this); } private void setDeviceManagementResources() { - if (mActivityContext.getStringCache() != null) { - Button personalTab = findViewById(R.id.tab_personal); - personalTab.setText(R.string.all_apps_personal_tab); - personalTab.setAllCaps(false); - FontManager.INSTANCE.get(getContext()).setCustomFont(personalTab, R.id.font_button); + Button personalTab = findViewById(R.id.tab_personal); + personalTab.setText(R.string.all_apps_personal_tab); + personalTab.setAllCaps(false); + FontManager.INSTANCE.get(getContext()).setCustomFont(personalTab, R.id.font_button); - Button workTab = findViewById(R.id.tab_work); - workTab.setText(R.string.all_apps_work_tab); - workTab.setAllCaps(false); - FontManager.INSTANCE.get(getContext()).setCustomFont(workTab, R.id.font_button); - } + Button workTab = findViewById(R.id.tab_work); + workTab.setText(R.string.all_apps_work_tab); + workTab.setAllCaps(false); + FontManager.INSTANCE.get(getContext()).setCustomFont(workTab, R.id.font_button); } @Override public void onActivePageChanged(int currentActivePage) { AdapterHolder currentAdapterHolder = mAdapters.get(currentActivePage); - WidgetsRecyclerView currentRecyclerView = - mAdapters.get(currentActivePage).mWidgetsRecyclerView; + WidgetsRecyclerView currentRecyclerView = mAdapters.get(currentActivePage).mWidgetsRecyclerView; updateRecyclerViewVisibility(currentAdapterHolder); attachScrollbarToRecyclerView(currentRecyclerView); } private void attachScrollbarToRecyclerView(WidgetsRecyclerView recyclerView) { + recyclerView.bindFastScrollbar(mFastScroller); if (mCurrentWidgetsRecyclerView != recyclerView) { - // Bind scrollbar if changing the recycler view. If widgets list updates, since - // scrollbar is already attached to the recycler view, it will automatically adjust as - // needed with recycler view's onScrollListener. - recyclerView.bindFastScrollbar(mFastScroller, WIDGET_SCROLLER); - // Only reset the scroll position & expanded apps if the currently shown recycler view + // Only reset the scroll position & expanded apps if the currently shown + // recycler view // has been updated. reset(); resetExpandedHeaders(); mCurrentWidgetsRecyclerView = recyclerView; - if (mStickyHeaderLayout != null) { - mStickyHeaderLayout.setCurrentRecyclerView(recyclerView); - } + mSearchScrollView.setCurrentRecyclerView(recyclerView); } } protected void updateRecyclerViewVisibility(AdapterHolder adapterHolder) { // The first item is always an empty space entry. Look for any more items. boolean isWidgetAvailable = adapterHolder.mWidgetsListAdapter.hasVisibleEntries(); + adapterHolder.mWidgetsRecyclerView.setVisibility(isWidgetAvailable ? VISIBLE : GONE); if (adapterHolder.mAdapterType == AdapterHolder.SEARCH) { mNoWidgetsView.setText(R.string.no_search_results); - adapterHolder.mWidgetsRecyclerView.setVisibility(isWidgetAvailable ? VISIBLE : GONE); } else if (adapterHolder.mAdapterType == AdapterHolder.WORK && mUserCache.getUserProfiles().stream() - .filter(userHandle -> mUserCache.getUserInfo(userHandle).isWork()) - .anyMatch(mUserManagerState::isUserQuiet) + .filter(userHandle -> mUserCache.getUserInfo(userHandle).isWork()) + .anyMatch(mUserManagerState::isUserQuiet) && mActivityContext.getStringCache() != null) { mNoWidgetsView.setText(mActivityContext.getStringCache().workProfilePausedTitle); } else { @@ -365,9 +326,7 @@ public class WidgetsFullSheet extends BaseWidgetSheet mAdapters.get(AdapterHolder.WORK).mWidgetsRecyclerView.scrollToTop(); } mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView.scrollToTop(); - if (mStickyHeaderLayout != null) { - mStickyHeaderLayout.reset(/* animate= */ true); - } + mSearchScrollView.reset(/* animate= */ true); } @VisibleForTesting @@ -411,8 +370,7 @@ public class WidgetsFullSheet extends BaseWidgetSheet setBottomPadding(mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView, mBottomPadding); setBottomPadding(mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView, mBottomPadding); if (mHasWorkProfile) { - setBottomPadding(mAdapters.get(AdapterHolder.WORK) - .mWidgetsRecyclerView, mBottomPadding); + setBottomPadding(mAdapters.get(AdapterHolder.WORK).mWidgetsRecyclerView, mBottomPadding); } ((MarginLayoutParams) mNoWidgetsView.getLayoutParams()).bottomMargin = mBottomPadding; @@ -463,8 +421,7 @@ public class WidgetsFullSheet extends BaseWidgetSheet } private static void setContentViewChildHorizontalMargin(View view, int horizontalMarginInPx) { - ViewGroup.MarginLayoutParams layoutParams = - (ViewGroup.MarginLayoutParams) view.getLayoutParams(); + ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) view.getLayoutParams(); layoutParams.setMarginStart(horizontalMarginInPx); layoutParams.setMarginEnd(horizontalMarginInPx); } @@ -476,18 +433,21 @@ public class WidgetsFullSheet extends BaseWidgetSheet @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - int availableWidth = MeasureSpec.getSize(widthMeasureSpec); - updateMaxSpansPerRow(availableWidth); doMeasure(widthMeasureSpec, heightMeasureSpec); + + if (updateMaxSpansPerRow()) { + doMeasure(widthMeasureSpec, heightMeasureSpec); + } } - /** Returns {@code true} if the max spans have been updated. - * - * @param availableWidth Total width available within parent (includes insets). - */ - private void updateMaxSpansPerRow(int availableWidth) { - @Px int maxHorizontalSpan = getAvailableWidthForSuggestions( - availableWidth - getInsetsWidth()); + /** Returns {@code true} if the max spans have been updated. */ + private boolean updateMaxSpansPerRow() { + if (getMeasuredWidth() == 0) + return false; + + @Px + int maxHorizontalSpan = getContentView().getMeasuredWidth() + - (2 * mContentHorizontalMargin); if (mMaxSpanPerRow != maxHorizontalSpan) { mMaxSpanPerRow = maxHorizontalSpan; mAdapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter.setMaxHorizontalSpansPxPerRow( @@ -498,15 +458,16 @@ public class WidgetsFullSheet extends BaseWidgetSheet mAdapters.get(AdapterHolder.WORK).mWidgetsListAdapter.setMaxHorizontalSpansPxPerRow( maxHorizontalSpan); } - post(this::onRecommendedWidgetsBound); + onRecommendedWidgetsBound(); + return true; } + return false; } - /** - * Returns the width available to display suggestions. - */ - protected int getAvailableWidthForSuggestions(int pickerAvailableWidth) { - return pickerAvailableWidth - (2 * mContentHorizontalMargin); + protected View getContentView() { + return mHasWorkProfile + ? mViewPager + : mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView; } @Override @@ -523,72 +484,41 @@ public class WidgetsFullSheet extends BaseWidgetSheet setTranslationShift(mTranslationShift); } - /** - * Returns all displayable widgets. - */ - // Used by the two pane sheet to show 3-dot menu to toggle between default lists and all lists - // when enableTieredWidgetsByDefaultInPicker is OFF. This code path and the 3-dot menu can be - // safely deleted when it's alternative "enableTieredWidgetsByDefaultInPicker" flag is inlined. - protected List getWidgetsToDisplay() { - return mActivityContext.getWidgetPickerDataProvider().get().getAllWidgets(); - } - @Override public void onWidgetsBound() { if (mIsInSearchMode) { return; } - List widgets; - List defaultWidgets = emptyList(); - - if (enableTieredWidgetsByDefaultInPicker()) { - WidgetPickerData dataProvider = - mActivityContext.getWidgetPickerDataProvider().get(); - widgets = dataProvider.getAllWidgets(); - defaultWidgets = dataProvider.getDefaultWidgets(); - } else { - // This code path can be deleted once enableTieredWidgetsByDefaultInPicker is inlined. - widgets = getWidgetsToDisplay(); - } + List allWidgets = mActivityContext.getPopupDataProvider().getAllWidgets(); AdapterHolder primaryUserAdapterHolder = mAdapters.get(AdapterHolder.PRIMARY); - primaryUserAdapterHolder.mWidgetsListAdapter.setWidgets(widgets, defaultWidgets); + primaryUserAdapterHolder.mWidgetsListAdapter.setWidgets(allWidgets); if (mHasWorkProfile) { mViewPager.setVisibility(VISIBLE); mTabBar.setVisibility(VISIBLE); AdapterHolder workUserAdapterHolder = mAdapters.get(AdapterHolder.WORK); - workUserAdapterHolder.mWidgetsListAdapter.setWidgets(widgets, defaultWidgets); + workUserAdapterHolder.mWidgetsListAdapter.setWidgets(allWidgets); onActivePageChanged(mViewPager.getCurrentPage()); } else { onActivePageChanged(0); } - // Update recommended widgets section so that it occupies appropriate space on screen to + // Update recommended widgets section so that it occupies appropriate space on + // screen to // leave enough space for presence/absence of mNoWidgetsView. - boolean isNoWidgetsViewNeeded = - !mAdapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter.hasVisibleEntries() - || (mHasWorkProfile && mAdapters.get(AdapterHolder.WORK) - .mWidgetsListAdapter.hasVisibleEntries()); + boolean isNoWidgetsViewNeeded = !mAdapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter.hasVisibleEntries() + || (mHasWorkProfile && mAdapters.get(AdapterHolder.WORK).mWidgetsListAdapter.hasVisibleEntries()); if (mIsNoWidgetsViewNeeded != isNoWidgetsViewNeeded) { mIsNoWidgetsViewNeeded = isNoWidgetsViewNeeded; - post(this::onRecommendedWidgetsBound); + onRecommendedWidgetsBound(); } } - @Override - public void onWidgetsListExpandButtonClick(View v) { - AdapterHolder currentAdapterHolder = mAdapters.get(getCurrentAdapterHolderType()); - currentAdapterHolder.mWidgetsListAdapter.useExpandedList(); - onWidgetsBound(); - currentAdapterHolder.mWidgetsRecyclerView.announceForAccessibility( - mActivityContext.getString(R.string.widgets_list_expanded)); - mActivityContext.getStatsLogManager().logger().log(LAUNCHER_WIDGETSTRAY_EXPAND_PRESS); - } - @Override public void enterSearchMode(boolean shouldLog) { - if (mIsInSearchMode) return; - setViewVisibilityBasedOnSearch(/*isInSearchMode= */ true); + if (mIsInSearchMode) + return; + setViewVisibilityBasedOnSearch(/* isInSearchMode= */ true); attachScrollbarToRecyclerView(mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView); if (shouldLog) { mActivityContext.getStatsLogManager().logger().log(LAUNCHER_WIDGETSTRAY_SEARCHED); @@ -597,14 +527,21 @@ public class WidgetsFullSheet extends BaseWidgetSheet @Override public void exitSearchMode() { - if (!mIsInSearchMode) return; + if (!mIsInSearchMode) + return; onSearchResults(new ArrayList<>()); - // Remove all views when exiting the search mode; this prevents animating from stale results - // to new ones the next time we enter search mode. By the time recycler view is hidden, - // layout may not have happened to clear up existing results. So, instead of waiting for it + WidgetsRecyclerView searchRecyclerView = mAdapters.get( + AdapterHolder.SEARCH).mWidgetsRecyclerView; + // Remove all views when exiting the search mode; this prevents animating from + // stale results + // to new ones the next time we enter search mode. By the time recycler view is + // hidden, + // layout may not have happened to clear up existing results. So, instead of + // waiting for it // to happen, we clear the views here. - mAdapters.get(AdapterHolder.SEARCH).reset(); - setViewVisibilityBasedOnSearch(/*isInSearchMode=*/ false); + searchRecyclerView.swapAdapter( + searchRecyclerView.getAdapter(), /* removeAndRecycleExistingViews= */ true); + setViewVisibilityBasedOnSearch(/* isInSearchMode= */ false); if (mHasWorkProfile) { mViewPager.snapToPage(AdapterHolder.PRIMARY); } @@ -632,13 +569,10 @@ public class WidgetsFullSheet extends BaseWidgetSheet mNoWidgetsView.setVisibility(GONE); } else { mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView.setVisibility(GONE); - AdapterHolder currentAdapterHolder = mAdapters.get(getCurrentAdapterHolderType()); - // Remove all views when exiting the search mode; this prevents animating / flashing old - // list position / state. - currentAdapterHolder.reset(); - currentAdapterHolder.mWidgetsRecyclerView.setVisibility(VISIBLE); - post(this::onRecommendedWidgetsBound); - // Visibility of recycler views and headers are handled in methods below. + // Visibility of recommended widgets, recycler views and headers are handled in + // methods + // below. + onRecommendedWidgetsBound(); onWidgetsBound(); } } @@ -653,30 +587,38 @@ public class WidgetsFullSheet extends BaseWidgetSheet if (mIsInSearchMode) { return; } - boolean forceUpdate = false; - // We avoid applying new recommendations when some are already displayed. - if (mRecommendedWidgetsMap.isEmpty()) { - mRecommendedWidgetsMap = - mActivityContext.getWidgetPickerDataProvider().get().getRecommendations(); - forceUpdate = true; - } - mRecommendedWidgetsCount = mWidgetRecommendationsView.setRecommendations( - mRecommendedWidgetsMap, - mDeviceProfile, - /* availableHeight= */ getMaxAvailableHeightForRecommendations(), - /* availableWidth= */ mMaxSpanPerRow, - /* cellPadding= */ mWidgetCellHorizontalPadding, - /* requestedPage= */ mRecommendationsCurrentPage, - /* forceUpdate= */ forceUpdate - ); + if (enableCategorizedWidgetSuggestions()) { + // We avoid applying new recommendations when some are already displayed. + if (mRecommendedWidgetsMap.isEmpty()) { + mRecommendedWidgetsMap = mActivityContext.getPopupDataProvider().getCategorizedRecommendedWidgets(); + } + mRecommendedWidgetsCount = mWidgetRecommendationsView.setRecommendations( + mRecommendedWidgetsMap, + mDeviceProfile, + /* availableHeight= */ getMaxAvailableHeightForRecommendations(), + /* availableWidth= */ mMaxSpanPerRow, + /* cellPadding= */ mWidgetCellHorizontalPadding, + /* requestedPage= */ mRecommendationsCurrentPage); + } else { + if (mRecommendedWidgets.isEmpty()) { + mRecommendedWidgets = mActivityContext.getPopupDataProvider().getRecommendedWidgets(); + } + mRecommendedWidgetsCount = mWidgetRecommendationsView.setRecommendations( + mRecommendedWidgets, + mDeviceProfile, + /* availableHeight= */ getMaxAvailableHeightForRecommendations(), + /* availableWidth= */ mMaxSpanPerRow, + /* cellPadding= */ mWidgetCellHorizontalPadding); + } mWidgetRecommendationsContainer.setVisibility( mRecommendedWidgetsCount > 0 ? VISIBLE : GONE); } @Px protected float getMaxAvailableHeightForRecommendations() { - // There isn't enough space to show recommendations in landscape orientation on phones with + // There isn't enough space to show recommendations in landscape orientation on + // phones with // a full sheet design. Tablets use a two pane picker. if (mDeviceProfile.isLandscape) { return 0f; @@ -726,14 +668,10 @@ public class WidgetsFullSheet extends BaseWidgetSheet public boolean onControllerInterceptTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { mNoIntercept = shouldScroll(ev); - } - - // Clear focus only if user touched outside of search area and handling focus out ourselves - // was necessary (e.g. when it's not predictive back, but other user interaction). - if (mSearchBar.isSearchBarFocused() - && !getPopupContainer().isEventOverView(mSearchBarContainer, ev) - && mSearchClearFocusHelper.shouldClearFocus(ev, mTouchSlop)) { - mSearchBar.clearSearchBarFocus(); + if (mSearchBar.isSearchBarFocused() + && !getPopupContainer().isEventOverView(mSearchBarContainer, ev)) { + mSearchBar.clearSearchBarFocus(); + } } return super.onControllerInterceptTouchEvent(ev); @@ -764,23 +702,13 @@ public class WidgetsFullSheet extends BaseWidgetSheet return sheet; } - /** - * Updates the widget picker's title and description in the header to the provided values (if - * present). - */ - public void mayUpdateTitleAndDescription(@Nullable String title, - @Nullable String descriptionRes) { - if (title != null) { - mHeaderTitle.setText(title); - } - // Full sheet doesn't support a description. - } - @Override public void saveHierarchyState(SparseArray sparseArray) { Bundle bundle = new Bundle(); - // With widget picker open, when we open shade to switch theme, Launcher re-creates the - // picker and calls save/restore hierarchy state. We save the state of recommendations + // With widget picker open, when we open shade to switch theme, Launcher + // re-creates the + // picker and calls save/restore hierarchy state. We save the state of + // recommendations // across those updates. bundle.putInt(RECOMMENDATIONS_SAVED_STATE_KEY, mRecommendationsCurrentPage); mWidgetRecommendationsView.saveState(bundle); @@ -794,13 +722,19 @@ public class WidgetsFullSheet extends BaseWidgetSheet public void restoreHierarchyState(SparseArray sparseArray) { Bundle state = (Bundle) sparseArray.get(0); mRecommendationsCurrentPage = state.getInt( - RECOMMENDATIONS_SAVED_STATE_KEY, /*defaultValue=*/0); + RECOMMENDATIONS_SAVED_STATE_KEY, /* defaultValue= */0); mWidgetRecommendationsView.restoreState(state); super.restoreHierarchyState(state.getSparseParcelableArray(SUPER_SAVED_STATE_KEY)); } private static int getWidgetSheetId(BaseActivity activity) { - boolean isTwoPane = activity.getDeviceProfile().isTablet; + boolean isTwoPane = (activity.getDeviceProfile().isTablet + // Enables two pane picker for tablets in all orientations when the + // enableCategorizedWidgetSuggestions flag is on. + && (activity.getDeviceProfile().isLandscape || enableCategorizedWidgetSuggestions()) + && !activity.getDeviceProfile().isTwoPanels) + // Enables two pane picker for unfolded foldables if the flag is on. + || (activity.getDeviceProfile().isTwoPanels && enableUnfoldedTwoPanePicker()); return isTwoPane ? R.layout.widgets_two_pane_sheet : R.layout.widgets_full_sheet; } @@ -850,7 +784,10 @@ public class WidgetsFullSheet extends BaseWidgetSheet return isOnScrollBar; } - /** Gets the {@link WidgetsRecyclerView} which shows all widgets in {@link WidgetsFullSheet}. */ + /** + * Gets the {@link WidgetsRecyclerView} which shows all widgets in + * {@link WidgetsFullSheet}. + */ @VisibleForTesting public static WidgetsRecyclerView getWidgetsView(BaseActivity launcher) { return launcher.findViewById(R.id.primary_widgets_list_view); @@ -884,7 +821,7 @@ public class WidgetsFullSheet extends BaseWidgetSheet + marginLayoutParams.topMargin; } - protected int getCurrentAdapterHolderType() { + private int getCurrentAdapterHolderType() { if (mIsInSearchMode) { return SEARCH; } else if (mViewPager != null) { @@ -906,14 +843,13 @@ public class WidgetsFullSheet extends BaseWidgetSheet public void onDeviceProfileChanged(DeviceProfile dp) { super.onDeviceProfileChanged(dp); - if (shouldRecreateLayout(/*oldDp=*/ mDeviceProfile, /*newDp=*/ dp)) { + if (shouldRecreateLayout(/* oldDp= */ mDeviceProfile, /* newDp= */ dp)) { SparseArray widgetsState = new SparseArray<>(); saveHierarchyState(widgetsState); handleClose(false); WidgetsFullSheet sheet = show(BaseActivity.fromContext(getContext()), false); sheet.restoreRecommendations(mRecommendedWidgets, mRecommendedWidgetsMap); sheet.restoreHierarchyState(widgetsState); - sheet.restoreAdapterStates(mAdapters); sheet.restorePreviousAdapterHolderType(getCurrentAdapterHolderType()); } else if (!isTwoPane()) { reset(); @@ -929,37 +865,33 @@ public class WidgetsFullSheet extends BaseWidgetSheet mRecommendedWidgetsMap = recommendedWidgetsMap; } - private void restoreAdapterStates(SparseArray adapters) { - if (Utilities.ATLEAST_R) { - if (adapters.contains(AdapterHolder.WORK)) { - mAdapters.get(AdapterHolder.WORK).mWidgetsListAdapter.restoreState( - adapters.get(AdapterHolder.WORK).mWidgetsListAdapter); - } - } else { - if (adapters.indexOfKey(AdapterHolder.WORK) >= 0) { - mAdapters.get(AdapterHolder.WORK).mWidgetsListAdapter.restoreState( - adapters.get(AdapterHolder.WORK).mWidgetsListAdapter); - } - } - mAdapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter.restoreState( - adapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter); - mAdapters.get(AdapterHolder.SEARCH).mWidgetsListAdapter.restoreState( - adapters.get(AdapterHolder.SEARCH).mWidgetsListAdapter); - } - /** - * Indicates if layout should be re-created on device profile change - so that a different + * Indicates if layout should be re-created on device profile change - so that a + * different * layout can be displayed. */ private static boolean shouldRecreateLayout(DeviceProfile oldDp, DeviceProfile newDp) { - // When folding/unfolding the foldables, we need to switch between the regular widget picker + // When folding/unfolding the foldables, we need to switch between the regular + // widget picker // and the two pane picker, so we rebuild the picker with the correct layout. - return oldDp.isTwoPanels != newDp.isTwoPanels; + boolean isFoldUnFold = oldDp.isTwoPanels != newDp.isTwoPanels && enableUnfoldedTwoPanePicker(); + // In tablets, on orientation change we switch between single and two pane + // picker unless the + // categorized suggestions flag was on. With the categorized suggestions + // feature, we use a + // two pane picker across all orientations. + boolean useDifferentLayoutOnOrientationChange = (!enableCategorizedWidgetSuggestions() + && (newDp.isTablet && !newDp.isTwoPanels + && oldDp.isLandscape != newDp.isLandscape)); + + return isFoldUnFold || useDifferentLayoutOnOrientationChange; } /** - * In widget search mode, we should scale down content inside widget bottom sheet, rather - * than the whole bottom sheet, to indicate we will navigate back within the widget + * In widget search mode, we should scale down content inside widget bottom + * sheet, rather + * than the whole bottom sheet, to indicate we will navigate back within the + * widget * bottom sheet. */ @Override @@ -971,7 +903,8 @@ public class WidgetsFullSheet extends BaseWidgetSheet public void onBackInvoked() { if (mIsInSearchMode) { mSearchBar.reset(); - // Posting animation to next frame will let widget sheet finish updating UI first, and + // Posting animation to next frame will let widget sheet finish updating UI + // first, and // make animation smoother. post(this::animateSwipeToDismissProgressToStart); } else { @@ -1001,14 +934,12 @@ public class WidgetsFullSheet extends BaseWidgetSheet : mViewPager == null ? AdapterHolder.PRIMARY : mViewPager.getCurrentPage()); - WidgetsRowViewHolder viewHolderForTip = - (WidgetsRowViewHolder) IntStream.range( - 0, adapterHolder.mWidgetsListAdapter.getItemCount()) - .mapToObj(adapterHolder.mWidgetsRecyclerView:: - findViewHolderForAdapterPosition) - .filter(viewHolder -> viewHolder instanceof WidgetsRowViewHolder) - .findFirst() - .orElse(null); + WidgetsRowViewHolder viewHolderForTip = (WidgetsRowViewHolder) IntStream.range( + 0, adapterHolder.mWidgetsListAdapter.getItemCount()) + .mapToObj(adapterHolder.mWidgetsRecyclerView::findViewHolderForAdapterPosition) + .filter(viewHolder -> viewHolder instanceof WidgetsRowViewHolder) + .findFirst() + .orElse(null); if (viewHolderForTip != null) { return ((ViewGroup) viewHolderForTip.tableContainer.getChildAt(0)).getChildAt(0); } @@ -1026,7 +957,10 @@ public class WidgetsFullSheet extends BaseWidgetSheet return mContent; } - /** Opens the first header in widget picker and scrolls to the top of the RecyclerView. */ + /** + * Opens the first header in widget picker and scrolls to the top of the + * RecyclerView. + */ @VisibleForTesting public void openFirstHeader() { mAdapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter.selectFirstHeaderEntry(); @@ -1065,9 +999,11 @@ public class WidgetsFullSheet extends BaseWidgetSheet recyclerView.smoothScrollBy(0, scrollByY); return; } else if (parent instanceof StickyHeaderLayout header) { - // Scrollable container for recommendations. We still scroll on the recycler (even + // Scrollable container for recommendations. We still scroll on the recycler + // (even // though the recommendations are not in the recycler view) because the - // StickyHeaderLayout scroll is connected to the currently visible recycler view. + // StickyHeaderLayout scroll is connected to the currently visible recycler + // view. WidgetsRecyclerView recyclerView = findVisibleRecyclerView(); if (recyclerView != null) { recyclerView.smoothScrollBy(0, scrollByY); @@ -1109,7 +1045,6 @@ public class WidgetsFullSheet extends BaseWidgetSheet this::getEmptySpaceHeight, /* iconClickListener= */ WidgetsFullSheet.this, /* iconLongClickListener= */ WidgetsFullSheet.this, - /* expandButtonClickListener= */ WidgetsFullSheet.this, isTwoPane()); mWidgetsListAdapter.setHasStableIds(true); switch (mAdapterType) { @@ -1125,23 +1060,8 @@ public class WidgetsFullSheet extends BaseWidgetSheet mWidgetsListItemAnimator = new WidgetsListItemAnimator(); } - /** - * Swaps the adapter to existing adapter to prevent the recycler view from using stale view - * to animate in the new visibility update. - * - *

For instance, when clearing search text and re-entering search with new list shouldn't - * use stale results to animate in new results. Alternative is setting list animators to - * null, but, we need animations with the default item animator. - */ - private void reset() { - mWidgetsRecyclerView.swapAdapter( - mWidgetsListAdapter, - /*removeAndRecycleExistingViews=*/ true - ); - } - private int getEmptySpaceHeight() { - return mStickyHeaderLayout != null ? mStickyHeaderLayout.getHeaderHeight() : 0; + return mSearchScrollView.getHeaderHeight(); } void setup(WidgetsRecyclerView recyclerView) { @@ -1150,7 +1070,7 @@ public class WidgetsFullSheet extends BaseWidgetSheet mWidgetsRecyclerView.setClipToOutline(true); mWidgetsRecyclerView.setClipChildren(false); mWidgetsRecyclerView.setAdapter(mWidgetsListAdapter); - mWidgetsRecyclerView.bindFastScrollbar(mFastScroller, WIDGET_SCROLLER); + mWidgetsRecyclerView.bindFastScrollbar(mFastScroller); mWidgetsRecyclerView.setItemAnimator(isTwoPane() ? null : mWidgetsListItemAnimator); mWidgetsRecyclerView.setHeaderViewDimensionsProvider(WidgetsFullSheet.this); if (!isTwoPane()) { @@ -1158,7 +1078,8 @@ public class WidgetsFullSheet extends BaseWidgetSheet ((SpringRelativeLayout) mContent).createEdgeEffectFactory()); } // Recycler view binds to fast scroller when it is attached to screen. Make sure - // search recycler view is bound to fast scroller if user is in search mode at the time + // search recycler view is bound to fast scroller if user is in search mode at + // the time // of attachment. if (mAdapterType == PRIMARY || mAdapterType == WORK) { mWidgetsRecyclerView.addOnAttachStateChangeListener(mBindScrollbarInSearchMode); @@ -1166,53 +1087,4 @@ public class WidgetsFullSheet extends BaseWidgetSheet mWidgetsListAdapter.setMaxHorizontalSpansPxPerRow(mMaxSpanPerRow); } } - - /** - * Helper to identify if searchbar's focus can be cleared when user performs an action - * outside search. - */ - private static class SearchClearFocusHelper { - private float mFirstInteractionX = -1f; - private float mFirstInteractionY = -1f; - - /** - * For a given [MotionEvent] indicates if we should clear focus from search (and hide IME). - */ - boolean shouldClearFocus(MotionEvent ev, float touchSlop) { - int action = ev.getAction(); - boolean clearFocus = false; - - if (action == MotionEvent.ACTION_DOWN) { - mFirstInteractionX = ev.getX(); - mFirstInteractionY = ev.getY(); - } else if (action == MotionEvent.ACTION_CANCEL) { - // This is when user performed a gesture e.g. predictive back - // We don't handle it ourselves and let IME handle the close. - mFirstInteractionY = -1; - mFirstInteractionX = -1; - } else if (action == MotionEvent.ACTION_UP) { - // Its clear that user action wasn't predictive back - but press / scroll etc. that - // should hide the keyboard. - clearFocus = true; - mFirstInteractionY = -1; - mFirstInteractionX = -1; - } else if (action == MotionEvent.ACTION_MOVE) { - // Sometimes, on move, we may not receive ACTION_UP, but if the move was within - // touch slop and we didn't know if its moved or cancelled, we can clear focus. - // Example case: Apps list is small and you do a little scroll on list - in such, we - // want to still hide the keyboard. - if (mFirstInteractionX != -1 && mFirstInteractionY != -1) { - float distY = abs(mFirstInteractionY - ev.getY()); - float distX = abs(mFirstInteractionX - ev.getX()); - if (distY >= touchSlop || distX >= touchSlop) { - clearFocus = true; - mFirstInteractionY = -1; - mFirstInteractionX = -1; - } - } - } - - return clearFocus; - } - } } diff --git a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java index 74a9a5c3fe..8dd1de4ac8 100644 --- a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java +++ b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java @@ -49,7 +49,6 @@ import com.android.launcher3.views.ActivityContext; import com.android.launcher3.widget.model.WidgetListSpaceEntry; import com.android.launcher3.widget.model.WidgetsListBaseEntry; import com.android.launcher3.widget.model.WidgetsListContentEntry; -import com.android.launcher3.widget.model.WidgetsListExpandActionEntry; import com.android.launcher3.widget.model.WidgetsListHeaderEntry; import com.android.launcher3.widget.util.WidgetSizes; @@ -83,7 +82,6 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC public static final int VIEW_TYPE_WIDGETS_SPACE = R.id.view_type_widgets_space; public static final int VIEW_TYPE_WIDGETS_LIST = R.id.view_type_widgets_list; public static final int VIEW_TYPE_WIDGETS_HEADER = R.id.view_type_widgets_header; - public static final int VIEW_TYPE_WIDGETS_EXPAND = R.id.view_type_widgets_list_expand; private final Context mContext; private final SparseArray mViewHolderBinders = new SparseArray<>(); @@ -92,9 +90,7 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC @Nullable private WidgetsTwoPaneSheet.HeaderChangeListener mHeaderChangeListener; private final List mAllEntries = new ArrayList<>(); - private final List mAllDefaultEntries = new ArrayList<>(); private ArrayList mVisibleEntries = new ArrayList<>(); - @Nullable private PackageUserKey mWidgetsContentVisiblePackageUserKey = null; private Predicate mHeaderAndSelectedContentFilter = entry -> @@ -103,15 +99,12 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC .equals(mWidgetsContentVisiblePackageUserKey); @Nullable private Predicate mFilter = null; @Nullable private RecyclerView mRecyclerView; - @Nullable private PackageUserKey mHeaderPositionToMaintain; + @Nullable private PackageUserKey mPendingClickHeader; @Px private int mMaxHorizontalSpan; - private boolean mShowOnlyDefaultList = true; - public WidgetsListAdapter(Context context, LayoutInflater layoutInflater, IntSupplier emptySpaceHeightProvider, OnClickListener iconClickListener, OnLongClickListener iconLongClickListener, - ExpandButtonClickListener expandButtonClickListener, boolean isTwoPane) { mContext = context; mMaxHorizontalSpan = WidgetSizes.getWidgetSizePx( @@ -130,16 +123,6 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC mViewHolderBinders.put( VIEW_TYPE_WIDGETS_SPACE, new WidgetsSpaceViewHolderBinder(emptySpaceHeightProvider)); - mViewHolderBinders.put(VIEW_TYPE_WIDGETS_EXPAND, - new WidgetsListExpandActionViewHolderBinder(layoutInflater, - expandButtonClickListener::onWidgetsListExpandButtonClick)); - } - - /** - * Copies state info from another adapter. - */ - public void restoreState(WidgetsListAdapter adapter) { - mShowOnlyDefaultList = adapter.mShowOnlyDefaultList; } public void setHeaderChangeListener(WidgetsTwoPaneSheet.HeaderChangeListener @@ -185,21 +168,10 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC } /** Updates the widget list based on {@code tempEntries}. */ - public void setWidgets(List tempEntries, - List tempDefaultEntries) { + public void setWidgets(List tempEntries) { mAllEntries.clear(); mAllEntries.add(new WidgetListSpaceEntry()); tempEntries.stream().sorted(mRowComparator).forEach(mAllEntries::add); - - mAllDefaultEntries.clear(); - - if (mShowOnlyDefaultList && !tempDefaultEntries.isEmpty()) { - mAllDefaultEntries.add(new WidgetListSpaceEntry()); - tempDefaultEntries.stream().sorted(mRowComparator).forEach(mAllDefaultEntries::add); - // Include view all action when default entries exist. - mAllDefaultEntries.add(new WidgetsListExpandActionEntry()); - } - updateVisibleEntries(); } @@ -207,23 +179,21 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC public void setWidgetsOnSearch(List searchResults) { // Forget the expanded package every time widget list is refreshed in search mode. mWidgetsContentVisiblePackageUserKey = null; - mShowOnlyDefaultList = false; - setWidgets(searchResults, /*tempDefaultEntries=*/ List.of()); + setWidgets(searchResults); } private void updateVisibleEntries() { // Get the current top of the header with the matching key before adjusting the visible // entries. OptionalInt previousPositionForPackageUserKey = - getPositionForPackageUserKey(mHeaderPositionToMaintain); + getPositionForPackageUserKey(mPendingClickHeader); OptionalInt topForPackageUserKey = getOffsetForPosition(previousPositionForPackageUserKey); - List newVisibleEntries = getAllEntries().stream() + List newVisibleEntries = mAllEntries.stream() .filter(entry -> (((mFilter == null || mFilter.test(entry)) && mHeaderAndSelectedContentFilter.test(entry)) - || entry instanceof WidgetListSpaceEntry - || entry instanceof WidgetsListExpandActionEntry) + || entry instanceof WidgetListSpaceEntry) && (mHeaderChangeListener == null || !(entry instanceof WidgetsListContentEntry))) .map(entry -> { @@ -247,23 +217,16 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC mVisibleEntries.addAll(newVisibleEntries); diffResult.dispatchUpdatesTo(this); - if (mHeaderPositionToMaintain != null && mRecyclerView != null) { + if (mPendingClickHeader != null) { // Get the position for the clicked header after adjusting the visible entries. The // position may have changed if another header had previously been expanded. OptionalInt positionForPackageUserKey = - getPositionForPackageUserKey(mHeaderPositionToMaintain); - // Post scroll updates to be applied after diff updates. - mRecyclerView.post(() -> scrollToPositionAndMaintainOffset(positionForPackageUserKey, - topForPackageUserKey)); - mHeaderPositionToMaintain = null; + getPositionForPackageUserKey(mPendingClickHeader); + scrollToPositionAndMaintainOffset(positionForPackageUserKey, topForPackageUserKey); + mPendingClickHeader = null; } } - private List getAllEntries() { - return (mShowOnlyDefaultList && !mAllDefaultEntries.isEmpty()) ? mAllDefaultEntries - : mAllEntries; - } - /** Returns whether {@code entry} matches {@code key}. */ private static boolean isHeaderForPackageUserKey( @NonNull WidgetsListBaseEntry entry, @Nullable PackageUserKey key) { @@ -299,13 +262,7 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC // The first entry has an empty space, count from second entries. int listPos = (pos > 1) ? POSITION_DEFAULT : POSITION_FIRST; - int lastIndex = getItemCount() - 1; - // Last index may be the view all entry - int actualLastItemIndex = (mVisibleEntries.get( - lastIndex) instanceof WidgetsListExpandActionEntry) ? getItemCount() - 2 - : getItemCount() - 1; - - if (pos == (actualLastItemIndex)) { + if (pos == (getItemCount() - 1)) { listPos |= POSITION_LAST; } viewHolderBinder.bindViewHolder(holder, mVisibleEntries.get(pos), listPos, payloads); @@ -362,8 +319,6 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC return VIEW_TYPE_WIDGETS_HEADER; } else if (entry instanceof WidgetListSpaceEntry) { return VIEW_TYPE_WIDGETS_SPACE; - } else if (entry instanceof WidgetsListExpandActionEntry) { - return VIEW_TYPE_WIDGETS_EXPAND; } throw new UnsupportedOperationException("ViewHolderBinder not found for " + entry); } @@ -386,7 +341,7 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC // Store the header that was clicked so that its position will be maintained the next time // we update the entries. - mHeaderPositionToMaintain = packageUserKey; + mPendingClickHeader = packageUserKey; updateVisibleEntries(); @@ -441,6 +396,15 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC LinearLayoutManager layoutManager = (LinearLayoutManager) mRecyclerView.getLayoutManager(); if (layoutManager == null) return; + if (position == mVisibleEntries.size() - 2 + && mVisibleEntries.get(mVisibleEntries.size() - 1) + instanceof WidgetsListContentEntry) { + // If the selected header is in the last position and its content is showing, then + // scroll to the final position so the last list of widgets will show. + layoutManager.scrollToPosition(mVisibleEntries.size() - 1); + return; + } + // Scroll to the header view's current offset, accounting for the recycler view's padding. // If the header view couldn't be found, then it will appear at the top of the list. layoutManager.scrollToPositionWithOffset( @@ -457,33 +421,6 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC updateVisibleEntries(); } - /** - * Returns the widget content {@link WidgetsListContentEntry} for a selected header. - */ - public WidgetsListContentEntry getContentEntry(PackageUserKey selectedHeader) { - return getAllEntries().stream().filter(entry -> entry instanceof WidgetsListContentEntry) - .map(entry -> (WidgetsListContentEntry) entry) - .filter(entry -> PackageUserKey.fromPackageItemInfo(entry.mPkgItem).equals( - selectedHeader)).findFirst().orElse(null); - } - - /** - * Sets adapter to use expanded list when updating widgets. - */ - public void useExpandedList() { - mShowOnlyDefaultList = false; - if (mWidgetsContentVisiblePackageUserKey != null) { - // Maintain selected header for the next update that expands the list. - mHeaderPositionToMaintain = mWidgetsContentVisiblePackageUserKey; - } else if (mVisibleEntries.size() > 2) { - // Maintain last visible header shown above expand button since there was no selected - // header. - mHeaderPositionToMaintain = PackageUserKey.fromPackageItemInfo( - mVisibleEntries.get(mVisibleEntries.size() - 2).mPkgItem); - } - - } - /** Comparator for sorting WidgetListRowEntry based on package title. */ public static class WidgetListBaseRowEntryComparator implements Comparator { @@ -502,10 +439,4 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC return 1; } } - - /** Callback interface for the interaction with the expand button */ - public interface ExpandButtonClickListener { - /** Called when user clicks the button at end of widget apps list to expand it. */ - void onWidgetsListExpandButtonClick(View view); - } } diff --git a/src/com/android/launcher3/widget/picker/WidgetsListHeader.java b/src/com/android/launcher3/widget/picker/WidgetsListHeader.java index f60883db72..7c2044c5ef 100644 --- a/src/com/android/launcher3/widget/picker/WidgetsListHeader.java +++ b/src/com/android/launcher3/widget/picker/WidgetsListHeader.java @@ -143,14 +143,6 @@ public final class WidgetsListHeader extends LinearLayout implements ItemInfoUpd public void setExpanded(boolean isExpanded) { this.mIsExpanded = isExpanded; refreshDrawableState(); - refreshTextAppearance(isExpanded); - } - - private void refreshTextAppearance(boolean isExpanded) { - mTitle.setTextAppearance(isExpanded ? R.style.WidgetListHeader_Title_Selected - : R.style.WidgetListHeader_Title); - mSubtitle.setTextAppearance(isExpanded ? R.style.WidgetListHeader_SubTitle_Selected - : R.style.WidgetListHeader_SubTitle); } /** @return true if this header is expanded. */ @@ -254,9 +246,12 @@ public final class WidgetsListHeader extends LinearLayout implements ItemInfoUpd mIconLoadRequest.cancel(); mIconLoadRequest = null; } - if (getTag() instanceof ItemInfoWithIcon info && info.getMatchingLookupFlag().useLowRes()) { - mIconLoadRequest = LauncherAppState.getInstance(getContext()).getIconCache() - .updateIconInBackground(this, info); + if (getTag() instanceof ItemInfoWithIcon) { + ItemInfoWithIcon info = (ItemInfoWithIcon) getTag(); + if (info.usingLowResIcon()) { + mIconLoadRequest = LauncherAppState.getInstance(getContext()).getIconCache() + .updateIconInBackground(this, info); + } } } } diff --git a/src/com/android/launcher3/widget/picker/WidgetsListItemAnimator.java b/src/com/android/launcher3/widget/picker/WidgetsListItemAnimator.java index 6a1921eb23..854700fed3 100644 --- a/src/com/android/launcher3/widget/picker/WidgetsListItemAnimator.java +++ b/src/com/android/launcher3/widget/picker/WidgetsListItemAnimator.java @@ -16,8 +16,6 @@ package com.android.launcher3.widget.picker; -import static android.animation.ValueAnimator.areAnimatorsEnabled; - import static com.android.launcher3.widget.picker.WidgetsListAdapter.VIEW_TYPE_WIDGETS_LIST; import androidx.recyclerview.widget.DefaultItemAnimator; @@ -28,14 +26,6 @@ public class WidgetsListItemAnimator extends DefaultItemAnimator { public static final int MOVE_DURATION_MS = 90; public static final int ADD_DURATION_MS = 120; - // DefaultItemAnimator runs change and move animations before running add animations (i.e. - // before expanded list item's content start animating to become visible on screen). - public static final int WIDGET_LIST_ITEM_APPEARANCE_START_DELAY = - areAnimatorsEnabled() ? (CHANGE_DURATION_MS + MOVE_DURATION_MS) : 0; - // Delay after which all item animations are ran and list item's content is visible. - public static final int WIDGET_LIST_ITEM_APPEARANCE_DELAY = - WIDGET_LIST_ITEM_APPEARANCE_START_DELAY + ADD_DURATION_MS; - public WidgetsListItemAnimator() { super(); diff --git a/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java b/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java index fc99fccf11..45d733a3a5 100644 --- a/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java +++ b/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java @@ -15,7 +15,10 @@ */ package com.android.launcher3.widget.picker; -import static com.android.launcher3.widget.picker.WidgetsListItemAnimator.WIDGET_LIST_ITEM_APPEARANCE_START_DELAY; +import static com.android.launcher3.widget.picker.WidgetsListItemAnimator.CHANGE_DURATION_MS; +import static com.android.launcher3.widget.picker.WidgetsListItemAnimator.MOVE_DURATION_MS; + +import static android.animation.ValueAnimator.areAnimatorsEnabled; import android.content.Context; import android.graphics.Bitmap; @@ -112,48 +115,21 @@ public final class WidgetsListTableViewHolderBinder // Bind the widget items. for (int i = 0; i < widgetItemsTable.size(); i++) { List widgetItemsPerRow = widgetItemsTable.get(i); - WidgetTableRow row = (WidgetTableRow) table.getChildAt(i); - - if (areRowItemsUnchanged(row, widgetItemsPerRow)) { // Just show widgets in row as is + for (int j = 0; j < widgetItemsPerRow.size(); j++) { + WidgetTableRow row = (WidgetTableRow) table.getChildAt(i); row.setVisibility(View.VISIBLE); - for (int j = 0; j < widgetItemsPerRow.size(); j++) { - WidgetCell widget = (WidgetCell) row.getChildAt(j); - widget.setVisibility(View.VISIBLE); - } - } else { - for (int j = 0; j < widgetItemsPerRow.size(); j++) { - row.setVisibility(View.VISIBLE); - WidgetCell widget = (WidgetCell) row.getChildAt(j); - widget.clear(); - WidgetItem widgetItem = widgetItemsPerRow.get(j); - widget.addPreviewReadyListener(row); - widget.setVisibility(View.VISIBLE); + WidgetCell widget = (WidgetCell) row.getChildAt(j); + widget.clear(); + widget.addPreviewReadyListener(row); + WidgetItem widgetItem = widgetItemsPerRow.get(j); + widget.setVisibility(View.VISIBLE); - widget.applyFromCellItem(widgetItem); - widget.requestLayout(); - } + widget.applyFromCellItem(widgetItem); + widget.requestLayout(); } } } - private boolean areRowItemsUnchanged(WidgetTableRow row, List widgetItemsPerRow) { - // NOTE: on rotation or fold / unfold, we bind different view holders - // so, we don't any special handling for that case. - if (row.getChildCount() != widgetItemsPerRow.size()) { // Items not equal - return false; - } - - for (int j = 0; j < widgetItemsPerRow.size(); j++) { - WidgetCell widgetCell = (WidgetCell) row.getChildAt(j); - WidgetItem widgetItem = widgetItemsPerRow.get(j); - if (widgetCell.getWidgetItem() == null - || !widgetCell.getWidgetItem().equals(widgetItem)) { - return false; // Items at given position in row aren't same. - } - } - return true; - } - /** * Adds and hides table rows and columns from {@code table} to ensure there is sufficient room * to display {@code widgetItemsTable}. @@ -178,31 +154,27 @@ public final class WidgetsListTableViewHolderBinder tableRow.setGravity(Gravity.TOP); table.addView(tableRow); } - - // If the row items are unchanged, we don't need to re-setup the row or the items; - // we can just show the row as is. - if (!areRowItemsUnchanged(tableRow, widgetItems)) { - // Pass resize delay to let the "move" and "change" animations run before resizing - // the row. - tableRow.setupRow(widgetItems.size(), - /*resizeDelayMs=*/ WIDGET_LIST_ITEM_APPEARANCE_START_DELAY); - if (tableRow.getChildCount() > widgetItems.size()) { - for (int j = widgetItems.size(); j < tableRow.getChildCount(); j++) { - tableRow.getChildAt(j).setVisibility(View.GONE); - } - } else { - for (int j = tableRow.getChildCount(); j < widgetItems.size(); j++) { - WidgetCell widget = (WidgetCell) mLayoutInflater.inflate( - R.layout.widget_cell, tableRow, false); - // set up touch. - widget.setOnClickListener(mIconClickListener); - widget.addPreviewReadyListener(tableRow); - View preview = widget.findViewById(R.id.widget_preview_container); - preview.setOnClickListener(mIconClickListener); - preview.setOnLongClickListener(mIconLongClickListener); - widget.setAnimatePreview(false); - tableRow.addView(widget); - } + // Pass resize delay to let the "move" and "change" animations run before resizing the + // row. + tableRow.setupRow(widgetItems.size(), + /*resizeDelayMs=*/ + areAnimatorsEnabled() ? (CHANGE_DURATION_MS + MOVE_DURATION_MS) : 0); + if (tableRow.getChildCount() > widgetItems.size()) { + for (int j = widgetItems.size(); j < tableRow.getChildCount(); j++) { + tableRow.getChildAt(j).setVisibility(View.GONE); + } + } else { + for (int j = tableRow.getChildCount(); j < widgetItems.size(); j++) { + WidgetCell widget = (WidgetCell) mLayoutInflater.inflate( + R.layout.widget_cell, tableRow, false); + // set up touch. + widget.setOnClickListener(mIconClickListener); + widget.addPreviewReadyListener(tableRow); + View preview = widget.findViewById(R.id.widget_preview_container); + preview.setOnClickListener(mIconClickListener); + preview.setOnLongClickListener(mIconLongClickListener); + widget.setAnimatePreview(false); + tableRow.addView(widget); } } } diff --git a/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java b/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java index a7b428f696..0cbad82178 100644 --- a/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java +++ b/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java @@ -15,9 +15,10 @@ */ package com.android.launcher3.widget.picker; +import static com.android.launcher3.Flags.enableCategorizedWidgetSuggestions; import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION; import static com.android.launcher3.widget.util.WidgetSizes.getWidgetSizePx; -import static com.android.launcher3.widget.util.WidgetsTableUtils.WIDGETS_TABLE_ROW_COUNT_COMPARATOR; +import static com.android.launcher3.widget.util.WidgetsTableUtils.WIDGETS_TABLE_ROW_SIZE_COMPARATOR; import static java.lang.Math.max; import static java.util.stream.Collectors.toList; @@ -42,7 +43,6 @@ import com.android.launcher3.widget.picker.util.WidgetPreviewContainerSize; import java.util.ArrayList; import java.util.List; -import java.util.stream.Collectors; import app.lawnchair.theme.drawable.DrawableTokens; @@ -52,8 +52,10 @@ public final class WidgetsRecommendationTableLayout extends TableLayout { private final float mWidgetCellVerticalPadding; private final float mWidgetCellTextViewsHeight; - @Nullable private OnLongClickListener mWidgetCellOnLongClickListener; - @Nullable private OnClickListener mWidgetCellOnClickListener; + @Nullable + private OnLongClickListener mWidgetCellOnLongClickListener; + @Nullable + private OnClickListener mWidgetCellOnClickListener; public WidgetsRecommendationTableLayout(Context context) { this(context, /* attrs= */ null); @@ -61,21 +63,27 @@ public final class WidgetsRecommendationTableLayout extends TableLayout { public WidgetsRecommendationTableLayout(Context context, AttributeSet attrs) { super(context, attrs); - // There are 1 row for title, 1 row for dimension and max 3 rows for description. + // There are 1 row for title, 1 row for dimension and max 3 rows for + // description. mWidgetsRecommendationTableVerticalPadding = 2 * getResources() .getDimensionPixelSize(R.dimen.widget_recommendations_table_vertical_padding); mWidgetCellVerticalPadding = 2 * getResources() .getDimensionPixelSize(R.dimen.widget_cell_vertical_padding); - mWidgetCellTextViewsHeight = - getResources().getDimension(R.dimen.widget_cell_title_line_height); + mWidgetCellTextViewsHeight = getResources().getDimension(R.dimen.widget_cell_title_line_height); } - /** Sets a {@link android.view.View.OnLongClickListener} for all widget cells in this table. */ + /** + * Sets a {@link android.view.View.OnLongClickListener} for all widget cells in + * this table. + */ public void setWidgetCellLongClickListener(OnLongClickListener onLongClickListener) { mWidgetCellOnLongClickListener = onLongClickListener; } - /** Sets a {@link android.view.View.OnClickListener} for all widget cells in this table. */ + /** + * Sets a {@link android.view.View.OnClickListener} for all widget cells in this + * table. + */ public void setWidgetCellOnClickListener(OnClickListener widgetCellOnClickListener) { mWidgetCellOnClickListener = widgetCellOnClickListener; } @@ -86,13 +94,19 @@ public final class WidgetsRecommendationTableLayout extends TableLayout { } /** - * Sets a list of recommended widgets that would like to be displayed in this table within the + * Sets a list of recommended widgets that would like to be displayed in this + * table within the * desired {@code recommendationTableMaxHeight}. * - *

If the content can't fit {@code recommendationTableMaxHeight}, this view will remove a - * last row from the {@code recommendedWidgets} until it fits or only one row left. + *

+ * If the content can't fit {@code recommendationTableMaxHeight}, this view will + * remove a + * last row from the {@code recommendedWidgets} until it fits or only one row + * left. * - *

Returns the list of widgets that could fit

+ *

+ * Returns the list of widgets that could fit + *

*/ public List> setRecommendedWidgets( List> recommendedWidgets, @@ -114,14 +128,16 @@ public final class WidgetsRecommendationTableLayout extends TableLayout { for (int i = 0; i < recommendationTable.size(); i++) { List widgetItems = recommendationTable.get(i); WidgetTableRow tableRow = new WidgetTableRow(getContext()); - tableRow.setupRow(widgetItems.size(), /*resizeDelayMs=*/ 0); + tableRow.setupRow(widgetItems.size(), /* resizeDelayMs= */ 0); tableRow.setGravity(Gravity.TOP); for (WidgetItem widgetItem : widgetItems) { WidgetCell widgetCell = addItemCell(tableRow); widgetCell.applyFromCellItem(widgetItem); widgetCell.showAppIconInWidgetTitle(true); - widgetCell.showDescription(false); - widgetCell.showDimensions(false); + if (enableCategorizedWidgetSuggestions()) { + widgetCell.showDescription(false); + widgetCell.showDimensions(false); + } } addView(tableRow); } @@ -148,7 +164,8 @@ public final class WidgetsRecommendationTableLayout extends TableLayout { List> recommendedWidgets, @Px float recommendationTableMaxHeight, DeviceProfile deviceProfile) { List> filteredRows = new ArrayList<>(); - // A naive estimation of the widgets recommendation table height without inflation. + // A naive estimation of the widgets recommendation table height without + // inflation. float totalHeight = mWidgetsRecommendationTableVerticalPadding; for (int i = 0; i < recommendedWidgets.size(); i++) { @@ -156,8 +173,8 @@ public final class WidgetsRecommendationTableLayout extends TableLayout { float rowHeight = 0; for (int j = 0; j < widgetItems.size(); j++) { WidgetItem widgetItem = widgetItems.get(j); - WidgetPreviewContainerSize previewContainerSize = - WidgetPreviewContainerSize.Companion.forItem(widgetItem, deviceProfile); + WidgetPreviewContainerSize previewContainerSize = WidgetPreviewContainerSize.Companion + .forItem(widgetItem, deviceProfile); float widgetItemHeight = getWidgetSizePx(deviceProfile, previewContainerSize.spanX, previewContainerSize.spanY).getHeight(); rowHeight = max(rowHeight, @@ -170,7 +187,6 @@ public final class WidgetsRecommendationTableLayout extends TableLayout { } // Perform re-ordering once we have filtered out recommendations that fit. - return filteredRows.stream().sorted(WIDGETS_TABLE_ROW_COUNT_COMPARATOR) - .collect(Collectors.toList()); + return filteredRows.stream().sorted(WIDGETS_TABLE_ROW_SIZE_COMPARATOR).collect(toList()); } } diff --git a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java index 678ddbab33..6e0d18d611 100644 --- a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java +++ b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java @@ -15,33 +15,25 @@ */ package com.android.launcher3.widget.picker; -import static com.android.launcher3.Flags.enableTieredWidgetsByDefaultInPicker; +import static com.android.launcher3.Flags.enableCategorizedWidgetSuggestions; +import static com.android.launcher3.Flags.enableUnfoldedTwoPanePicker; import static com.android.launcher3.UtilitiesKt.CLIP_CHILDREN_FALSE_MODIFIER; import static com.android.launcher3.UtilitiesKt.CLIP_TO_PADDING_FALSE_MODIFIER; import static com.android.launcher3.UtilitiesKt.modifyAttributesOnViewTree; import static com.android.launcher3.UtilitiesKt.restoreAttributesOnViewTree; -import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG; -import static com.android.launcher3.widget.picker.WidgetsListItemAnimator.WIDGET_LIST_ITEM_APPEARANCE_DELAY; -import static com.android.launcher3.widget.picker.model.data.WidgetPickerDataUtils.findContentEntryForPackageUser; import android.content.Context; import android.graphics.Rect; import android.os.Process; -import android.os.UserHandle; import android.util.AttributeSet; -import android.view.Gravity; import android.view.LayoutInflater; -import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; -import android.view.accessibility.AccessibilityNodeInfo; import android.widget.FrameLayout; import android.widget.LinearLayout; -import android.widget.PopupMenu; import android.widget.ScrollView; -import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -50,7 +42,6 @@ import androidx.annotation.Px; import com.android.launcher3.DeviceProfile; import com.android.launcher3.R; import com.android.launcher3.Utilities; -import com.android.launcher3.icons.cache.CacheLookupFlag; import com.android.launcher3.model.WidgetItem; import com.android.launcher3.model.data.PackageItemInfo; import com.android.launcher3.recyclerview.ViewHolderBinder; @@ -67,6 +58,9 @@ import java.util.List; * Popup for showing the full list of available widgets with a two-pane layout. */ public class WidgetsTwoPaneSheet extends WidgetsFullSheet { + + private static final int PERSONAL_TAB = 0; + private static final int WORK_TAB = 1; private static final int MINIMUM_WIDTH_LEFT_PANE_FOLDABLE_DP = 268; private static final int MAXIMUM_WIDTH_LEFT_PANE_FOLDABLE_DP = 395; private static final String SUGGESTIONS_PACKAGE_NAME = "widgets_list_suggestions_entry"; @@ -87,18 +81,6 @@ public class WidgetsTwoPaneSheet extends WidgetsFullSheet { private int mActivePage = -1; @Nullable private PackageUserKey mSelectedHeader; - private TextView mHeaderDescription; - - /** - * A menu displayed for options (e.g. "show all widgets" filter) around widget lists in the - * picker. - */ - protected View mWidgetOptionsMenu; - /** - * State of the options in the menu (if displayed to the user). - */ - @Nullable - protected WidgetOptionsMenuState mWidgetOptionsMenuState = null; public WidgetsTwoPaneSheet(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); @@ -136,22 +118,12 @@ public class WidgetsTwoPaneSheet extends WidgetsFullSheet { mWidgetRecommendationsView.initParentViews(mWidgetRecommendationsContainer); mWidgetRecommendationsView.setWidgetCellLongClickListener(this); mWidgetRecommendationsView.setWidgetCellOnClickListener(this); - if (!mDeviceProfile.isTwoPanels) { - mWidgetRecommendationsView.enableFullPageViewIfLowDensity(); - } // To save the currently displayed page, so that, it can be requested when rebinding // recommendations with different size constraints. mWidgetRecommendationsView.addPageSwitchListener( newPage -> mRecommendationsCurrentPage = newPage); mHeaderTitle = mContent.findViewById(R.id.title); - mHeaderDescription = mContent.findViewById(R.id.widget_picker_description); - - mWidgetOptionsMenu = mContent.findViewById(R.id.widget_picker_widget_options_menu); - if (!enableTieredWidgetsByDefaultInPicker()) { - setupWidgetOptionsMenu(); - } - mRightPane = mContent.findViewById(R.id.right_pane); mRightPaneScrollView = mContent.findViewById(R.id.right_pane_scroll_view); mRightPaneScrollView.setOverScrollMode(View.OVER_SCROLL_NEVER); @@ -166,56 +138,24 @@ public class WidgetsTwoPaneSheet extends WidgetsFullSheet { mFastScroller.setVisibility(GONE); } - @Override - public void mayUpdateTitleAndDescription(@Nullable String title, @Nullable String description) { - if (title != null) { - mHeaderTitle.setText(title); - } - if (description != null) { - mHeaderDescription.setText(description); - mHeaderDescription.setVisibility(VISIBLE); - } - } - - protected void setupWidgetOptionsMenu() { - mWidgetOptionsMenu.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - if (mWidgetOptionsMenuState != null) { - PopupMenu popupMenu = new PopupMenu(mActivityContext, /*anchor=*/ v, - Gravity.END); - MenuItem menuItem = popupMenu.getMenu().add( - R.string.widget_picker_show_all_widgets_menu_item_title); - menuItem.setCheckable(true); - menuItem.setChecked(mWidgetOptionsMenuState.showAllWidgets); - menuItem.setOnMenuItemClickListener( - item -> onShowAllWidgetsMenuItemClick(item)); - popupMenu.show(); - } - } - }); - } - - private boolean onShowAllWidgetsMenuItemClick(MenuItem menuItem) { - mWidgetOptionsMenuState.showAllWidgets = !mWidgetOptionsMenuState.showAllWidgets; - menuItem.setChecked(mWidgetOptionsMenuState.showAllWidgets); - - // Refresh widgets - onWidgetsBound(); - if (mIsInSearchMode) { - mSearchBar.reset(); - } else if (!mSuggestedWidgetsPackageUserKey.equals(mSelectedHeader)) { - mAdapters.get(mActivePage).mWidgetsListAdapter.selectFirstHeaderEntry(); - mAdapters.get(mActivePage).mWidgetsRecyclerView.scrollToTop(); - } - return true; - } - @Override protected int getTabletHorizontalMargin(DeviceProfile deviceProfile) { - // two pane picker is full width for fold as well as tablet. - return getResources().getDimensionPixelSize( - R.dimen.widget_picker_two_panels_left_right_margin); + if (enableCategorizedWidgetSuggestions()) { + // two pane picker is full width for fold as well as tablet. + return getResources().getDimensionPixelSize( + R.dimen.widget_picker_two_panels_left_right_margin); + } + if (deviceProfile.isTwoPanels && enableUnfoldedTwoPanePicker()) { + // enableUnfoldedTwoPanePicker made two pane picker full-width for fold only. + return getResources().getDimensionPixelSize( + R.dimen.widget_picker_two_panels_left_right_margin); + } + if (deviceProfile.isLandscape && !deviceProfile.isTwoPanels) { + // non-fold tablet landscape margins (ag/22163531) + return getResources().getDimensionPixelSize( + R.dimen.widget_picker_landscape_tablet_left_right_margin); + } + return deviceProfile.allAppsLeftRightMargin; } @Override @@ -242,7 +182,7 @@ public class WidgetsTwoPaneSheet extends WidgetsFullSheet { @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); - if (changed && mDeviceProfile.isTwoPanels) { + if (changed && mDeviceProfile.isTwoPanels && enableUnfoldedTwoPanePicker()) { LinearLayout layout = mContent.findViewById(R.id.linear_layout_container); FrameLayout leftPane = layout.findViewById(R.id.recycler_view_container); LinearLayout.LayoutParams layoutParams = (LayoutParams) leftPane.getLayoutParams(); @@ -276,32 +216,6 @@ public class WidgetsTwoPaneSheet extends WidgetsFullSheet { } } - // Used by the two pane sheet to show 3-dot menu to toggle between default lists and all lists - // when enableTieredWidgetsByDefaultInPicker is OFF. This code path and the 3-dot menu can be - // safely deleted when it's alternative "enableTieredWidgetsByDefaultInPicker" flag is inlined. - @Override - protected List getWidgetsToDisplay() { - List allWidgets = - mActivityContext.getWidgetPickerDataProvider().get().getAllWidgets(); - List defaultWidgets = - mActivityContext.getWidgetPickerDataProvider().get().getDefaultWidgets(); - - if (allWidgets.isEmpty() || defaultWidgets.isEmpty()) { - // no menu if there are no default widgets to show - mWidgetOptionsMenuState = null; - mWidgetOptionsMenu.setVisibility(GONE); - } else { - if (mWidgetOptionsMenuState == null) { - mWidgetOptionsMenuState = new WidgetOptionsMenuState(); - } - - mWidgetOptionsMenu.setVisibility(VISIBLE); - return mWidgetOptionsMenuState.showAllWidgets ? allWidgets : defaultWidgets; - } - - return allWidgets; - } - @Override public void onWidgetsBound() { super.onWidgetsBound(); @@ -311,15 +225,6 @@ public class WidgetsTwoPaneSheet extends WidgetsFullSheet { } } - @Override - public void onWidgetsListExpandButtonClick(View v) { - super.onWidgetsListExpandButtonClick(v); - // Refresh right pane with updated data for the selected header. - if (mSelectedHeader != null && mSelectedHeader != mSuggestedWidgetsPackageUserKey) { - getHeaderChangeListener().onHeaderChanged(mSelectedHeader); - } - } - @Override public void onRecommendedWidgetsBound() { super.onRecommendedWidgetsBound(); @@ -345,9 +250,14 @@ public class WidgetsTwoPaneSheet extends WidgetsFullSheet { false); mSuggestedWidgetsHeader.setExpanded(true); - PackageItemInfo packageItemInfo = new HighresPackageItemInfo( + PackageItemInfo packageItemInfo = new PackageItemInfo( /* packageName= */ SUGGESTIONS_PACKAGE_NAME, - Process.myUserHandle()); + Process.myUserHandle()) { + @Override + public boolean usingLowResIcon() { + return false; + } + }; String suggestionsHeaderTitle = getContext().getString( R.string.suggested_widgets_header_title); String suggestionsRightPaneTitle = getContext().getString( @@ -358,7 +268,7 @@ public class WidgetsTwoPaneSheet extends WidgetsFullSheet { WidgetsListHeaderEntry widgetsListHeaderEntry = WidgetsListHeaderEntry.create( packageItemInfo, /*titleSectionName=*/ suggestionsHeaderTitle, - /*items=*/ List.of(), // not necessary + /*items=*/ mActivityContext.getPopupDataProvider().getRecommendedWidgets(), /*visibleWidgetsCount=*/ 0) .withWidgetListShown(); @@ -371,19 +281,13 @@ public class WidgetsTwoPaneSheet extends WidgetsFullSheet { mRightPane.removeAllViews(); mRightPane.addView(mWidgetRecommendationsContainer); mRightPaneScrollView.setScrollY(0); + if (Utilities.ATLEAST_P) { + mRightPane.setAccessibilityPaneTitle(suggestionsRightPaneTitle); + } mSuggestedWidgetsPackageUserKey = PackageUserKey.fromPackageItemInfo(packageItemInfo); final boolean isChangingHeaders = mSelectedHeader == null || !mSelectedHeader.equals(mSuggestedWidgetsPackageUserKey); if (isChangingHeaders) { - // If the initial focus view is still focused or widget picker is still opening, it - // is likely a programmatic header click. - if (mSelectedHeader != null && !mOpenCloseAnimation.getAnimationPlayer().isRunning() - && !getAccessibilityInitialFocusView().isAccessibilityFocused()) { - if (Utilities.ATLEAST_P) { - mRightPaneScrollView.setAccessibilityPaneTitle(suggestionsRightPaneTitle); - } - focusOnFirstWidgetCell(mWidgetRecommendationsView); - } // If switching from another header, unselect any WidgetCells. This is necessary // because we do not clear/recycle the WidgetCells in the recommendations container // when the header is clicked, only when onRecommendationsBound is called. That @@ -394,6 +298,7 @@ public class WidgetsTwoPaneSheet extends WidgetsFullSheet { mSelectedHeader = mSuggestedWidgetsPackageUserKey; }); mSuggestedWidgetsContainer.addView(mSuggestedWidgetsHeader); + mRightPane.setAccessibilityPaneTitle(suggestionsRightPaneTitle); } @Override @@ -409,30 +314,6 @@ public class WidgetsTwoPaneSheet extends WidgetsFullSheet { * RECOMMENDATION_SECTION_HEIGHT_RATIO_TWO_PANE; } - @Override - @Px - protected int getAvailableWidthForSuggestions(int pickerAvailableWidth) { - int rightPaneWidth = (int) Math.ceil(0.67 * pickerAvailableWidth); - - if (mDeviceProfile.isTwoPanels) { - // See onLayout - int leftPaneWidth = (int) (0.33 * pickerAvailableWidth); - @Px int minLeftPaneWidthPx = Utilities.dpToPx(MINIMUM_WIDTH_LEFT_PANE_FOLDABLE_DP); - @Px int maxLeftPaneWidthPx = Utilities.dpToPx(MAXIMUM_WIDTH_LEFT_PANE_FOLDABLE_DP); - if (leftPaneWidth < minLeftPaneWidthPx) { - leftPaneWidth = minLeftPaneWidthPx; - } else if (leftPaneWidth > maxLeftPaneWidthPx) { - leftPaneWidth = maxLeftPaneWidthPx; - } - rightPaneWidth = pickerAvailableWidth - leftPaneWidth; - } - - // Since suggestions are shown in right pane, the available width is 2/3 of total width of - // bottom sheet. - return rightPaneWidth - getResources().getDimensionPixelSize( - R.dimen.widget_list_horizontal_margin_two_pane); // right pane end margin. - } - @Override public void onActivePageChanged(int currentActivePage) { super.onActivePageChanged(currentActivePage); @@ -444,31 +325,21 @@ public class WidgetsTwoPaneSheet extends WidgetsFullSheet { mActivePage = currentActivePage; - // When using talkback, swiping left while on right pane, should navigate to the widgets - // list on left. - mAdapters.get(mActivePage).mWidgetsRecyclerView.setAccessibilityTraversalBefore( - mRightPaneScrollView.getId()); - - // On page change, select the first item in the list to show in the right pane. - mAdapters.get(currentActivePage).mWidgetsListAdapter.selectFirstHeaderEntry(); - mAdapters.get(currentActivePage).mWidgetsRecyclerView.scrollToTop(); + if (mSuggestedWidgetsHeader == null) { + mAdapters.get(currentActivePage).mWidgetsListAdapter.selectFirstHeaderEntry(); + mAdapters.get(currentActivePage).mWidgetsRecyclerView.scrollToTop(); + } else if (currentActivePage == PERSONAL_TAB || currentActivePage == WORK_TAB) { + mSuggestedWidgetsHeader.callOnClick(); + } } @Override protected void updateRecyclerViewVisibility(AdapterHolder adapterHolder) { // The first item is always an empty space entry. Look for any more items. boolean isWidgetAvailable = adapterHolder.mWidgetsListAdapter.hasVisibleEntries(); - if (!isWidgetAvailable) { - mRightPane.removeAllViews(); - mRightPane.addView(mNoWidgetsView); - // with no widgets message, no header is selected on left - if (mSuggestedWidgetsPackageUserKey != null - && mSuggestedWidgetsPackageUserKey.equals(mSelectedHeader) - && mSuggestedWidgetsHeader != null) { - mSuggestedWidgetsHeader.setExpanded(false); - } - mSelectedHeader = null; - } + + mRightPane.setVisibility(isWidgetAvailable ? VISIBLE : GONE); + super.updateRecyclerViewVisibility(adapterHolder); } @@ -503,32 +374,20 @@ public class WidgetsTwoPaneSheet extends WidgetsFullSheet { } + @Override + protected View getContentView() { + return mRightPane; + } + private HeaderChangeListener getHeaderChangeListener() { return new HeaderChangeListener() { @Override public void onHeaderChanged(@NonNull PackageUserKey selectedHeader) { final boolean isSameHeader = mSelectedHeader != null && mSelectedHeader.equals(selectedHeader); - // If the initial focus view is still focused or widget picker is still opening, it - // is likely a programmatic header click. - final boolean isUserClick = mSelectedHeader != null - && !mOpenCloseAnimation.getAnimationPlayer().isRunning() - && !getAccessibilityInitialFocusView().isAccessibilityFocused(); mSelectedHeader = selectedHeader; - - WidgetsListContentEntry contentEntry; - if (enableTieredWidgetsByDefaultInPicker()) { - contentEntry = mAdapters.get( - getCurrentAdapterHolderType()).mWidgetsListAdapter.getContentEntry( - selectedHeader); - } else { // Can be deleted when inlining the "enableTieredWidgetsByDefaultInPicker" - // flag - final boolean showDefaultWidgets = mWidgetOptionsMenuState != null - && !mWidgetOptionsMenuState.showAllWidgets; - contentEntry = findContentEntryForPackageUser( - mActivityContext.getWidgetPickerDataProvider().get(), - selectedHeader, showDefaultWidgets); - } + WidgetsListContentEntry contentEntry = mActivityContext.getPopupDataProvider() + .getSelectedAppWidgets(selectedHeader); if (contentEntry == null || mRightPane == null) { return; @@ -539,9 +398,13 @@ public class WidgetsTwoPaneSheet extends WidgetsFullSheet { } WidgetsListContentEntry contentEntryToBind; - // Setting max span size enables row to understand how to fit more than one item - // in a row. - contentEntryToBind = contentEntry.withMaxSpanSize(mMaxSpanPerRow); + if (enableCategorizedWidgetSuggestions()) { + // Setting max span size enables row to understand how to fit more than one item + // in a row. + contentEntryToBind = contentEntry.withMaxSpanSize(mMaxSpanPerRow); + } else { + contentEntryToBind = contentEntry; + } WidgetsRowViewHolder widgetsRowViewHolder = mWidgetsListTableViewHolderBinder.newViewHolder(mRightPane); @@ -566,16 +429,11 @@ public class WidgetsTwoPaneSheet extends WidgetsFullSheet { }; mRightPane.removeAllViews(); mRightPane.addView(widgetsRowViewHolder.itemView); - if (isUserClick) { - if (Utilities.ATLEAST_P) { - mRightPaneScrollView.setAccessibilityPaneTitle(getContext().getString( + mRightPaneScrollView.setScrollY(0); + mRightPane.setAccessibilityPaneTitle( + getContext().getString( R.string.widget_picker_right_pane_accessibility_title, contentEntry.mPkgItem.title)); - } - postDelayed(() -> focusOnFirstWidgetCell(widgetsRowViewHolder.tableContainer), - WIDGET_LIST_ITEM_APPEARANCE_DELAY); - } - mRightPaneScrollView.setScrollY(0); } }; } @@ -589,18 +447,6 @@ public class WidgetsTwoPaneSheet extends WidgetsFullSheet { } } - /** - * Requests focus on the first widget cell in the given widget section. - */ - private static void focusOnFirstWidgetCell(ViewGroup parent) { - if (parent == null) return; - WidgetCell cell = Utilities.findViewByPredicate(parent, v -> v instanceof WidgetCell); - if (cell != null) { - cell.performAccessibilityAction( - AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null); - } - } - private static void unselectWidgetCell(ViewGroup parent, WidgetItem item) { if (parent == null || item == null) return; WidgetCell cell = Utilities.findViewByPredicate(parent, v -> v instanceof WidgetCell wc @@ -660,26 +506,4 @@ public class WidgetsTwoPaneSheet extends WidgetsFullSheet { */ void onHeaderChanged(@NonNull PackageUserKey selectedHeader); } - - /** - * Holds the selection state of the options menu (if presented to the user). - */ - protected static class WidgetOptionsMenuState { - /** - * UI state indicating whether to show default or all widgets. - *

If true, shows all widgets; else shows the default widgets.

- */ - public boolean showAllWidgets = false; - } - - private static class HighresPackageItemInfo extends PackageItemInfo { - HighresPackageItemInfo(String packageName, UserHandle user) { - super(packageName, user); - } - - @Override - public CacheLookupFlag getMatchingLookupFlag() { - return DEFAULT_LOOKUP_FLAG; - } - } } diff --git a/src/com/android/launcher3/widget/picker/search/LauncherWidgetsSearchBar.java b/src/com/android/launcher3/widget/picker/search/LauncherWidgetsSearchBar.java index 9804bba3c4..3eff95e6f3 100644 --- a/src/com/android/launcher3/widget/picker/search/LauncherWidgetsSearchBar.java +++ b/src/com/android/launcher3/widget/picker/search/LauncherWidgetsSearchBar.java @@ -26,6 +26,7 @@ import androidx.annotation.Nullable; import com.android.launcher3.ExtendedEditText; import com.android.launcher3.R; +import com.android.launcher3.popup.PopupDataProvider; import app.lawnchair.font.FontManager; import app.lawnchair.theme.drawable.DrawableTokens; @@ -53,8 +54,7 @@ public class LauncherWidgetsSearchBar extends LinearLayout implements WidgetsSea } @Override - public void initialize(WidgetsSearchDataProvider dataProvider, - SearchModeListener searchModeListener) { + public void initialize(PopupDataProvider dataProvider, SearchModeListener searchModeListener) { mController = new WidgetsSearchBarController( new SimpleWidgetsSearchAlgorithm(dataProvider), mEditText, mCancelButton, searchModeListener); diff --git a/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithm.java b/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithm.java index 0e88787e12..613066a048 100644 --- a/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithm.java +++ b/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithm.java @@ -21,13 +21,13 @@ import static com.android.launcher3.search.StringMatcherUtility.matches; import android.os.Handler; import com.android.launcher3.model.WidgetItem; +import com.android.launcher3.popup.PopupDataProvider; import com.android.launcher3.search.SearchAlgorithm; import com.android.launcher3.search.SearchCallback; import com.android.launcher3.search.StringMatcherUtility.StringMatcher; import com.android.launcher3.widget.model.WidgetsListBaseEntry; import com.android.launcher3.widget.model.WidgetsListContentEntry; import com.android.launcher3.widget.model.WidgetsListHeaderEntry; -import com.android.launcher3.widget.picker.search.WidgetsSearchBar.WidgetsSearchDataProvider; import java.util.ArrayList; import java.util.List; @@ -39,9 +39,9 @@ import java.util.stream.Collectors; public final class SimpleWidgetsSearchAlgorithm implements SearchAlgorithm { private final Handler mResultHandler; - private final WidgetsSearchDataProvider mDataProvider; + private final PopupDataProvider mDataProvider; - public SimpleWidgetsSearchAlgorithm(WidgetsSearchDataProvider dataProvider) { + public SimpleWidgetsSearchAlgorithm(PopupDataProvider dataProvider) { mResultHandler = new Handler(); mDataProvider = dataProvider; } @@ -63,9 +63,9 @@ public final class SimpleWidgetsSearchAlgorithm implements SearchAlgorithm getFilteredWidgets( - WidgetsSearchDataProvider dataProvider, String input) { + PopupDataProvider dataProvider, String input) { ArrayList results = new ArrayList<>(); - dataProvider.getWidgets().stream() + dataProvider.getAllWidgets().stream() .filter(entry -> entry instanceof WidgetsListHeaderEntry) .forEach(headerEntry -> { List matchedWidgetItems = filterWidgetItems( diff --git a/src/com/android/launcher3/widget/picker/search/WidgetsSearchBar.java b/src/com/android/launcher3/widget/picker/search/WidgetsSearchBar.java index ab504e7c94..44a5e80f5e 100644 --- a/src/com/android/launcher3/widget/picker/search/WidgetsSearchBar.java +++ b/src/com/android/launcher3/widget/picker/search/WidgetsSearchBar.java @@ -16,9 +16,7 @@ package com.android.launcher3.widget.picker.search; -import com.android.launcher3.widget.model.WidgetsListBaseEntry; - -import java.util.List; +import com.android.launcher3.popup.PopupDataProvider; /** * Interface for a widgets picker search bar. @@ -27,7 +25,7 @@ public interface WidgetsSearchBar { /** * Attaches a controller to the search bar which interacts with {@code searchModeListener}. */ - void initialize(WidgetsSearchDataProvider dataProvider, SearchModeListener searchModeListener); + void initialize(PopupDataProvider dataProvider, SearchModeListener searchModeListener); /** * Clears search bar. @@ -46,15 +44,4 @@ public interface WidgetsSearchBar { * Sets the vertical location, in pixels, of this search bar relative to its top position. */ void setTranslationY(float translationY); - - - /** - * Provides corpus from which search results must be returned. - */ - interface WidgetsSearchDataProvider { - /** - * Returns the widgets from which the search should return the results. - */ - List getWidgets(); - } } diff --git a/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarController.java b/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarController.java index 3008d180f7..2d96cbdc80 100644 --- a/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarController.java +++ b/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarController.java @@ -37,7 +37,8 @@ import java.util.ArrayList; * Controller for a search bar with an edit text and a cancel button. */ public class WidgetsSearchBarController implements TextWatcher, - SearchCallback, View.OnKeyListener { + SearchCallback, ExtendedEditText.OnBackKeyListener, + View.OnKeyListener { private static final String TAG = "WidgetsSearchBarController"; private static final boolean DEBUG = false; @@ -53,6 +54,7 @@ public class WidgetsSearchBarController implements TextWatcher, mSearchAlgorithm = algo; mInput = editText; mInput.addTextChangedListener(this); + mInput.setOnBackKeyListener(this); mInput.setOnKeyListener(this); mCancelButton = cancelButton; mCancelButton.setOnClickListener(v -> clearSearchResult()); @@ -105,6 +107,12 @@ public class WidgetsSearchBarController implements TextWatcher, mSearchAlgorithm.destroy(); } + @Override + public boolean onBackKey() { + clearFocus(); + return true; + } + @Override public boolean onKey(View view, int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_ENTER && event.getAction() == KeyEvent.ACTION_UP) { diff --git a/src/com/android/launcher3/widget/picker/util/WidgetPreviewContainerSizes.kt b/src/com/android/launcher3/widget/picker/util/WidgetPreviewContainerSizes.kt index 1ab8f8bb54..a016676320 100644 --- a/src/com/android/launcher3/widget/picker/util/WidgetPreviewContainerSizes.kt +++ b/src/com/android/launcher3/widget/picker/util/WidgetPreviewContainerSizes.kt @@ -28,7 +28,6 @@ val HANDHELD_WIDGET_PREVIEW_SIZES: List = WidgetPreviewContainerSize(spanX = 2, spanY = 3), WidgetPreviewContainerSize(spanX = 2, spanY = 2), WidgetPreviewContainerSize(spanX = 4, spanY = 1), - WidgetPreviewContainerSize(spanX = 3, spanY = 1), WidgetPreviewContainerSize(spanX = 2, spanY = 1), WidgetPreviewContainerSize(spanX = 1, spanY = 1), ) diff --git a/src/com/android/launcher3/widget/util/WidgetsTableUtils.java b/src/com/android/launcher3/widget/util/WidgetsTableUtils.java index 9d323ffb51..0e007a0edc 100644 --- a/src/com/android/launcher3/widget/util/WidgetsTableUtils.java +++ b/src/com/android/launcher3/widget/util/WidgetsTableUtils.java @@ -70,21 +70,6 @@ public final class WidgetsTableUtils { } }); - /** - * Comparator that enables displaying rows with more number of items at the top, and then - * rest of widgets shown in increasing order of their size (totalW * H). - */ - public static final Comparator> WIDGETS_TABLE_ROW_COUNT_COMPARATOR = - Comparator.comparingInt(row -> { - if (row.size() > 1) { - return -row.size(); - } else { - int rowWidth = row.stream().mapToInt(w -> w.spanX).sum(); - int rowHeight = row.get(0).spanY; - return (rowWidth * rowHeight); - } - }); - /** * Groups {@code widgetItems} items into a 2D array which matches their appearance in a UI * table. This takes liberty to rearrange widgets to make the table visually appealing. @@ -97,7 +82,8 @@ public final class WidgetsTableUtils { List> rows = groupWidgetItemsUsingRowPxWithoutReordering( sortedWidgetItems, context, dp, rowPx, cellPadding); - return rows.stream().sorted(WIDGETS_TABLE_ROW_SIZE_COMPARATOR).collect(Collectors.toList()); + Stream> sortedRows = rows.stream().sorted(WIDGETS_TABLE_ROW_SIZE_COMPARATOR); + return sortedRows.collect(Collectors.toList()); } /** diff --git a/src/com/android/launcher3/workprofile/PersonalWorkSlidingTabStrip.java b/src/com/android/launcher3/workprofile/PersonalWorkSlidingTabStrip.java index de94edebd2..6819623eb4 100644 --- a/src/com/android/launcher3/workprofile/PersonalWorkSlidingTabStrip.java +++ b/src/com/android/launcher3/workprofile/PersonalWorkSlidingTabStrip.java @@ -18,7 +18,6 @@ package com.android.launcher3.workprofile; import android.content.Context; import android.content.res.TypedArray; import android.graphics.drawable.RippleDrawable; -import android.graphics.drawable.StateListDrawable; import android.util.AttributeSet; import android.widget.Button; import android.widget.LinearLayout; @@ -28,12 +27,9 @@ import androidx.annotation.Nullable; import com.android.launcher3.DeviceProfile; import com.android.launcher3.R; -import com.android.launcher3.pageindicators.Direction; import com.android.launcher3.pageindicators.PageIndicator; import com.android.launcher3.views.ActivityContext; -import java.util.function.Consumer; - import app.lawnchair.font.FontManager; import app.lawnchair.theme.color.tokens.ColorStateListTokens; import app.lawnchair.theme.drawable.DrawableTokens; @@ -55,7 +51,6 @@ public class PersonalWorkSlidingTabStrip extends LinearLayout implements PageInd typedArray.recycle(); } - // Lawnchair: This function theme the work mode tab and toggle @Override protected void onFinishInflate() { super.onFinishInflate(); @@ -64,9 +59,8 @@ public class PersonalWorkSlidingTabStrip extends LinearLayout implements PageInd for (int i = 0; i < getChildCount(); i++) { Button tab = (Button) getChildAt(i); tab.setAllCaps(false); - // Lawnchair-TODO: StateListDrawable -// RippleDrawable background = (RippleDrawable) tab.getBackground(); -// background.setDrawableByLayerId(android.R.id.mask, DrawableTokens.AllAppsTabsMaskDrawable.resolve(getContext())); + RippleDrawable background = (RippleDrawable) tab.getBackground(); + background.setDrawableByLayerId(android.R.id.mask, DrawableTokens.AllAppsTabsMaskDrawable.resolve(getContext())); tab.setBackground(DrawableTokens.AllAppsTabsBackground.resolve(getContext())); tab.setTextColor(ColorStateListTokens.AllAppsTabText.resolve(getContext())); fontManager.setCustomFont(tab, R.id.font_body_medium); @@ -104,11 +98,6 @@ public class PersonalWorkSlidingTabStrip extends LinearLayout implements PageInd public void setMarkersCount(int numMarkers) { } - @Override - public void setArrowClickListener(Consumer listener) { - // No-Op. All Apps doesn't need accessibility arrows for single click navigation. - } - @Override public boolean hasOverlappingRendering() { return false; @@ -135,7 +124,8 @@ public class PersonalWorkSlidingTabStrip extends LinearLayout implements PageInd } /** - * Interface definition for a callback to be invoked when an active page has been changed. + * Interface definition for a callback to be invoked when an active page has + * been changed. */ public interface OnActivePageChangedListener { /** Called when the active page has been changed. */ diff --git a/src_no_quickstep/com/android/launcher3/uioverrides/states/AllAppsState.java b/src_no_quickstep/com/android/launcher3/uioverrides/states/AllAppsState.java index 03a553534f..9865516c4c 100644 --- a/src_no_quickstep/com/android/launcher3/uioverrides/states/AllAppsState.java +++ b/src_no_quickstep/com/android/launcher3/uioverrides/states/AllAppsState.java @@ -18,6 +18,8 @@ package com.android.launcher3.uioverrides.states; import static com.android.app.animation.Interpolators.DECELERATE; import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_ALLAPPS; +import android.content.Context; + import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; import com.android.launcher3.R; @@ -29,6 +31,8 @@ import com.android.launcher3.views.ActivityContext; */ public class AllAppsState extends LauncherState { + private static final float PARALLAX_COEFFICIENT = .125f; + private static final int STATE_FLAGS = FLAG_WORKSPACE_INACCESSIBLE; public AllAppsState(int id) { @@ -36,7 +40,8 @@ public class AllAppsState extends LauncherState { } @Override - public int getTransitionDuration(ActivityContext context, boolean isToState) { + public + int getTransitionDuration(DEVICE_PROFILE_CONTEXT context, boolean isToState) { return isToState ? context.getDeviceProfile().allAppsOpenDuration : context.getDeviceProfile().allAppsCloseDuration; diff --git a/src_no_quickstep/com/android/launcher3/uioverrides/states/OverviewState.java b/src_no_quickstep/com/android/launcher3/uioverrides/states/OverviewState.java index 532a338237..7a228c42b8 100644 --- a/src_no_quickstep/com/android/launcher3/uioverrides/states/OverviewState.java +++ b/src_no_quickstep/com/android/launcher3/uioverrides/states/OverviewState.java @@ -17,11 +17,12 @@ package com.android.launcher3.uioverrides.states; import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_OVERVIEW; +import android.content.Context; + import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; import com.android.launcher3.R; import com.android.launcher3.util.Themes; -import com.android.launcher3.views.ActivityContext; /** * Definition for overview state @@ -33,7 +34,7 @@ public class OverviewState extends LauncherState { } @Override - public int getTransitionDuration(ActivityContext context, boolean isToState) { + public int getTransitionDuration(Context context, boolean isToState) { return 250; } diff --git a/src_plugins/com/android/systemui/plugins/shared/LauncherOverlayManager.java b/src_plugins/com/android/systemui/plugins/shared/LauncherOverlayManager.java index eaa9ef0b20..819ff05a71 100644 --- a/src_plugins/com/android/systemui/plugins/shared/LauncherOverlayManager.java +++ b/src_plugins/com/android/systemui/plugins/shared/LauncherOverlayManager.java @@ -15,6 +15,9 @@ */ package com.android.systemui.plugins.shared; +import android.app.Activity; +import android.app.Application; +import android.os.Bundle; import android.view.MotionEvent; import java.io.PrintWriter; @@ -22,7 +25,7 @@ import java.io.PrintWriter; /** * Interface to control the overlay on Launcher */ -public interface LauncherOverlayManager { +public interface LauncherOverlayManager extends Application.ActivityLifecycleCallbacks { default void onDeviceProvideChanged() { } @@ -39,23 +42,32 @@ public interface LauncherOverlayManager { default void hideOverlay(int duration) { } - default void onActivityStarted() { } + default boolean startSearch(byte[] config, Bundle extras) { + return false; + } - default void onActivityResumed() { } + @Override + default void onActivityCreated(Activity activity, Bundle bundle) { } - default void onActivityPaused() { } + @Override + default void onActivityStarted(Activity activity) { } - default void onActivityStopped() { } + @Override + default void onActivityResumed(Activity activity) { } - default void onActivityDestroyed() { } + @Override + default void onActivityPaused(Activity activity) { } - default void onDisallowSwipeToMinusOnePage() {} + @Override + default void onActivityStopped(Activity activity) { } - /** - * @deprecated use LauncherOverlayTouchProxy directly - */ - @Deprecated - interface LauncherOverlay extends LauncherOverlayTouchProxy { + @Override + default void onActivitySaveInstanceState(Activity activity, Bundle bundle) { } + + @Override + default void onActivityDestroyed(Activity activity) { } + + interface LauncherOverlay extends LauncherOverlayTouchProxy{ /** * Touch interaction leading to overscroll has begun diff --git a/systemUI/README.md b/systemUI/README.md index 738820b278..5b44a6c6c3 100644 --- a/systemUI/README.md +++ b/systemUI/README.md @@ -2,8 +2,4 @@ This directory contains all of the required SystemUI modules. Some examples of modules include `Common`, which serves as a helper for other SystemUI modules, and `Unfold`, which handles devices with hinges. -Upstream source code: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/main/packages/SystemUI/ - -## `utils` Module - -A stripped-down version to only include WindowManagerUtils code to be use by `wmshell`. +Upstream source code: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/main/packages/SystemUI/ \ No newline at end of file diff --git a/systemUI/anim/Android.bp b/systemUI/anim/Android.bp index dec664fa7a..6f53b42371 100644 --- a/systemUI/anim/Android.bp +++ b/systemUI/anim/Android.bp @@ -13,7 +13,6 @@ // limitations under the License. package { - default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_", // See: http://go/android-license-faq // A large-scale-change added 'default_applicable_licenses' to import // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license" @@ -24,8 +23,7 @@ package { android_library { - name: "PlatformAnimationLib", - use_resource_processor: true, + name: "SystemUIAnimationLib", srcs: [ "src/**/*.java", @@ -41,14 +39,11 @@ android_library { ], static_libs: [ - "androidx.core_core-animation", + "androidx.core_core-animation-nodeps", "androidx.core_core-ktx", "androidx.annotation_annotation", - "com_android_systemui_flags_lib", "SystemUIShaderLib", - "WindowManager-Shell-shared", - "//frameworks/libs/systemui:animationlib", - "//frameworks/libs/systemui:com_android_systemui_shared_flags_lib", + "animationlib", ], manifest: "AndroidManifest.xml", @@ -57,7 +52,6 @@ android_library { android_library { name: "SystemUIShaderLib", - use_resource_processor: true, srcs: [ "src/com/android/systemui/surfaceeffects/**/*.java", @@ -65,7 +59,7 @@ android_library { ], static_libs: [ - "androidx.core_core-animation", + "androidx.core_core-animation-nodeps", "androidx.core_core-ktx", "androidx.annotation_annotation", ], diff --git a/systemUI/anim/build.gradle b/systemUI/anim/build.gradle index e4668e6b11..41de7d5500 100644 --- a/systemUI/anim/build.gradle +++ b/systemUI/anim/build.gradle @@ -1,13 +1,9 @@ -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - plugins { alias(libs.plugins.android.library) alias(libs.plugins.kotlin.android) } -// TODO: Pull out surfaceeffects outside of src and have separate build files there. android { - buildToolsVersion "36.1.0" namespace "com.android.systemui.animation" buildFeatures { aidl true @@ -20,17 +16,9 @@ android { res.srcDirs = ['res'] } } - lint { - abortOnError false - } - - tasks.lint.enabled = false - tasks.withType(JavaCompile) { - options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation" - } } -addFrameworkJar('framework-16.jar') +addFrameworkJar('framework-15.jar') compileOnlyCommonJars() dependencies { diff --git a/systemUI/anim/res/values/ids.xml b/systemUI/anim/res/values/ids.xml index f6bb95a42f..1a224ac7c1 100644 --- a/systemUI/anim/res/values/ids.xml +++ b/systemUI/anim/res/values/ids.xml @@ -26,7 +26,4 @@ - - - - + \ No newline at end of file diff --git a/systemUI/anim/src/com/android/systemui/animation/ActivityTransitionAnimator.kt b/systemUI/anim/src/com/android/systemui/animation/ActivityTransitionAnimator.kt index ba7c8ac973..c14ee62081 100644 --- a/systemUI/anim/src/com/android/systemui/animation/ActivityTransitionAnimator.kt +++ b/systemUI/anim/src/com/android/systemui/animation/ActivityTransitionAnimator.kt @@ -24,21 +24,18 @@ import android.app.WindowConfiguration import android.content.ComponentName import android.graphics.Color import android.graphics.Matrix -import android.graphics.PointF import android.graphics.Rect import android.graphics.RectF import android.os.Binder +import android.os.Build import android.os.Handler -import android.os.IBinder import android.os.Looper import android.os.RemoteException -import android.util.ArrayMap import android.util.Log import android.view.IRemoteAnimationFinishedCallback import android.view.IRemoteAnimationRunner import android.view.RemoteAnimationAdapter import android.view.RemoteAnimationTarget -import android.view.SurfaceControl import android.view.SyncRtSurfaceTransactionApplier import android.view.View import android.view.ViewGroup @@ -48,12 +45,8 @@ import android.view.WindowManager.TRANSIT_OPEN import android.view.WindowManager.TRANSIT_TO_BACK import android.view.WindowManager.TRANSIT_TO_FRONT import android.view.animation.PathInterpolator -import android.window.IRemoteTransition -import android.window.IRemoteTransitionFinishedCallback import android.window.RemoteTransition import android.window.TransitionFilter -import android.window.TransitionInfo -import android.window.WindowAnimationState import androidx.annotation.AnyThread import androidx.annotation.BinderThread import androidx.annotation.UiThread @@ -61,24 +54,12 @@ import com.android.app.animation.Interpolators import com.android.internal.annotations.VisibleForTesting import com.android.internal.policy.ScreenDecorationsUtils import com.android.systemui.Flags.activityTransitionUseLargestWindow -import com.android.systemui.Flags.moveTransitionAnimationLayer import com.android.systemui.Flags.translucentOccludingActivityFix -import com.android.systemui.animation.ActivityTransitionAnimator.Companion.LONG_TRANSITION_TIMEOUT -import com.android.systemui.animation.ActivityTransitionAnimator.Companion.TRANSITION_TIMEOUT -import com.android.systemui.animation.TransitionAnimator.Companion.assertLongLivedReturnAnimations -import com.android.systemui.animation.TransitionAnimator.Companion.assertReturnAnimations -import com.android.systemui.animation.TransitionAnimator.Companion.longLivedReturnAnimationsEnabled -import com.android.systemui.animation.TransitionAnimator.Companion.returnAnimationsEnabled -import com.android.systemui.animation.TransitionAnimator.Companion.toTransitionState -import com.android.systemui.animation.TransitionAnimator.Controller +import com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary import com.android.wm.shell.shared.IShellTransitions import com.android.wm.shell.shared.ShellTransitions -import com.android.wm.shell.shared.TransitionUtil import java.util.concurrent.Executor import kotlin.math.roundToInt -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch -import kotlinx.coroutines.withTimeoutOrNull private const val TAG = "ActivityTransitionAnimator" @@ -109,16 +90,6 @@ constructor( */ // TODO(b/301385865): Remove this flag. private val disableWmTimeout: Boolean = false, - - /** - * Whether we should disable the reparent transaction that puts the opening/closing window above - * the view's window. This should be set to true in tests only, where we can't currently use a - * valid leash. - * - * TODO(b/397180418): Remove this flag when we don't have the RemoteAnimation wrapper anymore - * and we can just inject a fake transaction. - */ - private val skipReparentTransaction: Boolean = false, ) { @JvmOverloads constructor( @@ -159,20 +130,7 @@ constructor( contentBeforeFadeOutDelay = 0L, contentBeforeFadeOutDuration = 150L, contentAfterFadeInDelay = 150L, - contentAfterFadeInDuration = 183L, - ) - - /** - * The timings when animating a View into an app using a spring animator. These timings - * represent fractions of the progress between the spring's initial value and its final - * value. - */ - val SPRING_TIMINGS = - TransitionAnimator.SpringTimings( - contentBeforeFadeOutDelay = 0f, - contentBeforeFadeOutDuration = 0.8f, - contentAfterFadeInDelay = 0.85f, - contentAfterFadeInDuration = 0.135f, + contentAfterFadeInDuration = 183L ) /** @@ -184,23 +142,16 @@ constructor( TIMINGS.copy(contentBeforeFadeOutDuration = 200L, contentAfterFadeInDelay = 200L) /** The interpolators when animating a View or a dialog into an app. */ - public val INTERPOLATORS = + val INTERPOLATORS = TransitionAnimator.Interpolators( positionInterpolator = Interpolators.EMPHASIZED, positionXInterpolator = Interpolators.EMPHASIZED_COMPLEMENT, contentBeforeFadeOutInterpolator = Interpolators.LINEAR_OUT_SLOW_IN, - contentAfterFadeInInterpolator = PathInterpolator(0f, 0f, 0.6f, 1f), - ) - - /** The interpolators when animating a View into an app using a spring animator. */ - val SPRING_INTERPOLATORS = - INTERPOLATORS.copy( - contentBeforeFadeOutInterpolator = Interpolators.DECELERATE_1_5, - contentAfterFadeInInterpolator = Interpolators.SLOW_OUT_LINEAR_IN, + contentAfterFadeInInterpolator = PathInterpolator(0f, 0f, 0.6f, 1f) ) // TODO(b/288507023): Remove this flag. - @JvmField val DEBUG_TRANSITION_ANIMATION = false + @JvmField val DEBUG_TRANSITION_ANIMATION = Build.IS_DEBUGGABLE /** Durations & interpolators for the navigation bar fading in & out. */ private const val ANIMATION_DURATION_NAV_FADE_IN = 266L @@ -221,13 +172,7 @@ constructor( private const val LONG_TRANSITION_TIMEOUT = 5_000L private fun defaultTransitionAnimator(mainExecutor: Executor): TransitionAnimator { - return TransitionAnimator( - mainExecutor, - TIMINGS, - INTERPOLATORS, - SPRING_TIMINGS, - SPRING_INTERPOLATORS, - ) + return TransitionAnimator(mainExecutor, TIMINGS, INTERPOLATORS) } private fun defaultDialogToAppAnimator(mainExecutor: Executor): TransitionAnimator { @@ -248,21 +193,19 @@ constructor( private val lifecycleListener = object : Listener { override fun onTransitionAnimationStart() { - LinkedHashSet(listeners).forEach { it.onTransitionAnimationStart() } + listeners.forEach { it.onTransitionAnimationStart() } } override fun onTransitionAnimationEnd() { - LinkedHashSet(listeners).forEach { it.onTransitionAnimationEnd() } + listeners.forEach { it.onTransitionAnimationEnd() } } override fun onTransitionAnimationProgress(linearProgress: Float) { - LinkedHashSet(listeners).forEach { - it.onTransitionAnimationProgress(linearProgress) - } + listeners.forEach { it.onTransitionAnimationProgress(linearProgress) } } override fun onTransitionAnimationCancelled() { - LinkedHashSet(listeners).forEach { it.onTransitionAnimationCancelled() } + listeners.forEach { it.onTransitionAnimationCancelled() } } } @@ -295,7 +238,7 @@ constructor( animate: Boolean = true, packageName: String? = null, showOverLockscreen: Boolean = false, - intentStarter: (RemoteAnimationAdapter?) -> Int, + intentStarter: (RemoteAnimationAdapter?) -> Int ) { if (controller == null || !animate) { Log.i(TAG, "Starting intent with no animation") @@ -309,8 +252,8 @@ constructor( ?: throw IllegalStateException( "ActivityTransitionAnimator.callback must be set before using this animator" ) - val runner = createEphemeralRunner(controller) - val runnerDelegate = runner.delegate + val runner = createRunner(controller) + val runnerDelegate = runner.delegate!! val hideKeyguardWithAnimation = callback.isOnKeyguard() && !showOverLockscreen // Pass the RemoteAnimationAdapter to the intent starter only if we are not hiding the @@ -320,7 +263,7 @@ constructor( RemoteAnimationAdapter( runner, TIMINGS.totalDuration, - TIMINGS.totalDuration - 150, /* statusBarTransitionDelay */ + TIMINGS.totalDuration - 150 /* statusBarTransitionDelay */ ) } else { null @@ -334,7 +277,7 @@ constructor( .registerRemoteAnimationForNextActivityStart( packageName, animationAdapter, - null, /* launchCookie */ + null /* launchCookie */ ) } catch (e: RemoteException) { Log.w(TAG, "Unable to register the remote animation", e) @@ -358,18 +301,14 @@ constructor( Log.i( TAG, "launchResult=$launchResult willAnimate=$willAnimate " + - "hideKeyguardWithAnimation=$hideKeyguardWithAnimation", + "hideKeyguardWithAnimation=$hideKeyguardWithAnimation" ) controller.callOnIntentStartedOnMainThread(willAnimate) // If we expect an animation, post a timeout to cancel it in case the remote animation is // never started. if (willAnimate) { - if (longLivedReturnAnimationsEnabled()) { - runner.postTimeouts() - } else { - runnerDelegate!!.postTimeouts() - } + runnerDelegate.postTimeouts() // Hide the keyguard using the launch animation instead of the default unlock animation. if (hideKeyguardWithAnimation) { @@ -389,7 +328,7 @@ constructor( Log.d( TAG, "Calling controller.onIntentStarted(willAnimate=$willAnimate) " + - "[controller=$this]", + "[controller=$this]" ) } this.onIntentStarted(willAnimate) @@ -411,7 +350,7 @@ constructor( animate: Boolean = true, packageName: String? = null, showOverLockscreen: Boolean = false, - intentStarter: PendingIntentStarter, + intentStarter: PendingIntentStarter ) { startIntentWithAnimation(controller, animate, packageName, showOverLockscreen) { intentStarter.startPendingIntent(it) @@ -427,13 +366,13 @@ constructor( */ private fun registerEphemeralReturnAnimation( launchController: Controller, - transitionRegister: TransitionRegister?, + transitionRegister: TransitionRegister? ) { - if (!returnAnimationsEnabled()) return + if (!returnAnimationFrameworkLibrary()) return var cleanUpRunnable: Runnable? = null val returnRunner = - createEphemeralRunner( + createRunner( object : DelegateTransitionAnimatorController(launchController) { override val isLaunching = false @@ -441,16 +380,15 @@ constructor( newKeyguardOccludedState: Boolean? ) { super.onTransitionAnimationCancelled(newKeyguardOccludedState) - onDispose() + cleanUp() } override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) { super.onTransitionAnimationEnd(isExpandingFullyAbove) - onDispose() + cleanUp() } - override fun onDispose() { - super.onDispose() + private fun cleanUp() { cleanUpRunnable?.run() } } @@ -472,10 +410,10 @@ constructor( val transition = RemoteTransition( RemoteAnimationRunnerCompat.wrap(returnRunner), - "${launchController.transitionCookie}_returnTransition", + "${launchController.transitionCookie}_returnTransition" ) - transitionRegister?.register(filter, transition, includeTakeover = false) + transitionRegister?.register(filter, transition) cleanUpRunnable = Runnable { transitionRegister?.unregister(transition) } } @@ -489,14 +427,9 @@ constructor( listeners.remove(listener) } - /** - * Create a new animation [Runner] controlled by [controller]. - * - * This method must only be used for ephemeral (launch or return) transitions. Otherwise, use - * [createLongLivedRunner]. - */ + /** Create a new animation [Runner] controlled by [controller]. */ @VisibleForTesting - fun createEphemeralRunner(controller: Controller): Runner { + fun createRunner(controller: Controller): Runner { // Make sure we use the modified timings when animating a dialog into an app. val transitionAnimator = if (controller.isDialogLaunch) { @@ -508,25 +441,6 @@ constructor( return Runner(controller, callback!!, transitionAnimator, lifecycleListener) } - /** - * Create a new animation [Runner] controlled by the [Controller] that [controllerFactory] can - * create based on [forLaunch] and within the given [scope]. - * - * This method must only be used for long-lived registrations. Otherwise, use - * [createEphemeralRunner]. - */ - @VisibleForTesting - fun createLongLivedRunner( - controllerFactory: ControllerFactory, - scope: CoroutineScope, - forLaunch: Boolean, - ): Runner { - assertLongLivedReturnAnimations() - return Runner(scope, callback!!, transitionAnimator, lifecycleListener) { - controllerFactory.createController(forLaunch) - } - } - interface PendingIntentStarter { /** * Start a pending intent using the provided [animationAdapter] and return the launch @@ -570,23 +484,6 @@ constructor( fun onTransitionAnimationProgress(linearProgress: Float) {} } - /** - * A factory used to create instances of [Controller] linked to a specific cookie [cookie] and - * [component]. - */ - abstract class ControllerFactory( - val cookie: TransitionCookie, - val component: ComponentName?, - val launchCujType: Int? = null, - val returnCujType: Int? = null, - ) { - /** - * Creates a [Controller] for launching or returning from the activity linked to [cookie] - * and [component]. - */ - abstract suspend fun createController(forLaunch: Boolean): Controller - } - /** * A controller that takes care of applying the animation to an expanding view. * @@ -611,8 +508,7 @@ constructor( cujType: Int? = null, cookie: TransitionCookie? = null, component: ComponentName? = null, - returnCujType: Int? = null, - isEphemeral: Boolean = true, + returnCujType: Int? = null ): Controller? { // Make sure the View we launch from implements LaunchableView to avoid visibility // issues. @@ -629,7 +525,7 @@ constructor( Log.e( TAG, "Skipping animation as view $view is not attached to a ViewGroup", - Exception(), + Exception() ) return null } @@ -639,8 +535,7 @@ constructor( cujType, cookie, component, - returnCujType, - isEphemeral, + returnCujType ) } } @@ -701,25 +596,20 @@ constructor( * appropriately. */ fun onTransitionAnimationCancelled(newKeyguardOccludedState: Boolean? = null) {} - - /** The controller will not be used again. Clean up the relevant internal state. */ - fun onDispose() {} } /** - * Registers [controllerFactory] as a long-lived transition handler for launch and return - * animations. + * Registers [controller] as a long-lived transition handler for launch and return animations. * - * The [Controller]s created by [controllerFactory] will only be used for transitions matching - * the [cookie], or the [ComponentName] defined within it if the cookie matching fails. These - * [Controller]s can only be created within [scope]. + * The [controller] will only be used for transitions matching the [TransitionCookie] defined + * within it, or the [ComponentName] if the cookie matching fails. Both fields are mandatory for + * this registration. */ - fun register( - cookie: TransitionCookie, - controllerFactory: ControllerFactory, - scope: CoroutineScope, - ) { - assertLongLivedReturnAnimations() + fun register(controller: Controller) { + check(returnAnimationFrameworkLibrary()) { + "Long-lived registrations cannot be used when the returnAnimationFrameworkLibrary " + + "flag is disabled" + } if (transitionRegister == null) { throw IllegalStateException( @@ -728,8 +618,13 @@ constructor( ) } + val cookie = + controller.transitionCookie + ?: throw IllegalStateException( + "A cookie must be defined in order to use long-lived animations" + ) val component = - controllerFactory.component + controller.component ?: throw IllegalStateException( "A component must be defined in order to use long-lived animations" ) @@ -750,14 +645,15 @@ constructor( } val launchRemoteTransition = RemoteTransition( - OriginTransition(createLongLivedRunner(controllerFactory, scope, forLaunch = true)), - "${cookie}_launchTransition", + RemoteAnimationRunnerCompat.wrap(createRunner(controller)), + "${cookie}_launchTransition" ) - // TODO(b/403529740): re-enable takeovers once we solve the Compose jank issues. - transitionRegister.register(launchFilter, launchRemoteTransition, includeTakeover = false) + transitionRegister.register(launchFilter, launchRemoteTransition) - // Cross-task close transitions should not use this animation, so we only register it for - // when the opening window is Launcher. + val returnController = + object : Controller by controller { + override val isLaunching: Boolean = false + } val returnFilter = TransitionFilter().apply { mRequirements = @@ -766,22 +662,15 @@ constructor( mActivityType = WindowConfiguration.ACTIVITY_TYPE_STANDARD mModes = intArrayOf(TRANSIT_CLOSE, TRANSIT_TO_BACK) mTopActivity = component - }, - TransitionFilter.Requirement().apply { - mActivityType = WindowConfiguration.ACTIVITY_TYPE_HOME - mModes = intArrayOf(TRANSIT_CLOSE, TRANSIT_TO_BACK) - }, + } ) } val returnRemoteTransition = RemoteTransition( - OriginTransition( - createLongLivedRunner(controllerFactory, scope, forLaunch = false) - ), - "${cookie}_returnTransition", + RemoteAnimationRunnerCompat.wrap(createRunner(returnController)), + "${cookie}_returnTransition" ) - // TODO(b/403529740): re-enable takeovers once we solve the Compose jank issues. - transitionRegister.register(returnFilter, returnRemoteTransition, includeTakeover = false) + transitionRegister.register(returnFilter, returnRemoteTransition) longLivedTransitions[cookie] = Pair(launchRemoteTransition, returnRemoteTransition) } @@ -801,7 +690,7 @@ constructor( @VisibleForTesting inner class DelegatingAnimationCompletionListener( private val delegate: Listener?, - private val onAnimationComplete: () -> Unit, + private val onAnimationComplete: () -> Unit ) : Listener { var cancelled = false @@ -827,330 +716,21 @@ constructor( } } - /** [Runner] wrapper that supports animation takeovers. */ - private inner class OriginTransition(private val runner: Runner) : IRemoteTransition { - private val delegate = RemoteAnimationRunnerCompat.wrap(runner) - - init { - assertLongLivedReturnAnimations() - } - - override fun startAnimation( - token: IBinder?, - info: TransitionInfo?, - t: SurfaceControl.Transaction?, - finishCallback: IRemoteTransitionFinishedCallback?, - ) { - delegate.startAnimation(token, info, t, finishCallback) - } - - override fun mergeAnimation( - transition: IBinder?, - info: TransitionInfo?, - t: SurfaceControl.Transaction?, - mergeTarget: IBinder?, - finishCallback: IRemoteTransitionFinishedCallback?, - ) { - delegate.mergeAnimation(transition, info, t, mergeTarget, finishCallback) - } - - override fun onTransitionConsumed(transition: IBinder?, aborted: Boolean) { - delegate.onTransitionConsumed(transition, aborted) - } - - override fun takeOverAnimation( - token: IBinder?, - info: TransitionInfo?, - t: SurfaceControl.Transaction?, - finishCallback: IRemoteTransitionFinishedCallback?, - states: Array, - ) { - if (info == null || t == null) { - Log.e( - TAG, - "Skipping the animation takeover because the required data is missing: " + - "info=$info, transaction=$t", - ) - return - } - - // The following code converts the contents of the given TransitionInfo into - // RemoteAnimationTargets. This is necessary because we must currently support both the - // new (Shell, remote transitions) and old (remote animations) framework to maintain - // functionality for all users of the library. - val apps = ArrayList() - val filteredStates = ArrayList() - val leashMap = ArrayMap() - val leafTaskFilter = TransitionUtil.LeafTaskFilter() - - // About layering: we divide up the "layer space" into 2 regions (each the size of the - // change count). This lets us categorize things into above and below while - // maintaining their relative ordering. - val belowLayers = info.changes.size - val aboveLayers = info.changes.size * 2 - for (i in info.changes.indices) { - val change = info.changes[i] - if (change == null || change.taskInfo == null) { - continue - } - - val taskInfo = change.taskInfo - - if (TransitionUtil.isWallpaper(change)) { - val target = - TransitionUtil.newTarget( - change, - belowLayers - i, // wallpapers go into the "below" layer space - info, - t, - leashMap, - ) - - // Make all the wallpapers opaque. - t.setAlpha(target.leash, 1f) - } else if (leafTaskFilter.test(change)) { - // Start by putting everything into the "below" layer space. - val target = - TransitionUtil.newTarget(change, belowLayers - i, info, t, leashMap) - apps.add(target) - filteredStates.add(states[i]) - - // Make all the apps opaque. - t.setAlpha(target.leash, 1f) - - if ( - TransitionUtil.isClosingType(change.mode) - // Lawnchair-TOOD: - // TODO: - // Disabled because of kotlin compiler dependency conflict - // In other word: - // You can't use feature from - // framework.jar and android.jar (SDK) at the same time - //&& taskInfo?.topActivityType != WindowConfiguration.ACTIVITY_TYPE_HOME - ) { - // Raise closing task to "above" layer so it isn't covered. - t.setLayer(target.leash, aboveLayers - i) - } else if (TransitionUtil.isOpeningType(change.mode)) { - // Put into the "below" layer space. - t.setLayer(target.leash, belowLayers - i) - } - } else if (TransitionInfo.isIndependent(change, info)) { - // Root tasks - if (TransitionUtil.isClosingType(change.mode)) { - // Raise closing task to "above" layer so it isn't covered. - t.setLayer(change.leash, aboveLayers - i) - } else if (TransitionUtil.isOpeningType(change.mode)) { - // Put into the "below" layer space. - t.setLayer(change.leash, belowLayers - i) - } - } else if (TransitionUtil.isDividerBar(change)) { - val target = - TransitionUtil.newTarget(change, belowLayers - i, info, t, leashMap) - apps.add(target) - filteredStates.add(states[i]) - } - } - - val wrappedCallback: IRemoteAnimationFinishedCallback = - object : IRemoteAnimationFinishedCallback.Stub() { - override fun onAnimationFinished() { - leashMap.clear() - val finishTransaction = SurfaceControl.Transaction() - finishCallback?.onTransitionFinished(null, finishTransaction) - finishTransaction.close() - } - } - - runner.takeOverAnimation( - apps.toTypedArray(), - filteredStates.toTypedArray(), - t, - wrappedCallback, - ) - } - - override fun asBinder(): IBinder { - return delegate.asBinder() - } - } - @VisibleForTesting - inner class Runner - private constructor( - /** - * This can hold a reference to a view, so it needs to be cleaned up and can't be held on to - * forever. In case of a long-lived [Runner], this must be null and [controllerFactory] must - * be defined instead. - */ - private var controller: Controller?, - /** - * Reusable factory to generate single-use controllers. In case of an ephemeral [Runner], - * this must be null and [controller] must be defined instead. - */ - private val controllerFactory: (suspend () -> Controller)?, - /** The scope to use when this runner is based on [controllerFactory]. */ - private val scope: CoroutineScope? = null, - private val callback: Callback, + inner class Runner( + controller: Controller, + callback: Callback, /** The animator to use to animate the window transition. */ - private val transitionAnimator: TransitionAnimator, + transitionAnimator: TransitionAnimator, /** Listener for animation lifecycle events. */ - private val listener: Listener?, + listener: Listener? = null ) : IRemoteAnimationRunner.Stub() { - constructor( - controller: Controller, - callback: Callback, - transitionAnimator: TransitionAnimator, - listener: Listener? = null, - ) : this( - controller = controller, - controllerFactory = null, - callback = callback, - transitionAnimator = transitionAnimator, - listener = listener, - ) - - constructor( - scope: CoroutineScope, - callback: Callback, - transitionAnimator: TransitionAnimator, - listener: Listener? = null, - controllerFactory: suspend () -> Controller, - ) : this( - controller = null, - controllerFactory = controllerFactory, - scope = scope, - callback = callback, - transitionAnimator = transitionAnimator, - listener = listener, - ) - // This is being passed across IPC boundaries and cycles (through PendingIntentRecords, // etc.) are possible. So we need to make sure we drop any references that might // transitively cause leaks when we're done with animation. @VisibleForTesting var delegate: AnimationDelegate? init { - assert((controller != null).xor(controllerFactory != null)) - - delegate = null - controller?.let { - // Ephemeral launches bundle the runner with the launch request (instead of being - // registered ahead of time for later use). This means that there could be a timeout - // between creation and invocation, so the delegate needs to exist from the - // beginning in order to handle such timeout. - createDelegate(it) - } - } - - @BinderThread - override fun onAnimationStart( - transit: Int, - apps: Array?, - wallpapers: Array?, - nonApps: Array?, - finishedCallback: IRemoteAnimationFinishedCallback?, - ) { - initAndRun(finishedCallback) { delegate -> - delegate.onAnimationStart(transit, apps, wallpapers, nonApps, finishedCallback) - } - } - - @VisibleForTesting - @BinderThread - fun takeOverAnimation( - apps: Array?, - windowAnimationStates: Array, - startTransaction: SurfaceControl.Transaction, - finishedCallback: IRemoteAnimationFinishedCallback?, - ) { - assertLongLivedReturnAnimations() - initAndRun(finishedCallback) { delegate -> - delegate.takeOverAnimation( - apps, - windowAnimationStates, - startTransaction, - finishedCallback, - ) - } - } - - @BinderThread - private fun initAndRun( - finishedCallback: IRemoteAnimationFinishedCallback?, - performAnimation: (AnimationDelegate) -> Unit, - ) { - val controller = controller - val controllerFactory = controllerFactory - - if (controller != null) { - maybeSetUp(controller) - val success = startAnimation(performAnimation) - if (!success) finishedCallback?.onAnimationFinished() - } else if (controllerFactory != null) { - scope?.launch { - val success = - withTimeoutOrNull(TRANSITION_TIMEOUT) { - setUp(controllerFactory) - startAnimation(performAnimation) - } ?: false - if (!success) finishedCallback?.onAnimationFinished() - } - } else { - // This happens when onDisposed() has already been called due to the animation being - // cancelled. Only issue the callback. - finishedCallback?.onAnimationFinished() - } - } - - /** Tries to start the animation on the main thread and returns whether it succeeded. */ - @BinderThread - private fun startAnimation(performAnimation: (AnimationDelegate) -> Unit): Boolean { - val delegate = delegate - return if (delegate != null) { - mainExecutor.execute { performAnimation(delegate) } - true - } else { - // Animation started too late and timed out already. - Log.i(TAG, "startAnimation called after completion") - false - } - } - - @BinderThread - override fun onAnimationCancelled() { - val delegate = delegate - if (delegate != null) { - mainExecutor.execute { delegate.onAnimationCancelled() } - } else { - Log.wtf(TAG, "onAnimationCancelled called after completion") - } - } - - /** - * Posts the default animation timeouts. Since this only applies to ephemeral launches, this - * method is a no-op if [controller] is not defined. - */ - @VisibleForTesting - @UiThread - fun postTimeouts() { - controller?.let { maybeSetUp(it) } - delegate?.postTimeouts() - } - - @AnyThread - private fun maybeSetUp(controller: Controller) { - if (delegate != null) return - createDelegate(controller) - } - - @AnyThread - private suspend fun setUp(createController: suspend () -> Controller) { - val controller = createController() - createDelegate(controller) - } - - @AnyThread - private fun createDelegate(controller: Controller) { delegate = AnimationDelegate( mainExecutor, @@ -1159,19 +739,44 @@ constructor( DelegatingAnimationCompletionListener(listener, this::dispose), transitionAnimator, disableWmTimeout, - skipReparentTransaction, ) } + @BinderThread + override fun onAnimationStart( + transit: Int, + apps: Array?, + wallpapers: Array?, + nonApps: Array?, + finishedCallback: IRemoteAnimationFinishedCallback? + ) { + val delegate = delegate + mainExecutor.execute { + if (delegate == null) { + Log.i(TAG, "onAnimationStart called after completion") + // Animation started too late and timed out already. We need to still + // signal back that we're done with it. + finishedCallback?.onAnimationFinished() + } else { + delegate.onAnimationStart(transit, apps, wallpapers, nonApps, finishedCallback) + } + } + } + + @BinderThread + override fun onAnimationCancelled() { + val delegate = delegate + mainExecutor.execute { + delegate ?: Log.wtf(TAG, "onAnimationCancelled called after completion") + delegate?.onAnimationCancelled() + } + } + @AnyThread fun dispose() { - // Drop references to animation controller once we're done with the animation to avoid - // leaking in case of ephemeral launches. When long-lived, [controllerFactory] will - // still be around to create new controllers. - mainExecutor.execute { - delegate = null - controller = null - } + // Drop references to animation controller once we're done with the animation + // to avoid leaking. + mainExecutor.execute { delegate = null } } } @@ -1193,16 +798,6 @@ constructor( */ // TODO(b/301385865): Remove this flag. disableWmTimeout: Boolean = false, - - /** - * Whether we should disable the reparent transaction that puts the opening/closing window - * above the view's window. This should be set to true in tests only, where we can't - * currently use a valid leash. - * - * TODO(b/397180418): Remove this flag when we don't have the RemoteAnimation wrapper - * anymore and we can just inject a fake transaction. - */ - private val skipReparentTransaction: Boolean = false, ) : RemoteAnimationDelegate { private val transitionContainer = controller.transitionContainer private val context = transitionContainer.context @@ -1224,13 +819,6 @@ constructor( private var cancelled = false private var animation: TransitionAnimator.Animation? = null - /** - * Whether the opening/closing window needs to reparented to the view's window at the - * beginning of the animation. Since we don't always do this, we need to keep track of it in - * order to have the rest of the animation behave correctly. - */ - var reparent = false - /** * A timeout to cancel the transition animation if the remote animation is not started or * cancelled within [TRANSITION_TIMEOUT] milliseconds after the intent was started. @@ -1250,14 +838,14 @@ constructor( Log.wtf( TAG, "The remote animation was neither cancelled or started within " + - "$LONG_TRANSITION_TIMEOUT", + "$LONG_TRANSITION_TIMEOUT" ) } init { // We do this check here to cover all entry points, including Launcher which doesn't // call startIntentWithAnimation() - if (!controller.isLaunching) assertReturnAnimations() + if (!controller.isLaunching) TransitionAnimator.checkReturnAnimationFrameworkFlag() } @UiThread @@ -1281,76 +869,21 @@ constructor( apps: Array?, wallpapers: Array?, nonApps: Array?, - callback: IRemoteAnimationFinishedCallback?, + callback: IRemoteAnimationFinishedCallback? ) { - val window = setUpAnimation(apps, callback) ?: return - - if (controller.windowAnimatorState == null || !longLivedReturnAnimationsEnabled()) { - val navigationBar = - nonApps?.firstOrNull { - it.windowType == WindowManager.LayoutParams.TYPE_NAVIGATION_BAR - } - - startAnimation(window, navigationBar, iCallback = callback) - } else { - // If a [controller.windowAnimatorState] exists, treat this like a takeover. - takeOverAnimationInternal( - window, - startWindowState = null, - startTransaction = null, - callback, - ) - } - } - - @UiThread - internal fun takeOverAnimation( - apps: Array?, - startWindowStates: Array, - startTransaction: SurfaceControl.Transaction, - callback: IRemoteAnimationFinishedCallback?, - ) { - val window = setUpAnimation(apps, callback) ?: return - val startWindowState = startWindowStates[apps!!.indexOf(window)] - takeOverAnimationInternal(window, startWindowState, startTransaction, callback) - } - - private fun takeOverAnimationInternal( - window: RemoteAnimationTarget, - startWindowState: WindowAnimationState?, - startTransaction: SurfaceControl.Transaction?, - callback: IRemoteAnimationFinishedCallback?, - ) { - val useSpring = - !controller.isLaunching && startWindowState != null && startTransaction != null - startAnimation( - window, - navigationBar = null, - useSpring, - startWindowState, - startTransaction, - callback, - ) - } - - @UiThread - private fun setUpAnimation( - apps: Array?, - callback: IRemoteAnimationFinishedCallback?, - ): RemoteAnimationTarget? { removeTimeouts() // The animation was started too late and we already notified the controller that it // timed out. if (timedOut) { callback?.invoke() - return null + return } // This should not happen, but let's make sure we don't start the animation if it was // cancelled before and we already notified the controller. if (cancelled) { - return null + return } val window = findTargetWindowIfPossible(apps) @@ -1361,15 +894,20 @@ constructor( if (DEBUG_TRANSITION_ANIMATION) { Log.d( TAG, - "Calling controller.onTransitionAnimationCancelled() [no window opening]", + "Calling controller.onTransitionAnimationCancelled() [no window opening]" ) } controller.onTransitionAnimationCancelled() listener?.onTransitionAnimationCancelled() - return null + return } - return window + val navigationBar = + nonApps?.firstOrNull { + it.windowType == WindowManager.LayoutParams.TYPE_NAVIGATION_BAR + } + + startAnimation(window, navigationBar, callback) } private fun findTargetWindowIfPossible( @@ -1390,7 +928,7 @@ constructor( for (it in apps) { if (it.mode == targetMode) { if (activityTransitionUseLargestWindow()) { - if (returnAnimationsEnabled()) { + if (returnAnimationFrameworkLibrary()) { // If the controller contains a cookie, _only_ match if either the // candidate contains the matching cookie, or a component is also // defined and is a match. @@ -1435,11 +973,8 @@ constructor( private fun startAnimation( window: RemoteAnimationTarget, - navigationBar: RemoteAnimationTarget? = null, - useSpring: Boolean = false, - startingWindowState: WindowAnimationState? = null, - startTransaction: SurfaceControl.Transaction? = null, - iCallback: IRemoteAnimationFinishedCallback? = null, + navigationBar: RemoteAnimationTarget?, + iCallback: IRemoteAnimationFinishedCallback? ) { if (TransitionAnimator.DEBUG) { Log.d(TAG, "Remote animation started") @@ -1448,28 +983,12 @@ constructor( val windowBounds = window.screenSpaceBounds val endState = if (controller.isLaunching) { - controller.windowAnimatorState?.toTransitionState() - ?: TransitionAnimator.State( - top = windowBounds.top, - bottom = windowBounds.bottom, - left = windowBounds.left, - right = windowBounds.right, - ) - .apply { - // TODO(b/184121838): We should somehow get the top and bottom - // radius of the window instead of recomputing isExpandingFullyAbove - // here. - getWindowRadius( - transitionAnimator.isExpandingFullyAbove( - controller.transitionContainer, - this, - ) - ) - .let { - topCornerRadius = it - bottomCornerRadius = it - } - } + TransitionAnimator.State( + top = windowBounds.top, + bottom = windowBounds.bottom, + left = windowBounds.left, + right = windowBounds.right + ) } else { controller.createAnimatorState() } @@ -1481,20 +1000,14 @@ constructor( ?: window.backgroundColor } + // TODO(b/184121838): We should somehow get the top and bottom radius of the window + // instead of recomputing isExpandingFullyAbove here. val isExpandingFullyAbove = transitionAnimator.isExpandingFullyAbove(controller.transitionContainer, endState) - val windowState = startingWindowState ?: controller.windowAnimatorState - - // We only reparent launch animations. In current integrations, returns are - // not affected by the issue solved by reparenting, and they present - // additional problems when the view lives in the Status Bar. - // TODO(b/397646693): remove this exception. - val isEligibleForReparenting = controller.isLaunching - //val viewRoot = controller.transitionContainer.viewRootImpl - val skipReparenting = - skipReparentTransaction || !window.leash.isValid //|| viewRoot == null - if (moveTransitionAnimationLayer() && isEligibleForReparenting && !skipReparenting) { - reparent = true + if (controller.isLaunching) { + val endRadius = getWindowRadius(isExpandingFullyAbove) + endState.topCornerRadius = endRadius + endState.bottomCornerRadius = endRadius } // We animate the opening window and delegate the view expansion to [this.controller]. @@ -1502,53 +1015,15 @@ constructor( val controller = object : Controller by delegate { override fun createAnimatorState(): TransitionAnimator.State { - if (isLaunching) { - return delegate.createAnimatorState() - } else if (!longLivedReturnAnimationsEnabled()) { - return delegate.windowAnimatorState?.toTransitionState() - ?: getWindowRadius(isExpandingFullyAbove).let { - TransitionAnimator.State( - top = windowBounds.top, - bottom = windowBounds.bottom, - left = windowBounds.left, - right = windowBounds.right, - topCornerRadius = it, - bottomCornerRadius = it, - ) - } - } - - // TODO(b/323863002): use the timestamp and velocity to update the initial - // position. - val bounds = windowState?.bounds - val left: Int = bounds?.left?.toInt() ?: windowBounds.left - val top: Int = bounds?.top?.toInt() ?: windowBounds.top - val right: Int = bounds?.right?.toInt() ?: windowBounds.right - val bottom: Int = bounds?.bottom?.toInt() ?: windowBounds.bottom - - val width = windowBounds.right - windowBounds.left - val height = windowBounds.bottom - windowBounds.top - // Scale the window. We use the max of (widthRatio, heightRatio) so that - // there is no blank space on any side. - val widthRatio = (right - left).toFloat() / width - val heightRatio = (bottom - top).toFloat() / height - val startScale = maxOf(widthRatio, heightRatio) - - val maybeRadius = windowState?.topLeftRadius - val windowRadius = - if (maybeRadius != null) { - maybeRadius * startScale - } else { - getWindowRadius(isExpandingFullyAbove) - } - + if (isLaunching) return delegate.createAnimatorState() + val windowRadius = getWindowRadius(isExpandingFullyAbove) return TransitionAnimator.State( - top = top, - bottom = bottom, - left = left, - right = right, + top = windowBounds.top, + bottom = windowBounds.bottom, + left = windowBounds.left, + right = windowBounds.right, topCornerRadius = windowRadius, - bottomCornerRadius = windowRadius, + bottomCornerRadius = windowRadius ) } @@ -1560,33 +1035,9 @@ constructor( TAG, "Calling controller.onTransitionAnimationStart(" + "isExpandingFullyAbove=$isExpandingFullyAbove) " + - "[controller=$delegate]", + "[controller=$delegate]" ) } - - if (reparent) { - // Ensure that the launching window is rendered above the view's window, - // so it is not obstructed. - // TODO(b/397180418): re-use the start transaction once the - // RemoteAnimation wrapper is cleaned up. - //SurfaceControl.Transaction().use { - // it.reparent(window.leash, viewRoot.surfaceControl) - // it.apply() - //} - } - - if (startTransaction != null) { - // Calling applyStateToWindow() here avoids skipping a frame when taking - // over an animation. - applyStateToWindow( - window, - createAnimatorState(), - linearProgress = 0f, - useSpring, - startTransaction, - ) - } - delegate.onTransitionAnimationStart(isExpandingFullyAbove) } @@ -1599,7 +1050,7 @@ constructor( TAG, "Calling controller.onTransitionAnimationEnd(" + "isExpandingFullyAbove=$isExpandingFullyAbove) " + - "[controller=$delegate]", + "[controller=$delegate]" ) } delegate.onTransitionAnimationEnd(isExpandingFullyAbove) @@ -1608,40 +1059,23 @@ constructor( override fun onTransitionAnimationProgress( state: TransitionAnimator.State, progress: Float, - linearProgress: Float, + linearProgress: Float ) { - applyStateToWindow(window, state, linearProgress, useSpring) + applyStateToWindow(window, state, linearProgress) navigationBar?.let { applyStateToNavigationBar(it, state, linearProgress) } listener?.onTransitionAnimationProgress(linearProgress) delegate.onTransitionAnimationProgress(state, progress, linearProgress) } } - val velocityPxPerS = - if (longLivedReturnAnimationsEnabled() && windowState?.velocityPxPerMs != null) { - val xVelocityPxPerS = windowState.velocityPxPerMs.x * 1000 - val yVelocityPxPerS = windowState.velocityPxPerMs.y * 1000 - PointF(xVelocityPxPerS, yVelocityPxPerS) - } else if (useSpring) { - PointF(0f, 0f) - } else { - null - } - val fadeWindowBackgroundLayer = - if (reparent) { - false - } else { - !controller.isBelowAnimatingWindow - } + animation = transitionAnimator.startAnimation( controller, endState, windowBackgroundColor, - fadeWindowBackgroundLayer = fadeWindowBackgroundLayer, + fadeWindowBackgroundLayer = !controller.isBelowAnimatingWindow, drawHole = !controller.isBelowAnimatingWindow, - startVelocity = velocityPxPerS, - startFrameTime = windowState?.timestamp ?: -1, ) } @@ -1661,10 +1095,8 @@ constructor( window: RemoteAnimationTarget, state: TransitionAnimator.State, linearProgress: Float, - useSpring: Boolean, - transaction: SurfaceControl.Transaction? = null, ) { - if (transactionApplierView == null || !window.leash.isValid) { + if (transactionApplierView.viewRootImpl == null || !window.leash.isValid) { // Don't apply any transaction if the view root we synchronize with was detached or // if the SurfaceControl associated with [window] is not valid, as // [SyncRtSurfaceTransactionApplier.scheduleApply] would otherwise throw. @@ -1703,63 +1135,41 @@ constructor( windowCropF.left.roundToInt(), windowCropF.top.roundToInt(), windowCropF.right.roundToInt(), - windowCropF.bottom.roundToInt(), + windowCropF.bottom.roundToInt() ) - val interpolators: TransitionAnimator.Interpolators - val windowProgress: Float - - if (useSpring) { - val windowAnimationDelay: Float - val windowAnimationDuration: Float + val windowAnimationDelay = if (controller.isLaunching) { - windowAnimationDelay = SPRING_TIMINGS.contentAfterFadeInDelay - windowAnimationDuration = SPRING_TIMINGS.contentAfterFadeInDuration + TIMINGS.contentAfterFadeInDelay } else { - windowAnimationDelay = SPRING_TIMINGS.contentBeforeFadeOutDelay - windowAnimationDuration = SPRING_TIMINGS.contentBeforeFadeOutDuration + TIMINGS.contentBeforeFadeOutDelay } - - interpolators = SPRING_INTERPOLATORS - windowProgress = - TransitionAnimator.getProgress( - linearProgress, - windowAnimationDelay, - windowAnimationDuration, - ) - } else { - val windowAnimationDelay: Long - val windowAnimationDuration: Long + val windowAnimationDuration = if (controller.isLaunching) { - windowAnimationDelay = TIMINGS.contentAfterFadeInDelay - windowAnimationDuration = TIMINGS.contentAfterFadeInDuration + TIMINGS.contentAfterFadeInDuration } else { - windowAnimationDelay = TIMINGS.contentBeforeFadeOutDelay - windowAnimationDuration = TIMINGS.contentBeforeFadeOutDuration + TIMINGS.contentBeforeFadeOutDuration } - - interpolators = INTERPOLATORS - windowProgress = - TransitionAnimator.getProgress( - TIMINGS, - linearProgress, - windowAnimationDelay, - windowAnimationDuration, - ) - } + val windowProgress = + TransitionAnimator.getProgress( + TIMINGS, + linearProgress, + windowAnimationDelay, + windowAnimationDuration + ) // The alpha of the opening window. If it opens above the expandable, then it should // fade in progressively. Otherwise, it should be fully opaque and will be progressively // revealed as the window background color layer above the window fades out. val alpha = - if (reparent || controller.isBelowAnimatingWindow) { + if (controller.isBelowAnimatingWindow) { if (controller.isLaunching) { - interpolators.contentAfterFadeInInterpolator.getInterpolation( + INTERPOLATORS.contentAfterFadeInInterpolator.getInterpolation( windowProgress ) } else { 1 - - interpolators.contentBeforeFadeOutInterpolator.getInterpolation( + INTERPOLATORS.contentBeforeFadeOutInterpolator.getInterpolation( windowProgress ) } @@ -1773,7 +1183,6 @@ constructor( // especially important for lock screen animations, where the window is not clipped by // the shade. val cornerRadius = maxOf(state.topCornerRadius, state.bottomCornerRadius) / scale - val params = SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(window.leash) .withAlpha(alpha) @@ -1781,21 +1190,17 @@ constructor( .withWindowCrop(windowCrop) .withCornerRadius(cornerRadius) .withVisibility(true) - if (transaction != null) params.withMergeTransaction(transaction) + .build() - transactionApplier.scheduleApply(params.build()) + transactionApplier.scheduleApply(params) } - // TODO(b/377643129): remote transitions have no way of identifying the navbar when - // converting to RemoteAnimationTargets (and in my testing it was never included in the - // transition at all). So this method is not used anymore. Remove or adapt once we fully - // convert to remote transitions. private fun applyStateToNavigationBar( navigationBar: RemoteAnimationTarget, state: TransitionAnimator.State, - linearProgress: Float, + linearProgress: Float ) { - if (transactionApplierView == null || !navigationBar.leash.isValid) { + if (transactionApplierView.viewRootImpl == null || !navigationBar.leash.isValid) { // Don't apply any transaction if the view root we synchronize with was detached or // if the SurfaceControl associated with [navigationBar] is not valid, as // [SyncRtSurfaceTransactionApplier.scheduleApply] would otherwise throw. @@ -1807,7 +1212,7 @@ constructor( TIMINGS, linearProgress, ANIMATION_DELAY_NAV_FADE_IN, - ANIMATION_DURATION_NAV_FADE_OUT, + ANIMATION_DURATION_NAV_FADE_OUT ) val params = SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(navigationBar.leash) @@ -1815,7 +1220,7 @@ constructor( matrix.reset() matrix.setTranslate( 0f, - (state.top - navigationBar.sourceContainerBounds.top).toFloat(), + (state.top - navigationBar.sourceContainerBounds.top).toFloat() ) windowCrop.set(state.left, 0, state.right, state.height) params @@ -1829,7 +1234,7 @@ constructor( TIMINGS, linearProgress, 0, - ANIMATION_DURATION_NAV_FADE_OUT, + ANIMATION_DURATION_NAV_FADE_OUT ) params.withAlpha(1f - NAV_FADE_OUT_INTERPOLATOR.getInterpolation(fadeOutProgress)) } @@ -1850,7 +1255,7 @@ constructor( if (DEBUG_TRANSITION_ANIMATION) { Log.d( TAG, - "Calling controller.onTransitionAnimationCancelled() [animation timed out]", + "Calling controller.onTransitionAnimationCancelled() [animation timed out]" ) } controller.onTransitionAnimationCancelled() @@ -1927,14 +1332,9 @@ constructor( internal fun register( filter: TransitionFilter, remoteTransition: RemoteTransition, - includeTakeover: Boolean, ) { shellTransitions?.registerRemote(filter, remoteTransition) iShellTransitions?.registerRemote(filter, remoteTransition) - if (includeTakeover) { - shellTransitions?.registerRemoteForTakeover(filter, remoteTransition) - iShellTransitions?.registerRemoteForTakeover(filter, remoteTransition) - } } /** Unregister [remoteTransition] from WM Shell. */ diff --git a/systemUI/anim/src/com/android/systemui/animation/DialogTransitionAnimator.kt b/systemUI/anim/src/com/android/systemui/animation/DialogTransitionAnimator.kt index 6a620b3b9c..e9ec614c7c 100644 --- a/systemUI/anim/src/com/android/systemui/animation/DialogTransitionAnimator.kt +++ b/systemUI/anim/src/com/android/systemui/animation/DialogTransitionAnimator.kt @@ -22,6 +22,7 @@ import android.animation.ValueAnimator import android.app.Dialog import android.graphics.Color import android.graphics.Rect +import android.os.Build import android.os.Looper import android.util.Log import android.util.MathUtils @@ -32,10 +33,10 @@ import android.view.ViewRootImpl import android.view.WindowInsets import android.view.WindowManager import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS +import androidx.annotation.RequiresApi import com.android.app.animation.Interpolators import com.android.internal.jank.Cuj.CujType import com.android.internal.jank.InteractionJankMonitor -import com.android.systemui.Flags import com.android.systemui.util.maybeForceFullscreen import com.android.systemui.util.registerAnimationOnBackInvoked import java.util.concurrent.Executor @@ -60,8 +61,13 @@ constructor( private val mainExecutor: Executor, private val callback: Callback, private val interactionJankMonitor: InteractionJankMonitor, + private val featureFlags: AnimationFeatureFlags, private val transitionAnimator: TransitionAnimator = - TransitionAnimator(mainExecutor, TIMINGS, INTERPOLATORS), + TransitionAnimator( + mainExecutor, + TIMINGS, + INTERPOLATORS, + ), private val isForTesting: Boolean = false, ) { private companion object { @@ -215,7 +221,7 @@ constructor( dialog: Dialog, view: View, cuj: DialogCuj? = null, - animateBackgroundBoundsChange: Boolean = false, + animateBackgroundBoundsChange: Boolean = false ) { val controller = Controller.fromView(view, cuj) if (controller == null) { @@ -241,7 +247,7 @@ constructor( fun show( dialog: Dialog, controller: Controller, - animateBackgroundBoundsChange: Boolean = false, + animateBackgroundBoundsChange: Boolean = false ) { if (Looper.myLooper() != Looper.getMainLooper()) { throw IllegalStateException( @@ -259,14 +265,15 @@ constructor( val controller = animatedParent?.dialogContentWithBackground?.let { Controller.fromView(it, controller.cuj) - } ?: controller + } + ?: controller // Make sure we don't run the launch animation from the same source twice at the same time. if (openedDialogs.any { it.controller.sourceIdentity == controller.sourceIdentity }) { Log.e( TAG, "Not running dialog launch animation from source as it is already expanded into a" + - " dialog", + " dialog" ) dialog.show() return @@ -283,6 +290,7 @@ constructor( animateBackgroundBoundsChange = animateBackgroundBoundsChange, parentAnimatedDialog = animatedParent, forceDisableSynchronization = isForTesting, + featureFlags = featureFlags, ) openedDialogs.add(animatedDialog) @@ -299,7 +307,7 @@ constructor( dialog: Dialog, animateFrom: Dialog, cuj: DialogCuj? = null, - animateBackgroundBoundsChange: Boolean = false, + animateBackgroundBoundsChange: Boolean = false ) { val view = openedDialogs.firstOrNull { it.dialog == animateFrom }?.dialogContentWithBackground @@ -307,7 +315,7 @@ constructor( Log.w( TAG, "Showing dialog $dialog normally as the dialog it is shown from was not shown " + - "using DialogTransitionAnimator", + "using DialogTransitionAnimator" ) dialog.show() return @@ -317,7 +325,7 @@ constructor( dialog, view, animateBackgroundBoundsChange = animateBackgroundBoundsChange, - cuj = cuj, + cuj = cuj ) } @@ -340,7 +348,8 @@ constructor( val animatedDialog = openedDialogs.firstOrNull { it.dialog.window?.decorView?.viewRootImpl == view.viewRootImpl - } ?: return null + } + ?: return null return createActivityTransitionController(animatedDialog, cujType) } @@ -366,7 +375,7 @@ constructor( private fun createActivityTransitionController( animatedDialog: AnimatedDialog, - cujType: Int? = null, + cujType: Int? = null ): ActivityTransitionAnimator.Controller? { // At this point, we know that the intent of the caller is to dismiss the dialog to show // an app, so we disable the exit animation into the source because we will never want to @@ -433,7 +442,7 @@ constructor( } private fun disableDialogDismiss() { - dialog.setDismissOverride { /* Do nothing */ } + dialog.setDismissOverride { /* Do nothing */} } private fun enableDialogDismiss() { @@ -523,6 +532,7 @@ private class AnimatedDialog( * Whether synchronization should be disabled, which can be useful if we are running in a test. */ private val forceDisableSynchronization: Boolean, + private val featureFlags: AnimationFeatureFlags, ) { /** * The DecorView of this dialog window. @@ -574,6 +584,7 @@ private class AnimatedDialog( private var hasInstrumentedJank = false + @RequiresApi(Build.VERSION_CODES.TIRAMISU) fun start() { val cuj = controller.cuj if (cuj != null) { @@ -635,7 +646,8 @@ private class AnimatedDialog( originalDialogBackgroundColor = GhostedViewTransitionAnimatorController.findGradientDrawable(background) ?.color - ?.defaultColor ?: Color.BLACK + ?.defaultColor + ?: Color.BLACK // Make the background view invisible until we start the animation. We use the transition // visibility like GhostView does so that we don't mess up with the accessibility tree (see @@ -691,7 +703,7 @@ private class AnimatedDialog( oldLeft: Int, oldTop: Int, oldRight: Int, - oldBottom: Int, + oldBottom: Int ) { dialogContentWithBackground.removeOnLayoutChangeListener(this) @@ -708,7 +720,9 @@ private class AnimatedDialog( // the dialog. dialog.setDismissOverride(this::onDialogDismissed) - dialog.registerAnimationOnBackInvoked(targetView = dialogContentWithBackground) + if (featureFlags.isPredictiveBackQsDialogAnim && Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + dialog.registerAnimationOnBackInvoked(targetView = dialogContentWithBackground) + } // Show the dialog. dialog.show() @@ -804,7 +818,7 @@ private class AnimatedDialog( if (hasInstrumentedJank) { interactionJankMonitor.end(controller.cuj!!.cujType) } - }, + } ) } @@ -877,14 +891,14 @@ private class AnimatedDialog( onAnimationFinished(true /* instantDismiss */) onDialogDismissed(this@AnimatedDialog) } - }, + } ) } private fun startAnimation( isLaunching: Boolean, onLaunchAnimationStart: () -> Unit = {}, - onLaunchAnimationEnd: () -> Unit = {}, + onLaunchAnimationEnd: () -> Unit = {} ) { // Create 2 controllers to animate both the dialog and the source. val startController = @@ -933,35 +947,32 @@ private class AnimatedDialog( } override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) { - val onEnd = { + // onLaunchAnimationEnd is called by an Animator at the end of the animation, + // on a Choreographer animation tick. The following calls will move the animated + // content from the dialog overlay back to its original position, and this + // change must be reflected in the next frame given that we then sync the next + // frame of both the content and dialog ViewRoots. However, in case that content + // is rendered by Compose, whose compositions are also scheduled on a + // Choreographer frame, any state change made *right now* won't be reflected in + // the next frame given that a Choreographer frame can't schedule another and + // have it happen in the same frame. So we post the forwarded calls to + // [Controller.onLaunchAnimationEnd], leaving this Choreographer frame, ensuring + // that the move of the content back to its original window will be reflected in + // the next frame right after [onLaunchAnimationEnd] is called. + // + // TODO(b/330672236): Move this to TransitionAnimator. + dialog.context.mainExecutor.execute { startController.onTransitionAnimationEnd(isExpandingFullyAbove) endController.onTransitionAnimationEnd(isExpandingFullyAbove) + onLaunchAnimationEnd() } - if (Flags.sceneContainer()) { - onEnd() - } else { - // onLaunchAnimationEnd is called by an Animator at the end of the - // animation, on a Choreographer animation tick. The following calls will - // move the animated content from the dialog overlay back to its original - // position, and this change must be reflected in the next frame given that - // we then sync the next frame of both the content and dialog ViewRoots. - // However, in case that content is rendered by Compose, whose compositions - // are also scheduled on a Choreographer frame, any state change made *right - // now* won't be reflected in the next frame given that a Choreographer - // frame can't schedule another and have it happen in the same frame. So we - // post the forwarded calls to [Controller.onLaunchAnimationEnd], leaving - // this Choreographer frame, ensuring that the move of the content back to - // its original window will be reflected in the next frame right after - // [onLaunchAnimationEnd] is called. - dialog.context.mainExecutor.execute { onEnd() } - } } override fun onTransitionAnimationProgress( state: TransitionAnimator.State, progress: Float, - linearProgress: Float, + linearProgress: Float ) { startController.onTransitionAnimationProgress(state, progress, linearProgress) @@ -1018,7 +1029,7 @@ private class AnimatedDialog( oldLeft: Int, oldTop: Int, oldRight: Int, - oldBottom: Int, + oldBottom: Int ) { // Don't animate if bounds didn't actually change. if (left == oldLeft && top == oldTop && right == oldRight && bottom == oldBottom) { diff --git a/systemUI/anim/src/com/android/systemui/animation/Expandable.kt b/systemUI/anim/src/com/android/systemui/animation/Expandable.kt index b56a68cb2d..3ba9a29748 100644 --- a/systemUI/anim/src/com/android/systemui/animation/Expandable.kt +++ b/systemUI/anim/src/com/android/systemui/animation/Expandable.kt @@ -39,8 +39,7 @@ interface Expandable { launchCujType: Int? = null, cookie: ActivityTransitionAnimator.TransitionCookie? = null, component: ComponentName? = null, - returnCujType: Int? = null, - isEphemeral: Boolean = true, + returnCujType: Int? = null ): ActivityTransitionAnimator.Controller? /** @@ -56,8 +55,7 @@ interface Expandable { launchCujType, cookie = null, component = null, - returnCujType = null, - isEphemeral = true, + returnCujType = null ) } @@ -82,16 +80,14 @@ interface Expandable { launchCujType: Int?, cookie: ActivityTransitionAnimator.TransitionCookie?, component: ComponentName?, - returnCujType: Int?, - isEphemeral: Boolean, + returnCujType: Int? ): ActivityTransitionAnimator.Controller? { return ActivityTransitionAnimator.Controller.fromView( view, launchCujType, cookie, component, - returnCujType, - isEphemeral, + returnCujType ) } diff --git a/systemUI/anim/src/com/android/systemui/animation/FontInterpolator.kt b/systemUI/anim/src/com/android/systemui/animation/FontInterpolator.kt index bc75b1dad4..addabcc028 100644 --- a/systemUI/anim/src/com/android/systemui/animation/FontInterpolator.kt +++ b/systemUI/anim/src/com/android/systemui/animation/FontInterpolator.kt @@ -22,66 +22,85 @@ import android.util.Log import android.util.LruCache import android.util.MathUtils import androidx.annotation.VisibleForTesting -import kotlin.math.roundToInt +import java.lang.Float.max +import java.lang.Float.min -/** Caches for font interpolation */ -interface FontCache { - val animationFrameCount: Int +private const val TAG_WGHT = "wght" +private const val TAG_ITAL = "ital" - fun get(key: InterpKey): Font? +private const val FONT_WEIGHT_DEFAULT_VALUE = 400f +private const val FONT_ITALIC_MAX = 1f +private const val FONT_ITALIC_MIN = 0f +private const val FONT_ITALIC_ANIMATION_STEP = 0.1f +private const val FONT_ITALIC_DEFAULT_VALUE = 0f - fun get(key: VarFontKey): Font? +// Benchmarked via Perfetto, difference between 10 and 50 entries is about 0.3ms in +// frame draw time on a Pixel 6. +@VisibleForTesting const val DEFAULT_FONT_CACHE_MAX_ENTRIES = 10 - fun put(key: InterpKey, font: Font) +/** Provide interpolation of two fonts by adjusting font variation settings. */ +class FontInterpolator( + numberOfAnimationSteps: Int? = null, +) { + /** + * Cache key for the interpolated font. + * + * This class is mutable for recycling. + */ + private data class InterpKey(var l: Font?, var r: Font?, var progress: Float) { + fun set(l: Font, r: Font, progress: Float) { + this.l = l + this.r = r + this.progress = progress + } + } - fun put(key: VarFontKey, font: Font) -} + /** + * Cache key for the font that has variable font. + * + * This class is mutable for recycling. + */ + private data class VarFontKey( + var sourceId: Int, + var index: Int, + val sortedAxes: MutableList + ) { + constructor( + font: Font, + axes: List + ) : this( + font.sourceIdentifier, + font.ttcIndex, + axes.toMutableList().apply { sortBy { it.tag } } + ) -/** Cache key for the interpolated font. */ -data class InterpKey(val start: Font?, val end: Font?, val frame: Int) + fun set(font: Font, axes: List) { + sourceId = font.sourceIdentifier + index = font.ttcIndex + sortedAxes.clear() + sortedAxes.addAll(axes) + sortedAxes.sortBy { it.tag } + } + } -/** Cache key for the font that has variable font. */ -data class VarFontKey(val sourceId: Int, val index: Int, val sortedAxes: List) { - constructor( - font: Font, - axes: List, - ) : this(font.sourceIdentifier, font.ttcIndex, axes.sortedBy { it.tag }) -} - -class FontCacheImpl(override val animationFrameCount: Int = DEFAULT_FONT_CACHE_MAX_ENTRIES / 2) : - FontCache { // Font interpolator has two level caches: one for input and one for font with different // variation settings. No synchronization is needed since FontInterpolator is not designed to be // thread-safe and can be used only on UI thread. - val cacheMaxEntries = animationFrameCount * 2 + val cacheMaxEntries = numberOfAnimationSteps?.let { it * 2 } ?: DEFAULT_FONT_CACHE_MAX_ENTRIES private val interpCache = LruCache(cacheMaxEntries) private val verFontCache = LruCache(cacheMaxEntries) - override fun get(key: InterpKey): Font? = interpCache[key] + // Mutable keys for recycling. + private val tmpInterpKey = InterpKey(null, null, 0f) + private val tmpVarFontKey = VarFontKey(0, 0, mutableListOf()) - override fun get(key: VarFontKey): Font? = verFontCache[key] - - override fun put(key: InterpKey, font: Font) { - interpCache.put(key, font) - } - - override fun put(key: VarFontKey, font: Font) { - verFontCache.put(key, font) - } - - companion object { - // Benchmarked via Perfetto, difference between 10 and 50 entries is about 0.3ms in frame - // draw time on a Pixel 6. - @VisibleForTesting const val DEFAULT_FONT_CACHE_MAX_ENTRIES = 10 - } -} - -/** Provide interpolation of two fonts by adjusting font variation settings. */ -class FontInterpolator(val fontCache: FontCache = FontCacheImpl()) { /** Linear interpolate the font variation settings. */ - fun lerp(start: Font, end: Font, progress: Float, linearProgress: Float): Font { - if (progress <= 0f) return start - if (progress >= 1f) return end + fun lerp(start: Font, end: Font, progress: Float): Font { + if (progress == 0f) { + return start + } else if (progress == 1f) { + return end + } val startAxes = start.axes ?: EMPTY_AXES val endAxes = end.axes ?: EMPTY_AXES @@ -92,13 +111,13 @@ class FontInterpolator(val fontCache: FontCache = FontCacheImpl()) { // Check we already know the result. This is commonly happens since we draws the different // text chunks with the same font. - val iKey = - InterpKey(start, end, (linearProgress * fontCache.animationFrameCount).roundToInt()) - fontCache.get(iKey)?.let { + tmpInterpKey.set(start, end, progress) + val cachedFont = interpCache[tmpInterpKey] + if (cachedFont != null) { if (DEBUG) { - Log.d(LOG_TAG, "[$progress, $linearProgress] Interp. cache hit for $iKey") + Log.d(LOG_TAG, "[$progress] Interp. cache hit for $tmpInterpKey") } - return it + return cachedFont } // General axes interpolation takes O(N log N), this is came from sorting the axes. Usually @@ -107,36 +126,58 @@ class FontInterpolator(val fontCache: FontCache = FontCacheImpl()) { // and also pre-fill the missing axes value with default value from 'fvar' table. val newAxes = lerp(startAxes, endAxes) { tag, startValue, endValue -> - MathUtils.lerp(startValue, endValue, progress) + when (tag) { + TAG_WGHT -> + MathUtils.lerp( + startValue ?: FONT_WEIGHT_DEFAULT_VALUE, + endValue ?: FONT_WEIGHT_DEFAULT_VALUE, + progress + ) + TAG_ITAL -> + adjustItalic( + MathUtils.lerp( + startValue ?: FONT_ITALIC_DEFAULT_VALUE, + endValue ?: FONT_ITALIC_DEFAULT_VALUE, + progress + ) + ) + else -> { + require(startValue != null && endValue != null) { + "Unable to interpolate due to unknown default axes value : $tag" + } + MathUtils.lerp(startValue, endValue, progress) + } + } } // Check if we already make font for this axes. This is typically happens if the animation - // happens backward and is being linearly interpolated. - val vKey = VarFontKey(start, newAxes) - fontCache.get(vKey)?.let { - fontCache.put(iKey, it) + // happens backward. + tmpVarFontKey.set(start, newAxes) + val axesCachedFont = verFontCache[tmpVarFontKey] + if (axesCachedFont != null) { + interpCache.put(InterpKey(start, end, progress), axesCachedFont) if (DEBUG) { - Log.d(LOG_TAG, "[$progress, $linearProgress] Axis cache hit for $vKey") + Log.d(LOG_TAG, "[$progress] Axis cache hit for $tmpVarFontKey") } - return it + return axesCachedFont } // This is the first time to make the font for the axes. Build and store it to the cache. // Font.Builder#build won't throw IOException since creating fonts from existing fonts will // not do any IO work. val newFont = Font.Builder(start).setFontVariationSettings(newAxes.toTypedArray()).build() - fontCache.put(iKey, newFont) - fontCache.put(vKey, newFont) + interpCache.put(InterpKey(start, end, progress), newFont) + verFontCache.put(VarFontKey(start, newAxes), newFont) // Cache misses are likely to create memory leaks, so this is logged at error level. - Log.e(LOG_TAG, "[$progress, $linearProgress] Cache MISS for $iKey / $vKey") + Log.e(LOG_TAG, "[$progress] Cache MISS for $tmpInterpKey / $tmpVarFontKey") return newFont } private fun lerp( start: Array, end: Array, - filter: (tag: String, left: Float, right: Float) -> Float, + filter: (tag: String, left: Float?, right: Float?) -> Float ): List { // Safe to modify result of Font#getAxes since it returns cloned object. start.sortBy { axis -> axis.tag } @@ -156,37 +197,39 @@ class FontInterpolator(val fontCache: FontCache = FontCacheImpl()) { else -> tagA.compareTo(tagB) } - val tag = + val axis = when { - comp == 0 -> tagA!! - comp < 0 -> tagA!! - else -> tagB!! + comp == 0 -> { + val v = filter(tagA!!, start[i++].styleValue, end[j++].styleValue) + FontVariationAxis(tagA, v) + } + comp < 0 -> { + val v = filter(tagA!!, start[i++].styleValue, null) + FontVariationAxis(tagA, v) + } + else -> { // comp > 0 + val v = filter(tagB!!, null, end[j++].styleValue) + FontVariationAxis(tagB, v) + } } - val axisDefinition = GSFAxes.getAxis(tag) - require(comp == 0 || axisDefinition != null) { - "Unable to interpolate due to unknown default axes value: $tag" - } - - val axisValue = - when { - comp == 0 -> filter(tag, start[i++].styleValue, end[j++].styleValue) - comp < 0 -> filter(tag, start[i++].styleValue, axisDefinition!!.defaultValue) - else -> filter(tag, axisDefinition!!.defaultValue, end[j++].styleValue) - } - - // Round axis value to valid intermediate steps. This improves the cache hit rate. - val step = axisDefinition?.animationStep ?: DEFAULT_ANIMATION_STEP - result.add(FontVariationAxis(tag, (axisValue / step).roundToInt() * step)) + result.add(axis) } return result } + // For the performance reasons, we animate italic with FONT_ITALIC_ANIMATION_STEP. This helps + // Cache hit ratio in the Skia glyph cache. + private fun adjustItalic(value: Float) = + coerceInWithStep(value, FONT_ITALIC_MIN, FONT_ITALIC_MAX, FONT_ITALIC_ANIMATION_STEP) + + private fun coerceInWithStep(v: Float, min: Float, max: Float, step: Float) = + (v.coerceIn(min, max) / step).toInt() * step + companion object { private const val LOG_TAG = "FontInterpolator" private val DEBUG = Log.isLoggable(LOG_TAG, Log.DEBUG) private val EMPTY_AXES = arrayOf() - private const val DEFAULT_ANIMATION_STEP = 1f // Returns true if given two font instance can be interpolated. fun canInterpolate(start: Font, end: Font) = diff --git a/systemUI/anim/src/com/android/systemui/animation/FontVariationUtils.kt b/systemUI/anim/src/com/android/systemui/animation/FontVariationUtils.kt index e07d7b337b..78ae4af258 100644 --- a/systemUI/anim/src/com/android/systemui/animation/FontVariationUtils.kt +++ b/systemUI/anim/src/com/android/systemui/animation/FontVariationUtils.kt @@ -1,51 +1,37 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - package com.android.systemui.animation -import kotlin.text.buildString +private const val TAG_WGHT = "wght" +private const val TAG_WDTH = "wdth" +private const val TAG_OPSZ = "opsz" +private const val TAG_ROND = "ROND" class FontVariationUtils { private var mWeight = -1 private var mWidth = -1 private var mOpticalSize = -1 private var mRoundness = -1 - private var mCurrentFVar = "" + private var isUpdated = false /* * generate fontVariationSettings string, used for key in typefaceCache in TextAnimator * the order of axes should align to the order of parameters + * if every axis remains unchanged, return "" */ fun updateFontVariation( weight: Int = -1, width: Int = -1, opticalSize: Int = -1, - roundness: Int = -1, + roundness: Int = -1 ): String { - var isUpdated = false + isUpdated = false if (weight >= 0 && mWeight != weight) { isUpdated = true mWeight = weight } - if (width >= 0 && mWidth != width) { isUpdated = true mWidth = width } - if (opticalSize >= 0 && mOpticalSize != opticalSize) { isUpdated = true mOpticalSize = opticalSize @@ -55,32 +41,19 @@ class FontVariationUtils { isUpdated = true mRoundness = roundness } - - if (!isUpdated) { - return mCurrentFVar + var resultString = "" + if (mWeight >= 0) { + resultString += "'$TAG_WGHT' $mWeight" } - - return buildString { - if (mWeight >= 0) { - if (!isBlank()) append(", ") - append("'${GSFAxes.WEIGHT.tag}' $mWeight") - } - - if (mWidth >= 0) { - if (!isBlank()) append(", ") - append("'${GSFAxes.WIDTH.tag}' $mWidth") - } - - if (mOpticalSize >= 0) { - if (!isBlank()) append(", ") - append("'${GSFAxes.OPTICAL_SIZE.tag}' $mOpticalSize") - } - - if (mRoundness >= 0) { - if (!isBlank()) append(", ") - append("'${GSFAxes.ROUND.tag}' $mRoundness") - } - } - .also { mCurrentFVar = it } + if (mWidth >= 0) { + resultString += (if (resultString.isBlank()) "" else ", ") + "'$TAG_WDTH' $mWidth" + } + if (mOpticalSize >= 0) { + resultString += (if (resultString.isBlank()) "" else ", ") + "'$TAG_OPSZ' $mOpticalSize" + } + if (mRoundness >= 0) { + resultString += (if (resultString.isBlank()) "" else ", ") + "'$TAG_ROND' $mRoundness" + } + return if (isUpdated) resultString else "" } } diff --git a/systemUI/anim/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt b/systemUI/anim/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt index 576ff61c4f..e626c04675 100644 --- a/systemUI/anim/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt +++ b/systemUI/anim/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt @@ -36,7 +36,6 @@ import android.view.ViewGroupOverlay import android.widget.FrameLayout import com.android.internal.jank.Cuj.CujType import com.android.internal.jank.InteractionJankMonitor -import com.android.systemui.Flags import java.util.LinkedList import kotlin.math.min import kotlin.math.roundToInt @@ -59,7 +58,7 @@ open class GhostedViewTransitionAnimatorController @JvmOverloads constructor( /** The view that will be ghosted and from which the background will be extracted. */ - transitioningView: View, + private val ghostedView: View, /** The [CujType] associated to this launch animation. */ private val launchCujType: Int? = null, @@ -68,32 +67,13 @@ constructor( /** The [CujType] associated to this return animation. */ private val returnCujType: Int? = null, - - /** - * Whether this controller should be invalidated after its first use, and whenever [ghostedView] - * is detached. - */ - private val isEphemeral: Boolean = false, private var interactionJankMonitor: InteractionJankMonitor = InteractionJankMonitor.getInstance(), - - /** [ViewTransitionRegistry] to store the mapping of transitioning view and its token */ - private val transitionRegistry: IViewTransitionRegistry? = - if (Flags.decoupleViewControllerInAnimlib()) { - ViewTransitionRegistry.instance - } else { - null - }, ) : ActivityTransitionAnimator.Controller { override val isLaunching: Boolean = true /** The container to which we will add the ghost view and expanding background. */ - override var transitionContainer: ViewGroup - get() = ghostedView.rootView as ViewGroup - set(_) { - // empty, should never be set to avoid memory leak - } - + override var transitionContainer = ghostedView.rootView as ViewGroup private val transitionContainerOverlay: ViewGroupOverlay get() = transitionContainer.overlay @@ -139,46 +119,9 @@ constructor( returnCujType } - /** - * Used to automatically clean up the internal state once [ghostedView] is detached from the - * hierarchy. - */ - private val detachListener = - object : View.OnAttachStateChangeListener { - override fun onViewAttachedToWindow(v: View) {} - - override fun onViewDetachedFromWindow(v: View) { - onDispose() - } - } - - /** [ViewTransitionToken] to be used for storing transitioning view in [transitionRegistry] */ - private val transitionToken = - if (Flags.decoupleViewControllerInAnimlib()) { - transitionRegistry?.register(transitioningView) - } else { - null - } - - /** The view that will be ghosted and from which the background will be extracted */ - private val ghostedView: View - get() = - if (Flags.decoupleViewControllerInAnimlib()) { - transitionToken?.let { token -> transitionRegistry?.getView(token) } - } else { - _ghostedView - }!! - - private val _ghostedView = - if (Flags.decoupleViewControllerInAnimlib()) { - null - } else { - transitioningView - } - init { // Make sure the View we launch from implements LaunchableView to avoid visibility issues. - if (transitioningView !is LaunchableView) { + if (ghostedView !is LaunchableView) { throw IllegalArgumentException( "A GhostedViewLaunchAnimatorController was created from a View that does not " + "implement LaunchableView. This can lead to subtle bugs where the visibility " + @@ -212,17 +155,6 @@ constructor( } background = findBackground(ghostedView) - - if (TransitionAnimator.returnAnimationsEnabled() && isEphemeral) { - ghostedView.addOnAttachStateChangeListener(detachListener) - } - } - - override fun onDispose() { - if (TransitionAnimator.returnAnimationsEnabled()) { - ghostedView.removeOnAttachStateChangeListener(detachListener) - } - transitionToken?.let { token -> transitionRegistry?.unregister(token) } } /** @@ -232,7 +164,7 @@ constructor( protected open fun setBackgroundCornerRadius( background: Drawable, topCornerRadius: Float, - bottomCornerRadius: Float, + bottomCornerRadius: Float ) { // By default, we rely on WrappedDrawable to set/restore the background radii before/after // each draw. @@ -263,7 +195,7 @@ constructor( val state = TransitionAnimator.State( topCornerRadius = getCurrentTopCornerRadius(), - bottomCornerRadius = getCurrentBottomCornerRadius(), + bottomCornerRadius = getCurrentBottomCornerRadius() ) fillGhostedViewState(state) return state @@ -276,7 +208,7 @@ constructor( val insets = backgroundInsets val boundCorrections: Rect = if (ghostedView is LaunchableView) { - (ghostedView as LaunchableView).getPaddingForLaunchAnimation() + ghostedView.getPaddingForLaunchAnimation() } else { Rect() } @@ -313,17 +245,9 @@ constructor( // visibility that is saved by `setShouldBlockVisibilityChanges()` for a later restoration. (ghostedView as? LaunchableView)?.setShouldBlockVisibilityChanges(true) - try { - // Create a ghost of the view that will be moving and fading out. This allows to fade - // out the content before fading out the background. - ghostView = GhostView.addGhost(ghostedView, transitionContainer) - } catch (e: Exception) { - // It is not 100% clear what conditions cause this exception to happen in practice, and - // we could never reproduce it, but it does show up extremely rarely. We already handle - // the scenario where ghostView is null, so we just avoid crashing and log the error. - // See b/315858472 for an investigation of the issue. - Log.e(TAG, "Failed to create ghostView", e) - } + // Create a ghost of the view that will be moving and fading out. This allows to fade out + // the content before fading out the background. + ghostView = GhostView.addGhost(ghostedView, transitionContainer) // [GhostView.addGhost], the result of which is our [ghostView], creates a [GhostView], and // adds it first to a [FrameLayout] container. It then adds _that_ container to an @@ -345,7 +269,7 @@ constructor( override fun onTransitionAnimationProgress( state: TransitionAnimator.State, progress: Float, - linearProgress: Float, + linearProgress: Float ) { val ghostView = this.ghostView ?: return val backgroundView = this.backgroundView!! @@ -393,11 +317,11 @@ constructor( scale, scale, ghostedViewState.centerX - transitionContainerLocation[0], - ghostedViewState.centerY - transitionContainerLocation[1], + ghostedViewState.centerY - transitionContainerLocation[1] ) ghostViewMatrix.postTranslate( (leftChange + rightChange) / 2f, - (topChange + bottomChange) / 2f, + (topChange + bottomChange) / 2f ) ghostView.animationMatrix = ghostViewMatrix @@ -434,8 +358,8 @@ constructor( if (ghostedView is LaunchableView) { // Restore the ghosted view visibility. - (ghostedView as LaunchableView).setShouldBlockVisibilityChanges(false) - (ghostedView as LaunchableView).onActivityLaunchAnimationEnd() + ghostedView.setShouldBlockVisibilityChanges(false) + ghostedView.onActivityLaunchAnimationEnd() } else { // Make the ghosted view visible. We ensure that the view is considered VISIBLE by // accessibility by first making it INVISIBLE then VISIBLE (see b/204944038#comment17 @@ -444,10 +368,6 @@ constructor( ghostedView.visibility = View.VISIBLE ghostedView.invalidate() } - - if (isEphemeral || Flags.decoupleViewControllerInAnimlib()) { - onDispose() - } } companion object { @@ -542,7 +462,7 @@ constructor( private fun updateRadii( radii: FloatArray, topCornerRadius: Float, - bottomCornerRadius: Float, + bottomCornerRadius: Float ) { radii[0] = topCornerRadius radii[1] = topCornerRadius diff --git a/systemUI/anim/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java b/systemUI/anim/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java index 21ec89646f..abfc2c879b 100644 --- a/systemUI/anim/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java +++ b/systemUI/anim/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java @@ -22,16 +22,8 @@ import static android.view.WindowManager.TRANSIT_OLD_NONE; 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 android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_EXIT_BY_MINIMIZE_TRANSITION_BUGFIX; -import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX; import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; -import static com.android.internal.util.Preconditions.checkArgument; -import static com.android.wm.shell.shared.TransitionUtil.FLAG_IS_DESKTOP_WALLPAPER_ACTIVITY; -import static com.android.wm.shell.shared.TransitionUtil.isClosingMode; -import static com.android.wm.shell.shared.TransitionUtil.isClosingType; -import static com.android.wm.shell.shared.TransitionUtil.isOpeningMode; - import android.os.IBinder; import android.os.RemoteException; import android.util.ArrayMap; @@ -46,6 +38,7 @@ import android.window.IRemoteTransition; import android.window.IRemoteTransitionFinishedCallback; import android.window.RemoteTransitionStub; import android.window.TransitionInfo; +import android.window.WindowAnimationState; import com.android.wm.shell.shared.CounterRotator; @@ -53,15 +46,15 @@ public abstract class RemoteAnimationRunnerCompat extends IRemoteAnimationRunner private static final String TAG = "RemoteAnimRunnerCompat"; public abstract void onAnimationStart(@WindowManager.TransitionOldType int transit, - RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers, - RemoteAnimationTarget[] nonApps, Runnable finishedCallback); + RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers, + RemoteAnimationTarget[] nonApps, Runnable finishedCallback); @Override public final void onAnimationStart(@TransitionOldType int transit, - RemoteAnimationTarget[] apps, - RemoteAnimationTarget[] wallpapers, - RemoteAnimationTarget[] nonApps, - final IRemoteAnimationFinishedCallback finishedCallback) { + RemoteAnimationTarget[] apps, + RemoteAnimationTarget[] wallpapers, + RemoteAnimationTarget[] nonApps, + final IRemoteAnimationFinishedCallback finishedCallback) { onAnimationStart(transit, apps, wallpapers, nonApps, () -> { @@ -73,19 +66,46 @@ public abstract class RemoteAnimationRunnerCompat extends IRemoteAnimationRunner }); } - public IRemoteTransition toRemoteTransition() { - return wrap(this); + // Called only in R + public void onAnimationStart(RemoteAnimationTarget[] appTargets, + RemoteAnimationTarget[] wallpaperTargets, IRemoteAnimationFinishedCallback finishedCallback) { + onAnimationStart(0 /* transit */, appTargets, wallpaperTargets, + new RemoteAnimationTarget[0], finishedCallback); } + // Called only in Q + public void onAnimationStart(RemoteAnimationTarget[] appTargets, + IRemoteAnimationFinishedCallback finishedCallback) { + onAnimationStart(appTargets, new RemoteAnimationTarget[0], finishedCallback); + } + + public void onAnimationCancelled(boolean isKeyguardOccluded) { + onAnimationCancelled(); + } + + // Called only in S+ + public void onAnimationCancelled() {} + /** Wraps a remote animation runner in a remote-transition. */ public static RemoteTransitionStub wrap(IRemoteAnimationRunner runner) { return new RemoteTransitionStub() { + @Override + public void startAnimation(IBinder token , TransitionInfo info , SurfaceControl.Transaction t , IRemoteTransitionFinishedCallback finishCallback) throws RemoteException { + + } + }; + } + + public IRemoteTransition toRemoteTransition() { + return new IRemoteTransition.Stub() { final ArrayMap mFinishRunnables = new ArrayMap<>(); + public void onTransitionConsumed(IBinder transition, boolean aborted) {} + @Override public void startAnimation(IBinder token, TransitionInfo info, - SurfaceControl.Transaction t, - IRemoteTransitionFinishedCallback finishCallback) throws RemoteException { + SurfaceControl.Transaction t, + IRemoteTransitionFinishedCallback finishCallback) { final ArrayMap leashMap = new ArrayMap<>(); final RemoteAnimationTarget[] apps = RemoteAnimationTargetCompat.wrapApps(info, t, leashMap); @@ -165,14 +185,13 @@ public abstract class RemoteAnimationRunnerCompat extends IRemoteAnimationRunner t.show(wallpapers[i].leash); t.setAlpha(wallpapers[i].leash, 1.f); } - if (ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX.isTrue()) { - resetLauncherAlphaOnDesktopExit(info, launcherTask, leashMap, t); - } } else { if (launcherTask != null) { counterLauncher.addChild(t, leashMap.get(launcherTask.getLeash())); } if (wallpaper != null && rotateDelta != 0 && wallpaper.getParent() != null) { + counterWallpaper.setup(t, info.getChange(wallpaper.getParent()).getLeash(), + rotateDelta, displayW, displayH); final TransitionInfo.Change parent = info.getChange(wallpaper.getParent()); if (parent != null) { counterWallpaper.setup(t, parent.getLeash(), rotateDelta, displayW, @@ -211,7 +230,7 @@ public abstract class RemoteAnimationRunnerCompat extends IRemoteAnimationRunner mFinishRunnables.put(token, animationFinishedCallback); } // TODO(bc-unlcok): Pass correct transit type. - runner.onAnimationStart(TRANSIT_OLD_NONE, + onAnimationStart(TRANSIT_OLD_NONE, apps, wallpapers, nonApps, new IRemoteAnimationFinishedCallback() { @Override public void onAnimationFinished() { @@ -230,8 +249,8 @@ public abstract class RemoteAnimationRunnerCompat extends IRemoteAnimationRunner @Override public void mergeAnimation(IBinder token, TransitionInfo info, - SurfaceControl.Transaction t, IBinder mergeTarget, - IRemoteTransitionFinishedCallback finishCallback) throws RemoteException { + SurfaceControl.Transaction t, IBinder mergeTarget, + IRemoteTransitionFinishedCallback finishCallback) throws RemoteException { // TODO: hook up merge to recents onTaskAppeared if applicable. Until then, adapt // to legacy cancel. final Runnable finishRunnable; @@ -242,52 +261,14 @@ public abstract class RemoteAnimationRunnerCompat extends IRemoteAnimationRunner t.close(); info.releaseAllSurfaces(); if (finishRunnable == null) return; - runner.onAnimationCancelled(); + onAnimationCancelled(false /* isKeyguardOccluded */); finishRunnable.run(); } @Override - public void onTransitionConsumed(IBinder transition, boolean aborted) - throws RemoteException { - // Notify the remote runner that the transition has been canceled if the transition - // was merged into another transition or aborted - synchronized (mFinishRunnables) { - mFinishRunnables.remove(transition); - } - runner.onAnimationCancelled(); + public void takeOverAnimation(IBinder transition , TransitionInfo info , SurfaceControl.Transaction t , IRemoteTransitionFinishedCallback finishCallback , WindowAnimationState[] states) throws RemoteException { + } }; } - - /** - * Reset the alpha of the Launcher leash to give the Launcher time to hide its Views before the - * exit-desktop animation starts. - * - * This method should only be called if the current transition is opening Launcher, otherwise we - * might not be exiting Desktop Mode. - */ - private static void resetLauncherAlphaOnDesktopExit( - TransitionInfo info, - TransitionInfo.Change launcherChange, - ArrayMap leashMap, - SurfaceControl.Transaction startTransaction - ) { - checkArgument(isOpeningMode(launcherChange.getMode())); - if (!isClosingType(info.getType()) - && !ENABLE_DESKTOP_WINDOWING_EXIT_BY_MINIMIZE_TRANSITION_BUGFIX.isTrue()) { - return; - } - for (int i = info.getChanges().size() - 1; i >= 0; --i) { - final TransitionInfo.Change change = info.getChanges().get(i); - // skip changes that we didn't wrap - if (!leashMap.containsKey(change.getLeash())) continue; - // Only make the update if we are closing Desktop tasks. - if (change.getTaskInfo() != null && (change.getTaskInfo().isFreeform() - || change.hasFlags(FLAG_IS_DESKTOP_WALLPAPER_ACTIVITY)) - && isClosingMode(change.getMode())) { - startTransaction.setAlpha(leashMap.get(launcherChange.getLeash()), 0f); - return; - } - } - } } diff --git a/systemUI/anim/src/com/android/systemui/animation/TextAnimator.kt b/systemUI/anim/src/com/android/systemui/animation/TextAnimator.kt index 4f01d7fcdb..978943ae7f 100644 --- a/systemUI/anim/src/com/android/systemui/animation/TextAnimator.kt +++ b/systemUI/anim/src/com/android/systemui/animation/TextAnimator.kt @@ -25,45 +25,40 @@ import android.graphics.Typeface import android.graphics.fonts.Font import android.graphics.fonts.FontVariationAxis import android.text.Layout -import android.util.Log import android.util.LruCache -import androidx.annotation.VisibleForTesting -import com.android.app.animation.Interpolators +import kotlin.math.roundToInt +import android.util.Log + +private const val DEFAULT_ANIMATION_DURATION: Long = 300 +private const val TYPEFACE_CACHE_MAX_ENTRIES = 5 typealias GlyphCallback = (TextAnimator.PositionedGlyph, Float) -> Unit interface TypefaceVariantCache { - val fontCache: FontCache - val animationFrameCount: Int - fun getTypefaceForVariant(fvar: String?): Typeface? companion object { - @JvmStatic fun createVariantTypeface(baseTypeface: Typeface, fVar: String?): Typeface { if (fVar.isNullOrEmpty()) { return baseTypeface } - val axes = - FontVariationAxis.fromFontVariationSettings(fVar)?.toMutableList() - ?: mutableListOf() + val axes = FontVariationAxis.fromFontVariationSettings(fVar) + ?.toMutableList() + ?: mutableListOf() axes.removeIf { !baseTypeface.isSupportedAxes(it.getOpenTypeTagValue()) } - if (axes.isEmpty()) { return baseTypeface - } else { - return Typeface.createFromTypefaceWithVariation(baseTypeface, axes) } + return Typeface.createFromTypefaceWithVariation(baseTypeface, axes) } } } -class TypefaceVariantCacheImpl(var baseTypeface: Typeface, override val animationFrameCount: Int) : - TypefaceVariantCache { +class TypefaceVariantCacheImpl( + var baseTypeface: Typeface, +) : TypefaceVariantCache { private val cache = LruCache(TYPEFACE_CACHE_MAX_ENTRIES) - override val fontCache = FontCacheImpl(animationFrameCount) - override fun getTypefaceForVariant(fvar: String?): Typeface? { if (fvar == null) { return baseTypeface @@ -76,14 +71,6 @@ class TypefaceVariantCacheImpl(var baseTypeface: Typeface, override val animatio cache.put(fvar, it) } } - - companion object { - private const val TYPEFACE_CACHE_MAX_ENTRIES = 5 - } -} - -interface TextAnimatorListener : TextInterpolatorListener { - fun onInvalidate() {} } /** @@ -113,21 +100,45 @@ interface TextAnimatorListener : TextInterpolatorListener { */ class TextAnimator( layout: Layout, - private val typefaceCache: TypefaceVariantCache, - private val listener: TextAnimatorListener? = null, + numberOfAnimationSteps: Int? = null, // Only do this number of discrete animation steps. + private val invalidateCallback: () -> Unit, ) { - var textInterpolator = TextInterpolator(layout, typefaceCache, listener) - @VisibleForTesting var createAnimator: () -> ValueAnimator = { ValueAnimator.ofFloat(1f) } + var typefaceCache: TypefaceVariantCache = TypefaceVariantCacheImpl(layout.paint.typeface) + get() = field + set(value) { + field = value + textInterpolator.typefaceCache = value + } - var animator: ValueAnimator? = null + // Following two members are for mutable for testing purposes. + public var textInterpolator: TextInterpolator = + TextInterpolator(layout, typefaceCache, numberOfAnimationSteps) + public var animator: ValueAnimator = + ValueAnimator.ofFloat(1f).apply { + duration = DEFAULT_ANIMATION_DURATION + addUpdateListener { + textInterpolator.progress = + calculateProgress(it.animatedValue as Float, numberOfAnimationSteps) + invalidateCallback() + } + addListener( + object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) = textInterpolator.rebase() + override fun onAnimationCancel(animation: Animator) = textInterpolator.rebase() + } + ) + } - val progress: Float - get() = textInterpolator.progress + private fun calculateProgress(animProgress: Float, numberOfAnimationSteps: Int?): Float { + if (numberOfAnimationSteps != null) { + // This clamps the progress to the nearest value of "numberOfAnimationSteps" + // discrete values between 0 and 1f. + return (animProgress * numberOfAnimationSteps).roundToInt() / + numberOfAnimationSteps.toFloat() + } - val linearProgress: Float - get() = textInterpolator.linearProgress - - val fontVariationUtils = FontVariationUtils() + return animProgress + } sealed class PositionedGlyph { /** Mutable X coordinate of the glyph position relative from drawing offset. */ @@ -166,6 +177,8 @@ class TextAnimator( protected set } + private val fontVariationUtils = FontVariationUtils() + fun updateLayout(layout: Layout, textSize: Float = -1f) { textInterpolator.layout = layout @@ -177,8 +190,9 @@ class TextAnimator( } } - val isRunning: Boolean - get() = animator?.isRunning ?: false + fun isRunning(): Boolean { + return animator.isRunning + } /** * GlyphFilter applied just before drawing to canvas for tweaking positions and text size. @@ -199,14 +213,12 @@ class TextAnimator( * * Here is an example of font runs: "fin. 終わり" * - * ``` * Characters : f i n . _ 終 わ り * Code Points: \u0066 \u0069 \u006E \u002E \u0020 \u7D42 \u308F \u308A * Font Runs : <-- Roboto-Regular.ttf --><-- NotoSans-CJK.otf --> * runStart = 0, runLength = 5 runStart = 5, runLength = 3 * Glyph IDs : 194 48 7 8 4367 1039 1002 * Glyph Index: 0 1 2 3 0 1 2 - * ``` * * In this example, the "fi" is converted into ligature form, thus the single glyph ID is * assigned for two characters, f and i. @@ -235,130 +247,163 @@ class TextAnimator( fun draw(c: Canvas) = textInterpolator.draw(c) - /** Style spec to use when rendering the font */ - data class Style( - val fVar: String? = null, - val textSize: Float? = null, - val color: Int? = null, - val strokeWidth: Float? = null, - ) { - fun withUpdatedFVar( - fontVariationUtils: FontVariationUtils, - weight: Int = -1, - width: Int = -1, - opticalSize: Int = -1, - roundness: Int = -1, - ): Style { - return this.copy( - fVar = - fontVariationUtils.updateFontVariation( - weight = weight, - width = width, - opticalSize = opticalSize, - roundness = roundness, - ) - ) - } - } - - /** Animation Spec for use when style changes should be animated */ - data class Animation( - val animate: Boolean = true, - val startDelay: Long = 0, - val duration: Long = DEFAULT_ANIMATION_DURATION, - val interpolator: TimeInterpolator = Interpolators.LINEAR, - val onAnimationEnd: Runnable? = null, - ) { - fun configureAnimator(animator: Animator) { - animator.startDelay = startDelay - animator.duration = duration - animator.interpolator = interpolator - if (onAnimationEnd != null) { - animator.addListener( - object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - onAnimationEnd.run() - } - } - ) - } - } - - companion object { - val DISABLED = Animation(animate = false) - } - } - - /** Sets the text style, optionally with animation */ - fun setTextStyle(style: Style, animation: Animation = Animation.DISABLED) { - animator?.cancel() - setTextStyleInternal(style, rebase = animation.animate) - - if (animation.animate) { - animator = buildAnimator(animation).apply { start() } - } else { - textInterpolator.progress = 1f - textInterpolator.linearProgress = 1f - textInterpolator.rebase() - listener?.onInvalidate() - } - } - - /** Builds a ValueAnimator from the specified animation parameters */ - private fun buildAnimator(animation: Animation): ValueAnimator { - return createAnimator().apply { - duration = DEFAULT_ANIMATION_DURATION - animation.configureAnimator(this) - - addUpdateListener { - textInterpolator.progress = it.animatedValue as Float - textInterpolator.linearProgress = it.currentPlayTime / it.duration.toFloat() - listener?.onInvalidate() - } - - addListener( - object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animator: Animator) = textInterpolator.rebase() - - override fun onAnimationCancel(animator: Animator) = textInterpolator.rebase() - } - ) - } - } + /** + * Set text style with animation. + * + * By passing -1 to weight, the view preserve the current weight. + * By passing -1 to textSize, the view preserve the current text size. + * Bu passing -1 to duration, the default text animation, 1000ms, is used. + * By passing false to animate, the text will be updated without animation. + * + * @param fvar an optional text fontVariationSettings. + * @param textSize an optional font size. + * @param colors an optional colors array that must be the same size as numLines passed to + * the TextInterpolator + * @param strokeWidth an optional paint stroke width + * @param animate an optional boolean indicating true for showing style transition as animation, + * false for immediate style transition. True by default. + * @param duration an optional animation duration in milliseconds. This is ignored if animate is + * false. + * @param interpolator an optional time interpolator. If null is passed, last set interpolator + * will be used. This is ignored if animate is false. + */ + fun setTextStyle( + fvar: String? = "", + textSize: Float = -1f, + color: Int? = null, + strokeWidth: Float = -1f, + animate: Boolean = true, + duration: Long = -1L, + interpolator: TimeInterpolator? = null, + delay: Long = 0, + onAnimationEnd: Runnable? = null, + ) = setTextStyleInternal(fvar, textSize, color, strokeWidth, animate, duration, + interpolator, delay, onAnimationEnd, updateLayoutOnFailure = true) private fun setTextStyleInternal( - style: Style, - rebase: Boolean, - updateLayoutOnFailure: Boolean = true, + fvar: String?, + textSize: Float, + color: Int?, + strokeWidth: Float, + animate: Boolean, + duration: Long, + interpolator: TimeInterpolator?, + delay: Long, + onAnimationEnd: Runnable?, + updateLayoutOnFailure: Boolean, ) { try { - if (rebase) textInterpolator.rebase() - style.color?.let { textInterpolator.targetPaint.color = it } - style.textSize?.let { textInterpolator.targetPaint.textSize = it } - style.strokeWidth?.let { textInterpolator.targetPaint.strokeWidth = it } - style.fVar?.let { - textInterpolator.targetPaint.typeface = typefaceCache.getTypefaceForVariant(it) + if (animate) { + animator.cancel() + textInterpolator.rebase() + } + + if (textSize >= 0) { + textInterpolator.targetPaint.textSize = textSize + } + if (!fvar.isNullOrBlank()) { + textInterpolator.targetPaint.typeface = typefaceCache.getTypefaceForVariant(fvar) + } + if (color != null) { + textInterpolator.targetPaint.color = color + } + if (strokeWidth >= 0F) { + textInterpolator.targetPaint.strokeWidth = strokeWidth } textInterpolator.onTargetPaintModified() + + if (animate) { + animator.startDelay = delay + animator.duration = + if (duration == -1L) { + DEFAULT_ANIMATION_DURATION + } else { + duration + } + interpolator?.let { animator.interpolator = it } + if (onAnimationEnd != null) { + val listener = object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + onAnimationEnd.run() + animator.removeListener(this) + } + override fun onAnimationCancel(animation: Animator) { + animator.removeListener(this) + } + } + animator.addListener(listener) + } + animator.start() + } else { + // No animation is requested, thus set base and target state to the same state. + textInterpolator.progress = 1f + textInterpolator.rebase() + invalidateCallback() + } } catch (ex: IllegalArgumentException) { if (updateLayoutOnFailure) { - Log.e( - TAG, - "setTextStyleInternal: Exception caught but retrying. This is usually" + - " due to the layout having changed unexpectedly without being notified.", - ex, - ) - + Log.e(TAG, "setTextStyleInternal: Exception caught but retrying. This is usually" + + " due to the layout having changed unexpectedly without being notified.", ex) updateLayout(textInterpolator.layout) - setTextStyleInternal(style, rebase, updateLayoutOnFailure = false) + setTextStyleInternal(fvar, textSize, color, strokeWidth, animate, duration, + interpolator, delay, onAnimationEnd, updateLayoutOnFailure = false) } else { throw ex } } } + /** + * Set text style with animation. Similar as + * fun setTextStyle( + * fvar: String? = "", + * textSize: Float = -1f, + * color: Int? = null, + * strokeWidth: Float = -1f, + * animate: Boolean = true, + * duration: Long = -1L, + * interpolator: TimeInterpolator? = null, + * delay: Long = 0, + * onAnimationEnd: Runnable? = null + * ) + * + * @param weight an optional style value for `wght` in fontVariationSettings. + * @param width an optional style value for `wdth` in fontVariationSettings. + * @param opticalSize an optional style value for `opsz` in fontVariationSettings. + * @param roundness an optional style value for `ROND` in fontVariationSettings. + */ + fun setTextStyle( + weight: Int = -1, + width: Int = -1, + opticalSize: Int = -1, + roundness: Int = -1, + textSize: Float = -1f, + color: Int? = null, + strokeWidth: Float = -1f, + animate: Boolean = true, + duration: Long = -1L, + interpolator: TimeInterpolator? = null, + delay: Long = 0, + onAnimationEnd: Runnable? = null + ) = setTextStyleInternal( + fvar = fontVariationUtils.updateFontVariation( + weight = weight, + width = width, + opticalSize = opticalSize, + roundness = roundness, + ), + textSize = textSize, + color = color, + strokeWidth = strokeWidth, + animate = animate, + duration = duration, + interpolator = interpolator, + delay = delay, + onAnimationEnd = onAnimationEnd, + updateLayoutOnFailure = true, + ) + companion object { private val TAG = TextAnimator::class.simpleName!! - const val DEFAULT_ANIMATION_DURATION = 300L } } diff --git a/systemUI/anim/src/com/android/systemui/animation/TextInterpolator.kt b/systemUI/anim/src/com/android/systemui/animation/TextInterpolator.kt index 22c5258edb..02caeeddd7 100644 --- a/systemUI/anim/src/com/android/systemui/animation/TextInterpolator.kt +++ b/systemUI/anim/src/com/android/systemui/animation/TextInterpolator.kt @@ -27,19 +27,12 @@ import android.util.MathUtils import com.android.internal.graphics.ColorUtils import java.lang.Math.max -interface TextInterpolatorListener { - fun onPaintModified() {} - - fun onRebased() {} -} - /** Provide text style linear interpolation for plain text. */ class TextInterpolator( layout: Layout, var typefaceCache: TypefaceVariantCache, - private val listener: TextInterpolatorListener? = null, + numberOfAnimationSteps: Int? = null, ) { - /** * Returns base paint used for interpolation. * @@ -73,7 +66,7 @@ class TextInterpolator( val start: Int, // inclusive val end: Int, // exclusive var baseFont: Font, - var targetFont: Font, + var targetFont: Font ) { val length: Int get() = end - start @@ -86,14 +79,14 @@ class TextInterpolator( val baseY: FloatArray, // same length as glyphIds val targetX: FloatArray, // same length as glyphIds val targetY: FloatArray, // same length as glyphIds - val fontRuns: List, + val fontRuns: List ) /** A class represents text layout of a single line. */ private class Line(val runs: List) private var lines = listOf() - private val fontInterpolator = FontInterpolator(typefaceCache.fontCache) + private val fontInterpolator = FontInterpolator(numberOfAnimationSteps) // Recycling object for glyph drawing and tweaking. private val tmpPaint = TextPaint() @@ -109,9 +102,6 @@ class TextInterpolator( */ var progress: Float = 0f - /** Linear progress value (not interpolated) */ - var linearProgress: Float = 0f - /** * The layout used for drawing text. * @@ -147,7 +137,6 @@ class TextInterpolator( */ fun onTargetPaintModified() { updatePositionsAndFonts(shapeText(layout, targetPaint), updateBase = false) - listener?.onPaintModified() } /** @@ -158,7 +147,6 @@ class TextInterpolator( */ fun onBasePaintModified() { updatePositionsAndFonts(shapeText(layout, basePaint), updateBase = true) - listener?.onPaintModified() } /** @@ -217,7 +205,6 @@ class TextInterpolator( */ fun rebase() { if (progress == 0f) { - listener?.onRebased() return } else if (progress == 1f) { basePaint.set(targetPaint) @@ -234,12 +221,7 @@ class TextInterpolator( } run.fontRuns.forEach { fontRun -> fontRun.baseFont = - fontInterpolator.lerp( - fontRun.baseFont, - fontRun.targetFont, - progress, - linearProgress, - ) + fontInterpolator.lerp(fontRun.baseFont, fontRun.targetFont, progress) val fvar = FontVariationAxis.toFontVariationSettings(fontRun.baseFont.axes) basePaint.typeface = typefaceCache.getTypefaceForVariant(fvar) } @@ -247,8 +229,6 @@ class TextInterpolator( } progress = 0f - linearProgress = 0f - listener?.onRebased() } /** @@ -363,16 +343,12 @@ class TextInterpolator( private class MutablePositionedGlyph : TextAnimator.PositionedGlyph() { override var runStart: Int = 0 public set - override var runLength: Int = 0 public set - override var glyphIndex: Int = 0 public set - override lateinit var font: Font public set - override var glyphId: Int = 0 public set } @@ -382,7 +358,7 @@ class TextInterpolator( // Draws single font run. private fun drawFontRun(c: Canvas, line: Run, run: FontRun, lineNo: Int, paint: Paint) { var arrayIndex = 0 - val font = fontInterpolator.lerp(run.baseFont, run.targetFont, progress, linearProgress) + val font = fontInterpolator.lerp(run.baseFont, run.targetFont, progress) val glyphFilter = glyphFilter if (glyphFilter == null) { @@ -425,7 +401,7 @@ class TextInterpolator( 0, i - prevStart, font, - tmpPaintForGlyph, + tmpPaintForGlyph ) prevStart = i arrayIndex = 0 @@ -442,13 +418,13 @@ class TextInterpolator( 0, run.end - prevStart, font, - tmpPaintForGlyph, + tmpPaintForGlyph ) } private fun updatePositionsAndFonts( layoutResult: List>, - updateBase: Boolean, + updateBase: Boolean ) { // Update target positions with newly calculated text layout. check(layoutResult.size == lines.size) { "The new layout result has different line count." } @@ -531,7 +507,7 @@ class TextInterpolator( lineStart, count, layout.textDirectionHeuristic, - paint, + paint ) { _, _, glyphs, _ -> runs.add(glyphs) } diff --git a/systemUI/anim/src/com/android/systemui/animation/TransitionAnimator.kt b/systemUI/anim/src/com/android/systemui/animation/TransitionAnimator.kt index 8886b9e5e2..8e824e60d4 100644 --- a/systemUI/anim/src/com/android/systemui/animation/TransitionAnimator.kt +++ b/systemUI/anim/src/com/android/systemui/animation/TransitionAnimator.kt @@ -20,33 +20,18 @@ import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.animation.ValueAnimator import android.content.Context -import android.graphics.PointF import android.graphics.PorterDuff import android.graphics.PorterDuffXfermode import android.graphics.drawable.GradientDrawable -import android.util.FloatProperty import android.util.Log import android.util.MathUtils -import android.util.TimeUtils -import android.view.Choreographer import android.view.View import android.view.ViewGroup -import android.view.ViewGroupOverlay -import android.view.ViewOverlay import android.view.animation.Interpolator -import android.window.WindowAnimationState +import androidx.annotation.VisibleForTesting import com.android.app.animation.Interpolators.LINEAR -import com.android.internal.annotations.VisibleForTesting -import com.android.internal.dynamicanimation.animation.SpringAnimation -import com.android.internal.dynamicanimation.animation.SpringForce -import com.android.systemui.Flags -import com.android.systemui.Flags.moveTransitionAnimationLayer import com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary -import com.android.systemui.shared.Flags.returnAnimationFrameworkLongLived import java.util.concurrent.Executor -import kotlin.math.abs -import kotlin.math.max -import kotlin.math.min import kotlin.math.roundToInt private const val TAG = "TransitionAnimator" @@ -56,27 +41,11 @@ class TransitionAnimator( private val mainExecutor: Executor, private val timings: Timings, private val interpolators: Interpolators, - - /** [springTimings] and [springInterpolators] must either both be null or both not null. */ - private val springTimings: SpringTimings? = null, - private val springInterpolators: Interpolators? = null, - private val springParams: SpringParams = DEFAULT_SPRING_PARAMS, ) { companion object { internal const val DEBUG = false private val SRC_MODE = PorterDuffXfermode(PorterDuff.Mode.SRC) - /** Default parameters for the multi-spring animator. */ - private val DEFAULT_SPRING_PARAMS = - SpringParams( - centerXStiffness = 450f, - centerXDampingRatio = 0.965f, - centerYStiffness = 400f, - centerYDampingRatio = 0.95f, - scaleStiffness = 500f, - scaleDampingRatio = 0.99f, - ) - /** * Given the [linearProgress] of a transition animation, return the linear progress of the * sub-animation starting [delay] ms after the transition animation and that lasts @@ -87,83 +56,19 @@ class TransitionAnimator( timings: Timings, linearProgress: Float, delay: Long, - duration: Long, - ): Float { - return getProgressInternal( - timings.totalDuration.toFloat(), - linearProgress, - delay.toFloat(), - duration.toFloat(), - ) - } - - /** - * Similar to [getProgress] above, bug the delay and duration are expressed as percentages - * of the animation duration (between 0f and 1f). - */ - internal fun getProgress(linearProgress: Float, delay: Float, duration: Float): Float { - return getProgressInternal(totalDuration = 1f, linearProgress, delay, duration) - } - - private fun getProgressInternal( - totalDuration: Float, - linearProgress: Float, - delay: Float, - duration: Float, + duration: Long ): Float { return MathUtils.constrain( - (linearProgress * totalDuration - delay) / duration, + (linearProgress * timings.totalDuration - delay) / duration, 0.0f, - 1.0f, + 1.0f ) } - fun assertReturnAnimations() { - check(returnAnimationsEnabled()) { - "isLaunching cannot be false when the returnAnimationFrameworkLibrary flag " + - "is disabled" - } - } - - fun returnAnimationsEnabled() = returnAnimationFrameworkLibrary() - - fun assertLongLivedReturnAnimations() { - check(longLivedReturnAnimationsEnabled()) { - "Long-lived registrations cannot be used when the " + - "returnAnimationFrameworkLibrary or the " + - "returnAnimationFrameworkLongLived flag are disabled" - } - } - - fun longLivedReturnAnimationsEnabled() = - returnAnimationFrameworkLibrary() && returnAnimationFrameworkLongLived() - - internal fun WindowAnimationState.toTransitionState() = - State().also { - bounds?.let { b -> - it.top = b.top.roundToInt() - it.left = b.left.roundToInt() - it.bottom = b.bottom.roundToInt() - it.right = b.right.roundToInt() - } - it.bottomCornerRadius = (bottomLeftRadius + bottomRightRadius) / 2 - it.topCornerRadius = (topLeftRadius + topRightRadius) / 2 - } - - /** Builds a [FloatProperty] for updating the defined [property] using a spring. */ - private fun buildProperty( - property: SpringProperty, - updateProgress: (SpringState) -> Unit, - ): FloatProperty { - return object : FloatProperty(property.name) { - override fun get(state: SpringState): Float { - return property.get(state) - } - - override fun setValue(state: SpringState, value: Float) { - property.setValue(state, value) - updateProgress(state) - } + internal fun checkReturnAnimationFrameworkFlag() { + check(returnAnimationFrameworkLibrary()) { + "isLaunching cannot be false when the returnAnimationFrameworkLibrary flag is " + + "disabled" } } } @@ -171,10 +76,6 @@ class TransitionAnimator( private val transitionContainerLocation = IntArray(2) private val cornerRadii = FloatArray(8) - init { - check((springTimings == null) == (springInterpolators == null)) - } - /** * A controller that takes care of applying the animation to an expanding view. * @@ -215,15 +116,6 @@ class TransitionAnimator( val openingWindowSyncView: View? get() = null - /** - * Window state for the animation. If [isLaunching], it would correspond to the end state - * otherwise the start state. - * - * If null, the state is inferred from the window targets - */ - val windowAnimatorState: WindowAnimationState? - get() = null - /** * Return the [State] of the view that will be animated. We will animate from this state to * the final window state. @@ -259,7 +151,7 @@ class TransitionAnimator( var left: Int = 0, var right: Int = 0, var topCornerRadius: Float = 0f, - var bottomCornerRadius: Float = 0f, + var bottomCornerRadius: Float = 0f ) { private val startTop = top @@ -282,151 +174,11 @@ class TransitionAnimator( var visible: Boolean = true } - /** Encapsulated the state of a multi-spring animation. */ - internal class SpringState( - // Animated values. - var centerX: Float, - var centerY: Float, - var scale: Float = 0f, - - // Update flags (used to decide whether it's time to update the transition state). - var isCenterXUpdated: Boolean = false, - var isCenterYUpdated: Boolean = false, - var isScaleUpdated: Boolean = false, - - // Completion flags. - var isCenterXDone: Boolean = false, - var isCenterYDone: Boolean = false, - var isScaleDone: Boolean = false, - ) { - /** Whether all springs composing the animation have settled in the final position. */ - val isDone - get() = isCenterXDone && isCenterYDone && isScaleDone - } - - /** Supported [SpringState] properties with getters and setters to update them. */ - private enum class SpringProperty { - CENTER_X { - override fun get(state: SpringState): Float { - return state.centerX - } - - override fun setValue(state: SpringState, value: Float) { - state.centerX = value - state.isCenterXUpdated = true - } - }, - CENTER_Y { - override fun get(state: SpringState): Float { - return state.centerY - } - - override fun setValue(state: SpringState, value: Float) { - state.centerY = value - state.isCenterYUpdated = true - } - }, - SCALE { - override fun get(state: SpringState): Float { - return state.scale - } - - override fun setValue(state: SpringState, value: Float) { - state.scale = value - state.isScaleUpdated = true - } - }; - - /** Extracts the current value of the underlying property from [state]. */ - abstract fun get(state: SpringState): Float - - /** Update's the [value] of the underlying property inside [state]. */ - abstract fun setValue(state: SpringState, value: Float) - } - interface Animation { - /** Start the animation. */ - fun start() - /** Cancel the animation. */ fun cancel() } - @VisibleForTesting - class InterpolatedAnimation(@get:VisibleForTesting val animator: Animator) : Animation { - override fun start() { - animator.start() - } - - override fun cancel() { - animator.cancel() - } - } - - @VisibleForTesting - class MultiSpringAnimation - internal constructor( - @get:VisibleForTesting val springX: SpringAnimation, - @get:VisibleForTesting val springY: SpringAnimation, - @get:VisibleForTesting val springScale: SpringAnimation, - private val springState: SpringState, - private val startFrameTime: Long, - private val onAnimationStart: Runnable, - ) : Animation { - @get:VisibleForTesting - val isDone - get() = springState.isDone - - override fun start() { - onAnimationStart.run() - - // If no start frame time is provided, we start the springs normally. - if (startFrameTime < 0) { - startSprings() - return - } - - // This function is not guaranteed to be called inside a frame. We try to access the - // frame time immediately, but if we're not inside a frame this will throw an exception. - // We must then post a callback to be run at the beginning of the next frame. - try { - initAndStartSprings(Choreographer.getInstance().frameTime) - } catch (_: IllegalStateException) { - Choreographer.getInstance().postFrameCallback { frameTimeNanos -> - initAndStartSprings(frameTimeNanos / TimeUtils.NANOS_PER_MS) - } - } - } - - private fun initAndStartSprings(frameTime: Long) { - // Initialize the spring as if it had started at the time that its start state - // was created. - springX.doAnimationFrame(startFrameTime) - springY.doAnimationFrame(startFrameTime) - springScale.doAnimationFrame(startFrameTime) - // Move the spring time forward to the current frame, so it updates its internal state - // following the initial momentum over the elapsed time. - springX.doAnimationFrame(frameTime) - springY.doAnimationFrame(frameTime) - springScale.doAnimationFrame(frameTime) - // Actually start the spring. We do this after the previous calls because the framework - // doesn't like it when you call doAnimationFrame() after start() with an earlier time. - startSprings() - } - - private fun startSprings() { - springX.start() - springY.start() - springScale.start() - } - - override fun cancel() { - springX.cancel() - springY.cancel() - springScale.cancel() - } - } - /** The timings (durations and delays) used by this animator. */ data class Timings( /** The total duration of the animation. */ @@ -445,26 +197,7 @@ class TransitionAnimator( val contentAfterFadeInDelay: Long, /** The duration of the expanded content fade in. */ - val contentAfterFadeInDuration: Long, - ) - - /** - * The timings (durations and delays) used by the multi-spring animator. These are expressed as - * fractions of 1, similar to how the progress of an animator can be expressed as a float value - * between 0 and 1. - */ - class SpringTimings( - /** The portion of animation to wait before fading out the expanding content. */ - val contentBeforeFadeOutDelay: Float, - - /** The portion of animation during which the expanding content fades out. */ - val contentBeforeFadeOutDuration: Float, - - /** The portion of animation to wait before fading in the expanded content. */ - val contentAfterFadeInDelay: Float, - - /** The portion of animation during which the expanded content fades in. */ - val contentAfterFadeInDuration: Float, + val contentAfterFadeInDuration: Long ) /** The interpolators used by this animator. */ @@ -482,22 +215,7 @@ class TransitionAnimator( val contentBeforeFadeOutInterpolator: Interpolator, /** The interpolator used when fading in the expanded content. */ - val contentAfterFadeInInterpolator: Interpolator, - ) - - /** The parameters (stiffnesses and damping ratios) used by the multi-spring animator. */ - data class SpringParams( - // Parameters for the X position spring. - val centerXStiffness: Float, - val centerXDampingRatio: Float, - - // Parameters for the Y position spring. - val centerYStiffness: Float, - val centerYDampingRatio: Float, - - // Parameters for the scale spring. - val scaleStiffness: Float, - val scaleDampingRatio: Float, + val contentAfterFadeInInterpolator: Interpolator ) /** @@ -510,13 +228,6 @@ class TransitionAnimator( * the animation (if ![Controller.isLaunching]), and will have SRC blending mode (ultimately * punching a hole in the [transition container][Controller.transitionContainer]) iff [drawHole] * is true. - * - * TODO(b/397646693): remove drawHole altogether. - * - * If [startVelocity] (expressed in pixels per second) is not null, a multi-spring animation - * using it for the initial momentum will be used instead of the default interpolators. In this - * case, [startFrameTime] (if non-negative) represents the frame time at which the springs - * should be started. */ fun startAnimation( controller: Controller, @@ -524,11 +235,8 @@ class TransitionAnimator( windowBackgroundColor: Int, fadeWindowBackgroundLayer: Boolean = true, drawHole: Boolean = false, - startVelocity: PointF? = null, - startFrameTime: Long = -1, ): Animation { - if (!controller.isLaunching) assertReturnAnimations() - if (startVelocity != null) assertLongLivedReturnAnimations() + if (!controller.isLaunching) checkReturnAnimationFrameworkFlag() // We add an extra layer with the same color as the dialog/app splash screen background // color, which is usually the same color of the app background. We first fade in this layer @@ -540,95 +248,33 @@ class TransitionAnimator( alpha = 0 } - return createAnimation( + val animator = + createAnimator( controller, - controller.createAnimatorState(), endState, windowBackgroundLayer, fadeWindowBackgroundLayer, - drawHole, - startVelocity, - startFrameTime, + drawHole ) - .apply { start() } - } + animator.start() - @VisibleForTesting - fun createAnimation( - controller: Controller, - startState: State, - endState: State, - windowBackgroundLayer: GradientDrawable, - fadeWindowBackgroundLayer: Boolean = true, - drawHole: Boolean = false, - startVelocity: PointF? = null, - startFrameTime: Long = -1, - ): Animation { - val transitionContainer = controller.transitionContainer - val transitionContainerOverlay = transitionContainer.overlay - val openingWindowSyncView = controller.openingWindowSyncView - val openingWindowSyncViewOverlay = openingWindowSyncView?.overlay - - // Whether we should move the [windowBackgroundLayer] into the overlay of - // [Controller.openingWindowSyncView] once the opening app window starts to be visible, or - // from it once the closing app window stops being visible. - // This is necessary as a one-off sync so we can avoid syncing at every frame, especially - // in complex interactions like launching an activity from a dialog. See - // b/214961273#comment2 for more details. - val moveBackgroundLayerWhenAppVisibilityChanges = - openingWindowSyncView != null && - openingWindowSyncView.viewRootImpl != controller.transitionContainer.viewRootImpl - - return if (startVelocity != null && springTimings != null && springInterpolators != null) { - createSpringAnimation( - controller, - startState, - endState, - startVelocity, - startFrameTime, - windowBackgroundLayer, - transitionContainer, - transitionContainerOverlay, - openingWindowSyncView, - openingWindowSyncViewOverlay, - fadeWindowBackgroundLayer, - drawHole, - moveBackgroundLayerWhenAppVisibilityChanges, - ) - } else { - createInterpolatedAnimation( - controller, - startState, - endState, - windowBackgroundLayer, - transitionContainer, - transitionContainerOverlay, - openingWindowSyncView, - openingWindowSyncViewOverlay, - fadeWindowBackgroundLayer, - drawHole, - moveBackgroundLayerWhenAppVisibilityChanges, - ) + return object : Animation { + override fun cancel() { + animator.cancel() + } } } - /** - * Creates an interpolator-based animator that uses [timings] and [interpolators] to calculate - * the new bounds and corner radiuses at each frame. - */ - private fun createInterpolatedAnimation( + @VisibleForTesting + fun createAnimator( controller: Controller, - state: State, endState: State, windowBackgroundLayer: GradientDrawable, - transitionContainer: View, - transitionContainerOverlay: ViewGroupOverlay, - openingWindowSyncView: View? = null, - openingWindowSyncViewOverlay: ViewOverlay? = null, fadeWindowBackgroundLayer: Boolean = true, - drawHole: Boolean = false, - moveBackgroundLayerWhenAppVisibilityChanges: Boolean = false, - ): Animation { + drawHole: Boolean = false + ): ValueAnimator { + val state = controller.createAnimatorState() + // Start state. val startTop = state.top val startBottom = state.bottom @@ -665,35 +311,60 @@ class TransitionAnimator( } } + val transitionContainer = controller.transitionContainer val isExpandingFullyAbove = isExpandingFullyAbove(transitionContainer, endState) - var movedBackgroundLayer = false // Update state. val animator = ValueAnimator.ofFloat(0f, 1f) animator.duration = timings.totalDuration animator.interpolator = LINEAR + // Whether we should move the [windowBackgroundLayer] into the overlay of + // [Controller.openingWindowSyncView] once the opening app window starts to be visible, or + // from it once the closing app window stops being visible. + // This is necessary as a one-off sync so we can avoid syncing at every frame, especially + // in complex interactions like launching an activity from a dialog. See + // b/214961273#comment2 for more details. + val openingWindowSyncView = controller.openingWindowSyncView + val openingWindowSyncViewOverlay = openingWindowSyncView?.overlay + val moveBackgroundLayerWhenAppVisibilityChanges = + openingWindowSyncView != null && + openingWindowSyncView.viewRootImpl != controller.transitionContainer.viewRootImpl + + val transitionContainerOverlay = transitionContainer.overlay + var movedBackgroundLayer = false + animator.addListener( object : AnimatorListenerAdapter() { override fun onAnimationStart(animation: Animator, isReverse: Boolean) { - onAnimationStart( - controller, - isExpandingFullyAbove, - windowBackgroundLayer, - transitionContainerOverlay, - openingWindowSyncViewOverlay, - ) + if (DEBUG) { + Log.d(TAG, "Animation started") + } + controller.onTransitionAnimationStart(isExpandingFullyAbove) + + // Add the drawable to the transition container overlay. Overlays always draw + // drawables after views, so we know that it will be drawn above any view added + // by the controller. + if (controller.isLaunching || openingWindowSyncViewOverlay == null) { + transitionContainerOverlay.add(windowBackgroundLayer) + } else { + openingWindowSyncViewOverlay.add(windowBackgroundLayer) + } } override fun onAnimationEnd(animation: Animator) { - onAnimationEnd( - controller, - isExpandingFullyAbove, - windowBackgroundLayer, - transitionContainerOverlay, - openingWindowSyncViewOverlay, - moveBackgroundLayerWhenAppVisibilityChanges, - ) + if (DEBUG) { + Log.d(TAG, "Animation ended") + } + + // TODO(b/330672236): Post this to the main thread instead so that it does not + // flicker with Flexiglass enabled. + controller.onTransitionAnimationEnd(isExpandingFullyAbove) + transitionContainerOverlay.remove(windowBackgroundLayer) + + if (moveBackgroundLayerWhenAppVisibilityChanges && controller.isLaunching) { + openingWindowSyncViewOverlay?.remove(windowBackgroundLayer) + } } } ) @@ -720,20 +391,63 @@ class TransitionAnimator( state.bottomCornerRadius = MathUtils.lerp(startBottomCornerRadius, endBottomCornerRadius, progress) - state.visible = checkVisibility(timings, linearProgress, controller.isLaunching) + state.visible = + if (controller.isLaunching) { + // The expanding view can/should be hidden once it is completely covered by the + // opening window. + getProgress( + timings, + linearProgress, + timings.contentBeforeFadeOutDelay, + timings.contentBeforeFadeOutDuration + ) < 1 + } else { + getProgress( + timings, + linearProgress, + timings.contentAfterFadeInDelay, + timings.contentAfterFadeInDuration + ) > 0 + } - if (!movedBackgroundLayer) { - movedBackgroundLayer = - maybeMoveBackgroundLayer( - controller, - state, - windowBackgroundLayer, - transitionContainer, - transitionContainerOverlay, - openingWindowSyncView, - openingWindowSyncViewOverlay, - moveBackgroundLayerWhenAppVisibilityChanges, - ) + if ( + controller.isLaunching && + moveBackgroundLayerWhenAppVisibilityChanges && + !state.visible && + !movedBackgroundLayer + ) { + // The expanding view is not visible, so the opening app is visible. If this is + // the first frame when it happens, trigger a one-off sync and move the + // background layer in its new container. + movedBackgroundLayer = true + + transitionContainerOverlay.remove(windowBackgroundLayer) + openingWindowSyncViewOverlay!!.add(windowBackgroundLayer) + + ViewRootSync.synchronizeNextDraw( + transitionContainer, + openingWindowSyncView, + then = {} + ) + } else if ( + !controller.isLaunching && + moveBackgroundLayerWhenAppVisibilityChanges && + state.visible && + !movedBackgroundLayer + ) { + // The contracting view is now visible, so the closing app is not. If this is + // the first frame when it happens, trigger a one-off sync and move the + // background layer in its new container. + movedBackgroundLayer = true + + openingWindowSyncViewOverlay!!.remove(windowBackgroundLayer) + transitionContainerOverlay.add(windowBackgroundLayer) + + ViewRootSync.synchronizeNextDraw( + openingWindowSyncView, + transitionContainer, + then = {} + ) } val container = @@ -742,6 +456,7 @@ class TransitionAnimator( } else { controller.transitionContainer } + applyStateToWindowBackgroundLayer( windowBackgroundLayer, state, @@ -749,365 +464,12 @@ class TransitionAnimator( container, fadeWindowBackgroundLayer, drawHole, - controller.isLaunching, - useSpring = false, + controller.isLaunching ) - controller.onTransitionAnimationProgress(state, progress, linearProgress) } - return InterpolatedAnimation(animator) - } - - /** - * Creates a compound animator made up of three springs: one for the center x position, one for - * the center-y position, and one for the overall scale. - * - * This animator uses [springTimings] and [springInterpolators] for opacity, based on the scale - * progress. - */ - private fun createSpringAnimation( - controller: Controller, - startState: State, - endState: State, - startVelocity: PointF, - startFrameTime: Long, - windowBackgroundLayer: GradientDrawable, - transitionContainer: View, - transitionContainerOverlay: ViewGroupOverlay, - openingWindowSyncView: View?, - openingWindowSyncViewOverlay: ViewOverlay?, - fadeWindowBackgroundLayer: Boolean = true, - drawHole: Boolean = false, - moveBackgroundLayerWhenAppVisibilityChanges: Boolean = false, - ): Animation { - var springX: SpringAnimation? = null - var springY: SpringAnimation? = null - var targetX = endState.centerX - var targetY = endState.centerY - - var movedBackgroundLayer = false - - fun maybeUpdateEndState() { - if (endState.centerX != targetX && endState.centerY != targetY) { - targetX = endState.centerX - targetY = endState.centerY - - springX?.animateToFinalPosition(targetX) - springY?.animateToFinalPosition(targetY) - } - } - - fun updateProgress(state: SpringState) { - if ( - !(state.isCenterXUpdated || state.isCenterXDone) || - !(state.isCenterYUpdated || state.isCenterYDone) || - !(state.isScaleUpdated || state.isScaleDone) - ) { - // Because all three springs use the same update method, we only actually update - // when all properties have received their new value (which could be unchanged from - // the previous one), avoiding two redundant calls per frame. - return - } - - // Reset the update flags. - state.isCenterXUpdated = false - state.isCenterYUpdated = false - state.isScaleUpdated = false - - // Current scale-based values, that will be used to find the new animation bounds. - val width = - MathUtils.lerp(startState.width.toFloat(), endState.width.toFloat(), state.scale) - val height = - MathUtils.lerp(startState.height.toFloat(), endState.height.toFloat(), state.scale) - - val newState = - State( - left = (state.centerX - width / 2).toInt(), - top = (state.centerY - height / 2).toInt(), - right = (state.centerX + width / 2).toInt(), - bottom = (state.centerY + height / 2).toInt(), - topCornerRadius = - MathUtils.lerp( - startState.topCornerRadius, - endState.topCornerRadius, - state.scale, - ), - bottomCornerRadius = - MathUtils.lerp( - startState.bottomCornerRadius, - endState.bottomCornerRadius, - state.scale, - ), - ) - .apply { - visible = checkVisibility(timings, state.scale, controller.isLaunching) - } - - if (!movedBackgroundLayer) { - movedBackgroundLayer = - maybeMoveBackgroundLayer( - controller, - newState, - windowBackgroundLayer, - transitionContainer, - transitionContainerOverlay, - openingWindowSyncView, - openingWindowSyncViewOverlay, - moveBackgroundLayerWhenAppVisibilityChanges, - ) - } - - val container = - if (movedBackgroundLayer) { - openingWindowSyncView!! - } else { - controller.transitionContainer - } - applyStateToWindowBackgroundLayer( - windowBackgroundLayer, - newState, - state.scale, - container, - fadeWindowBackgroundLayer, - drawHole, - isLaunching = false, - useSpring = true, - ) - - controller.onTransitionAnimationProgress(newState, state.scale, state.scale) - - maybeUpdateEndState() - } - - val springState = SpringState(centerX = startState.centerX, centerY = startState.centerY) - val isExpandingFullyAbove = isExpandingFullyAbove(transitionContainer, endState) - - /** End listener for each spring, which only does the end work if all springs are done. */ - fun onAnimationEnd() { - if (!springState.isDone) return - onAnimationEnd( - controller, - isExpandingFullyAbove, - windowBackgroundLayer, - transitionContainerOverlay, - openingWindowSyncViewOverlay, - moveBackgroundLayerWhenAppVisibilityChanges, - ) - } - - springX = - SpringAnimation( - springState, - buildProperty(SpringProperty.CENTER_X) { state -> updateProgress(state) }, - ) - .apply { - spring = - SpringForce(endState.centerX).apply { - stiffness = springParams.centerXStiffness - dampingRatio = springParams.centerXDampingRatio - } - - setStartValue(startState.centerX) - setStartVelocity(startVelocity.x) - setMinValue(min(startState.centerX, endState.centerX)) - setMaxValue(max(startState.centerX, endState.centerX)) - - addEndListener { _, _, _, _ -> - springState.isCenterXDone = true - onAnimationEnd() - } - } - springY = - SpringAnimation( - springState, - buildProperty(SpringProperty.CENTER_Y) { state -> updateProgress(state) }, - ) - .apply { - spring = - SpringForce(endState.centerY).apply { - stiffness = springParams.centerYStiffness - dampingRatio = springParams.centerYDampingRatio - } - - setStartValue(startState.centerY) - setStartVelocity(startVelocity.y) - setMinValue(min(startState.centerY, endState.centerY)) - setMaxValue(max(startState.centerY, endState.centerY)) - - addEndListener { _, _, _, _ -> - springState.isCenterYDone = true - onAnimationEnd() - } - } - val springScale = - SpringAnimation( - springState, - buildProperty(SpringProperty.SCALE) { state -> updateProgress(state) }, - ) - .apply { - spring = - SpringForce(1f).apply { - stiffness = springParams.scaleStiffness - dampingRatio = springParams.scaleDampingRatio - } - - setStartValue(0f) - setMaxValue(1f) - setMinimumVisibleChange(abs(1f / startState.height)) - - addEndListener { _, _, _, _ -> - springState.isScaleDone = true - onAnimationEnd() - } - } - - return MultiSpringAnimation(springX, springY, springScale, springState, startFrameTime) { - onAnimationStart( - controller, - isExpandingFullyAbove, - windowBackgroundLayer, - transitionContainerOverlay, - openingWindowSyncViewOverlay, - ) - } - } - - private fun onAnimationStart( - controller: Controller, - isExpandingFullyAbove: Boolean, - windowBackgroundLayer: GradientDrawable, - transitionContainerOverlay: ViewGroupOverlay, - openingWindowSyncViewOverlay: ViewOverlay?, - ) { - if (DEBUG) { - Log.d(TAG, "Animation started") - } - controller.onTransitionAnimationStart(isExpandingFullyAbove) - - // Add the drawable to the transition container overlay. Overlays always draw - // drawables after views, so we know that it will be drawn above any view added - // by the controller. - if (controller.isLaunching || openingWindowSyncViewOverlay == null) { - transitionContainerOverlay.add(windowBackgroundLayer) - } else { - openingWindowSyncViewOverlay.add(windowBackgroundLayer) - } - } - - private fun onAnimationEnd( - controller: Controller, - isExpandingFullyAbove: Boolean, - windowBackgroundLayer: GradientDrawable, - transitionContainerOverlay: ViewGroupOverlay, - openingWindowSyncViewOverlay: ViewOverlay?, - moveBackgroundLayerWhenAppVisibilityChanges: Boolean, - ) { - if (DEBUG) { - Log.d(TAG, "Animation ended") - } - - val onEnd = { - controller.onTransitionAnimationEnd(isExpandingFullyAbove) - transitionContainerOverlay.remove(windowBackgroundLayer) - - if (moveBackgroundLayerWhenAppVisibilityChanges && controller.isLaunching) { - openingWindowSyncViewOverlay?.remove(windowBackgroundLayer) - } - } - if (Flags.sceneContainer() || !controller.isLaunching) { - // onAnimationEnd is called at the end of the animation, on a Choreographer animation - // tick. During dialog launches, the following calls will move the animated content from - // the dialog overlay back to its original position, and this change must be reflected - // in the next frame given that we then sync the next frame of both the content and - // dialog ViewRoots. During SysUI activity launches, we will instantly collapse the - // shade at the end of the transition. However, if those are rendered by Compose, whose - // compositions are also scheduled on a Choreographer frame, any state change made - // *right now* won't be reflected in the next frame given that a Choreographer frame - // can't schedule another and have it happen in the same frame. So we post the forwarded - // calls to [Controller.onLaunchAnimationEnd] in the main executor, leaving this - // Choreographer frame, ensuring that any state change applied by - // onTransitionAnimationEnd() will be reflected in the same frame. - mainExecutor.execute { onEnd() } - } else { - onEnd() - } - } - - /** Returns whether is the controller's view should be visible with the given [timings]. */ - private fun checkVisibility(timings: Timings, progress: Float, isLaunching: Boolean): Boolean { - return if (isLaunching) { - // The expanding view can/should be hidden once it is completely covered by the opening - // window. - getProgress( - timings, - progress, - timings.contentBeforeFadeOutDelay, - timings.contentBeforeFadeOutDuration, - ) < 1 - } else { - // The shrinking view can/should be hidden while it is completely covered by the closing - // window. - getProgress( - timings, - progress, - timings.contentAfterFadeInDelay, - timings.contentAfterFadeInDuration, - ) > 0 - } - } - - /** - * If necessary, moves the background layer from the view container's overlay to the window sync - * view overlay, or vice versa. - * - * @return true if the background layer vwas moved, false otherwise. - */ - private fun maybeMoveBackgroundLayer( - controller: Controller, - state: State, - windowBackgroundLayer: GradientDrawable, - transitionContainer: View, - transitionContainerOverlay: ViewGroupOverlay, - openingWindowSyncView: View?, - openingWindowSyncViewOverlay: ViewOverlay?, - moveBackgroundLayerWhenAppVisibilityChanges: Boolean, - ): Boolean { - if ( - controller.isLaunching && moveBackgroundLayerWhenAppVisibilityChanges && !state.visible - ) { - // The expanding view is not visible, so the opening app is visible. If this is the - // first frame when it happens, trigger a one-off sync and move the background layer - // in its new container. - transitionContainerOverlay.remove(windowBackgroundLayer) - openingWindowSyncViewOverlay!!.add(windowBackgroundLayer) - - ViewRootSync.synchronizeNextDraw( - transitionContainer, - openingWindowSyncView!!, - then = {}, - ) - - return true - } else if ( - !controller.isLaunching && moveBackgroundLayerWhenAppVisibilityChanges && state.visible - ) { - // The contracting view is now visible, so the closing app is not. If this is the first - // frame when it happens, trigger a one-off sync and move the background layer in its - // new container. - openingWindowSyncViewOverlay!!.remove(windowBackgroundLayer) - transitionContainerOverlay.add(windowBackgroundLayer) - - ViewRootSync.synchronizeNextDraw( - openingWindowSyncView!!, - transitionContainer, - then = {}, - ) - - return true - } - - return false + return animator } /** Return whether we are expanding fully above the [transitionContainer]. */ @@ -1126,8 +488,7 @@ class TransitionAnimator( transitionContainer: View, fadeWindowBackgroundLayer: Boolean, drawHole: Boolean, - isLaunching: Boolean, - useSpring: Boolean, + isLaunching: Boolean ) { // Update position. transitionContainer.getLocationOnScreen(transitionContainerLocation) @@ -1135,7 +496,7 @@ class TransitionAnimator( state.left - transitionContainerLocation[0], state.top - transitionContainerLocation[1], state.right - transitionContainerLocation[0], - state.bottom - transitionContainerLocation[1], + state.bottom - transitionContainerLocation[1] ) // Update radius. @@ -1149,51 +510,29 @@ class TransitionAnimator( cornerRadii[7] = state.bottomCornerRadius drawable.cornerRadii = cornerRadii - val interpolators: Interpolators - val fadeInProgress: Float - val fadeOutProgress: Float - if (useSpring) { - interpolators = springInterpolators!! - val timings = springTimings!! - fadeInProgress = - getProgress( - linearProgress, - timings.contentBeforeFadeOutDelay, - timings.contentBeforeFadeOutDuration, - ) - fadeOutProgress = - getProgress( - linearProgress, - timings.contentAfterFadeInDelay, - timings.contentAfterFadeInDuration, - ) - } else { - interpolators = this.interpolators - fadeInProgress = - getProgress( - timings, - linearProgress, - timings.contentBeforeFadeOutDelay, - timings.contentBeforeFadeOutDuration, - ) - fadeOutProgress = - getProgress( - timings, - linearProgress, - timings.contentAfterFadeInDelay, - timings.contentAfterFadeInDuration, - ) - } + // We first fade in the background layer to hide the expanding view, then fade it out + // with SRC mode to draw a hole punch in the status bar and reveal the opening window. + val fadeInProgress = + getProgress( + timings, + linearProgress, + timings.contentBeforeFadeOutDelay, + timings.contentBeforeFadeOutDuration + ) - // We first fade in the background layer to hide the expanding view, then fade it out with - // SRC mode to draw a hole punch in the status bar and reveal the opening window (if - // needed). If !isLaunching, the reverse happens. if (isLaunching) { if (fadeInProgress < 1) { val alpha = interpolators.contentBeforeFadeOutInterpolator.getInterpolation(fadeInProgress) drawable.alpha = (alpha * 0xFF).roundToInt() } else if (fadeWindowBackgroundLayer) { + val fadeOutProgress = + getProgress( + timings, + linearProgress, + timings.contentAfterFadeInDelay, + timings.contentAfterFadeInDuration + ) val alpha = 1 - interpolators.contentAfterFadeInInterpolator.getInterpolation( @@ -1204,10 +543,6 @@ class TransitionAnimator( if (drawHole) { drawable.setXfermode(SRC_MODE) } - } else if (moveTransitionAnimationLayer() && fadeOutProgress >= 1 && drawHole) { - // If [drawHole] is true, draw it once the opening content is done fading in. - drawable.alpha = 0x00 - drawable.setXfermode(SRC_MODE) } else { drawable.alpha = 0xFF } @@ -1221,6 +556,13 @@ class TransitionAnimator( drawable.setXfermode(SRC_MODE) } } else { + val fadeOutProgress = + getProgress( + timings, + linearProgress, + timings.contentAfterFadeInDelay, + timings.contentAfterFadeInDuration + ) val alpha = 1 - interpolators.contentAfterFadeInInterpolator.getInterpolation( diff --git a/systemUI/anim/src/com/android/systemui/animation/ViewHierarchyAnimator.kt b/systemUI/anim/src/com/android/systemui/animation/ViewHierarchyAnimator.kt index 300bdf2ffb..00d9056529 100644 --- a/systemUI/anim/src/com/android/systemui/animation/ViewHierarchyAnimator.kt +++ b/systemUI/anim/src/com/android/systemui/animation/ViewHierarchyAnimator.kt @@ -48,7 +48,7 @@ class ViewHierarchyAnimator { Bound.LEFT to createViewProperty(Bound.LEFT), Bound.TOP to createViewProperty(Bound.TOP), Bound.RIGHT to createViewProperty(Bound.RIGHT), - Bound.BOTTOM to createViewProperty(Bound.BOTTOM), + Bound.BOTTOM to createViewProperty(Bound.BOTTOM) ) private fun createViewProperty(bound: Bound): IntProperty { @@ -89,7 +89,7 @@ class ViewHierarchyAnimator { interpolator: Interpolator = DEFAULT_INTERPOLATOR, duration: Long = DEFAULT_DURATION, animateChildren: Boolean = true, - excludedViews: Set = emptySet(), + excludedViews: Set = emptySet() ): Boolean { return animate( rootView, @@ -97,7 +97,7 @@ class ViewHierarchyAnimator { duration, ephemeral = false, animateChildren = animateChildren, - excludedViews = excludedViews, + excludedViews = excludedViews ) } @@ -111,7 +111,7 @@ class ViewHierarchyAnimator { interpolator: Interpolator = DEFAULT_INTERPOLATOR, duration: Long = DEFAULT_DURATION, animateChildren: Boolean = true, - excludedViews: Set = emptySet(), + excludedViews: Set = emptySet() ): Boolean { return animate( rootView, @@ -119,7 +119,7 @@ class ViewHierarchyAnimator { duration, ephemeral = true, animateChildren = animateChildren, - excludedViews = excludedViews, + excludedViews = excludedViews ) } @@ -129,7 +129,7 @@ class ViewHierarchyAnimator { duration: Long, ephemeral: Boolean, animateChildren: Boolean, - excludedViews: Set = emptySet(), + excludedViews: Set = emptySet() ): Boolean { if ( !occupiesSpace( @@ -137,7 +137,7 @@ class ViewHierarchyAnimator { rootView.left, rootView.top, rootView.right, - rootView.bottom, + rootView.bottom ) ) { return false @@ -149,7 +149,7 @@ class ViewHierarchyAnimator { listener, recursive = true, animateChildren = animateChildren, - excludedViews = excludedViews, + excludedViews = excludedViews ) return true } @@ -164,7 +164,7 @@ class ViewHierarchyAnimator { private fun createUpdateListener( interpolator: Interpolator, duration: Long, - ephemeral: Boolean, + ephemeral: Boolean ): View.OnLayoutChangeListener { return createListener(interpolator, duration, ephemeral) } @@ -196,9 +196,9 @@ class ViewHierarchyAnimator { * * @param includeFadeIn true if the animator should also fade in the view and child views. * @param fadeInInterpolator the interpolator to use when fading in the view. Unused if - * [includeFadeIn] is false. - * @param onAnimationEnd an optional runnable that will be run once the animation finishes, - * regardless of whether the animation is cancelled or finishes successfully. + * [includeFadeIn] is false. + * @param onAnimationEnd an optional runnable that will be run once the animation + * finishes successfully. Will not be run if the animation is cancelled. */ @JvmOverloads fun animateAddition( @@ -217,7 +217,7 @@ class ViewHierarchyAnimator { rootView.left, rootView.top, rootView.right, - rootView.bottom, + rootView.bottom ) ) { return false @@ -241,10 +241,7 @@ class ViewHierarchyAnimator { // First, fade in the container view val containerDuration = duration / 6 createAndStartFadeInAnimator( - rootView, - containerDuration, - startDelay = 0, - interpolator = fadeInInterpolator, + rootView, containerDuration, startDelay = 0, interpolator = fadeInInterpolator ) // Then, fade in the child views @@ -256,7 +253,7 @@ class ViewHierarchyAnimator { childDuration, // Wait until the container fades in before fading in the children startDelay = containerDuration, - interpolator = fadeInInterpolator, + interpolator = fadeInInterpolator ) } // For now, we don't recursively fade in additional sub views (e.g. grandchild @@ -267,7 +264,7 @@ class ViewHierarchyAnimator { rootView, duration / 2, startDelay = 0, - interpolator = fadeInInterpolator, + interpolator = fadeInInterpolator ) } @@ -326,7 +323,7 @@ class ViewHierarchyAnimator { previousLeft: Int, previousTop: Int, previousRight: Int, - previousBottom: Int, + previousBottom: Int ) { if (view == null) return @@ -356,14 +353,14 @@ class ViewHierarchyAnimator { startTop, startRight, startBottom, - ignorePreviousValues, + ignorePreviousValues ) val endValues = mapOf( Bound.LEFT to left, Bound.TOP to top, Bound.RIGHT to right, - Bound.BOTTOM to bottom, + Bound.BOTTOM to bottom ) val boundsToAnimate = mutableSetOf() @@ -399,8 +396,8 @@ class ViewHierarchyAnimator { * added on the side(s) of the [destination], the translation of those margins can be * included by specifying [includeMargins]. * - * @param onAnimationEnd an optional runnable that will be run once the animation finishes, - * regardless of whether the animation is cancelled or finishes successfully. + * @param onAnimationEnd an optional runnable that will be run once the animation finishes + * successfully. Will not be run if the animation is cancelled. */ @JvmOverloads fun animateRemoval( @@ -417,7 +414,7 @@ class ViewHierarchyAnimator { rootView.left, rootView.top, rootView.right, - rootView.bottom, + rootView.bottom ) ) { return false @@ -461,7 +458,7 @@ class ViewHierarchyAnimator { Bound.LEFT to rootView.left, Bound.TOP to rootView.top, Bound.RIGHT to rootView.right, - Bound.BOTTOM to rootView.bottom, + Bound.BOTTOM to rootView.bottom ) val endValues = processEndValuesForRemoval( @@ -553,7 +550,7 @@ class ViewHierarchyAnimator { destination: Hotspot, endValues: Map, interpolator: Interpolator, - duration: Long, + duration: Long ) { for (i in 0 until rootView.childCount) { val child = rootView.getChildAt(i) @@ -562,7 +559,7 @@ class ViewHierarchyAnimator { Bound.LEFT to child.left, Bound.TOP to child.top, Bound.RIGHT to child.right, - Bound.BOTTOM to child.bottom, + Bound.BOTTOM to child.bottom ) val childEndValues = processChildEndValuesForRemoval( @@ -572,7 +569,7 @@ class ViewHierarchyAnimator { child.right, child.bottom, endValues.getValue(Bound.RIGHT) - endValues.getValue(Bound.LEFT), - endValues.getValue(Bound.BOTTOM) - endValues.getValue(Bound.TOP), + endValues.getValue(Bound.BOTTOM) - endValues.getValue(Bound.TOP) ) val boundsToAnimate = mutableSetOf() @@ -590,7 +587,7 @@ class ViewHierarchyAnimator { childEndValues, interpolator, duration, - ephemeral = true, + ephemeral = true ) } } @@ -604,7 +601,7 @@ class ViewHierarchyAnimator { left: Int, top: Int, right: Int, - bottom: Int, + bottom: Int ): Boolean { return visibility != View.GONE && left != right && top != bottom } @@ -619,7 +616,6 @@ class ViewHierarchyAnimator { * not newly introduced margins are included. * * Base case - * * ``` * 1) origin=TOP * x---------x x---------x x---------x x---------x x---------x @@ -640,11 +636,9 @@ class ViewHierarchyAnimator { * x-----x x-------x | | * x---------x * ``` - * * In case the start and end values differ in the direction of the origin, and * [ignorePreviousValues] is false, the previous values are used and a translation is * included in addition to the view expansion. - * * ``` * origin=TOP_LEFT - (0,0,0,0) -> (30,30,70,70) * x @@ -666,7 +660,7 @@ class ViewHierarchyAnimator { previousTop: Int, previousRight: Int, previousBottom: Int, - ignorePreviousValues: Boolean, + ignorePreviousValues: Boolean ): Map { val startLeft = if (ignorePreviousValues) newLeft else previousLeft val startTop = if (ignorePreviousValues) newTop else previousTop @@ -733,7 +727,7 @@ class ViewHierarchyAnimator { Bound.LEFT to left, Bound.TOP to top, Bound.RIGHT to right, - Bound.BOTTOM to bottom, + Bound.BOTTOM to bottom ) } @@ -783,17 +777,18 @@ class ViewHierarchyAnimator { includeMargins: Boolean = false, ): Map { val marginAdjustment = - if (includeMargins && (rootView.layoutParams is ViewGroup.MarginLayoutParams)) { + if (includeMargins && + (rootView.layoutParams is ViewGroup.MarginLayoutParams)) { val marginLp = rootView.layoutParams as ViewGroup.MarginLayoutParams DimenHolder( left = marginLp.leftMargin, top = marginLp.topMargin, right = marginLp.rightMargin, - bottom = marginLp.bottomMargin, + bottom = marginLp.bottomMargin ) - } else { - DimenHolder(0, 0, 0, 0) - } + } else { + DimenHolder(0, 0, 0, 0) + } // These are the end values to use *if* this bound is part of the destination. val endLeft = left - marginAdjustment.left @@ -810,69 +805,60 @@ class ViewHierarchyAnimator { // - If destination=BOTTOM_LEFT, then endBottom == endTop AND endLeft == endRight. return when (destination) { - Hotspot.TOP -> - mapOf( - Bound.TOP to endTop, - Bound.BOTTOM to endTop, - Bound.LEFT to left, - Bound.RIGHT to right, - ) - Hotspot.TOP_RIGHT -> - mapOf( - Bound.TOP to endTop, - Bound.BOTTOM to endTop, - Bound.RIGHT to endRight, - Bound.LEFT to endRight, - ) - Hotspot.RIGHT -> - mapOf( - Bound.RIGHT to endRight, - Bound.LEFT to endRight, - Bound.TOP to top, - Bound.BOTTOM to bottom, - ) - Hotspot.BOTTOM_RIGHT -> - mapOf( - Bound.BOTTOM to endBottom, - Bound.TOP to endBottom, - Bound.RIGHT to endRight, - Bound.LEFT to endRight, - ) - Hotspot.BOTTOM -> - mapOf( - Bound.BOTTOM to endBottom, - Bound.TOP to endBottom, - Bound.LEFT to left, - Bound.RIGHT to right, - ) - Hotspot.BOTTOM_LEFT -> - mapOf( - Bound.BOTTOM to endBottom, - Bound.TOP to endBottom, - Bound.LEFT to endLeft, - Bound.RIGHT to endLeft, - ) - Hotspot.LEFT -> - mapOf( - Bound.LEFT to endLeft, - Bound.RIGHT to endLeft, - Bound.TOP to top, - Bound.BOTTOM to bottom, - ) - Hotspot.TOP_LEFT -> - mapOf( - Bound.TOP to endTop, - Bound.BOTTOM to endTop, - Bound.LEFT to endLeft, - Bound.RIGHT to endLeft, - ) - Hotspot.CENTER -> - mapOf( - Bound.LEFT to (endLeft + endRight) / 2, - Bound.RIGHT to (endLeft + endRight) / 2, - Bound.TOP to (endTop + endBottom) / 2, - Bound.BOTTOM to (endTop + endBottom) / 2, - ) + Hotspot.TOP -> mapOf( + Bound.TOP to endTop, + Bound.BOTTOM to endTop, + Bound.LEFT to left, + Bound.RIGHT to right, + ) + Hotspot.TOP_RIGHT -> mapOf( + Bound.TOP to endTop, + Bound.BOTTOM to endTop, + Bound.RIGHT to endRight, + Bound.LEFT to endRight, + ) + Hotspot.RIGHT -> mapOf( + Bound.RIGHT to endRight, + Bound.LEFT to endRight, + Bound.TOP to top, + Bound.BOTTOM to bottom, + ) + Hotspot.BOTTOM_RIGHT -> mapOf( + Bound.BOTTOM to endBottom, + Bound.TOP to endBottom, + Bound.RIGHT to endRight, + Bound.LEFT to endRight, + ) + Hotspot.BOTTOM -> mapOf( + Bound.BOTTOM to endBottom, + Bound.TOP to endBottom, + Bound.LEFT to left, + Bound.RIGHT to right, + ) + Hotspot.BOTTOM_LEFT -> mapOf( + Bound.BOTTOM to endBottom, + Bound.TOP to endBottom, + Bound.LEFT to endLeft, + Bound.RIGHT to endLeft, + ) + Hotspot.LEFT -> mapOf( + Bound.LEFT to endLeft, + Bound.RIGHT to endLeft, + Bound.TOP to top, + Bound.BOTTOM to bottom, + ) + Hotspot.TOP_LEFT -> mapOf( + Bound.TOP to endTop, + Bound.BOTTOM to endTop, + Bound.LEFT to endLeft, + Bound.RIGHT to endLeft, + ) + Hotspot.CENTER -> mapOf( + Bound.LEFT to (endLeft + endRight) / 2, + Bound.RIGHT to (endLeft + endRight) / 2, + Bound.TOP to (endTop + endBottom) / 2, + Bound.BOTTOM to (endTop + endBottom) / 2, + ) } } @@ -901,7 +887,7 @@ class ViewHierarchyAnimator { right: Int, bottom: Int, parentWidth: Int, - parentHeight: Int, + parentHeight: Int ): Map { val halfWidth = (right - left) / 2 val halfHeight = (bottom - top) / 2 @@ -959,7 +945,7 @@ class ViewHierarchyAnimator { Bound.LEFT to endLeft, Bound.TOP to endTop, Bound.RIGHT to endRight, - Bound.BOTTOM to endBottom, + Bound.BOTTOM to endBottom ) } @@ -968,7 +954,7 @@ class ViewHierarchyAnimator { listener: View.OnLayoutChangeListener, recursive: Boolean = false, animateChildren: Boolean = true, - excludedViews: Set = emptySet(), + excludedViews: Set = emptySet() ) { if (excludedViews.contains(view)) return @@ -987,7 +973,7 @@ class ViewHierarchyAnimator { listener, recursive = true, animateChildren = animateChildren, - excludedViews = excludedViews, + excludedViews = excludedViews ) } } @@ -1041,7 +1027,7 @@ class ViewHierarchyAnimator { PropertyValuesHolder.ofInt( PROPERTIES[bound], startValues.getValue(bound), - endValues.getValue(bound), + endValues.getValue(bound) ) ) } @@ -1070,10 +1056,9 @@ class ViewHierarchyAnimator { // listener. recursivelyRemoveListener(view) } - // Run the end runnable regardless of whether the animation was cancelled or - // not - this ensures critical actions (like removing a window) always occur - // (see b/344049884). - onAnimationEnd?.run() + if (!cancelled) { + onAnimationEnd?.run() + } } override fun onAnimationCancel(animation: Animator) { @@ -1092,19 +1077,17 @@ class ViewHierarchyAnimator { view: View, duration: Long, startDelay: Long, - interpolator: Interpolator, + interpolator: Interpolator ) { val animator = ObjectAnimator.ofFloat(view, "alpha", 1f) animator.startDelay = startDelay animator.duration = duration animator.interpolator = interpolator - animator.addListener( - object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - view.setTag(R.id.tag_alpha_animator, null /* tag */) - } + animator.addListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + view.setTag(R.id.tag_alpha_animator, null /* tag */) } - ) + }) (view.getTag(R.id.tag_alpha_animator) as? ObjectAnimator)?.cancel() view.setTag(R.id.tag_alpha_animator, animator) @@ -1122,7 +1105,7 @@ class ViewHierarchyAnimator { RIGHT, BOTTOM_RIGHT, BOTTOM, - BOTTOM_LEFT, + BOTTOM_LEFT } private enum class Bound(val label: String, val overrideTag: Int) { @@ -1164,10 +1147,14 @@ class ViewHierarchyAnimator { }; abstract fun setValue(view: View, value: Int) - abstract fun getValue(view: View): Int } /** Simple data class to hold a set of dimens for left, top, right, bottom. */ - private data class DimenHolder(val left: Int, val top: Int, val right: Int, val bottom: Int) + private data class DimenHolder( + val left: Int, + val top: Int, + val right: Int, + val bottom: Int, + ) } diff --git a/systemUI/anim/src/com/android/systemui/animation/back/BackAnimationSpec.kt b/systemUI/anim/src/com/android/systemui/animation/back/BackAnimationSpec.kt index 9e872fc5d3..6c982a0450 100644 --- a/systemUI/anim/src/com/android/systemui/animation/back/BackAnimationSpec.kt +++ b/systemUI/anim/src/com/android/systemui/animation/back/BackAnimationSpec.kt @@ -58,12 +58,7 @@ fun BackAnimationSpec.Companion.createFloatingSurfaceAnimationSpec( val maxTranslationY = maxTranslationYByScale - maxMarginYPx val minScaleReversed = 1f - minScale - val direction = - when (backEvent.swipeEdge) { - BackEvent.EDGE_LEFT -> 1 - BackEvent.EDGE_RIGHT -> -1 - else -> 0 - } + val direction = if (backEvent.swipeEdge == BackEvent.EDGE_LEFT) 1 else -1 val progressX = backEvent.progress val ratioTranslateX = translateXEasing.getInterpolation(progressX) diff --git a/systemUI/anim/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtension.kt b/systemUI/anim/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtension.kt index 05b0588094..f8f56bc0a8 100644 --- a/systemUI/anim/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtension.kt +++ b/systemUI/anim/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtension.kt @@ -17,12 +17,14 @@ package com.android.systemui.animation.back import android.annotation.IntRange +import android.os.Build import android.util.DisplayMetrics import android.view.View import android.window.BackEvent import android.window.OnBackAnimationCallback import android.window.OnBackInvokedDispatcher -import com.android.app.animation.Interpolators +import android.window.OnBackInvokedDispatcher.Priority +import androidx.annotation.RequiresApi /** * Generates an [OnBackAnimationCallback] given a [backAnimationSpec]. [onBackProgressed] will be @@ -32,6 +34,7 @@ import com.android.app.animation.Interpolators * * @sample com.android.systemui.util.registerAnimationOnBackInvoked */ +@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) fun onBackAnimationCallbackFrom( backAnimationSpec: BackAnimationSpec, displayMetrics: DisplayMetrics, // TODO(b/265060720): We could remove this @@ -40,16 +43,16 @@ fun onBackAnimationCallbackFrom( onBackInvoked: () -> Unit = {}, onBackCancelled: () -> Unit = {}, ): OnBackAnimationCallback { - return object : FlingOnBackAnimationCallback(progressInterpolator = Interpolators.LINEAR) { + return object : OnBackAnimationCallback { private var initialY = 0f private val lastTransformation = BackTransformation() - override fun onBackStartedCompat(backEvent: BackEvent) { + override fun onBackStarted(backEvent: BackEvent) { initialY = backEvent.touchY onBackStarted(backEvent) } - override fun onBackProgressedCompat(backEvent: BackEvent) { + override fun onBackProgressed(backEvent: BackEvent) { val progressY = (backEvent.touchY - initialY) / displayMetrics.heightPixels backAnimationSpec.getBackTransformation( @@ -61,11 +64,11 @@ fun onBackAnimationCallbackFrom( onBackProgressed(lastTransformation) } - override fun onBackInvokedCompat() { + override fun onBackInvoked() { onBackInvoked() } - override fun onBackCancelledCompat() { + override fun onBackCancelled() { onBackCancelled() } } @@ -76,17 +79,18 @@ fun onBackAnimationCallbackFrom( * * @sample com.android.systemui.util.registerAnimationOnBackInvoked */ +@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) fun View.registerOnBackInvokedCallbackOnViewAttached( onBackInvokedDispatcher: OnBackInvokedDispatcher, onBackAnimationCallback: OnBackAnimationCallback, - @OnBackInvokedDispatcher.Priority @IntRange(from = 0) priority: Int = OnBackInvokedDispatcher.PRIORITY_DEFAULT, + @Priority @IntRange(from = 0) priority: Int = OnBackInvokedDispatcher.PRIORITY_DEFAULT, ) { addOnAttachStateChangeListener( object : View.OnAttachStateChangeListener { override fun onViewAttachedToWindow(v: View) { onBackInvokedDispatcher.registerOnBackInvokedCallback( priority, - onBackAnimationCallback, + onBackAnimationCallback ) } diff --git a/systemUI/anim/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt b/systemUI/anim/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt index f426aa597a..025c8b9dce 100644 --- a/systemUI/anim/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt +++ b/systemUI/anim/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt @@ -70,19 +70,6 @@ class TurbulenceNoiseShader(val baseType: Type = Type.SIMPLEX_NOISE) : } """ - private const val SIMPLEX_SIMPLE_SHADER = - """ - vec4 main(vec2 p) { - vec2 uv = p / in_size.xy; - uv.x *= in_aspectRatio; - - // Compute turbulence effect with the uv distorted with simplex noise. - vec3 noisePos = vec3(uv + in_noiseMove.xy, in_noiseMove.z) * in_gridNum; - float mixFactor = simplex3d(noisePos) * 0.5 + 0.5; - return mix(in_color, in_screenColor, mixFactor); - } - """ - private const val FRACTAL_SHADER = """ vec4 main(vec2 p) { @@ -168,8 +155,6 @@ class TurbulenceNoiseShader(val baseType: Type = Type.SIMPLEX_NOISE) : return sparkleLayer; } """ - private const val SIMPLEX_NOISE_SIMPLE_SHADER = - ShaderUtilLibrary.SHADER_LIB + UNIFORMS + SIMPLEX_SIMPLE_SHADER private const val SIMPLEX_NOISE_SHADER = ShaderUtilLibrary.SHADER_LIB + UNIFORMS + COMMON_FUNCTIONS + SIMPLEX_SHADER private const val FRACTAL_NOISE_SHADER = @@ -178,20 +163,17 @@ class TurbulenceNoiseShader(val baseType: Type = Type.SIMPLEX_NOISE) : ShaderUtilLibrary.SHADER_LIB + UNIFORMS + COMMON_FUNCTIONS + SIMPLEX_SPARKLE_SHADER enum class Type { - /** Effect with a color noise turbulence with luma matte. */ + /** Effect with a simple color noise turbulence. */ SIMPLEX_NOISE, - /** Effect with a noise interpolating between foreground and background colors. */ - SIMPLEX_NOISE_SIMPLE, /** Effect with a simple color noise turbulence, with fractal. */ SIMPLEX_NOISE_FRACTAL, /** Effect with color & sparkle turbulence with screen color layer. */ - SIMPLEX_NOISE_SPARKLE, + SIMPLEX_NOISE_SPARKLE } fun getShader(type: Type): String { return when (type) { Type.SIMPLEX_NOISE -> SIMPLEX_NOISE_SHADER - Type.SIMPLEX_NOISE_SIMPLE -> SIMPLEX_NOISE_SIMPLE_SHADER Type.SIMPLEX_NOISE_FRACTAL -> FRACTAL_NOISE_SHADER Type.SIMPLEX_NOISE_SPARKLE -> SPARKLE_NOISE_SHADER } @@ -224,15 +206,15 @@ class TurbulenceNoiseShader(val baseType: Type = Type.SIMPLEX_NOISE) : setFloatUniform("in_pixelDensity", pixelDensity) } - /** - * Sets the noise color of the effect. Alpha is ignored for all types except - * [Type.SIMPLEX_NOISE_SIMPLE]. - */ + /** Sets the noise color of the effect. Alpha is ignored. */ fun setColor(color: Int) { setColorUniform("in_color", color) } - /** Sets the color that is used for blending on top of the background color/image. */ + /** + * Sets the color that is used for blending on top of the background color/image. Only relevant + * to [Type.SIMPLEX_NOISE_SPARKLE]. + */ fun setScreenColor(color: Int) { setColorUniform("in_screenColor", color) } @@ -277,7 +259,7 @@ class TurbulenceNoiseShader(val baseType: Type = Type.SIMPLEX_NOISE) : */ fun setLumaMatteFactors( lumaMatteBlendFactor: Float = 1f, - lumaMatteOverallBrightness: Float = 0f, + lumaMatteOverallBrightness: Float = 0f ) { setFloatUniform("in_lumaMatteBlendFactor", lumaMatteBlendFactor) setFloatUniform("in_lumaMatteOverallBrightness", lumaMatteOverallBrightness) @@ -297,10 +279,8 @@ class TurbulenceNoiseShader(val baseType: Type = Type.SIMPLEX_NOISE) : /** Current noise movements in x, y, and z axes. */ var noiseOffsetX: Float = 0f private set - var noiseOffsetY: Float = 0f private set - var noiseOffsetZ: Float = 0f private set diff --git a/systemUI/anim/src/com/android/systemui/util/Dialog.kt b/systemUI/anim/src/com/android/systemui/util/Dialog.kt index 9dd23289d8..ce9e17c9e8 100644 --- a/systemUI/anim/src/com/android/systemui/util/Dialog.kt +++ b/systemUI/anim/src/com/android/systemui/util/Dialog.kt @@ -17,11 +17,13 @@ package com.android.systemui.util import android.app.Dialog +import android.os.Build import android.view.View import android.view.ViewGroup import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.widget.FrameLayout import android.window.OnBackInvokedDispatcher +import androidx.annotation.RequiresApi import com.android.systemui.animation.back.BackAnimationSpec import com.android.systemui.animation.back.BackTransformation import com.android.systemui.animation.back.applyTo @@ -35,6 +37,7 @@ import com.android.systemui.animation.view.LaunchableFrameLayout * The [BackTransformation] will be applied on the [targetView]. */ @JvmOverloads +@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) fun Dialog.registerAnimationOnBackInvoked( targetView: View, backAnimationSpec: BackAnimationSpec = diff --git a/systemUI/common/Android.bp b/systemUI/common/Android.bp index 9f1598364d..6fc13d7e0d 100644 --- a/systemUI/common/Android.bp +++ b/systemUI/common/Android.bp @@ -22,17 +22,20 @@ package { default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"], } -java_library { +android_library { + name: "SystemUICommon", + use_resource_processor: true, srcs: [ "src/**/*.java", "src/**/*.kt", ], - static_libs: ["SystemUI-shared-utils"], - - libs: ["//frameworks/libs/systemui:tracinglib-platform"], + static_libs: [ + "androidx.core_core-ktx", + ], + manifest: "AndroidManifest.xml", kotlincflags: ["-Xjvm-default=all"], } diff --git a/systemUI/common/README.md b/systemUI/common/README.md index 17e5412669..1cc5277aa8 100644 --- a/systemUI/common/README.md +++ b/systemUI/common/README.md @@ -1,9 +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. +`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. +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/systemUI/common/build.gradle b/systemUI/common/build.gradle index fbee1b025a..f528016016 100644 --- a/systemUI/common/build.gradle +++ b/systemUI/common/build.gradle @@ -4,7 +4,6 @@ plugins { } android { - buildToolsVersion "36.1.0" namespace "com.android.systemui.common" sourceSets { @@ -15,4 +14,4 @@ android { } } -addFrameworkJar('framework-16.jar') +addFrameworkJar('framework-15.jar') diff --git a/systemUI/log/Android.bp b/systemUI/log/Android.bp index afdcae481d..627ac4b7c3 100644 --- a/systemUI/log/Android.bp +++ b/systemUI/log/Android.bp @@ -13,7 +13,6 @@ // limitations under the License. package { - default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_", // See: http://go/android-license-faq // A large-scale-change added 'default_applicable_licenses' to import // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license" @@ -22,15 +21,18 @@ package { default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"], } -java_library { +android_library { name: "SystemUILogLib", srcs: [ "src/**/*.java", "src/**/*.kt", ], static_libs: [ + "androidx.core_core-ktx", + "androidx.annotation_annotation", "error_prone_annotations", "SystemUICommon", ], + manifest: "AndroidManifest.xml", kotlincflags: ["-Xjvm-default=all"], } diff --git a/systemUI/log/build.gradle b/systemUI/log/build.gradle index eb49c410f5..019cab7767 100644 --- a/systemUI/log/build.gradle +++ b/systemUI/log/build.gradle @@ -4,7 +4,6 @@ plugins { } android { - buildToolsVersion "36.1.0" namespace "com.android.systemui.log" buildFeatures { aidl true @@ -18,7 +17,7 @@ android { } } -addFrameworkJar('framework-16.jar') +addFrameworkJar('framework-15.jar') compileOnlyCommonJars() dependencies { diff --git a/systemUI/log/src/com/android/systemui/log/LogBuffer.kt b/systemUI/log/src/com/android/systemui/log/LogBuffer.kt index d1c7e2fe51..4b5e9de2cc 100644 --- a/systemUI/log/src/com/android/systemui/log/LogBuffer.kt +++ b/systemUI/log/src/com/android/systemui/log/LogBuffer.kt @@ -16,6 +16,7 @@ package com.android.systemui.log +import android.os.Trace import android.util.Log import com.android.systemui.common.buffer.RingBuffer import com.android.systemui.log.core.LogLevel @@ -74,7 +75,6 @@ constructor( private val maxSize: Int, private val logcatEchoTracker: LogcatEchoTracker, private val systrace: Boolean = true, - private val systraceTrackName: String = DEFAULT_LOGBUFFER_TRACK_NAME, ) : MessageBuffer { private val buffer = RingBuffer(maxSize) { LogMessageImpl.create() } @@ -193,7 +193,7 @@ constructor( logcatEchoTracker.isBufferLoggable(name, message.level) || logcatEchoTracker.isTagLoggable(message.tag, message.level) - val includeInSystrace = systrace && false + val includeInSystrace = systrace && Trace.isTagEnabled(Trace.TRACE_TAG_APP) if (includeInLogcat || includeInSystrace) { val strMessage = message.messagePrinter(message) @@ -244,7 +244,11 @@ constructor( } private fun echoToSystrace(level: LogLevel, tag: String, strMessage: String) { - return // LC-Ignored + Trace.instantForTrack( + Trace.TRACE_TAG_APP, + "UI Events", + "$name - ${level.shortString} $tag: $strMessage" + ) } private fun echoToLogcat(message: LogMessage, strMessage: String) { @@ -257,10 +261,6 @@ constructor( LogLevel.WTF -> Log.wtf(message.tag, strMessage, message.exception) } } - - companion object { - const val DEFAULT_LOGBUFFER_TRACK_NAME = "UI Events" - } } private const val TAG = "LogBuffer" diff --git a/systemUI/log/src/com/android/systemui/log/core/LogLevel.kt b/systemUI/log/src/com/android/systemui/log/core/LogLevel.kt index a30d099066..d30d8e9fe0 100644 --- a/systemUI/log/src/com/android/systemui/log/core/LogLevel.kt +++ b/systemUI/log/src/com/android/systemui/log/core/LogLevel.kt @@ -19,7 +19,7 @@ package com.android.systemui.log.core import android.util.Log /** Enum version of @Log.Level */ -enum class LogLevel(val nativeLevel: Int, val shortString: String) { +enum class LogLevel(@Log.Level val nativeLevel: Int, val shortString: String) { VERBOSE(Log.VERBOSE, "V"), DEBUG(Log.DEBUG, "D"), INFO(Log.INFO, "I"), diff --git a/systemUI/plugin/Android.bp b/systemUI/plugin/Android.bp index 2cd3346934..bb47a2f472 100644 --- a/systemUI/plugin/Android.bp +++ b/systemUI/plugin/Android.bp @@ -13,7 +13,6 @@ // limitations under the License. package { - default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_", // See: http://go/android-license-faq // A large-scale-change added 'default_applicable_licenses' to import // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license" @@ -23,25 +22,15 @@ package { } java_library { + name: "SystemUIPluginLib", + srcs: [ - "bcsmartspace/src/**/*.java", - "bcsmartspace/src/**/*.kt", "src/**/*.java", "src/**/*.kt", + "bcsmartspace/src/**/*.java", + "bcsmartspace/src/**/*.kt", ], - exclude_srcs: [ - "src/**/PluginProtectorStub.kt", - ], - - optimize: { - proguard_flags_files: [ - "proguard_plugins.flags", - ], - export_proguard_flags_files: true, - }, - - plugins: ["PluginAnnotationProcessor"], // If you add a static lib here, you may need to also add the package to the ClassLoaderFilter // in PluginInstance. That will ensure that loaded plugins have access to the related classes. @@ -49,22 +38,18 @@ java_library { // of the library which are used by the plugins but not by systemui itself. static_libs: [ "androidx.annotation_annotation", - "androidx-constraintlayout_constraintlayout", - "PlatformAnimationLib", "PluginCoreLib", + "SystemUIAnimationLib", "SystemUICommon", "SystemUILogLib", - "androidx.annotation_annotation", - "androidx.compose.ui_ui", - "androidx.compose.runtime_runtime", ], + } android_app { // Dummy to generate .toc files. name: "PluginDummyLib", - use_resource_processor: true, platform_apis: true, srcs: ["src/**/*.java"], diff --git a/systemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceConfigPlugin.kt b/systemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceConfigPlugin.kt index d16017a5cb..509f022310 100644 --- a/systemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceConfigPlugin.kt +++ b/systemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceConfigPlugin.kt @@ -21,8 +21,4 @@ package com.android.systemui.plugins interface BcSmartspaceConfigPlugin { /** Gets default date/weather disabled status. */ val isDefaultDateWeatherDisabled: Boolean - /** Gets if Smartspace should use ViewPager2 */ - val isViewPager2Enabled: Boolean - /** Gets if card swipe event should be logged */ - val isSwipeEventLoggingEnabled: Boolean } diff --git a/systemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java b/systemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java index d7b4cba101..9ad4012cfd 100644 --- a/systemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java +++ b/systemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java @@ -24,7 +24,6 @@ import android.app.smartspace.uitemplatedata.TapAction; import android.content.ActivityNotFoundException; import android.content.Intent; import android.graphics.drawable.Drawable; -import android.os.Handler; import android.os.Parcelable; import android.util.Log; import android.view.View; @@ -85,10 +84,6 @@ public interface BcSmartspaceDataPlugin extends Plugin { throw new UnsupportedOperationException("Not implemented by " + getClass()); } - default SmartspaceView getLargeClockView(ViewGroup parent) { - throw new UnsupportedOperationException("Not implemented by " + getClass()); - } - /** * As the smartspace view becomes available, allow listeners to receive an event. */ @@ -107,15 +102,6 @@ public interface BcSmartspaceDataPlugin extends Plugin { void onSmartspaceTargetsUpdated(List targets); } - /** - * Sets {@link BcSmartspaceConfigPlugin}. - * - * TODO: b/259566300 - Remove once isViewPager2Enabled is fully rolled out - */ - default void registerConfigProvider(BcSmartspaceConfigPlugin configProvider) { - throw new UnsupportedOperationException("Not implemented by " + getClass()); - } - /** View to which this plugin can be registered, in order to get updates. */ interface SmartspaceView { void registerDataProvider(BcSmartspaceDataPlugin plugin); @@ -137,9 +123,6 @@ public interface BcSmartspaceDataPlugin extends Plugin { */ void setUiSurface(String uiSurface); - /** Set background handler to make binder calls. */ - void setBgHandler(Handler bgHandler); - /** * Range [0.0 - 1.0] when transitioning from Lockscreen to/from AOD */ @@ -217,13 +200,6 @@ public interface BcSmartspaceDataPlugin extends Plugin { default int getCurrentCardTopPadding() { throw new UnsupportedOperationException("Not implemented by " + getClass()); } - - /** - * Set the horizontal paddings for applicable children inside the SmartspaceView. - */ - default void setHorizontalPaddings(int horizontalPadding) { - throw new UnsupportedOperationException("Not implemented by " + getClass()); - } } /** Interface for launching Intents, which can differ on the lockscreen */ diff --git a/systemUI/plugin/build.gradle b/systemUI/plugin/build.gradle index 29f58a4f21..0fbfcc05a4 100644 --- a/systemUI/plugin/build.gradle +++ b/systemUI/plugin/build.gradle @@ -4,7 +4,6 @@ plugins { } android { - buildToolsVersion "36.1.0" namespace "com.android.systemui.plugins" buildFeatures { aidl true @@ -19,7 +18,7 @@ android { } } -addFrameworkJar('framework-16.jar') +addFrameworkJar('framework-15.jar') compileOnlyCommonJars() dependencies { diff --git a/systemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java b/systemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java index 18891dba4b..7cf56aa5c4 100644 --- a/systemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java +++ b/systemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java @@ -16,7 +16,6 @@ package com.android.systemui.plugins; import android.annotation.Nullable; import android.app.PendingIntent; -import android.content.ComponentName; import android.content.Intent; import android.os.Bundle; import android.os.UserHandle; @@ -25,8 +24,6 @@ import android.view.View; import com.android.systemui.animation.ActivityTransitionAnimator; import com.android.systemui.plugins.annotations.ProvidesInterface; -import kotlinx.coroutines.CoroutineScope; - /** * An interface to start activities. This is used as a callback from the views to * {@link PhoneStatusBar} to allow custom handling for starting the activity, i.e. dismissing the @@ -36,23 +33,6 @@ import kotlinx.coroutines.CoroutineScope; public interface ActivityStarter { int VERSION = 2; - /** - * Registers the given {@link ActivityTransitionAnimator.ControllerFactory} for launching and - * closing transitions matching the {@link ActivityTransitionAnimator.TransitionCookie} and the - * {@link ComponentName} that it contains, within the given {@link CoroutineScope}. - */ - void registerTransition( - ActivityTransitionAnimator.TransitionCookie cookie, - ActivityTransitionAnimator.ControllerFactory controllerFactory, - CoroutineScope scope); - - /** - * Unregisters the {@link ActivityTransitionAnimator.ControllerFactory} previously registered - * containing the given {@link ActivityTransitionAnimator.TransitionCookie}. If no such - * registration exists, this is a no-op. - */ - void unregisterTransition(ActivityTransitionAnimator.TransitionCookie cookie); - void startPendingIntentDismissingKeyguard(PendingIntent intent); /** @@ -104,17 +84,14 @@ public interface ActivityStarter { * Similar to {@link #startPendingIntentMaybeDismissingKeyguard(PendingIntent, Runnable, * ActivityTransitionAnimator.Controller)}, but also specifies a fill-in intent and extra * option that could be used to populate the pending intent and launch the activity. This also - * allows the caller to avoid dismissing the shade. An optional custom message can be set as - * the unlock reason in the alternate bouncer. + * allows the caller to avoid dismissing the shade. */ void startPendingIntentMaybeDismissingKeyguard(PendingIntent intent, boolean dismissShade, @Nullable Runnable intentSentUiThreadCallback, @Nullable ActivityTransitionAnimator.Controller animationController, @Nullable Intent fillInIntent, - @Nullable Bundle extraOptions, - @Nullable String customMessage - ); + @Nullable Bundle extraOptions); /** * The intent flag can be specified in startActivity(). @@ -143,11 +120,6 @@ public interface ActivityStarter { void postStartActivityDismissingKeyguard(Intent intent, int delay, @Nullable ActivityTransitionAnimator.Controller animationController, @Nullable String customMessage); - /** Posts a start activity intent that dismisses keyguard. */ - void postStartActivityDismissingKeyguard(Intent intent, int delay, - @Nullable ActivityTransitionAnimator.Controller animationController, - @Nullable String customMessage, - @Nullable UserHandle userHandle); void postStartActivityDismissingKeyguard(PendingIntent intent); /** @@ -162,20 +134,14 @@ public interface ActivityStarter { void dismissKeyguardThenExecute(OnDismissAction action, @Nullable Runnable cancel, boolean afterKeyguardGone); - /** - * Authenticates if needed and dismisses keyguard to execute an action. - * - * TODO(b/348431835) Display the custom message in the new alternate bouncer, when the - * device_entry_udfps_refactor flag is enabled. - */ + /** Authenticates if needed and dismisses keyguard to execute an action. */ void dismissKeyguardThenExecute(OnDismissAction action, @Nullable Runnable cancel, boolean afterKeyguardGone, @Nullable String customMessage); /** Starts an activity and dismisses keyguard. */ void startActivityDismissingKeyguard(Intent intent, boolean onlyProvisioned, - boolean dismissShade, - @Nullable String customMessage); + boolean dismissShade); /** Starts an activity and dismisses keyguard. */ void startActivityDismissingKeyguard(Intent intent, diff --git a/systemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java b/systemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java index 43185fd086..403c7c5004 100644 --- a/systemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java +++ b/systemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java @@ -36,9 +36,6 @@ import java.util.Collection; public interface DarkIconDispatcher { int VERSION = 2; - /** Called when work should stop and resources should be cleaned up. */ - default void stop() {} - /** * Sets the dark area so {@link #applyDark} only affects the icons in the specified area. * diff --git a/systemUI/plugin/src/com/android/systemui/plugins/VolumeDialog.java b/systemUI/plugin/src/com/android/systemui/plugins/VolumeDialog.java index cd86402620..9e5db73cf8 100644 --- a/systemUI/plugin/src/com/android/systemui/plugins/VolumeDialog.java +++ b/systemUI/plugin/src/com/android/systemui/plugins/VolumeDialog.java @@ -26,7 +26,6 @@ import com.android.systemui.plugins.annotations.ProvidesInterface; @DependsOn(target = Callback.class) public interface VolumeDialog extends Plugin { String ACTION = "com.android.systemui.action.PLUGIN_VOLUME"; - String ACTION_VOLUME_UNDO = "com.android.systemui.volume.ACTION_VOLUME_UNDO"; int VERSION = 1; void init(int windowType, Callback callback); diff --git a/systemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java b/systemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java index c09509d869..b1736b1687 100644 --- a/systemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java +++ b/systemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java @@ -14,6 +14,7 @@ package com.android.systemui.plugins; +import android.annotation.IntegerRes; import android.content.ComponentName; import android.media.AudioManager; import android.media.AudioSystem; @@ -21,8 +22,6 @@ import android.os.Handler; import android.os.VibrationEffect; import android.util.SparseArray; -import androidx.annotation.StringRes; - import com.android.systemui.plugins.VolumeDialogController.Callbacks; import com.android.systemui.plugins.VolumeDialogController.State; import com.android.systemui.plugins.VolumeDialogController.StreamState; @@ -91,7 +90,7 @@ public interface VolumeDialogController { public int levelMax; public boolean muted; public boolean muteSupported; - public @StringRes int name; + public @IntegerRes int name; public String remoteLabel; public boolean routedToBluetooth; diff --git a/systemUI/plugin/src/com/android/systemui/plugins/clocks/AlarmData.kt b/systemUI/plugin/src/com/android/systemui/plugins/clocks/AlarmData.kt new file mode 100644 index 0000000000..837857bfa3 --- /dev/null +++ b/systemUI/plugin/src/com/android/systemui/plugins/clocks/AlarmData.kt @@ -0,0 +1,6 @@ +package com.android.systemui.plugins.clocks + +data class AlarmData( + val nextAlarmMillis: Long?, + val descriptionId: String?, +) diff --git a/systemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt b/systemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt new file mode 100644 index 0000000000..8ba8e40768 --- /dev/null +++ b/systemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt @@ -0,0 +1,122 @@ +/* + * 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. + */ +package com.android.systemui.plugins.clocks + +import com.android.internal.annotations.Keep +import org.json.JSONObject + +/** Identifies a clock design */ +typealias ClockId = String + +data class AodClockBurnInModel( + val scale: Float, + val translationX: Float, + val translationY: Float, +) + +/** Tick rates for clocks */ +enum class ClockTickRate(val value: Int) { + PER_MINUTE(2), // Update the clock once per minute. + PER_SECOND(1), // Update the clock once per second. + PER_FRAME(0), // Update the clock every second. +} + +/** Some data about a clock design */ +data class ClockMetadata( + val clockId: ClockId, +) + +/** Render configuration for the full clock. Modifies the way systemUI behaves with this clock. */ +data class ClockConfig( + val id: String, + + /** Localized name of the clock */ + val name: String, + + /** Localized accessibility description for the clock */ + val description: String, + + /** 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, + + /** True if the clock is large frame clock, which will use weather in compose. */ + val useCustomClockScene: Boolean = false, +) + +/** Render configuration options for a clock face. Modifies the way SystemUI behaves. */ +data class ClockFaceConfig( + /** Expected interval between calls to onTimeTick. Can always reduce to PER_MINUTE in AOD. */ + val tickRate: ClockTickRate = ClockTickRate.PER_MINUTE, + + /** Call to check whether the clock consumes weather data */ + val hasCustomWeatherDataDisplay: Boolean = false, + + /** + * Whether this clock has a custom position update animation. If true, the keyguard will call + * `onPositionUpdated` to notify the clock of a position update animation. If false, a default + * animation will be used (e.g. a simple translation). + */ + val hasCustomPositionUpdatedAnimation: Boolean = false, + + /** True if the clock is large frame clock, which will use weatherBlueprint in compose. */ + val useCustomClockScene: Boolean = false, +) + +/** Structure for keeping clock-specific settings */ +@Keep +data class ClockSettings( + val clockId: ClockId? = null, + val seedColor: Int? = null, +) { + // Exclude metadata from equality checks + var metadata: JSONObject = JSONObject() + + companion object { + private val KEY_CLOCK_ID = "clockId" + private val KEY_SEED_COLOR = "seedColor" + private val KEY_METADATA = "metadata" + + fun serialize(setting: ClockSettings?): String { + if (setting == null) { + return "" + } + + return JSONObject() + .put(KEY_CLOCK_ID, setting.clockId) + .put(KEY_SEED_COLOR, setting.seedColor) + .put(KEY_METADATA, setting.metadata) + .toString() + } + + fun deserialize(jsonStr: String?): ClockSettings? { + if (jsonStr.isNullOrEmpty()) { + return null + } + + val json = JSONObject(jsonStr) + val result = + ClockSettings( + if (!json.isNull(KEY_CLOCK_ID)) json.getString(KEY_CLOCK_ID) else null, + if (!json.isNull(KEY_SEED_COLOR)) json.getInt(KEY_SEED_COLOR) else null + ) + if (!json.isNull(KEY_METADATA)) { + result.metadata = json.getJSONObject(KEY_METADATA) + } + return result + } + } +} diff --git a/systemUI/plugin/src/com/android/systemui/plugins/clocks/WeatherData.kt b/systemUI/plugin/src/com/android/systemui/plugins/clocks/WeatherData.kt new file mode 100644 index 0000000000..789a47304e --- /dev/null +++ b/systemUI/plugin/src/com/android/systemui/plugins/clocks/WeatherData.kt @@ -0,0 +1,123 @@ +package com.android.systemui.plugins.clocks + +import android.os.Bundle +import android.util.Log +import android.view.View +import androidx.annotation.VisibleForTesting + +typealias WeatherTouchAction = (View) -> Unit + +data class WeatherData( + val description: String, + val state: WeatherStateIcon, + val useCelsius: Boolean, + val temperature: Int, + val touchAction: WeatherTouchAction? = null, +) { + companion object { + const val DEBUG = true + private const val TAG = "WeatherData" + @VisibleForTesting const val DESCRIPTION_KEY = "description" + @VisibleForTesting const val STATE_KEY = "state" + @VisibleForTesting const val USE_CELSIUS_KEY = "use_celsius" + @VisibleForTesting const val TEMPERATURE_KEY = "temperature" + private const val INVALID_WEATHER_ICON_STATE = -1 + + fun fromBundle(extras: Bundle, touchAction: WeatherTouchAction? = null): WeatherData? { + val description = extras.getString(DESCRIPTION_KEY) + val state = + WeatherStateIcon.fromInt(extras.getInt(STATE_KEY, INVALID_WEATHER_ICON_STATE)) + val temperature = readIntFromBundle(extras, TEMPERATURE_KEY) + if ( + description == null || + state == null || + !extras.containsKey(USE_CELSIUS_KEY) || + temperature == null + ) { + if (DEBUG) { + Log.w(TAG, "Weather data did not parse from $extras") + } + return null + } else { + val result = + WeatherData( + description = description, + state = state, + useCelsius = extras.getBoolean(USE_CELSIUS_KEY), + temperature = temperature, + touchAction = touchAction + ) + if (DEBUG) { + Log.i(TAG, "Weather data parsed $result from $extras") + } + return result + } + } + + private fun readIntFromBundle(extras: Bundle, key: String): Int? = + try { + extras.getString(key)?.toInt() + } catch (e: Exception) { + null + } + } + + // Values for WeatherStateIcon must stay in sync with go/g3-WeatherStateIcon + enum class WeatherStateIcon(val id: Int) { + UNKNOWN_ICON(0), + + // Clear, day & night. + SUNNY(1), + CLEAR_NIGHT(2), + + // Mostly clear, day & night. + MOSTLY_SUNNY(3), + MOSTLY_CLEAR_NIGHT(4), + + // Partly cloudy, day & night. + PARTLY_CLOUDY(5), + PARTLY_CLOUDY_NIGHT(6), + + // Mostly cloudy, day & night. + MOSTLY_CLOUDY_DAY(7), + MOSTLY_CLOUDY_NIGHT(8), + CLOUDY(9), + HAZE_FOG_DUST_SMOKE(10), + DRIZZLE(11), + HEAVY_RAIN(12), + SHOWERS_RAIN(13), + + // Scattered showers, day & night. + SCATTERED_SHOWERS_DAY(14), + SCATTERED_SHOWERS_NIGHT(15), + + // Isolated scattered thunderstorms, day & night. + ISOLATED_SCATTERED_TSTORMS_DAY(16), + ISOLATED_SCATTERED_TSTORMS_NIGHT(17), + STRONG_TSTORMS(18), + BLIZZARD(19), + BLOWING_SNOW(20), + FLURRIES(21), + HEAVY_SNOW(22), + + // Scattered snow showers, day & night. + SCATTERED_SNOW_SHOWERS_DAY(23), + SCATTERED_SNOW_SHOWERS_NIGHT(24), + SNOW_SHOWERS_SNOW(25), + MIXED_RAIN_HAIL_RAIN_SLEET(26), + SLEET_HAIL(27), + TORNADO(28), + TROPICAL_STORM_HURRICANE(29), + WINDY_BREEZY(30), + WINTRY_MIX_RAIN_SNOW(31); + + companion object { + fun fromInt(value: Int) = values().firstOrNull { it.id == value } + } + } + + override fun toString(): String { + val unit = if (useCelsius) "C" else "F" + return "$state (\"$description\") $temperature°$unit" + } +} diff --git a/systemUI/plugin/src/com/android/systemui/plugins/clocks/ZenData.kt b/systemUI/plugin/src/com/android/systemui/plugins/clocks/ZenData.kt new file mode 100644 index 0000000000..e927ec3c85 --- /dev/null +++ b/systemUI/plugin/src/com/android/systemui/plugins/clocks/ZenData.kt @@ -0,0 +1,22 @@ +package com.android.systemui.plugins.clocks + +import android.provider.Settings.Global.ZEN_MODE_ALARMS +import android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS +import android.provider.Settings.Global.ZEN_MODE_NO_INTERRUPTIONS +import android.provider.Settings.Global.ZEN_MODE_OFF + +data class ZenData( + val zenMode: ZenMode, + val descriptionId: String?, +) { + enum class ZenMode(val zenMode: Int) { + OFF(ZEN_MODE_OFF), + IMPORTANT_INTERRUPTIONS(ZEN_MODE_IMPORTANT_INTERRUPTIONS), + NO_INTERRUPTIONS(ZEN_MODE_NO_INTERRUPTIONS), + ALARMS(ZEN_MODE_ALARMS); + + companion object { + fun fromInt(zenMode: Int) = values().firstOrNull { it.zenMode == zenMode } + } + } +} diff --git a/systemUI/plugin/src/com/android/systemui/plugins/log/TableLogBufferBase.kt b/systemUI/plugin/src/com/android/systemui/plugins/log/TableLogBufferBase.kt index 8a962f9078..50b3f78a49 100644 --- a/systemUI/plugin/src/com/android/systemui/plugins/log/TableLogBufferBase.kt +++ b/systemUI/plugin/src/com/android/systemui/plugins/log/TableLogBufferBase.kt @@ -25,34 +25,34 @@ interface TableLogBufferBase { * * For Java overloading. */ - fun logChange(prefix: String = "", columnName: String, value: String?) { + fun logChange(prefix: String, columnName: String, value: String?) { logChange(prefix, columnName, value, isInitial = false) } /** Logs a String? change. */ - fun logChange(prefix: String = "", columnName: String, value: String?, isInitial: Boolean) + fun logChange(prefix: String, columnName: String, value: String?, isInitial: Boolean) /** * Logs a Boolean change. * * For Java overloading. */ - fun logChange(prefix: String = "", columnName: String, value: Boolean) { + fun logChange(prefix: String, columnName: String, value: Boolean) { logChange(prefix, columnName, value, isInitial = false) } /** Logs a Boolean change. */ - fun logChange(prefix: String = "", columnName: String, value: Boolean, isInitial: Boolean) + fun logChange(prefix: String, columnName: String, value: Boolean, isInitial: Boolean) /** * Logs an Int? change. * * For Java overloading. */ - fun logChange(prefix: String = "", columnName: String, value: Int?) { + fun logChange(prefix: String, columnName: String, value: Int?) { logChange(prefix, columnName, value, isInitial = false) } /** Logs an Int? change. */ - fun logChange(prefix: String = "", columnName: String, value: Int?, isInitial: Boolean) + fun logChange(prefix: String, columnName: String, value: Int?, isInitial: Boolean) } diff --git a/systemUI/plugin/src/com/android/systemui/plugins/qs/QS.java b/systemUI/plugin/src/com/android/systemui/plugins/qs/QS.java index d3218ad8c9..bf58eee9a9 100644 --- a/systemUI/plugin/src/com/android/systemui/plugins/qs/QS.java +++ b/systemUI/plugin/src/com/android/systemui/plugins/qs/QS.java @@ -14,7 +14,6 @@ package com.android.systemui.plugins.qs; -import android.graphics.Rect; import android.view.View; import androidx.annotation.FloatRange; @@ -36,7 +35,7 @@ public interface QS extends FragmentBase { String ACTION = "com.android.systemui.action.PLUGIN_QS"; - int VERSION = 16; + int VERSION = 15; String TAG = "QS"; @@ -90,45 +89,8 @@ public interface QS extends FragmentBase { */ int getHeightDiff(); - /** - * Returns the header view that contains QQS. This might return null (or throw) if there's no - * actual header view. - */ View getHeader(); - /** - * Returns the top of the header view that contains QQS wrt to the container view - */ - int getHeaderTop(); - - /** - * Returns the bottom of the header view that contains QQS wrt to the container view - */ - int getHeaderBottom(); - - /** - * Returns the left bound of the header view that contains QQS wrt to the container view - */ - int getHeaderLeft(); - - /** - * Fills outBounds with the bounds of the header view (container of QQS) on the screen - */ - void getHeaderBoundsOnScreen(Rect outBounds); - - /** - * Returns the height of the header view that contains QQS. It defaults to bottom - top. - */ - default int getHeaderHeight() { - return getHeaderBottom() - getHeaderTop(); - } - - /** - * Returns whether the header view that contains QQS is shown on screen (similar semantics to - * View.isShown). - */ - boolean isHeaderShown(); - default void setHasNotifications(boolean hasNotifications) { } diff --git a/systemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/systemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java index d197cdb792..d13c750827 100644 --- a/systemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java +++ b/systemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java @@ -14,6 +14,7 @@ package com.android.systemui.plugins.qs; +import android.annotation.NonNull; import android.content.Context; import android.content.res.Resources; import android.graphics.drawable.Drawable; @@ -21,7 +22,6 @@ import android.metrics.LogMaker; import android.service.quicksettings.Tile; import android.text.TextUtils; -import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.logging.InstanceId; @@ -33,7 +33,6 @@ import com.android.systemui.plugins.qs.QSTile.Icon; import com.android.systemui.plugins.qs.QSTile.State; import java.util.Objects; -import java.util.function.Consumer; import java.util.function.Supplier; @ProvidesInterface(version = QSTile.VERSION) @@ -42,7 +41,7 @@ import java.util.function.Supplier; @DependsOn(target = Icon.class) @DependsOn(target = State.class) public interface QSTile { - int VERSION = 5; + int VERSION = 4; String getTileSpec(); @@ -78,7 +77,6 @@ public interface QSTile { void longClick(@Nullable Expandable expandable); void userSwitch(int currentUser); - int getCurrentTileUser(); /** * @deprecated not needed as {@link com.android.internal.logging.UiEvent} will use @@ -94,7 +92,6 @@ public interface QSTile { CharSequence getTileLabel(); - @NonNull State getState(); default LogMaker populate(LogMaker logMaker) { @@ -123,36 +120,6 @@ public interface QSTile { */ boolean isListening(); - /** - * Get this tile's {@link TileDetailsViewModel} through a callback. - * - * Please only override this method if the tile can't get its {@link TileDetailsViewModel} - * synchronously and thus need a callback to defer it. - * - * @return a boolean indicating whether this tile has a {@link TileDetailsViewModel}. The tile's - * {@link TileDetailsViewModel} will be passed to the callback. Please always return true when - * overriding this method. Return false will make the tile display its dialog instead of details - * view, and it will not wait for the callback to be returned before proceeding to show the - * dialog. - */ - default boolean getDetailsViewModel(Consumer callback) { - TileDetailsViewModel tileDetailsViewModel = getDetailsViewModel(); - callback.accept(tileDetailsViewModel); - return tileDetailsViewModel != null; - } - - /** - * Return this tile's {@link TileDetailsViewModel} to be used to render the TileDetailsView. - * - * Please only override this method if the tile doesn't need a callback to set its - * {@link TileDetailsViewModel}. - */ - default TileDetailsViewModel getDetailsViewModel() { - return null; - } - - boolean isDestroyed(); - @ProvidesInterface(version = Callback.VERSION) interface Callback { static final int VERSION = 2; @@ -202,7 +169,6 @@ public interface QSTile { public boolean isTransient = false; public String expandedAccessibilityClassName; public boolean handlesLongClick = true; - public boolean handlesSecondaryClick = false; @Nullable public Drawable sideViewCustomDrawable; public String spec; @@ -217,10 +183,7 @@ public interface QSTile { } } - /** - * If the current secondaryLabel value is not empty, ignore the given input and return - * the current value. Otherwise return current value. - */ + /** Get the text for secondaryLabel. */ public CharSequence getSecondaryLabel(CharSequence stateText) { // Use a local reference as the value might change from other threads CharSequence localSecondaryLabel = secondaryLabel; @@ -249,7 +212,6 @@ public interface QSTile { || !Objects.equals(other.isTransient, isTransient) || !Objects.equals(other.dualTarget, dualTarget) || !Objects.equals(other.handlesLongClick, handlesLongClick) - || !Objects.equals(other.handlesSecondaryClick, handlesSecondaryClick) || !Objects.equals(other.sideViewCustomDrawable, sideViewCustomDrawable); other.spec = spec; other.icon = icon; @@ -265,7 +227,6 @@ public interface QSTile { other.dualTarget = dualTarget; other.isTransient = isTransient; other.handlesLongClick = handlesLongClick; - other.handlesSecondaryClick = handlesSecondaryClick; other.sideViewCustomDrawable = sideViewCustomDrawable; return changed; } @@ -291,13 +252,11 @@ public interface QSTile { sb.append(",disabledByPolicy=").append(disabledByPolicy); sb.append(",dualTarget=").append(dualTarget); sb.append(",isTransient=").append(isTransient); - sb.append(",handlesSecondaryClick=").append(handlesSecondaryClick); sb.append(",state=").append(state); sb.append(",sideViewCustomDrawable=").append(sideViewCustomDrawable); return sb.append(']'); } - @NonNull public State copy() { State state = new State(); copyTo(state); @@ -333,7 +292,6 @@ public interface QSTile { return rt; } - @androidx.annotation.NonNull @Override public State copy() { AdapterState state = new AdapterState(); @@ -346,7 +304,6 @@ public interface QSTile { class BooleanState extends AdapterState { public static final int VERSION = 1; - @androidx.annotation.NonNull @Override public State copy() { BooleanState state = new BooleanState(); diff --git a/systemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java b/systemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java index 9b961d2535..94fdbae832 100644 --- a/systemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java +++ b/systemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java @@ -122,7 +122,7 @@ public interface NotificationMenuRowPlugin extends Plugin { public void setAppName(String appName); - public void createMenu(ViewGroup parent); + public void createMenu(ViewGroup parent, StatusBarNotification sbn); public void resetMenu(); @@ -215,8 +215,9 @@ public interface NotificationMenuRowPlugin extends Plugin { /** * Callback used to signal the menu that its parent notification has been updated. + * @param sbn */ - public void onNotificationUpdated(); + public void onNotificationUpdated(StatusBarNotification sbn); /** * Callback used to signal the menu that a user is moving the parent notification. diff --git a/systemUI/plugin_core/Android.bp b/systemUI/plugin_core/Android.bp index 98b50269ec..4e39f1ac95 100644 --- a/systemUI/plugin_core/Android.bp +++ b/systemUI/plugin_core/Android.bp @@ -13,7 +13,6 @@ // limitations under the License. package { - default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_", // See: http://go/android-license-faq // A large-scale-change added 'default_applicable_licenses' to import // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license" @@ -22,77 +21,15 @@ package { default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"], } -java_library { - sdk_version: "current", - name: "PluginAnnotationLib", - host_supported: true, - device_supported: true, - srcs: [ - "src/**/annotations/*.java", - "src/**/annotations/*.kt", - ], - optimize: { - proguard_flags_files: ["proguard.flags"], - // Ensure downstream clients that reference this as a shared lib - // inherit the appropriate flags to preserve annotations. - export_proguard_flags_files: true, - }, - - // Enforce that the library is built against java 8 so that there are - // no compatibility issues with launcher - java_version: "1.8", -} - java_library { sdk_version: "current", name: "PluginCoreLib", - device_supported: true, - srcs: [ - "src/**/*.java", - "src/**/*.kt", - ], - exclude_srcs: [ - "src/**/annotations/*.java", - "src/**/annotations/*.kt", - ], - static_libs: [ - "PluginAnnotationLib", - ], + srcs: ["src/**/*.java"], optimize: { proguard_flags_files: ["proguard.flags"], - // Ensure downstream clients that reference this as a shared lib - // inherit the appropriate flags to preserve annotations. - export_proguard_flags_files: true, }, // Enforce that the library is built against java 8 so that there are // no compatibility issues with launcher java_version: "1.8", } - -java_library { - java_version: "1.8", - name: "PluginAnnotationProcessorLib", - host_supported: true, - device_supported: false, - srcs: [ - "processor/src/**/*.java", - "processor/src/**/*.kt", - ], - plugins: ["auto_service_plugin"], - static_libs: [ - "androidx.annotation_annotation", - "auto_service_annotations", - "auto_common", - "PluginAnnotationLib", - "guava", - "jsr330", - ], -} - -java_plugin { - name: "PluginAnnotationProcessor", - processor_class: "com.android.systemui.plugins.processor.ProtectedPluginProcessor", - static_libs: ["PluginAnnotationProcessorLib"], - java_version: "1.8", -} diff --git a/systemUI/plugin_core/build.gradle b/systemUI/plugin_core/build.gradle index e40f11e008..77d4f8ade2 100644 --- a/systemUI/plugin_core/build.gradle +++ b/systemUI/plugin_core/build.gradle @@ -4,7 +4,6 @@ plugins { } android { - buildToolsVersion "36.1.0" namespace "com.android.systemui.plugin_core" buildFeatures { aidl true @@ -18,5 +17,5 @@ android { } } -addFrameworkJar('framework-16.jar') +addFrameworkJar('framework-15.jar') compileOnlyCommonJars() diff --git a/systemUI/plugin_core/proguard.flags b/systemUI/plugin_core/proguard.flags index 8b78ba47fd..6240898b3b 100644 --- a/systemUI/plugin_core/proguard.flags +++ b/systemUI/plugin_core/proguard.flags @@ -8,7 +8,4 @@ -keep interface com.android.systemui.plugins.annotations.** { *; } -# TODO(b/373579455): Evaluate if needs to be kept. --keep,allowshrinking,allowoptimization,allowobfuscation,allowaccessmodification @com.android.systemui.plugins.annotations.** class * { - void (); -} +-keep,allowshrinking,allowoptimization,allowobfuscation,allowaccessmodification @com.android.systemui.plugins.annotations.** class * diff --git a/systemUI/plugin_core/src/com/android/systemui/plugins/Plugin.java b/systemUI/plugin_core/src/com/android/systemui/plugins/Plugin.java index 84040f984e..8ff6c114dd 100644 --- a/systemUI/plugin_core/src/com/android/systemui/plugins/Plugin.java +++ b/systemUI/plugin_core/src/com/android/systemui/plugins/Plugin.java @@ -15,7 +15,6 @@ package com.android.systemui.plugins; import android.content.Context; -import com.android.systemui.plugins.annotations.ProtectedReturn; import com.android.systemui.plugins.annotations.Requires; /** @@ -117,8 +116,6 @@ public interface Plugin { * @deprecated * @see Requires */ - @Deprecated - @ProtectedReturn(statement = "return -1;") default int getVersion() { // Default of -1 indicates the plugin supports the new Requires model. return -1; diff --git a/systemUI/shared/Android.bp b/systemUI/shared/Android.bp index 8d7cab41ea..ca30e159a0 100644 --- a/systemUI/shared/Android.bp +++ b/systemUI/shared/Android.bp @@ -13,7 +13,6 @@ // limitations under the License. package { - default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_", // See: http://go/android-license-faq // A large-scale-change added 'default_applicable_licenses' to import // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license" @@ -35,31 +34,22 @@ java_library { srcs: [ ":statslog-SystemUI-java-gen", ], - libs: [ - "androidx.annotation_annotation", - ], } android_library { name: "SystemUISharedLib", - use_resource_processor: true, srcs: [ "src/**/*.java", "src/**/*.kt", "src/**/*.aidl", ":wm_shell-aidls", - ":wm_shell-shared-aidls", + ":wm_shell_util-sources", ], static_libs: [ - "com.android.systemui.dagger-api", - "BiometricsSharedLib", - "PlatformAnimationLib", "PluginCoreLib", + "SystemUIAnimationLib", "SystemUIPluginLib", "SystemUIUnfoldLib", - "SystemUISharedLib-Keyguard", - "WindowManager-Shell-shared", - "//frameworks/libs/systemui:tracinglib-platform", "androidx.dynamicanimation_dynamicanimation", "androidx.concurrent_concurrent-futures", "androidx.lifecycle_lifecycle-runtime-ktx", @@ -69,10 +59,6 @@ android_library { "kotlinx_coroutines", "dagger2", "jsr330", - "//frameworks/libs/systemui:com_android_systemui_shared_flags_lib", - "//frameworks/libs/systemui:msdl", - "//frameworks/libs/systemui:view_capture", - "am_flags_lib", ], resource_dirs: [ "res", diff --git a/systemUI/shared/build.gradle b/systemUI/shared/build.gradle index 66f90b7e46..85f058cfa2 100644 --- a/systemUI/shared/build.gradle +++ b/systemUI/shared/build.gradle @@ -1,5 +1,3 @@ -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - plugins { alias(libs.plugins.android.library) alias(libs.plugins.kotlin.android) @@ -7,7 +5,6 @@ plugins { } android { - buildToolsVersion "36.1.0" namespace "com.android.systemui.shared" buildFeatures { aidl true @@ -26,7 +23,7 @@ ksp { arg("dagger.hilt.disableModulesHaveInstallInCheck", "true") } -addFrameworkJar('framework-16.jar') +addFrameworkJar('framework-15.jar') compileOnlyCommonJars() dependencies { @@ -35,12 +32,9 @@ dependencies { compileOnly projects.plugin compileOnly projects.plugincore compileOnly projects.flags - compileOnly projects.wmshell - compileOnly projects.animationlib - compileOnly projects.utils - implementation libs.dagger.hilt.android - ksp libs.dagger.hilt.compiler + implementation libs.hilt.android + ksp libs.hilt.compiler implementation libs.androidx.concurrent.futures } diff --git a/systemUI/shared/res/values/bools.xml b/systemUI/shared/res/values/bools.xml index a7ad48d17a..f22dac4b9f 100644 --- a/systemUI/shared/res/values/bools.xml +++ b/systemUI/shared/res/values/bools.xml @@ -22,4 +22,4 @@ false - + \ No newline at end of file diff --git a/systemUI/shared/src/app/lawnchair/compat/LawnchairQuickstepCompat.kt b/systemUI/shared/src/app/lawnchair/compat/LawnchairQuickstepCompat.kt index 7b55576a7f..3e48b50c05 100644 --- a/systemUI/shared/src/app/lawnchair/compat/LawnchairQuickstepCompat.kt +++ b/systemUI/shared/src/app/lawnchair/compat/LawnchairQuickstepCompat.kt @@ -9,7 +9,6 @@ import app.lawnchair.compatlib.RemoteTransitionCompat import app.lawnchair.compatlib.eleven.QuickstepCompatFactoryVR import app.lawnchair.compatlib.fifteen.QuickstepCompatFactoryVV import app.lawnchair.compatlib.fourteen.QuickstepCompatFactoryVU -import app.lawnchair.compatlib.sixteen.QuickstepCompatFactoryVBaklava import app.lawnchair.compatlib.ten.QuickstepCompatFactoryVQ import app.lawnchair.compatlib.thirteen.QuickstepCompatFactoryVT import app.lawnchair.compatlib.twelve.QuickstepCompatFactoryVS @@ -40,13 +39,8 @@ object LawnchairQuickstepCompat { @JvmField val ATLEAST_V: Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM - @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.BAKLAVA) - @JvmField - val ATLEAST_BAKLAVA: Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA - @JvmStatic val factory: QuickstepCompatFactory = when { - ATLEAST_BAKLAVA -> QuickstepCompatFactoryVBaklava() ATLEAST_V -> QuickstepCompatFactoryVV() ATLEAST_U -> QuickstepCompatFactoryVU() ATLEAST_T -> QuickstepCompatFactoryVT() diff --git a/systemUI/shared/src/com/android/systemui/shared/condition/CombinedCondition.kt b/systemUI/shared/src/com/android/systemui/shared/condition/CombinedCondition.kt index c30580fa01..a2b6e2cf9a 100644 --- a/systemUI/shared/src/com/android/systemui/shared/condition/CombinedCondition.kt +++ b/systemUI/shared/src/com/android/systemui/shared/condition/CombinedCondition.kt @@ -37,43 +37,51 @@ import kotlinx.coroutines.launch * @param operand The [Evaluator.ConditionOperand] to apply to the conditions. */ @OptIn(ExperimentalCoroutinesApi::class) -class CombinedCondition( +class CombinedCondition +constructor( private val scope: CoroutineScope, private val conditions: Collection, - @Evaluator.ConditionOperand private val operand: Int, + @Evaluator.ConditionOperand private val operand: Int ) : Condition(scope, null, false) { + private var job: Job? = null private val _startStrategy by lazy { calculateStartStrategy() } - override suspend fun start() { - val groupedConditions = conditions.groupBy { it.isOverridingCondition } + override fun start() { + job = + scope.launch { + val groupedConditions = conditions.groupBy { it.isOverridingCondition } - lazilyEvaluate( - conditions = groupedConditions.getOrDefault(true, emptyList()), - filterUnknown = true, - ) - .distinctUntilChanged() - .flatMapLatest { overriddenValue -> - // If there are overriding conditions with values set, they take precedence. - if (overriddenValue == null) { - lazilyEvaluate( - conditions = groupedConditions.getOrDefault(false, emptyList()), - filterUnknown = false, + lazilyEvaluate( + conditions = groupedConditions.getOrDefault(true, emptyList()), + filterUnknown = true ) - } else { - flowOf(overriddenValue) - } - } - .collect { conditionMet -> - if (conditionMet == null) { - clearCondition() - } else { - updateCondition(conditionMet) - } + .distinctUntilChanged() + .flatMapLatest { overriddenValue -> + // If there are overriding conditions with values set, they take precedence. + if (overriddenValue == null) { + lazilyEvaluate( + conditions = groupedConditions.getOrDefault(false, emptyList()), + filterUnknown = false + ) + } else { + flowOf(overriddenValue) + } + } + .collect { conditionMet -> + if (conditionMet == null) { + clearCondition() + } else { + updateCondition(conditionMet) + } + } } } - override fun stop() {} + override fun stop() { + job?.cancel() + job = null + } /** * Evaluates a list of conditions lazily with support for short-circuiting. Conditions are @@ -180,6 +188,7 @@ class CombinedCondition( return startStrategy } - override val startStrategy: Int - get() = _startStrategy + override fun getStartStrategy(): Int { + return _startStrategy + } } diff --git a/systemUI/shared/src/com/android/systemui/shared/condition/ConditionExtensions.kt b/systemUI/shared/src/com/android/systemui/shared/condition/ConditionExtensions.kt index 849b3d6d8d..84edc35770 100644 --- a/systemUI/shared/src/com/android/systemui/shared/condition/ConditionExtensions.kt +++ b/systemUI/shared/src/com/android/systemui/shared/condition/ConditionExtensions.kt @@ -14,12 +14,12 @@ import kotlinx.coroutines.launch fun Flow.toCondition( scope: CoroutineScope, @StartStrategy strategy: Int, - initialValue: Boolean? = null, + initialValue: Boolean? = null ): Condition { return object : Condition(scope, initialValue, false) { var job: Job? = null - override suspend fun start() { + override fun start() { job = scope.launch { collect { updateCondition(it) } } } @@ -28,8 +28,7 @@ fun Flow.toCondition( job = null } - override val startStrategy: Int - get() = strategy + override fun getStartStrategy() = strategy } } @@ -37,16 +36,13 @@ fun Flow.toCondition( fun Condition.toFlow(): Flow { return callbackFlow { val callback = - object : Condition.Callback { - override fun onConditionChanged(condition: Condition) { - if (condition.isConditionSet) { - trySend(condition.isConditionMet) - } else { - trySend(null) - } + Condition.Callback { condition -> + if (condition.isConditionSet) { + trySend(condition.isConditionMet) + } else { + trySend(null) } } - addCallback(callback) callback.onConditionChanged(this@toFlow) awaitClose { removeCallback(callback) } diff --git a/systemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java b/systemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java index f2899889a3..c225cbcc6e 100644 --- a/systemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java +++ b/systemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java @@ -128,20 +128,19 @@ public class PipSurfaceTransactionHelper { mTmpDestinationRect.inset(insets); // Scale by the shortest edge and offset such that the top/left of the scaled inset // source rect aligns with the top/left of the destination bounds - final float scale = Math.max((float) destinationBounds.width() / sourceBounds.width(), - (float) destinationBounds.height() / sourceBounds.height()); + final float scale = sourceBounds.width() <= sourceBounds.height() + ? (float) destinationBounds.width() / sourceBounds.width() + : (float) destinationBounds.height() / sourceBounds.height(); mTmpTransform.setRotate(degree, 0, 0); mTmpTransform.postScale(scale, scale); final float cornerRadius = getScaledCornerRadius(mTmpDestinationRect, destinationBounds); // adjust the positions, take account also the insets final float adjustedPositionX, adjustedPositionY; if (degree < 0) { - // Counter-clockwise rotation. - adjustedPositionX = positionX - insets.top * scale; + adjustedPositionX = positionX + insets.top * scale; adjustedPositionY = positionY + insets.left * scale; } else { - // Clockwise rotation. - adjustedPositionX = positionX + insets.top * scale; + adjustedPositionX = positionX - insets.top * scale; adjustedPositionY = positionY - insets.left * scale; } tx.setMatrix(leash, mTmpTransform, mTmpFloat9) diff --git a/systemUI/shared/src/com/android/systemui/shared/plugins/PluginEnabler.java b/systemUI/shared/src/com/android/systemui/shared/plugins/PluginEnabler.java index 3d2ce4229b..1c5da827ee 100644 --- a/systemUI/shared/src/com/android/systemui/shared/plugins/PluginEnabler.java +++ b/systemUI/shared/src/com/android/systemui/shared/plugins/PluginEnabler.java @@ -27,10 +27,9 @@ public interface PluginEnabler { int DISABLED_INVALID_VERSION = 2; int DISABLED_FROM_EXPLICIT_CRASH = 3; int DISABLED_FROM_SYSTEM_CRASH = 4; - int DISABLED_UNKNOWN = 100; @IntDef({ENABLED, DISABLED_MANUALLY, DISABLED_INVALID_VERSION, DISABLED_FROM_EXPLICIT_CRASH, - DISABLED_FROM_SYSTEM_CRASH, DISABLED_UNKNOWN}) + DISABLED_FROM_SYSTEM_CRASH}) @interface DisableReason { } diff --git a/systemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java b/systemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java index 69f5a79ece..87cc86f18f 100644 --- a/systemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java +++ b/systemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java @@ -32,9 +32,6 @@ import com.android.systemui.plugins.Plugin; import com.android.systemui.plugins.PluginFragment; import com.android.systemui.plugins.PluginLifecycleManager; import com.android.systemui.plugins.PluginListener; -import com.android.systemui.plugins.PluginProtector; -import com.android.systemui.plugins.PluginWrapper; -import com.android.systemui.plugins.ProtectedPluginListener; import dalvik.system.PathClassLoader; @@ -52,8 +49,7 @@ import java.util.function.Supplier; * * @param The type of plugin that this contains. */ -public class PluginInstance - implements PluginLifecycleManager, ProtectedPluginListener { +public class PluginInstance implements PluginLifecycleManager { private static final String TAG = "PluginInstance"; private final Context mAppContext; @@ -62,7 +58,6 @@ public class PluginInstance private final PluginFactory mPluginFactory; private final String mTag; - private boolean mHasError = false; private BiConsumer mLogConsumer = null; private Context mPluginContext; private T mPlugin; @@ -92,11 +87,6 @@ public class PluginInstance return mTag; } - /** */ - public boolean hasError() { - return mHasError; - } - public void setLogFunc(BiConsumer logConsumer) { mLogConsumer = logConsumer; } @@ -107,22 +97,8 @@ public class PluginInstance } } - @Override - public synchronized boolean onFail(String className, String methodName, Throwable failure) { - Log.e(TAG, "Failure from " + mPlugin + ". Disabling Plugin."); - mHasError = true; - unloadPlugin(); - mListener.onPluginDetached(this); - return true; - } - /** Alerts listener and plugin that the plugin has been created. */ public synchronized void onCreate() { - if (mHasError) { - log("Previous Fatal Exception detected for plugin class"); - return; - } - boolean loadPlugin = mListener.onPluginAttached(this); if (!loadPlugin) { if (mPlugin != null) { @@ -133,17 +109,13 @@ public class PluginInstance } if (mPlugin == null) { - log("onCreate: auto-load"); + log("onCreate auto-load"); loadPlugin(); return; } - if (!checkVersion()) { - log("onCreate: version check failed"); - return; - } - log("onCreate: load callbacks"); + mPluginFactory.checkVersion(mPlugin); if (!(mPlugin instanceof PluginFragment)) { // Only call onCreate for plugins that aren't fragments, as fragments // will get the onCreate as part of the fragment lifecycle. @@ -154,12 +126,6 @@ public class PluginInstance /** Alerts listener and plugin that the plugin is being shutdown. */ public synchronized void onDestroy() { - if (mHasError) { - // Detached in error handler - log("onDestroy - no-op"); - return; - } - log("onDestroy"); unloadPlugin(); mListener.onPluginDetached(this); @@ -168,37 +134,28 @@ public class PluginInstance /** Returns the current plugin instance (if it is loaded). */ @Nullable public T getPlugin() { - return mHasError ? null : mPlugin; + return mPlugin; } /** * Loads and creates the plugin if it does not exist. */ public synchronized void loadPlugin() { - if (mHasError) { - log("Previous Fatal Exception detected for plugin class"); - return; - } - if (mPlugin != null) { log("Load request when already loaded"); return; } // Both of these calls take about 1 - 1.5 seconds in test runs - mPlugin = mPluginFactory.createPlugin(this); + mPlugin = mPluginFactory.createPlugin(); mPluginContext = mPluginFactory.createPluginContext(); if (mPlugin == null || mPluginContext == null) { Log.e(mTag, "Requested load, but failed"); return; } - if (!checkVersion()) { - log("loadPlugin: version check failed"); - return; - } - log("Loaded plugin; running callbacks"); + mPluginFactory.checkVersion(mPlugin); if (!(mPlugin instanceof PluginFragment)) { // Only call onCreate for plugins that aren't fragments, as fragments // will get the onCreate as part of the fragment lifecycle. @@ -207,29 +164,6 @@ public class PluginInstance mListener.onPluginLoaded(mPlugin, mPluginContext, this); } - /** - * Checks the plugin version, and permanently destroys the plugin instance on a failure - */ - private synchronized boolean checkVersion() { - if (mHasError) { - return false; - } - - if (mPlugin == null) { - return true; - } - - if (mPluginFactory.checkVersion(mPlugin)) { - return true; - } - - Log.wtf(TAG, "Version check failed for " + mPlugin.getClass().getSimpleName()); - mHasError = true; - unloadPlugin(); - mListener.onPluginDetached(this); - return false; - } - /** * Unloads and destroys the current plugin instance if it exists. * @@ -270,7 +204,7 @@ public class PluginInstance } public VersionInfo getVersionInfo() { - return mPluginFactory.getVersionInfo(mPlugin); + return mPluginFactory.checkVersion(mPlugin); } @VisibleForTesting @@ -361,19 +295,16 @@ public class PluginInstance /** Class that compares a plugin class against an implementation for version matching. */ public interface VersionChecker { - /** Compares two plugin classes. Returns true when match. */ - boolean checkVersion( + /** Compares two plugin classes. */ + VersionInfo checkVersion( Class instanceClass, Class pluginClass, Plugin plugin); - - /** Returns VersionInfo for the target class */ - VersionInfo getVersionInfo(Class instanceclass); } /** Class that compares a plugin class against an implementation for version matching. */ public static class VersionCheckerImpl implements VersionChecker { @Override /** Compares two plugin classes. */ - public boolean checkVersion( + public VersionInfo checkVersion( Class instanceClass, Class pluginClass, Plugin plugin) { VersionInfo pluginVersion = new VersionInfo().addClass(pluginClass); VersionInfo instanceVersion = new VersionInfo().addClass(instanceClass); @@ -382,17 +313,11 @@ public class PluginInstance } else if (plugin != null) { int fallbackVersion = plugin.getVersion(); if (fallbackVersion != pluginVersion.getDefaultVersion()) { - return false; + throw new VersionInfo.InvalidVersionException("Invalid legacy version", false); } + return null; } - return true; - } - - @Override - /** Returns the version info for the class */ - public VersionInfo getVersionInfo(Class instanceClass) { - VersionInfo instanceVersion = new VersionInfo().addClass(instanceClass); - return instanceVersion.hasVersionInfo() ? instanceVersion : null; + return instanceVersion; } } @@ -439,16 +364,20 @@ public class PluginInstance } /** Creates the related plugin object from the factory */ - public T createPlugin(ProtectedPluginListener listener) { + public T createPlugin() { try { ClassLoader loader = mClassLoaderFactory.get(); Class instanceClass = (Class) Class.forName( mComponentName.getClassName(), true, loader); T result = (T) mInstanceFactory.create(instanceClass); Log.v(TAG, "Created plugin: " + result); - return PluginProtector.protectIfAble(result, listener); - } catch (ReflectiveOperationException ex) { - Log.wtf(TAG, "Failed to load plugin", ex); + return result; + } catch (ClassNotFoundException ex) { + Log.e(TAG, "Failed to load plugin", ex); + } catch (IllegalAccessException ex) { + Log.e(TAG, "Failed to load plugin", ex); + } catch (InstantiationException ex) { + Log.e(TAG, "Failed to load plugin", ex); } return null; } @@ -465,27 +394,13 @@ public class PluginInstance return null; } - /** Check Version for the instance */ - public boolean checkVersion(T instance) { + /** Check Version and create VersionInfo for instance */ + public VersionInfo checkVersion(T instance) { if (instance == null) { - instance = createPlugin(null); - } - if (instance instanceof PluginWrapper) { - instance = ((PluginWrapper) instance).getPlugin(); + instance = createPlugin(); } return mVersionChecker.checkVersion( (Class) instance.getClass(), mPluginClass, instance); } - - /** Get Version Info for the instance */ - public VersionInfo getVersionInfo(T instance) { - if (instance == null) { - instance = createPlugin(null); - } - if (instance instanceof PluginWrapper) { - instance = ((PluginWrapper) instance).getPlugin(); - } - return mVersionChecker.getVersionInfo((Class) instance.getClass()); - } } } diff --git a/systemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java b/systemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java index 9a264c6ca4..1e668b84cd 100644 --- a/systemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java +++ b/systemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java @@ -28,8 +28,6 @@ import android.util.ArraySet; import android.util.Log; import android.widget.Toast; -import androidx.annotation.Nullable; - import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.systemui.plugins.Plugin; import com.android.systemui.plugins.PluginListener; @@ -64,7 +62,7 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage public PluginManagerImpl(Context context, PluginActionManager.Factory actionManagerFactory, boolean debuggable, - @Nullable UncaughtExceptionPreHandlerManager preHandlerManager, + UncaughtExceptionPreHandlerManager preHandlerManager, PluginEnabler pluginEnabler, PluginPrefs pluginPrefs, List privilegedPlugins) { @@ -75,9 +73,7 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage mPluginPrefs = pluginPrefs; mPluginEnabler = pluginEnabler; - if (preHandlerManager != null) { - preHandlerManager.registerHandler(new PluginExceptionHandler()); - } + preHandlerManager.registerHandler(new PluginExceptionHandler()); } public boolean isDebuggable() { diff --git a/systemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/systemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl index 1f6bea18d5..090033d41f 100644 --- a/systemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl +++ b/systemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl @@ -21,7 +21,6 @@ import android.graphics.Insets; import android.graphics.Rect; import android.os.Bundle; import android.os.UserHandle; -import android.view.KeyEvent; import android.view.MotionEvent; import com.android.internal.util.ScreenshotRequest; @@ -69,10 +68,10 @@ interface ISystemUiProxy { /** * Indicates that the given Assist invocation types should be handled by Launcher via - * LauncherProxy#onAssistantOverrideInvoked and should not be invoked by SystemUI. + * OverviewProxy#onAssistantOverrideInvoked and should not be invoked by SystemUI. * * @param invocationTypes The invocation types that will henceforth be handled via - * LauncherProxy (Launcher); other invocation types should be handled by SysUI. + * OverviewProxy (Launcher); other invocation types should be handled by SysUI. */ oneway void setAssistantOverridesRequested(in int[] invocationTypes) = 53; @@ -103,9 +102,9 @@ interface ISystemUiProxy { oneway void expandNotificationPanel() = 29; /** - * Notifies SystemUI of a back KeyEvent. + * Notifies SystemUI to invoke Back. */ - oneway void onBackEvent(in KeyEvent keyEvent) = 44; + oneway void onBackPressed() = 44; /** Sets home rotation enabled. */ oneway void setHomeRotationEnabled(boolean enabled) = 45; @@ -121,7 +120,7 @@ interface ISystemUiProxy { oneway void notifyTaskbarAutohideSuspend(boolean suspend) = 48; /** - * Notifies that the IME switcher button has been pressed. + * Notifies SystemUI to invoke IME Switcher. */ oneway void onImeSwitcherPressed() = 49; @@ -168,15 +167,5 @@ interface ISystemUiProxy { */ oneway void toggleQuickSettingsPanel() = 56; - /** - * Notifies that the IME Switcher button has been long pressed. - */ - oneway void onImeSwitcherLongPress() = 57; - - /** - * Updates contextual education stats when target gesture type is triggered. - */ - oneway void updateContextualEduStats(boolean isTrackpadGesture, String gestureType) = 58; - - // Next id = 59 + // Next id = 57 } diff --git a/systemUI/shared/src/com/android/systemui/shared/recents/model/Task.java b/systemUI/shared/src/com/android/systemui/shared/recents/model/Task.java index 96307c7c30..f06e333512 100644 --- a/systemUI/shared/src/com/android/systemui/shared/recents/model/Task.java +++ b/systemUI/shared/src/com/android/systemui/shared/recents/model/Task.java @@ -19,8 +19,8 @@ package com.android.systemui.shared.recents.model; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.view.Display.DEFAULT_DISPLAY; -import static com.android.wm.shell.shared.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES; -import static com.android.wm.shell.shared.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE; +import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES; +import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE; import android.app.ActivityManager; import android.app.ActivityManager.TaskDescription; @@ -72,25 +72,6 @@ public class Task { @ViewDebug.ExportedProperty(category = "recents") public final int displayId; - /** - * The component of the first activity in the task, can be considered the "application" of - * this task. - */ - @Nullable - public ComponentName baseActivity; - /** - * The number of activities in this task (including running). - */ - public int numActivities; - /** - * Whether the top activity is to be displayed. See {@link android.R.attr#windowNoDisplay}. - */ - public boolean isTopActivityNoDisplay; - /** - * Whether fillsParent() is false for every activity in the tasks stack. - */ - public boolean isActivityStackTransparent; - // The source component name which started this task public final ComponentName sourceComponent; @@ -109,10 +90,6 @@ public class Task { this.userId = t.userId; this.lastActiveTime = t.lastActiveTime; this.displayId = t.displayId; - this.baseActivity = t.baseActivity; - this.numActivities = t.numActivities; - this.isTopActivityNoDisplay = t.isTopActivityNoDisplay; - this.isActivityStackTransparent = t.isActivityStackTransparent; updateHashCode(); } @@ -129,9 +106,7 @@ public class Task { } public TaskKey(int id, int windowingMode, @NonNull Intent intent, - ComponentName sourceComponent, int userId, long lastActiveTime, int displayId, - @Nullable ComponentName baseActivity, int numActivities, - boolean isTopActivityNoDisplay, boolean isActivityStackTransparent) { + ComponentName sourceComponent, int userId, long lastActiveTime, int displayId) { this.id = id; this.windowingMode = windowingMode; this.baseIntent = intent; @@ -139,10 +114,6 @@ public class Task { this.userId = userId; this.lastActiveTime = lastActiveTime; this.displayId = displayId; - this.baseActivity = baseActivity; - this.numActivities = numActivities; - this.isTopActivityNoDisplay = isTopActivityNoDisplay; - this.isActivityStackTransparent = isActivityStackTransparent; updateHashCode(); } @@ -214,10 +185,6 @@ public class Task { parcel.writeLong(lastActiveTime); parcel.writeInt(displayId); parcel.writeTypedObject(sourceComponent, flags); - parcel.writeTypedObject(baseActivity, flags); - parcel.writeInt(numActivities); - parcel.writeBoolean(isTopActivityNoDisplay); - parcel.writeBoolean(isActivityStackTransparent); } private static TaskKey readFromParcel(Parcel parcel) { @@ -228,14 +195,9 @@ public class Task { long lastActiveTime = parcel.readLong(); int displayId = parcel.readInt(); ComponentName sourceComponent = parcel.readTypedObject(ComponentName.CREATOR); - ComponentName baseActivity = parcel.readTypedObject(ComponentName.CREATOR); - int numActivities = parcel.readInt(); - boolean isTopActivityNoDisplay = parcel.readBoolean(); - boolean isActivityStackTransparent = parcel.readBoolean(); return new TaskKey(id, windowingMode, baseIntent, sourceComponent, userId, - lastActiveTime, displayId, baseActivity, numActivities, isTopActivityNoDisplay, - isActivityStackTransparent); + lastActiveTime, displayId); } @Override @@ -256,7 +218,6 @@ public class Task { @ViewDebug.ExportedProperty(category="recents") public String title; @ViewDebug.ExportedProperty(category="recents") - @Nullable public String titleDescription; @ViewDebug.ExportedProperty(category="recents") public int colorPrimary; @@ -281,11 +242,9 @@ public class Task { public Rect appBounds; - @ViewDebug.ExportedProperty(category="recents") - public boolean isVisible; - - @ViewDebug.ExportedProperty(category = "recents") - public boolean isMinimized; + // Last snapshot data, only used for recent tasks + public ActivityManager.RecentTaskInfo.PersistedTaskSnapshotData lastSnapshotData = + new ActivityManager.RecentTaskInfo.PersistedTaskSnapshotData(); public Task() { // Do nothing @@ -309,13 +268,6 @@ public class Task { taskInfo.topActivity); } - /** - * Creates a task object from the given [taskInfo]. - */ - public static Task from(TaskInfo taskInfo) { - return from(new TaskKey(taskInfo), taskInfo, /* isLocked= */ false); - } - public Task(TaskKey key) { this.key = key; this.taskDescription = new TaskDescription(); @@ -324,10 +276,9 @@ public class Task { public Task(Task other) { this(other.key, other.colorPrimary, other.colorBackground, other.isDockable, other.isLocked, other.taskDescription, other.topActivity); + lastSnapshotData.set(other.lastSnapshotData); positionInParent = other.positionInParent; appBounds = other.appBounds; - isVisible = other.isVisible; - isMinimized = other.isMinimized; } /** @@ -355,10 +306,33 @@ public class Task { : key.baseIntent.getComponent(); } + public void setLastSnapshotData(ActivityManager.RecentTaskInfo rawTask) { + lastSnapshotData.set(rawTask.lastSnapshotData); + } + public TaskKey getKey() { return key; } + /** + * Returns the visible width to height ratio. Returns 0f if snapshot data is not available. + */ + public float getVisibleThumbnailRatio(boolean clipInsets) { + if (lastSnapshotData.taskSize == null || lastSnapshotData.contentInsets == null) { + return 0f; + } + + float availableWidth = lastSnapshotData.taskSize.x; + float availableHeight = lastSnapshotData.taskSize.y; + if (clipInsets) { + availableWidth -= + (lastSnapshotData.contentInsets.left + lastSnapshotData.contentInsets.right); + availableHeight -= + (lastSnapshotData.contentInsets.top + lastSnapshotData.contentInsets.bottom); + } + return availableWidth / availableHeight; + } + @Override public boolean equals(Object o) { if (o == this) { @@ -374,11 +348,6 @@ public class Task { return key.equals(t.key); } - @Override - public int hashCode() { - return key.hashCode(); - } - @Override public String toString() { return "[" + key.toString() + "] " + title; diff --git a/systemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java b/systemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java index b8cd5bec2c..3cb5bc745e 100644 --- a/systemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java +++ b/systemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java @@ -10,6 +10,7 @@ import android.graphics.Rect; import android.graphics.RectF; import com.android.systemui.shared.recents.model.ThumbnailData; +import com.android.wm.shell.util.SplitBounds; /** * Utility class to position the thumbnail in the TaskView @@ -34,6 +35,8 @@ public class PreviewPositionHelper { private final Matrix mMatrix = new Matrix(); private boolean mIsOrientationChanged; + private SplitBounds mSplitBounds; + private int mDesiredStagePosition; public Matrix getMatrix() { return mMatrix; @@ -47,6 +50,11 @@ public class PreviewPositionHelper { return mIsOrientationChanged; } + public void setSplitBounds(SplitBounds splitBounds, int desiredStagePosition) { + mSplitBounds = splitBounds; + mDesiredStagePosition = desiredStagePosition; + } + /** * Updates the matrix based on the provided parameters */ @@ -208,19 +216,4 @@ public class PreviewPositionHelper { } mMatrix.postTranslate(translateX, translateY); } - - /** - * A factory that returns a new instance of the {@link PreviewPositionHelper}. - *

{@link PreviewPositionHelper} is a stateful helper, and hence when using it in distinct - * scenarios, prefer fetching an object using this factory

- *

Additionally, helpful for injecting mocks in tests

- */ - public static class PreviewPositionHelperFactory { - /** - * Returns a new {@link PreviewPositionHelper} for use in a distinct scenario. - */ - public PreviewPositionHelper create() { - return new PreviewPositionHelper(); - } - } } diff --git a/systemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java b/systemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java index b1fc560f40..68d2eb3581 100644 --- a/systemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java +++ b/systemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java @@ -16,19 +16,16 @@ package com.android.systemui.shared.recents.utilities; -import static android.app.StatusBarManager.NAVBAR_BACK_DISMISS_IME; -import static android.app.StatusBarManager.NAVBAR_IME_SWITCHER_BUTTON_VISIBLE; -import static android.app.StatusBarManager.NAVBAR_IME_VISIBLE; -import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT; +import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN; +import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN; import android.annotation.TargetApi; -import android.app.StatusBarManager.NavbarFlags; import android.content.Context; import android.content.res.Resources; import android.graphics.Color; import android.graphics.Rect; import android.inputmethodservice.InputMethodService; -import android.inputmethodservice.InputMethodService.BackDispositionMode; import android.os.Build; import android.os.Handler; import android.os.Message; @@ -36,9 +33,6 @@ import android.util.DisplayMetrics; import android.view.Surface; import android.view.WindowManager; -import com.android.systemui.shared.recents.model.Task; -import com.android.systemui.utils.windowmanager.WindowManagerUtils; - /* Common code */ public class Utilities { @@ -108,52 +102,44 @@ public class Utilities { } /** - * Updates the navigation bar state flags with the given IME state. - * - * @param oldFlags current navigation bar state flags. - * @param backDisposition the IME back disposition mode. Only takes effect if - * {@code isImeVisible} is {@code true}. - * @param isImeVisible whether the IME is currently visible. - * @param showImeSwitcher whether the IME Switcher button should be shown. Only takes effect if - * {@code isImeVisible} is {@code true}. + * @return updated set of flags from InputMethodService based off {@param oldHints} + * Leaves original hints unmodified */ - @NavbarFlags - public static int updateNavbarFlagsFromIme(@NavbarFlags int oldFlags, - @BackDispositionMode int backDisposition, boolean isImeVisible, - boolean showImeSwitcher) { - int flags = oldFlags; + public static int calculateBackDispositionHints(int oldHints, int backDisposition, + boolean imeShown, boolean showImeSwitcher) { + int hints = oldHints; switch (backDisposition) { case InputMethodService.BACK_DISPOSITION_DEFAULT: case InputMethodService.BACK_DISPOSITION_WILL_NOT_DISMISS: case InputMethodService.BACK_DISPOSITION_WILL_DISMISS: - if (isImeVisible) { - flags |= NAVBAR_BACK_DISMISS_IME; + if (imeShown) { + hints |= NAVIGATION_HINT_BACK_ALT; } else { - flags &= ~NAVBAR_BACK_DISMISS_IME; + hints &= ~NAVIGATION_HINT_BACK_ALT; } break; case InputMethodService.BACK_DISPOSITION_ADJUST_NOTHING: - flags &= ~NAVBAR_BACK_DISMISS_IME; + hints &= ~NAVIGATION_HINT_BACK_ALT; break; } - if (isImeVisible) { - flags |= NAVBAR_IME_VISIBLE; + if (imeShown) { + hints |= NAVIGATION_HINT_IME_SHOWN; } else { - flags &= ~NAVBAR_IME_VISIBLE; + hints &= ~NAVIGATION_HINT_IME_SHOWN; } - if (showImeSwitcher && isImeVisible) { - flags |= NAVBAR_IME_SWITCHER_BUTTON_VISIBLE; + if (showImeSwitcher) { + hints |= NAVIGATION_HINT_IME_SWITCHER_SHOWN; } else { - flags &= ~NAVBAR_IME_SWITCHER_BUTTON_VISIBLE; + hints &= ~NAVIGATION_HINT_IME_SWITCHER_SHOWN; } - return flags; + return hints; } /** @return whether or not {@param context} represents that of a large screen device or not */ @TargetApi(Build.VERSION_CODES.R) public static boolean isLargeScreen(Context context) { - return isLargeScreen(WindowManagerUtils.getWindowManager(context), context.getResources()); + return isLargeScreen(context.getSystemService(WindowManager.class), context.getResources()); } /** @return whether or not {@param context} represents that of a large screen device or not */ @@ -169,10 +155,4 @@ public class Utilities { float densityRatio = (float) densityDpi / DisplayMetrics.DENSITY_DEFAULT; return (size / densityRatio); } - - /** Whether a task is in freeform mode. */ - public static boolean isFreeformTask(Task task) { - return task != null && task.getKey() != null - && task.getKey().windowingMode == WINDOWING_MODE_FREEFORM; - } } diff --git a/systemUI/shared/src/com/android/systemui/shared/recents/view/RecentsTransition.java b/systemUI/shared/src/com/android/systemui/shared/recents/view/RecentsTransition.java index c8de9f6660..b8319e51ec 100644 --- a/systemUI/shared/src/com/android/systemui/shared/recents/view/RecentsTransition.java +++ b/systemUI/shared/src/com/android/systemui/shared/recents/view/RecentsTransition.java @@ -35,6 +35,36 @@ import java.util.function.Consumer; */ public class RecentsTransition { + /** + * Creates a new transition aspect scaled transition activity options. + */ + public static ActivityOptions createAspectScaleAnimation(Context context, Handler handler, + boolean scaleUp, AppTransitionAnimationSpecsFuture animationSpecsFuture, + final Runnable animationStartCallback) { + final OnAnimationStartedListener animStartedListener = new OnAnimationStartedListener() { + private boolean mHandled; + + @Override + public void onAnimationStarted(long elapsedRealTime) { + // OnAnimationStartedListener can be called numerous times, so debounce here to + // prevent multiple callbacks + if (mHandled) { + return; + } + mHandled = true; + + if (animationStartCallback != null) { + animationStartCallback.run(); + } + } + }; + final ActivityOptions opts = ActivityOptions.makeMultiThumbFutureAspectScaleAnimation( + context, handler, + animationSpecsFuture != null ? animationSpecsFuture.getFuture() : null, + animStartedListener, scaleUp); + return opts; + } + /** * Wraps a animation-start callback in a binder that can be called from window manager. */ diff --git a/systemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt b/systemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt index 20380773e8..f3296f0632 100644 --- a/systemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt +++ b/systemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt @@ -136,9 +136,9 @@ constructor( val sampledRegionWithOffset = convertBounds(screenLocationBounds) if ( sampledRegionWithOffset.left < 0.0 || - sampledRegionWithOffset.right > 1.0 || - sampledRegionWithOffset.top < 0.0 || - sampledRegionWithOffset.bottom > 1.0 + sampledRegionWithOffset.right > 1.0 || + sampledRegionWithOffset.top < 0.0 || + sampledRegionWithOffset.bottom > 1.0 ) { if (DEBUG) Log.d( @@ -192,7 +192,7 @@ constructor( pw.println("screen width: ${displaySize.x}, screen height: ${displaySize.y}") pw.println( "sampledRegionWithOffset: ${convertBounds( - calculateScreenLocation(sampledView) ?: RectF())}" + calculateScreenLocation(sampledView) ?: RectF())}" ) pw.println( "initialSampling for ${if (isLockscreen) "lockscreen" else "homescreen" }" + diff --git a/systemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java b/systemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java index 570d774b95..317201d2c2 100644 --- a/systemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java +++ b/systemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java @@ -39,7 +39,6 @@ import androidx.annotation.BoolRes; import androidx.core.view.OneShotPreDrawListener; import com.android.systemui.shared.rotation.FloatingRotationButtonPositionCalculator.Position; -import com.android.systemui.utils.windowmanager.WindowManagerUtils; /** * Containing logic for the rotation button on the physical left bottom corner of the screen. @@ -89,7 +88,7 @@ public class FloatingRotationButton implements RotationButton { @DimenRes int taskbarBottomMargin, @DimenRes int buttonDiameter, @DimenRes int rippleMaxWidth, @BoolRes int floatingRotationBtnPositionLeftResource) { mContext = context; - mWindowManager = WindowManagerUtils.getWindowManager(mContext); + mWindowManager = mContext.getSystemService(WindowManager.class); mKeyButtonContainer = (ViewGroup) LayoutInflater.from(mContext).inflate(layout, null); mKeyButtonView = mKeyButtonContainer.findViewById(keyButtonId); mKeyButtonView.setVisibility(View.VISIBLE); @@ -126,7 +125,6 @@ public class FloatingRotationButton implements RotationButton { taskbarMarginLeft, taskbarMarginBottom, floatingRotationButtonPositionLeft); final int diameter = res.getDimensionPixelSize(mButtonDiameterResource); - mKeyButtonView.setDiameter(diameter); mContainerSize = diameter + Math.max(defaultMargin, Math.max(taskbarMarginLeft, taskbarMarginBottom)); } @@ -197,7 +195,6 @@ public class FloatingRotationButton implements RotationButton { public void updateIcon(int lightIconColor, int darkIconColor) { mAnimatedDrawable = (AnimatedVectorDrawable) mKeyButtonView.getContext() .getDrawable(mRotationButtonController.getIconResId()); - mAnimatedDrawable.setBounds(0, 0, mKeyButtonView.getWidth(), mKeyButtonView.getHeight()); mKeyButtonView.setImageDrawable(mAnimatedDrawable); mKeyButtonView.setColors(lightIconColor, darkIconColor); } @@ -251,14 +248,8 @@ public class FloatingRotationButton implements RotationButton { updateDimensionResources(); if (mIsShowing) { - updateIcon(mRotationButtonController.getLightIconColor(), - mRotationButtonController.getDarkIconColor()); final LayoutParams layoutParams = adjustViewPositionAndCreateLayoutParams(); mWindowManager.updateViewLayout(mKeyButtonContainer, layoutParams); - if (mAnimatedDrawable != null) { - mAnimatedDrawable.reset(); - mAnimatedDrawable.start(); - } } } diff --git a/systemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonView.java b/systemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonView.java index 75412f94cc..2145166e9b 100644 --- a/systemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonView.java +++ b/systemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonView.java @@ -37,7 +37,6 @@ public class FloatingRotationButtonView extends ImageView { private static final float BACKGROUND_ALPHA = 0.92f; private KeyButtonRipple mRipple; - private int mDiameter; private final Paint mOvalBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); private final Configuration mLastConfiguration; @@ -94,25 +93,10 @@ public class FloatingRotationButtonView extends ImageView { mRipple.setDarkIntensity(darkIntensity); } - /** - * Sets the view's diameter. - * - * @param diameter the diameter value for the view - */ - void setDiameter(int diameter) { - mDiameter = diameter; - } - @Override public void draw(Canvas canvas) { int d = Math.min(getWidth(), getHeight()); canvas.drawOval(0, 0, d, d, mOvalBgPaint); super.draw(canvas); } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - setMeasuredDimension(mDiameter, mDiameter); - } } diff --git a/systemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java b/systemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java index 2eac3931c2..660f0db042 100644 --- a/systemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java +++ b/systemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java @@ -53,9 +53,6 @@ import android.view.accessibility.AccessibilityManager; import android.view.animation.Interpolator; import android.view.animation.LinearInterpolator; -import androidx.annotation.Nullable; -import androidx.annotation.WorkerThread; - import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.UiEvent; import com.android.internal.logging.UiEventLogger; @@ -145,14 +142,12 @@ public class RotationButtonController { }; private final IRotationWatcher.Stub mRotationWatcher = new IRotationWatcher.Stub() { - @WorkerThread @Override public void onRotationChanged(final int rotation) { - @Nullable Boolean rotationLocked = RotationPolicyUtil.isRotationLocked(mContext); // We need this to be scheduled as early as possible to beat the redrawing of // window in response to the orientation change. mMainThreadHandler.postAtFrontOfQueue(() -> { - onRotationWatcherChanged(rotation, rotationLocked); + onRotationWatcherChanged(rotation); }); } }; @@ -286,8 +281,8 @@ public class RotationButtonController { TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener); } - public void setRotationLockedAtAngle( - @Nullable Boolean isLocked, int rotationSuggestion, String caller) { + public void setRotationLockedAtAngle(int rotationSuggestion, String caller) { + final Boolean isLocked = isRotationLocked(); if (isLocked == null) { // Ignore if we can't read the setting for the current user return; @@ -296,6 +291,21 @@ public class RotationButtonController { /* rotation= */ rotationSuggestion, caller); } + /** + * @return whether rotation is currently locked, or null if the setting couldn't + * be read + */ + public Boolean isRotationLocked() { + try { + return RotationPolicy.isRotationLocked(mContext); + } catch (SecurityException e) { + // TODO(b/279561841): RotationPolicy uses the current user to resolve the setting which + // may change before the rotation watcher can be unregistered + Log.e(TAG, "Failed to get isRotationLocked", e); + return null; + } + } + public void setRotateSuggestionButtonState(boolean visible) { setRotateSuggestionButtonState(visible, false /* force */); } @@ -459,7 +469,7 @@ public class RotationButtonController { * Called when the rotation watcher rotation changes, either from the watcher registered * internally in this class, or a signal propagated from NavBarHelper. */ - public void onRotationWatcherChanged(int rotation, @Nullable Boolean isRotationLocked) { + public void onRotationWatcherChanged(int rotation) { if (!mListenersRegistered) { // Ignore if not registered return; @@ -467,16 +477,17 @@ public class RotationButtonController { // If the screen rotation changes while locked, potentially update lock to flow with // new screen rotation and hide any showing suggestions. - if (isRotationLocked == null) { + Boolean rotationLocked = isRotationLocked(); + if (rotationLocked == null) { // Ignore if we can't read the setting for the current user return; } // The isVisible check makes the rotation button disappear when we are not locked // (e.g. for tabletop auto-rotate). - if (isRotationLocked || mRotationButton.isVisible()) { + if (rotationLocked || mRotationButton.isVisible()) { // Do not allow a change in rotation to set user rotation when docked. - if (shouldOverrideUserLockPrefs(rotation) && isRotationLocked && !mDocked) { - setRotationLockedAtAngle(true, rotation, /* caller= */ + if (shouldOverrideUserLockPrefs(rotation) && rotationLocked && !mDocked) { + setRotationLockedAtAngle(rotation, /* caller= */ "RotationButtonController#onRotationWatcherChanged"); } setRotateSuggestionButtonState(false /* visible */, true /* forced */); @@ -581,8 +592,7 @@ public class RotationButtonController { private void onRotateSuggestionClick(View v) { mUiEventLogger.log(RotationButtonEvent.ROTATION_SUGGESTION_ACCEPTED); incrementNumAcceptedRotationSuggestionsIfNeeded(); - setRotationLockedAtAngle( - RotationPolicyUtil.isRotationLocked(mContext), mLastRotationSuggestion, + setRotationLockedAtAngle(mLastRotationSuggestion, /* caller= */ "RotationButtonController#onRotateSuggestionClick"); Log.i(TAG, "onRotateSuggestionClick() mLastRotationSuggestion=" + mLastRotationSuggestion); v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); diff --git a/systemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextView.kt b/systemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextView.kt index e928525afc..bd20777c71 100644 --- a/systemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextView.kt +++ b/systemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextView.kt @@ -16,7 +16,6 @@ package com.android.systemui.shared.shadow import android.content.Context -import android.content.res.TypedArray import android.graphics.Canvas import android.graphics.drawable.Drawable import android.util.AttributeSet @@ -32,23 +31,19 @@ constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, - defStyleRes: Int = 0, + defStyleRes: Int = 0 ) : TextView(context, attrs, defStyleAttr, defStyleRes) { - private lateinit var mKeyShadowInfo: ShadowInfo - private lateinit var mAmbientShadowInfo: ShadowInfo + private val mKeyShadowInfo: ShadowInfo + private val mAmbientShadowInfo: ShadowInfo init { - updateShadowDrawables( + val attributes = context.obtainStyledAttributes( attrs, R.styleable.DoubleShadowTextView, defStyleAttr, - defStyleRes, + defStyleRes ) - ) - } - - private fun updateShadowDrawables(attributes: TypedArray) { val drawableSize: Int val drawableInsetSize: Int try { @@ -75,17 +70,17 @@ constructor( ambientShadowBlur, ambientShadowOffsetX, ambientShadowOffsetY, - ambientShadowAlpha, + ambientShadowAlpha ) drawableSize = attributes.getDimensionPixelSize( R.styleable.DoubleShadowTextView_drawableIconSize, - 0, + 0 ) drawableInsetSize = attributes.getDimensionPixelSize( R.styleable.DoubleShadowTextView_drawableIconInsetSize, - 0, + 0 ) } finally { attributes.recycle() @@ -100,19 +95,12 @@ constructor( mAmbientShadowInfo, drawable, drawableSize, - drawableInsetSize, + drawableInsetSize ) } setCompoundDrawablesRelative(drawables[0], drawables[1], drawables[2], drawables[3]) } - override fun setTextAppearance(resId: Int) { - super.setTextAppearance(resId) - updateShadowDrawables( - context.obtainStyledAttributes(resId, R.styleable.DoubleShadowTextView) - ) - } - public override fun onDraw(canvas: Canvas) { applyShadows(mKeyShadowInfo, mAmbientShadowInfo, this, canvas) { super.onDraw(canvas) } } diff --git a/systemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/systemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java index 4546844fee..baf8d75872 100644 --- a/systemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java +++ b/systemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java @@ -18,16 +18,13 @@ package com.android.systemui.shared.system; import static android.app.ActivityManager.LOCK_TASK_MODE_LOCKED; import static android.app.ActivityManager.LOCK_TASK_MODE_NONE; -import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE; import static android.app.ActivityTaskManager.getService; -import static app.lawnchair.compat.LawnchairQuickstepCompat.ATLEAST_R; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Activity; import android.app.ActivityClient; import android.app.ActivityManager; -import android.app.ActivityManager.RecentTaskInfo; import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityOptions; import android.app.ActivityTaskManager; @@ -41,7 +38,7 @@ import android.content.pm.UserInfo; import android.graphics.Rect; import android.os.Build; import android.os.Bundle; -import android.os.DeadSystemException; +import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; @@ -49,6 +46,8 @@ import android.os.SystemClock; import android.provider.Settings; import android.util.Log; import android.view.Display; +import android.view.IRecentsAnimationController; +import android.view.RemoteAnimationTarget; import android.window.TaskSnapshot; import com.android.internal.app.IVoiceInteractionManagerService; @@ -59,6 +58,11 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.List; +import java.util.function.Consumer; + +import app.lawnchair.compat.LawnchairQuickstepCompat; +import app.lawnchair.compatlib.RecentsAnimationRunnerCompat; +import app.lawnchair.compatlib.eleven.ActivityManagerCompatVR; public class ActivityManagerWrapper { @@ -83,17 +87,13 @@ public class ActivityManagerWrapper { /** * @return the current user's id. */ - public int getCurrentUserId() throws DeadSystemException { + public int getCurrentUserId() { UserInfo ui; try { ui = ActivityManager.getService().getCurrentUser(); return ui != null ? ui.id : 0; } catch (RemoteException e) { - if (ATLEAST_R) { - throw e.rethrowFromSystemServer(); - } else { - throw new DeadSystemException(); - } + throw e.rethrowFromSystemServer(); } } @@ -104,6 +104,14 @@ public class ActivityManagerWrapper { return getRunningTask(false /* filterVisibleRecents */); } + /** + * @return a list of the recents tasks. + */ + @NonNull + public List getRecentTasks(int numTasks, int userId) { + return LawnchairQuickstepCompat.getActivityManagerCompat().getRecentTasks(numTasks, userId); + } + /** * @return the top running task filtering only for tasks that can be visible in the recent tasks * list (can be {@code null}). @@ -212,13 +220,96 @@ public class ActivityManagerWrapper { } /** - * Preloads the recents activity. The caller should manage the thread on which this is called. + * Starts the recents activity. The caller should manage the thread on which this is called. */ - public void preloadRecentsActivity(Intent intent) { + public void startRecentsActivity(Intent intent, long eventTime, + final RecentsAnimationListener animationHandler, final Consumer resultCallback, + Handler resultCallbackHandler) { + boolean result = startRecentsActivity(intent, eventTime, animationHandler); + if (resultCallback != null && resultCallbackHandler != null) { + resultCallbackHandler.post(new Runnable() { + @Override + public void run() { + resultCallback.accept(result); + } + }); + } + } + + /** + * Starts the recents activity. The caller should manage the thread on which this is called. + */ + public boolean startRecentsActivity( + Intent intent, long eventTime, RecentsAnimationListener animationHandler) { try { - getService().preloadRecentsActivity(intent); + RecentsAnimationRunnerCompat runner = null; + if (animationHandler != null) { + runner = new RecentsAnimationRunnerCompat() { + @Override + public void onAnimationStart(IRecentsAnimationController controller, + RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers, + Rect homeContentInsets, Rect minimizedHomeBounds) { + final RecentsAnimationControllerCompat controllerCompat = + new RecentsAnimationControllerCompat(controller); + animationHandler.onAnimationStart(controllerCompat, apps, + wallpapers, homeContentInsets, minimizedHomeBounds, new Bundle()); + } + + @Override + public void onAnimationCanceled(int[] taskIds, TaskSnapshot[] taskSnapshots) { + animationHandler.onAnimationCanceled( + ThumbnailData.wrap(taskIds, taskSnapshots)); + } + + + /** + * compat for android 12/11/10 + */ + public void onAnimationCanceled(Object taskSnapshot) { + if (LawnchairQuickstepCompat.ATLEAST_S) { + animationHandler.onAnimationCanceled( + ThumbnailData.wrap(new int[]{0}, new TaskSnapshot[]{(TaskSnapshot) taskSnapshot})); + } else if (LawnchairQuickstepCompat.ATLEAST_R) { + ActivityManagerCompatVR compat = (ActivityManagerCompatVR) LawnchairQuickstepCompat.getActivityManagerCompat(); + ActivityManagerCompatVR.ThumbnailData data = compat.convertTaskSnapshotToThumbnailData(taskSnapshot); + HashMap thumbnailDatas = new HashMap<>(); + if (data != null) { + thumbnailDatas.put(0, new ThumbnailData()); + } + animationHandler.onAnimationCanceled(thumbnailDatas); + } else { + animationHandler.onAnimationCanceled(new HashMap<>()); + } + } + + /** + * compat for android 12/11 + */ + public void onTaskAppeared(RemoteAnimationTarget app) { + animationHandler.onTasksAppeared(new RemoteAnimationTarget[]{app}); + } + + @Override + public void onTasksAppeared(RemoteAnimationTarget[] apps) { + animationHandler.onTasksAppeared(apps); + } + }; + } + LawnchairQuickstepCompat.getActivityManagerCompat().startRecentsActivity(intent, eventTime, runner); + return true; } catch (Exception e) { - Log.w(TAG, "Failed to preload recents activity", e); + return false; + } + } + + /** + * Cancels the remote recents animation started from {@link #startRecentsActivity}. + */ + public void cancelRecentsAnimation(boolean restoreHomeRootTaskPosition) { + try { + getService().cancelRecentsAnimation(restoreHomeRootTaskPosition); + } catch (RemoteException e) { + Log.e(TAG, "Failed to cancel recents animation", e); } } @@ -229,6 +320,24 @@ public class ActivityManagerWrapper { return startActivityFromRecents(taskKey.id, options); } + /** + * Preloads the recents activity. The caller should manage the thread on which this is called. + */ + public void preloadRecentsActivity(Intent intent) { + try { + Class activityTaskManagerClass = Class.forName("android.app.ActivityTaskManager"); + Method getServiceMethod = activityTaskManagerClass.getMethod("getService"); + Object activityTaskManagerService = getServiceMethod.invoke(null); + Method preloadRecentsActivityMethod = activityTaskManagerService.getClass() + .getMethod("preloadRecentsActivity", Intent.class); + + preloadRecentsActivityMethod.invoke(activityTaskManagerService, intent); + } catch (Throwable e) { + Log.w(TAG, "Failed to preload recents activity", e); + startRecentsActivity(intent, 0, null, null, null); + } + } + /** * Starts a task from Recents synchronously. */ @@ -254,17 +363,6 @@ public class ActivityManagerWrapper { } } - /** - * Sets whether or not the specified task is perceptible. - */ - public boolean setTaskIsPerceptible(int taskId, boolean isPerceptible) { - try { - return getService().setTaskIsPerceptible(taskId, isPerceptible); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - /** * Removes a task by id. */ @@ -321,7 +419,7 @@ public class ActivityManagerWrapper { * Shows a voice session identified by {@code token} * @return true if the session was shown, false otherwise */ - public boolean showVoiceSession(IBinder token, @NonNull Bundle args, int flags, + public boolean showVoiceSession(@NonNull IBinder token, @NonNull Bundle args, int flags, @Nullable String attributionTag) { IVoiceInteractionManagerService service = IVoiceInteractionManagerService.Stub.asInterface( ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE)); @@ -356,9 +454,4 @@ public class ActivityManagerWrapper { return info.configuration.windowConfiguration.getActivityType() == WindowConfiguration.ACTIVITY_TYPE_HOME; } - - public boolean isRunningInTestHarness() { - return ActivityManager.isRunningInTestHarness() - || ActivityManager.isRunningInUserTestHarness(); - } } diff --git a/systemUI/shared/src/com/android/systemui/shared/system/BlurUtils.java b/systemUI/shared/src/com/android/systemui/shared/system/BlurUtils.java index 61b0e4d902..6627b776f8 100644 --- a/systemUI/shared/src/com/android/systemui/shared/system/BlurUtils.java +++ b/systemUI/shared/src/com/android/systemui/shared/system/BlurUtils.java @@ -21,6 +21,8 @@ import static android.view.CrossWindowBlurListeners.CROSS_WINDOW_BLUR_SUPPORTED; import android.app.ActivityManager; import android.os.SystemProperties; +import app.lawnchair.compat.LawnchairQuickstepCompat; + public abstract class BlurUtils { /** @@ -29,7 +31,7 @@ public abstract class BlurUtils { * @return {@code true} when supported. */ public static boolean supportsBlursOnWindows() { - return CROSS_WINDOW_BLUR_SUPPORTED && ActivityManager.isHighEndGfx() + return LawnchairQuickstepCompat.ATLEAST_R && CROSS_WINDOW_BLUR_SUPPORTED && ActivityManager.isHighEndGfx() && !SystemProperties.getBoolean("persist.sysui.disableBlur", false); } } diff --git a/systemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java b/systemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java index a63462bcf4..454ea4e631 100644 --- a/systemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java +++ b/systemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java @@ -32,7 +32,7 @@ public final class InteractionJankMonitorWrapper { * @param cujType the specific {@link Cuj.CujType}. */ public static void begin(View v, @Cuj.CujType int cujType) { - if (true) return; // LC-Ignored + if (true) return; InteractionJankMonitor.getInstance().begin(v, cujType); } @@ -44,7 +44,7 @@ public final class InteractionJankMonitorWrapper { * @param timeout duration to cancel the instrumentation in ms */ public static void begin(View v, @Cuj.CujType int cujType, long timeout) { - if (true) return; // LC-Ignored + if (true) return; Configuration.Builder builder = Configuration.Builder.withView(cujType, v) .setTimeout(timeout); @@ -59,7 +59,7 @@ public final class InteractionJankMonitorWrapper { * @param tag the tag to distinguish different flow of same type CUJ. */ public static void begin(View v, @Cuj.CujType int cujType, String tag) { - if (true) return; // LC-Ignored + if (true) return; Configuration.Builder builder = Configuration.Builder.withView(cujType, v); if (!TextUtils.isEmpty(tag)) { @@ -74,7 +74,7 @@ public final class InteractionJankMonitorWrapper { * @param cujType the specific {@link Cuj.CujType}. */ public static void end(@Cuj.CujType int cujType) { - if (true) return; // LC-Ignored + if (true) return; InteractionJankMonitor.getInstance().end(cujType); } @@ -82,13 +82,13 @@ public final class InteractionJankMonitorWrapper { * Cancel the trace session. */ public static void cancel(@Cuj.CujType int cujType) { - if (true) return; // LC-Ignored + if (true) return; InteractionJankMonitor.getInstance().cancel(cujType); } /** Return true if currently instrumenting a trace session. */ public static boolean isInstrumenting(@Cuj.CujType int cujType) { - if (true) return true; // LC-Ignored + if (true) return true; return InteractionJankMonitor.getInstance().isInstrumenting(cujType); } } diff --git a/systemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/systemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java index 89f699507e..2b663bf15b 100644 --- a/systemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java +++ b/systemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java @@ -25,17 +25,9 @@ import static com.android.systemui.shared.Flags.shadeAllowBackGesture; import android.annotation.LongDef; import android.content.Context; import android.content.res.Resources; -import android.os.Bundle; -import android.os.IBinder; -import android.os.IInterface; -import android.os.RemoteException; -import android.util.Log; import android.view.ViewConfiguration; import android.view.WindowManagerPolicyConstants; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - import com.android.internal.policy.ScreenDecorationsUtils; import java.lang.annotation.Retention; @@ -49,7 +41,10 @@ import app.lawnchair.compat.LawnchairQuickstepCompat; */ public class QuickStepContract { - private static final String TAG = "QuickStepContract"; + public static final String KEY_EXTRA_SYSUI_PROXY = "extra_sysui_proxy"; + public static final String KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER = "extra_unfold_animation"; + // See ISysuiUnlockAnimationController.aidl + public static final String KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER = "unlock_animation"; public static final String NAV_BAR_MODE_3BUTTON_OVERLAY = WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY; @@ -99,12 +94,12 @@ public class QuickStepContract { public static final long SYSUI_STATE_ONE_HANDED_ACTIVE = 1L << 16; // Allow system gesture no matter the system bar(s) is visible or not public static final long SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY = 1L << 17; - // The IME is visible. - public static final long SYSUI_STATE_IME_VISIBLE = 1L << 18; + // The IME is showing + public static final long SYSUI_STATE_IME_SHOWING = 1L << 18; // The window magnification is overlapped with system gesture insets at the bottom. public static final long SYSUI_STATE_MAGNIFICATION_OVERLAP = 1L << 19; - // The IME Switcher button is visible. - public static final long SYSUI_STATE_IME_SWITCHER_BUTTON_VISIBLE = 1L << 20; + // ImeSwitcher is showing + public static final long SYSUI_STATE_IME_SWITCHER_SHOWING = 1L << 20; // Device dozing/AOD state public static final long SYSUI_STATE_DEVICE_DOZING = 1L << 21; // The home feature is disabled (either by SUW/SysUI/device policy) @@ -131,14 +126,6 @@ public class QuickStepContract { public static final long SYSUI_STATE_SHORTCUT_HELPER_SHOWING = 1L << 32; // Touchpad gestures are disabled public static final long SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED = 1L << 33; - // PiP animation is running - public static final long SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING = 1L << 34; - // Communal hub is showing - public static final long SYSUI_STATE_COMMUNAL_HUB_SHOWING = 1L << 35; - // The back button is visually adjusted to indicate that it will dismiss the IME when pressed. - // This only takes effect while the IME is visible. By default, it is set while the IME is - // visible, but may be overridden by the backDispositionMode set by the IME. - public static final long SYSUI_STATE_BACK_DISMISS_IME = 1L << 36; // Mask for SystemUiStateFlags to isolate SYSUI_STATE_AWAKE and // SYSUI_STATE_WAKEFULNESS_TRANSITION, to match WAKEFULNESS_* constants @@ -152,7 +139,7 @@ public class QuickStepContract { SYSUI_STATE_WAKEFULNESS_TRANSITION | SYSUI_STATE_AWAKE; // Whether the back gesture is allowed (or ignored) by the Shade - public static final boolean ALLOW_BACK_GESTURE_IN_SHADE = shadeAllowBackGesture(); + public static final boolean ALLOW_BACK_GESTURE_IN_SHADE = false; @Retention(RetentionPolicy.SOURCE) @LongDef({SYSUI_STATE_SCREEN_PINNING, @@ -173,9 +160,9 @@ public class QuickStepContract { SYSUI_STATE_DIALOG_SHOWING, SYSUI_STATE_ONE_HANDED_ACTIVE, SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY, - SYSUI_STATE_IME_VISIBLE, + SYSUI_STATE_IME_SHOWING, SYSUI_STATE_MAGNIFICATION_OVERLAP, - SYSUI_STATE_IME_SWITCHER_BUTTON_VISIBLE, + SYSUI_STATE_IME_SWITCHER_SHOWING, SYSUI_STATE_DEVICE_DOZING, SYSUI_STATE_BACK_DISABLED, SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED, @@ -188,9 +175,6 @@ public class QuickStepContract { SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY, SYSUI_STATE_SHORTCUT_HELPER_SHOWING, SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED, - SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING, - SYSUI_STATE_COMMUNAL_HUB_SHOWING, - SYSUI_STATE_BACK_DISMISS_IME, }) public @interface SystemUiStateFlags {} @@ -250,14 +234,14 @@ public class QuickStepContract { if ((flags & SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY) != 0) { str.add("allow_gesture"); } - if ((flags & SYSUI_STATE_IME_VISIBLE) != 0) { + if ((flags & SYSUI_STATE_IME_SHOWING) != 0) { str.add("ime_visible"); } if ((flags & SYSUI_STATE_MAGNIFICATION_OVERLAP) != 0) { str.add("magnification_overlap"); } - if ((flags & SYSUI_STATE_IME_SWITCHER_BUTTON_VISIBLE) != 0) { - str.add("ime_switcher_button_visible"); + if ((flags & SYSUI_STATE_IME_SWITCHER_SHOWING) != 0) { + str.add("ime_switcher_showing"); } if ((flags & SYSUI_STATE_DEVICE_DOZING) != 0) { str.add("device_dozing"); @@ -295,15 +279,6 @@ public class QuickStepContract { if ((flags & SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED) != 0) { str.add("touchpad_gestures_disabled"); } - if ((flags & SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING) != 0) { - str.add("disable_gesture_pip_animating"); - } - if ((flags & SYSUI_STATE_COMMUNAL_HUB_SHOWING) != 0) { - str.add("communal_hub_showing"); - } - if ((flags & SYSUI_STATE_BACK_DISMISS_IME) != 0) { - str.add("back_dismiss_ime"); - } return str.toString(); } @@ -360,15 +335,6 @@ public class QuickStepContract { || (sysuiStateFlags & SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING) != 0) { return false; } - // Disable back gesture on the hub, but not when the shade is showing. - if ((sysuiStateFlags & SYSUI_STATE_COMMUNAL_HUB_SHOWING) != 0) { - // Use QS expanded signal as the notification panel is always considered visible - // expanded when on the lock screen and when opening hub over lock screen. This does - // mean that back gesture is disabled when opening shade over hub while in portrait - // mode, since QS is not expanded. - // TODO(b/370108274): allow back gesture on shade over hub in portrait - return (sysuiStateFlags & SYSUI_STATE_QUICK_SETTINGS_EXPANDED) == 0; - } if ((sysuiStateFlags & SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY) != 0) { sysuiStateFlags &= ~SYSUI_STATE_NAV_BAR_HIDDEN; } @@ -412,18 +378,21 @@ public class QuickStepContract { return mode == NAV_BAR_MODE_3BUTTON; } - // LC-specific - public static boolean sRecentsDisabled = false; - public static boolean sHasCustomCornerRadius = false; - public static float sCustomCornerRadius = 0f; - /** * Corner radius that should be used on windows in order to cover the display. * These values are expressed in pixels because they should not respect display or font * scaling. The corner radius may change when folding/unfolding the device. */ + public static boolean sRecentsDisabled = false; + public static boolean sHasCustomCornerRadius = false; + public static float sCustomCornerRadius = 0f; + + /** + * Corner radius that should be used on windows in order to cover the display. + * These values are expressed in pixels because they should not respect display or font + * scaling, this means that we don't have to reload them on config changes. + */ public static float getWindowCornerRadius(Context context) { - // LC-Wrapped if (sRecentsDisabled || !LawnchairQuickstepCompat.ATLEAST_S) { return 0; } @@ -441,27 +410,11 @@ public class QuickStepContract { * If live rounded corners are supported on windows. */ public static boolean supportsRoundedCornersOnWindows(Resources resources) { - // LC-Wrapped try { return ScreenDecorationsUtils.supportsRoundedCornersOnWindows(resources); + } catch (Throwable t) { return false; } } - - /** - * Adds the provided interface to the bundle using the interface descriptor as the key - */ - public static void addInterface(@Nullable IInterface iInterface, @NonNull Bundle out) { - if (iInterface != null) { - IBinder binder = iInterface.asBinder(); - if (binder != null) { - try { - out.putIBinder(binder.getInterfaceDescriptor(), binder); - } catch (RemoteException e) { - Log.d(TAG, "Invalid interface description " + binder, e); - } - } - } - } } diff --git a/systemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java b/systemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java index 883ef0a784..be2a250dd8 100644 --- a/systemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java +++ b/systemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java @@ -19,13 +19,13 @@ package com.android.systemui.shared.system; import android.os.Build; import android.os.RemoteException; import android.util.Log; -import android.view.RemoteAnimationTarget; +import android.view.IRecentsAnimationController; import android.view.SurfaceControl; import android.window.PictureInPictureSurfaceTransaction; -import android.window.WindowAnimationState; +import android.window.TaskSnapshot; import com.android.internal.os.IResultReceiver; -import com.android.wm.shell.recents.IRecentsAnimationController; +import com.android.systemui.shared.recents.model.ThumbnailData; import java.lang.reflect.Method; @@ -41,6 +41,18 @@ public class RecentsAnimationControllerCompat { mAnimationController = animationController; } + public ThumbnailData screenshotTask(int taskId) { + try { + final TaskSnapshot snapshot = mAnimationController.screenshotTask(taskId); + if (snapshot != null) { + return ThumbnailData.fromSnapshot(snapshot); + } + } catch (RemoteException e) { + Log.e(TAG, "Failed to screenshot task", e); + } + return new ThumbnailData(); + } + public void setInputConsumerEnabled(boolean enabled) { try { mAnimationController.setInputConsumerEnabled(enabled); @@ -49,6 +61,14 @@ public class RecentsAnimationControllerCompat { } } + public void setAnimationTargetsBehindSystemBars(boolean behindSystemBars) { + try { + mAnimationController.setAnimationTargetsBehindSystemBars(behindSystemBars); + } catch (RemoteException e) { + Log.e(TAG, "Failed to set whether animation targets are behind system bars", e); + } + } + /** * Sets the final surface transaction on a Task. This is used by Launcher to notify the system * that animating Activity to PiP has completed and the associated task surface should be @@ -103,6 +123,22 @@ public class RecentsAnimationControllerCompat { } } + public void setDeferCancelUntilNextTransition(boolean defer, boolean screenshot) { + try { + mAnimationController.setDeferCancelUntilNextTransition(defer, screenshot); + } catch (RemoteException e) { + Log.e(TAG, "Failed to set deferred cancel with screenshot", e); + } + } + + public void cleanupScreenshot() { + try { + mAnimationController.cleanupScreenshot(); + } catch (RemoteException e) { + Log.e(TAG, "Failed to clean up screenshot of recents animation", e); + } + } + /** * @see {{@link IRecentsAnimationController#setWillFinishToHome(boolean)}}. */ @@ -115,13 +151,14 @@ public class RecentsAnimationControllerCompat { } /** - * @see IRecentsAnimationController#handOffAnimation + * @see IRecentsAnimationController#removeTask */ - public void handOffAnimation(RemoteAnimationTarget[] targets, WindowAnimationState[] states) { + public boolean removeTask(int taskId) { try { - mAnimationController.handOffAnimation(targets, states); + return mAnimationController.removeTask(taskId); } catch (RemoteException e) { - Log.e(TAG, "Failed to hand off animation", e); + Log.e(TAG, "Failed to remove remote animation target", e); + return false; } } @@ -135,4 +172,15 @@ public class RecentsAnimationControllerCompat { Log.e(TAG, "Failed to detach the navigation bar from app", e); } } + + /** + * @see IRecentsAnimationController#animateNavigationBarToApp(long) + */ + public void animateNavigationBarToApp(long duration) { + try { + mAnimationController.animateNavigationBarToApp(duration); + } catch (RemoteException e) { + Log.e(TAG, "Failed to animate the navigation bar to app", e); + } + } } diff --git a/systemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java b/systemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java index fcde508b07..c82e0dee2a 100644 --- a/systemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java +++ b/systemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java @@ -16,10 +16,10 @@ package com.android.systemui.shared.system; -import android.annotation.Nullable; import android.graphics.Rect; import android.os.Bundle; import android.view.RemoteAnimationTarget; +import android.view.SurfaceControl; import android.window.TransitionInfo; import com.android.systemui.shared.recents.model.ThumbnailData; @@ -32,7 +32,13 @@ public interface RecentsAnimationListener { */ void onAnimationStart(RecentsAnimationControllerCompat controller, RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers, - Rect homeContentInsets, Rect minimizedHomeBounds, Bundle extras, TransitionInfo info); + Rect homeContentInsets, Rect minimizedHomeBounds, Bundle extras); + + // Introduced in NothingOS 2.5.5, needed in 2.6 + void onAnimationStart(RecentsAnimationControllerCompat controller, + TransitionInfo transitionInfo, SurfaceControl.Transaction transaction, + RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers, + Rect homeContentInsets, Rect minimizedHomeBounds); /** * Called when the animation into Recents was canceled. This call is made on the binder thread. @@ -43,5 +49,15 @@ public interface RecentsAnimationListener { * Called when the task of an activity that has been started while the recents animation * was running becomes ready for control. */ - void onTasksAppeared(RemoteAnimationTarget[] app, @Nullable TransitionInfo transitionInfo); + void onTasksAppeared(RemoteAnimationTarget[] app); + + /** + * Called to request that the current task tile be switched out for a screenshot (if not + * already). Once complete, onFinished should be called. + * @return true if this impl will call onFinished. No other onSwitchToScreenshot impls will + * be called afterwards (to avoid multiple calls to onFinished). + */ + default boolean onSwitchToScreenshot(Runnable onFinished) { + return false; + } } diff --git a/systemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java b/systemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java index cf8ec62f19..a8cbf275dc 100644 --- a/systemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java +++ b/systemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java @@ -36,6 +36,8 @@ import com.android.systemui.shared.recents.model.ThumbnailData; import java.util.ArrayList; import java.util.List; +import app.lawnchair.compat.LawnchairQuickstepCompat; + /** * Tracks all the task stack listeners */ @@ -167,9 +169,10 @@ public class TaskStackChangeListeners { if (!mRegistered) { // Register mTaskStackListener to IActivityManager only once if needed. try { + if (!LawnchairQuickstepCompat.ATLEAST_V) return; ActivityTaskManager.getService().registerTaskStackListener(this); mRegistered = true; - } catch (Exception e) { + } catch (Throwable e) { Log.w(TAG, "Failed to call registerTaskStackListener", e); } } @@ -184,9 +187,10 @@ public class TaskStackChangeListeners { if (isEmpty && mRegistered) { // Unregister mTaskStackListener once we have no more listeners try { + if (!LawnchairQuickstepCompat.ATLEAST_V) return; ActivityTaskManager.getService().unregisterTaskStackListener(this); mRegistered = false; - } catch (Exception e) { + } catch (Throwable e) { Log.w(TAG, "Failed to call unregisterTaskStackListener", e); } } diff --git a/systemUI/shared/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerManager.kt b/systemUI/shared/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerManager.kt index dd1ac5d6d0..44f90f4de5 100644 --- a/systemUI/shared/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerManager.kt +++ b/systemUI/shared/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerManager.kt @@ -28,7 +28,14 @@ class UncaughtExceptionPreHandlerManager @Inject constructor() { * Verifies that the global handler is set in Thread. If not, sets is up. */ private fun checkGlobalHandlerSetup() { - // LC-Ignored + val currentHandler = Thread.getDefaultUncaughtExceptionHandler() + if (currentHandler != globalUncaughtExceptionPreHandler) { + if (currentHandler is GlobalUncaughtExceptionHandler) { + throw IllegalStateException("Two UncaughtExceptionPreHandlerManagers created") + } + currentHandler?.let { addHandler(it) } + Thread.setDefaultUncaughtExceptionHandler(globalUncaughtExceptionPreHandler) + } } /** @@ -61,4 +68,4 @@ class UncaughtExceptionPreHandlerManager @Inject constructor() { handleUncaughtException(thread, throwable) } } -} +} \ No newline at end of file diff --git a/systemUI/unfold/build.gradle b/systemUI/unfold/build.gradle index 0fc0dad1e2..e9ead6dbb0 100644 --- a/systemUI/unfold/build.gradle +++ b/systemUI/unfold/build.gradle @@ -5,7 +5,7 @@ plugins { } android { - buildToolsVersion "36.1.0" + buildToolsVersion "35.0.1" namespace "com.android.systemui.unfold" buildFeatures { aidl true @@ -22,12 +22,12 @@ ksp { arg("dagger.hilt.disableModulesHaveInstallInCheck", "true") } -addFrameworkJar('framework-16.jar') +addFrameworkJar('framework-15.jar') compileOnlyCommonJars() dependencies { - implementation libs.dagger.hilt.android - ksp libs.dagger.hilt.compiler + implementation libs.hilt.android + ksp libs.hilt.compiler implementation libs.androidx.concurrent.futures implementation libs.androidx.lifecycle.common diff --git a/systemUI/unfold/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfig.kt b/systemUI/unfold/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfig.kt index 66a3ac4c27..336981140a 100644 --- a/systemUI/unfold/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfig.kt +++ b/systemUI/unfold/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfig.kt @@ -21,43 +21,37 @@ import javax.inject.Singleton @Singleton class ResourceUnfoldTransitionConfig @Inject constructor() : UnfoldTransitionConfig { - override val isEnabled: Boolean by lazy { - try { - val id = Resources.getSystem() - .getIdentifier("config_unfoldTransitionEnabled", "bool", "android") + private fun getBooleanResource(resourceName: String): Boolean { + val id = Resources.getSystem().getIdentifier(resourceName, "bool", "android") + return if (id != 0) { Resources.getSystem().getBoolean(id) - } catch (_: Resources.NotFoundException) { + } else { false } } + private fun getIntResource(resourceName: String): Int { + val id = Resources.getSystem().getIdentifier(resourceName, "integer", "android") + return if (id != 0) { + Resources.getSystem().getInteger(id) + } else { + 0 + } + } + + override val isEnabled: Boolean by lazy { + getBooleanResource("config_unfoldTransitionEnabled") + } + override val isHingeAngleEnabled: Boolean by lazy { - try { - val id = Resources.getSystem() - .getIdentifier("config_unfoldTransitionHingeAngle", "bool", "android") - Resources.getSystem().getBoolean(id) - } catch (_: Resources.NotFoundException) { - false - } + getBooleanResource("config_unfoldTransitionHingeAngle") } override val isHapticsEnabled: Boolean by lazy { - try { - val id = Resources.getSystem() - .getIdentifier("config_unfoldTransitionHapticsEnabled", "bool", "android") - Resources.getSystem().getBoolean(id) - } catch (_: Resources.NotFoundException) { - false - } + getBooleanResource("config_unfoldTransitionHapticsEnabled") } override val halfFoldedTimeoutMillis: Int by lazy { - try { - val id = Resources.getSystem() - .getIdentifier("config_unfoldTransitionHalfFoldedTimeout", "integer", "android") - Resources.getSystem().getInteger(id) - } catch (_: Resources.NotFoundException) { - 1000 - } + getIntResource("config_unfoldTransitionHalfFoldedTimeout") } } diff --git a/systemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt b/systemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt index 3a0f8c0b02..a10097427a 100644 --- a/systemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt +++ b/systemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt @@ -128,11 +128,7 @@ constructor( val currentDirection = if (angle < lastHingeAngle) FOLD_UPDATE_START_CLOSING else FOLD_UPDATE_START_OPENING - val changedDirectionWhileInTransition = - isTransitionInProgress && currentDirection != lastFoldUpdate - val unfoldedPastThresholdSinceLastTransition = - angle - lastHingeAngleBeforeTransition > HINGE_ANGLE_CHANGE_THRESHOLD_DEGREES - if (changedDirectionWhileInTransition || unfoldedPastThresholdSinceLastTransition) { + if (isTransitionInProgress && currentDirection != lastFoldUpdate) { lastHingeAngleBeforeTransition = lastHingeAngle } @@ -157,7 +153,7 @@ constructor( isOnLargeScreen // Avoids sending closing event when on small screen. // Start event is sent regardless due to hall sensor. ) { - notifyFoldUpdate(transitionUpdate, angle) + notifyFoldUpdate(transitionUpdate, lastHingeAngle) } if (isTransitionInProgress) { @@ -306,7 +302,7 @@ constructor( } private fun isOnLargeScreen(): Boolean { - return context.applicationContext.resources.configuration.smallestScreenWidthDp > + return context.resources.configuration.smallestScreenWidthDp > INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP } diff --git a/systemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt b/systemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt index b1e6fe246f..0458f535c1 100644 --- a/systemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt +++ b/systemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt @@ -103,7 +103,7 @@ constructor( if (displayId == display.displayId) { val currentRotation = display.rotation - if (lastRotation.getAndSet(currentRotation) != currentRotation) { + if (lastRotation.compareAndSet(lastRotation.get(), currentRotation)) { listeners.forEach { it.onRotationChanged(currentRotation) } } } diff --git a/systemUI/viewcapture/Android.bp b/systemUI/viewcapture/Android.bp index fa772e6b73..33da2dd36d 100644 --- a/systemUI/viewcapture/Android.bp +++ b/systemUI/viewcapture/Android.bp @@ -13,7 +13,6 @@ // limitations under the License. package { - default_team: "trendy_team_launcher", default_applicable_licenses: ["Android-Apache-2.0"], } @@ -22,8 +21,8 @@ java_library { srcs: ["src/com/android/app/viewcapture/proto/*.proto"], proto: { type: "lite", - local_include_dirs: [ - "src/com/android/app/viewcapture/proto", + local_include_dirs:[ + "src/com/android/app/viewcapture/proto" ], }, static_libs: ["libprotobuf-java-lite"], @@ -34,7 +33,7 @@ android_library { name: "view_capture", manifest: "AndroidManifest.xml", platform_apis: true, - min_sdk_version: "30", + min_sdk_version: "26", static_libs: [ "androidx.core_core", @@ -43,7 +42,7 @@ android_library { srcs: [ "src/**/*.java", - "src/**/*.kt", + "src/**/*.kt" ], } @@ -51,7 +50,7 @@ android_test { name: "view_capture_tests", manifest: "tests/AndroidManifest.xml", platform_apis: true, - min_sdk_version: "30", + min_sdk_version: "26", static_libs: [ "androidx.core_core", @@ -59,20 +58,16 @@ android_test { "androidx.test.ext.junit", "androidx.test.rules", "testables", - "mockito-kotlin2", "mockito-target-extended-minus-junit4", ], srcs: [ "**/*.java", - "**/*.kt", + "**/*.kt" ], libs: [ - "android.test.runner.stubs.system", - "android.test.base.stubs.system", - "android.test.mock.stubs.system", - ], - jni_libs: [ - "libdexmakerjvmtiagent", + "android.test.runner", + "android.test.base", + "android.test.mock", ], test_suites: ["device-tests"], } diff --git a/systemUI/viewcapture/AndroidManifest.xml b/systemUI/viewcapture/AndroidManifest.xml index d86f1c5bca..1da812903b 100644 --- a/systemUI/viewcapture/AndroidManifest.xml +++ b/systemUI/viewcapture/AndroidManifest.xml @@ -15,4 +15,9 @@ limitations under the License. --> - \ No newline at end of file + + + diff --git a/systemUI/viewcapture/OWNERS b/systemUI/viewcapture/OWNERS index 2f30b7c445..30bdc84a4d 100644 --- a/systemUI/viewcapture/OWNERS +++ b/systemUI/viewcapture/OWNERS @@ -1,3 +1,2 @@ sunnygoyal@google.com andonian@google.com -include platform/development:/tools/winscope/OWNERS diff --git a/systemUI/viewcapture/README.md b/systemUI/viewcapture/README.md index 4a6993f30d..e2834cb22b 100644 --- a/systemUI/viewcapture/README.md +++ b/systemUI/viewcapture/README.md @@ -1,11 +1,11 @@ -###ViewCapture Library Readme +# ViewCapture Library -ViewCapture.java is extremely performance sensitive. Any changes should be carried out with great caution not to hurt performance. +> [!CAUTION] +> `ViewCapture.java` is **extremely performance sensitive**. +> **Any changes should be carried out with great caution** not to hurt performance. The following measurements should serve as a performance baseline (as of 02.10.2022): - The onDraw() function invocation time in WindowListener within ViewCapture is measured with System.nanoTime(). The following scenario was measured: 1. Capturing the notification shade window root view on a freshly rebooted bluejay device (2 notifications present) -> avg. time = 204237ns (0.2ms) - diff --git a/systemUI/viewcapture/build.gradle b/systemUI/viewcapture/build.gradle index 6b2f9bf2da..d965880433 100644 --- a/systemUI/viewcapture/build.gradle +++ b/systemUI/viewcapture/build.gradle @@ -5,12 +5,7 @@ plugins { } android { - namespace = "com.android.app.viewcapture" - testNamespace = "com.android.app.viewcapture.test" - defaultConfig { - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - + namespace "com.android.app.viewcapture" sourceSets { main { java.srcDirs = ['src'] @@ -22,11 +17,7 @@ android { manifest.srcFile "tests/AndroidManifest.xml" } } - lint { - abortOnError false - } - } -addFrameworkJar('framework-16.jar') +addFrameworkJar('framework-15.jar') compileOnlyCommonJars() diff --git a/systemUI/viewcapture/src/com/android/app/viewcapture/NoOpViewCapture.kt b/systemUI/viewcapture/src/com/android/app/viewcapture/NoOpViewCapture.kt index 795212d12d..743bdfcadb 100644 --- a/systemUI/viewcapture/src/com/android/app/viewcapture/NoOpViewCapture.kt +++ b/systemUI/viewcapture/src/com/android/app/viewcapture/NoOpViewCapture.kt @@ -10,7 +10,7 @@ import android.view.Window * 1p apps, and has memory / cpu load that we don't want to risk negatively impacting release builds */ class NoOpViewCapture: ViewCapture(0, 0, - createAndStartNewLooperExecutor("NoOpViewCapture", HandlerThread.MIN_PRIORITY)) { + createAndStartNewLooperExecutor("NoOpViewCapture", HandlerThread.MIN_PRIORITY)) { override fun startCapture(view: View, name: String): SafeCloseable { return SafeCloseable { } } diff --git a/systemUI/viewcapture/src/com/android/app/viewcapture/SettingsAwareViewCapture.kt b/systemUI/viewcapture/src/com/android/app/viewcapture/SettingsAwareViewCapture.kt index 5879669ba8..f75166b73e 100644 --- a/systemUI/viewcapture/src/com/android/app/viewcapture/SettingsAwareViewCapture.kt +++ b/systemUI/viewcapture/src/com/android/app/viewcapture/SettingsAwareViewCapture.kt @@ -19,8 +19,11 @@ package com.android.app.viewcapture import android.content.Context import android.content.pm.LauncherApps import android.database.ContentObserver +import android.os.Build import android.os.Handler +import android.os.Looper import android.os.ParcelFileDescriptor +import android.os.Process import android.provider.Settings import android.util.Log import android.window.IDumpCallback @@ -35,11 +38,12 @@ private val TAG = SettingsAwareViewCapture::class.java.simpleName * WindowListeners accordingly. The Settings toggle is currently controlled by the Winscope * developer tile in the System developer options. */ -internal class SettingsAwareViewCapture -internal constructor(private val context: Context, executor: Executor) : - ViewCapture(DEFAULT_MEMORY_SIZE, DEFAULT_INIT_POOL_SIZE, executor) { +class SettingsAwareViewCapture +@VisibleForTesting +internal constructor(private val context: Context, executor: Executor) + : ViewCapture(DEFAULT_MEMORY_SIZE, DEFAULT_INIT_POOL_SIZE, executor) { /** Dumps all the active view captures to the wm trace directory via LauncherAppService */ - private val mDumpCallback: IDumpCallback.Stub = object : IDumpCallback.Stub() { + private val mDumpCallback: IDumpCallback.Stub? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) object : IDumpCallback.Stub() { override fun onDump(out: ParcelFileDescriptor) { try { ParcelFileDescriptor.AutoCloseOutputStream(out).use { os -> dumpTo(os, context) } @@ -47,20 +51,18 @@ internal constructor(private val context: Context, executor: Executor) : Log.e(TAG, "failed to dump data to wm trace", e) } } - } + } else null init { enableOrDisableWindowListeners() - mBgExecutor.execute { - context.contentResolver.registerContentObserver( - Settings.Global.getUriFor(VIEW_CAPTURE_ENABLED), - false, - object : ContentObserver(Handler()) { - override fun onChange(selfChange: Boolean) { - enableOrDisableWindowListeners() - } - }) - } + context.contentResolver.registerContentObserver( + Settings.Global.getUriFor(VIEW_CAPTURE_ENABLED), + false, + object : ContentObserver(Handler()) { + override fun onChange(selfChange: Boolean) { + enableOrDisableWindowListeners() + } + }) } @AnyThread @@ -72,15 +74,34 @@ internal constructor(private val context: Context, executor: Executor) : enableOrDisableWindowListeners(isEnabled) } val launcherApps = context.getSystemService(LauncherApps::class.java) - if (isEnabled) { - //launcherApps?.registerDumpCallback(mDumpCallback) - } else { - //launcherApps?.unRegisterDumpCallback(mDumpCallback) + if (mDumpCallback != null) { + if (isEnabled) { + launcherApps?.registerDumpCallback(mDumpCallback) + } else { + launcherApps?.unRegisterDumpCallback(mDumpCallback) + } } } } companion object { @VisibleForTesting internal const val VIEW_CAPTURE_ENABLED = "view_capture_enabled" + + private var INSTANCE: ViewCapture? = null + + @JvmStatic + fun getInstance(context: Context): ViewCapture = when { + INSTANCE != null -> INSTANCE!! + Looper.myLooper() == Looper.getMainLooper() -> SettingsAwareViewCapture( + context.applicationContext, + createAndStartNewLooperExecutor("SAViewCapture", + Process.THREAD_PRIORITY_FOREGROUND)).also { INSTANCE = it } + else -> try { + MAIN_EXECUTOR.submit { getInstance(context) }.get() + } catch (e: Exception) { + throw e + } + } + } -} +} \ No newline at end of file diff --git a/systemUI/viewcapture/src/com/android/app/viewcapture/ViewCapture.java b/systemUI/viewcapture/src/com/android/app/viewcapture/ViewCapture.java index e6f0c72da0..85df9a9e36 100644 --- a/systemUI/viewcapture/src/com/android/app/viewcapture/ViewCapture.java +++ b/systemUI/viewcapture/src/com/android/app/viewcapture/ViewCapture.java @@ -19,18 +19,18 @@ package com.android.app.viewcapture; import static com.android.app.viewcapture.data.ExportedData.MagicNumber.MAGIC_NUMBER_H; import static com.android.app.viewcapture.data.ExportedData.MagicNumber.MAGIC_NUMBER_L; +import static java.util.stream.Collectors.toList; + import android.content.ComponentCallbacks2; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; import android.media.permission.SafeCloseable; -import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.SystemClock; import android.os.Trace; import android.text.TextUtils; -import android.util.Log; import android.util.SparseArray; import android.view.View; import android.view.ViewGroup; @@ -54,7 +54,6 @@ import java.io.DataOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; @@ -63,7 +62,6 @@ import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import java.util.function.Predicate; -import java.util.stream.Collectors; /** * Utility class for capturing view data every frame @@ -81,10 +79,6 @@ public abstract class ViewCapture { // Number of frames to keep in memory private final int mMemorySize; - - // Number of ViewPropertyRef to preallocate per window - private final int mInitPoolSize; - protected static final int DEFAULT_MEMORY_SIZE = 2000; // Initial size of the reference pool. This is at least be 5 * total number of views in // Launcher. This allows the first free frames avoid object allocation during view capture. @@ -92,19 +86,18 @@ public abstract class ViewCapture { public static final LooperExecutor MAIN_EXECUTOR = new LooperExecutor(Looper.getMainLooper()); - private final List mListeners = Collections.synchronizedList(new ArrayList<>()); + private final List mListeners = new ArrayList<>(); protected final Executor mBgExecutor; + // Pool used for capturing view tree on the UI thread. + private ViewRef mPool = new ViewRef(); private boolean mIsEnabled = true; - @VisibleForTesting - public boolean mIsStarted = false; - protected ViewCapture(int memorySize, int initPoolSize, Executor bgExecutor) { mMemorySize = memorySize; mBgExecutor = bgExecutor; - mInitPoolSize = initPoolSize; + mBgExecutor.execute(() -> initPool(initPoolSize)); } public static LooperExecutor createAndStartNewLooperExecutor(String name, int priority) { @@ -113,10 +106,29 @@ public abstract class ViewCapture { return new LooperExecutor(thread.getLooper()); } + @UiThread + private void addToPool(ViewRef start, ViewRef end) { + end.next = mPool; + mPool = start; + } + + @WorkerThread + private void initPool(int initPoolSize) { + ViewRef start = new ViewRef(); + ViewRef current = start; + + for (int i = 0; i < initPoolSize; i++) { + current.next = new ViewRef(); + current = current.next; + } + + ViewRef finalCurrent = current; + MAIN_EXECUTOR.execute(() -> addToPool(start, finalCurrent)); + } + /** * Attaches the ViewCapture to the provided window and returns a handle to detach the listener */ - @AnyThread @NonNull public SafeCloseable startCapture(@NonNull Window window) { String title = window.getAttributes().getTitle().toString(); @@ -128,18 +140,11 @@ public abstract class ViewCapture { * Attaches the ViewCapture to the provided window and returns a handle to detach the listener. * Verifies that ViewCapture is enabled before actually attaching an onDrawListener. */ - @AnyThread @NonNull public SafeCloseable startCapture(@NonNull View view, @NonNull String name) { - mIsStarted = true; WindowListener listener = new WindowListener(view, name); - - if (mIsEnabled) { - listener.attachToRoot(); - } - + if (mIsEnabled) MAIN_EXECUTOR.execute(listener::attachToRoot); mListeners.add(listener); - view.getContext().registerComponentCallbacks(listener); return () -> { @@ -147,7 +152,6 @@ public abstract class ViewCapture { listener.mRoot.getContext().unregisterComponentCallbacks(listener); } mListeners.remove(listener); - listener.detachFromRoot(); }; } @@ -162,22 +166,16 @@ public abstract class ViewCapture { * are still technically enabled to allow for dumping. */ @VisibleForTesting - @AnyThread public void stopCapture(@NonNull View rootView) { - mIsStarted = false; mListeners.forEach(it -> { if (rootView == it.mRoot) { - runOnUiThread(() -> { - if (it.mRoot != null) { - it.mRoot.getViewTreeObserver().removeOnDrawListener(it); - it.mRoot = null; - } - }, it.mRoot); + it.mRoot.getViewTreeObserver().removeOnDrawListener(it); + it.mRoot = null; } }); } - @AnyThread + @UiThread protected void enableOrDisableWindowListeners(boolean isEnabled) { mIsEnabled = isEnabled; mListeners.forEach(WindowListener::detachFromRoot); @@ -210,7 +208,7 @@ public abstract class ViewCapture { } private static List toStringList(List classList) { - return classList.stream().map(Class::getName).collect(Collectors.toList()); + return classList.stream().map(Class::getName).collect(toList()); } public CompletableFuture> getDumpTask(View view) { @@ -225,40 +223,17 @@ public abstract class ViewCapture { @AnyThread private CompletableFuture> getWindowData(Context context, - ArrayList outClassList, Predicate filter) { + ArrayList outClassList, Predicate filter) { ViewIdProvider idProvider = new ViewIdProvider(context.getResources()); - return CompletableFuture.supplyAsync( - () -> mListeners.stream() - .filter(filter) - .collect(Collectors.toList()), - MAIN_EXECUTOR).thenApplyAsync( - it -> it.stream() - .map(l -> l.dumpToProto(idProvider, outClassList)) - .collect(Collectors.toList()), - mBgExecutor); + return CompletableFuture.supplyAsync(() -> + mListeners.stream().filter(filter).collect(toList()), MAIN_EXECUTOR).thenApplyAsync(it -> + it.stream().map(l -> l.dumpToProto(idProvider, outClassList)).collect(toList()), + mBgExecutor); } @WorkerThread protected void onCapturedViewPropertiesBg(long elapsedRealtimeNanos, String windowName, - ViewPropertyRef startFlattenedViewTree) { - } - - @AnyThread - void runOnUiThread(Runnable action, View view) { - if (view == null) { - // Corner case. E.g.: the capture is stopped (root view set to null), - // but the bg thread is still processing work. - Log.i(TAG, "Skipping run on UI thread. Provided view == null."); - return; - } - - Handler handlerUi = view.getHandler(); - if (handlerUi != null && handlerUi.getLooper().getThread() == Thread.currentThread()) { - action.run(); - return; - } - - view.post(action); + ViewPropertyRef startFlattenedViewTree) { } /** @@ -268,11 +243,11 @@ public abstract class ViewCapture { * background thread, and prepared for being dumped into a bugreport. *

* Since some of the work needs to be done on the main thread after every draw, this piece of - * code needs to be hyper optimized. That is why we are recycling ViewPropertyRef objects - * and storing the list of nodes as a flat LinkedList, rather than as a tree. This data + * code needs to be hyper optimized. That is why we are recycling ViewRef and ViewPropertyRef + * objects and storing the list of nodes as a flat LinkedList, rather than as a tree. This data * structure allows recycling to happen in O(1) time via pointer assignment. Without this - * optimization, a lot of time is wasted creating ViewPropertyRef objects, or finding - * ViewPropertyRef objects to recycle. + * optimization, a lot of time is wasted creating ViewRef objects, or finding ViewRef objects to + * recycle. *

* Another optimization is to only traverse view nodes on the main thread that have potentially * changed since the last frame was drawn. This can be determined via a combination of private @@ -292,7 +267,7 @@ public abstract class ViewCapture { * TODO: b/262585897: Another memory optimization could be to store all integer, float, and * boolean information via single integer values via the Chinese remainder theorem, or a similar * algorithm, which enables multiple numerical values to be stored inside 1 number. Doing this - * would allow each ViewPropertyRef to slim down its memory footprint significantly. + * would allow each ViewProperty / ViewRef to slim down its memory footprint significantly. *

* One important thing to remember is that bugs related to recycling will usually only appear * after at least 2000 frames have been rendered. If that code is changed, the tester can @@ -310,9 +285,7 @@ public abstract class ViewCapture { public View mRoot; public final String name; - // Pool used for capturing view tree on the UI thread. - private ViewPropertyRef mPool = new ViewPropertyRef(); - private final ViewPropertyRef mViewPropertyRef = new ViewPropertyRef(); + private final ViewRef mViewRef = new ViewRef(); private int mFrameIndexBg = -1; private boolean mIsFirstFrame = true; @@ -320,13 +293,11 @@ public abstract class ViewCapture { private ViewPropertyRef[] mNodesBg = new ViewPropertyRef[mMemorySize]; private boolean mIsActive = true; - private final Consumer mCaptureCallback = - this::copyCleanViewsFromLastFrameBg; + private final Consumer mCaptureCallback = this::captureViewPropertiesBg; WindowListener(View view, String name) { mRoot = view; this.name = name; - initPool(mInitPoolSize); } /** @@ -336,39 +307,28 @@ public abstract class ViewCapture { * thread via mExecutor. */ @Override - @UiThread public void onDraw() { Trace.beginSection("vc#onDraw"); - try { - View root = mRoot; - if (root == null) { - // Handle the corner case where another (non-UI) thread - // concurrently stopped the capture and set mRoot = null - return; - } - captureViewTree(root, mViewPropertyRef); - ViewPropertyRef captured = mViewPropertyRef.next; - if (captured != null) { - captured.callback = mCaptureCallback; - captured.elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos(); - mBgExecutor.execute(captured); - } - mIsFirstFrame = false; - } finally { - Trace.endSection(); + captureViewTree(mRoot, mViewRef); + ViewRef captured = mViewRef.next; + if (captured != null) { + captured.callback = mCaptureCallback; + captured.elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos(); + mBgExecutor.execute(captured); } + mIsFirstFrame = false; + Trace.endSection(); } /** - * Copy clean views from the last frame on the background thread. Clean views are - * the remaining part of the view hierarchy that was not already copied by the UI thread. - * Then transfer the received ViewPropertyRef objects back to the UI thread's pool. + * Captures the View property on the background thread, and transfer all the ViewRef objects + * back to the pool */ @WorkerThread - private void copyCleanViewsFromLastFrameBg(ViewPropertyRef start) { - Trace.beginSection("vc#copyCleanViewsFromLastFrameBg"); + private void captureViewPropertiesBg(ViewRef viewRefStart) { + Trace.beginSection("vc#captureViewPropertiesBg"); - long elapsedRealtimeNanos = start.elapsedRealtimeNanos; + long elapsedRealtimeNanos = viewRefStart.elapsedRealtimeNanos; mFrameIndexBg++; if (mFrameIndexBg >= mMemorySize) { mFrameIndexBg = 0; @@ -380,11 +340,8 @@ public abstract class ViewCapture { ViewPropertyRef resultStart = null; ViewPropertyRef resultEnd = null; - ViewPropertyRef end = start; - - while (end != null) { - end.completeTransferFromViewBg(); - + ViewRef viewRefEnd = viewRefStart; + while (viewRefEnd != null) { ViewPropertyRef propertyRef = recycle; if (propertyRef == null) { propertyRef = new ViewPropertyRef(); @@ -394,15 +351,11 @@ public abstract class ViewCapture { } ViewPropertyRef copy = null; - if (end.childCount < 0) { - copy = findInLastFrame(end.hashCode); - if (copy != null) { - copy.transferTo(end); - } else { - end.childCount = 0; - } + if (viewRefEnd.childCount < 0) { + copy = findInLastFrame(viewRefEnd.view.hashCode()); + viewRefEnd.childCount = (copy != null) ? copy.childCount : 0; } - end.transferTo(propertyRef); + viewRefEnd.transferTo(propertyRef); if (resultStart == null) { resultStart = propertyRef; @@ -433,14 +386,14 @@ public abstract class ViewCapture { } } - if (end.next == null) { + if (viewRefEnd.next == null) { // The compiler will complain about using a non-final variable from - // an outer class in a lambda if we pass in 'end' directly. - final ViewPropertyRef finalEnd = end; - runOnUiThread(() -> addToPool(start, finalEnd), mRoot); + // an outer class in a lambda if we pass in viewRefEnd directly. + final ViewRef finalViewRefEnd = viewRefEnd; + MAIN_EXECUTOR.execute(() -> addToPool(viewRefStart, finalViewRefEnd)); break; } - end = end.next; + viewRefEnd = viewRefEnd.next; } mNodesBg[mFrameIndexBg] = resultStart; @@ -449,7 +402,6 @@ public abstract class ViewCapture { Trace.endSection(); } - @WorkerThread private @Nullable ViewPropertyRef findInLastFrame(int hashCode) { int lastFrameIndex = (mFrameIndexBg == 0) ? mMemorySize - 1 : mFrameIndexBg - 1; ViewPropertyRef viewPropertyRef = mNodesBg[lastFrameIndex]; @@ -459,72 +411,35 @@ public abstract class ViewCapture { return viewPropertyRef; } - private void initPool(int initPoolSize) { - ViewPropertyRef start = new ViewPropertyRef(); - ViewPropertyRef current = start; - - for (int i = 0; i < initPoolSize; i++) { - current.next = new ViewPropertyRef(); - current = current.next; - } - - ViewPropertyRef finalCurrent = current; - addToPool(start, finalCurrent); - } - - private void addToPool(ViewPropertyRef start, ViewPropertyRef end) { - end.next = mPool; - mPool = start; - } - - @UiThread - private ViewPropertyRef getFromPool() { - ViewPropertyRef ref = mPool; - if (ref != null) { - mPool = ref.next; - ref.next = null; - } else { - ref = new ViewPropertyRef(); - } - return ref; - } - - @AnyThread void attachToRoot() { if (mRoot == null) return; mIsActive = true; - runOnUiThread(() -> { - if (mRoot.isAttachedToWindow()) { - safelyEnableOnDrawListener(); - } else { - mRoot.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { - @Override - public void onViewAttachedToWindow(View v) { - if (mIsActive) { - safelyEnableOnDrawListener(); - } - mRoot.removeOnAttachStateChangeListener(this); + if (mRoot.isAttachedToWindow()) { + safelyEnableOnDrawListener(); + } else { + mRoot.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(View v) { + if (mIsActive) { + safelyEnableOnDrawListener(); } + mRoot.removeOnAttachStateChangeListener(this); + } - @Override - public void onViewDetachedFromWindow(View v) { - } - }); - } - }, mRoot); + @Override + public void onViewDetachedFromWindow(View v) { + } + }); + } } - @AnyThread void detachFromRoot() { mIsActive = false; - runOnUiThread(() -> { - if (mRoot != null) { - mRoot.getViewTreeObserver().removeOnDrawListener(this); - } - }, mRoot); + if (mRoot != null) { + mRoot.getViewTreeObserver().removeOnDrawListener(this); + } } - @UiThread private void safelyEnableOnDrawListener() { if (mRoot != null) { mRoot.getViewTreeObserver().removeOnDrawListener(this); @@ -548,9 +463,16 @@ public abstract class ViewCapture { return builder.build(); } - @UiThread - private ViewPropertyRef captureViewTree(View view, ViewPropertyRef start) { - ViewPropertyRef ref = getFromPool(); + private ViewRef captureViewTree(View view, ViewRef start) { + ViewRef ref; + if (mPool != null) { + ref = mPool; + mPool = mPool.next; + ref.next = null; + } else { + ref = new ViewRef(); + } + ref.view = view; start.next = ref; if (view instanceof ViewGroup) { ViewGroup parent = (ViewGroup) view; @@ -559,20 +481,17 @@ public abstract class ViewCapture { if ((view.mPrivateFlags & (PFLAG_INVALIDATED | PFLAG_DIRTY_MASK)) == 0 && !mIsFirstFrame) { // A negative child count is the signal to copy this view from the last frame. - ref.childCount = -1; - ref.view = view; + ref.childCount = -parent.getChildCount(); return ref; } - ViewPropertyRef result = ref; + ViewRef result = ref; int childCount = ref.childCount = parent.getChildCount(); - ref.transferFrom(view); for (int i = 0; i < childCount; i++) { result = captureViewTree(parent.getChildAt(i), result); } return result; } else { ref.childCount = 0; - ref.transferFrom(view); return ref; } } @@ -601,12 +520,11 @@ public abstract class ViewCapture { } } - protected static class ViewPropertyRef implements Runnable { - public View view; - + protected static class ViewPropertyRef { // We store reference in memory to avoid generating and storing too many strings public Class clazz; public int hashCode; + public int childCount = 0; public int id; public int left, top, right, bottom; @@ -620,45 +538,9 @@ public abstract class ViewCapture { public int visibility; public boolean willNotDraw; public boolean clipChildren; - public int childCount = 0; public ViewPropertyRef next; - public Consumer callback = null; - public long elapsedRealtimeNanos = 0; - - - public void transferFrom(View in) { - view = in; - - left = in.getLeft(); - top = in.getTop(); - right = in.getRight(); - bottom = in.getBottom(); - scrollX = in.getScrollX(); - scrollY = in.getScrollY(); - - translateX = in.getTranslationX(); - translateY = in.getTranslationY(); - scaleX = in.getScaleX(); - scaleY = in.getScaleY(); - alpha = in.getAlpha(); - elevation = in.getElevation(); - - visibility = in.getVisibility(); - willNotDraw = in.willNotDraw(); - } - - /** - * Transfer in backgroup thread view properties that remain unchanged between frames. - */ - public void completeTransferFromViewBg() { - clazz = view.getClass(); - hashCode = view.hashCode(); - id = view.getId(); - view = null; - } - public void transferTo(ViewPropertyRef out) { out.clazz = this.clazz; out.hashCode = this.hashCode; @@ -686,7 +568,7 @@ public abstract class ViewCapture { * at the end of the iteration. */ public ViewPropertyRef toProto(ViewIdProvider idProvider, ArrayList classList, - ViewNode.Builder viewNode) { + ViewNode.Builder viewNode) { int classnameIndex = classList.indexOf(clazz); if (classnameIndex < 0) { classnameIndex = classList.size(); @@ -720,10 +602,48 @@ public abstract class ViewCapture { } return result; } + } + + + private static class ViewRef implements Runnable { + public View view; + public int childCount = 0; + @Nullable + public ViewRef next; + + public Consumer callback = null; + public long elapsedRealtimeNanos = 0; + + public void transferTo(ViewPropertyRef out) { + out.childCount = this.childCount; + + View view = this.view; + this.view = null; + + out.clazz = view.getClass(); + out.hashCode = view.hashCode(); + out.id = view.getId(); + out.left = view.getLeft(); + out.top = view.getTop(); + out.right = view.getRight(); + out.bottom = view.getBottom(); + out.scrollX = view.getScrollX(); + out.scrollY = view.getScrollY(); + + out.translateX = view.getTranslationX(); + out.translateY = view.getTranslationY(); + out.scaleX = view.getScaleX(); + out.scaleY = view.getScaleY(); + out.alpha = view.getAlpha(); + out.elevation = view.getElevation(); + + out.visibility = view.getVisibility(); + out.willNotDraw = view.willNotDraw(); + } @Override public void run() { - Consumer oldCallback = callback; + Consumer oldCallback = callback; callback = null; if (oldCallback != null) { oldCallback.accept(this); diff --git a/systemUI/viewcapture/src/com/android/app/viewcapture/ViewCaptureDataSource.java b/systemUI/viewcapture/src/com/android/app/viewcapture/ViewCaptureDataSource.java index 61a2c038f7..e057848005 100644 --- a/systemUI/viewcapture/src/com/android/app/viewcapture/ViewCaptureDataSource.java +++ b/systemUI/viewcapture/src/com/android/app/viewcapture/ViewCaptureDataSource.java @@ -28,7 +28,7 @@ import java.util.HashMap; import java.util.Map; class ViewCaptureDataSource - extends DataSource { +extends DataSource { public static String DATA_SOURCE_NAME = "android.viewcapture"; private final Runnable mOnStartStaticCallback; @@ -44,7 +44,7 @@ class ViewCaptureDataSource @Override public IncrementalState createIncrementalState( - CreateIncrementalStateArgs args) { + CreateIncrementalStateArgs args) { return new IncrementalState(); } @@ -59,20 +59,20 @@ class ViewCaptureDataSource @Override public DataSourceInstance createInstance(ProtoInputStream configStream, int instanceIndex) { return new DataSourceInstance(this, instanceIndex) { - @Override - protected void onStart(StartCallbackArguments args) { - mOnStartStaticCallback.run(); - } + @Override + protected void onStart(StartCallbackArguments args) { + mOnStartStaticCallback.run(); + } - @Override - protected void onFlush(FlushCallbackArguments args) { - mOnFlushStaticCallback.run(); - } + @Override + protected void onFlush(FlushCallbackArguments args) { + mOnFlushStaticCallback.run(); + } - @Override - protected void onStop(StopCallbackArguments args) { - mOnStopStaticCallback.run(); - } - }; + @Override + protected void onStop(StopCallbackArguments args) { + mOnStopStaticCallback.run(); + } + }; } } diff --git a/systemUI/viewcapture/src/com/android/app/viewcapture/ViewCaptureFactory.kt b/systemUI/viewcapture/src/com/android/app/viewcapture/ViewCaptureFactory.kt index c9790eac87..5fb9ceb3b0 100644 --- a/systemUI/viewcapture/src/com/android/app/viewcapture/ViewCaptureFactory.kt +++ b/systemUI/viewcapture/src/com/android/app/viewcapture/ViewCaptureFactory.kt @@ -17,6 +17,7 @@ package com.android.app.viewcapture import android.content.Context +import android.os.Looper import android.os.Process import android.tracing.Flags import android.util.Log @@ -25,46 +26,47 @@ import android.util.Log * Factory to create polymorphic instances of ViewCapture according to build configurations and * flags. */ -object ViewCaptureFactory { - private val TAG = ViewCaptureFactory::class.java.simpleName - private val instance: ViewCapture by lazy { createInstance() } - private lateinit var appContext: Context +class ViewCaptureFactory { + companion object { + private val TAG = ViewCaptureFactory::class.java.simpleName + private var instance: ViewCapture? = null - private fun createInstance(): ViewCapture { - return when { - !false -> { - Log.i(TAG, "instantiating ${NoOpViewCapture::class.java.simpleName}") - NoOpViewCapture() + @JvmStatic + fun getInstance(context: Context): ViewCapture { + if (Looper.myLooper() != Looper.getMainLooper()) { + return ViewCapture.MAIN_EXECUTOR.submit { getInstance(context) }.get() } - !Flags.perfettoViewCaptureTracing() -> { - Log.i(TAG, "instantiating ${SettingsAwareViewCapture::class.java.simpleName}") - SettingsAwareViewCapture( - appContext, - ViewCapture.createAndStartNewLooperExecutor( - "SAViewCapture", - Process.THREAD_PRIORITY_FOREGROUND, - ), - ) - } - else -> { - Log.i(TAG, "instantiating ${PerfettoViewCapture::class.java.simpleName}") - PerfettoViewCapture( - appContext, - ViewCapture.createAndStartNewLooperExecutor( - "PerfettoViewCapture", - Process.THREAD_PRIORITY_FOREGROUND, - ), - ) - } - } - } - /** Returns an instance of [ViewCapture]. */ - @JvmStatic - fun getInstance(context: Context): ViewCapture { - if (!this::appContext.isInitialized) { - synchronized(this) { appContext = context.applicationContext } + if (instance != null) { + return instance!! + } + + return when { + !android.os.Build.IS_DEBUGGABLE -> { + Log.i(TAG, "instantiating ${NoOpViewCapture::class.java.simpleName}") + NoOpViewCapture() + } + !Flags.perfettoViewCaptureTracing() -> { + Log.i(TAG, "instantiating ${SettingsAwareViewCapture::class.java.simpleName}") + SettingsAwareViewCapture( + context.applicationContext, + ViewCapture.createAndStartNewLooperExecutor( + "SAViewCapture", + Process.THREAD_PRIORITY_FOREGROUND + ) + ) + } + else -> { + Log.i(TAG, "instantiating ${PerfettoViewCapture::class.java.simpleName}") + PerfettoViewCapture( + context.applicationContext, + ViewCapture.createAndStartNewLooperExecutor( + "PerfettoViewCapture", + Process.THREAD_PRIORITY_FOREGROUND + ) + ) + } + }.also { instance = it } } - return instance } } diff --git a/systemUI/viewcapture/tests/AndroidManifest.xml b/systemUI/viewcapture/tests/AndroidManifest.xml index f32f93cb6e..8d31c0eb5d 100644 --- a/systemUI/viewcapture/tests/AndroidManifest.xml +++ b/systemUI/viewcapture/tests/AndroidManifest.xml @@ -15,12 +15,7 @@ --> - - - diff --git a/systemUI/viewcapture/tests/com/android/app/viewcapture/SettingsAwareViewCaptureTest.kt b/systemUI/viewcapture/tests/com/android/app/viewcapture/SettingsAwareViewCaptureTest.kt index 5654f7fdec..15352aa760 100644 --- a/systemUI/viewcapture/tests/com/android/app/viewcapture/SettingsAwareViewCaptureTest.kt +++ b/systemUI/viewcapture/tests/com/android/app/viewcapture/SettingsAwareViewCaptureTest.kt @@ -97,4 +97,12 @@ class SettingsAwareViewCaptureTest { } } } + + @Test + fun getInstance_calledTwiceInARow_returnsSameObject() { + assertEquals( + SettingsAwareViewCapture.getInstance(context).hashCode(), + SettingsAwareViewCapture.getInstance(context).hashCode() + ) + } } diff --git a/tests/Android.bp b/tests/Android.bp index fc08e86284..e51242f48a 100644 --- a/tests/Android.bp +++ b/tests/Android.bp @@ -63,6 +63,7 @@ filegroup { "src/com/android/launcher3/dragging/TaplDragTest.java", "src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java", "src/com/android/launcher3/ui/TaplTestsLauncher3Test.java", + "src/com/android/launcher3/ui/widget/TaplWidgetPickerTest.java", "src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java", ], } @@ -70,9 +71,6 @@ filegroup { // Library with all the dependencies for building quickstep android_library { name: "Launcher3TestLib", - defaults: [ - "launcher_compose_tests_defaults", - ], srcs: [], asset_dirs: ["assets"], resource_dirs: ["res"], @@ -97,8 +95,6 @@ android_library { "com_android_launcher3_flags_lib", "com_android_wm_shell_flags_lib", "android.appwidget.flags-aconfig-java", - "platform-parametric-runner-lib", - "kotlin-reflect", ], manifest: "AndroidManifest-common.xml", platform_apis: true, @@ -112,16 +108,10 @@ android_library { asset_dirs: ["assets"], // TODO(b/319712088): re-enable use_resource_processor use_resource_processor: false, - static_libs: [ - "kotlin-reflect", - ], } android_test { name: "Launcher3Tests", - defaults: [ - "launcher_compose_tests_defaults", - ], srcs: [ ":launcher-tests-src", ":launcher-non-quickstep-tests-src", @@ -131,9 +121,9 @@ android_test { "com_android_launcher3_flags_lib", ], libs: [ - "android.test.base.stubs.system", - "android.test.runner.stubs.system", - "android.test.mock.stubs.system", + "android.test.base", + "android.test.runner", + "android.test.mock", ], // Libraries used by mockito inline extended jni_libs: [ @@ -147,19 +137,30 @@ android_test { platform_apis: true, test_config: "Launcher3Tests.xml", data: [":Launcher3"], - plugins: ["dagger2-compiler"], test_suites: ["general-tests"], } +// Shared between tests and launcher +android_library { + name: "launcher-testing-shared", + srcs: [ + "multivalentTests/shared/com/android/launcher3/testing/shared/**/*.java", + "multivalentTests/shared/com/android/launcher3/testing/shared/**/*.kt", + ], + resource_dirs: [], + manifest: "multivalentTests/shared/AndroidManifest.xml", + sdk_version: "current", + min_sdk_version: min_launcher3_sdk_version, +} + filegroup { - name: "launcher-testing-helpers-robo", + name: "launcher-testing-helpers", srcs: [ "src/**/*.java", "src/**/*.kt", "multivalentTests/src/**/*.java", "multivalentTests/src/**/*.kt", "src/com/android/launcher3/ui/AbstractLauncherUiTest.java", - "src/com/android/launcher3/ui/BaseLauncherTaplTest.java", "tapl/com/android/launcher3/tapl/*.java", "tapl/com/android/launcher3/tapl/*.kt", ], @@ -167,27 +168,20 @@ filegroup { // Test classes "src/**/*Test.java", "src/**/*Test.kt", - "src/**/RoboApiWrapper.kt", - "src/**/EventsRule.kt", "multivalentTests/src/**/*Test.java", "multivalentTests/src/**/*Test.kt", ], } -filegroup { - name: "launcher-testing-helpers", - srcs: [ - ":launcher-testing-helpers-robo", - "src/**/RoboApiWrapper.kt", - ], -} - android_robolectric_test { enabled: true, name: "Launcher3RoboTests", srcs: [ ":launcher3-robo-src", - ":launcher-testing-helpers-robo", + + // Test util classes + ":launcher-testing-helpers", + ":launcher-testing-shared", ], exclude_srcs: [ //"src/com/android/launcher3/util/CellContentDimensionsTest.kt", // Failing - b/316553889 @@ -220,12 +214,12 @@ android_robolectric_test { "android.appwidget.flags-aconfig-java", ], libs: [ - "android.test.runner.stubs.system", - "android.test.base.stubs.system", - "android.test.mock.stubs.system", + "android.test.runner", + "android.test.base", + "android.test.mock", "truth", ], instrumentation_for: "Launcher3", - plugins: ["dagger2-compiler"], + upstream: true, strict_mode: false, } diff --git a/tests/AndroidManifest-common.xml b/tests/AndroidManifest-common.xml index 68e493d86a..4b926a8005 100644 --- a/tests/AndroidManifest-common.xml +++ b/tests/AndroidManifest-common.xml @@ -183,7 +183,6 @@ @@ -193,7 +192,6 @@ @@ -203,7 +201,6 @@ @@ -213,7 +210,6 @@ @@ -223,7 +219,6 @@ @@ -233,7 +228,6 @@ @@ -243,7 +237,6 @@ @@ -253,7 +246,6 @@ @@ -262,7 +254,6 @@ @@ -271,7 +262,6 @@ @@ -280,7 +270,6 @@ @@ -289,7 +278,6 @@ @@ -298,7 +286,6 @@ @@ -376,7 +363,7 @@ @@ -420,36 +407,6 @@ - - - - - - - - - - - - - - - - - - - - + android:targetPackage="com.android.launcher3" > diff --git a/tests/Launcher3Tests.xml b/tests/Launcher3Tests.xml index da86357e97..270a610814 100644 --- a/tests/Launcher3Tests.xml +++ b/tests/Launcher3Tests.xml @@ -48,10 +48,6 @@

If a dp (or sp) value typically returns a half pixel, such as 20dp at a 2.625 density + * returning 52.5px, there is a small chance that due to floating-point errors, the value will + * be stored as 52.499999. As we round to the nearest pixel, this could cause a 1px difference + * in final values, which we correct for in this method. + */ + public static int roundPxValueFromFloat(float value) { + float fraction = (float) (value - Math.floor(value)); + if (Math.abs(0.5f - fraction) < EPSILON) { + // Note: we add for negative values as well, as Math.round brings -.5 to the next + // "highest" value, e.g. Math.round(-2.5) == -2 [i.e. (int)Math.floor(a + 0.5d)] + value += EPSILON; + } + return Math.round(value); + } +} \ No newline at end of file diff --git a/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestInformationRequest.java b/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestInformationRequest.java new file mode 100644 index 0000000000..3828203280 --- /dev/null +++ b/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestInformationRequest.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.testing.shared; + +import android.os.Parcelable; + +/** + * A Request sent to TestInformationHandler can implement this interface to carry more information. + */ +public interface TestInformationRequest extends Parcelable { + /** + * The name for handler to dispatch request. + */ + String getRequestName(); +} diff --git a/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestProtocol.java b/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestProtocol.java new file mode 100644 index 0000000000..59d0de69ee --- /dev/null +++ b/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestProtocol.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.testing.shared; + +import android.util.Log; + +/** + * Protocol for custom accessibility events for communication with UI Automation tests. + */ +public final class TestProtocol { + public static final String STATE_FIELD = "state"; + public static final String SWITCHED_TO_STATE_MESSAGE = "TAPL_SWITCHED_TO_STATE"; + public static final String SCROLL_FINISHED_MESSAGE = "TAPL_SCROLL_FINISHED"; + public static final String PAUSE_DETECTED_MESSAGE = "TAPL_PAUSE_DETECTED"; + public static final String DISMISS_ANIMATION_ENDS_MESSAGE = "TAPL_DISMISS_ANIMATION_ENDS"; + public static final String FOLDER_OPENED_MESSAGE = "TAPL_FOLDER_OPENED"; + public static final String SEARCH_RESULT_COMPLETE = "SEARCH_RESULT_COMPLETE"; + public static final String LAUNCHER_ACTIVITY_STOPPED_MESSAGE = "TAPL_LAUNCHER_ACTIVITY_STOPPED"; + public static final String WALLPAPER_OPEN_ANIMATION_FINISHED_MESSAGE = + "TAPL_WALLPAPER_OPEN_ANIMATION_FINISHED"; + public static final int NORMAL_STATE_ORDINAL = 0; + public static final int SPRING_LOADED_STATE_ORDINAL = 1; + public static final int OVERVIEW_STATE_ORDINAL = 2; + public static final int OVERVIEW_MODAL_TASK_STATE_ORDINAL = 3; + public static final int QUICK_SWITCH_STATE_ORDINAL = 4; + public static final int ALL_APPS_STATE_ORDINAL = 5; + public static final int BACKGROUND_APP_STATE_ORDINAL = 6; + public static final int HINT_STATE_ORDINAL = 7; + public static final int HINT_STATE_TWO_BUTTON_ORDINAL = 8; + public static final int OVERVIEW_SPLIT_SELECT_ORDINAL = 9; + public static final int EDIT_MODE_STATE_ORDINAL = 10; + public static final String SEQUENCE_MAIN = "Main"; + public static final String SEQUENCE_TIS = "TIS"; + public static final String SEQUENCE_PILFER = "Pilfer"; + + public static String stateOrdinalToString(int ordinal) { + switch (ordinal) { + case NORMAL_STATE_ORDINAL: + return "Normal"; + case SPRING_LOADED_STATE_ORDINAL: + return "SpringLoaded"; + case OVERVIEW_STATE_ORDINAL: + return "Overview"; + case OVERVIEW_MODAL_TASK_STATE_ORDINAL: + return "OverviewModal"; + case QUICK_SWITCH_STATE_ORDINAL: + return "QuickSwitch"; + case ALL_APPS_STATE_ORDINAL: + return "AllApps"; + case BACKGROUND_APP_STATE_ORDINAL: + return "Background"; + case HINT_STATE_ORDINAL: + return "Hint"; + case HINT_STATE_TWO_BUTTON_ORDINAL: + return "Hint2Button"; + case OVERVIEW_SPLIT_SELECT_ORDINAL: + return "OverviewSplitSelect"; + case EDIT_MODE_STATE_ORDINAL: + return "EditMode"; + default: + return "Unknown"; + } + } + + public static final String TEST_INFO_REQUEST_FIELD = "request"; + public static final String TEST_INFO_RESPONSE_FIELD = "response"; + + public static final String REQUEST_HOME_TO_OVERVIEW_SWIPE_HEIGHT = + "home-to-overview-swipe-height"; + public static final String REQUEST_BACKGROUND_TO_OVERVIEW_SWIPE_HEIGHT = + "background-to-overview-swipe-height"; + public static final String REQUEST_HOME_TO_ALL_APPS_SWIPE_HEIGHT = + "home-to-all-apps-swipe-height"; + public static final String REQUEST_ICON_HEIGHT = + "icon-height"; + public static final String REQUEST_IS_LAUNCHER_INITIALIZED = "is-launcher-initialized"; + public static final String REQUEST_IS_LAUNCHER_LAUNCHER_ACTIVITY_STARTED = + "is-launcher-activity-started"; + public static final String REQUEST_FREEZE_APP_LIST = "freeze-app-list"; + public static final String REQUEST_UNFREEZE_APP_LIST = "unfreeze-app-list"; + public static final String REQUEST_ENABLE_BLOCK_TIMEOUT = "enable-block-timeout"; + public static final String REQUEST_DISABLE_BLOCK_TIMEOUT = "disable-block-timeout"; + public static final String REQUEST_ENABLE_TRANSIENT_TASKBAR = "enable-transient-taskbar"; + public static final String REQUEST_DISABLE_TRANSIENT_TASKBAR = "disable-transient-taskbar"; + public static final String REQUEST_IS_TRANSIENT_TASKBAR = "is-transient-taskbar"; + public static final String REQUEST_UNSTASH_TASKBAR_IF_STASHED = "unstash-taskbar-if-stashed"; + public static final String REQUEST_TASKBAR_FROM_NAV_THRESHOLD = "taskbar-from-nav-threshold"; + public static final String REQUEST_STASHED_TASKBAR_SCALE = "taskbar-stash-handle-scale"; + public static final String REQUEST_RECREATE_TASKBAR = "recreate-taskbar"; + public static final String REQUEST_TASKBAR_IME_DOCKED = "taskbar-ime-docked"; + public static final String REQUEST_APP_LIST_FREEZE_FLAGS = "app-list-freeze-flags"; + public static final String REQUEST_APPS_LIST_SCROLL_Y = "apps-list-scroll-y"; + public static final String REQUEST_TASKBAR_APPS_LIST_SCROLL_Y = "taskbar-apps-list-scroll-y"; + public static final String REQUEST_WIDGETS_SCROLL_Y = "widgets-scroll-y"; + public static final String REQUEST_TARGET_INSETS = "target-insets"; + public static final String REQUEST_WINDOW_INSETS = "window-insets"; + public static final String REQUEST_SYSTEM_GESTURE_REGION = "gesture-region"; + public static final String REQUEST_PID = "pid"; + public static final String REQUEST_FORCE_GC = "gc"; + public static final String REQUEST_RECENT_TASKS_LIST = "recent-tasks-list"; + public static final String REQUEST_START_EVENT_LOGGING = "start-event-logging"; + public static final String REQUEST_GET_TEST_EVENTS = "get-test-events"; + public static final String REQUEST_GET_HAD_NONTEST_EVENTS = "get-had-nontest-events"; + public static final String REQUEST_STOP_EVENT_LOGGING = "stop-event-logging"; + public static final String REQUEST_REINITIALIZE_DATA = "reinitialize-data"; + public static final String REQUEST_CLEAR_DATA = "clear-data"; + public static final String REQUEST_HOTSEAT_ICON_NAMES = "get-hotseat-icon-names"; + public static final String REQUEST_IS_TABLET = "is-tablet"; + public static final String REQUEST_IS_PREDICTIVE_BACK_SWIPE_ENABLED = + "is-predictive-back-swipe-enabled"; + public static final String REQUEST_ENABLE_TASKBAR_NAVBAR_UNIFICATION = + "enable-taskbar-navbar-unification"; + public static final String REQUEST_NUM_ALL_APPS_COLUMNS = "num-all-apps-columns"; + public static final String REQUEST_IS_TWO_PANELS = "is-two-panel"; + public static final String REQUEST_CELL_LAYOUT_BOARDER_HEIGHT = "cell-layout-boarder-height"; + public static final String REQUEST_START_DRAG_THRESHOLD = "start-drag-threshold"; + public static final String REQUEST_SHELL_DRAG_READY = "shell-drag-ready"; + public static final String REQUEST_GET_ACTIVITIES_CREATED_COUNT = + "get-activities-created-count"; + public static final String REQUEST_GET_ACTIVITIES = "get-activities"; + public static final String REQUEST_HAS_TIS = "has-touch-interaction-service"; + public static final String REQUEST_TASKBAR_ALL_APPS_TOP_PADDING = + "taskbar-all-apps-top-padding"; + public static final String REQUEST_ALL_APPS_TOP_PADDING = "all-apps-top-padding"; + public static final String REQUEST_ALL_APPS_BOTTOM_PADDING = "all-apps-bottom-padding"; + public static final String REQUEST_REFRESH_OVERVIEW_TARGET = "refresh-overview-target"; + + public static final String REQUEST_WORKSPACE_CELL_LAYOUT_SIZE = "workspace-cell-layout-size"; + public static final String REQUEST_WORKSPACE_CELL_CENTER = "workspace-cell-center"; + public static final String REQUEST_WORKSPACE_COLUMNS_ROWS = "workspace-columns-rows"; + + public static final String REQUEST_WORKSPACE_CURRENT_PAGE_INDEX = + "workspace-current-page-index"; + + public static final String REQUEST_HOTSEAT_CELL_CENTER = "hotseat-cell-center"; + + public static final String REQUEST_GET_FOCUSED_TASK_HEIGHT_FOR_TABLET = + "get-focused-task-height-for-tablet"; + public static final String REQUEST_GET_GRID_TASK_SIZE_RECT_FOR_TABLET = + "get-grid-task-size-rect-for-tablet"; + public static final String REQUEST_GET_OVERVIEW_PAGE_SPACING = "get-overview-page-spacing"; + public static final String REQUEST_GET_OVERVIEW_CURRENT_PAGE_INDEX = + "get-overview-current-page-index"; + public static final String REQUEST_GET_SPLIT_SELECTION_ACTIVE = "get-split-selection-active"; + public static final String REQUEST_ENABLE_ROTATION = "enable_rotation"; + public static final String REQUEST_ENABLE_SUGGESTION = "enable-suggestion"; + public static final String REQUEST_MODEL_QUEUE_CLEARED = "model-queue-cleared"; + + public static boolean sDebugTracing = false; + public static final String REQUEST_ENABLE_DEBUG_TRACING = "enable-debug-tracing"; + public static final String REQUEST_DISABLE_DEBUG_TRACING = "disable-debug-tracing"; + + + public static boolean sDisableSensorRotation; + public static final String REQUEST_MOCK_SENSOR_ROTATION = "mock-sensor-rotation"; + + public static final String PERMANENT_DIAG_TAG = "TaplTarget"; + public static final String ICON_MISSING = "b/282963545"; + public static final String UIOBJECT_STALE_ELEMENT = "b/319501259"; + public static final String TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE = "b/326908466"; + public static final String WIDGET_CONFIG_NULL_EXTRA_INTENT = "b/324419890"; + public static final String OVERVIEW_SELECT_TOOLTIP_MISALIGNED = "b/332485341"; + + public static final String REQUEST_FLAG_ENABLE_GRID_ONLY_OVERVIEW = "enable-grid-only-overview"; + public static final String REQUEST_FLAG_ENABLE_APP_PAIRS = "enable-app-pairs"; + + public static final String REQUEST_UNSTASH_BUBBLE_BAR_IF_STASHED = + "unstash-bubble-bar-if-stashed"; + + public static final String REQUEST_INJECT_FAKE_TRACKPAD = "inject-fake-trackpad"; + public static final String REQUEST_EJECT_FAKE_TRACKPAD = "eject-fake-trackpad"; + + /** Logs {@link Log#d(String, String)} if {@link #sDebugTracing} is true. */ + public static void testLogD(String tag, String message) { + if (!sDebugTracing) { + return; + } + Log.d(tag, message); + } +} diff --git a/tests/multivalentTests/shared/com/android/launcher3/testing/shared/WorkspaceCellCenterRequest.java b/tests/multivalentTests/shared/com/android/launcher3/testing/shared/WorkspaceCellCenterRequest.java new file mode 100644 index 0000000000..e2cd8ea965 --- /dev/null +++ b/tests/multivalentTests/shared/com/android/launcher3/testing/shared/WorkspaceCellCenterRequest.java @@ -0,0 +1,138 @@ +/* + * 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. + */ + +package com.android.launcher3.testing.shared; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Request object for querying a workspace cell region in Rect. + */ +public class WorkspaceCellCenterRequest implements TestInformationRequest { + public final int cellX; + public final int cellY; + public final int spanX; + public final int spanY; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(cellX); + dest.writeInt(cellY); + dest.writeInt(spanX); + dest.writeInt(spanY); + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + + @Override + public WorkspaceCellCenterRequest createFromParcel(Parcel source) { + return new WorkspaceCellCenterRequest(source); + } + + @Override + public WorkspaceCellCenterRequest[] newArray(int size) { + return new WorkspaceCellCenterRequest[size]; + } + }; + + private WorkspaceCellCenterRequest(int cellX, int cellY, int spanX, int spanY) { + this.cellX = cellX; + this.cellY = cellY; + this.spanX = spanX; + this.spanY = spanY; + } + + private WorkspaceCellCenterRequest(Parcel in) { + this(in.readInt(), in.readInt(), in.readInt(), in.readInt()); + } + + /** + * Create a builder for WorkspaceCellRectRequest. + * + * @return WorkspaceCellRectRequest builder. + */ + public static WorkspaceCellCenterRequest.Builder builder() { + return new WorkspaceCellCenterRequest.Builder(); + } + + @Override + public String getRequestName() { + return TestProtocol.REQUEST_WORKSPACE_CELL_CENTER; + } + + /** + * WorkspaceCellRectRequest Builder. + */ + public static final class Builder { + private int mCellX; + private int mCellY; + private int mSpanX; + private int mSpanY; + + private Builder() { + this.mCellX = 0; + this.mCellY = 0; + this.mSpanX = 1; + this.mSpanY = 1; + } + + /** + * Set X coordinate of upper left corner expressed as a cell position + */ + public WorkspaceCellCenterRequest.Builder setCellX(int x) { + this.mCellX = x; + return this; + } + + /** + * Set Y coordinate of upper left corner expressed as a cell position + */ + public WorkspaceCellCenterRequest.Builder setCellY(int y) { + this.mCellY = y; + return this; + } + + /** + * Set span Width in cells + */ + public WorkspaceCellCenterRequest.Builder setSpanX(int x) { + this.mSpanX = x; + return this; + } + + /** + * Set span Height in cells + */ + public WorkspaceCellCenterRequest.Builder setSpanY(int y) { + this.mSpanY = y; + return this; + } + + /** + * build the WorkspaceCellRectRequest. + */ + public WorkspaceCellCenterRequest build() { + return new WorkspaceCellCenterRequest(mCellX, mCellY, mSpanX, mSpanY); + } + } +} diff --git a/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt b/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt index dc49ba0ef4..8770859957 100644 --- a/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt +++ b/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt @@ -26,25 +26,18 @@ import android.platform.test.rule.IgnoreLimit import android.platform.test.rule.LimitDevicesRule import android.util.DisplayMetrics import android.view.Surface -import androidx.test.core.app.ApplicationProvider.getApplicationContext +import androidx.test.core.app.ApplicationProvider import androidx.test.platform.app.InstrumentationRegistry -import com.android.launcher3.LauncherPrefs.Companion.GRID_NAME -import com.android.launcher3.dagger.LauncherAppComponent -import com.android.launcher3.dagger.LauncherAppSingleton import com.android.launcher3.testing.shared.ResourceUtils -import com.android.launcher3.util.AllModulesMinusWMProxy import com.android.launcher3.util.DisplayController -import com.android.launcher3.util.FakePrefsModule +import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext import com.android.launcher3.util.NavigationMode -import com.android.launcher3.util.SandboxContext import com.android.launcher3.util.WindowBounds import com.android.launcher3.util.rule.TestStabilityRule import com.android.launcher3.util.rule.setFlags import com.android.launcher3.util.window.CachedDisplayInfo import com.android.launcher3.util.window.WindowManagerProxy import com.google.common.truth.Truth -import dagger.BindsInstance -import dagger.Component import java.io.BufferedReader import java.io.File import java.io.PrintWriter @@ -69,10 +62,10 @@ import org.mockito.kotlin.whenever abstract class AbstractDeviceProfileTest { protected val testContext: Context = InstrumentationRegistry.getInstrumentation().context protected lateinit var context: SandboxContext - protected open val runningContext: Context = getApplicationContext() + protected open val runningContext: Context = ApplicationProvider.getApplicationContext() private val displayController: DisplayController = mock() private val windowManagerProxy: WindowManagerProxy = mock() - private lateinit var launcherPrefs: LauncherPrefs + private val launcherPrefs: LauncherPrefs = mock() @get:Rule val setFlagsRule = SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT) @@ -86,7 +79,7 @@ abstract class AbstractDeviceProfileTest { val statusBarNaturalPx: Int, val statusBarRotatedPx: Int, val gesturePx: Int, - val cutoutPx: Int, + val cutoutPx: Int ) open val deviceSpecs = @@ -98,7 +91,7 @@ abstract class AbstractDeviceProfileTest { statusBarNaturalPx = 118, statusBarRotatedPx = 74, gesturePx = 63, - cutoutPx = 118, + cutoutPx = 118 ), "tablet" to DeviceSpec( @@ -107,7 +100,7 @@ abstract class AbstractDeviceProfileTest { statusBarNaturalPx = 104, statusBarRotatedPx = 104, gesturePx = 0, - cutoutPx = 0, + cutoutPx = 0 ), "twopanel-phone" to DeviceSpec( @@ -116,7 +109,7 @@ abstract class AbstractDeviceProfileTest { statusBarNaturalPx = 133, statusBarRotatedPx = 110, gesturePx = 63, - cutoutPx = 133, + cutoutPx = 133 ), "twopanel-tablet" to DeviceSpec( @@ -125,16 +118,14 @@ abstract class AbstractDeviceProfileTest { statusBarNaturalPx = 110, statusBarRotatedPx = 133, gesturePx = 0, - cutoutPx = 0, - ), + cutoutPx = 0 + ) ) protected fun initializeVarsForPhone( deviceSpec: DeviceSpec, isGestureMode: Boolean = true, - isVerticalBar: Boolean = false, - isFixedLandscape: Boolean = false, - gridName: String? = GRID_NAME.defaultValue, + isVerticalBar: Boolean = false ) { val (naturalX, naturalY) = deviceSpec.naturalSize val windowsBounds = phoneWindowsBounds(deviceSpec, isGestureMode, naturalX, naturalY) @@ -146,17 +137,14 @@ abstract class AbstractDeviceProfileTest { displayInfo, rotation = if (isVerticalBar) Surface.ROTATION_90 else Surface.ROTATION_0, isGestureMode, - densityDpi = deviceSpec.densityDpi, - isFixedLandscape = isFixedLandscape, - gridName = gridName, + densityDpi = deviceSpec.densityDpi ) } protected fun initializeVarsForTablet( deviceSpec: DeviceSpec, isLandscape: Boolean = false, - isGestureMode: Boolean = true, - gridName: String? = GRID_NAME.defaultValue, + isGestureMode: Boolean = true ) { val (naturalX, naturalY) = deviceSpec.naturalSize val windowsBounds = tabletWindowsBounds(deviceSpec, naturalX, naturalY) @@ -168,8 +156,7 @@ abstract class AbstractDeviceProfileTest { displayInfo, rotation = if (isLandscape) Surface.ROTATION_0 else Surface.ROTATION_90, isGestureMode, - densityDpi = deviceSpec.densityDpi, - gridName = gridName, + densityDpi = deviceSpec.densityDpi ) } @@ -178,8 +165,7 @@ abstract class AbstractDeviceProfileTest { deviceSpecFolded: DeviceSpec, isLandscape: Boolean = false, isGestureMode: Boolean = true, - isFolded: Boolean = false, - gridName: String? = GRID_NAME.defaultValue, + isFolded: Boolean = false ) { val (unfoldedNaturalX, unfoldedNaturalY) = deviceSpecUnfolded.naturalSize val unfoldedWindowsBounds = @@ -196,7 +182,7 @@ abstract class AbstractDeviceProfileTest { val perDisplayBoundsCache = mapOf( unfoldedDisplayInfo to unfoldedWindowsBounds, - foldedDisplayInfo to foldedWindowsBounds, + foldedDisplayInfo to foldedWindowsBounds ) if (isFolded) { @@ -205,8 +191,7 @@ abstract class AbstractDeviceProfileTest { displayInfo = foldedDisplayInfo, rotation = if (isLandscape) Surface.ROTATION_90 else Surface.ROTATION_0, isGestureMode = isGestureMode, - densityDpi = deviceSpecFolded.densityDpi, - gridName = gridName, + densityDpi = deviceSpecFolded.densityDpi ) } else { initializeCommonVars( @@ -214,8 +199,7 @@ abstract class AbstractDeviceProfileTest { displayInfo = unfoldedDisplayInfo, rotation = if (isLandscape) Surface.ROTATION_0 else Surface.ROTATION_90, isGestureMode = isGestureMode, - densityDpi = deviceSpecUnfolded.densityDpi, - gridName = gridName, + densityDpi = deviceSpecUnfolded.densityDpi ) } } @@ -224,7 +208,7 @@ abstract class AbstractDeviceProfileTest { deviceSpec: DeviceSpec, isGestureMode: Boolean, naturalX: Int, - naturalY: Int, + naturalY: Int ): List { val buttonsNavHeight = Utilities.dpToPx(48f, deviceSpec.densityDpi) @@ -233,14 +217,14 @@ abstract class AbstractDeviceProfileTest { 0, max(deviceSpec.statusBarNaturalPx, deviceSpec.cutoutPx), 0, - if (isGestureMode) deviceSpec.gesturePx else buttonsNavHeight, + if (isGestureMode) deviceSpec.gesturePx else buttonsNavHeight ) val rotation90Insets = Rect( deviceSpec.cutoutPx, deviceSpec.statusBarRotatedPx, if (isGestureMode) 0 else buttonsNavHeight, - if (isGestureMode) deviceSpec.gesturePx else 0, + if (isGestureMode) deviceSpec.gesturePx else 0 ) val rotation180Insets = Rect( @@ -249,29 +233,29 @@ abstract class AbstractDeviceProfileTest { 0, max( if (isGestureMode) deviceSpec.gesturePx else buttonsNavHeight, - deviceSpec.cutoutPx, - ), + deviceSpec.cutoutPx + ) ) val rotation270Insets = Rect( if (isGestureMode) 0 else buttonsNavHeight, deviceSpec.statusBarRotatedPx, deviceSpec.cutoutPx, - if (isGestureMode) deviceSpec.gesturePx else 0, + if (isGestureMode) deviceSpec.gesturePx else 0 ) return listOf( WindowBounds(Rect(0, 0, naturalX, naturalY), rotation0Insets, Surface.ROTATION_0), WindowBounds(Rect(0, 0, naturalY, naturalX), rotation90Insets, Surface.ROTATION_90), WindowBounds(Rect(0, 0, naturalX, naturalY), rotation180Insets, Surface.ROTATION_180), - WindowBounds(Rect(0, 0, naturalY, naturalX), rotation270Insets, Surface.ROTATION_270), + WindowBounds(Rect(0, 0, naturalY, naturalX), rotation270Insets, Surface.ROTATION_270) ) } private fun tabletWindowsBounds( deviceSpec: DeviceSpec, naturalX: Int, - naturalY: Int, + naturalY: Int ): List { val naturalInsets = Rect(0, deviceSpec.statusBarNaturalPx, 0, 0) val rotatedInsets = Rect(0, deviceSpec.statusBarRotatedPx, 0, 0) @@ -280,7 +264,7 @@ abstract class AbstractDeviceProfileTest { WindowBounds(Rect(0, 0, naturalX, naturalY), naturalInsets, Surface.ROTATION_0), WindowBounds(Rect(0, 0, naturalY, naturalX), rotatedInsets, Surface.ROTATION_90), WindowBounds(Rect(0, 0, naturalX, naturalY), naturalInsets, Surface.ROTATION_180), - WindowBounds(Rect(0, 0, naturalY, naturalX), rotatedInsets, Surface.ROTATION_270), + WindowBounds(Rect(0, 0, naturalY, naturalX), rotatedInsets, Surface.ROTATION_270) ) } @@ -289,13 +273,10 @@ abstract class AbstractDeviceProfileTest { displayInfo: CachedDisplayInfo, rotation: Int, isGestureMode: Boolean = true, - densityDpi: Int, - isFixedLandscape: Boolean = false, - gridName: String? = GRID_NAME.defaultValue, + densityDpi: Int ) { setFlagsRule.setFlags(true, Flags.FLAG_ENABLE_TWOLINE_TOGGLE) - // TODO: re-enable as part of b/396211437 - setFlagsRule.setFlags(false, Flags.FLAG_ENABLE_LAUNCHER_ICON_SHAPES) + LauncherPrefs.get(testContext).put(LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE, true) val windowsBounds = perDisplayBoundsCache[displayInfo]!! val realBounds = windowsBounds[rotation] whenever(windowManagerProxy.getDisplayInfo(any())).thenReturn(displayInfo) @@ -306,9 +287,9 @@ abstract class AbstractDeviceProfileTest { .thenReturn( if (isGestureMode) NavigationMode.NO_BUTTON else NavigationMode.THREE_BUTTONS ) - doReturn(WindowManagerProxy.INSTANCE[getApplicationContext()].isTaskbarDrawnInProcess) + doReturn(WindowManagerProxy.INSTANCE[runningContext].isTaskbarDrawnInProcess) .whenever(windowManagerProxy) - .isTaskbarDrawnInProcess + .isTaskbarDrawnInProcess() val density = densityDpi / DisplayMetrics.DENSITY_DEFAULT.toFloat() val config = @@ -320,26 +301,12 @@ abstract class AbstractDeviceProfileTest { } val configurationContext = runningContext.createConfigurationContext(config) context = SandboxContext(configurationContext) - context.initDaggerComponent( - DaggerAbsDPTestSandboxComponent.builder() - .bindWMProxy(windowManagerProxy) - .bindDisplayController(displayController) - ) - launcherPrefs = context.appComponent.launcherPrefs - launcherPrefs.put( - LauncherPrefs.TASKBAR_PINNING.to(false), - LauncherPrefs.TASKBAR_PINNING_IN_DESKTOP_MODE.to(true), - LauncherPrefs.FIXED_LANDSCAPE_MODE.to(isFixedLandscape), - LauncherPrefs.HOTSEAT_COUNT.to(-1), - LauncherPrefs.DEVICE_TYPE.to(-1), - LauncherPrefs.WORKSPACE_SIZE.to(""), - LauncherPrefs.DB_FILE.to(""), - LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE.to(true), - ) - if (gridName != null) { - launcherPrefs.put(GRID_NAME, gridName) - } + context.putObject(DisplayController.INSTANCE, displayController) + context.putObject(WindowManagerProxy.INSTANCE, windowManagerProxy) + context.putObject(LauncherPrefs.INSTANCE, launcherPrefs) + whenever(launcherPrefs.get(LauncherPrefs.TASKBAR_PINNING)).thenReturn(false) + whenever(launcherPrefs.get(LauncherPrefs.TASKBAR_PINNING_IN_DESKTOP_MODE)).thenReturn(true) val info = spy(DisplayController.Info(context, windowManagerProxy, perDisplayBoundsCache)) whenever(displayController.info).thenReturn(info) whenever(info.isTransientTaskbar).thenReturn(isGestureMode) @@ -364,8 +331,7 @@ abstract class AbstractDeviceProfileTest { context.assets.open("dumpTests/$fileName").bufferedReader().use(BufferedReader::readText) private fun writeToDevice(context: Context, fileName: String, content: String) { - val file = File(context.getDir("dumpTests", Context.MODE_PRIVATE), fileName) - file.writeText(content) + File(context.getDir("dumpTests", Context.MODE_PRIVATE), fileName).writeText(content) } protected fun Float.dpToPx(): Float { @@ -381,17 +347,3 @@ abstract class AbstractDeviceProfileTest { return context.resources.getIdentifier(this, "xml", context.packageName) } } - -@LauncherAppSingleton -@Component(modules = [AllModulesMinusWMProxy::class, FakePrefsModule::class]) -interface AbsDPTestSandboxComponent : LauncherAppComponent { - - @Component.Builder - interface Builder : LauncherAppComponent.Builder { - @BindsInstance fun bindWMProxy(proxy: WindowManagerProxy): Builder - - @BindsInstance fun bindDisplayController(displayController: DisplayController): Builder - - override fun build(): AbsDPTestSandboxComponent - } -} diff --git a/tests/multivalentTests/src/com/android/launcher3/AppWidgetsRestoredReceiverTest.kt b/tests/multivalentTests/src/com/android/launcher3/AppWidgetsRestoredReceiverTest.kt index 6483bd5ffd..21abab4d49 100644 --- a/tests/multivalentTests/src/com/android/launcher3/AppWidgetsRestoredReceiverTest.kt +++ b/tests/multivalentTests/src/com/android/launcher3/AppWidgetsRestoredReceiverTest.kt @@ -6,7 +6,7 @@ import android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_IDS import android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_OLD_IDS import android.appwidget.AppWidgetManager.EXTRA_HOST_ID import android.content.Intent -import android.platform.uiautomatorhelpers.DeviceHelpers +import android.platform.uiautomator_helpers.DeviceHelpers import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.launcher3.LauncherPrefs.Companion.APP_WIDGET_IDS @@ -29,7 +29,7 @@ class AppWidgetsRestoredReceiverTest { @Before fun setup() { - launcherPrefs = LauncherPrefs.get(DeviceHelpers.context) + launcherPrefs = LauncherPrefs(DeviceHelpers.context) receiverUnderTest = AppWidgetsRestoredReceiver() } diff --git a/tests/multivalentTests/src/com/android/launcher3/AutoInstallsLayoutTest.kt b/tests/multivalentTests/src/com/android/launcher3/AutoInstallsLayoutTest.kt index 97ecafeeab..b04bccaa86 100644 --- a/tests/multivalentTests/src/com/android/launcher3/AutoInstallsLayoutTest.kt +++ b/tests/multivalentTests/src/com/android/launcher3/AutoInstallsLayoutTest.kt @@ -41,11 +41,8 @@ import com.android.launcher3.LauncherSettings.Favorites.PROFILE_ID import com.android.launcher3.LauncherSettings.Favorites.SPANX import com.android.launcher3.LauncherSettings.Favorites.SPANY import com.android.launcher3.LauncherSettings.Favorites._ID -import com.android.launcher3.dagger.LauncherAppComponent -import com.android.launcher3.dagger.LauncherAppSingleton import com.android.launcher3.model.data.AppInfo import com.android.launcher3.pm.UserCache -import com.android.launcher3.util.AllModulesMinusApiWrapper import com.android.launcher3.util.ApiWrapper import com.android.launcher3.util.Executors import com.android.launcher3.util.LauncherLayoutBuilder @@ -57,8 +54,6 @@ import com.android.launcher3.util.UserIconInfo.TYPE_MAIN import com.android.launcher3.util.UserIconInfo.TYPE_WORK import com.android.launcher3.widget.LauncherWidgetHolder import com.google.common.truth.Truth.assertThat -import dagger.BindsInstance -import dagger.Component import java.io.StringReader import org.junit.After import org.junit.Before @@ -67,7 +62,7 @@ import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.MockitoAnnotations import org.mockito.kotlin.doReturn -import org.mockito.kotlin.mock +import org.mockito.kotlin.spy import org.mockito.kotlin.whenever /** Tests for [AutoInstallsLayout] */ @@ -166,12 +161,8 @@ class AutoInstallsLayoutTest { @Test fun work_item_added_to_home() { - val original = ApiWrapper.INSTANCE[targetContext] - val apiWrapperMock = - mock(defaultAnswer = { it.method.invoke(original, *it.arguments) }) - targetContext.initDaggerComponent( - DaggerAutoInstallsLayoutTestComponent.builder().bindApiWrapper(apiWrapperMock) - ) + val apiWrapperMock = spy(ApiWrapper.INSTANCE[targetContext]) + targetContext.putObject(ApiWrapper.INSTANCE, apiWrapperMock) doReturn( mapOf( myUserHandle() to UserIconInfo(myUserHandle(), TYPE_MAIN, 0), @@ -207,7 +198,7 @@ class AutoInstallsLayoutTest { callback, SourceResources.wrap(targetContext.resources), { Xml.newPullParser().also { it.setInput(StringReader(build())) } }, - TAG_WORKSPACE, + TAG_WORKSPACE ) class MyCallback : LayoutParserCallback { @@ -223,17 +214,3 @@ class AutoInstallsLayoutTest { } } } - -class MyApiWrapper : ApiWrapper(null) - -@LauncherAppSingleton -@Component(modules = [AllModulesMinusApiWrapper::class]) -interface AutoInstallsLayoutTestComponent : LauncherAppComponent { - - @Component.Builder - interface Builder : LauncherAppComponent.Builder { - @BindsInstance fun bindApiWrapper(wrapper: ApiWrapper): Builder - - override fun build(): AutoInstallsLayoutTestComponent - } -} diff --git a/tests/multivalentTests/src/com/android/launcher3/DeleteDropTargetTest.kt b/tests/multivalentTests/src/com/android/launcher3/DeleteDropTargetTest.kt index fa368e52d1..46e66e404d 100644 --- a/tests/multivalentTests/src/com/android/launcher3/DeleteDropTargetTest.kt +++ b/tests/multivalentTests/src/com/android/launcher3/DeleteDropTargetTest.kt @@ -1,36 +1,20 @@ package com.android.launcher3 import android.content.Context -import android.platform.test.annotations.EnableFlags -import android.platform.test.flag.junit.SetFlagsRule import androidx.test.core.app.ApplicationProvider.getApplicationContext import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.launcher3.Utilities.* -import com.android.launcher3.dragndrop.DragView import com.android.launcher3.util.ActivityContextWrapper -import com.android.launcher3.util.MSDLPlayerWrapper -import com.google.android.msdl.data.model.MSDLToken import com.google.common.truth.Truth.assertThat import org.junit.Before -import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mock -import org.mockito.kotlin.eq -import org.mockito.kotlin.mock -import org.mockito.kotlin.times -import org.mockito.kotlin.verify -import org.mockito.kotlin.verifyNoMoreInteractions @SmallTest @RunWith(AndroidJUnit4::class) class DeleteDropTargetTest { - @get:Rule val mSetFlagsRule = SetFlagsRule() - - @Mock private val msdlPlayerWrapper = mock() - private var mContext: Context = ActivityContextWrapper(getApplicationContext()) // Use a non-abstract class implementation @@ -53,16 +37,4 @@ class DeleteDropTargetTest { // A lot of space for text so the text should not be clipped assertThat(buttonDropTarget.isTextClippedVertically(1000)).isFalse() } - - @Test - @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) - fun onDragEnter_performsMSDLSwipeThresholdFeedback() { - buttonDropTarget.setMSDLPlayerWrapper(msdlPlayerWrapper) - val target = DropTarget.DragObject(mContext) - target.dragView = mock>() - buttonDropTarget.onDragEnter(target) - - verify(msdlPlayerWrapper, times(1)).playToken(eq(MSDLToken.SWIPE_THRESHOLD_INDICATOR)) - verifyNoMoreInteractions(msdlPlayerWrapper) - } } diff --git a/tests/multivalentTests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt b/tests/multivalentTests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt index f855c51f8e..0538870132 100644 --- a/tests/multivalentTests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt +++ b/tests/multivalentTests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt @@ -15,6 +15,7 @@ */ package com.android.launcher3 +import android.content.Context import android.graphics.PointF import android.graphics.Rect import android.platform.test.rule.AllowedDevices @@ -22,11 +23,10 @@ import android.platform.test.rule.DeviceProduct import android.platform.test.rule.IgnoreLimit import android.platform.test.rule.LimitDevicesRule import android.util.SparseArray +import androidx.test.core.app.ApplicationProvider import com.android.launcher3.DeviceProfile.DEFAULT_DIMENSION_PROVIDER import com.android.launcher3.DeviceProfile.DEFAULT_PROVIDER -import com.android.launcher3.testing.shared.ResourceUtils.INVALID_RESOURCE_HANDLE import com.android.launcher3.util.DisplayController.Info -import com.android.launcher3.util.SandboxApplication import com.android.launcher3.util.WindowBounds import java.io.PrintWriter import java.io.StringWriter @@ -46,20 +46,21 @@ import org.mockito.kotlin.whenever @IgnoreLimit(ignoreLimit = BuildConfig.IS_STUDIO_BUILD) abstract class FakeInvariantDeviceProfileTest { - @get:Rule val context = SandboxApplication() - - protected lateinit var inv: InvariantDeviceProfile - protected val info = mock() - protected lateinit var windowBounds: WindowBounds - private var transposeLayoutWithOrientation = false - private var useTwoPanels = false - private var isGestureMode = true - private var isTransientTaskbar = true + protected lateinit var context: Context + protected var inv: InvariantDeviceProfile? = null + protected val info: Info = mock() + protected var windowBounds: WindowBounds? = null + protected var isMultiWindowMode: Boolean = false + protected var transposeLayoutWithOrientation: Boolean = false + protected var useTwoPanels: Boolean = false + protected var isGestureMode: Boolean = true + protected var isTransientTaskbar: Boolean = true @Rule @JvmField val limitDevicesRule = LimitDevicesRule() @Before - open fun setUp() { + fun setUp() { + context = ApplicationProvider.getApplicationContext() // make sure to reset values useTwoPanels = false isGestureMode = true @@ -70,11 +71,9 @@ abstract class FakeInvariantDeviceProfileTest { context, inv, info, - context.appComponent.wmProxy, - context.appComponent.themeManager, windowBounds, SparseArray(), - /*isMultiWindowMode=*/ false, + isMultiWindowMode, transposeLayoutWithOrientation, useTwoPanels, isGestureMode, @@ -85,7 +84,7 @@ abstract class FakeInvariantDeviceProfileTest { protected fun initializeVarsForPhone( isGestureMode: Boolean = true, - isVerticalBar: Boolean = false, + isVerticalBar: Boolean = false ) { val (x, y) = if (isVerticalBar) Pair(2400, 1080) else Pair(1080, 2400) @@ -96,8 +95,8 @@ abstract class FakeInvariantDeviceProfileTest { if (isVerticalBar) 118 else 0, if (isVerticalBar) 74 else 118, if (!isGestureMode && isVerticalBar) 126 else 0, - if (isGestureMode) 63 else if (isVerticalBar) 0 else 126, - ), + if (isGestureMode) 63 else if (isVerticalBar) 0 else 126 + ) ) whenever(info.isTablet(any())).thenReturn(false) @@ -109,7 +108,7 @@ abstract class FakeInvariantDeviceProfileTest { transposeLayoutWithOrientation = true inv = - context.appComponent.idp.apply { + InvariantDeviceProfile().apply { numRows = 5 numColumns = 4 numSearchContainerColumns = 4 @@ -123,7 +122,7 @@ abstract class FakeInvariantDeviceProfileTest { PointF(80f, 104f), PointF(80f, 104f), PointF(80f, 104f), - PointF(80f, 104f), + PointF(80f, 104f) ) .toTypedArray() @@ -145,7 +144,7 @@ abstract class FakeInvariantDeviceProfileTest { PointF(80f, 104f), PointF(80f, 104f), PointF(80f, 104f), - PointF(80f, 104f), + PointF(80f, 104f) ) .toTypedArray() allAppsIconSize = floatArrayOf(60f, 60f, 60f, 60f) @@ -171,20 +170,12 @@ abstract class FakeInvariantDeviceProfileTest { inlineQsb = BooleanArray(4) { false } devicePaddingId = R.xml.paddings_handhelds - - isFixedLandscape = false - workspaceSpecsId = INVALID_RESOURCE_HANDLE - allAppsSpecsId = INVALID_RESOURCE_HANDLE - folderSpecsId = INVALID_RESOURCE_HANDLE - hotseatSpecsId = INVALID_RESOURCE_HANDLE - workspaceCellSpecsId = INVALID_RESOURCE_HANDLE - allAppsCellSpecsId = INVALID_RESOURCE_HANDLE } } protected fun initializeVarsForTablet( isLandscape: Boolean = false, - isGestureMode: Boolean = true, + isGestureMode: Boolean = true ) { val (x, y) = if (isLandscape) Pair(2560, 1600) else Pair(1600, 2560) @@ -199,7 +190,7 @@ abstract class FakeInvariantDeviceProfileTest { useTwoPanels = false inv = - context.appComponent.idp.apply { + InvariantDeviceProfile().apply { numRows = 5 numColumns = 6 numSearchContainerColumns = 3 @@ -213,7 +204,7 @@ abstract class FakeInvariantDeviceProfileTest { PointF(102f, 120f), PointF(120f, 104f), PointF(102f, 120f), - PointF(102f, 120f), + PointF(102f, 120f) ) .toTypedArray() @@ -235,7 +226,7 @@ abstract class FakeInvariantDeviceProfileTest { PointF(96f, 142f), PointF(126f, 126f), PointF(96f, 142f), - PointF(96f, 142f), + PointF(96f, 142f) ) .toTypedArray() allAppsIconSize = FloatArray(4) { 60f } @@ -262,14 +253,6 @@ abstract class FakeInvariantDeviceProfileTest { inlineQsb = booleanArrayOf(false, true, false, false) devicePaddingId = R.xml.paddings_handhelds - - isFixedLandscape = false - workspaceSpecsId = INVALID_RESOURCE_HANDLE - allAppsSpecsId = INVALID_RESOURCE_HANDLE - folderSpecsId = INVALID_RESOURCE_HANDLE - hotseatSpecsId = INVALID_RESOURCE_HANDLE - workspaceCellSpecsId = INVALID_RESOURCE_HANDLE - allAppsCellSpecsId = INVALID_RESOURCE_HANDLE } } @@ -292,7 +275,7 @@ abstract class FakeInvariantDeviceProfileTest { useTwoPanels = true inv = - context.appComponent.idp.apply { + InvariantDeviceProfile().apply { numRows = rows numColumns = cols numSearchContainerColumns = cols @@ -306,7 +289,7 @@ abstract class FakeInvariantDeviceProfileTest { PointF(80f, 104f), PointF(80f, 104f), PointF(68f, 116f), - PointF(80f, 102f), + PointF(80f, 102f) ) .toTypedArray() @@ -350,14 +333,6 @@ abstract class FakeInvariantDeviceProfileTest { inlineQsb = booleanArrayOf(false, false, false, false) devicePaddingId = R.xml.paddings_handhelds - - isFixedLandscape = false - workspaceSpecsId = INVALID_RESOURCE_HANDLE - allAppsSpecsId = INVALID_RESOURCE_HANDLE - folderSpecsId = INVALID_RESOURCE_HANDLE - hotseatSpecsId = INVALID_RESOURCE_HANDLE - workspaceCellSpecsId = INVALID_RESOURCE_HANDLE - allAppsCellSpecsId = INVALID_RESOURCE_HANDLE } } diff --git a/tests/multivalentTests/src/com/android/launcher3/LauncherPrefsTest.kt b/tests/multivalentTests/src/com/android/launcher3/LauncherPrefsTest.kt index da9cc8635d..b81309506a 100644 --- a/tests/multivalentTests/src/com/android/launcher3/LauncherPrefsTest.kt +++ b/tests/multivalentTests/src/com/android/launcher3/LauncherPrefsTest.kt @@ -17,6 +17,7 @@ package com.android.launcher3 import android.content.Context import android.content.SharedPreferences +import android.content.SharedPreferences.OnSharedPreferenceChangeListener import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import androidx.test.platform.app.InstrumentationRegistry @@ -24,18 +25,12 @@ import com.android.launcher3.LauncherPrefs.Companion.BOOT_AWARE_PREFS_KEY import com.google.common.truth.Truth.assertThat import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit -import org.junit.Assert.assertThrows import org.junit.Test import org.junit.runner.RunWith private val TEST_BOOLEAN_ITEM = LauncherPrefs.nonRestorableItem("1", false) private val TEST_STRING_ITEM = LauncherPrefs.nonRestorableItem("2", "( ͡❛ ͜ʖ ͡❛)") private val TEST_INT_ITEM = LauncherPrefs.nonRestorableItem("3", -1) -private val TEST_FLOAT_ITEM = LauncherPrefs.nonRestorableItem("4", -1f) -private val TEST_LONG_ITEM = LauncherPrefs.nonRestorableItem("5", -1L) -private val TEST_SET_ITEM = LauncherPrefs.nonRestorableItem("6", setOf()) -private val TEST_HASHSET_ITEM = LauncherPrefs.nonRestorableItem("7", hashSetOf()) - private val TEST_CONTEXTUAL_ITEM = ContextualItem("4", true, { true }, EncryptionType.ENCRYPTED, Boolean::class.java) @@ -68,7 +63,7 @@ class LauncherPrefsTest { @Test fun addListener_listeningForStringItemUpdates_isCorrectlyNotifiedOfUpdates() { val latch = CountDownLatch(1) - val listener = LauncherPrefChangeListener { latch.countDown() } + val listener = OnSharedPreferenceChangeListener { _, _ -> latch.countDown() } with(launcherPrefs) { putSync(TEST_STRING_ITEM.to(TEST_STRING_ITEM.defaultValue)) @@ -83,7 +78,7 @@ class LauncherPrefsTest { @Test fun removeListener_previouslyListeningForStringItemUpdates_isNoLongerNotifiedOfUpdates() { val latch = CountDownLatch(1) - val listener = LauncherPrefChangeListener { latch.countDown() } + val listener = OnSharedPreferenceChangeListener { _, _ -> latch.countDown() } with(launcherPrefs) { addListener(listener, TEST_STRING_ITEM) @@ -99,14 +94,14 @@ class LauncherPrefsTest { @Test fun addListenerAndRemoveListener_forMultipleItems_bothWorkProperly() { var latch = CountDownLatch(3) - val listener = LauncherPrefChangeListener { latch.countDown() } + val listener = OnSharedPreferenceChangeListener { _, _ -> latch.countDown() } with(launcherPrefs) { addListener(listener, TEST_INT_ITEM, TEST_STRING_ITEM, TEST_BOOLEAN_ITEM) putSync( TEST_INT_ITEM.to(TEST_INT_ITEM.defaultValue + 123), TEST_STRING_ITEM.to(TEST_STRING_ITEM.defaultValue + "abc"), - TEST_BOOLEAN_ITEM.to(!TEST_BOOLEAN_ITEM.defaultValue), + TEST_BOOLEAN_ITEM.to(!TEST_BOOLEAN_ITEM.defaultValue) ) assertThat(latch.await(WAIT_TIME_IN_SECONDS, TimeUnit.SECONDS)).isTrue() @@ -115,7 +110,7 @@ class LauncherPrefsTest { putSync( TEST_INT_ITEM.to(TEST_INT_ITEM.defaultValue), TEST_STRING_ITEM.to(TEST_STRING_ITEM.defaultValue), - TEST_BOOLEAN_ITEM.to(TEST_BOOLEAN_ITEM.defaultValue), + TEST_BOOLEAN_ITEM.to(TEST_BOOLEAN_ITEM.defaultValue) ) remove(TEST_INT_ITEM, TEST_STRING_ITEM, TEST_BOOLEAN_ITEM) @@ -149,50 +144,16 @@ class LauncherPrefsTest { } } - @Test - fun whenItemType_isInvalid_thenThrowException() { - val badItem = LauncherPrefs.nonRestorableItem("8", mapOf()) - with(launcherPrefs) { - assertThrows(IllegalArgumentException::class.java) { - putSync(badItem.to(badItem.defaultValue)) - } - assertThrows(IllegalArgumentException::class.java) { get(badItem) } - } - } - @Test fun put_storesListOfItemsInLauncherPrefs_successfully() { with(launcherPrefs) { putSync( TEST_STRING_ITEM.to(TEST_STRING_ITEM.defaultValue), TEST_INT_ITEM.to(TEST_INT_ITEM.defaultValue), - TEST_BOOLEAN_ITEM.to(TEST_BOOLEAN_ITEM.defaultValue), - TEST_FLOAT_ITEM.to(TEST_FLOAT_ITEM.defaultValue), - TEST_LONG_ITEM.to(TEST_LONG_ITEM.defaultValue), - TEST_SET_ITEM.to(TEST_SET_ITEM.defaultValue), - TEST_HASHSET_ITEM.to(TEST_HASHSET_ITEM.defaultValue), - ) - assertThat( - has( - TEST_STRING_ITEM, - TEST_INT_ITEM, - TEST_BOOLEAN_ITEM, - TEST_FLOAT_ITEM, - TEST_LONG_ITEM, - TEST_SET_ITEM, - TEST_HASHSET_ITEM, - ) - ) - .isTrue() - remove( - TEST_STRING_ITEM, - TEST_INT_ITEM, - TEST_BOOLEAN_ITEM, - TEST_FLOAT_ITEM, - TEST_LONG_ITEM, - TEST_SET_ITEM, - TEST_HASHSET_ITEM, + TEST_BOOLEAN_ITEM.to(TEST_BOOLEAN_ITEM.defaultValue) ) + assertThat(has(TEST_BOOLEAN_ITEM, TEST_INT_ITEM, TEST_STRING_ITEM)).isTrue() + remove(TEST_STRING_ITEM, TEST_INT_ITEM, TEST_BOOLEAN_ITEM) } } @@ -230,7 +191,7 @@ class LauncherPrefsTest { LauncherPrefs.backedUpItem( TEST_PREF_KEY, TEST_DEFAULT_VALUE, - EncryptionType.DEVICE_PROTECTED, + EncryptionType.DEVICE_PROTECTED ) val bootAwarePrefs: SharedPreferences = @@ -251,7 +212,7 @@ class LauncherPrefsTest { LauncherPrefs.backedUpItem( TEST_PREF_KEY, TEST_DEFAULT_VALUE, - EncryptionType.DEVICE_PROTECTED, + EncryptionType.DEVICE_PROTECTED ) val bootAwarePrefs: SharedPreferences = diff --git a/tests/multivalentTests/src/com/android/launcher3/UtilitiesTest.kt b/tests/multivalentTests/src/com/android/launcher3/UtilitiesTest.kt index d0aa7a8dd9..60a4197c99 100644 --- a/tests/multivalentTests/src/com/android/launcher3/UtilitiesTest.kt +++ b/tests/multivalentTests/src/com/android/launcher3/UtilitiesTest.kt @@ -17,19 +17,12 @@ package com.android.launcher3 import android.content.Context -import android.content.ContextWrapper -import android.graphics.Rect -import android.graphics.RectF import android.view.View import android.view.ViewGroup import androidx.test.core.app.ApplicationProvider.getApplicationContext import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.launcher3.util.ActivityContextWrapper -import kotlin.random.Random -import org.junit.Assert.assertArrayEquals -import org.junit.Assert.assertEquals -import org.junit.Assert.assertFalse -import org.junit.Assert.assertTrue +import org.junit.Assert.* import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -37,10 +30,6 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class UtilitiesTest { - companion object { - const val SEED = 827 - } - private lateinit var mContext: Context @Before @@ -105,284 +94,4 @@ class UtilitiesTest { assertTrue(Utilities.pointInView(view, -5f, -5f, 10f)) // Inside slop assertFalse(Utilities.pointInView(view, 115f, 115f, 10f)) // Outside slop } - - @Test - fun testNumberBounding() { - assertEquals(887.99f, Utilities.boundToRange(887.99f, 0f, 1000f)) - assertEquals(2.777f, Utilities.boundToRange(887.99f, 0f, 2.777f)) - assertEquals(900f, Utilities.boundToRange(887.99f, 900f, 1000f)) - - assertEquals(9383667L, Utilities.boundToRange(9383667L, -999L, 9999999L)) - assertEquals(9383668L, Utilities.boundToRange(9383667L, 9383668L, 9999999L)) - assertEquals(42L, Utilities.boundToRange(9383667L, -999L, 42L)) - - assertEquals(345, Utilities.boundToRange(345, 2, 500)) - assertEquals(400, Utilities.boundToRange(345, 400, 500)) - assertEquals(300, Utilities.boundToRange(345, 2, 300)) - - val random = Random(SEED) - for (i in 1..300) { - val value = random.nextFloat() - val lowerBound = random.nextFloat() - val higherBound = lowerBound + random.nextFloat() - - assertEquals( - "Utilities.boundToRange doesn't match Kotlin coerceIn", - value.coerceIn(lowerBound, higherBound), - Utilities.boundToRange(value, lowerBound, higherBound) - ) - assertEquals( - "Utilities.boundToRange doesn't match Kotlin coerceIn", - value.toInt().coerceIn(lowerBound.toInt(), higherBound.toInt()), - Utilities.boundToRange(value.toInt(), lowerBound.toInt(), higherBound.toInt()) - ) - assertEquals( - "Utilities.boundToRange doesn't match Kotlin coerceIn", - value.toLong().coerceIn(lowerBound.toLong(), higherBound.toLong()), - Utilities.boundToRange(value.toLong(), lowerBound.toLong(), higherBound.toLong()) - ) - assertEquals( - "If the lower bound is higher than lower bound, it should return the lower bound", - higherBound, - Utilities.boundToRange(value, higherBound, lowerBound) - ) - } - } - - @Test - fun testTranslateOverlappingView() { - testConcentricOverlap() - leftDownCornerOverlap() - noOverlap() - } - - /* - Test Case: Rectangle Contained Within Another Rectangle - - +-------------+ <-- exclusionBounds - | | - | +-----+ | - | | | | <-- targetViewBounds - | | | | - | +-----+ | - | | - +-------------+ - */ - private fun testConcentricOverlap() { - val targetView = View(ContextWrapper(getApplicationContext())) - val targetViewBounds = Rect(40, 40, 60, 60) - val inclusionBounds = Rect(0, 0, 100, 100) - val exclusionBounds = Rect(30, 30, 70, 70) - - Utilities.translateOverlappingView( - targetView, - targetViewBounds, - inclusionBounds, - exclusionBounds, - Utilities.TRANSLATE_RIGHT - ) - assertEquals(30f, targetView.translationX) - Utilities.translateOverlappingView( - targetView, - targetViewBounds, - inclusionBounds, - exclusionBounds, - Utilities.TRANSLATE_LEFT - ) - assertEquals(-30f, targetView.translationX) - Utilities.translateOverlappingView( - targetView, - targetViewBounds, - inclusionBounds, - exclusionBounds, - Utilities.TRANSLATE_DOWN - ) - assertEquals(30f, targetView.translationY) - Utilities.translateOverlappingView( - targetView, - targetViewBounds, - inclusionBounds, - exclusionBounds, - Utilities.TRANSLATE_UP - ) - assertEquals(-30f, targetView.translationY) - } - - /* - Test Case: Non-Overlapping Rectangles - - +-----------------+ <-- targetViewBounds - | | - | | - +-----------------+ - - +-----------+ <-- exclusionBounds - | | - | | - +-----------+ - */ - private fun noOverlap() { - val targetView = View(ContextWrapper(getApplicationContext())) - val targetViewBounds = Rect(10, 10, 20, 20) - - val inclusionBounds = Rect(0, 0, 100, 100) - val exclusionBounds = Rect(30, 30, 40, 40) - - Utilities.translateOverlappingView( - targetView, - targetViewBounds, - inclusionBounds, - exclusionBounds, - Utilities.TRANSLATE_RIGHT - ) - assertEquals(0f, targetView.translationX) - Utilities.translateOverlappingView( - targetView, - targetViewBounds, - inclusionBounds, - exclusionBounds, - Utilities.TRANSLATE_LEFT - ) - assertEquals(0f, targetView.translationX) - Utilities.translateOverlappingView( - targetView, - targetViewBounds, - inclusionBounds, - exclusionBounds, - Utilities.TRANSLATE_DOWN - ) - assertEquals(0f, targetView.translationY) - Utilities.translateOverlappingView( - targetView, - targetViewBounds, - inclusionBounds, - exclusionBounds, - Utilities.TRANSLATE_UP - ) - assertEquals(0f, targetView.translationY) - } - - /* - Test Case: Rectangles Overlapping at Corners - - +------------+ <-- exclusionBounds - | | - +-------+ | - | | | | <-- targetViewBounds - | +------------+ - | | - +-------+ - */ - private fun leftDownCornerOverlap() { - val targetView = View(ContextWrapper(getApplicationContext())) - val targetViewBounds = Rect(20, 20, 30, 30) - - val inclusionBounds = Rect(0, 0, 100, 100) - val exclusionBounds = Rect(25, 25, 35, 35) - - Utilities.translateOverlappingView( - targetView, - targetViewBounds, - inclusionBounds, - exclusionBounds, - Utilities.TRANSLATE_RIGHT - ) - assertEquals(15f, targetView.translationX) - Utilities.translateOverlappingView( - targetView, - targetViewBounds, - inclusionBounds, - exclusionBounds, - Utilities.TRANSLATE_LEFT - ) - assertEquals(-5f, targetView.translationX) - Utilities.translateOverlappingView( - targetView, - targetViewBounds, - inclusionBounds, - exclusionBounds, - Utilities.TRANSLATE_DOWN - ) - assertEquals(15f, targetView.translationY) - Utilities.translateOverlappingView( - targetView, - targetViewBounds, - inclusionBounds, - exclusionBounds, - Utilities.TRANSLATE_UP - ) - assertEquals(-5f, targetView.translationY) - } - - @Test - fun trim() { - val expectedString = "Hello World" - assertEquals(expectedString, Utilities.trim("Hello World ")) - // Basic trimming - assertEquals(expectedString, Utilities.trim(" Hello World ")) - assertEquals(expectedString, Utilities.trim(" Hello World")) - - // Non-breaking whitespace - assertEquals("Hello World", Utilities.trim("\u00A0\u00A0Hello World\u00A0\u00A0")) - - // Whitespace combinations - assertEquals(expectedString, Utilities.trim("\t \r\n Hello World \n\r")) - assertEquals(expectedString, Utilities.trim("\nHello World ")) - - // Null input - assertEquals("", Utilities.trim(null)) - - // Empty String - assertEquals("", Utilities.trim("")) - } - - @Test - fun getProgress() { - // Basic test - assertEquals(0.5f, Utilities.getProgress(50f, 0f, 100f), 0.001f) - - // Negative values - assertEquals(0.5f, Utilities.getProgress(-20f, -50f, 10f), 0.001f) - - // Outside of range - assertEquals(1.2f, Utilities.getProgress(120f, 0f, 100f), 0.001f) - } - - @Test - fun scaleRectFAboutPivot() { - // Enlarge - var rectF = RectF(10f, 20f, 50f, 80f) - Utilities.scaleRectFAboutPivot(rectF, 30f, 50f, 1.5f) - assertEquals(RectF(0f, 5f, 60f, 95f), rectF) - - // Shrink - rectF = RectF(10f, 20f, 50f, 80f) - Utilities.scaleRectFAboutPivot(rectF, 30f, 50f, 0.5f) - assertEquals(RectF(20f, 35f, 40f, 65f), rectF) - - // No scale - rectF = RectF(10f, 20f, 50f, 80f) - Utilities.scaleRectFAboutPivot(rectF, 30f, 50f, 1.0f) - assertEquals(RectF(10f, 20f, 50f, 80f), rectF) - } - - @Test - fun rotateBounds() { - var rect = Rect(20, 70, 60, 80) - Utilities.rotateBounds(rect, 100, 100, 0) - assertEquals(Rect(20, 70, 60, 80), rect) - - rect = Rect(20, 70, 60, 80) - Utilities.rotateBounds(rect, 100, 100, 1) - assertEquals(Rect(70, 40, 80, 80), rect) - - // case removed for b/28435189 - // rect = Rect(20, 70, 60, 80) - // Utilities.rotateBounds(rect, 100, 100, 2) - // assertEquals(Rect(40, 20, 80, 30), rect) - - rect = Rect(20, 70, 60, 80) - Utilities.rotateBounds(rect, 100, 100, 3) - assertEquals(Rect(20, 20, 30, 60), rect) - } } diff --git a/tests/multivalentTests/src/com/android/launcher3/allapps/AlphabeticalAppsListTest.java b/tests/multivalentTests/src/com/android/launcher3/allapps/AlphabeticalAppsListTest.java index d93811929b..d2238ffda4 100644 --- a/tests/multivalentTests/src/com/android/launcher3/allapps/AlphabeticalAppsListTest.java +++ b/tests/multivalentTests/src/com/android/launcher3/allapps/AlphabeticalAppsListTest.java @@ -65,7 +65,6 @@ public class AlphabeticalAppsListTest { private static final int PRIVATE_SPACE_HEADER_ITEM_COUNT = 1; private static final int MAIN_USER_APP_COUNT = 2; private static final int PRIVATE_USER_APP_COUNT = 2; - private static final int VIEW_AT_END_OF_APP_LIST = 1; private static final int NUM_APP_COLS = 4; private static final int NUM_APP_ROWS = 3; private static final int PRIVATE_SPACE_SYS_APP_SEPARATOR_ITEM_COUNT = 1; @@ -108,8 +107,7 @@ public class AlphabeticalAppsListTest { && info.user.equals(MAIN_HANDLE)); assertEquals(MAIN_USER_APP_COUNT + PRIVATE_SPACE_HEADER_ITEM_COUNT - + PRIVATE_USER_APP_COUNT + VIEW_AT_END_OF_APP_LIST, - mAlphabeticalAppsList.getAdapterItems().size()); + + PRIVATE_USER_APP_COUNT, mAlphabeticalAppsList.getAdapterItems().size()); assertEquals(PRIVATE_SPACE_HEADER_ITEM_COUNT, mAlphabeticalAppsList.getAdapterItems().stream().filter(item -> item.viewType == VIEW_TYPE_PRIVATE_SPACE_HEADER).toList().size()); @@ -138,7 +136,7 @@ public class AlphabeticalAppsListTest { && info.user.equals(MAIN_HANDLE)); assertEquals(MAIN_USER_APP_COUNT + PRIVATE_SPACE_HEADER_ITEM_COUNT - + PRIVATE_SPACE_SYS_APP_SEPARATOR_ITEM_COUNT + VIEW_AT_END_OF_APP_LIST + + PRIVATE_SPACE_SYS_APP_SEPARATOR_ITEM_COUNT + PRIVATE_USER_APP_COUNT, mAlphabeticalAppsList.getAdapterItems().size()); assertEquals(PRIVATE_SPACE_HEADER_ITEM_COUNT, mAlphabeticalAppsList.getAdapterItems().stream().filter(item -> @@ -168,8 +166,7 @@ public class AlphabeticalAppsListTest { mAlphabeticalAppsList.updateItemFilter(info -> info != null && info.user.equals(MAIN_HANDLE)); - assertEquals(MAIN_USER_APP_COUNT + PRIVATE_SPACE_HEADER_ITEM_COUNT + - VIEW_AT_END_OF_APP_LIST, + assertEquals(MAIN_USER_APP_COUNT + PRIVATE_SPACE_HEADER_ITEM_COUNT, mAlphabeticalAppsList.getAdapterItems().size()); assertEquals(PRIVATE_SPACE_HEADER_ITEM_COUNT, mAlphabeticalAppsList .getAdapterItems().stream().filter(item -> @@ -190,8 +187,8 @@ public class AlphabeticalAppsListTest { mAlphabeticalAppsList.updateItemFilter(info -> info != null && info.user.equals(MAIN_HANDLE)); - assertEquals(MAIN_USER_APP_COUNT + PRIVATE_SPACE_HEADER_ITEM_COUNT + - VIEW_AT_END_OF_APP_LIST, mAlphabeticalAppsList.getAdapterItems().size()); + assertEquals(MAIN_USER_APP_COUNT + PRIVATE_SPACE_HEADER_ITEM_COUNT, + mAlphabeticalAppsList.getAdapterItems().size()); assertEquals(PRIVATE_SPACE_HEADER_ITEM_COUNT, mAlphabeticalAppsList .getAdapterItems().stream().filter(item -> item.viewType == VIEW_TYPE_PRIVATE_SPACE_HEADER).toList().size()); @@ -209,8 +206,7 @@ public class AlphabeticalAppsListTest { mAlphabeticalAppsList.updateItemFilter(info -> info != null && info.user.equals(MAIN_HANDLE)); - assertEquals(MAIN_USER_APP_COUNT + VIEW_AT_END_OF_APP_LIST, - mAlphabeticalAppsList.getAdapterItems().size()); + assertEquals(MAIN_USER_APP_COUNT, mAlphabeticalAppsList.getAdapterItems().size()); assertEquals(0, mAlphabeticalAppsList.getAdapterItems().stream().filter(item -> item.viewType == VIEW_TYPE_PRIVATE_SPACE_HEADER).toList().size()); assertEquals(0, mAlphabeticalAppsList.getAdapterItems().stream().filter(item -> @@ -226,8 +222,7 @@ public class AlphabeticalAppsListTest { mAlphabeticalAppsList.updateItemFilter(info -> info != null && info.user.equals(MAIN_HANDLE)); - assertEquals(MAIN_USER_APP_COUNT + VIEW_AT_END_OF_APP_LIST, - mAlphabeticalAppsList.getAdapterItems().size()); + assertEquals(2, mAlphabeticalAppsList.getAdapterItems().size()); assertEquals(0, mAlphabeticalAppsList.getAdapterItems().stream().filter(item -> item.itemInfo != null && item.itemInfo.itemType == VIEW_TYPE_PRIVATE_SPACE_HEADER) diff --git a/tests/multivalentTests/src/com/android/launcher3/celllayout/CellLayoutTestCaseReader.java b/tests/multivalentTests/src/com/android/launcher3/celllayout/CellLayoutTestCaseReader.java index f1403e5d59..419cb3d52e 100644 --- a/tests/multivalentTests/src/com/android/launcher3/celllayout/CellLayoutTestCaseReader.java +++ b/tests/multivalentTests/src/com/android/launcher3/celllayout/CellLayoutTestCaseReader.java @@ -54,7 +54,7 @@ public class CellLayoutTestCaseReader { } public static class Arguments extends TestSection { - public String[] arguments; + String[] arguments; public Arguments(String[] arguments) { super(State.ARGUMENTS); diff --git a/tests/multivalentTests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java b/tests/multivalentTests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java index f7723392d4..0c3081fa83 100644 --- a/tests/multivalentTests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java +++ b/tests/multivalentTests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java @@ -15,13 +15,13 @@ */ package com.android.launcher3.celllayout; +import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; import static com.android.launcher3.util.TestUtil.runOnExecutorSync; import android.content.Context; -import com.android.launcher3.Flags; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherModel; import com.android.launcher3.LauncherSettings; @@ -59,13 +59,7 @@ public class FavoriteItemsTransaction { runOnExecutorSync(MODEL_EXECUTOR, () -> { ModelDbController controller = model.getModelDbController(); // Migrate any previous data so that the DB state is correct - if (Flags.gridMigrationRefactor()) { - controller.attemptMigrateDb( - null /* restoreEventLogger */, model.getModelDelegate()); - } else { - controller.tryMigrateDB(null /* restoreEventLogger */, - model.getModelDelegate()); - } + controller.tryMigrateDB(null /* restoreEventLogger */); // Create DB again to load fresh data controller.createEmptyDB(); @@ -88,7 +82,7 @@ public class FavoriteItemsTransaction { item.onAddToDatabase(writer); writer.put(LauncherSettings.Favorites._ID, i); - controller.insert(writer.getValues(mContext)); + controller.insert(TABLE_NAME, writer.getValues(mContext)); } for (int i = 0; i < containerItems.size(); i++) { @@ -96,7 +90,7 @@ public class FavoriteItemsTransaction { ItemInfo item = containerItems.get(i); item.onAddToDatabase(writer); writer.put(LauncherSettings.Favorites._ID, count + i); - controller.insert(writer.getValues(mContext)); + controller.insert(TABLE_NAME, writer.getValues(mContext)); } transaction.commit(); } diff --git a/tests/multivalentTests/src/com/android/launcher3/celllayout/HotseatReorderUnitTest.kt b/tests/multivalentTests/src/com/android/launcher3/celllayout/HotseatReorderUnitTest.kt index a3c7f4fb49..c32461ea98 100644 --- a/tests/multivalentTests/src/com/android/launcher3/celllayout/HotseatReorderUnitTest.kt +++ b/tests/multivalentTests/src/com/android/launcher3/celllayout/HotseatReorderUnitTest.kt @@ -104,7 +104,7 @@ class HotseatReorderUnitTest { val cl = cellLayoutBuilder.createCellLayout(board.width, board.height, false) // The views have to be sorted or the result can vary board.icons - .map(IconPoint::coord) + .map(IconPoint::getCoord) .sortedWith( Comparator.comparing { p: Any -> (p as Point).x } .thenComparing { p: Any -> (p as Point).y } @@ -120,7 +120,9 @@ class HotseatReorderUnitTest { ) } board.widgets - .sortedWith(Comparator.comparing(WidgetRect::cellX).thenComparing(WidgetRect::cellY)) + .sortedWith( + Comparator.comparing(WidgetRect::getCellX).thenComparing(WidgetRect::getCellY) + ) .forEach { widget -> addViewInCellLayout( cl, diff --git a/tests/multivalentTests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java b/tests/multivalentTests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java index bf8e8b1c3f..8a9711d2be 100644 --- a/tests/multivalentTests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java +++ b/tests/multivalentTests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java @@ -37,7 +37,6 @@ import com.android.launcher3.celllayout.board.WidgetRect; import com.android.launcher3.celllayout.testgenerator.RandomBoardGenerator; import com.android.launcher3.celllayout.testgenerator.RandomMultiBoardGenerator; import com.android.launcher3.util.ActivityContextWrapper; -import com.android.launcher3.util.rule.TestStabilityRule; import com.android.launcher3.views.DoubleShadowBubbleTextView; import org.junit.Rule; @@ -68,9 +67,6 @@ public class ReorderAlgorithmUnitTest { private static final int TOTAL_OF_CASES_GENERATED = 300; private Context mApplicationContext; - @Rule - public TestStabilityRule mTestStabilityRule = new TestStabilityRule(); - @Rule public UnitTestCellLayoutBuilderRule mCellLayoutBuilder = new UnitTestCellLayoutBuilderRule(); @@ -148,8 +144,8 @@ public class ReorderAlgorithmUnitTest { public ItemConfiguration solve(CellLayoutBoard board, int x, int y, int spanX, int spanY, int minSpanX, int minSpanY, boolean isMulti) { - CellLayout cl = mCellLayoutBuilder.createCellLayoutDefaultSize(board.getWidth(), - board.getHeight(), isMulti); + CellLayout cl = mCellLayoutBuilder.createCellLayout(board.getWidth(), board.getHeight(), + isMulti); // The views have to be sorted or the result can vary board.getIcons() diff --git a/tests/multivalentTests/src/com/android/launcher3/celllayout/UnitTestCellLayoutBuilderRule.kt b/tests/multivalentTests/src/com/android/launcher3/celllayout/UnitTestCellLayoutBuilderRule.kt index f624be103a..b63966db74 100644 --- a/tests/multivalentTests/src/com/android/launcher3/celllayout/UnitTestCellLayoutBuilderRule.kt +++ b/tests/multivalentTests/src/com/android/launcher3/celllayout/UnitTestCellLayoutBuilderRule.kt @@ -64,21 +64,11 @@ class UnitTestCellLayoutBuilderRule : TestWatcher() { dp.inv.numRows = prevNumRows } - fun createCellLayoutDefaultSize(columns: Int, rows: Int, isMulti: Boolean): CellLayout { - return createCellLayout(columns, rows, isMulti) - } - - fun createCellLayout( - columns: Int, - rows: Int, - isMulti: Boolean, - width: Int = 1000, - height: Int = 1000 - ): CellLayout { + fun createCellLayout(width: Int, height: Int, isMulti: Boolean): CellLayout { val dp = getDeviceProfile() // modify the device profile. - dp.inv.numColumns = if (isMulti) columns / 2 else columns - dp.inv.numRows = rows + dp.inv.numColumns = if (isMulti) width / 2 else width + dp.inv.numRows = height dp.cellLayoutBorderSpacePx = Point(0, 0) val cl = if (isMulti) MultipageCellLayout(getWrappedContext(applicationContext, dp)) @@ -86,8 +76,8 @@ class UnitTestCellLayoutBuilderRule : TestWatcher() { // I put a very large number for width and height so that all the items can fit, it doesn't // need to be exact, just bigger than the sum of cell border cl.measure( - View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY), - View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY) + View.MeasureSpec.makeMeasureSpec(10000, View.MeasureSpec.EXACTLY), + View.MeasureSpec.makeMeasureSpec(10000, View.MeasureSpec.EXACTLY) ) return cl } diff --git a/tests/multivalentTests/src/com/android/launcher3/celllayout/board/CellLayoutBoard.java b/tests/multivalentTests/src/com/android/launcher3/celllayout/board/CellLayoutBoard.java index 04bfee97be..e5ad888a8c 100644 --- a/tests/multivalentTests/src/com/android/launcher3/celllayout/board/CellLayoutBoard.java +++ b/tests/multivalentTests/src/com/android/launcher3/celllayout/board/CellLayoutBoard.java @@ -88,7 +88,7 @@ public class CellLayoutBoard implements Comparable { public WidgetRect getWidgetOfType(char type) { return mWidgetsRects.stream() - .filter(widgetRect -> widgetRect.getType() == type).findFirst().orElse(null); + .filter(widgetRect -> widgetRect.mType == type).findFirst().orElse(null); } public WidgetRect getWidgetAt(int x, int y) { @@ -117,8 +117,8 @@ public class CellLayoutBoard implements Comparable { } private void removeWidgetFromBoard(WidgetRect widget) { - for (int xi = widget.getBounds().left; xi <= widget.getBounds().right; xi++) { - for (int yi = widget.getBounds().bottom; yi <= widget.getBounds().top; yi++) { + for (int xi = widget.mBounds.left; xi <= widget.mBounds.right; xi++) { + for (int yi = widget.mBounds.bottom; yi <= widget.mBounds.top; yi++) { mWidget[xi][yi] = '-'; } } @@ -127,7 +127,7 @@ public class CellLayoutBoard implements Comparable { private void removeOverlappingItems(Rect rect) { // Remove overlapping widgets and remove them from the board mWidgetsRects = mWidgetsRects.stream().filter(widget -> { - if (rect.intersect(widget.getBounds())) { + if (rect.intersect(widget.mBounds)) { removeWidgetFromBoard(widget); return false; } @@ -135,8 +135,8 @@ public class CellLayoutBoard implements Comparable { }).collect(Collectors.toList()); // Remove overlapping icons and remove them from the board mIconPoints = mIconPoints.stream().filter(iconPoint -> { - int x = iconPoint.getCoord().x; - int y = iconPoint.getCoord().y; + int x = iconPoint.coord.x; + int y = iconPoint.coord.y; if (rect.contains(x, y)) { mWidget[x][y] = '-'; return false; @@ -146,8 +146,8 @@ public class CellLayoutBoard implements Comparable { // Remove overlapping folders and remove them from the board mFolderPoints = mFolderPoints.stream().filter(folderPoint -> { - int x = folderPoint.getCoord().x; - int y = folderPoint.getCoord().y; + int x = folderPoint.coord.x; + int y = folderPoint.coord.y; if (rect.contains(x, y)) { mWidget[x][y] = '-'; return false; @@ -159,7 +159,7 @@ public class CellLayoutBoard implements Comparable { private void removeOverlappingItems(Point p) { // Remove overlapping widgets and remove them from the board mWidgetsRects = mWidgetsRects.stream().filter(widget -> { - if (IdenticalBoardComparator.Companion.touchesPoint(widget.getBounds(), p)) { + if (IdenticalBoardComparator.Companion.touchesPoint(widget.mBounds, p)) { removeWidgetFromBoard(widget); return false; } @@ -167,8 +167,8 @@ public class CellLayoutBoard implements Comparable { }).collect(Collectors.toList()); // Remove overlapping icons and remove them from the board mIconPoints = mIconPoints.stream().filter(iconPoint -> { - int x = iconPoint.getCoord().x; - int y = iconPoint.getCoord().y; + int x = iconPoint.coord.x; + int y = iconPoint.coord.y; if (p.x == x && p.y == y) { mWidget[x][y] = '-'; return false; @@ -178,8 +178,8 @@ public class CellLayoutBoard implements Comparable { // Remove overlapping folders and remove them from the board mFolderPoints = mFolderPoints.stream().filter(folderPoint -> { - int x = folderPoint.getCoord().x; - int y = folderPoint.getCoord().y; + int x = folderPoint.coord.x; + int y = folderPoint.coord.y; if (p.x == x && p.y == y) { mWidget[x][y] = '-'; return false; @@ -226,7 +226,7 @@ public class CellLayoutBoard implements Comparable { public void removeItem(char type) { mWidgetsRects.stream() - .filter(widgetRect -> widgetRect.getType() == type) + .filter(widgetRect -> widgetRect.mType == type) .forEach(widgetRect -> removeOverlappingItems( new Point(widgetRect.getCellX(), widgetRect.getCellY()))); } @@ -365,10 +365,10 @@ public class CellLayoutBoard implements Comparable { board.mWidth = lines[0].length(); board.mWidgetsRects = getRects(board.mWidget); board.mWidgetsRects.forEach(widgetRect -> { - if (widgetRect.getType() == CellType.MAIN_WIDGET) { + if (widgetRect.mType == CellType.MAIN_WIDGET) { board.mMain = widgetRect; } - board.mWidgetsMap.put(widgetRect.getType(), widgetRect); + board.mWidgetsMap.put(widgetRect.mType, widgetRect); }); board.mIconPoints = getIconPoints(board.mWidget); board.mFolderPoints = getFolderPoints(board.mWidget); diff --git a/tests/multivalentTests/src/com/android/launcher3/celllayout/board/CellType.java b/tests/multivalentTests/src/com/android/launcher3/celllayout/board/CellType.java new file mode 100644 index 0000000000..49c146b32a --- /dev/null +++ b/tests/multivalentTests/src/com/android/launcher3/celllayout/board/CellType.java @@ -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.launcher3.celllayout.board; + +public class CellType { + // The cells marked by this will be filled by 1x1 widgets and will be ignored when + // validating + public static final char IGNORE = 'x'; + // The cells marked by this will be filled by app icons + public static final char ICON = 'i'; + // The cells marked by FOLDER will be filled by folders with 27 app icons inside + public static final char FOLDER = 'Z'; + // Empty space + public static final char EMPTY = '-'; + // Widget that will be saved as "main widget" for easier retrieval + public static final char MAIN_WIDGET = 'm'; + // Everything else will be consider a widget +} diff --git a/tests/multivalentTests/src/com/android/launcher3/celllayout/board/FolderPoint.java b/tests/multivalentTests/src/com/android/launcher3/celllayout/board/FolderPoint.java new file mode 100644 index 0000000000..39ba434dc0 --- /dev/null +++ b/tests/multivalentTests/src/com/android/launcher3/celllayout/board/FolderPoint.java @@ -0,0 +1,37 @@ +/* + * 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.launcher3.celllayout.board; + +import android.graphics.Point; + +public class FolderPoint { + public Point coord; + public char mType; + + public FolderPoint(Point coord, char type) { + this.coord = coord; + mType = type; + } + + /** + * [A-Z]: Represents a folder and number of icons in the folder is represented by + * the order of letter in the alphabet, A=2, B=3, C=4 ... etc. + */ + public int getNumberIconsInside() { + return (mType - 'A') + 2; + } +} diff --git a/tests/multivalentTests/src/com/android/launcher3/celllayout/board/IconPoint.java b/tests/multivalentTests/src/com/android/launcher3/celllayout/board/IconPoint.java new file mode 100644 index 0000000000..d3d297003d --- /dev/null +++ b/tests/multivalentTests/src/com/android/launcher3/celllayout/board/IconPoint.java @@ -0,0 +1,45 @@ +/* + * 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.launcher3.celllayout.board; + +import android.graphics.Point; + +public class IconPoint { + public Point coord; + public char mType; + + public IconPoint(Point coord, char type) { + this.coord = coord; + mType = type; + } + + public char getType() { + return mType; + } + + public void setType(char type) { + mType = type; + } + + public Point getCoord() { + return coord; + } + + public void setCoord(Point coord) { + this.coord = coord; + } +} diff --git a/tests/multivalentTests/src/com/android/launcher3/celllayout/board/IdenticalBoardComparator.kt b/tests/multivalentTests/src/com/android/launcher3/celllayout/board/IdenticalBoardComparator.kt index aacd940460..a4a420cf59 100644 --- a/tests/multivalentTests/src/com/android/launcher3/celllayout/board/IdenticalBoardComparator.kt +++ b/tests/multivalentTests/src/com/android/launcher3/celllayout/board/IdenticalBoardComparator.kt @@ -26,11 +26,11 @@ class IdenticalBoardComparator : Comparator { /** Converts a list of WidgetRect into a map of the count of different widget.bounds */ private fun widgetsToBoundsMap(widgets: List) = - widgets.groupingBy { it.bounds }.eachCount() + widgets.groupingBy { it.mBounds }.eachCount() /** Converts a list of IconPoint into a map of the count of different icon.coord */ private fun iconsToPosCountMap(widgets: List) = - widgets.groupingBy { it.coord }.eachCount() + widgets.groupingBy { it.getCoord() }.eachCount() override fun compare( cellLayoutBoard: CellLayoutBoard, @@ -47,7 +47,7 @@ class IdenticalBoardComparator : Comparator { widgetsToBoundsMap( otherCellLayoutBoard.widgets .filter { !it.shouldIgnore() } - .filter { !overlapsWithIgnored(ignoredRectangles, it.bounds) } + .filter { !overlapsWithIgnored(ignoredRectangles, it.mBounds) } ) if (widgetsMap != otherWidgetMap) { diff --git a/tests/multivalentTests/src/com/android/launcher3/celllayout/board/TestWorkspaceBuilder.java b/tests/multivalentTests/src/com/android/launcher3/celllayout/board/TestWorkspaceBuilder.java new file mode 100644 index 0000000000..8a427dd81b --- /dev/null +++ b/tests/multivalentTests/src/com/android/launcher3/celllayout/board/TestWorkspaceBuilder.java @@ -0,0 +1,192 @@ +/* + * 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.launcher3.celllayout.board; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + +import static com.android.launcher3.ui.TestViewHelpers.findWidgetProvider; +import static com.android.launcher3.util.WidgetUtils.createWidgetInfo; + +import android.content.ComponentName; +import android.content.Context; +import android.graphics.Rect; +import android.os.Process; +import android.os.UserHandle; +import android.util.Log; + +import com.android.launcher3.InvariantDeviceProfile; +import com.android.launcher3.LauncherSettings; +import com.android.launcher3.celllayout.FavoriteItemsTransaction; +import com.android.launcher3.model.data.AppInfo; +import com.android.launcher3.model.data.FolderInfo; +import com.android.launcher3.model.data.ItemInfo; +import com.android.launcher3.model.data.LauncherAppWidgetInfo; +import com.android.launcher3.model.data.WorkspaceItemInfo; +import com.android.launcher3.widget.LauncherAppWidgetProviderInfo; + +import java.util.function.Supplier; +import java.util.stream.IntStream; + +public class TestWorkspaceBuilder { + + private static final String TAG = "CellLayoutBoardBuilder"; + private static final String TEST_ACTIVITY_PACKAGE_PREFIX = "com.android.launcher3.tests."; + private ComponentName mAppComponentName = new ComponentName( + "com.google.android.calculator", "com.android.calculator2.Calculator"); + private UserHandle mMyUser; + + private Context mContext; + + public TestWorkspaceBuilder(Context context) { + mMyUser = Process.myUserHandle(); + mContext = context; + } + + /** + * Fills the given rect in WidgetRect with 1x1 widgets. This is useful to equalize cases. + */ + private FavoriteItemsTransaction fillWithWidgets(WidgetRect widgetRect, + FavoriteItemsTransaction transaction, int screenId) { + int initX = widgetRect.getCellX(); + int initY = widgetRect.getCellY(); + for (int x = initX; x < initX + widgetRect.getSpanX(); x++) { + for (int y = initY; y < initY + widgetRect.getSpanY(); y++) { + try { + // this widgets are filling, we don't care if we can't place them + transaction.addItem(createWidgetInCell( + new WidgetRect(CellType.IGNORE, + new Rect(x, y, x, y)), screenId)); + } catch (Exception e) { + Log.d(TAG, "Unable to place filling widget at " + x + "," + y); + } + } + } + return transaction; + } + + private AppInfo getApp() { + return new AppInfo(mAppComponentName, "test icon", mMyUser, + AppInfo.makeLaunchIntent(mAppComponentName)); + } + + /** + * Helper to set the app to use for the test workspace, + * using activity-alias from AndroidManifest-common. + * @param testAppName the android:name field of the test app activity-alias to use + */ + public void setTestAppActivityAlias(String testAppName) { + this.mAppComponentName = new ComponentName( + getInstrumentation().getContext().getPackageName(), + TEST_ACTIVITY_PACKAGE_PREFIX + testAppName + ); + } + + private void addCorrespondingWidgetRect(WidgetRect widgetRect, + FavoriteItemsTransaction transaction, int screenId) { + if (widgetRect.mType == 'x') { + fillWithWidgets(widgetRect, transaction, screenId); + } else { + transaction.addItem(createWidgetInCell(widgetRect, screenId)); + } + } + + /** + * Builds the given board into the transaction + */ + public FavoriteItemsTransaction buildFromBoard(CellLayoutBoard board, + FavoriteItemsTransaction transaction, final int screenId) { + board.getWidgets().forEach( + (widgetRect) -> addCorrespondingWidgetRect(widgetRect, transaction, screenId)); + board.getIcons().forEach((iconPoint) -> + transaction.addItem(() -> createIconInCell(iconPoint, screenId)) + ); + board.getFolders().forEach((folderPoint) -> + transaction.addItem(() -> createFolderInCell(folderPoint, screenId)) + ); + return transaction; + } + + /** + * Fills the hotseat row with apps instead of suggestions, for this to work the workspace should + * be clean otherwise this doesn't overrides the existing icons. + */ + public FavoriteItemsTransaction fillHotseatIcons(FavoriteItemsTransaction transaction) { + IntStream.range(0, InvariantDeviceProfile.INSTANCE.get(mContext).numDatabaseHotseatIcons) + .forEach(i -> transaction.addItem(() -> getHotseatValues(i))); + return transaction; + } + + private Supplier createWidgetInCell( + WidgetRect widgetRect, int screenId) { + // Create the widget lazily since the appWidgetId can get lost during setup + return () -> { + LauncherAppWidgetProviderInfo info = findWidgetProvider(false); + LauncherAppWidgetInfo item = createWidgetInfo(info, getApplicationContext(), true); + item.cellX = widgetRect.getCellX(); + item.cellY = widgetRect.getCellY(); + item.spanX = widgetRect.getSpanX(); + item.spanY = widgetRect.getSpanY(); + item.screenId = screenId; + return item; + }; + } + + public FolderInfo createFolderInCell(FolderPoint folderPoint, int screenId) { + FolderInfo folderInfo = new FolderInfo(); + folderInfo.screenId = screenId; + folderInfo.container = LauncherSettings.Favorites.CONTAINER_DESKTOP; + folderInfo.cellX = folderPoint.coord.x; + folderInfo.cellY = folderPoint.coord.y; + folderInfo.minSpanY = folderInfo.minSpanX = folderInfo.spanX = folderInfo.spanY = 1; + folderInfo.setOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION, true, null); + + for (int i = 0; i < folderPoint.getNumberIconsInside(); i++) { + folderInfo.add(getDefaultWorkspaceItem(screenId), false); + } + + return folderInfo; + } + + private WorkspaceItemInfo getDefaultWorkspaceItem(int screenId) { + WorkspaceItemInfo item = new WorkspaceItemInfo(getApp()); + item.screenId = screenId; + item.minSpanY = item.minSpanX = item.spanX = item.spanY = 1; + item.container = LauncherSettings.Favorites.CONTAINER_DESKTOP; + return item; + } + + private ItemInfo createIconInCell(IconPoint iconPoint, int screenId) { + WorkspaceItemInfo item = new WorkspaceItemInfo(getApp()); + item.screenId = screenId; + item.cellX = iconPoint.getCoord().x; + item.cellY = iconPoint.getCoord().y; + item.minSpanY = item.minSpanX = item.spanX = item.spanY = 1; + item.container = LauncherSettings.Favorites.CONTAINER_DESKTOP; + return item; + } + + private ItemInfo getHotseatValues(int x) { + WorkspaceItemInfo item = new WorkspaceItemInfo(getApp()); + item.cellX = x; + item.cellY = 0; + item.minSpanY = item.minSpanX = item.spanX = item.spanY = 1; + item.rank = x; + item.screenId = x; + item.container = LauncherSettings.Favorites.CONTAINER_HOTSEAT; + return item; + } +} diff --git a/tests/multivalentTests/src/com/android/launcher3/celllayout/board/WidgetRect.java b/tests/multivalentTests/src/com/android/launcher3/celllayout/board/WidgetRect.java new file mode 100644 index 0000000000..c90ce8504f --- /dev/null +++ b/tests/multivalentTests/src/com/android/launcher3/celllayout/board/WidgetRect.java @@ -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.launcher3.celllayout.board; + +import android.graphics.Rect; + +public class WidgetRect { + public char mType; + public Rect mBounds; + + public WidgetRect(char type, Rect bounds) { + this.mType = type; + this.mBounds = bounds; + } + + public int getSpanX() { + return mBounds.right - mBounds.left + 1; + } + + public int getSpanY() { + return mBounds.top - mBounds.bottom + 1; + } + + public int getCellX() { + return mBounds.left; + } + + public int getCellY() { + return mBounds.bottom; + } + + boolean shouldIgnore() { + return this.mType == CellType.IGNORE; + } + + boolean contains(int x, int y) { + return mBounds.contains(x, y); + } + + @Override + public String toString() { + return "WidgetRect type = " + mType + " x = " + getCellX() + " | y " + getCellY() + + " xs = " + getSpanX() + " ys = " + getSpanY(); + } +} diff --git a/tests/multivalentTests/src/com/android/launcher3/icons/IconCacheTest.java b/tests/multivalentTests/src/com/android/launcher3/icons/IconCacheTest.java index 0aaf4d7c40..495d583b09 100644 --- a/tests/multivalentTests/src/com/android/launcher3/icons/IconCacheTest.java +++ b/tests/multivalentTests/src/com/android/launcher3/icons/IconCacheTest.java @@ -15,95 +15,55 @@ */ package com.android.launcher3.icons; -import static android.os.Process.myUserHandle; - import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; import static com.android.launcher3.icons.IconCache.EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE; -import static com.android.launcher3.icons.IconCacheUpdateHandlerTestKt.waitForUpdateHandlerToFinish; -import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG; -import static com.android.launcher3.model.data.AppInfo.makeLaunchIntent; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; -import static com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY; -import static com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY2; -import static com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE; -import static com.android.launcher3.util.TestUtil.runOnExecutorSync; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.spy; import android.content.ComponentName; import android.content.Context; import android.content.ContextWrapper; import android.content.Intent; -import android.content.pm.LauncherActivityInfo; -import android.content.pm.LauncherApps; import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutInfo.Builder; -import android.graphics.Bitmap; -import android.graphics.Bitmap.Config; -import android.graphics.drawable.Icon; import android.os.PersistableBundle; -import android.os.UserHandle; import android.text.TextUtils; import androidx.annotation.Nullable; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import com.android.launcher3.LauncherAppState; -import com.android.launcher3.icons.cache.CachingLogic; -import com.android.launcher3.icons.cache.IconCacheUpdateHandler; -import com.android.launcher3.icons.cache.LauncherActivityCachingLogic; +import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.model.data.ItemInfoWithIcon; import com.android.launcher3.model.data.PackageItemInfo; -import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.settings.SettingsActivity; -import com.android.launcher3.shortcuts.ShortcutKey; -import com.android.launcher3.util.ApplicationInfoWrapper; -import com.android.launcher3.util.ComponentKey; -import com.android.launcher3.util.PackageUserKey; -import com.android.launcher3.util.SandboxApplication; -import com.google.common.truth.Truth; - -import org.junit.After; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; - @SmallTest @RunWith(AndroidJUnit4.class) public class IconCacheTest { - @Rule public SandboxApplication mContext = new SandboxApplication(); - + private Context mContext; private IconCache mIconCache; private ComponentName mMyComponent; @Before public void setup() { + mContext = getInstrumentation().getTargetContext(); mMyComponent = new ComponentName(mContext, SettingsActivity.class); // In memory icon cache - mIconCache = LauncherAppState.getInstance(mContext).getIconCache(); - } - - @After - public void tearDown() { - mIconCache.close(); + mIconCache = new IconCache(mContext, + InvariantDeviceProfile.INSTANCE.get(mContext), null, + new LauncherIconProvider(mContext)); } @Test @@ -152,162 +112,6 @@ public class IconCacheTest { assertEquals(((PackageItemInfo) item).packageName, otherPackage); } - @Test - public void launcherActivityInfo_cached_in_memory() { - ComponentName cn = new ComponentName(TEST_PACKAGE, TEST_ACTIVITY); - UserHandle user = myUserHandle(); - ComponentKey cacheKey = new ComponentKey(cn, user); - - LauncherActivityInfo lai = mContext.getSystemService(LauncherApps.class) - .resolveActivity(makeLaunchIntent(cn), user); - assertNotNull(lai); - - WorkspaceItemInfo info = new WorkspaceItemInfo(); - info.intent = makeLaunchIntent(cn); - runOnExecutorSync(MODEL_EXECUTOR, - () -> mIconCache.getTitleAndIcon(info, lai, DEFAULT_LOOKUP_FLAG)); - assertNotNull(info.bitmap); - assertFalse(info.bitmap.isLowRes()); - - // Verify that icon is in memory cache - runOnExecutorSync(MODEL_EXECUTOR, - () -> assertNotNull(mIconCache.getInMemoryEntryLocked(cacheKey))); - - // Schedule async update and wait for it to complete - Set updates = - executeIconUpdate(lai, LauncherActivityCachingLogic.INSTANCE); - - // Verify that the icon was not updated and is still in memory cache - Truth.assertThat(updates).isEmpty(); - runOnExecutorSync(MODEL_EXECUTOR, - () -> assertNotNull(mIconCache.getInMemoryEntryLocked(cacheKey))); - } - - @Test - public void shortcutInfo_not_cached_in_memory() { - CacheableShortcutInfo si = mockShortcutInfo(0); - ShortcutKey cacheKey = ShortcutKey.fromInfo(si.getShortcutInfo()); - - WorkspaceItemInfo info = new WorkspaceItemInfo(); - runOnExecutorSync(MODEL_EXECUTOR, () -> mIconCache.getShortcutIcon(info, si)); - assertNotNull(info.bitmap); - assertFalse(info.bitmap.isLowRes()); - - // Verify that icon is in memory cache - runOnExecutorSync(MODEL_EXECUTOR, - () -> assertNull(mIconCache.getInMemoryEntryLocked(cacheKey))); - - Set updates = - executeIconUpdate(si, CacheableShortcutCachingLogic.INSTANCE); - // Verify that the icon was not updated and is still in memory cache - Truth.assertThat(updates).isEmpty(); - runOnExecutorSync(MODEL_EXECUTOR, - () -> assertNull(mIconCache.getInMemoryEntryLocked(cacheKey))); - - // Now update the shortcut with a newer version - updates = executeIconUpdate( - mockShortcutInfo(System.currentTimeMillis() + 2000), - CacheableShortcutCachingLogic.INSTANCE); - - // Verify that icon was updated but it is still not in mem-cache - Truth.assertThat(updates).containsExactly( - new PackageUserKey(cacheKey.getPackageName(), cacheKey.user)); - runOnExecutorSync(MODEL_EXECUTOR, - () -> assertNull(mIconCache.getInMemoryEntryLocked(cacheKey))); - } - - @Test - public void item_kept_in_db_if_nothing_changes() { - ComponentName cn = new ComponentName(TEST_PACKAGE, TEST_ACTIVITY); - UserHandle user = myUserHandle(); - - LauncherActivityInfo lai = mContext.getSystemService(LauncherApps.class) - .resolveActivity(makeLaunchIntent(cn), user); - assertNotNull(lai); - - // Since this is a new update, there should not be any update - Truth.assertThat(executeIconUpdate(lai, LauncherActivityCachingLogic.INSTANCE)).isEmpty(); - assertTrue(mIconCache.isItemInDb(new ComponentKey(cn, user))); - - // Another update should not cause any changes - Truth.assertThat(executeIconUpdate(lai, LauncherActivityCachingLogic.INSTANCE)).isEmpty(); - assertTrue(mIconCache.isItemInDb(new ComponentKey(cn, user))); - } - - @Test - public void item_updated_in_db_if_appInfo_changes() { - ComponentName cn = new ComponentName(TEST_PACKAGE, TEST_ACTIVITY); - UserHandle user = myUserHandle(); - - LauncherActivityInfo lai = mContext.getSystemService(LauncherApps.class) - .resolveActivity(makeLaunchIntent(cn), user); - assertNotNull(lai); - - // Since this is a new update, there should not be any update - Truth.assertThat(executeIconUpdate(lai, LauncherActivityCachingLogic.INSTANCE)).isEmpty(); - assertTrue(mIconCache.isItemInDb(new ComponentKey(cn, user))); - - // Another update should trigger an update - lai.getApplicationInfo().sourceDir = "some-random-source-dir"; - Truth.assertThat(executeIconUpdate(lai, LauncherActivityCachingLogic.INSTANCE)) - .containsExactly(new PackageUserKey(TEST_PACKAGE, user)); - assertTrue(mIconCache.isItemInDb(new ComponentKey(cn, user))); - } - - @Test - public void item_removed_in_db_if_item_removed() { - ComponentName cn = new ComponentName(TEST_PACKAGE, TEST_ACTIVITY); - UserHandle user = myUserHandle(); - - LauncherActivityInfo lai = mContext.getSystemService(LauncherApps.class) - .resolveActivity(makeLaunchIntent(cn), user); - assertNotNull(lai); - - // Since this is a new update, there should not be any update - Truth.assertThat(executeIconUpdate(lai, LauncherActivityCachingLogic.INSTANCE)).isEmpty(); - assertTrue(mIconCache.isItemInDb(new ComponentKey(cn, user))); - - // Another update should trigger an update - ComponentName cn2 = new ComponentName(TEST_PACKAGE, TEST_ACTIVITY2); - LauncherActivityInfo lai2 = mContext.getSystemService(LauncherApps.class) - .resolveActivity(makeLaunchIntent(cn2), user); - - Truth.assertThat(executeIconUpdate(lai2, LauncherActivityCachingLogic.INSTANCE)).isEmpty(); - assertFalse(mIconCache.isItemInDb(new ComponentKey(cn, user))); - assertTrue(mIconCache.isItemInDb(new ComponentKey(cn2, user))); - } - - /** - * Executes the icon update for the provided entry and returns the updated packages - */ - private Set executeIconUpdate(T object, CachingLogic cachingLogic) { - HashSet updates = new HashSet<>(); - - runOnExecutorSync(MODEL_EXECUTOR, () -> { - IconCacheUpdateHandler updateHandler = mIconCache.getUpdateHandler(); - updateHandler.updateIcons( - Collections.singletonList(object), - cachingLogic, - (a, b) -> a.forEach(p -> updates.add(new PackageUserKey(p, b)))); - updateHandler.finish(); - }); - waitForUpdateHandlerToFinish(mIconCache); - return updates; - } - - private CacheableShortcutInfo mockShortcutInfo(long updateTime) { - ShortcutInfo info = new ShortcutInfo.Builder( - getInstrumentation().getContext(), "test-shortcut") - .setIntent(new Intent(Intent.ACTION_VIEW)) - .setShortLabel("Test") - .setIcon(Icon.createWithBitmap(Bitmap.createBitmap(200, 200, Config.ARGB_8888))) - .build(); - ShortcutInfo spied = spy(info); - doReturn(updateTime).when(spied).getLastChangedTimestamp(); - return new CacheableShortcutInfo(spied, - new ApplicationInfoWrapper(getInstrumentation().getContext().getApplicationInfo())); - } - private ItemInfoWithIcon getBadgingInfo(Context context, @Nullable ComponentName cn, @Nullable String badgeOverride) throws Exception { Builder builder = new Builder(context, "test-shortcut") diff --git a/tests/multivalentTests/src/com/android/launcher3/icons/UserBadgeDrawableTest.kt b/tests/multivalentTests/src/com/android/launcher3/icons/UserBadgeDrawableTest.kt index 91ba6289ba..d611ae8dd6 100644 --- a/tests/multivalentTests/src/com/android/launcher3/icons/UserBadgeDrawableTest.kt +++ b/tests/multivalentTests/src/com/android/launcher3/icons/UserBadgeDrawableTest.kt @@ -34,20 +34,19 @@ class UserBadgeDrawableTest { private val context = InstrumentationRegistry.getInstrumentation().targetContext private val canvas = mock() private val systemUnderTest = - UserBadgeDrawable( - context, - R.drawable.ic_work_app_badge, - R.color.badge_tint_work, - false /* isThemed */, - null, /* shape */ - ) + UserBadgeDrawable(context, R.drawable.ic_work_app_badge, R.color.badge_tint_work, false) @Test fun draw_opaque() { val colorList = mutableListOf() - whenever(canvas.drawCircle(any(), any(), any(), any())).then { - colorList.add(it.getArgument(3).color) - } + whenever( + canvas.drawCircle( + any(), + any(), + any(), + any() + ) + ).then { colorList.add(it.getArgument(3).color) } systemUnderTest.alpha = 255 systemUnderTest.draw(canvas) @@ -58,9 +57,14 @@ class UserBadgeDrawableTest { @Test fun draw_transparent() { val colorList = mutableListOf() - whenever(canvas.drawCircle(any(), any(), any(), any())).then { - colorList.add(it.getArgument(3).color) - } + whenever( + canvas.drawCircle( + any(), + any(), + any(), + any() + ) + ).then { colorList.add(it.getArgument(3).color) } systemUnderTest.alpha = 0 systemUnderTest.draw(canvas) diff --git a/tests/multivalentTests/src/com/android/launcher3/model/DatabaseHelperTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/DatabaseHelperTest.kt index 89e676f52f..c9ea421039 100644 --- a/tests/multivalentTests/src/com/android/launcher3/model/DatabaseHelperTest.kt +++ b/tests/multivalentTests/src/com/android/launcher3/model/DatabaseHelperTest.kt @@ -1,6 +1,5 @@ package com.android.launcher3.model -import android.content.Context import android.database.sqlite.SQLiteDatabase import android.os.UserHandle import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -11,12 +10,9 @@ import com.android.launcher3.LauncherSettings.Favorites.TMP_TABLE import com.android.launcher3.LauncherSettings.Favorites.addTableToDb import com.android.launcher3.pm.UserCache import com.android.launcher3.provider.LauncherDbUtils -import com.android.launcher3.util.ModelTestExtensions import java.util.function.ToLongFunction -import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse -import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -28,19 +24,6 @@ private const val ICON_RESOURCE = "iconResource" @SmallTest @RunWith(AndroidJUnit4::class) class DatabaseHelperTest { - val context: Context = InstrumentationRegistry.getInstrumentation().targetContext - // v30 - 21 columns - lateinit var db: SQLiteDatabase - - @Before - fun setUp() { - db = ModelTestExtensions.createInMemoryDb(INSERTION_SQL) - } - - @After - fun tearDown() { - db.close() - } /** * b/304687723 occurred when a return was accidentally added to a case statement in @@ -50,11 +33,13 @@ class DatabaseHelperTest { */ @Test fun onUpgrade_to_version_32_from_30() { + val context = InstrumentationRegistry.getInstrumentation().targetContext val userSerialProvider = ToLongFunction { UserCache.INSTANCE.get(context).getSerialNumberForUser(it) } val dbHelper = DatabaseHelper(context, null, userSerialProvider) {} + val db = FactitiousDbController(context, INSERTION_SQL).inMemoryDb dbHelper.onUpgrade(db, 30, 32) @@ -69,6 +54,9 @@ class DatabaseHelperTest { */ @Test fun after_migrating_from_db_v30_to_v32_copy_table() { + val context = InstrumentationRegistry.getInstrumentation().targetContext + val db = FactitiousDbController(context, INSERTION_SQL).inMemoryDb // v30 - 21 columns + addTableToDb(db, 1, true, TMP_TABLE) LauncherDbUtils.copyTable(db, TABLE_NAME, db, TMP_TABLE, context) diff --git a/tests/multivalentTests/src/com/android/launcher3/model/FactitiousDbController.kt b/tests/multivalentTests/src/com/android/launcher3/model/FactitiousDbController.kt new file mode 100644 index 0000000000..711e1d2d65 --- /dev/null +++ b/tests/multivalentTests/src/com/android/launcher3/model/FactitiousDbController.kt @@ -0,0 +1,60 @@ +package com.android.launcher3.model + +import android.content.Context +import android.database.Cursor +import android.database.sqlite.SQLiteDatabase +import androidx.test.platform.app.InstrumentationRegistry +import java.io.BufferedReader +import java.io.InputStreamReader + +private val All_COLUMNS = + arrayOf( + "_id", + "title", + "intent", + "container", + "screen", + "cellX", + "cellY", + "spanX", + "spanY", + "itemType", + "appWidgetId", + "icon", + "appWidgetProvider", + "modified", + "restored", + "profileId", + "rank", + "options", + "appWidgetSource" + ) + +class FactitiousDbController(context: Context, insertFile: String) : ModelDbController(context) { + + val inMemoryDb: SQLiteDatabase by lazy { + SQLiteDatabase.createInMemory(SQLiteDatabase.OpenParams.Builder().build()).also { db -> + BufferedReader( + InputStreamReader( + InstrumentationRegistry.getInstrumentation().context.assets.open(insertFile) + ) + ) + .lines() + .forEach { sqlStatement -> db.execSQL(sqlStatement) } + } + } + + override fun query( + table: String, + projection: Array?, + selection: String?, + selectionArgs: Array?, + sortOrder: String? + ): Cursor { + return inMemoryDb.query(table, All_COLUMNS, selection, selectionArgs, null, null, sortOrder) + } + + override fun loadDefaultFavoritesIfNecessary() { + // No-Op + } +} diff --git a/tests/multivalentTests/src/com/android/launcher3/model/GridSizeMigrationUtilTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/GridSizeMigrationUtilTest.kt new file mode 100644 index 0000000000..761f06d514 --- /dev/null +++ b/tests/multivalentTests/src/com/android/launcher3/model/GridSizeMigrationUtilTest.kt @@ -0,0 +1,772 @@ +/* + * 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. + */ +package com.android.launcher3.model + +import android.content.ContentValues +import android.content.Context +import android.content.Intent +import android.database.Cursor +import android.database.sqlite.SQLiteDatabase +import android.graphics.Point +import android.os.Process +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.launcher3.InvariantDeviceProfile +import com.android.launcher3.LauncherPrefs +import com.android.launcher3.LauncherPrefs.Companion.WORKSPACE_SIZE +import com.android.launcher3.LauncherSettings.Favorites.* +import com.android.launcher3.model.GridSizeMigrationUtil.DbReader +import com.android.launcher3.pm.UserCache +import com.android.launcher3.provider.LauncherDbUtils +import com.android.launcher3.util.LauncherModelHelper +import com.google.common.truth.Truth.assertThat +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +/** Unit tests for [GridSizeMigrationUtil] */ +@SmallTest +@RunWith(AndroidJUnit4::class) +class GridSizeMigrationUtilTest { + + private lateinit var modelHelper: LauncherModelHelper + private lateinit var context: Context + private lateinit var validPackages: Set + private lateinit var idp: InvariantDeviceProfile + private lateinit var dbHelper: DatabaseHelper + private lateinit var db: SQLiteDatabase + private val testPackage1 = "com.android.launcher3.validpackage1" + private val testPackage2 = "com.android.launcher3.validpackage2" + private val testPackage3 = "com.android.launcher3.validpackage3" + private val testPackage4 = "com.android.launcher3.validpackage4" + private val testPackage5 = "com.android.launcher3.validpackage5" + private val testPackage6 = "com.android.launcher3.validpackage6" + private val testPackage7 = "com.android.launcher3.validpackage7" + private val testPackage8 = "com.android.launcher3.validpackage8" + private val testPackage9 = "com.android.launcher3.validpackage9" + private val testPackage10 = "com.android.launcher3.validpackage10" + + @Before + fun setUp() { + modelHelper = LauncherModelHelper() + context = modelHelper.sandboxContext + dbHelper = + DatabaseHelper( + context, + null, + UserCache.INSTANCE.get(context)::getSerialNumberForUser + ) {} + db = dbHelper.writableDatabase + + validPackages = + setOf( + testPackage1, + testPackage2, + testPackage3, + testPackage4, + testPackage5, + testPackage6, + testPackage7, + testPackage8, + testPackage9, + testPackage10 + ) + + idp = InvariantDeviceProfile.INSTANCE[context] + val userSerial = UserCache.INSTANCE[context].getSerialNumberForUser(Process.myUserHandle()) + LauncherDbUtils.dropTable(db, TMP_TABLE) + addTableToDb(db, userSerial, false, TMP_TABLE) + } + + @After + fun tearDown() { + modelHelper.destroy() + } + + /** Old migration logic, should be modified once is not needed anymore */ + @Test + @Throws(Exception::class) + fun testMigration() { + // Src Hotseat icons + addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_HOTSEAT, 0, 0, testPackage1, 1, TMP_TABLE) + addItem(ITEM_TYPE_DEEP_SHORTCUT, 1, CONTAINER_HOTSEAT, 0, 0, testPackage2, 2, TMP_TABLE) + addItem(ITEM_TYPE_DEEP_SHORTCUT, 3, CONTAINER_HOTSEAT, 0, 0, testPackage3, 3, TMP_TABLE) + addItem(ITEM_TYPE_APPLICATION, 4, CONTAINER_HOTSEAT, 0, 0, testPackage4, 4, TMP_TABLE) + // Src grid icons + // _ _ _ _ _ + // _ _ _ _ 5 + // _ _ 6 _ 7 + // _ _ 8 _ 9 + // _ _ _ _ _ + addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 4, 1, testPackage5, 5, TMP_TABLE) + addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 2, 2, testPackage6, 6, TMP_TABLE) + addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 4, 2, testPackage7, 7, TMP_TABLE) + addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 2, 3, testPackage8, 8, TMP_TABLE) + addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 4, 3, testPackage9, 9, TMP_TABLE) + + // Dest hotseat icons + addItem(ITEM_TYPE_DEEP_SHORTCUT, 1, CONTAINER_HOTSEAT, 0, 0, testPackage2) + // Dest grid icons + addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 2, 2, testPackage10) + + idp.numDatabaseHotseatIcons = 4 + idp.numColumns = 4 + idp.numRows = 4 + val srcReader = DbReader(db, TMP_TABLE, context, validPackages) + val destReader = DbReader(db, TABLE_NAME, context, validPackages) + GridSizeMigrationUtil.migrate( + dbHelper, + srcReader, + destReader, + idp.numDatabaseHotseatIcons, + Point(idp.numColumns, idp.numRows), + DeviceGridState(context), + DeviceGridState(idp) + ) + + // Check hotseat items + var c = + db.query( + TABLE_NAME, + arrayOf(SCREEN, INTENT), + "container=$CONTAINER_HOTSEAT", + null, + SCREEN, + null, + null + ) + ?: throw IllegalStateException() + + assertThat(c.count).isEqualTo(idp.numDatabaseHotseatIcons) + + val screenIndex = c.getColumnIndex(SCREEN) + var intentIndex = c.getColumnIndex(INTENT) + c.moveToNext() + assertThat(c.getInt(screenIndex).toLong()).isEqualTo(0) + assertThat(c.getString(intentIndex)).contains(testPackage1) + c.moveToNext() + assertThat(c.getInt(screenIndex).toLong()).isEqualTo(1) + assertThat(c.getString(intentIndex)).contains(testPackage2) + c.moveToNext() + assertThat(c.getInt(screenIndex).toLong()).isEqualTo(2) + assertThat(c.getString(intentIndex)).contains(testPackage3) + c.moveToNext() + assertThat(c.getInt(screenIndex).toLong()).isEqualTo(3) + assertThat(c.getString(intentIndex)).contains(testPackage4) + c.close() + + // Check workspace items + c = + db.query( + TABLE_NAME, + arrayOf(CELLX, CELLY, INTENT), + "container=$CONTAINER_DESKTOP", + null, + null, + null, + null + ) + ?: throw IllegalStateException() + + intentIndex = c.getColumnIndex(INTENT) + val cellXIndex = c.getColumnIndex(CELLX) + val cellYIndex = c.getColumnIndex(CELLY) + val locMap = HashMap() + while (c.moveToNext()) { + locMap[Intent.parseUri(c.getString(intentIndex), 0).getPackage()] = + Point(c.getInt(cellXIndex), c.getInt(cellYIndex)) + } + c.close() + // Expected dest grid icons + // _ _ _ _ + // 5 6 7 8 + // 9 _ _ _ + // _ _ _ _ + assertThat(locMap.size.toLong()).isEqualTo(5) + assertThat(locMap[testPackage5]).isEqualTo(Point(0, 1)) + assertThat(locMap[testPackage6]).isEqualTo(Point(1, 1)) + assertThat(locMap[testPackage7]).isEqualTo(Point(2, 1)) + assertThat(locMap[testPackage8]).isEqualTo(Point(3, 1)) + assertThat(locMap[testPackage9]).isEqualTo(Point(0, 2)) + } + + /** Old migration logic, should be modified once is not needed anymore */ + @Test + @Throws(Exception::class) + fun testMigrationBackAndForth() { + // Hotseat items in grid A + // 1 2 _ 3 4 + addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_HOTSEAT, 0, 0, testPackage1, 1, TMP_TABLE) + addItem(ITEM_TYPE_DEEP_SHORTCUT, 1, CONTAINER_HOTSEAT, 0, 0, testPackage2, 2, TMP_TABLE) + addItem(ITEM_TYPE_DEEP_SHORTCUT, 3, CONTAINER_HOTSEAT, 0, 0, testPackage3, 3, TMP_TABLE) + addItem(ITEM_TYPE_APPLICATION, 4, CONTAINER_HOTSEAT, 0, 0, testPackage4, 4, TMP_TABLE) + // Workspace items in grid A + // _ _ _ _ _ + // _ _ _ _ 5 + // _ _ 6 _ 7 + // _ _ 8 _ _ + // _ _ _ _ _ + addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 4, 1, testPackage5, 5, TMP_TABLE) + addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 2, 2, testPackage6, 6, TMP_TABLE) + addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 4, 2, testPackage7, 7, TMP_TABLE) + addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 2, 3, testPackage8, 8, TMP_TABLE) + + // Hotseat items in grid B + // 2 _ _ _ + addItem(ITEM_TYPE_DEEP_SHORTCUT, 0, CONTAINER_HOTSEAT, 0, 0, testPackage2) + // Workspace items in grid B + // _ _ _ _ + // _ _ _ 10 + // _ _ _ _ + // _ _ _ _ + addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 1, 3, testPackage10) + + idp.numDatabaseHotseatIcons = 4 + idp.numColumns = 4 + idp.numRows = 4 + val readerGridA = DbReader(db, TMP_TABLE, context, validPackages) + val readerGridB = DbReader(db, TABLE_NAME, context, validPackages) + // migrate from A -> B + GridSizeMigrationUtil.migrate( + dbHelper, + readerGridA, + readerGridB, + idp.numDatabaseHotseatIcons, + Point(idp.numColumns, idp.numRows), + DeviceGridState(context), + DeviceGridState(idp) + ) + + // Check hotseat items in grid B + var c = + db.query( + TABLE_NAME, + arrayOf(SCREEN, INTENT), + "container=$CONTAINER_HOTSEAT", + null, + SCREEN, + null, + null + ) + ?: throw IllegalStateException() + // Expected hotseat items in grid B + // 2 1 3 4 + verifyHotseat( + c, + idp, + mutableListOf(testPackage2, testPackage1, testPackage3, testPackage4).toList() + ) + + // Check workspace items in grid B + c = + db.query( + TABLE_NAME, + arrayOf(SCREEN, CELLX, CELLY, INTENT), + "container=$CONTAINER_DESKTOP", + null, + null, + null, + null + ) + ?: throw IllegalStateException() + var locMap = parseLocMap(c) + // Expected items in grid B + // _ _ _ _ + // 5 6 7 8 + // _ _ _ _ + // _ _ _ _ + assertThat(locMap.size.toLong()).isEqualTo(4) + assertThat(locMap[testPackage5]).isEqualTo(Triple(0, 0, 1)) + assertThat(locMap[testPackage6]).isEqualTo(Triple(0, 1, 1)) + assertThat(locMap[testPackage7]).isEqualTo(Triple(0, 2, 1)) + assertThat(locMap[testPackage8]).isEqualTo(Triple(0, 3, 1)) + + // add item in B + addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 0, 2, testPackage9) + + // migrate from B -> A + GridSizeMigrationUtil.migrate( + dbHelper, + readerGridB, + readerGridA, + 5, + Point(5, 5), + DeviceGridState(idp), + DeviceGridState(context) + ) + // Check hotseat items in grid A + c = + db.query( + TMP_TABLE, + arrayOf(SCREEN, INTENT), + "container=$CONTAINER_HOTSEAT", + null, + SCREEN, + null, + null + ) + ?: throw IllegalStateException() + // Expected hotseat items in grid A + // 1 2 _ 3 4 + verifyHotseat( + c, + idp, + mutableListOf(testPackage1, testPackage2, null, testPackage3, testPackage4).toList() + ) + + // Check workspace items in grid A + c = + db.query( + TMP_TABLE, + arrayOf(SCREEN, CELLX, CELLY, INTENT), + "container=$CONTAINER_DESKTOP", + null, + null, + null, + null + ) + ?: throw IllegalStateException() + locMap = parseLocMap(c) + // Expected workspace items in grid A + // _ _ _ _ _ + // _ _ _ _ 5 + // 9 _ 6 _ 7 + // _ _ 8 _ _ + // _ _ _ _ _ + assertThat(locMap.size.toLong()).isEqualTo(5) + // Verify items that existed in grid A remains in same position + assertThat(locMap[testPackage5]).isEqualTo(Triple(0, 4, 1)) + assertThat(locMap[testPackage6]).isEqualTo(Triple(0, 2, 2)) + assertThat(locMap[testPackage7]).isEqualTo(Triple(0, 4, 2)) + assertThat(locMap[testPackage8]).isEqualTo(Triple(0, 2, 3)) + // Verify items that didn't exist in grid A are added in new screen + assertThat(locMap[testPackage9]).isEqualTo(Triple(0, 0, 2)) + + // remove item from B + db.delete(TMP_TABLE, "$_ID=7", null) + + // migrate from A -> B + GridSizeMigrationUtil.migrate( + dbHelper, + readerGridA, + readerGridB, + idp.numDatabaseHotseatIcons, + Point(idp.numColumns, idp.numRows), + DeviceGridState(context), + DeviceGridState(idp) + ) + + // Check hotseat items in grid B + c = + db.query( + TABLE_NAME, + arrayOf(SCREEN, INTENT), + "container=$CONTAINER_HOTSEAT", + null, + SCREEN, + null, + null + ) + ?: throw IllegalStateException() + // Expected hotseat items in grid B + // 2 1 3 4 + verifyHotseat( + c, + idp, + mutableListOf(testPackage2, testPackage1, testPackage3, testPackage4).toList() + ) + + // Check workspace items in grid B + c = + db.query( + TABLE_NAME, + arrayOf(SCREEN, CELLX, CELLY, INTENT), + "container=$CONTAINER_DESKTOP", + null, + null, + null, + null + ) + ?: throw IllegalStateException() + locMap = parseLocMap(c) + // Expected workspace items in grid B + // _ _ _ _ + // 5 6 _ 8 + // 9 _ _ _ + // _ _ _ _ + assertThat(locMap.size.toLong()).isEqualTo(4) + assertThat(locMap[testPackage5]).isEqualTo(Triple(0, 0, 1)) + assertThat(locMap[testPackage6]).isEqualTo(Triple(0, 1, 1)) + assertThat(locMap[testPackage8]).isEqualTo(Triple(0, 3, 1)) + assertThat(locMap[testPackage9]).isEqualTo(Triple(0, 0, 2)) + } + + private fun verifyHotseat(c: Cursor, idp: InvariantDeviceProfile, expected: List) { + assertThat(c.count).isEqualTo(idp.numDatabaseHotseatIcons) + val screenIndex = c.getColumnIndex(SCREEN) + val intentIndex = c.getColumnIndex(INTENT) + expected.forEachIndexed { idx, pkg -> + if (pkg == null) return@forEachIndexed + c.moveToNext() + assertThat(c.getInt(screenIndex).toLong()).isEqualTo(idx) + assertThat(c.getString(intentIndex)).contains(pkg) + } + c.close() + } + + private fun parseLocMap(c: Cursor): Map> { + // Check workspace items + val intentIndex = c.getColumnIndex(INTENT) + val screenIndex = c.getColumnIndex(SCREEN) + val cellXIndex = c.getColumnIndex(CELLX) + val cellYIndex = c.getColumnIndex(CELLY) + val locMap = mutableMapOf>() + while (c.moveToNext()) { + locMap[Intent.parseUri(c.getString(intentIndex), 0).getPackage()] = + Triple(c.getInt(screenIndex), c.getInt(cellXIndex), c.getInt(cellYIndex)) + } + c.close() + return locMap.toMap() + } + + @Test + fun migrateToLargerHotseat() { + val srcHotseatItems = + intArrayOf( + addItem( + ITEM_TYPE_APPLICATION, + 0, + CONTAINER_HOTSEAT, + 0, + 0, + testPackage1, + 1, + TMP_TABLE + ), + addItem( + ITEM_TYPE_DEEP_SHORTCUT, + 1, + CONTAINER_HOTSEAT, + 0, + 0, + testPackage2, + 2, + TMP_TABLE + ), + addItem( + ITEM_TYPE_APPLICATION, + 2, + CONTAINER_HOTSEAT, + 0, + 0, + testPackage3, + 3, + TMP_TABLE + ), + addItem( + ITEM_TYPE_DEEP_SHORTCUT, + 3, + CONTAINER_HOTSEAT, + 0, + 0, + testPackage4, + 4, + TMP_TABLE + ) + ) + val numSrcDatabaseHotseatIcons = srcHotseatItems.size + idp.numDatabaseHotseatIcons = 6 + idp.numColumns = 4 + idp.numRows = 4 + val srcReader = DbReader(db, TMP_TABLE, context, validPackages) + val destReader = DbReader(db, TABLE_NAME, context, validPackages) + GridSizeMigrationUtil.migrate( + dbHelper, + srcReader, + destReader, + idp.numDatabaseHotseatIcons, + Point(idp.numColumns, idp.numRows), + DeviceGridState(context), + DeviceGridState(idp) + ) + + // Check hotseat items + val c = + db.query( + TABLE_NAME, + arrayOf(SCREEN, INTENT), + "container=$CONTAINER_HOTSEAT", + null, + SCREEN, + null, + null + ) + ?: throw IllegalStateException() + + assertThat(c.count.toLong()).isEqualTo(numSrcDatabaseHotseatIcons.toLong()) + val screenIndex = c.getColumnIndex(SCREEN) + val intentIndex = c.getColumnIndex(INTENT) + c.moveToNext() + assertThat(c.getInt(screenIndex)).isEqualTo(0) + assertThat(c.getString(intentIndex)).contains(testPackage1) + + c.moveToNext() + assertThat(c.getInt(screenIndex)).isEqualTo(1) + assertThat(c.getString(intentIndex)).contains(testPackage2) + + c.moveToNext() + assertThat(c.getInt(screenIndex)).isEqualTo(2) + assertThat(c.getString(intentIndex)).contains(testPackage3) + + c.moveToNext() + assertThat(c.getInt(screenIndex)).isEqualTo(3) + assertThat(c.getString(intentIndex)).contains(testPackage4) + + c.close() + } + + @Test + fun migrateFromLargerHotseat() { + addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_HOTSEAT, 0, 0, testPackage1, 1, TMP_TABLE) + addItem(ITEM_TYPE_DEEP_SHORTCUT, 2, CONTAINER_HOTSEAT, 0, 0, testPackage2, 2, TMP_TABLE) + addItem(ITEM_TYPE_APPLICATION, 3, CONTAINER_HOTSEAT, 0, 0, testPackage3, 3, TMP_TABLE) + addItem(ITEM_TYPE_DEEP_SHORTCUT, 4, CONTAINER_HOTSEAT, 0, 0, testPackage4, 4, TMP_TABLE) + addItem(ITEM_TYPE_APPLICATION, 5, CONTAINER_HOTSEAT, 0, 0, testPackage5, 5, TMP_TABLE) + + idp.numDatabaseHotseatIcons = 4 + idp.numColumns = 4 + idp.numRows = 4 + val srcReader = DbReader(db, TMP_TABLE, context, validPackages) + val destReader = DbReader(db, TABLE_NAME, context, validPackages) + GridSizeMigrationUtil.migrate( + dbHelper, + srcReader, + destReader, + idp.numDatabaseHotseatIcons, + Point(idp.numColumns, idp.numRows), + DeviceGridState(context), + DeviceGridState(idp) + ) + + // Check hotseat items + val c = + db.query( + TABLE_NAME, + arrayOf(SCREEN, INTENT), + "container=$CONTAINER_HOTSEAT", + null, + SCREEN, + null, + null + ) + ?: throw IllegalStateException() + + assertThat(c.count.toLong()).isEqualTo(idp.numDatabaseHotseatIcons.toLong()) + val screenIndex = c.getColumnIndex(SCREEN) + val intentIndex = c.getColumnIndex(INTENT) + + c.moveToNext() + assertThat(c.getInt(screenIndex)).isEqualTo(0) + assertThat(c.getString(intentIndex)).contains(testPackage1) + + c.moveToNext() + assertThat(c.getInt(screenIndex)).isEqualTo(1) + assertThat(c.getString(intentIndex)).contains(testPackage2) + + c.moveToNext() + assertThat(c.getInt(screenIndex)).isEqualTo(2) + assertThat(c.getString(intentIndex)).contains(testPackage3) + + c.moveToNext() + assertThat(c.getInt(screenIndex)).isEqualTo(3) + assertThat(c.getString(intentIndex)).contains(testPackage4) + + c.close() + } + + /** + * Migrating from a smaller grid to a large one should reflow the pages if the column difference + * is more than 2 + */ + @Test + @Throws(Exception::class) + fun migrateFromSmallerGridBigDifference() { + enableNewMigrationLogic("2,2") + + // Setup src grid + addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 0, 1, testPackage1, 5, TMP_TABLE) + addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 1, 1, testPackage2, 6, TMP_TABLE) + addItem(ITEM_TYPE_APPLICATION, 1, CONTAINER_DESKTOP, 0, 0, testPackage3, 7, TMP_TABLE) + addItem(ITEM_TYPE_APPLICATION, 1, CONTAINER_DESKTOP, 1, 0, testPackage4, 8, TMP_TABLE) + addItem(ITEM_TYPE_APPLICATION, 2, CONTAINER_DESKTOP, 0, 0, testPackage5, 9, TMP_TABLE) + + idp.numDatabaseHotseatIcons = 4 + idp.numColumns = 5 + idp.numRows = 5 + val srcReader = DbReader(db, TMP_TABLE, context, validPackages) + val destReader = DbReader(db, TABLE_NAME, context, validPackages) + GridSizeMigrationUtil.migrate( + dbHelper, + srcReader, + destReader, + idp.numDatabaseHotseatIcons, + Point(idp.numColumns, idp.numRows), + DeviceGridState(context), + DeviceGridState(idp) + ) + + // Get workspace items + val c = + db.query( + TABLE_NAME, + arrayOf(INTENT, SCREEN), + "container=$CONTAINER_DESKTOP", + null, + null, + null, + null + ) + ?: throw IllegalStateException() + + val intentIndex = c.getColumnIndex(INTENT) + val screenIndex = c.getColumnIndex(SCREEN) + + // Get in which screen the icon is + val locMap = HashMap() + while (c.moveToNext()) { + locMap[Intent.parseUri(c.getString(intentIndex), 0).getPackage()] = + c.getInt(screenIndex) + } + c.close() + + // All icons fit the first screen + assertThat(locMap.size).isEqualTo(5) + assertThat(locMap[testPackage1]).isEqualTo(0) + assertThat(locMap[testPackage2]).isEqualTo(0) + assertThat(locMap[testPackage3]).isEqualTo(0) + assertThat(locMap[testPackage4]).isEqualTo(0) + assertThat(locMap[testPackage5]).isEqualTo(0) + } + + /** Migrating from a larger grid to a smaller, we reflow from page 0 */ + @Test + @Throws(Exception::class) + fun migrateFromLargerGrid() { + enableNewMigrationLogic("5,5") + + // Setup src grid + addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 0, 1, testPackage1, 5, TMP_TABLE) + addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 1, 1, testPackage2, 6, TMP_TABLE) + addItem(ITEM_TYPE_APPLICATION, 1, CONTAINER_DESKTOP, 0, 0, testPackage3, 7, TMP_TABLE) + addItem(ITEM_TYPE_APPLICATION, 1, CONTAINER_DESKTOP, 1, 0, testPackage4, 8, TMP_TABLE) + addItem(ITEM_TYPE_APPLICATION, 2, CONTAINER_DESKTOP, 0, 0, testPackage5, 9, TMP_TABLE) + + idp.numDatabaseHotseatIcons = 4 + idp.numColumns = 4 + idp.numRows = 4 + val srcReader = DbReader(db, TMP_TABLE, context, validPackages) + val destReader = DbReader(db, TABLE_NAME, context, validPackages) + GridSizeMigrationUtil.migrate( + dbHelper, + srcReader, + destReader, + idp.numDatabaseHotseatIcons, + Point(idp.numColumns, idp.numRows), + DeviceGridState(context), + DeviceGridState(idp) + ) + + // Get workspace items + val c = + db.query( + TABLE_NAME, + arrayOf(INTENT, SCREEN), + "container=$CONTAINER_DESKTOP", + null, + null, + null, + null + ) + ?: throw IllegalStateException() + val intentIndex = c.getColumnIndex(INTENT) + val screenIndex = c.getColumnIndex(SCREEN) + + // Get in which screen the icon is + val locMap = HashMap() + while (c.moveToNext()) { + locMap[Intent.parseUri(c.getString(intentIndex), 0).getPackage()] = + c.getInt(screenIndex) + } + c.close() + + // All icons fit the first screen + assertThat(locMap.size).isEqualTo(5) + assertThat(locMap[testPackage1]).isEqualTo(0) + assertThat(locMap[testPackage2]).isEqualTo(0) + assertThat(locMap[testPackage3]).isEqualTo(0) + assertThat(locMap[testPackage4]).isEqualTo(0) + assertThat(locMap[testPackage5]).isEqualTo(0) + } + + private fun enableNewMigrationLogic(srcGridSize: String) { + LauncherPrefs.get(context).putSync(WORKSPACE_SIZE.to(srcGridSize)) + } + + private fun addItem( + type: Int, + screen: Int, + container: Int, + x: Int, + y: Int, + packageName: String? + ): Int { + return addItem( + type, + screen, + container, + x, + y, + packageName, + dbHelper.generateNewItemId(), + TABLE_NAME + ) + } + + private fun addItem( + type: Int, + screen: Int, + container: Int, + x: Int, + y: Int, + packageName: String?, + id: Int, + tableName: String + ): Int { + val values = ContentValues() + values.put(_ID, id) + values.put(CONTAINER, container) + values.put(SCREEN, screen) + values.put(CELLX, x) + values.put(CELLY, y) + values.put(SPANX, 1) + values.put(SPANY, 1) + values.put(ITEM_TYPE, type) + values.put(INTENT, Intent(Intent.ACTION_MAIN).setPackage(packageName).toUri(0)) + db.insert(tableName, null, values) + return id + } +} diff --git a/tests/multivalentTests/src/com/android/launcher3/ui/BubbleTextViewTest.java b/tests/multivalentTests/src/com/android/launcher3/ui/BubbleTextViewTest.java index 5c326f98c7..b83349e715 100644 --- a/tests/multivalentTests/src/com/android/launcher3/ui/BubbleTextViewTest.java +++ b/tests/multivalentTests/src/com/android/launcher3/ui/BubbleTextViewTest.java @@ -16,64 +16,42 @@ package com.android.launcher3.ui; -import static android.graphics.fonts.FontStyle.FONT_WEIGHT_BOLD; -import static android.graphics.fonts.FontStyle.FONT_WEIGHT_NORMAL; -import static android.text.style.DynamicDrawableSpan.ALIGN_CENTER; +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; import static com.android.launcher3.BubbleTextView.DISPLAY_ALL_APPS; import static com.android.launcher3.BubbleTextView.DISPLAY_PREDICTION_ROW; import static com.android.launcher3.BubbleTextView.DISPLAY_SEARCH_RESULT; import static com.android.launcher3.BubbleTextView.DISPLAY_SEARCH_RESULT_SMALL; -import static com.android.launcher3.Flags.FLAG_ENABLE_SUPPORT_FOR_ARCHIVING; -import static com.android.launcher3.Flags.FLAG_USE_NEW_ICON_FOR_ARCHIVED_APPS; import static com.android.launcher3.LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE; -import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_ARCHIVED; -import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.graphics.Bitmap; -import android.graphics.Bitmap.Config; import android.graphics.Typeface; -import android.os.Build; import android.os.UserHandle; -import android.platform.test.annotations.DisableFlags; -import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; -import android.text.SpannedString; -import android.text.style.ImageSpan; import android.view.ViewGroup; import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.filters.SdkSuppress; import androidx.test.filters.SmallTest; import com.android.launcher3.BubbleTextView; import com.android.launcher3.Flags; import com.android.launcher3.LauncherPrefs; import com.android.launcher3.Utilities; -import com.android.launcher3.graphics.PreloadIconDrawable; -import com.android.launcher3.icons.BitmapInfo; import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.model.data.ItemInfoWithIcon; -import com.android.launcher3.pm.PackageInstallInfo; import com.android.launcher3.search.StringMatcherUtility; import com.android.launcher3.util.ActivityContextWrapper; import com.android.launcher3.util.FlagOp; import com.android.launcher3.util.IntArray; -import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext; -import com.android.launcher3.util.TestUtil; import com.android.launcher3.views.BaseDragLayer; -import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -119,22 +97,19 @@ public class BubbleTextViewTest { private static final float SPACE_MULTIPLIER = 1; private static final float SPACE_EXTRA = 0; - private SandboxModelContext mModelContext; - private BubbleTextView mBubbleTextView; private ItemInfoWithIcon mItemInfoWithIcon; private Context mContext; private int mLimitedWidth; private AppInfo mGmailAppInfo; + private LauncherPrefs mLauncherPrefs; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); Utilities.enableRunningInTestHarnessForTests(); - mModelContext = new SandboxModelContext(); - LauncherPrefs.get(mModelContext).put(ENABLE_TWOLINE_ALLAPPS_TOGGLE, true); - - mContext = new ActivityContextWrapper(mModelContext); + mContext = new ActivityContextWrapper(getApplicationContext()); + mLauncherPrefs = LauncherPrefs.get(mContext); mBubbleTextView = new BubbleTextView(mContext); mBubbleTextView.reset(); @@ -160,14 +135,10 @@ public class BubbleTextViewTest { mGmailAppInfo = new AppInfo(componentName, "Gmail", WORK_HANDLE, new Intent()); } - @After - public void tearDown() { - mModelContext.onDestroy(); - } - @Test - @EnableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE) public void testEmptyString_flagOn() { + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE); + mLauncherPrefs.put(ENABLE_TWOLINE_ALLAPPS_TOGGLE, true); mItemInfoWithIcon.title = EMPTY_STRING; mBubbleTextView.setDisplay(DISPLAY_ALL_APPS); mBubbleTextView.applyLabel(mItemInfoWithIcon); @@ -180,8 +151,8 @@ public class BubbleTextViewTest { } @Test - @DisableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE) public void testEmptyString_flagOff() { + mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE); mItemInfoWithIcon.title = EMPTY_STRING; mBubbleTextView.setDisplay(DISPLAY_ALL_APPS); mBubbleTextView.applyLabel(mItemInfoWithIcon); @@ -194,8 +165,9 @@ public class BubbleTextViewTest { } @Test - @EnableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE) public void testStringWithSpaceLongerThanCharLimit_flagOn() { + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE); + mLauncherPrefs.put(ENABLE_TWOLINE_ALLAPPS_TOGGLE, true); // test string: "Battery Stats" mItemInfoWithIcon.title = TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT; mBubbleTextView.applyLabel(mItemInfoWithIcon); @@ -209,8 +181,8 @@ public class BubbleTextViewTest { } @Test - @DisableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE) public void testStringWithSpaceLongerThanCharLimit_flagOff() { + mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE); // test string: "Battery Stats" mItemInfoWithIcon.title = TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT; mBubbleTextView.applyLabel(mItemInfoWithIcon); @@ -224,8 +196,9 @@ public class BubbleTextViewTest { } @Test - @EnableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE) public void testLongStringNoSpaceLongerThanCharLimit_flagOn() { + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE); + mLauncherPrefs.put(ENABLE_TWOLINE_ALLAPPS_TOGGLE, true); // test string: "flutterappflorafy" mItemInfoWithIcon.title = TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT; mBubbleTextView.applyLabel(mItemInfoWithIcon); @@ -239,8 +212,8 @@ public class BubbleTextViewTest { } @Test - @DisableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE) public void testLongStringNoSpaceLongerThanCharLimit_flagOff() { + mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE); // test string: "flutterappflorafy" mItemInfoWithIcon.title = TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT; mBubbleTextView.applyLabel(mItemInfoWithIcon); @@ -254,8 +227,9 @@ public class BubbleTextViewTest { } @Test - @EnableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE) public void testLongStringWithSpaceLongerThanCharLimit_flagOn() { + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE); + mLauncherPrefs.put(ENABLE_TWOLINE_ALLAPPS_TOGGLE, true); // test string: "System UWB Field Test" mItemInfoWithIcon.title = TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT; mBubbleTextView.applyLabel(mItemInfoWithIcon); @@ -269,8 +243,8 @@ public class BubbleTextViewTest { } @Test - @DisableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE) public void testLongStringWithSpaceLongerThanCharLimit_flagOff() { + mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE); // test string: "System UWB Field Test" mItemInfoWithIcon.title = TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT; mBubbleTextView.applyLabel(mItemInfoWithIcon); @@ -284,8 +258,9 @@ public class BubbleTextViewTest { } @Test - @EnableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE) public void testLongStringSymbolLongerThanCharLimit_flagOn() { + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE); + mLauncherPrefs.put(ENABLE_TWOLINE_ALLAPPS_TOGGLE, true); // test string: "LEGO®Builder" mItemInfoWithIcon.title = TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT; mBubbleTextView.applyLabel(mItemInfoWithIcon); @@ -299,8 +274,8 @@ public class BubbleTextViewTest { } @Test - @DisableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE) public void testLongStringSymbolLongerThanCharLimit_flagOff() { + mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE); // test string: "LEGO®Builder" mItemInfoWithIcon.title = TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT; mBubbleTextView.applyLabel(mItemInfoWithIcon); @@ -370,8 +345,9 @@ public class BubbleTextViewTest { } @Test - @EnableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE) public void testEnsurePredictionRowIsTwoLine() { + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE); + mLauncherPrefs.put(ENABLE_TWOLINE_ALLAPPS_TOGGLE, true); // test string: "Battery Stats" mItemInfoWithIcon.title = TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT; mBubbleTextView.setDisplay(DISPLAY_PREDICTION_ROW); @@ -385,8 +361,9 @@ public class BubbleTextViewTest { } @Test - @EnableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE) public void modifyTitleToSupportMultiLine_whenLimitedHeight_shouldBeOneLine() { + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE); + mLauncherPrefs.put(ENABLE_TWOLINE_ALLAPPS_TOGGLE, true); // test string: "LEGO®Builder" mItemInfoWithIcon.title = TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT; mBubbleTextView.applyLabel(mItemInfoWithIcon); @@ -399,8 +376,9 @@ public class BubbleTextViewTest { } @Test - @EnableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE) public void modifyTitleToSupportMultiLine_whenUnlimitedHeight_shouldBeTwoLine() { + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE); + mLauncherPrefs.put(ENABLE_TWOLINE_ALLAPPS_TOGGLE, true); // test string: "LEGO®Builder" mItemInfoWithIcon.title = TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT; mBubbleTextView.setDisplay(DISPLAY_ALL_APPS); @@ -425,62 +403,6 @@ public class BubbleTextViewTest { assertThat(mBubbleTextView.getIcon().hasBadge()).isEqualTo(false); } - @EnableFlags({FLAG_ENABLE_SUPPORT_FOR_ARCHIVING, FLAG_USE_NEW_ICON_FOR_ARCHIVED_APPS}) - @Test - public void applyIconAndLabel_setsImageSpan_whenInactiveArchivedApp() { - // Given - BubbleTextView spyTextView = spy(mBubbleTextView); - mGmailAppInfo.runtimeStatusFlags |= FLAG_ARCHIVED; - BubbleTextView expectedTextView = new BubbleTextView(mContext); - mContext.getResources().getConfiguration().fontWeightAdjustment = 0; - int expectedDrawableId = mContext.getResources().getIdentifier( - "cloud_download_24px", /* name */ - "drawable", /* defType */ - mContext.getPackageName() - ); - expectedTextView.setTextWithStartIcon(mGmailAppInfo.title, expectedDrawableId); - // When - spyTextView.applyIconAndLabel(mGmailAppInfo); - // Then - SpannedString expectedText = (SpannedString) expectedTextView.getText(); - SpannedString actualText = (SpannedString) spyTextView.getText(); - ImageSpan actualSpan = actualText.getSpans( - 0, /* queryStart */ - 1, /* queryEnd */ - ImageSpan.class - )[0]; - ImageSpan expectedSpan = expectedText.getSpans( - 0, /* queryStart */ - 1, /* queryEnd */ - ImageSpan.class - )[0]; - verify(spyTextView).setTextWithStartIcon(mGmailAppInfo.title, expectedDrawableId); - assertThat(actualText.toString()).isEqualTo(expectedText.toString()); - assertThat(actualSpan.getDrawable().getBounds()) - .isEqualTo(expectedSpan.getDrawable().getBounds()); - assertThat(actualSpan.getVerticalAlignment()).isEqualTo(ALIGN_CENTER); - } - - @EnableFlags({FLAG_ENABLE_SUPPORT_FOR_ARCHIVING, FLAG_USE_NEW_ICON_FOR_ARCHIVED_APPS}) - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S) - @Test - public void applyIconAndLabel_setsBoldDrawable_whenBoldedTextForArchivedApp() { - // Given - int expectedDrawableId = mContext.getResources().getIdentifier( - "cloud_download_semibold_24px", /* name */ - "drawable", /* defType */ - mContext.getPackageName() - ); - mContext.getResources().getConfiguration().fontWeightAdjustment = - FONT_WEIGHT_BOLD - FONT_WEIGHT_NORMAL; - BubbleTextView spyTextView = spy(mBubbleTextView); - mGmailAppInfo.runtimeStatusFlags |= FLAG_ARCHIVED; - // When - spyTextView.applyIconAndLabel(mGmailAppInfo); - // Then - verify(spyTextView).setTextWithStartIcon(mGmailAppInfo.title, expectedDrawableId); - } - @Test public void applyIconAndLabel_whenDisplay_DISPLAY_SEARCH_RESULT_hasBadge() { FlagOp op = FlagOp.NO_OP; @@ -492,38 +414,4 @@ public class BubbleTextViewTest { assertThat(mBubbleTextView.getIcon().hasBadge()).isEqualTo(true); } - - @Test - public void applyingPendingIcon_preserves_last_icon() throws Exception { - mItemInfoWithIcon.bitmap = - BitmapInfo.fromBitmap(Bitmap.createBitmap(100, 100, Config.ARGB_8888)); - mItemInfoWithIcon.setProgressLevel(30, PackageInstallInfo.STATUS_INSTALLING); - - TestUtil.runOnExecutorSync(MAIN_EXECUTOR, - () -> mBubbleTextView.applyIconAndLabel(mItemInfoWithIcon)); - assertThat(mBubbleTextView.getIcon()).isInstanceOf(PreloadIconDrawable.class); - assertThat(mBubbleTextView.getIcon().getLevel()).isEqualTo(30); - PreloadIconDrawable oldIcon = (PreloadIconDrawable) mBubbleTextView.getIcon(); - - // Same icon is used when progress changes - mItemInfoWithIcon.setProgressLevel(50, PackageInstallInfo.STATUS_INSTALLING); - TestUtil.runOnExecutorSync(MAIN_EXECUTOR, - () -> mBubbleTextView.applyIconAndLabel(mItemInfoWithIcon)); - assertThat(mBubbleTextView.getIcon()).isSameInstanceAs(oldIcon); - assertThat(mBubbleTextView.getIcon().getLevel()).isEqualTo(50); - - // Icon is replaced with a non pending icon when download finishes - mItemInfoWithIcon.setProgressLevel(100, PackageInstallInfo.STATUS_INSTALLED); - - TestUtil.runOnExecutorSync(MAIN_EXECUTOR, () -> { - mBubbleTextView.applyIconAndLabel(mItemInfoWithIcon); - assertThat(mBubbleTextView.getIcon()).isSameInstanceAs(oldIcon); - assertThat(oldIcon.getActiveAnimation()).isNotNull(); - oldIcon.getActiveAnimation().end(); - }); - - // Assert that the icon is replaced with a non-pending icon - assertThat(mBubbleTextView.getIcon()).isNotInstanceOf(PreloadIconDrawable.class); - } - } diff --git a/tests/multivalentTests/src/com/android/launcher3/util/ActivityContextWrapper.java b/tests/multivalentTests/src/com/android/launcher3/util/ActivityContextWrapper.java index f4dc88d5de..191d284939 100644 --- a/tests/multivalentTests/src/com/android/launcher3/util/ActivityContextWrapper.java +++ b/tests/multivalentTests/src/com/android/launcher3/util/ActivityContextWrapper.java @@ -15,31 +15,33 @@ */ package com.android.launcher3.util; -import android.R; import android.content.Context; import android.content.ContextWrapper; +import android.view.ContextThemeWrapper; import com.android.launcher3.DeviceProfile; +import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener; import com.android.launcher3.InvariantDeviceProfile; +import com.android.launcher3.views.ActivityContext; import com.android.launcher3.views.BaseDragLayer; +import java.util.ArrayList; +import java.util.List; + /** * {@link ContextWrapper} with internal Launcher interface for testing */ -public class ActivityContextWrapper extends BaseContext { +public class ActivityContextWrapper extends ContextThemeWrapper implements ActivityContext { + + private final List mDpChangeListeners = new ArrayList<>(); private final DeviceProfile mProfile; private final MyDragLayer mMyDragLayer; public ActivityContextWrapper(Context base) { - this(base, R.style.Theme_DeviceDefault); - } - - public ActivityContextWrapper(Context base, int theme) { - super(base, theme); + super(base, android.R.style.Theme_DeviceDefault); mProfile = InvariantDeviceProfile.INSTANCE.get(base).getDeviceProfile(base).copy(base); mMyDragLayer = new MyDragLayer(this); - Executors.MAIN_EXECUTOR.execute(this::onViewCreated); } @Override @@ -47,6 +49,11 @@ public class ActivityContextWrapper extends BaseContext { return mMyDragLayer; } + @Override + public List getOnDeviceProfileChangeListeners() { + return mDpChangeListeners; + } + @Override public DeviceProfile getDeviceProfile() { return mProfile; diff --git a/tests/multivalentTests/src/com/android/launcher3/util/DisplayControllerTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/DisplayControllerTest.kt index 0ecb38eb0f..41effa2f01 100644 --- a/tests/multivalentTests/src/com/android/launcher3/util/DisplayControllerTest.kt +++ b/tests/multivalentTests/src/com/android/launcher3/util/DisplayControllerTest.kt @@ -15,6 +15,7 @@ */ package com.android.launcher3.util +import android.content.Context import android.content.res.Configuration import android.content.res.Resources import android.graphics.Point @@ -25,27 +26,19 @@ import android.util.DisplayMetrics import android.view.Display import android.view.Surface import androidx.test.annotation.UiThreadTest +import androidx.test.core.app.ApplicationProvider import androidx.test.filters.SmallTest import com.android.launcher3.LauncherPrefs import com.android.launcher3.LauncherPrefs.Companion.TASKBAR_PINNING import com.android.launcher3.LauncherPrefs.Companion.TASKBAR_PINNING_IN_DESKTOP_MODE -import com.android.launcher3.dagger.LauncherAppComponent -import com.android.launcher3.dagger.LauncherAppSingleton import com.android.launcher3.util.DisplayController.CHANGE_DENSITY -import com.android.launcher3.util.DisplayController.CHANGE_DESKTOP_MODE import com.android.launcher3.util.DisplayController.CHANGE_ROTATION -import com.android.launcher3.util.DisplayController.CHANGE_SHOW_LOCKED_TASKBAR import com.android.launcher3.util.DisplayController.CHANGE_TASKBAR_PINNING import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener -import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext +import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext import com.android.launcher3.util.window.CachedDisplayInfo import com.android.launcher3.util.window.WindowManagerProxy -import dagger.BindsInstance -import dagger.Component -import junit.framework.Assert.assertFalse -import junit.framework.Assert.assertTrue import kotlin.math.min -import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -55,7 +48,6 @@ import org.mockito.kotlin.doNothing import org.mockito.kotlin.doReturn import org.mockito.kotlin.eq import org.mockito.kotlin.mock -import org.mockito.kotlin.spy import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import org.mockito.stubbing.Answer @@ -65,10 +57,12 @@ import org.mockito.stubbing.Answer @RunWith(LauncherMultivalentJUnit::class) class DisplayControllerTest { - private val context = spy(SandboxModelContext()) - private val windowManagerProxy: MyWmProxy = mock() + private val appContext: Context = ApplicationProvider.getApplicationContext() + + private val context: SandboxContext = mock() + private val windowManagerProxy: WindowManagerProxy = mock() private val launcherPrefs: LauncherPrefs = mock() - private lateinit var displayManager: DisplayManager + private val displayManager: DisplayManager = mock() private val display: Display = mock() private val resources: Resources = mock() private val displayInfoChangeListener: DisplayInfoChangeListener = mock() @@ -85,10 +79,10 @@ class DisplayControllerTest { WindowBounds(Rect(0, 0, width, height), Rect(0, inset, 0, 0), Surface.ROTATION_0), WindowBounds(Rect(0, 0, height, width), Rect(0, inset, 0, 0), Surface.ROTATION_90), WindowBounds(Rect(0, 0, width, height), Rect(0, inset, 0, 0), Surface.ROTATION_180), - WindowBounds(Rect(0, 0, height, width), Rect(0, inset, 0, 0), Surface.ROTATION_270), + WindowBounds(Rect(0, 0, height, width), Rect(0, inset, 0, 0), Surface.ROTATION_270) ) private val configuration = - Configuration(context.resources.configuration).apply { + Configuration(appContext.resources.configuration).apply { densityDpi = this@DisplayControllerTest.densityDpi screenWidthDp = (bounds[0].bounds.width() / density).toInt() screenHeightDp = (bounds[0].bounds.height() / density).toInt() @@ -97,13 +91,8 @@ class DisplayControllerTest { @Before fun setUp() { - context.initDaggerComponent( - DaggerDisplayControllerTestComponent.builder() - .bindWMProxy(windowManagerProxy) - .bindLauncherPrefs(launcherPrefs) - ) - displayManager = context.spyService(DisplayManager::class.java) - + whenever(context.getObject(eq(WindowManagerProxy.INSTANCE))).thenReturn(windowManagerProxy) + whenever(context.getObject(eq(LauncherPrefs.INSTANCE))).thenReturn(launcherPrefs) whenever(launcherPrefs.get(TASKBAR_PINNING)).thenReturn(false) whenever(launcherPrefs.get(TASKBAR_PINNING_IN_DESKTOP_MODE)).thenReturn(true) @@ -122,17 +111,18 @@ class DisplayControllerTest { whenever(windowManagerProxy.getRealBounds(any(), any())).thenAnswer { i -> bounds[i.getArgument(1).rotation] } - whenever(windowManagerProxy.showLockedTaskbarOnHome(any())).thenReturn(false) whenever(windowManagerProxy.getNavigationMode(any())).thenReturn(NavigationMode.NO_BUTTON) // Mock context - doReturn(context).whenever(context).createWindowContext(any(), any(), anyOrNull()) + whenever(context.createWindowContext(any(), any(), anyOrNull())).thenReturn(context) + whenever(context.getSystemService(eq(DisplayManager::class.java))) + .thenReturn(displayManager) doNothing().whenever(context).registerComponentCallbacks(any()) // Mock display whenever(display.rotation).thenReturn(displayInfo.rotation) - doReturn(display).whenever(context).display - doReturn(display).whenever(displayManager).getDisplay(any()) + whenever(context.display).thenReturn(display) + whenever(displayManager.getDisplay(any())).thenReturn(display) // Mock resources doReturn(context).whenever(context).applicationContext @@ -140,18 +130,10 @@ class DisplayControllerTest { whenever(context.resources).thenReturn(resources) // Initialize DisplayController - displayController = DisplayController.INSTANCE.get(context) + displayController = DisplayController(context) displayController.addChangeListener(displayInfoChangeListener) } - @After - fun tearDown() { - // We need to reset the taskbar mode preference override even if a test throws an exception. - // Otherwise, it may break the following tests' assumptions. - DisplayController.enableTaskbarModePreferenceForTests(false) - context.onDestroy() - } - @Test @UiThreadTest fun testRotation() { @@ -185,7 +167,7 @@ class DisplayControllerTest { @UiThreadTest fun testTaskbarPinning() { whenever(launcherPrefs.get(TASKBAR_PINNING)).thenReturn(true) - displayController.notifyConfigChange() + displayController.handleInfoChange(display) verify(displayInfoChangeListener) .onDisplayInfoChanged(any(), any(), eq(CHANGE_TASKBAR_PINNING)) } @@ -194,123 +176,8 @@ class DisplayControllerTest { @UiThreadTest fun testTaskbarPinningChangeInDesktopMode() { whenever(launcherPrefs.get(TASKBAR_PINNING_IN_DESKTOP_MODE)).thenReturn(false) - displayController.notifyConfigChange() + displayController.handleInfoChange(display) verify(displayInfoChangeListener) .onDisplayInfoChanged(any(), any(), eq(CHANGE_TASKBAR_PINNING)) } - - @Test - @UiThreadTest - fun testTaskbarPinningChangeInLockedTaskbarChange() { - whenever(windowManagerProxy.showLockedTaskbarOnHome(any())).thenReturn(true) - whenever(windowManagerProxy.isHomeVisible(any())).thenReturn(true) - whenever(windowManagerProxy.isInDesktopMode(any())).thenReturn(false) - whenever(launcherPrefs.get(TASKBAR_PINNING)).thenReturn(false) - DisplayController.enableTaskbarModePreferenceForTests(true) - - assertTrue(displayController.getInfo().isTransientTaskbar()) - displayController.notifyConfigChange() - - verify(displayInfoChangeListener) - .onDisplayInfoChanged( - any(), - any(), - eq(CHANGE_TASKBAR_PINNING or CHANGE_SHOW_LOCKED_TASKBAR), - ) - assertFalse(displayController.getInfo().isTransientTaskbar()) - } - - @Test - @UiThreadTest - fun testLockedTaskbarChangeOnConfigurationChanged() { - whenever(windowManagerProxy.showLockedTaskbarOnHome(any())).thenReturn(true) - whenever(windowManagerProxy.isHomeVisible(any())).thenReturn(true) - whenever(windowManagerProxy.isInDesktopMode(any())).thenReturn(false) - whenever(launcherPrefs.get(TASKBAR_PINNING)).thenReturn(false) - DisplayController.enableTaskbarModePreferenceForTests(true) - assertTrue(displayController.getInfo().isTransientTaskbar()) - - displayController.onConfigurationChanged(configuration) - - verify(displayInfoChangeListener) - .onDisplayInfoChanged( - any(), - any(), - eq(CHANGE_TASKBAR_PINNING or CHANGE_SHOW_LOCKED_TASKBAR), - ) - assertFalse(displayController.getInfo().isTransientTaskbar()) - } - - @Test - @UiThreadTest - fun testTaskbarPinnedForDesktopTaskbar_inDesktopMode() { - whenever(windowManagerProxy.showDesktopTaskbarForFreeformDisplay(any())).thenReturn(true) - whenever(launcherPrefs.get(TASKBAR_PINNING)).thenReturn(false) - whenever(launcherPrefs.get(TASKBAR_PINNING_IN_DESKTOP_MODE)).thenReturn(false) - whenever(windowManagerProxy.isInDesktopMode(any())).thenReturn(true) - whenever(windowManagerProxy.isHomeVisible(any())).thenReturn(false) - DisplayController.enableTaskbarModePreferenceForTests(true) - - assertTrue(displayController.getInfo().isTransientTaskbar()) - - displayController.onConfigurationChanged(configuration) - - verify(displayInfoChangeListener) - .onDisplayInfoChanged(any(), any(), eq(CHANGE_TASKBAR_PINNING or CHANGE_DESKTOP_MODE)) - assertFalse(displayController.getInfo().isTransientTaskbar()) - } - - @Test - @UiThreadTest - fun testTaskbarPinnedForDesktopTaskbar_notInDesktopMode() { - whenever(windowManagerProxy.showDesktopTaskbarForFreeformDisplay(any())).thenReturn(true) - whenever(launcherPrefs.get(TASKBAR_PINNING)).thenReturn(false) - whenever(launcherPrefs.get(TASKBAR_PINNING_IN_DESKTOP_MODE)).thenReturn(false) - whenever(windowManagerProxy.isInDesktopMode(any())).thenReturn(false) - whenever(windowManagerProxy.isHomeVisible(any())).thenReturn(false) - DisplayController.enableTaskbarModePreferenceForTests(true) - - assertTrue(displayController.getInfo().isTransientTaskbar()) - - displayController.onConfigurationChanged(configuration) - - verify(displayInfoChangeListener) - .onDisplayInfoChanged(any(), any(), eq(CHANGE_TASKBAR_PINNING)) - assertFalse(displayController.getInfo().isTransientTaskbar()) - } - - @Test - @UiThreadTest - fun testTaskbarPinnedForDesktopTaskbar_onHome() { - whenever(windowManagerProxy.showDesktopTaskbarForFreeformDisplay(any())).thenReturn(true) - whenever(launcherPrefs.get(TASKBAR_PINNING)).thenReturn(false) - whenever(launcherPrefs.get(TASKBAR_PINNING_IN_DESKTOP_MODE)).thenReturn(false) - whenever(windowManagerProxy.isInDesktopMode(any())).thenReturn(false) - whenever(windowManagerProxy.isHomeVisible(any())).thenReturn(true) - DisplayController.enableTaskbarModePreferenceForTests(true) - - assertTrue(displayController.getInfo().isTransientTaskbar()) - - displayController.onConfigurationChanged(configuration) - - verify(displayInfoChangeListener) - .onDisplayInfoChanged(any(), any(), eq(CHANGE_TASKBAR_PINNING)) - assertFalse(displayController.getInfo().isTransientTaskbar()) - } -} - -class MyWmProxy : WindowManagerProxy() - -@LauncherAppSingleton -@Component(modules = [AllModulesMinusWMProxy::class]) -interface DisplayControllerTestComponent : LauncherAppComponent { - - @Component.Builder - interface Builder : LauncherAppComponent.Builder { - @BindsInstance fun bindWMProxy(proxy: WindowManagerProxy): Builder - - @BindsInstance fun bindLauncherPrefs(prefs: LauncherPrefs): Builder - - override fun build(): DisplayControllerTestComponent - } } diff --git a/tests/multivalentTests/src/com/android/launcher3/util/LauncherModelHelper.java b/tests/multivalentTests/src/com/android/launcher3/util/LauncherModelHelper.java index 4458e8fb01..f18c02b372 100644 --- a/tests/multivalentTests/src/com/android/launcher3/util/LauncherModelHelper.java +++ b/tests/multivalentTests/src/com/android/launcher3/util/LauncherModelHelper.java @@ -21,7 +21,6 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; -import static com.android.launcher3.util.TestUtil.grantWriteSecurePermission; import static com.android.launcher3.util.TestUtil.runOnExecutorSync; import static org.mockito.ArgumentMatchers.anyInt; @@ -31,7 +30,6 @@ import static org.mockito.Mockito.spy; import android.content.ContentProvider; import android.content.ContentResolver; -import android.content.Context; import android.content.pm.PackageInstaller; import android.content.pm.PackageInstaller.SessionParams; import android.content.pm.PackageManager; @@ -47,24 +45,21 @@ import android.test.mock.MockContentResolver; import android.util.ArrayMap; import androidx.test.core.app.ApplicationProvider; +import androidx.test.uiautomator.UiDevice; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherModel; -import com.android.launcher3.dagger.LauncherBaseAppComponent; import com.android.launcher3.model.BgDataModel; import com.android.launcher3.model.BgDataModel.Callbacks; -import com.android.launcher3.model.ModelDbController; import com.android.launcher3.testing.TestInformationProvider; +import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext; -import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.OutputStreamWriter; -import java.util.Arrays; -import java.util.List; import java.util.UUID; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; @@ -90,23 +85,6 @@ public class LauncherModelHelper { public static final String TEST_ACTIVITY13 = "com.android.launcher3.tests.Activity14"; public static final String TEST_ACTIVITY14 = "com.android.launcher3.tests.Activity15"; - public static final List ACTIVITY_LIST = Arrays.asList( - TEST_ACTIVITY, - TEST_ACTIVITY2, - TEST_ACTIVITY3, - TEST_ACTIVITY4, - TEST_ACTIVITY5, - TEST_ACTIVITY6, - TEST_ACTIVITY7, - TEST_ACTIVITY8, - TEST_ACTIVITY9, - TEST_ACTIVITY10, - TEST_ACTIVITY11, - TEST_ACTIVITY12, - TEST_ACTIVITY13, - TEST_ACTIVITY14 - ); - // Authority for providing a test default-workspace-layout data. private static final String TEST_PROVIDER_AUTHORITY = LauncherModelHelper.class.getName().toLowerCase(); @@ -150,9 +128,7 @@ public class LauncherModelHelper { icon.eraseColor(Color.RED); sp.setAppIcon(icon); sp.setAppLabel(pkg); - sp.setInstallerPackageName(ApplicationProvider.getApplicationContext().getPackageName()); - PackageInstaller pi = ApplicationProvider.getApplicationContext().getPackageManager() - .getPackageInstaller(); + PackageInstaller pi = sandboxContext.getPackageManager().getPackageInstaller(); int sessionId = pi.createSession(sp); mDestroyTask.add(() -> pi.abandonSession(sessionId)); return sessionId; @@ -187,22 +163,12 @@ public class LauncherModelHelper { */ public LauncherModelHelper setupDefaultLayoutProvider(LauncherLayoutBuilder builder) throws Exception { - grantWriteSecurePermission(); - InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(sandboxContext); - if (idp.numRows == 0 && idp.numColumns == 0) { - idp.numRows = idp.numColumns = idp.numDatabaseHotseatIcons = DEFAULT_GRID_SIZE; - } - if (idp.iconBitmapSize == 0) { - idp.iconBitmapSize = DEFAULT_BITMAP_SIZE; - } + idp.numRows = idp.numColumns = idp.numDatabaseHotseatIcons = DEFAULT_GRID_SIZE; + idp.iconBitmapSize = DEFAULT_BITMAP_SIZE; - Settings.Secure.putString(sandboxContext.getContentResolver(), "launcher3.layout.provider", - TEST_PROVIDER_AUTHORITY); - - // TODO: use a wrapper class to differentiate the behavior - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - builder.build(new OutputStreamWriter(bos)); + UiDevice.getInstance(getInstrumentation()).executeShellCommand( + "settings put secure launcher3.layout.provider " + TEST_PROVIDER_AUTHORITY); ContentProvider cp = new TestInformationProvider() { @Override @@ -211,6 +177,8 @@ public class LauncherModelHelper { try { ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe(); AutoCloseOutputStream outputStream = new AutoCloseOutputStream(pipe[1]); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + builder.build(new OutputStreamWriter(bos)); outputStream.write(bos.toByteArray()); outputStream.flush(); outputStream.close(); @@ -221,13 +189,9 @@ public class LauncherModelHelper { } }; setupProvider(TEST_PROVIDER_AUTHORITY, cp); - RoboApiWrapper.INSTANCE.registerInputStream(sandboxContext.getContentResolver(), - ModelDbController.getLayoutUri(TEST_PROVIDER_AUTHORITY, sandboxContext), - ()-> new ByteArrayInputStream(bos.toByteArray())); - mDestroyTask.add(() -> runOnExecutorSync(MODEL_EXECUTOR, () -> - Settings.Secure.putString(sandboxContext.getContentResolver(), - "launcher3.layout.provider", ""))); + UiDevice.getInstance(getInstrumentation()).executeShellCommand( + "settings delete secure launcher3.layout.provider"))); return this; } @@ -239,7 +203,7 @@ public class LauncherModelHelper { MAIN_EXECUTOR.submit(() -> getModel().addCallbacksAndLoad(mockCb)).get(); Executors.MODEL_EXECUTOR.submit(() -> { }).get(); - getInstrumentation().waitForIdleSync(); + MAIN_EXECUTOR.submit(() -> { }).get(); MAIN_EXECUTOR.submit(() -> getModel().removeCallbacks(mockCb)).get(); } @@ -251,21 +215,28 @@ public class LauncherModelHelper { private final File mDbDir; public SandboxModelContext() { - this(ApplicationProvider.getApplicationContext()); - } - - public SandboxModelContext(Context context) { - super(context); + super(ApplicationProvider.getApplicationContext()); // System settings cache content provider. Ensure that they are statically initialized - Settings.Secure.getString(context.getContentResolver(), "test"); - Settings.System.getString(context.getContentResolver(), "test"); - Settings.Global.getString(context.getContentResolver(), "test"); + Settings.Secure.getString( + ApplicationProvider.getApplicationContext().getContentResolver(), "test"); + Settings.System.getString( + ApplicationProvider.getApplicationContext().getContentResolver(), "test"); + Settings.Global.getString( + ApplicationProvider.getApplicationContext().getContentResolver(), "test"); mPm = spy(getBaseContext().getPackageManager()); mDbDir = new File(getCacheDir(), UUID.randomUUID().toString()); } + @Override + public T createObject(MainThreadInitializedObject object) { + if (object == LauncherAppState.INSTANCE) { + return (T) new LauncherAppState(this, null /* iconCacheFileName */); + } + return super.createObject(object); + } + @Override public File getDatabasePath(String name) { if (!mDbDir.exists()) { @@ -280,11 +251,11 @@ public class LauncherModelHelper { } @Override - protected void cleanUpObjects() { + public void onDestroy() { if (deleteContents(mDbDir)) { mDbDir.delete(); } - super.cleanUpObjects(); + super.onDestroy(); } @Override @@ -334,10 +305,5 @@ public class LauncherModelHelper { } return success; } - - @Override - public void initDaggerComponent(LauncherBaseAppComponent.Builder componentBuilder) { - super.initDaggerComponent(componentBuilder.iconsDbName(null)); - } } } diff --git a/tests/multivalentTests/src/com/android/launcher3/util/LockedUserStateTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/LockedUserStateTest.kt index 99f5a5bdd4..2711d7a66d 100644 --- a/tests/multivalentTests/src/com/android/launcher3/util/LockedUserStateTest.kt +++ b/tests/multivalentTests/src/com/android/launcher3/util/LockedUserStateTest.kt @@ -22,10 +22,7 @@ import android.os.Process import android.os.UserManager import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.launcher3.util.Executors.MAIN_EXECUTOR -import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR import com.google.common.truth.Truth.assertThat -import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -41,24 +38,17 @@ class LockedUserStateTest { private val userManager: UserManager = mock() private val context: Context = mock() - private val lifeCycle: DaggerSingletonTracker = mock() @Before fun setup() { whenever(context.getSystemService(UserManager::class.java)).thenReturn(userManager) } - @After - fun tearDown() { - UI_HELPER_EXECUTOR.submit {}.get() - MAIN_EXECUTOR.submit {}.get() - } - @Test fun runOnUserUnlocked_runs_action_immediately_if_already_unlocked() { whenever(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(true) val action: Runnable = mock() - LockedUserState(context, lifeCycle).runOnUserUnlocked(action) + LockedUserState(context).runOnUserUnlocked(action) verify(action).run() } @@ -66,23 +56,23 @@ class LockedUserStateTest { fun runOnUserUnlocked_waits_to_run_action_until_user_is_unlocked() { whenever(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(false) val action: Runnable = mock() - val state = LockedUserState(context, lifeCycle) + val state = LockedUserState(context) state.runOnUserUnlocked(action) // b/343530737 verifyNoMoreInteractions(action) - state.userUnlockedReceiver.onReceive(context, Intent(Intent.ACTION_USER_UNLOCKED)) + state.mUserUnlockedReceiver.onReceive(context, Intent(Intent.ACTION_USER_UNLOCKED)) verify(action).run() } @Test fun isUserUnlocked_returns_true_when_user_is_unlocked() { whenever(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(true) - assertThat(LockedUserState(context, lifeCycle).isUserUnlocked).isTrue() + assertThat(LockedUserState(context).isUserUnlocked).isTrue() } @Test fun isUserUnlocked_returns_false_when_user_is_locked() { whenever(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(false) - assertThat(LockedUserState(context, lifeCycle).isUserUnlocked).isFalse() + assertThat(LockedUserState(context).isUserUnlocked).isFalse() } } diff --git a/tests/multivalentTests/src/com/android/launcher3/util/ModelTestExtensions.kt b/tests/multivalentTests/src/com/android/launcher3/util/ModelTestExtensions.kt index 524acff61d..6bd182b1fe 100644 --- a/tests/multivalentTests/src/com/android/launcher3/util/ModelTestExtensions.kt +++ b/tests/multivalentTests/src/com/android/launcher3/util/ModelTestExtensions.kt @@ -1,11 +1,8 @@ package com.android.launcher3.util import android.content.ContentValues -import android.database.sqlite.SQLiteDatabase -import android.os.Process -import androidx.test.platform.app.InstrumentationRegistry -import com.android.launcher3.Flags import com.android.launcher3.LauncherModel +import com.android.launcher3.LauncherSettings.Favorites import com.android.launcher3.LauncherSettings.Favorites.APPWIDGET_ID import com.android.launcher3.LauncherSettings.Favorites.APPWIDGET_PROVIDER import com.android.launcher3.LauncherSettings.Favorites.APPWIDGET_SOURCE @@ -25,8 +22,6 @@ import com.android.launcher3.LauncherSettings.Favorites.TITLE import com.android.launcher3.LauncherSettings.Favorites._ID import com.android.launcher3.model.BgDataModel import com.android.launcher3.model.ModelDbController -import java.io.BufferedReader -import java.io.InputStreamReader object ModelTestExtensions { /** Clears and reloads Launcher db to cleanup the workspace */ @@ -35,9 +30,7 @@ object ModelTestExtensions { loadModelSync() TestUtil.runOnExecutorSync(Executors.MODEL_EXECUTOR) { modelDbController.run { - if (Flags.gridMigrationRefactor()) - attemptMigrateDb(null /* restoreEventLogger */, modelDelegate) - else tryMigrateDB(null /* restoreEventLogger */, modelDelegate) + tryMigrateDB(null /* restoreEventLogger */) createEmptyDB() clearEmptyDbFlag() } @@ -70,15 +63,16 @@ object ModelTestExtensions { spanX: Int = 1, spanY: Int = 1, id: Int = 0, - profileId: Int = Process.myUserHandle().identifier, + profileId: Int = 0, + tableName: String = Favorites.TABLE_NAME, appWidgetId: Int = -1, appWidgetSource: Int = -1, - appWidgetProvider: String? = null, + appWidgetProvider: String? = null ) { loadModelSync() TestUtil.runOnExecutorSync(Executors.MODEL_EXECUTOR) { val controller: ModelDbController = modelDbController - controller.attemptMigrateDb(null /* restoreEventLogger */, modelDelegate) + controller.tryMigrateDB(null /* restoreEventLogger */) modelDbController.newTransaction().use { transaction -> val values = ContentValues().apply { @@ -99,21 +93,9 @@ object ModelTestExtensions { values[APPWIDGET_PROVIDER] = appWidgetProvider } // Migrate any previous data so that the DB state is correct - controller.insert(values) + controller.insert(tableName, values) transaction.commit() } } } - - /** Creates an in-memory sqlite DB and initializes with the data in [insertFile] */ - fun createInMemoryDb(insertFile: String): SQLiteDatabase = - SQLiteDatabase.createInMemory(SQLiteDatabase.OpenParams.Builder().build()).also { db -> - BufferedReader( - InputStreamReader( - InstrumentationRegistry.getInstrumentation().context.assets.open(insertFile) - ) - ) - .lines() - .forEach { sqlStatement -> db.execSQL(sqlStatement) } - } } diff --git a/tests/multivalentTests/src/com/android/launcher3/util/PackageManagerHelperTest.java b/tests/multivalentTests/src/com/android/launcher3/util/PackageManagerHelperTest.java new file mode 100644 index 0000000000..b5e797ed39 --- /dev/null +++ b/tests/multivalentTests/src/com/android/launcher3/util/PackageManagerHelperTest.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.util; + +import static com.android.launcher3.Flags.FLAG_ENABLE_SUPPORT_FOR_ARCHIVING; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.LauncherApps; +import android.content.pm.PackageManager; +import android.os.UserHandle; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; + +/** Unit tests for {@link PackageManagerHelper}. */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public final class PackageManagerHelperTest { + @Rule + public ExpectedException exception = ExpectedException.none(); + + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + + private static final String TEST_PACKAGE = "com.android.test.package"; + private static final int TEST_USER = 2; + + private Context mContext; + private LauncherApps mLauncherApps; + private PackageManagerHelper mPackageManagerHelper; + + @Before + public void setup() { + mContext = mock(Context.class); + mLauncherApps = mock(LauncherApps.class); + when(mContext.getSystemService(eq(LauncherApps.class))).thenReturn(mLauncherApps); + when(mContext.getResources()).thenReturn( + InstrumentationRegistry.getInstrumentation().getTargetContext().getResources()); + mPackageManagerHelper = new PackageManagerHelper(mContext); + } + + @Test + @RequiresFlagsEnabled(FLAG_ENABLE_SUPPORT_FOR_ARCHIVING) + public void getApplicationInfo_archivedApp_appInfoIsNotNull() + throws PackageManager.NameNotFoundException { + ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.isArchived = true; + when(mLauncherApps.getApplicationInfo(TEST_PACKAGE, 0 /* flags */, + UserHandle.of(TEST_USER))) + .thenReturn(applicationInfo); + + assertThat(mPackageManagerHelper.getApplicationInfo(TEST_PACKAGE, UserHandle.of(TEST_USER), + 0 /* flags */)) + .isNotNull(); + } +} diff --git a/tests/multivalentTests/src/com/android/launcher3/util/TestSandboxModelContextWrapper.java b/tests/multivalentTests/src/com/android/launcher3/util/TestSandboxModelContextWrapper.java index acd17d1586..3f37563302 100644 --- a/tests/multivalentTests/src/com/android/launcher3/util/TestSandboxModelContextWrapper.java +++ b/tests/multivalentTests/src/com/android/launcher3/util/TestSandboxModelContextWrapper.java @@ -18,9 +18,10 @@ package com.android.launcher3.util; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; +import static com.android.launcher3.util.MainThreadInitializedObject.SandboxContext; + import android.content.ContextWrapper; -import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.test.platform.app.InstrumentationRegistry; @@ -28,11 +29,9 @@ import com.android.launcher3.allapps.ActivityAllAppsContainerView; import com.android.launcher3.allapps.AllAppsStore; import com.android.launcher3.allapps.AlphabeticalAppsList; import com.android.launcher3.model.BgDataModel; -import com.android.launcher3.model.WidgetsFilterDataProvider; import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.pm.UserCache; import com.android.launcher3.popup.PopupDataProvider; -import com.android.launcher3.widget.picker.model.WidgetPickerDataProvider; import java.util.Map; import java.util.concurrent.CountDownLatch; @@ -44,7 +43,7 @@ import java.util.concurrent.CountDownLatch; * There are 2 constructors in this class. The base context can be {@link SandboxContext} or * Instrumentation target context. * Using {@link SandboxContext} as base context allows custom implementations for - * providing objects in Dagger components. + * MainThreadInitializedObject providers. */ public class TestSandboxModelContextWrapper extends ActivityContextWrapper implements @@ -57,14 +56,12 @@ public class TestSandboxModelContextWrapper extends ActivityContextWrapper imple protected ActivityAllAppsContainerView mAppsView; - private final PopupDataProvider mPopupDataProvider = new PopupDataProvider(this); - private final WidgetPickerDataProvider mWidgetPickerDataProvider = - new WidgetPickerDataProvider(new WidgetsFilterDataProvider()); + private final PopupDataProvider mPopupDataProvider = new PopupDataProvider(i -> {}); protected final UserCache mUserCache; public TestSandboxModelContextWrapper(SandboxContext base) { super(base); - mUserCache = UserCache.getInstance(base); + mUserCache = base.getObject(UserCache.INSTANCE); InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> mAppsView = new ActivityAllAppsContainerView<>(this)); mAppsList = mAppsView.getPersonalAppList(); @@ -79,19 +76,12 @@ public class TestSandboxModelContextWrapper extends ActivityContextWrapper imple mAppsList = mAppsView.getPersonalAppList(); mAllAppsStore = mAppsView.getAppsStore(); } - - @NonNull + @Nullable @Override public PopupDataProvider getPopupDataProvider() { return mPopupDataProvider; } - @Nullable - @Override - public WidgetPickerDataProvider getWidgetPickerDataProvider() { - return mWidgetPickerDataProvider; - } - @Override public ActivityAllAppsContainerView getAppsView() { return mAppsView; diff --git a/tests/multivalentTests/src/com/android/launcher3/util/TestUtil.java b/tests/multivalentTests/src/com/android/launcher3/util/TestUtil.java index eb25acf14a..3646f0c4a4 100644 --- a/tests/multivalentTests/src/com/android/launcher3/util/TestUtil.java +++ b/tests/multivalentTests/src/com/android/launcher3/util/TestUtil.java @@ -15,24 +15,22 @@ */ package com.android.launcher3.util; +import static android.util.Base64.NO_PADDING; +import static android.util.Base64.NO_WRAP; + import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; +import static com.android.launcher3.LauncherSettings.Settings.LAYOUT_DIGEST_KEY; import static com.android.launcher3.LauncherSettings.Settings.LAYOUT_DIGEST_LABEL; import static com.android.launcher3.LauncherSettings.Settings.LAYOUT_DIGEST_TAG; -import static com.android.launcher3.LauncherSettings.Settings.LAYOUT_PROVIDER_KEY; -import static com.android.launcher3.LauncherSettings.Settings.createBlobProviderKey; import static org.junit.Assert.assertTrue; -import android.Manifest; import android.app.Instrumentation; import android.app.blob.BlobHandle; import android.app.blob.BlobStoreManager; import android.content.Context; -import android.content.Intent; -import android.content.pm.ActivityInfo; import android.content.pm.LauncherApps; -import android.content.pm.PackageManager; import android.content.res.Resources; import android.graphics.Point; import android.os.AsyncTask; @@ -43,6 +41,7 @@ import android.os.Process; import android.os.UserHandle; import android.provider.Settings; import android.system.OsConstants; +import android.util.Base64; import android.util.Log; import androidx.test.uiautomator.UiDevice; @@ -169,12 +168,11 @@ public class TestUtil { session.commit(AsyncTask.THREAD_POOL_EXECUTOR, i -> wait.countDown()); } - grantWriteSecurePermission(); - Settings.Secure.putString( - context.getContentResolver(), LAYOUT_PROVIDER_KEY, createBlobProviderKey(digest)); + String key = Base64.encodeToString(digest, NO_WRAP | NO_PADDING); + Settings.Secure.putString(context.getContentResolver(), LAYOUT_DIGEST_KEY, key); wait.await(); return () -> - Settings.Secure.putString(context.getContentResolver(), LAYOUT_PROVIDER_KEY, null); + Settings.Secure.putString(context.getContentResolver(), LAYOUT_DIGEST_KEY, null); } /** @@ -226,23 +224,6 @@ public class TestUtil { assertTrue(message, failed); } - /** - * Grants [WRITE_SECURE_SETTINGS] permission in runtime. - */ - public static void grantWriteSecurePermission() { - getInstrumentation().getUiAutomation() - .adoptShellPermissionIdentity(Manifest.permission.WRITE_SECURE_SETTINGS); - } - - /** - * Returns the activity info corresponding to the system app for the provided category - */ - public static ActivityInfo resolveSystemAppInfo(String category) { - return getInstrumentation().getTargetContext().getPackageManager().resolveActivity( - new Intent(Intent.ACTION_MAIN).addCategory(category), - PackageManager.MATCH_SYSTEM_ONLY).activityInfo; - } - /** Interface to indicate a runnable which can throw any exception. */ public interface UncheckedRunnable { /** Method to run the task */ diff --git a/tests/multivalentTests/src/com/android/launcher3/util/WidgetUtils.java b/tests/multivalentTests/src/com/android/launcher3/util/WidgetUtils.java index 9fbd7ff311..deb0ef39a3 100644 --- a/tests/multivalentTests/src/com/android/launcher3/util/WidgetUtils.java +++ b/tests/multivalentTests/src/com/android/launcher3/util/WidgetUtils.java @@ -15,15 +15,12 @@ */ package com.android.launcher3.util; -import static android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_HIDE_FROM_PICKER; - import android.appwidget.AppWidgetProviderInfo; import android.content.ComponentName; import android.content.Context; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.os.Bundle; -import android.os.Process; import com.android.launcher3.LauncherSettings; import com.android.launcher3.model.data.LauncherAppWidgetInfo; @@ -85,30 +82,13 @@ public class WidgetUtils { /** * Creates a {@link AppWidgetProviderInfo} for the provided component name - * - * @param cn component name of the appwidget provider - * @param hideFromPicker indicates if the widget should appear in widget picker */ - public static AppWidgetProviderInfo createAppWidgetProviderInfo(ComponentName cn, - boolean hideFromPicker) { + public static AppWidgetProviderInfo createAppWidgetProviderInfo(ComponentName cn) { ActivityInfo activityInfo = new ActivityInfo(); activityInfo.applicationInfo = new ApplicationInfo(); - activityInfo.applicationInfo.uid = Process.myUid(); AppWidgetProviderInfo info = new AppWidgetProviderInfo(); - if (hideFromPicker) { - info.widgetFeatures = WIDGET_FEATURE_HIDE_FROM_PICKER; - } info.providerInfo = activityInfo; info.provider = cn; return info; } - - /** - * Creates a {@link AppWidgetProviderInfo} for the provided component name - * - * @param cn component name of the appwidget provider - */ - public static AppWidgetProviderInfo createAppWidgetProviderInfo(ComponentName cn) { - return createAppWidgetProviderInfo(cn, /*hideFromPicker=*/ false); - } } diff --git a/tests/multivalentTests/src/com/android/launcher3/util/rule/TestStabilityRule.java b/tests/multivalentTests/src/com/android/launcher3/util/rule/TestStabilityRule.java index 6313cf02f7..909aabd248 100644 --- a/tests/multivalentTests/src/com/android/launcher3/util/rule/TestStabilityRule.java +++ b/tests/multivalentTests/src/com/android/launcher3/util/rule/TestStabilityRule.java @@ -47,7 +47,7 @@ public class TestStabilityRule implements TestRule { + ")$"); private static final Pattern PLATFORM_BUILD = Pattern.compile("^(" - + "(?eng\\..+)|" + + "(?eng\\.[a-z]+\\.[0-9]+\\.[0-9]+)|" + "(?P[0-9]+)|" + "(?[0-9]+)" + ")$"); @@ -109,9 +109,6 @@ public class TestStabilityRule implements TestRule { getPackageManager(). getPackageInfo(launcherPackageName, 0) .versionName; - if (launcherVersion == null) { - return LOCAL; - } } catch (PackageManager.NameNotFoundException e) { throw new RuntimeException(e); } diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/GeneratedPreviewTest.kt b/tests/multivalentTests/src/com/android/launcher3/widget/GeneratedPreviewTest.kt index 6af095047d..b239aed7cf 100644 --- a/tests/multivalentTests/src/com/android/launcher3/widget/GeneratedPreviewTest.kt +++ b/tests/multivalentTests/src/com/android/launcher3/widget/GeneratedPreviewTest.kt @@ -1,75 +1,71 @@ package com.android.launcher3.widget -import android.appwidget.AppWidgetManager import android.appwidget.AppWidgetProviderInfo import android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN +import android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD +import android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX import android.content.ComponentName import android.content.Context import android.content.pm.ActivityInfo import android.content.pm.ApplicationInfo +import android.platform.test.annotations.RequiresFlagsDisabled import android.platform.test.annotations.RequiresFlagsEnabled import android.platform.test.flag.junit.CheckFlagsRule import android.platform.test.flag.junit.DeviceFlagsValueProvider import android.view.ContextThemeWrapper import android.view.LayoutInflater import android.widget.RemoteViews +import androidx.test.core.app.ApplicationProvider.getApplicationContext import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import com.android.launcher3.Flags.FLAG_ENABLE_GENERATED_PREVIEWS -import com.android.launcher3.R +import com.android.launcher3.InvariantDeviceProfile import com.android.launcher3.icons.IconCache +import com.android.launcher3.icons.IconProvider import com.android.launcher3.model.WidgetItem import com.android.launcher3.util.ActivityContextWrapper import com.android.launcher3.util.Executors -import com.android.launcher3.util.Executors.MAIN_EXECUTOR -import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext -import com.android.launcher3.util.TestUtil import com.google.common.truth.Truth.assertThat -import org.junit.After import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mock -import org.mockito.MockitoAnnotations -import org.mockito.kotlin.any -import org.mockito.kotlin.doAnswer -import org.mockito.kotlin.whenever @SmallTest @RunWith(AndroidJUnit4::class) -@RequiresFlagsEnabled(FLAG_ENABLE_GENERATED_PREVIEWS) class GeneratedPreviewTest { @get:Rule val checkFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule() private val providerName = ComponentName( - getInstrumentation().context.packageName, - "com.android.launcher3.testcomponent.AppWidgetNoConfig", + "com.android.launcher3.tests", + "com.android.launcher3.testcomponent.AppWidgetNoConfig" ) private val generatedPreviewLayout = getInstrumentation().context.run { resources.getIdentifier("test_layout_appwidget_blue", "layout", packageName) } - - private lateinit var context: SandboxModelContext - private lateinit var uiContext: Context + private lateinit var context: Context private lateinit var generatedPreview: RemoteViews private lateinit var widgetCell: WidgetCell + private lateinit var helper: WidgetManagerHelper private lateinit var appWidgetProviderInfo: LauncherAppWidgetProviderInfo private lateinit var widgetItem: WidgetItem - @Mock lateinit var iconCache: IconCache - @Before fun setup() { - MockitoAnnotations.initMocks(this) - context = SandboxModelContext() + context = getApplicationContext() generatedPreview = RemoteViews(context.packageName, generatedPreviewLayout) - uiContext = - ActivityContextWrapper(ContextThemeWrapper(context, R.style.WidgetContainerTheme)) widgetCell = - LayoutInflater.from(uiContext).inflate(R.layout.widget_cell, null) as WidgetCell + LayoutInflater.from( + ActivityContextWrapper( + ContextThemeWrapper( + context, + com.android.launcher3.R.style.WidgetContainerTheme + ) + ) + ) + .inflate(com.android.launcher3.R.layout.widget_cell, null) as WidgetCell appWidgetProviderInfo = AppWidgetProviderInfo() .apply { @@ -78,54 +74,95 @@ class GeneratedPreviewTest { providerInfo = ActivityInfo().apply { applicationInfo = ApplicationInfo() } } .let { LauncherAppWidgetProviderInfo.fromProviderInfo(context, it) } - - val widgetManager = context.spyService(AppWidgetManager::class.java) - doAnswer { i -> - generatedPreview.takeIf { - i.arguments[0] == appWidgetProviderInfo.provider && - i.arguments[1] == appWidgetProviderInfo.user && - i.arguments[2] == WIDGET_CATEGORY_HOME_SCREEN - } + helper = + object : WidgetManagerHelper(context) { + override fun loadGeneratedPreview( + info: AppWidgetProviderInfo, + widgetCategory: Int + ) = + generatedPreview.takeIf { + info === appWidgetProviderInfo && + widgetCategory == WIDGET_CATEGORY_HOME_SCREEN + } } - .whenever(widgetManager) - .getWidgetPreview(any(), any(), any()) createWidgetItem() } - @After - fun tearDown() { - context.destroy() - } - private fun createWidgetItem() { Executors.MODEL_EXECUTOR.submit { - val idp = context.appComponent.idp - widgetItem = WidgetItem(appWidgetProviderInfo, idp, iconCache, context) + val idp = InvariantDeviceProfile() + widgetItem = + WidgetItem( + appWidgetProviderInfo, + idp, + IconCache(context, idp, null, IconProvider(context)), + context, + helper, + ) } .get() } @Test + @RequiresFlagsEnabled(FLAG_ENABLE_GENERATED_PREVIEWS) + fun widgetItem_hasGeneratedPreview() { + assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_HOME_SCREEN)).isTrue() + assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_KEYGUARD)).isFalse() + assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_SEARCHBOX)).isFalse() + } + + @Test + @RequiresFlagsEnabled(FLAG_ENABLE_GENERATED_PREVIEWS) fun widgetItem_hasGeneratedPreview_noPreview() { appWidgetProviderInfo.generatedPreviewCategories = 0 createWidgetItem() - val preview = DatabaseWidgetPreviewLoader(uiContext).generatePreviewInfoBg(widgetItem, 1, 1) - assertThat(preview.remoteViews).isNull() + assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_HOME_SCREEN)).isFalse() + assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_KEYGUARD)).isFalse() + assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_SEARCHBOX)).isFalse() } @Test + @RequiresFlagsEnabled(FLAG_ENABLE_GENERATED_PREVIEWS) + fun widgetItem_hasGeneratedPreview_nullPreview() { + appWidgetProviderInfo.generatedPreviewCategories = + WIDGET_CATEGORY_HOME_SCREEN or WIDGET_CATEGORY_KEYGUARD + createWidgetItem() + assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_HOME_SCREEN)).isTrue() + // loadGeneratedPreview returns null for KEYGUARD, so this should still be false. + assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_KEYGUARD)).isFalse() + assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_SEARCHBOX)).isFalse() + } + + @Test + @RequiresFlagsDisabled(FLAG_ENABLE_GENERATED_PREVIEWS) + fun widgetItem_hasGeneratedPreview_flagDisabled() { + assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_HOME_SCREEN)).isFalse() + assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_KEYGUARD)).isFalse() + assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_SEARCHBOX)).isFalse() + } + + @Test + @RequiresFlagsEnabled(FLAG_ENABLE_GENERATED_PREVIEWS) fun widgetItem_getGeneratedPreview() { - val preview = DatabaseWidgetPreviewLoader(uiContext).generatePreviewInfoBg(widgetItem, 1, 1) - assertThat(preview.remoteViews).isEqualTo(generatedPreview) + val preview = widgetItem.generatedPreviews.get(WIDGET_CATEGORY_HOME_SCREEN) + assertThat(preview).isEqualTo(generatedPreview) } @Test + @RequiresFlagsEnabled(FLAG_ENABLE_GENERATED_PREVIEWS) fun widgetCell_showGeneratedPreview() { widgetCell.applyFromCellItem(widgetItem) - TestUtil.runOnExecutorSync(DatabaseWidgetPreviewLoader.getLoaderExecutor()) {} - TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {} + DatabaseWidgetPreviewLoader.getLoaderExecutor().submit {}.get() assertThat(widgetCell.appWidgetHostViewPreview).isNotNull() assertThat(widgetCell.appWidgetHostViewPreview?.appWidgetInfo) .isEqualTo(appWidgetProviderInfo) } + + @Test + @RequiresFlagsDisabled(FLAG_ENABLE_GENERATED_PREVIEWS) + fun widgetCell_showGeneratedPreview_flagDisabled() { + widgetCell.applyFromCellItem(widgetItem) + DatabaseWidgetPreviewLoader.getLoaderExecutor().submit {}.get() + assertThat(widgetCell.appWidgetHostViewPreview).isNull() + } } diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfoTest.java b/tests/multivalentTests/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfoTest.java index b3fd0f750f..48cf3df0e5 100644 --- a/tests/multivalentTests/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfoTest.java +++ b/tests/multivalentTests/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfoTest.java @@ -15,12 +15,15 @@ */ package com.android.launcher3.widget; +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; import android.appwidget.AppWidgetHostView; +import android.content.Context; import android.graphics.Point; import android.graphics.Rect; @@ -29,14 +32,16 @@ import androidx.test.filters.SmallTest; import com.android.launcher3.DeviceProfile; import com.android.launcher3.InvariantDeviceProfile; -import com.android.launcher3.util.SandboxApplication; +import com.android.launcher3.LauncherAppState; -import org.junit.Rule; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; +import java.util.ArrayList; import java.util.Collections; +import java.util.List; @SmallTest @RunWith(AndroidJUnit4.class) @@ -47,7 +52,12 @@ public final class LauncherAppWidgetProviderInfoTest { private static final int NUM_OF_COLS = 4; private static final int NUM_OF_ROWS = 5; - @Rule public SandboxApplication mContext = new SandboxApplication(); + private Context mContext; + + @Before + public void setUp() { + mContext = getApplicationContext(); + } @Test public void initSpans_minWidthSmallerThanCellWidth_shouldInitializeSpansToOne() { @@ -246,9 +256,8 @@ public final class LauncherAppWidgetProviderInfoTest { } private InvariantDeviceProfile createIDP() { - InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(mContext); - - DeviceProfile dp = idp.getDeviceProfile(mContext).copy(mContext); + DeviceProfile dp = LauncherAppState.getIDP(mContext) + .getDeviceProfile(mContext).copy(mContext); DeviceProfile profile = Mockito.spy(dp); doAnswer(i -> { ((Point) i.getArgument(0)).set(CELL_SIZE, CELL_SIZE); @@ -258,7 +267,10 @@ public final class LauncherAppWidgetProviderInfoTest { profile.cellLayoutBorderSpacePx = new Point(SPACE_SIZE, SPACE_SIZE); profile.widgetPadding.setEmpty(); - idp.supportedProfiles = Collections.singletonList(profile); + InvariantDeviceProfile idp = new InvariantDeviceProfile(); + List supportedProfiles = new ArrayList<>(idp.supportedProfiles); + supportedProfiles.add(profile); + idp.supportedProfiles = Collections.unmodifiableList(supportedProfiles); idp.numColumns = NUM_OF_COLS; idp.numRows = NUM_OF_ROWS; return idp; diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/picker/OWNERS b/tests/multivalentTests/src/com/android/launcher3/widget/picker/OWNERS index 716ab90043..775b0c7d9b 100644 --- a/tests/multivalentTests/src/com/android/launcher3/widget/picker/OWNERS +++ b/tests/multivalentTests/src/com/android/launcher3/widget/picker/OWNERS @@ -5,6 +5,7 @@ set noparent # # Widget Picker OWNERS +zakcohen@google.com shamalip@google.com wvk@google.com diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProviderTest.java b/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProviderTest.java index 2fbeaf1beb..3024d26af7 100644 --- a/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProviderTest.java +++ b/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProviderTest.java @@ -25,17 +25,19 @@ import static android.content.pm.ApplicationInfo.CATEGORY_UNDEFINED; import static android.content.pm.ApplicationInfo.CATEGORY_VIDEO; import static android.content.pm.ApplicationInfo.FLAG_INSTALLED; +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; import android.appwidget.AppWidgetProviderInfo; import android.content.ComponentName; +import android.content.Context; +import android.content.ContextWrapper; import android.content.pm.ApplicationInfo; import android.content.pm.LauncherApps; import android.os.Process; @@ -49,14 +51,12 @@ import com.android.launcher3.R; import com.android.launcher3.icons.IconCache; import com.android.launcher3.model.WidgetItem; import com.android.launcher3.util.Executors; -import com.android.launcher3.util.SandboxApplication; import com.android.launcher3.util.WidgetUtils; import com.android.launcher3.widget.LauncherAppWidgetProviderInfo; import com.google.common.collect.ImmutableMap; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -90,22 +90,27 @@ public class WidgetRecommendationCategoryProviderTest { private final ApplicationInfo mTestAppInfo = ApplicationInfoBuilder.newBuilder().setPackageName( TEST_PACKAGE).setName(TEST_APP_NAME).build(); - - @Rule public SandboxApplication mContext = new SandboxApplication(); + private Context mContext; @Mock private IconCache mIconCache; private WidgetItem mTestWidgetItem; - + @Mock private LauncherApps mLauncherApps; private InvariantDeviceProfile mTestProfile; @Before public void setUp() { MockitoAnnotations.initMocks(this); - mLauncherApps = mContext.spyService(LauncherApps.class); + mContext = new ContextWrapper(getInstrumentation().getTargetContext()) { + @Override + public Object getSystemService(String name) { + return LAUNCHER_APPS_SERVICE.equals(name) ? mLauncherApps : super.getSystemService( + name); + } + }; mTestAppInfo.flags = FLAG_INSTALLED; - mTestProfile = InvariantDeviceProfile.INSTANCE.get(mContext); + mTestProfile = new InvariantDeviceProfile(); mTestProfile.numRows = 5; mTestProfile.numColumns = 5; createTestWidgetItem(); @@ -126,10 +131,10 @@ public class WidgetRecommendationCategoryProviderTest { testCategories.entrySet()) { mTestAppInfo.category = testCategory.getKey(); - doReturn(mTestAppInfo).when(mLauncherApps).getApplicationInfo( - /*packageName=*/ eq(TEST_PACKAGE), - /*flags=*/ anyInt(), - /*user=*/ eq(Process.myUserHandle())); + when(mLauncherApps.getApplicationInfo(/*packageName=*/ eq(TEST_PACKAGE), + /*flags=*/ eq(0), + /*user=*/ eq(Process.myUserHandle()))) + .thenReturn(mTestAppInfo); WidgetRecommendationCategory category = Executors.MODEL_EXECUTOR.submit(() -> new WidgetRecommendationCategoryProvider().getWidgetRecommendationCategory( @@ -146,12 +151,11 @@ public class WidgetRecommendationCategoryProviderTest { doAnswer(invocation -> widgetLabel).when(mIconCache).getTitleNoCache(any()); - AppWidgetProviderInfo providerInfo = WidgetUtils.createAppWidgetProviderInfo( - ComponentName.createRelative(TEST_PACKAGE, widgetClassName)); + AppWidgetProviderInfo providerInfo = WidgetUtils.createAppWidgetProviderInfo(ComponentName + .createRelative(TEST_PACKAGE, widgetClassName)); LauncherAppWidgetProviderInfo launcherAppWidgetProviderInfo = - spy(LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, providerInfo)); - doReturn(Process.myUserHandle()).when(launcherAppWidgetProviderInfo).getProfile(); + LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, providerInfo); launcherAppWidgetProviderInfo.spanX = 2; launcherAppWidgetProviderInfo.spanY = 2; launcherAppWidgetProviderInfo.label = widgetLabel; diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java b/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java index 767ab63ad4..d4e061a161 100644 --- a/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java +++ b/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java @@ -15,6 +15,8 @@ */ package com.android.launcher3.widget.picker; +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -40,19 +42,17 @@ import androidx.test.filters.SmallTest; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.R; import com.android.launcher3.icons.BitmapInfo; +import com.android.launcher3.icons.ComponentWithLabel; import com.android.launcher3.icons.IconCache; -import com.android.launcher3.icons.cache.CachedObject; import com.android.launcher3.model.WidgetItem; import com.android.launcher3.model.data.PackageItemInfo; import com.android.launcher3.util.ActivityContextWrapper; import com.android.launcher3.util.PackageUserKey; -import com.android.launcher3.util.SandboxApplication; import com.android.launcher3.util.WidgetUtils; import com.android.launcher3.widget.LauncherAppWidgetProviderInfo; import com.android.launcher3.widget.model.WidgetsListHeaderEntry; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -67,7 +67,6 @@ public final class WidgetsListHeaderViewHolderBinderTest { private static final String TEST_PACKAGE = "com.google.test"; private static final String APP_NAME = "Test app"; - @Rule public SandboxApplication app = new SandboxApplication(); private Context mContext; private WidgetsListHeaderViewHolderBinder mViewHolderBinder; private InvariantDeviceProfile mTestProfile; @@ -81,14 +80,14 @@ public final class WidgetsListHeaderViewHolderBinderTest { public void setUp() { MockitoAnnotations.initMocks(this); - mContext = new ActivityContextWrapper(new ContextThemeWrapper( - app, R.style.WidgetContainerTheme)); - mTestProfile = InvariantDeviceProfile.INSTANCE.get(app); + mContext = new ActivityContextWrapper(new ContextThemeWrapper(getApplicationContext(), + R.style.WidgetContainerTheme)); + mTestProfile = new InvariantDeviceProfile(); mTestProfile.numRows = 5; mTestProfile.numColumns = 5; doAnswer(invocation -> { - CachedObject componentWithLabel = invocation.getArgument(0); + ComponentWithLabel componentWithLabel = (ComponentWithLabel) invocation.getArgument(0); return componentWithLabel.getComponent().getShortClassName(); }).when(mIconCache).getTitleNoCache(any()); mViewHolderBinder = new WidgetsListHeaderViewHolderBinder( diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java b/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java index e6f13a69da..e1cc010e61 100644 --- a/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java +++ b/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java @@ -15,6 +15,7 @@ */ package com.android.launcher3.widget.picker; +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; import static com.google.common.truth.Truth.assertThat; @@ -44,20 +45,19 @@ import androidx.test.filters.SmallTest; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.R; import com.android.launcher3.icons.BitmapInfo; +import com.android.launcher3.icons.ComponentWithLabel; import com.android.launcher3.icons.IconCache; -import com.android.launcher3.icons.cache.CachedObject; import com.android.launcher3.model.WidgetItem; import com.android.launcher3.model.data.PackageItemInfo; import com.android.launcher3.util.ActivityContextWrapper; -import com.android.launcher3.util.SandboxApplication; import com.android.launcher3.util.WidgetUtils; import com.android.launcher3.widget.DatabaseWidgetPreviewLoader; import com.android.launcher3.widget.LauncherAppWidgetProviderInfo; import com.android.launcher3.widget.WidgetCell; +import com.android.launcher3.widget.WidgetManagerHelper; import com.android.launcher3.widget.model.WidgetsListContentEntry; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -72,7 +72,6 @@ public final class WidgetsListTableViewHolderBinderTest { private static final String TEST_PACKAGE = "com.google.test"; private static final String APP_NAME = "Test app"; - @Rule public SandboxApplication app = new SandboxApplication(); private Context mContext; private WidgetsListTableViewHolderBinder mViewHolderBinder; private InvariantDeviceProfile mTestProfile; @@ -87,13 +86,13 @@ public final class WidgetsListTableViewHolderBinderTest { @Before public void setUp() { MockitoAnnotations.initMocks(this); - mContext = new ActivityContextWrapper(app); - mTestProfile = InvariantDeviceProfile.INSTANCE.get(app); + mContext = new ActivityContextWrapper(getApplicationContext()); + mTestProfile = new InvariantDeviceProfile(); mTestProfile.numRows = 5; mTestProfile.numColumns = 5; doAnswer(invocation -> { - CachedObject componentWithLabel = invocation.getArgument(0); + ComponentWithLabel componentWithLabel = (ComponentWithLabel) invocation.getArgument(0); return componentWithLabel.getComponent().getShortClassName(); }).when(mIconCache).getTitleNoCache(any()); @@ -144,6 +143,7 @@ public final class WidgetsListTableViewHolderBinderTest { } private List generateWidgetItems(String packageName, int numOfWidgets) { + WidgetManagerHelper widgetManager = new WidgetManagerHelper(mContext); ArrayList widgetItems = new ArrayList<>(); for (int i = 0; i < numOfWidgets; i++) { ComponentName cn = ComponentName.createRelative(packageName, ".SampleWidget" + i); @@ -151,7 +151,7 @@ public final class WidgetsListTableViewHolderBinderTest { widgetItems.add(new WidgetItem( LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, widgetInfo), - mTestProfile, mIconCache, mContext)); + mTestProfile, mIconCache, mContext, widgetManager)); } return widgetItems; } diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/picker/model/WidgetsListContentEntryTest.java b/tests/multivalentTests/src/com/android/launcher3/widget/picker/model/WidgetsListContentEntryTest.java index bd34de605d..755261915d 100644 --- a/tests/multivalentTests/src/com/android/launcher3/widget/picker/model/WidgetsListContentEntryTest.java +++ b/tests/multivalentTests/src/com/android/launcher3/widget/picker/model/WidgetsListContentEntryTest.java @@ -33,16 +33,14 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.launcher3.InvariantDeviceProfile; +import com.android.launcher3.icons.ComponentWithLabel; import com.android.launcher3.icons.IconCache; -import com.android.launcher3.icons.cache.CachedObject; import com.android.launcher3.model.WidgetItem; import com.android.launcher3.model.data.PackageItemInfo; -import com.android.launcher3.util.SandboxApplication; import com.android.launcher3.widget.LauncherAppWidgetProviderInfo; import com.android.launcher3.widget.model.WidgetsListContentEntry; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -66,7 +64,6 @@ public final class WidgetsListContentEntryTest { private final ComponentName mWidget3 = ComponentName.createRelative(PACKAGE_NAME, ".mWidget3"); private final Map mWidgetsToLabels = new HashMap(); - @Rule public SandboxApplication app = new SandboxApplication(); @Mock private IconCache mIconCache; private InvariantDeviceProfile mTestProfile; @@ -79,12 +76,12 @@ public final class WidgetsListContentEntryTest { mWidgetsToLabels.put(mWidget2, "Dog"); mWidgetsToLabels.put(mWidget3, "Bird"); - mTestProfile = InvariantDeviceProfile.INSTANCE.get(app); + mTestProfile = new InvariantDeviceProfile(); mTestProfile.numRows = 5; mTestProfile.numColumns = 5; doAnswer(invocation -> { - CachedObject componentWithLabel = invocation.getArgument(0); + ComponentWithLabel componentWithLabel = (ComponentWithLabel) invocation.getArgument(0); return mWidgetsToLabels.get(componentWithLabel.getComponent()); }).when(mIconCache).getTitleNoCache(any()); } diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithmTest.java b/tests/multivalentTests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithmTest.java index 0cdda3a127..0370a6b3fc 100644 --- a/tests/multivalentTests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithmTest.java +++ b/tests/multivalentTests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithmTest.java @@ -41,20 +41,18 @@ import androidx.test.filters.SmallTest; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.icons.BitmapInfo; +import com.android.launcher3.icons.ComponentWithLabel; import com.android.launcher3.icons.IconCache; -import com.android.launcher3.icons.cache.CachedObject; import com.android.launcher3.model.WidgetItem; import com.android.launcher3.model.data.PackageItemInfo; +import com.android.launcher3.popup.PopupDataProvider; import com.android.launcher3.search.SearchCallback; -import com.android.launcher3.util.SandboxApplication; import com.android.launcher3.widget.LauncherAppWidgetProviderInfo; import com.android.launcher3.widget.model.WidgetsListBaseEntry; import com.android.launcher3.widget.model.WidgetsListContentEntry; import com.android.launcher3.widget.model.WidgetsListHeaderEntry; -import com.android.launcher3.widget.picker.search.WidgetsSearchBar.WidgetsSearchDataProvider; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -68,7 +66,6 @@ import java.util.List; @RunWith(AndroidJUnit4.class) public class SimpleWidgetsSearchAlgorithmTest { - @Rule public SandboxApplication app = new SandboxApplication(); @Mock private IconCache mIconCache; private InvariantDeviceProfile mTestProfile; @@ -82,7 +79,7 @@ public class SimpleWidgetsSearchAlgorithmTest { private SimpleWidgetsSearchAlgorithm mSimpleWidgetsSearchAlgorithm; @Mock - private WidgetsSearchDataProvider mDataProvider; + private PopupDataProvider mDataProvider; @Mock private SearchCallback mSearchCallback; @@ -90,10 +87,10 @@ public class SimpleWidgetsSearchAlgorithmTest { public void setUp() throws Exception { MockitoAnnotations.initMocks(this); doAnswer(invocation -> { - CachedObject componentWithLabel = invocation.getArgument(0); + ComponentWithLabel componentWithLabel = (ComponentWithLabel) invocation.getArgument(0); return componentWithLabel.getComponent().getShortClassName(); }).when(mIconCache).getTitleNoCache(any()); - mTestProfile = InvariantDeviceProfile.INSTANCE.get(app); + mTestProfile = new InvariantDeviceProfile(); mTestProfile.numRows = 5; mTestProfile.numColumns = 5; mContext = getApplicationContext(); @@ -109,7 +106,7 @@ public class SimpleWidgetsSearchAlgorithmTest { mSimpleWidgetsSearchAlgorithm = MAIN_EXECUTOR.submit( () -> new SimpleWidgetsSearchAlgorithm(mDataProvider)).get(); - doReturn(Collections.EMPTY_LIST).when(mDataProvider).getWidgets(); + doReturn(Collections.EMPTY_LIST).when(mDataProvider).getAllWidgets(); } @Test @@ -117,7 +114,7 @@ public class SimpleWidgetsSearchAlgorithmTest { doReturn(List.of(mCalendarHeaderEntry, mCalendarContentEntry, mCameraHeaderEntry, mCameraContentEntry, mClockHeaderEntry, mClockContentEntry)) .when(mDataProvider) - .getWidgets(); + .getAllWidgets(); assertEquals(List.of( WidgetsListHeaderEntry.createForSearch( @@ -138,7 +135,7 @@ public class SimpleWidgetsSearchAlgorithmTest { doReturn(List.of(mCalendarHeaderEntry, mCalendarContentEntry, mCameraHeaderEntry, mCameraContentEntry)) .when(mDataProvider) - .getWidgets(); + .getAllWidgets(); assertEquals(List.of( WidgetsListHeaderEntry.createForSearch( @@ -165,7 +162,7 @@ public class SimpleWidgetsSearchAlgorithmTest { doReturn(List.of(mCalendarHeaderEntry, mCalendarContentEntry, mCameraHeaderEntry, mCameraContentEntry, mClockHeaderEntry, mClockContentEntry)) .when(mDataProvider) - .getWidgets(); + .getAllWidgets(); mSimpleWidgetsSearchAlgorithm.doSearch("Ca", mSearchCallback); getInstrumentation().waitForIdleSync(); verify(mSearchCallback).onSearchResult( diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/picker/util/WidgetPreviewContainerSizesTest.kt b/tests/multivalentTests/src/com/android/launcher3/widget/picker/util/WidgetPreviewContainerSizesTest.kt index 2452a88b14..7b629bfe89 100644 --- a/tests/multivalentTests/src/com/android/launcher3/widget/picker/util/WidgetPreviewContainerSizesTest.kt +++ b/tests/multivalentTests/src/com/android/launcher3/widget/picker/util/WidgetPreviewContainerSizesTest.kt @@ -25,7 +25,6 @@ import androidx.test.filters.SmallTest import com.android.launcher3.DeviceProfile import com.android.launcher3.InvariantDeviceProfile import com.android.launcher3.LauncherAppState -import com.android.launcher3.dagger.LauncherComponentProvider.appComponent import com.android.launcher3.icons.IconCache import com.android.launcher3.model.WidgetItem import com.android.launcher3.util.ActivityContextWrapper @@ -54,7 +53,7 @@ class WidgetPreviewContainerSizesTest { context = ActivityContextWrapper(ApplicationProvider.getApplicationContext()) testInvariantProfile = LauncherAppState.getIDP(context) widgetItemInvariantProfile = - context.appComponent.idp.apply { + InvariantDeviceProfile().apply { numRows = TEST_GRID_SIZE numColumns = TEST_GRID_SIZE } @@ -86,8 +85,7 @@ class WidgetPreviewContainerSizesTest { Point(1, 1) to WidgetPreviewContainerSize(1, 1), // 2x1 Point(2, 1) to WidgetPreviewContainerSize(2, 1), - // 3x1 - Point(3, 1) to WidgetPreviewContainerSize(3, 1), + Point(3, 1) to WidgetPreviewContainerSize(2, 1), // 4x1 Point(4, 1) to WidgetPreviewContainerSize(4, 1), // 2x2 @@ -144,13 +142,13 @@ class WidgetPreviewContainerSizesTest { widgetSize: Point, context: Context, invariantDeviceProfile: InvariantDeviceProfile, - iconCache: IconCache, + iconCache: IconCache ): WidgetItem { val providerInfo = createAppWidgetProviderInfo( ComponentName.createRelative( TEST_PACKAGE, - /*cls=*/ ".WidgetProvider_" + widgetSize.x + "x" + widgetSize.y, + /*cls=*/ ".WidgetProvider_" + widgetSize.x + "x" + widgetSize.y ) ) val widgetInfo = diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/picker/util/WidgetsTableUtilsTest.java b/tests/multivalentTests/src/com/android/launcher3/widget/picker/util/WidgetsTableUtilsTest.java index a17e472d2f..b2cb26613d 100644 --- a/tests/multivalentTests/src/com/android/launcher3/widget/picker/util/WidgetsTableUtilsTest.java +++ b/tests/multivalentTests/src/com/android/launcher3/widget/picker/util/WidgetsTableUtilsTest.java @@ -15,6 +15,8 @@ */ package com.android.launcher3.widget.picker.util; +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; + import static com.android.launcher3.util.WidgetUtils.createAppWidgetProviderInfo; import static com.google.common.truth.Truth.assertThat; @@ -26,6 +28,7 @@ import static org.mockito.Mockito.when; import android.appwidget.AppWidgetProviderInfo; import android.content.ComponentName; import android.content.Context; +import android.content.pm.PackageManager; import android.graphics.Point; import android.graphics.drawable.Drawable; import android.os.UserHandle; @@ -36,18 +39,15 @@ import androidx.test.filters.SmallTest; import com.android.launcher3.DeviceProfile; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.LauncherAppState; +import com.android.launcher3.icons.ComponentWithLabel; import com.android.launcher3.icons.IconCache; -import com.android.launcher3.icons.cache.BaseIconCache; -import com.android.launcher3.icons.cache.CachedObject; import com.android.launcher3.model.WidgetItem; import com.android.launcher3.pm.ShortcutConfigActivityInfo; import com.android.launcher3.util.ActivityContextWrapper; -import com.android.launcher3.util.SandboxApplication; import com.android.launcher3.widget.LauncherAppWidgetProviderInfo; import com.android.launcher3.widget.util.WidgetsTableUtils; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -68,8 +68,6 @@ public final class WidgetsTableUtilsTest { private static final int NUM_OF_COLS = 5; private static final int NUM_OF_ROWS = 5; - @Rule public SandboxApplication app = new SandboxApplication(); - @Mock private IconCache mIconCache; @@ -91,8 +89,9 @@ public final class WidgetsTableUtilsTest { public void setUp() { MockitoAnnotations.initMocks(this); - mContext = new ActivityContextWrapper(app); - mTestInvariantProfile = InvariantDeviceProfile.INSTANCE.get(app); + mContext = new ActivityContextWrapper(getApplicationContext()); + + mTestInvariantProfile = new InvariantDeviceProfile(); mTestInvariantProfile.numColumns = NUM_OF_COLS; mTestInvariantProfile.numRows = NUM_OF_ROWS; @@ -100,7 +99,7 @@ public final class WidgetsTableUtilsTest { initTestWidgets(); initTestShortcuts(); - doAnswer(invocation -> ((CachedObject) invocation.getArgument(0)) + doAnswer(invocation -> ((ComponentWithLabel) invocation.getArgument(0)) .getComponent().getPackageName()) .when(mIconCache).getTitleNoCache(any()); } @@ -281,31 +280,32 @@ public final class WidgetsTableUtilsTest { } private void initTestShortcuts() { + PackageManager packageManager = mContext.getPackageManager(); mShortcut1 = new WidgetItem(new TestShortcutConfigActivityInfo( ComponentName.createRelative(TEST_PACKAGE, ".shortcut1"), UserHandle.CURRENT), - mIconCache); + mIconCache, packageManager); mShortcut2 = new WidgetItem(new TestShortcutConfigActivityInfo( ComponentName.createRelative(TEST_PACKAGE, ".shortcut2"), UserHandle.CURRENT), - mIconCache); + mIconCache, packageManager); mShortcut3 = new WidgetItem(new TestShortcutConfigActivityInfo( ComponentName.createRelative(TEST_PACKAGE, ".shortcut3"), UserHandle.CURRENT), - mIconCache); + mIconCache, packageManager); } private final class TestShortcutConfigActivityInfo extends ShortcutConfigActivityInfo { TestShortcutConfigActivityInfo(ComponentName componentName, UserHandle user) { - super(componentName, user, mContext); + super(componentName, user); } @Override - public Drawable getFullResIcon(BaseIconCache cache) { + public Drawable getFullResIcon(IconCache cache) { return null; } @Override - public CharSequence getLabel() { + public CharSequence getLabel(PackageManager pm) { return null; } } diff --git a/tests/shared/com/android/launcher3/testing/OWNERS b/tests/shared/com/android/launcher3/testing/OWNERS new file mode 100644 index 0000000000..02e8ebcaba --- /dev/null +++ b/tests/shared/com/android/launcher3/testing/OWNERS @@ -0,0 +1,4 @@ +vadimt@google.com +sunnygoyal@google.com +winsonc@google.com +hyunyoungs@google.com diff --git a/tests/src/com/android/launcher3/LauncherIntentTest.java b/tests/src/com/android/launcher3/LauncherIntentTest.java index a3d9614560..aeeb42a363 100644 --- a/tests/src/com/android/launcher3/LauncherIntentTest.java +++ b/tests/src/com/android/launcher3/LauncherIntentTest.java @@ -23,27 +23,21 @@ import android.content.Intent; import android.platform.test.annotations.LargeTest; import android.view.KeyEvent; -import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.runner.AndroidJUnit4; import com.android.launcher3.allapps.ActivityAllAppsContainerView; import com.android.launcher3.allapps.SearchRecyclerView; -import com.android.launcher3.util.BaseLauncherActivityTest; +import com.android.launcher3.ui.AbstractLauncherUiTest; -import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @LargeTest @RunWith(AndroidJUnit4.class) -public class LauncherIntentTest extends BaseLauncherActivityTest { +public class LauncherIntentTest extends AbstractLauncherUiTest { public final Intent allAppsIntent = new Intent(Intent.ACTION_ALL_APPS); - @Before - public void setUp() { - loadLauncherSync(); - } - @Test public void testAllAppsIntent() { // Try executing ALL_APPS intent @@ -51,6 +45,7 @@ public class LauncherIntentTest extends BaseLauncherActivityTest { // A-Z view with Main adapter should be loaded assertOnMainAdapterAToZView(); + // Try Moving to search view now moveToSearchView(); // Try executing ALL_APPS intent @@ -68,14 +63,12 @@ public class LauncherIntentTest extends BaseLauncherActivityTest { // Search view should be in focus waitForLauncherCondition("Search view is not in focus.", launcher -> launcher.getAppsView().getSearchView().hasFocus()); - - injectKeyEvent(KeyEvent.KEYCODE_C, true); + mLauncher.pressAndHoldKeyCode(KeyEvent.KEYCODE_C, 0); // Upon key press, search recycler view should be loaded waitForLauncherCondition("Search view not active.", launcher -> launcher.getAppsView().getActiveRecyclerView() instanceof SearchRecyclerView); - - injectKeyEvent(KeyEvent.KEYCODE_C, false); + mLauncher.unpressKeyCode(KeyEvent.KEYCODE_C, 0); } // Checks if main adapter view is selected, search bar is out of focus and scroller is at start. diff --git a/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java b/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java new file mode 100644 index 0000000000..57117cb1c6 --- /dev/null +++ b/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java @@ -0,0 +1,222 @@ +/* + * 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.launcher3.allapps; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; + +import static com.android.launcher3.allapps.UserProfileManager.STATE_DISABLED; +import static com.android.launcher3.allapps.UserProfileManager.STATE_ENABLED; +import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED; +import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; +import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL; +import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.content.pm.LauncherApps; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.res.Resources; +import android.os.Process; +import android.os.UserHandle; +import android.os.UserManager; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.launcher3.logging.StatsLogManager; +import com.android.launcher3.pm.UserCache; +import com.android.launcher3.util.ActivityContextWrapper; +import com.android.launcher3.util.ApiWrapper; +import com.android.launcher3.util.UserIconInfo; +import com.android.launcher3.util.rule.TestStabilityRule; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.Arrays; + +@RunWith(AndroidJUnit4.class) +public class PrivateProfileManagerTest { + + @Rule(order = 0) + public TestRule testStabilityRule = new TestStabilityRule(); + + private static final UserHandle MAIN_HANDLE = Process.myUserHandle(); + private static final UserHandle PRIVATE_HANDLE = new UserHandle(11); + private static final UserIconInfo MAIN_ICON_INFO = + new UserIconInfo(MAIN_HANDLE, UserIconInfo.TYPE_MAIN); + private static final UserIconInfo PRIVATE_ICON_INFO = + new UserIconInfo(PRIVATE_HANDLE, UserIconInfo.TYPE_PRIVATE); + + private PrivateProfileManager mPrivateProfileManager; + @Mock + private ActivityAllAppsContainerView mAllApps; + @Mock + private StatsLogManager mStatsLogManager; + @Mock + private UserCache mUserCache; + @Mock + private UserManager mUserManager; + @Mock + private Context mContext; + @Mock + private AllAppsStore mAllAppsStore; + @Mock + private PackageManager mPackageManager; + @Mock + private LauncherApps mLauncherApps; + @Mock + private AllAppsRecyclerView mAllAppsRecyclerView; + @Mock + private Resources mResources; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + when(mUserCache.getUserProfiles()) + .thenReturn(Arrays.asList(MAIN_HANDLE, PRIVATE_HANDLE)); + when(mUserCache.getUserInfo(Process.myUserHandle())).thenReturn(MAIN_ICON_INFO); + when(mUserCache.getUserInfo(PRIVATE_HANDLE)).thenReturn(PRIVATE_ICON_INFO); + when(mAllApps.getContext()).thenReturn(mContext); + when(mContext.getResources()).thenReturn(mResources); + when(mContext.getApplicationContext()).thenReturn(getApplicationContext()); + when(mAllApps.getAppsStore()).thenReturn(mAllAppsStore); + when(mAllApps.getActiveRecyclerView()).thenReturn(mAllAppsRecyclerView); + when(mContext.getPackageManager()).thenReturn(mPackageManager); + when(mPackageManager.resolveActivity(any(), any())).thenReturn(new ResolveInfo()); + when(mContext.getSystemService(LauncherApps.class)).thenReturn(mLauncherApps); + when(mLauncherApps.getAppMarketActivityIntent(any(), any())).thenReturn(PendingIntent + .getActivity(new ActivityContextWrapper(getApplicationContext()), 0, + new Intent(), PendingIntent.FLAG_IMMUTABLE).getIntentSender()); + when(mContext.getPackageName()) + .thenReturn("com.android.launcher3.tests.privateProfileManager"); + when(mLauncherApps.getPreInstalledSystemPackages(any())).thenReturn(new ArrayList<>()); + mPrivateProfileManager = new PrivateProfileManager(mUserManager, + mAllApps, mStatsLogManager, mUserCache); + } + + @Test + public void lockPrivateProfile_requestsQuietModeAsTrue() throws Exception { + when(mAllAppsStore.hasModelFlag(FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED)).thenReturn(false); + + mPrivateProfileManager.setQuietMode(true /* lock */); + + awaitTasksCompleted(); + Mockito.verify(mUserManager).requestQuietModeEnabled(true, PRIVATE_HANDLE); + } + + @Test + public void unlockPrivateProfile_requestsQuietModeAsFalse() throws Exception { + when(mAllAppsStore.hasModelFlag(FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED)).thenReturn(true); + + mPrivateProfileManager.setQuietMode(false /* unlock */); + + awaitTasksCompleted(); + Mockito.verify(mUserManager).requestQuietModeEnabled(false, PRIVATE_HANDLE); + } + + @Test + public void quietModeFlagPresent_privateSpaceIsResetToDisabled() { + PrivateProfileManager privateProfileManager = spy(mPrivateProfileManager); + doNothing().when(privateProfileManager).addPrivateSpaceDecorator(anyInt()); + doNothing().when(privateProfileManager).executeLock(); + doReturn(mAllAppsRecyclerView).when(privateProfileManager).getMainRecyclerView(); + when(mAllAppsStore.hasModelFlag(FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED)) + .thenReturn(false, true); + + // In first call the state should be disabled. + privateProfileManager.reset(); + assertEquals("Profile State is not Disabled", STATE_ENABLED, + privateProfileManager.getCurrentState()); + + // In the next call the state should be disabled. + privateProfileManager.reset(); + assertEquals("Profile State is not Disabled", STATE_DISABLED, + privateProfileManager.getCurrentState()); + } + + @Test + public void transitioningToUnlocked_resetCallsPostUnlock() throws Exception { + PrivateProfileManager privateProfileManager = spy(mPrivateProfileManager); + doNothing().when(privateProfileManager).addPrivateSpaceDecorator(anyInt()); + doReturn(mAllAppsRecyclerView).when(privateProfileManager).getMainRecyclerView(); + when(mAllAppsStore.hasModelFlag(FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED)) + .thenReturn(false); + doNothing().when(privateProfileManager).expandPrivateSpace(); + when(privateProfileManager.getCurrentState()).thenReturn(STATE_DISABLED); + + privateProfileManager.setQuietMode(false /* unlock */); + privateProfileManager.reset(); + + awaitTasksCompleted(); + Mockito.verify(privateProfileManager).postUnlock(); + } + + @Test + public void transitioningToLocked_resetCallsExecuteLock() throws Exception { + PrivateProfileManager privateProfileManager = spy(mPrivateProfileManager); + doNothing().when(privateProfileManager).addPrivateSpaceDecorator(anyInt()); + doNothing().when(privateProfileManager).executeLock(); + doReturn(mAllAppsRecyclerView).when(privateProfileManager).getMainRecyclerView(); + when(mAllAppsStore.hasModelFlag(FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED)) + .thenReturn(true); + doNothing().when(privateProfileManager).expandPrivateSpace(); + when(privateProfileManager.getCurrentState()).thenReturn(STATE_ENABLED); + + privateProfileManager.setQuietMode(true /* lock */); + privateProfileManager.reset(); + + awaitTasksCompleted(); + Mockito.verify(privateProfileManager).executeLock(); + } + + @Test + @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/339109319 + public void openPrivateSpaceSettings_triggersCorrectIntent() { + Intent expectedIntent = ApiWrapper.INSTANCE.get(mContext).getPrivateSpaceSettingsIntent(); + ArgumentCaptor acIntent = ArgumentCaptor.forClass(Intent.class); + mPrivateProfileManager.setPrivateSpaceSettingsAvailable(true); + + mContext.startActivity(expectedIntent); + + Mockito.verify(mContext).startActivity(acIntent.capture()); + assertEquals("Intent Action is different", + expectedIntent == null ? null : expectedIntent.toUri(0), + acIntent.getValue() == null ? null : acIntent.getValue().toUri(0)); + } + + private static void awaitTasksCompleted() throws Exception { + UI_HELPER_EXECUTOR.submit(() -> null).get(); + } +} diff --git a/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewTest.java b/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewTest.java new file mode 100644 index 0000000000..eac7f63fb9 --- /dev/null +++ b/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewTest.java @@ -0,0 +1,467 @@ +/* + * 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.launcher3.allapps; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; + +import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_PRIVATE_SPACE_HEADER; +import static com.android.launcher3.allapps.UserProfileManager.STATE_DISABLED; +import static com.android.launcher3.allapps.UserProfileManager.STATE_ENABLED; +import static com.android.launcher3.allapps.UserProfileManager.STATE_TRANSITION; +import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.AdditionalAnswers.answer; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.os.Process; +import android.os.UserHandle; +import android.os.UserManager; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.launcher3.R; +import com.android.launcher3.logging.StatsLogManager; +import com.android.launcher3.model.data.AppInfo; +import com.android.launcher3.pm.UserCache; +import com.android.launcher3.util.ActivityContextWrapper; +import com.android.launcher3.util.UserIconInfo; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.function.Predicate; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class PrivateSpaceHeaderViewTest { + + private static final UserHandle MAIN_HANDLE = Process.myUserHandle(); + private static final UserHandle PRIVATE_HANDLE = new UserHandle(11); + private static final UserIconInfo MAIN_ICON_INFO = + new UserIconInfo(MAIN_HANDLE, UserIconInfo.TYPE_MAIN); + private static final UserIconInfo PRIVATE_ICON_INFO = + new UserIconInfo(PRIVATE_HANDLE, UserIconInfo.TYPE_PRIVATE); + private static final String CAMERA_PACKAGE_NAME = "com.android.launcher3.tests.camera"; + private static final int CONTAINER_HEADER_ELEMENT_COUNT = 1; + private static final int LOCK_UNLOCK_BUTTON_COUNT = 1; + private static final int PS_SETTINGS_BUTTON_COUNT_VISIBLE = 1; + private static final int PS_SETTINGS_BUTTON_COUNT_INVISIBLE = 0; + private static final int PS_TRANSITION_IMAGE_COUNT = 1; + private static final int NUM_APP_COLS = 4; + private static final int NUM_PRIVATE_SPACE_APPS = 50; + private static final int ALL_APPS_HEIGHT = 10; + private static final int ALL_APPS_CELL_HEIGHT = 1; + private static final int PS_HEADER_HEIGHT = 1; + private static final int BIGGER_PS_HEADER_HEIGHT = 2; + private static final int SCROLL_NO_WHERE = -1; + private static final float HEADER_PROTECTION_HEIGHT = 1F; + + private Context mContext; + private RelativeLayout mPsHeaderLayout; + private AlphabeticalAppsList mAlphabeticalAppsList; + private PrivateProfileManager mPrivateProfileManager; + @Mock + private ActivityAllAppsContainerView mAllApps; + @Mock + private AllAppsStore mAllAppsStore; + @Mock + private UserCache mUserCache; + @Mock + private UserManager mUserManager; + @Mock + private StatsLogManager mStatsLogManager; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = new ActivityContextWrapper(getApplicationContext()); + when(mAllApps.getContext()).thenReturn(mContext); + when(mUserCache.getUserInfo(PRIVATE_HANDLE)).thenReturn(PRIVATE_ICON_INFO); + when(mUserCache.getUserProfiles()) + .thenReturn(Arrays.asList(MAIN_HANDLE, PRIVATE_HANDLE)); + when(mUserCache.getUserInfo(Process.myUserHandle())).thenReturn(MAIN_ICON_INFO); + mPrivateProfileManager = new PrivateProfileManager(mUserManager, + mAllApps, mStatsLogManager, mUserCache); + mPsHeaderLayout = (RelativeLayout) LayoutInflater.from(mContext).inflate( + R.layout.private_space_header, null); + } + + @Test + public void privateProfileDisabled_psHeaderContainsLockedView() throws Exception { + Bitmap unlockButton = getBitmap(mContext.getDrawable(R.drawable.ic_lock)); + PrivateProfileManager privateProfileManager = spy(mPrivateProfileManager); + when(privateProfileManager.getCurrentState()).thenReturn(STATE_DISABLED); + privateProfileManager.bindPrivateSpaceHeaderViewElements(mPsHeaderLayout); + awaitTasksCompleted(); + + int totalContainerHeaderView = 0; + int totalLockUnlockButtonView = 0; + for (int i = 0; i < mPsHeaderLayout.getChildCount(); i++) { + View view = mPsHeaderLayout.getChildAt(i); + if (view.getId() == R.id.ps_container_header) { + totalContainerHeaderView += 1; + assertEquals(View.VISIBLE, view.getVisibility()); + } else if (view.getId() == R.id.settingsAndLockGroup) { + ImageView lockIcon = view.findViewById(R.id.lock_icon); + assertTrue(getBitmap(lockIcon.getDrawable()).sameAs(unlockButton)); + assertEquals(View.VISIBLE, lockIcon.getVisibility()); + + // Verify textView shouldn't be showing when disabled. + TextView lockText = view.findViewById(R.id.lock_text); + assertEquals(View.GONE, lockText.getVisibility()); + totalLockUnlockButtonView += 1; + } else { + assertEquals(View.GONE, view.getVisibility()); + } + } + + assertEquals(CONTAINER_HEADER_ELEMENT_COUNT, totalContainerHeaderView); + assertEquals(LOCK_UNLOCK_BUTTON_COUNT, totalLockUnlockButtonView); + } + + @Test + public void privateProfileEnabled_psHeaderContainsUnlockedView() throws Exception { + Bitmap lockImage = getBitmap(mContext.getDrawable(R.drawable.ic_lock)); + Bitmap settingsImage = getBitmap(mContext.getDrawable(R.drawable.ic_ps_settings)); + PrivateProfileManager privateProfileManager = spy(mPrivateProfileManager); + when(privateProfileManager.getCurrentState()).thenReturn(STATE_ENABLED); + when(privateProfileManager.isPrivateSpaceSettingsAvailable()).thenReturn(true); + privateProfileManager.bindPrivateSpaceHeaderViewElements(mPsHeaderLayout); + awaitTasksCompleted(); + + int totalContainerHeaderView = 0; + int totalLockUnlockButtonView = 0; + int totalSettingsImageView = 0; + for (int i = 0; i < mPsHeaderLayout.getChildCount(); i++) { + View view = mPsHeaderLayout.getChildAt(i); + if (view.getId() == R.id.ps_container_header) { + totalContainerHeaderView += 1; + assertEquals(View.VISIBLE, view.getVisibility()); + } else if (view.getId() == R.id.settingsAndLockGroup) { + // Look for settings button. + ImageButton settingsButton = view.findViewById(R.id.ps_settings_button); + assertEquals(View.VISIBLE, settingsButton.getVisibility()); + totalSettingsImageView += 1; + assertTrue(getBitmap(settingsButton.getDrawable()).sameAs(settingsImage)); + + // Look for lock_icon and lock_text. + ImageView lockIcon = view.findViewById(R.id.lock_icon); + assertTrue(getBitmap(lockIcon.getDrawable()).sameAs(lockImage)); + assertEquals(View.VISIBLE, lockIcon.getVisibility()); + TextView lockText = view.findViewById(R.id.lock_text); + assertEquals(View.VISIBLE, lockText.getVisibility()); + totalLockUnlockButtonView += 1; + } else { + assertEquals(View.GONE, view.getVisibility()); + } + } + + assertEquals(CONTAINER_HEADER_ELEMENT_COUNT, totalContainerHeaderView); + assertEquals(LOCK_UNLOCK_BUTTON_COUNT, totalLockUnlockButtonView); + assertEquals(PS_SETTINGS_BUTTON_COUNT_VISIBLE, totalSettingsImageView); + } + + @Test + public void privateProfileEnabledAndNoSettingsIntent_psHeaderContainsUnlockedView() + throws Exception { + Bitmap lockImage = getBitmap(mContext.getDrawable(R.drawable.ic_lock)); + PrivateProfileManager privateProfileManager = spy(mPrivateProfileManager); + when(privateProfileManager.getCurrentState()).thenReturn(STATE_ENABLED); + when(privateProfileManager.isPrivateSpaceSettingsAvailable()).thenReturn(false); + privateProfileManager.bindPrivateSpaceHeaderViewElements(mPsHeaderLayout); + awaitTasksCompleted(); + + int totalContainerHeaderView = 0; + int totalLockUnlockButtonView = 0; + int totalSettingsImageView = 0; + for (int i = 0; i < mPsHeaderLayout.getChildCount(); i++) { + View view = mPsHeaderLayout.getChildAt(i); + if (view.getId() == R.id.ps_container_header) { + totalContainerHeaderView += 1; + assertEquals(View.VISIBLE, view.getVisibility()); + } else if (view.getId() == R.id.settingsAndLockGroup) { + // Ensure there is no settings button. + ImageButton settingsImage = view.findViewById(R.id.ps_settings_button); + assertEquals(View.GONE, settingsImage.getVisibility()); + + // Check lock icon and lock text is there. + ImageView lockIcon = view.findViewById(R.id.lock_icon); + assertTrue(getBitmap(lockIcon.getDrawable()).sameAs(lockImage)); + assertEquals(View.VISIBLE, lockIcon.getVisibility()); + TextView lockText = view.findViewById(R.id.lock_text); + assertEquals(View.VISIBLE, lockText.getVisibility()); + totalLockUnlockButtonView += 1; + } else { + assertEquals(View.GONE, view.getVisibility()); + } + } + + assertEquals(CONTAINER_HEADER_ELEMENT_COUNT, totalContainerHeaderView); + assertEquals(LOCK_UNLOCK_BUTTON_COUNT, totalLockUnlockButtonView); + assertEquals(PS_SETTINGS_BUTTON_COUNT_INVISIBLE, totalSettingsImageView); + } + + @Test + public void privateProfileTransitioning_psHeaderContainsTransitionView() throws Exception { + Bitmap transitionImage = getBitmap(mContext.getDrawable(R.drawable.bg_ps_transition_image)); + PrivateProfileManager privateProfileManager = spy(mPrivateProfileManager); + when(privateProfileManager.getCurrentState()).thenReturn(STATE_TRANSITION); + privateProfileManager.bindPrivateSpaceHeaderViewElements(mPsHeaderLayout); + awaitTasksCompleted(); + + int totalContainerHeaderView = 0; + int totalLockUnlockButtonView = 0; + for (int i = 0; i < mPsHeaderLayout.getChildCount(); i++) { + View view = mPsHeaderLayout.getChildAt(i); + if (view.getId() == R.id.ps_container_header) { + totalContainerHeaderView += 1; + assertEquals(View.VISIBLE, view.getVisibility()); + } else if (view.getId() == R.id.ps_transition_image + && view instanceof ImageView imageView) { + totalLockUnlockButtonView += 1; + assertEquals(View.VISIBLE, view.getVisibility()); + assertTrue(getBitmap(imageView.getDrawable()).sameAs(transitionImage)); + } else if (view.getId() == R.id.settingsAndLockGroup) { + LinearLayout lockUnlockButton = view.findViewById(R.id.ps_lock_unlock_button); + assertEquals(View.GONE, lockUnlockButton.getVisibility()); + } else { + assertEquals(View.GONE, view.getVisibility()); + } + } + + assertEquals(CONTAINER_HEADER_ELEMENT_COUNT, totalContainerHeaderView); + assertEquals(PS_TRANSITION_IMAGE_COUNT, totalLockUnlockButtonView); + } + + @Test + public void scrollForViewToBeVisibleInContainer_withHeader() { + when(mAllAppsStore.getApps()).thenReturn(createAppInfoList()); + PrivateProfileManager privateProfileManager = spy(mPrivateProfileManager); + when(privateProfileManager.getCurrentState()).thenReturn(STATE_ENABLED); + doReturn(splitIntoUserInstalledAndSystemApps()).when(privateProfileManager) + .splitIntoUserInstalledAndSystemApps(any()); + doReturn(0).when(privateProfileManager).addPrivateSpaceHeader(any()); + doAnswer(answer(this::addPrivateSpaceHeader)).when(privateProfileManager) + .addPrivateSpaceHeader(any()); + doNothing().when(privateProfileManager).addPrivateSpaceInstallAppButton(any()); + doReturn(0).when(privateProfileManager).addSystemAppsDivider(any()); + when(mAllApps.getHeight()).thenReturn(ALL_APPS_HEIGHT); + when(mAllApps.getHeaderProtectionHeight()).thenReturn(HEADER_PROTECTION_HEIGHT); + when(mAllApps.isUsingTabs()).thenReturn(true); + mAlphabeticalAppsList = new AlphabeticalAppsList<>(mContext, mAllAppsStore, + null, privateProfileManager); + mAlphabeticalAppsList.setNumAppsPerRowAllApps(NUM_APP_COLS); + mAlphabeticalAppsList.updateItemFilter(info -> info != null + && info.user.equals(MAIN_HANDLE)); + + int rows = (int) (ALL_APPS_HEIGHT - PS_HEADER_HEIGHT - HEADER_PROTECTION_HEIGHT); + int position = rows * NUM_APP_COLS - (NUM_APP_COLS-1) + 1; + + // The number of adapterItems should be the private space apps + one main app + header. + assertEquals(NUM_PRIVATE_SPACE_APPS + 1 + 1, + mAlphabeticalAppsList.getAdapterItems().size()); + assertEquals(position, + privateProfileManager.scrollForHeaderToBeVisibleInContainer( + new AllAppsRecyclerView(mContext), + mAlphabeticalAppsList.getAdapterItems(), + PS_HEADER_HEIGHT, + ALL_APPS_CELL_HEIGHT)); + } + + @Test + public void scrollForViewToBeVisibleInContainer_withHeaderNoTabs() { + when(mAllAppsStore.getApps()).thenReturn(createAppInfoList()); + PrivateProfileManager privateProfileManager = spy(mPrivateProfileManager); + when(privateProfileManager.getCurrentState()).thenReturn(STATE_ENABLED); + doReturn(splitIntoUserInstalledAndSystemApps()).when(privateProfileManager) + .splitIntoUserInstalledAndSystemApps(any()); + doReturn(0).when(privateProfileManager).addPrivateSpaceHeader(any()); + doAnswer(answer(this::addPrivateSpaceHeader)).when(privateProfileManager) + .addPrivateSpaceHeader(any()); + doNothing().when(privateProfileManager).addPrivateSpaceInstallAppButton(any()); + doReturn(0).when(privateProfileManager).addSystemAppsDivider(any()); + when(mAllApps.getHeight()).thenReturn(ALL_APPS_HEIGHT); + when(mAllApps.getHeaderProtectionHeight()).thenReturn(HEADER_PROTECTION_HEIGHT); + when(mAllApps.isUsingTabs()).thenReturn(false); + mAlphabeticalAppsList = new AlphabeticalAppsList<>(mContext, mAllAppsStore, + null, privateProfileManager); + mAlphabeticalAppsList.setNumAppsPerRowAllApps(NUM_APP_COLS); + mAlphabeticalAppsList.updateItemFilter(info -> info != null + && info.user.equals(MAIN_HANDLE)); + + int rows = (int) (ALL_APPS_HEIGHT - PS_HEADER_HEIGHT - HEADER_PROTECTION_HEIGHT) - 1; + int position = rows * NUM_APP_COLS - (NUM_APP_COLS-1) + 1; + + // The number of adapterItems should be the private space apps + one main app + header. + assertEquals(NUM_PRIVATE_SPACE_APPS + 1 + 1, + mAlphabeticalAppsList.getAdapterItems().size()); + assertEquals(position, + privateProfileManager.scrollForHeaderToBeVisibleInContainer( + new AllAppsRecyclerView(mContext), + mAlphabeticalAppsList.getAdapterItems(), + PS_HEADER_HEIGHT, + ALL_APPS_CELL_HEIGHT)); + } + + @Test + public void scrollForViewToBeVisibleInContainer_withHeaderAndLessAppRowSpace() { + when(mAllAppsStore.getApps()).thenReturn(createAppInfoList()); + PrivateProfileManager privateProfileManager = spy(mPrivateProfileManager); + when(privateProfileManager.getCurrentState()).thenReturn(STATE_ENABLED); + doReturn(splitIntoUserInstalledAndSystemApps()).when(privateProfileManager) + .splitIntoUserInstalledAndSystemApps(any()); + doReturn(0).when(privateProfileManager).addPrivateSpaceHeader(any()); + doAnswer(answer(this::addPrivateSpaceHeader)).when(privateProfileManager) + .addPrivateSpaceHeader(any()); + doNothing().when(privateProfileManager).addPrivateSpaceInstallAppButton(any()); + doReturn(0).when(privateProfileManager).addSystemAppsDivider(any()); + when(mAllApps.getHeight()).thenReturn(ALL_APPS_HEIGHT); + when(mAllApps.isUsingTabs()).thenReturn(true); + when(mAllApps.getHeaderProtectionHeight()).thenReturn(HEADER_PROTECTION_HEIGHT); + mAlphabeticalAppsList = new AlphabeticalAppsList<>(mContext, mAllAppsStore, + null, privateProfileManager); + mAlphabeticalAppsList.setNumAppsPerRowAllApps(NUM_APP_COLS); + mAlphabeticalAppsList.updateItemFilter(info -> info != null + && info.user.equals(MAIN_HANDLE)); + + int rows = (int) (ALL_APPS_HEIGHT - BIGGER_PS_HEADER_HEIGHT - HEADER_PROTECTION_HEIGHT); + int position = rows * NUM_APP_COLS - (NUM_APP_COLS-1) + 1; + + // The number of adapterItems should be the private space apps + one main app + header. + assertEquals(NUM_PRIVATE_SPACE_APPS + 1 + 1, + mAlphabeticalAppsList.getAdapterItems().size()); + assertEquals(position, + privateProfileManager.scrollForHeaderToBeVisibleInContainer( + new AllAppsRecyclerView(mContext), + mAlphabeticalAppsList.getAdapterItems(), + BIGGER_PS_HEADER_HEIGHT, + ALL_APPS_CELL_HEIGHT)); + } + + @Test + public void scrollForViewToBeVisibleInContainer_withNoHeader() { + when(mAllAppsStore.getApps()).thenReturn(createAppInfoList()); + PrivateProfileManager privateProfileManager = spy(mPrivateProfileManager); + when(privateProfileManager.getCurrentState()).thenReturn(STATE_ENABLED); + doReturn(splitIntoUserInstalledAndSystemApps()).when(privateProfileManager) + .splitIntoUserInstalledAndSystemApps(any()); + doReturn(0).when(privateProfileManager).addPrivateSpaceHeader(any()); + doNothing().when(privateProfileManager).addPrivateSpaceInstallAppButton(any()); + doReturn(0).when(privateProfileManager).addSystemAppsDivider(any()); + when(mAllApps.getHeight()).thenReturn(ALL_APPS_HEIGHT); + when(mAllApps.getHeaderProtectionHeight()).thenReturn(HEADER_PROTECTION_HEIGHT); + mAlphabeticalAppsList = new AlphabeticalAppsList<>(mContext, mAllAppsStore, + null, privateProfileManager); + mAlphabeticalAppsList.setNumAppsPerRowAllApps(NUM_APP_COLS); + mAlphabeticalAppsList.updateItemFilter(info -> info != null + && info.user.equals(MAIN_HANDLE)); + + // The number of adapterItems should be the private space apps + one main app. + assertEquals(NUM_PRIVATE_SPACE_APPS + 1, + mAlphabeticalAppsList.getAdapterItems().size()); + assertEquals(SCROLL_NO_WHERE, privateProfileManager.scrollForHeaderToBeVisibleInContainer( + new AllAppsRecyclerView(mContext), + mAlphabeticalAppsList.getAdapterItems(), + BIGGER_PS_HEADER_HEIGHT, + ALL_APPS_CELL_HEIGHT)); + } + + private Bitmap getBitmap(Drawable drawable) { + Bitmap result; + if (drawable instanceof BitmapDrawable) { + result = ((BitmapDrawable) drawable).getBitmap(); + } else { + int width = drawable.getIntrinsicWidth(); + int height = drawable.getIntrinsicHeight(); + // Some drawables have no intrinsic width - e.g. solid colours. + if (width <= 0) { + width = 1; + } + if (height <= 0) { + height = 1; + } + + result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(result); + drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); + drawable.draw(canvas); + } + return result; + } + + private static void awaitTasksCompleted() throws Exception { + UI_HELPER_EXECUTOR.submit(() -> null).get(); + } + + private int addPrivateSpaceHeader(List adapterItemList) { + BaseAllAppsAdapter.AdapterItem privateSpaceHeader = + new BaseAllAppsAdapter.AdapterItem(VIEW_TYPE_PRIVATE_SPACE_HEADER); + adapterItemList.add(privateSpaceHeader); + return adapterItemList.size(); + } + + private AppInfo[] createAppInfoList() { + List appInfos = new ArrayList<>(); + ComponentName gmailComponentName = new ComponentName(mContext, + "com.android.launcher3.tests.Activity" + "Gmail"); + AppInfo gmailAppInfo = new + AppInfo(gmailComponentName, "Gmail", MAIN_HANDLE, new Intent()); + appInfos.add(gmailAppInfo); + ComponentName privateCameraComponentName = new ComponentName( + CAMERA_PACKAGE_NAME, "CameraActivity"); + for (int i = 0; i < NUM_PRIVATE_SPACE_APPS; i++) { + AppInfo privateCameraAppInfo = new AppInfo(privateCameraComponentName, + "Private Camera " + i, PRIVATE_HANDLE, new Intent()); + appInfos.add(privateCameraAppInfo); + } + return appInfos.toArray(AppInfo[]::new); + } + + private Predicate splitIntoUserInstalledAndSystemApps() { + return iteminfo -> iteminfo.componentName == null + || !iteminfo.componentName.getPackageName() + .equals(CAMERA_PACKAGE_NAME); + } +} diff --git a/tests/src/com/android/launcher3/allapps/PrivateSpaceSettingsButtonTest.java b/tests/src/com/android/launcher3/allapps/PrivateSpaceSettingsButtonTest.java new file mode 100644 index 0000000000..9537e1c63f --- /dev/null +++ b/tests/src/com/android/launcher3/allapps/PrivateSpaceSettingsButtonTest.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.allapps; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; + +import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PRIVATESPACE; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.launcher3.model.data.AppInfo; +import com.android.launcher3.util.ActivityContextWrapper; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidJUnit4.class) +public class PrivateSpaceSettingsButtonTest { + + private PrivateSpaceSettingsButton mVut; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + Context context = new ActivityContextWrapper(getApplicationContext()); + mVut = new PrivateSpaceSettingsButton(context); + } + + @Test + public void privateSpaceSettingsAppInfo_hasCorrectIdAndContainer() { + AppInfo appInfo = mVut.createPrivateSpaceSettingsAppInfo(); + + assertThat(appInfo.id).isEqualTo(CONTAINER_PRIVATESPACE); + assertThat(appInfo.container).isEqualTo(CONTAINER_PRIVATESPACE); + } +} diff --git a/tests/src/com/android/launcher3/allapps/TaplKeyboardFocusTest.java b/tests/src/com/android/launcher3/allapps/TaplKeyboardFocusTest.java new file mode 100644 index 0000000000..4e627a9d5b --- /dev/null +++ b/tests/src/com/android/launcher3/allapps/TaplKeyboardFocusTest.java @@ -0,0 +1,108 @@ +/* + * 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.launcher3.allapps; + +import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL; +import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import android.view.KeyEvent; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherState; +import com.android.launcher3.tapl.HomeAllApps; +import com.android.launcher3.ui.AbstractLauncherUiTest; +import com.android.launcher3.util.rule.TestStabilityRule; +import com.android.launcher3.views.ActivityContext; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class TaplKeyboardFocusTest extends AbstractLauncherUiTest { + + @Test + public void testAllAppsFocusApp() { + final HomeAllApps allApps = mLauncher.goHome().switchToAllApps(); + assertTrue("Launcher internal state is not All Apps", + isInState(() -> LauncherState.ALL_APPS)); + allApps.freeze(); + try { + mLauncher.pressAndHoldKeyCode(KeyEvent.KEYCODE_DPAD_DOWN, 0); + executeOnLauncher(launcher -> assertNotNull("No focused child.", + launcher.getAppsView().getActiveRecyclerView().getApps().getFocusedChild())); + } finally { + allApps.unfreeze(); + } + } + + @Test + public void testAllAppsExitSearchAndFocusApp() { + final HomeAllApps allApps = mLauncher.goHome().switchToAllApps(); + assertTrue("Launcher internal state is not All Apps", + isInState(() -> LauncherState.ALL_APPS)); + allApps.freeze(); + try { + executeOnLauncher(launcher -> launcher.getAppsView().getSearchView().requestFocus()); + waitForLauncherCondition("Search view does not have focus.", + launcher -> launcher.getAppsView().getSearchView().hasFocus()); + + mLauncher.pressAndHoldKeyCode(KeyEvent.KEYCODE_DPAD_DOWN, 0); + executeOnLauncher(launcher -> assertNotNull("No focused child.", + launcher.getAppsView().getActiveRecyclerView().getApps().getFocusedChild())); + } finally { + allApps.unfreeze(); + } + } + + @Test + public void testAllAppsExitSearchAndFocusSearchResults() { + final HomeAllApps allApps = mLauncher.goHome().switchToAllApps(); + assertTrue("Launcher internal state is not All Apps", + isInState(() -> LauncherState.ALL_APPS)); + allApps.freeze(); + try { + executeOnLauncher(launcher -> launcher.getAppsView().getSearchView().requestFocus()); + waitForLauncherCondition("Search view does not have focus.", + launcher -> launcher.getAppsView().getSearchView().hasFocus()); + + mLauncher.pressAndHoldKeyCode(KeyEvent.KEYCODE_C, 0); + waitForLauncherCondition("Search view not active.", + launcher -> launcher.getAppsView().getActiveRecyclerView() + instanceof SearchRecyclerView); + mLauncher.unpressKeyCode(KeyEvent.KEYCODE_C, 0); + + executeOnLauncher(launcher -> launcher.getAppsView().getSearchUiManager().getEditText() + .hideKeyboard(/* clearFocus= */ false)); + waitForLauncherCondition("Keyboard still visible.", + ActivityContext::isSoftwareKeyboardHidden); + + mLauncher.pressAndHoldKeyCode(KeyEvent.KEYCODE_DPAD_DOWN, 0); + mLauncher.unpressKeyCode(KeyEvent.KEYCODE_DPAD_DOWN, 0); + waitForLauncherCondition("No focused child", launcher -> + launcher.getAppsView().getActiveRecyclerView().getApps().getFocusedChild() + != null); + } finally { + allApps.unfreeze(); + } + } +} diff --git a/tests/src/com/android/launcher3/allapps/TaplOpenCloseAllAppsTest.java b/tests/src/com/android/launcher3/allapps/TaplOpenCloseAllAppsTest.java index c7c9dbb4bb..05a122456f 100644 --- a/tests/src/com/android/launcher3/allapps/TaplOpenCloseAllAppsTest.java +++ b/tests/src/com/android/launcher3/allapps/TaplOpenCloseAllAppsTest.java @@ -25,6 +25,7 @@ import static org.junit.Assume.assumeTrue; import android.content.Intent; import android.platform.test.annotations.PlatinumTest; +import androidx.test.filters.FlakyTest; import androidx.test.platform.app.InstrumentationRegistry; import com.android.launcher3.Launcher; @@ -32,6 +33,7 @@ import com.android.launcher3.LauncherState; import com.android.launcher3.tapl.AllApps; import com.android.launcher3.ui.AbstractLauncherUiTest; import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape; +import com.android.launcher3.util.rule.ScreenRecordRule; import org.junit.Test; @@ -189,6 +191,7 @@ public class TaplOpenCloseAllAppsTest extends AbstractLauncherUiTest { /** * Makes sure that when pressing back when AllApps is open we go back to the Home screen. */ + @FlakyTest(bugId = 256615483) @Test @PortraitLandscape public void testPressBackFromAllAppsToHome() { diff --git a/tests/src/com/android/launcher3/backuprestore/BackupAndRestoreDBSelectionTest.kt b/tests/src/com/android/launcher3/backuprestore/BackupAndRestoreDBSelectionTest.kt index 34d9d40d67..479b201f61 100644 --- a/tests/src/com/android/launcher3/backuprestore/BackupAndRestoreDBSelectionTest.kt +++ b/tests/src/com/android/launcher3/backuprestore/BackupAndRestoreDBSelectionTest.kt @@ -16,17 +16,13 @@ package com.android.launcher3.backuprestore -import android.platform.test.annotations.DisableFlags -import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.SetFlagsRule import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.MediumTest import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import com.android.launcher3.Flags -import com.android.launcher3.LauncherAppState import com.android.launcher3.LauncherPrefs -import com.android.launcher3.model.ModelDelegate -import com.android.launcher3.provider.RestoreDbTask +import com.android.launcher3.model.ModelDbController import com.android.launcher3.util.Executors.MODEL_EXECUTOR import com.android.launcher3.util.TestUtil import com.android.launcher3.util.rule.BackAndRestoreRule @@ -35,7 +31,6 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.mockito.kotlin.mock /** * Makes sure to test {@code RestoreDbTask#removeOldDBs}, we need to remove all the dbs that are not @@ -45,35 +40,21 @@ import org.mockito.kotlin.mock @MediumTest class BackupAndRestoreDBSelectionTest { - @get:Rule var backAndRestoreRule = BackAndRestoreRule() - @get:Rule val setFlagsRule = SetFlagsRule() + @JvmField @Rule var backAndRestoreRule = BackAndRestoreRule() - val modelDelegate = mock() + @JvmField + @Rule + val setFlagsRule = SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT) @Before fun setUp() { setFlagsRule.setFlags(true, Flags.FLAG_ENABLE_NARROW_GRID_RESTORE) } - @EnableFlags(Flags.FLAG_GRID_MIGRATION_REFACTOR) - fun oldDatabasesNotPresentAfterRestoreRefactorFlagEnabled() { - oldDatabasesNotPresentAfterRestore() - } - - @DisableFlags(Flags.FLAG_GRID_MIGRATION_REFACTOR) - fun oldDatabasesNotPresentAfterRestoreRefactorFlagDisabled() { - oldDatabasesNotPresentAfterRestore() - } - @Test fun oldDatabasesNotPresentAfterRestore() { - val dbController = - LauncherAppState.getInstance(getInstrumentation().targetContext).model.modelDbController - if (Flags.gridMigrationRefactor()) { - dbController.attemptMigrateDb(null, modelDelegate) - } else { - dbController.tryMigrateDB(null, modelDelegate) - } + val dbController = ModelDbController(getInstrumentation().targetContext) + dbController.tryMigrateDB(null) TestUtil.runOnExecutorSync(MODEL_EXECUTOR) { assert(backAndRestoreRule.getDatabaseFiles().size == 1) { "There should only be one database after restoring, the last one used. Actual databases ${backAndRestoreRule.getDatabaseFiles()}" @@ -86,13 +67,4 @@ class BackupAndRestoreDBSelectionTest { } } } - - @Test - fun testExistingDbsAndRemovingDbs() { - var existingDbs = RestoreDbTask.existingDbs(getInstrumentation().targetContext) - assert(existingDbs.size == 4) - RestoreDbTask.removeOldDBs(getInstrumentation().targetContext, "launcher_4_by_4.db") - existingDbs = RestoreDbTask.existingDbs(getInstrumentation().targetContext) - assert(existingDbs.size == 1) - } } diff --git a/tests/src/com/android/launcher3/celllayout/TaplReorderWidgetsTest.java b/tests/src/com/android/launcher3/celllayout/TaplReorderWidgetsTest.java new file mode 100644 index 0000000000..28a1325944 --- /dev/null +++ b/tests/src/com/android/launcher3/celllayout/TaplReorderWidgetsTest.java @@ -0,0 +1,312 @@ +/* + * 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. + */ +package com.android.launcher3.celllayout; + +import static android.platform.uiautomator_helpers.DeviceHelpers.getContext; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import android.content.ContentResolver; +import android.content.ContentValues; +import android.graphics.Point; +import android.net.Uri; +import android.util.Log; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.android.launcher3.InvariantDeviceProfile; +import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.MultipageCellLayout; +import com.android.launcher3.celllayout.board.CellLayoutBoard; +import com.android.launcher3.celllayout.board.TestWorkspaceBuilder; +import com.android.launcher3.celllayout.board.WidgetRect; +import com.android.launcher3.tapl.Widget; +import com.android.launcher3.tapl.WidgetResizeFrame; +import com.android.launcher3.ui.AbstractLauncherUiTest; +import com.android.launcher3.util.ModelTestExtensions; +import com.android.launcher3.util.rule.ShellCommandRule; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Assume; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class TaplReorderWidgetsTest extends AbstractLauncherUiTest { + + @Rule + public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind(); + + private static final String TAG = TaplReorderWidgetsTest.class.getSimpleName(); + + private static final List FOLDABLE_GRIDS = List.of("normal", "practical", "reasonable"); + + TestWorkspaceBuilder mWorkspaceBuilder; + + @Before + public void setup() throws Throwable { + mWorkspaceBuilder = new TestWorkspaceBuilder(mTargetContext); + super.setUp(); + } + + @After + public void tearDown() { + ModelTestExtensions.INSTANCE.clearModelDb( + LauncherAppState.getInstance(getContext()).getModel() + ); + } + + /** + * Validate if the given board represent the current CellLayout + **/ + private boolean validateBoard(List testBoards) { + ArrayList workspaceBoards = workspaceToBoards(); + if (workspaceBoards.size() < testBoards.size()) { + return false; + } + for (int i = 0; i < testBoards.size(); i++) { + if (testBoards.get(i).compareTo(workspaceBoards.get(i)) != 0) { + return false; + } + } + return true; + } + + private FavoriteItemsTransaction buildWorkspaceFromBoards(List boards, + FavoriteItemsTransaction transaction) { + for (int i = 0; i < boards.size(); i++) { + CellLayoutBoard board = boards.get(i); + mWorkspaceBuilder.buildFromBoard(board, transaction, i); + } + return transaction; + } + + private void printCurrentWorkspace() { + InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(mTargetContext); + ArrayList boards = workspaceToBoards(); + for (int i = 0; i < boards.size(); i++) { + Log.d(TAG, "Screen number " + i); + Log.d(TAG, ".\n" + boards.get(i).toString(idp.numColumns, idp.numRows)); + } + } + + private ArrayList workspaceToBoards() { + return getFromLauncher(CellLayoutTestUtils::workspaceToBoards); + } + + private WidgetRect getWidgetClosestTo(Point point) { + ArrayList workspaceBoards = workspaceToBoards(); + int maxDistance = 9999; + WidgetRect bestRect = null; + for (int i = 0; i < workspaceBoards.get(0).getWidgets().size(); i++) { + WidgetRect widget = workspaceBoards.get(0).getWidgets().get(i); + if (widget.getCellX() == 0 && widget.getCellY() == 0) { + continue; + } + int distance = Math.abs(point.x - widget.getCellX()) + + Math.abs(point.y - widget.getCellY()); + if (distance == 0) { + break; + } + if (distance < maxDistance) { + maxDistance = distance; + bestRect = widget; + } + } + return bestRect; + } + + /** + * This function might be odd, its function is to select a widget and leave it in its place. + * The idea is to make the test broader and also test after a widgets resized because the + * underlying code does different things in that case + */ + private void triggerWidgetResize(ReorderTestCase testCase) { + WidgetRect widgetRect = getWidgetClosestTo(testCase.moveMainTo); + if (widgetRect == null) { + // Some test doesn't have a widget in the final position, in those cases we will ignore + // them + return; + } + Widget widget = mLauncher.getWorkspace().getWidgetAtCell(widgetRect.getCellX(), + widgetRect.getCellY()); + WidgetResizeFrame resizeFrame = widget.dragWidgetToWorkspace(widgetRect.getCellX(), + widgetRect.getCellY(), widgetRect.getSpanX(), widgetRect.getSpanY()); + resizeFrame.dismiss(); + } + + private void runTestCase(ReorderTestCase testCase) { + WidgetRect mainWidgetCellPos = CellLayoutBoard.getMainFromList( + testCase.mStart); + + FavoriteItemsTransaction transaction = + new FavoriteItemsTransaction(mTargetContext); + transaction = buildWorkspaceFromBoards(testCase.mStart, transaction); + transaction.commit(); + mLauncher.waitForLauncherInitialized(); + // resetLoaderState triggers the launcher to start loading the workspace which allows + // waitForLauncherCondition to wait for that condition, otherwise the condition would + // always be true and it wouldn't wait for the changes to be applied. + waitForLauncherCondition("Workspace didn't finish loading", l -> !l.isWorkspaceLoading()); + + triggerWidgetResize(testCase); + + Widget widget = mLauncher.getWorkspace().getWidgetAtCell(mainWidgetCellPos.getCellX(), + mainWidgetCellPos.getCellY()); + assertNotNull(widget); + WidgetResizeFrame resizeFrame = widget.dragWidgetToWorkspace(testCase.moveMainTo.x, + testCase.moveMainTo.y, mainWidgetCellPos.getSpanX(), mainWidgetCellPos.getSpanY()); + resizeFrame.dismiss(); + + boolean isValid = false; + for (List boards : testCase.mEnd) { + isValid |= validateBoard(boards); + if (isValid) break; + } + printCurrentWorkspace(); + assertTrue("Non of the valid boards match with the current state", isValid); + } + + /** + * Run only the test define for the current grid size if such test exist + * + * @param testCaseMap map containing all the tests per grid size (Point) + */ + private boolean runTestCaseMap(Map testCaseMap, String testName) { + Point iconGridDimensions = mLauncher.getWorkspace().getIconGridDimensions(); + Log.d(TAG, "Running test " + testName + " for grid " + iconGridDimensions); + if (!testCaseMap.containsKey(iconGridDimensions)) { + Log.d(TAG, "The test " + testName + " doesn't support " + iconGridDimensions + + " grid layout"); + return false; + } + runTestCase(testCaseMap.get(iconGridDimensions)); + + return true; + } + + private void runTestCaseMapForAllGrids(Map testCaseMap, + String testName) { + boolean runAtLeastOnce = false; + for (String grid : FOLDABLE_GRIDS) { + applyGridOption(grid); + mLauncher.waitForLauncherInitialized(); + runAtLeastOnce |= runTestCaseMap(testCaseMap, testName); + } + Assume.assumeTrue("None of the grids are supported", runAtLeastOnce); + } + + private void applyGridOption(Object argValue) { + String testProviderAuthority = mTargetContext.getPackageName() + ".grid_control"; + Uri gridUri = new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(testProviderAuthority) + .appendPath("default_grid") + .build(); + ContentValues values = new ContentValues(); + values.putObject("name", argValue); + Assert.assertEquals(1, + mTargetContext.getContentResolver().update(gridUri, values, null, null)); + } + + @Test + public void simpleReorder() throws Exception { + runTestCaseMap(getTestMap("ReorderWidgets/simple_reorder_case"), + "push_reorder_case"); + } + + @Test + public void pushTest() throws Exception { + runTestCaseMap(getTestMap("ReorderWidgets/push_reorder_case"), + "push_reorder_case"); + } + + @Test + public void fullReorder() throws Exception { + runTestCaseMap(getTestMap("ReorderWidgets/full_reorder_case"), + "full_reorder_case"); + } + + @Test + public void moveOutReorder() throws Exception { + runTestCaseMap(getTestMap("ReorderWidgets/move_out_reorder_case"), + "move_out_reorder_case"); + } + + @Test + public void multipleCellLayoutsSimpleReorder() throws Exception { + Assume.assumeTrue("Test doesn't support foldables", getFromLauncher( + l -> l.getWorkspace().getScreenWithId(0) instanceof MultipageCellLayout)); + runTestCaseMapForAllGrids(getTestMap("ReorderWidgets/multiple_cell_layouts_simple_reorder"), + "multiple_cell_layouts_simple_reorder"); + } + + @Test + public void multipleCellLayoutsNoSpaceReorder() throws Exception { + Assume.assumeTrue("Test doesn't support foldables", getFromLauncher( + l -> l.getWorkspace().getScreenWithId(0) instanceof MultipageCellLayout)); + runTestCaseMapForAllGrids( + getTestMap("ReorderWidgets/multiple_cell_layouts_no_space_reorder"), + "multiple_cell_layouts_no_space_reorder"); + } + + @Test + public void multipleCellLayoutsReorderToOtherSide() throws Exception { + Assume.assumeTrue("Test doesn't support foldables", getFromLauncher( + l -> l.getWorkspace().getScreenWithId(0) instanceof MultipageCellLayout)); + runTestCaseMapForAllGrids( + getTestMap("ReorderWidgets/multiple_cell_layouts_reorder_other_side"), + "multiple_cell_layouts_reorder_other_side"); + } + + private void addTestCase(Iterator sections, + Map testCaseMap) { + CellLayoutTestCaseReader.Board startBoard = + ((CellLayoutTestCaseReader.Board) sections.next()); + CellLayoutTestCaseReader.Arguments point = + ((CellLayoutTestCaseReader.Arguments) sections.next()); + CellLayoutTestCaseReader.Board endBoard = + ((CellLayoutTestCaseReader.Board) sections.next()); + Point moveTo = new Point(Integer.parseInt(point.arguments[0]), + Integer.parseInt(point.arguments[1])); + testCaseMap.put(endBoard.gridSize, + new ReorderTestCase(startBoard.board, moveTo, endBoard.board)); + } + + private Map getTestMap(String testPath) throws IOException { + Map testCaseMap = new HashMap<>(); + Iterator iterableSection = + CellLayoutTestCaseReader.readFromFile(testPath).parse().iterator(); + while (iterableSection.hasNext()) { + addTestCase(iterableSection, testCaseMap); + } + return testCaseMap; + } +} diff --git a/tests/src/com/android/launcher3/celllayout/testgenerator/ValidGridMigrationTestCaseGenerator.kt b/tests/src/com/android/launcher3/celllayout/testgenerator/ValidGridMigrationTestCaseGenerator.kt new file mode 100644 index 0000000000..a006fd7463 --- /dev/null +++ b/tests/src/com/android/launcher3/celllayout/testgenerator/ValidGridMigrationTestCaseGenerator.kt @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.celllayout.testgenerator + +import android.graphics.Point +import com.android.launcher3.LauncherSettings +import com.android.launcher3.celllayout.board.CellLayoutBoard +import com.android.launcher3.model.data.LauncherAppWidgetInfo +import com.android.launcher3.model.gridmigration.WorkspaceItem +import java.util.Random +import java.util.concurrent.atomic.AtomicInteger + +/** + * Generate a list of WorkspaceItem's for the given test case. + * + * @param repeatAfter a number after which we would repeat the same number of icons and widgets to + * account for cases where the user have the same item multiple times. + */ +fun generateItemsForTest( + boards: List, + repeatAfterRange: Point +): List { + val id = AtomicInteger(0) + val widgetId = AtomicInteger(LauncherAppWidgetInfo.CUSTOM_WIDGET_ID - 1) + // Repeat the same appWidgetProvider and intent to have repeating widgets and icons and test + // that case too + val getIntent = { i: Int -> "Intent ${(i + repeatAfterRange.x) % repeatAfterRange.y}" } + val getProvider = { i: Int -> + "com.test/test.Provider${(i + repeatAfterRange.x) % repeatAfterRange.y }" + } + val hotseatEntries = + (0 until boards[0].width).map { + WorkspaceItem( + x = it, + y = 0, + spanX = 1, + spanY = 1, + id = id.getAndAdd(1), + screenId = it, + title = "Hotseat ${id.get()}", + appWidgetId = -1, + appWidgetProvider = "Hotseat icons don't have a provider", + intent = getIntent(id.get()), + type = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION, + container = LauncherSettings.Favorites.CONTAINER_HOTSEAT + ) + } + var widgetEntries = + boards + .flatMapIndexed { i, board -> board.widgets.map { Pair(i, it) } } + .map { + WorkspaceItem( + x = it.second.cellX, + y = it.second.cellY, + spanX = it.second.spanX, + spanY = it.second.spanY, + id = id.getAndAdd(1), + screenId = it.first, + title = "Title Widget ${id.get()}", + appWidgetId = widgetId.getAndAdd(-1), + appWidgetProvider = getProvider(id.get()), + intent = "Widgets don't have intent", + type = LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET, + container = LauncherSettings.Favorites.CONTAINER_DESKTOP + ) + } + widgetEntries = widgetEntries.filter { it.appWidgetProvider.contains("Provider4") } + val iconEntries = + boards + .flatMapIndexed { i, board -> board.icons.map { Pair(i, it) } } + .map { + WorkspaceItem( + x = it.second.coord.x, + y = it.second.coord.y, + spanX = 1, + spanY = 1, + id = id.getAndAdd(1), + screenId = it.first, + title = "Title Icon ${id.get()}", + appWidgetId = -1, + appWidgetProvider = "Icons don't have providers", + intent = getIntent(id.get()), + type = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION, + container = LauncherSettings.Favorites.CONTAINER_DESKTOP + ) + } + return widgetEntries + hotseatEntries + iconEntries +} + +data class GridMigrationUnitTestCase( + val boards: List, + val destBoards: List, + val srcSize: Point, + val targetSize: Point, + val seed: Long +) + +class ValidGridMigrationTestCaseGenerator(private val generator: Random) : + DeterministicRandomGenerator(generator) { + + companion object { + const val MAX_BOARD_SIZE = 12 + const val MAX_BOARD_COUNT = 10 + const val SEED = 10342 + } + + private fun generateBoards( + boardGenerator: RandomBoardGenerator, + width: Int, + height: Int, + boardCount: Int + ): List { + val boards = mutableListOf() + for (i in 0 until boardCount) { + boards.add( + boardGenerator.generateBoard( + width, + height, + boardGenerator.getRandom(0, width * height) + ) + ) + } + return boards + } + + fun generateTestCase(isDestEmpty: Boolean): GridMigrationUnitTestCase { + val seed = generator.nextLong() + val randomBoardGenerator = RandomBoardGenerator(Random(seed)) + val width = randomBoardGenerator.getRandom(3, MAX_BOARD_SIZE) + val height = randomBoardGenerator.getRandom(3, MAX_BOARD_SIZE) + val targetSize = + Point( + randomBoardGenerator.getRandom(3, MAX_BOARD_SIZE), + randomBoardGenerator.getRandom(3, MAX_BOARD_SIZE) + ) + val destBoards = + if (isDestEmpty) { + listOf() + } else { + generateBoards( + boardGenerator = randomBoardGenerator, + width = targetSize.x, + height = targetSize.y, + boardCount = randomBoardGenerator.getRandom(3, MAX_BOARD_COUNT) + ) + } + return GridMigrationUnitTestCase( + boards = + generateBoards( + boardGenerator = randomBoardGenerator, + width = width, + height = height, + boardCount = randomBoardGenerator.getRandom(3, MAX_BOARD_COUNT) + ), + destBoards = destBoards, + srcSize = Point(width, height), + targetSize = targetSize, + seed = seed + ) + } +} diff --git a/tests/src/com/android/launcher3/compat/TaplPromiseIconUiTest.java b/tests/src/com/android/launcher3/compat/TaplPromiseIconUiTest.java new file mode 100644 index 0000000000..1500538633 --- /dev/null +++ b/tests/src/com/android/launcher3/compat/TaplPromiseIconUiTest.java @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.android.launcher3.compat; + +import static com.android.launcher3.Flags.FLAG_ENABLE_SUPPORT_FOR_ARCHIVING; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.pm.PackageInstaller.SessionParams; +import android.content.pm.PackageManager; +import android.graphics.Bitmap; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.text.TextUtils; + +import androidx.test.filters.LargeTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherState; +import com.android.launcher3.tapl.AllApps; +import com.android.launcher3.ui.AbstractLauncherUiTest; +import com.android.launcher3.util.LauncherBindableItemsContainer.ItemOperator; +import com.android.launcher3.util.TestUtil; +import com.android.launcher3.util.rule.ViewCaptureRule; + +import org.junit.After; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.IOException; +import java.util.UUID; + + +/** + * Test to verify promise icon flow. + */ +@LargeTest +@RunWith(AndroidJUnit4.class) +public class TaplPromiseIconUiTest extends AbstractLauncherUiTest { + + @Rule + public final CheckFlagsRule mCheckFlagsRule = + DeviceFlagsValueProvider.createCheckFlagsRule(); + + public static final String PACKAGE_NAME = "test.promise.app"; + public static final String DUMMY_PACKAGE = "com.example.android.aardwolf"; + public static final String DUMMY_LABEL = "Aardwolf"; + + private int mSessionId = -1; + + @Override + public void setUp() throws Exception { + super.setUp(); + mDevice.pressHome(); + waitForLauncherCondition("Launcher didn't start", launcher -> launcher != null); + waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL); + mSessionId = -1; + } + + @After + public void tearDown() throws IOException { + if (mSessionId > -1) { + mTargetContext.getPackageManager().getPackageInstaller().abandonSession(mSessionId); + } + TestUtil.uninstallDummyApp(); + } + + /** + * Create a session and return the id. + */ + private int createSession(String packageName, String label, Bitmap icon) throws Throwable { + SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL); + params.setAppPackageName(packageName); + params.setAppLabel(label); + params.setAppIcon(icon); + params.setInstallReason(PackageManager.INSTALL_REASON_USER); + return mTargetContext.getPackageManager().getPackageInstaller().createSession(params); + } + + @Test + public void testPromiseIcon_addedFromEligibleSession() throws Throwable { + final String appLabel = "Test Promise App " + UUID.randomUUID().toString(); + final ItemOperator findPromiseApp = (info, view) -> + info != null && TextUtils.equals(info.title, appLabel); + + // Create and add test session + mSessionId = createSession(PACKAGE_NAME, appLabel, + Bitmap.createBitmap(100, 100, Bitmap.Config.ALPHA_8)); + + // Verify promise icon is added + waitForLauncherCondition("Test Promise App not found on workspace", launcher -> + launcher.getWorkspace().getFirstMatch(findPromiseApp) != null); + + // Remove session + mTargetContext.getPackageManager().getPackageInstaller().abandonSession(mSessionId); + mSessionId = -1; + + // Verify promise icon is removed + waitForLauncherCondition("Test Promise App not removed from workspace", launcher -> + launcher.getWorkspace().getFirstMatch(findPromiseApp) == null); + } + + @Test + @ViewCaptureRule.MayProduceNoFrames + public void testPromiseIcon_notAddedFromIneligibleSession() throws Throwable { + final String appLabel = "Test Promise App " + UUID.randomUUID().toString(); + final ItemOperator findPromiseApp = (info, view) -> + info != null && TextUtils.equals(info.title, appLabel); + + // Create and add test session without icon or label + mSessionId = createSession(PACKAGE_NAME, null, null); + + // Sleep for duration of animation if a view was to be added + some buffer time. + Thread.sleep(Launcher.NEW_APPS_PAGE_MOVE_DELAY + Launcher.NEW_APPS_ANIMATION_DELAY + 500); + + // Verify promise icon is not added + waitForLauncherCondition("Test Promise App not found on workspace", launcher -> + launcher.getWorkspace().getFirstMatch(findPromiseApp) == null); + } + + @Test + @RequiresFlagsEnabled(FLAG_ENABLE_SUPPORT_FOR_ARCHIVING) + public void testPromiseIcon_addedArchivedApp() throws Throwable { + installDummyAppAndWaitForUIUpdate(); + assertThat(mDevice.executeShellCommand(String.format("pm archive %s", DUMMY_PACKAGE))) + .isEqualTo("Success\n"); + + // Create and add test session + mSessionId = createSession(DUMMY_PACKAGE, /* label= */ "", + Bitmap.createBitmap(100, 100, Bitmap.Config.ALPHA_8)); + + // Verify promise icon is added to all apps view. The icon may not be added to the + // workspace even if there might be no icon present for archived app. But icon will + // always be in all apps view. In case an icon is not added, an exception would be thrown. + final AllApps allApps = mLauncher.getWorkspace().switchToAllApps(); + + // Wait for the promise icon to be added. + waitForLauncherCondition( + DUMMY_PACKAGE + " app was not found on all apps after being archived", + launcher -> { + try { + allApps.getAppIcon(DUMMY_LABEL); + } catch (Throwable t) { + return false; + } + return true; + }); + + // Remove session + mTargetContext.getPackageManager().getPackageInstaller().abandonSession(mSessionId); + mSessionId = -1; + } + + private void installDummyAppAndWaitForUIUpdate() throws IOException { + TestUtil.installDummyApp(); + mLauncher.waitForModelQueueCleared(); + mLauncher.waitForLauncherInitialized(); + } +} diff --git a/tests/src/com/android/launcher3/dragging/TaplDragTest.java b/tests/src/com/android/launcher3/dragging/TaplDragTest.java index 2e02eb0591..41abcf885d 100644 --- a/tests/src/com/android/launcher3/dragging/TaplDragTest.java +++ b/tests/src/com/android/launcher3/dragging/TaplDragTest.java @@ -15,6 +15,7 @@ */ package com.android.launcher3.dragging; +import static com.android.launcher3.testing.shared.TestProtocol.TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE; import static com.android.launcher3.util.TestConstants.AppNames.GMAIL_APP_NAME; import static com.android.launcher3.util.TestConstants.AppNames.MAPS_APP_NAME; import static com.android.launcher3.util.TestConstants.AppNames.PHOTOS_APP_NAME; @@ -64,7 +65,6 @@ public class TaplDragTest extends AbstractLauncherUiTest { @Test @PortraitLandscape @PlatinumTest(focusArea = "launcher") - @ScreenRecordRule.ScreenRecord // b/383917141 public void testDragToFolder() { // TODO: add the use case to drag an icon to an existing folder. Currently it either fails // on tablets or phones due to difference in resolution. @@ -174,13 +174,13 @@ public class TaplDragTest extends AbstractLauncherUiTest { public void testDragAndCancelAppIcon() { final HomeAppIcon homeAppIcon = createShortcutInCenterIfNotExist(GMAIL_APP_NAME); Point positionBeforeDrag = - mLauncher.getWorkspace().getWorkspaceIconPosition(GMAIL_APP_NAME); + mLauncher.getWorkspace().getWorkspaceIconsPositions().get(GMAIL_APP_NAME); assertNotNull("App not found in Workspace before dragging.", positionBeforeDrag); mLauncher.getWorkspace().dragAndCancelAppIcon(homeAppIcon); Point positionAfterDrag = - mLauncher.getWorkspace().getWorkspaceIconPosition(GMAIL_APP_NAME); + mLauncher.getWorkspace().getWorkspaceIconsPositions().get(GMAIL_APP_NAME); assertNotNull("App not found in Workspace after dragging.", positionAfterDrag); assertEquals("App not returned to same position in Workspace after drag & cancel", positionBeforeDrag, positionAfterDrag); @@ -195,6 +195,7 @@ public class TaplDragTest extends AbstractLauncherUiTest { @PlatinumTest(focusArea = "launcher") @Test @PortraitLandscape + @ScreenRecordRule.ScreenRecord // b/343953783 public void testDragAppIcon() { final HomeAllApps allApps = mLauncher.getWorkspace().switchToAllApps(); @@ -227,6 +228,11 @@ public class TaplDragTest extends AbstractLauncherUiTest { final HomeAppIcon launcherTestAppIcon = createShortcutInCenterIfNotExist(TEST_APP_NAME); for (Point target : targets) { startTime = SystemClock.uptimeMillis(); + Log.d(TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE, + "TaplDragTest.java.testDragAppIconToMultipleWorkspaceCells: shortcut name: " + + launcherTestAppIcon.getIconName() + + " | target cell coordinates: (" + target.x + ", " + target.y + + ") | start time: " + startTime); launcherTestAppIcon.dragToWorkspace(target.x, target.y); endTime = SystemClock.uptimeMillis(); elapsedTime = endTime - startTime; diff --git a/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java b/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java index a03c1612c1..362596c1ab 100644 --- a/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java +++ b/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java @@ -16,17 +16,19 @@ package com.android.launcher3.dragging; import static com.android.launcher3.testing.shared.TestProtocol.ICON_MISSING; +import static com.android.launcher3.testing.shared.TestProtocol.UIOBJECT_STALE_ELEMENT; import static com.android.launcher3.util.TestConstants.AppNames.DUMMY_APP_NAME; import static com.android.launcher3.util.TestConstants.AppNames.GMAIL_APP_NAME; import static com.android.launcher3.util.TestConstants.AppNames.MAPS_APP_NAME; import static com.android.launcher3.util.TestConstants.AppNames.STORE_APP_NAME; import static com.android.launcher3.util.TestConstants.AppNames.TEST_APP_NAME; +import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL; +import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT; import static com.google.common.truth.Truth.assertThat; import android.graphics.Point; import android.platform.test.annotations.PlatinumTest; -import android.platform.test.rule.ScreenRecordRule; import android.util.Log; import com.android.launcher3.Launcher; @@ -37,11 +39,14 @@ import com.android.launcher3.ui.AbstractLauncherUiTest; import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape; import com.android.launcher3.util.TestUtil; import com.android.launcher3.util.Wait; +import com.android.launcher3.util.rule.ScreenRecordRule; +import com.android.launcher3.util.rule.TestStabilityRule; import org.junit.Test; import java.io.IOException; import java.util.Arrays; +import java.util.Map; /** * Test runs in Out of process (Oop) and In process (Ipc) @@ -68,7 +73,8 @@ public class TaplUninstallRemoveTest extends AbstractLauncherUiTest { private void verifyAppUninstalledFromAllApps(Workspace workspace, String appName) { final HomeAllApps allApps = workspace.switchToAllApps(); Wait.atMost(appName + " app was found on all apps after being uninstalled", - () -> allApps.tryGetAppIcon(appName) == null, mLauncher); + () -> allApps.tryGetAppIcon(appName) == null, + DEFAULT_UI_TIMEOUT, mLauncher); } private void installDummyAppAndWaitForUIUpdate() throws IOException { @@ -92,6 +98,7 @@ public class TaplUninstallRemoveTest extends AbstractLauncherUiTest { @Test @PortraitLandscape @PlatinumTest(focusArea = "launcher") + @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/311099513 public void testUninstallFromWorkspace() throws Exception { installDummyAppAndWaitForUIUpdate(); try { @@ -108,11 +115,7 @@ public class TaplUninstallRemoveTest extends AbstractLauncherUiTest { @Test @PortraitLandscape @PlatinumTest(focusArea = "launcher") - @ScreenRecordRule.ScreenRecord // b/386231522 public void testUninstallFromAllApps() throws Exception { - // Ensure no existing app icons on the workspace cause scroll to all apps interruptions - mLauncher.clearLauncherData(); - installDummyAppAndWaitForUIUpdate(); try { Workspace workspace = mLauncher.getWorkspace(); @@ -129,6 +132,7 @@ public class TaplUninstallRemoveTest extends AbstractLauncherUiTest { */ @Test @PlatinumTest(focusArea = "launcher") + @ScreenRecordRule.ScreenRecord // b/319501259 public void uninstallWorkspaceIcon() throws IOException { Point[] gridPositions = TestUtil.getCornersAndCenterPositions(mLauncher); StringBuilder sb = new StringBuilder(); @@ -147,17 +151,22 @@ public class TaplUninstallRemoveTest extends AbstractLauncherUiTest { 0, Math.min(gridPositions.length, appNameCandidates.length)); for (int i = 0; i < appNames.length; ++i) { + Log.d(UIOBJECT_STALE_ELEMENT, "creatingShortcut for: " + appNames[i]); createShortcutIfNotExist(appNames[i], gridPositions[i]); } - Point initialPosition = - mLauncher.getWorkspace().getWorkspaceIconPosition(DUMMY_APP_NAME); - assertThat(initialPosition).isNotNull(); + Map initialPositions = + mLauncher.getWorkspace().getWorkspaceIconsPositions(); + assertThat(initialPositions.keySet()).containsAtLeastElementsIn(appNames); - final Workspace workspace = mLauncher.getWorkspace().getWorkspaceAppIcon( - DUMMY_APP_NAME).uninstall(); - workspace.verifyWorkspaceAppIconIsGone( + mLauncher.getWorkspace().getWorkspaceAppIcon(DUMMY_APP_NAME).uninstall(); + mLauncher.getWorkspace().verifyWorkspaceAppIconIsGone( DUMMY_APP_NAME + " was expected to disappear after uninstall.", DUMMY_APP_NAME); + + Log.d(UIOBJECT_STALE_ELEMENT, "second getWorkspaceIconsPositions()"); + Map finalPositions = + mLauncher.getWorkspace().getWorkspaceIconsPositions(); + assertThat(finalPositions).doesNotContainKey(DUMMY_APP_NAME); } finally { TestUtil.uninstallDummyApp(); } @@ -168,6 +177,8 @@ public class TaplUninstallRemoveTest extends AbstractLauncherUiTest { */ @Test @PortraitLandscape + @ScreenRecordRule.ScreenRecord // b/338869019 + @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/338869019 public void testAddDeleteShortcutOnHotseat() { mLauncher.getWorkspace() .deleteAppIcon(mLauncher.getWorkspace().getHotseatAppIcon(0)) diff --git a/tests/src/com/android/launcher3/folder/FolderNameProviderTest.java b/tests/src/com/android/launcher3/folder/FolderNameProviderTest.java new file mode 100644 index 0000000000..9c15309f82 --- /dev/null +++ b/tests/src/com/android/launcher3/folder/FolderNameProviderTest.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.folder; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.os.UserHandle; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.android.launcher3.model.data.AppInfo; +import com.android.launcher3.model.data.WorkspaceItemInfo; +import com.android.launcher3.util.ActivityContextWrapper; +import com.android.launcher3.util.Executors; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public final class FolderNameProviderTest { + private Context mContext; + private WorkspaceItemInfo mItem1; + private WorkspaceItemInfo mItem2; + + @Before + public void setUp() { + mContext = new ActivityContextWrapper(getApplicationContext()); + mItem1 = new WorkspaceItemInfo(new AppInfo( + new ComponentName("a.b.c", "a.b.c/a.b.c.d"), + "title1", + UserHandle.of(10), + new Intent().setComponent(new ComponentName("a.b.c", "a.b.c/a.b.c.d")) + )); + mItem2 = new WorkspaceItemInfo(new AppInfo( + new ComponentName("a.b.c", "a.b.c/a.b.c.d"), + "title2", + UserHandle.of(10), + new Intent().setComponent(new ComponentName("a.b.c", "a.b.c/a.b.c.d")) + )); + } + + @Test + public void getSuggestedFolderName_workAssignedToEnd() throws Exception { + ArrayList list = new ArrayList<>(); + list.add(mItem1); + list.add(mItem2); + FolderNameInfos nameInfos = new FolderNameInfos(); + Executors.MODEL_EXECUTOR.submit(() -> + new FolderNameProvider().getSuggestedFolderName(mContext, list, nameInfos)).get(); + assertEquals("Work", nameInfos.getLabels()[0]); + + nameInfos.setLabel(0, "candidate1", 1.0f); + nameInfos.setLabel(1, "candidate2", 1.0f); + nameInfos.setLabel(2, "candidate3", 1.0f); + Executors.MODEL_EXECUTOR.submit(() -> + new FolderNameProvider().getSuggestedFolderName(mContext, list, nameInfos)).get(); + assertEquals("Work", nameInfos.getLabels()[3]); + assertTrue(nameInfos.hasSuggestions()); + assertTrue(nameInfos.hasPrimary()); + } +} diff --git a/tests/src/com/android/launcher3/folder/PreviewBackgroundTest.java b/tests/src/com/android/launcher3/folder/PreviewBackgroundTest.java new file mode 100644 index 0000000000..7242e9c77a --- /dev/null +++ b/tests/src/com/android/launcher3/folder/PreviewBackgroundTest.java @@ -0,0 +1,325 @@ +/* + * 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.launcher3.folder; + +import static com.android.launcher3.folder.PreviewBackground.ACCEPT_SCALE_FACTOR; +import static com.android.launcher3.folder.PreviewBackground.CONSUMPTION_ANIMATION_DURATION; +import static com.android.launcher3.folder.PreviewBackground.HOVER_ANIMATION_DURATION; +import static com.android.launcher3.folder.PreviewBackground.HOVER_SCALE; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.view.animation.AccelerateDecelerateInterpolator; +import android.view.animation.PathInterpolator; + +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.launcher3.CellLayout; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +public class PreviewBackgroundTest { + + private static final float REST_SCALE = 1f; + private static final float EPSILON = 0.00001f; + + @Mock + CellLayout mCellLayout; + + private final PreviewBackground mPreviewBackground = + new PreviewBackground(InstrumentationRegistry.getInstrumentation().getContext()); + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mPreviewBackground.mScale = REST_SCALE; + mPreviewBackground.mIsAccepting = false; + mPreviewBackground.mIsHovered = false; + mPreviewBackground.mIsHoveredOrAnimating = false; + mPreviewBackground.invalidate(); + } + + @Test + public void testAnimateScale_restToHovered() { + mPreviewBackground.setHovered(true); + runAnimationToFraction(1f); + + assertEquals("Scale not changed.", mPreviewBackground.mScale, HOVER_SCALE, EPSILON); + assertEquals("Duration not correct.", mPreviewBackground.mScaleAnimator.getDuration(), + HOVER_ANIMATION_DURATION); + assertTrue("Wrong interpolator used.", + mPreviewBackground.mScaleAnimator.getInterpolator() instanceof PathInterpolator); + endAnimation(); + assertEquals("Scale progress not 0.", mPreviewBackground.getAcceptScaleProgress(), 0, + EPSILON); + } + + @Test + public void testAnimateScale_restToNotHovered() { + mPreviewBackground.setHovered(false); + + assertEquals("Scale changed.", mPreviewBackground.mScale, REST_SCALE, EPSILON); + assertNull("Animator not null.", mPreviewBackground.mScaleAnimator); + assertEquals("Scale progress not 0.", mPreviewBackground.getAcceptScaleProgress(), 0, + EPSILON); + } + + @Test + public void testAnimateScale_hoveredToHovered() { + mPreviewBackground.mScale = HOVER_SCALE; + mPreviewBackground.mIsHovered = true; + mPreviewBackground.mIsHoveredOrAnimating = true; + mPreviewBackground.invalidate(); + + mPreviewBackground.setHovered(true); + + assertEquals("Scale changed.", mPreviewBackground.mScale, HOVER_SCALE, EPSILON); + assertNull("Animator not null.", mPreviewBackground.mScaleAnimator); + assertEquals("Scale progress not 0.", mPreviewBackground.getAcceptScaleProgress(), 0, + EPSILON); + } + + @Test + public void testAnimateScale_hoveredToRest() { + mPreviewBackground.mScale = HOVER_SCALE; + mPreviewBackground.mIsHovered = true; + mPreviewBackground.mIsHoveredOrAnimating = true; + mPreviewBackground.invalidate(); + + mPreviewBackground.setHovered(false); + runAnimationToFraction(1f); + + assertEquals("Scale not changed.", mPreviewBackground.mScale, REST_SCALE, EPSILON); + assertEquals("Duration not correct.", mPreviewBackground.mScaleAnimator.getDuration(), + HOVER_ANIMATION_DURATION); + assertTrue("Wrong interpolator used.", + mPreviewBackground.mScaleAnimator.getInterpolator() instanceof PathInterpolator); + endAnimation(); + assertEquals("Scale progress not 0.", mPreviewBackground.getAcceptScaleProgress(), 0, + EPSILON); + } + + @Test + public void testAnimateScale_restToAccept() { + mPreviewBackground.animateToAccept(mCellLayout, 0, 0); + runAnimationToFraction(1f); + + assertEquals("Scale changed.", mPreviewBackground.mScale, ACCEPT_SCALE_FACTOR, EPSILON); + assertEquals("Duration not correct.", mPreviewBackground.mScaleAnimator.getDuration(), + CONSUMPTION_ANIMATION_DURATION); + assertTrue("Wrong interpolator used.", + mPreviewBackground.mScaleAnimator.getInterpolator() + instanceof AccelerateDecelerateInterpolator); + endAnimation(); + assertEquals("Scale progress not 1.", mPreviewBackground.getAcceptScaleProgress(), 1, + EPSILON); + } + + @Test + public void testAnimateScale_restToRest() { + mPreviewBackground.animateToRest(); + + assertEquals("Scale changed.", mPreviewBackground.mScale, REST_SCALE, EPSILON); + assertNull("Animator not null.", mPreviewBackground.mScaleAnimator); + assertEquals("Scale progress not 0.", mPreviewBackground.getAcceptScaleProgress(), 0, + EPSILON); + } + + @Test + public void testAnimateScale_acceptToRest() { + mPreviewBackground.mScale = ACCEPT_SCALE_FACTOR; + mPreviewBackground.mIsAccepting = true; + mPreviewBackground.invalidate(); + + mPreviewBackground.animateToRest(); + runAnimationToFraction(1f); + + assertEquals("Scale not changed.", mPreviewBackground.mScale, REST_SCALE, EPSILON); + assertEquals("Duration not correct.", mPreviewBackground.mScaleAnimator.getDuration(), + CONSUMPTION_ANIMATION_DURATION); + assertTrue("Wrong interpolator used.", + mPreviewBackground.mScaleAnimator.getInterpolator() + instanceof AccelerateDecelerateInterpolator); + endAnimation(); + assertEquals("Scale progress not 0.", mPreviewBackground.getAcceptScaleProgress(), 0, + EPSILON); + } + + @Test + public void testAnimateScale_acceptToHover() { + mPreviewBackground.mScale = ACCEPT_SCALE_FACTOR; + mPreviewBackground.mIsAccepting = true; + mPreviewBackground.invalidate(); + + mPreviewBackground.mIsAccepting = false; + mPreviewBackground.setHovered(true); + runAnimationToFraction(1f); + + assertEquals("Scale not changed.", mPreviewBackground.mScale, HOVER_SCALE, EPSILON); + assertEquals("Duration not correct.", mPreviewBackground.mScaleAnimator.getDuration(), + HOVER_ANIMATION_DURATION); + assertTrue("Wrong interpolator used.", + mPreviewBackground.mScaleAnimator.getInterpolator() instanceof PathInterpolator); + endAnimation(); + assertEquals("Scale progress not 0.", mPreviewBackground.getAcceptScaleProgress(), 0, + EPSILON); + } + + @Test + public void testAnimateScale_hoverToAccept() { + mPreviewBackground.mScale = HOVER_SCALE; + mPreviewBackground.mIsHovered = true; + mPreviewBackground.mIsHoveredOrAnimating = true; + mPreviewBackground.invalidate(); + + mPreviewBackground.animateToAccept(mCellLayout, 0, 0); + runAnimationToFraction(1f); + + assertEquals("Scale not changed.", mPreviewBackground.mScale, ACCEPT_SCALE_FACTOR, EPSILON); + assertEquals("Duration not correct.", mPreviewBackground.mScaleAnimator.getDuration(), + CONSUMPTION_ANIMATION_DURATION); + assertTrue("Wrong interpolator used.", + mPreviewBackground.mScaleAnimator.getInterpolator() + instanceof AccelerateDecelerateInterpolator); + mPreviewBackground.mIsHovered = false; + endAnimation(); + assertEquals("Scale progress not 1.", mPreviewBackground.getAcceptScaleProgress(), 1, + EPSILON); + } + + @Test + public void testAnimateScale_midwayToHoverToAccept() { + mPreviewBackground.setHovered(true); + runAnimationToFraction(0.5f); + assertTrue("Scale not changed.", + mPreviewBackground.mScale > REST_SCALE && mPreviewBackground.mScale < HOVER_SCALE); + assertEquals("Scale progress not 0.", mPreviewBackground.getAcceptScaleProgress(), 0, + EPSILON); + + mPreviewBackground.animateToAccept(mCellLayout, 0, 0); + runAnimationToFraction(1f); + + assertEquals("Scale not changed.", mPreviewBackground.mScale, ACCEPT_SCALE_FACTOR, EPSILON); + assertEquals("Duration not correct.", mPreviewBackground.mScaleAnimator.getDuration(), + CONSUMPTION_ANIMATION_DURATION); + assertTrue("Wrong interpolator used.", + mPreviewBackground.mScaleAnimator.getInterpolator() + instanceof AccelerateDecelerateInterpolator); + mPreviewBackground.mIsHovered = false; + endAnimation(); + assertEquals("Scale progress not 1.", mPreviewBackground.getAcceptScaleProgress(), 1, + EPSILON); + assertNull("Animator not null.", mPreviewBackground.mScaleAnimator); + } + + @Test + public void testAnimateScale_partWayToAcceptToHover() { + mPreviewBackground.animateToAccept(mCellLayout, 0, 0); + runAnimationToFraction(0.25f); + assertTrue("Scale not changed part way.", mPreviewBackground.mScale > REST_SCALE + && mPreviewBackground.mScale < ACCEPT_SCALE_FACTOR); + + mPreviewBackground.mIsAccepting = false; + mPreviewBackground.setHovered(true); + runAnimationToFraction(1f); + + assertEquals("Scale not changed.", mPreviewBackground.mScale, HOVER_SCALE, EPSILON); + assertEquals("Duration not correct.", mPreviewBackground.mScaleAnimator.getDuration(), + HOVER_ANIMATION_DURATION); + assertTrue("Wrong interpolator used.", + mPreviewBackground.mScaleAnimator.getInterpolator() instanceof PathInterpolator); + endAnimation(); + assertEquals("Scale progress not 0.", mPreviewBackground.getAcceptScaleProgress(), 0, + EPSILON); + } + + @Test + public void testAnimateScale_midwayToAcceptEqualsHover() { + mPreviewBackground.animateToAccept(mCellLayout, 0, 0); + runAnimationToFraction(0.5f); + assertEquals("Scale not changed.", mPreviewBackground.mScale, HOVER_SCALE, EPSILON); + mPreviewBackground.mIsAccepting = false; + + mPreviewBackground.setHovered(true); + + assertEquals("Scale changed.", mPreviewBackground.mScale, HOVER_SCALE, EPSILON); + assertNull("Animator not null.", mPreviewBackground.mScaleAnimator); + assertEquals("Scale progress not 0.", mPreviewBackground.getAcceptScaleProgress(), 0, + EPSILON); + } + + @Test + public void testAnimateScale_midwayToHoverToRest() { + mPreviewBackground.setHovered(true); + runAnimationToFraction(0.5f); + assertTrue("Scale not changed midway.", + mPreviewBackground.mScale > REST_SCALE && mPreviewBackground.mScale < HOVER_SCALE); + + mPreviewBackground.mIsHovered = false; + mPreviewBackground.animateToRest(); + runAnimationToFraction(1f); + + assertEquals("Scale not changed.", mPreviewBackground.mScale, REST_SCALE, EPSILON); + assertEquals("Duration not correct.", mPreviewBackground.mScaleAnimator.getDuration(), + HOVER_ANIMATION_DURATION); + assertTrue("Wrong interpolator used.", + mPreviewBackground.mScaleAnimator.getInterpolator() instanceof PathInterpolator); + endAnimation(); + assertEquals("Scale progress not 0.", mPreviewBackground.getAcceptScaleProgress(), 0, + EPSILON); + } + + @Test + public void testAnimateScale_midwayToAcceptToRest() { + mPreviewBackground.animateToAccept(mCellLayout, 0, 0); + runAnimationToFraction(0.5f); + assertTrue("Scale not changed.", mPreviewBackground.mScale > REST_SCALE + && mPreviewBackground.mScale < ACCEPT_SCALE_FACTOR); + + mPreviewBackground.animateToRest(); + runAnimationToFraction(1f); + + assertEquals("Scale not changed.", mPreviewBackground.mScale, REST_SCALE, EPSILON); + assertEquals("Duration not correct.", mPreviewBackground.mScaleAnimator.getDuration(), + CONSUMPTION_ANIMATION_DURATION); + assertTrue("Wrong interpolator used.", + mPreviewBackground.mScaleAnimator.getInterpolator() + instanceof AccelerateDecelerateInterpolator); + endAnimation(); + assertEquals("Scale progress not 0.", mPreviewBackground.getAcceptScaleProgress(), 0, + EPSILON); + } + + private void runAnimationToFraction(float animationFraction) { + mPreviewBackground.mScaleAnimator.setCurrentFraction(animationFraction); + } + + private void endAnimation() { + mPreviewBackground.mScaleAnimator.end(); + } +} diff --git a/tests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt b/tests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt index c956395503..da14425df0 100644 --- a/tests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt +++ b/tests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2025 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. @@ -17,89 +17,49 @@ package com.android.launcher3.folder import android.R +import android.content.Context import android.os.Process +import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn -import com.android.launcher3.LauncherAppState +import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import com.android.launcher3.LauncherPrefs -import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER -import com.android.launcher3.dagger.LauncherAppComponent -import com.android.launcher3.dagger.LauncherAppSingleton -import com.android.launcher3.graphics.PreloadIconDrawable -import com.android.launcher3.graphics.ThemeManager -import com.android.launcher3.icons.BitmapInfo +import com.android.launcher3.LauncherPrefs.Companion.get +import com.android.launcher3.icons.BaseIconFactory import com.android.launcher3.icons.FastBitmapDrawable -import com.android.launcher3.icons.IconCache -import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver -import com.android.launcher3.icons.PlaceHolderIconDrawable import com.android.launcher3.icons.UserBadgeDrawable import com.android.launcher3.model.data.FolderInfo -import com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_ARCHIVED -import com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE -import com.android.launcher3.model.data.WorkspaceItemInfo +import com.android.launcher3.model.data.ItemInfo import com.android.launcher3.util.ActivityContextWrapper -import com.android.launcher3.util.AllModulesForTest -import com.android.launcher3.util.Executors -import com.android.launcher3.util.FakePrefsModule import com.android.launcher3.util.FlagOp import com.android.launcher3.util.LauncherLayoutBuilder import com.android.launcher3.util.LauncherModelHelper -import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext -import com.android.launcher3.util.TestUtil import com.android.launcher3.util.UserIconInfo -import com.google.common.truth.Truth.assertThat -import dagger.Component -import kotlin.annotation.AnnotationRetention.RUNTIME -import kotlin.annotation.AnnotationTarget.FUNCTION -import kotlin.annotation.AnnotationTarget.PROPERTY_GETTER -import kotlin.annotation.AnnotationTarget.PROPERTY_SETTER import org.junit.After import org.junit.Before -import org.junit.Rule import org.junit.Test -import org.junit.rules.TestRule -import org.junit.runner.Description import org.junit.runner.RunWith -import org.junit.runners.model.Statement -import org.mockito.MockitoAnnotations -import org.mockito.kotlin.any -import org.mockito.kotlin.argumentCaptor -import org.mockito.kotlin.doReturn -import org.mockito.kotlin.eq -import org.mockito.kotlin.verify -import org.mockito.kotlin.whenever /** Tests for [PreviewItemManager] */ @SmallTest @RunWith(AndroidJUnit4::class) class PreviewItemManagerTest { - @get:Rule val theseStateRule = ThemeStateRule() - private lateinit var previewItemManager: PreviewItemManager - private lateinit var context: SandboxModelContext - private lateinit var folderItems: ArrayList + private lateinit var context: Context + private lateinit var folderItems: ArrayList private lateinit var modelHelper: LauncherModelHelper private lateinit var folderIcon: FolderIcon - private lateinit var iconCache: IconCache @Before fun setup() { - MockitoAnnotations.initMocks(this) - modelHelper = LauncherModelHelper() - context = modelHelper.sandboxContext - context.initDaggerComponent(DaggerPreviewItemManagerTestComponent.builder()) - theseStateRule.themeState?.let { - LauncherPrefs.get(context).putSync(ThemeManager.THEMED_ICONS.to(it)) + getInstrumentation().runOnMainSync { + folderIcon = + FolderIcon(ActivityContextWrapper(ApplicationProvider.getApplicationContext())) } - folderIcon = FolderIcon(ActivityContextWrapper(context)) - - iconCache = LauncherAppState.INSTANCE[context].iconCache - spyOn(iconCache) - doReturn(null).whenever(iconCache).updateIconInBackground(any(), any()) - + context = getInstrumentation().targetContext previewItemManager = PreviewItemManager(folderIcon) + modelHelper = LauncherModelHelper() modelHelper .setupDefaultLayoutProvider( LauncherLayoutBuilder() @@ -112,26 +72,62 @@ class PreviewItemManagerTest { .build() ) .loadModelSync() + folderItems = modelHelper.bgDataModel.collections.valueAt(0).getContents() + folderIcon.mInfo = modelHelper.bgDataModel.collections.valueAt(0) as FolderInfo + folderIcon.mInfo.getContents().addAll(folderItems) - folderIcon.mInfo = - modelHelper.bgDataModel.itemsIdMap.find { it.itemType == ITEM_TYPE_FOLDER } - as FolderInfo // Use getAppContents() to "cast" contents to WorkspaceItemInfo so we can set bitmaps - folderItems = folderIcon.mInfo.getAppContents() + val folderApps = modelHelper.bgDataModel.collections.valueAt(0).getAppContents() + // Set first icon to be themed. + folderApps[0] + .bitmap + .setMonoIcon( + folderApps[0].bitmap.icon, + BaseIconFactory( + context, + context.resources.configuration.densityDpi, + previewItemManager.mIconSize + ) + ) // Set second icon to be non-themed. - folderItems[1].bitmap.themedBitmap = null + folderApps[1] + .bitmap + .setMonoIcon( + null, + BaseIconFactory( + context, + context.resources.configuration.densityDpi, + previewItemManager.mIconSize + ) + ) // Set third icon to be themed with badge. - folderItems[2].bitmap = - folderItems[2].bitmap.withFlags(profileFlagOp(UserIconInfo.TYPE_WORK)) + folderApps[2] + .bitmap + .setMonoIcon( + folderApps[2].bitmap.icon, + BaseIconFactory( + context, + context.resources.configuration.densityDpi, + previewItemManager.mIconSize + ) + ) + folderApps[2].bitmap = folderApps[2].bitmap.withFlags(profileFlagOp(UserIconInfo.TYPE_WORK)) // Set fourth icon to be non-themed with badge. - folderItems[3].bitmap = - folderItems[3].bitmap.withFlags(profileFlagOp(UserIconInfo.TYPE_WORK)) - folderItems[3].bitmap.themedBitmap = null + folderApps[3].bitmap = folderApps[3].bitmap.withFlags(profileFlagOp(UserIconInfo.TYPE_WORK)) + folderApps[3] + .bitmap + .setMonoIcon( + null, + BaseIconFactory( + context, + context.resources.configuration.densityDpi, + previewItemManager.mIconSize + ) + ) } - @After @Throws(Exception::class) fun tearDown() { @@ -139,8 +135,8 @@ class PreviewItemManagerTest { } @Test - @MonoThemeEnabled(true) fun checkThemedIconWithThemingOn_iconShouldBeThemed() { + get(context).put(LauncherPrefs.THEMED_ICONS, true) val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f) previewItemManager.setDrawable(drawingParams, folderItems[0]) @@ -149,8 +145,8 @@ class PreviewItemManagerTest { } @Test - @MonoThemeEnabled(false) fun checkThemedIconWithThemingOff_iconShouldNotBeThemed() { + get(context).put(LauncherPrefs.THEMED_ICONS, false) val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f) previewItemManager.setDrawable(drawingParams, folderItems[0]) @@ -159,8 +155,8 @@ class PreviewItemManagerTest { } @Test - @MonoThemeEnabled(true) fun checkUnthemedIconWithThemingOn_iconShouldNotBeThemed() { + get(context).put(LauncherPrefs.THEMED_ICONS, true) val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f) previewItemManager.setDrawable(drawingParams, folderItems[1]) @@ -169,8 +165,8 @@ class PreviewItemManagerTest { } @Test - @MonoThemeEnabled(false) fun checkUnthemedIconWithThemingOff_iconShouldNotBeThemed() { + get(context).put(LauncherPrefs.THEMED_ICONS, false) val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f) previewItemManager.setDrawable(drawingParams, folderItems[1]) @@ -179,8 +175,8 @@ class PreviewItemManagerTest { } @Test - @MonoThemeEnabled(true) fun checkThemedIconWithBadgeWithThemingOn_iconAndBadgeShouldBeThemed() { + get(context).put(LauncherPrefs.THEMED_ICONS, true) val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f) previewItemManager.setDrawable(drawingParams, folderItems[2]) @@ -192,8 +188,8 @@ class PreviewItemManagerTest { } @Test - @MonoThemeEnabled(true) fun checkUnthemedIconWithBadgeWithThemingOn_badgeShouldBeThemed() { + get(context).put(LauncherPrefs.THEMED_ICONS, true) val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f) previewItemManager.setDrawable(drawingParams, folderItems[3]) @@ -205,8 +201,8 @@ class PreviewItemManagerTest { } @Test - @MonoThemeEnabled(false) fun checkUnthemedIconWithBadgeWithThemingOff_iconAndBadgeShouldNotBeThemed() { + get(context).put(LauncherPrefs.THEMED_ICONS, false) val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f) previewItemManager.setDrawable(drawingParams, folderItems[3]) @@ -217,89 +213,6 @@ class PreviewItemManagerTest { ) } - @Test - fun `Inactive archived app previews are not drawn as preload icon`() { - // Given - val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f) - val archivedApp = - WorkspaceItemInfo().apply { - runtimeStatusFlags = runtimeStatusFlags or FLAG_ARCHIVED - runtimeStatusFlags = runtimeStatusFlags and FLAG_INSTALL_SESSION_ACTIVE.inv() - } - // When - previewItemManager.setDrawable(drawingParams, archivedApp) - // Then - assertThat(drawingParams.drawable).isNotInstanceOf(PreloadIconDrawable::class.java) - } - - @Test - fun `Actively installing archived app previews are drawn as preload icon`() { - // Given - val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f) - val archivedApp = - WorkspaceItemInfo().apply { - runtimeStatusFlags = runtimeStatusFlags or FLAG_ARCHIVED - runtimeStatusFlags = runtimeStatusFlags or FLAG_INSTALL_SESSION_ACTIVE - } - // When - TestUtil.runOnExecutorSync(Executors.MAIN_EXECUTOR) { - // Run on main thread because preload drawable triggers animator - previewItemManager.setDrawable(drawingParams, archivedApp) - } - // Then - assertThat(drawingParams.drawable).isInstanceOf(PreloadIconDrawable::class.java) - } - - @Test - fun `Preview item loads and apply high res icon`() { - val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f) - val originalBitmap = folderItems[3].bitmap - folderItems[3].bitmap = BitmapInfo.LOW_RES_INFO - - previewItemManager.setDrawable(drawingParams, folderItems[3]) - assertThat(drawingParams.drawable).isInstanceOf(PlaceHolderIconDrawable::class.java) - - val callbackCaptor = argumentCaptor() - verify(iconCache).updateIconInBackground(callbackCaptor.capture(), eq(folderItems[3])) - - // Restore high-res icon - folderItems[3].bitmap = originalBitmap - - // Calling with a different item info will ignore the update - callbackCaptor.firstValue.reapplyItemInfo(folderItems[2]) - assertThat(drawingParams.drawable).isInstanceOf(PlaceHolderIconDrawable::class.java) - - // Calling with correct value will update the drawable to high-res - callbackCaptor.firstValue.reapplyItemInfo(folderItems[3]) - assertThat(drawingParams.drawable).isNotInstanceOf(PlaceHolderIconDrawable::class.java) - assertThat(drawingParams.drawable).isInstanceOf(FastBitmapDrawable::class.java) - } - private fun profileFlagOp(type: Int) = UserIconInfo(Process.myUserHandle(), type).applyBitmapInfoFlags(FlagOp.NO_OP) } - -class ThemeStateRule : TestRule { - - var themeState: Boolean? = null - - override fun apply(base: Statement, description: Description): Statement { - themeState = description.getAnnotation(MonoThemeEnabled::class.java)?.value - return base - } -} - -// Annotation for tests that need to be run with quickstep enabled and disabled. -@Retention(RUNTIME) -@Target(FUNCTION, PROPERTY_GETTER, PROPERTY_SETTER) -annotation class MonoThemeEnabled(val value: Boolean = false) - -@LauncherAppSingleton -@Component(modules = [AllModulesForTest::class, FakePrefsModule::class]) -interface PreviewItemManagerTestComponent : LauncherAppComponent { - - @Component.Builder - interface Builder : LauncherAppComponent.Builder { - override fun build(): PreviewItemManagerTestComponent - } -} diff --git a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt new file mode 100644 index 0000000000..370af0cea5 --- /dev/null +++ b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt @@ -0,0 +1,225 @@ +/* + * 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. + */ + +package com.android.launcher3.model + +import android.util.Pair +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.launcher3.model.data.ItemInfo +import com.android.launcher3.model.data.WorkspaceItemInfo +import com.android.launcher3.util.Executors +import com.android.launcher3.util.IntArray +import com.android.launcher3.util.TestUtil.runOnExecutorSync +import com.google.common.truth.Truth.assertThat +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.times +import org.mockito.kotlin.any +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.same +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoMoreInteractions +import org.mockito.kotlin.whenever + +/** Tests for [AddWorkspaceItemsTask] */ +@SmallTest +@RunWith(AndroidJUnit4::class) +class AddWorkspaceItemsTaskTest : AbstractWorkspaceModelTest() { + + private lateinit var mDataModelCallbacks: MyCallbacks + + private val mWorkspaceItemSpaceFinder: WorkspaceItemSpaceFinder = mock() + + @Before + override fun setup() { + super.setup() + mDataModelCallbacks = MyCallbacks() + Executors.MAIN_EXECUTOR.submit { mModelHelper.model.addCallbacks(mDataModelCallbacks) } + .get() + } + + @After + override fun tearDown() { + super.tearDown() + } + + @Test + fun givenNewItemAndNonEmptyPages_whenExecuteTask_thenAddNewItem() { + val itemToAdd = getNewItem() + val nonEmptyScreenIds = listOf(0, 1, 2) + givenNewItemSpaces(NewItemSpace(1, 2, 2)) + + val addedItems = testAddItems(nonEmptyScreenIds, itemToAdd) + + assertThat(addedItems.size).isEqualTo(1) + assertThat(addedItems.first().itemInfo.screenId).isEqualTo(1) + assertThat(addedItems.first().isAnimated).isTrue() + verifyItemSpaceFinderCall(nonEmptyScreenIds, numberOfExpectedCall = 1) + } + + @Test + fun givenNewAndExistingItems_whenExecuteTask_thenOnlyAddNewItem() { + val itemsToAdd = arrayOf(getNewItem(), getExistingItem()) + givenNewItemSpaces(NewItemSpace(1, 0, 0)) + val nonEmptyScreenIds = listOf(0) + + val addedItems = testAddItems(nonEmptyScreenIds, *itemsToAdd) + + assertThat(addedItems.size).isEqualTo(1) + assertThat(addedItems.first().itemInfo.screenId).isEqualTo(1) + assertThat(addedItems.first().isAnimated).isTrue() + verifyItemSpaceFinderCall(nonEmptyScreenIds, numberOfExpectedCall = 1) + } + + @Test + fun givenOnlyExistingItem_whenExecuteTask_thenDoNotAddItem() { + val itemToAdd = getExistingItem() + givenNewItemSpaces(NewItemSpace(1, 0, 0)) + val nonEmptyScreenIds = listOf(0) + + val addedItems = testAddItems(nonEmptyScreenIds, itemToAdd) + + assertThat(addedItems.size).isEqualTo(0) + // b/343530737 + verifyNoMoreInteractions(mWorkspaceItemSpaceFinder) + } + + @Test + fun givenNonSequentialScreenIds_whenExecuteTask_thenReturnNewScreenId() { + val itemToAdd = getNewItem() + givenNewItemSpaces(NewItemSpace(2, 1, 3)) + val nonEmptyScreenIds = listOf(0, 2, 3) + + val addedItems = testAddItems(nonEmptyScreenIds, itemToAdd) + + assertThat(addedItems.size).isEqualTo(1) + assertThat(addedItems.first().itemInfo.screenId).isEqualTo(2) + assertThat(addedItems.first().isAnimated).isTrue() + verifyItemSpaceFinderCall(nonEmptyScreenIds, numberOfExpectedCall = 1) + } + + @Test + fun givenMultipleItems_whenExecuteTask_thenAddThem() { + val itemsToAdd = + arrayOf( + getNewItem(), + getExistingItem(), + getNewItem(), + getNewItem(), + getExistingItem(), + ) + givenNewItemSpaces( + NewItemSpace(1, 3, 3), + NewItemSpace(2, 0, 0), + NewItemSpace(2, 0, 1), + ) + val nonEmptyScreenIds = listOf(0, 1) + + val addedItems = testAddItems(nonEmptyScreenIds, *itemsToAdd) + + // Only the new items should be added + assertThat(addedItems.size).isEqualTo(3) + + // Items that are added to the first screen should not be animated + val itemsAddedToFirstScreen = addedItems.filter { it.itemInfo.screenId == 1 } + assertThat(itemsAddedToFirstScreen.size).isEqualTo(1) + assertThat(itemsAddedToFirstScreen.first().isAnimated).isFalse() + + // Items that are added to the second screen should be animated + val itemsAddedToSecondScreen = addedItems.filter { it.itemInfo.screenId == 2 } + assertThat(itemsAddedToSecondScreen.size).isEqualTo(2) + itemsAddedToSecondScreen.forEach { assertThat(it.isAnimated).isTrue() } + verifyItemSpaceFinderCall(nonEmptyScreenIds, numberOfExpectedCall = 3) + } + + /** Sets up the item space data that will be returned from WorkspaceItemSpaceFinder. */ + private fun givenNewItemSpaces(vararg newItemSpaces: NewItemSpace) { + val spaceStack = newItemSpaces.toMutableList() + whenever( + mWorkspaceItemSpaceFinder.findSpaceForItem(any(), any(), any(), any(), any(), any()) + ) + .then { spaceStack.removeFirst().toIntArray() } + } + + /** + * Verifies if WorkspaceItemSpaceFinder was called with proper arguments and how many times was + * it called. + */ + private fun verifyItemSpaceFinderCall(nonEmptyScreenIds: List, numberOfExpectedCall: Int) { + verify(mWorkspaceItemSpaceFinder, times(numberOfExpectedCall)) + .findSpaceForItem( + same(mAppState), + same(mModelHelper.bgDataModel), + eq(IntArray.wrap(*nonEmptyScreenIds.toIntArray())), + eq(IntArray()), + eq(1), + eq(1) + ) + } + + /** + * Sets up the workspaces with items, executes the task, collects the added items from the model + * callback then returns it. + */ + private fun testAddItems( + nonEmptyScreenIds: List, + vararg itemsToAdd: WorkspaceItemInfo + ): List { + setupWorkspaces(nonEmptyScreenIds) + val task = newTask(*itemsToAdd) + + val addedItems = mutableListOf() + + runOnExecutorSync(Executors.MODEL_EXECUTOR) { + mDataModelCallbacks.addedItems.clear() + mModelHelper.model.enqueueModelUpdateTask(task) + runOnExecutorSync(Executors.MAIN_EXECUTOR) {} + addedItems.addAll(mDataModelCallbacks.addedItems) + } + + return addedItems + } + + /** + * Creates the task with the given items and replaces the WorkspaceItemSpaceFinder dependency + * with a mock. + */ + private fun newTask(vararg items: ItemInfo): AddWorkspaceItemsTask = + items + .map { Pair.create(it, Any()) } + .toMutableList() + .let { AddWorkspaceItemsTask(it, mWorkspaceItemSpaceFinder) } +} + +private data class AddedItem(val itemInfo: ItemInfo, val isAnimated: Boolean) + +private class MyCallbacks : BgDataModel.Callbacks { + + val addedItems = mutableListOf() + + override fun bindAppsAdded( + newScreens: IntArray?, + addNotAnimated: ArrayList, + addAnimated: ArrayList + ) { + addedItems.addAll(addAnimated.map { AddedItem(it, true) }) + addedItems.addAll(addNotAnimated.map { AddedItem(it, false) }) + } +} diff --git a/tests/src/com/android/launcher3/model/AsyncBindingTest.kt b/tests/src/com/android/launcher3/model/AsyncBindingTest.kt new file mode 100644 index 0000000000..af367a814a --- /dev/null +++ b/tests/src/com/android/launcher3/model/AsyncBindingTest.kt @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.model + +import android.os.Looper +import android.platform.test.flag.junit.SetFlagsRule +import android.util.Pair +import android.util.SparseArray +import android.view.View +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.launcher3.Flags +import com.android.launcher3.model.BgDataModel.Callbacks +import com.android.launcher3.model.data.ItemInfo +import com.android.launcher3.util.Executors.MAIN_EXECUTOR +import com.android.launcher3.util.Executors.MODEL_EXECUTOR +import com.android.launcher3.util.IntArray +import com.android.launcher3.util.IntSet +import com.android.launcher3.util.ItemInflater +import com.android.launcher3.util.LauncherLayoutBuilder +import com.android.launcher3.util.LauncherModelHelper +import com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE +import com.android.launcher3.util.RunnableList +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.MockitoAnnotations +import org.mockito.Spy +import org.mockito.kotlin.any +import org.mockito.kotlin.argThat +import org.mockito.kotlin.atLeastOnce +import org.mockito.kotlin.doAnswer +import org.mockito.kotlin.isNull +import org.mockito.kotlin.never +import org.mockito.kotlin.reset +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +/** Tests to verify async binding of model views */ +@SmallTest +@RunWith(AndroidJUnit4::class) +class AsyncBindingTest { + + @get:Rule val setFlagsRule = SetFlagsRule() + + @Spy private var callbacks = MyCallbacks() + @Mock private lateinit var itemInflater: ItemInflater<*> + + private val inflationLooper = SparseArray() + + private lateinit var modelHelper: LauncherModelHelper + + @Before + fun setUp() { + setFlagsRule.enableFlags(Flags.FLAG_ENABLE_WORKSPACE_INFLATION) + MockitoAnnotations.initMocks(this) + modelHelper = LauncherModelHelper() + + doAnswer { i -> + inflationLooper[(i.arguments[0] as ItemInfo).id] = Looper.myLooper() + View(modelHelper.sandboxContext) + } + .whenever(itemInflater) + .inflateItem(any(), any(), isNull()) + + // Set up the workspace with 3 pages of apps + modelHelper.setupDefaultLayoutProvider( + LauncherLayoutBuilder() + .atWorkspace(0, 1, 0) + .putApp(TEST_PACKAGE, TEST_PACKAGE) + .atWorkspace(1, 1, 0) + .putApp(TEST_PACKAGE, TEST_PACKAGE) + .atWorkspace(0, 1, 1) + .putApp(TEST_PACKAGE, TEST_PACKAGE) + .atWorkspace(1, 1, 1) + .putApp(TEST_PACKAGE, TEST_PACKAGE) + .atWorkspace(0, 1, 2) + .putApp(TEST_PACKAGE, TEST_PACKAGE) + ) + } + + @After + fun tearDown() { + modelHelper.destroy() + } + + @Test + fun test_bind_normally_without_itemInflater() { + MAIN_EXECUTOR.execute { modelHelper.model.addCallbacksAndLoad(callbacks) } + waitForLoaderAndTempMainThread() + + verify(callbacks, never()).bindInflatedItems(any()) + verify(callbacks, atLeastOnce()).bindItems(any(), any()) + } + + @Test + fun test_bind_inflates_item_on_background() { + callbacks.inflater = itemInflater + MAIN_EXECUTOR.execute { modelHelper.model.addCallbacksAndLoad(callbacks) } + waitForLoaderAndTempMainThread() + + verify(callbacks, never()).bindItems(any(), any()) + verify(callbacks, times(1)).bindInflatedItems(argThat { t -> t.size == 2 }) + + // Verify remaining items are bound using pendingTasks + reset(callbacks) + MAIN_EXECUTOR.submit(callbacks.pendingTasks!!::executeAllAndDestroy).get() + verify(callbacks, times(1)).bindInflatedItems(argThat { t -> t.size == 3 }) + + // Verify that all items were inflated on the background thread + assertEquals(5, inflationLooper.size()) + for (i in 0..4) assertEquals(MODEL_EXECUTOR.looper, inflationLooper.valueAt(i)) + } + + @Test + fun test_bind_sync_partially_inflates_on_background() { + modelHelper.loadModelSync() + assertTrue(modelHelper.model.isModelLoaded) + callbacks.inflater = itemInflater + + val firstPageBindIds = IntSet() + + MAIN_EXECUTOR.submit { + modelHelper.model.addCallbacksAndLoad(callbacks) + verify(callbacks, never()).bindItems(any(), any()) + verify(callbacks, times(1)) + .bindInflatedItems( + argThat { t -> + t.forEach { firstPageBindIds.add(it.first.id) } + t.size == 2 + } + ) + + // Verify that onInitialBindComplete is called and the binding is not yet complete + assertFalse(callbacks.onCompleteSignal!!.isDestroyed) + } + .get() + + waitForLoaderAndTempMainThread() + assertTrue(callbacks.onCompleteSignal!!.isDestroyed) + + // Verify that firstPageBindIds are loaded on the main thread and remaining + // on the background thread. + assertEquals(5, inflationLooper.size()) + for (i in 0..4) { + if (firstPageBindIds.contains(inflationLooper.keyAt(i))) + assertEquals(MAIN_EXECUTOR.looper, inflationLooper.valueAt(i)) + else assertEquals(MODEL_EXECUTOR.looper, inflationLooper.valueAt(i)) + } + + MAIN_EXECUTOR.submit { + reset(callbacks) + callbacks.pendingTasks!!.executeAllAndDestroy() + // Verify remaining 3 times are bound using pending tasks + verify(callbacks, times(1)).bindInflatedItems(argThat { t -> t.size == 3 }) + } + .get() + } + + private fun waitForLoaderAndTempMainThread() { + MAIN_EXECUTOR.submit {}.get() + MODEL_EXECUTOR.submit {}.get() + MAIN_EXECUTOR.submit {}.get() + } + + class MyCallbacks : Callbacks { + + var inflater: ItemInflater<*>? = null + var pendingTasks: RunnableList? = null + var onCompleteSignal: RunnableList? = null + + override fun bindItems(shortcuts: MutableList, forceAnimateIcons: Boolean) {} + + override fun bindInflatedItems(items: MutableList>) {} + + override fun getPagesToBindSynchronously(orderedScreenIds: IntArray?) = IntSet.wrap(0) + + override fun onInitialBindComplete( + boundPages: IntSet, + pendingTasks: RunnableList, + onCompleteSignal: RunnableList, + workspaceItemCount: Int, + isBindSync: Boolean + ) { + this.pendingTasks = pendingTasks + this.onCompleteSignal = onCompleteSignal + } + + override fun getItemInflater() = inflater + } +} diff --git a/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java b/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java new file mode 100644 index 0000000000..328558d1a7 --- /dev/null +++ b/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java @@ -0,0 +1,161 @@ +package com.android.launcher3.model; + +import static android.os.Process.myUserHandle; + +import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; +import static com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY; +import static com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY2; +import static com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY3; +import static com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE; +import static com.android.launcher3.util.TestUtil.runOnExecutorSync; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.content.Context; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.icons.BitmapInfo; +import com.android.launcher3.model.data.FolderInfo; +import com.android.launcher3.model.data.WorkspaceItemInfo; +import com.android.launcher3.util.IntSet; +import com.android.launcher3.util.LauncherLayoutBuilder; +import com.android.launcher3.util.LauncherModelHelper; +import com.android.launcher3.util.PackageUserKey; +import com.android.launcher3.util.rule.TestStabilityRule; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; +import org.junit.runner.RunWith; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; + +/** + * Tests for {@link CacheDataUpdatedTask} + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class CacheDataUpdatedTaskTest { + + @Rule(order = 0) + public TestRule testStabilityRule = new TestStabilityRule(); + + private static final String PENDING_APP_1 = TEST_PACKAGE + ".pending1"; + private static final String PENDING_APP_2 = TEST_PACKAGE + ".pending2"; + + private LauncherModelHelper mModelHelper; + private Context mContext; + + private int mSession1; + + @Before + public void setup() throws Exception { + mModelHelper = new LauncherModelHelper(); + mContext = mModelHelper.sandboxContext; + mSession1 = mModelHelper.createInstallerSession(PENDING_APP_1); + mModelHelper.createInstallerSession(PENDING_APP_2); + + LauncherLayoutBuilder builder = new LauncherLayoutBuilder() + .atHotseat(1).putFolder("MyFolder") + .addApp(TEST_PACKAGE, TEST_ACTIVITY) // 2 + .addApp(TEST_PACKAGE, TEST_ACTIVITY2) // 3 + .addApp(TEST_PACKAGE, TEST_ACTIVITY3) // 4 + + // Pending App 1 + .addApp(PENDING_APP_1, TEST_ACTIVITY) // 5 + .addApp(PENDING_APP_1, TEST_ACTIVITY2) // 6 + .addApp(PENDING_APP_1, TEST_ACTIVITY3) // 7 + + // Pending App 2 + .addApp(PENDING_APP_2, TEST_ACTIVITY) // 8 + .addApp(PENDING_APP_2, TEST_ACTIVITY2) // 9 + .addApp(PENDING_APP_2, TEST_ACTIVITY3) // 10 + .build(); + mModelHelper.setupDefaultLayoutProvider(builder); + mModelHelper.loadModelSync(); + assertEquals(10, mModelHelper.getBgDataModel().itemsIdMap.size()); + } + + @After + public void tearDown() { + mModelHelper.destroy(); + } + + private CacheDataUpdatedTask newTask(int op, String... pkg) { + return new CacheDataUpdatedTask(op, myUserHandle(), + new HashSet<>(Arrays.asList(pkg))); + } + + @Test + public void testCacheUpdate_update_apps() { + // Run on model executor so that no other task runs in the middle. + runOnExecutorSync(MODEL_EXECUTOR, () -> { + // Clear all icons from apps list so that its easy to check what was updated + allItems().forEach(wi -> wi.bitmap = BitmapInfo.LOW_RES_INFO); + + mModelHelper.getModel().enqueueModelUpdateTask( + newTask(CacheDataUpdatedTask.OP_CACHE_UPDATE, TEST_PACKAGE)); + + // Verify that only the app icons of TEST_PACKAGE (id 2, 3, 4) are updated. + verifyUpdate(2, 3, 4); + }); + } + + @Test + public void testSessionUpdate_ignores_normal_apps() { + // Run on model executor so that no other task runs in the middle. + runOnExecutorSync(MODEL_EXECUTOR, () -> { + // Clear all icons from apps list so that its easy to check what was updated + allItems().forEach(wi -> wi.bitmap = BitmapInfo.LOW_RES_INFO); + + mModelHelper.getModel().enqueueModelUpdateTask( + newTask(CacheDataUpdatedTask.OP_SESSION_UPDATE, TEST_PACKAGE)); + + // TEST_PACKAGE has no restored shortcuts. Verify that nothing was updated. + verifyUpdate(); + }); + } + + @Test + public void testSessionUpdate_updates_pending_apps() { + // Run on model executor so that no other task runs in the middle. + runOnExecutorSync(MODEL_EXECUTOR, () -> { + LauncherAppState.getInstance(mContext).getIconCache().updateSessionCache( + new PackageUserKey(PENDING_APP_1, myUserHandle()), + mContext.getPackageManager().getPackageInstaller().getSessionInfo(mSession1)); + + // Clear all icons from apps list so that its easy to check what was updated + allItems().forEach(wi -> wi.bitmap = BitmapInfo.LOW_RES_INFO); + + mModelHelper.getModel().enqueueModelUpdateTask( + newTask(CacheDataUpdatedTask.OP_SESSION_UPDATE, PENDING_APP_1)); + + // Only restored apps from PENDING_APP_1 (id 5, 6, 7) are updated + verifyUpdate(5, 6, 7); + }); + } + + private void verifyUpdate(int... idsUpdated) { + IntSet updates = IntSet.wrap(idsUpdated); + for (WorkspaceItemInfo info : allItems()) { + if (updates.contains(info.id)) { + assertFalse(info.bitmap.isNullOrLowRes()); + } else { + assertTrue(info.bitmap.isNullOrLowRes()); + } + } + } + + private List allItems() { + return ((FolderInfo) mModelHelper.getBgDataModel().itemsIdMap.get(1)).getAppContents(); + } +} diff --git a/tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java b/tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java new file mode 100644 index 0000000000..cea95e5a50 --- /dev/null +++ b/tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.model; + +import static androidx.test.InstrumentationRegistry.getContext; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertNotSame; +import static junit.framework.Assert.assertTrue; + +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.content.ContentValues; +import android.content.Context; +import android.content.res.Resources; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.launcher3.LauncherSettings.Favorites; +import com.android.launcher3.R; +import com.android.launcher3.pm.UserCache; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; + +/** + * Tests for {@link DbDowngradeHelper} + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class DbDowngradeHelperTest { + + private static final String SCHEMA_FILE = "test_schema.json"; + private static final String DB_FILE = "test.db"; + + private Context mContext; + private File mSchemaFile; + private File mDbFile; + + @Before + public void setup() { + mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + mSchemaFile = mContext.getFileStreamPath(SCHEMA_FILE); + mDbFile = mContext.getDatabasePath(DB_FILE); + } + + @Test + public void testDowngradeSchemaMatchesVersion() throws Exception { + mSchemaFile.delete(); + assertFalse(mSchemaFile.exists()); + DbDowngradeHelper.updateSchemaFile(mSchemaFile, 0, mContext); + assertEquals(DatabaseHelper.SCHEMA_VERSION, DbDowngradeHelper.parse(mSchemaFile).version); + } + + @Test + public void testUpdateSchemaFile() throws Exception { + // Setup mock resources + Resources res = spy(mContext.getResources()); + Resources myRes = getContext().getResources(); + doAnswer(i -> myRes.openRawResource( + myRes.getIdentifier("db_schema_v10", "raw", getContext().getPackageName()))) + .when(res).openRawResource(R.raw.downgrade_schema); + Context context = spy(mContext); + when(context.getResources()).thenReturn(res); + + mSchemaFile.delete(); + assertFalse(mSchemaFile.exists()); + + DbDowngradeHelper.updateSchemaFile(mSchemaFile, 10, context); + assertTrue(mSchemaFile.exists()); + assertEquals(10, DbDowngradeHelper.parse(mSchemaFile).version); + + // Schema is updated on version upgrade + assertTrue(mSchemaFile.setLastModified(0)); + DbDowngradeHelper.updateSchemaFile(mSchemaFile, 11, context); + assertNotSame(0, mSchemaFile.lastModified()); + + // Schema is not updated when version is same + assertTrue(mSchemaFile.setLastModified(0)); + DbDowngradeHelper.updateSchemaFile(mSchemaFile, 10, context); + assertEquals(0, mSchemaFile.lastModified()); + + // Schema is not updated on version downgrade + DbDowngradeHelper.updateSchemaFile(mSchemaFile, 3, context); + assertEquals(0, mSchemaFile.lastModified()); + } + + @Test + public void testDowngrade_success_v31() throws Exception { + setupTestDb(); + + try (SQLiteOpenHelper helper = new MyDatabaseHelper()) { + assertFalse(hasFavoritesColumn(helper.getWritableDatabase(), "iconPackage")); + assertFalse(hasFavoritesColumn(helper.getWritableDatabase(), "iconResource")); + } + + try (TestOpenHelper helper = new TestOpenHelper(24)) { + assertTrue(hasFavoritesColumn(helper.getWritableDatabase(), "iconPackage")); + assertTrue(hasFavoritesColumn(helper.getWritableDatabase(), "iconResource")); + } + } + + @Test + public void testDowngrade_success_v24() throws Exception { + setupTestDb(); + + TestOpenHelper helper = new TestOpenHelper(24); + assertEquals(24, helper.getReadableDatabase().getVersion()); + helper.close(); + } + + @Test + public void testDowngrade_success_v22() throws Exception { + setupTestDb(); + + try (SQLiteOpenHelper helper = new TestOpenHelper(22)) { + assertEquals(22, helper.getWritableDatabase().getVersion()); + assertFalse(hasFavoritesColumn(helper.getWritableDatabase(), Favorites.OPTIONS)); + assertEquals(10, getFavoriteDataCount(helper.getWritableDatabase())); + } + + try (SQLiteOpenHelper helper = new MyDatabaseHelper()) { + assertEquals(DatabaseHelper.SCHEMA_VERSION, + helper.getWritableDatabase().getVersion()); + assertTrue(hasFavoritesColumn(helper.getWritableDatabase(), Favorites.OPTIONS)); + assertEquals(10, getFavoriteDataCount(helper.getWritableDatabase())); + } + } + + @Test(expected = DowngradeFailException.class) + public void testDowngrade_fail_v20() throws Exception { + setupTestDb(); + + TestOpenHelper helper = new TestOpenHelper(20); + helper.getReadableDatabase().getVersion(); + } + + private void setupTestDb() throws Exception { + mSchemaFile.delete(); + mDbFile.delete(); + + DbDowngradeHelper.updateSchemaFile(mSchemaFile, DatabaseHelper.SCHEMA_VERSION, mContext); + + DatabaseHelper dbHelper = new MyDatabaseHelper(); + // Insert mock data + for (int i = 0; i < 10; i++) { + ContentValues values = new ContentValues(); + values.put(Favorites._ID, i); + values.put(Favorites.TITLE, "title " + i); + dbHelper.getWritableDatabase().insert(Favorites.TABLE_NAME, null, values); + } + dbHelper.close(); + } + + private class TestOpenHelper extends SQLiteOpenHelper { + + public TestOpenHelper(int version) { + super(mContext, DB_FILE, null, version); + } + + @Override + public void onCreate(SQLiteDatabase sqLiteDatabase) { + throw new RuntimeException("DB should already be created"); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + throw new RuntimeException("Only downgrade supported"); + } + + @Override + public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { + try { + DbDowngradeHelper.parse(mSchemaFile).onDowngrade(db, oldVersion, newVersion); + } catch (Exception e) { + throw new DowngradeFailException(e); + } + } + } + + private static class DowngradeFailException extends RuntimeException { + public DowngradeFailException(Exception e) { + super(e); + } + } + + private static boolean hasFavoritesColumn(SQLiteDatabase db, String columnName) { + try (Cursor c = db.query(Favorites.TABLE_NAME, null, null, null, null, null, null)) { + return c.getColumnIndex(columnName) >= 0; + } + } + + public static int getFavoriteDataCount(SQLiteDatabase db) { + try (Cursor c = db.query(Favorites.TABLE_NAME, null, null, null, null, null, null)) { + return c.getCount(); + } + } + + private class MyDatabaseHelper extends DatabaseHelper { + + MyDatabaseHelper() { + super(mContext, DB_FILE, + UserCache.INSTANCE.get(mContext)::getSerialNumberForUser, + () -> { }); + } + + @Override + public void onOpen(SQLiteDatabase db) { } + } +} diff --git a/tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java b/tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java new file mode 100644 index 0000000000..10785f7406 --- /dev/null +++ b/tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.model; + +import static com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY; +import static com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assume.assumeTrue; + +import android.content.Context; +import android.content.pm.LauncherApps; +import android.content.pm.PackageInstaller; +import android.content.pm.PackageInstaller.SessionParams; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.android.launcher3.LauncherSettings; +import com.android.launcher3.icons.BitmapInfo; +import com.android.launcher3.model.data.FolderInfo; +import com.android.launcher3.model.data.ItemInfo; +import com.android.launcher3.util.LauncherLayoutBuilder; +import com.android.launcher3.util.LauncherModelHelper; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for layout parser for remote layout + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class DefaultLayoutProviderTest { + + private LauncherModelHelper mModelHelper; + private Context mTargetContext; + + @Before + public void setUp() { + mModelHelper = new LauncherModelHelper(); + mTargetContext = mModelHelper.sandboxContext; + } + + @After + public void tearDown() { + mModelHelper.destroy(); + } + + @Test + public void testCustomProfileLoaded_with_icon_on_hotseat() throws Exception { + writeLayoutAndLoad(new LauncherLayoutBuilder().atHotseat(0) + .putApp(TEST_PACKAGE, TEST_ACTIVITY)); + + // Verify one item in hotseat + assertEquals(1, mModelHelper.getBgDataModel().workspaceItems.size()); + ItemInfo info = mModelHelper.getBgDataModel().workspaceItems.get(0); + assertEquals(LauncherSettings.Favorites.CONTAINER_HOTSEAT, info.container); + assertEquals(LauncherSettings.Favorites.ITEM_TYPE_APPLICATION, info.itemType); + } + + @Test + public void testCustomProfileLoaded_with_folder() throws Exception { + writeLayoutAndLoad(new LauncherLayoutBuilder().atHotseat(0).putFolder(android.R.string.copy) + .addApp(TEST_PACKAGE, TEST_ACTIVITY) + .addApp(TEST_PACKAGE, TEST_ACTIVITY) + .addApp(TEST_PACKAGE, TEST_ACTIVITY) + .build()); + + // Verify folder + assertEquals(1, mModelHelper.getBgDataModel().workspaceItems.size()); + ItemInfo info = mModelHelper.getBgDataModel().workspaceItems.get(0); + assertEquals(LauncherSettings.Favorites.ITEM_TYPE_FOLDER, info.itemType); + assertEquals(3, ((FolderInfo) info).getContents().size()); + } + + @Test + public void testCustomProfileLoaded_with_folder_custom_title() throws Exception { + writeLayoutAndLoad(new LauncherLayoutBuilder().atHotseat(0).putFolder("CustomFolder") + .addApp(TEST_PACKAGE, TEST_ACTIVITY) + .addApp(TEST_PACKAGE, TEST_ACTIVITY) + .addApp(TEST_PACKAGE, TEST_ACTIVITY) + .build()); + + // Verify folder + assertEquals(1, mModelHelper.getBgDataModel().workspaceItems.size()); + ItemInfo info = mModelHelper.getBgDataModel().workspaceItems.get(0); + assertEquals(LauncherSettings.Favorites.ITEM_TYPE_FOLDER, info.itemType); + assertEquals(3, ((FolderInfo) info).getContents().size()); + assertEquals("CustomFolder", info.title.toString()); + } + + @Test + public void testCustomProfileLoaded_with_widget() throws Exception { + String pendingAppPkg = "com.test.pending"; + + // Add a placeholder session info so that the widget exists + SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL); + params.setAppPackageName(pendingAppPkg); + params.setAppIcon(BitmapInfo.LOW_RES_ICON); + + PackageInstaller installer = mTargetContext.getPackageManager().getPackageInstaller(); + installer.createSession(params); + + writeLayoutAndLoad(new LauncherLayoutBuilder().atWorkspace(0, 1, 0) + .putWidget(pendingAppPkg, "PlaceholderWidget", 2, 2)); + + // Verify widget + assertEquals(1, mModelHelper.getBgDataModel().appWidgets.size()); + ItemInfo info = mModelHelper.getBgDataModel().appWidgets.get(0); + assertEquals(LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET, info.itemType); + assertEquals(2, info.spanX); + assertEquals(2, info.spanY); + } + + @Test + public void testCustomProfileLoaded_with_shortcut_on_hotseat() throws Exception { + assumeTrue(mTargetContext.getSystemService(LauncherApps.class).hasShortcutHostPermission()); + writeLayoutAndLoad(new LauncherLayoutBuilder().atHotseat(0) + .putShortcut(TEST_PACKAGE, "shortcut2")); + + // Verify one item in hotseat + assertEquals(1, mModelHelper.getBgDataModel().workspaceItems.size()); + ItemInfo info = mModelHelper.getBgDataModel().workspaceItems.get(0); + assertEquals(LauncherSettings.Favorites.CONTAINER_HOTSEAT, info.container); + assertEquals(LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT, info.itemType); + } + + @Test + public void testCustomProfileLoaded_with_shortcut_in_folder() throws Exception { + assumeTrue(mTargetContext.getSystemService(LauncherApps.class).hasShortcutHostPermission()); + writeLayoutAndLoad(new LauncherLayoutBuilder().atHotseat(0).putFolder(android.R.string.copy) + .addApp(TEST_PACKAGE, TEST_ACTIVITY) + .addApp(TEST_PACKAGE, TEST_ACTIVITY) + .addShortcut(TEST_PACKAGE, "shortcut2") + .build()); + + // Verify folder + assertEquals(1, mModelHelper.getBgDataModel().workspaceItems.size()); + FolderInfo info = (FolderInfo) mModelHelper.getBgDataModel().workspaceItems.get(0); + assertEquals(3, info.getContents().size()); + + // Verify last icon + assertEquals(LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT, + info.getContents().get(info.getContents().size() - 1).itemType); + } + + private void writeLayoutAndLoad(LauncherLayoutBuilder builder) throws Exception { + mModelHelper.setupDefaultLayoutProvider(builder).loadModelSync(); + } +} diff --git a/tests/src/com/android/launcher3/model/FirstScreenBroadcastHelperTest.kt b/tests/src/com/android/launcher3/model/FirstScreenBroadcastHelperTest.kt new file mode 100644 index 0000000000..aadf72e801 --- /dev/null +++ b/tests/src/com/android/launcher3/model/FirstScreenBroadcastHelperTest.kt @@ -0,0 +1,386 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.model + +import android.app.PendingIntent +import android.content.ComponentName +import android.content.Intent +import android.content.pm.PackageInstaller.SessionInfo +import android.os.UserHandle +import androidx.test.platform.app.InstrumentationRegistry +import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP +import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT +import com.android.launcher3.model.FirstScreenBroadcastHelper.MAX_BROADCAST_SIZE +import com.android.launcher3.model.FirstScreenBroadcastHelper.getTotalItemCount +import com.android.launcher3.model.FirstScreenBroadcastHelper.truncateModelForBroadcast +import com.android.launcher3.model.data.FolderInfo +import com.android.launcher3.model.data.LauncherAppWidgetInfo +import com.android.launcher3.model.data.WorkspaceItemInfo +import com.android.launcher3.util.PackageManagerHelper +import com.android.launcher3.util.PackageUserKey +import junit.framework.Assert.assertEquals +import org.junit.Test +import org.mockito.ArgumentCaptor +import org.mockito.kotlin.mock +import org.mockito.kotlin.spy +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +class FirstScreenBroadcastHelperTest { + private val context = spy(InstrumentationRegistry.getInstrumentation().targetContext) + private val mockPmHelper = mock() + private val expectedAppPackage = "appPackageExpected" + private val expectedComponentName = ComponentName(expectedAppPackage, "expectedClass") + private val expectedInstallerPackage = "installerPackage" + private val expectedIntent = + Intent().apply { + component = expectedComponentName + setPackage(expectedAppPackage) + } + private val unexpectedAppPackage = "appPackageUnexpected" + private val unexpectedComponentName = ComponentName(expectedAppPackage, "unexpectedClass") + private val firstScreenItems = + listOf( + WorkspaceItemInfo().apply { + container = CONTAINER_DESKTOP + intent = expectedIntent + }, + WorkspaceItemInfo().apply { + container = CONTAINER_HOTSEAT + intent = expectedIntent + }, + LauncherAppWidgetInfo().apply { providerName = expectedComponentName } + ) + + @Test + fun `Broadcast Models are created with Pending Items from first screen`() { + // Given + val sessionInfoExpected = + SessionInfo().apply { + installerPackageName = expectedInstallerPackage + appPackageName = expectedAppPackage + } + val sessionInfoUnexpected = + SessionInfo().apply { + installerPackageName = expectedInstallerPackage + appPackageName = unexpectedAppPackage + } + val sessionInfoMap: HashMap = + hashMapOf( + PackageUserKey(unexpectedAppPackage, UserHandle(0)) to sessionInfoExpected, + PackageUserKey(expectedAppPackage, UserHandle(0)) to sessionInfoUnexpected + ) + + // When + val actualResult = + FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast( + packageManagerHelper = mockPmHelper, + firstScreenItems = firstScreenItems, + userKeyToSessionMap = sessionInfoMap, + allWidgets = listOf() + ) + + // Then + val expectedResult = + listOf( + FirstScreenBroadcastModel( + installerPackage = expectedInstallerPackage, + pendingWorkspaceItems = mutableSetOf(expectedAppPackage), + pendingHotseatItems = mutableSetOf(expectedAppPackage), + pendingWidgetItems = mutableSetOf(expectedAppPackage) + ) + ) + + assertEquals(expectedResult, actualResult) + } + + @Test + fun `Broadcast Models are created with Installed Items from first screen`() { + // Given + whenever(mockPmHelper.getAppInstallerPackage(expectedAppPackage)) + .thenReturn(expectedInstallerPackage) + + // When + val actualResult = + FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast( + packageManagerHelper = mockPmHelper, + firstScreenItems = firstScreenItems, + userKeyToSessionMap = hashMapOf(), + allWidgets = + listOf( + LauncherAppWidgetInfo().apply { + providerName = expectedComponentName + screenId = 0 + } + ) + ) + + // Then + val expectedResult = + listOf( + FirstScreenBroadcastModel( + installerPackage = expectedInstallerPackage, + installedHotseatItems = mutableSetOf(expectedAppPackage), + installedWorkspaceItems = mutableSetOf(expectedAppPackage), + firstScreenInstalledWidgets = mutableSetOf(expectedAppPackage) + ) + ) + assertEquals(expectedResult, actualResult) + } + + @Test + fun `Broadcast Models are created with Installed Widgets from every screen`() { + // Given + val expectedAppPackage2 = "appPackageExpected2" + val expectedComponentName2 = ComponentName(expectedAppPackage2, "expectedClass2") + whenever(mockPmHelper.getAppInstallerPackage(expectedAppPackage)) + .thenReturn(expectedInstallerPackage) + whenever(mockPmHelper.getAppInstallerPackage(expectedAppPackage2)) + .thenReturn(expectedInstallerPackage) + + // When + val actualResult = + FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast( + packageManagerHelper = mockPmHelper, + firstScreenItems = listOf(), + userKeyToSessionMap = hashMapOf(), + allWidgets = + listOf( + LauncherAppWidgetInfo().apply { + providerName = expectedComponentName + screenId = 0 + }, + LauncherAppWidgetInfo().apply { + providerName = expectedComponentName2 + screenId = 1 + }, + LauncherAppWidgetInfo().apply { + providerName = unexpectedComponentName + screenId = 0 + } + ) + ) + + // Then + val expectedResult = + listOf( + FirstScreenBroadcastModel( + installerPackage = expectedInstallerPackage, + installedHotseatItems = mutableSetOf(), + installedWorkspaceItems = mutableSetOf(), + firstScreenInstalledWidgets = mutableSetOf(expectedAppPackage), + secondaryScreenInstalledWidgets = mutableSetOf(expectedAppPackage2) + ) + ) + assertEquals(expectedResult, actualResult) + } + + @Test + fun `Broadcast Models are created with Pending Items in Collections from the first screen`() { + // Given + val sessionInfoExpected = + SessionInfo().apply { + installerPackageName = expectedInstallerPackage + appPackageName = expectedAppPackage + } + val sessionInfoUnexpected = + SessionInfo().apply { + installerPackageName = expectedInstallerPackage + appPackageName = unexpectedAppPackage + } + val sessionInfoMap: HashMap = + hashMapOf( + PackageUserKey(unexpectedAppPackage, UserHandle(0)) to sessionInfoExpected, + PackageUserKey(expectedAppPackage, UserHandle(0)) to sessionInfoUnexpected, + ) + val expectedItemInfo = WorkspaceItemInfo().apply { intent = expectedIntent } + val expectedFolderInfo = FolderInfo().apply { add(expectedItemInfo) } + val firstScreenItems = listOf(expectedFolderInfo) + + // When + val actualResult = + FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast( + packageManagerHelper = mockPmHelper, + firstScreenItems = firstScreenItems, + userKeyToSessionMap = sessionInfoMap, + allWidgets = listOf() + ) + + // Then + val expectedResult = + listOf( + FirstScreenBroadcastModel( + installerPackage = expectedInstallerPackage, + pendingCollectionItems = mutableSetOf(expectedAppPackage) + ) + ) + assertEquals(expectedResult, actualResult) + } + + @Test + fun `Models with too many items get truncated to max Broadcast size`() { + // given + val broadcastModel = + FirstScreenBroadcastModel( + installerPackage = expectedInstallerPackage, + pendingCollectionItems = + mutableSetOf().apply { repeat(20) { add(it.toString()) } }, + pendingWorkspaceItems = + mutableSetOf().apply { repeat(20) { add(it.toString()) } }, + pendingHotseatItems = + mutableSetOf().apply { repeat(20) { add(it.toString()) } }, + pendingWidgetItems = + mutableSetOf().apply { repeat(20) { add(it.toString()) } }, + installedWorkspaceItems = + mutableSetOf().apply { repeat(20) { add(it.toString()) } }, + installedHotseatItems = + mutableSetOf().apply { repeat(20) { add(it.toString()) } }, + firstScreenInstalledWidgets = + mutableSetOf().apply { repeat(20) { add(it.toString()) } }, + secondaryScreenInstalledWidgets = + mutableSetOf().apply { repeat(20) { add(it.toString()) } } + ) + + // When + broadcastModel.truncateModelForBroadcast() + + // Then + assertEquals(MAX_BROADCAST_SIZE, broadcastModel.getTotalItemCount()) + } + + @Test + fun `Broadcast truncates installed Hotseat items before other installed items`() { + // Given + val broadcastModel = + FirstScreenBroadcastModel( + installerPackage = expectedInstallerPackage, + installedWorkspaceItems = + mutableSetOf().apply { repeat(50) { add(it.toString()) } }, + firstScreenInstalledWidgets = + mutableSetOf().apply { repeat(10) { add(it.toString()) } }, + secondaryScreenInstalledWidgets = + mutableSetOf().apply { repeat(10) { add((it + 10).toString()) } }, + installedHotseatItems = + mutableSetOf().apply { repeat(10) { add(it.toString()) } }, + ) + + // When + broadcastModel.truncateModelForBroadcast() + + // Then + assertEquals(MAX_BROADCAST_SIZE, broadcastModel.getTotalItemCount()) + assertEquals(50, broadcastModel.installedWorkspaceItems.size) + assertEquals(10, broadcastModel.firstScreenInstalledWidgets.size) + assertEquals(10, broadcastModel.secondaryScreenInstalledWidgets.size) + assertEquals(0, broadcastModel.installedHotseatItems.size) + } + + @Test + fun `Broadcast truncates Widgets before the rest of the first screen items`() { + // Given + val broadcastModel = + FirstScreenBroadcastModel( + installerPackage = expectedInstallerPackage, + installedWorkspaceItems = + mutableSetOf().apply { repeat(70) { add(it.toString()) } }, + firstScreenInstalledWidgets = + mutableSetOf().apply { repeat(20) { add(it.toString()) } }, + secondaryScreenInstalledWidgets = + mutableSetOf().apply { repeat(20) { add(it.toString()) } }, + ) + + // When + broadcastModel.truncateModelForBroadcast() + + // Then + assertEquals(MAX_BROADCAST_SIZE, broadcastModel.getTotalItemCount()) + assertEquals(70, broadcastModel.installedWorkspaceItems.size) + assertEquals(0, broadcastModel.firstScreenInstalledWidgets.size) + assertEquals(0, broadcastModel.secondaryScreenInstalledWidgets.size) + } + + @Test + fun `Broadcasts are correctly formed with Extras for each Installer`() { + // Given + val broadcastModels: List = + listOf( + FirstScreenBroadcastModel( + installerPackage = expectedInstallerPackage, + pendingCollectionItems = mutableSetOf("pendingCollectionItem"), + pendingWorkspaceItems = mutableSetOf("pendingWorkspaceItem"), + pendingHotseatItems = mutableSetOf("pendingHotseatItems"), + pendingWidgetItems = mutableSetOf("pendingWidgetItems"), + installedWorkspaceItems = mutableSetOf("installedWorkspaceItems"), + installedHotseatItems = mutableSetOf("installedHotseatItems"), + firstScreenInstalledWidgets = mutableSetOf("firstScreenInstalledWidgetItems"), + secondaryScreenInstalledWidgets = mutableSetOf("secondaryInstalledWidgetItems") + ) + ) + val expectedPendingIntent = + PendingIntent.getActivity( + context, + 0 /* requestCode */, + Intent(), + PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE + ) + + // When + FirstScreenBroadcastHelper.sendBroadcastsForModels(context, broadcastModels) + + // Then + val argumentCaptor = ArgumentCaptor.forClass(Intent::class.java) + verify(context).sendBroadcast(argumentCaptor.capture()) + + assertEquals( + "com.android.launcher3.action.FIRST_SCREEN_ACTIVE_INSTALLS", + argumentCaptor.value.action + ) + assertEquals(expectedInstallerPackage, argumentCaptor.value.`package`) + assertEquals( + expectedPendingIntent, + argumentCaptor.value.getParcelableExtra("verificationToken") + ) + assertEquals( + arrayListOf("pendingCollectionItem"), + argumentCaptor.value.getStringArrayListExtra("folderItem") + ) + assertEquals( + arrayListOf("pendingWorkspaceItem"), + argumentCaptor.value.getStringArrayListExtra("workspaceItem") + ) + assertEquals( + arrayListOf("pendingHotseatItems"), + argumentCaptor.value.getStringArrayListExtra("hotseatItem") + ) + assertEquals( + arrayListOf("pendingWidgetItems"), + argumentCaptor.value.getStringArrayListExtra("widgetItem") + ) + assertEquals( + arrayListOf("installedWorkspaceItems"), + argumentCaptor.value.getStringArrayListExtra("workspaceInstalledItems") + ) + assertEquals( + arrayListOf("installedHotseatItems"), + argumentCaptor.value.getStringArrayListExtra("hotseatInstalledItems") + ) + assertEquals( + arrayListOf("firstScreenInstalledWidgetItems", "secondaryInstalledWidgetItems"), + argumentCaptor.value.getStringArrayListExtra("widgetInstalledItems") + ) + } +} diff --git a/tests/src/com/android/launcher3/model/FolderIconLoadTest.kt b/tests/src/com/android/launcher3/model/FolderIconLoadTest.kt new file mode 100644 index 0000000000..c4a4c9b7b6 --- /dev/null +++ b/tests/src/com/android/launcher3/model/FolderIconLoadTest.kt @@ -0,0 +1,184 @@ +/* + * 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.launcher3.model + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.launcher3.LauncherAppState +import com.android.launcher3.model.data.WorkspaceItemInfo +import com.android.launcher3.util.Executors +import com.android.launcher3.util.LauncherLayoutBuilder +import com.android.launcher3.util.LauncherModelHelper +import com.android.launcher3.util.LauncherModelHelper.* +import com.android.launcher3.util.TestUtil +import com.google.common.truth.Truth.assertThat +import com.google.common.truth.Truth.assertWithMessage +import java.util.concurrent.CountDownLatch +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +/** Tests to verify that folder icons are loaded with appropriate resolution */ +@SmallTest +@RunWith(AndroidJUnit4::class) +class FolderIconLoadTest { + private lateinit var modelHelper: LauncherModelHelper + + private val uniqueActivities = + listOf( + TEST_ACTIVITY, + TEST_ACTIVITY2, + TEST_ACTIVITY3, + TEST_ACTIVITY4, + TEST_ACTIVITY5, + TEST_ACTIVITY6, + TEST_ACTIVITY7, + TEST_ACTIVITY8, + TEST_ACTIVITY9, + TEST_ACTIVITY10, + TEST_ACTIVITY11, + TEST_ACTIVITY12, + TEST_ACTIVITY13, + TEST_ACTIVITY14 + ) + + @Before + fun setUp() { + modelHelper = LauncherModelHelper() + } + + @After + @Throws(Exception::class) + fun tearDown() { + modelHelper.destroy() + TestUtil.uninstallDummyApp() + } + + @Test + @Throws(Exception::class) + fun folderLoadedWithHighRes_2x2() { + val items = setupAndLoadFolder(4) + assertThat(items.size).isEqualTo(4) + verifyHighRes(items, 0, 1, 2, 3) + } + + @Test + @Throws(Exception::class) + fun folderLoadedWithHighRes_3x2() { + val items = setupAndLoadFolder(6) + assertThat(items.size).isEqualTo(6) + verifyHighRes(items, 0, 1, 3, 4) + verifyLowRes(items, 2, 5) + } + + @Test + @Throws(Exception::class) + fun folderLoadedWithHighRes_max_3x3() { + val idp = LauncherAppState.getIDP(modelHelper.sandboxContext) + idp.numFolderColumns = intArrayOf(3, 3, 3, 3) + idp.numFolderRows = intArrayOf(3, 3, 3, 3) + recreateSupportedDeviceProfiles() + + val items = setupAndLoadFolder(14) + verifyHighRes(items, 0, 1, 3, 4) + verifyLowRes(items, 2, 5, 6, 7, 8, 9, 10, 11, 12, 13) + } + + @Test + @Throws(Exception::class) + fun folderLoadedWithHighRes_max_4x4() { + val idp = LauncherAppState.getIDP(modelHelper.sandboxContext) + idp.numFolderColumns = intArrayOf(4, 4, 4, 4) + idp.numFolderRows = intArrayOf(4, 4, 4, 4) + recreateSupportedDeviceProfiles() + + val items = setupAndLoadFolder(14) + verifyHighRes(items, 0, 1, 4, 5) + verifyLowRes(items, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13) + } + + @Test + @Throws(Exception::class) + fun folderLoadedWithHighRes_differentFolderConfigurations() { + val idp = LauncherAppState.getIDP(modelHelper.sandboxContext) + idp.numFolderColumns = intArrayOf(4, 3, 4, 4) + idp.numFolderRows = intArrayOf(4, 3, 4, 4) + recreateSupportedDeviceProfiles() + + val items = setupAndLoadFolder(14) + verifyHighRes(items, 0, 1, 3, 4, 5) + verifyLowRes(items, 2, 6, 7, 8, 9, 10, 11, 12, 13) + } + + @Throws(Exception::class) + private fun setupAndLoadFolder(itemCount: Int): ArrayList { + val builder = + LauncherLayoutBuilder() + .atWorkspace(0, 0, 1) + .putFolder("Sample") + .apply { + for (i in 0..itemCount - 1) this.addApp(TEST_PACKAGE, uniqueActivities[i]) + } + .build() + + modelHelper.setupDefaultLayoutProvider(builder) + modelHelper.loadModelSync() + + // The first load initializes the DB, load again so that icons are now used from the DB + // Wait for the icon cache to be updated and then reload + val app = LauncherAppState.getInstance(modelHelper.sandboxContext) + val cache = app.iconCache + while (cache.isIconUpdateInProgress) { + val wait = CountDownLatch(1) + Executors.MODEL_EXECUTOR.handler.postDelayed({ wait.countDown() }, 10) + wait.await() + } + TestUtil.runOnExecutorSync(Executors.MODEL_EXECUTOR) { cache.clearMemoryCache() } + // Reload again with correct icon state + app.model.forceReload() + modelHelper.loadModelSync() + val collections = modelHelper.getBgDataModel().collections + + assertThat(collections.size()).isEqualTo(1) + assertThat(collections.valueAt(0).getAppContents().size).isEqualTo(itemCount) + return collections.valueAt(0).getAppContents() + } + + private fun verifyHighRes(items: ArrayList, vararg indices: Int) { + for (index in indices) { + assertWithMessage("Index $index was not highRes") + .that(items[index].bitmap.isNullOrLowRes) + .isFalse() + } + } + + private fun verifyLowRes(items: ArrayList, vararg indices: Int) { + for (index in indices) { + assertWithMessage("Index $index was not lowRes") + .that(items[index].bitmap.isNullOrLowRes) + .isTrue() + } + } + + /** Recreate DeviceProfiles after changing InvariantDeviceProfile */ + private fun recreateSupportedDeviceProfiles() { + LauncherAppState.getIDP(modelHelper.sandboxContext).supportedProfiles = + LauncherAppState.getIDP(modelHelper.sandboxContext).supportedProfiles.map { + it.copy(modelHelper.sandboxContext) + } + } +} diff --git a/tests/src/com/android/launcher3/model/GridMigrationTest.kt b/tests/src/com/android/launcher3/model/GridMigrationTest.kt index eed373f23a..15222a4fcf 100644 --- a/tests/src/com/android/launcher3/model/GridMigrationTest.kt +++ b/tests/src/com/android/launcher3/model/GridMigrationTest.kt @@ -17,12 +17,10 @@ package com.android.launcher3.model import android.platform.test.flag.junit.SetFlagsRule -import android.util.Log import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import androidx.test.platform.app.InstrumentationRegistry import com.android.launcher3.Flags -import com.android.launcher3.GridType.Companion.GRID_TYPE_ANY import com.android.launcher3.InvariantDeviceProfile.TYPE_PHONE import com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME import com.android.launcher3.celllayout.board.CellLayoutBoard @@ -33,19 +31,10 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.mockito.kotlin.mock -import org.mockito.kotlin.verify private val phoneContext = InstrumentationRegistry.getInstrumentation().targetContext -data class EntryData( - val x: Int, - val y: Int, - val screenId: Int, - val spanX: Int, - val spanY: Int, - val rank: Int, -) +data class EntryData(val x: Int, val y: Int, val spanX: Int, val spanY: Int, val rank: Int) /** * Holds the data needed to run a test in GridMigrationTest, usually we would have a src @@ -63,15 +52,11 @@ class GridMigrationData(dbFileName: String?, val gridState: DeviceGridState) { phoneContext, dbFileName, { UserCache.INSTANCE.get(phoneContext).getSerialNumberForUser(it) }, - {}, + {} ) - fun readEntries(): List = - GridSizeMigrationDBController.readAllEntries( - dbHelper.readableDatabase, - TABLE_NAME, - phoneContext, - ) + fun readEntries(): List = + GridSizeMigrationUtil.readAllEntries(dbHelper.readableDatabase, TABLE_NAME, phoneContext) } /** @@ -88,36 +73,29 @@ class GridMigrationTest { @Rule val setFlagsRule = SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT) - val modelDelegate = mock() + // Copying the src db for all tests. + @JvmField + @Rule + val fileCopier = + TestToPhoneFileCopier( + src = "databases/GridMigrationTest/$DB_FILE", + dest = "databases/$DB_FILE", + removeOnFinish = true + ) @Before fun setup() { - setFlagsRule.setFlags(true, Flags.FLAG_ONE_GRID_SPECS) + setFlagsRule.setFlags(false, Flags.FLAG_ENABLE_GRID_MIGRATION_FIX) } private fun migrate(src: GridMigrationData, dst: GridMigrationData) { - if (Flags.gridMigrationRefactor()) { - val gridSizeMigrationLogic = GridSizeMigrationLogic() - gridSizeMigrationLogic.migrateGrid( - phoneContext, - src.gridState, - dst.gridState, - dst.dbHelper, - src.dbHelper.readableDatabase, - true, - modelDelegate, - ) - } else { - GridSizeMigrationDBController.migrateGridIfNeeded( - phoneContext, - src.gridState, - dst.gridState, - dst.dbHelper, - src.dbHelper.readableDatabase, - true, - modelDelegate, - ) - } + GridSizeMigrationUtil.migrateGridIfNeeded( + phoneContext, + src.gridState, + dst.gridState, + dst.dbHelper, + src.dbHelper.readableDatabase + ) } /** @@ -136,30 +114,25 @@ class GridMigrationTest { } } - private fun compare(dst: GridMigrationData, target: GridMigrationData, src: GridMigrationData) { - val sort = compareBy({ it.screenId }, { it.cellX }, { it.cellY }) - val mapF = { it: DbEntry -> - EntryData(it.cellX, it.cellY, it.screenId, it.spanX, it.spanY, it.rank) + private fun compare(dst: GridMigrationData, target: GridMigrationData) { + val sort = compareBy({ it.cellX }, { it.cellY }) + val mapF = { it: GridSizeMigrationUtil.DbEntry -> + EntryData(it.cellX, it.cellY, it.spanX, it.spanY, it.rank) } val entriesDst = dst.readEntries().sortedWith(sort).map(mapF) val entriesTarget = target.readEntries().sortedWith(sort).map(mapF) - val entriesSrc = src.readEntries().sortedWith(sort).map(mapF) - Log.i( - TAG, - "entriesSrc: $entriesSrc\n entriesDst: $entriesDst\n entriesTarget: $entriesTarget", - ) + assert(entriesDst == entriesTarget) { "The elements on the dst database is not the same as in the target" } } /** - * Migrate src into dst and compare to target. This method validates 4 things: + * Migrate src into dst and compare to target. This method validates 3 things: * 1. dst has the same number of items as src after the migration, meaning, none of the items * were removed during the migration. * 2. dst is valid, meaning that none of the items overlap with each other. * 3. dst is equal to target to ensure we don't unintentionally change the migration logic. - * 4. migration notifies the complete callback. */ private fun runTest(src: GridMigrationData, dst: GridMigrationData, target: GridMigrationData) { migrate(src, dst) @@ -167,48 +140,30 @@ class GridMigrationTest { "Source db and destination db do not contain the same number of elements" } validateDb(dst) - compare(dst, target, src) - verify(modelDelegate).gridMigrationComplete(src.gridState, dst.gridState) + compare(dst, target) } - // Copying the src db for all tests. - @JvmField - @Rule - val fileCopier = - TestToPhoneFileCopier( - src = "databases/GridMigrationTest/$DB_FILE", - dest = "databases/$DB_FILE", - removeOnFinish = true, - ) - @JvmField @Rule val result5x5to3x3 = TestToPhoneFileCopier( src = "databases/GridMigrationTest/result5x5to3x3.db", dest = "databases/result5x5to3x3.db", - removeOnFinish = true, + removeOnFinish = true ) @Test fun `5x5 to 3x3`() = runTest( - src = - GridMigrationData( - DB_FILE, - DeviceGridState(5, 5, 5, TYPE_PHONE, DB_FILE, GRID_TYPE_ANY), - ), + src = GridMigrationData(DB_FILE, DeviceGridState(5, 5, 5, TYPE_PHONE, DB_FILE)), dst = GridMigrationData( - null, // in memory db, to download a new db change null for - // the filename of the db name to store it. Do not use existing names. - DeviceGridState(3, 3, 3, TYPE_PHONE, "", GRID_TYPE_ANY), + null, // in memory db, to download a new db change null for the filename of the + // db name to store it. Do not use existing names. + DeviceGridState(3, 3, 3, TYPE_PHONE, "") ), target = - GridMigrationData( - "result5x5to3x3.db", - DeviceGridState(3, 3, 3, TYPE_PHONE, "", GRID_TYPE_ANY), - ), + GridMigrationData("result5x5to3x3.db", DeviceGridState(3, 3, 3, TYPE_PHONE, "")) ) @JvmField @@ -217,28 +172,21 @@ class GridMigrationTest { TestToPhoneFileCopier( src = "databases/GridMigrationTest/result5x5to4x7.db", dest = "databases/result5x5to4x7.db", - removeOnFinish = true, + removeOnFinish = true ) @Test fun `5x5 to 4x7`() = runTest( - src = - GridMigrationData( - DB_FILE, - DeviceGridState(5, 5, 5, TYPE_PHONE, DB_FILE, GRID_TYPE_ANY), - ), + src = GridMigrationData(DB_FILE, DeviceGridState(5, 5, 5, TYPE_PHONE, DB_FILE)), dst = GridMigrationData( - null, // in memory db, to download a new db change null for - // the filename of the db name to store it. Do not use existing names. - DeviceGridState(4, 7, 4, TYPE_PHONE, "", GRID_TYPE_ANY), + null, // in memory db, to download a new db change null for the filename of the + // db name to store it. Do not use existing names. + DeviceGridState(4, 7, 4, TYPE_PHONE, "") ), target = - GridMigrationData( - "result5x5to4x7.db", - DeviceGridState(4, 7, 4, TYPE_PHONE, "", GRID_TYPE_ANY), - ), + GridMigrationData("result5x5to4x7.db", DeviceGridState(4, 7, 4, TYPE_PHONE, "")) ) @JvmField @@ -247,32 +195,48 @@ class GridMigrationTest { TestToPhoneFileCopier( src = "databases/GridMigrationTest/result5x5to5x8.db", dest = "databases/result5x5to5x8.db", - removeOnFinish = true, + removeOnFinish = true ) @Test fun `5x5 to 5x8`() = runTest( - src = - GridMigrationData( - DB_FILE, - DeviceGridState(5, 5, 5, TYPE_PHONE, DB_FILE, GRID_TYPE_ANY), - ), + src = GridMigrationData(DB_FILE, DeviceGridState(5, 5, 5, TYPE_PHONE, DB_FILE)), dst = GridMigrationData( - null, // in memory db, to download a new db change null - // for - // the filename of the db name to store it. Do not use existing names. - DeviceGridState(5, 8, 5, TYPE_PHONE, "", GRID_TYPE_ANY), + null, // in memory db, to download a new db change null for the filename of the + // db name to store it. Do not use existing names. + DeviceGridState(5, 8, 5, TYPE_PHONE, "") + ), + target = + GridMigrationData("result5x5to5x8.db", DeviceGridState(5, 8, 5, TYPE_PHONE, "")) + ) + + @JvmField + @Rule + val flaggedResult5x5to5x8 = + TestToPhoneFileCopier( + src = "databases/GridMigrationTest/flagged_result5x5to5x8.db", + dest = "databases/flagged_result5x5to5x8.db", + removeOnFinish = true + ) + + @Test + fun `flagged 5x5 to 5x8`() { + setFlagsRule.setFlags(true, Flags.FLAG_ENABLE_GRID_MIGRATION_FIX) + runTest( + src = GridMigrationData(DB_FILE, DeviceGridState(5, 5, 5, TYPE_PHONE, DB_FILE)), + dst = + GridMigrationData( + null, // in memory db, to download a new db change null for the filename of the + // db name to store it. Do not use existing names. + DeviceGridState(5, 8, 5, TYPE_PHONE, "") ), target = GridMigrationData( - "result5x5to5x8.db", - DeviceGridState(5, 8, 5, TYPE_PHONE, "", GRID_TYPE_ANY), - ), + "flagged_result5x5to5x8.db", + DeviceGridState(5, 8, 5, TYPE_PHONE, "") + ) ) - - companion object { - private const val TAG = "GridMigrationTest" } } diff --git a/tests/src/com/android/launcher3/model/LoaderCursorTest.java b/tests/src/com/android/launcher3/model/LoaderCursorTest.java new file mode 100644 index 0000000000..b4945d7129 --- /dev/null +++ b/tests/src/com/android/launcher3/model/LoaderCursorTest.java @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.model; + +import static androidx.test.InstrumentationRegistry.getContext; + +import static com.android.launcher3.LauncherSettings.Favorites.APPWIDGET_ID; +import static com.android.launcher3.LauncherSettings.Favorites.APPWIDGET_PROVIDER; +import static com.android.launcher3.LauncherSettings.Favorites.APPWIDGET_SOURCE; +import static com.android.launcher3.LauncherSettings.Favorites.CELLX; +import static com.android.launcher3.LauncherSettings.Favorites.CELLY; +import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER; +import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP; +import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT; +import static com.android.launcher3.LauncherSettings.Favorites.ICON; +import static com.android.launcher3.LauncherSettings.Favorites.INTENT; +import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE; +import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; +import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT; +import static com.android.launcher3.LauncherSettings.Favorites.OPTIONS; +import static com.android.launcher3.LauncherSettings.Favorites.PROFILE_ID; +import static com.android.launcher3.LauncherSettings.Favorites.RANK; +import static com.android.launcher3.LauncherSettings.Favorites.RESTORED; +import static com.android.launcher3.LauncherSettings.Favorites.SCREEN; +import static com.android.launcher3.LauncherSettings.Favorites.SPANX; +import static com.android.launcher3.LauncherSettings.Favorites.SPANY; +import static com.android.launcher3.LauncherSettings.Favorites.TITLE; +import static com.android.launcher3.LauncherSettings.Favorites._ID; +import static com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY; + +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 android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.database.MatrixCursor; +import android.os.Process; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.android.launcher3.InvariantDeviceProfile; +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.model.data.ItemInfo; +import com.android.launcher3.model.data.WorkspaceItemInfo; +import com.android.launcher3.util.Executors; +import com.android.launcher3.util.LauncherModelHelper; +import com.android.launcher3.util.PackageManagerHelper; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for {@link LoaderCursor} + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class LoaderCursorTest { + + private LauncherModelHelper mModelHelper; + private LauncherAppState mApp; + private PackageManagerHelper mPmHelper; + + private MatrixCursor mCursor; + private InvariantDeviceProfile mIDP; + private Context mContext; + + private LoaderCursor mLoaderCursor; + + @Before + public void setup() { + mModelHelper = new LauncherModelHelper(); + mContext = mModelHelper.sandboxContext; + mIDP = InvariantDeviceProfile.INSTANCE.get(mContext); + mApp = LauncherAppState.getInstance(mContext); + mPmHelper = PackageManagerHelper.INSTANCE.get(mContext); + + mCursor = new MatrixCursor(new String[] { + ICON, TITLE, _ID, CONTAINER, ITEM_TYPE, + PROFILE_ID, SCREEN, CELLX, CELLY, RESTORED, + INTENT, APPWIDGET_ID, APPWIDGET_PROVIDER, + SPANX, SPANY, RANK, OPTIONS, APPWIDGET_SOURCE + }); + + UserManagerState ums = new UserManagerState(); + mLoaderCursor = new LoaderCursor(mCursor, mApp, ums, mPmHelper, null); + ums.allUsers.put(0, Process.myUserHandle()); + } + + @After + public void tearDown() { + mModelHelper.destroy(); + } + + private void initCursor(int itemType, String title) { + mCursor.newRow() + .add(_ID, 1) + .add(PROFILE_ID, 0) + .add(ITEM_TYPE, itemType) + .add(TITLE, title) + .add(CONTAINER, CONTAINER_DESKTOP); + } + + @Test + public void getAppShortcutInfo_dontAllowMissing_invalidComponent() { + initCursor(ITEM_TYPE_APPLICATION, ""); + assertTrue(mLoaderCursor.moveToNext()); + ComponentName cn = new ComponentName(mContext.getPackageName(), "placeholder-do"); + assertNull(mLoaderCursor.getAppShortcutInfo( + new Intent().setComponent(cn), false /* allowMissingTarget */, true)); + } + + @Test + public void getAppShortcutInfo_dontAllowMissing_validComponent() throws Exception { + ComponentName cn = new ComponentName(getContext(), TEST_ACTIVITY); + initCursor(ITEM_TYPE_APPLICATION, ""); + assertTrue(mLoaderCursor.moveToNext()); + + WorkspaceItemInfo info = Executors.MODEL_EXECUTOR.submit(() -> + mLoaderCursor.getAppShortcutInfo( + new Intent().setComponent(cn), false /* allowMissingTarget */, true)) + .get(); + assertNotNull(info); + assertTrue(PackageManagerHelper.isLauncherAppTarget(info.getIntent())); + } + + @Test + public void getAppShortcutInfo_allowMissing_invalidComponent() throws Exception { + initCursor(ITEM_TYPE_APPLICATION, ""); + assertTrue(mLoaderCursor.moveToNext()); + + ComponentName cn = new ComponentName(mContext.getPackageName(), "placeholder-do"); + WorkspaceItemInfo info = Executors.MODEL_EXECUTOR.submit(() -> + mLoaderCursor.getAppShortcutInfo( + new Intent().setComponent(cn), true /* allowMissingTarget */, true)) + .get(); + assertNotNull(info); + assertTrue(PackageManagerHelper.isLauncherAppTarget(info.getIntent())); + } + + @Test + public void loadSimpleShortcut() { + initCursor(ITEM_TYPE_DEEP_SHORTCUT, "my-shortcut"); + assertTrue(mLoaderCursor.moveToNext()); + + WorkspaceItemInfo info = mLoaderCursor.loadSimpleWorkspaceItem(); + assertTrue(mApp.getIconCache().isDefaultIcon(info.bitmap, info.user)); + assertEquals("my-shortcut", info.title); + assertEquals(ITEM_TYPE_DEEP_SHORTCUT, info.itemType); + } + + @Test + public void checkItemPlacement_outsideBounds() { + mIDP.numRows = 4; + mIDP.numColumns = 4; + mIDP.numDatabaseHotseatIcons = 3; + + // Item outside screen bounds are not placed + assertFalse(mLoaderCursor.checkItemPlacement( + newItemInfo(4, 4, 1, 1, CONTAINER_DESKTOP, 1), true)); + } + + @Test + public void checkItemPlacement_overlappingItems() { + mIDP.numRows = 4; + mIDP.numColumns = 4; + mIDP.numDatabaseHotseatIcons = 3; + + // Overlapping mItems are not placed + assertTrue(mLoaderCursor.checkItemPlacement( + newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 1), true)); + assertFalse(mLoaderCursor.checkItemPlacement( + newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 1), true)); + + assertTrue(mLoaderCursor.checkItemPlacement( + newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 2), true)); + assertFalse(mLoaderCursor.checkItemPlacement( + newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 2), true)); + + assertTrue(mLoaderCursor.checkItemPlacement( + newItemInfo(1, 1, 1, 1, CONTAINER_DESKTOP, 1), true)); + assertTrue(mLoaderCursor.checkItemPlacement( + newItemInfo(2, 2, 2, 2, CONTAINER_DESKTOP, 1), true)); + + assertFalse(mLoaderCursor.checkItemPlacement( + newItemInfo(3, 2, 1, 2, CONTAINER_DESKTOP, 1), true)); + } + + @Test + public void checkItemPlacement_hotseat() { + mIDP.numRows = 4; + mIDP.numColumns = 4; + mIDP.numDatabaseHotseatIcons = 3; + + // Hotseat mItems are only placed based on screenId + assertTrue(mLoaderCursor.checkItemPlacement( + newItemInfo(3, 3, 1, 1, CONTAINER_HOTSEAT, 1), true)); + assertTrue(mLoaderCursor.checkItemPlacement( + newItemInfo(3, 3, 1, 1, CONTAINER_HOTSEAT, 2), true)); + + assertFalse(mLoaderCursor.checkItemPlacement( + newItemInfo(3, 3, 1, 1, CONTAINER_HOTSEAT, 3), true)); + } + + private ItemInfo newItemInfo(int cellX, int cellY, int spanX, int spanY, + int container, int screenId) { + ItemInfo info = new ItemInfo(); + info.cellX = cellX; + info.cellY = cellY; + info.spanX = spanX; + info.spanY = spanY; + info.container = container; + info.screenId = screenId; + return info; + } +} diff --git a/tests/src/com/android/launcher3/model/LoaderTaskTest.kt b/tests/src/com/android/launcher3/model/LoaderTaskTest.kt index f9c4a1704e..d16674c6c2 100644 --- a/tests/src/com/android/launcher3/model/LoaderTaskTest.kt +++ b/tests/src/com/android/launcher3/model/LoaderTaskTest.kt @@ -1,54 +1,29 @@ package com.android.launcher3.model import android.appwidget.AppWidgetManager -import android.content.ComponentName import android.content.Intent -import android.content.pm.ApplicationInfo -import android.content.pm.LauncherActivityInfo -import android.database.sqlite.SQLiteDatabase -import android.os.Process import android.os.UserHandle -import android.platform.test.annotations.DisableFlags -import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.SetFlagsRule import android.provider.Settings import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.dx.mockito.inline.extended.ExtendedMockito -import com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn import com.android.launcher3.Flags +import com.android.launcher3.InvariantDeviceProfile +import com.android.launcher3.LauncherAppState import com.android.launcher3.LauncherModel import com.android.launcher3.LauncherModel.LoaderTransaction -import com.android.launcher3.LauncherPrefs -import com.android.launcher3.LauncherPrefs.Companion.IS_FIRST_LOAD_AFTER_RESTORE -import com.android.launcher3.LauncherPrefs.Companion.RESTORE_DEVICE -import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP -import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT -import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR -import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER -import com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME -import com.android.launcher3.dagger.LauncherAppComponent -import com.android.launcher3.dagger.LauncherAppSingleton import com.android.launcher3.icons.IconCache import com.android.launcher3.icons.cache.CachingLogic import com.android.launcher3.icons.cache.IconCacheUpdateHandler -import com.android.launcher3.model.LoaderTask.LoaderTaskFactory -import com.android.launcher3.model.data.AppInfo -import com.android.launcher3.model.data.IconRequestInfo -import com.android.launcher3.model.data.WorkspaceItemInfo import com.android.launcher3.pm.UserCache import com.android.launcher3.provider.RestoreDbTask import com.android.launcher3.ui.TestViewHelpers -import com.android.launcher3.util.AllModulesForTest import com.android.launcher3.util.Executors.MODEL_EXECUTOR import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext import com.android.launcher3.util.LooperIdleLock -import com.android.launcher3.util.ModelTestExtensions -import com.android.launcher3.util.TestUtil import com.android.launcher3.util.UserIconInfo -import com.google.common.truth.Truth.assertThat -import dagger.BindsInstance -import dagger.Component +import com.google.common.truth.Truth import java.util.concurrent.CountDownLatch import junit.framework.Assert.assertEquals import org.junit.After @@ -57,6 +32,10 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.anyList +import org.mockito.ArgumentMatchers.anyMap import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.times @@ -64,11 +43,9 @@ import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations import org.mockito.MockitoSession import org.mockito.Spy -import org.mockito.kotlin.any import org.mockito.kotlin.anyOrNull -import org.mockito.kotlin.doAnswer import org.mockito.kotlin.doReturn -import org.mockito.kotlin.mock +import org.mockito.kotlin.spy import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import org.mockito.quality.Strictness @@ -89,36 +66,25 @@ class LoaderTaskTest { installedHotseatItems = mutableSetOf("installedHotseatItem"), installedWorkspaceItems = mutableSetOf("installedWorkspaceItem"), firstScreenInstalledWidgets = mutableSetOf("installedFirstScreenWidget"), - secondaryScreenInstalledWidgets = mutableSetOf("installedSecondaryScreenWidget"), + secondaryScreenInstalledWidgets = mutableSetOf("installedSecondaryScreenWidget") ) private lateinit var mockitoSession: MockitoSession + @Mock private lateinit var app: LauncherAppState @Mock private lateinit var bgAllAppsList: AllAppsList @Mock private lateinit var modelDelegate: ModelDelegate - @Mock private lateinit var launcherModel: LauncherModel - @Mock private lateinit var iconCache: IconCache - @Mock private lateinit var userCache: UserCache - @Mock private lateinit var modelDbController: ModelDbController - @Mock private lateinit var launcherBinder: BaseLauncherBinder + @Mock private lateinit var launcherModel: LauncherModel @Mock private lateinit var transaction: LoaderTransaction + @Mock private lateinit var iconCache: IconCache @Mock private lateinit var idleLock: LooperIdleLock @Mock private lateinit var iconCacheUpdateHandler: IconCacheUpdateHandler + @Mock private lateinit var userCache: UserCache - @Spy private var userManagerState: UserManagerState = UserManagerState() + @Spy private var userManagerState: UserManagerState? = UserManagerState() @get:Rule val setFlagsRule = SetFlagsRule() - private val testComponent: TestComponent - get() = context.appComponent as TestComponent - - private val bgDataModel: BgDataModel - get() = testComponent.getDataModel() - - private val inMemoryDb: SQLiteDatabase by lazy { - ModelTestExtensions.createInMemoryDb(INSERTION_STATEMENT_FILE) - } - @Before fun setup() { MockitoAnnotations.initMocks(this) @@ -127,103 +93,65 @@ class LoaderTaskTest { .strictness(Strictness.LENIENT) .mockStatic(FirstScreenBroadcastHelper::class.java) .startMocking() + val idp = + InvariantDeviceProfile().apply { + numRows = 5 + numColumns = 6 + numDatabaseHotseatIcons = 5 + } + context.putObject(InvariantDeviceProfile.INSTANCE, idp) + context.putObject(LauncherAppState.INSTANCE, app) + doReturn(TestViewHelpers.findWidgetProvider(false)) .`when`(context.spyService(AppWidgetManager::class.java)) - .getAppWidgetInfo(any()) - - `when`(launcherModel.beginLoader(any())).thenReturn(transaction) - - `when`(launcherModel.modelDbController).thenReturn(modelDbController) - doAnswer {}.whenever(modelDbController).loadDefaultFavoritesIfNecessary() - doAnswer { i -> - inMemoryDb.query( - TABLE_NAME, - i.getArgument(0), - i.getArgument(1), - i.getArgument(2), - null, - null, - i.getArgument(3), - ) - } - .whenever(modelDbController) - .query(anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull()) - - `when`(launcherModel.modelDelegate).thenReturn(modelDelegate) - `when`(launcherBinder.newIdleLock(any())).thenReturn(idleLock) + .getAppWidgetInfo(anyInt()) + `when`(app.context).thenReturn(context) + `when`(app.model).thenReturn(launcherModel) + `when`(launcherModel.beginLoader(any(LoaderTask::class.java))).thenReturn(transaction) + `when`(app.iconCache).thenReturn(iconCache) + `when`(launcherModel.modelDbController) + .thenReturn(FactitiousDbController(context, INSERTION_STATEMENT_FILE)) + `when`(app.invariantDeviceProfile).thenReturn(idp) + `when`(launcherBinder.newIdleLock(any(LoaderTask::class.java))).thenReturn(idleLock) `when`(idleLock.awaitLocked(1000)).thenReturn(false) - `when`(iconCache.getUpdateHandler()).thenReturn(iconCacheUpdateHandler) - - context.initDaggerComponent( - DaggerLoaderTaskTest_TestComponent.builder() - .bindUserCache(userCache) - .bindIconCache(iconCache) - .bindLauncherModel(launcherModel) - .bindAllAppsList(bgAllAppsList) - ) - context.appComponent.idp.apply { - numRows = 5 - numColumns = 6 - numDatabaseHotseatIcons = 5 - } - TestUtil.grantWriteSecurePermission() + `when`(iconCache.updateHandler).thenReturn(iconCacheUpdateHandler) + context.putObject(UserCache.INSTANCE, userCache) } @After fun tearDown() { - LauncherPrefs.get(context).removeSync(RESTORE_DEVICE) - LauncherPrefs.get(context).putSync(IS_FIRST_LOAD_AFTER_RESTORE.to(false)) - inMemoryDb.close() context.onDestroy() mockitoSession.finishMocking() } @Test fun loadsDataProperly() = - with(bgDataModel) { - val MAIN_HANDLE = Process.myUserHandle() + with(BgDataModel()) { + val MAIN_HANDLE = UserHandle.of(0) val mockUserHandles = arrayListOf(MAIN_HANDLE) `when`(userCache.userProfiles).thenReturn(mockUserHandles) `when`(userCache.getUserInfo(MAIN_HANDLE)).thenReturn(UserIconInfo(MAIN_HANDLE, 1)) - testComponent - .getLoaderTaskFactory() - .newLoaderTask(launcherBinder, userManagerState) + LoaderTask(app, bgAllAppsList, this, modelDelegate, launcherBinder) .runSyncOnBackgroundThread() - assertThat( - itemsIdMap - .filter { - it.container == CONTAINER_DESKTOP || it.container == CONTAINER_HOTSEAT - } - .size - ) - .isAtLeast(32) - assertThat(itemsIdMap.filter { ModelUtils.WIDGET_FILTER.test(it) }.size).isAtLeast(7) - assertThat( - itemsIdMap - .filter { - it.itemType == ITEM_TYPE_FOLDER || it.itemType == ITEM_TYPE_APP_PAIR - } - .size - ) - .isAtLeast(8) - assertThat(itemsIdMap.size()).isAtLeast(40) + Truth.assertThat(workspaceItems.size).isAtLeast(25) + Truth.assertThat(appWidgets.size).isAtLeast(7) + Truth.assertThat(collections.size()).isAtLeast(8) + Truth.assertThat(itemsIdMap.size()).isAtLeast(40) } @Test fun bindsLoadedDataCorrectly() { - testComponent - .getLoaderTaskFactory() - .newLoaderTask(launcherBinder, userManagerState) + LoaderTask(app, bgAllAppsList, BgDataModel(), modelDelegate, launcherBinder) .runSyncOnBackgroundThread() verify(launcherBinder).bindWorkspace(true, false) verify(modelDelegate).workspaceLoadComplete() - verify(modelDelegate).loadAndBindAllAppsItems(any(), anyOrNull(), any()) + verify(modelDelegate).loadAndBindAllAppsItems(any(), any(), any()) verify(launcherBinder).bindAllApps() verify(iconCacheUpdateHandler, times(4)).updateIcons(any(), any>(), any()) verify(launcherBinder).bindDeepShortcuts() verify(launcherBinder).bindWidgets() - verify(modelDelegate).loadAndBindOtherItems(anyOrNull()) + verify(modelDelegate).loadAndBindOtherItems(any()) verify(iconCacheUpdateHandler).finish() verify(modelDelegate).modelLoadComplete() verify(transaction).commit() @@ -231,17 +159,15 @@ class LoaderTaskTest { @Test fun setsQuietModeFlagCorrectlyForWorkProfile() = - with(bgDataModel) { + with(BgDataModel()) { setFlagsRule.enableFlags(Flags.FLAG_ENABLE_PRIVATE_SPACE) - val MAIN_HANDLE = Process.myUserHandle() + val MAIN_HANDLE = UserHandle.of(0) val mockUserHandles = arrayListOf(MAIN_HANDLE) `when`(userCache.userProfiles).thenReturn(mockUserHandles) `when`(userManagerState?.isUserQuiet(MAIN_HANDLE)).thenReturn(true) `when`(userCache.getUserInfo(MAIN_HANDLE)).thenReturn(UserIconInfo(MAIN_HANDLE, 1)) - testComponent - .getLoaderTaskFactory() - .newLoaderTask(launcherBinder, userManagerState) + LoaderTask(app, bgAllAppsList, this, modelDelegate, launcherBinder, userManagerState) .runSyncOnBackgroundThread() verify(bgAllAppsList) @@ -254,17 +180,15 @@ class LoaderTaskTest { @Test fun setsQuietModeFlagCorrectlyForPrivateProfile() = - with(bgDataModel) { + with(BgDataModel()) { setFlagsRule.enableFlags(Flags.FLAG_ENABLE_PRIVATE_SPACE) - val MAIN_HANDLE = Process.myUserHandle() + val MAIN_HANDLE = UserHandle.of(0) val mockUserHandles = arrayListOf(MAIN_HANDLE) `when`(userCache.userProfiles).thenReturn(mockUserHandles) `when`(userManagerState?.isUserQuiet(MAIN_HANDLE)).thenReturn(true) `when`(userCache.getUserInfo(MAIN_HANDLE)).thenReturn(UserIconInfo(MAIN_HANDLE, 3)) - testComponent - .getLoaderTaskFactory() - .newLoaderTask(launcherBinder, userManagerState) + LoaderTask(app, bgAllAppsList, this, modelDelegate, launcherBinder, userManagerState) .runSyncOnBackgroundThread() verify(bgAllAppsList) @@ -276,17 +200,16 @@ class LoaderTaskTest { } @Test - @EnableFlags(Flags.FLAG_ENABLE_FIRST_SCREEN_BROADCAST_ARCHIVING_EXTRAS) - fun `When broadcast flag on and is restore and secure setting off then send new broadcast`() { + fun `When launcher_broadcast_installed_apps and is restore then send installed item broadcast`() { // Given - spyOn(context) - val spyContext = context + val spyContext = spy(context) + `when`(app.context).thenReturn(spyContext) whenever( FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast( - any(), - any(), - any(), - any(), + anyOrNull(), + anyList(), + anyMap(), + anyList() ) ) .thenReturn(listOf(expectedBroadcastModel)) @@ -294,18 +217,16 @@ class LoaderTaskTest { whenever( FirstScreenBroadcastHelper.sendBroadcastsForModels( spyContext, - listOf(expectedBroadcastModel), + listOf(expectedBroadcastModel) ) ) .thenCallRealMethod() - Settings.Secure.putInt(spyContext.contentResolver, "launcher_broadcast_installed_apps", 0) + Settings.Secure.putInt(spyContext.contentResolver, "launcher_broadcast_installed_apps", 1) RestoreDbTask.setPending(spyContext) // When - testComponent - .getLoaderTaskFactory() - .newLoaderTask(launcherBinder, userManagerState) + LoaderTask(app, bgAllAppsList, BgDataModel(), modelDelegate, launcherBinder) .runSyncOnBackgroundThread() // Then @@ -315,49 +236,48 @@ class LoaderTaskTest { assertEquals(expectedBroadcastModel.installerPackage, actualBroadcastIntent.`package`) assertEquals( ArrayList(expectedBroadcastModel.installedWorkspaceItems), - actualBroadcastIntent.getStringArrayListExtra("workspaceInstalledItems"), + actualBroadcastIntent.getStringArrayListExtra("workspaceInstalledItems") ) assertEquals( ArrayList(expectedBroadcastModel.installedHotseatItems), - actualBroadcastIntent.getStringArrayListExtra("hotseatInstalledItems"), + actualBroadcastIntent.getStringArrayListExtra("hotseatInstalledItems") ) assertEquals( ArrayList( expectedBroadcastModel.firstScreenInstalledWidgets + expectedBroadcastModel.secondaryScreenInstalledWidgets ), - actualBroadcastIntent.getStringArrayListExtra("widgetInstalledItems"), + actualBroadcastIntent.getStringArrayListExtra("widgetInstalledItems") ) assertEquals( ArrayList(expectedBroadcastModel.pendingCollectionItems), - actualBroadcastIntent.getStringArrayListExtra("folderItem"), + actualBroadcastIntent.getStringArrayListExtra("folderItem") ) assertEquals( ArrayList(expectedBroadcastModel.pendingWorkspaceItems), - actualBroadcastIntent.getStringArrayListExtra("workspaceItem"), + actualBroadcastIntent.getStringArrayListExtra("workspaceItem") ) assertEquals( ArrayList(expectedBroadcastModel.pendingHotseatItems), - actualBroadcastIntent.getStringArrayListExtra("hotseatItem"), + actualBroadcastIntent.getStringArrayListExtra("hotseatItem") ) assertEquals( ArrayList(expectedBroadcastModel.pendingWidgetItems), - actualBroadcastIntent.getStringArrayListExtra("widgetItem"), + actualBroadcastIntent.getStringArrayListExtra("widgetItem") ) } @Test - @EnableFlags(Flags.FLAG_ENABLE_FIRST_SCREEN_BROADCAST_ARCHIVING_EXTRAS) fun `When not a restore then installed item broadcast not sent`() { // Given - spyOn(context) - val spyContext = context + val spyContext = spy(context) + `when`(app.context).thenReturn(spyContext) whenever( FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast( - any(), - any(), - any(), - any(), + anyOrNull(), + anyList(), + anyMap(), + anyList() ) ) .thenReturn(listOf(expectedBroadcastModel)) @@ -365,303 +285,53 @@ class LoaderTaskTest { whenever( FirstScreenBroadcastHelper.sendBroadcastsForModels( spyContext, - listOf(expectedBroadcastModel), + listOf(expectedBroadcastModel) + ) + ) + .thenCallRealMethod() + + Settings.Secure.putInt(spyContext.contentResolver, "launcher_broadcast_installed_apps", 1) + + // When + LoaderTask(app, bgAllAppsList, BgDataModel(), modelDelegate, launcherBinder) + .runSyncOnBackgroundThread() + + // Then + verify(spyContext, times(0)).sendBroadcast(any(Intent::class.java)) + } + + @Test + fun `When launcher_broadcast_installed_apps false then installed item broadcast not sent`() { + // Given + val spyContext = spy(context) + `when`(app.context).thenReturn(spyContext) + whenever( + FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast( + anyOrNull(), + anyList(), + anyMap(), + anyList() + ) + ) + .thenReturn(listOf(expectedBroadcastModel)) + + whenever( + FirstScreenBroadcastHelper.sendBroadcastsForModels( + spyContext, + listOf(expectedBroadcastModel) ) ) .thenCallRealMethod() Settings.Secure.putInt(spyContext.contentResolver, "launcher_broadcast_installed_apps", 0) - - // When - testComponent - .getLoaderTaskFactory() - .newLoaderTask(launcherBinder, userManagerState) - .runSyncOnBackgroundThread() - - // Then - verify(spyContext, times(0)).sendBroadcast(any()) - } - - @Test - @DisableFlags(Flags.FLAG_ENABLE_FIRST_SCREEN_BROADCAST_ARCHIVING_EXTRAS) - fun `When broadcast flag off then installed item broadcast not sent`() { - // Given - spyOn(context) - val spyContext = context - whenever( - FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast( - any(), - any(), - any(), - any(), - ) - ) - .thenReturn(listOf(expectedBroadcastModel)) - - whenever( - FirstScreenBroadcastHelper.sendBroadcastsForModels( - spyContext, - listOf(expectedBroadcastModel), - ) - ) - .thenCallRealMethod() - - Settings.Secure.putInt( - spyContext.contentResolver, - "disable_launcher_broadcast_installed_apps", - 0, - ) RestoreDbTask.setPending(spyContext) // When - testComponent - .getLoaderTaskFactory() - .newLoaderTask(launcherBinder, userManagerState) + LoaderTask(app, bgAllAppsList, BgDataModel(), modelDelegate, launcherBinder) .runSyncOnBackgroundThread() // Then - verify(spyContext, times(0)).sendBroadcast(any()) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_FIRST_SCREEN_BROADCAST_ARCHIVING_EXTRAS) - fun `When failsafe secure setting on then installed item broadcast not sent`() { - // Given - spyOn(context) - val spyContext = context - whenever( - FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast( - any(), - any(), - any(), - any(), - ) - ) - .thenReturn(listOf(expectedBroadcastModel)) - - whenever( - FirstScreenBroadcastHelper.sendBroadcastsForModels( - spyContext, - listOf(expectedBroadcastModel), - ) - ) - .thenCallRealMethod() - - Settings.Secure.putInt( - spyContext.contentResolver, - "disable_launcher_broadcast_installed_apps", - 1, - ) - RestoreDbTask.setPending(spyContext) - - // When - testComponent - .getLoaderTaskFactory() - .newLoaderTask(launcherBinder, userManagerState) - .runSyncOnBackgroundThread() - - // Then - verify(spyContext, times(0)).sendBroadcast(any()) - } - - @Test - @EnableFlags(Flags.FLAG_RESTORE_ARCHIVED_APP_ICONS_FROM_DB) - fun `When flag on and restore then archived AllApps icons on Workspace load from db`() { - // Given - val activityInfo: LauncherActivityInfo = mock() - val applicationInfo: ApplicationInfo = mock().apply { isArchived = true } - whenever(activityInfo.applicationInfo).thenReturn(applicationInfo) - val expectedIconBlob = byteArrayOf(0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08) - val expectedComponent = ComponentName("package", "class") - val workspaceIconRequests = - listOf( - IconRequestInfo( - WorkspaceItemInfo().apply { - intent = Intent().apply { component = expectedComponent } - }, - activityInfo, - expectedIconBlob, - false, /* useLowResIcon */ - ) - ) - val expectedAppInfo = AppInfo().apply { componentName = expectedComponent } - // When - val loader = - testComponent.getLoaderTaskFactory().newLoaderTask(launcherBinder, userManagerState) - val actualIconRequest = - loader.getAppInfoIconRequestInfo( - expectedAppInfo, - activityInfo, - workspaceIconRequests, - /* isRestoreFromBackup */ true, - ) - // Then - assertThat(actualIconRequest.iconBlob).isEqualTo(expectedIconBlob) - assertThat(actualIconRequest.itemInfo).isEqualTo(expectedAppInfo) - } - - @Test - @EnableFlags(Flags.FLAG_RESTORE_ARCHIVED_APP_ICONS_FROM_DB) - fun `When flag on and not restore then archived AllApps icons do not load from db`() { - // Given - val activityInfo: LauncherActivityInfo = mock() - val applicationInfo: ApplicationInfo = mock().apply { isArchived = true } - whenever(activityInfo.applicationInfo).thenReturn(applicationInfo) - val expectedIconBlob = byteArrayOf(0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08) - val expectedComponent = ComponentName("package", "class") - val workspaceIconRequests = - listOf( - IconRequestInfo( - WorkspaceItemInfo().apply { - intent = Intent().apply { component = expectedComponent } - }, - activityInfo, - expectedIconBlob, - false, /* useLowResIcon */ - ) - ) - val expectedAppInfo = AppInfo().apply { componentName = expectedComponent } - // When - val loader = - testComponent.getLoaderTaskFactory().newLoaderTask(launcherBinder, userManagerState) - val actualIconRequest = - loader.getAppInfoIconRequestInfo( - expectedAppInfo, - activityInfo, - workspaceIconRequests, - /* isRestoreFromBackup */ false, - ) - // Then - assertThat(actualIconRequest.iconBlob).isNull() - assertThat(actualIconRequest.itemInfo).isEqualTo(expectedAppInfo) - } - - @Test - @EnableFlags(Flags.FLAG_RESTORE_ARCHIVED_APP_ICONS_FROM_DB) - fun `When flag on and restore then unarchived AllApps icons not loaded from db`() { - // Given - val activityInfo: LauncherActivityInfo = mock() - val applicationInfo: ApplicationInfo = mock().apply { isArchived = false } - whenever(activityInfo.applicationInfo).thenReturn(applicationInfo) - val expectedIconBlob = byteArrayOf(0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08) - val expectedComponent = ComponentName("package", "class") - val workspaceIconRequests = - listOf( - IconRequestInfo( - WorkspaceItemInfo().apply { - intent = Intent().apply { component = expectedComponent } - }, - activityInfo, - expectedIconBlob, - false, /* useLowResIcon */ - ) - ) - val expectedAppInfo = AppInfo().apply { componentName = expectedComponent } - // When - val loader = - testComponent.getLoaderTaskFactory().newLoaderTask(launcherBinder, userManagerState) - val actualIconRequest = - loader.getAppInfoIconRequestInfo( - expectedAppInfo, - activityInfo, - workspaceIconRequests, - /* isRestoreFromBackup */ true, - ) - // Then - assertThat(actualIconRequest.iconBlob).isNull() - assertThat(actualIconRequest.itemInfo).isEqualTo(expectedAppInfo) - } - - @Test - @EnableFlags(Flags.FLAG_RESTORE_ARCHIVED_APP_ICONS_FROM_DB) - fun `When flag on and restore then all apps icon not on workspace is not loaded from db`() { - // Given - val activityInfo: LauncherActivityInfo = mock() - val applicationInfo: ApplicationInfo = mock().apply { isArchived = true } - whenever(activityInfo.applicationInfo).thenReturn(applicationInfo) - val expectedIconBlob = byteArrayOf(0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08) - val expectedComponent = ComponentName("package", "class") - val workspaceIconRequests = - listOf( - IconRequestInfo( - WorkspaceItemInfo().apply { - intent = Intent().apply { component = expectedComponent } - }, - activityInfo, - expectedIconBlob, - false, /* useLowResIcon */ - ) - ) - val expectedAppInfo = - AppInfo().apply { componentName = ComponentName("differentPkg", "differentClass") } - // When - val loader = - testComponent.getLoaderTaskFactory().newLoaderTask(launcherBinder, userManagerState) - val actualIconRequest = - loader.getAppInfoIconRequestInfo( - expectedAppInfo, - activityInfo, - workspaceIconRequests, - /* isRestoreFromBackup */ true, - ) - // Then - assertThat(actualIconRequest.iconBlob).isNull() - assertThat(actualIconRequest.itemInfo).isEqualTo(expectedAppInfo) - } - - @Test - @DisableFlags(Flags.FLAG_RESTORE_ARCHIVED_APP_ICONS_FROM_DB) - fun `When flag off and restore then archived AllApps icons not loaded from db`() { - // Given - val activityInfo: LauncherActivityInfo = mock() - val applicationInfo: ApplicationInfo = mock().apply { isArchived = true } - whenever(activityInfo.applicationInfo).thenReturn(applicationInfo) - val expectedIconBlob = byteArrayOf(0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08) - val workspaceIconRequests = - listOf( - IconRequestInfo( - WorkspaceItemInfo(), - activityInfo, - expectedIconBlob, - false, /* useLowResIcon */ - ) - ) - val expectedAppInfo = AppInfo() - // When - val loader = - testComponent.getLoaderTaskFactory().newLoaderTask(launcherBinder, userManagerState) - val actualIconRequest = - loader.getAppInfoIconRequestInfo( - expectedAppInfo, - activityInfo, - workspaceIconRequests, - /* isRestoreFromBackup */ true, - ) - // Then - assertThat(actualIconRequest.iconBlob).isNull() - assertThat(actualIconRequest.itemInfo).isEqualTo(expectedAppInfo) - } - - @LauncherAppSingleton - @Component(modules = [AllModulesForTest::class]) - interface TestComponent : LauncherAppComponent { - - fun getLoaderTaskFactory(): LoaderTaskFactory - - fun getDataModel(): BgDataModel - - @Component.Builder - interface Builder : LauncherAppComponent.Builder { - @BindsInstance fun bindUserCache(userCache: UserCache): Builder - - @BindsInstance fun bindLauncherModel(model: LauncherModel): Builder - - @BindsInstance fun bindIconCache(iconCache: IconCache): Builder - - @BindsInstance fun bindAllAppsList(list: AllAppsList): Builder - - override fun build(): TestComponent - } + verify(spyContext, times(0)).sendBroadcast(any(Intent::class.java)) } } diff --git a/tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java b/tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java new file mode 100644 index 0000000000..4ba61ac315 --- /dev/null +++ b/tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java @@ -0,0 +1,122 @@ +package com.android.launcher3.model; + +import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; +import static com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY; +import static com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY2; +import static com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY3; +import static com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE; +import static com.android.launcher3.util.TestUtil.runOnExecutorSync; + +import static org.junit.Assert.assertEquals; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.android.launcher3.model.data.ItemInfo; +import com.android.launcher3.model.data.LauncherAppWidgetInfo; +import com.android.launcher3.model.data.WorkspaceItemInfo; +import com.android.launcher3.pm.PackageInstallInfo; +import com.android.launcher3.util.IntSet; +import com.android.launcher3.util.LauncherLayoutBuilder; +import com.android.launcher3.util.LauncherModelHelper; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for {@link PackageInstallStateChangedTask} + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class PackageInstallStateChangedTaskTest { + + private static final String PENDING_APP_1 = TEST_PACKAGE + ".pending1"; + private static final String PENDING_APP_2 = TEST_PACKAGE + ".pending2"; + + private LauncherModelHelper mModelHelper; + private IntSet mDownloadingApps; + + @Before + public void setup() throws Exception { + mModelHelper = new LauncherModelHelper(); + mModelHelper.createInstallerSession(PENDING_APP_1); + mModelHelper.createInstallerSession(PENDING_APP_2); + + LauncherLayoutBuilder builder = new LauncherLayoutBuilder() + .atWorkspace(0, 0, 1).putApp(TEST_PACKAGE, TEST_ACTIVITY) // 1 + .atWorkspace(0, 0, 2).putApp(TEST_PACKAGE, TEST_ACTIVITY2) // 2 + .atWorkspace(0, 0, 3).putApp(TEST_PACKAGE, TEST_ACTIVITY3) // 3 + + .atWorkspace(0, 0, 4).putApp(PENDING_APP_1, TEST_ACTIVITY) // 4 + .atWorkspace(0, 0, 5).putApp(PENDING_APP_1, TEST_ACTIVITY2) // 5 + .atWorkspace(0, 0, 6).putApp(PENDING_APP_1, TEST_ACTIVITY3) // 6 + .atWorkspace(0, 0, 7).putWidget(PENDING_APP_1, "pending.widget", 1, 1) // 7 + + .atWorkspace(0, 0, 8).putApp(PENDING_APP_2, TEST_ACTIVITY) // 8 + .atWorkspace(0, 0, 9).putApp(PENDING_APP_2, TEST_ACTIVITY2) // 9 + .atWorkspace(0, 0, 10).putApp(PENDING_APP_2, TEST_ACTIVITY3); // 10 + + mDownloadingApps = IntSet.wrap(4, 5, 6, 7, 8, 9, 10); + mModelHelper.setupDefaultLayoutProvider(builder); + mModelHelper.loadModelSync(); + assertEquals(10, mModelHelper.getBgDataModel().itemsIdMap.size()); + } + + @After + public void tearDown() { + mModelHelper.destroy(); + } + + private PackageInstallStateChangedTask newTask(String pkg, int progress) { + int state = PackageInstallInfo.STATUS_INSTALLING; + PackageInstallInfo installInfo = new PackageInstallInfo(pkg, state, progress, + android.os.Process.myUserHandle()); + return new PackageInstallStateChangedTask(installInfo); + } + + @Test + public void testSessionUpdate_ignore_installed() { + // Run on model executor so that no other task runs in the middle. + runOnExecutorSync(MODEL_EXECUTOR, () -> { + mModelHelper.getModel().enqueueModelUpdateTask(newTask(TEST_PACKAGE, 30)); + + // No shortcuts were updated + verifyProgressUpdate(0); + }); + } + + @Test + public void testSessionUpdate_shortcuts_updated() { + // Run on model executor so that no other task runs in the middle. + runOnExecutorSync(MODEL_EXECUTOR, () -> { + mModelHelper.getModel().enqueueModelUpdateTask(newTask(PENDING_APP_1, 30)); + + verifyProgressUpdate(30, 4, 5, 6, 7); + }); + } + + @Test + public void testSessionUpdate_widgets_updated() { + // Run on model executor so that no other task runs in the middle. + runOnExecutorSync(MODEL_EXECUTOR, () -> { + mModelHelper.getModel().enqueueModelUpdateTask(newTask(PENDING_APP_2, 30)); + + verifyProgressUpdate(30, 8, 9, 10); + }); + } + + private void verifyProgressUpdate(int progress, int... idsUpdated) { + IntSet updates = IntSet.wrap(idsUpdated); + for (ItemInfo info : mModelHelper.getBgDataModel().itemsIdMap) { + int expectedProgress = updates.contains(info.id) ? progress + : (mDownloadingApps.contains(info.id) ? 0 : 100); + if (info instanceof WorkspaceItemInfo wi) { + assertEquals(expectedProgress, wi.getProgressLevel()); + } else { + assertEquals(expectedProgress, ((LauncherAppWidgetInfo) info).installProgress); + } + } + } +} diff --git a/tests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt b/tests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt new file mode 100644 index 0000000000..6cf3b198bf --- /dev/null +++ b/tests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt @@ -0,0 +1,847 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.model + +import android.appwidget.AppWidgetProviderInfo +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.pm.LauncherApps +import android.content.pm.PackageInstaller +import android.content.pm.ShortcutInfo +import android.os.Process +import android.os.UserHandle +import android.platform.test.annotations.EnableFlags +import android.util.LongSparseArray +import com.android.dx.mockito.inline.extended.ExtendedMockito +import com.android.launcher3.Flags.FLAG_ENABLE_SUPPORT_FOR_ARCHIVING +import com.android.launcher3.LauncherAppState +import com.android.launcher3.LauncherSettings.Favorites +import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP +import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION +import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET +import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT +import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER +import com.android.launcher3.Utilities +import com.android.launcher3.Utilities.EMPTY_PERSON_ARRAY +import com.android.launcher3.backuprestore.LauncherRestoreEventLogger +import com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RestoreError.Companion.MISSING_INFO +import com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RestoreError.Companion.MISSING_WIDGET_PROVIDER +import com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RestoreError.Companion.PROFILE_DELETED +import com.android.launcher3.model.data.FolderInfo +import com.android.launcher3.model.data.IconRequestInfo +import com.android.launcher3.model.data.ItemInfo +import com.android.launcher3.model.data.LauncherAppWidgetInfo +import com.android.launcher3.model.data.LauncherAppWidgetInfo.FLAG_RESTORE_STARTED +import com.android.launcher3.model.data.LauncherAppWidgetInfo.FLAG_UI_NOT_READY +import com.android.launcher3.model.data.WorkspaceItemInfo +import com.android.launcher3.pm.UserCache +import com.android.launcher3.shortcuts.ShortcutKey +import com.android.launcher3.util.ComponentKey +import com.android.launcher3.util.PackageManagerHelper +import com.android.launcher3.util.PackageUserKey +import com.android.launcher3.util.UserIconInfo +import com.android.launcher3.widget.LauncherAppWidgetProviderInfo +import com.android.launcher3.widget.WidgetInflater +import com.android.launcher3.widget.WidgetSections +import com.google.common.truth.Truth.assertThat +import com.google.common.truth.Truth.assertWithMessage +import org.junit.Before +import org.junit.Test +import org.mockito.ArgumentCaptor +import org.mockito.Mock +import org.mockito.Mockito.RETURNS_DEEP_STUBS +import org.mockito.Mockito.mock +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.kotlin.any +import org.mockito.kotlin.anyOrNull +import org.mockito.kotlin.doAnswer +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever +import org.mockito.quality.Strictness + +class WorkspaceItemProcessorTest { + + @Mock private lateinit var mockIconRequestInfo: IconRequestInfo + @Mock private lateinit var mockWorkspaceInfo: WorkspaceItemInfo + @Mock private lateinit var mockBgDataModel: BgDataModel + @Mock private lateinit var mockContext: Context + @Mock private lateinit var mockAppState: LauncherAppState + @Mock private lateinit var mockPmHelper: PackageManagerHelper + @Mock private lateinit var mockLauncherApps: LauncherApps + @Mock private lateinit var mockCursor: LoaderCursor + @Mock private lateinit var mockUserCache: UserCache + @Mock private lateinit var mockUserManagerState: UserManagerState + @Mock private lateinit var mockWidgetInflater: WidgetInflater + + private var intent: Intent = Intent() + private var mUserHandle: UserHandle = UserHandle(0) + private var mIconRequestInfos: MutableList> = mutableListOf() + private var mComponentName: ComponentName = ComponentName("package", "class") + private var mUnlockedUsersArray: LongSparseArray = LongSparseArray() + private var mKeyToPinnedShortcutsMap: MutableMap = mutableMapOf() + private var mInstallingPkgs: HashMap = hashMapOf() + private var mAllDeepShortcuts: MutableList = mutableListOf() + private var mWidgetProvidersMap: MutableMap = + mutableMapOf() + private var mPendingPackages: MutableSet = mutableSetOf() + + private lateinit var itemProcessorUnderTest: WorkspaceItemProcessor + + @Before + fun setup() { + mUserHandle = UserHandle(0) + mockIconRequestInfo = mock>() + mockWorkspaceInfo = mock() + mockBgDataModel = mock() + mComponentName = ComponentName("package", "class") + mUnlockedUsersArray = LongSparseArray(1).apply { put(101, true) } + intent = + Intent().apply { + component = mComponentName + `package` = "pkg" + putExtra(ShortcutKey.EXTRA_SHORTCUT_ID, "") + } + mockContext = + mock().apply { + whenever(packageManager).thenReturn(mock()) + whenever(packageManager.getUserBadgedLabel(any(), any())).thenReturn("") + } + mockAppState = + mock().apply { + whenever(context).thenReturn(mockContext) + whenever(iconCache).thenReturn(mock()) + whenever(iconCache.getShortcutIcon(any(), any(), any())).then {} + } + mockPmHelper = + mock().apply { + whenever(getAppLaunchIntent(mComponentName.packageName, mUserHandle)) + .thenReturn(intent) + } + mockLauncherApps = + mock().apply { + whenever(isPackageEnabled("package", mUserHandle)).thenReturn(true) + whenever(isActivityEnabled(mComponentName, mUserHandle)).thenReturn(true) + } + mockCursor = + mock(LoaderCursor::class.java, RETURNS_DEEP_STUBS).apply { + user = mUserHandle + itemType = ITEM_TYPE_APPLICATION + id = 1 + restoreFlag = 1 + serialNumber = 101 + whenever(parseIntent()).thenReturn(intent) + whenever(markRestored()).doAnswer { restoreFlag = 0 } + whenever(updater().put(Favorites.INTENT, intent.toUri(0)).commit()).thenReturn(1) + whenever(getAppShortcutInfo(any(), any(), any(), any())) + .thenReturn(mockWorkspaceInfo) + whenever(createIconRequestInfo(any(), any())).thenReturn(mockIconRequestInfo) + } + mockUserCache = + mock().apply { + val userIconInfo = + mock().apply { whenever(isPrivate).thenReturn(false) } + whenever(getUserInfo(any())).thenReturn(userIconInfo) + } + + mockUserManagerState = mock() + mockWidgetInflater = mock() + mKeyToPinnedShortcutsMap = mutableMapOf() + mInstallingPkgs = hashMapOf() + mAllDeepShortcuts = mutableListOf() + mWidgetProvidersMap = mutableMapOf() + mIconRequestInfos = mutableListOf() + mPendingPackages = mutableSetOf() + } + + /** + * Helper to create WorkspaceItemProcessor with defaults. WorkspaceItemProcessor has a lot of + * dependencies, so this method can be used to inject concrete arguments while keeping the rest + * as mocks/defaults, or to recreate it after modifying the default vars. + */ + private fun createWorkspaceItemProcessorUnderTest( + cursor: LoaderCursor = mockCursor, + memoryLogger: LoaderMemoryLogger? = null, + userCache: UserCache = mockUserCache, + userManagerState: UserManagerState = mockUserManagerState, + launcherApps: LauncherApps = mockLauncherApps, + shortcutKeyToPinnedShortcuts: Map = mKeyToPinnedShortcutsMap, + app: LauncherAppState = mockAppState, + bgDataModel: BgDataModel = mockBgDataModel, + widgetProvidersMap: MutableMap = mWidgetProvidersMap, + widgetInflater: WidgetInflater = mockWidgetInflater, + pmHelper: PackageManagerHelper = mockPmHelper, + iconRequestInfos: MutableList> = mIconRequestInfos, + isSdCardReady: Boolean = false, + pendingPackages: MutableSet = mPendingPackages, + unlockedUsers: LongSparseArray = mUnlockedUsersArray, + installingPkgs: HashMap = mInstallingPkgs, + allDeepShortcuts: MutableList = mAllDeepShortcuts + ) = + WorkspaceItemProcessor( + c = cursor, + memoryLogger = memoryLogger, + userCache = userCache, + userManagerState = userManagerState, + launcherApps = launcherApps, + app = app, + bgDataModel = bgDataModel, + widgetProvidersMap = widgetProvidersMap, + widgetInflater = widgetInflater, + pmHelper = pmHelper, + unlockedUsers = unlockedUsers, + iconRequestInfos = iconRequestInfos, + pendingPackages = pendingPackages, + isSdCardReady = isSdCardReady, + shortcutKeyToPinnedShortcuts = shortcutKeyToPinnedShortcuts, + installingPkgs = installingPkgs, + allDeepShortcuts = allDeepShortcuts + ) + + @Test + fun `When user is null then mark item deleted`() { + // Given + mockCursor = mock().apply { id = 1 } + + // When + itemProcessorUnderTest = createWorkspaceItemProcessorUnderTest() + itemProcessorUnderTest.processItem() + + // Then + verify(mockCursor).markDeleted("User has been deleted for item id=1", PROFILE_DELETED) + verify(mockCursor, times(0)).checkAndAddItem(any(), any(), anyOrNull()) + } + + @Test + fun `When app has null intent then mark deleted`() { + // Given + mockCursor.apply { whenever(parseIntent()).thenReturn(null) } + + // When + itemProcessorUnderTest = createWorkspaceItemProcessorUnderTest() + itemProcessorUnderTest.processItem() + // Then + verify(mockCursor).markDeleted("Null intent from db for item id=1", MISSING_INFO) + verify(mockCursor, times(0)).checkAndAddItem(any(), any(), anyOrNull()) + } + + @Test + fun `When app has null target package then mark deleted`() { + + // Given + intent.apply { + component = null + `package` = null + } + + // When + itemProcessorUnderTest = createWorkspaceItemProcessorUnderTest() + itemProcessorUnderTest.processItem() + + // Then + verify(mockCursor).markDeleted("No target package for item id=1", MISSING_INFO) + verify(mockCursor, times(0)).checkAndAddItem(any(), any(), anyOrNull()) + } + + @Test + fun `When app has empty String target package then mark deleted`() { + + // Given + mComponentName = ComponentName("", "") + intent.component = mComponentName + intent.`package` = "" + + // When + itemProcessorUnderTest = createWorkspaceItemProcessorUnderTest() + itemProcessorUnderTest.processItem() + + // Then + verify(mockCursor).markDeleted("No target package for item id=1", MISSING_INFO) + verify(mockCursor, times(0)).checkAndAddItem(any(), any(), anyOrNull()) + } + + @Test + fun `When valid app then mark restored`() { + + // When + itemProcessorUnderTest = createWorkspaceItemProcessorUnderTest() + itemProcessorUnderTest.processItem() + + // Then + assertWithMessage("item restoreFlag should be set to 0") + .that(mockCursor.restoreFlag) + .isEqualTo(0) + // currently gets marked restored twice, although markRestore() has check for restoreFlag + verify(mockCursor, times(2)).markRestored() + assertThat(mIconRequestInfos).containsExactly(mockIconRequestInfo) + verify(mockCursor).checkAndAddItem(mockWorkspaceInfo, mockBgDataModel, null) + } + + @Test + fun `When fallback Activity found for app then mark restored`() { + + // Given + mockLauncherApps = + mock().apply { + whenever(isPackageEnabled("package", mUserHandle)).thenReturn(true) + whenever(isActivityEnabled(mComponentName, mUserHandle)).thenReturn(false) + } + mockPmHelper = + mock().apply { + whenever(getAppLaunchIntent(mComponentName.packageName, mUserHandle)) + .thenReturn(intent) + } + + // When + itemProcessorUnderTest = createWorkspaceItemProcessorUnderTest() + itemProcessorUnderTest.processItem() + + // Then + assertWithMessage("item restoreFlag should be set to 0") + .that(mockCursor.restoreFlag) + .isEqualTo(0) + verify(mockCursor.updater().put(Favorites.INTENT, intent.toUri(0))).commit() + assertThat(mIconRequestInfos).containsExactly(mockIconRequestInfo) + verify(mockCursor).checkAndAddItem(mockWorkspaceInfo, mockBgDataModel, null) + } + + @Test + fun `When app with disabled activity and no fallback found then mark deleted`() { + + // Given + mockLauncherApps = + mock().apply { + whenever(isPackageEnabled("package", mUserHandle)).thenReturn(true) + whenever(isActivityEnabled(mComponentName, mUserHandle)).thenReturn(false) + } + mockPmHelper = + mock().apply { + whenever(getAppLaunchIntent(mComponentName.packageName, mUserHandle)) + .thenReturn(null) + } + + // When + itemProcessorUnderTest = createWorkspaceItemProcessorUnderTest() + itemProcessorUnderTest.processItem() + + // Then + assertWithMessage("item restoreFlag should be unchanged") + .that(mockCursor.restoreFlag) + .isEqualTo(1) + verify(mockCursor) + .markDeleted( + "No Activities found for id=1," + + " targetPkg=package," + + " component=ComponentInfo{package/class}." + + " Unable to create launch Intent.", + MISSING_INFO + ) + verify(mockCursor, times(0)).checkAndAddItem(any(), any(), anyOrNull()) + } + + @Test + fun `When valid Pinned Deep Shortcut then mark restored`() { + + // Given + mockCursor.itemType = ITEM_TYPE_DEEP_SHORTCUT + val expectedShortcutInfo = + mock().apply { + whenever(id).thenReturn("") + whenever(`package`).thenReturn("") + whenever(activity).thenReturn(mock()) + whenever(longLabel).thenReturn("") + whenever(isEnabled).thenReturn(true) + whenever(disabledMessage).thenReturn("") + whenever(disabledReason).thenReturn(0) + whenever(persons).thenReturn(EMPTY_PERSON_ARRAY) + } + val shortcutKey = ShortcutKey.fromIntent(intent, mockCursor.user) + mKeyToPinnedShortcutsMap[shortcutKey] = expectedShortcutInfo + mIconRequestInfos = mutableListOf() + + // When + itemProcessorUnderTest = + createWorkspaceItemProcessorUnderTest(allDeepShortcuts = mAllDeepShortcuts) + itemProcessorUnderTest.processItem() + + // Then + assertWithMessage("item restoreFlag should be set to 0") + .that(mockCursor.restoreFlag) + .isEqualTo(0) + assertThat(mIconRequestInfos).isEmpty() + assertThat(mAllDeepShortcuts).containsExactly(expectedShortcutInfo) + verify(mockCursor).markRestored() + verify(mockCursor).checkAndAddItem(any(), any(), anyOrNull()) + } + + @Test + fun `When Pinned Deep Shortcut not found then mark deleted`() { + + // Given + mockCursor.itemType = ITEM_TYPE_DEEP_SHORTCUT + mIconRequestInfos = mutableListOf() + mKeyToPinnedShortcutsMap = hashMapOf() + + // When + itemProcessorUnderTest = createWorkspaceItemProcessorUnderTest() + itemProcessorUnderTest.processItem() + + // Then + assertWithMessage("item restoreFlag should be set to 0") + .that(mockCursor.restoreFlag) + .isEqualTo(0) + assertThat(mIconRequestInfos).isEmpty() + verify(mockCursor, times(0)).checkAndAddItem(any(), any(), anyOrNull()) + verify(mockCursor) + .markDeleted( + "Pinned shortcut not found from request. package=pkg, user=UserHandle{0}", + "shortcut_not_found" + ) + } + + @Test + fun `When valid Pinned Deep Shortcut with null intent package then use targetPkg`() { + + // Given + mockCursor.itemType = ITEM_TYPE_DEEP_SHORTCUT + val expectedShortcutInfo = + mock().apply { + whenever(id).thenReturn("") + whenever(`package`).thenReturn("") + whenever(activity).thenReturn(mock()) + whenever(longLabel).thenReturn("") + whenever(isEnabled).thenReturn(true) + whenever(disabledMessage).thenReturn("") + whenever(disabledReason).thenReturn(0) + whenever(persons).thenReturn(EMPTY_PERSON_ARRAY) + whenever(userHandle).thenReturn(Process.myUserHandle()) + } + mIconRequestInfos = mutableListOf() + // Make sure shortcuts map has expected key from expected package + intent.`package` = mComponentName.packageName + val shortcutKey = ShortcutKey.fromIntent(intent, mockCursor.user) + mKeyToPinnedShortcutsMap[shortcutKey] = expectedShortcutInfo + // set intent package back to null to test scenario + intent.`package` = null + + // When + itemProcessorUnderTest = + createWorkspaceItemProcessorUnderTest(allDeepShortcuts = mAllDeepShortcuts) + itemProcessorUnderTest.processItem() + + // Then + assertWithMessage("item restoreFlag should be set to 0") + .that(mockCursor.restoreFlag) + .isEqualTo(0) + assertThat(mIconRequestInfos).isEmpty() + assertThat(mAllDeepShortcuts).containsExactly(expectedShortcutInfo) + verify(mockCursor).markRestored() + verify(mockCursor).checkAndAddItem(any(), any(), anyOrNull()) + } + + @Test + fun `When processing Folder then create FolderInfo and mark restored`() { + val actualFolderInfo = FolderInfo() + mockBgDataModel = + mock().apply { whenever(findOrMakeFolder(1)).thenReturn(actualFolderInfo) } + mockCursor = + mock().apply { + user = UserHandle(0) + itemType = ITEM_TYPE_FOLDER + id = 1 + container = 100 + restoreFlag = 1 + serialNumber = 101 + whenever(applyCommonProperties(any())).then {} + whenever(markRestored()).doAnswer { restoreFlag = 0 } + whenever(getColumnIndex(Favorites.TITLE)).thenReturn(4) + whenever(getString(4)).thenReturn("title") + whenever(options).thenReturn(5) + } + val expectedFolderInfo = + FolderInfo().apply { + itemType = ITEM_TYPE_FOLDER + spanX = 1 + spanY = 1 + options = 5 + } + itemProcessorUnderTest = createWorkspaceItemProcessorUnderTest() + + // When + itemProcessorUnderTest.processItem() + + // Then + assertWithMessage("item restoreFlag should be set to 0") + .that(mockCursor.restoreFlag) + .isEqualTo(0) + verify(mockCursor).markRestored() + assertThat(actualFolderInfo.id).isEqualTo(expectedFolderInfo.id) + assertThat(actualFolderInfo.container).isEqualTo(expectedFolderInfo.container) + assertThat(actualFolderInfo.itemType).isEqualTo(expectedFolderInfo.itemType) + assertThat(actualFolderInfo.screenId).isEqualTo(expectedFolderInfo.screenId) + assertThat(actualFolderInfo.cellX).isEqualTo(expectedFolderInfo.cellX) + assertThat(actualFolderInfo.cellY).isEqualTo(expectedFolderInfo.cellY) + assertThat(actualFolderInfo.spanX).isEqualTo(expectedFolderInfo.spanX) + assertThat(actualFolderInfo.spanY).isEqualTo(expectedFolderInfo.spanY) + assertThat(actualFolderInfo.options).isEqualTo(expectedFolderInfo.options) + verify(mockCursor).checkAndAddItem(actualFolderInfo, mockBgDataModel, null) + } + + @Test + fun `When valid TYPE_REAL App Widget then add item`() { + + // Given + val expectedProvider = "com.google.android.testApp/com.android.testApp.testAppProvider" + val expectedComponentName = + ComponentName.unflattenFromString(expectedProvider)!!.flattenToString() + val expectedRestoreStatus = FLAG_UI_NOT_READY + val expectedAppWidgetId = 0 + mockCursor.apply { + itemType = ITEM_TYPE_APPWIDGET + user = mUserHandle + restoreFlag = FLAG_UI_NOT_READY + container = CONTAINER_DESKTOP + whenever(isOnWorkspaceOrHotseat).thenCallRealMethod() + whenever(appWidgetProvider).thenReturn(expectedProvider) + whenever(appWidgetId).thenReturn(expectedAppWidgetId) + whenever(spanX).thenReturn(2) + whenever(spanY).thenReturn(1) + whenever(options).thenReturn(0) + whenever(appWidgetSource).thenReturn(20) + whenever(applyCommonProperties(any())).thenCallRealMethod() + whenever( + updater() + .put(Favorites.APPWIDGET_PROVIDER, expectedComponentName) + .put(Favorites.APPWIDGET_ID, expectedAppWidgetId) + .put(Favorites.RESTORED, expectedRestoreStatus) + .commit() + ) + .thenReturn(1) + } + val expectedWidgetInfo = + LauncherAppWidgetInfo().apply { + appWidgetId = expectedAppWidgetId + providerName = ComponentName.unflattenFromString(expectedProvider) + restoreStatus = expectedRestoreStatus + } + val expectedWidgetProviderInfo = + mock().apply { + provider = ComponentName.unflattenFromString(expectedProvider) + whenever(user).thenReturn(mUserHandle) + } + val inflationResult = + WidgetInflater.InflationResult( + type = WidgetInflater.TYPE_REAL, + widgetInfo = expectedWidgetProviderInfo + ) + mockWidgetInflater = + mock().apply { + whenever(inflateAppWidget(any())).thenReturn(inflationResult) + } + val packageUserKey = PackageUserKey("com.google.android.testApp", mUserHandle) + mInstallingPkgs[packageUserKey] = PackageInstaller.SessionInfo() + + // When + itemProcessorUnderTest = + createWorkspaceItemProcessorUnderTest(widgetProvidersMap = mWidgetProvidersMap) + itemProcessorUnderTest.processItem() + + // Then + val widgetInfoCaptor = ArgumentCaptor.forClass(LauncherAppWidgetInfo::class.java) + verify(mockCursor).checkAndAddItem(widgetInfoCaptor.capture(), eq(mockBgDataModel)) + val actualWidgetInfo = widgetInfoCaptor.value + with(actualWidgetInfo) { + assertThat(providerName).isEqualTo(expectedWidgetInfo.providerName) + assertThat(restoreStatus).isEqualTo(expectedWidgetInfo.restoreStatus) + assertThat(targetComponent).isEqualTo(expectedWidgetInfo.targetComponent) + assertThat(appWidgetId).isEqualTo(expectedWidgetInfo.appWidgetId) + } + val expectedComponentKey = + ComponentKey(expectedWidgetProviderInfo.provider, expectedWidgetProviderInfo.user) + assertThat(mWidgetProvidersMap[expectedComponentKey]).isEqualTo(expectedWidgetProviderInfo) + } + + @Test + fun `When valid Pending Widget then checkAndAddItem`() { + + // Given + mockCursor = + mock().apply { + itemType = ITEM_TYPE_APPWIDGET + id = 1 + user = UserHandle(1) + restoreFlag = FLAG_UI_NOT_READY + container = CONTAINER_DESKTOP + whenever(isOnWorkspaceOrHotseat).thenCallRealMethod() + whenever(appWidgetProvider) + .thenReturn("com.google.android.testApp/com.android.testApp.testAppProvider") + whenever(appWidgetId).thenReturn(0) + whenever(spanX).thenReturn(2) + whenever(spanY).thenReturn(1) + whenever(options).thenReturn(0) + whenever(appWidgetSource).thenReturn(20) + whenever(applyCommonProperties(any())).thenCallRealMethod() + } + val mockProviderInfo = + mock().apply { + provider = mock() + whenever(user).thenReturn(UserHandle(1)) + } + val inflationResult = + WidgetInflater.InflationResult( + type = WidgetInflater.TYPE_PENDING, + widgetInfo = mockProviderInfo + ) + mockWidgetInflater = + mock().apply { + whenever(inflateAppWidget(any())).thenReturn(inflationResult) + } + itemProcessorUnderTest = + createWorkspaceItemProcessorUnderTest(widgetProvidersMap = mWidgetProvidersMap) + + // When + itemProcessorUnderTest.processItem() + + // Then + verify(mockCursor).checkAndAddItem(any(), any()) + } + + @Test + fun `When Unrestored Pending App Widget then mark deleted`() { + + // Given + val expectedProvider = "com.google.android.testApp/com.android.testApp.testAppProvider" + mockCursor = + mock().apply { + itemType = ITEM_TYPE_APPWIDGET + id = 1 + user = UserHandle(1) + restoreFlag = FLAG_UI_NOT_READY + container = CONTAINER_DESKTOP + whenever(isOnWorkspaceOrHotseat).thenCallRealMethod() + whenever(appWidgetProvider).thenReturn(expectedProvider) + whenever(appWidgetId).thenReturn(0) + whenever(spanX).thenReturn(2) + whenever(spanY).thenReturn(1) + whenever(options).thenReturn(0) + whenever(appWidgetSource).thenReturn(20) + whenever(applyCommonProperties(any())).thenCallRealMethod() + } + mInstallingPkgs = hashMapOf() + val inflationResult = + WidgetInflater.InflationResult(type = WidgetInflater.TYPE_PENDING, widgetInfo = null) + mockWidgetInflater = + mock().apply { + whenever(inflateAppWidget(any())).thenReturn(inflationResult) + } + val expectedComponentName = ComponentName.unflattenFromString(expectedProvider) + + // When + itemProcessorUnderTest = + createWorkspaceItemProcessorUnderTest(widgetProvidersMap = mWidgetProvidersMap) + itemProcessorUnderTest.processItem() + + // Then + verify(mockCursor) + .markDeleted( + "processWidget: Unrestored Pending widget removed: id=1, appWidgetId=0, component=$expectedComponentName, restoreFlag:=4", + LauncherRestoreEventLogger.RestoreError.APP_NOT_INSTALLED + ) + } + + @Test + fun `When Pending App Widget has not started restore then update db and add item`() { + + val mockitoSession = + ExtendedMockito.mockitoSession() + .strictness(Strictness.LENIENT) + .mockStatic(WidgetSections::class.java) + .startMocking() + try { + // Given + val expectedProvider = "com.google.android.testApp/com.android.testApp.testAppProvider" + val expectedComponentName = + ComponentName.unflattenFromString(expectedProvider)!!.flattenToString() + val expectedRestoreStatus = FLAG_UI_NOT_READY or FLAG_RESTORE_STARTED + val expectedAppWidgetId = 0 + mockCursor.apply { + itemType = ITEM_TYPE_APPWIDGET + user = mUserHandle + restoreFlag = FLAG_UI_NOT_READY + container = CONTAINER_DESKTOP + whenever(isOnWorkspaceOrHotseat).thenCallRealMethod() + whenever(appWidgetProvider).thenReturn(expectedProvider) + whenever(appWidgetId).thenReturn(expectedAppWidgetId) + whenever(spanX).thenReturn(2) + whenever(spanY).thenReturn(1) + whenever(options).thenReturn(0) + whenever(appWidgetSource).thenReturn(20) + whenever(applyCommonProperties(any())).thenCallRealMethod() + whenever( + updater() + .put(Favorites.APPWIDGET_PROVIDER, expectedComponentName) + .put(Favorites.APPWIDGET_ID, expectedAppWidgetId) + .put(Favorites.RESTORED, expectedRestoreStatus) + .commit() + ) + .thenReturn(1) + } + val inflationResult = + WidgetInflater.InflationResult( + type = WidgetInflater.TYPE_PENDING, + widgetInfo = null + ) + mockWidgetInflater = + mock().apply { + whenever(inflateAppWidget(any())).thenReturn(inflationResult) + } + val packageUserKey = PackageUserKey("com.google.android.testApp", mUserHandle) + mInstallingPkgs[packageUserKey] = PackageInstaller.SessionInfo() + + // When + itemProcessorUnderTest = + createWorkspaceItemProcessorUnderTest(widgetProvidersMap = mWidgetProvidersMap) + itemProcessorUnderTest.processItem() + + // Then + val expectedWidgetInfo = + LauncherAppWidgetInfo().apply { + appWidgetId = expectedAppWidgetId + providerName = ComponentName.unflattenFromString(expectedProvider) + restoreStatus = expectedRestoreStatus + } + verify( + mockCursor + .updater() + .put(Favorites.APPWIDGET_PROVIDER, expectedProvider) + .put(Favorites.APPWIDGET_ID, expectedAppWidgetId) + .put(Favorites.RESTORED, expectedRestoreStatus) + ) + .commit() + val widgetInfoCaptor = ArgumentCaptor.forClass(LauncherAppWidgetInfo::class.java) + verify(mockCursor).checkAndAddItem(widgetInfoCaptor.capture(), eq(mockBgDataModel)) + val actualWidgetInfo = widgetInfoCaptor.value + with(actualWidgetInfo) { + assertThat(providerName).isEqualTo(expectedWidgetInfo.providerName) + assertThat(restoreStatus).isEqualTo(expectedWidgetInfo.restoreStatus) + assertThat(targetComponent).isEqualTo(expectedWidgetInfo.targetComponent) + assertThat(appWidgetId).isEqualTo(expectedWidgetInfo.appWidgetId) + } + } finally { + mockitoSession.finishMocking() + } + } + + @Test + @EnableFlags(FLAG_ENABLE_SUPPORT_FOR_ARCHIVING) + fun `When Archived Pending App Widget then checkAndAddItem`() { + val mockitoSession = + ExtendedMockito.mockitoSession().mockStatic(Utilities::class.java).startMocking() + try { + // Given + val expectedProvider = "com.google.android.testApp/com.android.testApp.testAppProvider" + val expectedComponentName = ComponentName.unflattenFromString(expectedProvider) + val expectedPackage = expectedComponentName!!.packageName + mockPmHelper = + mock().apply { + whenever(isAppArchived(expectedPackage)).thenReturn(true) + } + mockCursor = + mock().apply { + itemType = ITEM_TYPE_APPWIDGET + id = 1 + user = UserHandle(1) + restoreFlag = FLAG_UI_NOT_READY + container = CONTAINER_DESKTOP + whenever(isOnWorkspaceOrHotseat).thenCallRealMethod() + whenever(appWidgetProvider).thenReturn(expectedProvider) + whenever(appWidgetId).thenReturn(0) + whenever(spanX).thenReturn(2) + whenever(spanY).thenReturn(1) + whenever(options).thenReturn(0) + whenever(appWidgetSource).thenReturn(20) + whenever(applyCommonProperties(any())).thenCallRealMethod() + } + mInstallingPkgs = hashMapOf() + val inflationResult = + WidgetInflater.InflationResult( + type = WidgetInflater.TYPE_PENDING, + widgetInfo = null + ) + mockWidgetInflater = + mock().apply { + whenever(inflateAppWidget(any())).thenReturn(inflationResult) + } + itemProcessorUnderTest = + createWorkspaceItemProcessorUnderTest(widgetProvidersMap = mWidgetProvidersMap) + + // When + itemProcessorUnderTest.processItem() + + // Then + verify(mockCursor).checkAndAddItem(any(), any()) + } finally { + mockitoSession.finishMocking() + } + } + + @Test + fun `When widget inflation result is TYPE_DELETE then mark deleted`() { + // Given + val expectedProvider = "com.google.android.testApp/com.android.testApp.testAppProvider" + val expectedComponentName = ComponentName.unflattenFromString(expectedProvider) + val expectedPackage = expectedComponentName!!.packageName + mockPmHelper = + mock().apply { + whenever(isAppArchived(expectedPackage)).thenReturn(true) + } + mockCursor = + mock().apply { + itemType = ITEM_TYPE_APPWIDGET + id = 1 + user = UserHandle(1) + container = CONTAINER_DESKTOP + whenever(spanX).thenReturn(2) + whenever(spanY).thenReturn(1) + whenever(appWidgetProvider).thenReturn(expectedProvider) + whenever(isOnWorkspaceOrHotseat).thenCallRealMethod() + whenever(applyCommonProperties(any())).thenCallRealMethod() + } + mInstallingPkgs = hashMapOf() + val inflationResult = + WidgetInflater.InflationResult( + type = WidgetInflater.TYPE_DELETE, + widgetInfo = null, + reason = "test_delete_reason", + restoreErrorType = MISSING_WIDGET_PROVIDER + ) + mockWidgetInflater = + mock().apply { + whenever(inflateAppWidget(any())).thenReturn(inflationResult) + } + itemProcessorUnderTest = + createWorkspaceItemProcessorUnderTest(widgetProvidersMap = mWidgetProvidersMap) + + // When + itemProcessorUnderTest.processItem() + + // Then + verify(mockCursor).markDeleted(inflationResult.reason, inflationResult.restoreErrorType) + } +} diff --git a/tests/src/com/android/launcher3/model/WorkspaceItemSpaceFinderTest.kt b/tests/src/com/android/launcher3/model/WorkspaceItemSpaceFinderTest.kt new file mode 100644 index 0000000000..b3d02be460 --- /dev/null +++ b/tests/src/com/android/launcher3/model/WorkspaceItemSpaceFinderTest.kt @@ -0,0 +1,179 @@ +/* + * 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. + */ +package com.android.launcher3.model + +import android.graphics.Rect +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.google.common.truth.Truth.assertThat +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +/** Tests for [WorkspaceItemSpaceFinder] */ +@SmallTest +@RunWith(AndroidJUnit4::class) +class WorkspaceItemSpaceFinderTest : AbstractWorkspaceModelTest() { + + private val mItemSpaceFinder = WorkspaceItemSpaceFinder() + + @Before + override fun setup() { + super.setup() + } + + @After + override fun tearDown() { + super.tearDown() + } + + private fun findSpace(spanX: Int, spanY: Int): NewItemSpace = + mItemSpaceFinder + .findSpaceForItem( + mAppState, + mModelHelper.bgDataModel, + mExistingScreens, + mNewScreens, + spanX, + spanY + ) + .let { NewItemSpace.fromIntArray(it) } + + private fun assertRegionVacant(newItemSpace: NewItemSpace, spanX: Int, spanY: Int) { + assertThat( + mScreenOccupancy[newItemSpace.screenId].isRegionVacant( + newItemSpace.cellX, + newItemSpace.cellY, + spanX, + spanY + ) + ) + .isTrue() + } + + @Test + fun justEnoughSpaceOnFirstScreen_whenFindSpaceForItem_thenReturnFirstScreenId() { + setupWorkspacesWithSpaces( + // 3x2 space on screen 0, but it should be skipped + screen0 = listOf(Rect(2, 0, 5, 2)), + screen1 = listOf(Rect(2, 2, 3, 3)), // 1x1 space + // 2 spaces of sizes 3x2 and 2x3 + screen2 = listOf(Rect(2, 0, 5, 2), Rect(0, 2, 2, 5)), + ) + + val spaceFound = findSpace(1, 1) + + assertThat(spaceFound.screenId).isEqualTo(1) + assertRegionVacant(spaceFound, 1, 1) + } + + @Test + fun notEnoughSpaceOnFirstScreen_whenFindSpaceForItem_thenReturnSecondScreenId() { + setupWorkspacesWithSpaces( + // 3x2 space on screen 0, but it should be skipped + screen0 = listOf(Rect(2, 0, 5, 2)), + screen1 = listOf(Rect(2, 2, 3, 3)), // 1x1 space + // 2 spaces of sizes 3x2 and 2x3 + screen2 = listOf(Rect(2, 0, 5, 2), Rect(0, 2, 2, 5)), + ) + + // Find a larger space + val spaceFound = findSpace(2, 3) + + assertThat(spaceFound.screenId).isEqualTo(2) + assertRegionVacant(spaceFound, 2, 3) + } + + @Test + fun notEnoughSpaceOnExistingScreens_returnNewScreenId() { + setupWorkspacesWithSpaces( + // 3x2 space on screen 0, but it should be skipped + screen0 = listOf(Rect(2, 0, 5, 2)), + // 2 spaces of sizes 3x2 and 2x3 + screen1 = listOf(Rect(2, 0, 5, 2), Rect(0, 2, 2, 5)), + // 2 spaces of sizes 1x2 and 2x2 + screen2 = listOf(Rect(1, 0, 2, 2), Rect(3, 2, 5, 4)), + ) + + val oldScreens = mExistingScreens.clone() + val spaceFound = findSpace(3, 3) + + assertThat(oldScreens.contains(spaceFound.screenId)).isFalse() + assertThat(mNewScreens.contains(spaceFound.screenId)).isTrue() + } + + @Test + fun firstScreenIsEmptyButSecondIsNotEmpty_returnSecondScreenId() { + setupWorkspacesWithSpaces( + // 3x2 space on screen 0, but it should be skipped + screen0 = listOf(Rect(2, 0, 5, 2)), + // empty screens are skipped + screen2 = listOf(Rect(2, 0, 5, 2)), // 3x2 space + ) + + val spaceFound = findSpace(2, 1) + + assertThat(spaceFound.screenId).isEqualTo(2) + assertRegionVacant(spaceFound, 2, 1) + } + + @Test + fun twoEmptyMiddleScreens_returnThirdScreen() { + setupWorkspacesWithSpaces( + // 3x2 space on screen 0, but it should be skipped + screen0 = listOf(Rect(2, 0, 5, 2)), + // empty screens are skipped + screen3 = listOf(Rect(1, 1, 4, 4)), // 3x3 space + ) + + val spaceFound = findSpace(2, 3) + + assertThat(spaceFound.screenId).isEqualTo(3) + assertRegionVacant(spaceFound, 2, 3) + } + + @Test + fun allExistingPagesAreFull_returnNewScreenId() { + setupWorkspacesWithSpaces( + // 3x2 space on screen 0, but it should be skipped + screen0 = listOf(Rect(2, 0, 5, 2)), + screen1 = fullScreenSpaces, + screen2 = fullScreenSpaces, + ) + + val spaceFound = findSpace(2, 3) + + assertThat(spaceFound.screenId).isEqualTo(3) + assertThat(mNewScreens.contains(spaceFound.screenId)).isTrue() + } + + @Test + fun firstTwoPagesAreFull_and_ThirdPageIsEmpty_returnThirdPage() { + setupWorkspacesWithSpaces( + // 3x2 space on screen 0, but it should be skipped + screen0 = listOf(Rect(2, 0, 5, 2)), + screen1 = fullScreenSpaces, // full screens are skipped + screen2 = fullScreenSpaces, // full screens are skipped + screen3 = emptyScreenSpaces + ) + + val spaceFound = findSpace(3, 1) + + assertThat(spaceFound.screenId).isEqualTo(3) + assertRegionVacant(spaceFound, 3, 1) + } +} diff --git a/tests/src/com/android/launcher3/model/gridmigration/GridMigrationUtils.kt b/tests/src/com/android/launcher3/model/gridmigration/GridMigrationUtils.kt new file mode 100644 index 0000000000..cc8e61d726 --- /dev/null +++ b/tests/src/com/android/launcher3/model/gridmigration/GridMigrationUtils.kt @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.model.gridmigration + +import android.content.ContentValues +import android.database.sqlite.SQLiteDatabase +import android.graphics.Point +import com.android.launcher3.LauncherSettings.Favorites +import com.android.launcher3.celllayout.board.CellLayoutBoard + +class MockSet(override val size: Int) : Set { + override fun contains(element: String): Boolean = true + override fun containsAll(elements: Collection): Boolean = true + override fun isEmpty(): Boolean = false + override fun iterator(): Iterator = listOf().iterator() +} + +fun itemListToBoard(itemsArg: List, boardSize: Point): List { + val items = itemsArg.filter { it.container != Favorites.CONTAINER_HOTSEAT } + val boardList = + List(items.maxOf { it.screenId + 1 }) { CellLayoutBoard(boardSize.x, boardSize.y) } + items.forEach { + when (it.type) { + Favorites.ITEM_TYPE_FOLDER, + Favorites.ITEM_TYPE_APP_PAIR -> throw Exception("Not implemented") + Favorites.ITEM_TYPE_APPWIDGET -> + boardList[it.screenId].addWidget(it.x, it.y, it.spanX, it.spanY) + Favorites.ITEM_TYPE_APPLICATION -> boardList[it.screenId].addIcon(it.x, it.y) + } + } + return boardList +} + +fun insertIntoDb(tableName: String, entry: WorkspaceItem, db: SQLiteDatabase) { + val values = ContentValues() + values.put(Favorites.SCREEN, entry.screenId) + values.put(Favorites.CELLX, entry.x) + values.put(Favorites.CELLY, entry.y) + values.put(Favorites.SPANX, entry.spanX) + values.put(Favorites.SPANY, entry.spanY) + values.put(Favorites.TITLE, entry.title) + values.put(Favorites.INTENT, entry.intent) + values.put(Favorites.APPWIDGET_PROVIDER, entry.appWidgetProvider) + values.put(Favorites.APPWIDGET_ID, entry.appWidgetId) + values.put(Favorites.CONTAINER, entry.container) + values.put(Favorites.ITEM_TYPE, entry.type) + values.put(Favorites._ID, entry.id) + db.insert(tableName, null, values) +} + +fun readDb(tableName: String, db: SQLiteDatabase): List { + val result = mutableListOf() + val cursor = db.query(tableName, null, null, null, null, null, null) + val indexCellX: Int = cursor.getColumnIndexOrThrow(Favorites.CELLX) + val indexCellY: Int = cursor.getColumnIndexOrThrow(Favorites.CELLY) + val indexSpanX: Int = cursor.getColumnIndexOrThrow(Favorites.SPANX) + val indexSpanY: Int = cursor.getColumnIndexOrThrow(Favorites.SPANY) + val indexId: Int = cursor.getColumnIndexOrThrow(Favorites._ID) + val indexScreen: Int = cursor.getColumnIndexOrThrow(Favorites.SCREEN) + val indexTitle: Int = cursor.getColumnIndexOrThrow(Favorites.TITLE) + val indexAppWidgetId: Int = cursor.getColumnIndexOrThrow(Favorites.APPWIDGET_ID) + val indexWidgetProvider: Int = cursor.getColumnIndexOrThrow(Favorites.APPWIDGET_PROVIDER) + val indexIntent: Int = cursor.getColumnIndexOrThrow(Favorites.INTENT) + val indexItemType: Int = cursor.getColumnIndexOrThrow(Favorites.ITEM_TYPE) + val container: Int = cursor.getColumnIndexOrThrow(Favorites.CONTAINER) + while (cursor.moveToNext()) { + result.add( + WorkspaceItem( + x = cursor.getInt(indexCellX), + y = cursor.getInt(indexCellY), + spanX = cursor.getInt(indexSpanX), + spanY = cursor.getInt(indexSpanY), + id = cursor.getInt(indexId), + screenId = cursor.getInt(indexScreen), + title = cursor.getString(indexTitle), + appWidgetId = cursor.getInt(indexAppWidgetId), + appWidgetProvider = cursor.getString(indexWidgetProvider), + intent = cursor.getString(indexIntent), + type = cursor.getInt(indexItemType), + container = cursor.getInt(container) + ) + ) + } + return result +} + +data class WorkspaceItem( + val x: Int, + val y: Int, + val spanX: Int, + val spanY: Int, + val id: Int, + val screenId: Int, + val title: String, + val appWidgetId: Int, + val appWidgetProvider: String, + val intent: String, + val type: Int, + val container: Int, +) diff --git a/tests/src/com/android/launcher3/model/gridmigration/ValidGridMigrationUnitTest.kt b/tests/src/com/android/launcher3/model/gridmigration/ValidGridMigrationUnitTest.kt new file mode 100644 index 0000000000..58b915f0ca --- /dev/null +++ b/tests/src/com/android/launcher3/model/gridmigration/ValidGridMigrationUnitTest.kt @@ -0,0 +1,203 @@ +/* + * 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.launcher3.model.gridmigration + +import android.content.Context +import android.database.sqlite.SQLiteDatabase +import android.graphics.Point +import android.os.Process +import android.util.Log +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import androidx.test.platform.app.InstrumentationRegistry +import com.android.launcher3.InvariantDeviceProfile +import com.android.launcher3.LauncherSettings.Favorites +import com.android.launcher3.celllayout.testgenerator.ValidGridMigrationTestCaseGenerator +import com.android.launcher3.celllayout.testgenerator.generateItemsForTest +import com.android.launcher3.model.DatabaseHelper +import com.android.launcher3.model.DeviceGridState +import com.android.launcher3.model.GridSizeMigrationUtil +import com.android.launcher3.pm.UserCache +import com.android.launcher3.provider.LauncherDbUtils +import com.android.launcher3.util.rule.TestStabilityRule +import com.android.launcher3.util.rule.TestStabilityRule.Stability +import java.util.Random +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +private data class Grid(val tableName: String, val size: Point, val items: List) { + fun toGridState(): DeviceGridState = + DeviceGridState(size.x, size.y, size.x, InvariantDeviceProfile.TYPE_PHONE, tableName) +} + +@SmallTest +@RunWith(AndroidJUnit4::class) +class ValidGridMigrationUnitTest { + + companion object { + const val SEED = 1044542 + val REPEAT_AFTER = Point(0, 10) + val REPEAT_AFTER_DST = Point(6, 15) + const val TAG = "ValidGridMigrationUnitTest" + const val SMALL_TEST_SIZE = 60 + const val LARGE_TEST_SIZE = 1000 + } + + private lateinit var context: Context + + @Before + fun setUp() { + context = InstrumentationRegistry.getInstrumentation().targetContext + } + + private fun validate(srcGrid: Grid, dstGrid: Grid, resultItems: List) { + // This returns a map with the number of repeated elements + // ex { calculatorIcon : 6, weatherWidget : 2 } + val itemsToMap = { it: List -> + it.filter { it.container != Favorites.CONTAINER_HOTSEAT } + .groupingBy { + when (it.type) { + Favorites.ITEM_TYPE_FOLDER, + Favorites.ITEM_TYPE_APP_PAIR -> throw Exception("Not implemented") + Favorites.ITEM_TYPE_APPWIDGET -> it.appWidgetProvider + Favorites.ITEM_TYPE_APPLICATION -> it.intent + else -> it.title + } + } + .eachCount() + } + resultItems.forEach { + assert((it.x in 0..dstGrid.size.x) && (it.y in 0..dstGrid.size.y)) { + "Item outside of the board size. Size = ${dstGrid.size} Item = $it" + } + assert( + (it.x + it.spanX in 0..dstGrid.size.x) && (it.y + it.spanY in 0..dstGrid.size.y) + ) { + "Item doesn't fit in the grid. Size = ${dstGrid.size} Item = $it" + } + } + + val srcCountMap = itemsToMap(srcGrid.items) + val resultCountMap = itemsToMap(resultItems) + val diff = resultCountMap - srcCountMap + + diff.forEach { (k, count) -> + assert(count >= 0) { "Source item $k not present on the result" } + } + } + + private fun addItemsToDb(db: SQLiteDatabase, grid: Grid) { + LauncherDbUtils.SQLiteTransaction(db).use { transaction -> + grid.items.forEach { insertIntoDb(grid.tableName, it, transaction.db) } + transaction.commit() + } + } + + private fun migrate( + srcGrid: Grid, + dstGrid: Grid, + ): List { + val userSerial = UserCache.INSTANCE[context].getSerialNumberForUser(Process.myUserHandle()) + val dbHelper = + DatabaseHelper( + context, + null, + { UserCache.INSTANCE.get(context).getSerialNumberForUser(it) }, + {} + ) + + Favorites.addTableToDb(dbHelper.writableDatabase, userSerial, false, srcGrid.tableName) + + addItemsToDb(dbHelper.writableDatabase, srcGrid) + addItemsToDb(dbHelper.writableDatabase, dstGrid) + + LauncherDbUtils.SQLiteTransaction(dbHelper.writableDatabase).use { + GridSizeMigrationUtil.migrate( + dbHelper, + GridSizeMigrationUtil.DbReader(it.db, srcGrid.tableName, context, MockSet(1)), + GridSizeMigrationUtil.DbReader(it.db, dstGrid.tableName, context, MockSet(1)), + dstGrid.size.x, + dstGrid.size, + srcGrid.toGridState(), + dstGrid.toGridState() + ) + it.commit() + } + return readDb(dstGrid.tableName, dbHelper.readableDatabase) + } + + @Test + fun runTestCase() { + val caseGenerator = ValidGridMigrationTestCaseGenerator(Random(SEED.toLong())) + for (i in 0..SMALL_TEST_SIZE) { + val testCase = caseGenerator.generateTestCase(isDestEmpty = true) + Log.d(TAG, "Test case = $testCase") + val srcGrid = + Grid( + tableName = Favorites.TMP_TABLE, + size = testCase.srcSize, + items = generateItemsForTest(testCase.boards, REPEAT_AFTER) + ) + val dstGrid = + Grid(tableName = Favorites.TABLE_NAME, size = testCase.targetSize, items = listOf()) + validate(srcGrid, dstGrid, migrate(srcGrid, dstGrid)) + } + } + + @Test + fun mergeBoards() { + val caseGenerator = ValidGridMigrationTestCaseGenerator(Random(SEED.toLong())) + for (i in 0..SMALL_TEST_SIZE) { + val testCase = caseGenerator.generateTestCase(isDestEmpty = false) + Log.d(TAG, "Test case = $testCase") + val srcGrid = + Grid( + tableName = Favorites.TMP_TABLE, + size = testCase.srcSize, + items = generateItemsForTest(testCase.boards, REPEAT_AFTER) + ) + val dstGrid = + Grid( + tableName = Favorites.TABLE_NAME, + size = testCase.targetSize, + items = generateItemsForTest(testCase.destBoards, REPEAT_AFTER_DST) + ) + validate(srcGrid, dstGrid, migrate(srcGrid, dstGrid)) + } + } + + // This test takes about 4 minutes, there is no need to run it in presubmit. + @Stability(flavors = TestStabilityRule.LOCAL or TestStabilityRule.PLATFORM_POSTSUBMIT) + @Test + fun runExtensiveTestCases() { + val caseGenerator = ValidGridMigrationTestCaseGenerator(Random(SEED.toLong())) + for (i in 0..LARGE_TEST_SIZE) { + val testCase = caseGenerator.generateTestCase(isDestEmpty = true) + Log.d(TAG, "Test case = $testCase") + val srcGrid = + Grid( + tableName = Favorites.TMP_TABLE, + size = testCase.srcSize, + items = generateItemsForTest(testCase.boards, REPEAT_AFTER) + ) + val dstGrid = + Grid(tableName = Favorites.TABLE_NAME, size = testCase.targetSize, items = listOf()) + validate(srcGrid, dstGrid, migrate(srcGrid, dstGrid)) + } + } +} diff --git a/tests/src/com/android/launcher3/nonquickstep/DeviceProfileDumpTest.kt b/tests/src/com/android/launcher3/nonquickstep/DeviceProfileDumpTest.kt index 05cf926aa1..60385a7c3a 100644 --- a/tests/src/com/android/launcher3/nonquickstep/DeviceProfileDumpTest.kt +++ b/tests/src/com/android/launcher3/nonquickstep/DeviceProfileDumpTest.kt @@ -19,7 +19,7 @@ import androidx.test.filters.SmallTest import com.android.launcher3.AbstractDeviceProfileTest import com.android.launcher3.DeviceProfile import com.android.launcher3.Flags -import com.android.launcher3.util.rule.setFlags +import com.android.launcher3.InvariantDeviceProfile import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -35,17 +35,17 @@ class DeviceProfileDumpTest : AbstractDeviceProfileTest() { @Before fun setUp() { - setFlagsRule.setFlags( - instance.decoupleDepth, - Flags.FLAG_ENABLE_SCALING_REVEAL_HOME_ANIMATION, - ) - setFlagsRule.setFlags(false, Flags.FLAG_ONE_GRID_SPECS) + if (instance.decoupleDepth) { + setFlagsRule.enableFlags(Flags.FLAG_ENABLE_SCALING_REVEAL_HOME_ANIMATION) + } else { + setFlagsRule.disableFlags(Flags.FLAG_ENABLE_SCALING_REVEAL_HOME_ANIMATION) + } } @Test fun dumpPortraitGesture() { initializeDevice(instance.deviceName, isGestureMode = true, isLandscape = false) - val dp = context.appComponent.idp.getDeviceProfile(context) + val dp = getDeviceProfileForGrid(instance.gridName) dp.isTaskbarPresentInApps = instance.isTaskbarPresentInApps assertDump(dp, instance.filename("Portrait")) @@ -54,7 +54,7 @@ class DeviceProfileDumpTest : AbstractDeviceProfileTest() { @Test fun dumpPortrait3Button() { initializeDevice(instance.deviceName, isGestureMode = false, isLandscape = false) - val dp = context.appComponent.idp.getDeviceProfile(context) + val dp = getDeviceProfileForGrid(instance.gridName) dp.isTaskbarPresentInApps = instance.isTaskbarPresentInApps assertDump(dp, instance.filename("Portrait3Button")) @@ -63,7 +63,7 @@ class DeviceProfileDumpTest : AbstractDeviceProfileTest() { @Test fun dumpLandscapeGesture() { initializeDevice(instance.deviceName, isGestureMode = true, isLandscape = true) - val dp = context.appComponent.idp.getDeviceProfile(context) + val dp = getDeviceProfileForGrid(instance.gridName) dp.isTaskbarPresentInApps = instance.isTaskbarPresentInApps val testName = @@ -78,7 +78,7 @@ class DeviceProfileDumpTest : AbstractDeviceProfileTest() { @Test fun dumpLandscape3Button() { initializeDevice(instance.deviceName, isGestureMode = false, isLandscape = true) - val dp = context.appComponent.idp.getDeviceProfile(context) + val dp = getDeviceProfileForGrid(instance.gridName) dp.isTaskbarPresentInApps = instance.isTaskbarPresentInApps val testName = @@ -100,25 +100,26 @@ class DeviceProfileDumpTest : AbstractDeviceProfileTest() { deviceSpecFolded = deviceSpecs["twopanel-phone"]!!, isLandscape = isLandscape, isGestureMode = isGestureMode, - gridName = instance.gridName, ) "tablet" -> initializeVarsForTablet( deviceSpec = deviceSpec, isLandscape = isLandscape, - isGestureMode = isGestureMode, - gridName = instance.gridName, + isGestureMode = isGestureMode ) else -> initializeVarsForPhone( deviceSpec = deviceSpec, isVerticalBar = isLandscape, - isGestureMode = isGestureMode, - gridName = instance.gridName, + isGestureMode = isGestureMode ) } } + private fun getDeviceProfileForGrid(gridName: String): DeviceProfile { + return InvariantDeviceProfile(context, gridName).getDeviceProfile(context) + } + private fun assertDump(dp: DeviceProfile, filename: String) { assertDump(dp, folderName, filename) } @@ -135,7 +136,7 @@ class DeviceProfileDumpTest : AbstractDeviceProfileTest() { "twopanel-tablet", gridName = "4_by_4", isTaskbarPresentInApps = true, - decoupleDepth = true, + decoupleDepth = true ), ) } @@ -144,7 +145,7 @@ class DeviceProfileDumpTest : AbstractDeviceProfileTest() { val deviceName: String, val gridName: String, val isTaskbarPresentInApps: Boolean = false, - val decoupleDepth: Boolean = false, + val decoupleDepth: Boolean = false ) { fun filename(testName: String = ""): String { val device = diff --git a/tests/src/com/android/launcher3/popup/SystemShortcutTest.java b/tests/src/com/android/launcher3/popup/SystemShortcutTest.java index 2531f6bacf..98b6b4b7a8 100644 --- a/tests/src/com/android/launcher3/popup/SystemShortcutTest.java +++ b/tests/src/com/android/launcher3/popup/SystemShortcutTest.java @@ -19,26 +19,19 @@ package com.android.launcher3.popup; import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; -import static com.android.launcher3.AbstractFloatingView.TYPE_SNACKBAR; -import static com.android.launcher3.Flags.FLAG_ENABLE_DISMISS_PREDICTION_UNDO; import static com.android.launcher3.Flags.FLAG_ENABLE_PRIVATE_SPACE; import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_ALL_APPS; import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION; import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; -import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_DISMISS_PREDICTION_UNDO; -import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_DONT_SUGGEST_APP_TAP; import static com.android.launcher3.model.data.WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI; -import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -57,29 +50,19 @@ import android.view.View; import androidx.test.annotation.UiThreadTest; import androidx.test.filters.SmallTest; -import androidx.test.platform.app.InstrumentationRegistry; -import com.android.launcher3.AbstractFloatingView; -import com.android.launcher3.R; import com.android.launcher3.allapps.PrivateProfileManager; -import com.android.launcher3.dagger.LauncherAppComponent; -import com.android.launcher3.dagger.LauncherAppSingleton; -import com.android.launcher3.logging.StatsLogManager; -import com.android.launcher3.logging.StatsLogManager.StatsLogger; import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.pm.UserCache; -import com.android.launcher3.util.AllModulesForTest; +import com.android.launcher3.util.ApiWrapper; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext; import com.android.launcher3.util.LauncherMultivalentJUnit; import com.android.launcher3.util.TestSandboxModelContextWrapper; -import com.android.launcher3.util.TestUtil; import com.android.launcher3.util.UserIconInfo; -import com.android.launcher3.views.Snackbar; -import com.android.launcher3.widget.picker.model.WidgetPickerDataProvider; -import com.android.launcher3.widget.picker.model.data.WidgetPickerData; +import com.android.launcher3.views.BaseDragLayer; import org.junit.After; import org.junit.Assert; @@ -87,12 +70,10 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Answers; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import dagger.BindsInstance; -import dagger.Component; +import java.util.ArrayList; @SmallTest @RunWith(LauncherMultivalentJUnit.class) @@ -105,33 +86,27 @@ public class SystemShortcutTest { private TestSandboxModelContextWrapper mTestContext; private final SandboxModelContext mSandboxContext = new SandboxModelContext(); private PrivateProfileManager mPrivateProfileManager; - private WidgetPickerDataProvider mWidgetPickerDataProvider; + private PopupDataProvider mPopupDataProvider; private AppInfo mAppInfo; - @Mock UserCache mUserCache; + @Mock ApiWrapper mApiWrapper; + @Mock BaseDragLayer mBaseDragLayer; @Mock UserIconInfo mUserIconInfo; @Mock LauncherActivityInfo mLauncherActivityInfo; @Mock ApplicationInfo mApplicationInfo; @Mock Intent mIntent; - @Mock StatsLogManager mStatsLogManager; - @Mock(answer = Answers.RETURNS_SELF) StatsLogger mStatsLogger; @Before public void setUp() { MockitoAnnotations.initMocks(this); - mSandboxContext.initDaggerComponent( - DaggerSystemShortcutTest_TestComponent.builder().bindUserCache(mUserCache) - ); - mTestContext = new TestSandboxModelContextWrapper(mSandboxContext) { - @Override - public StatsLogManager getStatsLogManager() { - return mStatsLogManager; - } - }; + mSandboxContext.putObject(UserCache.INSTANCE, mUserCache); + mSandboxContext.putObject(ApiWrapper.INSTANCE, mApiWrapper); + mTestContext = new TestSandboxModelContextWrapper(mSandboxContext); + mView = new View(mSandboxContext); + spyOn(mTestContext); spyOn(mSandboxContext); - doReturn(mStatsLogger).when(mStatsLogManager).logger(); + doReturn(mBaseDragLayer).when(mTestContext).getDragLayer(); - mView = new View(mTestContext); mItemInfo = new ItemInfo(); LauncherApps mLauncherApps = mSandboxContext.spyService(LauncherApps.class); @@ -139,12 +114,13 @@ public class SystemShortcutTest { when(mLauncherActivityInfo.getApplicationInfo()).thenReturn(mApplicationInfo); when(mUserCache.getUserInfo(any())).thenReturn(mUserIconInfo); + when(mBaseDragLayer.getChildCount()).thenReturn(0); mPrivateProfileManager = mTestContext.getAppsView().getPrivateProfileManager(); spyOn(mPrivateProfileManager); when(mPrivateProfileManager.getProfileUser()).thenReturn(PRIVATE_HANDLE); - mWidgetPickerDataProvider = mTestContext.getWidgetPickerDataProvider(); - spyOn(mWidgetPickerDataProvider); + mPopupDataProvider = mTestContext.getPopupDataProvider(); + spyOn(mPopupDataProvider); } @After @@ -165,7 +141,7 @@ public class SystemShortcutTest { mAppInfo = new AppInfo(); mAppInfo.componentName = new ComponentName(mTestContext, getClass()); assertNotNull(mAppInfo.getTargetComponent()); - doReturn(new WidgetPickerData()).when(mWidgetPickerDataProvider).get(); + doReturn(new ArrayList<>()).when(mPopupDataProvider).getWidgetsForPackageUser(any()); spyOn(mAppInfo); SystemShortcut systemShortcut = SystemShortcut.WIDGETS .getShortcut(mTestContext, mAppInfo, mView); @@ -192,7 +168,6 @@ public class SystemShortcutTest { } @Test - @DisableFlags(FLAG_ENABLE_DISMISS_PREDICTION_UNDO) public void testDontSuggestAppForPredictedItem() { mAppInfo = new AppInfo(); mAppInfo.componentName = new ComponentName(mTestContext, getClass()); @@ -201,36 +176,7 @@ public class SystemShortcutTest { SystemShortcut systemShortcut = SystemShortcut.DONT_SUGGEST_APP .getShortcut(mTestContext, mAppInfo, mView); assertNotNull(systemShortcut); - - TestUtil.runOnExecutorSync(MAIN_EXECUTOR, () -> systemShortcut.onClick(mView)); - InstrumentationRegistry.getInstrumentation().waitForIdleSync(); - - verify(mStatsLogger).log(eq(LAUNCHER_SYSTEM_SHORTCUT_DONT_SUGGEST_APP_TAP)); - assertFalse(AbstractFloatingView.hasOpenView(mTestContext, TYPE_SNACKBAR)); - } - - @Test - @EnableFlags(FLAG_ENABLE_DISMISS_PREDICTION_UNDO) - public void testDontSuggestAppForPredictedItemWithUndo() { - mAppInfo = new AppInfo(); - mAppInfo.componentName = new ComponentName(mTestContext, getClass()); - mAppInfo.container = CONTAINER_HOTSEAT_PREDICTION; - assertTrue(mAppInfo.isPredictedItem()); - SystemShortcut systemShortcut = SystemShortcut.DONT_SUGGEST_APP - .getShortcut(mTestContext, mAppInfo, mView); - assertNotNull(systemShortcut); - - TestUtil.runOnExecutorSync(MAIN_EXECUTOR, () -> systemShortcut.onClick(mView)); - InstrumentationRegistry.getInstrumentation().waitForIdleSync(); - verify(mStatsLogger).log(eq(LAUNCHER_SYSTEM_SHORTCUT_DONT_SUGGEST_APP_TAP)); - - // Undo bar shown - Snackbar snackbar = AbstractFloatingView.getOpenView(mTestContext, TYPE_SNACKBAR); - assertNotNull(snackbar); - reset(mStatsLogger); - TestUtil.runOnExecutorSync(MAIN_EXECUTOR, snackbar.findViewById( - R.id.action)::performClick); - verify(mStatsLogger).log(eq(LAUNCHER_DISMISS_PREDICTION_UNDO)); + systemShortcut.onClick(mView); } @Test @@ -410,15 +356,4 @@ public class SystemShortcutTest { systemShortcut.onClick(mView); verify(mSandboxContext).startActivity(any()); } - - @LauncherAppSingleton - @Component(modules = { AllModulesForTest.class }) - interface TestComponent extends LauncherAppComponent { - @Component.Builder - interface Builder extends LauncherAppComponent.Builder { - @BindsInstance - SystemShortcutTest.TestComponent.Builder bindUserCache(UserCache userCache); - @Override LauncherAppComponent build(); - } - } } diff --git a/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java b/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java new file mode 100644 index 0000000000..b3675a6e0c --- /dev/null +++ b/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java @@ -0,0 +1,417 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.provider; + +import static android.os.Process.myUserHandle; + +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + +import static com.android.launcher3.LauncherPrefs.APP_WIDGET_IDS; +import static com.android.launcher3.LauncherPrefs.OLD_APP_WIDGET_IDS; +import static com.android.launcher3.LauncherPrefs.RESTORE_DEVICE; +import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP; +import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; +import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME; +import static com.android.launcher3.widget.LauncherWidgetHolder.APPWIDGET_HOST_ID; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import android.app.backup.BackupManager; +import android.appwidget.AppWidgetHost; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.os.UserHandle; +import android.os.UserManager; +import android.util.LongSparseArray; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.LauncherPrefs; +import com.android.launcher3.LauncherSettings; +import com.android.launcher3.LauncherSettings.Favorites; +import com.android.launcher3.backuprestore.LauncherRestoreEventLogger; +import com.android.launcher3.model.ModelDbController; +import com.android.launcher3.util.IntArray; +import com.android.launcher3.util.LauncherModelHelper; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; + +import java.util.Arrays; +import java.util.stream.IntStream; + +/** + * Tests for {@link RestoreDbTask} + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class RestoreDbTaskTest { + + private static final int PER_USER_RANGE = 200000; + + private final UserHandle mWorkUser = UserHandle.getUserHandleForUid(PER_USER_RANGE); + + private LauncherModelHelper mModelHelper; + private Context mContext; + private RestoreDbTask mTask; + private ModelDbController mMockController; + private SQLiteDatabase mMockDb; + private Cursor mMockCursor; + private LauncherPrefs mPrefs; + private LauncherRestoreEventLogger mMockRestoreEventLogger; + + @Before + public void setup() { + mModelHelper = new LauncherModelHelper(); + mContext = mModelHelper.sandboxContext; + mTask = new RestoreDbTask(); + mMockController = Mockito.mock(ModelDbController.class); + mMockDb = mock(SQLiteDatabase.class); + mMockCursor = mock(Cursor.class); + mPrefs = new LauncherPrefs(mContext); + mMockRestoreEventLogger = mock(LauncherRestoreEventLogger.class); + } + + @After + public void teardown() { + mModelHelper.destroy(); + LauncherPrefs.get(mContext).removeSync(RESTORE_DEVICE); + } + + @Test + public void testGetProfileId() throws Exception { + SQLiteDatabase db = new MyModelDbController(23).getDb(); + assertEquals(23, new RestoreDbTask().getDefaultProfileId(db)); + } + + @Test + public void testMigrateProfileId() throws Exception { + SQLiteDatabase db = new MyModelDbController(42).getDb(); + // Add some mock data + for (int i = 0; i < 5; i++) { + ContentValues values = new ContentValues(); + values.put(Favorites._ID, i); + values.put(Favorites.TITLE, "item " + i); + db.insert(Favorites.TABLE_NAME, null, values); + } + // Verify item add + assertEquals(5, getCount(db, "select * from favorites where profileId = 42")); + + new RestoreDbTask().migrateProfileId(db, 42, 33); + + // verify data migrated + assertEquals(0, getCount(db, "select * from favorites where profileId = 42")); + assertEquals(5, getCount(db, "select * from favorites where profileId = 33")); + } + + @Test + public void testChangeDefaultColumn() throws Exception { + SQLiteDatabase db = new MyModelDbController(42).getDb(); + // Add some mock data + for (int i = 0; i < 5; i++) { + ContentValues values = new ContentValues(); + values.put(Favorites._ID, i); + values.put(Favorites.TITLE, "item " + i); + db.insert(Favorites.TABLE_NAME, null, values); + } + // Verify default column is 42 + assertEquals(5, getCount(db, "select * from favorites where profileId = 42")); + + new RestoreDbTask().changeDefaultColumn(db, 33); + + // Verify default value changed + ContentValues values = new ContentValues(); + values.put(Favorites._ID, 100); + values.put(Favorites.TITLE, "item 100"); + db.insert(Favorites.TABLE_NAME, null, values); + assertEquals(1, getCount(db, "select * from favorites where profileId = 33")); + } + + @Test + public void testSanitizeDB_bothProfiles() throws Exception { + UserHandle myUser = myUserHandle(); + long myProfileId = mContext.getSystemService(UserManager.class) + .getSerialNumberForUser(myUser); + long myProfileId_old = myProfileId + 1; + long workProfileId = myProfileId + 2; + long workProfileId_old = myProfileId + 3; + + MyModelDbController controller = new MyModelDbController(myProfileId); + SQLiteDatabase db = controller.getDb(); + BackupManager bm = spy(new BackupManager(mContext)); + doReturn(myUserHandle()).when(bm).getUserForAncestralSerialNumber(eq(myProfileId_old)); + doReturn(mWorkUser).when(bm).getUserForAncestralSerialNumber(eq(workProfileId_old)); + controller.users.put(workProfileId, mWorkUser); + + addIconsBulk(controller, 10, 1, myProfileId_old); + addIconsBulk(controller, 6, 2, workProfileId_old); + assertEquals(10, getItemCountForProfile(db, myProfileId_old)); + assertEquals(6, getItemCountForProfile(db, workProfileId_old)); + + mTask.sanitizeDB(mContext, controller, controller.getDb(), bm, mMockRestoreEventLogger); + + // All the data has been migrated to the new user ids + assertEquals(0, getItemCountForProfile(db, myProfileId_old)); + assertEquals(0, getItemCountForProfile(db, workProfileId_old)); + assertEquals(10, getItemCountForProfile(db, myProfileId)); + assertEquals(6, getItemCountForProfile(db, workProfileId)); + } + + @Test + public void testSanitizeDB_workItemsRemoved() throws Exception { + UserHandle myUser = myUserHandle(); + long myProfileId = mContext.getSystemService(UserManager.class) + .getSerialNumberForUser(myUser); + long myProfileId_old = myProfileId + 1; + long workProfileId_old = myProfileId + 3; + + MyModelDbController controller = new MyModelDbController(myProfileId); + SQLiteDatabase db = controller.getDb(); + BackupManager bm = spy(new BackupManager(mContext)); + doReturn(myUserHandle()).when(bm).getUserForAncestralSerialNumber(eq(myProfileId_old)); + // Work profile is not migrated + doReturn(null).when(bm).getUserForAncestralSerialNumber(eq(workProfileId_old)); + + addIconsBulk(controller, 10, 1, myProfileId_old); + addIconsBulk(controller, 6, 2, workProfileId_old); + assertEquals(10, getItemCountForProfile(db, myProfileId_old)); + assertEquals(6, getItemCountForProfile(db, workProfileId_old)); + + mTask.sanitizeDB(mContext, controller, controller.getDb(), bm, mMockRestoreEventLogger); + + // All the data has been migrated to the new user ids + assertEquals(0, getItemCountForProfile(db, myProfileId_old)); + assertEquals(0, getItemCountForProfile(db, workProfileId_old)); + assertEquals(10, getItemCountForProfile(db, myProfileId)); + assertEquals(10, getCount(db, "select * from favorites")); + } + + @Test + public void givenLauncherPrefsHasNoIds_whenRestoreAppWidgetIdsIfExists_thenIdsAreRemoved() { + // When + mTask.restoreAppWidgetIdsIfExists(mContext, mMockController, mMockRestoreEventLogger); + // Then + assertThat(mPrefs.has(OLD_APP_WIDGET_IDS, APP_WIDGET_IDS)).isFalse(); + } + + @Test + public void givenNoPendingRestore_WhenRestoreAppWidgetIds_ThenRemoveNewWidgetIds() { + // Given + AppWidgetHost expectedHost = new AppWidgetHost(mContext, APPWIDGET_HOST_ID); + int[] expectedOldIds = generateOldWidgetIds(expectedHost); + int[] expectedNewIds = generateNewWidgetIds(expectedHost, expectedOldIds); + when(mMockController.getDb()).thenReturn(mMockDb); + mPrefs.remove(RESTORE_DEVICE); + + // When + setRestoredAppWidgetIds(mContext, expectedOldIds, expectedNewIds); + mTask.restoreAppWidgetIdsIfExists(mContext, mMockController, mMockRestoreEventLogger); + + // Then + assertThat(expectedHost.getAppWidgetIds()).isEqualTo(expectedOldIds); + assertThat(mPrefs.has(OLD_APP_WIDGET_IDS, APP_WIDGET_IDS)).isFalse(); + // b/343530737 + verifyNoMoreInteractions(mMockController); + } + + @Test + public void givenRestoreWithNonExistingWidgets_WhenRestoreAppWidgetIds_ThenRemoveNewIds() { + // Given + AppWidgetHost expectedHost = new AppWidgetHost(mContext, APPWIDGET_HOST_ID); + int[] expectedOldIds = generateOldWidgetIds(expectedHost); + int[] expectedNewIds = generateNewWidgetIds(expectedHost, expectedOldIds); + when(mMockController.getDb()).thenReturn(mMockDb); + when(mMockDb.query(any(), any(), any(), any(), any(), any(), any())).thenReturn( + mMockCursor); + when(mMockCursor.moveToFirst()).thenReturn(false); + RestoreDbTask.setPending(mContext); + + // When + setRestoredAppWidgetIds(mContext, expectedOldIds, expectedNewIds); + mTask.restoreAppWidgetIdsIfExists(mContext, mMockController, mMockRestoreEventLogger); + + // Then + assertThat(expectedHost.getAppWidgetIds()).isEqualTo(expectedOldIds); + assertThat(mPrefs.has(OLD_APP_WIDGET_IDS, APP_WIDGET_IDS)).isFalse(); + verify(mMockController, times(expectedOldIds.length)).update(any(), any(), any(), any()); + } + + @Test + public void givenRestore_WhenRestoreAppWidgetIds_ThenAddNewIds() { + // Given + AppWidgetHost expectedHost = new AppWidgetHost(mContext, APPWIDGET_HOST_ID); + int[] expectedOldIds = generateOldWidgetIds(expectedHost); + int[] expectedNewIds = generateNewWidgetIds(expectedHost, expectedOldIds); + int[] allExpectedIds = IntStream.concat( + Arrays.stream(expectedOldIds), + Arrays.stream(expectedNewIds) + ).toArray(); + + when(mMockController.getDb()).thenReturn(mMockDb); + when(mMockDb.query(any(), any(), any(), any(), any(), any(), any())) + .thenReturn(mMockCursor); + when(mMockCursor.moveToFirst()).thenReturn(true); + when(mMockCursor.getColumnNames()).thenReturn(new String[] {}); + when(mMockCursor.isAfterLast()).thenReturn(true); + RestoreDbTask.setPending(mContext); + + // When + setRestoredAppWidgetIds(mContext, expectedOldIds, expectedNewIds); + mTask.restoreAppWidgetIdsIfExists(mContext, mMockController, mMockRestoreEventLogger); + + // Then + assertThat(expectedHost.getAppWidgetIds()).isEqualTo(allExpectedIds); + assertThat(mPrefs.has(OLD_APP_WIDGET_IDS, APP_WIDGET_IDS)).isFalse(); + verify(mMockController, times(expectedOldIds.length)).update(any(), any(), any(), any()); + } + + private void addIconsBulk(MyModelDbController controller, + int count, int screen, long profileId) { + int columns = LauncherAppState.getIDP(mContext).numColumns; + String packageName = getInstrumentation().getContext().getPackageName(); + for (int i = 0; i < count; i++) { + ContentValues values = new ContentValues(); + values.put(LauncherSettings.Favorites._ID, controller.generateNewItemId()); + values.put(LauncherSettings.Favorites.CONTAINER, CONTAINER_DESKTOP); + values.put(LauncherSettings.Favorites.SCREEN, screen); + values.put(LauncherSettings.Favorites.CELLX, i % columns); + values.put(LauncherSettings.Favorites.CELLY, i / columns); + values.put(LauncherSettings.Favorites.SPANX, 1); + values.put(LauncherSettings.Favorites.SPANY, 1); + values.put(LauncherSettings.Favorites.PROFILE_ID, profileId); + values.put(LauncherSettings.Favorites.ITEM_TYPE, ITEM_TYPE_APPLICATION); + values.put(LauncherSettings.Favorites.INTENT, + new Intent(Intent.ACTION_MAIN).setPackage(packageName).toUri(0)); + + controller.insert(TABLE_NAME, values); + } + } + + @Test + public void testRemoveScreenIdGaps_firstScreenEmpty() { + runRemoveScreenIdGapsTest( + new int[]{1, 2, 5, 6, 6, 7, 9, 9}, + new int[]{1, 2, 3, 4, 4, 5, 6, 6}); + } + + @Test + public void testRemoveScreenIdGaps_firstScreenOccupied() { + runRemoveScreenIdGapsTest( + new int[]{0, 2, 5, 6, 6, 7, 9, 9}, + new int[]{0, 1, 2, 3, 3, 4, 5, 5}); + } + + @Test + public void testRemoveScreenIdGaps_noGap() { + runRemoveScreenIdGapsTest( + new int[]{0, 1, 1, 2, 3, 3, 4, 5}, + new int[]{0, 1, 1, 2, 3, 3, 4, 5}); + } + + private void runRemoveScreenIdGapsTest(int[] screenIds, int[] expectedScreenIds) { + SQLiteDatabase db = new MyModelDbController(42).getDb(); + // Add some mock data + for (int i = 0; i < screenIds.length; i++) { + ContentValues values = new ContentValues(); + values.put(Favorites._ID, i); + values.put(Favorites.SCREEN, screenIds[i]); + values.put(Favorites.CONTAINER, CONTAINER_DESKTOP); + db.insert(Favorites.TABLE_NAME, null, values); + } + // Verify items are added + assertEquals(screenIds.length, + getCount(db, "select * from favorites where container = -100")); + + new RestoreDbTask().removeScreenIdGaps(db); + + // verify screenId gaps removed + int[] resultScreenIds = new int[screenIds.length]; + try (Cursor c = db.rawQuery( + "select screen from favorites where container = -100 order by screen", null)) { + int i = 0; + while (c.moveToNext()) { + resultScreenIds[i++] = c.getInt(0); + } + } + + assertArrayEquals(expectedScreenIds, resultScreenIds); + } + + public int getItemCountForProfile(SQLiteDatabase db, long profileId) { + return getCount(db, "select * from favorites where profileId = " + profileId); + } + + private int getCount(SQLiteDatabase db, String sql) { + try (Cursor c = db.rawQuery(sql, null)) { + return c.getCount(); + } + } + + private int[] generateOldWidgetIds(AppWidgetHost host) { + // generate some widget ids in case there are none + host.allocateAppWidgetId(); + host.allocateAppWidgetId(); + return host.getAppWidgetIds(); + } + + private int[] generateNewWidgetIds(AppWidgetHost host, int[] oldWidgetIds) { + // map as many new ids as old ids + return Arrays.stream(oldWidgetIds) + .map(id -> host.allocateAppWidgetId()).toArray(); + } + + private class MyModelDbController extends ModelDbController { + + public final LongSparseArray users = new LongSparseArray<>(); + + MyModelDbController(long profileId) { + super(mContext); + users.put(profileId, myUserHandle()); + } + + @Override + public long getSerialNumberForUser(UserHandle user) { + int index = users.indexOfValue(user); + return index >= 0 ? users.keyAt(index) : -1; + } + } + + private void setRestoredAppWidgetIds(Context context, int[] oldIds, int[] newIds) { + LauncherPrefs.get(context).putSync( + OLD_APP_WIDGET_IDS.to(IntArray.wrap(oldIds).toConcatString()), + APP_WIDGET_IDS.to(IntArray.wrap(newIds).toConcatString())); + } +} diff --git a/tests/src/com/android/launcher3/testcomponent/TouchEventGenerator.java b/tests/src/com/android/launcher3/testcomponent/TouchEventGenerator.java new file mode 100644 index 0000000000..80d6341e70 --- /dev/null +++ b/tests/src/com/android/launcher3/testcomponent/TouchEventGenerator.java @@ -0,0 +1,275 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.testcomponent; + +import android.graphics.Point; +import android.util.Pair; +import android.view.InputDevice; +import android.view.MotionEvent; +import android.view.MotionEvent.PointerCoords; +import android.view.MotionEvent.PointerProperties; + +import java.util.HashMap; +import java.util.Map; + +/** + * Utility class to generate MotionEvent event sequences for testing touch gesture detectors. + */ +public class TouchEventGenerator { + + /** + * Amount of time between two generated events. + */ + private static final long TIME_INCREMENT_MS = 20L; + + /** + * Id of the fake device generating the events. + */ + private static final int DEVICE_ID = 2104; + + /** + * The fingers currently present on the emulated touch screen. + */ + private Map mFingers; + + /** + * Initial event time for the current sequence. + */ + private long mInitialTime; + + /** + * Time of the last generated event. + */ + private long mLastEventTime; + + /** + * Time of the next event. + */ + private long mTime; + + /** + * Receives the generated events. + */ + public interface Listener { + + /** + * Called when an event was generated. + */ + void onTouchEvent(MotionEvent event); + } + private final Listener mListener; + + public TouchEventGenerator(Listener listener) { + mListener = listener; + mFingers = new HashMap(); + } + + /** + * Adds a finger on the touchscreen. + */ + public TouchEventGenerator put(int id, int x, int y, long ms) { + checkFingerExistence(id, false); + boolean isInitialDown = mFingers.isEmpty(); + mFingers.put(id, new Point(x, y)); + int action; + if (isInitialDown) { + action = MotionEvent.ACTION_DOWN; + } else { + action = MotionEvent.ACTION_POINTER_DOWN; + // Set the id of the changed pointer. + action |= id << MotionEvent.ACTION_POINTER_INDEX_SHIFT; + } + generateEvent(action, ms); + return this; + } + + /** + * Adds a finger on the touchscreen after advancing default time interval. + */ + public TouchEventGenerator put(int id, int x, int y) { + return put(id, x, y, TIME_INCREMENT_MS); + } + + /** + * Adjusts the position of a finger for an upcoming move event. + * + * @see #move(long ms) + */ + public TouchEventGenerator position(int id, int x, int y) { + checkFingerExistence(id, true); + mFingers.get(id).set(x, y); + return this; + } + + /** + * Commits the finger position changes of {@link #position(int, int, int)} by generating a move + * event. + * + * @see #position(int, int, int) + */ + public TouchEventGenerator move(long ms) { + generateEvent(MotionEvent.ACTION_MOVE, ms); + return this; + } + + /** + * Commits the finger position changes of {@link #position(int, int, int)} by generating a move + * event after advancing the default time interval. + * + * @see #position(int, int, int) + */ + public TouchEventGenerator move() { + return move(TIME_INCREMENT_MS); + } + + /** + * Moves a single finger on the touchscreen. + */ + public TouchEventGenerator move(int id, int x, int y, long ms) { + return position(id, x, y).move(ms); + } + + /** + * Moves a single finger on the touchscreen after advancing default time interval. + */ + public TouchEventGenerator move(int id, int x, int y) { + return move(id, x, y, TIME_INCREMENT_MS); + } + + /** + * Removes an existing finger from the touchscreen. + */ + public TouchEventGenerator lift(int id, long ms) { + checkFingerExistence(id, true); + boolean isFinalUp = mFingers.size() == 1; + int action; + if (isFinalUp) { + action = MotionEvent.ACTION_UP; + } else { + action = MotionEvent.ACTION_POINTER_UP; + // Set the id of the changed pointer. + action |= id << MotionEvent.ACTION_POINTER_INDEX_SHIFT; + } + generateEvent(action, ms); + mFingers.remove(id); + return this; + } + + /** + * Removes a finger from the touchscreen. + */ + public TouchEventGenerator lift(int id, int x, int y, long ms) { + checkFingerExistence(id, true); + mFingers.get(id).set(x, y); + return lift(id, ms); + } + + /** + * Removes an existing finger from the touchscreen after advancing default time interval. + */ + public TouchEventGenerator lift(int id) { + return lift(id, TIME_INCREMENT_MS); + } + + /** + * Cancels an ongoing sequence. + */ + public TouchEventGenerator cancel(long ms) { + generateEvent(MotionEvent.ACTION_CANCEL, ms); + mFingers.clear(); + return this; + } + + /** + * Cancels an ongoing sequence. + */ + public TouchEventGenerator cancel() { + return cancel(TIME_INCREMENT_MS); + } + + private void checkFingerExistence(int id, boolean shouldExist) { + if (shouldExist != mFingers.containsKey(id)) { + throw new IllegalArgumentException( + shouldExist ? "Finger does not exist" : "Finger already exists"); + } + } + + private void generateEvent(int action, long ms) { + mTime = mLastEventTime + ms; + Pair state = getFingerState(); + MotionEvent event = MotionEvent.obtain( + mInitialTime, + mTime, + action, + state.first.length, + state.first, + state.second, + 0 /* metaState */, + 0 /* buttonState */, + 1.0f /* xPrecision */, + 1.0f /* yPrecision */, + DEVICE_ID, + 0 /* edgeFlags */, + InputDevice.SOURCE_TOUCHSCREEN, + 0 /* flags */); + mListener.onTouchEvent(event); + if (action == MotionEvent.ACTION_UP) { + resetTime(); + } + event.recycle(); + mLastEventTime = mTime; + } + + /** + * Returns the description of the fingers' state expected by MotionEvent. + */ + private Pair getFingerState() { + int nFingers = mFingers.size(); + PointerProperties[] properties = new PointerProperties[nFingers]; + PointerCoords[] coordinates = new PointerCoords[nFingers]; + + int index = 0; + for (Map.Entry entry : mFingers.entrySet()) { + int id = entry.getKey(); + Point location = entry.getValue(); + + PointerProperties property = new PointerProperties(); + property.id = id; + property.toolType = MotionEvent.TOOL_TYPE_FINGER; + properties[index] = property; + + PointerCoords coordinate = new PointerCoords(); + coordinate.x = location.x; + coordinate.y = location.y; + coordinate.pressure = 1.0f; + coordinates[index] = coordinate; + + index++; + } + + return new Pair( + properties, coordinates); + } + + /** + * Resets the time references for a new sequence. + */ + private void resetTime() { + mInitialTime = 0L; + mLastEventTime = -1L; + mTime = 0L; + } +} diff --git a/tests/src/com/android/launcher3/touch/SingleAxisSwipeDetectorTest.java b/tests/src/com/android/launcher3/touch/SingleAxisSwipeDetectorTest.java new file mode 100644 index 0000000000..260f5568e0 --- /dev/null +++ b/tests/src/com/android/launcher3/touch/SingleAxisSwipeDetectorTest.java @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.touch; + +import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_BOTH; +import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_NEGATIVE; +import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_POSITIVE; +import static com.android.launcher3.touch.SingleAxisSwipeDetector.HORIZONTAL; +import static com.android.launcher3.touch.SingleAxisSwipeDetector.VERTICAL; + +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Matchers.anyFloat; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.util.Log; +import android.view.MotionEvent; +import android.view.ViewConfiguration; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.launcher3.testcomponent.TouchEventGenerator; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class SingleAxisSwipeDetectorTest { + + private static final String TAG = SingleAxisSwipeDetectorTest.class.getSimpleName(); + public static void L(String s, Object... parts) { + Log.d(TAG, (parts.length == 0) ? s : String.format(s, parts)); + } + + private TouchEventGenerator mGenerator; + private SingleAxisSwipeDetector mDetector; + private int mTouchSlop; + Context mContext; + + @Mock + private SingleAxisSwipeDetector.Listener mMockListener; + + @Mock + private ViewConfiguration mMockConfig; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mGenerator = new TouchEventGenerator((ev) -> mDetector.onTouchEvent(ev)); + mContext = InstrumentationRegistry.getTargetContext(); + ViewConfiguration orgConfig = ViewConfiguration.get(mContext); + doReturn(orgConfig.getScaledMaximumFlingVelocity()).when(mMockConfig) + .getScaledMaximumFlingVelocity(); + + mDetector = new SingleAxisSwipeDetector(mContext, + mMockConfig, mMockListener, VERTICAL, false); + mDetector.setDetectableScrollConditions(DIRECTION_BOTH, false); + mTouchSlop = orgConfig.getScaledTouchSlop(); + doReturn(mTouchSlop).when(mMockConfig).getScaledTouchSlop(); + + L("mTouchSlop=", mTouchSlop); + } + + @Test + public void testDragStart_verticalPositive() { + mDetector = new SingleAxisSwipeDetector(mContext, + mMockConfig, mMockListener, VERTICAL, false); + mDetector.setDetectableScrollConditions(DIRECTION_POSITIVE, false); + mGenerator.put(0, 100, 100); + mGenerator.move(0, 100, 100 - mTouchSlop); + // TODO: actually calculate the following parameters and do exact value checks. + verify(mMockListener).onDragStart(anyBoolean(), anyFloat()); + } + + @Test + public void testDragStart_verticalNegative() { + mDetector = new SingleAxisSwipeDetector(mContext, + mMockConfig, mMockListener, VERTICAL, false); + mDetector.setDetectableScrollConditions(DIRECTION_NEGATIVE, false); + mGenerator.put(0, 100, 100); + mGenerator.move(0, 100, 100 + mTouchSlop); + // TODO: actually calculate the following parameters and do exact value checks. + verify(mMockListener).onDragStart(anyBoolean(), anyFloat()); + } + + @Test + public void testDragStart_failed() { + mGenerator.put(0, 100, 100); + mGenerator.move(0, 100 + mTouchSlop, 100); + // TODO: actually calculate the following parameters and do exact value checks. + verify(mMockListener, never()).onDragStart(anyBoolean(), anyFloat()); + } + + @Test + public void testDragStart_horizontalPositive() { + mDetector = new SingleAxisSwipeDetector(mContext, + mMockConfig, mMockListener, HORIZONTAL, false); + mDetector.setDetectableScrollConditions(DIRECTION_POSITIVE, false); + + mGenerator.put(0, 100, 100); + mGenerator.move(0, 100 + mTouchSlop, 100); + // TODO: actually calculate the following parameters and do exact value checks. + verify(mMockListener).onDragStart(anyBoolean(), anyFloat()); + } + + @Test + public void testDragStart_horizontalNegative() { + mDetector = new SingleAxisSwipeDetector(mContext, + mMockConfig, mMockListener, HORIZONTAL, false); + mDetector.setDetectableScrollConditions(DIRECTION_NEGATIVE, false); + + mGenerator.put(0, 100, 100); + mGenerator.move(0, 100 - mTouchSlop, 100); + // TODO: actually calculate the following parameters and do exact value checks. + verify(mMockListener).onDragStart(anyBoolean(), anyFloat()); + } + + @Test + public void testDragStart_horizontalRtlPositive() { + mDetector = new SingleAxisSwipeDetector(mContext, + mMockConfig, mMockListener, HORIZONTAL, true); + mDetector.setDetectableScrollConditions(DIRECTION_POSITIVE, false); + + mGenerator.put(0, 100, 100); + mGenerator.move(0, 100 - mTouchSlop, 100); + // TODO: actually calculate the following parameters and do exact value checks. + verify(mMockListener).onDragStart(anyBoolean(), anyFloat()); + } + + @Test + public void testDragStart_horizontalRtlNegative() { + mDetector = new SingleAxisSwipeDetector(mContext, + mMockConfig, mMockListener, HORIZONTAL, true); + mDetector.setDetectableScrollConditions(DIRECTION_NEGATIVE, false); + + mGenerator.put(0, 100, 100); + mGenerator.move(0, 100 + mTouchSlop, 100); + // TODO: actually calculate the following parameters and do exact value checks. + verify(mMockListener).onDragStart(anyBoolean(), anyFloat()); + } + + @Test + public void testDrag() { + mGenerator.put(0, 100, 100); + mGenerator.move(0, 100, 100 + mTouchSlop); + // TODO: actually calculate the following parameters and do exact value checks. + verify(mMockListener).onDrag(anyFloat(), anyFloat(), any(MotionEvent.class)); + } + + @Test + public void testDragEnd() { + mGenerator.put(0, 100, 100); + mGenerator.move(0, 100, 100 + mTouchSlop); + mGenerator.move(0, 100, 100 + mTouchSlop * 2); + mGenerator.lift(0); + // TODO: actually calculate the following parameters and do exact value checks. + verify(mMockListener).onDragEnd(anyFloat()); + } + + @Test + public void testInterleavedSetState() { + doAnswer(invocationOnMock -> { + // Sets state to IDLE. (Normally onDragEnd() will have state SETTLING.) + mDetector.finishedScrolling(); + return null; + }).when(mMockListener).onDragEnd(anyFloat()); + + mGenerator.put(0, 100, 100); + mGenerator.move(0, 100, 100 + mTouchSlop); + mGenerator.move(0, 100, 100 + mTouchSlop * 2); + mGenerator.lift(0); + verify(mMockListener).onDragEnd(anyFloat()); + assertTrue("SwipeDetector should be IDLE but was " + mDetector.mState, + mDetector.isIdleState()); + } +} diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java index 8e4db5c6cf..a6f4441e2a 100644 --- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java +++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java @@ -15,41 +15,77 @@ */ package com.android.launcher3.ui; +import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT; + import static androidx.test.InstrumentationRegistry.getInstrumentation; +import static com.android.launcher3.testing.shared.TestProtocol.ICON_MISSING; +import static com.android.launcher3.testing.shared.TestProtocol.WIDGET_CONFIG_NULL_EXTRA_INTENT; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import android.content.BroadcastReceiver; import android.content.ComponentName; +import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ActivityInfo; +import android.content.pm.LauncherActivityInfo; +import android.content.pm.LauncherApps; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.graphics.Point; +import android.os.Debug; import android.os.Process; +import android.os.RemoteException; +import android.os.UserHandle; +import android.os.UserManager; +import android.platform.test.flag.junit.SetFlagsRule; import android.system.OsConstants; import android.util.Log; +import androidx.annotation.NonNull; +import androidx.test.InstrumentationRegistry; import androidx.test.uiautomator.By; import androidx.test.uiautomator.BySelector; +import androidx.test.uiautomator.UiDevice; import androidx.test.uiautomator.Until; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; import com.android.launcher3.Utilities; +import com.android.launcher3.celllayout.FavoriteItemsTransaction; +import com.android.launcher3.tapl.HomeAllApps; +import com.android.launcher3.tapl.HomeAppIcon; import com.android.launcher3.tapl.LauncherInstrumentation; import com.android.launcher3.tapl.TestHelpers; import com.android.launcher3.testcomponent.TestCommandReceiver; import com.android.launcher3.util.LooperExecutor; +import com.android.launcher3.util.SimpleBroadcastReceiver; import com.android.launcher3.util.TestUtil; import com.android.launcher3.util.Wait; +import com.android.launcher3.util.rule.ExtendedLongPressTimeoutRule; import com.android.launcher3.util.rule.FailureWatcher; +import com.android.launcher3.util.rule.SamplerRule; +import com.android.launcher3.util.rule.ScreenRecordRule; import com.android.launcher3.util.rule.ShellCommandRule; import com.android.launcher3.util.rule.TestIsolationRule; +import com.android.launcher3.util.rule.TestStabilityRule; import com.android.launcher3.util.rule.ViewCaptureRule; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; import org.junit.rules.RuleChain; import org.junit.rules.TestRule; +import java.io.IOException; import java.util.Objects; import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.function.Consumer; @@ -59,51 +95,163 @@ import java.util.function.Supplier; /** * Base class for all instrumentation tests providing various utility methods. */ -public abstract class AbstractLauncherUiTest - extends BaseLauncherTaplTest { +public abstract class AbstractLauncherUiTest { + public static final long DEFAULT_ACTIVITY_TIMEOUT = TimeUnit.SECONDS.toMillis(10); + public static final long DEFAULT_BROADCAST_TIMEOUT_SECS = 10; + + public static final long DEFAULT_UI_TIMEOUT = TestUtil.DEFAULT_UI_TIMEOUT; private static final String TAG = "AbstractLauncherUiTest"; + private static boolean sDumpWasGenerated = false; + private static boolean sActivityLeakReported = false; + private static boolean sSeenKeyguard = false; + private static boolean sFirstTimeWaitingForWizard = true; + + private static final String SYSTEMUI_PACKAGE = "com.android.systemui"; + protected LooperExecutor mMainThreadExecutor = MAIN_EXECUTOR; + protected final UiDevice mDevice = getUiDevice(); + protected final LauncherInstrumentation mLauncher = createLauncherInstrumentation(); + + @NonNull + public static LauncherInstrumentation createLauncherInstrumentation() { + waitForSetupWizardDismissal(); // precondition for creating LauncherInstrumentation + return new LauncherInstrumentation(true); + } + + protected Context mTargetContext; + protected String mTargetPackage; + private int mLauncherPid; + + /** Detects activity leaks and throws an exception if a leak is found. */ + public static void checkDetectedLeaks(LauncherInstrumentation launcher) { + checkDetectedLeaks(launcher, false); + } + + /** Detects activity leaks and throws an exception if a leak is found. */ + public static void checkDetectedLeaks(LauncherInstrumentation launcher, + boolean requireOneActiveActivityUnused) { + if (TestStabilityRule.isPresubmit()) return; // b/313501215 + + final boolean requireOneActiveActivity = + false; // workaround for leaks when there is an unexpected Recents activity + + if (sActivityLeakReported) return; + + // Check whether activity leak detector has found leaked activities. + Wait.atMost(() -> getActivityLeakErrorMessage(launcher, requireOneActiveActivity), + () -> { + launcher.forceGc(); + return MAIN_EXECUTOR.submit( + () -> launcher.noLeakedActivities(requireOneActiveActivity)).get(); + }, DEFAULT_UI_TIMEOUT, launcher); + } + + public static String getAppPackageName() { + return getInstrumentation().getContext().getPackageName(); + } + + private static String getActivityLeakErrorMessage(LauncherInstrumentation launcher, + boolean requireOneActiveActivity) { + sActivityLeakReported = true; + return "Activity leak detector has found leaked activities, requirining 1 activity: " + + requireOneActiveActivity + "; " + + dumpHprofData(launcher, false, requireOneActiveActivity) + "."; + } + + private static String dumpHprofData(LauncherInstrumentation launcher, boolean intentionalLeak, + boolean requireOneActiveActivity) { + if (intentionalLeak) return "intentional leak; not generating dump"; + + String result; + if (sDumpWasGenerated) { + result = "dump has already been generated by another test"; + } else { + try { + final String fileName = + getInstrumentation().getTargetContext().getFilesDir().getPath() + + "/ActivityLeakHeapDump.hprof"; + if (TestHelpers.isInLauncherProcess()) { + Debug.dumpHprofData(fileName); + } else { + final UiDevice device = getUiDevice(); + device.executeShellCommand( + "am dumpheap " + device.getLauncherPackageName() + " " + fileName); + } + Log.d(TAG, "Saved leak dump, the leak is still present: " + + !launcher.noLeakedActivities(requireOneActiveActivity)); + sDumpWasGenerated = true; + result = "saved memory dump as an artifact"; + } catch (Throwable e) { + Log.e(TAG, "dumpHprofData failed", e); + result = "failed to save memory dump"; + } + } + return result + ". Full list of activities: " + launcher.getRootedActivitiesList(); + } protected AbstractLauncherUiTest() { + mLauncher.enableCheckEventsForSuccessfulGestures(); + mLauncher.setAnomalyChecker(AbstractLauncherUiTest::verifyKeyguardInvisible); + try { + mDevice.setOrientationNatural(); + } catch (RemoteException e) { + throw new RuntimeException(e); + } if (TestHelpers.isInLauncherProcess()) { Utilities.enableRunningInTestHarnessForTests(); mLauncher.setSystemHealthSupplier(startTime -> TestCommandReceiver.callCommand( TestCommandReceiver.GET_SYSTEM_HEALTH_MESSAGE, startTime.toString()) .getString("result")); } + mLauncher.enableDebugTracing(); + // Avoid double-reporting of Launcher crashes. + mLauncher.setOnLauncherCrashed(() -> mLauncherPid = 0); } - /** - * @deprecated call {@link #performInitialization} instead - */ - @Deprecated + @Rule + public ShellCommandRule mDisableHeadsUpNotification = + ShellCommandRule.disableHeadsUpNotification(); + + @Rule + public ScreenRecordRule mScreenRecordRule = new ScreenRecordRule(); + + @Rule + public SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT); + + @Rule + public ExtendedLongPressTimeoutRule mLongPressTimeoutRule = new ExtendedLongPressTimeoutRule(); + public static void initialize(AbstractLauncherUiTest test) throws Exception { - test.performInitialization(); - } - - @Override - protected void performInitialization() { - reinitializeLauncherData(); - mDevice.pressHome(); - // Check that we switched to home. - mLauncher.getWorkspace(); - - waitForLauncherCondition("Launcher didn't start", Objects::nonNull); - waitForState("Launcher internal state didn't switch to Home", + test.reinitializeLauncherData(); + test.mDevice.pressHome(); + test.waitForLauncherCondition("Launcher didn't start", Objects::nonNull); + test.waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL); - waitForResumed("Launcher internal state is still Background"); - - checkDetectedLeaks(mLauncher, true); + test.waitForResumed("Launcher internal state is still Background"); + // Check that we switched to home. + test.mLauncher.getWorkspace(); + AbstractLauncherUiTest.checkDetectedLeaks(test.mLauncher, true); + } + + protected void clearPackageData(String pkg) throws IOException, InterruptedException { + final CountDownLatch count = new CountDownLatch(2); + final SimpleBroadcastReceiver broadcastReceiver = + new SimpleBroadcastReceiver(i -> count.countDown()); + broadcastReceiver.registerPkgActions(mTargetContext, pkg, + Intent.ACTION_PACKAGE_RESTARTED, Intent.ACTION_PACKAGE_DATA_CLEARED); + + mDevice.executeShellCommand("pm clear " + pkg); + assertTrue(pkg + " didn't restart", count.await(10, TimeUnit.SECONDS)); + mTargetContext.unregisterReceiver(broadcastReceiver); } - @Override protected TestRule getRulesInsideActivityMonitor() { final ViewCaptureRule viewCaptureRule = new ViewCaptureRule( - Launcher.ACTIVITY_TRACKER::getCreatedContext); + Launcher.ACTIVITY_TRACKER::getCreatedActivity); final RuleChain inner = RuleChain - .outerRule(new PortraitLandscapeRunner<>(this)) + .outerRule(new PortraitLandscapeRunner(this)) .around(new FailureWatcher(mLauncher, viewCaptureRule::getViewCaptureData)) // .around(viewCaptureRule) // b/315482167 .around(new TestIsolationRule(mLauncher, true)); @@ -113,12 +261,162 @@ public abstract class AbstractLauncherUiTest : inner; } + @Rule + public TestRule mOrderSensitiveRules = RuleChain + .outerRule(new SamplerRule()) + .around(new TestStabilityRule()) + .around(getRulesInsideActivityMonitor()); + + public UiDevice getDevice() { + return mDevice; + } + + @Before + public void setUp() throws Exception { + mLauncher.onTestStart(); + + final String launcherPackageName = mDevice.getLauncherPackageName(); + try { + final Context context = InstrumentationRegistry.getContext(); + final PackageManager pm = context.getPackageManager(); + final PackageInfo launcherPackage = pm.getPackageInfo(launcherPackageName, 0); + + if (!launcherPackage.versionName.equals("BuildFromAndroidStudio")) { + Assert.assertEquals("Launcher version doesn't match tests version", + pm.getPackageInfo(context.getPackageName(), 0).getLongVersionCode(), + launcherPackage.getLongVersionCode()); + } + } catch (PackageManager.NameNotFoundException e) { + throw new RuntimeException(e); + } + + mLauncherPid = 0; + + mTargetContext = InstrumentationRegistry.getTargetContext(); + mTargetPackage = mTargetContext.getPackageName(); + mLauncherPid = mLauncher.getPid(); + + UserManager userManager = mTargetContext.getSystemService(UserManager.class); + if (userManager != null) { + for (UserHandle userHandle : userManager.getUserProfiles()) { + if (!userHandle.isSystem()) { + mDevice.executeShellCommand( + "pm remove-user --wait " + userHandle.getIdentifier()); + } + } + } + + onTestStart(); + + initialize(this); + } + + /** Method that should be called when a test starts. */ + public static void onTestStart() { + waitForSetupWizardDismissal(); + + if (TestStabilityRule.isPresubmit()) { + aggressivelyUnlockSysUi(); + } else { + verifyKeyguardInvisible(); + } + } + + private static boolean hasSystemUiObject(String resId) { + return getUiDevice().hasObject( + By.res(SYSTEMUI_PACKAGE, resId)); + } + + @NonNull + private static UiDevice getUiDevice() { + return UiDevice.getInstance(getInstrumentation()); + } + + private static void aggressivelyUnlockSysUi() { + final UiDevice device = getUiDevice(); + for (int i = 0; i < 10 && hasSystemUiObject("keyguard_status_view"); ++i) { + Log.d(TAG, "Before attempting to unlock the phone"); + try { + device.executeShellCommand("input keyevent 82"); + } catch (IOException e) { + throw new RuntimeException(e); + } + device.waitForIdle(); + } + Assert.assertTrue("Keyguard still visible", + TestHelpers.wait( + Until.gone(By.res(SYSTEMUI_PACKAGE, "keyguard_status_view")), 60000)); + Log.d(TAG, "Keyguard is not visible"); + } + + /** Waits for setup wizard to go away. */ + private static void waitForSetupWizardDismissal() { + if (!TestStabilityRule.isPresubmit()) return; + + if (sFirstTimeWaitingForWizard) { + try { + getUiDevice().executeShellCommand( + "am force-stop com.google.android.setupwizard"); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + final boolean wizardDismissed = TestHelpers.wait( + Until.gone(By.pkg("com.google.android.setupwizard").depth(0)), + sFirstTimeWaitingForWizard ? 120000 : 0); + sFirstTimeWaitingForWizard = false; + Assert.assertTrue("Setup wizard is still visible", wizardDismissed); + } + + /** Asserts that keyguard is not visible */ + public static void verifyKeyguardInvisible() { + final boolean keyguardAlreadyVisible = sSeenKeyguard; + + sSeenKeyguard = sSeenKeyguard + || !TestHelpers.wait( + Until.gone(By.res(SYSTEMUI_PACKAGE, "keyguard_status_view")), 60000); + + Assert.assertFalse( + "Keyguard is visible, which is likely caused by a crash in SysUI, seeing keyguard" + + " for the first time = " + + !keyguardAlreadyVisible, + sSeenKeyguard); + } + + @After + public void verifyLauncherState() { + try { + // Limits UI tests affecting tests running after them. + mLauncher.waitForLauncherInitialized(); + if (mLauncherPid != 0) { + assertEquals("Launcher crashed, pid mismatch:", + mLauncherPid, mLauncher.getPid().intValue()); + } + } finally { + mLauncher.onTestFinish(); + } + } + + protected void reinitializeLauncherData() { + reinitializeLauncherData(false); + } + + protected void reinitializeLauncherData(boolean clearWorkspace) { + if (clearWorkspace) { + mLauncher.clearLauncherData(); + } else { + mLauncher.reinitializeLauncherData(); + } + mLauncher.waitForLauncherInitialized(); + } + /** * Runs the callback on the UI thread and returns the result. */ protected T getOnUiThread(final Callable callback) { try { - return mMainThreadExecutor.submit(callback).get(TestUtil.DEFAULT_UI_TIMEOUT, + return mMainThreadExecutor.submit(callback).get(DEFAULT_UI_TIMEOUT, TimeUnit.MILLISECONDS); } catch (TimeoutException e) { Log.e(TAG, "Timeout in getOnUiThread, sending SIGABRT", e); @@ -131,7 +429,7 @@ public abstract class AbstractLauncherUiTest protected T getFromLauncher(Function f) { if (!TestHelpers.isInLauncherProcess()) return null; - return getOnUiThread(() -> f.apply(Launcher.ACTIVITY_TRACKER.getCreatedContext())); + return getOnUiThread(() -> f.apply(Launcher.ACTIVITY_TRACKER.getCreatedActivity())); } protected void executeOnLauncher(Consumer f) { @@ -173,7 +471,13 @@ public abstract class AbstractLauncherUiTest // flakiness. protected void waitForLauncherCondition(String message, Function condition) { - waitForLauncherCondition(message, condition, TestUtil.DEFAULT_UI_TIMEOUT); + waitForLauncherCondition(message, condition, DEFAULT_ACTIVITY_TIMEOUT); + } + + // Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide + // flakiness. + protected O getOnceNotNull(String message, Function f) { + return getOnceNotNull(message, f, DEFAULT_ACTIVITY_TIMEOUT); } // Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide @@ -182,12 +486,12 @@ public abstract class AbstractLauncherUiTest String message, Function condition, long timeout) { verifyKeyguardInvisible(); if (!TestHelpers.isInLauncherProcess()) return; - Wait.atMost(message, () -> getFromLauncher(condition), mLauncher, timeout); + Wait.atMost(message, () -> getFromLauncher(condition), timeout, mLauncher); } // Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide // flakiness. - protected T getOnceNotNull(String message, Function f) { + protected T getOnceNotNull(String message, Function f, long timeout) { if (!TestHelpers.isInLauncherProcess()) return null; final Object[] output = new Object[1]; @@ -195,7 +499,7 @@ public abstract class AbstractLauncherUiTest final Object fromLauncher = getFromLauncher(f); output[0] = fromLauncher; return fromLauncher != null; - }, mLauncher); + }, timeout, mLauncher); return (T) output[0]; } @@ -209,7 +513,54 @@ public abstract class AbstractLauncherUiTest Wait.atMost(message, () -> { testThreadAction.run(); return getFromLauncher(condition); - }, mLauncher, timeout); + }, timeout, mLauncher); + } + + protected LauncherActivityInfo getSettingsApp() { + return mTargetContext.getSystemService(LauncherApps.class) + .getActivityList("com.android.settings", Process.myUserHandle()).get(0); + } + + /** + * Broadcast receiver which blocks until the result is received. + */ + public class BlockingBroadcastReceiver extends BroadcastReceiver { + + private final CountDownLatch latch = new CountDownLatch(1); + private Intent mIntent; + + public BlockingBroadcastReceiver(String action) { + mTargetContext.registerReceiver(this, new IntentFilter(action), + Context.RECEIVER_EXPORTED/*UNAUDITED*/); + } + + @Override + public void onReceive(Context context, Intent intent) { + Log.d(WIDGET_CONFIG_NULL_EXTRA_INTENT, intent == null + ? "AbstractLauncherUiTest.onReceive(): inputted intent NULL" + : "AbstractLauncherUiTest.onReceive(): inputted intent NOT NULL"); + mIntent = intent; + latch.countDown(); + Log.d(WIDGET_CONFIG_NULL_EXTRA_INTENT, + "AbstractLauncherUiTest.onReceive() Countdown Latch started"); + } + + public Intent blockingGetIntent() throws InterruptedException { + Log.d(WIDGET_CONFIG_NULL_EXTRA_INTENT, + "AbstractLauncherUiTest.blockingGetIntent()"); + assertTrue("Timed Out", latch.await(DEFAULT_BROADCAST_TIMEOUT_SECS, TimeUnit.SECONDS)); + mTargetContext.unregisterReceiver(this); + Log.d(WIDGET_CONFIG_NULL_EXTRA_INTENT, mIntent == null + ? "AbstractLauncherUiTest.onReceive(): mIntent NULL" + : "AbstractLauncherUiTest.onReceive(): mIntent NOT NULL"); + return mIntent; + } + + public Intent blockingGetExtraIntent() throws InterruptedException { + Intent intent = blockingGetIntent(); + return intent == null ? null : (Intent) intent.getParcelableExtra( + Intent.EXTRA_INTENT); + } } public static void startAppFast(String packageName) { @@ -265,13 +616,20 @@ public abstract class AbstractLauncherUiTest } getInstrumentation().getTargetContext().startActivity(intent); assertTrue("App didn't start: " + selector, - TestHelpers.wait(Until.hasObject(selector), TestUtil.DEFAULT_UI_TIMEOUT)); + TestHelpers.wait(Until.hasObject(selector), DEFAULT_UI_TIMEOUT)); // Wait for the Launcher to stop. final LauncherInstrumentation launcherInstrumentation = new LauncherInstrumentation(); Wait.atMost("Launcher activity didn't stop", () -> !launcherInstrumentation.isLauncherActivityStarted(), - launcherInstrumentation); + DEFAULT_ACTIVITY_TIMEOUT, launcherInstrumentation); + } + + public static ActivityInfo resolveSystemAppInfo(String category) { + return getInstrumentation().getContext().getPackageManager().resolveActivity( + new Intent(Intent.ACTION_MAIN).addCategory(category), + PackageManager.MATCH_SYSTEM_ONLY). + activityInfo; } @@ -287,7 +645,8 @@ public abstract class AbstractLauncherUiTest launcher.finish(); } }); - waitForLauncherCondition("Launcher still active", launcher -> launcher == null); + waitForLauncherCondition( + "Launcher still active", launcher -> launcher == null, DEFAULT_UI_TIMEOUT); } protected boolean isInLaunchedApp(LAUNCHER_TYPE launcher) { @@ -306,4 +665,45 @@ public abstract class AbstractLauncherUiTest protected void onLauncherActivityClose(LAUNCHER_TYPE launcher) { } + + protected HomeAppIcon createShortcutInCenterIfNotExist(String name) { + Point dimension = mLauncher.getWorkspace().getIconGridDimensions(); + return createShortcutIfNotExist(name, dimension.x / 2, dimension.y / 2); + } + + protected HomeAppIcon createShortcutIfNotExist(String name, Point cellPosition) { + return createShortcutIfNotExist(name, cellPosition.x, cellPosition.y); + } + + protected HomeAppIcon createShortcutIfNotExist(String name, int cellX, int cellY) { + HomeAppIcon homeAppIcon = mLauncher.getWorkspace().tryGetWorkspaceAppIcon(name); + Log.d(ICON_MISSING, "homeAppIcon: " + homeAppIcon + " name: " + name + + " cell: " + cellX + ", " + cellY); + if (homeAppIcon == null) { + HomeAllApps allApps = mLauncher.getWorkspace().switchToAllApps(); + allApps.freeze(); + try { + allApps.getAppIcon(name).dragToWorkspace(cellX, cellY); + } finally { + allApps.unfreeze(); + } + homeAppIcon = mLauncher.getWorkspace().getWorkspaceAppIcon(name); + } + return homeAppIcon; + } + + protected void commitTransactionAndLoadHome(FavoriteItemsTransaction transaction) { + transaction.commit(); + + // Launch the home activity + UiDevice.getInstance(getInstrumentation()).pressHome(); + mLauncher.waitForLauncherInitialized(); + } + + /** Clears all recent tasks */ + protected void clearAllRecentTasks() { + if (!mLauncher.getRecentTasks().isEmpty()) { + mLauncher.goHome().switchToOverview().dismissAllTasks(); + } + } } diff --git a/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java b/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java index e38cfec104..e5c5c196bf 100644 --- a/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java +++ b/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java @@ -1,13 +1,9 @@ package com.android.launcher3.ui; -import static com.android.launcher3.LauncherPrefs.FIXED_LANDSCAPE_MODE; - import android.util.Log; import android.view.Surface; -import com.android.launcher3.Flags; import com.android.launcher3.Launcher; -import com.android.launcher3.LauncherPrefs; import com.android.launcher3.tapl.TestHelpers; import com.android.launcher3.util.rule.FailureWatcher; @@ -71,12 +67,10 @@ public class PortraitLandscapeRunner implements Log.e(TAG, "Error", e); throw e; } finally { - mTest.mDevice.setOrientationNatural(); mTest.executeOnLauncher(launcher -> { if (launcher != null) { - LauncherPrefs.get(launcher).put(FIXED_LANDSCAPE_MODE, false); launcher.getRotationHelper().forceAllowRotationForTesting(false); } }); @@ -96,20 +90,12 @@ public class PortraitLandscapeRunner implements } private void evaluateInLandscape() throws Throwable { - mTest.executeOnLauncher(launcher -> LauncherPrefs.get(launcher) - .put(FIXED_LANDSCAPE_MODE, shouldHaveFixedLandscape(launcher))); mTest.mDevice.setOrientationLeft(); mTest.mLauncher.setExpectedRotation(Surface.ROTATION_90); AbstractLauncherUiTest.checkDetectedLeaks(mTest.mLauncher, true); base.evaluate(); mTest.getDevice().pressHome(); } - - private boolean shouldHaveFixedLandscape(Launcher launcher) { - return Flags.oneGridSpecs() - && !launcher.getDeviceProfile().isTablet - && !launcher.getDeviceProfile().isMultiDisplay; - } }; } } diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3Test.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3Test.java index 1338e60a49..342eedf2be 100644 --- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3Test.java +++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3Test.java @@ -21,6 +21,7 @@ import static org.junit.Assert.assertNotNull; import androidx.test.filters.LargeTest; import androidx.test.runner.AndroidJUnit4; +import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord; import com.android.launcher3.Launcher; import org.junit.Test; @@ -30,8 +31,9 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public class TaplTestsLauncher3Test extends AbstractLauncherUiTest { + @ScreenRecord // b/322823478 @Test - public void testDevicePressMenu() { + public void testDevicePressMenu() throws Exception { mDevice.pressMenu(); mDevice.waitForIdle(); executeOnLauncher( diff --git a/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java b/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java new file mode 100644 index 0000000000..b2e413de20 --- /dev/null +++ b/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.ui; + +import static com.android.launcher3.LauncherPrefs.WORK_EDU_STEP; +import static com.android.launcher3.LauncherState.ALL_APPS; +import static com.android.launcher3.LauncherState.NORMAL; +import static com.android.launcher3.allapps.AllAppsStore.DEFER_UPDATES_TEST; +import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL; +import static com.android.launcher3.util.TestUtil.installDummyAppForUser; +import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL; +import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeTrue; + +import android.util.Log; +import android.view.View; + +import androidx.recyclerview.widget.RecyclerView.ViewHolder; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.LargeTest; + +import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherPrefs; +import com.android.launcher3.R; +import com.android.launcher3.allapps.ActivityAllAppsContainerView; +import com.android.launcher3.allapps.AllAppsPagedView; +import com.android.launcher3.allapps.WorkEduCard; +import com.android.launcher3.allapps.WorkPausedCard; +import com.android.launcher3.allapps.WorkProfileManager; +import com.android.launcher3.tapl.LauncherInstrumentation; +import com.android.launcher3.util.TestUtil; +import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord; +import com.android.launcher3.util.rule.TestStabilityRule.Stability; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.IOException; +import java.util.Objects; +import java.util.function.Predicate; + +@LargeTest +@RunWith(AndroidJUnit4.class) +public class TaplWorkProfileTest extends AbstractLauncherUiTest { + + private static final int WORK_PAGE = ActivityAllAppsContainerView.AdapterHolder.WORK; + + private int mProfileUserId; + private boolean mWorkProfileSetupSuccessful; + private final String TAG = "WorkProfileTest"; + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + initialize(this); + String output = + mDevice.executeShellCommand( + "pm create-user --profileOf 0 --managed TestProfile"); + updateWorkProfileSetupSuccessful("pm create-user", output); + + String[] tokens = output.split("\\s+"); + mProfileUserId = Integer.parseInt(tokens[tokens.length - 1]); + StringBuilder logStr = new StringBuilder().append("profileId: ").append(mProfileUserId); + for (String str : tokens) { + logStr.append(str).append("\n"); + } + installDummyAppForUser(mProfileUserId); + updateWorkProfileSetupSuccessful("am start-user", output); + + if (!mWorkProfileSetupSuccessful) { + return; // no need to setup launcher since all tests will skip. + } + + mDevice.pressHome(); + waitForLauncherCondition("Launcher didn't start", Objects::nonNull); + waitForStateTransitionToEnd("Launcher internal state didn't switch to Normal", + () -> NORMAL); + waitForResumed("Launcher internal state is still Background"); + mLauncher.getWorkspace().switchToAllApps(); + waitForStateTransitionToEnd("Launcher internal state didn't switch to All Apps", + () -> ALL_APPS); + } + + @After + public void removeWorkProfile() throws Exception { + executeOnLauncherInTearDown(launcher -> { + if (launcher.getAppsView() == null) { + return; + } + launcher.getAppsView().getAppsStore().disableDeferUpdates(DEFER_UPDATES_TEST); + }); + TestUtil.uninstallDummyApp(); + + mLauncher.runToState( + () -> { + try { + mDevice.executeShellCommand("pm remove-user --wait " + mProfileUserId); + } catch (IOException e) { + throw new RuntimeException(e); + } + }, + NORMAL_STATE_ORDINAL, + "executing pm 'remove-user' command"); + } + + private void waitForWorkTabSetup() { + waitForLauncherCondition("Work tab not setup", launcher -> { + if (launcher.getAppsView().getContentView() instanceof AllAppsPagedView) { + launcher.getAppsView().getAppsStore().enableDeferUpdates(DEFER_UPDATES_TEST); + return true; + } + return false; + }, LauncherInstrumentation.WAIT_TIME_MS); + } + + @Test + @com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord // b/325383911 + public void workTabExists() { + assumeTrue(mWorkProfileSetupSuccessful); + waitForWorkTabSetup(); + waitForLauncherCondition("Personal tab is missing", + launcher -> launcher.getAppsView().isPersonalTabVisible(), + LauncherInstrumentation.WAIT_TIME_MS); + waitForLauncherCondition("Work tab is missing", + launcher -> launcher.getAppsView().isWorkTabVisible(), + LauncherInstrumentation.WAIT_TIME_MS); + } + + // Staging; will be promoted to presubmit if stable + @Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) + @ScreenRecord + @Test + public void toggleWorks() { + assumeTrue(mWorkProfileSetupSuccessful); + waitForWorkTabSetup(); + executeOnLauncher(launcher -> { + AllAppsPagedView pagedView = (AllAppsPagedView) launcher.getAppsView().getContentView(); + pagedView.setCurrentPage(WORK_PAGE); + }); + + WorkProfileManager manager = getFromLauncher(l -> l.getAppsView().getWorkManager()); + + + waitForLauncherCondition("work profile initial state check failed", launcher -> + manager.getWorkModeSwitch() != null + && manager.getCurrentState() == WorkProfileManager.STATE_ENABLED + && manager.getWorkModeSwitch().isEnabled(), + LauncherInstrumentation.WAIT_TIME_MS); + + //start work profile toggle OFF test + executeOnLauncher(l -> { + // Ensure updates are not deferred so notification happens when apps pause. + l.getAppsView().getAppsStore().disableDeferUpdates(DEFER_UPDATES_TEST); + l.getAppsView().getWorkManager().getWorkModeSwitch().performClick(); + }); + + waitForLauncherCondition("Work profile toggle OFF failed", launcher -> { + manager.reset(); // pulls current state from system + return manager.getCurrentState() == WorkProfileManager.STATE_DISABLED; + }, LauncherInstrumentation.WAIT_TIME_MS); + + waitForWorkCard("Work paused card not shown", view -> view instanceof WorkPausedCard); + + // start work profile toggle ON test + executeOnLauncher(l -> { + ActivityAllAppsContainerView allApps = l.getAppsView(); + assertEquals("Work tab is not focused", allApps.getCurrentPage(), WORK_PAGE); + View workPausedCard = allApps.getActiveRecyclerView() + .findViewHolderForAdapterPosition(0).itemView; + workPausedCard.findViewById(R.id.enable_work_apps).performClick(); + }); + waitForLauncherCondition("Work profile toggle ON failed", launcher -> { + manager.reset(); // pulls current state from system + return manager.getCurrentState() == WorkProfileManager.STATE_ENABLED; + }, LauncherInstrumentation.WAIT_TIME_MS); + + } + + @ScreenRecord // b/322823478 + @Test + public void testEdu() { + assumeTrue(mWorkProfileSetupSuccessful); + waitForWorkTabSetup(); + executeOnLauncher(l -> { + LauncherPrefs.get(l).putSync(WORK_EDU_STEP.to(0)); + ((AllAppsPagedView) l.getAppsView().getContentView()).setCurrentPage(WORK_PAGE); + l.getAppsView().getWorkManager().reset(); + }); + + waitForWorkCard("Work profile education not shown", view -> view instanceof WorkEduCard); + } + + private void waitForWorkCard(String message, Predicate workCardCheck) { + waitForLauncherCondition(message, l -> { + l.getAppsView().getAppsStore().disableDeferUpdates(DEFER_UPDATES_TEST); + ViewHolder holder = l.getAppsView().getActiveRecyclerView() + .findViewHolderForAdapterPosition(0); + try { + return holder != null && workCardCheck.test(holder.itemView); + } finally { + l.getAppsView().getAppsStore().enableDeferUpdates(DEFER_UPDATES_TEST); + } + }, LauncherInstrumentation.WAIT_TIME_MS); + } + + private void updateWorkProfileSetupSuccessful(String cli, String output) { + Log.d(TAG, "updateWorkProfileSetupSuccessful, cli=" + cli + " " + "output=" + output); + if (output.startsWith("Success")) { + assertTrue(output, output.startsWith("Success")); + mWorkProfileSetupSuccessful = true; + } else { + mWorkProfileSetupSuccessful = false; + } + } +} diff --git a/tests/src/com/android/launcher3/ui/widget/TaplAddConfigWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/TaplAddConfigWidgetTest.java new file mode 100644 index 0000000000..e6e02b49ac --- /dev/null +++ b/tests/src/com/android/launcher3/ui/widget/TaplAddConfigWidgetTest.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.android.launcher3.ui.widget; + +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + +import static com.android.launcher3.util.LauncherBindableItemsContainer.ItemOperator; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNotSame; + +import android.appwidget.AppWidgetManager; +import android.content.Intent; +import android.view.View; + +import androidx.test.filters.LargeTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.launcher3.Launcher; +import com.android.launcher3.celllayout.FavoriteItemsTransaction; +import com.android.launcher3.model.data.ItemInfo; +import com.android.launcher3.model.data.LauncherAppWidgetInfo; +import com.android.launcher3.testcomponent.WidgetConfigActivity; +import com.android.launcher3.ui.AbstractLauncherUiTest; +import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape; +import com.android.launcher3.ui.TestViewHelpers; +import com.android.launcher3.util.Wait; +import com.android.launcher3.util.rule.ShellCommandRule; +import com.android.launcher3.widget.LauncherAppWidgetProviderInfo; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Test to verify widget configuration is properly shown. + */ +@LargeTest +@RunWith(AndroidJUnit4.class) +public class TaplAddConfigWidgetTest extends AbstractLauncherUiTest { + + @Rule + public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind(); + + private LauncherAppWidgetProviderInfo mWidgetInfo; + private AppWidgetManager mAppWidgetManager; + + private int mWidgetId; + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + mWidgetInfo = TestViewHelpers.findWidgetProvider(true /* hasConfigureScreen */); + mAppWidgetManager = AppWidgetManager.getInstance(mTargetContext); + } + + @Test + @PortraitLandscape + public void testWidgetConfig() throws Throwable { + runTest(true); + } + + @Test + @PortraitLandscape + public void testConfigCancelled() throws Throwable { + runTest(false); + } + + + /** + * @param acceptConfig accept the config activity + */ + private void runTest(boolean acceptConfig) throws Throwable { + commitTransactionAndLoadHome(new FavoriteItemsTransaction(mTargetContext)); + + // Drag widget to homescreen + WidgetConfigStartupMonitor monitor = new WidgetConfigStartupMonitor(); + mLauncher.getWorkspace() + .openAllWidgets() + .getWidget(mWidgetInfo.getLabel(mTargetContext.getPackageManager())) + .dragToWorkspace(true, false); + // Widget id for which the config activity was opened + mWidgetId = monitor.getWidgetId(); + + // Verify that the widget id is valid and bound + assertNotNull(mAppWidgetManager.getAppWidgetInfo(mWidgetId)); + + setResultAndWaitForAnimation(acceptConfig); + if (acceptConfig) { + Wait.atMost("", new WidgetSearchCondition(), DEFAULT_ACTIVITY_TIMEOUT, mLauncher); + assertNotNull(mAppWidgetManager.getAppWidgetInfo(mWidgetId)); + } else { + // Verify that the widget id is deleted. + Wait.atMost("", () -> mAppWidgetManager.getAppWidgetInfo(mWidgetId) == null, + DEFAULT_ACTIVITY_TIMEOUT, mLauncher); + } + } + + private static void setResult(boolean success) { + getInstrumentation().getTargetContext().sendBroadcast( + WidgetConfigActivity.getCommandIntent(WidgetConfigActivity.class, + success ? "clickOK" : "clickCancel")); + } + + private void setResultAndWaitForAnimation(boolean success) { + if (mLauncher.isLauncher3()) { + setResult(success); + } else { + mLauncher.executeAndWaitForWallpaperAnimation( + () -> setResult(success), + "setting widget coinfig result"); + } + } + + /** + * Condition for searching widget id + */ + private class WidgetSearchCondition implements Wait.Condition, ItemOperator { + + @Override + public boolean isTrue() throws Throwable { + return mMainThreadExecutor.submit(() -> { + Launcher l = Launcher.ACTIVITY_TRACKER.getCreatedActivity(); + return l != null && l.getWorkspace().getFirstMatch(this) != null; + }).get(); + } + + @Override + public boolean evaluate(ItemInfo info, View view) { + return info instanceof LauncherAppWidgetInfo + && ((LauncherAppWidgetInfo) info).providerName.getClassName().equals( + mWidgetInfo.provider.getClassName()) + && ((LauncherAppWidgetInfo) info).appWidgetId == mWidgetId; + } + } + + /** + * Broadcast receiver for receiving widget config activity status. + */ + private class WidgetConfigStartupMonitor extends BlockingBroadcastReceiver { + + public WidgetConfigStartupMonitor() { + super(WidgetConfigActivity.class.getName()); + } + + public int getWidgetId() throws InterruptedException { + Intent intent = blockingGetExtraIntent(); + assertNotNull("Null EXTRA_INTENT", intent); + assertEquals("Intent action is not ACTION_APPWIDGET_CONFIGURE", + AppWidgetManager.ACTION_APPWIDGET_CONFIGURE, intent.getAction()); + int widgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, + LauncherAppWidgetInfo.NO_ID); + assertNotSame("Widget id is NO_ID", widgetId, LauncherAppWidgetInfo.NO_ID); + return widgetId; + } + } +} diff --git a/tests/src/com/android/launcher3/ui/widget/TaplAddWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/TaplAddWidgetTest.java index 3e3e6433ee..9b184ae664 100644 --- a/tests/src/com/android/launcher3/ui/widget/TaplAddWidgetTest.java +++ b/tests/src/com/android/launcher3/ui/widget/TaplAddWidgetTest.java @@ -15,6 +15,9 @@ */ package com.android.launcher3.ui.widget; +import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL; +import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT; + import static org.junit.Assert.assertNotNull; import android.platform.test.annotations.PlatinumTest; @@ -29,8 +32,8 @@ import com.android.launcher3.tapl.WidgetResizeFrame; import com.android.launcher3.ui.AbstractLauncherUiTest; import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape; import com.android.launcher3.ui.TestViewHelpers; -import com.android.launcher3.util.TestUtil; import com.android.launcher3.util.rule.ShellCommandRule; +import com.android.launcher3.util.rule.TestStabilityRule.Stability; import com.android.launcher3.widget.LauncherAppWidgetProviderInfo; import org.junit.Assume; @@ -51,6 +54,7 @@ public class TaplAddWidgetTest extends AbstractLauncherUiTest { @Test @PortraitLandscape public void testDragIcon() throws Throwable { + mLauncher.enableDebugTracing(); // b/289161193 commitTransactionAndLoadHome(new FavoriteItemsTransaction(mTargetContext)); waitForLauncherCondition("Workspace didn't finish loading", l -> !l.isWorkspaceLoading()); @@ -61,16 +65,17 @@ public class TaplAddWidgetTest extends AbstractLauncherUiTest { WidgetResizeFrame resizeFrame = mLauncher .getWorkspace() .openAllWidgets() - .getWidget(widgetInfo.getLabel()) + .getWidget(widgetInfo.getLabel(mTargetContext.getPackageManager())) .dragWidgetToWorkspace(); assertNotNull("Widget resize frame not shown after widget add", resizeFrame); resizeFrame.dismiss(); final Widget widget = mLauncher.getWorkspace().tryGetWidget(widgetInfo.label, - TestUtil.DEFAULT_UI_TIMEOUT); + DEFAULT_UI_TIMEOUT); assertNotNull("Widget not found on the workspace", widget); widget.launch(getAppPackageName()); + mLauncher.disableDebugTracing(); // b/289161193 } /** @@ -97,6 +102,7 @@ public class TaplAddWidgetTest extends AbstractLauncherUiTest { /** * Test dragging a widget to the workspace and resize it. */ + @Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/316910614 @PlatinumTest(focusArea = "launcher") @Test public void testResizeWidget() throws Throwable { @@ -110,7 +116,7 @@ public class TaplAddWidgetTest extends AbstractLauncherUiTest { WidgetResizeFrame resizeFrame = mLauncher .getWorkspace() .openAllWidgets() - .getWidget(widgetInfo.getLabel()) + .getWidget(widgetInfo.getLabel(mTargetContext.getPackageManager())) .dragWidgetToWorkspace(); assertNotNull("Widget resize frame not shown after widget add", resizeFrame); diff --git a/tests/src/com/android/launcher3/ui/widget/TaplBindWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/TaplBindWidgetTest.java new file mode 100644 index 0000000000..28d1faa5e7 --- /dev/null +++ b/tests/src/com/android/launcher3/ui/widget/TaplBindWidgetTest.java @@ -0,0 +1,319 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.android.launcher3.ui.widget; + +import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME; +import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID; +import static com.android.launcher3.model.data.LauncherAppWidgetInfo.FLAG_ID_NOT_VALID; +import static com.android.launcher3.model.data.LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY; +import static com.android.launcher3.model.data.LauncherAppWidgetInfo.FLAG_RESTORE_STARTED; +import static com.android.launcher3.provider.LauncherDbUtils.itemIdMatch; +import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; +import static com.android.launcher3.util.WidgetUtils.createWidgetInfo; +import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL; +import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import android.appwidget.AppWidgetManager; +import android.content.ComponentName; +import android.content.pm.PackageInstaller; +import android.content.pm.PackageInstaller.SessionParams; +import android.content.pm.PackageManager; +import android.database.Cursor; +import android.os.Bundle; +import android.widget.RemoteViews; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.LargeTest; + +import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.LauncherModel; +import com.android.launcher3.LauncherSettings; +import com.android.launcher3.R; +import com.android.launcher3.celllayout.FavoriteItemsTransaction; +import com.android.launcher3.model.data.LauncherAppWidgetInfo; +import com.android.launcher3.pm.InstallSessionHelper; +import com.android.launcher3.tapl.Widget; +import com.android.launcher3.tapl.Workspace; +import com.android.launcher3.ui.AbstractLauncherUiTest; +import com.android.launcher3.ui.TestViewHelpers; +import com.android.launcher3.util.rule.ShellCommandRule; +import com.android.launcher3.util.rule.TestStabilityRule; +import com.android.launcher3.widget.LauncherAppWidgetProviderInfo; +import com.android.launcher3.widget.WidgetManagerHelper; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.HashSet; +import java.util.Set; +import java.util.function.Consumer; + +/** + * Tests for bind widget flow. + * + * Note running these tests will clear the workspace on the device. + */ +@LargeTest +@RunWith(AndroidJUnit4.class) +public class TaplBindWidgetTest extends AbstractLauncherUiTest { + + @Rule + public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind(); + + // Objects created during test, which should be cleaned up in the end. + private Cursor mCursor; + // App install session id. + private int mSessionId = -1; + + private LauncherModel mModel; + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + mModel = LauncherAppState.getInstance(mTargetContext).getModel(); + } + + @After + public void tearDown() { + if (mCursor != null) { + mCursor.close(); + } + + if (mSessionId > -1) { + mTargetContext.getPackageManager().getPackageInstaller().abandonSession(mSessionId); + } + } + + @Test + public void testBindNormalWidget_withConfig() { + LauncherAppWidgetProviderInfo info = addWidgetToScreen(true, true, i -> { }); + verifyWidgetPresent(info); + } + + @Test + public void testBindNormalWidget_withoutConfig() { + LauncherAppWidgetProviderInfo info = addWidgetToScreen(false, true, i -> { }); + verifyWidgetPresent(info); + } + + @Test + public void testUnboundWidget_removed() { + LauncherAppWidgetProviderInfo info = addWidgetToScreen(false, false, + item -> item.appWidgetId = -33); + + final Workspace workspace = mLauncher.getWorkspace(); + // Item deleted from db + mCursor = queryItem(); + assertEquals(0, mCursor.getCount()); + + // The view does not exist + assertTrue("Widget exists", workspace.tryGetWidget(info.label, 0) == null); + } + + @Test + public void testPendingWidget_autoRestored() { + // A non-restored widget with no config screen gets restored automatically. + // Do not bind the widget + LauncherAppWidgetProviderInfo info = addWidgetToScreen(false, false, + item -> item.restoreStatus = FLAG_ID_NOT_VALID); + verifyWidgetPresent(info); + } + + @Test + @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/310242894 + public void testPendingWidget_withConfigScreen() { + // A non-restored widget with config screen get bound and shows a 'Click to setup' UI. + // Do not bind the widget + LauncherAppWidgetProviderInfo info = addWidgetToScreen(true, false, + item -> item.restoreStatus = FLAG_ID_NOT_VALID); + verifyPendingWidgetPresent(); + + mCursor = queryItem(); + mCursor.moveToNext(); + + // Widget has a valid Id now. + assertEquals(0, mCursor.getInt(mCursor.getColumnIndex(LauncherSettings.Favorites.RESTORED)) + & FLAG_ID_NOT_VALID); + assertNotNull(AppWidgetManager.getInstance(mTargetContext) + .getAppWidgetInfo(mCursor.getInt(mCursor.getColumnIndex( + LauncherSettings.Favorites.APPWIDGET_ID)))); + + // send OPTION_APPWIDGET_RESTORE_COMPLETED + int appWidgetId = mCursor.getInt( + mCursor.getColumnIndex(LauncherSettings.Favorites.APPWIDGET_ID)); + AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mTargetContext); + + Bundle b = new Bundle(); + b.putBoolean(WidgetManagerHelper.WIDGET_OPTION_RESTORE_COMPLETED, true); + RemoteViews remoteViews = new RemoteViews(mTargetPackage, R.layout.appwidget_not_ready); + appWidgetManager.updateAppWidgetOptions(appWidgetId, b); + appWidgetManager.updateAppWidget(appWidgetId, remoteViews); + + // verify changes are reflected + waitForLauncherCondition("App widget options did not update", + l -> appWidgetManager.getAppWidgetOptions(appWidgetId).getBoolean( + WidgetManagerHelper.WIDGET_OPTION_RESTORE_COMPLETED)); + executeOnLauncher(l -> l.getAppWidgetHolder().startListening()); + verifyWidgetPresent(info); + assertNull(mLauncher.getWorkspace().tryGetPendingWidget(100)); + } + + @Test + public void testPendingWidget_notRestored_removed() { + addPendingItemToScreen(getInvalidWidgetInfo(), FLAG_ID_NOT_VALID | FLAG_PROVIDER_NOT_READY); + + assertTrue("Pending widget exists", + mLauncher.getWorkspace().tryGetPendingWidget(0) == null); + // Item deleted from db + mCursor = queryItem(); + assertEquals(0, mCursor.getCount()); + } + + @Test + @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/310242894 + public void testPendingWidget_notRestored_brokenInstall() { + // A widget which is was being installed once, even if its not being + // installed at the moment is not removed. + addPendingItemToScreen(getInvalidWidgetInfo(), + FLAG_ID_NOT_VALID | FLAG_RESTORE_STARTED | FLAG_PROVIDER_NOT_READY); + verifyPendingWidgetPresent(); + + // Verify item still exists in db + mCursor = queryItem(); + assertEquals(1, mCursor.getCount()); + + // Widget still has an invalid id. + mCursor.moveToNext(); + assertEquals(FLAG_ID_NOT_VALID, + mCursor.getInt(mCursor.getColumnIndex(LauncherSettings.Favorites.RESTORED)) + & FLAG_ID_NOT_VALID); + } + + @Test + public void testPendingWidget_notRestored_activeInstall() throws Exception { + // A widget which is being installed is not removed + LauncherAppWidgetInfo item = getInvalidWidgetInfo(); + + // Create an active installer session + SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL); + params.setAppPackageName(item.providerName.getPackageName()); + PackageInstaller installer = mTargetContext.getPackageManager().getPackageInstaller(); + mSessionId = installer.createSession(params); + + addPendingItemToScreen(item, FLAG_ID_NOT_VALID | FLAG_PROVIDER_NOT_READY); + verifyPendingWidgetPresent(); + + // Verify item still exists in db + mCursor = queryItem(); + assertEquals(1, mCursor.getCount()); + + // Widget still has an invalid id. + mCursor.moveToNext(); + assertEquals(FLAG_ID_NOT_VALID, + mCursor.getInt(mCursor.getColumnIndex(LauncherSettings.Favorites.RESTORED)) + & FLAG_ID_NOT_VALID); + } + + private void verifyWidgetPresent(LauncherAppWidgetProviderInfo info) { + final Widget widget = mLauncher.getWorkspace().tryGetWidget(info.label, DEFAULT_UI_TIMEOUT); + assertTrue("Widget is not present", + widget != null); + } + + private void verifyPendingWidgetPresent() { + final Widget widget = mLauncher.getWorkspace().tryGetPendingWidget(DEFAULT_UI_TIMEOUT); + assertTrue("Pending widget is not present", + widget != null); + } + + private void addPendingItemToScreen(LauncherAppWidgetInfo item, int restoreStatus) { + item.restoreStatus = restoreStatus; + item.screenId = FIRST_SCREEN_ID; + commitTransactionAndLoadHome( + new FavoriteItemsTransaction(mTargetContext).addItem(() -> item)); + } + + private LauncherAppWidgetProviderInfo addWidgetToScreen(boolean hasConfigureScreen, + boolean bindWidget, Consumer itemOverride) { + LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(hasConfigureScreen); + commitTransactionAndLoadHome(new FavoriteItemsTransaction(mTargetContext) + .addItem(() -> { + LauncherAppWidgetInfo item = createWidgetInfo(info, mTargetContext, bindWidget); + item.screenId = FIRST_SCREEN_ID; + itemOverride.accept(item); + return item; + })); + return info; + } + + /** + * Returns a LauncherAppWidgetInfo with package name which is not present on the device + */ + private LauncherAppWidgetInfo getInvalidWidgetInfo() { + String invalidPackage = "com.invalidpackage"; + int count = 0; + String pkg = invalidPackage; + + Set activePackage = getOnUiThread(() -> { + Set packages = new HashSet<>(); + InstallSessionHelper.INSTANCE.get(mTargetContext).getActiveSessions() + .keySet().forEach(packageUserKey -> packages.add(packageUserKey.mPackageName)); + return packages; + }); + while (true) { + try { + mTargetContext.getPackageManager().getPackageInfo( + pkg, PackageManager.GET_UNINSTALLED_PACKAGES); + } catch (Exception e) { + if (!activePackage.contains(pkg)) { + break; + } + } + pkg = invalidPackage + count; + count++; + } + LauncherAppWidgetInfo item = new LauncherAppWidgetInfo(10, + new ComponentName(pkg, "com.test.widgetprovider")); + item.spanX = 2; + item.spanY = 2; + item.minSpanX = 2; + item.minSpanY = 2; + item.cellX = 0; + item.cellY = 1; + item.container = LauncherSettings.Favorites.CONTAINER_DESKTOP; + return item; + } + + private Cursor queryItem() { + try { + return MODEL_EXECUTOR.submit(() -> + mModel.getModelDbController().query( + TABLE_NAME, null, itemIdMatch(0), null, null)).get(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/tests/src/com/android/launcher3/ui/widget/TaplRequestPinItemTest.java b/tests/src/com/android/launcher3/ui/widget/TaplRequestPinItemTest.java new file mode 100644 index 0000000000..74047f0d01 --- /dev/null +++ b/tests/src/com/android/launcher3/ui/widget/TaplRequestPinItemTest.java @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.android.launcher3.ui.widget; + +import static android.app.PendingIntent.FLAG_MUTABLE; +import static android.app.PendingIntent.FLAG_ONE_SHOT; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNotSame; + +import android.app.PendingIntent; +import android.appwidget.AppWidgetManager; +import android.content.Intent; +import android.graphics.Color; +import android.view.View; + +import androidx.test.filters.LargeTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherSettings.Favorites; +import com.android.launcher3.celllayout.FavoriteItemsTransaction; +import com.android.launcher3.model.data.ItemInfo; +import com.android.launcher3.model.data.LauncherAppWidgetInfo; +import com.android.launcher3.model.data.WorkspaceItemInfo; +import com.android.launcher3.shortcuts.ShortcutKey; +import com.android.launcher3.tapl.AddToHomeScreenPrompt; +import com.android.launcher3.testcomponent.AppWidgetNoConfig; +import com.android.launcher3.testcomponent.AppWidgetWithConfig; +import com.android.launcher3.testcomponent.RequestPinItemActivity; +import com.android.launcher3.ui.AbstractLauncherUiTest; +import com.android.launcher3.util.LauncherBindableItemsContainer.ItemOperator; +import com.android.launcher3.util.Wait; +import com.android.launcher3.util.Wait.Condition; +import com.android.launcher3.util.rule.ShellCommandRule; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.UUID; + +/** + * Test to verify pin item request flow. + */ +@LargeTest +@RunWith(AndroidJUnit4.class) +public class TaplRequestPinItemTest extends AbstractLauncherUiTest { + + @Rule + public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind(); + + private String mCallbackAction; + private String mShortcutId; + private int mAppWidgetId; + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + mCallbackAction = UUID.randomUUID().toString(); + mShortcutId = UUID.randomUUID().toString(); + } + + @Test + public void testEmpty() throws Throwable { /* needed while the broken tests are being fixed */ } + + @Test + public void testPinWidgetNoConfig() throws Throwable { + runTest("pinWidgetNoConfig", true, (info, view) -> info instanceof LauncherAppWidgetInfo && + ((LauncherAppWidgetInfo) info).appWidgetId == mAppWidgetId && + ((LauncherAppWidgetInfo) info).providerName.getClassName() + .equals(AppWidgetNoConfig.class.getName())); + } + + @Test + public void testPinWidgetNoConfig_customPreview() throws Throwable { + // Command to set custom preview + Intent command = RequestPinItemActivity.getCommandIntent( + RequestPinItemActivity.class, "setRemoteViewColor").putExtra( + RequestPinItemActivity.EXTRA_PARAM + "0", Color.RED); + + runTest("pinWidgetNoConfig", true, (info, view) -> info instanceof LauncherAppWidgetInfo && + ((LauncherAppWidgetInfo) info).appWidgetId == mAppWidgetId && + ((LauncherAppWidgetInfo) info).providerName.getClassName() + .equals(AppWidgetNoConfig.class.getName()), command); + } + + @Test + public void testPinWidgetWithConfig() throws Throwable { + runTest("pinWidgetWithConfig", true, + (info, view) -> info instanceof LauncherAppWidgetInfo && + ((LauncherAppWidgetInfo) info).appWidgetId == mAppWidgetId && + ((LauncherAppWidgetInfo) info).providerName.getClassName() + .equals(AppWidgetWithConfig.class.getName())); + } + + @Test + public void testPinShortcut() throws Throwable { + // Command to set the shortcut id + Intent command = RequestPinItemActivity.getCommandIntent( + RequestPinItemActivity.class, "setShortcutId").putExtra( + RequestPinItemActivity.EXTRA_PARAM + "0", mShortcutId); + + runTest("pinShortcut", false, new ItemOperator() { + @Override + public boolean evaluate(ItemInfo info, View view) { + return info instanceof WorkspaceItemInfo && + info.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT && + ShortcutKey.fromItemInfo(info).getId().equals(mShortcutId); + } + }, command); + } + + private void runTest(String activityMethod, boolean isWidget, ItemOperator itemMatcher, + Intent... commandIntents) throws Throwable { + commitTransactionAndLoadHome(new FavoriteItemsTransaction(mTargetContext)); + + // Open Pin item activity + BlockingBroadcastReceiver openMonitor = new BlockingBroadcastReceiver( + RequestPinItemActivity.class.getName()); + mLauncher. + getWorkspace(). + switchToAllApps(). + getAppIcon("Test Pin Item"). + launch(getAppPackageName()); + assertNotNull(openMonitor.blockingGetExtraIntent()); + + // Set callback + PendingIntent callback = PendingIntent.getBroadcast(mTargetContext, 0, + new Intent(mCallbackAction).setPackage(mTargetContext.getPackageName()), + FLAG_ONE_SHOT | FLAG_MUTABLE); + mTargetContext.sendBroadcast(RequestPinItemActivity.getCommandIntent( + RequestPinItemActivity.class, "setCallback").putExtra( + RequestPinItemActivity.EXTRA_PARAM + "0", callback)); + + for (Intent command : commandIntents) { + mTargetContext.sendBroadcast(command); + } + + // call the requested method to start the flow + mTargetContext.sendBroadcast(RequestPinItemActivity.getCommandIntent( + RequestPinItemActivity.class, activityMethod)); + final AddToHomeScreenPrompt addToHomeScreenPrompt = mLauncher.getAddToHomeScreenPrompt(); + + // Accept confirmation: + BlockingBroadcastReceiver resultReceiver = new BlockingBroadcastReceiver(mCallbackAction); + addToHomeScreenPrompt.addAutomatically(); + Intent result = resultReceiver.blockingGetIntent(); + assertNotNull(result); + mAppWidgetId = result.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1); + if (isWidget) { + assertNotSame(-1, mAppWidgetId); + } + + // Go back to home + mLauncher.goHome(); + Wait.atMost("", new ItemSearchCondition(itemMatcher), DEFAULT_ACTIVITY_TIMEOUT, + mLauncher); + } + + /** + * Condition for for an item + */ + private class ItemSearchCondition implements Condition { + + private final ItemOperator mOp; + + ItemSearchCondition(ItemOperator op) { + mOp = op; + } + + @Override + public boolean isTrue() throws Throwable { + return mMainThreadExecutor.submit(() -> { + Launcher l = Launcher.ACTIVITY_TRACKER.getCreatedActivity(); + return l != null && l.getWorkspace().getFirstMatch(mOp) != null; + }).get(); + } + } +} diff --git a/tests/src/com/android/launcher3/ui/widget/TaplWidgetPickerTest.java b/tests/src/com/android/launcher3/ui/widget/TaplWidgetPickerTest.java new file mode 100644 index 0000000000..19c585085b --- /dev/null +++ b/tests/src/com/android/launcher3/ui/widget/TaplWidgetPickerTest.java @@ -0,0 +1,86 @@ +/* + * 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.launcher3.ui.widget; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.MediumTest; + +import com.android.launcher3.Launcher; +import com.android.launcher3.tapl.Widgets; +import com.android.launcher3.ui.AbstractLauncherUiTest; +import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape; +import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord; +import com.android.launcher3.widget.picker.WidgetsFullSheet; +import com.android.launcher3.widget.picker.WidgetsRecyclerView; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * This test run in both Out of process (Oop) and in-process (Ipc). + * Make sure the basic interactions with the WidgetPicker works. + */ +@MediumTest +@RunWith(AndroidJUnit4.class) +public class TaplWidgetPickerTest extends AbstractLauncherUiTest { + + private WidgetsRecyclerView getWidgetsView(Launcher launcher) { + return WidgetsFullSheet.getWidgetsView(launcher); + } + + private int getWidgetsScroll(Launcher launcher) { + return getWidgetsView(launcher).computeVerticalScrollOffset(); + } + + /** + * Open Widget picker, make sure the widget picker can scroll and then go to home screen. + */ + @Test + @ScreenRecord + @PortraitLandscape + public void testWidgets() { + mLauncher.goHome(); + // Test opening widgets. + executeOnLauncher(launcher -> + assertTrue("Widgets is initially opened", getWidgetsView(launcher) == null)); + Widgets widgets = mLauncher.getWorkspace().openAllWidgets(); + assertNotNull("openAllWidgets() returned null", widgets); + widgets = mLauncher.getAllWidgets(); + assertNotNull("getAllWidgets() returned null", widgets); + executeOnLauncher(launcher -> + assertTrue("Widgets is not shown", getWidgetsView(launcher).isShown())); + executeOnLauncher(launcher -> assertEquals("Widgets is scrolled upon opening", + 0, getWidgetsScroll(launcher))); + + // Test flinging widgets. + widgets.flingForward(); + Integer flingForwardY = getFromLauncher(launcher -> getWidgetsScroll(launcher)); + executeOnLauncher(launcher -> assertTrue("Flinging forward didn't scroll widgets", + flingForwardY > 0)); + + widgets.flingBackward(); + executeOnLauncher(launcher -> assertTrue("Flinging backward didn't scroll widgets", + getWidgetsScroll(launcher) < flingForwardY)); + + mLauncher.goHome(); + waitForLauncherCondition("Widgets were not closed", + launcher -> getWidgetsView(launcher) == null); + } +} diff --git a/tests/src/com/android/launcher3/ui/workspace/TaplThemeIconsTest.java b/tests/src/com/android/launcher3/ui/workspace/TaplThemeIconsTest.java new file mode 100644 index 0000000000..d6533171d3 --- /dev/null +++ b/tests/src/com/android/launcher3/ui/workspace/TaplThemeIconsTest.java @@ -0,0 +1,177 @@ +/* + * 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. + */ +package com.android.launcher3.ui.workspace; + +import static com.android.launcher3.util.TestConstants.AppNames.TEST_APP_NAME; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import android.content.ContentProviderClient; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.net.Uri; +import android.view.View; +import android.view.ViewGroup; + +import androidx.test.filters.LargeTest; + +import com.android.launcher3.BubbleTextView; +import com.android.launcher3.Launcher; +import com.android.launcher3.icons.ThemedIconDrawable; +import com.android.launcher3.tapl.HomeAllApps; +import com.android.launcher3.tapl.HomeAppIcon; +import com.android.launcher3.tapl.HomeAppIconMenuItem; +import com.android.launcher3.ui.AbstractLauncherUiTest; +import com.android.launcher3.util.Executors; + +import org.junit.Test; + +import java.util.ArrayDeque; +import java.util.Queue; + +/** + * Tests for theme icon support in Launcher + * + * Note running these tests will clear the workspace on the device. + */ +@LargeTest +public class TaplThemeIconsTest extends AbstractLauncherUiTest { + + private static final String APP_NAME = "IconThemedActivity"; + private static final String SHORTCUT_NAME = "Shortcut 1"; + + @Test + public void testIconWithoutTheme() throws Exception { + setThemeEnabled(false); + initialize(this); + + HomeAllApps allApps = mLauncher.getWorkspace().switchToAllApps(); + allApps.freeze(); + + try { + HomeAppIcon icon = allApps.getAppIcon(APP_NAME); + executeOnLauncher(l -> verifyIconTheme(APP_NAME, l.getAppsView(), false)); + icon.dragToWorkspace(false, false); + executeOnLauncher(l -> verifyIconTheme(APP_NAME, l.getWorkspace(), false)); + } finally { + allApps.unfreeze(); + } + } + + @Test + public void testShortcutIconWithoutTheme() throws Exception { + setThemeEnabled(false); + initialize(this); + + HomeAllApps allApps = mLauncher.getWorkspace().switchToAllApps(); + allApps.freeze(); + + try { + HomeAppIcon icon = allApps.getAppIcon(TEST_APP_NAME); + HomeAppIconMenuItem shortcutItem = + (HomeAppIconMenuItem) icon.openDeepShortcutMenu().getMenuItem(SHORTCUT_NAME); + shortcutItem.dragToWorkspace(false, false); + executeOnLauncher(l -> verifyIconTheme(SHORTCUT_NAME, l.getWorkspace(), false)); + } finally { + allApps.unfreeze(); + } + } + + @Test + public void testIconWithTheme() throws Exception { + setThemeEnabled(true); + initialize(this); + + HomeAllApps allApps = mLauncher.getWorkspace().switchToAllApps(); + allApps.freeze(); + + try { + HomeAppIcon icon = allApps.getAppIcon(APP_NAME); + executeOnLauncher(l -> verifyIconTheme(APP_NAME, l.getAppsView(), false)); + icon.dragToWorkspace(false, false); + executeOnLauncher(l -> verifyIconTheme(APP_NAME, l.getWorkspace(), true)); + } finally { + allApps.unfreeze(); + } + } + + @Test + public void testShortcutIconWithTheme() throws Exception { + setThemeEnabled(true); + initialize(this); + + HomeAllApps allApps = mLauncher.getWorkspace().switchToAllApps(); + allApps.freeze(); + + try { + HomeAppIcon icon = allApps.getAppIcon(TEST_APP_NAME); + HomeAppIconMenuItem shortcutItem = + (HomeAppIconMenuItem) icon.openDeepShortcutMenu().getMenuItem(SHORTCUT_NAME); + shortcutItem.dragToWorkspace(false, false); + executeOnLauncher(l -> verifyIconTheme(SHORTCUT_NAME, l.getWorkspace(), true)); + } finally { + allApps.unfreeze(); + } + } + + private void verifyIconTheme(String title, ViewGroup parent, boolean isThemed) { + // Wait for Launcher model to be completed + try { + Executors.MODEL_EXECUTOR.submit(() -> { }).get(); + } catch (Exception e) { + throw new RuntimeException(e); + } + + // Find the app icon + Queue viewQueue = new ArrayDeque<>(); + viewQueue.add(parent); + BubbleTextView icon = null; + while (!viewQueue.isEmpty()) { + View view = viewQueue.poll(); + if (view instanceof ViewGroup) { + parent = (ViewGroup) view; + for (int i = parent.getChildCount() - 1; i >= 0; i--) { + viewQueue.add(parent.getChildAt(i)); + } + } else if (view instanceof BubbleTextView btv) { + if (title.equals(btv.getContentDescription().toString())) { + icon = btv; + break; + } + } + } + + assertNotNull(icon.getIcon()); + assertEquals(isThemed, icon.getIcon() instanceof ThemedIconDrawable); + } + + private void setThemeEnabled(boolean isEnabled) throws Exception { + Uri uri = new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(mTargetPackage + ".grid_control") + .appendPath("set_icon_themed") + .build(); + ContentValues values = new ContentValues(); + values.put("boolean_value", isEnabled); + try (ContentProviderClient client = mTargetContext.getContentResolver() + .acquireContentProviderClient(uri)) { + int result = client.update(uri, values, null); + assertTrue(result > 0); + } + } +} diff --git a/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java b/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java index e544b19ac9..e92d6415f2 100644 --- a/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java +++ b/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java @@ -20,6 +20,8 @@ import static com.android.launcher3.util.TestConstants.AppNames.CHROME_APP_NAME; import static com.android.launcher3.util.TestConstants.AppNames.MAPS_APP_NAME; import static com.android.launcher3.util.TestConstants.AppNames.MESSAGES_APP_NAME; import static com.android.launcher3.util.TestConstants.AppNames.STORE_APP_NAME; +import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL; +import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -37,6 +39,8 @@ import com.android.launcher3.ui.AbstractLauncherUiTest; import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape; import com.android.launcher3.util.LauncherLayoutBuilder; import com.android.launcher3.util.TestUtil; +import com.android.launcher3.util.rule.ScreenRecordRule; +import com.android.launcher3.util.rule.TestStabilityRule; import org.junit.After; import org.junit.Before; @@ -109,6 +113,8 @@ public class TaplTwoPanelWorkspaceTest extends AbstractLauncherUiTest @Test @PortraitLandscape + @ScreenRecordRule.ScreenRecord // b/329935119 + @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/329935119 public void testSinglePageDragIconWhenMultiplePageScrollingIsPossible() { Workspace workspace = mLauncher.getWorkspace(); @@ -240,6 +246,8 @@ public class TaplTwoPanelWorkspaceTest extends AbstractLauncherUiTest }); } + @ScreenRecordRule.ScreenRecord // b/329935119 + @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/329935119 @Test @PortraitLandscape public void testEmptyPageDoesNotGetRemovedIfPagePairIsNotEmpty() { diff --git a/tests/src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java b/tests/src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java index cab1ebee23..490cff20b4 100644 --- a/tests/src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java +++ b/tests/src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java @@ -32,6 +32,7 @@ import com.android.launcher3.util.TestUtil; import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord; import org.junit.After; +import org.junit.Before; import org.junit.Test; /** @@ -49,6 +50,12 @@ public class TaplWorkspaceTest extends AbstractLauncherUiTest { return launcher.getWorkspace().getCurrentPage(); } + @Before + public void setUp() throws Exception { + super.setUp(); + initialize(this); + } + @After public void tearDown() throws Exception { if (mLauncherLayout != null) { @@ -114,15 +121,8 @@ public class TaplWorkspaceTest extends AbstractLauncherUiTest { * Similar to {@link TaplWorkspaceTest#testWorkspace} but here we also make sure we can delete * the pages. */ - @ScreenRecord // b/381918059 @Test - public void testAddAndDeletePageAndFling() throws Exception { - // Set workspace that includes the chrome Activity app icon on the hotseat. - LauncherLayoutBuilder builder = new LauncherLayoutBuilder() - .atHotseat(0).putApp("com.android.chrome", "com.google.android.apps.chrome.Main"); - mLauncherLayout = TestUtil.setLauncherDefaultLayout(mTargetContext, builder); - reinitializeLauncherData(); - + public void testAddAndDeletePageAndFling() { Workspace workspace = mLauncher.getWorkspace(); // Get the first app from the hotseat HomeAppIcon hotSeatIcon = workspace.getHotseatAppIcon(0); diff --git a/tests/src/com/android/launcher3/util/Wait.java b/tests/src/com/android/launcher3/util/Wait.java new file mode 100644 index 0000000000..50bc32e509 --- /dev/null +++ b/tests/src/com/android/launcher3/util/Wait.java @@ -0,0 +1,66 @@ +package com.android.launcher3.util; + +import android.os.SystemClock; +import android.util.Log; + +import com.android.launcher3.tapl.LauncherInstrumentation; + +import org.junit.Assert; + +import java.util.function.Supplier; + +/** + * A utility class for waiting for a condition to be true. + */ +public class Wait { + + private static final long DEFAULT_SLEEP_MS = 200; + + public static void atMost(String message, Condition condition, long timeout, + LauncherInstrumentation launcher) { + atMost(() -> message, condition, timeout, DEFAULT_SLEEP_MS, launcher); + } + + public static void atMost(Supplier message, Condition condition, long timeout, + LauncherInstrumentation launcher) { + atMost(message, condition, timeout, DEFAULT_SLEEP_MS, launcher); + } + + public static void atMost(Supplier message, Condition condition, long timeout, + long sleepMillis, + LauncherInstrumentation launcher) { + final long startTime = SystemClock.uptimeMillis(); + long endTime = startTime + timeout; + Log.d("Wait", "atMost: " + startTime + " - " + endTime); + while (SystemClock.uptimeMillis() < endTime) { + try { + if (condition.isTrue()) { + return; + } + } catch (Throwable t) { + throw new RuntimeException(t); + } + SystemClock.sleep(sleepMillis); + } + + // Check once more before returning false. + try { + if (condition.isTrue()) { + return; + } + } catch (Throwable t) { + throw new RuntimeException(t); + } + Log.d("Wait", "atMost: timed out: " + SystemClock.uptimeMillis()); + launcher.checkForAnomaly(false, false); + Assert.fail(message.get()); + } + + /** + * Interface representing a generic condition + */ + public interface Condition { + + boolean isTrue() throws Throwable; + } +} diff --git a/tests/src/com/android/launcher3/util/rule/ExtendedLongPressTimeoutRule.java b/tests/src/com/android/launcher3/util/rule/ExtendedLongPressTimeoutRule.java index 8a9ff3ed38..702988c05b 100644 --- a/tests/src/com/android/launcher3/util/rule/ExtendedLongPressTimeoutRule.java +++ b/tests/src/com/android/launcher3/util/rule/ExtendedLongPressTimeoutRule.java @@ -16,9 +16,6 @@ package com.android.launcher3.util.rule; -import static com.android.launcher3.util.TestUtil.grantWriteSecurePermission; - -import android.app.Instrumentation; import android.content.ContentResolver; import android.provider.Settings; import android.util.Log; @@ -54,7 +51,6 @@ public class ExtendedLongPressTimeoutRule implements TestRule { try { Log.d(TAG, "In try-block: Setting long press timeout from " + prevLongPressTimeout + "ms to " + newLongPressTimeout + "ms"); - grantWriteSecurePermission(); Settings.Secure.putInt( contentResolver, Settings.Secure.LONG_PRESS_TIMEOUT, @@ -67,7 +63,6 @@ public class ExtendedLongPressTimeoutRule implements TestRule { } finally { Log.d(TAG, "In finally-block: resetting long press timeout to " + prevLongPressTimeout + "ms"); - grantWriteSecurePermission(); Settings.Secure.putInt( contentResolver, Settings.Secure.LONG_PRESS_TIMEOUT, diff --git a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java index 3b8530984c..7bdc040afc 100644 --- a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java +++ b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java @@ -12,7 +12,7 @@ import androidx.test.uiautomator.UiDevice; import com.android.app.viewcapture.data.ExportedData; import com.android.launcher3.tapl.LauncherInstrumentation; -import com.android.launcher3.ui.BaseLauncherTaplTest; +import com.android.launcher3.ui.AbstractLauncherUiTest; import org.junit.rules.TestWatcher; import org.junit.runner.Description; @@ -57,7 +57,7 @@ public class FailureWatcher extends TestWatcher { @Override protected void succeeded(Description description) { super.succeeded(description); - BaseLauncherTaplTest.checkDetectedLeaks(mLauncher); + AbstractLauncherUiTest.checkDetectedLeaks(mLauncher); } @Override diff --git a/tests/src/com/android/launcher3/util/rule/ShellCommandRule.java b/tests/src/com/android/launcher3/util/rule/ShellCommandRule.java index d093bf70a5..2219003254 100644 --- a/tests/src/com/android/launcher3/util/rule/ShellCommandRule.java +++ b/tests/src/com/android/launcher3/util/rule/ShellCommandRule.java @@ -21,9 +21,9 @@ import static com.android.launcher3.tapl.TestHelpers.getLauncherInMyProcess; import android.content.ComponentName; import android.content.pm.ActivityInfo; -import android.os.Process; import androidx.annotation.Nullable; +import androidx.test.InstrumentationRegistry; import androidx.test.uiautomator.UiDevice; import com.android.systemui.shared.system.PackageManagerWrapper; @@ -91,9 +91,8 @@ public class ShellCommandRule implements TestRule { * Grants the launcher permission to bind widgets. */ public static ShellCommandRule grantWidgetBind() { - return new ShellCommandRule(String.format("appwidget grantbind --package %s --user %d", - getInstrumentation().getTargetContext().getPackageName(), - Process.myUserHandle().getIdentifier()), null); + return new ShellCommandRule("appwidget grantbind --package " + + InstrumentationRegistry.getTargetContext().getPackageName(), null); } /** @@ -110,9 +109,8 @@ public class ShellCommandRule implements TestRule { } public static String getLauncherCommand(ActivityInfo launcher) { - return String.format("cmd package set-home-activity --user %d %s", - Process.myUserHandle().getIdentifier(), - new ComponentName(launcher.packageName, launcher.name).flattenToString()); + return "cmd package set-home-activity " + + new ComponentName(launcher.packageName, launcher.name).flattenToString(); } /** diff --git a/tests/src/com/android/launcher3/widget/picker/OWNERS b/tests/src/com/android/launcher3/widget/picker/OWNERS index 716ab90043..775b0c7d9b 100644 --- a/tests/src/com/android/launcher3/widget/picker/OWNERS +++ b/tests/src/com/android/launcher3/widget/picker/OWNERS @@ -5,6 +5,7 @@ set noparent # # Widget Picker OWNERS +zakcohen@google.com shamalip@google.com wvk@google.com diff --git a/tests/tapl/com/android/launcher3/tapl/AppIcon.java b/tests/tapl/com/android/launcher3/tapl/AppIcon.java index 9294755f1b..7c6d684e66 100644 --- a/tests/tapl/com/android/launcher3/tapl/AppIcon.java +++ b/tests/tapl/com/android/launcher3/tapl/AppIcon.java @@ -16,8 +16,9 @@ package com.android.launcher3.tapl; +import static com.android.launcher3.testing.shared.TestProtocol.TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE; -import android.graphics.Point; +import android.util.Log; import android.widget.TextView; import androidx.annotation.NonNull; @@ -97,6 +98,8 @@ public abstract class AppIcon extends Launchable { @Override protected void waitForLongPressConfirmation() { + Log.d(TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE, + "AppIcon.waitForLongPressConfirmation, resName: popupContainer"); mLauncher.waitForLauncherObject("popup_container"); } @@ -125,14 +128,6 @@ public abstract class AppIcon extends Launchable { return getObject().getContentDescription(); } - /** - * @return the center coordinates of the icon - */ - @NonNull - public Point getVisibleCenter() { - return getObject().getVisibleCenter(); - } - /** * Create a regular expression pattern that matches strings containing all of the non-whitespace * characters of the app name, with any amount of whitespace added between characters (e.g. diff --git a/tests/tapl/com/android/launcher3/tapl/AppIconMenu.java b/tests/tapl/com/android/launcher3/tapl/AppIconMenu.java index 033cfb0664..bbcc6a85ba 100644 --- a/tests/tapl/com/android/launcher3/tapl/AppIconMenu.java +++ b/tests/tapl/com/android/launcher3/tapl/AppIconMenu.java @@ -63,12 +63,5 @@ public abstract class AppIconMenu { return new SplitScreenMenuItem(mLauncher, menuItem); } - /** Returns the Bubble menu item. */ - public BubbleMenuItem getBubbleMenuItem() { - final UiObject2 menuItem = mLauncher.waitForObjectInContainer(mDeepShortcutsContainer, - AppIcon.getMenuItemSelector("Bubble", mLauncher)); - return new BubbleMenuItem(mLauncher, menuItem); - } - protected abstract AppIconMenuItem createMenuItem(UiObject2 menuItem); } diff --git a/tests/tapl/com/android/launcher3/tapl/Background.java b/tests/tapl/com/android/launcher3/tapl/Background.java index 512db394ac..988aa94c28 100644 --- a/tests/tapl/com/android/launcher3/tapl/Background.java +++ b/tests/tapl/com/android/launcher3/tapl/Background.java @@ -16,7 +16,7 @@ package com.android.launcher3.tapl; -import static com.android.launcher3.tapl.BaseOverview.TASK_SELECTOR; +import static com.android.launcher3.tapl.BaseOverview.TASK_RES_ID; import static com.android.launcher3.tapl.OverviewTask.TASK_START_EVENT; import static com.android.launcher3.testing.shared.TestProtocol.OVERVIEW_STATE_ORDINAL; @@ -29,7 +29,6 @@ import androidx.test.uiautomator.UiObject2; import com.android.launcher3.tapl.LauncherInstrumentation.NavigationModel; import com.android.launcher3.tapl.LauncherInstrumentation.TrackpadGestureType; -import com.android.launcher3.tapl.OverviewTask.TaskViewType; import com.android.launcher3.testing.shared.TestProtocol; import java.util.List; @@ -118,35 +117,16 @@ public abstract class Background extends LauncherInstrumentation.VisibleContaine // non-tablet overview, snapshots can be on either side of the swiped // task, but we still check that they become visible after swiping and // pausing. - mLauncher.waitForObjectBySelector(TASK_SELECTOR); + mLauncher.waitForOverviewObject(TASK_RES_ID); if (mLauncher.isTablet()) { List tasks = mLauncher.getDevice().findObjects( - TASK_SELECTOR); - + mLauncher.getOverviewObjectSelector(TASK_RES_ID)); final int centerX = mLauncher.getDevice().getDisplayWidth() / 2; - UiObject2 centerTask = tasks.stream() - .filter(t -> t.getVisibleCenter().x == centerX) - .findFirst() - .orElse(null); - - if (centerTask != null) { - mLauncher.assertTrue( - "Task(s) found to the right of the swiped task", - tasks.stream() - .filter(t -> t != centerTask - && OverviewTask.getType(t) - != TaskViewType.DESKTOP) - .allMatch(t -> t.getVisibleBounds().right - < centerTask.getVisibleBounds().left)); - mLauncher.assertTrue( - "DesktopTask(s) found to the left of the swiped task", - tasks.stream() - .filter(t -> t != centerTask - && OverviewTask.getType(t) - == TaskViewType.DESKTOP) - .allMatch(t -> t.getVisibleBounds().left - > centerTask.getVisibleBounds().right)); - } + mLauncher.assertTrue( + "All tasks not to the left of the swiped task", + tasks.stream() + .allMatch( + t -> t.getVisibleBounds().right < centerX)); } } diff --git a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java index 214f1587f5..27f6c1661e 100644 --- a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java +++ b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java @@ -19,13 +19,11 @@ package com.android.launcher3.tapl; import static android.view.KeyEvent.KEYCODE_ESCAPE; import static com.android.launcher3.tapl.LauncherInstrumentation.TASKBAR_RES_ID; -import static com.android.launcher3.tapl.LauncherInstrumentation.log; import static com.android.launcher3.tapl.OverviewTask.TASK_START_EVENT; -import static com.android.launcher3.tapl.TestHelpers.getOverviewPackageName; import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL; -import static com.android.launcher3.testing.shared.TestProtocol.testLogD; import android.graphics.Rect; +import android.util.Log; import android.view.KeyEvent; import androidx.annotation.NonNull; @@ -37,11 +35,9 @@ import androidx.test.uiautomator.UiObject2; import com.android.launcher3.testing.shared.TestProtocol; -import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; -import java.util.Optional; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -49,10 +45,7 @@ import java.util.stream.Collectors; * Common overview panel for both Launcher and fallback recents */ public class BaseOverview extends LauncherInstrumentation.VisibleContainer { - private static final String TAG = "BaseOverview"; - protected static final BySelector TASK_SELECTOR = By.res(Pattern.compile( - getOverviewPackageName() - + ":id/(task_view_single|task_view_grouped|task_view_desktop)")); + protected static final String TASK_RES_ID = "task"; private static final Pattern EVENT_ALT_ESC_UP = Pattern.compile( "Key event: KeyEvent.*?action=ACTION_UP.*?keyCode=KEYCODE_ESCAPE.*?metaState=0"); private static final Pattern EVENT_ENTER_DOWN = Pattern.compile( @@ -62,22 +55,10 @@ public class BaseOverview extends LauncherInstrumentation.VisibleContainer { private static final int FLINGS_FOR_DISMISS_LIMIT = 40; - private final @Nullable UiObject2 mLiveTileTask; - - BaseOverview(LauncherInstrumentation launcher) { - this(launcher, /*launchedFromApp=*/false); - } - - BaseOverview(LauncherInstrumentation launcher, boolean launchedFromApp) { super(launcher); verifyActiveContainer(); verifyActionsViewVisibility(); - if (launchedFromApp) { - mLiveTileTask = getCurrentTaskUnchecked(); - } else { - mLiveTileTask = null; - } } @Override @@ -97,7 +78,7 @@ public class BaseOverview extends LauncherInstrumentation.VisibleContainer { private void flingForwardImpl() { try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer("want to fling forward in overview")) { - log("Overview.flingForward before fling"); + LauncherInstrumentation.log("Overview.flingForward before fling"); final UiObject2 overview = verifyActiveContainer(); final int leftMargin = mLauncher.getTargetInsets().left + mLauncher.getEdgeSensitivityWidth(); @@ -123,7 +104,7 @@ public class BaseOverview extends LauncherInstrumentation.VisibleContainer { private void flingBackwardImpl() { try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer("want to fling backward in overview")) { - log("Overview.flingBackward before fling"); + LauncherInstrumentation.log("Overview.flingBackward before fling"); final UiObject2 overview = verifyActiveContainer(); final int rightMargin = mLauncher.getTargetInsets().right + mLauncher.getEdgeSensitivityWidth(); @@ -157,7 +138,12 @@ public class BaseOverview extends LauncherInstrumentation.VisibleContainer { LauncherInstrumentation.Closable c = mLauncher.addContextLayer( "dismissing all tasks")) { final BySelector clearAllSelector = mLauncher.getOverviewObjectSelector("clear_all"); - flingForwardUntilClearAllVisibleImpl(); + for (int i = 0; + i < FLINGS_FOR_DISMISS_LIMIT + && !verifyActiveContainer().hasObject(clearAllSelector); + ++i) { + flingForwardImpl(); + } final Runnable clickClearAll = () -> mLauncher.clickLauncherObject( mLauncher.waitForObjectInContainer(verifyActiveContainer(), @@ -177,26 +163,6 @@ public class BaseOverview extends LauncherInstrumentation.VisibleContainer { } } - /** - * Scrolls until Clear-all button is visible. - */ - public void flingForwardUntilClearAllVisible() { - try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) { - flingForwardUntilClearAllVisibleImpl(); - } - } - - private void flingForwardUntilClearAllVisibleImpl() { - try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer( - "flinging forward to clear all")) { - final BySelector clearAllSelector = mLauncher.getOverviewObjectSelector("clear_all"); - for (int i = 0; i < FLINGS_FOR_DISMISS_LIMIT && !verifyActiveContainer().hasObject( - clearAllSelector); ++i) { - flingForwardImpl(); - } - } - } - /** * Touch to the right of current task. This should dismiss overview and go back to Workspace. */ @@ -293,7 +259,7 @@ public class BaseOverview extends LauncherInstrumentation.VisibleContainer { if (mLauncher.isTablet()) { mLauncher.assertTrue("current task is not grid height", getCurrentTask().getVisibleHeight() == mLauncher - .getOverviewGridTaskSize().height()); + .getGridTaskRectForTablet().height()); } mLauncher.assertTrue("Current task not scrolled off screen", !getCurrentTask().equals(task)); @@ -309,56 +275,37 @@ public class BaseOverview extends LauncherInstrumentation.VisibleContainer { */ @NonNull public OverviewTask getCurrentTask() { - UiObject2 currentTask = getCurrentTaskUnchecked(); - mLauncher.assertNotNull("Unable to find a task", currentTask); - return new OverviewTask(mLauncher, currentTask, this); - } - - @Nullable - private UiObject2 getCurrentTaskUnchecked() { final List taskViews = getTasks(); - if (taskViews.isEmpty()) { - return null; - } + mLauncher.assertNotEquals("Unable to find a task", 0, taskViews.size()); // The widest, and most top-right task should be the current task - return Collections.max(taskViews, + UiObject2 currentTask = Collections.max(taskViews, Comparator.comparingInt((UiObject2 t) -> t.getVisibleBounds().width()) .thenComparingInt((UiObject2 t) -> t.getVisibleCenter().x) .thenComparing(Comparator.comparing( (UiObject2 t) -> t.getVisibleCenter().y).reversed())); + return new OverviewTask(mLauncher, currentTask, this); } - /** - * Returns an overview task that contains the specified test activity in its thumbnails. - * - * @param activityIndex index of TestActivity to match against - */ + /** Returns an overview task matching TestActivity {@param activityNumber}. */ @NonNull - public OverviewTask getTestActivityTask(int activityIndex) { - return getTestActivityTask(Collections.singleton(activityIndex)); - } - - /** - * Returns an overview task that contains all the specified test activities in its thumbnails. - * - * @param activityNumbers collection of indices of TestActivity to match against - */ - @NonNull - public OverviewTask getTestActivityTask(Collection activityNumbers) { + public OverviewTask getTestActivityTask(int activityNumber) { final List taskViews = getTasks(); mLauncher.assertNotEquals("Unable to find a task", 0, taskViews.size()); - Optional task = taskViews.stream().filter( - taskView -> activityNumbers.stream().allMatch(activityNumber -> - // TODO(b/239452415): Use equals instead of descEndsWith - taskView.hasObject(By.descEndsWith("TestActivity" + activityNumber)) - )).findFirst(); + final String activityName = "TestActivity" + activityNumber; + UiObject2 task = null; + for (UiObject2 taskView : taskViews) { + // TODO(b/239452415): Use equals instead of descEndsWith + if (taskView.getParent().hasObject(By.descEndsWith(activityName))) { + task = taskView; + break; + } + } + mLauncher.assertNotNull( + "Unable to find a task with " + activityName + " from the task list", task); - mLauncher.assertTrue("Unable to find a task with test activities " + activityNumbers - + " from the task list", task.isPresent()); - - return new OverviewTask(mLauncher, task.get(), this); + return new OverviewTask(mLauncher, task, this); } /** @@ -369,7 +316,7 @@ public class BaseOverview extends LauncherInstrumentation.VisibleContainer { final List taskViews = getTasks(); mLauncher.assertNotEquals("Unable to find a task", 0, taskViews.size()); - final int gridTaskWidth = mLauncher.getOverviewGridTaskSize().width(); + final int gridTaskWidth = mLauncher.getGridTaskRectForTablet().width(); return taskViews.stream().filter(t -> t.getVisibleBounds().width() == gridTaskWidth).map( t -> new OverviewTask(mLauncher, t, this)).collect(Collectors.toList()); @@ -380,10 +327,12 @@ public class BaseOverview extends LauncherInstrumentation.VisibleContainer { try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer( "want to get overview tasks")) { verifyActiveContainer(); - return mLauncher.getDevice().findObjects(TASK_SELECTOR); + return mLauncher.getDevice().findObjects( + mLauncher.getOverviewObjectSelector(TASK_RES_ID)); } } + int getTaskCount() { return getTasks().size(); } @@ -435,33 +384,39 @@ public class BaseOverview extends LauncherInstrumentation.VisibleContainer { } protected boolean isActionsViewVisible() { - if (!hasTasks() || isClearAllVisible()) { - testLogD(TAG, "Not expecting an actions bar: no tasks/'Clear all' is visible"); + boolean hasTasks = hasTasks(); + if (!hasTasks || isClearAllVisible()) { + LauncherInstrumentation.log("Not expecting an actions bar:" + + (!hasTasks ? "no recent tasks" : "clear all button is visible")); return false; } boolean isTablet = mLauncher.isTablet(); if (isTablet && mLauncher.isGridOnlyOverviewEnabled()) { - testLogD(TAG, "Not expecting an actions bar: device is tablet with grid-only Overview"); + LauncherInstrumentation.log("Not expecting an actions bar: " + + "device is tablet with grid-only Overview"); return false; } OverviewTask task = isTablet ? getFocusedTaskForTablet() : getCurrentTask(); if (task == null) { - testLogD(TAG, "Not expecting an actions bar: no current task"); + LauncherInstrumentation.log("Not expecting an actions bar: no focused task"); return false; } + float centerOffset = Math.abs(task.getExactCenterX() - mLauncher.getExactScreenCenterX()); // In tablets, if focused task is not in center, overview actions aren't visible. - if (isTablet && Math.abs(task.getExactCenterX() - mLauncher.getExactScreenCenterX()) >= 1) { - testLogD(TAG, - "Not expecting an actions bar: device is tablet and task is not centered"); + if (isTablet && centerOffset >= 1) { + LauncherInstrumentation.log("Not expecting an actions bar: " + + "device is tablet and task is not centered; center offset by " + + centerOffset + "px"); return false; } - if (task.isGrouped() && !isTablet) { - testLogD(TAG, "Not expecting an actions bar: device is phone and task is split"); + if (task.isTaskSplit() && (!mLauncher.isAppPairsEnabled() || !isTablet)) { + LauncherInstrumentation.log("Not expecting an actions bar: " + + "device is phone and task is split"); // Overview actions aren't visible for split screen tasks, except for save app pair // button on tablets. return false; } - testLogD(TAG, "Expecting an actions bar"); + LauncherInstrumentation.log("Expecting an actions bar"); return true; } @@ -518,11 +473,11 @@ public class BaseOverview extends LauncherInstrumentation.VisibleContainer { "want to assert overview actions view visibility=" + isActionsViewVisible() + ", focused task is " - + (task == null ? "null" : (task.isGrouped() ? "split" : "not split")) + + (task == null ? "null" : (task.isTaskSplit() ? "split" : "not split")) )) { if (isActionsViewVisible()) { - if (task.isGrouped()) { + if (task.isTaskSplit()) { mLauncher.waitForOverviewObject("action_save_app_pair"); } else { mLauncher.waitForOverviewObject("action_buttons"); @@ -544,27 +499,22 @@ public class BaseOverview extends LauncherInstrumentation.VisibleContainer { throw new IllegalStateException("Must be run on tablet device."); } final List taskViews = getTasks(); - if (taskViews.isEmpty()) { + if (!hasTasks()) { + LauncherInstrumentation.log("no recent tasks"); return null; } - Rect focusTaskSize = mLauncher.getOverviewTaskSize(); - int focusedTaskHeight = focusTaskSize.height(); + int focusedTaskHeight = mLauncher.getFocusedTaskHeightForTablet(); for (UiObject2 task : taskViews) { OverviewTask overviewTask = new OverviewTask(mLauncher, task, this); - // Desktop tasks can't be focused tasks, but are the same size. - if (overviewTask.isDesktop()) { - continue; - } + + LauncherInstrumentation.log("checking task height (" + + overviewTask.getVisibleHeight() + + ") against defined focused task height (" + + focusedTaskHeight + ")"); if (overviewTask.getVisibleHeight() == focusedTaskHeight) { return overviewTask; } } return null; } - - protected boolean isLiveTile(UiObject2 task) { - // UiObject2.equals returns false even when mLiveTileTask and task have the same node, hence - // compare only hashCode as a workaround. - return mLiveTileTask != null && mLiveTileTask.hashCode() == task.hashCode(); - } } diff --git a/tests/tapl/com/android/launcher3/tapl/Home.java b/tests/tapl/com/android/launcher3/tapl/Home.java index 4055100520..85e28e86cb 100644 --- a/tests/tapl/com/android/launcher3/tapl/Home.java +++ b/tests/tapl/com/android/launcher3/tapl/Home.java @@ -60,8 +60,7 @@ public abstract class Home extends Background { @Override protected boolean zeroButtonToOverviewGestureStateTransitionWhileHolding() { - return !mLauncher.isRecentsWindowEnabled() - || super.zeroButtonToOverviewGestureStateTransitionWhileHolding(); + return true; } @Override diff --git a/tests/tapl/com/android/launcher3/tapl/KeyboardQuickSwitch.java b/tests/tapl/com/android/launcher3/tapl/KeyboardQuickSwitch.java index 7cb26142a5..7ff55fe5cf 100644 --- a/tests/tapl/com/android/launcher3/tapl/KeyboardQuickSwitch.java +++ b/tests/tapl/com/android/launcher3/tapl/KeyboardQuickSwitch.java @@ -162,32 +162,6 @@ public final class KeyboardQuickSwitch { } } - /** - * Dismisses the Keyboard Quick Switch view by going home. After the Keyboard Quick Switch view - * gets hidden, it unpresses ALT key, which is generally used to keep the view visible. - */ - public Workspace dismissByGoingHome() { - try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer( - "verifying keyboard quick switch view is shown")) { - mLauncher.waitForLauncherObject(KEYBOARD_QUICK_SWITCH_RES_ID); - } - - mLauncher.goHome(); - - try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer( - "waiting for keyboard quick switch dismissal"); - LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) { - mLauncher.waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID); - } - - try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer( - "get workspace after releasing ALT key")) { - mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_HOME_ALT_LEFT_UP); - mLauncher.unpressKeyCode(KeyEvent.KEYCODE_ALT_LEFT, 0); - return mLauncher.getWorkspace(); - } - } - /** * Launches the currently-focused app task. *

diff --git a/tests/tapl/com/android/launcher3/tapl/Launchable.java b/tests/tapl/com/android/launcher3/tapl/Launchable.java index c40e5a93d8..9d3bc6e952 100644 --- a/tests/tapl/com/android/launcher3/tapl/Launchable.java +++ b/tests/tapl/com/android/launcher3/tapl/Launchable.java @@ -17,8 +17,10 @@ package com.android.launcher3.tapl; import static com.android.launcher3.testing.shared.TestProtocol.SPRING_LOADED_STATE_ORDINAL; +import static com.android.launcher3.testing.shared.TestProtocol.TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE; import android.graphics.Point; +import android.util.Log; import android.view.MotionEvent; import androidx.test.uiautomator.UiObject2; @@ -113,6 +115,10 @@ public abstract class Launchable { iconCenter.y - getStartDragThreshold()); if (runToSpringLoadedState) { + Log.d(TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE, + "Launchable.startDrag: actionName: long-pressing and triggering drag start" + + " iconCenter: " + iconCenter + " dragStartCenter: " + + dragStartCenter); mLauncher.runToState(() -> movePointerForStartDrag( downTime, iconCenter, diff --git a/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java b/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java index b3ad930659..200f2fff17 100644 --- a/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java +++ b/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java @@ -33,7 +33,6 @@ import android.view.InputDevice; import android.view.MotionEvent; import android.view.ViewConfiguration; -import androidx.annotation.NonNull; import androidx.test.uiautomator.Condition; import androidx.test.uiautomator.UiDevice; @@ -76,20 +75,6 @@ public final class LaunchedAppState extends Background { return false; } - @NonNull - @Override - public BaseOverview switchToOverview() { - try (LauncherInstrumentation.Closable ignored = mLauncher.eventsCheck(); - LauncherInstrumentation.Closable ignored1 = mLauncher.addContextLayer( - "want to switch from background to overview")) { - verifyActiveContainer(); - goToOverviewUnchecked(); - return mLauncher.is3PLauncher() - ? new BaseOverview(mLauncher, /*launchedFromApp=*/true) - : new Overview(mLauncher, /*launchedFromApp=*/true); - } - } - /** * Returns the taskbar. * diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java index e0d2f394e1..f02a0c2296 100644 --- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java +++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java @@ -31,6 +31,7 @@ import static com.android.launcher3.tapl.TestHelpers.getOverviewPackageName; import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL; import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_GET_SPLIT_SELECTION_ACTIVE; import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_NUM_ALL_APPS_COLUMNS; +import static com.android.launcher3.testing.shared.TestProtocol.TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE; import static com.android.launcher3.testing.shared.TestProtocol.TEST_INFO_RESPONSE_FIELD; import android.app.ActivityManager; @@ -41,7 +42,6 @@ import android.content.ComponentName; import android.content.ContentProviderClient; import android.content.ContentResolver; import android.content.Context; -import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ProviderInfo; import android.content.res.Configuration; @@ -55,7 +55,6 @@ import android.os.DeadObjectException; import android.os.Parcelable; import android.os.RemoteException; import android.os.SystemClock; -import android.os.Trace; import android.text.TextUtils; import android.util.Log; import android.view.InputDevice; @@ -80,6 +79,7 @@ import androidx.test.uiautomator.UiObject2; import androidx.test.uiautomator.Until; import com.android.launcher3.testing.shared.ResourceUtils; +import com.android.launcher3.testing.shared.TestInformationRequest; import com.android.launcher3.testing.shared.TestProtocol; import com.android.systemui.shared.system.QuickStepContract; @@ -98,7 +98,6 @@ import java.util.concurrent.TimeoutException; import java.util.function.BooleanSupplier; import java.util.function.Function; import java.util.function.Supplier; -import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -211,8 +210,6 @@ public final class LauncherInstrumentation { private TrackpadGestureType mTrackpadGestureType = TrackpadGestureType.NONE; private int mPointerCount = 0; - private boolean mWaitingForMotionUpEvent; - private static Pattern getKeyEventPattern(String action, String keyCode) { return Pattern.compile("Key event: KeyEvent.*action=" + action + ".*keyCode=" + keyCode); } @@ -379,8 +376,10 @@ public final class LauncherInstrumentation { } } - Bundle getTestInfo(Intent request) { - return getTestInfo(request.getAction(), null, request.getExtras()); + Bundle getTestInfo(TestInformationRequest request) { + Bundle extra = new Bundle(); + extra.putParcelable(TestProtocol.TEST_INFO_REQUEST_FIELD, request); + return getTestInfo(request.getRequestName(), null, extra); } Insets getTargetInsets() { @@ -428,14 +427,14 @@ public final class LauncherInstrumentation { .getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD); } - Rect getOverviewTaskSize() { - return getTestInfo(TestProtocol.REQUEST_GET_OVERVIEW_TASK_SIZE) - .getParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD, Rect.class); + int getFocusedTaskHeightForTablet() { + return getTestInfo(TestProtocol.REQUEST_GET_FOCUSED_TASK_HEIGHT_FOR_TABLET).getInt( + TestProtocol.TEST_INFO_RESPONSE_FIELD); } - Rect getOverviewGridTaskSize() { - return getTestInfo(TestProtocol.REQUEST_GET_OVERVIEW_GRID_TASK_SIZE) - .getParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD, Rect.class); + Rect getGridTaskRectForTablet() { + return ((Rect) getTestInfo(TestProtocol.REQUEST_GET_GRID_TASK_SIZE_RECT_FOR_TABLET) + .getParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD)); } int getOverviewPageSpacing() { @@ -456,6 +455,10 @@ public final class LauncherInstrumentation { getTestInfo(TestProtocol.REQUEST_ENABLE_ROTATION, Boolean.toString(on)); } + public void setEnableSuggestion(boolean enableSuggestion) { + getTestInfo(TestProtocol.REQUEST_ENABLE_SUGGESTION, Boolean.toString(enableSuggestion)); + } + public boolean hadNontestEvents() { return getTestInfo(TestProtocol.REQUEST_GET_HAD_NONTEST_EVENTS) .getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD); @@ -524,19 +527,16 @@ public final class LauncherInstrumentation { Closable addContextLayer(String piece) { mDiagnosticContext.addLast(piece); - Trace.beginSection("Context: " + piece); log("Entering context: " + piece); return () -> { - Trace.endSection(); log("Leaving context: " + piece); mDiagnosticContext.removeLast(); }; } public void dumpViewHierarchy() { + final ByteArrayOutputStream stream = new ByteArrayOutputStream(); try { - Trace.beginSection("dumpViewHierarchy"); - final ByteArrayOutputStream stream = new ByteArrayOutputStream(); mDevice.dumpWindowHierarchy(stream); stream.flush(); stream.close(); @@ -545,8 +545,6 @@ public final class LauncherInstrumentation { } } catch (IOException e) { Log.e(TAG, "error dumping XML to logcat", e); - } finally { - Trace.endSection(); } } @@ -626,20 +624,15 @@ public final class LauncherInstrumentation { */ public void checkForAnomaly( boolean ignoreNavmodeChangeStates, boolean ignoreOnlySystemUiViews) { - try { - Trace.beginSection("checkForAnomaly"); - if (mTestAnomalyChecker != null) mTestAnomalyChecker.run(); + if (mTestAnomalyChecker != null) mTestAnomalyChecker.run(); - final String systemAnomalyMessage = - getSystemAnomalyMessage(ignoreNavmodeChangeStates, ignoreOnlySystemUiViews); - if (systemAnomalyMessage != null) { - if (mOnFailure != null) mOnFailure.run(); - Assert.fail(formatSystemHealthMessage(formatErrorWithEvents( - "http://go/tapl : Tests are broken by a non-Launcher system error: " - + systemAnomalyMessage, false))); - } - } finally { - Trace.endSection(); + final String systemAnomalyMessage = + getSystemAnomalyMessage(ignoreNavmodeChangeStates, ignoreOnlySystemUiViews); + if (systemAnomalyMessage != null) { + if (mOnFailure != null) mOnFailure.run(); + Assert.fail(formatSystemHealthMessage(formatErrorWithEvents( + "http://go/tapl : Tests are broken by a non-Launcher system error: " + + systemAnomalyMessage, false))); } } @@ -754,29 +747,7 @@ public final class LauncherInstrumentation { } } - private void cleanUpInputStream() { - if (!mWaitingForMotionUpEvent) { - return; - } - long downTime = SystemClock.uptimeMillis(); - MotionEvent cleanUpEvent = getMotionEvent( - downTime, - downTime, - MotionEvent.ACTION_UP, - 0, - 0, - InputDevice.SOURCE_TOUCHSCREEN, - Configurator.getInstance().getToolType()); - log("Test failed while a ACTION_UP event was still pending. " - + "Cleaning up the input stream by sending an ACTION_UP event forcefully: " - + "event= " + cleanUpEvent); - - injectEventUnchecked(cleanUpEvent); - mWaitingForMotionUpEvent = false; - } - void fail(String message) { - cleanUpInputStream(); checkForAnomaly(); if (mOnFailure != null) mOnFailure.run(); Assert.fail(formatSystemHealthMessage(formatErrorWithEvents( @@ -929,11 +900,7 @@ public final class LauncherInstrumentation { waitUntilSystemLauncherObjectGone(OVERVIEW_RES_ID); waitUntilSystemLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID); waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID); - if (isTaskbarShownOnHome()) { - waitForSystemLauncherObject(TASKBAR_RES_ID); - } else { - waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID); - } + waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID); return waitForLauncherObject(WORKSPACE_RES_ID); } @@ -951,9 +918,7 @@ public final class LauncherInstrumentation { waitUntilLauncherObjectGone(WORKSPACE_RES_ID); waitUntilLauncherObjectGone(WIDGETS_RES_ID); waitUntilSystemLauncherObjectGone(OVERVIEW_RES_ID); - if (isTransientTaskbar()) { - waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID); - } + waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID); waitUntilSystemLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID); waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID); @@ -965,8 +930,7 @@ public final class LauncherInstrumentation { waitUntilSystemLauncherObjectGone(OVERVIEW_RES_ID); waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID); - if ((is3PLauncher() && isTablet() && !isTransientTaskbar()) - || isTaskbarShownOnHome()) { + if (is3PLauncher() && isTablet() && !isTransientTaskbar()) { waitForSystemLauncherObject(TASKBAR_RES_ID); } else { waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID); @@ -985,7 +949,7 @@ public final class LauncherInstrumentation { waitUntilLauncherObjectGone(APPS_RES_ID); waitUntilLauncherObjectGone(WORKSPACE_RES_ID); waitUntilLauncherObjectGone(WIDGETS_RES_ID); - if (isTablet() && !is3PLauncher() && !isRecentsWindowEnabled()) { + if (isTablet() && !is3PLauncher()) { waitForSystemLauncherObject(TASKBAR_RES_ID); } else { waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID); @@ -1039,30 +1003,21 @@ public final class LauncherInstrumentation { } } - boolean isRecentsWindowEnabled() { - return getTestInfo(TestProtocol.REQUEST_IS_RECENTS_WINDOW_ENABLED) - .getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD); - } - public void waitForModelQueueCleared() { getTestInfo(TestProtocol.REQUEST_MODEL_QUEUE_CLEARED); } public void waitForLauncherInitialized() { - try { - Trace.beginSection("waitForLauncherInitialized"); - for (int i = 0; i < 100; ++i) { - if (getTestInfo(TestProtocol.REQUEST_IS_LAUNCHER_INITIALIZED).getBoolean( - TestProtocol.TEST_INFO_RESPONSE_FIELD)) { - return; - } - SystemClock.sleep(100); + for (int i = 0; i < 100; ++i) { + if (getTestInfo( + TestProtocol.REQUEST_IS_LAUNCHER_INITIALIZED). + getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD)) { + return; } - checkForAnomaly(); - fail("Launcher didn't initialize"); - } finally { - Trace.endSection(); + SystemClock.sleep(100); } + checkForAnomaly(); + fail("Launcher didn't initialize"); } public boolean isLauncherActivityStarted() { @@ -1245,6 +1200,11 @@ public final class LauncherInstrumentation { log("Hierarchy before clicking home:"); dumpViewHierarchy(); action = "clicking home button"; + Log.d(TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE, + "LauncherInstrumentation.goHome: isThreeFingerTrackpadGesture: " + + isThreeFingerTrackpadGesture + + "getNavigationModel() == NavigationModel.ZERO_BUTTON: " + ( + getNavigationModel() == NavigationModel.ZERO_BUTTON)); runToState( getHomeButton()::click, NORMAL_STATE_ORDINAL, @@ -1272,13 +1232,14 @@ public final class LauncherInstrumentation { void pressBackImpl() { waitForLauncherInitialized(); final boolean launcherVisible = - (isTablet() || isTaskbarNavbarUnificationEnabled()) ? isLauncherContainerVisible() - : isLauncherVisible(); + isTablet() ? isLauncherContainerVisible() : isLauncherVisible(); boolean isThreeFingerTrackpadGesture = mTrackpadGestureType == TrackpadGestureType.THREE_FINGER; if (getNavigationModel() == NavigationModel.ZERO_BUTTON || isThreeFingerTrackpadGesture) { final Point displaySize = getRealDisplaySize(); + // TODO(b/225505986): change startY and endY back to displaySize.y / 2 once the + // issue is solved. int startX = isThreeFingerTrackpadGesture ? displaySize.x / 4 : 0; int endX = isThreeFingerTrackpadGesture ? displaySize.x * 3 / 4 : displaySize.x / 2; linearGesture(startX, displaySize.y / 4, endX, displaySize.y / 4, @@ -1300,13 +1261,8 @@ public final class LauncherInstrumentation { } boolean isLauncherVisible() { - try { - Trace.beginSection("isLauncherVisible"); - mDevice.waitForIdle(); - return hasLauncherObject(getAnyObjectSelector()); - } finally { - Trace.endSection(); - } + mDevice.waitForIdle(); + return hasLauncherObject(getAnyObjectSelector()); } boolean isLauncherContainerVisible() { @@ -1593,6 +1549,8 @@ public final class LauncherInstrumentation { @NonNull UiObject2 waitForLauncherObject(String resName) { + Log.d(TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE, + "LauncherInstrumentation.waitForLauncherObject"); return waitForObjectBySelector(getLauncherObjectSelector(resName)); } @@ -1622,12 +1580,16 @@ public final class LauncherInstrumentation { @NonNull List waitForObjectsBySelector(BySelector selector) { + Log.d(TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE, + "LauncherInstrumentation.waitForObjectsBySelector"); final List objects = mDevice.wait(Until.findObjects(selector), WAIT_TIME_MS); assertNotNull("Can't find any view in Launcher, selector: " + selector, objects); return objects; } - UiObject2 waitForObjectBySelector(BySelector selector) { + private UiObject2 waitForObjectBySelector(BySelector selector) { + Log.d(TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE, + "LauncherInstrumentation.waitForObjectBySelector"); final UiObject2 object = mDevice.wait(Until.findObject(selector), WAIT_TIME_MS); assertNotNull("Can't find a view in Launcher, selector: " + selector, object); return object; @@ -1670,6 +1632,9 @@ public final class LauncherInstrumentation { void runToState(Runnable command, int expectedState, boolean requireEvent, String actionName) { if (requireEvent) { + Log.d(TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE, + "LauncherInstrumentation.runToState: command: " + command + " expectedState: " + + expectedState + " actionName: " + actionName + "requireEvent: true"); runToState(command, expectedState, actionName); } else { command.run(); @@ -1760,27 +1725,6 @@ public final class LauncherInstrumentation { scrollDownByDistance(container, distance, appsListBottomPadding); } - /** Scrolls up by given distance within the container. */ - void scrollUpByDistance(UiObject2 container, int distance) { - scrollUpByDistance(container, distance, 0); - } - - /** Scrolls up by given distance within the container considering the given bottom padding. */ - void scrollUpByDistance(UiObject2 container, int distance, int bottomPadding) { - final Rect containerRect = getVisibleBounds(container); - final int bottomGestureMarginInContainer = getBottomGestureMarginInContainer(container); - scroll( - container, - Direction.UP, - new Rect( - 0, - containerRect.height() - bottomGestureMarginInContainer - distance, - 0, - bottomGestureMarginInContainer + bottomPadding), - /* steps= */ 10, - /* slowDown= */ true); - } - void scrollDownByDistance(UiObject2 container, int distance) { scrollDownByDistance(container, distance, 0); } @@ -2044,6 +1988,11 @@ public final class LauncherInstrumentation { TestProtocol.TEST_INFO_RESPONSE_FIELD); } + boolean isAppPairsEnabled() { + return getTestInfo(TestProtocol.REQUEST_FLAG_ENABLE_APP_PAIRS).getBoolean( + TestProtocol.TEST_INFO_RESPONSE_FIELD); + } + public void sendPointer(long downTime, long currentTime, int action, Point point, GestureScope gestureScope) { sendPointer(downTime, currentTime, action, point, gestureScope, @@ -2051,38 +2000,8 @@ public final class LauncherInstrumentation { } private void injectEvent(InputEvent event) { - if (event instanceof MotionEvent motionEvent) { - switch (motionEvent.getAction()) { - case MotionEvent.ACTION_DOWN: - assertTrue("Attempting to inject a second ACTION_DOWN event before a " - + "ACTION_UP event: " + event, - !mWaitingForMotionUpEvent); - mWaitingForMotionUpEvent = true; - break; - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: - assertTrue("Attempting to inject an unexpected ACTION_UP event: " + event, - mWaitingForMotionUpEvent); - mWaitingForMotionUpEvent = false; - - break; - default: - // Nothing to do - break; - } - } - assertTrue("injectInputEvent failed: event=" + event, injectEventUnchecked(event)); - } - - private boolean injectEventUnchecked(InputEvent event) { - boolean result = mInstrumentation.getUiAutomation().injectInputEvent(event, true, false); - - // Only MotionEvents need to be recycled. - if (event instanceof MotionEvent motionEvent) { - motionEvent.recycle(); - } - - return result; + assertTrue("injectInputEvent failed: event=" + event, + mInstrumentation.getUiAutomation().injectInputEvent(event, true, false)); } public void sendPointer(long downTime, long currentTime, int action, Point point, @@ -2115,11 +2034,15 @@ public final class LauncherInstrumentation { mPointerCount = 1; pointerCount = mPointerCount; } + Log.d(TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE, + "LauncherInstrumentation.sendPointer: ACTION_DOWN"); break; case MotionEvent.ACTION_UP: if (hasTIS && gestureScope == GestureScope.EXPECT_PILFER) { expectEvent(TestProtocol.SEQUENCE_PILFER, EVENT_PILFER_POINTERS); } + Log.d(TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE, + "LauncherInstrumentation.sendPointer: ACTION_UP"); break; case MotionEvent.ACTION_POINTER_DOWN: mPointerCount++; @@ -2136,13 +2059,12 @@ public final class LauncherInstrumentation { downTime, currentTime, action, point.x, point.y, pointerCount, mTrackpadGestureType) : getMotionEvent(downTime, currentTime, action, point.x, point.y, source, toolType); - int button = isRightClick ? MotionEvent.BUTTON_SECONDARY : MotionEvent.BUTTON_PRIMARY; - if (action == MotionEvent.ACTION_BUTTON_PRESS) { - event.setButtonState(event.getButtonState() | button); - } if (action == MotionEvent.ACTION_BUTTON_PRESS || action == MotionEvent.ACTION_BUTTON_RELEASE) { - event.setActionButton(button); + event.setActionButton(MotionEvent.BUTTON_PRIMARY); + } + if (isRightClick) { + event.setButtonState(event.getButtonState() | MotionEvent.BUTTON_SECONDARY); } injectEvent(event); } @@ -2253,17 +2175,11 @@ public final class LauncherInstrumentation { sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, targetCenter, GestureScope.DONT_EXPECT_PILFER, InputDevice.SOURCE_TOUCHSCREEN, /* isRightClick= */ true, MotionEvent.TOOL_TYPE_STYLUS); - sendPointer(downTime, downTime, MotionEvent.ACTION_BUTTON_PRESS, targetCenter, - GestureScope.DONT_EXPECT_PILFER, InputDevice.SOURCE_TOUCHSCREEN, - /* isRightClick= */ true, MotionEvent.TOOL_TYPE_STYLUS); try { expectEvent(TestProtocol.SEQUENCE_MAIN, longClickEvent); final UiObject2 result = waitForLauncherObject(resName); return result; } finally { - sendPointer(downTime, downTime, MotionEvent.ACTION_BUTTON_RELEASE, targetCenter, - GestureScope.DONT_EXPECT_PILFER, InputDevice.SOURCE_TOUCHSCREEN, - /* isRightClick= */ true, MotionEvent.TOOL_TYPE_STYLUS); sendPointer(downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, targetCenter, GestureScope.DONT_EXPECT_PILFER, InputDevice.SOURCE_TOUCHSCREEN, /* isRightClick= */ true, MotionEvent.TOOL_TYPE_STYLUS); @@ -2278,17 +2194,11 @@ public final class LauncherInstrumentation { sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, targetCenter, GestureScope.DONT_EXPECT_PILFER, InputDevice.SOURCE_MOUSE, /* isRightClick= */ true); - sendPointer(downTime, downTime, MotionEvent.ACTION_BUTTON_PRESS, targetCenter, - GestureScope.DONT_EXPECT_PILFER, InputDevice.SOURCE_MOUSE, - /* isRightClick= */ true); try { expectEvent(TestProtocol.SEQUENCE_MAIN, rightClickEvent); final UiObject2 result = waitForLauncherObject(resName); return result; } finally { - sendPointer(downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_BUTTON_RELEASE, - targetCenter, GestureScope.DONT_EXPECT_PILFER, InputDevice.SOURCE_MOUSE, - /* isRightClick= */ true); sendPointer(downTime, SystemClock.uptimeMillis(), ACTION_UP, targetCenter, GestureScope.DONT_EXPECT_PILFER, InputDevice.SOURCE_MOUSE, /* isRightClick= */ true); @@ -2428,12 +2338,6 @@ public final class LauncherInstrumentation { .getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD); } - /** Whether taskbar will be shown on home for current default display. */ - public boolean isTaskbarShownOnHome() { - return getTestInfo(TestProtocol.REQUEST_TASKBAR_SHOWN_ON_HOME).getBoolean( - TEST_INFO_RESPONSE_FIELD); - } - public boolean isImeDocked() { return getTestInfo(TestProtocol.REQUEST_TASKBAR_IME_DOCKED).getBoolean( TestProtocol.TEST_INFO_RESPONSE_FIELD); @@ -2498,16 +2402,6 @@ public final class LauncherInstrumentation { disableSensorRotation(); final Integer initialPid = getPid(); final LogEventChecker eventChecker = new LogEventChecker(this); - eventChecker.setLogExclusionRule(event -> { - Matcher matcher = Pattern.compile("KeyEvent.*flags=0x([0-9a-fA-F]+)").matcher(event); - if (matcher.find()) { - long keyEventFlags = Long.parseLong(matcher.group(1), 16); - // ignore KeyEvents with FLAG_CANCELED - return (keyEventFlags & KeyEvent.FLAG_CANCELED) != 0; - } - return false; - }); - if (eventChecker.start()) mEventChecker = eventChecker; return () -> { @@ -2565,8 +2459,7 @@ public final class LauncherInstrumentation { } float getWindowCornerRadius() { - // Return a larger corner radius to ensure gesture calculated from the radius are offset to - // prevent overlapping + // TODO(b/197326121): Check if the touch is overlapping with the corners by offsetting final float tmpBuffer = 100f; final Resources resources = getResources(); if (!supportsRoundedCornersOnWindows(resources)) { diff --git a/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java b/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java index 3a49160088..055a357049 100644 --- a/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java +++ b/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java @@ -35,8 +35,6 @@ public class LogEventChecker { // Map from an event sequence name to an ordered list of expected events in that sequence. private final ListMap mExpectedEvents = new ListMap<>(); - private LogExclusionRule mLogExclusionRule = null; - LogEventChecker(LauncherInstrumentation launcher) { mLauncher = launcher; } @@ -50,10 +48,6 @@ public class LogEventChecker { mExpectedEvents.add(sequence, pattern); } - void setLogExclusionRule(LogExclusionRule logExclusionRule) { - mLogExclusionRule = logExclusionRule; - } - // Waits for the expected number of events and returns them. private ListMap finishSync(long waitForExpectedCountMs) { final long startTime = SystemClock.uptimeMillis(); @@ -80,9 +74,7 @@ public class LogEventChecker { final ListMap eventSequences = new ListMap<>(); for (String rawEvent : rawEvents) { final String[] split = rawEvent.split("/"); - if (mLogExclusionRule == null || !mLogExclusionRule.shouldExclude(split[1])) { - eventSequences.add(split[0], split[1]); - } + eventSequences.add(split[0], split[1]); } return eventSequences; } @@ -183,8 +175,4 @@ public class LogEventChecker { return list; } } - - interface LogExclusionRule { - boolean shouldExclude(String event); - } } diff --git a/tests/tapl/com/android/launcher3/tapl/Overview.java b/tests/tapl/com/android/launcher3/tapl/Overview.java index deb27e4f87..50c21368e0 100644 --- a/tests/tapl/com/android/launcher3/tapl/Overview.java +++ b/tests/tapl/com/android/launcher3/tapl/Overview.java @@ -22,12 +22,9 @@ import com.android.launcher3.tapl.LauncherInstrumentation.ContainerType; * Overview pane. */ public class Overview extends BaseOverview { - Overview(LauncherInstrumentation launcher) { - this(launcher, /*launchedFromApp=*/false); - } - Overview(LauncherInstrumentation launcher, boolean launchedFromApp) { - super(launcher, launchedFromApp); + Overview(LauncherInstrumentation launcher) { + super(launcher); } @Override diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java index 8fbb5e3172..6f420afe78 100644 --- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java +++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java @@ -16,14 +16,14 @@ package com.android.launcher3.tapl; -import static com.android.launcher3.tapl.OverviewTask.OverviewTaskContainer.DEFAULT; -import static com.android.launcher3.tapl.OverviewTask.OverviewTaskContainer.DESKTOP; -import static com.android.launcher3.tapl.OverviewTask.OverviewTaskContainer.SPLIT_BOTTOM_OR_RIGHT; -import static com.android.launcher3.tapl.OverviewTask.OverviewTaskContainer.SPLIT_TOP_OR_LEFT; +import static com.android.launcher3.tapl.OverviewTask.OverviewSplitTask.DEFAULT; +import static com.android.launcher3.tapl.OverviewTask.OverviewSplitTask.SPLIT_BOTTOM_OR_RIGHT; +import static com.android.launcher3.tapl.OverviewTask.OverviewSplitTask.SPLIT_TOP_OR_LEFT; import android.graphics.Rect; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.test.uiautomator.By; import androidx.test.uiautomator.BySelector; import androidx.test.uiautomator.UiObject2; @@ -40,23 +40,16 @@ import java.util.stream.Collectors; public final class OverviewTask { private static final String SYSTEMUI_PACKAGE = "com.android.systemui"; static final Pattern TASK_START_EVENT = Pattern.compile("startActivityFromRecentsAsync"); - static final Pattern TASK_START_EVENT_DESKTOP = Pattern.compile("launchDesktopFromRecents"); - static final Pattern TASK_START_EVENT_LIVE_TILE = Pattern.compile( - "composeRecentsLaunchAnimator"); static final Pattern SPLIT_SELECT_EVENT = Pattern.compile("enterSplitSelect"); static final Pattern SPLIT_START_EVENT = Pattern.compile("launchSplitTasks"); private final LauncherInstrumentation mLauncher; - @NonNull private final UiObject2 mTask; - private final TaskViewType mType; private final BaseOverview mOverview; - OverviewTask(LauncherInstrumentation launcher, @NonNull UiObject2 task, BaseOverview overview) { + OverviewTask(LauncherInstrumentation launcher, UiObject2 task, BaseOverview overview) { mLauncher = launcher; - mLauncher.assertNotNull("task must not be null", task); mTask = task; mOverview = overview; - mType = getType(task); verifyActiveContainer(); } @@ -69,11 +62,11 @@ public final class OverviewTask { * divider between. */ int getVisibleHeight() { - if (isGrouped()) { + if (isTaskSplit()) { return getCombinedSplitTaskHeight(); } - UiObject2 taskSnapshot1 = findObjectInTask((isDesktop() ? DESKTOP : DEFAULT).snapshotRes); + UiObject2 taskSnapshot1 = findObjectInTask(DEFAULT.snapshotRes); return taskSnapshot1.getVisibleBounds().height(); } @@ -102,7 +95,7 @@ public final class OverviewTask { * divider between. */ int getVisibleWidth() { - if (isGrouped()) { + if (isTaskSplit()) { return getCombinedSplitTaskWidth(); } @@ -125,11 +118,11 @@ public final class OverviewTask { return right - left; } - public int getTaskCenterX() { + int getTaskCenterX() { return mTask.getVisibleCenter().x; } - public int getTaskCenterY() { + int getTaskCenterY() { return mTask.getVisibleCenter().y; } @@ -156,20 +149,16 @@ public final class OverviewTask { return; } - boolean taskWasFocused = mLauncher.isTablet() - && !isDesktop() - && getVisibleHeight() == mLauncher.getOverviewTaskSize().height(); + boolean taskWasFocused = mLauncher.isTablet() && getVisibleHeight() == mLauncher + .getFocusedTaskHeightForTablet(); List originalTasksCenterX = getCurrentTasksCenterXList().stream().sorted().toList(); boolean isClearAllVisibleBeforeDismiss = mOverview.isClearAllVisible(); dismissBySwipingUp(); - long numNonDesktopTasks = mOverview.getCurrentTasksForTablet() - .stream().filter(t -> !t.isDesktop()).count(); - try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer("dismissed")) { - if (taskWasFocused && numNonDesktopTasks > 0) { + if (taskWasFocused) { mLauncher.assertNotNull("No task became focused", mOverview.getFocusedTaskForTablet()); } @@ -210,36 +199,6 @@ public final class OverviewTask { : List.of(mOverview.getCurrentTask().getTaskCenterX()); } - /** - * Starts dismissing the task by swiping up, then cancels, and task springs back to start. - */ - public void dismissCancel() { - try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck(); - LauncherInstrumentation.Closable c = mLauncher.addContextLayer( - "want to start dismissing an overview task then cancel")) { - verifyActiveContainer(); - int taskCountBeforeDismiss = mOverview.getTaskCount(); - mLauncher.assertNotEquals("Unable to find a task", 0, taskCountBeforeDismiss); - - final Rect taskBounds = mLauncher.getVisibleBounds(mTask); - final int centerX = taskBounds.centerX(); - final int centerY = taskBounds.bottom - 1; - final int endCenterY = centerY - (taskBounds.height() / 4); - mLauncher.executeAndWaitForLauncherEvent( - // Set slowDown to true so we do not fling the task at the end of the drag, as - // we want it to cancel and return back to the origin. We use 30 steps to - // perform the gesture slowly as well, to avoid flinging. - () -> mLauncher.linearGesture(centerX, centerY, centerX, endCenterY, - /* steps= */ 30, /* slowDown= */ true, - LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER), - event -> TestProtocol.DISMISS_ANIMATION_ENDS_MESSAGE.equals( - event.getClassName()), - () -> "Canceling swipe to dismiss did not end with task at origin.", - "cancel swiping to dismiss"); - - } - } - /** * Clicks the task. */ @@ -261,22 +220,7 @@ public final class OverviewTask { return new LaunchedAppState(mLauncher); } } else { - final Pattern event; - if (mOverview.isLiveTile(mTask)) { - event = TASK_START_EVENT_LIVE_TILE; - } else if (mType == TaskViewType.DESKTOP) { - event = TASK_START_EVENT_DESKTOP; - } else { - event = TASK_START_EVENT; - } - mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, event); - - if (mType == TaskViewType.DESKTOP) { - try (LauncherInstrumentation.Closable ignored = mLauncher.addContextLayer( - "launched desktop")) { - mLauncher.waitForSystemUiObject("desktop_mode_caption"); - } - } + mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, TASK_START_EVENT); return new LaunchedAppState(mLauncher); } } @@ -290,7 +234,7 @@ public final class OverviewTask { /** Taps the task menu of the split task. Returns the split task's menu object. */ @NonNull - public OverviewTaskMenu tapMenu(OverviewTaskContainer task) { + public OverviewTaskMenu tapMenu(OverviewSplitTask task) { try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck(); LauncherInstrumentation.Closable c = mLauncher.addContextLayer( "want to tap the task menu")) { @@ -304,6 +248,10 @@ public final class OverviewTask { } } + boolean isTaskSplit() { + return findObjectInTask(SPLIT_BOTTOM_OR_RIGHT.snapshotRes) != null; + } + private UiObject2 findObjectInTask(String resName) { return mTask.findObject(mLauncher.getOverviewObjectSelector(resName)); } @@ -311,10 +259,11 @@ public final class OverviewTask { /** * Returns whether the given String is contained in this Task's contentDescription. Also returns * true if both Strings are null. + * + * TODO(b/326565120): remove Nullable support once the bug causing it to be null is fixed. */ - public boolean containsContentDescription(String expected, - OverviewTaskContainer overviewTaskContainer) { - String actual = findObjectInTask(overviewTaskContainer.snapshotRes).getContentDescription(); + public boolean containsContentDescription(@Nullable String expected) { + String actual = mTask.getContentDescription(); if (actual == null && expected == null) { return true; } @@ -325,61 +274,22 @@ public final class OverviewTask { } /** - * Returns whether the given String is contained in this Task's contentDescription. Also returns - * true if both Strings are null + * Enum used to specify which task is retrieved when it is a split task. */ - public boolean containsContentDescription(String expected) { - return containsContentDescription(expected, DEFAULT); - } - - /** - * Returns the TaskView type of the task. It will return whether the task is a single TaskView, - * a GroupedTaskView or a DesktopTaskView. - */ - static TaskViewType getType(UiObject2 task) { - String resourceName = task.getResourceName(); - if (resourceName.endsWith("task_view_grouped")) { - return TaskViewType.GROUPED; - } else if (resourceName.endsWith("task_view_desktop")) { - return TaskViewType.DESKTOP; - } else { - return TaskViewType.SINGLE; - } - } - - boolean isGrouped() { - return mType == TaskViewType.GROUPED; - } - - public boolean isDesktop() { - return mType == TaskViewType.DESKTOP; - } - - /** - * Enum used to specify which resource name should be used depending on the type of the task. - */ - public enum OverviewTaskContainer { + public enum OverviewSplitTask { // The main task when the task is not split. DEFAULT("snapshot", "icon"), // The first task in split task. SPLIT_TOP_OR_LEFT("snapshot", "icon"), // The second task in split task. - SPLIT_BOTTOM_OR_RIGHT("bottomright_snapshot", "bottomRight_icon"), - // The desktop task. - DESKTOP("background", "icon"); + SPLIT_BOTTOM_OR_RIGHT("bottomright_snapshot", "bottomRight_icon"); public final String snapshotRes; public final String iconAppRes; - OverviewTaskContainer(String snapshotRes, String iconAppRes) { + OverviewSplitTask(String snapshotRes, String iconAppRes) { this.snapshotRes = snapshotRes; this.iconAppRes = iconAppRes; } } - - enum TaskViewType { - SINGLE, - GROUPED, - DESKTOP - } } diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenu.java b/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenu.java index 90d32f3249..902ad5b568 100644 --- a/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenu.java +++ b/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenu.java @@ -97,35 +97,20 @@ public class OverviewTaskMenu { } } - /** - * Taps the Desktop item from the overview task menu and returns the LaunchedAppState - * representing the Desktop. - */ - @NonNull - public LaunchedAppState tapDesktopMenuItem() { - try (LauncherInstrumentation.Closable ignored = mLauncher.eventsCheck(); - LauncherInstrumentation.Closable ignored1 = mLauncher.addContextLayer( - "before tapping the desktop menu item")) { - mLauncher.executeAndWaitForLauncherStop( - () -> mLauncher.clickLauncherObject( - mLauncher.findObjectInContainer(mMenu, By.text("Desktop"))), - "tapped desktop menu item"); - - try (LauncherInstrumentation.Closable ignored2 = mLauncher.addContextLayer( - "tapped desktop menu item")) { - mLauncher.waitUntilSystemLauncherObjectGone("overview_panel"); - mLauncher.waitForSystemUiObject("desktop_mode_caption"); - return new LaunchedAppState(mLauncher); - } - } - } - /** Returns true if an item matching the given string is present in the menu. */ public boolean hasMenuItem(String expectedMenuItemText) { UiObject2 menuItem = mLauncher.findObjectInContainer(mMenu, By.text(expectedMenuItemText)); return menuItem != null; } + /** + * Returns the menu item specified by name if present. + */ + public OverviewTaskMenuItem getMenuItemByName(String menuItemName) { + return new OverviewTaskMenuItem(mLauncher, + mLauncher.waitForObjectInContainer(mMenu, By.text(menuItemName))); + } + /** * Taps outside task menu to dismiss it. */ diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenuItem.java b/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenuItem.java new file mode 100644 index 0000000000..e3035bf71b --- /dev/null +++ b/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenuItem.java @@ -0,0 +1,39 @@ +/* + * 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.launcher3.tapl; + +import android.graphics.Rect; + +import androidx.test.uiautomator.UiObject2; + +/** Represents an item in the overview task menu. */ +public class OverviewTaskMenuItem { + + private final LauncherInstrumentation mLauncher; + private final UiObject2 mMenuItem; + + OverviewTaskMenuItem(LauncherInstrumentation launcher, UiObject2 menuItem) { + mLauncher = launcher; + mMenuItem = menuItem; + } + + /** + * Returns this menu item's visible bounds. + */ + public Rect getVisibleBounds() { + return mMenuItem.getVisibleBounds(); + } +} diff --git a/tests/tapl/com/android/launcher3/tapl/Taskbar.java b/tests/tapl/com/android/launcher3/tapl/Taskbar.java index d4e6d3114b..e6315f3d3b 100644 --- a/tests/tapl/com/android/launcher3/tapl/Taskbar.java +++ b/tests/tapl/com/android/launcher3/tapl/Taskbar.java @@ -52,7 +52,7 @@ public final class Taskbar { if (!mLauncher.isTransientTaskbar()) { Assert.assertEquals("Persistent taskbar should fill screen width", - mLauncher.getRealDisplaySize().x, getVisibleBounds().width()); + getVisibleBounds().width(), mLauncher.getRealDisplaySize().x); } } @@ -115,23 +115,6 @@ public final class Taskbar { } } - /** - * Opens the Home all apps page by clicking the taskbar all apps icon. To be used to open all - * apps when taskbar is visible on home. - */ - public HomeAllApps openAllAppsOnHome() { - try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer( - "want to open home all apps from taskbar"); - LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) { - - mLauncher.clickLauncherObject(mLauncher.waitForObjectInContainer( - mLauncher.waitForSystemLauncherObject(TASKBAR_RES_ID), - getAllAppsButtonSelector())); - - return mLauncher.getAllApps(); - } - } - /** Opens the Taskbar all apps page with the meta keyboard shortcut. */ public TaskbarAllApps openAllAppsFromKeyboardShortcut() { try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) { diff --git a/tests/tapl/com/android/launcher3/tapl/TestHelpers.java b/tests/tapl/com/android/launcher3/tapl/TestHelpers.java index b43bfcfe50..7f6062f72a 100644 --- a/tests/tapl/com/android/launcher3/tapl/TestHelpers.java +++ b/tests/tapl/com/android/launcher3/tapl/TestHelpers.java @@ -33,9 +33,10 @@ import android.util.Log; import androidx.test.uiautomator.SearchCondition; import androidx.test.uiautomator.UiDevice; +import org.junit.Assert; + import java.util.Date; import java.util.List; -import java.util.Objects; public class TestHelpers { @@ -112,8 +113,8 @@ public class TestHelpers { } private static String checkCrash(Context context, String label, long startTime) { - DropBoxManager dropbox = Objects.requireNonNull( - context.getSystemService(DropBoxManager.class)); + DropBoxManager dropbox = (DropBoxManager) context.getSystemService(Context.DROPBOX_SERVICE); + Assert.assertNotNull("Unable access the DropBoxManager service", dropbox); long timestamp = startTime; DropBoxManager.Entry entry; diff --git a/tests/tapl/com/android/launcher3/tapl/Widgets.java b/tests/tapl/com/android/launcher3/tapl/Widgets.java index ac2748e6a1..6387b05cc8 100644 --- a/tests/tapl/com/android/launcher3/tapl/Widgets.java +++ b/tests/tapl/com/android/launcher3/tapl/Widgets.java @@ -19,7 +19,6 @@ package com.android.launcher3.tapl; import static com.android.launcher3.tapl.LauncherInstrumentation.WAIT_TIME_MS; import static com.android.launcher3.tapl.LauncherInstrumentation.log; -import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.Rect; @@ -32,7 +31,6 @@ import androidx.test.uiautomator.Until; import com.android.launcher3.testing.shared.TestProtocol; import java.util.Collection; -import java.util.List; /** * All widgets container. @@ -118,8 +116,8 @@ public final class Widgets extends LauncherInstrumentation.VisibleContainer } /** Get widget with supplied text. */ - public Widget getWidget(CharSequence labelText) { - return getWidget(labelText.toString(), null); + public Widget getWidget(String labelText) { + return getWidget(labelText, null); } /** Get widget with supplied text and app package */ @@ -130,10 +128,8 @@ public final class Widgets extends LauncherInstrumentation.VisibleContainer final UiObject2 searchBar = findSearchBar(); final int searchBarHeight = searchBar.getVisibleBounds().height(); final UiObject2 fullWidgetsPicker = verifyActiveContainer(); - - // Widget picker may not be scrollable if there are few items. Instead of waiting on - // picker being scrollable, we wait on widget headers to be available. - waitForWidgetListItems(fullWidgetsPicker); + mLauncher.assertTrue("Widgets container didn't become scrollable", + fullWidgetsPicker.wait(Until.scrollable(true), WAIT_TIME_MS)); final UiObject2 widgetsContainer = findTestAppWidgetsTableContainer(testAppWidgetPackage); @@ -180,13 +176,6 @@ public final class Widgets extends LauncherInstrumentation.VisibleContainer } } - private void waitForWidgetListItems(UiObject2 fullWidgetsPicker) { - List headers = fullWidgetsPicker.wait(Until.findObjects( - By.res(mLauncher.getLauncherPackageName(), "widgets_list_header")), WAIT_TIME_MS); - mLauncher.assertTrue("Widgets list is not available", - headers != null && !headers.isEmpty()); - } - private UiObject2 findSearchBar() { final BySelector searchBarContainerSelector = By.res(mLauncher.getLauncherPackageName(), "search_and_recommendations_container"); @@ -210,38 +199,19 @@ public final class Widgets extends LauncherInstrumentation.VisibleContainer "container"); String packageName = mLauncher.getContext().getPackageName(); - String packageNameToFind = - (testAppWidgetPackage == null || testAppWidgetPackage.isEmpty()) ? packageName - : testAppWidgetPackage; - final BySelector targetAppSelector = By .clazz("android.widget.TextView") - .text(packageNameToFind); - final BySelector expandListButtonSelector = - By.res(mLauncher.getLauncherPackageName(), "widget_list_expand_button"); + .text((testAppWidgetPackage == null || testAppWidgetPackage.isEmpty()) + ? packageName + : testAppWidgetPackage); final BySelector widgetsContainerSelector = By.res(mLauncher.getLauncherPackageName(), "widgets_table"); boolean hasHeaderExpanded = false; - // List was expanded by clicking "Show all" button. - boolean hasListExpanded = false; - int scrollDistance = 0; for (int i = 0; i < SCROLL_ATTEMPTS; i++) { UiObject2 widgetPicker = mLauncher.waitForLauncherObject(widgetPickerSelector); UiObject2 widgetListView = verifyActiveContainer(); - - // Press "Show all" button if it exists. Otherwise, keep scrolling to - // find the header or show all button. - UiObject2 expandListButton = - mLauncher.findObjectInContainer(widgetListView, expandListButtonSelector); - if (expandListButton != null) { - expandListButton.click(); - hasListExpanded = true; - i = -1; - continue; - } - UiObject2 header = mLauncher.waitForObjectInContainer(widgetListView, headerSelector); // If a header is barely visible in the bottom edge of the screen, its height could be @@ -252,17 +222,6 @@ public final class Widgets extends LauncherInstrumentation.VisibleContainer // Look for a header that has the test app name. UiObject2 headerTitle = mLauncher.findObjectInContainer(widgetListView, targetAppSelector); - - final UiObject2 searchBar = findSearchBar(); - // If header's title is under or above search bar, let's not process the header yet, - // scroll a bit more to bring the header into visible area. - if (headerTitle != null - && headerTitle.getVisibleCenter().y <= searchBar.getVisibleCenter().y) { - log("Test app's header is behind the searchbar, scrolling up"); - mLauncher.scrollUpByDistance(widgetListView, scrollDistance); - continue; - } - if (headerTitle != null) { // If we find the header and it has not been expanded, let's click it to see the // widgets list. Note that we wait until the header is out of the gesture region at @@ -299,24 +258,11 @@ public final class Widgets extends LauncherInstrumentation.VisibleContainer widgetPicker, widgetsContainerSelector); - if (hasListExpanded && packageNameToFind.compareToIgnoreCase( - getFirstHeaderTitle(widgetListView)) < 0) { - mLauncher.scrollUpByDistance(hasHeaderExpanded && rightPane != null - ? rightPane - : widgetListView, scrollDistance); - } else { - mLauncher.scrollDownByDistance(hasHeaderExpanded && rightPane != null - ? rightPane - : widgetListView, scrollDistance); - } + mLauncher.scrollDownByDistance(hasHeaderExpanded && rightPane != null + ? rightPane + : widgetListView, scrollDistance); } return null; } - - @NonNull - private String getFirstHeaderTitle(UiObject2 widgetListView) { - UiObject2 firstHeader = mLauncher.getObjectsInContainer(widgetListView, "app_title").get(0); - return firstHeader != null ? firstHeader.getText() : ""; - } } diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java index 68c7049a11..9ac6768f42 100644 --- a/tests/tapl/com/android/launcher3/tapl/Workspace.java +++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java @@ -25,14 +25,16 @@ import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_SCROLLED; import static com.android.launcher3.testing.shared.TestProtocol.ALL_APPS_STATE_ORDINAL; import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL; import static com.android.launcher3.testing.shared.TestProtocol.OVERVIEW_STATE_ORDINAL; +import static com.android.launcher3.testing.shared.TestProtocol.TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE; +import static com.android.launcher3.testing.shared.TestProtocol.UIOBJECT_STALE_ELEMENT; import static junit.framework.TestCase.assertNotNull; import static junit.framework.TestCase.assertTrue; -import android.content.Intent; import android.graphics.Point; import android.graphics.Rect; import android.os.SystemClock; +import android.util.Log; import android.view.KeyEvent; import android.view.MotionEvent; @@ -45,7 +47,9 @@ import androidx.test.uiautomator.UiDevice; import androidx.test.uiautomator.UiObject2; import androidx.test.uiautomator.Until; +import com.android.launcher3.testing.shared.HotseatCellCenterRequest; import com.android.launcher3.testing.shared.TestProtocol; +import com.android.launcher3.testing.shared.WorkspaceCellCenterRequest; import java.util.List; import java.util.Map; @@ -58,7 +62,7 @@ import java.util.stream.Collectors; */ public final class Workspace extends Home { private static final int FLING_STEPS = 10; - private static final int DEFAULT_DRAG_STEPS = 20; + private static final int DEFAULT_DRAG_STEPS = 10; private static final String DROP_BAR_RES_ID = "drop_target_bar"; private static final String DELETE_TARGET_TEXT_ID = "delete_target_text"; private static final String UNINSTALL_TARGET_TEXT_ID = "uninstall_target_text"; @@ -341,35 +345,23 @@ public final class Workspace extends Home { * @return map of text -> center of the view. In case of icons with the same name, the one with * lower x coordinate is selected. */ - public Map getAllWorkspaceIconsPositions() { + public Map getWorkspaceIconsPositions() { final UiObject2 workspace = verifyActiveContainer(); + mLauncher.waitForLauncherInitialized(); // b/319501259 List workspaceIcons = mLauncher.waitForObjectsInContainer(workspace, AppIcon.getAnyAppIconSelector()); - return getIconPositionMap(workspaceIcons); - } - - /** - * @return point where icon is found for given the app name, - * point is visible center of the icon. - */ - @NonNull - public Point getWorkspaceIconPosition(String appName) { - final UiObject2 workspace = verifyActiveContainer(); - - UiObject2 workspaceIcon = - mLauncher.waitForObjectInContainer(workspace, - AppIcon.getAppIconSelector(appName, mLauncher)); - return workspaceIcon.getVisibleCenter(); - } - - private Map getIconPositionMap(List icons) { - return icons.stream() + return workspaceIcons.stream() .collect( Collectors.toMap( /* keyMapper= */ uiObject21 -> { + Log.d(UIOBJECT_STALE_ELEMENT, "keyText: " + + uiObject21.getText()); return uiObject21.getText(); }, /* valueMapper= */ uiObject2 -> { + Log.d(UIOBJECT_STALE_ELEMENT, uiObject2.getText() + + " dispId" + uiObject2.getDisplayId() + + " parent" + uiObject2.getParent()); return uiObject2.getVisibleCenter(); }, /* mergeFunction= */ (p1, p2) -> p1.x < p2.x ? p1 : p2)); @@ -498,23 +490,20 @@ public final class Workspace extends Home { } static Point getCellCenter(LauncherInstrumentation launcher, int cellX, int cellY) { - return getCellCenter(launcher, cellX, cellY, 1, 1); + return launcher.getTestInfo(WorkspaceCellCenterRequest.builder().setCellX(cellX).setCellY( + cellY).build()).getParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD); } static Point getCellCenter(LauncherInstrumentation launcher, int cellX, int cellY, int spanX, int spanY) { - return launcher.getTestInfo( - new Intent(TestProtocol.REQUEST_WORKSPACE_CELL_CENTER) - .putExtra(TestProtocol.TEST_INFO_PARAM_CELL_SPAN, - new Rect(cellX, cellY, cellX + spanX, cellY + spanY))) + return launcher.getTestInfo(WorkspaceCellCenterRequest.builder().setCellX(cellX) + .setCellY(cellY).setSpanX(spanX).setSpanY(spanY).build()) .getParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD); } - static Point getHotseatCellCenter(LauncherInstrumentation launcher, int cellIndex) { - return launcher.getTestInfo( - new Intent(TestProtocol.REQUEST_HOTSEAT_CELL_CENTER) - .putExtra(TestProtocol.TEST_INFO_PARAM_INDEX, cellIndex)) - .getParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD); + static Point getHotseatCellCenter(LauncherInstrumentation launcher, int cellInd) { + return launcher.getTestInfo(HotseatCellCenterRequest.builder() + .setCellInd(cellInd).build()).getParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD); } /** Returns the number of rows and columns in the workspace */ @@ -609,7 +598,7 @@ public final class Workspace extends Home { launcher, launchable, destSupplier, - /* isDecelerating= */ !isDraggingToFolder, + /* isDecelerating= */ false, () -> launcher.expectEvent(TestProtocol.SEQUENCE_MAIN, LONG_CLICK_EVENT), /* expectDropEvents= */ null, /* startsActivity = */ false, @@ -640,6 +629,8 @@ public final class Workspace extends Home { try (LauncherInstrumentation.Closable ignored = launcher.addContextLayer( "want to drag icon to workspace")) { final long downTime = SystemClock.uptimeMillis(); + Log.d(TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE, + "Workspace.dragIconToWorkspace: starting drag | downtime: " + downTime); Point dragStart = launchable.startDrag( downTime, expectLongClickEvents, @@ -682,7 +673,7 @@ public final class Workspace extends Home { launcher.movePointer(dragStart, targetDest, DEFAULT_DRAG_STEPS, isDecelerating, downTime, SystemClock.uptimeMillis(), - !isDraggingToFolder, LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER); + false, LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER); dropDraggedIcon(launcher, targetDest, downTime, expectDropEvents, startsActivity); } @@ -843,9 +834,7 @@ public final class Workspace extends Home { @Override protected String getSwipeHeightRequestName() { - return mLauncher.isRecentsWindowEnabled() - ? super.getSwipeHeightRequestName() - : TestProtocol.REQUEST_HOME_TO_OVERVIEW_SWIPE_HEIGHT; + return TestProtocol.REQUEST_HOME_TO_OVERVIEW_SWIPE_HEIGHT; } @Override diff --git a/tests/tapl/com/android/launcher3/tapl/WorkspaceDragSource.java b/tests/tapl/com/android/launcher3/tapl/WorkspaceDragSource.java index e5a2a2ef6c..b42d43b0db 100644 --- a/tests/tapl/com/android/launcher3/tapl/WorkspaceDragSource.java +++ b/tests/tapl/com/android/launcher3/tapl/WorkspaceDragSource.java @@ -15,7 +15,10 @@ */ package com.android.launcher3.tapl; +import static com.android.launcher3.testing.shared.TestProtocol.TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE; + import android.graphics.Point; +import android.util.Log; import java.util.function.Supplier; @@ -76,6 +79,9 @@ interface WorkspaceDragSource { LauncherInstrumentation.Closable c = launcher.addContextLayer( String.format("want to drag the icon to cell(%d, %d)", cellX, cellY))) { final Supplier dest = () -> Workspace.getCellCenter(launcher, cellX, cellY); + Log.d(TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE, + "WorkspaceDragSource.dragToWorkspace: dragging icon to workspace | dest: " + + dest.get()); Workspace.dragIconToWorkspace( launcher, launchable, diff --git a/wmshell/build.gradle b/wmshell/build.gradle index 883d1c4309..3e1ce19aa9 100644 --- a/wmshell/build.gradle +++ b/wmshell/build.gradle @@ -1,5 +1,3 @@ -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - plugins { alias(libs.plugins.android.library) alias(libs.plugins.kotlin.android) @@ -7,7 +5,6 @@ plugins { } android { - buildToolsVersion "36.1.0" namespace "com.android.wm.shell" buildFeatures { aidl true @@ -17,24 +14,17 @@ android { java.srcDirs = ['shared/src'] aidl.srcDirs = ['shared/src'] manifest.srcFile 'AndroidManifest.xml' - res.srcDirs = ['shared', 'shared/res'] + res.srcDirs = ['shared'] } } } -addFrameworkJar('framework-16.jar') +addFrameworkJar('framework-15.jar') compileOnlyCommonJars() dependencies { - implementation libs.dagger.hilt.android - ksp libs.dagger.hilt.compiler + implementation libs.hilt.android + ksp libs.hilt.compiler implementation libs.androidx.core.animation implementation libs.androidx.dynamicanimation - compileOnly projects.iconloaderlib - compileOnly projects.flags - compileOnly projects.utils -} - -ksp { - arg("dagger.hilt.disableModulesHaveInstallInCheck", "true") } diff --git a/wmshell/res/color/bubble_drop_target_background_color.xml b/wmshell/res/color/bubble_drop_target_background_color.xml index 87d36288cf..ab1ab984fd 100644 --- a/wmshell/res/color/bubble_drop_target_background_color.xml +++ b/wmshell/res/color/bubble_drop_target_background_color.xml @@ -16,5 +16,5 @@ - + \ No newline at end of file diff --git a/wmshell/res/color/desktop_mode_maximize_menu_button_color_selector.xml b/wmshell/res/color/desktop_mode_maximize_menu_button_color_selector.xml index 047f22fe03..640d184e64 100644 --- a/wmshell/res/color/desktop_mode_maximize_menu_button_color_selector.xml +++ b/wmshell/res/color/desktop_mode_maximize_menu_button_color_selector.xml @@ -22,5 +22,5 @@ android:color="?androidprv:attr/colorAccentPrimary"/> - + \ No newline at end of file diff --git a/wmshell/res/drawable/bubble_drop_target_background.xml b/wmshell/res/drawable/bubble_drop_target_background.xml index 20e00e1a9b..b928a0b207 100644 --- a/wmshell/res/drawable/bubble_drop_target_background.xml +++ b/wmshell/res/drawable/bubble_drop_target_background.xml @@ -21,6 +21,6 @@ + android:color="?androidprv:attr/materialColorPrimaryContainer" /> diff --git a/wmshell/res/drawable/bubble_manage_btn_bg.xml b/wmshell/res/drawable/bubble_manage_btn_bg.xml index 5acca45654..657720ee60 100644 --- a/wmshell/res/drawable/bubble_manage_btn_bg.xml +++ b/wmshell/res/drawable/bubble_manage_btn_bg.xml @@ -19,7 +19,7 @@ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:shape="rectangle"> diff --git a/wmshell/res/drawable/bubble_manage_menu_bg.xml b/wmshell/res/drawable/bubble_manage_menu_bg.xml index f4d1de89d7..8fd2e68f64 100644 --- a/wmshell/res/drawable/bubble_manage_menu_bg.xml +++ b/wmshell/res/drawable/bubble_manage_menu_bg.xml @@ -17,7 +17,7 @@ - + + android:pathData="M160,800Q127,800 103.5,776.5Q80,753 80,720L80,240Q80,207 103.5,183.5Q127,160 160,160L800,160Q833,160 856.5,183.5Q880,207 880,240L880,720Q880,753 856.5,776.5Q833,800 800,800L160,800ZM160,720L800,720Q800,720 800,720Q800,720 800,720L800,320L160,320L160,720Q160,720 160,720Q160,720 160,720Z"/> \ No newline at end of file diff --git a/wmshell/res/drawable/decor_handle_dark.xml b/wmshell/res/drawable/decor_handle_dark.xml index 05c1e094d7..ce242751c1 100644 --- a/wmshell/res/drawable/decor_handle_dark.xml +++ b/wmshell/res/drawable/decor_handle_dark.xml @@ -13,10 +13,20 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - - - - - - + + + + + + diff --git a/wmshell/res/drawable/decor_maximize_button_dark.xml b/wmshell/res/drawable/decor_maximize_button_dark.xml index 7b3353462b..ab4e29ac97 100644 --- a/wmshell/res/drawable/decor_maximize_button_dark.xml +++ b/wmshell/res/drawable/decor_maximize_button_dark.xml @@ -17,16 +17,19 @@ + android:viewportWidth="32.0" + android:viewportHeight="32.0" + android:tint="@color/decor_button_dark_color"> + android:translateX="8.0" + android:translateY="8.0" > + android:fillColor="@android:color/white" + android:pathData="M2.0,4.0l0.0,16.0l28.0,0.0L30.0,4.0L2.0,4.0zM26.0,16.0L6.0,16.0L6.0,8.0l20.0,0.0L26.0,16.0z"/> + diff --git a/wmshell/res/drawable/desktop_mode_decor_handle_menu_background.xml b/wmshell/res/drawable/desktop_mode_decor_handle_menu_background.xml index 5769a851a8..15837adc2c 100644 --- a/wmshell/res/drawable/desktop_mode_decor_handle_menu_background.xml +++ b/wmshell/res/drawable/desktop_mode_decor_handle_menu_background.xml @@ -18,5 +18,5 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> - + diff --git a/wmshell/res/drawable/desktop_mode_maximize_menu_background.xml b/wmshell/res/drawable/desktop_mode_maximize_menu_background.xml index bab2c95820..9566f2f140 100644 --- a/wmshell/res/drawable/desktop_mode_maximize_menu_background.xml +++ b/wmshell/res/drawable/desktop_mode_maximize_menu_background.xml @@ -17,6 +17,6 @@ - + diff --git a/wmshell/res/drawable/desktop_mode_maximize_menu_layout_background.xml b/wmshell/res/drawable/desktop_mode_maximize_menu_layout_background.xml index b03b134276..a30cfb74bf 100644 --- a/wmshell/res/drawable/desktop_mode_maximize_menu_layout_background.xml +++ b/wmshell/res/drawable/desktop_mode_maximize_menu_layout_background.xml @@ -20,6 +20,6 @@ android:shape="rectangle"> - - + + \ No newline at end of file diff --git a/wmshell/res/drawable/desktop_windowing_transition_background.xml b/wmshell/res/drawable/desktop_windowing_transition_background.xml index 75ec2ab9f6..4e673e65e0 100644 --- a/wmshell/res/drawable/desktop_windowing_transition_background.xml +++ b/wmshell/res/drawable/desktop_windowing_transition_background.xml @@ -18,15 +18,15 @@ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> - + - + diff --git a/wmshell/res/drawable/letterbox_education_dialog_background.xml b/wmshell/res/drawable/letterbox_education_dialog_background.xml index 527cc31f2f..e7c89d1f9c 100644 --- a/wmshell/res/drawable/letterbox_education_dialog_background.xml +++ b/wmshell/res/drawable/letterbox_education_dialog_background.xml @@ -17,6 +17,6 @@ - + \ No newline at end of file diff --git a/wmshell/res/drawable/letterbox_education_dismiss_button_background_ripple.xml b/wmshell/res/drawable/letterbox_education_dismiss_button_background_ripple.xml index 5336b3a228..72ebef625f 100644 --- a/wmshell/res/drawable/letterbox_education_dismiss_button_background_ripple.xml +++ b/wmshell/res/drawable/letterbox_education_dismiss_button_background_ripple.xml @@ -32,7 +32,7 @@ - + - - - - + android:viewportWidth="45" + android:viewportHeight="44"> + + \ No newline at end of file diff --git a/wmshell/res/drawable/letterbox_education_ic_reposition.xml b/wmshell/res/drawable/letterbox_education_ic_reposition.xml index 29e58a12f5..22a8f39ca6 100644 --- a/wmshell/res/drawable/letterbox_education_ic_reposition.xml +++ b/wmshell/res/drawable/letterbox_education_ic_reposition.xml @@ -15,16 +15,16 @@ ~ limitations under the License. --> + android:fillType="evenOdd" + android:pathData="M4 0C1.79086 0 0 1.79086 0 4V28C0 30.2091 1.79086 32 4 32H36C38.2091 32 40 30.2091 40 28V4C40 1.79086 38.2091 0 36 0H4ZM36 4H4V28H36V4Z" /> + android:fillColor="@color/letterbox_education_text_secondary" + android:pathData="M19.98 8L17.16 10.82L20.32 14L12 14V18H20.32L17.14 21.18L19.98 24L28 16.02L19.98 8Z" /> diff --git a/wmshell/res/drawable/letterbox_education_ic_split_screen.xml b/wmshell/res/drawable/letterbox_education_ic_split_screen.xml index 6a766d37fc..15e65f716b 100644 --- a/wmshell/res/drawable/letterbox_education_ic_split_screen.xml +++ b/wmshell/res/drawable/letterbox_education_ic_split_screen.xml @@ -17,10 +17,10 @@ + android:viewportWidth="40" + android:viewportHeight="32"> + android:pathData="M40 28L40 4C40 1.8 38.2 -7.86805e-08 36 -1.74846e-07L26 -6.11959e-07C23.8 -7.08124e-07 22 1.8 22 4L22 28C22 30.2 23.8 32 26 32L36 32C38.2 32 40 30.2 40 28ZM14 28L4 28L4 4L14 4L14 28ZM18 28L18 4C18 1.8 16.2 -1.04033e-06 14 -1.1365e-06L4 -1.57361e-06C1.8 -1.66978e-06 -7.86805e-08 1.8 -1.74846e-07 4L-1.22392e-06 28C-1.32008e-06 30.2 1.8 32 4 32L14 32C16.2 32 18 30.2 18 28Z" /> \ No newline at end of file diff --git a/wmshell/res/drawable/letterbox_restart_button_background_ripple.xml b/wmshell/res/drawable/letterbox_restart_button_background_ripple.xml index 4e77720bd1..1f12514877 100644 --- a/wmshell/res/drawable/letterbox_restart_button_background_ripple.xml +++ b/wmshell/res/drawable/letterbox_restart_button_background_ripple.xml @@ -32,7 +32,7 @@ - + - + \ No newline at end of file diff --git a/wmshell/res/drawable/letterbox_restart_dismiss_button_background_ripple.xml b/wmshell/res/drawable/letterbox_restart_dismiss_button_background_ripple.xml index d64e63261a..3aa0981e45 100644 --- a/wmshell/res/drawable/letterbox_restart_dismiss_button_background_ripple.xml +++ b/wmshell/res/drawable/letterbox_restart_dismiss_button_background_ripple.xml @@ -32,9 +32,9 @@ - - + - - + android:viewportWidth="45" + android:viewportHeight="44"> + + android:pathData="M0,36V24.5H3V30.85L10.4,23.45L12.55,25.6L5.15,33H11.5V36H0ZM24.5,36V33H30.85L23.5,25.65L25.65,23.5L33,30.85V24.5H36V36H24.5ZM10.35,12.5L3,5.15V11.5H0V0H11.5V3H5.15L12.5,10.35L10.35,12.5ZM25.65,12.5L23.5,10.35L30.85,3H24.5V0H36V11.5H33V5.15L25.65,12.5Z" + android:fillColor="?androidprv:attr/colorAccentPrimaryVariant"/> \ No newline at end of file diff --git a/wmshell/res/layout/bubble_bar_expanded_view.xml b/wmshell/res/layout/bubble_bar_expanded_view.xml index c2755ef6cc..34f03c2f22 100644 --- a/wmshell/res/layout/bubble_bar_expanded_view.xml +++ b/wmshell/res/layout/bubble_bar_expanded_view.xml @@ -19,8 +19,7 @@ android:layout_height="wrap_content" android:layout_width="wrap_content" android:orientation="vertical" - android:clipChildren="false" - android:id="@+id/bubble_expanded_view"> + android:id="@+id/bubble_bar_expanded_view"> - + android:src="@drawable/pip_ic_settings"/> - + \ No newline at end of file diff --git a/wmshell/res/layout/bubble_bar_menu_item.xml b/wmshell/res/layout/bubble_bar_menu_item.xml index 0653d24ee1..ddcd5c60d9 100644 --- a/wmshell/res/layout/bubble_bar_menu_item.xml +++ b/wmshell/res/layout/bubble_bar_menu_item.xml @@ -16,7 +16,6 @@ --> - + \ No newline at end of file diff --git a/wmshell/res/layout/bubble_bar_menu_view.xml b/wmshell/res/layout/bubble_bar_menu_view.xml index 8064e254c0..82e5aee41f 100644 --- a/wmshell/res/layout/bubble_bar_menu_view.xml +++ b/wmshell/res/layout/bubble_bar_menu_view.xml @@ -14,18 +14,19 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License --> - + android:elevation="@dimen/bubble_manage_menu_elevation" + android:paddingTop="@dimen/bubble_bar_manage_menu_padding_top" + android:paddingHorizontal="@dimen/bubble_bar_manage_menu_padding" + android:paddingBottom="@dimen/bubble_bar_manage_menu_padding" + android:clipToPadding="false"> + android:textAppearance="@*android:style/TextAppearance.DeviceDefault" /> - + \ No newline at end of file diff --git a/wmshell/res/layout/bubble_bar_stack_education.xml b/wmshell/res/layout/bubble_bar_stack_education.xml index 96f5cffadf..b489a5c1ac 100644 --- a/wmshell/res/layout/bubble_bar_stack_education.xml +++ b/wmshell/res/layout/bubble_bar_stack_education.xml @@ -14,47 +14,43 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License --> - - + \ No newline at end of file diff --git a/wmshell/res/layout/bubble_flyout.xml b/wmshell/res/layout/bubble_flyout.xml index 2e62ec0a52..65a07a7186 100644 --- a/wmshell/res/layout/bubble_flyout.xml +++ b/wmshell/res/layout/bubble_flyout.xml @@ -49,7 +49,7 @@ android:fontFamily="@*android:string/config_bodyFontFamilyMedium" android:maxLines="1" android:ellipsize="end" - android:textColor="?android:attr/textColorPrimary" + android:textColor="?androidprv:attr/materialColorOnSurface" android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Body2"/> - + \ No newline at end of file diff --git a/wmshell/res/layout/bubble_manage_button.xml b/wmshell/res/layout/bubble_manage_button.xml index f7b117c794..f88d63d796 100644 --- a/wmshell/res/layout/bubble_manage_button.xml +++ b/wmshell/res/layout/bubble_manage_button.xml @@ -28,6 +28,6 @@ android:focusable="true" android:text="@string/manage_bubbles_text" android:textSize="@*android:dimen/text_size_body_2_material" - android:textColor="?android:attr/textColorPrimary" + android:textColor="?androidprv:attr/materialColorOnSurface" android:background="@drawable/bubble_manage_btn_bg" - /> + /> \ No newline at end of file diff --git a/wmshell/res/layout/bubble_manage_menu.xml b/wmshell/res/layout/bubble_manage_menu.xml index 27cca1e57a..d8ae9c8c64 100644 --- a/wmshell/res/layout/bubble_manage_menu.xml +++ b/wmshell/res/layout/bubble_manage_menu.xml @@ -40,11 +40,10 @@ android:tint="@color/bubbles_icon_tint"/> @@ -68,11 +67,10 @@ android:tint="@color/bubbles_icon_tint"/> @@ -100,40 +98,9 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="16dp" - android:textColor="?android:attr/textColorPrimary" + android:textColor="?androidprv:attr/materialColorOnSurface" android:textAppearance="@*android:style/TextAppearance.DeviceDefault" /> - - - - - - - - - - + \ No newline at end of file diff --git a/wmshell/res/layout/caption_window_decor.xml b/wmshell/res/layout/caption_window_decor.xml index 819d4ab360..f3d2198720 100644 --- a/wmshell/res/layout/caption_window_decor.xml +++ b/wmshell/res/layout/caption_window_decor.xml @@ -37,17 +37,20 @@ style="@style/CaptionButtonStyle" android:id="@+id/minimize_window" android:layout_gravity="center_vertical|end" + android:contentDescription="@string/minimize_button_text" android:background="@drawable/decor_minimize_button_dark" android:duplicateParentState="true"/>