From 270030e5b112b4c50a2ed59916fa2859cbac1e8f Mon Sep 17 00:00:00 2001 From: Pun Butrach Date: Sat, 10 Jan 2026 22:36:39 +0700 Subject: [PATCH] fix(post-clean): Cleanup mixed branch status Signed-off-by: Pun Butrach --- aconfig/launcher_accessibility.aconfig | 12 - docs/assets/badge-github.png | Bin 6930 -> 0 bytes docs/assets/badge-google-play.png | Bin 11693 -> 0 bytes docs/assets/badge-izzyondroid.png | Bin 24397 -> 0 bytes docs/assets/badge-obtainium.png | Bin 16050 -> 0 bytes docs/assets/device-frame.png | Bin 601521 -> 0 bytes lawnchair/res/drawable/ic_work_app_badge.xml | 42 - lawnchair/res/font/inter_bold.ttf | Bin 293448 -> 0 bytes lawnchair/res/font/inter_medium.ttf | Bin 292140 -> 0 bytes lawnchair/res/font/inter_regular.ttf | Bin 287928 -> 0 bytes lawnchair/res/font/inter_semi_bold.ttf | Bin 293028 -> 0 bytes .../lawnchair/LauncherActivityCachingLogic.kt | 23 - .../src/app/lawnchair/qsb/providers/Mull.kt | 24 - .../engine/provider/AppSearchProvider.kt | 61 - .../components/DraggablePreference.kt | 348 ----- .../res/drawable/app_chip_menu_item_fg.xml | 20 - .../res/drawable/bg_bubble_dismiss_circle.xml | 27 - quickstep/res/drawable/bg_circle.xml | 20 - .../bg_overview_add_desktop_button.xml | 27 - .../drawable/bg_overview_clear_all_button.xml | 27 - .../res/drawable/ic_bubble_dismiss_white.xml | 25 - quickstep/res/drawable/ic_close_option.xml | 26 - quickstep/res/drawable/ic_empty_recents.xml | 30 - ...uick_switch_overview_button_background.xml | 21 - .../res/layout/gesture_tutorial_dialog.xml | 88 -- ...eyboard_quick_switch_textonly_taskview.xml | 64 - .../res/layout/task_thumbnail_view_header.xml | 72 - .../res/layout/widget_picker_activity.xml | 24 - 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 - .../launcher3/WidgetPickerActivity.java | 377 ----- .../hybridhotseat/HotseatPredictionModel.java | 65 - .../launcher3/model/PredictionHelper.java | 133 -- .../SecondaryDisplayPredictionsImpl.java | 55 - .../DesktopVisibilityController.java | 358 ----- .../launcher3/taskbar/TaskbarManager.java | 686 --------- ...TaskbarAllAppsFallbackSearchContainer.java | 54 - .../bubbles/BubbleStashController.java | 481 ------ .../BaseRecentsViewStateController.java | 161 -- .../uioverrides/QuickstepAppWidgetHost.java | 74 - .../RecentsViewStateController.java | 185 --- .../TaskViewTouchController.java | 395 ----- .../LauncherSearchIndexablesProvider.java | 93 -- .../quickstep/OverviewCommandHelper.java | 525 ------- .../com/android/quickstep/TaskIconCache.java | 296 ---- .../android/quickstep/TaskThumbnailCache.java | 279 ---- .../fallback/RecentsTaskController.java | 36 - .../fallback/window/RecentsDisplayModel.kt | 128 -- .../ResetGestureInputConsumer.java | 55 - .../interaction/TutorialStepIndicator.java | 118 -- .../orientation/PortraitPagedViewHandler.java | 820 ---------- .../task/thumbnail/TaskThumbnailViewModel.kt | 91 -- .../quickstep/task/viewmodel/TaskViewData.kt | 24 - .../util/ActiveGestureErrorDetector.java | 454 ------ .../quickstep/util/ActiveGestureLog.java | 376 ----- .../quickstep/util/ActivityInitListener.java | 73 - .../quickstep/util/AssistStateManager.java | 93 -- .../android/quickstep/util/AssistUtils.java | 45 - .../android/quickstep/util/DesktopTask.java | 71 - .../com/android/quickstep/util/GroupTask.java | 94 -- .../quickstep/util/TaskGridNavHelper.java | 136 -- .../quickstep/views/ClearAllButton.java | 345 ----- .../views/DigitalWellBeingToast.java | 443 ------ .../quickstep/views/IconAppChipView.java | 464 ------ .../com/android/quickstep/views/IconView.java | 209 --- .../android/quickstep/views/TaskMenuView.java | 435 ------ .../views/TaskThumbnailViewHeader.kt | 41 - .../bubbles/testing/FakeBubbleViewFactory.kt | 99 -- .../launcher3/taskbar/TaskbarUnitTestRule.kt | 164 -- .../animation/BubbleBarViewAnimatorTest.kt | 454 ------ .../recents/window/RecentsDisplayModelTest.kt | 118 -- .../thumbnail/TaskThumbnailViewModelTest.kt | 178 --- .../util/RecentOrientedStateHandlerTests.kt | 166 -- .../util/RecentsOrientedStateTest.java | 90 -- .../quickstep/util/TaskGridNavHelperTest.java | 510 ------ .../model/QuickstepModelDelegateTest.kt | 127 -- .../model/WidgetsPredictionsRequesterTest.kt | 221 --- .../DesktopVisibilityControllerTest.kt | 80 - .../FallbackTaskbarUIControllerTest.kt | 80 - .../launcher3/taskbar/TaskbarBaseTestCase.kt | 103 -- .../TaskbarHoverToolTipControllerTest.java | 219 --- .../taskbar/TaskbarKeyguardControllerTest.kt | 111 -- .../TaskbarRecentAppsControllerTest.kt | 242 --- .../quickstep/RecentTasksListTest.java | 107 -- .../RecentsAnimationDeviceStateTest.kt | 205 --- .../android/quickstep/RecentsModelTest.java | 173 --- .../TaplDigitalWellBeingToastTest.java | 109 -- .../quickstep/TaskThumbnailCacheTest.java | 95 -- .../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 - .../material_color_surface_dim.xml | 19 - .../material_color_surface_inverse.xml | 19 - .../material_color_surface_variant.xml | 19 - .../popup_color_background.xml | 20 - ...dget_picker_primary_surface_color_dark.xml | 20 - 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 - 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 - ...get_picker_primary_surface_color_light.xml | 20 - res/color/popup_color_background.xml | 18 - .../ic_transient_taskbar_all_apps_button.xml | 48 - res/drawable/bg_ps_header.xml | 26 - res/drawable/horizontal_ellipsis.xml | 27 - res/drawable/ic_mull.xml | 59 - res/drawable/ic_mull_tinted.xml | 46 - res/drawable/ic_taskbar_all_apps_button.xml | 48 - .../ic_transient_taskbar_all_apps_button.xml | 48 - res/layout/page_indicator_dots.xml | 22 - res/values-v33/style.xml | 40 - res/xml/folder_shapes.xml | 33 - .../launcher3/BaseDraggingActivity.java | 192 --- src/com/android/launcher3/LauncherModel.java | 724 --------- .../AccessibleDragListenerAdapter.java | 89 -- .../launcher3/allapps/WorkModeSwitch.java | 235 --- .../dagger/ActivityContextScope.java | 32 - .../launcher3/dagger/ApplicationContext.java | 32 - .../dagger/LauncherAppSingleton.java | 32 - .../dragndrop/SpringLoadedDragController.java | 75 - .../graphics/GridCustomizationsProvider.java | 270 ---- .../android/launcher3/graphics/IconShape.java | 513 ------ .../launcher3/icons/ComponentWithLabel.java | 75 - .../icons/ComponentWithLabelAndIcon.java | 58 - .../icons/LauncherActivityCachingLogic.java | 74 - .../launcher3/icons/LauncherIcons.java | 132 -- .../icons/MonochromeIconFactory.java | 172 --- .../launcher3/icons/ShortcutCachingLogic.java | 116 -- .../launcher3/logging/StatsLogManager.java | 1064 ------------- .../model/GridSizeMigrationUtil.java | 815 ---------- .../launcher3/model/ShortcutsChangedTask.java | 136 -- .../model/WidgetsFilterDataProvider.kt | 53 - .../WorkspacePageIndicator.java | 265 ---- .../launcher3/provider/LauncherDbUtils.java | 246 --- .../SecondaryDisplayPredictions.java | 48 - .../android/launcher3/states/HintState.java | 72 - .../launcher3/util/ActivityTracker.java | 117 -- .../util/LauncherBindableItemsContainer.java | 124 -- .../launcher3/util/LooperExecutor.java | 118 -- .../util/window/RefreshRateTracker.java | 83 - .../launcher3/views/ComposeInitializer.java | 229 --- systemUI/anim/.gitignore | 9 - systemUI/anim/Android.bp | 73 - systemUI/anim/AndroidManifest.xml | 22 - systemUI/anim/build.gradle | 30 - systemUI/anim/lib/Android.bp | 68 - systemUI/anim/lib/OWNERS | 3 - .../animation/OriginRemoteTransition.java | 516 ------- .../animation/OriginTransitionSession.java | 333 ---- .../animation/SurfaceUIComponent.java | 175 --- .../systemui/animation/Transactions.java | 87 -- .../systemui/animation/UIComponent.java | 86 -- .../systemui/animation/ViewUIComponent.java | 388 ----- .../server/IOriginTransitionsImpl.java | 471 ------ .../animation/shared/IOriginTransitions.aidl | 44 - systemUI/anim/lib/tests/Android.bp | 47 - systemUI/anim/lib/tests/AndroidManifest.xml | 28 - systemUI/anim/lib/tests/AndroidTest.xml | 39 - .../OriginTransitionSessionTest.java | 406 ----- .../anim/res/anim/launch_dialog_enter.xml | 23 - systemUI/anim/res/anim/launch_dialog_exit.xml | 22 - systemUI/anim/res/values/ids.xml | 29 - systemUI/anim/res/values/styles.xml | 24 - .../animation/ActivityTransitionAnimator.kt | 1370 ----------------- .../animation/AnimationFeatureFlags.kt | 6 - .../animation/ComposableControllerFactory.kt | 60 - .../DelegateTransitionAnimatorController.kt | 26 - .../animation/DialogTransitionAnimator.kt | 1103 ------------- .../android/systemui/animation/Expandable.kt | 102 -- .../systemui/animation/FontInterpolator.kt | 238 --- .../systemui/animation/FontVariationUtils.kt | 59 - .../com/android/systemui/animation/GSFAxes.kt | 99 -- ...GhostedViewTransitionAnimatorController.kt | 532 ------- .../animation/IViewTransitionRegistry.kt | 56 - .../systemui/animation/LaunchableView.kt | 96 -- .../animation/RemoteAnimationDelegate.kt | 30 - .../RemoteAnimationRunnerCompat.java | 274 ---- .../RemoteAnimationTargetCompat.java | 86 -- .../systemui/animation/ShadeInterpolation.kt | 39 - .../systemui/animation/TextAnimator.kt | 409 ----- .../systemui/animation/TextInterpolator.kt | 531 ------- .../systemui/animation/TransitionAnimator.kt | 576 ------- .../ViewDialogTransitionAnimatorController.kt | 128 -- .../animation/ViewHierarchyAnimator.kt | 1160 -------------- .../systemui/animation/ViewRootSync.kt | 43 - .../animation/ViewTransitionRegistry.kt | 166 -- .../systemui/animation/ViewTransitionToken.kt | 34 - .../animation/back/BackAnimationSpec.kt | 74 - .../back/BackAnimationSpecForSysUi.kt | 83 - .../animation/back/BackTransformation.kt | 63 - .../back/BottomsheetBackAnimationSpec.kt | 42 - .../back/FlingOnBackAnimationCallback.kt | 186 --- .../back/OnBackAnimationCallbackExtension.kt | 107 -- .../animation/view/LaunchableFrameLayout.kt | 55 - .../animation/view/LaunchableImageView.kt | 56 - .../animation/view/LaunchableLinearLayout.kt | 55 - .../animation/view/LaunchableTextView.kt | 49 - .../surfaceeffects/PaintDrawCallback.kt | 53 - .../RenderEffectDrawCallback.kt | 39 - .../glowboxeffect/GlowBoxConfig.kt | 46 - .../glowboxeffect/GlowBoxEffect.kt | 185 --- .../glowboxeffect/GlowBoxShader.kt | 58 - .../loadingeffect/LoadingEffect.kt | 366 ----- .../loadingeffect/LoadingEffectView.kt | 51 - .../ripple/MultiRippleController.kt | 49 - .../surfaceeffects/ripple/MultiRippleView.kt | 61 - .../surfaceeffects/ripple/RippleAnimation.kt | 93 -- .../ripple/RippleAnimationConfig.kt | 29 - .../surfaceeffects/ripple/RippleShader.kt | 460 ------ .../surfaceeffects/ripple/RippleView.kt | 252 --- .../shaders/SolidColorShader.kt | 36 - .../surfaceeffects/shaders/SparkleShader.kt | 115 -- .../shaderutil/SdfShaderLibrary.kt | 128 -- .../shaderutil/ShaderUtilLibrary.kt | 201 --- .../TurbulenceNoiseAnimationConfig.kt | 89 -- .../TurbulenceNoiseController.kt | 115 -- .../turbulencenoise/TurbulenceNoiseShader.kt | 294 ---- .../turbulencenoise/TurbulenceNoiseView.kt | 236 --- .../surfaceeffects/utils/MathUtils.kt | 24 - .../systemui/util/AnimatorExtensions.kt | 80 - .../src/com/android/systemui/util/Dialog.kt | 165 -- .../com/android/systemui/util/Dimension.kt | 32 - .../systemui/plugins/clocks/AlarmData.kt | 6 - .../plugins/clocks/ClockProviderPlugin.kt | 122 -- .../systemui/plugins/clocks/WeatherData.kt | 123 -- .../systemui/plugins/clocks/ZenData.kt | 22 - .../systemui/shared/condition/Condition.java | 306 ---- .../flagged_result5x5to5x8.db | Bin 77824 -> 0 bytes .../shared/AndroidManifest.xml | 21 - .../shared/HotseatCellCenterRequest.java | 99 -- .../testing/shared/ResourceUtils.java | 85 - .../shared/TestInformationRequest.java | 29 - .../testing/shared/TestProtocol.java | 194 --- .../shared/WorkspaceCellCenterRequest.java | 138 -- .../celllayout/FavoriteItemsTransaction.java | 103 -- .../launcher3/celllayout/board/CellType.java | 32 - .../celllayout/board/FolderPoint.java | 37 - .../launcher3/celllayout/board/IconPoint.java | 45 - .../board/TestWorkspaceBuilder.java | 192 --- .../celllayout/board/WidgetRect.java | 59 - .../launcher3/model/FactitiousDbController.kt | 60 - .../model/GridSizeMigrationUtilTest.kt | 772 ---------- .../settings/SettingsActivityTest.java | 143 -- .../android/launcher3/ui/TestViewHelpers.java | 66 - .../util/PackageManagerHelperTest.java | 86 -- .../com/android/launcher3/testing/OWNERS | 4 - .../allapps/PrivateProfileManagerTest.java | 222 --- .../allapps/PrivateSpaceHeaderViewTest.java | 467 ------ .../PrivateSpaceSettingsButtonTest.java | 56 - .../allapps/TaplKeyboardFocusTest.java | 108 -- .../celllayout/TaplReorderWidgetsTest.java | 312 ---- .../celllayout/integrationtest/TestUtils.kt | 75 - .../integrationtest/events/EventWaiter.kt | 52 - .../integrationtest/events/EventsRule.kt | 98 -- .../ValidGridMigrationTestCaseGenerator.kt | 175 --- .../compat/TaplPromiseIconUiTest.java | 175 --- .../folder/FolderNameProviderTest.java | 85 - .../folder/PreviewBackgroundTest.java | 325 ---- .../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/LoaderCursorTest.java | 236 --- .../PackageInstallStateChangedTaskTest.java | 122 -- .../model/WorkspaceItemProcessorTest.kt | 847 ---------- .../model/WorkspaceItemSpaceFinderTest.kt | 179 --- .../model/gridmigration/GridMigrationUtils.kt | 114 -- .../ValidGridMigrationUnitTest.kt | 203 --- .../launcher3/provider/RestoreDbTaskTest.java | 417 ----- .../testcomponent/TouchEventGenerator.java | 275 ---- .../touch/SingleAxisSwipeDetectorTest.java | 200 --- .../launcher3/ui/AbstractLauncherUiTest.java | 709 --------- .../launcher3/ui/BaseLauncherTaplTest.java | 531 ------- .../ui/DoubleShadowIconDrawableTest.kt | 86 -- .../launcher3/ui/PortraitLandscapeRunner.java | 101 -- .../android/launcher3/ui/ShadowInfoTest.kt | 80 - .../launcher3/ui/TaplWorkProfileTest.java | 234 --- .../android/launcher3/ui/WorkProfileTest.java | 212 --- .../ui/widget/AddConfigWidgetTest.java | 188 --- .../launcher3/ui/widget/BindWidgetTest.java | 325 ---- .../ui/widget/RequestPinItemTest.java | 192 --- .../ui/widget/TaplAddConfigWidgetTest.java | 173 --- .../ui/widget/TaplAddWidgetTest.java | 125 -- .../ui/widget/TaplBindWidgetTest.java | 319 ---- .../ui/widget/TaplRequestPinItemTest.java | 195 --- .../ui/widget/TaplWidgetPickerTest.java | 86 -- .../launcher3/ui/widget/WidgetPickerTest.java | 83 - .../ui/workspace/TaplThemeIconsTest.java | 177 --- .../workspace/TaplTwoPanelWorkspaceTest.java | 381 ----- .../ui/workspace/TaplWorkspaceTest.java | 146 -- .../ui/workspace/ThemeIconsTest.java | 174 --- .../src/com/android/launcher3/util/Wait.java | 66 - .../rule/ExtendedLongPressTimeoutRule.java | 74 - .../launcher3/util/rule/ViewCaptureRule.kt | 168 -- .../AlphaJumpDetector.java | 184 --- .../viewcapture_analysis/AnomalyDetector.java | 85 - .../viewcapture_analysis/FlashDetector.java | 180 --- .../PositionJumpDetector.java | 126 -- .../ViewCaptureAnalyzer.java | 315 ---- .../launcher3/tapl/OverviewTaskMenuItem.java | 39 - .../DesktopModeTransitionSource.aidl | 19 - .../DesktopModeTransitionSource.kt | 54 - .../common/magnetictarget/MagnetizedObject.kt | 701 --------- .../shell/shared/IHomeTransitionListener.aidl | 33 - .../wm/shell/shared/IShellTransitions.aidl | 62 - 328 files changed, 50906 deletions(-) delete mode 100644 aconfig/launcher_accessibility.aconfig delete mode 100644 docs/assets/badge-github.png delete mode 100644 docs/assets/badge-google-play.png delete mode 100644 docs/assets/badge-izzyondroid.png delete mode 100644 docs/assets/badge-obtainium.png delete mode 100644 docs/assets/device-frame.png delete mode 100644 lawnchair/res/drawable/ic_work_app_badge.xml delete mode 100644 lawnchair/res/font/inter_bold.ttf delete mode 100644 lawnchair/res/font/inter_medium.ttf delete mode 100644 lawnchair/res/font/inter_regular.ttf delete mode 100644 lawnchair/res/font/inter_semi_bold.ttf delete mode 100644 lawnchair/src/app/lawnchair/LauncherActivityCachingLogic.kt delete mode 100644 lawnchair/src/app/lawnchair/qsb/providers/Mull.kt delete mode 100644 lawnchair/src/app/lawnchair/search/algorithms/engine/provider/AppSearchProvider.kt delete mode 100644 lawnchair/src/app/lawnchair/ui/preferences/components/DraggablePreference.kt delete mode 100644 quickstep/res/drawable/app_chip_menu_item_fg.xml delete mode 100644 quickstep/res/drawable/bg_bubble_dismiss_circle.xml delete mode 100644 quickstep/res/drawable/bg_circle.xml delete mode 100644 quickstep/res/drawable/bg_overview_add_desktop_button.xml delete mode 100644 quickstep/res/drawable/bg_overview_clear_all_button.xml delete mode 100644 quickstep/res/drawable/ic_bubble_dismiss_white.xml delete mode 100644 quickstep/res/drawable/ic_close_option.xml delete mode 100644 quickstep/res/drawable/ic_empty_recents.xml delete mode 100644 quickstep/res/drawable/keyboard_quick_switch_overview_button_background.xml delete mode 100644 quickstep/res/layout/gesture_tutorial_dialog.xml delete mode 100644 quickstep/res/layout/keyboard_quick_switch_textonly_taskview.xml delete mode 100644 quickstep/res/layout/task_thumbnail_view_header.xml delete mode 100644 quickstep/res/layout/widget_picker_activity.xml delete mode 100644 quickstep/res/raw-h480dp/all_set_page_bg.json delete mode 100644 quickstep/res/raw-land/all_set_page_bg.json delete mode 100644 quickstep/res/raw-sw600dp-land/all_set_page_bg.json delete mode 100644 quickstep/res/raw-sw600dp/all_set_page_bg.json delete mode 100644 quickstep/res/raw-sw720dp-land/all_set_page_bg.json delete mode 100644 quickstep/res/raw-sw720dp/all_set_page_bg.json delete mode 100644 quickstep/src/com/android/launcher3/WidgetPickerActivity.java delete mode 100644 quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java delete mode 100644 quickstep/src/com/android/launcher3/model/PredictionHelper.java delete mode 100644 quickstep/src/com/android/launcher3/secondarydisplay/SecondaryDisplayPredictionsImpl.java delete mode 100644 quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java delete mode 100644 quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java delete mode 100644 quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsFallbackSearchContainer.java delete mode 100644 quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java delete mode 100644 quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java delete mode 100644 quickstep/src/com/android/launcher3/uioverrides/QuickstepAppWidgetHost.java delete mode 100644 quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java delete mode 100644 quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java delete mode 100644 quickstep/src/com/android/quickstep/LauncherSearchIndexablesProvider.java delete mode 100644 quickstep/src/com/android/quickstep/OverviewCommandHelper.java delete mode 100644 quickstep/src/com/android/quickstep/TaskIconCache.java delete mode 100644 quickstep/src/com/android/quickstep/TaskThumbnailCache.java delete mode 100644 quickstep/src/com/android/quickstep/fallback/RecentsTaskController.java delete mode 100644 quickstep/src/com/android/quickstep/fallback/window/RecentsDisplayModel.kt delete mode 100644 quickstep/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java delete mode 100644 quickstep/src/com/android/quickstep/interaction/TutorialStepIndicator.java delete mode 100644 quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java delete mode 100644 quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModel.kt delete mode 100644 quickstep/src/com/android/quickstep/task/viewmodel/TaskViewData.kt delete mode 100644 quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java delete mode 100644 quickstep/src/com/android/quickstep/util/ActiveGestureLog.java delete mode 100644 quickstep/src/com/android/quickstep/util/ActivityInitListener.java delete mode 100644 quickstep/src/com/android/quickstep/util/AssistStateManager.java delete mode 100644 quickstep/src/com/android/quickstep/util/AssistUtils.java delete mode 100644 quickstep/src/com/android/quickstep/util/DesktopTask.java delete mode 100644 quickstep/src/com/android/quickstep/util/GroupTask.java delete mode 100644 quickstep/src/com/android/quickstep/util/TaskGridNavHelper.java delete mode 100644 quickstep/src/com/android/quickstep/views/ClearAllButton.java delete mode 100644 quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java delete mode 100644 quickstep/src/com/android/quickstep/views/IconAppChipView.java delete mode 100644 quickstep/src/com/android/quickstep/views/IconView.java delete mode 100644 quickstep/src/com/android/quickstep/views/TaskMenuView.java delete mode 100644 quickstep/src/com/android/quickstep/views/TaskThumbnailViewHeader.kt delete mode 100644 quickstep/testing/com/android/launcher3/taskbar/bubbles/testing/FakeBubbleViewFactory.kt delete mode 100644 quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarUnitTestRule.kt delete mode 100644 quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt delete mode 100644 quickstep/tests/multivalentTests/src/com/android/quickstep/recents/window/RecentsDisplayModelTest.kt delete mode 100644 quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelTest.kt delete mode 100644 quickstep/tests/multivalentTests/src/com/android/quickstep/util/RecentOrientedStateHandlerTests.kt delete mode 100644 quickstep/tests/multivalentTests/src/com/android/quickstep/util/RecentsOrientedStateTest.java delete mode 100644 quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskGridNavHelperTest.java delete mode 100644 quickstep/tests/src/com/android/launcher3/model/QuickstepModelDelegateTest.kt delete mode 100644 quickstep/tests/src/com/android/launcher3/model/WidgetsPredictionsRequesterTest.kt delete mode 100644 quickstep/tests/src/com/android/launcher3/statehandlers/DesktopVisibilityControllerTest.kt delete mode 100644 quickstep/tests/src/com/android/launcher3/taskbar/FallbackTaskbarUIControllerTest.kt delete mode 100644 quickstep/tests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt delete mode 100644 quickstep/tests/src/com/android/launcher3/taskbar/TaskbarHoverToolTipControllerTest.java delete mode 100644 quickstep/tests/src/com/android/launcher3/taskbar/TaskbarKeyguardControllerTest.kt delete mode 100644 quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt delete mode 100644 quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java delete mode 100644 quickstep/tests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt delete mode 100644 quickstep/tests/src/com/android/quickstep/RecentsModelTest.java delete mode 100644 quickstep/tests/src/com/android/quickstep/TaplDigitalWellBeingToastTest.java delete mode 100644 quickstep/tests/src/com/android/quickstep/TaskThumbnailCacheTest.java delete mode 100644 quickstep/tests/src/com/android/quickstep/taskbar/controllers/TaskbarPinningControllerTest.kt delete mode 100644 quickstep/tests/src/com/android/quickstep/taskbar/customization/TaskbarSpecsEvaluatorTest.kt delete mode 100644 quickstep/tests/src/com/android/quickstep/util/GestureExclusionManagerTest.kt delete mode 100644 res/anim-v33/shared_x_axis_activity_close_enter.xml delete mode 100644 res/anim-v33/shared_x_axis_activity_close_exit.xml delete mode 100644 res/anim-v33/shared_x_axis_activity_open_enter.xml delete mode 100644 res/anim-v33/shared_x_axis_activity_open_exit.xml delete mode 100644 res/color-night-v31/material_color_surface.xml delete mode 100644 res/color-night-v31/material_color_surface_bright.xml delete mode 100644 res/color-night-v31/material_color_surface_container.xml delete mode 100644 res/color-night-v31/material_color_surface_container_high.xml delete mode 100644 res/color-night-v31/material_color_surface_container_highest.xml delete mode 100644 res/color-night-v31/material_color_surface_container_low.xml delete mode 100644 res/color-night-v31/material_color_surface_dim.xml delete mode 100644 res/color-night-v31/material_color_surface_inverse.xml delete mode 100644 res/color-night-v31/material_color_surface_variant.xml delete mode 100644 res/color-night-v31/popup_color_background.xml delete mode 100644 res/color-night-v31/widget_picker_primary_surface_color_dark.xml delete mode 100644 res/color-v31/material_color_surface.xml delete mode 100644 res/color-v31/material_color_surface_bright.xml delete mode 100644 res/color-v31/material_color_surface_container.xml delete mode 100644 res/color-v31/material_color_surface_container_high.xml delete mode 100644 res/color-v31/material_color_surface_container_highest.xml delete mode 100644 res/color-v31/material_color_surface_container_low.xml delete mode 100644 res/color-v31/material_color_surface_dim.xml delete mode 100644 res/color-v31/material_color_surface_inverse.xml delete mode 100644 res/color-v31/material_color_surface_variant.xml delete mode 100644 res/color-v31/popup_color_background.xml delete mode 100644 res/color-v31/widget_picker_primary_surface_color_light.xml delete mode 100644 res/color/popup_color_background.xml delete mode 100644 res/drawable-sw720dp/ic_transient_taskbar_all_apps_button.xml delete mode 100644 res/drawable/bg_ps_header.xml delete mode 100644 res/drawable/horizontal_ellipsis.xml delete mode 100644 res/drawable/ic_mull.xml delete mode 100644 res/drawable/ic_mull_tinted.xml delete mode 100644 res/drawable/ic_taskbar_all_apps_button.xml delete mode 100644 res/drawable/ic_transient_taskbar_all_apps_button.xml delete mode 100644 res/layout/page_indicator_dots.xml delete mode 100644 res/values-v33/style.xml delete mode 100644 res/xml/folder_shapes.xml delete mode 100644 src/com/android/launcher3/BaseDraggingActivity.java delete mode 100644 src/com/android/launcher3/LauncherModel.java delete mode 100644 src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.java delete mode 100644 src/com/android/launcher3/allapps/WorkModeSwitch.java delete mode 100644 src/com/android/launcher3/dagger/ActivityContextScope.java delete mode 100644 src/com/android/launcher3/dagger/ApplicationContext.java delete mode 100644 src/com/android/launcher3/dagger/LauncherAppSingleton.java delete mode 100644 src/com/android/launcher3/dragndrop/SpringLoadedDragController.java delete mode 100644 src/com/android/launcher3/graphics/GridCustomizationsProvider.java delete mode 100644 src/com/android/launcher3/graphics/IconShape.java delete mode 100644 src/com/android/launcher3/icons/ComponentWithLabel.java delete mode 100644 src/com/android/launcher3/icons/ComponentWithLabelAndIcon.java delete mode 100644 src/com/android/launcher3/icons/LauncherActivityCachingLogic.java delete mode 100644 src/com/android/launcher3/icons/LauncherIcons.java delete mode 100644 src/com/android/launcher3/icons/MonochromeIconFactory.java delete mode 100644 src/com/android/launcher3/icons/ShortcutCachingLogic.java delete mode 100644 src/com/android/launcher3/logging/StatsLogManager.java delete mode 100644 src/com/android/launcher3/model/GridSizeMigrationUtil.java delete mode 100644 src/com/android/launcher3/model/ShortcutsChangedTask.java delete mode 100644 src/com/android/launcher3/model/WidgetsFilterDataProvider.kt delete mode 100644 src/com/android/launcher3/pageindicators/WorkspacePageIndicator.java delete mode 100644 src/com/android/launcher3/provider/LauncherDbUtils.java delete mode 100644 src/com/android/launcher3/secondarydisplay/SecondaryDisplayPredictions.java delete mode 100644 src/com/android/launcher3/states/HintState.java delete mode 100644 src/com/android/launcher3/util/ActivityTracker.java delete mode 100644 src/com/android/launcher3/util/LauncherBindableItemsContainer.java delete mode 100644 src/com/android/launcher3/util/LooperExecutor.java delete mode 100644 src/com/android/launcher3/util/window/RefreshRateTracker.java delete mode 100644 src/com/android/launcher3/views/ComposeInitializer.java delete mode 100644 systemUI/anim/.gitignore delete mode 100644 systemUI/anim/Android.bp delete mode 100644 systemUI/anim/AndroidManifest.xml delete mode 100644 systemUI/anim/build.gradle delete mode 100644 systemUI/anim/lib/Android.bp delete mode 100644 systemUI/anim/lib/OWNERS delete mode 100644 systemUI/anim/lib/src/com/android/systemui/animation/OriginRemoteTransition.java delete mode 100644 systemUI/anim/lib/src/com/android/systemui/animation/OriginTransitionSession.java delete mode 100644 systemUI/anim/lib/src/com/android/systemui/animation/SurfaceUIComponent.java delete mode 100644 systemUI/anim/lib/src/com/android/systemui/animation/Transactions.java delete mode 100644 systemUI/anim/lib/src/com/android/systemui/animation/UIComponent.java delete mode 100644 systemUI/anim/lib/src/com/android/systemui/animation/ViewUIComponent.java delete mode 100644 systemUI/anim/lib/src/com/android/systemui/animation/server/IOriginTransitionsImpl.java delete mode 100644 systemUI/anim/lib/src/com/android/systemui/animation/shared/IOriginTransitions.aidl delete mode 100644 systemUI/anim/lib/tests/Android.bp delete mode 100644 systemUI/anim/lib/tests/AndroidManifest.xml delete mode 100644 systemUI/anim/lib/tests/AndroidTest.xml delete mode 100644 systemUI/anim/lib/tests/src/com/android/systemui/animation/OriginTransitionSessionTest.java delete mode 100644 systemUI/anim/res/anim/launch_dialog_enter.xml delete mode 100644 systemUI/anim/res/anim/launch_dialog_exit.xml delete mode 100644 systemUI/anim/res/values/ids.xml delete mode 100644 systemUI/anim/res/values/styles.xml delete mode 100644 systemUI/anim/src/com/android/systemui/animation/ActivityTransitionAnimator.kt delete mode 100644 systemUI/anim/src/com/android/systemui/animation/AnimationFeatureFlags.kt delete mode 100644 systemUI/anim/src/com/android/systemui/animation/ComposableControllerFactory.kt delete mode 100644 systemUI/anim/src/com/android/systemui/animation/DelegateTransitionAnimatorController.kt delete mode 100644 systemUI/anim/src/com/android/systemui/animation/DialogTransitionAnimator.kt delete mode 100644 systemUI/anim/src/com/android/systemui/animation/Expandable.kt delete mode 100644 systemUI/anim/src/com/android/systemui/animation/FontInterpolator.kt delete mode 100644 systemUI/anim/src/com/android/systemui/animation/FontVariationUtils.kt delete mode 100644 systemUI/anim/src/com/android/systemui/animation/GSFAxes.kt delete mode 100644 systemUI/anim/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt delete mode 100644 systemUI/anim/src/com/android/systemui/animation/IViewTransitionRegistry.kt delete mode 100644 systemUI/anim/src/com/android/systemui/animation/LaunchableView.kt delete mode 100644 systemUI/anim/src/com/android/systemui/animation/RemoteAnimationDelegate.kt delete mode 100644 systemUI/anim/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java delete mode 100644 systemUI/anim/src/com/android/systemui/animation/RemoteAnimationTargetCompat.java delete mode 100644 systemUI/anim/src/com/android/systemui/animation/ShadeInterpolation.kt delete mode 100644 systemUI/anim/src/com/android/systemui/animation/TextAnimator.kt delete mode 100644 systemUI/anim/src/com/android/systemui/animation/TextInterpolator.kt delete mode 100644 systemUI/anim/src/com/android/systemui/animation/TransitionAnimator.kt delete mode 100644 systemUI/anim/src/com/android/systemui/animation/ViewDialogTransitionAnimatorController.kt delete mode 100644 systemUI/anim/src/com/android/systemui/animation/ViewHierarchyAnimator.kt delete mode 100644 systemUI/anim/src/com/android/systemui/animation/ViewRootSync.kt delete mode 100644 systemUI/anim/src/com/android/systemui/animation/ViewTransitionRegistry.kt delete mode 100644 systemUI/anim/src/com/android/systemui/animation/ViewTransitionToken.kt delete mode 100644 systemUI/anim/src/com/android/systemui/animation/back/BackAnimationSpec.kt delete mode 100644 systemUI/anim/src/com/android/systemui/animation/back/BackAnimationSpecForSysUi.kt delete mode 100644 systemUI/anim/src/com/android/systemui/animation/back/BackTransformation.kt delete mode 100644 systemUI/anim/src/com/android/systemui/animation/back/BottomsheetBackAnimationSpec.kt delete mode 100644 systemUI/anim/src/com/android/systemui/animation/back/FlingOnBackAnimationCallback.kt delete mode 100644 systemUI/anim/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtension.kt delete mode 100644 systemUI/anim/src/com/android/systemui/animation/view/LaunchableFrameLayout.kt delete mode 100644 systemUI/anim/src/com/android/systemui/animation/view/LaunchableImageView.kt delete mode 100644 systemUI/anim/src/com/android/systemui/animation/view/LaunchableLinearLayout.kt delete mode 100644 systemUI/anim/src/com/android/systemui/animation/view/LaunchableTextView.kt delete mode 100644 systemUI/anim/src/com/android/systemui/surfaceeffects/PaintDrawCallback.kt delete mode 100644 systemUI/anim/src/com/android/systemui/surfaceeffects/RenderEffectDrawCallback.kt delete mode 100644 systemUI/anim/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxConfig.kt delete mode 100644 systemUI/anim/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxEffect.kt delete mode 100644 systemUI/anim/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxShader.kt delete mode 100644 systemUI/anim/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffect.kt delete mode 100644 systemUI/anim/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectView.kt delete mode 100644 systemUI/anim/src/com/android/systemui/surfaceeffects/ripple/MultiRippleController.kt delete mode 100644 systemUI/anim/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt delete mode 100644 systemUI/anim/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt delete mode 100644 systemUI/anim/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt delete mode 100644 systemUI/anim/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt delete mode 100644 systemUI/anim/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt delete mode 100644 systemUI/anim/src/com/android/systemui/surfaceeffects/shaders/SolidColorShader.kt delete mode 100644 systemUI/anim/src/com/android/systemui/surfaceeffects/shaders/SparkleShader.kt delete mode 100644 systemUI/anim/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt delete mode 100644 systemUI/anim/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt delete mode 100644 systemUI/anim/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt delete mode 100644 systemUI/anim/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt delete mode 100644 systemUI/anim/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt delete mode 100644 systemUI/anim/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt delete mode 100644 systemUI/anim/src/com/android/systemui/surfaceeffects/utils/MathUtils.kt delete mode 100644 systemUI/anim/src/com/android/systemui/util/AnimatorExtensions.kt delete mode 100644 systemUI/anim/src/com/android/systemui/util/Dialog.kt delete mode 100644 systemUI/anim/src/com/android/systemui/util/Dimension.kt delete mode 100644 systemUI/plugin/src/com/android/systemui/plugins/clocks/AlarmData.kt delete mode 100644 systemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt delete mode 100644 systemUI/plugin/src/com/android/systemui/plugins/clocks/WeatherData.kt delete mode 100644 systemUI/plugin/src/com/android/systemui/plugins/clocks/ZenData.kt delete mode 100644 systemUI/shared/src/com/android/systemui/shared/condition/Condition.java delete mode 100644 tests/assets/databases/GridMigrationTest/flagged_result5x5to5x8.db delete mode 100644 tests/multivalentTests/shared/AndroidManifest.xml delete mode 100644 tests/multivalentTests/shared/com/android/launcher3/testing/shared/HotseatCellCenterRequest.java delete mode 100644 tests/multivalentTests/shared/com/android/launcher3/testing/shared/ResourceUtils.java delete mode 100644 tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestInformationRequest.java delete mode 100644 tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestProtocol.java delete mode 100644 tests/multivalentTests/shared/com/android/launcher3/testing/shared/WorkspaceCellCenterRequest.java delete mode 100644 tests/multivalentTests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java delete mode 100644 tests/multivalentTests/src/com/android/launcher3/celllayout/board/CellType.java delete mode 100644 tests/multivalentTests/src/com/android/launcher3/celllayout/board/FolderPoint.java delete mode 100644 tests/multivalentTests/src/com/android/launcher3/celllayout/board/IconPoint.java delete mode 100644 tests/multivalentTests/src/com/android/launcher3/celllayout/board/TestWorkspaceBuilder.java delete mode 100644 tests/multivalentTests/src/com/android/launcher3/celllayout/board/WidgetRect.java delete mode 100644 tests/multivalentTests/src/com/android/launcher3/model/FactitiousDbController.kt delete mode 100644 tests/multivalentTests/src/com/android/launcher3/model/GridSizeMigrationUtilTest.kt delete mode 100644 tests/multivalentTests/src/com/android/launcher3/settings/SettingsActivityTest.java delete mode 100644 tests/multivalentTests/src/com/android/launcher3/ui/TestViewHelpers.java delete mode 100644 tests/multivalentTests/src/com/android/launcher3/util/PackageManagerHelperTest.java delete mode 100644 tests/shared/com/android/launcher3/testing/OWNERS delete mode 100644 tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java delete mode 100644 tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewTest.java delete mode 100644 tests/src/com/android/launcher3/allapps/PrivateSpaceSettingsButtonTest.java delete mode 100644 tests/src/com/android/launcher3/allapps/TaplKeyboardFocusTest.java delete mode 100644 tests/src/com/android/launcher3/celllayout/TaplReorderWidgetsTest.java delete mode 100644 tests/src/com/android/launcher3/celllayout/integrationtest/TestUtils.kt delete mode 100644 tests/src/com/android/launcher3/celllayout/integrationtest/events/EventWaiter.kt delete mode 100644 tests/src/com/android/launcher3/celllayout/integrationtest/events/EventsRule.kt delete mode 100644 tests/src/com/android/launcher3/celllayout/testgenerator/ValidGridMigrationTestCaseGenerator.kt delete mode 100644 tests/src/com/android/launcher3/compat/TaplPromiseIconUiTest.java delete mode 100644 tests/src/com/android/launcher3/folder/FolderNameProviderTest.java delete mode 100644 tests/src/com/android/launcher3/folder/PreviewBackgroundTest.java delete mode 100644 tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt delete mode 100644 tests/src/com/android/launcher3/model/AsyncBindingTest.kt delete mode 100644 tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java delete mode 100644 tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java delete mode 100644 tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java delete mode 100644 tests/src/com/android/launcher3/model/FirstScreenBroadcastHelperTest.kt delete mode 100644 tests/src/com/android/launcher3/model/FolderIconLoadTest.kt delete mode 100644 tests/src/com/android/launcher3/model/LoaderCursorTest.java delete mode 100644 tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java delete mode 100644 tests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt delete mode 100644 tests/src/com/android/launcher3/model/WorkspaceItemSpaceFinderTest.kt delete mode 100644 tests/src/com/android/launcher3/model/gridmigration/GridMigrationUtils.kt delete mode 100644 tests/src/com/android/launcher3/model/gridmigration/ValidGridMigrationUnitTest.kt delete mode 100644 tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java delete mode 100644 tests/src/com/android/launcher3/testcomponent/TouchEventGenerator.java delete mode 100644 tests/src/com/android/launcher3/touch/SingleAxisSwipeDetectorTest.java delete mode 100644 tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java delete mode 100644 tests/src/com/android/launcher3/ui/BaseLauncherTaplTest.java delete mode 100644 tests/src/com/android/launcher3/ui/DoubleShadowIconDrawableTest.kt delete mode 100644 tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java delete mode 100644 tests/src/com/android/launcher3/ui/ShadowInfoTest.kt delete mode 100644 tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java delete mode 100644 tests/src/com/android/launcher3/ui/WorkProfileTest.java delete mode 100644 tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java delete mode 100644 tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java delete mode 100644 tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java delete mode 100644 tests/src/com/android/launcher3/ui/widget/TaplAddConfigWidgetTest.java delete mode 100644 tests/src/com/android/launcher3/ui/widget/TaplAddWidgetTest.java delete mode 100644 tests/src/com/android/launcher3/ui/widget/TaplBindWidgetTest.java delete mode 100644 tests/src/com/android/launcher3/ui/widget/TaplRequestPinItemTest.java delete mode 100644 tests/src/com/android/launcher3/ui/widget/TaplWidgetPickerTest.java delete mode 100644 tests/src/com/android/launcher3/ui/widget/WidgetPickerTest.java delete mode 100644 tests/src/com/android/launcher3/ui/workspace/TaplThemeIconsTest.java delete mode 100644 tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java delete mode 100644 tests/src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java delete mode 100644 tests/src/com/android/launcher3/ui/workspace/ThemeIconsTest.java delete mode 100644 tests/src/com/android/launcher3/util/Wait.java delete mode 100644 tests/src/com/android/launcher3/util/rule/ExtendedLongPressTimeoutRule.java delete mode 100644 tests/src/com/android/launcher3/util/rule/ViewCaptureRule.kt delete mode 100644 tests/src/com/android/launcher3/util/viewcapture_analysis/AlphaJumpDetector.java delete mode 100644 tests/src/com/android/launcher3/util/viewcapture_analysis/AnomalyDetector.java delete mode 100644 tests/src/com/android/launcher3/util/viewcapture_analysis/FlashDetector.java delete mode 100644 tests/src/com/android/launcher3/util/viewcapture_analysis/PositionJumpDetector.java delete mode 100644 tests/src/com/android/launcher3/util/viewcapture_analysis/ViewCaptureAnalyzer.java delete mode 100644 tests/tapl/com/android/launcher3/tapl/OverviewTaskMenuItem.java delete mode 100644 wmshell/shared/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.aidl delete mode 100644 wmshell/shared/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.kt delete mode 100644 wmshell/shared/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt delete mode 100644 wmshell/shared/src/com/android/wm/shell/shared/IHomeTransitionListener.aidl delete mode 100644 wmshell/shared/src/com/android/wm/shell/shared/IShellTransitions.aidl diff --git a/aconfig/launcher_accessibility.aconfig b/aconfig/launcher_accessibility.aconfig deleted file mode 100644 index 13e1127efb..0000000000 --- a/aconfig/launcher_accessibility.aconfig +++ /dev/null @@ -1,12 +0,0 @@ -package: "com.android.launcher3" -container: "system_ext" - -flag { - name: "remove_exclude_from_screen_magnification_flag_usage" - namespace: "accessibility" - description: "Remove the WindowManager PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION flag usage in Launcher" - bug: "369019568" - metadata { - purpose: PURPOSE_BUGFIX - } -} diff --git a/docs/assets/badge-github.png b/docs/assets/badge-github.png deleted file mode 100644 index 5129ff38b7182d0daccacea2a148742cb9d4a68f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6930 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

Fy4IIj&2H2AE_|b|8p`j0w_-0nM5$P=KwD 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 Sr4eAiM|vr*Dpw_A7W6;5H}PWt diff --git a/docs/assets/badge-obtainium.png b/docs/assets/badge-obtainium.png deleted file mode 100644 index c5cee26fca89e9be538966e69e6e8aa6d40cf71e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16050 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

v6wU^p-fw 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 +n$WChI_vH}%PkyVgN= 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 z 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 z zTMu@9=RLsjtZxjde@ZnCS9BqMJ((a;kFYeLW~2AYzUZJcm7}n0X|05HK9C@z-vW<4 z>y=NnKk-A~VAntP+6%rZ$H!xza3jA?-CGvNPyU9z{@?wo-FxS~j?<5fc!j1lP*C;M zj?5(}4=4zb?iym@amRhYiK*5BlHkEs8<~&?NgSrYU@GPDBNrbjb(;h0cb5)n_?Qlm z9@x91@4+y#8MD{<@pEw&P|e8FGBgFeRQGOO4W#qgI%7Kda^*5nEh7|r^y434ulc5z z+B2T_6w0zZT*r|DFTVO4USh9$&5P_EZ-1Ztx1atMd;j}BA{^fx4>l)iKrrSfFFIk% zyYHrfZQQVl;v*_LKmkfWWz|g@-^5ht=>0mgnzHGb+mN-9ED^KcRrU#+87eWL*==+qC9dw!E)rPVXu(Btx-0X9$L}nBLY5k6L<->WQik zH@x>Re$8|2)&J8=?Yc)@^RVp+yzdjA@pyjSUSRY${KT)>8-MD}jQmqOYRV3S?zUpA za6&ubtsR}$Tm>yU8OfawW$&`2&Z5y==E3F9y#5$T4Q&rH1ukFllG&rQa`?l>$}Wx` zq``6N)X!mm{2D65G^9iP(7sju8P54}Pkxkr+Yfw$-Td4q7Nlp>9U$&2Y_R}InLE3DlpqNvep`+RK-rA$!ih~~AZgYwP*vK2hWCpb- z7-{NdM@Y}pKO6gLHl49&G=b!?a8gT`9o1+!W(Lkuuw>_nPvg@c`a`d;ea>^A^P&fP z!Q0;Ro)^_2g*H6AoT7ioYH_Z9-(OduGJM0&Wq<-4B1FCAz(*;VRiRftXyt7dBaearWMy`8x9B0HZ!U>D-pC{gnhpZ-{T-m7l0y%JG;@X)9C$Pa@w%&GZ|euDE1=GpvEELieQjw!I34DqSvY=laFGk;y$Z67mlGA z`0x6@Z?Yfy(LZgEdh88kCVe>$T>sVAUSThI$usThtFN#Re&A!ae|n#IeJ1Np25(+6 zlAQuE&iD4kV45hZt`WWIdElOr);w_$S64;Bvy6d~tC-`&h*AA@8m{Se!A8$|ey}7A z93$MhC%RlSDg-WYV9Zd#xP^w{Tme9@B7pUK9Y=HY%py$drWLxu*fp7Jb(#g;^Vu(X zy8Y$<_n)&@e%p)eq5{c&x%R}7<2&}$=RL`uQ((t??>uFn{^;jiI9Gd_0@tnd7*Rd| zsDx1=Y>R-gq%m!Weaib>(tK}`hyTdCbhlnjSAV7TZ&OGBx!W6 zu%M<*zPqxn8eO~n8BU?GG0rzwCy5-0zsO(pzg<+|@9+8R-(^4ex4*jt4Oc%bh|jqW zthpN>d%ZpH<AGHYZZ;h5P@c07SJ`E6*MD9TDPd*(jJ!cde)Usc=Ll@*P#yU zci;P7{yZWH9+48e^iIG}f!~ggnjxj(-_1y$jAa(r##72|Yp*q1Uoa^?qZbYHPyO{j zVSn)-{5iYkkyqLu*8zIzDiw!DRl1E%#3Qj& z9z+84QSozKT>F{N+CIxsQ0{wf3r4 zztC>K^@}8IkST@@hYZw2wckL-g&NX^itRC+kYf%~sIe<>YB(M~v7RIbq}0eKag90G zW>*|o+Z1Z;r36fPkj`9W3^eEli?nGZ;e_+X&oS(gO8egRkAP#Apw_TpUrp!mEnu(g z1yWL!lGS?L@#9DBhyK=g+n@O>ud|aSc>Ciz5a~bv)z7q}7wy=`-~VZ<)(1ovJuh+& z63Ok^xa>jer12R_F{a3>fUXp8vKC$PV9}8XIuT5{y2^z8?%#NH>|kjp|j|p7Y9E>~H?dAF-#Dtm?}ZFr0m$M)|lWJj$N^ z?5EkMKlVAh ztC!;|;JEh2tL%ke{~WvX3wJTHOr8bW$N*~7YO|5r&6<*LCa=B3nh&Cj?y+O!yxqE2 zgub!{yJ2B2(GtL}^~9!GY+6W3p#c#1zIu$TJ~ygiXMNssi6+abCK0Dp8)ZRx#qYD% z$@^z;zEqSi=K>tb*ewgfz0lCFS^A}mgx3_fA|rSFbMukR3q8k zDv@_Fpqx;Qd+C32upHjN6~zarqbErw#x%eQZfJ8#~_ z&qB<1LNf$n0+kta;B-#_+(x#!6 z3<4%~-V^@UR=T|RnE`A-GwxEMe)894_kR&^*1$UF`kCV&A$%8=X??iVL1@=ZFUV zPWtPUe={;SHsv+aIRw*3YS4)|GQ0f&2?9C-9eQiZ`bg(^a!>j20q@s1_YK=Vy5qH% z3DuR!hJ*I9fNQ5ItB891_!0YafAKr*wcqx#FB{bHr5ulZ^!4`Sr`}|5|Lu1bXikn8 zE`rQdI0;In%?Rlsva}}X*rv?4G9Ei0!J=ovx|FvB;P4>?_1-|GK?DdRLy$jXOTaLK zLfUbCB@GNJ8{W|Ee@?rJ&F`IaP+!1N%1Q>QEUb$cyyBVmxBkgrwv(5gu&=b^#AO$e z^l|H_zF?pE_~)zgMX+9D?=w?v{XULrR}s2~p^Hq7(4WJ$)W7o{H)t}hmfmyd(084M zXY6w1{o~-n-Uk`hI+ga9`#=9q0Do$J5A9AIYxS-2trG=){eS)EzhSq$_-5Aezmg7| z&*N@-ls)>9H`v?%`?~}Oq81L)5Sqq0pQ@<{Ok}rs1{8UA5p3#ZdXN$G0y(_`nc1@3 zp93H~+a+t-w7pV)MV%I~?}jV4G~VllAbWp)VDqw9f^B?399I0jKERZGb8A8MBgoJE zXZ7{j)|vtJ{o&_K6LWHX%`_BSEuBTU(4Q~)*5}#Z_&0yqwss!A!sSCfu(2LnimZS| z01+Y`#xYz;&+#Ek;ORnGHPAT5vM5AFqz|+Ns<-+#@nf6TS^m3&-r)n)edr#;Eu@oVoYaB~Sq_j{Lkl#-9;yU95*+v=$(BuBGkWdUruMF1AH^<3;&p%oAKe1h-!a`6>H_ zQen=nMK2+aTP}Irn;-1l&jS*C_lw50q&OVlQ-RbXdoqtqeX+$0`#gL^!2Nu}(;sVR zN{#o!@BNsZKI(m-N{2ABnwm&%na)}k_LWIZ*oQ<4x!=F(#F2!RoZqPGYQcP^Fl)f; zo=8zWf&(rkkA=1xs(_@lUWgbx)bMW`mq&*3#${JuVt@PJ|F3rSBmVem>0iomq6Bh} zFWJ_w|EFKK`Q5uDQcW-cvSdejUJ#b0)H0^9Ae}^Xi|4T!{tZ$r+H#h~axY6TvMNSp zH{0Ti2gO;HdTV*R=1d8G&z!asH(Y6Z%d)Q=JwOfYqLU|T>l5X>%5B`=-{&-pTOMkL z^}FvX8CD*_fy$39#+`FRjy%>{io8a)uz~7n(h~%~pMy$k0zP(()g|&hAMu3i3Jmz8 zcI@O)`)WEK^Q1?T2=Re;e8g*54u6odEXp}`!8F{78#6loHH}jdbwuu#N4*}68u*X_ zaMn}F+7`v=Lg_yF^v}4D8qTLjLVY?XZ}bt7u0-(#>=$GJ(t$(Sd7_#O2n#>@V}I7Z z=BbafucqT@fjXa3;K8^4_B(BkIvq9&P>{WaHY)rG2jLZm2HLobF}JC)(81xfTbJKz zIijH%#;`c7OMNtlNm^IYpzk5IUC);9(doU!wQ1NN)RyQK&u_7q6IPs-^SbYI_gY%2 z=19oPhCt+LrI`QM{{9bsRXD3SaQ!!zn$~TfyN!K9J=vD6-^aeffmB^tW>L&R!6xyj zlOrO8mqd*YI=c6C@-rH>CXg}c2*p~q<|GUDi9wcaYsOwRI4wkUnzhzcqD7OVty@QB zFXQLsTxKNz{NMlM->@6L!ZWM0kBdv4>DotJV{iNAciQT7f#uGWeQ~DTpYpZ5vv{4; zoh|Px!5ma{PW5a;?l5-?-@Eg`x{^t)?wZ^3i*xJlU2sYQYFk*#6KJl^mkdTOzvfC# zwg#$n*~OQVJa|CiTeO^Pki7_&Ggz}vJ^AQ2KiD}R>agDap7*{e^uTxoWY>p7848+p zvQa-2EW^=B&B4Ei7eVK4(b*WJUTeCG8dmw)v5Sw||Mnk#wA8FFv#+)To8pO2f1G{b zogcQ_KX<3$V!2VW%OfpBIAKE`Yaw2wA3=dCE^FLH-1Sf`uQc37y>FC#f;`JqnJ@aY zLP3MH^@*BXy)}{)`@{inS+sRb#03kjSLc6)ezW@I{>-2MHhbx-pKD)@$HkYNuc)(niK};?DixJM+Qsb{a$r*l$L?*t8prP=(8`@K{q8_ntr&QwSd zWjn>Bgr@2;Dr@<&)fqUe-^mh&}~re4Wyp97R$du25MI2p6!*O0p;N?RRo3% z=0*t6a&f@m_sG_kow@C9+r9f$l%$_uO@g--F#^Ujvyr$yrVw3^Y;9O#}NWR6=^;^bsvuBjSa!phhCHOVD;_o*8H} zXCWx_!?Nf4dzfA@h}dTy^#3cJjn= z``x#`yWr*}x;wp3Ns=6NX@^^>PQ6kO)bbRf%o15bg&J~ai|pfaCd|U7WwUL7yx~O{ zP^Bl52=TSwDrpJ&ZTq{22T7pZl!>rxr*QqjR=qR2w0>&?NO*VNDl@w7H?WA0^zJ{lalG zlV*^6YOOYN2We>#WEPb|&C8)+qMjrU&n(eB8eJ*Pb>qATyIe1!gP`1Vd zWIs=KEd7Q$FYl! zva$BvC6EI$Fb9OxsV+`cad$`?P)&f}hGbB$aTGBQD94~T@7ro!-b~eqRyB4vhMKvh zbw8PEEK*E`#sPNia(C{wO=|$hIMZqz`N21De*P2f2mj9ZkVx{?d|*#J<(W^kKX}K7 z?9R{KrJ;{m2uliQKim#y7iu~H`~iBXBrCUcw@{wpcDcVh6OA9Cy@3FM;Ph2-U>9F~ zsm)9DH`$(v{8C_5Ojf`7=%e2JVCQ-`OYmLqeo-E-%wme^_+oK1F5Sc6$I_5C`|EOK zUReeZDQ60a|71gBt^XZn;|b4vjQ!Bx{oaRFX6eg$TwWd$Zr6L>_CZB1AUQNs>h}ru zFu%W}evjNk>cJoa%n+V8yO9d_o!Biz!w1VOHXI8ejyVRlXEDr7V`^wj;QjFd$#pvSP?m8gW-M0{j*>HKg^zZz2m5CS&(`qX6WgVAj?=+mt!zgEC&5qxi#Kp5P(sk2+NB+I5#^oZ`#;T^s71mSe3 zngQ(R3|tb*myZ-^XX? zw~x1>5t!Si`$DcJ5_#fFZzeQSRlR`YYdF;6RBgvke2#CkCFbK(H7FkYea%W}&_jN{|L(5gZSd>7jN{ zvWzkN0$HPV$~qNAinD@EXrhP>?5CMn^7HDA5QLiR=E>RXSDYc$lM)RA2O40T4cBn&q<~=Epp;)5B2g!Z zaU$s5%E=r2CLDkWW#^-p9V<}g)V}+#yzZRO{j2?W+1EYKF1_kf;^*wvr`0lw%5p}M z>#Zd=Zu{^*C%KSAh!;Q$bH3si0|Y0`1j(AK+9OiN2R#L#SfsY%n3@h>jf&QwQlVOn zo)joCAUbV2wn5;tU+|h|abD8Heaz=`-zR&+xSNOZxa_Kn?Hm8}*9rnmUR$!{V77Gn z#&y2sZl_GBtlh0rhXkD36{z;$)IO=$AP98(D-s5l2O4v;EYAlubD$YPTuCr)oj;%K zLp8a_TCoH@XbCAPPWCR(_`1hjV=w)-7umyoAj8Bnz_Y+J`EopqZ~qJ5Y)4NXagagx zg>p(Da`h3+B}dZyHN6N~>>tD|!172q(g?S#(`b?MU?>;2q-?aF>A~ImcIRhrV*~dF z#HbH0z&tD*MEYuao$Uej0>{5|*1z>WNur0Y!g*aTgq6YU$m`PG~e^yNMtck`p{(KkK9KJuZDYu1b_C`&fUOk=w{3C=o` z49;Xp9lD0Et-}LNaz9h5CQC6K^Rbp^c6CpCXGNK6V0}Eg7%s_um>e=(iFL)$q6Zns zUjD5wvTJU*aFOIaci(HD|J<#1>eQL@dOgN+Tyf=PcIA~(Orz(O=91MUYtc?4g<>hV~F^dE&|Ng4DY=z=`$QrQ;g==(2N{@(JYk5jdD*) z=P=9c+rRG{?BP64-FMo4>o?zO@BIDul>M^H%>D9m?_c=h=h)3pd-B6dEO=#EXFvNd z-{6AP_F7$!*^0~4%KKKV8wvT?~6L$AqciUcZR+JMf zb_#M}S6y|5-SouA+LNAq6aRgo59k0s=T*9YWceumydPf?asJB4# zpJ`-N1#qTRl>&r_dfQt0Deec)V|M6G;5gSjTcF;9qjOqS$RUlP;K&cL? zBko>jkTtdEr}L2`*kqukqsg*cs&)lo)y^mAO4U1gub1TgDaDkeVnK2Sr`%=+4&VOW z-)Il@z&U^9!ymI>eEqN3hyL)R_D4A`zT~96>g!%+w><1?Q;m;&>n!J&&9fpXo$dp41x}AG>4r z+Sk3T7?Wf6a2?nq|JUFB$9BiBU>g*>jf_A>i3 zfAw|tFaFP;kWFZnU2gAe+ddHy1+<(vkQ7)1Z4GyDR*d(`9S!el@)~-hI&2LJcwIh& zEcb4kT06RZ%+7rAzG13F9mr_4g$FN&qxzOV_Zqv92R`S0@A(7!`JemcVv|09zn^12 z-gD1={QD=CC0+p1MZ1Z>*_+?num9R_GW$btaOq_imrZtl?~8$nU%2%)8q}Zo_djj#e#d+5JO9+{_#PMH z07Uz|uYIQd{J;HG&-qIb;EEO)O9FBQgZ~z`9VJ8TwuE_KmSMnn$GHqlF5GEYhP~H-*7GU;(qV#@33Eg(_8E< zzwtYE#TA#?OJDIqd$^A0zVaFNAO7*r+CBmTA1`t+pASK8_UB`wuTL7!njkBNb)KT# zLr3&(sV*GV%1C;Z!QxgQDG@%{qRZW-_hAiBG@bSAzV^Fb%53*S9jER)W&hekAC!x_UOkxiq8NlEw~0aZ8$9id>{GHNA1&}`mFuJe|w|7^|yb|zWLi; zV^4p^%@-^KzUC2E*)1=*+1~Po-(5op?ze7X&RtTB0tRI-Z5`dD-RXS#dWaA#7oF9a zex?KlSs4QMQ+M8Lm)~%?-E->RL(lEJ@BG+d3Byk{+md7QXvJAKK>~tEmlg@Xaa6uGRXU=5I8c6bDtPjX#4bMi%urDxr$*9Z%Y zzqX{om=;lHg`mtKkKST#8IEvPbZo^?zT}k;tw9)p3pN#?SSUBV=;bf8SG?-QcJksc z%?Sb)dH1{CZ$I;s|II%5fe+h{|NVbvKl0aqnEgl>;&|2TUTVMkQ@?5R-MIn>lQ1jU zYQ<@+&@f4;s;TR}UgJn??n^ZosnVw{T6=0u0IWEw#Fw8v@(DNCjZeJ69n6EA2a=I|*lB9LLEd{l>*RyvO5{8Qud1A@n#<;Fi?HsQyH1O>EF4im zc#IIiL%n~j3aO>y3VE^`C{UG+Nx9yamm1w~{o>n)Z>6(T z>0=i}@|iWV#XRsx`SE%`R_OhtR$JbuYYNk-|@v$mPQy`k3vMDCLDODGuuA zf0aE-I9~Fy7uX+u;3M|QPkh?m_=~@0-}>zjZ++lg$MawLOnbwBdXpV27o3Hp`8`%4 z*viiI<~>AOgfX(_(`Y``pe7PEECA0)!@0;vt(Ge-*yLU zEDx8%0!r|#7eCG3SR59q>Af^IHU3Hy;rgUHVsc1{dXbqXRm1&FGAG9;&v>G3- znXPrmkixs=XIqug#pc+QJ^l%gu{Zquuh<*k@Fx4YpLqkbuy6Xd3)95>v==_v zF24LCyZes&JTJ-Xuy_58!~mdmebVJ>EGyt?4kkV(1u1z%$q;`rQW zKX31P=X?40@A#A7T0ivksnh)P*zsfb+Hd??`$zwue^EB$Z`(^s^ndc=FYmy+N8WS; z`#C=MnJ-Y`fYLFYN0e-MG>;%d%ghG~LQ%Xy?FsLaINdW>@`@G*MOjPKcCgqJfdjLD z`dl$*94_mq>@10y)i~7njAuQizV455K%xHUe(LAx0AKa>FR#w(t^(~LaCpzV-fL&h z?46UG^I%6rkRSMiKV-o1(+=1gH#EFaOu=f1557&X&Szzm}*atYN z_3uDqDg_`{)AzRv?&QYF8)O>|Nkf1@rl2rHg-nrDRj@vzV-pzonu>oaBUX)y0 zn&r)IF3;hi?1?z;z4t!*r~lu7O=tC}0{{KFAN(_aq#yw2@|tgWm0f)CN&Aog?k5Y9 z@#gYA$1ezMi}$|i>5o&El?JPhBl%zXL`qVXCuPlUARt((9$xPG)<&r*76`rP^z6o; z%}_$pTPSeYKC(&ZAH+C2!~`GeumbO`Ghm!GlN`q5Psb0@^LXuT?`7#?qVUj#>AsI< z7))Li>n^?ia(mPhA7K~b0MiK($EBBF!nzgGZk+#JcimlI^SCEG)*khk8w)1!+g$UF;`a>lHc=&9# zDW+9tRlu{09xsO#1M^40p9&f)kvX3BoQGy4Kl+i6myLOf8PM03;xG=}=YRf>|4ngJ zyZrYhmtJDmTzlntkzT-%{Nf#V@;S!w*57_RpY4T+C~tZ3Q|u@I-p{cx99d9`;40wo zbA3*&$LI4tkJ_IY(33kY)_xmjBetr+Tyx`JWf2t6PCnA~wsC-I>~Cd&-_o2s8s7KwsSM z?ygwjfzmmdDEnjzG7ynrO{XQmo!fkWVTT=uI;^!VI3`Mro~u;7^{w8YdGhb0KWMq+ znVHv=bx5s)JxCkH!$L9nvg<{ifHyg${kqcnY-=1IA!N(OKFK>p%@OBu)zuHJ-{%t_{}lhc@liMM{)3VEum1VJr?a~D zx@+v!uX&k0rWBR|*Pc(*igP{KpW7!s@yP<={yLd!?|aV&?DyXG4twTv9(q&r<=0$h z8%Jk0Ki&ELwuj|dpYL`q2dElAatP&-1+IL!8-*Y9*hkv$75Mh;Z~J|F{)?V%7hSZj zQTU!x4>;IE{Q-oD~uCR&@Z4KytsTS-H2n_wLo6Hp|M`ILaug9+@li5(p7m zb~-8dA95VN6V9r!)ntzQEv>!I!95LRG<_j0Y`m!~+;rA7FbW{HJD7*l*+I8ova9Pa zNVE*ke#dQJYcs33%yG#nN?{sIvf8xA9|Z=1QW0zML`S13Ke89ntISvO~1niuWyZC73M&_2Kk@H>hENFDPk;Ug|8y~aU)q=$JR?kV ze!>$UUxJT|?Z^Hfe}_iwzx=2FYL6%p<+(QemvUfi&DD>-(mwX?&p7jM_*@)^L6?7& zb(`HNRy7%GDooP)P}5sp@DzKvkEh-86#JE5{_kW7{*xd7*Y>*a_(r?#`fC_Oy|WnC zpZUqx(@{M0nNKH+?cqLdddg$$_5c1?d|+j&W+tXJ=ZMrTuMslxsMSXGtr7qxXW6FY zMpEbkoAyf}7kZ`xb=yYH%4$sv4cia3tpK0#3;*qxd0&wM?G$9;1Ap)#`)@zsL1SOsb^AR+V{BA^otA+4 zXvwU$G|3q?!h_Shc4YIYHLDU}?9JH-Pu>IHE$;CM0#8Ej;3LkQI%CWE1!^1~A7{_I zQa{f)-D+HmGO-T`&3GIGAn>m&pt8Vb#rRxw#QI5Nb2PSODPUZF)k8BnaR3WNWP;DV z@+w6|FyhzUaIM`|9M#nvxjsuf>0fX$%oeGM8WYrHjcq3{k zbp9AY224^nl_E`S+QyZq)l1cIa&W_G!iliRw4L$(t4fAN3foLDXM<>ES+*<>I;gR={s+`+uLqY z>+Y0Y6&s#5F~%sd`5LvV&8=b;1)NoCEvD?yPNiXpFCAYHX6C(Z7JI}A?1voZ zkzqwKKmKiY7Nco`(imdh@`*gk_g76t^C#f)(a zHIwc2aO*>F1nDvALfxvv*mj3ZJVRn6x^k8F@&-^rUX+LjnJoJ)8#ypV^#1&^^w>~u zy!yI_4t-p9xq=UnUr;y1`@izlFMY_)3da{d{{{XDhzh~UyWa6$d+j&Bni=#%IiB?F zC)iuw@HSiBy{rp*?03cu@+pmfKD;Pb;^q0*wBkthbM!tt$F@oJ{&E~wms-kS{E;89 zU;2e#wLf_Ohg?-9m2CS+F{aNWQ}Cf0?DIV?x$5Flh~F+z_paJ!7>$d@h33-8q*NDC zw#1oNU3L&pgEUzW) zf7SIjTtkQToKk=K^)}H>-kNlwh%zyqlu4gE~zwkxRv!^`mYxoSnW`5Hfe}nr8 zqglS^`~IX|d;R@`?DxF;{q~aub$I{#|B#HqhkD%b*lX<*ANaJp0nbn;nMuc{&td~x z8TySIEnwDt>lVGBKw*cfBU)ckqBL?`U^Sn<=d^8~JhFytJkW6-8J21&K3Yl5zs*qS zWW?`=PiJ6^?9tm$t8>YVbj7f49xu+S7)}jUQ|3dxsd8~AF1|4HeNZ#O=E49nq{-XG zS^fAw_-FQs;;7H&rM2UxzMRtw6DxjO{Vmfj1p+AR5Jn{zq z8J+v+%zyk%4}D584C$}^>Rb4CFc1H*Kqp`L{B7(*d8iKSqDzk1_QgAP?|pZ1ke$0G zcRtEuD`R#o_#K>6%zZnjG}$`sZ1mgi_=3okrCNgdP)Ck``T7Gdzv43cGvD`JL>_i` z_Ziq+a`DLvE{S#6fuWQ$&xSPX%3(q88D#y{ycSXopsCd0Tr`r3Qm$k%p+JoXYF)7r zutN(UHtV`~DT*2%#^oUtKY8gxYoN#R$xnQWe}WkZXN3dmEpIP%*iU}^)AsuR`iu5E zzx{Rw*?;2Ozu8{!;^)-gg*@34pY(Y9hkx&%unzXt-+8exLD_o1bKFc>Npg z^W`GS<671TVaW{%QBqgt{H5Y)(U^B34m1I8_?V1)?z-1bm3j)C6*7+-9-&C-<0UnJ zxC-lE%7Mj+{)HT=PV1Wu$#s2zjsiD&=vqICFk$PxagD2bx6TmkDQF9|2Gy$g9I{o? z3y0eIbmQo=i&lh<3ozg35jS3Mk9)#n9HQ(=csYLjORHO*>-eqTe4Bmzqn{}7!=}CL z6)&=j+)3Sc-)VcO$96H4^U2CKPi}DlT@I!uBQ^686Augn;)v^gAStH%|DOM8)zrFy zR&UDmk6~H?j43kYOE10n%kHdjfb`?Hyxt4214A2=-}d`NIK1(u;^egVq#jR|&67K> z#Gtq}*aa{RSA?^$qnenh#oO<7m-PQ_)+yNWAexb`{s-VQG+pe%%t$O3^y5ZNS~ zXfVMTuo>H9Gxp~+Gxpds9{(OZ_`jLA7lLJ0*>&N)B5{5srl&i?mW zRkhE#-Thv-^jaW1cR#hd`}Ped?7gdM)v8sYPkH3vKP*c?6d@W%LUVR_9>yL4dhL(L-I&-2I!Gp3W%#QeBut!7O7 zc-T}(Z_AW2o71(&S_Fx0aqqmuMPX|fUh^>0FB+yMDrPOI3c_pga7w-Hy6fKLtgk-T zS7!-!4a)~Lz0yvs%&wTnqn?P^gbB9LMcR}=CQ8vGrHjqex`WXrswsd7Hi@KUuz)Bddmlkl_q6 zo$UJH=kApD%*stMtJ*vseLfn`Ee`s;enndBSb(+lw84J}*~!ugl9^I2*;INTGkb3r zCSH&6W3PXe{Mn!UjqKU8myjhE7#7|A888CpopT{6!MF}Ddhzq*@U)>;R*YDNgRhTq zwS*0aZkENJ+tP*8B6nV%Jl|W-590;FtG<5-2hK~7ccGo5oxK^>X-ynx;Fz$Pq9}OV z-P`tC9|z4;LF?Z?_P5>3N3u709}|mYLw5eeoo76r-lxBNQ8pg2dIYh$NlPCLnV$+wKSWQp5-MuP2zgT8 zI7ZkCA%~AS6QlnJ=f2LeCrYh0^Ot@ef;iW^mV!7 z@~h;j2}(NajC17^AOD;@^3f;D)7}vQ2%9LC-3wj${}39wXl%lIl@qG1 zDO)o!St+rO?Oq;x%A@7u|MD5xw(VZI`Q}^Y*$EFm>&$cHvP-U%pZ)otl<%aF8nq># z|J+yQ$KUvB`JN5E1nT8M_!hVwi|;DK@U;ellhd0$ibAxMa0)8?*(pqcx~dG;C^U&l z{5}wShI~qYVZmC#YhJ*+5f=uBtfSAs@uZYAh8hJ}PY@zTg+G$WAeWSD5I6?w3;7fn zZ6uq97vJ9{rg&s znD*O>W6c9){Q}-)1NA_VFGIP3ALE9Nv%Eimz%Vax?RW3mm9D=UPq6fm7ed_@7;pdi zzy9Kia!v<3=y(OT5EW0LH@U-@p~`4P7}D4c8pL;Z*6^B&suyK;q*Z-MVOyXTOv|ID zQDR0V@~!iq*I`|3+N6;9zJ1GE>zwxFC(7ApoKFQ8=w%|ewUR@3W%}E~;sWo(O3nft z!+}Y>;XWSpKrglgljD0i+ml|(UBxhzF3Mt(lYov6qkWYOb`8=~dS-n@yK+R;!i>7* zOK#JWf0iue7lwt*-LpD`xw^p$jZ}X*jryGq1wp-cjI#)%#m@B8V*^)Fj;WTW2NuJ@ zp!E;Ym6X+uMMu%~8pj-aR7&zck~jZrUphm6`Yk_^HqOV(d1qfB-}vg;^1>f}u6+EX zpH7qHTsi5ahmGmn!OOYgva966Z+(Y`0<9GuZX|;B9`Klk2KUh;DGi3e+N<60mkOU! z9bA-j5sg}1*Q&!6V_vvUipgJs3jeeTCy%l(^J*lm?y3pdMO%+VVb%G14X?z%^gEXm zpT~v>rqq2Ic+DvZCV9aR{~(KvF+dT9jvB7t9g#Z)$nYAJ8tvX45fmU59%T6Vfn! z^oF!w45iqZoXnXr<*F8xM3Edw6bbLV>n;iXu0~5`tr4rqXSuO2rIr7U4hw43MJFSD zAM9HPr3;-5`bG~W>R?!Ipu z*?|V~SkM2V=aRw>5!M&K;zjbxAAPA@dD+!+^;Op<9R7HD=5wAd-%JMUz3=`Y6~rNF zf=%lkfBkN`;?k@0d!3t%+)qQi_4FTD_nZPYjQ)II*^+nw5FQvd-4s}-Xjb7myX&Fp z!bkK3UryfMcO} zfrgbokUYsBNf8p1^xbRU-*Ar~3$K0M(S7y3@BUD_w>MG~@|CZCiLA%im2v{CA52$y zX2Xm!okk*Iza~q^fg-Cl=eTTb;r=1{jJ~W`BMWnV*|cSS4HnwHAi>{0rZ@cLYvl)@ z|Ey)t#T)|T^Vfd!m*kKC&tJ+7>6*jK|M_3~8Tso!e;1o^elfk*>t4Tp;?WC;s9_*t z%uF*LX=#o#scMl>V9x0xZyQW<41^V2!>Ver2qVb|fXX@dQM(0QAyHNS3>|tNOloOF zrA6j;AH0Sl9B4q>m#4G@;8$k~`aqp19EQ()@{24lKwBLYbz{xKIY1A;y?geh->0b0 zxzf1l#+xanh4r=`gqdCY_DD6Y#o9B6q@3TVugWXT0d~`x*=1$4g{FfLk!3o2rV$xy zxo)K$bmxyP=Q<2mKi*X_nZj}#)aokK3kOpq&Y*D2bnUFYJ8!&oL~i=4!Qj<)uoeoE zlOJ=2i{4Fo__pirko6p(fg<05_y0tK2N6QQ;U{0iMfkNZpD8D%1STLWM0P0FyXb;% z%OC&#pVL^)%+4fS`G@7{&wMK31sF9zDC<2?M}&=%kyWh_U=TeOu(>gEtKAr4cU67_RYfm0qMdDlnW<)q&K|1U2`LaR#B|o{sMLPu$0XAFNhH%p}UY^OcV%1i2>^Gzx&(&hYjzL2m7U8 z`Pp1wcd+BSt8bF57Lp>Y&a@&2Q9KtAmz`ImQnB)Rx3vPtM=J!*uBdQbXao^ue(#*@ z+O~R+p!+gFHiC2yj_H_Vj^<-X36Q+Jbm4dYje*_Q*N=It2-=v#Cc1 zAf$-9cjN|{dT9DOW>%(>QR{3f6MUHNMGq{ObbU*Fjf~b#&3&~4!P+E6EmKHOTJ@OL zAb#tV2@GvTIZ}zMQ_@dgJ;UEEuNO z{MgG=n0+-1>b{*exznEVBzpQ^OK`|PCH($1KlUT6;eiM9j=%bQz7BFiZ~pghkVic7 z;lzVM$;R_P`2LTQsKiH@RLrEm%aRl z|3yxEu6ZZ-N|`4 z{^a#jVfo@`zsd;piKjhY4%xa@ZomBwd35spr`FJm=){LVj0`s@?%#FiHhEmi6Jrh{ z2eu=5+UwO+bLYLgR8c)x-A+eo(gXq0JOR74E$u_OG7);~ewVy3Rdcn&`LS2Z(-P!&?l&)B?bc$7n2>MBeLMEJqveNw`1vb3N#DNc61Iv#{_%0A9K6&V z?!h4CuZ}wDD3ZYz_sk{ZUb3qOnw(;9>;rb4DzuCy<;W3jfp&qKD#C)sXa*C!nyUz3vUKqF^d6I4aB$KDWn{ z(qi_-hveq#-}GumVrZ=Z7zD;`y~edy+?e$O#d=;*tsB1^{_gy}HZ#!w<%L@}hOSX# zXeFa0qJIW$+xbM^%{5IdAD4B^|ibgKk??*GY^Py_!_Ct zsE^uglx(mI5p~TbBmzzn+C!|@xasO!iP9in)tM?;M9ip(z3h}3SeDlNsxU)k*O7m- zNQ}e{6J51jKc5S^CNxZCH}3+qZatn$C#4Zel!d^rPI=6#T$H#e<$fQwN%l?;vsT~z z9XO_!CMeIvpwIl;*(>HVslxNnyKT@MG=6(;vikKk}YtcU^FcHDX$e}*h}-F zGnC%^p6k)N> zr%fXrW$H#~^GZm?lU^Wr3W|-o~hJx$bu9r_B=Ddh0n} z@S^9)cT$9f;$gJ6_^q_rp+^?#G0=wO>t8vG&jaHH8ib!uu+z5^{P&VHxt{T?r_u=g z`~UEhDZ%`Aaz)C40bcz75)`!_QZ?7XVEMumWm|$WIw~WL3Sp0EwOK(j>(bSa-5d9O z)>4Q{tNgXuoiKS{^Y_klip~~BOm^zcmE5&>@2-7v!?m}_QOB;YjvbEqP5 z1E;bMe&F79icCVn=YQwi%b8?f55@LdZoZwAX%L7u-_Ig$Z?-u-GsEvAFP&J&dve)D zS1FqqMyfxuKuhn5=R~~z&L40_GS&cy6-t8-`aU#Xy59uL1H&*4=Lc!6MX_2PP?J>O z4y?G@jvR8_L!@`utkfyhwYyrBZ98@(Nblg3M-Dl3E9_@!uZSPhLSQXGLlkF*%s>wwb3ySML= z8?U@Y)_WjIddn~TlpK2KA+jyOe}DC7e@o&4jL-{S^jx0TDUW}w{Ler5?+KTGrNSP` zt4C9`-~Nr?qh!hr*WM)0{Xy-Qb>|&-%kJF=PfUOjx#;Z6tkb5#di3OxEH+(1FR~`( z8hw2%8f7P8C$7Ypz0PqZSu%;w;+NTm! zHB(FPs43a{xTEDEPdG-VkKZ7J6j}9)#q^nJt$ycnS fuYI{Z=?9)b$nE`q|4&q8 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 tTn-*wT4zWxJGUU~VY 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 z zzScl4XhXuZF>H-)PBYqnm~#pXh~J!Ks*r#OAMVP-brlYJcp#et8)(A)k*t5)=@RA zNvAh+7)tw^ZyMa!w5%kyXcFfMP5uhOLH0*=h|$hVv`h#^0% zUuW5H`t{ivo>Tysxbi~ftmd9ULl_MLm`gRLPO$AMiIXZR13U|<+jK?H0OwjeV>ztK z|4I~)Ov55cTFe5^<=Od@@B9OQ_@_Vi;otX|o})1o_~N4%KTjv~v(bLw2Y=v|@p}C8 z?Z*px&ej;$$A9%7{`x1MK7IDVyRJ)+bUU%CBAv}hU7~ezPBjD%x=qQk>MeLB66yYo z023w(na!6*b0c&WRZVTPrNKzqX}?U`Tl9etXvZ-j>}@tD#7QL2Zk~hGyJRTj8<4NH zs@rjP1#9fVmuN-N3vnG9+-`!CexfXoU^yfC2ORjeu-P9Rb8klM1M_epaE$>mCCN!VVTU z4E>QLre-^qB&TILYJx-7q)aoo0h7bMQQ!+d8{M;gv0h(XrE`&#F%Rd7&Oh)19nyib zHDMjb5kqqtpke434`1tpI!$-dGM?dz5?u@fqwVUZM{rIb|zIp-~rrYlL z3!nN6AAFP^{dszq?YF-4?6;vZ(g~%*}pOe2MzOJ>rM}mVG*TU zy5vb);jNQ{;hP9Vr!E=5T8izWKXnZ+bxDW6i(wVsJPDF%vUnY$ZANLtA$3@QJ!WLX z^I@JNfw1wU+n;BB^v|j?t+rvf=o$@H^M|@NR!?nA@wxFmr?4rx?sKvw0)tZMws>h3 z%VbbU)dC}C+Wq7N!QcRk&4>)hAQ>f$nvYCe;!DqFUYt0PYpQF4E1F(NwrYl7?Tl(t zVnaC=ae&eH7dNnr7G;8h&4$OtWqmKC%$~hK1Knp#n` z^5YM@{I#$D{(t?Wzxtt9-hZ#1YTd5t)sKJn)1TeUahvYC@drNj10NsL_$TP0J+z1R z&>q@D`xo4r#{Dy|e)`p0wXxx@n_v66*Ixhlw}1TCc4_x9+OT&$w1@W49@;~DXukvP zrfF7xx?enkQeDC*s?=Q~NJl^N>{?H!ULwjfs?V&wSW7qW;AHDcf zKm5Z#e6O23w?yxw0Jp?V&xi zhqh`rnerD7hr_SES4L&+t7cfSG4r~ZFFrDkJ~ZUvq@Dduab^w3O15 trbXS&jCwWL%kv@P&WVo002ovPDHLkV1h9vFCYK_ diff --git a/lawnchair/res/drawable/ic_work_app_badge.xml b/lawnchair/res/drawable/ic_work_app_badge.xml deleted file mode 100644 index e79c621ab1..0000000000 --- a/lawnchair/res/drawable/ic_work_app_badge.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - diff --git a/lawnchair/res/font/inter_bold.ttf b/lawnchair/res/font/inter_bold.ttf deleted file mode 100644 index e98b84ce87fa0bb23d2267cb20789cb560b753d5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 293448 zcmcFscYG8@``_8U+q(-j2?>ILgg}Cnh@^m&NI*bBlh8s*fFuM6EujfWhfqWUQlukI zg(wJ75kz=H5m5nO5wL)Yh@v#bLd)&k@B7T`-EA-m?;pPpg2{KEedd{Go_Xe(XUZx} zQIzud$EGN4pK9I4zQq2$qCI*_QB>acna*9`y1Xt_(dyy%yIb0J?cUNAkd~;}A}1+I z(yN`jKGrbe#E2&qHFXEL^zYg&q1)9vU#N<0Vom&BGky4g?DNA8e5|Pb%H#2@K?6o- zQ)%FT4F7iy8a5%b;r3e76or*m)H3G=4;+wDJNTphiW*1!;syh#_E)7{;Qs=CuQ_=5 znDKWPhuiS{XGMwJJZxn80H<;0Jw+|&#P3^%4;Y^vpfZ=Dmc?_W`iKF;2Yx;Nc3VaL z&l*M1*JqC$J?6ziqq?FVX{IRYdD)`|W)Ie1$i?%epm!*C7H&)n7!oi|(ePU-rJNG1 zC<%dKf$ZMD;jT6}-z)ytn5HURakij;)k6CBmiSlxtf)#W6mb)R5@%&V7^6SoEG#fg z4dxBhLu!zqfqoQ71e0@^LOP z`RIC|=l!Tx+q&ygo7+BGKRWpaX!%*$seF=5F@7h`p4#`|Q#o7~@X&kkX&k-}e6HVv zPdDH~K!1X8S9c%(8J?$<2Ee!6gU|F-Bi!t%UJW>;27I_zdPngdxmEbsBw!dx%IB&ZD?m(PNy^40_E;W~GB)P}j2YVY1%vCzoSZMB^WU(FxF-->*--s4U_`|wsfRnS6c(N_mkBd%6xTX}KslJ6c!AeynQc*?+hBdT>hE&jXM@Tgl|F>aK zY8V$A6A=*(K^w-=kKthjTFeC&wW9xPL&qMUwt*k{iML?`-gz}?`Cz_`CG?u#W$hyV zu2Y$PZPoa9Mn6=+KJo1GO`fGJZT^_GR#x4sX3ycFK zlnU6UVSd#FwlipH{Rc2VtJm6jswmrN-e5Glonn?NAm=QMmLOC8`*Ug4OP9S z8dyH4Ce`V<=C#kIY00ZKj7DSmpg8@gOx2{k6&G8 zkM5qE+HfsDeflgvvm&b3v?O%_FFOA0ZFYurVx8HUTN(f1S7Eu3z}49quLLTMNQT%L zXs}^u#Xy(_bQ%)!KvMPJ%0f5C42XNE-9s&=b^jlP{Nb&eQ#BXg!fcOa4|wB2M1~Uc zjB%ti7}C?iz%tb+4=0?IDSC&*li5mPcoL@}Q{pKs+JcjzN<2;7Zow&Jk$8rep33#{ zkJLrE=$)b*QYBpa0pZdg^*Z$XHmT~+o@93SLEGC8tTpZqaQ<~$Z}cGAf{EoqE4!tM zb&U%`>sGe~1(sKBVKr(-sFAg*Y0(Yif?{J-w0Bw6ksG@73Ts%v%Ca2@OuO*wAMhg= zW(To%%6A;ItjC%wvu0dd*K7Ic#4`5i0#@Na8Mk;HzLOX5I=7!a&g=`+q-njPRZ%V_cjZfOBd=?sV_T5ITa;92_eTC7-k9tmsFDksTm%o%{A(%=U6P;f8z#! z(%8g;3z;J~m)|YqSDngVJdPjWg*@ga8$FMO{rCf`J{L}pyuyC)Eu%c7geyu{=?ns; zYao~W_i{4Ko-ZANcKx4AwMSBy=L?=19{=p!uS%9d_HW0i05YAf!Zb6K+S2z?orO=7 zcpBR%aYS>_g^zxcE~8-hL@N%Tct-jUBioAWgH_GM_Y}h?>c0{0c}eQp%ipz!f2Wj^ za+cW!-)hRK9f96V{{(!B4?j*gjEH=CW&Dh34F(h6p8|V^cTBU6WwHRhvgj+A2B44h zRzkgsD2691yDd2NQAwYoBw28p77?zyE{vl|kq}3WkCM8d(!oMa(T&9WD}@%Edb-5Z z*l~#)2lZw`{SqIf&G4!LQc^QYJfoO2H2z9_m_ES5pC%g;&(=?f$wq-Op48^UGg%iu z|5E3?xNDFdqqMhP7Ch7buEdQ!#rRCrF}o&u<7_c}x}&Ozqg)Z?kv#BJR4!VC=suSOZO3WyidBu?GAR7iD2ypik45^sn`veZ}_g}*Zt@8aGr>(Kiu zGD9Hg@9CV} z+7N?SY^<;CZm^xbS0Bx;Ix$$w zY1E~7E+$ApkYx)Et*9#@9fr^Ew)Wbr8P_*1`D|pe@tfA`BG2ZWpXVL^c#-L>?>xVZB;e> zjq_6)ro~ikR;|s1M4tG-GTXCQ<2ff5JYA{`-=;aL_3XxvK4=ACD58mlN1}YC1L>D2 z;Rz*9#x3#wY>fq{kw@Zb>K2I`W64x}_+WLvV)yZ|HuM00fz2HCtMgM3gq9mjYZO-He2Hty&@*5EbNZe|a-w1CfajQ|nXkbhdw;JU)A{>&q)hNFaL7v2|M){2h&m`_O zN_bhv40zLfqe!+>8_9O?XWTD3757_O$J>h5$z4Eh88a@t0D;zvie-8xi{e382xidPOJEn zfkAUNDyE549^7rPZzu{2bk4r@?N(HA1l!3wyBF(gfB*eXy0xNUP+)8jnh91Op*ax- zHsk_mU_&YZeYBQ^o&6-+Q7LiJ&)XWLmaEvV(vzbT@1FN7Fulp#IoYk8Hr9(ds&#Ik zXf1~-z=K?&81+qsx_;94%L*I2oL^Y*AN4VwHF9hY*p7;oLFcf@5EdA)-F=E0jCZE8 zX)L`nkQz;vM{jyAmfC?gF;KUzQB#eLr2$a>sX&#Y!McKKa~}GMZ{OqP!*;y>((+f> z&fR=Rm8~q{hqYdYcXw_tDA=w=xWE397jE&kvD>m5%b zxm6GJ96>1fnffr2i|GT;by`XD=>^ZNdZFhn#dA|Xcy84XJ(uO1dct$7p6K~3@xG}q zBe|Hq@Z7#eJU8`*=T^PZ^Mm5K)L#K|yOth~Tyi0viV3ZXPKX{W3|md-ohg`eLWiDt zfES9TIBGd8#qrg&6z730X%kER0Xe#K7~Ik=n>-Eas#@+mJTb|Nm4!4 z^PYI)6}&N8IAHlkKb|9SSb+AN7!V}hpZ_R0wKNW@xjspCebC2bORzmvCM_g&n&)@H zMan}lio`RdJf3!-zi5$vm~FP8UxiX92GHzS(r4R#m$PHgd&Cl&#B=O1Vz#--IG|?x zq`_>@wn*4A^h0e1+Xg-4)4?}S^!dZAuBd+>jy`2+L0$PkP@?5*L82ENX7CiSUY{4x zu9-LzFMg}Xj}D1zn+5Lk0`_-IJW1d_FJS-7#L+6?W|d z(x-S9S#U~oNIcaeyNBfCFSnoPc?&%S{gS@FhfCZzXt!JB9Hb^%bqtc&SdkA zvEq$so=!eq)6FL^aJ2q(4~7O=zGJ=U2A9-*g4c|%X^@0mjw2@igTQBcap^Cp ztjp-;t!Jm905H;jC7;h6Bnal2uZpH89Pxk`ckNNjDyt+t6|Cz-fA8;dymKH-Q%cJu zOSD{W)&dLI&WF|SRfpl!m~q$m^fUsv3ECX@(4u8pB6l0_fmeYsirz(;cpI_|R$IYw z%UEM1Md2o%p+E^f9CK9@PiB=RZX7DcXOfP6LqyN3SaFx;2t`QDlqkeFQINSTLShzf z29H8={S#x*EZ&(vu1(8Oe^u+dr)y7WL)^>t?JUWCLTl)Ltmrc^e$P1IY>0M_(A?09 z!}XK>YtMF58NWMgQ7ch4JDSDCg;vxebO$ZLn>tyab=8%4Q117K725BOE0|>^ASn!x~LwIxN)Eu{UrTq!aeH=r}1CVM6ZP$QPgNd8$B0CAG)mGXgfMe{qaAe*pt;9zd);duLuELfwaRxeOP)tHCW7&Vml4K%Lv z3&!<8-WQ!_y}kc!#U6Y2w7tRIBT`AORH4)`sZ_O>*BpVv>oSR_X>vytlFL-451-_O zp%EWr7p>)cao3N|&Ym@1{-TY+=YGGNRz@KvX1fW2qAK_|I?$2MpLge$!^CNlHYSM1 zN4x7Tf`q%b?M}(nksg3Ch(YOSvQ*|<+5k=_Bk^RG;x!ou1w~$bu=;{mI?z++l=K-yY}`xy#yhpThF9xUd&mqn&RR&)p`S7WO0kv>XG)qgxmY2hYf}ZO zS-X<&Fio|9?FiJyvgdf`a_-rDo@`I~s;^b4^{`t3t=ika?1k3A$YtXFS-Pc_s8uC> zKjpdw$G9VLBql0}U4pP#=MEpANbE@hA9%bU^-jWeFXNY0!ggaDnYJrhNhj+d`HaGV z$3?#st*`EL9HUmFbf>d4GI!-DFOlHQU(o)>)i5hLl6tdU$Hu?R`D}meZ|kLTE*%>AW<+4u#6l8 zZHMeDDk||*AN~VFtP8)C=UE3qQ}I(UE&jHSWEF$&BQE>&kbUo=8TXetZI-jyEO*2M z2rdM<=={`~flWgdY3ofwSa~%pwqe};gmLU+L7ZP(JiPP$<#A^4bA11~|GIlTfJi=F zlJN;$!uX1WF-cwe@np8nf>Z1v=~Gx^3ra2+$Q{u(ytd!rUf7fZYS**@Vyt^{E)L zT8ehF+Lsl#+T*7*ePNQ`T8~e~kSOs~wm`N6%12l8(fiAVFA(J>{HOeVX@FdZEA}q-@)l)}2`3>AIn%rnUj)hzPwRmzJ@7T;IG=ss={fic-%NuAa+uh#hs8Y%@dIe-ga!&4ALSnw=-c0BYrt>v-oO9OUS$D#U+*50 zyDM(Lcz){C^DkauAF>XtBm4M@f1REUw8!P~FE9Pfzn(9nD;+!N;brCa#-I>nll{9H zX=7u7R{IzB^tK)Rt*eH?_D$Z@rOW0i{G3zS$v-@Hj33?3`t)2qFmq-1J2c}$c1hnY ztY!?=3VrvKuri5L_$cu-_6wR-r3|co5CUkF;!a?gJJ7bn62!2Du%cbuZlq|D5NEq7 z&ij)$cj@xVWPZ+`;Jz_dD_;~{l3XZJ@1}r@5h%{bcTF z)JA%%&zJIN+R|P}wU9+j-jtL?dXQV1yt*(3i=B{%(%ign#_2k2@PS75@|EQ-Py?%-(u^MlvUq8UG z2gRY`986dUZ5H^4#-SM>?Br+GPTI*2?2Ace!>AjW!E5lcPURK;`6tKt*)^>1Q*#Gq zzR>PYnzq^9SKBO=g)H*{axV3px53@KUcA*_lb3rQLb; z>U!kQ%b@|w2QR(Z>7P{$5l97>MF*&&RHvX@7(ezhJHj--)dz-!p>+DEKnuOYI=nXa z_>2@IPGjyNixb-{%z`87Y(HqElULDTc@^sxbc&$ z^bhZ`&^2rL<#%EELQY}%^MoZOD_4XiNt~ibiKi%H6+p?xUO3dthi5W5QI+%*4NCe9 zAAMRe`oU_L=XYPZWciXljTT@DHwuc;Ptsqr&>!^DyFSy4*w_{x#(gD8TY``ZisJ0UDiwgeJbc1Kxk<(BKWjl#Sw7q2yH6t702;bv{1i97%U<23_ofj!JBB#eCi`*z;_bV9?LNl(Z2mrXcdwE1`9!BW3WpoJdl}Y7g(^9wfSZlYsR9c@y2iP`S0;} zPv`v6|JkS3c3M~Pc+$WT4c}+uTJOslGP9Zesqa~voqgA58oz{Ya%2dnig^kg!UK25D)(jO>BKS_tLp>lB|!NLdW zF^|Nt4by@nw^2!$0OWBX$Bn#$SWHCfN^YHxjZwlJ$yBXPU6aH3DtL)Nxm2#NP8`*I z{0D!rDlpy*>}msBEV73nZi3N7M}tEY*398(#UIF0BK7)Jsj8kmqp!X+F**ge%j}zzlj=g)?Gw1CM@BT4%*Wkgs z#zHc1Lozo}aY@Y2kt5qUr=ff;`IKOpeWnQE)w}e6xkMnO=p3mIhq@=>ofI@WP zn>Go<>8;3_+r4-)>n03A;^ea=eTp~TLMjkh2!SKg#m+q+e@fCx`c&4+!iNl2;{6mH zr}WX&3|Zp+Szil|!%jXUpr>`af_aZ>4v3V6tzfsvK?76 zYBBZ|lYfe{rN>SDjW3Gf(;Wv){GDR>L`Sy7slFEZ&+BIDR^Su7<+{FtoHYdh;^j_q z1X%dcj8n>)<9J=noQ1K9dTkc=W3dxglm)lgl`BfjTqS*RK4d2n_t}ZDk2-@77xJJb z|M2|a%tDvb6gU>KZBup+%%!Xy&zW^=p8W^5;}ms1h0pjLy>IUn^gi1Qw{L4>O6nBo z&#mC4$j!j%CQynUf7F`n*I@P=J8#4rCynMv-(S@$$XyVUT+F%;2_2-G31@KtRoI}! z#erC`N20P+1F%O5e*joh0kV*ArcO0>v9DOyKsJsoV`GC@SN4^$i}&Ue%k$^pF>>q+ z?~br9Mz*x+-4D=Qf|W=t91%sQs4_7naZ02}ytgX1-`|TTYnm`aiBl3$(7P_E2SxXl zIE@FAPnzu^NpE~eRq)}HoO`gzfGOBK@0gFO(Jm-aDb$TtCz$#)TU(yJ%ziNzs#AGc z-nTsO%gd|N4Nx0x)c4$qTT%BKt!+58hI$D;BD6Rz>Xn8w%zm8Y6u9dGUL!Ztg(Igb zE-4YbbhA_w)k8Y48-hu;!U zqy#vT%f1l54*_LwG5!9WttLujp3=mVp6Gd4NW#SYbFC|$VCnsJ;*Bg!skcr1rg&qz zr=oZRQ=5R_X(1hNP^r3W=6qUUmu1p8C2QTEb+>5MuRU)opnMlHKE#Ke6ntjU?l;d) z;1g&e9r$~qOUcKlxyM<+snkrwzs`u(%^oPwU0yL`7Pk-$i zl91hos_3nPzi8JURzn=k5~2zVAyJvK+}NTYb(|AfrW#SU@bFktTL`OW!=8dhfx2%0 za_Z67H@|ak;au%^cPYI>(O**zZ(6*h`x9wV^(Grzdd^)nx94y6JeGR>Zp+$pR?h2L zwjAFcR0%Ozn&$`m0lflFpT^NH&4`HD7@RweN8pL$z%_J7Ep-3rhL|M;hQ)@LRf8$T z6kAr4GaI%4lAm%@H-0*5_U6QAhmW2c%EwjP!7A>acIDkHKCE=DN!y*Ni;^m|?$IN> zeRllRs)IgRF>QNRT&c2^8a}S~8$7T>^%sthY&PYCc}0JoetLLw7SVoMmrZ3t0?XJ- zHR#f;XCJ6Y$mb+~{})t2t^bl}9f^~fO1!_a&Vu9a1Btu-t1MUO?t?TehaYfy>9IOS z^x!v1Z&KoWC%x~Heq6X4!Q}T1PRH21a=GUD@H5I1h3-a>a*_3sT)LjofNWKwWP1`# z@uH4UCne`a9dL9?LqggstCu=L2en^P&lr-(NvV7J)-g|<=e#UK@9wJN!%r(~JamhM zDBD%tTej<58M;G4O61q;FkL2oB~DIQO4_%ZPkxa)l4Ty!3q0q1T&NEU9A{?or8Y_1 zdIcX|Ov_^QlY9>9|5eKR_;{t!_1=H@N_A0aCb{S6*A%*6L&`{@rNp!K*Oh}lTrqK7 z?^j1~!A`6T7Fh;qwJk0FhnI`<7j?WsNdc*^CSF`mXBK)FZXc1Qx;|kOkpKa`an@Q_ z+!_*M;ZMVdlrmK;FsRA~&sDoeuPS?7Lc>rT%EzHwWD023hwf}g@?K2+(8HtSRZYwz zku*S`jES(Ri!aSe+4$+`+|BLB4;wu%gw3zEga0CW99vpCV&a>wltoFwZMyfY(LN`B zs$MnonCx<8DnH(^SeLVvrY^_7F4pCedwUfCh3O=?_jg~{amcRY!!bIkGEbQ1x!~Gh zx{}^v%Xg$k&I6~`afUoFED8s;==uR~MuaNLiusekfA&8WHFzKW?iakUY1w|P^4^uq zad>1=S#1D&WP=R8 zU)vX^rdN%5_|Z47R@?iAHs`gq0}k@DhsQE!-@GopUg}*m>eb%0pJ`G#@AI)NvY3*3 z_TgU-I<|b;wyeehWh*~Uo-Csyd9un)GTIgW++)g9LOD6Q(i=hozf% zvg-9eLU~Xo(W^?1=aQZXWrivda`!wfq~M@bXvJv68Gc_JV zZyQ>r4BN{422~rrt<<1}9Xl)VFNI}I$6IrdrpsM>Rsh#i^)l>YtFHG z^FAIi>f_wm$45hWQMeGYiV!|oIU|IZIGm^|@&3xU7937j;&8fO2&bEap*4{#N8sdi zK`&g{#1h{->3xqXPfpk5_lD$BoR4X^Q~i^al+|R8OzA&n)qquoN&5Q((2x_ zUFSNI)0GlY<4gSw)2CYW0Hed!3Mm!90N$^DT2yKuXb58SRvkLxugJ?WcVor#;$ z%W}!>ntTpA$_uw!TpqaHXkV%1c1I%2p-B0g?+co*5 z(*AFee7dB+fUr$;D6!=)9y>DSJqSpDMnr~(Xx^-r*{KRxewivmw@u%|s^q`Ie|&S& zwh>i>HT0#yi`pkH8Zvlc2TYxK{Pdo0@(uhpa$qmNlQg--O--41ENArb*|{H&8u2kA z;l;2KlnjZQlJ#RF+*uX3nIS_ZC3;A~0F!`T4R3#sL44GUP*sSx5@kq6s(B!X$zJFlGr7Axj+BC@wA{G6H)gbv-mcW$k~`+YO1V z+^}-vAy55XUE7+D&WfsEseZF2IfLs*RjR8uS+Q?Wgv-wNYWB(r&G_NJ38{v(3Xg5s zV(rUMwXPl0y7jts=m=6(WWYj&NW^0$l5iNjMYZUz&r2M!y9Gz=jtLY3>0zD=dRchY z>e8pi$~1UT#W24YrCqurS+DiK%D27qE=&65BNny8#YY6bI5vGrQkSLa?Z$R2%@zl| z&QDeQh-LnAl@0u8F~40fdcn|RFD^MTtl8);JNREOLA%3{)y3K&W{Nc_ZGf}sSi1Sw zLhZBN&;6z4@=xzyHZGtj3}|ud!hJ;WTyQkdU&3tuafm>7yTtihIOM+a=s|p3?!~-% zz3NqpuG+hg(dqtjX-6-w`YNY%Y5ue3OwKBb{&)FuJmd8sw8%pWCZXb#SV?WdYQ8znYSJcqm)JPo15= zQdOJOh{>5eZ)LR)Smq5J;Xg{PHY$%=?PLz`v}fY^{V#OtzAk6xSJT4bWxK}r+Ffd7v0$j=%8<~WOO8#>Y7n}%()e$&7@|^kuh$F<4Aw+o zYmOrn@fHux-hH>)ux;$A%vTQfeR6z_s?nj3jY{Hum#yMQ{xMi9x{XLpZ@==ukVnfp z`1`u8Y|D;!POwzAh!wb3SpzsJGr|=)*wsNYR>X_6ZfsR9vxrAtk8%onq6``*-K)|5 z-VkyKBy?}E=|y)bbi7A}7+$<#WX>`BJbnVQjKhBKPaw-9$S|}aA0$t-M$#C9?e>w_ zd5=-g5vuEA7_N1XlqJWywsq_c)}zT2?HFsb9m5yjV&xed_5G_SZ)Gvw z)>{cA9jcn~@Q61`WiIN_e$k+;g^7uau=Pai(*DWEquVm=nb!Pm4{O@F`zT~O?({RSSDW5V;<+w zKC{z81w4qcC4f3>W98|8vY^1QU>1tmomd!*Dz+(s){w*Q4PNwM-Z!Mm;FtPu<3DYA zgH?I8|MJ0A%CoJuBxd~ncG1j2zU^GD=gZaMY}G?4%Z86wlQi(H(fir=#)6{XJ1ol{ zH@|uO{IS_Dwcpw(Ir<&epQ&Od6Vt1)x|1vXsxiCDX3k$fz<7?;f1mpN#gGg-6a z6raj!nb-L1yLYqB7gjLG2P5wOs!e-lLCSz--B|ZEL)(t)7RsKlwugUH?Ep*v;~L96 zYMpK_>tmU2n%+FpNfUy8XbWBMi}|z+C*%U96B;RJ z@4McyC{m1FgwmL`Mp!0|fRRob7bSH{oS&NPUZVX(ZcNfXWTKRGfK~c5L%mA!PGd+k z6K-75(Oy1$kQ%N$>Z7O8RdUa;lFwrw#ZL z>wA;*B#T@QZ~1zT{)d>tNv+c4NY*b^neU}{R1uS6fg=+cK$Bw6MA2e2?Und3PjfNt zB|d$`v{&M%m5pNBEBL#roAkQtTrf?0hmn-NcE{8jsb-p5TVxfH3wRN=DAU_En3P&< zijqzJEmZf|hv>Igp^<9TZxg&bB6<)|%+*S0EubmrF ztu!xmmhIW;&Pp(g2eU9QGc26q&ppiIwedz9$*g^4Fl(nxWPMm&zNIMn9(EoN!?KU9 z86^Q+-wPW`Q+^QZC_-kD9GGh2rzLJ|b4_&i$9N%d^-m4sfEX9@^^Wk2V#=5R-Qog4 zI@=fKwody7vzD_;Ek-@PfpzpSJ4@Xzl2~_c6mnyh_Qs$CbLM!5CfvIhf9KmSrQ*3tYK(P5g%TDo8-&On5It+UBu}2gWwC%&H1hUr-@a>;lf)4v@OXw0RngiQ4%z``=xc;y?$MR{6sUnxZxPh+9|uG)k~n!&iKm%aN`bq2cyZUa z>^Ft3*Oc_+O(pjX3wP?Vl1s9($HHClQTM|XhAH_F>j}gap|Xb{?IEV5K@M~mn%1F< zcjGTJ2Y*su^=9?S2t_&>wj z4XOQb!zxL$j3XsUXf)7%OkJ>dczu_hwbty-duwM(5?NhZRD^y+dT1!@^HFHxUh51A zwn~H5H?MA5@T}xzd(zquj>L>SZpc#=AjwNX(Z9XE{`xHZxOiHRneB9?`Lex(goSDL zN-dfiP2F?hXKu`1`vN!KBGoN;IdK8{NIq0TGLTA;S^0OB_=V8^CarBQ%TI!W5`|b@ za*bmph!t{==zr7I-(^&56|V{D4aXvC3Hw1KibS5YAFMIRHA~$QXeT#>c9S#9pg{as z9&dV$3W{1pCdF*89_E)9v;5`*VxE1aJIjBQuk@SPNnv2=?zvT#uu4br^9N06c#-{= zrRL}Jy2dihZD7mLUt`^3%i&@cW6o-6Qbm@-(*k3D*}mb|SuB6ijO5(*>RfI7twOCc zESYI*=%Ty~`cNMEb;;tNQD)h1!i?eMa2PY0MT$F_FF}pp| z{pqsH%*suw9BVtkTH9UqhrgG{pC<0<)*@9~@8)({p0gLqL+YiZ^8E91xgKRHo%-oL z#U~7GT&eLxbq2K`zHeb?QeXMX9;{`p%-IBg9ms{}j zjo*sVKd0Xmo9^;4XS49Zob90Se5Q|s>j59Qs8tv^Go2*IM&Xsyelk6w+6~sDNjuJG z`MN^Ej%PBknIilra$KLt7YgwNPFo=a8;9$C+j#}2={yTSzscDJL-j_i9^t?+|5DIz z!|gMNM6nwLpXv4%eq1MyDfxTx=fP*BpB`%e+*1ks_d@(`BA}P_y?K;{?WLTW}Ht>+)1;Slvy2U znvin)HMEzWc9L`^%On$!JTlBOn?v?5Eiw+X$rT+m3{K-ys4wZWZ9j=nA9Ygy5UU>& z&#~7Qp)`p$if2i^7Bm`$+*vo}3n~=3>OiC zOI7Q!cy<*#{)P@TGFU!tC1}Q~sH@p~%%iq-yA521bz4!_vij~gbDVz$V{B=q8b(+~ zVU9R6?!uwqViBpsHic&`#kiSYft_MEjQ7<+ZX55>XT{#MRwE-T#Xj6+7E@bnoK) zI6S14&m=vO_$cJ@EI2&gOG05TuGsg&I=l+gXmez=sW4j;_9U;sA5xcQs;hZV<1#zM ze>4W#+Hhv<(QK-l_L~~%{B!MN86d*jP%DTj6h(vb3L&`MD}aN=Gupz6TPUamE!h2~ zI)wMI$6n>eRqcd%ht~_B9VJvD+7{mI4DGJ+`~ut~5?;sqkcv`0G431GP_Qk!o*;-q zQ`&=1Q6Xie{TuPsZt>}{564!*z&qkUFD7AV?~z(;B*SGAR64jlhU+h~@RRH=VPa>4 zcSsKl6yvuZqK8J~JOJ(~&T=nWc6`Xl^IKMYI;j!w(Kxkn)u2mm@xOReeRTkx`XcXG!pr~*-L|ch25WfKWpU2^9LT?G=Qqcat(Le zK?z1SH7_iYa9tnl5q1SGEkzbpHr7S8zOc8IaIcv@YI3i$g*`7f4rrNTX`JXIC63bj zK|y5iD|jW%@}5hxofPl2hrzvN;WyEC74IXG^IX%gwNlRphrZ)TeIl+ z>NQJ071r;Wzb-Rukv`Tgf$4VsaX^UX`s6I^lH>Jpc%AMZkhi1IvWA5T?k)WCBbFc2 zJS@CStLptbgOAr-@8JgFUjR30x#u7ptr$zo%>%w!2rqDq^Ir+eeav&sw6%IDy_l_G zbL+X&&}KIP9T@um^CF_8?W@wFg9Q7HYDGE%(?1(UUy@x7fpbum{}8VV2^6 zJ^Ur!8v%PbPhrvh8T3u-(>tUI5R1{NJg(gNq$-W=-kidM* z${L(sv+mnkp|tBGyjVEA-Qe1lqaW@%>-LR*m8#f; zEcL!JGUZjzRTt=x3lgqcP7rX9FkGe*3kk*C#rFdyF|JgeWl_r+{yW=BYGG?<3~oCr zfkuB;PAY~4-nF^h?Wav!`mC1kZYdO_t%71+H3~(c8r?~h5ExcT^-hfbtz^eh_R_zW zji9Fmma1CZPXg)MTxlYvyey0?S?wu|1}+ach4~Us(}qbL*9^gUeE1}18Lv_44PjIm zZ%_|kiw2P$SyCbQPVXrb^7pB-lnc{&O7Gqfa-}NU@P9F~IdV`AP4ngHvU>+MuQ6GQ z)*H_3`tT!`~B2QxDnE3XM!-q|r(D3r9@A|y{T7K`zAB}tN!kVFb=65Vnz`2v3 zc)IlyO%hnhW1X7tJNs^CJ=?h4;5`#2>>a|IjM+X~>t3?Z2-m9iK4d(6mh7s9#1vk>mXSV5le(aHr!)m-KSHMd~D?* zTjvjqasMJ^+AKY0*4?rnvW#n9S+DUmLe|_lBp%u<=i44Kp^ z?fujk^-Z*2Yd?x~gQ@ubX1-eTz>!N6@5NFmy<#>L%-Q6UA$eniBMit#J&0i@MAU>+ zM4^$B$970dnSQy4LB)PqkcKeJ%+`;{6-P2pif<+EI;0;LTR(-2u5W$#5&e=<#wR1X zk;x~G&b4`QTHP`E9K@zg+Ho)VP&8)ZNA1-u^gi9V_Uf1Y{As1rq#pv@&!>eCA8g-Y zYT-aJ?RY;gBjra}ZOWPBaC%oJ9gourq{;ttR!yuxE+jqB##<9V=1KCcKxWelq=}za z-u10Oc5_uXajZbr_O3v7qqKpb#tLM6iVdwY{Ymakv!lg|+ms)N!}3|+pOhdnsVz0o z|D?g!#6@=&_HckYln?ipif-=Vs86^KiJnV{a*eK75A{ zAE_oObS=7g-|5G**+OMU33!h3nuUJ5kIyLOqO!dN`q8Y6g^!9bHdv8%W%1v0e8Xx&vDc<-*_ ze6~6G-d)9IT-M#Y6ovA??JIVFY;R%NSPXr>jbS=ZXtuIa2O*@qfan24&@QVrmm1$0 zec0xpszbL;+sYn({Z;S;m^) zn)v{dqM?PPqR+?5-iKrTWB?K;e<^WfGyJ$P9KvaLR3U9Ymkp0SZRXndzwNKn6M4@v zIFM0nZ(R`E%!}=ukHLM%!(DbyEazcrU4v)~G=LZv~n4%v*HlL=;k)rP} zr%lNDJk%6~(7(QdT)f!x>06mAzULEH+~C%WvMMfw2u2e5-Yt~a_PKvi7q(R_Diz!I zIpD>?Z9=f8a`-m&I`5e^kZm@qTDK2~&7VVu`Z9Iat)J0JX^)A$l@U0SzKRv%lQm6> z?WV+~W|(k3f()*-X*sF&%GtAi4QDMR_x(<24(-jD;sw;I{4G4bMKm<>d<#p2OMVTN|-JCw8fd<*^qQy}yA~ zV>8Zw$EL8VFYkSU)xDGc8ms@wQ5LmjGynYfDSql@nWV6F(nEch8dJW}vYS62C)Mj5 znO;4yO0}$dsY|+J4*BQ8B~AyQ?JC=4QL1~jbZCMFHCRcFMSb`lw(5H$un5WWXMZ8;_snW4Lz5&(?~K;!W6zVRg6s(71IWyVhxQg_IltFD3TR4(-T%`rg&-1;$FtK zXGR=c+KsIpGrnrWDvuA{drvXOmbydtESxxKNT$uke|EW?$$u}FDf5hj+D!ZJ=m7AB z$P&@Qsl&UrtIg!;?Q8lV-2pCDGEN8r_peLzM8t_p~8$ zoXGbUHxHQ*DY306xohhr+$Xh_t|xy;&a;(~ZD~QI^2CPMmY~gdQur*|RnIWbtYd(% z@W3WKyV$p(%mSW4D|04&w*4FL${gs)HA_Be3Q~P26)SU=QYj@kN8k&ABOha!$n;#We}Vyw;w!s2 z!HNV@MLV6^h2O7UGqRRkCc*)-T6B0`d|cPQg-;AvwLW0stg|igp@v-^@7lUihl-7(j=j44;#AMM(3Al^TQ_N6 zA-4XvZ!A4C2Kx<^OyjhWr9AD`p+#lO3RTTm_Q2w0vX)H-CRB;5Qonn{GyJZ-<^Luv z?vvBo^?DSUze}YYch1|^JfQG}Lij)hX}E;YASruA;mji<5pF-Ia@hxCUK;pJRQIV- z(^?;$IeFLICyH!;{EszQe4bxl(BSFF@0yMuxg(9esr9~p9pGt%F$hCXk%m5o3|*o$ zxs+&%m?mZ#KwfRxa4o~aj?zj`dH zam5aeT6Z1N9?}%R+kB0{469x^yC~0^qhC5g@SSudFT)Y5U<($m+QVrC`U#hGK%S7m zf;U<7(??nRqwAhOdX=%)_?L(HA0PF}ZN~~$uFGmSw5hs$*36CeA9i}?O}V-2RAqx+P}-J{~8W@Tp1 zvTwSZV&C)`tDN`T<=1+QPfcmkxL4n?o5#)nZf3n2EFrK?_g)<`yQa<^(rI{~^j4{Z zC&ORFdw$ez;kqwP+}{S#f_*+iE^S6M($2cmPqO@)33aPAE8nX6n69fXO{4LwkhNgh zcLz63s9Z|phXNkf^wcBE!6@E1Xj8$mJT^GPQW9!mXA33sy40`)R>{15eNUar+6lQTyg`b(_cL?3{Gqa$=8xw?W%AX}=|7hP!C$ z3x@`>P5jKUU-|2Fmi6*c_81OIj=(|5w53TIJ4Pj>#>O;DSiGcF*H4Ppt~x#3-}vo1 zwd~(#%*qbQYqRG4w7`0bQyiJxaIAl|#*c>AiS6H_)1nlX`SDUzrW0R;%Wh~tX$UIg zi+44+gSgO82U&*}$9vRDsPjm(a!-Xxb2DPJb$pS1g=iGk9LDD8cVv0dde(v$lF9k3 z*g;pKbw$`7SfZ81YimoeD@{FiOywop5*%Z^G2Ry8$cAGaAsk;Ot{@;cjZ(3SeZq_5 zb1O@f3_+hww&%t5!N``G^x7@gdLN#qw1>?T{{zM7C)u+kJ%Z2nem*YE{;t3q7<+6h z{5aO5|4TS>q3Fvl%C1Q{`|`i=!86*oa8<95X^n3QQO;u2NT^7-hes6y$p*cbe!E)r7OP_R&mbe7nmycpw3;__5}zm7>c!u$xsN69!;j|NtBYCgwk5Y zcKlz(V*gdg9lblQ7psm2kH!H2y3vu^**l*SlE^)*4)qqx{Nv>OnUThPtWFK~%k<)e zj=5d+?%S`7+n>11{d!5FQC*GCb!?FoRsjCh=+uGS7#nNO-NF%_=dX=g#`>yuNwQJd@X#n17uDV0$HY$5(9+0$;Xvwc*8 z)Y|o%H~IRKB=)I|GRsn#y{q_@=|w5uY%Zr!TZ$~Tq)zGOr&fGC`X%{M%G1jR>92e! z>_;>dU87*)>E`F=2~TxCzvah8fF)}+4E@VrTbcz(JX=2{ zX93uy*4iTd6vt&rkMmUM6+Zgu4%zGSaeJ#37q_?i!>}-U#{yzFCYALORRd( z{$VR0@2#;?`?NM7<;=k?z;t7g=q93G(?s`4R*nk(#c`}KL{M!7DpnXIb&lf^3m58y z5>Hj6&v+Xb+o2Qs@JagnD235!Bpo|V&p@;?g;jt&-oMG5RHp32_5UiLP7&0Xd)+s^x;u-n1>HpIGZa~5$M zE~(LuxSN2zU?W`9M!~)>6ZP~U40NuQ>=SV9e7Gk5%K8?ujl2uk^Il+&yW=;oL(Cb> zoT4sH6rmPzU2!+j=<`vs0E*;g0V!;SXeNo1c}ToB!^S}$y&q3z+=5foF6q-WnOts! zeIU?-51-_C!%r_dt)x#^7YV(!focO#u~KkN4D~VpQ_Hf-`0*XyihYEy?RMu9c%WeA2m1J%Q;Sz+~h&7SDnchSB%E#Kp-SvlT1 zyk3(gefliiH?L(O%VMt^U}=BI{Z4%2a(s8p%!w@17@{@N9&g#SEPFa;_9VVsU5Rhg zz#NT3!g?ulCgSI~MvYKPG!A~@%>B+T3!?hxtgc_l_zF4O`0^})9Wq|8yX3ras8SGn zGCXW>f?ls^g>A5Ur|)y`4um2EKVdyPWQ9>zI&!ha5#ksij$A;&xcg3i2rdE>2nQ3N zVD?70jT*IKds!3!R75`O7UGTo2%S_K6kFf=Z#!a4wuBnVTaZ`$4{xsaKub8AKNQRlklo!mWgA?yLe)1*yK91b z*B_-Z)2516=%*|e`(P!G4rt@X*};2P}PU@LG2qhoO>4G|E-?#_+s zZ$=&47e>5#j%%Arv2Of|drT?*2J~aTff^p6l3jb0arm?hQ#SMY_V2-odKRV+xVO^0 zPcpi?5)xvYTCG@{51%g7I#~8q`x@ z^N{!|1P&|ViWa%G6#I;ENo6puZeS2IL~XF~xt-I}lhdB8+r4eY7g`_59kF)Y;E{Rh zO>>Q3WjQPqjvk$xtaVLzq#ZvtHBox z+BuZ5mh(?9TT}FdQ1n+uO_tO)rfSWYv4!*b+>M{HN^I1-savv>^7DH7HSVT`14Q^b zPF%t{(XGWZMM@l?T@M@G0D0`SI0YWg^4f|xAm%$-Kx-=>cS16O@lRcjQ!P&`6?u$6iUgU-Rb z3$+G3yF77c18pBdLVSlO^QMB#Rq9l6|>%(8s+Of`jrxwln^Yu{36og4LC?E;-S%fu6D`B2 z!CzB{8p~KOy5OG;)R*v;Y>>NLh`a1bt&Udv6nJbw9S-Tm>#xgec*mVBR zwBzHSJ;84B0T`HsCZ;>!&O<{ur(-{if- zNs{0B^9K&#s`0YwS|fe^xtxAq?o!u^;}T4nib@Bog#L7#mnV zhh;OI-AQ9CFAw*@tviK(7Uh(O`U~srHkQ4~Le}?r^}AWijNSEk3wUUx44sSVl4R|y zGF|xSTu%Tw2hT}7c>$L~M`&lCBgfN+zb5cQz^CEUU%*EK$4ALph>wybAjA%$Pl9P@ znJPF<_B8Qw3MiCM)HlVGDg0;gmRLu;Fn?aFi~Th6L}5Co^!9N(0n|o?JJd;C*nLX+A+MT@@rZrimxBJn(#Y z=a7+Xsb{3Khn6ay`|4{X7ppH({3a-T^)`~J-f+9;33v9euamU~Zml)mw6_;;`f3jf zYwd{wj>@V9P*}A<6maUG@M#0`S+xNQ+pB`Yrxj3GwL%nV5x;hnsn7uo6*V|ZxU+|P z9sh>`Q*YZJ)L?}RCnhC;~C{oLaSYgD-g5#4N3hho%)Tt+S^66SV80_5N z){H$Q3|6bhr^Bkbo3A}}im&CkrF<)6EIX$>JVpy)u>1-P_WVpTSXRhammkSt*QfY8 z?#asoX&R(KyEuk8+>`(sV175ofrKCJJYeE(CyJpc?~e4JR+gt#Xx*?vGke4Onavk{ zh4YR~Y5m!nxDin?j;6t}PX+T-b)vRA>Tz5Ho>;Epi-(P&>YTUVME?D4W{a)k?qJM- zaomL2RC1=n)NU{ty!GLqc>aqHC4`}`b!c)oabM+k^aiGkMf|W2ez_KnWQ- zH7(i21<}Mpe5(f@h9aWKvFwW4yw!}X7IS}?JMWhTt+MB|RG(ni-3#kBeI)#`24N3B zQO|Mi?#Y$>(zTrppMSk`=hvTaxZ^6TwAvnZ`{TYd)9Th~+M-tN{<;06SHo9{=o{7# z$q@l<O^Cx({)3PkJR4yQ=si-tKEH^3cKOXzk^wi=Otqc4YW|m=dhhfY^wMU z1Czt`u>v1J!z{ipfa7(TnpcEsXm3YQFf!25f*pPc({xpDH6QmE1vRm^KhOIY)&EYb zV4SU9rfF9mq_vIS%i6QjTH2jn?!D|PAy`h-KH*2MfO!{0t1pAOh5<$lYdG@=O^dNS zjOicqzD0G_8OBOAgdNicutH2*x2WTcBHr0ui$5ig(%n{Ujq#D`ceM8IrbQoUwcKaX z%=juoX-JJ)-9atzRc{4t*rxorFnz5Tx35n(GV$?n zwsW9*U5)a7Ivhv;%@2pmR(YkRW{i2K&q##i@cd$%S*X2U!JH zicS5L?dR)mvaLr{?Uxh($J&3uRdqaZ?6Jt!V0QWBc@9w!2P4Yay=l#4PSGarj?9R^4 z&d$uv%o-lh+F6nZ6&hx-HF6_zIl@-H$Ig;{hPoObyr!~->uQe$jSSZ@Y8)gW#Vzj* z8yxgmF{6AcC3NZ4Dt3B@GPRWvtgrE$ZKZVf^G6C*%okHH`dXd^Nn@?ijcs7QS21g* zTOUCjwRk+ECt@KRNy2@lrsVp6ldp6WC!JcgVD+gEeM)(s zaV~b$*)+P8ekvyBRC;w5sjqXDO@W+~bKx9~$YV723R^Jr=88qS-) z-sTs)o0cnm)`s$3{tol$)p$E|)ONs`wvk`nKU$i1kleDby_3BTUp4Rvy%Q>U{0q3z?h>K2|)lOfba(T9?f^|i+nN;_+H187@ zIVsH2T*nu`7NP=Q{JO39tvc-TF1fz)HK$gdf=+dk_@ZNN_8Z&+_+Nc12Mg{YT%_ZF zKmbJNqg$?}h`|js9dkiHv*4k&SlWDW`bgH-f6LS+>C?y8Z!kV<7NiYKQK(+@=4^^z~Gl{(Pc4=E~nsLMtpk(Sjg%kMo`1c9i;QT(QbUD@y&Z1nn% zzm;x*dKj)?{q)f7D7nG%ly@Wlx`2e$cPq;kk1*bvI|dDj%mv z=-vvYUcZDJtv(vrf{WUPoQ9d%j$QM}=7)9X$T~ZC&8YL#;mBL&%WkiO*}KQDJE=A1 z8!i}SD?*QO6w$g-iVu#l=Qh0L0Zfp)%ry&%8~1gb;DvIDMO&oMI`cnOHjQ9>{OBgL z9_+z{wYVyHJl!$={43kFV=HxjCD$$3MmKQ9Q*GVy&NBu_PW5KFOUdEl1^Q-f0WO2( zaIjj)qX)VZ(w5%*lmzJ$<-6I}WjBlRJ7)ot`6*paAp=Y$7=)=u#UQ^XlB>Ktiy9{# z8};4Y&QEi z>oE0t95T&!>Zgszsn+tA5nk;hTi&1<)P{YxfUGF_88x|0L1%BVX@4`r9lY|}l59r) zrzrFljlR$3j=h@3R_C(9d7H^`Keb9f5I$hrXmM4*DfI2CDYj-?1D0$ons+)Cj3k-z9$V0Ih=o5 zUhu<_YY$^yA6P<_`+K)zCzY%7RvRNWqp?i-W0~+Nw$%WL%93y$lw_{CS(}cr)g`0ls#6^~9+_Z{Jel$b zJMukyz(!plqOe)~E5KQq z+?}-dW14z*s$qP?Z-t{LxFk;3tnhc1*3&N^v4MGSxWrKS)C?v*v7%~mvJoCwNCykw zD<@M3rG0I!6*vPASC=o2pmI4+DdJMV5q9>OG=4+CnJqEweQOq=t(7+d2C-(g&YA4D zn;EZn4b+lPr;NUHcGS}j;l`nc+=7KJ;|d=u_sml9mnxAWeK4tuS9qA%I5(1Uk*Nk) z{+B3DEi**S6Hr7XDb_>;}LZS3sPuxXt;PYdfewR5M* z`XJ_ehxUBjb%cCxUnS=~Hf1bYH0iTw#!O!-(;Eb{n0YbWth?#}`S$&8@|4|ugLhBM z+!G4uco<1HphgE-g!{^2^gsjBNPH0;^koiaxG&6~4^M2(p=#j^BG*%mE$i9ytpgVf zu6{_a*mZtOK8@%;<|L6D}u@ zyoi|w(sT#mv9>yLs~yyIW$CurW6)HlCW+)g5mENQ<~c@otw zDK~?Ij;Y^q)LFBVw{HyA9pvG7q8vsZx+un7Jih;UUOdkKcwRhS@w{lfCG8iF`#;_< z9{+#5UyKL-ym-7N?HA+YKc0V%7aY>5c+m~u31f=!&ZT%x2MCk(5Tyc|A}p5l_8B(0 zSFPHbip^py|MR_MLZYnV-tvmxGap|Bh3K!Syre(z9GePX$1wCkZEpz%Cxm;E?Ap;u zZ*sp@Q=8gb@mziUk;&7PyAJYQ5{Vov>qvRWZ;|8T4eaU8sMEd6cbzdPEXy}AJ1BkN zpUvL>uqY&ARFka49us_61ns95+LdeAyEVJn+a;~L;7TC>8UDU$J%h%#*Dmp$ zePLoY`*R1C?~&LddV2ebeLp$tUmmAwOR&@&!R@{QfFNDM4o|B`a zmFe&e_rP&W8AeGlM6JleCqAx|+pe%76R1|dzT`*Uwy^J?zh?Wlj6FA6PMY_j{fwC5 zQ`>i*7CwB2x9+=}Z!6ZYVkaybd$q3Ks;zAt8^a#exXCu3IYwPB*Z8(@OHDKPy@m@m zW6q8pcXqT*@ZPCbNhbg@c2ngiFth6X#u86EYawQ;r-61tw{&!JmlZo6;A8R#Hr?*Q zJ(QiLbL4WbYla_5^QO<5QI-~H;|{&cJjcGr1=}5Sp3_ZsVcxx29e0uI**)a2L(d}3 z=fq7+Cz_soPj*=2)T>>`p}o2%>=@l9=Ik`T8N*t)ZaXBiWA>@2m`k&}c8+M@!5_pX zKol%awU9reu}}_#w5J#a=#WKmCxf_a&)HcF0Z4^#XJN->1GyCNmezKve8np`Tgxqe zU>A0^n|ANp?E${=?Z&1o-AeP~P8DhlvoB|~%O%GX^GQAt!=ghLhQ<$xO$exAf^&HT zfA@}2e-YBeUb+x2>r7zxKg=GT+IlV!kO%Js=n25?3&g+bv~58{l4c0L-z&zppmFYw z%5%M|IOi@PF4iu9-Ay&}+KCGTwdx6F%JSzqosO_W=QI8|6sypJax=2C<`Ma2bp7$i z@zaUYC&tYw!y?UgP@O$z$!#YlVb+iHWQW$nX827z8`Ea=j)d;LhIVY%%V|yPz5X5A zM|AEw`%+BQsqBuKL)rqoc!2i=;ML&LJ;$izVkZeHnbRg96Dc$isUh<}O(5!kQdJAx)g!K(|F&=MPxaW%E(LG%PaH`UHLCBz5EdQ%14;9#4h^Ap znU`yXUm)kpjQ!Q5dt-P12FpzV%etMxfHLsh)>CA6xc~E80!~>I1L3+m;Iu#xWG5~u ziImCBc!nwAOQZb1Vpg%G8&&MP_NeAy>DDB%#gw_-svGC7C_#3d_Fs^l8q1Nb-$4!~ z$eu5+CA&e>&kU1UezodIM7-;N!hJE7^NmfV@{y8_*a%hXFDZ}3#Y}vo{jxkx6H}@_ z-@w#%;t@}z#*<^Ai;Yt)AEmios-2Sh37Vs&>hmo?tv?O{7b`&{!;LSLIUr^`5%`B& zGlikw#zq_gz_%NMqob1(CL&{d!$oi#sn2%jWMkt$Le|)U=ZVGrANd)0wh#24mmly;V;0l)XTsXihK}Uz7ZF zOvq1aIY+x%K2qv0(EDY1EF5_FbbMJJhaHj2Pii?wui8$$<`041FYAwj!%s;&CG``a zdh_WM^@ZG^wv$McO4=_~os58f992K+UHK$GG{H>z$A}GaEXfQuPGzd21X&bD?!VyXwKR|#i zV4;S&Yc0v7{M)EG2VIqQ21UD-wzJb$uCWulc2b+GSE%(a7U7c`JOhk>L3Yr@PTJ+% z8^0o3$!@WfLx%_sJ2U`_T>9a|EXCNKZfv;H@7X){&8MG3uVVe2E_^GGgG>WG)!$B5 zcO8+G(b4HNEmz3Dd)-zxE|WT)dm}-*c@pZblh_%{Dp%j(^s7qYTdkG(R17_mFBV-S zixPguqD`@gc)8B}(Q9PXz(s9u<;C1X&{R8=trdt;YxAWgS}`99KRUitzUPn$(p1Z1 z3s3PSQMmt$@;LU8*DpLO$~nQQ_2UbxiKWsB<;zR8GYY5Ud}-A|`C>t!YW;-5#-jc= zyd6PaYI$PeCei-mFUpe&`w2L`c)1`cwf?xmbiT9-*P}d>`_GxiJAnPp$6L4!uDnUqU`{PaRq@es@8Ju66#ygh%4UZE^)3NY{ zZm5|fjP$knt_>lQWT!?*#wAXTacrzkGB!sCB70Jf#w~+o*|4FBok#re0Zn7u&$^yn zkM7vIv@TP@!m(OdevmYf%8qBJzv`aiBfX^DM86?l*P^bLRR(U1DRA)XDHA(mRh#)WEfQi7P1%@%eWx}t}ex*Srr?bn*5GdzFk1FaB^xS z^J5()%A@O$V3(*&&z$wQ?%ikm_pGNTm-^3{z^+|8mvd%t=#tQ^kw@Yrid@|Nj<i5()8=up~^PqjRIQrh<@Ae(9HGkCRMUE{PL=E&hUO?I#;RnRk!RhJ-=J+ zvchY;V|{O%8clj9pZxZFk}|OzcNf0ZXJS2Y7b6@fdtzaVA5%btnwS9kNqAmQX+9ey zJJ7dO$H&#)-Nt2N*K@ytJ+sr)O89+0Y3o&uuNDiNtM9PV))`OgEOZsPp$h51MKS|^ zt&FPhNKS5*Ra2Fdxa*o@%xWENrj|DklYULtIqcn$?;a%AIa0OFh@cV4O>y?05*;zd zRfbA39ywhhJ>=L?-W<|O-$I}%xA^o8+BC*tytg5E{{VjY0q1e5P7 z==aO=SOh`xb(W^W3;I&)A7B^q-(wtEMuT!rd84Td>~P~dBxo_g!j%U}CQ9a!TSNFn z%DoODuL4UPHEwijvu?}B&-gxEp@;?D?s^4v+4eYDAF6Yc0?%P)c7S>AMC6V&a0#o6zYE7EdIk|$b3;6M zs6&b2Pqm*Crf~->coQlX`&Q!yqSLQSJb; z$|j029gMk}L{Jyy3JwSF&@#I=bsM*`Yv3`q=l#EwJKPJQprEZ+^>SwHR5i=@1zeut zZGryaq6M=Scgy03HI9-{3HV)5+_?S+4t4mWGbZ*#d3;XL3d&_pi zJ-S6ie3R)vjvmKT*2D+=$)4qGV)q`5hsNp#LRiVW5Nw+}Md@^re^x@N=vc64wKTM` z*!y^0!OD3lwL=hZs<^&j+G;zAMRJcAE4MSLIKVSui4Km`PU%N%SlW#|q+MrAW&w7?s9!qPJG1hD_mP1XxnTZ7fJk)V@X(T(@@#tSH2Ovt~ z=eTy8Xd#54+y|sjlx*s`*;0aomvha*&w!y` zYScA~2LOjFoRW*9rS22W4M_Ev^KSbmc8jP5P3qsPe?7Co`C}ur>RV?o9K^ol5Umi$B=T zZQ~!3!=~rN11|)mVudbHSExKjtWdR_uSB(cq_&En&M(VjCvj$#S|7!_|4?Tua6Gcqv6K7yT8Lm%uBYgMVef z1@1gHKU=I5lfxhP_tWWYiV~$e;VZ12Fb1ug_A0B&H?`%mw8Pl2b03JGjT_q%b?a!X zO}nL!ZcT_LKQV6TwkpM8U~Fq8C;x}3%i7g*Z_~hYb%xR^e_T{gt9)(bNT&cyB;T4R_su4;3PTa z?qfS%y=L3@!5d*s{QfNCuh>_+Wruf3^8aSDS#$P_t8*n;8u9DlnM<~?BTs%|>kpqq zrYM^fsex2gD@m2}st!Jqk-l$$>(Ir6PW=ofx(QAke}ijP!ugcFa22rC^2`a`h>iUl zE(5Cng-fv~#wKYN)u1j1*G~T-DvP~8#+}LgL`L?(@3J0!o5qJT#L?Y7GhyA=WrnVc zOg|Js?E<^__0M3tQtmay~J4y_FcH z>fGoqbH@y4(JcP3)9a*WNzU!+*_hYWE_us_83I!zFE*%>g$Hw&E8EUE@-U7*6!;n5 z&WDDBU+4wRYEPRyXcoOdP()Q$ zktMcaL2cM5xt8?AxL)cbKD7)_q+s|x;)6xdky(~;7DL)2n8pkJJ^Ut3UnJfb2@7Il zzR3uFu39*`-x~gce5)_Ayz+U3a49AdCegmMOiqg$lHDSr{lkah!$FC%dCnxU~yNFv6)`f#(VaLC!u zXG$kdfiP3gA*U=gwKxPVmQ&qUijj%)&@r@{{x&|59B|y3xdMCR7Ae4}GvrHlM($dP zyj42>rj_dRrFz@I^F)R~z;?p#6?$kB{~YnnLU<*;$5UY&hU z$=S6>^P0+6+SzOv%dBeNHRE71`MHcalOt_Fs4pU3j?5y%Y^bt;a_Ek> z9v?!cCE;inmBvIUElH*x((3My*S zG=IsW!ISFzDG=KSC{%^y;e7Kex z#&hkBqIIOaM(=tP5u&e6N;@{x_=^a52CD^CfW(yZY9)VE;6EWsEVE$4v=|fTuHdH% z5*dFmLqwLK#2WBo;OA|q3V#b#?B_lsGItvhyH*uYCsYGf4n+cOH4p*Eq4G4IiF_w} za5Ev{Ce_*5qetV$L6bup2dtK=_2{va>hN;*U?<9&1g;#_*!ZDLnfD*)&8Qn>zkMg! z-x!5!u6^YtJ`*+!Oud%OzSA`ub%QW@Zy!ZFUQDt6a5H4VNHi(pGNl zw1vIpan!6n-8O1in)kZnF=H-Gz|8oXm8&1QOZ*-FBPG+f zEK(k!zP;R-pnuEhTTGhZL&_ta1emcod7`?N1-zM|E#Xg94^SSl=qpWgR(522SO(>^ zof0<`#K4;{_Gde)I8`MJYABU6{@rKDHx0##si;qaSDK^ts%gc1UC14%v0_X;iM%Yr z-S*%5lMntbSN><0+OdW>c@|CikB-$n!R#6^hw($ND*k~I1uszX`S>Ho?Wf(wKlv)M zeahJ2mH%*1En`!Yjotj|=h7{}K{XF_z)@e)lDfs|(Gs_p*x72tW-35XPZ1G)PZy{u z1ubDeT`<;1gYAanJahVR=Ay039{#obcbMo3dPE2GiNjnD!-7=9yiEo>LX${+R_u|< zcW^uUnH#$>qO(m ze>OfHuF2-Wy#@RdO7C0huJy0Do58m-=bE~&qoQfJm%iFwAp+r687 zpFO9pyLYoa&qwEYd*_T!n%AMjJb4utOr$Wr$n&1UOAPlXM4Ua#mcD+?7G0#^#7pVv z7ZOID&&;^UuTMtwmbgSr^{eL|x?DxWvcs0+<_M!a{sd3gHe!zx#}SQApk1sxJWiJq zEhYQY($@SnFi0UAjiG$7AlyTJZBk$f34|$7p{O8>5`Wq*_7i;mna+vZ{O` zD`Mm$sqq^cc3B$t>xLaYGkQrhXF z3`;XViVnC(LQx0sG5Aav#TYEK*J#Ik8hyP@%9$BELnn8ZXygy$^XsWQwG>1>=CRv9 z`|sa1zX?@p7?l|kKffz(@CB0wm*V!^{?G<~7qeCe{eUPO6xE1azixj}^_Z`<^}D`GW1?6Zrf)s`b4*CNFQ1*NDk+=O<6O zII3m1yXXAJ<1f+BS1&2z+{9O4%eM;e@|%abI1p(K%NF+ucExc?(Mf^Hr0$G;2WJN+ z)02w;kAK(dhp{!R)x&Z3a+W-wc#~xq|3YL7yleNl1~eYDH7@OR9Qk{D`|qI62cDd# zklUnRN%Epi?5FSYPq9;bPm%wvJqO8YGuvd=D>Hac!il7qJ;T|UIa#BOvwxiqLQZ8q zdM*Y0XCRAqn4yZk`&VpqGPO{5QI)wC6mJEkHdJ>k5DCN8YZ~!=$mCXBvN-s-F{SDm zlD=rb0{`yRJaG)<%ONaR+23phfn)}%U z?U~t2*(a)FXmFgu|N4yvIh4kI03%dGUirw|ng5)i#> zcj?f-ef1XYsNBy7smju&?3aCjI~s6T(ESa#`Qm`O?^H5daHs!bp};_@l{E)L-mtAa zLEh_g)My71=QYhHNg#BSv%r_5%C-Ae!Wr*cnAXTlpbbNIzMb1z4?8P#hC zd%pm32l%+oUI@)xG2u!8RAhz6J#v{};VEAD=NW>W?&BWQWzgde8IM+G0kTIyIRx&P zGeh2^pR_LwlFSOKQH_H0TGuKcKeB|6A2FcgXnloGYs%l_Wzr{E$;k7&o??sES&om=g|6CfV8v z5m(MGmKvV&-xTs|tsMsAa`Nl7&QNly_No@Hq)eyR8~bA1$GA!R)Pa$WL%fAZ=uLS zt_5sH(e9s-AfO=QRu@{6eJ*B#p^bdf(1yp9N@h~QA$Fem{8F(jb(~IBwWHyQ@Pz7a zWcT?mkWd;n{tM`=C=r3l3I-t!lgPIX9!|q=UecZ_IIMLWx3o)_MezuY_LFV@qQ3iy z4zb)-1+mg|>g$sc5}X~tvo~yIw+^3XKP}JTVJ(3XcJ8#gC7>vCM&<*eY@F%GL^*qd)zK8tzw3?YlU zRb;8OyMLecd;C~F%{JBWYCMD8ef0~wKdq5Rb+##_Zyxel_#%M!ZovQmG#J~j8Ud(Y zS}EIE5xMcPbKgES{Rb93&xd1eg@?+U&iOz1NSVd}xb(#>@+Dfammwc^?LrFNOceLW z@k&y}v#td@y6Uv-7pjtHkQ?O(%nqHtox&Eg7e8!eY)gHe(aI9hYA@hUBp}e`6|Ww? zgNh&Gn4ZLXX`S+7baU~cyh0tZm2ev=1ACWr zB4Gp7KSkxKS-~X3Y^1ZO?_KY!mX$rL%p040Ia#_+;gJiwcFG9g*-+NApY|PN=k`(Z z*RH;GySH!PW?!zgMVo=M2d5mF&@SQbBHNCA8rEr7ueF;^nYJ~CZ=1CEUc!s(>?$=O zpZs|q?X2|*J7I1&{L~bncn8#l#5V|1V4bFdIowG2P%(S2Uq`Rz_zD>ce!E9F-xwX-kn#q$DJzGdar97Uy zqhQxa_N}x-jz{{Fd88{CjNwE>KdfqM8b;pY;UG^Kc0hb!HN&TD#rK2DEO|c|ky42k zlnP^9ss$y|4C52liDgw{(^x0t6XguXk^JC+d?Y_knUz06$@!#FTIZia^IFXhV7;rw zYUL+=jwa^&IpYO|nY?v%g3AN9y4Cr$m7fd?9JFx@*l!IRR*=5ZEhooQm&kT88`^P1 zyVl{YX=A}tZOwwG6sjF;SUzoE`;@Xi5iat$@2Pyw99FP3D58ODEIaY|F`M@25ytEV z?)1SlX6_Y*jHt-`f~Zq9iMc4 zrq|eThtRqM+&lOgmryNZGpcHEAhQ=8(uRB;P&cVXQ2+j;j(q*ISqJ>f!EG zh&m0?9w*L5JS?V|2fNsC6ci;CGNp^*{_)?l_fk)5-P#Jg41Fx%MsXJtNl_6Jt zEdG_=vo7VjBrFVG@g^<(&9}YhCv>Ywdo8B?%+8#GOGd9e1CEv|?4qV`=Bq${&}adv zTVQ7YX3N;ETld-RMUDET`bjfc!KKlk=r)od^`P4yBaX5+Cj6;T)-&Pn{|=w_8at(J z@c#&w!TEoN=$U>IXuGne6(BNQZotDa?5v%ISnQ^JiNOjGXZcCJ34vAgN@m4wjr*i_ zgr`7a`6`2^b#I-xm-V*^Oq%0A=kdgekLUEtP6(_*8|vpiXRnvSWZU=8Kj5lUZW(>` zB3C<~Z8NtH$m+!=YBkroM>ef~yG6jnz=nwSe)*c+pI*;@yr0|34;zC@_TbP19Oq+Z0i9Wp)TMe4pqGm*xV z6^?;$s>8=E3T_e>{7xEEkRe1Dp3z!s3Ec%mP5jn&Y=UrZgTFjXy0sh0JpG~2-JHOv zO**8d$$81K#1~9hCAgrew^AOg+|YK|hwJm%P4?YM7*;?2^^zk09)FlEFs^Gd=FYl& z*pleuqiN^YELb12{Fm)n6TF*tC+gwcr48v4;7E2$IFb=TMm_x9!}0dVY}!Nz4Il69 zJYggp$w;n2eru`jnb2e8{GEgMH{{nYU@7~RW#0*A`a_8WEHlJbx}ULzdQwvo%(P91WRyN&vOWR2L1X-V7> z4dlF`Fu=UN?y<^hJi%4D>~3-*XkcMtKSlw0G)`x|shr17TM=^lSd(9ba3Gjx>CPD*fz?PKuNX?D4VxQvU0 zEFRC;T|*1`T<_?nlL63aSfk>h(w0}fu3$U_7+fZ^uoF~)Ge8tn*4UlaHbrhwJ+T4Q zeTWfPOG$lbrPMF1H?2gHvzO3w|M-Iu74{7(=ph#xd-fP4zcpC?!TvxeyRoetL?@=K z2BsKpDDie878z08R)k*k2id^!4`$82KaNGbhOtm0nHK&^Mqf7I^<}Dw@WrQ>Uq7S9 zwEy?t5u18Vsll~r=&KOX%FX6M&@^6yuH=5YQ~0fDPT&gyQ-N)+GCAO+LE{NUf}bbw z{v5cCfv-s(Ms8=)YeX=#otv{>iT>pmHsRM_4UcuV3@ahPk2?RDc%m+evY)b{6n|^} z+#9jcza2N$H-nq$Zf&;$e2@eLxdVnjrp$Z<9?$;q0gGhMF0)b5AFmu`uNPIVio<0% z87PcWRr8CeH*pBNIwl#4({@wNs8R#eK31)it<0n=RCduurwt=FMLu}A%a$U++ zvo&wnsbx^TWiVBHCg^AxJVT~;DP_5)|AG0;*Wcp6qcTrgQqM*JT6OJAov@oD^dhg(zV3`e+ zPg1w*H_06lLwB}3{f+1XTUz5=ve?}<&b{OKUVamO$lZHvuWpI$+a+}0Nfj0^VFjzV zyr@;_>%ybk*}J8f$0>k3ALuj}p5^=q+nytYEZjhJQ{>*{xXt)nd(;?V1|lCqfr=Na zYuH?gt!0bo>Oy7lCvI`sieZALxjL*4(`4@gJ3wTd-T}IUoosUZ5ChA($@ZMkQm+1B z>fmL4`mT%$+1Qv(9H@O}nB-E<_1=^NRC@!_akjq3QnK7RdS2f(!=iITsf$MkZ40*C zSm+zEja@r+g&pOVj&SsT486<3drpZyQu zOfLLcx7VyTj^COA9ga9Al&eW%pHZzYk}Mdes)vMw!cH|PZ zf>9*!tCdM-uSgXdEjt&xmuln!EiWRLhxAz*(RE^&Yu{xxXvi-JANUH(eF=R%e#jAZMb=b^qAG^v9?W4c}xq}C<9?&8py-RrRAk5itOuU)8jz&Rl=4ksU_cGWk z%M9Cevn$Xbwm2^eIGqaY9u|wJG4~Va^DAN?VaqHIEyTtLR|~Ewm5fJ?rnV|lKKZhP z{wS}0$rfKsJU=1#;z8JN=Lckledw_U<#SM~`WJdS`Z( z=YsZoj_uH4Y|mcfI|^RKhhVSHh$oi{t;!S`?Kq`cS|Qe(+wi!}jwdOCy&c7nCmTs} z)^c(X6LODTI&z7fK3bVJ^9i|16&fu)7q^G~oJ(|!`n-x<2BV&7Qiwv9o7Eor3!4bT zX90_1Pv@^7D@+UZqb4DfI&Pu{$F7psezsXmOIVBW^seE#1A*?Dg}1?1YvL~}et_#~ zgOCug{{QB#+7G#g{H1j5|H@h%ER(+ zs2uFl5~SJY$AqguvB7X;O1%8A3F$ck?ga#puY=tKADJ66%LSN+3O zXZ;cO-K|S(^F`X=mop%2PA~sC{RZUtNsFUzKTDU680(sq-^6a7y1`Cv&n4G8QOiR@ zmPbY{>(gf$`p?8_^usDM^>5mqaApE9xN-5H-De)5uD35zAb3PVFmq(x=pEoV!XB)n zm40*jhvoSD%?;~6$4}{hgW7Cg!_Gh8a2VT5_sW#tL=A89o|g_5P+$YgD7>LFAa#sR zQ9eRS!R5Ldr30iCTxvw%>Zoxa5ax;+`p&8x^GtV5d^fk_-?1<2K@4c2j2ZuaPw)-? zz3eyq`#P}2+h(=#y}T{|{tE47iux7A-5P6AAGa9cdBvLiIWn>6?wZ--dnJ*AJEQP6 zAgqb+76QV9fUu^3(30v32sh%5v!X>Sk;=OSqRIlI$9TR?JhyJgd$bb#K5+dWqd%Q9@p{e9A`NvwZhFQ1G)(KogZp@Dum{cErJK3r>KR4uuAQdEOy%Wo+`zRcFE3?DHW)pcK z=T`n;Wy!}EGs6eXm@%k*j3;n|HgsKpD@O&m)##PFGPnV6+n6sMz}bp$@bW~9!t#yQ zU2&s<9i8JX&JN?PdyXJm=2e$Q=qk^M;0=34dwzVOEKwUbZwJH|HQvzF`0&CP$~G(l zd*LnW0#;KZvlYolB4Z`@BDI!FkE$xQP4agi)T;eRFVBQ7G8>}orG|UXP_u2_ruXj= z*_8G~%p{r_p^jJ@TzmV~)aG@AhI)1w(xT-^zq6!FO8>MyaZgr@#)Bshs%=ayjSRq5 z_9p=^>>@x$LtPU=a40u>U8=k;F9(v+j8~LXIt56=cOVHO9e2HPmk0u<4!{?mkMUy{ zEN+p06N7bgk^LgFB`G^1Y?ee`I23SC3}_o(G%8x8-N$b2Jz37zk4mQ$69NwC@nrvSqB_S(cA6?ZBAannX?+SR3?EgkT;74B-T6&w1RhkdcJ;YLY=Y zjyaUpb|9Y5(A>FxWXRC)PH~=JAbIbM;6YC8STHp!-{^Bh+fF+>+qvz^s4hd@OnA=B z-~L^GkZV{{A6vP;;dUuh1Hv@pdF7N+2-Gza5SBt7GA{7NWm<0lycF^{p}2qmt>liS z@%OmiU=D7)2M3oHDSJ)W+dHGrKqvADW|zw=g}`7|)*!DK4`ZtB)U&gjw^Pr^SABE4|EGl3br-ek?U!YY>0Vjoe{QI3HYmIX=SG?!|h`@QIjMxJ9CK^cAp&4 zV&M8AZ0MykvJ32g@l-Y(fgfAg{9V7Xui2msDl5f<-E?DK@|dEAB-zfj8U!mIKV)es zk2!OR4K+1H3wc8<%b3mHhvWWRe~RAq8~senGT3`#HXy1Gh}xkclSU*&x%#p-n?hrm zm-)}^#HS_fxJD|jCw!?qxxj}H|IxNH=CeQ4H)Lc+Am{wV;`d172|$BBZ(j~F{EF76 zm^psDPINOsdbJLeRJn$&qk)3EeaUgr(J8fv&0vpHpM+bgdGW}fJq0_pt2LmRymHwhs zmI`Pv1Z_PG)dY>FHSWn1XCts1!MK3sQiqzssTd2`*&6HCQO*0sq0!dcG9~JOaghLr zsu}VY84pBF+46+F5a7tK*kaio;QYkFp>}O24xl>S0FL$*2j`&*jxW_2II*pqHOqda3dU(K+`IzyZ<(!eA~ z$2!P9B}w`!mRb>;#LsVpoQ{{os;?<2FWGTqbldRV9tx$8)Sl|&M644IuiDRiwmM;U z*RtiR&NvsBbYo3FqJgVUN5q}avZz2Usf}}oevQL-{=$BroyTT;N=7OR($JW{M?Ki( z8s6>>0^P>VX(v(ZSrbQo+Y7BNGVa=noL-ONX4Qz^(7+?xaB)N@_I^LR_&n3xym?fI zHUnMs`T={X+>UJ|?TDygQQNm+tf5H<-*JW?5S(}}rPV_Aj?KOE8=Lzn)vj?B_EVj9 zPV&_L2d0i$-@E_D_+f|P6F_E#6|hGC)aey)In4!ZIo%2?V6pKMsN%eY>Z49OQ38{{ zsN;z`L88RfRMNY+PK%N{AQ0XTGU14~X0a<}&YN4Kq*dVys)+*y7)113vr;WNIGPv; zv4N0MicG)dT@b%L*p7k*E;nC#t;v7jA{Ab+LQcmn2rt={-ocd zI}f#hyGb>~)&jWIWFI1r$7fEbZjO?eb#Nvv6bKuc+(dlyF_uF{<&y5A4}7s+D*8C{@Sb&yP~A8tHGS)xLey z@|U!NW2%i_Ws1pWU*oe`+f69_I9O~PEw7Nd3UMCG4|@@Nve8Geuo0~ z44luNUDP_74FG~*$%iA8qcNJzk>;5V_z-PA4-C!Q7%kl`GfzCn%EEzIbeVa7-Yt(a zX>wwzQmzTP;&hX*Jq&RIOXnY{wV{J~s-d&I@AH1(2x4bXBc*w44(|pJk4r1HQSzTf zooJ&BA@ZNxPYcvydWIx%KA2^~p1x8^Evc)d=HnZ$t3K10dZthP^x3nrnv(a*BB@q! zA83<5Lm$4Pk6ScG+d=z^n_-ILb4)^}PMSIP?C8n!a@ekvc7_?b$cqU5b8 z@#iHVt8bN5Da5Qd4$816axo2(wzc)QK1m%8wdkaz-VHsrspZJvV@dZ=x2IB1s)#j# zx}HKv;_>+|QbjmA9%_dCdv`;RZfrfW$FYg`0CFHILmz@3h3HtkJVX0|4v7UN_H6iU zTt!yB4>G^74bSA`LJJrOu=L7639q0Wz-j%>dP9K6zJ&7rlqbpoh*l@+hl%nq`XI`? zvUQ(q8yD57mdUCtKBN`bmx3T5TxJrr{$sAt+KyiD;mW^ZJ>Z`glKex zz=!sVaB(_C#QoIEl7&bCbrFE#J9ERraQ0^F}&_s*cEDdAHlPzCpUFv4DR! zRKB@%5@Iv91g=ehb#O|cHQ_U7h7XwS6Q|rSP@65|JZ>71q{W`P+Y?=WF_^tgLPxx^5i8o?RZ`2WQ zEJ1k^e2#R8xZo3s9^nOc65J3F6@ABhIwe7TUxoVLD<4rmiJ#bOc1v@lx!RGSDD)(4 z<|SHJzyQTX^xYFBxp?P@`VK^6+|;CbARkmVV28$mV5A+4QP_}ONIu9?STwBL5CPdR zn)g5ot}G2O&Z{g1`qRK(6*Mv2V-R*94^ zQ#e~@9K!$A$W%t-p&N-aCsl;PK936xIm^dKTbJUEN7>8=>WezZ{AzM`sh1^9o@S(O z63kelqe#9ku40$9FwGwDOL8_B#NkmZqHyP2UoaJVYEvAUjkqoT?48AlN% zs}Y)X$rs+0bSN@AJ5tz2Mzo(55uTMLKV-)Mx^6E69EzU z1D^tcL;MyKfZ3Z#gH`lMNXp(5a%M0H*i zo|4w|e|UC>hM!D}Tovva+SnJN86Bk&XEOS)k7@7I*f)qB_&+?9#~t}Iu}d$PM)A@6 zKThxoZtfU4!kF;SG7$DP%GYhwCWr%GN8wOi45oI6L@A^|w zat?J!?xq{7-HnoIraI541}N+@N~WXa1h1p{SvOYyHA=Efb&OOS@F*ymfs$K*=QhpL znrEvPf^C^Pto6e|E^3Wqk+c^7NiMLQquI(4p21u8+ey*dB`=y z87y~NlcxLl7;nInB$(E*BkZLWd#$e>W85r{GR%}F7}G&5&*W6a4=`HbO)JI8!NEE3-r%PdzHx5maT6v#GHpy97CUK35-SV3jitf8{d$KJ15 z!``m|eqRx zPWHKS3BNF%WfiUTU=eASPJOkYBD+&@N>I0nzP=N?1*KG?dKGh?OQ$0o6d zYmv!^YE#O+`!td4aA=mxeqyQj7n1&TEXAE>`3vT=f-~_HcLrQmvy?Q8#$2A%HLT0T zZfG;C>!izL(2-$$F4KMq^pd{H5W{Vb;<)vJ_JH0&uH2@79N<_Sa}_y6;-i|E!$<0K*%IMqTVEKRfj6TPmc(D(&QM!&xNjaU1IbD-xU?laGGQM;dTuzdigX3e;3v-f z5YbwDj@?_cZ|@p-y0kn%yAS3cJIDr0;}l!zF{_3=1Nd$FtTYagIxSv|g&eDVr5gvk z49`W?Oal`SbY&N3k#H7E7`ZPUogBIS%-S9c8fU;*8TbHY4(QZbg#&+B;1;5FM_RRa z^Qzy%Z^B4PUOzQ!*^wqKntBj9HS9Th$||xtxR%{tvv=Pbc5mGQ<*Sqt^%~hbe(h*h z<NrP!iFY#Ac3E!=aX=zMRFl-jQSVDzqh#YXmOz=fT>F;wRKlEqG?w;iDpfBf<)Q z+gZ!QD350oEci{J0T)O#KX#D5;pkY*{1!_lB(Rq`IqW(*JFpl%q$;*%JiVv;eEpalT|3kLnm#Df492YRG=1}*L}BV=sI^zI;mRHeG^l9^%x;hz8~ zMbes$y($g~>Tq+0BJ~9TbZ_5*J^Pk#HrrmdN2fl)efoCvEw`=A>VLwa>_2>X|C}r@ zkKrR?W7@UP&c@cdRO_aVH>&_Cv6i4har{$c?D^7zLYgx5QAWn2sp`M2`n$RHZ`HDY zix&O0Zkdm!PI;7>`3N;KsYVM^c}w^gqm!5Ne(h-eJDeytL?^1>p0$&z8N!6lM`*=! zE^|{hH*TvZd0cgrLwW^-M!o;z%z>XPR4u4z<$h^gT#ulZNj=4@*XrutI>KC9MkUV-`;aT zSbML2kQ;`ftiG8qj}YUs3P@f^#N06!YrnRR7PD()Th4y@sO2ZvwU8dYhUn6aDO!&` zeTH1#Z)_#a2@M|x2q3lTaO7<|425~C*xJRF6{Qo4O?tx4tyn>BPb3}eT)O=RkM(4K za6j2^YEDjzW9oH}~MWG_h{{aL+RO;E~@b0lk;9zc=h>FPGDpDT%%r z?tzYMQoT{bJRE682;j+*^A zE$dH177RV1kLesY*xwA)4I)~AfM5l^qLMyom)lsbFA)O z(W;q!yScqPCv{M6`;2T=rH-|=gLR)pNCmQK(v!^tI}UI0m4mfSovNpXJvzjBd&hNfXji9NN6Q*5?OJv5iUTom7l)~4J{nuo;i--3 zh)3iU<(IDT>-IR6a-%FX3x2v~5`&PkPM$d8d|-{VBV#)c9n`i{*Gstuiatpj*db(H zI=taq#H(41n%cT$WV3pQRa2(vE)Tc#ah* zZ+W} zaxNp=+1G5rE+Wl!#n8+VUttzAtu?~Wgj9ecHZ z&Pb1udq)P&k8>xAIp~d=L-NZdqf+h~`ufM@|FoY zgCjsvgE1~HCg=s5#hCa}79!$gZao_;wV+l(Fv0h1%kHNjYsy|Psw=q~FR&lMVm0i9 z(cRdLU8ZKzW$6UD8#D&a^4iF6Tr3iu;jxb=& z1_u*I|Fj@{S%`PsRIfq9>v{Qf4=B%G*uUF9>_R4cY-XEX+dZ^tg>He>S_Jiy-*lK3 z6_V7UwysRYT6Siw+IOt~^_(^BmrgZ?q_qjT*{Dl{;bpAKYt7tRHFpL~vGQAOlz9MT zPPjYq-9+_j7gkhzfaqjz4Mt%K@Z~HbmKvK`dBpaKKRPP*NK!zYw^ex>KFGUoB zlRQ>D8#nIRva5M!?B{YpzV7{;okQDs^(#xZ<{#uYliw~~`gZdDZC=4%seSvUdG+YL z{XT}%3Bz$O8jdIM$oZXm%_~Q(4WvdA$Szv{T6cE0VkM8bK8Z);6OP6AiS?{hmKw5q z!y=*vHx6yzKD3EeGxzImt;hVjb?57Z);(JrKq?S#BTDpfdr_tf{JG)h&YPF2=n~87MtoS`6 z=H-E&hg$?Rb`5sk7ad=vS^di2Zov3zXf@JL<_ob*tk56&u)^UyMrNZ|1vd+6t8wJ~ zQ**tOs<$fR_O*wTndbR(WP0$~wAR|}nOwHjHw8a%G)eK=_5$c80&FJ-!3|yhKjPj4 ztjeS58$Wxua{voP#TEq%*flC(L8ORcrCSgI8`4yY6;Tk49ec0Xd+)}Ay+n<%w-_}> zP1M8~qXOq%zTfVB4*CSh^S=M<`>y|`95}OWc6N7mc6MgAD7|o{4!H%w89VMX#)5D~F>_6V$=4V*vtYtrqD8O%Q!sHVU1MHa z_G$V<@4&zW#alftho~Pz6D}W7Q-wfHGSOtCy&%tjm5s0@rQ|}{NGP}}WRMLOdr=U~ zGMM$+xkine&Kg}{NmeLg#gfL?b42hqtMZ2S4HtV47~t*GuOIvqe>}KBdM6Th7im@> zAMeOWZ|?{o!ep*sbj=(K3r8wP9Obdn5bc(baSh?z6G7Xzrwzb#L+Qgy=P}V@79h zH=Qr649SDHhife_h{pfqKe&7;Tu7X)f&tyMvh__29&sWGtEfkkw{=-khcO^5p+>#i zfddH{f?bWSuA^UV0}E;y;JA|NG8cDD4+r_Uwhk67Nrw=s%J5v9@v+hsD%;%$xT`SZo+3`51P6_ zTM00jdSeLa(ju&d@8OxNQ*TbX^06GLTRObGZ?85Udza4r>)hp^l>6nK+c&OTqiz|e zs^5%?UfO-@ymGBtHLc^&pmeQj3x+S;z7-^^A-)r~8s2|qX>M42j5O@(7luAAIL2qr zB#j%@DPPB;N#)Khmz`ov?Y%LyWi37V#ZXp|rmteNQhYDf!iH_0m23|M@8_;jtUBQ% zX-FSg)mS-d(Q(o^tfN<|d{Ss@)P36{@$wO2lDeaN|Nf{392O(U_I*v3v1?|BAzF;L zMi&#$OQkO0k{AS3J%m%pTuss`j>yc67hnk53=#r)&hBMO#!{E}bf-8H=n4^Ltd=-x#6?{6!lrBw|qX(9k zNIRO?@U)Oz!QzdbbvtLNvRY5Loj-T%TGSMcsf6dG879FlkV>HkAyAD(lRs#6(VsRW z55XLSE`|^1)zcU=LDyUSS^C!S5Cxe3H6o2!>m2zfF>e`c$3X0KdIt|Z$oN+u;TG}} zCr?fsGUXfbXI!{7!oN;1fw)G$r@Ih5;moO{M^BwTrePx-GF;Zg+?ujYZOF^>_4FQT zOYDH?{=;}hJij?Ivcql{V8L`bE}F*^HY>Pe;|Rphe~4OuZj2!>S_Cy35fU~SbyU2+ z;Nj?wi$?Ad@X*zd`@_YThz3uyd~zu#E$wx+@e?0MAAwg-6L#VdSX^qpXC05p`%24Pni5HYu4?N3u5Q2 znf?yf;~R&z@aW*_?*DV=)~!0UYcY6zctm!;gv>IQW#$h{*bo6ChoXB&KnKlP$ps$H z%vq_t%$=1&1($1SxOi+#8_1-Fbn%^NgB!38Rv#`B+XS&2r_T^{A|4ggDAI`<3UgC} z#jD0OG}L2f{rrtY8tO;+_eo0|&?>PS{oO|lQMWo+*2wBL_VmyRFU7Y}n-W@gj1CMO z(x6roVPPIMHfR%fXn6W*COe0qxv7Sya4Y9jW2WQWvfm`;X_0U>GJVPCaqt$#$DYfM zm`lip?}?Q?y=EKJA$_`F+3djJ)SMv`ep~4o?cTD#m&d?1k}^(w>ptVR9S=$EId_-4 zy6#*O5E`>@SmwE*Z@=#D+B~#rlMq**F&M!1AfB~0iGoctdX3OT9VT8vOeCFn-U_%k z1@{RB(vvXIU*RatyoNQg(M|LBVbTbrjd^sxL>m*_j>+nUd|ilN{^!&+nF*7|-Jg;5 zAZ=po$n7)UFZ1f})~a7ykG^ih`nd%)8W`jn)Hqgr+k4f(!P{*Nb_)jdUDUnDqNo82 z>%gKCc`?IG?!pUc z-4q;FV|`q|X0Hitn5xhSI0*w|&aLVby!dKb)}72Uq;a|E&KXlIzdn$VdNOgstCgMu zTaAhG9Oy2};|)*S%y_k9-G_BS{WlGAYrbcJf5;$4-o$qxZ@2hnferncRI)?3Cp@(n z!sV^zw#+u!WESis?3g;8IQS-cw@dP&kEc&nYEY8BBY?i92Uf132k6_b+X#~;>Z67z ztp=}h0<(8#7e~yI%~pt-l0_f;Ci(az`4We$DN2nz+Gm?ToHqSdWs{D?E?@_ZKowVo z$HE)D>Ia_9HbA(JmcJ@gfA>y!tSo<*@AnQ_9FfJ-l*NUqTOofTLv`WtJL%B7+yS8K zD#=^&*I&j|FdeIzkVZZcL0vEIlDx%f#u20)Ary|xx>fQd$zNd)a&ReJ@;3%3-{2k% zwyP5I>RE&rh7IGN_}L;ZHpYmHl}NmV8E7;jqdT1;{w!?6h1)TmE-xF9L^@L%B)$g( zM`{l7p);hiJkJReqyzGJ@H{zRT$>=6NL4v zEr)z(f+VO8QhyG)Y=U$E#Dhbwe6B~VDC_`+S4|KP(C{~&=X(>R2AwNx73U+*wZc;K zrCLHZAU~KOpbu(hkn1K$HJYtBa>xx6q#p8A;Eahv3Lme$VmCB%=CniXJK%zM0 zsR?pb%@E#5Rgvds6T}e^PY!uzf~=xXC4YhS^cOQk=Op=)+JHPaLADeGNZuqLkQWSs z#S&2PQu2`kH0i;&29qA3D#s`ItHw{6^w8-I{fm@&{fd;K?_LcUHEO`X)KL{lT~d~m zBDL^B@_U#@-+jJ*`-XLEH*VX2)fob9%W%n)t-!uEHDwnjH}o^mJiLP`i7nRU?#<6Z zt{%ALe}Kj-!%f;nb9mB(2FC2!7S3gc!Q@DX@VIXN{qZdM*$aPoPmNze9$g?kTJ&k{ z>C?$?>)A{5{3m6!OR!`XzCi_VC0kU9Z4dTnh3ptRgLc2ew}|3|g(fp^jS@*!DYjck z%FyTu69NbI>f#qK(Z8hBtLYi1;?o9xGh^I+>mCau8@Gh}iqEyS&6|3*ZVYd?kXb!? zjVf(f`kR4~^Fu(=&ago9Mc=usF!Hmk;KCf6JCpq5L+CZ(C)!gY@{UKsK0G`z!Q$zT zKL-lGaia7ucq>vAhotb29zarXW|G2}ySGq(!sQVnDXUNP4jG!*t7%N-kM6M7d0AC) z$n281d(f!s);$(QG;7nNy>Ddg>J5cX&mPrriC7ntw7ow=71XK>YmI8`aS`7dTuflJ zH;DDBZVnpEWVby9Yds=-?Akw?l)r%%IEzb6|@$Q9ie|Zq+!u-;kZrBW}%X(c5iEcaN~XN z9@g*N*gLXu2d6fJb%eIT+{59F?TUS&<)Cfk3?k=cu&WwuZ~=6Y)|Zz8H8`Zdh$YM+poW$Qr79aj zP&25(As(oC3(o^;a7eepnn4W?@du(MrW@#BWN=d-TJ^bo%k3`*ghM zM09mfzK&L26Dkq@2KGB#sH~GVo~o>ZL2ZecK2E84iSC)w;vTV@m`N<|x1LN7VZZ{! zO&A_)ZFodxGZ!M}V0d)e@Nh^EF$crL@?dy4BnX0{Er(!uIHa?fgW+MNFgzU6U)+V^ zVGs*^{$}Mo3f@|r(BQ&j zUamPWS4G}o#@piWV(_ZM{bjpV6iB5EZ{YR(3%s2ezUX%q1+tt@*1aw` zY{)Wnfcm65Zf4rD2y0&O!sSOkCI_$^gxWaP;|#@KzXN9{|DG&@`N?_`dtuJJi-Te= z&6#&$5UD}h_3n-1gI7eDFT&2oUYs?brOlm-v|6NH&v2F&*~{mQ#xqGq!kF*JrhT8r zevSS9MM8&hX&n`UsqC0`QNL_!{ zCAd@kOP^#@pK!$0y6pD9?y{0}dGeS;0|y=&Gx>573}$)<1@=H}2>+noFf)%nIC|=L zNrWU`nw)xg&@0j>Fffde-XYz>KXv(^kYo6f!Gn(sPvF0vBy@<5?!Z3wYJehe@yWlF z$%2uo^zMIEoACddRKz?GB~hBqp8p4=H24hqJOpD74|+|)gV+#+2mAN_P^xg+ z{a>PU-r*8PMg8YT8L zZjzweHF5(L;p%_WT!XLp6uh@eF_eP81J37+pGw{ETQ3{+{v?X0zqXoLO!@~?x_iNE zD`%d5%v8QTFaMjqA}^10%WsQG&r`0GC>;?hDCeYMhAh6zLTfBkxJjnb@0D{1Gu0AX zZC7QD^q0ZYv|D3Rv4280(uTy)DaPx>3eEtBl{I7~?ZJzu1oI?u_oQ`7guDCU;IsSx~HXe?=gC`P}=l|a(#4g z=!g-a!K2N;v0*<^4$2)3X&^qXO=;Y1=yZAM1-c~s#p14dJ zx;G@DzWQTVNW<2R;L1pv>nq93o=McEpez`h8gwsSprOOcL}{U+A~$Ss^(Rx+)(krqelmPha_O&PoY>gp4P?tjM(RhZl7{a7;hNPwCYeR zYWFADSje)-DN`b%CQnw*rT6NYp5C(;yFxmrtQV7Ey}>UcO2hBz#4p~ z5DvB_!+uh0C5MI%8x|UpA`T(RJ%bSREG4LCvVJYOMeBF%7#rKM)1X0qonr@e?KB`7 zRBxq@68(V-?oI|asZQBO@x z585!zIi*3gUz6rG`iJG6QJFU`Wc&))Wi5BX^G+kv9;k`rcWPTupe8Hl3a%NlB)!rz zKb(mZ`E8YR>K4l^h#Ng~>#+rk->w42Iq#6NKwZRF9KSm-*%!$a8Wwj5JCC z`!MAoJ!MD(_I*s)rz^YYcLr}hDRBbl^U~-{dy)?Ke!%$&$3tvF`=k36fBWkqVufP=q!t(QhqFnFn zA-Y^D?=~>t`|9g5*E;`_$!PDga#vCJ*D!^)JDf`Z(W>PZ@g z(#}en3W*x>dRi)@-B7$qR~bI0j8dCF#v$h`_4k4X1z*EGbw%!*g0Inq!RiA#fwvry zd*~x`^WTHp!DNIBVw<)jdyy^$mJG0~!6%P&Rh}^Qh*`$rX0MD`CM^i`d3#7-(bWdQ zO4k|B+sKE+VbpigkE2FCT)6nb$l(tbN5;nV>pLh0(RtG4@rbdNmN!x8{^AA6%6vSt zXV#O<%qLkrvmQ_QG&!kf;>3x#shkbN+=H8sc&3 zs)!^5c}24;W!GCKV@stRVg<940<$t?A>y5!?ohL}5~ZU`Dq(TsL&M@q zR4uWMit}FlvkkEjD_OGwWSCjNMuq@A6LQS8THQ+zDd%%r5AS)W;9Xd3q>syqr$_5}%8fq6N@H5bH0ggsyQ^i>(Sco2vTjfR&ye z`ru`8qNJ*b;7#rsu-rHY#KlILPZo{ z<_7rh1+uazmv*B55AjdcQjqJ?Ljt*ntfYP3ZQ*a09XUM|XH)$!Y``N229LZug7Cpu z7NrEaUfyleUVQh5u?#QZjRG;I0+`^KM~_3GG_D=E*0Jm5Ke>&qxg2pv@*|wcMt!goidZ@nRok%Un!WY{nvH~S&RXB4*MVs zcwZKD1T(}&TBN@EP29xkC@=U-mb`dSa%sVP!%Op&VxoW>!Hai7`QMB^jbYCPdocHS z!TzObc;p4Fh&s~n+@cDXM_6`KGsIr@G>s5O&^&7DNItI00rlleaig)%>V<~NprLJr4RIF;=#1Elh+H~nFITZX zV6vY&PS5iCCr<3UIx2E?KP6jT_gpxkdOa662zS0EtxwF#Kb}B$0GWQPqrkSsm%?!& z2a^-0t_nJ9F#NaB4lcn~l%YYuAsJ-pX9yTJgDm?DF_pp=_>WR6Opqwl9LGzoWDpDj zAegvs`BLR;2)xPSi1^lMUC0FW_+qp+u}vU@BH)3n>FgrauKo;R)gsRt6QqJJ43G_g zZ2SztTxO6>g%FMmAe%o!kcZW?g+YLD8QmC*3d#Pm`r5 z`PDW2)aJrvftf{kqVc=D1KidS&3|#Zg%@EQJ>@QPm%M#}NlAeL$;s_WgEI7xg$~3l zog5IDoD854Dy#$r%`UmP`ameBBL23r;a^-x9oCWJJz+tf&-FSNUvqJuy#Lx-@qW4` z;!uJ&E+Lpm76yFajMVzHZ%eIfH%X9hD6=Y;9}geR8z3D+D5m_}Mvr zPSS~mO)&fNnwSnih1ov;TSH`{ay-|2bqTG>L+OM31OM0C+@zey^~x@xP4NI;*BbgS zZ4%u=nqyVLg6YP>v<1`ULUNj8ya>PXA zY~yT9k}r#B+dO-hsyZt!)z({xvYWHMwuNZtEZ4RWWH(V1p7Z7d)Lle};1A;8W^D5F zRq+i+1*lQjF;VL;l86asN{K*eUJmPtx+^b_(oI@tw1iD&ILih8^}e8-Tb90sdn z#xN)2#_+Ru)3s(%vzhveDQDQV16-k8+F5bM`Txs%+rV6yECcni|=;$E!=njqFT3#YAHq{;7vAt%M0{>`qa3d)fPZK8JIXQ zFxmR_DS5-eINE&ftoFR~=Pv)VtN-h+z4`yp87bg@g};E{DUBklFdhF@Zs;V~H2>8arjmIO2)lsX>8b@{MBER?f9s zwOO{zgU+M#JeDnM)6&Vgm4hUWA?=CJC{}FZM3!|FJw}g=Q7@yw^5reuo$I)>!1C^O zoZVY2U)~B^!e`WqMXY2bN^A_rcvGc{qTGSNfG{J_sApgY!+$J$oV)N}&QVK|dN}J~ z#EFBLty+?LLK*cDZjftF(wxp5F^tY6 z%|`ySRQ z(DOMTjy$HE&MdnlDJ&q6F2Z)wMeXysq>&XD)`Gd+S6`YMvV+^zAMx5kaT zm3jKK_Iv!TaWp^u)&#d+u4i3)xw!OlIp^BT4ZmD^Ni|Np^>lIR>E_z2`59OK*XicH z)ILO}`3=)=XJp>WAQGy|yqTVUlU2xnqgrarxRp8nRyy*WJ)3bmo!B-Hb2;l0=IRpG zT-tRS)iv+s`sHU{7BaXZ4}ii_0#|RMh&S{{4CT0p%a2~<^>Z;y412E55O2H?!;BkV z2q~DL3Ss$}gu!MvhEO{}5ldxTo~yZp!u>7tYkyG=3&YZrmt)rxAQ%2Ksu3kqJZ|*P z8z%;dQE*n+VO$O81C-B&$0kH>ED2LdwnH+>g_lA}8=@0tiIHNEnrzG#nuy0qkg|jB z$PLnbJ&7k4pw^KF|!-oWNhD}q1cSV4F9aCZJ1Q)p+u66iJ@{I(t^Nm>LcjHL0 z^k28|oJyrtE9rBZb?YY0qR&^6pxEQNGiT-wVR3!X8HC|I)DfUf3YO zoBuzX@5}qm9~iT#!NO&m2!IIdI=s`wnr9texeAY7q}-aBdo(`wSnjNu=!mI7S|IGv zx_J{+stgTa+_Bu5vv`klXKG}S*2rDdFmQq2;lUq;f!92D;XBfo^8hyV7!WrbY|PS2 zqax<%N99(Km*z#QUG!f+F@`*OB5r#Et0R+Os`}AQFU7?#^E22d@WiwzmCWlkKxxkE zwSm?HwXtZdjN>{Ioqm*CD=cLAMpYk z`A7O{^jp%|I9H6h&j#3th_`f?Tqp0Yc$fX9gL?BnD$;#X&3{z!kK!m;5e2OaG0+Xf zbTGBAj}Z2JG?32pTYa_rY;ZTR77N?c0dY?g;<%UMXcs|Q;1g2AH@}SHoqL=&sY8>W z%i`2(>h70K3h!B)xR^pA>7A+BiUp4i*9~9nf`oQWEGg$w>tjt5$^MO|+ z^NL=xzahLh7}Ltuv6_hMnGXo-%WfqG7<0-zv+@MwBZKG!W2`aFD#>ahZo+X9!y;vC zp|Z7D*$8}K4IPDqhg5W?ziT-KA(`jr%usT zv^r8x5^M4b59~Btzn2_t>4mec-!U1`t_tCSwdbL)BdG+VH4ylmVGJWUzaVm z%JQiE#(MN=yD`VAp_L~;Q82OkUr(P@-n47iCaY`g^Zi#=f7?~4VE5k2#@^nTQwVN? zU>slB+giQniKT4qzaXx&P7o)9l%I)gCcCrFQ-U>Zu_=Dy*b-8LrPd{+L`d!>q)d^z zl#nt*992R}wwO^u${A)wSRB@f*O*yCN-6O|2`Lq%xh14H@+h9g(2dj;nWnP16c4dS z2`R&*4ke_F5xbX=k|x=fkTMm|R~C=KBJpDhDQiWht}G7g(c}OH zPXlRh31z%Xn!R#}i~upkVlYc&mS)AwDtWd9>x5!c=7}##NLf%~%EA&;7L}N?xTF+i zs}Nm02Ff;JKnW?kglQ$D>=!&sNI4FZpuxq_Ksf>Zb;=hh=8g0-_vahx7h5dj{!k90 zOwklG_b-|fEFLJKjG6lvEi*-mDxr*-`xh;fEo#&$nqubuMN+{1HBMeMrIfg{cnnAd zX9d&Ni*?~qA6zXUo^$c{fnlUxqs0VGxsl= zV&?uuQ_S4IXo{Kp7fms9{~{@c+`njwnfn(_F?0W-DHb9vp4PIZxUGZ~GxslATWLJ= zQ0yv^%akpg3ALHIf6+2#?q4*;uA~_vo4J3{GG^{yG{wyQi>8>lf6){( z_b-}a=Ke)f%-p|dikbTtO)+!-qA6zXUo^$c{fnlUxqs0VGxsl=V&?uuQVO|$(G)ZH zFPdWJ{zX&F+`niF0lF!-R!V4$Ys9@*d6 zk97Tu^v%hktNx;UN!MZ7!H0Lyy-@8tE6e2J#>s|Ca@YxJ}j_5yW`1ExwjHbx&A~x(kj|Hn!j;PW*x*{is z^nXV>8VAW;Xjgi0_s(w1M{Fd{IVVZ=Y%+!P%N-!}*q9oU6^e>i!CLJUuJbGDTyQ!> z#mx4Y*?Lwe@2F`@nZ;}~87}i?qXj5WStMjFT3&OVw2_`aLoe(-NqRp&Py8?Axy3CL zhs|qG$f7lQ^gL;qH+X44_`3K(^Zf}d?pt%zz3i{}gKj&jy>p3v8G_tJBJPCZJxu-%`vY&lMG(hui+Xn<}H1XYAZ z0lW?{stwx#JWj!=V+u{@rajGGo_x0ZU?kMnk*EAduk5-&k3UO=8Frb2>OiGxeu2>X z(YzniqQ0FPb9}6_Z!LXyHh$`#q}ATJq}-D^*5!mC(t<9qrXoG}&5L=N@0Rx2I27Z! zQ{ANO*8dJqD_79 zA4uTo7o^vTLv%H@Ad`CRC6;rq(y3ITH(!$eZ^yXClQqP*+w^&RN2D%o+TZU$!K6%j zJ@sdlr^!YN;j=ms~=dc zP@1vzq*pw+8srF^w(ep*0gmkgHnBpo>aOK4&9NYLwfqKFPSnj*&seZ{tQ9~$wh(Oa z5STWCF17hn;%vmAo04sq^8kAQu(sKvvjWrHAn=U7T9dQ%?2`-h&>tD(NZ%Wq2TlyG zgh<}u)xt6(#_mgTrR_6*Cw>=yCaum08)*df6RJydje}cEjLgU-F~3Gg{l29ympL>l zC%hGWjPbJYu}b~QL93oGh!bL7keJ-eh>7R^eru?Dj+|!3Cr1mO>30C-I=D5#Gq@tV zlfhHN{Gvm<>5%lIhuF*kucGsXAhm=ee9Cu3-ebfgycajjKR+<;)2-w5Wl)C-qj%j* z#@*e4pCphc1xzmoQO!^ji$P?b^Io72Ob<-Y#R5yS#Z^s5G1a(Pl*mgn2USI~ z%gjS7G9F3>s#az$3UddIkD~MQ!L}}8y5o);)**s3D%G6tY`F8C-iBQ$>uhW=<;9&Z z>C(%D>?M2m?b|^}NPJ}v$EN*Ts>BJ;Dpy@cJb#sI=KZ+;=+XV6#qgezLVA@d1xmd& zetb!D)}s&8Xf^$PLlpk1s&mw|z_qphCPCd72$#2k5r^DXH3Tm-}qu-lWrv3Z+!RO6FTJ1pw;Ba zynS|P@W+B@7HfIv%!-IP!%69A=cdJJ7qqxluq1YMtz8UEPsw^Z(eTX#ntyWXFXKqI zTVK!A*=>zU(<% zM~;jlr5>9=1GZyyU#VyGp_q9hqXrvYJFpU4TQ`?RB4aq5_?UHedy&9sMiR?;T=-Sm!U zKg~TwS|80nLVE-2Q$5ptcI+VOU8nY#Ltmd2x_xQ@;)?t+`kSbfxp#V@PnsPdwm9$r z9+}7v9pf7zd`Fy|M83#iw_wAWDOd&L6>Q^l1LLsARxGZo_7mxQ?{PGnt zprG37L)ZYpjTo=PumTQYy^wng)Hkbcg+{R8~$NB|l9c>Cd>V`6?jy#4WDLU-Bu1$60M z4>U>v_j`)&e8mUI#1XjHlliS!TYC%+&}QTk#wAzF9e0S>EV@WH-P(4OZo5DR@GxhBE)&cDu_?wYZiz%( z=Qu*4pHChAh?nr4F`7EkD#9h=rIaH%pBgCTa$oCj!Zr{y#Bfk!;QpwBR1ltnlMvl8 zEb!s_*ZlGiI$5@rn;9~@{BpCyd%fjN><-=|PU4YoXTF@ao1VD6nZ7z7zl2mI6J9l# z`F2k~(tc7@&rCl&=Q~QWBn?`ANB>-XgWg<~e1p{4-~AK3#~L1~N;g+ZIz@lHG&^l~ zgzv;Y16PiKnm!m?mZhmrm6$n%7Z$gOo3EOL1)eoUtm3GdLX(?hB*{^zKyDZ(#?s?L zO~03_J4s(jjmwu2Y1L{XEnY^AD_e%UjtFfX?mAqIlj3qc$$r@?f93_b)hFx4boo+3 zmMx`p=~6s-Kn664XcwKpKR}mTczt*pybGMz%F{L}Eo3nh*-TL}K8G$wka`+``+#e}OFyyZ<3JbLs9s?lcZ< zSh;zX-UEq6lMVC_;I`q^G2(ykHfgj2xRswddBA-CpcO+SMs#4fJv{v~uap&iCF(;G z=+ys;sK~A@rPhe=!z?-3jWs4Y`^P4`)Y`aL7(v5@F~+^}H5NQMuZeuE@X3|&D0$UX z(!|H9Mq@26F+oc3VLNn$mX_9BhPB*l;OC~f7Dkq|xB4Nh5e zNwenqQWg#k1DhYe2d9XtPw8s7WA^#6{tu)Ro*3BszG}?D6vWGkPC1k?@&XJ#p7slo zNQ?5NLG({Bv|*87p6NY-s{`NK-~`qT&an2?L=0g~Jl@M>MQ6Tq*VZ$X;r+&+;bt?> z&51>y#&%bMRQ|60(ItJ>E?ngA6Vt)@?=!WlxDW8?IwYiBO=o{ax>mEBvr~uQgw7t( zt*g{M_ctN6{bJks&tJSgYQZt7=EtB6|3!DFChyD|>$!P5sn?)lt=Rd|8EbpjYJTHm zt>#V;jj2_!0lmF_bKA6;yHlp#UgV#FRW@1OB!36wYk29z06@J#*%NMc{&VVK;sww+y?^PL`X2(ga7#8?uZFZ^{wv+vs-8$4n| zPg2zhT zxIowc=z4}8JPQgXA!__XL%4PU+t)_;B2;{N_OhEZ_H0ms1(YhMBR{}_nn-P88%dSj z>c@{%FSbsoFW9%KGk6#wo9Shx8zH2$wG}-9Z|I2JJ?bU|*!EJC0bxqF+&u;KPc#^e zdGG=a*5b>J1)S!VUu+AK?1{cMUu+lwH0t6meT~&-{UuJ)kf_%;H$6%I`|%c{j3M(+ z(B~5`#rOnubLrfcdA1|4vg{J$B2{c#JAN!|kYZ-Oo7;b9^7bp81L)C$H_<~}nznQa z^(~vYaCRh2n>fN{OKAu*cA)N1h8a>hIhdGU+jNq}o>&th%ZQU{u4+PzAaf~_adpU) z;#{&WI=rfTt;TU~dGw{~j0l@nIhq%mXR}rc&VTl&(EU=)6Ah3nLojto}%@88<6B`ba%) zU2ZKkZcKY|Al7B@*5UiEw~BP3sSnAxwtZY1)oEGHJ0YWOCNrk^5l)M`x#-Gnpa>=k z-4v6faj3yzrFg{@vR(B`!RiMkM4{6M=T0gd=Ah*7B3~` z_D{{*Ufx=00FBhjGPDl8(e<0q9vSW2le%Ky=V8Sj#MZ0Fd)%N*dBS5haQFK{HVzrO zR&5MEh{K()AXf0#3?51M)VA~i>8j?yiZnCEYfP|2vX;<$x6jcBt4K9sH+W(z#EI#< zfB#8hJ2!m!$>UYE<@3CL!+PwotKVLNR`-x}oG%w%4<7;1kQe)h;wAdx_ zzU7^Iunk?^qI6{&x-gMX)5$n8T?T)tifxje|6qk~+QD&T(mZz&i>2_$6ul`iwJ{uY zMvopcU8KXsEu{MH--$amoE7xKubc4jX)jTqcx=R(xZ@*}`>y#g?L6In^UO`U^*rfn z%oX+E8Riy6vzqd^evIwLk;NLJ1bW5>=9MgMan}gEQ%e9fj z6bjX3cgZq1L>GDKBXfe+4RS=cj=uagv^qH(>(oRTdXmr zkyS0b`UH&JH%Yjs*6cf?i%<9Ii4(scx#Y!2|K85Eyrn35oiqaVS>L}ighPqSeXcDI z>*g76W6n$lMwg)s)JB0%30sZqn6ik>#n`(xFbpZN%)F^)qMyP0UQgDFWIDF_F{7Ly ze~gNxy<~Ude}Qi0l)pHW7!JhD%{iw3&;JPE#W<`yLC8T8Yk*!dGqMxN;~H?_0zlc+c5qQ%Wv z7(2lcE9vTCJ=!4B0FSDGSkz9KXbuvSB_%sI7P40-aV;&@2MJpFaeME;6t4j-z>+tT zN?S-R5eN!ta&pY@2^mIHUZOQ6Ev`EuuyH;GR4Nlh|KgDHCedDO=NwC1h;CWSA zGvxHbP@I)MbPaY;WcrKVs($>@*?cW=VF07a90{QXEXX&q=LIj}se|!>FpB0-cfpm^pe>dkZX|TIuIuVlwvvHV z_5XU<`Xs!`w6ao|pLN~JmW^iXBk5mM#p}ZDjbU=0AoNa4%1dx+-;nNCgih1@!`!pK zM{6AaTb3@1LENK0(d=BzC|7=s+aGOauWpM5?%J<0KFaqLsXxc&06|p1o!x+Gw0x>H|O}3Hh;+4 zLtn3mk3{=P&}JRSB&yBTf|{gN20Lis(Gk0jO>J$r*Yp7dE9F?CwDjY9#bpASa}hZG31wvjaQaHewt5@9gK0jqg0V1>na?Q1`L4wb zxtTtgVXU)*lA`eam|C*)U-VH%oyedz1Ko-1$$2Sh6*F@H- z6S-!z!?5fD4eR$^ofl%6_V+}LTpU=y9UvqXK8+x20=%o6Js&qGp&70+QmBLI(5rgV zoQ`71pbN7dI*{JVFCTL($vny6JE^Y|9$wjQ08!bxaN?%}U0)otzZAsDP8{12&A+T` zQY#0C){O?nnxy|y{JE$IpfF$Eq~DIxB5q@xxT*PAy>|aK(vN*4r!UvvrLV#D?;|3?jqg8A-nJ}(z0%RDgj_4fr&0{9fw0n>v}kp zKrw`UiX`VP?B#21E6{UR)$GSUSxD>r=Sig#Kb)A)D!_L`!80ip#{j0AW{e8QFAt2O zSx1H_i|9$=+ZCL}q9bB08qs~>nIRpb8``>6a~s-`C`P;sx+b8Tb1Qqdv3^s|50;nG zIY;B`Sy|EFWW7U3w+jrvi2N<#-ik=im$MDOxEiB)soB(b9W{wCOGMRP~ zQf)+Edf;QFxPPOaYF|JqlSZA>nJwQ5P;071G(A#1b$+$GH!>x_s z>Ruc1g&7fWA0V*F4SBvMVN=@V0*?(HBPn8flavk}Xr4fhR*75CXCtY)ohhK7Zy&g@ zf90d1HR-%Cal{OF_vs^tF6@&3j%-=BfQB19)u@->sm7MQn(f4G~e`Z4V| zTNc-R^%LoH@d91@_^T&$4U|d|^s5cue!JnTO$QIIAF?uJK-V3l)RIed?StA6=(@{` zNvU03qk@+YmY!tNKknS2_ZKcCwQ)INlSyxFo4;V|wgvOH5r5Y)gNBT#M7&8x{?XeA zS)WLBw+|aL2|ZqE#E_UVE|?wbuut5?Jh8*>RS)k*L-B7`pqVsiu57-@X3a{W3Js)3 zJTmQZW`2%jLP{;hEVa$obcj?eZcd<|&Szbo5mK&P(6q~07m4Kr8qsLX@WhE82Fo@R zh9##qGPc0m*_lS$8Kl&S{qJT}E#GtYMF%GC85>x=1(W3PGw!59$LP9=!`2fbLijG$%e1f5{Hj(ZDrv# zVQ}oY7Wyeu=%<{S*QSS*t2*WF;nT!&GL3AN9+Q~a)}oY0W^&wES6sH;g|ET0Tx+=k z>)nau%k<6ahD+(YE5pu_9)DMjJ~?*ay2$CnH})Kwlj3-nZazQqDk(Mp>xU%d)KR*f zs*WR%4Tg!r{HW~6loMc5uz-;_fB=;d(_0s;J^H~9%v$_&%`cDLCt$4iEFu+8J|c`H$rbrMU zPy5SREEYGO|8&r!tj@!9b2%5A*X=w1nMd5X~GrFurJr5^qfAKwbT z#)tfi(E8V0gW&gEJ%z8wp5Wtz;KkDpJrq2iV;Jgy4CVM1ZKo@;Ni&NEfp64M+qD|f zK&t3bD`5<2Z0z)(8=wdC_l8(j1-$91VT7be?l4~xb<1!Hh~nfeynkU;vVZ!h+}6Ss z8X~pFJCuXjDRmwrAtxjA@O`F4V*_QFt2iP2A3o1+ie)zNS43Z-bj(tFg6rW>JL72%=;DI9NtNkLVT# z54K?;?vZX|x-4UNcu?joCdz8^eRYzE+lKQ#8}8$XvsH~JY0{8f_eQ;aV+S{hYCmWY zU5;1Wk)JGEOzHBu7o_F6mkvDW`~Q!Siu)~$#kW|fa7ppfTr6==#ZGR+cW!n%6spWdx{wT2CgeK2U`fvn`F z4)w|rk47OW?K{t&wIH~8Tu)a_<=IdgoCcy+ni>V8!_>~$wsFdU6_oaFG1qu9pJ8qi zQs)f4xrtyBv`FypHl%f6SNr;v8icLuG;?#OE%YW{Gu<(kl)f)cF^+;61Y78Cqx+F* zk$q*E)T!z)?D}-t@IL)(EE=cakO^p9;x`m*ii>(&0r3qC6BQ?US%h%}i*3i}kFXx{ z<}|yV<%6eRorO!EY4hpNbRhkN<5jyOZe!yUCVEKwa;*)e)LxBO5xXlX$LRJaj`!%X zUpKWSohq**l`lCa9Z&AD9!7)Hu-1lxt2qg4v~xGTQBVmuEfX`PJ^CA%%Y`>~K#ZO0 zfdeo!xlW7Y=q7~H?WI1WGWq&O&z^*YcE=N9%@*DrKJtNlT_E{8r9k7}4y|1~5nbZG zL2+B7!CUVZJQof#Q$-m(1g)JxYYQw6@T)M+M@_*TOc%qVUi)qj6f_c$4~QA-J9|jA zkghiN4eb3qYk87Bd&cd!Naz*Py^nvthK8^52TDh0tnVP}CHhV;cUDuR z3b->E^?l&(3{Aya63I0dH|Q_uOSm`!b~!g-W(lqsteF(KxM8A0K4cuMW_V?vhVi3) z!&b$7RUa}=vv*=TX=Pb@WD>(q{!1K~zZ2_q9d1XC2-Vo35*lyzP&{#&pSQp6&WXs5bZA?0V~H!MsWD z*G;=!vB!Xb8GY)FThgZWZof3_H|ya_3sVZvL34Kn;sX&h^lKJu2S3n~ApC%%opPC; zG6s`2XK3fM%JTCx@T}a2o;G$Tt)=Jqc4pf)eVuL08ixPeHGe~Aaz#m~r)Tf@^3fIQ z(eL$P`CFv!NJ}QSgr)i)vE^||nvu!bh09_tB#ty8*N)fob1vVgl20%FkNGPv-`87w z66HWGarokrQKis#~=L6%u(p-Ts|80ea7+HqKQaq6^;$h{6+Bnyi`JFJ>ye;M{bo4Sa`GujO z4U`qk0-yo`p8Az}iG~?8!oJ6d*+FxjWMn*<6Oc8$yFJu@(>Sgui=-(^9FG zq~eRUAOEEO(mKXa@3-hau6w;1^e!IIelV?0P+ExFvcI>6<&s-a_;)6E^LuW~B~Q3m zAnE#mYTsjy7rLQ)Vbg5}e{TE$33jTM=BSJDfX1!2gM%G~vEYi`N3Xp-TW6uH`?aOr;oT}x~z80cM zv1hF^T)r?_7z^`ik_tW4uFa&<)g;<^=^iy;(Bfbs{fa=dy97t_NUisdjuPs&{Znlq z#ZZ58iTIHgo^wAQq^{)B%Jha~hAWoLvNB(l#QZAKFZqAnm3}d{%>PR&4IXkB{6r&) zp6kS&c(@+E^qE+U6^V=0e_+b3?*0=|eQ+Ku8~tT_+KG%MlZD+|NaZVu99{oaJEiCO zCxL8DhU|NqDmZ!kuGRsv0a75{OnQhr8L1G|>K~{lZ7UgbQ8zOOS>3VoS{r3KYTN`FU zSXfHuuKk{jAshD5EYTKJBExIr#bh zT`!Y=pE_tzGJK@p8;{W3=^fqNgIvnyRiAfj!Vj0*#QV-!pj^tyDMQo9#4;L}G9b1A zxE8i2eD}f8!hvxolfPktzCPz7Gu6{&9dB7pk#_+xi0F4FLq(>vO4nL3hBxm4*JSybi}OdUI%Gc-vy&aX9omiX8ph_9U zr+1Pt^yosA4$iQJaF(0F6(!8l42|J+9O1cp>Zaf~KSNkpfo2ed{%w16GVO0$$fA_E zF=NU6;Uf};k%g=2v%?WR!Mb-iOY>kjAPBNx0+qN;A{m$N~{k}`@Eov0jZs4GW z#2Ne7nu4c>ZFn6?)EKK52v%Fxh!sVeq9DDA6e*%&qlrkffC`8rb}ZOY5gXQ6 zu%WT{9>o%4k1_V%OQOct*tol$`~I_cpeA|C_x-+~9=kg`JAIyc`a_@Xd`frk+(jav z?k0^7l^>;Xi2Mb*cWubv$vJ1IPCK9L(>o}5!Gkdu$bc8mN#8T$Uyx`Gju#wPZ~-80 zSC)T!a6w0$#}6%I2N$0K5mhwThWL|e^k^aR=0fOu?eE+a`i`Hd8>cMSsQ8K82#pF_ zi=T)|0W~9UPRy^P>cvselhHAqMl32Cho(WtB#{16#-hBEB zaaqflq@!n0pZsZXpmGnK`bp%RRQ3R^nCbaII>N&dJ|!Wf+EA*!PU(_S*=vQ%^gSB* zh_}-D@uzgRO7Q26)RsGfa|;mAv`iill`KAHtVP)r2CZN%vGz2aTV@uOP5g)RRXnE2 zCp?W4wt-9=NP@_@Ur8(2aF7u*sOPxpL};XaNLG@m2A!q8WP=%KCI{-=2;>xx46ehT+h9WLNal$b)-(wu|ejT>RCUwNVOdvkRvfu_}WZz&`qmz6JnFPidrb5EL=&g?)1Vbj4M< zoGhGK)mb{%){ss*$>|BShVraj4xlVMTlhM93FWv*CzV&^cCpz ztj1Zxo&gvW6TXznP9{UCiR7`IyQltlcSfqm?w5cn#<@WSlD@F5}5GKnr)eqf+?H)HK6#!dtUxz+0G#sV>U2lI;I94FwR0ZLKRvBUru=#E}DrX{4RJ%#@}e zdxm`gJS&HcD&hTmsL`mQ6Sn#T?KJ-G?AZ_V5x8LX-SIwBAvY9oZwGDh@e?Sa&iHrZ z^Ji#Q?fi{2n=zB#c=?Lno;Hox;cR^g4TOn68n8@*d6@y{VCT%qN~*FNiJ2wTR%Mo@ zjOju@fhqO~E4kL`fo(!*=7eWE(s5Mz{{W!h>zFz1qVBkE&f){nNgIcg2haQ%9@3Uw^LIYP;`6$-z*GZbzQ0?GI^kpvG4uG+X}-NxR;)Pt+Rukw+0>8=WSwhdL0 z-I`#O+?9O%$<_5**4GtFqhpV*s2^y*3D28wKsGvwQ+1cAeP`m&DL5x-(+lD% z?INKDevrkIE4C8YhXl@m55n9@!)E|5$uF(ud?MPu!NF ztD|ug+mAU$%;$DkGW;^}-~I~c)zy*oD(&=YKd@*y9LLwR48n12=7W(w1*>R3qkR~) zA&2Qml+$r~Fy$nes{!przKzna;zo9U@Dt>&FzD7F(4`uvbH)zvs?T{_bBI+iO*%^U zlMyw0jtsX|lND;T;f1Ksu3iAS6i4c9pcxQ1!}pp|LO`ZV-t;G7+jTDBsY8>!O?)jM`;qJ%p)RTcZR!;1=;3Jdnu9JFY@5!5j+zcRSP zELQ##`XWZz&*c(y0=DT&=vKqzF>Z@gk9*F3SO-Tz`>c|cvQ*$eD0;!^9zhAprd%yt z81Ihh-Ew{3agX0>M?<4+#t(M@u3X$|s~wzUCo3Z-Yj!g=t%gKFOcAxys&}-K{#YAP z7AkdVe!WdEZs4L>vczpK*p$@%x1GzNu1W*$&Q#`R=_Bxcso)Sd}x{z;9`&@9=E zfSWg08>)Ed#XZjz8=Xk$J z*5(caFzbV)L7F8ZvmTZJ7^c=wxVHyL>l1@G{z#9>)X!5nkboVZ1Ive@;DT&;* zi|&3pVm_i!hRz=`Y<_6yd_Spx8ySBd*2^zR-_xh*vX?LE;`1ba*!js*&gKq1H)-d#I}I^!*1fzX-^?Vcah($i{aha_EBE? z^d1tKP-Zd39hU8;e&ZNN{5ZBGBnr4=RG7RWLGpMSY#&!@b=+J@PZyEz1%dXbft)~M zg~`lI1Rt;M`6s_q`=c&Jn2fK7Lgx4ia?h%YnoI*L+D=g5v{2UMI6zeZX^tmDL3Etp zx{#PcniqeOu9nLA$J)aig)nkczNj`_-b1aWdAi?d?Tcgsw-zy2Hj$pv1Ze@Wgp{7D2LZ6_iGph=Pf%9N<@#7$s=3O*H@)H-nZ5 zgGds!g&Vgz_6?nS$9lQso?6%}#if>=>CC+L{hVssT1rpps!`GPyc;wd?>>1;@8-~{ z@+Xl~em@&C@yz!9xouYzCBTZnL&^6}j9c|nQSicT(hhozf88$vLbjGB)jCbv))%|! z0l?KX$f=zSa!8{QIhHO?P%}h=uk-?Jv^X(HRe*YVd70yQt)TglEHpSq>MU4KPu=-I zcYlAGjQF_j(a@3q+8=*;TwKT1ZKM0e#b&R`@aXkt%lMgHLZ_vr7lbYq-Y=(*%MR14 z+pa=M%r~0!gCogm>07hY_U2mI6Vt}l?k!w{$NWk^%<0*KZVjBAmNu_z*Li6R=V8@< zkES2MVd#p5A?v3?FbNa|k`04lI%7e|1~P20E98_F!fkP5jjm^s|D>ayK*3tG`d_5} z4tny}i}cK9LJCWZI}B{??d2DdadA<~9$^SwS*OJw5_;8YyEKiSp)YkePSIM)h*f<>MD&f@p;g|IVtaf1~O`e)o*PL%@f8NZ6YgV8kFrk?KwMZ zkyBMD1skqbbiCM>m&D&zMIz5rTa$Qm{xj`m^@@^ zoMY}1yNwTJyn7uEawg{G!Rvr>9XpgAzG(Y0n>J>71b!|yvfO&6!%X@L*(6yz{6{4j z6;6I!~Q2l)b{F{b`)Pe4%%x7l%#NL*}l7u;n8B-UN(NXLj?+Oqni-HN8X11#;v2rD) z+q}p0TSfIdQ%IQFY2dbzi!P1#qr&WeosFv--~>MmApor*>x=0F`Y>l|Yp*FIhEDNq zJyjaw+Pb-=%|@6x&nHzqJlzk`*M|nxZP;~a2{$zD=MpFH=5-q0BgyCX(PQVWexfJ$ zk*t6^DlrP{Jv;meVmH-x{0VuP3Y#z3I2-XJg2VJRz z*YpG^wSZUbe#khF7Gp`WluG>Q)>9;!sRu{@M0aE!9%-9$bR2kCHxV_2VO3>fCx##j z@YH~@TLt3jCoDXp3)NsuBK|R*8&;3dlFT5nfCJB^og}&!6vXp^dOYV}GOch1g4tCA zL6*TDQ(X*7xe3GSO!5fi7DxvtSb0xQ2QNiVpKQeL%0xFgJ<^zdPEJ3if5VTK)6ZIDX`&CPPd2d$y>BZksZ>(6p240mL$8TTb!F2|5Q zQjsT@^A_7LMa~aCJj>5d!j*tUDf$CNLcjP8MswcW3rfdz=in1RH1$OO*_u~e3ViHgy{jyV-m z$*hoDTcWMt&AFou?4S8p_xv+yOMQAlD4-WCEARNGuV3r#@lDxJ*#7pd?g8I~{n*MT zm-&mb9KQ)p({JVY(AJS<%&0;Zw|Wn@YG;=HF7*R~)`;4HP?0GH@oLCjd0es2aewAH;3e+=jW*vZcCt z^R-KtlowAMJZ$phq2sx_y7~M-9tuw_pY4A}>Qo? z1gwvpf04U@Nv}*Y_~KDv{V7gz8hO1UU5#im@J@9S=?+NOvGu7qDH?cTcyD^8^1TZ9 z$UXVH2xQVCT_h)zY!`MwKDd8@eCmXud{vp859!qqv)-7F@>LfNg&UUWNKDDCK^rddm5bUafg$ zI%@~gSwBrr$?q{@ zyfi%9bE{ZA+$xUB`H-&eQHaq_gr(I?L}jV!SY)AUiL}&%WS}-V$qL z{Mue%{TQNuE_TFxiCkv{*^q@(CcPQ&llu+j@Q@TMOp`$wvxCfoVYJY3Qq=NfTsYjy zJO}J7xW~sa!}xUOz+S*3!#gnPKy^UF!CKrz`;dZiQnUZ#gEWh5Cbh|ix6>tfoza62 zIUKyZ7c}lIA=7oF$0f3rW~_RZPe|XFKS6AfNE}G(#QmgtF-iRw?M5HPUk8_Kx>1>? z*ScQuNbl$!PldM&f4r`zo~iXADArW4nPR_JYbz69u&FD%;J&mde6YhN5{{Ccm_Lbjm zk}4b-bsJnoClZo)kknmDI=@4EAAj9mP5nQe1|&F7&U!=3Z#d1qH5v>!h&8K>scdL_ zMJgJLfd!^$U)fgX?gys{&j#7k&{if<$F0KB0x;uT*T{9T+q=AjVAl$AEUY zyNkm~Ec#Uylp}!-8Z? zFyLF)$a>SnEt6Lrj)H>Iu_c#x)S?L%M@cW}Z7E6q^i#mEVp8GN#{kR-pNU) zh-Gov&eJ65@b9!Uy`E36(JsI5BCT;6y{0u)<4`jTvZM%>#hi>Q&CH8frACG@ho!@G zdf+t*b0yl6nw>7uXGdWtH0k11lF!(Cq}R#XG|41(Q_|??`V+v!sm^BXcj!qQ8=vdx_-=dWc@9BQN+Qc=S)Y z`0Vt<%eUAbf3kzLdz5&WbUSj+U;K?^sIH#_M-5Er#hUUNpHPuOAo+Pe)9`v zZ6h4|M_wp9=CE%k&YT<2x0xbut(J;VMIyG=2*q4%uYcRQf9M5|uKiPwWtoIL`_MaV zDlpAyGr`@O1sx_b4#ZseeA#qRzcaUH6qHQ0uuqtyJ4X9itE|bc{lt9HWQ-CGB*)k& zJ$*)dz)xD;4Qx0P5Bz-k(XTg`-AmpusblV?nGnfltq#KhP?{&n@0>Ya=>t!o!Wp-w}(wB!ia6mKH<} zy*h_9{`)vTq}|?f1HTzkdi@`UogOx9Uebtcdw20D-F7GOAw97337b7`VCafz4kwOW zu;A)>7rH4R-*zPs&9Vn6e|Rnor2;XBJZfHCt_v0F7?RToR}3%<%ETonP$KtOp~ zv+yNKpzU@npy_{k7EG83>zH(57UxZU&Qd?leXevyXu;6gqtb<$NcB4n<5lXu#=OrNXLpX;*>Dpets_y+E&aP0pDRh(*i=-=i`I@@7hd75K>87;iA94ao`}UmZAX`+l#R#_0>IP;s)X19lUHA zP!cRv_O_6=Z>LS?m1bf1Hi4wAhXvLqX6`&%Rn=rf*P_e~z50LOXI_U1C}l5Kz|9mq znb)Q0uh_kN#|_NL&(Fvj&nGA5IKV)M} zPEJhhkRj6ExFJL0qH}VTGmSUWLE|gT!BSe4+oSx1fnZAB;K(>QSX%j1x@0JpZ@FoY8@U%$~<`cqbE5xGZVo`TGZ><%jqZy zh?|=Npj_~jZl+C(u+DtRroXh*RG;>JdXY;~n=ze>25v}!*4IF4qI>|X(*f<8uD}U| zYmsI;y0o4fr20{~1ANoS*>h%sR+t#Ta8NeFQZ+7(`#696-7`O?_g^1boJzQbLoWYx zBq_C2h@hG-lcTev+xZgnZo}9O3jAuY!~WL0Z2%@mNg(UO13LW)-AbDjm1Yo6vbn_2 zWdD)f*d9R?mxc;Xe5?GS5=hBh9Yfee#GnItD!$dUR9>MQwZFX)>QFndvR~om4d+85 zb+?4NFSIX&5t=aNASrt0Dw72Vag0*nA1ZKFog^0_Nyk2l5k%8zo6?+iJG1I91DLpu9nBmWRz zEYr{C2B=cxFQyx66{f4+;>{J5m1TT$On4%fRcN@1rG}hhDNrF%F;Ra~+@Ku!mp#so zK=!j|dl||#*voeEzBb$&_P8-!&SMZOiP4%ozK{;#lw@L7#&|-;XJky)?#$2XKY@@* z=@}CzW~5IdWJ3R}e4z`Ol$kMZTt?<3z7H)VLpcjgq;j{mZuH>6(ej2iT!j%Vq5I&$ z-HliDZUg$gL)FbNrm9D*cftbUJ{uX~7jm(3WDGf`8*@xo8gfpDk~_$wbx_u0$BkzXNoPFlW&5n`dmo2H3YiBaJ{F%ou{7(n0fHSyl6% z&bxS#WM96FwL1f0;5&fXWQzT54evKhr`BNx2_o%$4u=hofPUny;brlId^eUQqLziq z<^mK11bg*rXxyvRt2w5456Q8#h3{Ze$_|uhQVZumEpmy}qJMlOlj#_`j8s?YNcDGg z%0~??K=vXoPb?MI%7F>3nW8$}##je%vf*wWv6SAa<)5)-%Cs&0Ti2!!nhx7X)O&Ul z)&AjJofM4jzZQF?7FQg^uRfqPm7&azpd=MeQ`GQ85SxkEij$leH&}H)Z^k6Bm zKlb(*v0V#d^3kXfF>{jMB^Sp^XXqZ%QD~>hR?d??uzRkGY*I}WJ0KXEtWH(<0p-O$ zW|l>a0tB$ZKtpI_Sd~VB4WxGtFs+6QB_)Od3@^@!?qkd~gP*;Dh-0*uINjye>`IU}6%I~E92QHJQ=f8`H z$sHY+dabW6Zq$~^0b7sSHX!XA*Kh9}az2N*3O{u(Yt@rcj_pFFanqZ$+k7#xN#?cNwhO?>zR!STP<+$BIh-z zgB#tsf-tkSXD1%xiF%RFF3aakUGjTicuJ@GFAtE9HCGf(UHU30G&PGm9kOilHM-~U z2@-ej4T;}RJ8mrPH!C80-{5P+ZS65yM9Y`Zi}c+kpA~&UzR{o18>Y8tfZ!?u0p<30K< z?z^#fKYJ(U2&52X)=t${s9Z4ZL9jIIz(UWKi9zIk0ezSG9L&=aTNOanEbT01bs?DR z!HPq6!r`ED5H@!|v+*hY=|n$!{|+9V6Fr4$+MgbMB0~AEZ+c7FEBW;+i0Lu^kcK<1 z-y%*2VkVw{ki400Qm)dSNh^+`55vZ+J(|vs3+e9a4$kw zP5rtw%N|G?cP-qLHt*v2*1MNoz?#^ik5MwV0*^UlA_yR$21LuStiQfjHkE49^W1CQ z@q8dmlLqvk!0GZbvnEW+$jnn+`IN8Dmwt=R&5e%A$WR<7<62!JGfyOG2( z2NMGS%+0wTJ@1m{I9i_35v0|%B)$$Q@1XC)JG~3{IsXIA>_1h zH|`p-qtm2p;&>r}`MvtKkvv=^ffVC;e(qhga{Gu>pLgvE@i<{kg*Ft>QcEiFhdie8Q+YJl-**%b`Q>iBP+^&GE#B&8i zMK!(7?F`83e!h^XP~c_7Wqu5Ah5g8mVHR;4gAurJ{ ztDqa*&CPAh&2f$#r*D2;)HXA4@h|kPWXjE>Q|SvJ;G?B%toG$3mmkXwMd?5kYFRJm z9!O2Np@=cgDxQjplMEFHqf!+zzyke`^V~)$j!%-BbJt%YC()lL3-}~qDzn)S;s4?% z(2e8#>sYtzT&smyYnPmW@kNo1xqGDf_?bt02|WG1UgL~CLx6&0kvF73FcVek7Yc>; zGg6|Ek}apaWGQISR)s>p!=%=$!Fe=z0VvWoK?Y8-7g;m@mNi4_vKCGX z>M`{Z@{^pE$@lguJLo%rA*KkcFf`)AFJ2~nVKnrKcHUc7R+Ww<1yxb(HPKm^pk@Z? zn2TT|`Xx-z#qyhUvFc&(-_xxhJ|N$>Q^DJSglFrAtH0=1VzxAEao}RaN|;I~bMvs= z#*jLg3Jzjf6TU>h(Kvrf1T(NxDn0GUb8-OF_K~)uC-ArPq~q_4QVRTLe)`)dc6Odh z;)#{bm(XLhJ@*kIvG=TVUKB-NSf+MEVZQe@16ErHLlnjTntexd;Fm#kp zC}O&F$iVNu8#rWXNt;%Vty?>`YC})=E-2_dVDi*{&c43Rjy^s>W1)j2nDJ(UN=#7L zYo;RQ98$cLW7QH&*f#}9 zH#r9l25G3c>f3BgF%arL$03C%7LSmLbX+5{lJ;UHN(H=^ez&1SnV7~NeFeJ_R%8L! zQo5o+iQi^pY$;jQ&`a720}R+tNCg@V%Zp;ROG|~-`n7U_;Ha==#uhsJ$8V5+>;@Z@ zck~^d{ow<#kUnyz?1u`TN(I8Oigr|dgx%qU59nBA_Li=2Em7)6xVj5ayr?KY4CMj< zI{>RjR==9n*BmmyOPQ0cGr?_Rr zFZ19C+tj1^qzm&x2{&Y0PI1}9f!@UO-7ak5Iy2FlGTVyLVJoH8+&cYujJYC=w3pX4`v-7Oq41+fEzn+SeWDIj%Q&N3}Ba~)4ux8B!KqsO-hIr z?UPBC5#djIgp_0m@wDbE=n=U=yx zQ;WuS1CoCD=+eo*PSQw#AI^=wN+|A_-Po7vmVVSh05Gl?;DZ7+0+-#iTZ1;W5#LKJOHKJMt+fEtt zHKPt+?N|2YW~k@LG&dq&fGLNl8~aJb@X()KF)@8fjoxQ>1q_TkUr52CA%j%)lmTp# z#gx_{^~o;rlwhWwh35o&G*xjO#`x3V!old1hp|LtM;R3^c|c0YG$-@PbN%z#HMa+A zKMv~HIoYvp`}RFTc{Kq4F}3slOB3FfQq3FsLK~dhk(k6y=@{=NrAqI=s)5niU`olw zS<1u;6F)FwMP{Tjbq8S@y{c>%HMk9F(W`U#pidwA+}!??Mw)A=q;+&@KK;}&H&{#R zz9Hh0cZHX98>IKFQf= zOiC0CFtX--C+*=n>hE)XLkby{bd?4 zN3T{ZW2ABFRP^({<}Kq4>CKhB6^|>{dSwZZf!F^04+ZFgX??m<|A65Mn%$qGF?92g z@-I0hUNiPnw1U%GN#4>OJqzuF{mI52;mxe2J8)LenOiUQ=qb(PvJc=S)h0{cv|&X? zwuT&V;A)dewGUjW@V ze`CCOhTVf%X=M3$dD1$>mOf#3Sd`(kI^C&H$D+hJ z6XtF}0NpX4jPv^KqrE14Sz^0RJy*ocPnofYJYNylv3*iW-29}JIbD~AMMsDATGsQY zxyGBG%M?V@S-MA;LXHK*I+%D`%eb}nBaJ5!H}4|!MOWz_8B_Q8adSEznWst*X+Qlq zd85M~%^;R%ydXpuzfO8)4(v(jb&gbd`VXgyl2S1^!KKJODY zTKQ|KHvOnC`m&A)MuUFTd{UZdJ`G_;!nA<>#m9p&-i2%rwBno^*_JolbMGsw7G@epB@2L$v_#1VR}Rt^PJx#&iK4j z)frzN(T|~r)XgeiAJQB>tPEB+`xK+z{LQL+sa?ueo#wbSgznLg!MtA7nCWj|2}es_ z(h>a}LtkWYY2ogvjpj0GpL3EImmrmr*~gD#B9p0^K1$yo!^c=QHU_NTMn>h8-#kfP zhcB#LdEszUbaayR1yfyVSGI8W$89*YtFY`mO^;g!b=&X?&x$pdU^DLTX_eKP%Om&+6xwFO#B6m!KB9 zT0F%O0C0d7ih7ZvK}rufC0=?8XvJ$LiaB(Mni*U%G4Z$CVdzW_Kv)ye=PTQwx-BrfDic#Z?mLptc5(7gVEzi(U*>`q5?@}C#$+! z%6s~+WYyP#QaJ*OQGhBJYB=34uoZ8j-9wQZfPx#vS1Lg892H$gI-O@%uFWq=BWFNp5UOcCldzrpEy2q zzy#G>hHs2Il=+hO1OqoFY6vC~hoO`75G(}4$yxyzp3NoNxeKVt0`fs}o=;xShgXER z3n+Aa-9$O>OXWvN zsjiQ&UuqYz6va!iKDSqO1{-sXa7~?n@d=mAq*MAu=&T~x+rit?!JYTER9UJVRNA6t zPg*>zHK)dnmd_UEOr9k5I~_DFQKC}9w4jsSuAEm0nl!05J%l5p8mvPdWk1=5wa6IK zI0?ZRVdRaMr$OYT{7)}6=}~hMrtOI?>N$OPscmR0=MZ>_4|NW;BRSznw#}LaJGAN1JhV9y*AI#OCNT{CY%j)3XVtG{ z{*ybx9NKtWYk$8HLwc^g5l7dlUp=6a4=~`_QXW4`KUh?mIO>lwOj^5D^yDw99Sy=c zL!elYYt~kXBNV`K>qv8_@7~ds??2OJ|61IVG;?dCyh$DTXqcJDENN4!nX zSRbD_47P5G_<|QzER>hbo#4gt`Kn7VAeq+n1{HZx?}B<;=+Asg#UUv|4jrQQm9bt~ zblVbwL2yEuJgh2<*(daQP+K~<5vwo?@dTIUr>|^szy9d5i3MaTIYf4o+z~m8)xIGm zC3UQu)CuYw+`p*3MUy5LZRI|xpi!>-XMrQ)MqO{3y+ylqn`Yx+J9@p<(mzS>Ki`p* zKbMIrn;x;=-myJwSl{C2VkK+SB@^LRB_J>`_pGt`tzn65@u{2Y3_ZowJCB$oeq__H zbmER3bQ&4Zku;_cLlk^@nAl41p$6prXQ7Efif!0#x;~@<6j(>lP2)2&^Cx6vju#qE z=+6{o`cF^?(O9yH?ur?b8x44QJ8p1JY;<-uDj1Cl2A~3F9LJ9M3VH#{Q;9ZyEhd-d zk%^g^ObnixIZ^taSdcSxxbBrf9L|K`;-5??(=9i*8tRID7Mn9T7Ni8mvrrPG0&tMs zfDFpO7&d>FBBSSVk!8qo>?vnnz8J_)ZDE@gp~;|8{33k`Y16fh@lX4%jWd?T;-QN_ zA^u&E92yTJ#U|BT^>o~9Yuh!*zJ+DYs#U5YDsH33xG|}xo@VMA+RWOvW>s|+(;C*+ z^=<2$o0yq4vH--XC&p8adVvw*SP}=7w^Bui6iCGducwjW1?mM4;4-j zl)+H$0LG)TlWkjNXyE0gQ%>GxS<|+E4|+0r1W-hmrtY~dB2r;qPeI-}XpmT3{L z9<@}rE#Oc{8;FH^Mc-Fm4nXL_cpGyi`!SU3yAe-Ea8_fUjG%8w|7A3v z-`vs8srmNVBZf`Q>DS2(b>0XYk0@T6@drb(|&cY+I8n|TokhBBqf64g3*%32Lf>)oN!LIzKDw2kgJzSDC|MD^nn>|QN#bXr`yZYh`~ zD=}4iV=`3MGsCE1QjPvOjY452MynJU>&9%@i?AgbAtBuYQoH-6HqEeKGAwUJVM3pp< zI+LWwYbQ1Ku@tH|GWTdrOopyrEh+f$$urc#CMECE3PY2Ha#mw#EQ}uc4BAQZq&vtk znk_HOe@sPON*l>-Fo|jF);XeCJ)0pUHq5?TSNr<5@JamF#JcwM<^B66IZcClH?nfJ zY7&rc)wtE6rh$DMH+E}i72Ky`qn3!EejKR(9%YjB5u?Y$$3YYH%^c=WY>6=D8CDPu_Bdn0ubO&kxs3Zsb{?t8P`>!~fyKRg)X} zG!Uva#)CMtauVH~B6No*&oULiSMsiyP|b1--&K<=44)cAiedP$-fehq11?x$|EF30 z`_9*7{llZ`NU~(%usP(8w@>%5it)P01xsp@Dy+UZcP;sux&()20?*7AhtlaNyTTzk zN{2wX!V$T<@s8KdU0fVEKi92a_30qD0MFOb#@-(O&?}&Zm#1OL zvPGi}4fpg54fXR1BNM`We8a+geZrc1db)Ys*|%W&{;IX^)u`2w8yykq)yy%(&ZXYy z?7=g4?J;fX8W`NlB{XQw_gat3;X$oko<68 zPlOr{_x24B9=;)-oZ7MT`0<@F>~%~`I9_~dtZSg$SY4W7z29A5bM2iSBjRhu=V85L z4GLN(vA=ZB*uaLeOhg7o25n=UXxL+hdvn98ku{PXa4&BFZ95X?<=YNsoBm#*FzX2O z^k>cR_YCuRc+@nKELx0}beFVt>ltF&(l~OiY$VwTx)>K#$tA<03JuN#sy|!oNBz`W zKplZssay#7xn5e#pVH6zH)!!GdZTI1X3duE<(snY>2>F2?|>Hn3uy6jX|ZjO7;m50 z9!(Q>7|>$S>ykZPsK02S*Ten|UH(7P>vNMpueWpgLa#F-UQPrN?^YwGFxJY!%G(3v zdc3Qf7rj<3x-g~@Qru0u&gubj9Yms5jMsab_C{xSmg{*>g;4GysT9W`EoBla*+Xe`2k=9OyMdby#Qvz#MYdwwiQS#E?)7LiiQR-^w zBd&5EaV44zaSdG!Gfe1r1e+@z>>yg?t$903C8Uk!I)ypCGYL+?&P^dUW~53j(qsyJ ziF-_?_iBl2NauYI7cY5y0HeL@z_W#m9`B($QQ|?-SHB)uE|9kn3&f;iMp&>t=<$T^ zxnIzfp0nQpr1OwE=$&V5FUP(I=QF?73ei`1WB8_5$4tsjmYU(4p zOV#;Z_xT6=IQEM5MAx2*Zu(}b-$9P{($7|dkB>rni6QC+Na+o`$i*OLmx{0SV^!Ax z@X-`&&q-1v)wSP#1L{6ae5K1aykH1|E?cVi<&8e-%O_hko`x5iebRFMdT@N?Zn3!q zfz1g$V4u{9UOCVTWOdPF5=m#g?mlm4BIs#v)-7qd@Ye8681Q#Nu=ZpMaQsiCx${Bz zF^Z(uACu0{W@RProY(!ecWh5IElyghU!sqe6>co-YM5_IBHR9HB-6HGw|Lzi5+m=JnINRNo8-+Ml2YX=_`r4Px`K2 zLrR8pcle>rZcS)%L`qtDGLx9;lu}EnP+!8xv@qjg;BBB0 zJatMfY2sANx@x^Z$AODe>0F2A9Jkh_>gbu6s!3v9@<^>_OA7ILuu&L(?@1oD84xnu z(WSOpJt?keK$EeJI`s6)`@hfrQLM?uf`~H(9p~xJ%*rf*PJMiD*2-~spUJARlk!}8 zcCi}kav0*`+hScI0WDF=A}(`YZyk44C=S6S-Q<2Ult&^q8<~up2|TY|5zhA_0YzDSkx%e z>7VLTg$x?tJIE&8eks@KFLh$U&k*zAXc!);WjpO}%(uQq7tIE0KfB_vN`}K=tzmEOjjR!thmH&I$h>7-3Wy4DxY5DikKf&R& zwxV#5IjrHWuzF=$z(90--Ei}N9I=1s%CPNI`g+H;@8HzTeaFOfr|7OxEo^=N);Dgh zOP>W`OocnSrYxy%+W=fhMVZ)yo2q(`Wev$2HibDi6;=hrahceJ>~Bb;6o`u zaRX4grL4hL@sYw6m{VNr-zuC7Tx zCKo?KJ3P>ivWj*^T(6XUvs{O1TA?@pWOk2g; zq5c$3Bf08eb2Fi8a~L6pDWahjK8Q`TVYL|qtmOa86aP&q=HEQ&OiM~;PA^&3wqwU2 zK4Q(P=`+@p1a|JwhNxGqo;GvU%AigWLG%UkOk0gS9fEb2N=l~BELjoMsY9Ud(&|++ zrmrpu=+rSly9%GKl9PWgSv_sWDx`J@6fX^$J9m&rOf>B{WZt|X-m$S#J(l7X6{Vwt z7R(#s6&FX_vR7VlvEqeq^583-K4*UMV6V9D7_n{AY4Q=)9~H>B!5Os!JMt{SpR;j* zKC-7bR9h`nU>G`h+gTDzmC}KzKwcwogtP(h_i9Q^ZR(;9txYazc9+%MP;iO;A^i(#UrE*M$KOO{ z@nmu`vAno|2K5E~ohPQzWhUTSU=2ISj_+{Ro5LUl3=(*SGIEtb34;zcI9g)7L3dHa zuC~yPp^L)qa(A|uk=S)3BQxL9Z3z*JXr?_|F)ufh`2VZsDpK8#dB;@}MIj;Mcm$$3?y?u+O@2y$m zUd7K^DeUu|dl z`HcxhW`1Gdx5W=|Y?kK|53J1!3^Gg5%Si05d{Q!vEU-tVDtBv!`uK*1`uc>L zWU*l@GqMdZL>7QI3bfCl(*Rzor-8aQa2xT&rin3@bThaANXU=dcON6<*zVX+jx%tt z!o<{@>%p}$Q1PSUDyeu*>k2CV5jZi%*s3yrM{ZH2eNYa16KbD3LgobTmqMC1 zBYdg4%7lq1r8e52k@=uZ)B`wY(390I9{!4I7W5kJy?|;|>&bZunn%=wUf`faq_xLb zRl`RNxwrIo2bSfavN7j%O-7CWbgL(Q7fB{Y`{MSs=pV^{vdf!s+D)9EZ#F4!eZ0mWVB zP}w`U%M2>JZy$B4ecYTmsq>@A9nQcUA7xX&pyLwDj|E ztGK}u=!Tv96u_+pv`^Zh3ZpHT@l3OD_`+vo(h0x;wwNoJ)C>l(Z~)fEufBpob-lb@ zJnrpZIAhO`0D56wj{e=%PL0Mgptp;>7{i$U!#3=a=8%QKAyI*SV!e7d&9YmeqFHW6 z#$$Sr3) z?OAEghR)~|f9pkTjb?H$_U${)a?j6!7EG9ZE^dCCVBDkvFfg!9-6)4+KcxnYs;Il@5j-XHl^uzheEeSYK$XgJDjX zkVSXq!Yg`k&vL?=e4nt9uT>pqPF)_uk6_=;aq18k`Pczy+pQW${*?Fwq)o+m({umk z^x?EEmDV!ktYL^rjJg@y6l28G6gUFC(_GG%I9lGKZXrOk}lPU<>y4+UMajZjQ^? zJtTKW-}p6y;|jZraini*55(+CfO;M2wb+ENZM5$5GUtgu4oX~>+HZM$yHSx%!}>4(Ci=Wf$p_$po;Jnlcw< zIL#c4f|Oc-hZqaqaOkk@`46X#Zb-*X9(`?6TgSyUI+?a(>iq3p=l1S5DxgM;{X;n9#6V z%g9K(9b*3j^Eb~wFtxzDdG}7MA`+`t9Z*d7w&;Rhw*vrnF+B{}&tlHXX9poF2XAk4 zAf!6```k}-^Y*TDPTD?LM%tPl*6xR^i0yRz_NBWYL%x$VhB|;6u>t7k94yy?r=BmsRnVr?7J^N)HE6;v@jW2fzYrs_b)cqx}M)4G{#f4 zL@2p5XB~AVmmp0*OSCA5Gq#wWN-E;*ie|vEQ!VLqDK#s-&#NAY$2xH@A}i`g?TygS z7x=1y?D5;i0W*VbY{NQ$z?Rt9uo1z32M7ikNMs8L)5uUb-A0K&;|=YQAP+d&k6U_( zkRb!W1tm2^lg@2Nj%gv7nmnr_;Js?b4$l_$UeZ|JW7^Qz;SFGBPXtpXml@E@(^c*4 zW@=h9v8Jo1hhZ?JAAsFi$XZ8Wn6V>8=A8i&%ZLo7*W5z;sDI0-l%U}oql8n@O(+lc1uUjy*LwZD`v5nig*)*#s!5Xj%T~-B)(pBswT{jsfg9&s2Hr+;A zP+pcyO$J?*QAvAGg9bJDnXUVF9TV~W{Kfn4@SgroeuVg@%v^AA!Js8weu?q%_ECk_ zYh_W>J0ZMR;mnNYu3#FsFl&(6FMU#{5p8^(*%D(|^iI>$G7kM0xu;ILUoxq&cLOCv zo34ROv^lZxi+nm5A6}5fnT7?9HAsjJ!oS<#PP!m%;lim!F?lpOX0e`{hCB)v-2&*u;HsC|b)7A>37e0=fzHfg%wqpf>rL?h27^WEEeNc$pv(_JSQdJXLHn3k;CE)Che>ic!%3T?^acP=CD zkGP@-#C-+)YNW297amAM9?=U(ce%&%towe=POfPCD(FH93V&4~)D-K(t#^<-1Qq~_ zAY*}EFxwTllVpen+gz4Rb%?!UDTtK-#TP>^pk){%2ga=iJA&C~RWh^~X$2|MxEfZ3 z_}Daol3g(I>l>K7usb1rHf?*Ebr?C*MXxdFUd-j8EDr=fEHi)5V|A)N0fUfFV8@>O`|by{dZtL8DCeXZI|qtP z>zrS8e|hZfnO0VLsb1~VTE1B{=gJk`_tbm3S6c6ar>EZUl{(ftQ@tqQ?U(P$mh}%7 z9=uYr;)Bu^oGi=KL-*{9H%%3_$lJ7n_bEU0^ojPz!Z$02eKCFXnqkALmoDPIFK(N@ z!I*EXA%gx_5p2`%=o{kWSutPYR>*II-23R=(>z>0d-vIb_y@di;E1=scdpjAd30~3 z`m}1%$GwTsTVH!puS?gubvtvzO(#wMRCjG>&-zh4dPLRlIn{kOpnR)34S0~@dA;dP3%BGkhP;(L zjkr!xt!l@ZDplLJuTs5j+wT(bUNjz}C|fd7^s4smLlj)ZTVNgZE)5#Qv+PY@ZSXze zS@wpno~kG8v=_5BdRGSJ;uazIc1Hh`O}xtT-B+W;FZtm#ZxHy{`eaht z%f3$80|%0N2lK-mt1RfU%T$ybbma}=GJ;{1>I$i_PJLM+rBxhU1D`Z z7F)4-OjwfyHk1VmWaVDgBO^XNKeOei$b88weON4iVpT7y+Wt0EXGW&FY^KVb(^IuQ z^IK-u^7agB$3@$VzW6uy=`+>(-8(xdGks2qc-_a(>TrL+=UhKcxQO(xDo&?a#MS9m zzd_HQ4eE8*cJ%UBQiC2>R9f#UZ$CS#a!g0JLMm795L2~EhYs3Z_lhee$>t3+-!lfg z*A#fb-hFj&O7{--6kJ{?kUVRqY-+9&HS9vZ9$USyH>m#8tG%c0@w=L;&X?Y~-UmG% zoLkvSm1r|}c_}52sl8UTv+17G{&!3HFXFvzP4+vj$$iuLn=fcS&CAhzisUY@+f&?FjU^Z21|U0V&~r)lmJPQ|E! zm7eGR8VLW_i;jBRRO4!Ory6?mLLO~LkEjMcdNhdY;hp!lCL5@W>TTOst{M}li+lU` zC-n|eqZ3gd5pP!MiIjVOhm{gVt-GnCK%adTe9(u!r^ zvJ;}Jdpv!*5tWnXE3Tn7{j;?MPXG9K+Bp1AIO~%#wcA?M@%FQ3weK{2dZ!#kisZ;v zv}i|_RQ2=n1Dumvx0yV-P3y_iibQ5ETsU)N5#D|q;l0MC(~{OfD;1A@y2n!PnkbUj zq-NoXbz_K{?fw4TdG1Y_{^sGs$KE~mEAO#Yt5l(1z2jA1f*55j;n^hjrj#`7to+OM z&)Mcu%q6J}Mk#jU*qf$cMXjd8g^5d0Tz) zTVkRb#&l{B731;Gu7~%Ux4W~KS(LBPq{+g0^U`_`snXmyFFUOX#ysZ?2edE z(*H26Tj|_k_3rxpb?M_OzxN)$4-r$pQH)YCjiNeQPgG0RS@p`dY*h~atW5ovS z__Ol-&YxN7*WP<9PG#9(UzogO$7HfnD3BE?VjZ!PJHG@fxGq~ms+h9y`p6k~)GY7! zXD?{I9;m9`z19&`$gSaxG?{VK?*wbOw>xo7#YJ^@d&+ji-S2X@mVdif_g}qy@J+W3 zBimH#(5!f5fiKnI_9Z&S6wO+Qd&fFPMRifCQ^Wc(TI(48^J;9W#yT17DdAXb9Wi%$Do_j_TTE!DW+rz>n1dMi4qEpY^ul>% z+vTg*y_eN^?b4MLQ8lBh1bug*M>+eA_ZTm@D>QbtkvUWD@WuT)SF07wM0Z)^jn6zo zsc!ewdxZaoBoc1x4_pZk91r=6>)&YVqj*cK_cwm`vcS1HwCUc+9qPQQM2cnU@RQMYg3x^;W^4jSv;=;JlJ*QnjKYwa4{-=-`mt32(z z{Xzz_phU-A*A{6kC_XP+1GhrgP@z$O&e>hl{)^j1z3pD-4f?aP8gy_gE2<++wLXp!$d|8}V71~sx>wf4Su;jXZQCHSTJ@@#!y;Kpe$PtsBUSICkH>K5 zLy@f6YK}=4k*++gZHvVc?b$$U^S?kMwYzeO3_N@i8Q~tTa1Mx;?4JK&=}yU0B)r6% zHBEBWq2DJR+InBV*RH8jJ)%OMz2rrmwOQ$G+BCgH{RSN->){!4r=QiWb(IP{S1f1D zAGO|2gTP%%9LT#T8B*D23NBddU5Zy*r6x)KVIvur*xI0 zlN$7kwL@L@W??_o=dANR@HbX>)9f}?_j+VO|FYmecVYSWEB|b$y>4tBQLbgp%GnDS z<*m9~hF5M?xk9ddB@&uz7JcJBm7`FO+NJFc{*D@CfY&M+#U1}kdRw`t9o(}NdSEq^ zOr;rdYg+WuY}MYYnzeZqtJ}(-yJiUCK83^$YQFlMeYwZZdgHgC3Kl=;$}JZ^(`UYO z+}IS7jF{h^w}2|y?M<#VQd!-pmEizKYvheqSzkZWQ!?*_ zd(XH7q#Ju1np||c>d`?zvE6rr_s+R-_rN`{=CWCtrcAAcHk#S6;mk&jXEba$qjAxC zb&D0NSI_BMcT)YRiS_DDs$YLn-F2!~k;1k8|Hy@S?#pxzcDi#!#;14wOqy}vkBIOp zb`c%{l?^zGYj{bd*eEGZbf{8j0P?HGU?H~S6z;;^mZd=+R6+~%!xXH*_29zydH zo^f+3SM;nLsE=jXgLAkgVz6LzZJN&f^z;h{XiUbfN-ihr!_JBGGyr#?SaoByk8bqd4lJ zGe%-Qw%`QFdlCcbkssC31_Ln@UxMM1a`iPSb&|9J$Y;`VSc=^^3-XoBMi`2q3W!NE zVv>xQB-_K!^QA&AG{J3=6vQTFNkpM5M&k=?14l0@?}()8C6YQZm`Cb|B57C>($K!s ze1vLfjZZ;7(;UXn_)R2j2r{EM>R}e%h`dK0-Xjn1wZ{=$z+;hg7BZk9YN8#8Yr5H3 z4~9*56%6}+V&q0mbj4(>$4NXENzb&>Q@81<+w^r1gW;HqO*oEQcr6l|611t%vY;J= z_QZHB!7eatD8q(QrVNxR17*rUnKDqO44p9&^RWddz_1yZZWz-IW7sf;4PzM!n*`cX z81)rKEW?OpII#>Tmf^%QoLGhv%Wz^DJ_BoU2$%6(BqA7@Py)1p2$r`9+CaooFwF?2 z8NoC&>R_4~X`2}3+*Ti^_zv|Cd*UdMfHdP?Jz6tFl!@p#6oPxDcpsRU-(IbJfLky zvL1;fR*{rBTWRzGamz;BvhBrryb#H5A{A&y*(qCg%9fq7S4q`AI zbFm4>aRYJu-dJ+vKv^_KPmISB?7|s55Xq$>9rB?nVlW(Yu?fd<192j`gFxNqE{TR< znz<)}yyiZDOL!oXhiT?Xj*MWsdCHLFPARMJp8^pR0 z^_H8O2c#oiP&g@k*pbHc*BV#J;}t6 z3EFl^+IGqJ@ew}73{bw3l&|DvFifdX5ZhA3wiK}~MQlqE+fu}~6tOLx7+Fyg3{!eC zh-K*=I0NdojDhr^JY^_PnKoe9GVAaSh*ufnTQ(80pajNZ5vcES#JC*!Do4J`k*{*k zMari@PLx9v^uQP_#9o}oFYW`scpo343fK-*_yp6i68rHZ7`7tIQbo#Ju>fkIEe45H z$^hC=rHY6WsoWZ$f_PP4DpG}ZQl&6xCsjItyjLObRfs{AA8-e+MXII*ZM$k!M58~Z zVigYHBA$rwTUk<#c~&cknjl`)24W_@#9{o5-$bf+#b}WlEN3;o!ZBP2bzhUb)T9n; zO%SQgdZl(HuwJO$2a`bFYahZ{+~fg|mLN}ca)D{o?IKc-IMgE!^@u~gSy+c}a0M?! z>L)@LktiLRuo&BUAu_RUz`PpJ{u@lfQtSrvYCxVEDzJ<+q+S{_Y{OQdUK&y_4OfG5 zG$aoVDMzDZ$c{2-1lISBm}a8|*o;HCEYg_o8m|EPYC^u6kgp~saGTvhhH1($O`nQ1 z^B@96Pz&ur`I?;sb=8b^-F%ct3x;d4SEOYXe8#y^>ZR3U?8Ir@7l~GQ4Lyr>M~-I{o}rVX@Sg?%`OM8{K+OpiWW&PWZcF~S?YP&9={Oz8Lv=2rm6hmEn0mf@j+1q%e?E)<9bjpN=!I7R0jSO~i}Dq(B`|))>kfLs>gDLmyB_ot9!Z&f>X9XXf3R zd3XK@%SE~nvo4goYiTq@cZ|hFJQ3;E4ZnzVr))jKkr%|S2eIqX7Zb4zdqC`Z5WAkl zt|!y!$#T(?vh^a4y_i=o^3jWY^rGH+`9ylBK^{~9dG6f{6F{DOGi+~$?fp=skB;|2 zp8HfoYkZ0sSc^lr4BA%TB#1-_)CY0zI|>W14&UGkXruiSAqyC`AH()z*nZSoKkBVN z_0~Tf@}Vk@ihR;YWB_>?KwburmjUH)P~_7Xk%5$BATb-ncY|hQJy<^s?gZvL_!p5O zWkHOGu+A7lY=#hiAB;>WhPt3k<40f~Hsd>y3DoffVl|;E$jd~=nb;9SFb7O$BKe+3z9*9JNqj$v zX-+DFkHN5$hT(I3g=4smS0a;>Av?-|JWcL_aaaiEHTe|C{}h&+Da3dRF`h#Eo5FWf z_-<-i&~B&FZl}_2r}oEGtOD(I8tru&bv=!kOj{1x#&q&Goq0@Wy3=W^Gg2WZ%ApC! z!wkNk!8&0E^O>;+}mN?mR10>z-Z2>S zK|XiM zIR;}kn9es$=bOhOM`)u*HIT2Pe0TI)ke_c$V;(k(9HaYKPfQ18JI*q8ye{U07=M=! zRY6<)ZVe71UgSgyQ05bSK5+>?k?*tOqR7d5=nUd;^0LUOT%g^YS^?VF51G&%-{ZE( z>9SZTa)$nA#)+I|nLNw-;OrkF=U7(G5y11*!TGBq7e2rdFrOcp-;eaW*aSO8ej@Kb zZ4tQ?fzcwDsng4!gZ0SIuSKpf&nw@FT+NUDoWi3`TqBK5hP5KsKgLaw8w+qtiKa=)I@W1#b7YM$6sJQ_JQT#aV(yQ(4^(pq#zDYn8p*z^yCvv6M32) zWkBqnJ`;I1Q{;I;3<3Fj&iKD&Lv4%_c|l#iSR(Q}_4xZEcsaGnv|nZf)8#F0@{+v2 zBp-h;k3Wd%A6r5B|6tfx>5u~q_bM8LLEc}n9L0r!dWnm|LJ*I*cz#wP5%@0tLzF{( zuuR4?{rCx>4&smDcahf?$jj@ZsDl^`1oL=Jy}dpJ>g`Q1DA$|PXo*1}_HVvJ9Cr+m zPcQA+OFenpfN6NC2k(4*i9I+8V(cYGKI+=n2t6>^PQdw+)Jq!K{Yf3^Z-uB8YtiWr-m65tJc<*hf&V zh+jY~GbTeO6hI|3LMITDjN~QbVr<4YpnMtcBVJUdL3SwoNy#3GV69Y*%Yc+AIo9028yyoKLIWn&uI(%}P?L~RhaY`sA}XX6DE z*@$g6>Mh$z#NwH#?6j@y)Lr&$D2mpgOxc%#Y3Im-LZ|}rn4=4ZU>cTUD~{qPyb_i3 zJrqGBd;*5c`4vv%si+TlfN{GTmAvy&Yk@G@PnuV7K&paCSx~fBL%L3 z{1v3G3Vw(fFib(}v>()PI9>OoQd4Fm+Iv<+*TXkhj9*wJ^(dk&LJZ z%30)rsG`KAXjM?AqU5PqQm~vA>kisqG0Io`JrJMb$AyPqKz){&4cb=8O4xv}LAxq> z8!ttbqFkjYSE-t431VAnBp9#MD(u1uybx7763nwSZJ{)6vh)n>!7Wi`l7KoZGXj@I zmF)wTjj}sMl}nGQqRLa&^0cY)#I`(nslYrd7DK$KO3bg)L(pBhAU+3qt)iigsHzNK zm1V0M^QlI=s5VSg^~9JYs)hsdUW2^U3_<}6#&$dwRVxSD;tO07RhxKou1eLWe6=ZG zZOZrYB2jfr&<5&IjyiQgd#^*=sls!Po3Wa|g+}OwNmz-maTzZ}HA#Z(D2Ha~hiO=gZ*UE-L^Vx;T&RRr_!P6S0mpC? zZ$vdqgAY*+Z7~F&V>7VjCbT8*#q z6P}8SUM8w_Hn47K%{Xmndu@nA+YFe8C!*Tz7uBBSu6+!Ki|WAoro&uO9aDqvI#RBf z%xEmCQwSKY6J_a~3=tr|UB<&Fsw?yBM!#;;MRm`M??m+|2Fl4fDAkiT-17rapS{ZC z2A+xP-3E6=^)Hq@@j#!V226Zs*H13NUuRyHE6PNMK zb9@<4uj4!7BFO&)<~f1#Po&Hf<3&xP%}=I{O&)`V*bZXGITbb8Cu$1qXG$cP_ml=` zk0F?Y4LFKxcqwXX5-{vkhMmf=Q@i13JQBqXkc#sxY8rJltul6rnoc^M7|)=*GiZ-9 zJBgZ=6V&4@+Sx4k^9alX-_JUP%Xltob}%yGBUC{v^ur_&x7iFoo3=BXyv=60nv)1D ze{)J;4bF@DjQM`HOw{K^FiOW(X-7SmprFwPR%!P1$cmW6`&FDK8-shj1Cu@j8H{62i5R-{25 zR6+|-t`!@wU)0Lhplz+n4%*tPQ=p8iUW!`Hc&iz2btyCetZ?Vz4kGu&#Qs5MCt z28LZz3boK4W3Uh#!0>DCidxICYa>wz47au$hF}U9cI^gHU$z&uE~%*X#CARN+E5-- zMQx=1Hl@HlQJcfS_ImSN906^5GyS(vS6hke*6Qd2roGJs!*3&o+jfZB9t4($?NJyD z+RXM7qIL`g-8?39Mty#@^gSTae(cg7sBd>GNTk?un4Dc4dm_E$Jhemee4E^&+#mv9LL*&_#K~)Wgs8lr9)K=z(nlE_h5P_ z9OMOU_CzNP!W5hk^}Pr0qb|t%_r&I8Dnz0V+Mq9{f$vX}$CKCboLhca22b?>^>~VX zksnec8;YVQM&b*6E9x|HKK+fTGvxowS9qA?={{f)R?w zqRyLO8s|%bVb4?U^V>vSAdVM^@rC(dT0chPwWy2PQ61etoGxy~9Z^4}0kQpQB8b~1 z=5eV!J`;601jO(1eNjKxz${T$h{KiHqONLahl8T7WyEb!v7<#@XL{F}*Y!T4ZWtg2 zH>lqmjC13FsGAMJG;f{|#imyVEZSGcgP?lRPN4I_yb(_50u7(BJfUj{@)E&mV zlMBS*&RERFmmvRleiwB&3+jP3ahGxK`b6C;3*vw8k*NE`{5~;%z;^us<$OS#A6&%~ z;Se545f19%VQJJxOLWIjtN<~2ME)MJoqa@||3aQ0GmppA*RKr!>oze?3*y8|vc?`SI=)<@-uBsgC=isgYQK>!NA=%7Qil zKZ&MWNQ=Dai?NsseoMrtf>olKyw<@?4}R#({8lt865MlbodVOg3t$iqh~_vb1HSWQ z#%K6Lw4mf*zCmY23(k(|U>+e%E2ILLPslC&E?Od{k%(~X{p z)FMgo7xF%*g7~{sX;~$ITI=*LFJoU1OdrE3HzkzIKZXQPRGHlGc@=rN|8L-Vg`&P} zNbMB$J|;bhRsVk8{(Ai2`rQ2R4!eSE`a8c%NzYhm{kES|Q6_r|$z(VE3#0tsals&{ zy0msk|7*V4iL1&c7f+M?AL5(+Pt)EfgWZ145*h78NlmA*3=8TlgSkWTlc16^BB+fF zbH0_q&MKMtPvdqYpX+@`EH~{YJ)P#n0iIle^zG+=O1txY&xGOJ?ti5K`T8plq>cN% zr!6werr_kVDWq{=oS;IoJ}8AO)Ahie)qfo{qBMPG%t64?|M6rpL{nQA5X|OlkPz;d=G=B`z{21;Cm8O-S^m2#rM!Z zo!L_U@1~b9Zg4)CY=;y7w<^jWPixj?e0I(-zxjM$nD74`RjA7@4BJgo^ZTaV|6?~8 zw=wPMuh0(P{ob)y#;224(*Kj(dHf@%neX|#+nC_CQs7^)f0mE`OMggFXM+(+Dan(Lp(VU)fPI_un+fik9gAjIZ~9HvC^s zfAPE13Q9!&-(kIx9{(BNmj7({znuOi>y2+Z)7xadkTw3)?RNE*&ej0wY^NX{!Zh5^ zf8}jCUPuqSP+*?+9qH@5_g~LHq^k6AC7LXKgI*>KGlO}!79^x^KmWI+z5)Je|HZV7 zl9Jc>P)ZurRT{UOrxo&Ad3}RidM@82(sc>OlkQIVzB=8XV>+KdVmld5x7)7- zCI^P`PisD(cfcJtf!p6V$b92ZcbFeaE*Jf(CUA$}VLm2L?a(M8ZRe}*|Lpb)@Fe8r zA;VA9SNiUA1#=C1|9On1zBI-d*^-ctGDb$XOycHu4n9LQy|%2;d4^7}&A3&3ll|i{ zj=z6xZc=2NK(}uW!|Ve48*H;2VjHl6)b;kWKkjbq?R4bvnXF-5zs3&ojelDwf0cSJ zFwOzm-@AVMhtKbR_rKlkoAOWda_9H1e|H|=FrF)6_@I3(Uom`km+jp=A?K!luZKO3 z_B~#Dc*10|F+|n`_$HF(Geabwky*wWS!MCxzLRovmqmVzOz1zCbc!*=H`iFkH2j#x zpWTeJOe&xt#w7H2hn;RL^Ubw#OFO@h64JMy|L19Ie%*AF{UQ676Iu1|a6u7!8||95u#=i$!h9ac})HSh2= zmO?J)wQtkg?te_J+N@9Bb(GI=QQw)Md%hDvOMJ0G-}Ul+j+m@e?9f4y93=l zou|^}&+pz2^KP0_I{kMp7ys3??1FNYvrI?yvA)y^>GVJ||1>sco1$Xs4;vYBOL#ozdbTXkguZFVD1JhouJXAKr5q;B{B5>G|h>1?JA zu}$GT2*)h{=h&5Gq;-%s)PQYo7(RAWUsJoK5|BJrGNGKPa}5 zwWP7NQfk`U@QQOHJ!Oq&IK#mH0c=P8-P1|gzFV23v9p^tlQOUlvM(p}AIh-D=_d6s zjiAnS_ov@MiSY>C&!v(pXn>3ldY{j!rL!j^^KL55t>z4$mwv5SZi~rzCzA|y`bkN5 zIpOSKUiX;GzYkhXve~WWYcsoav@^=*EaSsj#_F&xY-cxz;eeOaJal+Dg@*<(4JJEGgKHvOx}T)N$U zd-Srh_|Nayrm^qfn#{iXGV2j-Vt~B)dn(8t;F4FQiu6`O>JE1Sr|}<;LtDRXmh^e8 zk+h*Y(#32h&8&iQ%VpP)(VPpq>vFs8q_qDy=}uej?yQrcc4Cx}p=M&4Z#I>?W_>xJ z8PeRCDKXYoep6uv+q}{8S=DeWZIDJ_?rPgOp{_MN0H?XzdoHkV2}_KAuc>!rI< zll`>yQpso~<+Qt!-rqe}V%%wJy6>b>mu`(|@01vK9>#EnUn8ASNzcLOY*JFYDH)B) zbT{U~;8K!-bACD8ZljR2GgC_w*3Io0KH9h{O-x7X8{bG*uP3MvPkhX{nHq!rEA_2 z#=kkN(M=}#_2f9}u*^rc{~P0SUb$Y-45`N}aO(wqA$3^pTU%MB3gFCqO|-IV3t-JW8{m*T$R9XN!w5*F7^ z(#B1|O357mg(Qw^O#dO6Em`9?NmzVuN&KdrWRH*Jd+vXZYvCVWUZ;?p@yjLitFIZR znDQ8eE#Gzr4b{pbZGy7yI4Md8 z#&uJM`wVkkAqQ1zB&Pe{n)W|V%d=4>#=+pxvH)KMjgWakBUEI<=LLU$PAjF~u9Je2 zD(4>suant-=Yaec>?$ok{As9?V1#6qOK$}&P#K*A%5nvl=6g_<(r+bdDUTDiq#yg^ zo}scMIIBvXAnn`Kb5ErWp24`Vd~(T^q_Tvg4flh0#JYsfZomH*v~kP+{{pTmZ%Puy zawZ((fqi-R7<3`qz0>R)wn@;=ZY~=*j&{!%%nBL7_OYexO3de=O6;e1kUcj0g3e;e zL^_jWv++TLq++ zaxBpp9s;W-6aD8CIF?Ql!af|w`Tk@3x6??tcHka&>m2X9$Nv6feU9llR_XlqT!4Gb z?jGa2c?oJtT-@s#&1JDWU-y{GJ$CZr*op1n8qY^O1eikxdA^nv94jp4DT}#Qgv@b| z_uS(Zj=#qEkGb6Q<{U4%$J;LVn8rOGbB~eUo|As}_{=|#x99RWK5~b1=kGs$a?khF zAvQ6};@B?6;oJ)Q>Gz5KGwQmnrz+=JigB#K`4^{%{E%>sew4pWa9+!L?LS`Y#xcZH z`tAR_HndN=QIGwo_pfP>2l>US-n8`z?!F^&SRGVMK4lqM&gW~iv+nNs3cg?Lwr$#Q zXZQG>{X6$qeSqbjgG@MXc8`1Y@*Ky*$s=RUK{CK7!Tx4h_FJpTV7E@JNZI8nCcD^Y z*v0;B<=~o9HE~_3nk-7H27Sf36}Jr>l&C~!B+9*J`se)xTr1VXWrlkm+liOCp6=8^ zS;_6mAUQ(1O5P-0B(J9?{c1}o(uyH9q@v~}4()h%Uw`T8o;T)L=ns~SQz4O)G}TjXTP!PPd6v!QQbjMtyJul`mCT-G@{;q@bKU;_eU;ks!qbZD z#H~~p+Qdq?Kku7NZx!IJXHVI$-!CV$K{DUkA?NIDGMxR}RqkhVlB&vGGw1xu%KO?j z8E@Q>*+x2+vph1{-KXHVbvWlPzM}31aGiPx_bNWq{qmcJQR zJ|2>S+s$%#oc+!-+@Z74NFxi}Zas(Zgt{o5QOC?6>zVI1rgfEJFZhS6!t(CQs)CG) z9JA(A-&s}Eb*49250;@uggj#Zt#=@mM=Zx1&Bnwjzx?i>S1I!U58O6G+%{q%>&(M^ z?q!tM@^LKoWrEG5JFFtAn>$aAX?}FM-CALpL*8D$&9gh6+mHRVCz6}zz{292`S<6t z#$OhRzlS9}o0US+y)J|4k}*C5{hCX<`1eR3GW-{>bf9&&5BX!(C_9g4`eRK9P8?YaFiQykJUw7Vd zj6WPF7`6;}el5=3XRVHQBCqbykLLp2KjtIxzmiTMb)S24htI+1%%t}j0Pz+1+?KQo zx?(;1GoK@x{#M{~d^@IF0R#9v5pG#|zAY{}PJunxS8l#|#_o?-IL`NF5f1VepOWux z;tJiNaAP(Po$1H(eep49O}ASXHxJ}7J_&Ncjps`GlLz;CzE^pf{$ZXGY^OV~p9Q+( zVk9VTms?^!-wvd&=Q8XVzo5A7fqt)|627BsufhY|Xa8O|VjiUad32-ufFz3xk?gNc zt*wl3@ig7b94~+iurP)-a zHXAF;C{OzZ`}HT>;g~1!j@v>WI)LMlxcrojI(fN)?oY{!8;gv5=f*4*+~*iSKt!P1 zedaO#7t{lBj?apRjN2F=qX;t5k8$FD4~S38XARu5_S*f9=Mv+VkPaXv#{N2@+ntwx zzO);6-1s1OyTJOnTX0?RdJ5`>dyhv@TKS1sMzXAKlVr5j)Nyqfeu1PQZ>i&07UH(L z>vE2>XlHTDFx4H#EG5}!xYw8O({u#>dQZD6w}@*p>Y@8lK+%mb)%h?^VHQ~J*n(= z>z93yi=sq=R=sH`5<2u{>~FvE#xeT5`^Ovn3_G{BM@){+vRZTb?rb2{FH!G&)`Cq< zcSkW|Me`+>#Xh?IQ>jz8MqODX;XYqr9zm1Ff^dbHk6@23yZ$LkaI#rkz) zf#Earr0f)0F0@K$jnLYmbwV44HVJJTIzMz#=6;U^$MZ}1Ru@Rp~ ztcmz4Vr#_ih#w-(M4XGb7I8b`Zp4F(T1G2ll8nhSrpuTiW6q3~Gq%jwHshR3dL}bd z+Dws|a%LKnX+ow`nKNbXkoi>R2U(V6*_>s2mc3cN&T=%%cUewlxs&DBtdiBrT0Cp3 ztZlO{icAriCNe6rP2||fUnApk6wYboOp!DF2LnGClFOGn?TUpfeXDcFN^Ed!&e*E4 zwPKsbwv6o<8xuP;c1-NF*x9j5V>id{jlCUvC-&8K?Rt*ug|3&l-u(AN-dtXv&#l{V zS){6}Z?q@6=*ji>`OWZ;^s;(Iy|vz5AE1vU`jhpg`V(VOLi8&U{aQpnis(oG8T|vH z$3xGC-V1#Y`Z_FWSlY1guxw$a!zzc>PKbVsu%TgN!#0L(3p)~aA?!-nv#{U8B|K?( zp78GBy~78D4-Fq5J~ez%_~nG?3(+_I(N7(bf#~P>yXZHLXc;jwVrsa1OBX%W3 z|7ygogy@?@KiNCcZF+IXE(TJznbV&}-(!7tuP;79KV-U>@!tCn7I2TX zJY?PfkZr*2{MXyu&Mk8D-i^@P1#XPHG4=*qkXvgp;_b9 z?{&S`{&v*usq~$2*Ihfvjjgvk-Hy4|=vKGe*4>u3eq-D>ZvWfn?GpUYaqGmbQ@76F zPI)`z){9%uZ*F1UtFh?joSTzxR=62?GyK-fn?K*YeDer>ci(Kn-RyE}!i~*0%HGI! zJ)xZL^h9Fgu64X_#-5Ko7yDUkpV*$U*{-dtYZA7+h zS)OKjku^z@<-vtL+)=IuYK8T&Yz@U?SnCn@LnQp!A$B^Ob(fvnu4n(?Va@CQd2ZlV zf;)kJfA(kY9=LxYNdsx%e@H(1bjFU5b0LpI$Wfwn$d)K7(Kb@ozC=e7T}@2iw||LK zyIqM>2GX~E{r#AB;1b+c}{;D z%I)I1(O>>0A|8nz{cVs$>}4i;9Qd5r`dk0EUDj|jn;C7EHSSuq%}BGAna}*lTx^at zyPK8G9%gydG%eFMJ!X&@Y$h@jn@P;1W^c2vIoup=jxawki;5cR^O-}F$T4JsrRg&sjWvQW6k(ydnt})fKduX+!HTx88G_L(=?WLX8 zLE38_Wr{XNrfOqlnl?_RYvW~!_BqFBpK;u_T)x-7l#|*zIi+ooAG9U*C)yt6Xa`gx z?Wp~gN~4`uX|)ULJ?%$(pmt7W&`zqH+8^oz?Ul-<#i{(-Z>pm1QI+%{Rap;Kz4VXN zAia_rtXEb;^eSq+-cn7_Td9e9w3?*1R-fs8)aQC%d#K)9t=5OAHN3fctv*J5sgG6b z^l@svK3*Nwm#YiLcx?>aNP_rajo+qWbHF>=9~~-pTH5 zf1!6%3+>%%ky@q?)t}hI)j55WdZr(>``G>L<@O5GF+=P<_8R+?ea=2_U$Dr=GBl1Q^8vF4Ey+Iuod z8!a=m2`aJnt*XlJJXF&Y+kNe&+SlBN+f?HmoiuuCry`SCRUN1|v zx%OzSu06)yub%4PN^;HSx>sjaQ%|O9>B&`XJ%##MPpK~I+qARV3%!T7%|2j%&1?K? zYvoj!TCBIwkJ)4GgHm1{GuS}WzzcIYMb5^AhoMqSl6Ybov7_8hIGy-lm2)z&_? z=h=(w#YSnPj8V=gt8Ldt*(>cy#(MiRZI(7&o1v|;H`r6OAG9;tX?wHwNc%;5sQs=z zx2NfO?dkSbd%IrN-l3P%>*^n~Pg&lcVb9c?+w<*>_CkAs-iv*l;r93TNqwQdOh0JP z(vRz>?IrdydzHP;{?h*3nqwa_O4*a`!`goBggsR+Zm+i2YK^sKT2p(Uy-V+@jc^*; z-`Gc-M%rv`j(ONVYaTIA+CMst?bGI2^Ss&B?B>LpEuCx5ednHY)4AnrayC0#oUg2A zRvYJzbKQC1+;AQ`cb(hL&(2lninGVr?VNVDIXj&lR!b||YHhW*np=&WT}~6{viYrf z$~2-KdE9)F{go3+$rIdGzY05s|n`=Bg}#FDR27zQN`+4jCSlzR?^Sv=VX9-&b7+( zazUk%Vk)aDu3u2C^&izI`bD)wUDJP3XY@HFf1e32r&{FiH#&iawCP2QcGoQFg6-r8C$dg z##UpyvBR;AUB-UnpmA9L&^V?S)oK{W^}WV-`g-G}@q=;JIA>fiel*S-KO0w!tJ~UZ4No2;y>Z9>)k$tY(eryYc{Y2tc(!`BdA2)-lij)G8SNS48S5G68Sk0k znW%~yI~~)wsQT#z_1}$6_ERT?k;=HH#~It~XHH6$!q}s~G4`tD2IoDwZvCZk#7J$# z8flFi_H!qd{hO28e&M9Ce|OTVAB^t})3|Ba#%aSb&KMqTk@22!%YNx_m%IJSNvC^_ z-FBRDfV1{TWv%h89q+uavKWW7`;u2nEg$k`_)^;YGDaI@q;t~Suboi)jg!IlI$_59 z#%)#D30Fm&2-Qu`r+PRURZl0A?Q=5ghxGmWVf}#qjsCSGP8LTwSsl%Z)L$B#)oJ5| zqdVEmedYo4YxAgi%sgTKV4gOgn6J#&<{QsEPf<@XPjOEPPf1THPiapXPg&1bo?4#T zo{v3sB!!k-%4nsetX7)41IkD(t(rt>^`(IpB@ML((nxD4jkQM7N$Vn=wXV`d>n2^b z?$V9t2ghr}WfoUcmud54xi()`XbWVqHbquyU&y!G3OS-JQ%SUA{8r&{l}!6irPD5| z_qCr?dhL=5)h?@y+GCYT`&DJuo~S(9Yn4}fqdwHUDxc<4b@WuKuAW-e)6=N>dRo;- zFRc3NMbvPvJdDt5sF9rA8Ku`!Q}niKs@_gb)7z`*dIvQ_@2KYK{ndQ^6SY7epuW&Q zRonFGYP&u|?a*hco%$@bOP{R{>xXg1t z{iJVKkM)!4SN)WFqW_>R)CcNo^c`9fZJ0LHKB{Kh-%3i&kyKi+q*gvjqbW(NY4RSY zAkt|f@2fu~y^51i6)zdoYY9`Yq`THrdT70*r`B6~X?>)()>ryy{iLtfU;1gE$T@AZ zoY%IfDE&RvKu^c3JlvPl| z{WG;!|6J|U=c@hsJRSv~ufEn7s7w4_@@0LO`dQztuIPL0@%Ax$f_>bcXn$v9G%^{P zjVwl1BhtubWS5MRi8CKrB&$Snb||}Sl&@ryY?dvum9tCRjgCf)(aGp+bTPUb-Hh&> zdAcN*eMqnH3k~fjOpr*x~uM~`)YuvpQpcPfbpfVPIXdU__?~D^HNP+ynl;Ujo$+Qbv!^r88fkrO)iK+f z9n6kqC$qEJ#Tji5HitM9&Fp4gXM!`?8S9L3Mwzv(P_uwp$joKtHgh^t%*|#Uv#vAM zndD4!raNCcGn|>uEN8YEL? zo*Qorud~W{X1p?fH(na?&T40k@x=Jmc;u`#mGO(Id+K`XS-)E^t@>6yYr46D-}ls|@9E&_=s!y+)msLR2f4%1JqzV$4JVBV;HohP^Z{lxX{G?PEGft5 zF5@m~mH@6~2}_-y8A+PJY(=*_t?Zm-;dq?(>}M_}b+?zxH@0m`knngNPfORWIqmdULfC^rtA)0aAQb6&=uKk}%? z(OoY<%^|HHpq7$G1*k)$4Fc2=QqCl~)c2&E`*NwXq>TgA1JWh|W`0t)oq?xTdC_=) z>DHAS7nqAkTLhR3NLvP2?)2O?1oeV6IzV&lvvq*x)0l{|9Gp0c=OHy>CxXkK?Y{=bQwWut#EelCuYQcajhwkPwLA?(S|kxVyW% zyF+ky_y4>4T@$#sepO%92leFLJ!kez_v+O>ZF}1DCK4<5kh}p_t~Z(3eW2p6z@7`m zxT9dthwe`770^A1y%xGBv64rU#~>UG-J68sk8)iQqMUg9l2Gh0g+vEI#TFo33@Y~n zSS{gs7mz^Gejy2mLoXu1r_hT@SVJ!%;f>HsiK}xi!)Fl6H7+ON)lf+f2=|9xNy4+C zSCMcPsGJ8viOV%4lDwAmgHX=Bj)X@;uLn2a_-jy!2Z+RfZzA>p=*=XQb8jJ`*zZN_Hd|2+hwQJ!}%xF3Yz0VRMws95Mj zN&CA7r<4l4YB zvKB#qB-TPDuE6@xpGkNW^cP|!?|ucpWhL@#F%l1ekxIOrmB_Qg%tZY4p~xE{-UT`f!H6ba)=H$#Hpo}O z9}S(INd1k`3WvWfbWS35xtWXj+d+}vg4F9~9^!8gotH=*Z{{QZ80h@OI}X~1U?deU zY$e|DP~?Gt(N)iM6Ym6Q55b75XZjNFL})*PQCQC`K%{;&3lfaXdS)Rab)H$6V6+x5 zHziU=~ZiT@IG8AbAHS>nG8T~3jF zTb}r@Kvy8~-q00^{~~m#B6&58_+p3QirA@__`g6qirB0s{;$xL6tUmR#QzN{ek_O$ zRwMoo(A5>W#u@~x0`bC5MXt9Nu^Du2MXtRL!D=MDkW-lmU5{9?{rbv2P$@sa$~8As z_JwXltXy+rWk2X9#I6F}R5=p58L?YKH&>2=Zb9q_=$6XS(5;9a36<{%#{fAGSShFS zH{o<3VPGdirK}2P04bjU?_KhYlt~cp58aN~Nzm<;%b;V3m9*@jTn^ol*h8UXl`Ej* zh&>FtlX4|=Ji*F7yns@<3c3riM?fbiS3@NafE8OwoPxFK8Nl}>=V$vlrNy-$G|=bl{^-{1d^}7J_VKZgZMV6*b88G#4`sF={J}INi69; zi1^Dv4<@mkdkDdN3SMAIq>XRHUO@U2Mr;Hy+v3{#uDvh*BDMh1_BTh7SZsDQ@x_0S zA()@RODT!8H_dS*mUJ9XFnfcSOA=4)e40;~1A@qC_ zOTJw|;v1nCD&iLx5jzNaF^MIwE>R?XmlC@p^fD4pf{NdRSmJdBvEtX_--5(Zd>X`e zK*f&*iSIRv#N}FI_lI6b>^D%c1&GCuZctu?N*aKbzl(nh-+`Nn6`S8e;>plkNxU0W z{PlKRQ_?MQ0$~EZlZ4Yl#TP*=$L=Pvr1c&WOMcx8?#DS2p9e_%JoF(VeMISJ3jStL zi4%zTfN?4F` zB=G}QzWahA`5)P;^lOpx=^sJoGyv z;|Q4Ha$vRqBE*wCT2vVeU5rRO(O;ZcvELFz+Km2^1iupY{QgARkN#4`ie1}8+LC@w ztk}39Qh)d*u{T32l`uo5rvMPMbq;_JY*d&Iwim3T?s0I55rUn@0R)7xp<7 zx}GA}U!TNc!wnRP`-Wg6Am87Z#AiV_0h{7FQ=ppx)LVhrX$umFJ+>r)*h%at+yb^% z4uMMgL3{~xB#DoKjv~R;P$@qkJ{>w*IR&~c*bbZswpT8Njv?_iQ1N3B9|zr0c@`=@ zEj$m#DbGQ7BC*(QJlGk00mRRQ&%p%cC+I}wd*~z*p8%aqY#X{Oi6x#==0SWDRQv$M zVv{|H6BOJFaA9!u=~P{|Jv zOFkY??Bh_e9k9RMH8o_?=u=kUYGcBpXBJ9!Pim5L9ddl1-peey#yx8_8o3 zO1iEi@e9!FN%%1I1`<9Ey^+MPK*e7HetqfrHL-GqE*D#Fnp;NNn{QcpYgR34Mb^+d(BB z0B>^fe7QG>B;O@YAQAr&`vUwz#q%W(KqA)=TYymPBXT>!6ekJMBxpdQanO(?UqB-gjfci0nhZ@y1pl-tiFSczB-#}^9f@{;PEVqV z&>6sth$qrxXCl#9=*%QRnxt(cL_0%g1@q#3q|eSrqTQhLgBFhe3he=X@%ayEKN9T? zT>vbI>+B0%h(!B97bejj&_zhJJ9JU71g`TJbV<-3-`x|s6iLN?Z4w;-%}FY@EJ(CJ zv;;#vFBQ8E108%8JJet#KF+d;1-!GX~0NFcVo9^8a;mWJL8Zo%i(ptpkC z@LBTfc7k6Od-e_z>;}CP+y!461ihOCyF>3$#D@2R`*0nx$^GCVe1^~2he;s$`G_L% zeH1)~b0lvcC&Bj6CrEG*^hsr5=u;#RpM9DHr$L_~Ne+D$JcoF8pw9!;wf0*eJ^=#p zC&^cktOR|9;LQl06}y83b%B+90?7(c$s2%Qu6y_6H(k;r2(8tP1^!B$AGw3Enj3 z*#Xih+P*XvDgY_$su+{J~_mea@{3>*bBZGNV+Ah@FzjW zT7n!%zFh+<k-oR|Zyk?AQ3eF5UxHrc322A7I!^LZK)DZAQl5pP%mk}|7XZqG z)MvrV06y(_2`cwn3%m)|2J65MdqGhi1nD~k>k+)c+zZxMP(FhVh}{9YA=n7#i%m8r z{sYiWz^34Juo;p5S+F@t#NW3dc%z3GY)PaZ4YpFAfo@IWkDwz+BIRNvi9d#pQeJ~@ zL*h@Mqe&vZu`P)|g>FX@DO=l<_%rAjl1Mp|w1N0@=#C_jGB=jQUqHu^WG1NiF^Io} zjwi{?(49&A6?7Mp%mSUDc+iPN+E&3NMas=&Mbf@2iNA*KMiTM!-AVinbPr<1$M*#L zp-pu?bbo?f61?C5k}L{6kR*~v2Z1AS{-Mw#iCG$YlyV#NXo8&)yxw@@u=oKW9w99ot`~&nVg16mzGKM9@KSHk|$z0HDN&FM^I+Dn}#MZz( z3%x;k3VNd=`FNA^K2&Tf#NZa?Iq0p5lvD8o;dyYol0xqwzSvT1EzASNzQPAUYzpF^ zq4yBHW!?+!1^40jFVOo*GB5N2V*i3ZsK|XJ4?rybAa)nz8jlc9Z2YJq#~ve|*!Xcp z{{94se}_IvlKG%wBVZPUKCMVxo*~i~4aBCvYeU6GK*sW&U0@q8$LAM_jOzz45^n|Q zOGL)+UA_D=&OZ#FUm-Fk7rd&79bO~J{Lt4){0H<6lJtSTN#Z}DZ;_-0eVfE$Ln%uj z>4HvGBo6N?A40`1fvjN(#3w+~4VC-@vYsW7^FY!A{fL00!d%! zXT(f^eopYVSTFd3#D7D-B*_BMuZVpB`ZbaEb?^BXP(yk1CSEOu7egFeM2qX^#@hP!8 zNGw#+29ib4=Uj@2-_O1y3E^{nhLi9v=t?BK8@e)B1>Zp$`>a7i*rd-|Bzy$AHVGet zu1iArc%KbPi1_r`2yBe&z<2v>M#ATzBS{Fm_8CP&q`S{HU^LE0+WMSLLfF606cXMK zy@Q1C<392?fHzQieI%}kOWKB_+zIJfP>B=nnJx`Q8R-MRN#)w`86h18g}(^tbx`$gJUuLzBZhwHauBJ(q? zgm`kVl*rskDIX*@Q#Fb5t#>QbrbItXb+Jw`c_}!T?p+*WK6uZ0P!w@qU;GWKHfrk6TFL|3lskw z=pw{B54tFkvHsR##JdE#IFT{+))K^f6uKmlvG7)Z;ynyqipaQm3w|k>nV>n5@!eKI zjO0&AWSqBE5hHmufXKLSYao&Jm90TU##~#2i4hwQA^vMn_`P7n-pddflWi?ajM#oT z;=ci1o*1#K_%V>N&(?~>h>gWxf&VpB{2LgtxA-*hzk&7=BR1#|{|{(QjM!r(;{OR< znHaInD#ZT_x+*bZr`3r6H*|Gk)`hM?toZ4g#H18zw&)xHh{`^ zfE8bs^MIKQmA?TiHkL95%&t%=SHOzBHzwvl=qAKU95*HAAn0bqN_;mb=3wX+#7f+^ zB<2w4R>Vp=wkGB{=m=s*Lq`&GJaiPX+d{V?<^-sm2kdrG`5Tb6U@Zv)yFFBqbz!YB z#ER}fWIb0)>H=VQhf4Ve=4|LVV)uaVM9ewR@kGW$TRRgYWoH*6NMsDM zHHny8pppkb#v@x|cVKRX?nY#cvL$f><~FFr1IUSw4iwIA!ZlBI}`AhY@=z z^l)OHgdRa;T(Wf}F;7E}A~GJ?I+~bgpvMpyn`|9R%xh5b6(Hk`E%6UvzJiKR02#w; ziLHV88Y=b$GM?8Gn*#FxPbV^l+B$>CxMu52;?D#X9|FGE zPy7b>Vps7M5I+JH{{TkrBliHY*j(}kc(X$#KY*;eX`N5JIiO-+AZu+}7ZPty=tac* z4V63t-ds@0C*T{X-`4uX*2Hwul+lW5_dOPu? z%-uo!iO@TVC+FNnWGz7JZsN%~_YheZ(7Km+a?X83)(EujC!U=10FkuMwZtHR4y#$pq17wc3^&~MHLZ2d5@<@Cdn2n&~ z$3W(>TH?FFYz%#l$h=nTd15w!zCdJ-t0nOOW>cu#8(7Jsmxm zSjnT;iP-}B2C?1HH;LI2`WCT0(6@=%3i=L_IkwhRVz!39OKd;rd&G=@zE5QSt@Qyh zlII^1nX74iM2zJB$3*6BTAvUjzVIoLIh@vK#E4&fPGl~p^#w8FBVQ7k+i878%)QXB ziOln~z9B|@=UXCkKCSPF5j%ZPWG=rK#V>W@}esNGvR=Io*B%6RdET`JG05|-t%yOMcWn)}!*`LtUE724xIX-=YiANH21UIfB)DG}>HKpMJ_Nts_qz56hvE3*(8IxTp63$-=eJ3;Ry*cry_ zf`1FCr2BsGAnql3f_ks(5qusAeH6e4y#t}}xi0vZCvicYCU~evyPhJc*!F3XN}fDJ zQptyB!SlE#@~rCxlB@%L8N7nya?Y#Z4P5hG=vxFk?s#3|2LL-UcwO(1RPu8wfPcw2 zYnS*INY;mnZ^5U6y`ZQ=x?oosW9|9`d=6igW2h4ZBl-L#_zKtG4f-`n<@k5t2YmN3 z=uhBhe7+L;3o$!Femm>BGlc3<8sZuBwQ2PkA%p(?gdD4By>R%BHz0gA|djwdts6s0bPnj z2SeK+M|$Nt1*mYZ?Vtm|Abf^xy9bj*Y%&BajpO@5mm$f~&}B)uD0Ddz%C(m#$x+Z1 zNCH3UUXdgyquoPEau{?NNlGgswuu#i5c< zAUO^y`3J%qpps{6;Pbjru_;K7hpt7EW1wwCqg6SD?F)@FD0VfN~$c0fn!3?~TtNLiYi%ariEDKN3C;-5($iBOiJs zfbFB1p%PcvHJTnOX#gS0S+~R+gkL~UBq7RBx5OKSe?w0u@vG2NNQg4heJVHu=X?h} zlY~D*&jM%T_;1iDB)J579+C0K?(<3T2UPqRgaPzIBIBFg7m+~Xa50fF&2G6Cka5%Q zOGzl{y^P2>Y4_zM^q^M|A9nASya6&U+kF+0G1l&@!8JIJa^HO|N#xqs5gC{5zMjZ< zYWEG`Mw~CUx{1hmYWL0H7UZwQAa~UJq>1gKI}P-h*oi5%RbP zMiL@?wya>sR)TIy zqE(@rkx0I`If>+ZTaai?D11eTR)=myqSc^VgM)B=Irm@^KM6eq9FF4$K#wO8%0>^$ zmJlI-d&FKKIv08piQtPpVqXx6O(e}A5<8y?aE<5!DEz4hX^r5gJz_8TW%MQV1`@%S zdTu1q*U+0t^f?s%)^i8W{{Z?diOz;TPondnFOo>^^EP+~=YI#4?|g{Q-#|Ym5&W;` zGZMiE`=aa#;muI^kr3j3ec?-j#kKp+L_(B_zO#@J<)rVdBs>B-F9{LfzAX|W9eo!k zA<9tSl}UIE6nP?~#Ah}r zY_tH*i++LPJ3+tkCRaDBk=~297Fnq z=rJg4CxmN3zb4TSp0|)kqL-i{h;aUE&}B&kpIK-GiC%_|CebU&x*>_4gKk5jSE2g@lvBxzg%KYixe)pziOz)nM56yde#kdFJGIcfL zUjkj7`0(4QYY_h?=$gbw8Jvo|6nxlx>e^s)e0~#(I#IAILboKghHgcyoVzu#>q3#g zQ^(-^^`ScuI}W-d*dIQFdSdDU;4pk%28w)}dICPn`6rTaGV~;X^o6@YPXia=^Knq* zs}LfOrixuI#^-CHl1}7j_&)S9a0Nc2%uc-$T!qhHLM8nmM1D`bhJ=4X<@?vcwlL$= z>%k59JR|f*fHD>#kEe=VKE&r1^dk~UTE8dpvry#mROEmBIu!mk75cu6*C+LJ=neFy>gULt-OQ<|j^Ys7vJcH>r59{$6y~_WHpN;2r+j;e~@#eQn>t`Q(CT^&o z@peRv#JlU8z?;?HtDi$}X8XK;j&S@v{TzEs*dO$B;w>F?>*o~vm~X0|Gp|2ZAn}^h zVPDGI_4D-DNAYF-JOd_q|I*JhdeevV>*txgEL_AJ?v2M->>e0j9pi21ZI5x&Zj4>D z9o^pgI5HYXR>8dJBz&5H&uin~@!k#yjdEuX#hB_OoHriFC*tpmxT{RURVQL(Vk!K; z9j+~BO!h`%++q~Y9EYQF_BgN<{@Pr9(SO}(7|t8(ZR3^rP8(}S3U3*2U2i>a4Y16= z&Rf!J&ir>Qn`0ZgSWm<~@CHxB?%!8qyq3h(2O?%U{vGHI#{Wb9Kd-(4&gk|=;ELNo z|9#&v&ITiJbP`fH0)HQkRF1&s9dT^Dw=Mo{Qr>^sr*(0LSYxvL_E2|SvGzp#FY%Uq z`L{*?xxzSHAG>nm4BokR=SwcG=k7KMBiM3x$wi(q7CH*yop5cuFC51wPrIMQnmN86 zQnfn%wifbH&h7U8x$^4&b%iDHxBsDiGL-YyCE#` z5DUwFhkEO{&y(O!-P3GA-yWtyBUPj@#SX%O3LX)ApA}nQvo^F{U2%I{nWgIgii`Ng=zrzUwm4U={ZEdGEq4D`cqezi(XdAoyCwe>JIU>T zEI$dc7EAtf?!QyA?th*8PnyJ=ns5GJab5{Ap5V%Zc+@s7#!aqI^fo{)insl@)yDkS zy~L7|-`HUnv6Fm}J4zi!-xW_EjaqGl^9aeQ5jd|KzZ3a)>L%i^VypF>MYh8kl0OsO zmE;P0AT$DNR+@a6gfr!CQcvMYD*ye}_U?5b{^uX;)`Ou%;~Q%N6PgHn93&<+nVAlI z9n4^6G&7l*%`9eCGn<*+%z@nx<}!1edCa_KJ~O}RV_JCUdbjB@eN8{JfLYKiWEM7y zV1I{x}4-C`Y2x_wu zcIsILyTz<#RyS*yHO*RPZL^M9*Q{sOHyfA@%|>Qpvx(W%Y-Tn$TbM1)R%UB6!i+Sd z%r<7U+16}lwl`zU4tUG!SToM-WX9ut`n#A3W+HZMm~3`6yP4h19%fIsd&53vU$dXt z-yC2LGzXc3%^~Jcy!-TU?CNl&Im#SujxooYzmUJMzlgu6znH(c zzl6V}-``)#Z~Hm+i!1%gAK(x42l<2jA^y_X8)I32Ie&S71%E|}?ip*zJNo@~{q_9y{SEvL{f+#M{Y~(8{LTE$@rL{@vAf3B{s@1h z+n-{zzpcNWzr8=k-@)I}AM20vck;*kJNvu%6a0z(B!9BMtG}DSyT6COr+dr!KK{Pg zn`3{xDf&SFAneg`h<~Vmn18r`gny)elz+5;40i1}&OhEi!R-TZGWG+&`}h6R{WJVC z{j>bD{d4?t{r~t=u(QYc{ssPp{zd-9c!TYwx=Y5D{#AIJ?KS?j{&oKK{tf<(c(d)z z*k9mQyx&&dt9~c;1GyXT{J+<~&%fV)z<&_CgFNg%;y>y?=0EN~;XjGJLZ0@Y!OjEE zVef$#{1^R~{FnV#{8#KcEs|q8;Os7JOUf)o*t>q z>~wZ|JA<9k&SYoCo>{Zn+3f6g4m+ov%g$}*!5&)k+4*fB?4;FYyKRr{Yq2SgUC=IM z7q*MoMeSmCal3?F()PDY*|yEG-&ScW?0GWK4zh#o5W6&XKUvl;hkY4Vz?*)DVmGMa zw%2y>&fk^n%61jIs$I>lZr8AD+O_Z&;C1Y}*j-_LyMf)%Ze%yMo7he5W_EMzp0XwO zd)V5Jup{j#yNw-fx3$~Z?d=%5gWb`Nwd1h6%6RO*u#265eHbR$$#z%l_qDs-1G`S` zh4+N-gZ)bO!)_V}*aLO{l0)rb_HcUyb_6>LyHXv4eW{MK$J-O^iS{IWvOUF~YEQGL z+cWH$_AGn0JqJ6t{Krng-eKos|Ede^MfPHQiM`ZbhBuI3VXw4T*{kg}*w^Jcd%eBE z-e_;KH``n6t@bv1yS>BSY45Uk$m>*z1?`P_5ZwG+kS1o;f@9N2m7P_$^LAA z!L9z0(c|;oWWdp=l(pw zym%A-{6U|f6?9=og`WR?LwT@Ruz0XUuw>9bSSn}-c~AspPz3{mfx#eo-}kgV7?uxK za64}d!`>UcK_{q#m4cOnRf1LB?hI=LYX)ltYX|ED>jvuu>jxVI8~*Rx#{YRsxa{Q+ zY>Pb}w*TMvp9j0}jp6dX@L;!Kcel%fydiv_X?r~!7#tKF92^oH8XOiJ9vl%I85|WH z9UQ~=iOZYBgOh@j-EI)4{qMWegDL;}hV$UE;PT)KdDnPwweH1meQ-l?BX(oBS@vTH zZo_^IcLa9^cLjH2Uxs^w`>;2|1HpsAL)g{ek>JtbvEcFGiQq}>WAJqF40dRE4!an< zfL$71a{DyA8oVYuGH|zs;GJM<@NV!P_B;3>_%Qe=_!#>JeHwfgd>(uee2Mpee~ov2 ze~Y(&e;@pSw}ZPkrLe%Y5HmVBFz%c8qOBZ z9?lWY8O{~X9nKTZ8_pNbANC1bVOQ84_Jn=Ie&GV)g5g5p!q|6d(QvVFaqOWa?>=8D zY=^nqeW}9x&=AWDcw~4K zc33(lJT^QoJU%=jJTW{eJUKiiJT*KmJUu)kJTp8Cdoi8k-VQ${JTE*yydbv8fDRR^2T|2ue!c}ezs`#XpU&kXs&4PXr5@^XufFvs87_2x}xr= zC+ZvZix!9$j24O(juwd)jTVa*kCup*jQU4QMeQh$il~gLXh1YD8WatVhD1w8%S6jY z%SFpaD?}?sL!)8Q@TfQHVE3w(u!q$ucyjK^*?yI{YX ziP*7bGWMz2E!rJB(d-%Rg?(xEaXZuO9~}@K7#)N+`yUb=8XXoL9vu-K866cJ9UT)L z8yy!NADs}L7@ZWI9Gw!K8l4uMjo3r^;9r7w*G%9^Db$8Qm4#9o-Y%8{HS(A3YE~ z7(Em{96b^}8a;-$;y)2R89fy}9X%608$B02AH5L07`+s|9K8~~8od_19=#F08NC&~ z9laAx#k=<3i{6huh(3%ya&O)L6z|>t9Pi`*GWrVd99xL4DpQdO!3U|Eb*-IZ1L>z9Pym- zT=CrTJlMT%zIgt)Puz;T;_kR7?i=@u7l;>(7m62-7l{{*7mF8^j7P=W#G~VFcwD?wJU-qz-X)$8PsAQtld*r*ZrEpPk9f~`uXyixAMCQVU%Wr|={hhz zC_XqoBt8^7PaPf~f!#Nb!p zZ;Nk_@4#MNcVU00du0Er`2P5T_(9p@DSjk=6gz)B9zPL3DZ55tH?L>o=i=w%7j##z zSK?RW*JP)I_|5pO`0e{8Rk1?12;i7XKdq5&s$gg`I0KsEB=PEcWFIlPHOk1Uvm?$#lu|$qdPi z$xO-2$t=mN$!y8&$sEa?$y~|Y$vnxt$$ZKDNuQ*ZbS2$MPtrH(mn?uiZx%`xP8Pxb zK8s;TrzNm6Qh)4<)W*I^1@}Hm2FgxL*zaj+?DOPydBXllD`M}YVc0jRS9Vvzu1eTH z2|FjPmaLwvk*t}lm8_ktldPMpm#m*`kZg$Ep*Bu7NjAkkRhuVUBwHq1C0i#Wl99=% zWSeAkvTd?mvVAfp*&*368Jmntc1p%4J14s&6OxI^q-1ikYqDFid$LEeXR=qacd}2i zZ?a#qf3vGaa!7J0_U<@5IU+d{J9!+9y*iFfj>DcEvSWwt*YV%GbzsL3>>rYxmz}(=d@k5?n~}Z z9!MTc9!efg9!VZe9!nlio=Bcdo=Toho=Kj?PF>Gq53U!n6W7bwQ40HHC9fxMU?;D) zu$R|6$yDs`^`6`H>qG4I^)Yq;`xN_qeV%+F`*9^-C*LIBCf_CBCqE=VCO;)VC%^o& zH&?SCSL&xW4bm`;(l|}hG|kfK(&^I~(izj4(wWm)(pl5l(%I8F(mB()(z(-l(s|SQ z()rUqX)EnYyVIVuZ`vyKGGOf}9>A-YQ zIyfEjf9!TNJnc<8X`QZ=uAHutu9~ivuAZ)uuG#E?Bzu&k8>Abi8>Jhko1~kjo28ql zTclg2Tcum4Bhr!SsC1ijbhGm8O^-{DPftisOixNrPEScsO;1ZtPtQot z#Ewm8r{|>SrvFK&r01pQrx&CbrWd6brleMz0tUK$;`eyyI z1+oRRg|daSMY2V+#j?e-C9);6{@GGlJIk{oE3+yakPXZRWrMRJ+0xlE*|OPk+49*6 z*^1fFY*;ot>&-e@ovoCuoUM|rnyr?to~@CsnXQ$rovo9to2{3vpKXwBm~E78oNbbA znr)VCo^6qBnQfJAosGywW}~ugveDVL*>>6X*_dpHY{zVDHZI#K8=vi*?UGH%CT5ee z$=R;iZrSeH9@(DRUfJH+KH0w6e%b!n0oj4sLD|9CA=#nXVcFr?5!sR1QQ6ViG1;-% zaoO?N3E7F+N!iKSDcPynY1!%78QGcHS=rgyIoY|{f3hjrdD;2d1=)q!McKvKCE2Cf zW!dG~71@>9RoT_qHQBYY*-N==Tc!UQrB`<9Ub2^UhFrKGaaGjb^M(%Q`9t0HaD2Fi z-Tm^;F#Y~8{r)ih{xJ9bb~%LUDF^dNc-iw})w%*RR_yU3uN+ed>12 zzYo*z57X}t({z@4onh`eFf;8?^)|}*)r0l>gZ2A^^?QT${)6@YgZ2J{_5OpK`@6p* zUA#|wIMbPTG+%1|z3SEct<)~GKkTmdY4dwU#r)3)(@s^+>y$&N?P-4>BVpQ8J!6?cXkImu~nWcFg&`JlA|xKQ9`470ka5zlZqHuR8o5!pzT3 zu69s=>CjK$ug!5y*KqaU;hIk@mw89?wWu^+^aJEO(^ocrr1!0J^(Vc5ovS}-JnCHi ziFV0rwM&)rdX<*bs;z#|%lo#Q`?DM&eJaGKIe}}qwLf!YE?!Hi$A5fQmsIxy*%SWa8#e73N7|(+7La|Z1 z^Ekrp`cUS7&TzX@zpq$NAxyujSYN>pnVxb8?NK)6Tm7XjG=F$qxUJ?-L4T+V&7XpP zQ5Ra?>9@!)rl)H1aR}2@57Fxmq2JZ47xH?DUU!IIcZgnhNON8HcbJ#yXbW>{QpC!}NF121Lwcf8w zO>e2`ZR%UrFL_V%PZQm3;A7*st z!O!Uz9rcS&rS&eKkGL)Ux@5V*bBgh)v>t0$^!Iiz>&Lu#F6#3puUPK$TI0p%Ti#*) z32lxy>E``%wR5G+80*7xKpbn%;bXmft4b9gTm7_AdCGM!BYa3atkUtv3p-2a3iYczxKz zou7AjeW=#MtY`52)4#L*ly?|ksPiMJ*3(+9J6fJQy)It(yMCYT(Y!OQeZ+W{tOrrg z@cdfyr)GVa*UT5F^KWQ#9{s3hKEqEKpIX~Bl|JvP=J}@gsaP(MpK3p*7mqdd!vUJE z0nFD*pZB>wU&^7*@A6vh-X1_dZ0mJ$ejnA1mcs$``$F5H1?wl+S^aQ;iwDALuK~_3 zMb7#KN;?(m5A6XZ%O$^u`?&8xd0sw{eo{2;AcphKK+V^In!bUWZvz>>iqCcUr7J&q zhtDb4#hu^I`F$wEMbo|-sPP}D@gL~ok#`2Ezp|fz>uNe!?%So>vps<2A|Kk67nZj+ z{Rq{*)`RpX+@JByd0&Jz{k$)#b-nK(maAOLRj%bP@74N&&!2X#*DtjFSv2iPejn*j zd$F7&tob%b)6Mz-ey#Rw>RtL5j;mcdYL{B`N88W+_y=G zhMRUH+g*7_%VVMSRymaUTQ=?Hp<0f4-@G2sgta^maDIUD#dOpi7e9n)@4BP@(D?r# zjsGAX&j;~3ogr#3_MhO->bKhN%!g^bhPZqyhU@dRJy6SYN1rcj|DwOkcy)$p`54A} zphLT%KcRlC?XfoNIegC@hibf<@->v{>d+pjx10Mio_R0JL&0_ezOV7oc(t4QbSU$! zU_8+rYT~W;ZJy^HjbBZBqank7WJkZR&zDNuiIqNQs^+=W)T4TSUeix1mMgSZoc}`I zaj5eXs7ptu&FezleWA{Opzi)qmu{$wC)DK&)Y%*A`~m9h26g@bb?JfX{n_3?Sntnv zC%W6tAE0`Fwl@&g`?EYEtoLVo0b#vApGyeq{k6T&Zt6vr&vrhzv8Pk?^BdLtV7nIG zCq1ueFKPKLv_5QiwEXM&Ir~+}U-fex7wPEnQv2UkGp?ldbk24$nxD>|P`$3!3kBP) zxUTxQj=yxKU0;v$I_P&Y-e}EGLYPNLsPyH%`b5nnzB3dXBl=zVh?&nPs% zispH(&*hx;5bm#bXMAvdwGWRYtajIab%*sUo4_CU=?)&t0QwKwYtTwn96sYkS5 zQ|NQQ-L$(pYF|EA@jWdU)4u2G0~}Yo>G)rv<)vu+oZ|<`U#&;jKf&`(^MlW$b}_Bo z^Z8sg?Zc)#u^vM{YQ3cG|APH~^q*)q3}m`;Q)>CCw0)R2<6{Fg9oqlSH6D0?$@kE| zQorQ=5Z3%?+G|aFNc&T{*8hcGw-`vf6?$Hw{lT2|BjTy$u&I}tej4L}`!(@!@kH2N zucPf}sMfFaGlZRgK(&5n{f4mC@3bGnTEDYhfUw$;ev2^kv(oX4c9V`lEDyy%=5xXJ zAL6Bc#rhEa92XDd5A&~G>-Prnc;5I~W6!4jsCLbpanfcSPW#~);9ol3YHUu)ozVnHT`p5KkxARP-iEU6PMpmwJ+NV=;!I**&aYx?V#g49k#P^ zT))ru4cf;py-@dkjNdUnCF@~4)?L1%-^cb`t@&B&czVrz#owKOL0J#h+HS2he~UpZ zCzbX;s;1u5{#2#y$hP($Dvt9bKX|65PpMIJ*{iCLy(e^qzhAy8m08IbW_H;+v(;eOq@pbmZ^)#Q^KJK)e zej@uF2)pY+wOz*c3Bp=#`CLF)%PpT92y403dcED)oBb5jqr7jf&!1fFkZXK$ZEqLN zbDClFS2bO{FT(0idY`JX3+JWKPt<#BH*)p2ys5XE@gf~(%A4^e?LX(*Z!WYwRW$7)ejo8y{~o0FrC;K>*6(bGpnU4z zo97Vg85~!8G|zFZSKHcuEOcC_)AY-lepYk8!HqpwKJq3$oCiaHSN(wbgs|4PI$qgs z`r|rp*Wr8}rnPw8Qrm0orhIGtl5>6%&vp7|Tl+IP;|0HM?(6=J$FJU(^)dRnYPVYL z(u_OlbEeYrThT5U4rF<)v_3DiKU``1rP6jm)zteOU%)W5w%eO_m)1K4`?nbH(C0PF zF~Y9iKztj&)O0lMJDrDZ>pXF(^F*cgkIQM}z^s2tt=CHJXO^4~M)!-)t5W;1wfb$@ z%#UjOv!?%JTE@i_O24Xgyr|alRO`4=t>vm_dB|&>cdncJH1iP6ID*bY*V@0X*>1)6 zm~VAc4%uICm-?J6wO?B{8kev;L!epA@uXV&%eBsr*X-xv@6PT}mbY5ld9{v<)mmTFIzCowy;18pS+`+VdG1>Kx3&6H*{rW<;??*+=cO>e$$FsH`PEwc zYqid^*4l5Yb-uOM{#&i%j9zKwYn@lG zIc|XYRF|Kyr`9Lzmt&Ym`$^jWESvdPoxiBHy;|$IWUcje&HfCACs;q%I!{$=|E1P( z+*;=|YHh#PIBf1i2TEP%Q|kJN zQriQi&KH&1uC8?+qSkhI&3+WdNm-BAIzLfsd$iW^Swi|t~aXre8F+`CvA7vy8fuvanqX5M|2a^9@FN1)bIE_#<+&ozdFuP(N8ekr^od< zSm|@CYUV|?eOPIGuG0Q@#qq~>#dZd+qxG=nYo+UeD(&xA+K;Pr-n3FX4`}LH?O#_q zUR>!sX{FEEO2^YG&5ugw_Dic~JXY(YO8cpmwzDf8cdN9XsI;6_+K;W+ zUq!n~pI^;7rd`$8Rr?{8j>A{luc>t0ztVP9rR|PN=Nl@WPpWj>tJ3r>*Nf+G>}!+9_v!gy9^QA5hnh#Q3922g0u126godj=TB>VRs*d zoAdR&X8e+N#Ch)iIM4YP!mi#&SijHsBitN!^#sDM{Q-69fHv{dc=36P>ow=oFLj(8 z%Wh}~9VbUSknIQ^FUQ~2F3q@-K9_ae9DirMs^jJeyY!*I=B@*E@qoJYLtQ+eF5VsO z2SZ&vpe}t-7f-0WKh&iU>g)q`_k}upL-oF_mk`$bH~r40Kic$r*+0OtHP-(q$jpB} z$1u%7|D0xj^;=8;%Xx*4*F*U{X>%L_-(x=O`Y-}t;oGeiBB{Sqwybn!#F)DKy15iiXz_OB3j{tDInVm}UH^%wSE5LSQT z^BU6;S}r*MjQF_x$8we?9ZmY1cxya3&W-c6Jg_~DeADw-ZzHVbkBSB`C8JC5f80T^f@tY zK4e-sR(o(f3d?bsu4eqb@z-WPK-U9dy2jZ9;~i>8ecmCB=hty2*wvK}sMcfJ-o=E6lv8}qr5DQt zU4B4^M3Zy0dRdHh+6Sp+_Up1mgy~*bj^s`ssNyb)!4<7ESlJ@n+)N9hc52#eG@}q` zrK%4FU5r>D)~+(qK{kXLD;+#VCb;Y3IGt~RHcoqWFtS$%fqQk*rb6 za~|!g4+)&7l|xgxu+f0?n|yWohdCW*Hyl?dZXW8|Dd}Zn6_fnU^|VrC;lTIx{wy2_ zGykU*675v=vhjnzH}`ed%X_u)+uID1HV^-%@?idUnnJ1%H>|;=H@ErlKv?a{hX=xH zS3W!tR=cvYMp*62N*Q6bEBy!eYp$nuWo3@@)J|;N;5;oKEEfp7`#`nQVE!YlcBg+J z++2qbS6xhw<4ruY@?fV4YsodA*f~Pj#Sf~LD?3LByZnJ_JlF_E*rgY$@n|OPI7xux zT6wWijj-OIojuHH>;2i;Ls;+6K_G~uXf&x z#(uhpq|n7zg$`1qV7f+Lse{4lcesvA55@|eozOOBd`cbsDs{1Zse@{z4px=AD7Mta z#H9}EmCa%a9i%FC(PXKMXG&d6Q0gFAse^Z=HWo`Alq^*uT1stP zl-l_$b#aY89AUTSA?f@Cj}uj<3%}W6KIvk7gqJ%kTW383535Bb=+=n(_Z~o3y4q_9hz~j@Ub`XbTalU#nYgvS8jhs&jJfw$CFI@t} zy8oyV6GyuMqV59IG5-Jj0g3o0fRy|bKyv;GAWbg7@Bix$NZr5B8uCvdA5#4jKw|z0 zAVvQKkSrHq8nt;ph{MYmJ#-}sN?Q(KjfyaHY=G7n1GL5;z?v3+r)>wSrww3@h*3z^ z6a$*$+MOMs-PmE;tr({97{HQ^SyXz?fTm_vPaVi^8Ct9KkbxW~!YUKy$slds4pfgF zs2)44sd+dYg$5YYQ)>4?OD0w=^Ez6hic+71r9RPewS#t(v09Po(-Mg=?NDm^by%y= zWq?pE3EC3Nb$CFhi4ZUPUd|dCzNj@WYj||CSaOGPnL1|O^@*tG=`cQKhZzqY&PSNO zuEY8WGu}GPjxeuNY0Y10cec{}uQZ=4K2fnsnCYnaL`9f()8QwC_5OUKBCPl46BS{- zKcA=w>;3seMVRT-<*678|KRU>e-3&htoLWX24TIwE*{eM z5svHq*$zTj@2{QFTo);z-|5;PZCxam>tg*}2YdCohs6XgeHe^$-h{CVmkxy8{n6KD zyQle4o<8C5;}<=@Sq!0rxw#H%=FMUgU6hw=yDDdGkNdjxVk||k-wblIlZ{0@t`uOb zl<6pR5llxXQ9GQJz@#q-~7R_fQ44jxtN-=S&pjD;gP5N}kn5wJCpe%>_^hKEE ztkV8YrTwEy7hzPosI}5Xew8i`taMRerRBELa#-o&?W$S4rv6gt;;Ksfefm^}J!pS@ z>LN^kYQ~EyU1U`0BBM&jp(^$BO3PiPi&87~ze@eO(tcj0exj`uq+9bv7e7|o@2m7j zH72mHJbq^TaCK-55Ku`egbv( zg}QnJ>f!};f#A?^*2=SPrtxgIAf$i`UGJ~snz{%J$Mya?&Zs{|!g0Mn$8Av$yLtku_t)`1{qYEnYr1vZFW25q z-i-V4xqxvT#zTM9g7Iu#SASfLVQi*De++~389)7TD#Emf{>T{LXFPQ=H^w6vKV8I) zuwIYtFoc;ex_BF5=7%ocMp*C1`Uqj>hc0f#WRHHI?Jb1$e%kBBgWmOqu`1ZmLZk}t?#!;Ga^k#ftdt2>hJe%zljDOJ2bP*PQ`=@@yrd=^?B3$0fQU2In9;{2v8iqZPNXcld2IWC$->bfYfXciCZbE0UT ztNOesns%X<_d@H%LKm?Wy4a!6MJk2XYemy8)OxMZMRA4J!-dwvMKgYlDSL&ia9i_l< zD&t$~BEVAn0j0JtOZ^dVsqL9k+cTw3K9{;^Pe&~<=c&&VofIy0F+iz{S4y4yFLm<2 z)I}?$PJWi!jx4oZQ0gRPsf$rcokTBn(MhR`;!EwHl)A{Y)c!=Nli;N;A}Mu}vDC@; zQWuMqI-geRB9c<)x60Awg&f6aSY4xoBpU5wVNKZ5PmAHVkMqU&D$QE{&> zzV2mxgh4629_tVMhE%Voi>7*Y(NwQ4n(Af!f%7ze+F$5peSqVv7j>Qkqgs5f>bwQs zHK5NQ-VbZcoc*BgzEEdBsIwo`JwKq%eo$BcL!JGg?s`yXKd5`IL!BL=?*33`NAxS) z_o43lQ1^YPmPhSBmAWXq)WuSz_K$1U7g*HEe5p0vwdPaJb{G~Zx_ScbB2~?|TH7_X z=0mOdP^VA?o(W<4tIoe5Oux_{`65ie$oafNn0}!@*6rvx6;%C#&o6}a{(OER ztoP^h3t^U*oTKarJ3B-5{%m(4toP@03}L-L+hZ7=b>$tZ_h)%WSntpBjX2qjs8J!jr~~-DWfbvyB=* zZrq3wqehM1Y0`u-+igDy$7UL}$AmFs$Br2_X~dYZ2+uezJZ2Pvv+p>5!uXxWOyrlw zj^0+jlc9v7B{H=C>GAKJW={OuV|wuK zqGnP2JHQOUzssBD@$V!v3IFbG_QtaJHxP-CN z5_{<>YZ!oTw09JP(be>5YfA6Q@;bm+c;as3I&mkXx$H8oWt@pY#tz0O(SLCtb0NnGK&|{Ygi&jPPMPXG|F!@A zH@|)2JAZps-@4QDMwI`KdSKLPb?->-wO_x}_Ox}~yt-@ZzL7s|{D#Wky4&)nrGD!* zVY?>do8*ld-X?YV-sbDg7{7k}`g)Jm+oZSD^M|z0pEjV@A@=z7d)v3|jO4fB>iKyM zw=~=`KQHmsa}A3dWi?veXjkL0CEp!QCXHLxWJmd


b6j$2m#p6~C!rY9!9d*3s^ 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 QYnf3{HI-Ghx~bCt4-ah3x&QzG diff --git a/lawnchair/res/font/inter_medium.ttf b/lawnchair/res/font/inter_medium.ttf deleted file mode 100644 index 721147d8311f1bed1bab5e39c18254beedcaf0ab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 292140 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%0DAB`*{C?!~6Pr26rI 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%0A^c* zvIDk-PI^oq>a2gH33p3BC0=O<=$=Py*3r+$gHIE$ucaqHjvugPQ1;-h1LL2vzAGm! zreGVsYH|Ba< zS$WUBnUqSuB@vI-%f?18^t3D5b7|Drl7m(Cq`V2k!`;lAciJ zF5P%~&=Df9==Yek-*=R5&MuSqIDXL9VEWegj{#GjfhDCthW7>mO0k>Q?&@VfKjT6Y z;-?pt5!fU0;sgjr{lq97sEOBORlkt{wUy1d{zI!!0`iwO3xb6GA!P;;k z@T{^E|fEAcNSM%E0*;uim`c?A?*@nNR z(C?%lmk{FKW?Z-KajjdpJGfPIXt%y|;*{!3=!bU{zC~;wF{5a_A)r5qMY9!229tbR<_2 zvfl}Kr-V4OmC>Z2IfkaXS?uLkO`J`d)XS!eR#B&ZFOK%r zva*Z48|kxY)E~zP{p91_*}sOqoxxRlICN2GT><%24o)Ug0%Xc9j5GGFMvW)#$Y1%9v=5Uc9GN=~|)N5I8(H%V>A;Ga zsu)d*6)v(cMp*i3s+=ME8FpiRF2fbXY@rN01iCXSGuyTmmQhKU1|jo}ep!y2p|*Lz&8RrUT9%1r;Tu-cC7DKow;pK_~cz}PN}!m1^$ z@9Mg=6N`Ml1O}$lv8kAEXo2PW_lrIKG z5rob7g?-vWuD2Hl=^}hb3<((5+;i%knDLL25+97Gi<#e5xkb0&g=1o7-;TJuc=e;G zpo3$TrlcRn{Ry`4hiqPoH~5y$zgsSmo!EEi&wFZ42<$x_;5ustCk0hFDZt_10k6Qz z-!Ye$Bpgv)Q0RKfKLsk=31`bJV`^~6bgNxcp9<@TBB?K*!z zjEE1*1rKdXmdEBzn>PRY_dNZSI3Or!Kq4uEX%908c{`eSk}zC+x?2goF|0ttvLvEy zi-_3!J6lC~3Z`XutxVNoEisLOtF|yuVbAf3T05|dZm##ybgumfn=yf&YxwyJJ^%fq^rAfUFijlB_JE z>x65k$qQv^?bE_pI>{&tOK_!HFykwq)NFQ(9kM}oU!|Dv7DlyeVbP1{l;}6}Q)~rc zjB!XvB254H&+1t-(o*9aZCT#T^;6JcEpa(1oTn3wI$<%rYILUO5x_(d)ADXkTa5Qb zt62&m@I3lr2bs1U_9t*oh*d>3_MED=Xos<7Y^TLpn+y{?)nU(d*ta)yCmBM#g+JC1 z`A`7ezW1ood-NaPL{bc=;mW2X8Ku-e3l}b)O-?EGBn{n7{>J;j3eD$y*h+25-xuaJ zT0Bkrn!YF2hNm#&?j-Fp+)k8s8NCd*VJfC$2Ji(kI7M_^UF`(Hw1zq(n4F8{m>W^m zIQSoU>g=*Ex?`lPn$TUpmZQ6EPnyza{}CX23dnXQBb5f{Y1T9k%ZRJbxr?rB_b3_4jNaSSccGUnn8+{g%U4 zd)>QuX3MKLUupDC8l$@z`*F*{$FWEMm=L{A387z&hiK-sZcROW8e3g(n3yv1#J(PL z`fl1RFMa&D2%SV?u`)R$`inE1KRE+wg$bX`7?T9<2%Zrtuj32^4|)sxGDohe<5h8o zK9c2UPwY9vGch=egY>h;8TFL1NSK`c4f$iK z)jyuSz7S`r3=Nq)IO9?oY75hMC&C#jp2s%IX6b zax|}J5*TqM%T9pf(H;8HSA&q%IrRf>s=u$$jl)yGm;+dT6FvHBLRGo~{ui&u+EmXX z1}B!N)SAO_iOK7K#q&R*R{x;)3!>ul8jJf6i|>+?R2T(-4>?7^;aGn;<98yA@#6}E z*uz^1WHZGmC*pABG>x~pHx z^cklI4LVKw(p?|v*Lx)93mNm7#QwR4UbzihjfE5z>=RZ~x=1M~Kpe{9|FZ5SryfqW z(n>v>WjleDEcYzTXa}hF796oVNe6hJeKlvyxdCUn8C;fo8_c;gIP{QO#~`+`orBK4 z6DR=5C!dYYbfcpFIY%7l298`!9A-v@tRN1v4Xb)Cimuk`e!{Y^(*+yxozZ6gq6G`t z--Qd*zXvnNRQGoG_aBo9?!7wu3F5O9!za#8`B_q{%GWu%9_=sJ&+NvRu~WIW!ChWW zAC=xXdqQepK*}WVsr?~xl-NMOZ@Ev8ZQV|q-bp5fwq{>ZuzGtQ zDIrzNrxoLCdP^Ac<_#I1XKUg!-V-fz4y_xElWsA{Xk{RxII)eYuFC)MRtI`%)!iQJ z*1xu?PdL={z`N(P)1>TTn!0!ubz8YYs$~3~#E?J8K;`ZMdYf#oeL%T|Q^(<#qyz5O z>9@aLzPvPV)-~z%+qcGhVkPFP)p%`$xL&ED5Fs18(?M{Jnw$zcetFCEfzIlVt?o{_ zvBO2#zp;}qXC)?KKoubAiBNXO&X6)HlFi^u#)Rbt5@f=%)6NMW?e()@ zwas&vE~CTtUnd@9&`SDdZ_D7Gt^FLs{da^5^j3>`8&Y>(@_Y67S2`LylJ=@^%9N-r zzT3jRy9Cn~C-)?s4N>=_6HXKMMW${W)^p%M?SaRSi?Z#gC}ROixb7uafz9VjA8JS0 zpD*$koF#-Zdv7q5IgEj@BPVAdU~4#XmO}Y3Y{nJv*1<`_#0BRIB4E#ePXhc5g9}(D z9KM9ZBdMi!BCqE>gIl~1zGY`2e;4JXb{WT`k&1`uyOZim0yp|$Ms`E&p)As^Y&^)30c zR<`M=we}@rdG4_6C)x@uH}5raC-b&AJ7qLd*{P34ty^WYEVs%|`&MDOCuKj8OS4xZ zBE&B9cUC9+&M>H|LesS|yrl;!^sc$3B^=6ng}jy=N-h5}MlXmb?c-Gh@u^YSg9rOx4I0AR)8EEI*8} z#VuDbJocbFq#5ksC${Eh4w7aHvK}_ctIR>3C8M7%4BvpXa{1I?kGP1amSwnP>>WABzyCD2c|~&?!XU3McIMK!CJZi`L@bC z8i;%4Od9z9J%=CL_?zn>Q0rY@Y9xbX_$F1^g(+Unx~`bQZXVQ@uDYj>n!{GT>4yo(CYb zge<*r>GHd4Gumm&+iiX$?h}{P_oVe+8@jtlw_VFO-3uPLX9T%DdnX~$&y{pyA(pD` zRNJ*!V)ZE(Ntwe-E|RWaChex@PQ(B58v5}6VeLP_qB`35aeU{T-Lp$kK}76|l(irV zND+}Df;5rdyMRh8h=|y+_l^w}OYFTzjWPD#HO834ZlbohjAY3AKx@#X#kVwpNEz0%)Q z8Ajqqs1%-Ggj?9wZ3I0{pJrq$f3g(~)%BPAIkz*7$aN*;y&V_s92j-sj9e!#IY zol1PUpGcXuynDJuP=*V~y@$gOFY7CAQc{cwLu)~&7y#E3j72L16|zQzAzMq#2upWq ziU=Iq0|*L2G4G@Y8;pBMA|8@3a`O3W>PwG5A{`d5p_hN&QC03I_nM?VwOsBqSo?## zaxLlj=Nx)jyiTXgr&DnB#AV|4D_KXH14%)4|2mUW>ctIwC@!F5x&F_@m8jD~suHe9 z>qI(qGyp>ZPc7brNBlca@`&yh>gdi>bvF{nWs$SuSG!1_nE8`I!K3R=El4z3%ANk; zL{;aBJHP8S=HPc&PhN~q=Wk%~6WKiQ#aGbm)@<)$1|zhxGiO-q)!sL(2Vj=9=jM?Z zi_|^kAO6U=zNO+|Mt!=|>JW*3UquF-y+~(^ujyA`aSO^0=aMf-=Pk#H%g&7b6HXGh z-7jbYeYk->qzNx}6L-|2TD@YFPH?=$K0X8K@TF0fGHFH@%}5i{6Q%UWs5O8CR>L@7FjBbB$QDLW`p2K5*Z9_&7m9{d z)V-miVbIvqVm?>FJ{bQK7@Pz!XpBxaMAHyYj^Jqpix_3_8kEc-@{4@ibK0e(JHKYh zHlXUdbZA)D!8v}CB4UzeAA>|S`}w`RvG&)Z-pbd_>SEBVu`AM`8%(AD6}w`xbM*g& zt_)uIugDbxAdeyF%_MG4!mVQQLlJ0&S@lmyIivlSA?>*@5~E|2Q(~eMPy7pNuFJCd zpCI?#qrmj^z#i!tVo>cpsB{mY^{&Ma$^(am&HfLIA^xcoz+uLI0tHHx_wNuM2k1Qw z&Jqm-z|{=l|Y2Pt$v_3J0?DmPU#v5to*UIW@bK4eOw4FE}U* znf)He4M_`>Z4UfZ`py3D>G55J>>_^Oe^2~&?V`uOpMMBv5G5U&k89GQXt9W!Q+ST# zy?jOT&Yq>qU%jNu&k@3#`112!qIPGf|kHqFP zRv+sJ-kIOZ>+A0G6}o)hmMt2^wW_zUq4dz}Ik-bCGYMNmIZTq~G7E-v zigl37uq`MoI1Ut)s9BuHNkmPKaA72FF1_9RB;6#w<0orRmC3u1z0yT_Zp}BekvLoT z3$<89mT^nD7UXL(RGcp^c*G6R9g!}0)g19BC?~&(H-@Y04o}1(H ztlezv`?ejlVQ?3?o5hg^6Yd`g8+l^a*Clh>77n?)>q0NUgYvvCJzDGaE=?l5<5i~0<`Rqb+64X-lTgE(ss4=c%(Xrw!U zq})D08dR*I*KR+gmnyfD=(p2vl;nQ5E$ydiaeX%V_s>b_SG;*Z=cJE`IqUj_E+0K+ zb>t3(yqrG0aGqY-c9-lV0T~c5-;f5Wn}?_GC~E2cxJ~O=C$H49hxGf+If(BNw{g^% zqXh*=#~fq_nD0f+^ReZ*pxzSh6UIY#$!=xvx|Xnzm@7u}jiHB`V+}Ls2+4JVHbi}- zSBc`}D<1to=iO%@N78|wesN-nru+cuvvmty`PBl= zvJ>ps#G4EpIUYFFFCUUT)O9U(>Jn+9S~Dv-+jZ6a=W`K0I8eWWfs{Iu0gOnTJe_!I zSFYmN^lNC>x81of? z((x(#E_n=)?qVA)6;F~-2DS}5N$JGfqg{u7KZEQ0j;Vali^l|4#`4MGCxvd?0a*yW z3_XW2s4Fu-*fOLG!AEXR&J$a4$~|!wX^}vWC`!RmERg#$U^Sz$?5gR6VkIv`RXN1l zRw!0!GK!ot3baoYE2VVkL!@-baE59qE=uVU#&mt-bzZ(L%Lux>IdZ3k4Nf2 zlOmMPQk5p9^p$KAN|D58mYXNWQlL;^r2`PKMJk1Hgse7DT~wz_ak(Lr9W zG(~yF=LA8OfO0rlA&AUz$Wu-XUlV|1@|?M91GAW1L>A}W8 zF@EaT>)uXbm_dFQ!wdp-I&1Qju>rFN1G3+A3H{I4GZCru+}y$CkR7<#@`bu4KqM_l z%xP;saPyc?fI-jD&J8_pd;`x3&uOFHl}&ZY7rvFORDukQw4$lR`*>i@A=A7K`64=GqCKi#g{-;$_ZlnRpr5 z#_|2+b4?6bNhq!ndDS3uGx#7@8OK7gW92dCiY+?u3bx#|Ils-B{c`U7mosPnXa3}2 zBPL8J9ZU4OE4(Y;Zvk=o<=`*Gan>w)7hmYD1y3&A{&45mu{$5`o*|o_U|u}LWTuqXnuQ{Wzy_=^`$}?>do86yai=UnxijCbl;2kRjn&LqsAA*-x8_gw{FmtOyZG(Mtzqxs)^OiQ z`H&9GUv;|Lkk0NaAPE|tQ*ElbujDI@&jn%GJP^>|eC0cqFA(KyW%>BZKc(^&bEM}n zE@sw-bk>jD8UYGUR=(;(_5C_G{&pYv*nO7I45~+CJ@RYSW2>y_hvaPAN@h?Yu&>n-d>E3PFm0QayCc)9 z;2y&sNZS08%YC^Tl!?;pL8q2N0H_@UgH;<^8|;fz9GFXkaW8H zFxAW`Bjz_j{LJC16k2eZblgZRo&(1HbN*erMl;C>a&dZ2wi3JiA4!I05`D9X-g;DU ziC*4@dhfwjv(;E{FoM~(p2m9HGsG0?&BHZMkTYrwG9Vd{C36VSR5BKnNY0znR5<}- zKmw-IKh98Jazd&$Art0rUDxPI`bWTCs+k8BS}6vwb4^7blkE`oyh>~Q-IvGwe%-GH9 z%IxBXx?+20icWBctm~~=OAS4*X1p$#UxW!@xEuqofp}>vKax%)U>5!J6wYpg$A)1z zw%uAuk35_F#e%Re7cE__A?~v&-3k21Tl$n{J)ZD88=Ww~*&os4(@4Xc#N2w;E-KE( zHYRRdwIq9X`1-Yz4IS`=_^kV{9dL^NE_DE%SZf4d(@I)d@B=-zbIv>>>=mD_IjDSWWy-nWhE*A4cPpx1gb z^!ypR=v@`vbAX#V>2MBEILCKhA#PiqoIXw5_q?D<^zjZTvXY+dAuia0{-730HD1aN z29gEXuTVmG~XcGA(lTj=F0JCD-~`-6x6s+}$$ zj@i5mt8y16iCn_DAOwt+odHfl?5qe`!8b{Nr&}OjO~1iGk&8a0@{TyidlkLIua|fH z@Lp-EJ*UehZTM_FkCKEPD2c~!} z$>(!Wg2mbGB0;Ok;j6@M*+TludFn#05=*l5;RBk5?hGcbnTrd&(szx*F-_U~h~+X& zqo3)HW&Saa@#(8J-n@9`9$V6v_LJVf7W_t{cAqvZA7qMuPI{bl>A$74*M`#xrxw%u zH$v%Ex(5T-s)twmq=@5RZ)v~r0JfMrAju1)mE~ zX(j1{URv_-5zVHLXT?%CQU#;Hx5|72&XoBnuR(h_31kC^`_ zeqMoh+Wtw8|GAI3k)L%Zz3>lRPLCKCE3qS6pP$}Mcl}oIE8V^S5*s;gWaNsW?YAC2 z-Xg@ey;qM`)FgV%>&nm@#CGv1r@q_A_gZl(4GNYEvVF<~B@>1}aiz>2#nzVrsDydO zvvV)NnL98Q{QvmP-_yP18|)D8_UGEueoN^9&T)|V9c;iz-7fx6ld2wAE46@YNBb?K zUvNmhP2McQnHcQ`h~KT}&2?`#u&=k}2~y%f@z#3YLic6^*B&{=9siIML}IpBM}AH} z0;|geM1qHY+KQ;%pq%~y?bGi47pOktYnT$|)6v`jv7AhLCYIx=Vli62l=qan0xKG> zsc5!*sSY6iv@|+l8(G4g)XXVX6lt1p=}X1oAb}Y|it1|!Ak0kGN)E)Sy*;)vxsB4% zUOt2FqoK1`^e3&ze9R3ivYt4^O`c0#lwM^i#f3Zb2JGrr8aD%_T;?`$zQ8q_fqk!~ zs=b_*oS2=Rn3T=Ohvemjgy!ag9ryD#A4P#l4m&_wmS~a#8FP

EgldKWv84`FE}6|o^51~BB|TA!2q;5^Gy=j2^A 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 GrT-t|qF|Z; diff --git a/lawnchair/res/font/inter_regular.ttf b/lawnchair/res/font/inter_regular.ttf deleted file mode 100644 index 96fd6a12d0e20d7010b196e425ca48b0b5286df2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 287928 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

Qe?PT{|VxcO4J23`XEyBR&d89m>IY`5{-wz+U6 z;C>tN+J<~@A+EO&*INkdmS*@npp!-DWDz=9giaQrlSNzME+MuLg+?KEY=`@RwCx~m zJ3fW~2yyEm7!A{4F>Hogg(x-wpSKa0+p^#@A#T3}4hXRm{n+`m5G9nE64F#cno7{) z5_GL(2cT;u<Za0xg}CDa2!~mK?C!7&v1=(HlU>D73AI34b{z+Pztb0Z=gxS@gnTH4 z!%z>c&?Q8v2?8Jzk|7%kVJ|!e$iB2qh~4PpZuD(8^4yI)cRvE?>s=PO2&TXSxEgK& z<a8JExeNK+O&Pr#eZ3n$@21?|eNu?BjnE8#2l9T;PzVFkxChzq$pgyC9&}<4Wn<5q zfKKe8Z0sd2dzS$DzV|jjo_q0Y?{OjSA^+~V8%SHZ0Y*Rs%z;%vo|b>l@Hq)KK!p(d zcxNBFwvYVX*CfP!=*NA8SwVhQ5KaZ*R1i+ZL3kcMfNzD^Pulkf0q*wWZvPe_-TRNi zdqCRn_XA{e|8lrah~G=t1f@V;RE`9mSKbTg!vS>gKn@f^8N4mT1NiX(-ydic;vn%q z_$d5Kh^hj35>5&6U@Gw5gYOD)2wgl>0(D>);vwYuP#%z%hZBMLJxqQ-%=3p|fcN2_ zLOepe9=#0S5uzIZs%=mu#2?OsXM}icCgA^LUxJ4D0lj>D0wCMRk?rHe?eV+d5qKH? z3b?Bo1=C>_Y=Z~jW%vlb6XJ=%Fc$FV3EVt^n<JsHOo*dqAPz@K%dretFT|5ZAdIIr z!=3ODya?pWQ{M>jG`~NMT%RT{o<_f(?iHeTD4<`p=vOWN)RNZPU2q7HK`k<<?H1x0 z;_^&5%m?K8%<VuvJ@XoLz>h*a>kGJf7B|nnC&Y7B2!tp|fwe#yp4$gU;7#}|@NOOc z*NuhguoSKZ!l-*di04PZheEtS9r41$@Dix+=-P|dK#vghaezGPpM_>X=j+Mimx$9# z#ObAY$b@_-1>*ivJ+wlX5HFh`03zXaAzmTAukhKh9(KU}@D%(J{tmrDygC%ZU>0P+ zjexsXUw}`bSBTd}z*NY98(}Zt&ujSe+CMQ<@%(j7h(^-UxEsi?#!rQKV-gV0H;Ct( z<6$M-45aTZWcpS#6aoG;`2c#+gbbQ)h2KL9bPDly7?c3AdAkvK_DAynonbHzR>Qq; z9FSKt@oC-#?+Wp!;Xpq8iG29eXF|M-Y~H0@{h9FoOkL7~F0~-fmOl&e-bAPo;{9YO z7vh6phyvo%N?KZv2=Sp6@aw}9LVQHI{|MdvOA@>Ry+V9^0Z^ZOOjw`9Lo+5kUwB1` z_QCK6AwF9G9YXxI68P*G0;IPCKR-`_wa_QT-$~!!i{S|&zQB(!!hvv(d%!j!{$Yk{ zAnsrC{#Td7t8h|?PW<S6P>8Ps0l9sB4AAqh-v{#k>+gm5rvZk-#X#EsISUp+Hsk|g z|Fawp3-QeccpT6tE+i6NBY=2y5uYy7`fZ*N-+2S^`R-pr{0q7LD--Sp>X(0gB}Df| zK+nG?PTv#W_wle4a^OZN1<K0z-(m867APP8=@#OIKOp}TSHMcR9`*q7Iq@POw;z&$ zH2<(2-V)-+^MJPNN9w#EXTuV>8g76);Q$;3<n`mX>~<zSC(*N$2|ySpx4>?oT{wxp zoO~C)6ru;2_e_BKum*Mj^6fzndhq8I<?mE1q(Ls+4y5VS5qKH!=hR<;boC;~UgY14 z9D0#=FX`_^*1aEb(ZC>>2Rngy_7S#yFoZ%nARl`dD+|aU$9JqJ5P)A=FhoHjWB_r{ zwm}&jf?9YJ+So}b;dd|#kiYX8A%)s}?hjuGDL)p{@Mj^7JD@{I)7{W5q(>HfDI_yc z()^x~*lnfdN%%%c>!q+6>V)+CgOFY@h{Ngz?S&VF^gbUrZ`k{5A<v71H-#KL8TfsO z1pFLQE2PgXV2icSt3sZC0dO<Q`2|3{hK9hMz+AwvT&Q845YK#h7kj7l<=9ofi+~j( zeoT}2eJJDw<AHcya74)A5pXShDx`lllnVJfj$Ha3VT~XTBM5uMRZs%>H{u=mUdWNG zO&v*GMux&vNQPCA2gqn-IXnWSedHg3c#Ql`NK`@w4258b0#3;a$N=IVuno%K5RmQw z<Pp#Y{{*{`7Y>F%m<aK(6xd6C;f+ANFMI%w!E4Y8<jaLULXJY_qlniio?jFPHv=*c z+yHxo48p%4;u(Z&g1&-NLS8%wE(G%M;+e1*ki*5-!yWK@cmiI5_kg^(_y-|JTi|yv z4mkE<^a5B7=)vgQ;67-8PXrU65DNvc3o3y;3Fi0U_u)AFDC8I`jDT=J&SQ|(7}7hY z2ATnP>`RwpgCQQWg$xN3GIX|(myl;+=t9_TAiiOLf^UT!=MBVfTqfiK=^sZL!Y2TE z5`F`a&hTf29FN?`PlNqHS|^ac3FzsBxj@<`6an#`K)fdq?@Nsk3HW;{d3xy|;6)(p zOaCTh1Yt*vf+cV@+z&O-2Hy*L8ELuf0to%Tzzg_6oGwF$F6$L?q8D5Y=-5P_Pcpzn zAYUid1MVXIVGitqcR>?!GWj`q1(ZOGkWsjaLPk-a2ss5^nKB)22V@(Ku0*3N(fApS z45QJN=qCVOiT<~cF~}#z4?-XYl3*ol1oQ*jqKx?*`h=WHI8!G9`82f{j>5-6P9qJ| zZWJ;We`3+m*mNMRmxsdxKv>g};q-aXCFBgsL);!AXY%{Z?V$c{7IM~QkOAo4EYcZI z-p5l`;va#}giJUe4huQw5_nw5xyWnoDo}raBxK?UNCo1Si2M@&Bjmg=$b$QUbj%+M z(J)^~>|Qd7@RN{9(lhX9_*%$hWRlEp$x~n<Yyiqj@<DhWJ^*BUg$06O8lXQ{AhRow z@f8ojOYj%?w~#4=U<}NF<**rc!=vyjd<s7ZxnKy8wgt0cC6H$e_5kTy@CN)1P6?Sh z493G;SPeJ9y>JxXhA+S&<ig=_86*KZu<#bx4^P9N;48u1S1=ML!va_bx55E<4&H-r zgk0<a7eNdxhHGFaJP0qshwv{U(>!4`Tn@|NI=B-ahF9QY_>YiF&Vvw`2`gX=+zo$# z*WoibDdf`gVH_mDm2d;x15dzP@HzAexy%nPg?W$#H^T=&+?Vlt`XJ!>a?-mTKbKRU zmQ(IBNK?j3K>Szu0Ofo|9H7IQUO=2O9~E*X>0h}Xz7TTNR_GS;%GpBNaAPX~>Kxmf zLav?zl;t(afIPCsz!gBflzj=@27edwsxX)e9YU@pPp(EDS3fOe&II85I?}T4c_G(R zw$@W8tS8*{$ZZ4Z*zlN;xg+5zcumM_aC;5Ut{nwY@Q{!jhXU!_NLU+th0G&OdDI`* z&4rJJ+*Aq<A+blv&3_RxpS0({EaVpAwB=bLx03$rk=6CcxS&zU8%W!YAut8-<Hj-} zZ<+zO!#;RN$U@Rth#YT51~+#Jxor)60^bXH3-Y*ydgGR6_zIjt79snhY#?6S#{utc zC*QWWL8p*A1o#7avLhO(4|Wi*9eIE}cYH16t;F%xD)>Rj;;R9FZ$mD(Ujz7c`@Qf4 zyaAs9?sg*UofpDnAkI5i!WP&K55r6FA$%)ji3i}e1h*x)Ex8Kb2jp7PBjg>(?T)b! z3y%u9D*}+ou2VwZiEK-!0qNgufDtee<^!_XeIx9GL+}FN@9uv>uaI})?_H$(uIaE8 za-k6RLN$<AcYOrk33;~}u7&%6ye(S~__2pP--FKVErCzr8zJw>f}7wTKu_*L=kCEx zId03hLz|HIlE3%j&%PY^hmiMefG6REkQK;kKQh@*`1db%Epzz&4k0UN!-IgkO5}n~ zMIIpj2k`d*@jpQP4j`8U55TiPo<BgmAGiQ`=b#sC0^(G~v#PN`+N)N<R^Z(#-mQ8W zz64Fk2L}W09-IS9VJ+bH!P{XU909_4@Jskn$V0e4Gy!mX=vvqYxI6SLya^uxZV&w^ z<U`a$hZhO?@b!Q$KXM&BFXW@;LjD0=c#QZyHXV5W*mLj&Ajij%&Es`K*7yRls<|1c zL!R&h%IOn)f8q@xkMR4ED6j!}dZY#j|7a1Q4@W5{M~?$>Ic5d)>evd{4E%nq)5Vip zg?tKspDJOw3HpYuMb;w2XGQ?=dlsF2b|1VW<Z}Xu`*Y;ybEM_D&xNcb9(BkByNIkq zf9gng-HSkap7(|*SOvuK`M1C><O}G>3(JAHyg-?G;WIcX<ck-=9Kin<?|~-h7P3AJ z7Q>A|+_0g@`X7XRX%wJ~FA?vTs)c;n2>ao2AU|IIw~()dKpId_yh2)E`AEoyNFbbs z)j%8?4nr#-uLe!XSJAy!F9yowt7Y&qbOFD;HV!U_1wh`u_5cvpYv?^T6ZsnHdwmMv z@9Vb#vS{>&*^m#nLk-|(V+&9Ry<vs15DoKzc)sxt;Mbe+umQ;TH_?MP$@@3|0;KIN z^5`v|H=PFu;FOSWPjZvMC;q&RKX2pDAJN@E2EtTGhE;Gs%ZrHDJL{nkNXI)J@V$`D zDNq5(<xlwer+b8am-pXAAKuLa_5B$j-G3(je~tm3|CxOF^Cv>KAioy$t>vhY?*##I ze~);)Z-YmL{D3_8pi;<I-fhhV!u~K6z7g`H9YX%a3j$y*oDj0j47}UMZ*9o$W8(es zRq!nQL&#5jVJslSPZk0)`=l720@Cy;ZN;a=?^E9Yv>YCR4}@$N5Dw^LJMXk3&vxS4 zj-1;G_p{Nk0C@J<pM?CY58(e_pBM6Pr15Xtfb@1`!%lb%>Y*9hp$qIne(nvV=X1jP zJQk9H_<a5}d<Z>4{(Txty{-}RIB7pldHIJQ+yJ=w(gMWe%cDYmH4>1;SMLegIRq9% z3A_ZfUtebk`OiT3yO7_Yqu6z1m-^(lZ}InAbmzM$NQDA;LCAmM=3mI}UkyTb6VL84 zcw5MSd%(Rye$TV->xKMJl8`6jg#2L!yes67D}lJ5JRgwDN$P}?eM0ukf$QK__&sz0 zdUeVKS#XRqgV4#|9M}QhvBZtC+K2l-(qX>@X2WhE90zV4DS(VM(yZZ!*22w@5ilPP z3sL|};3Z*@zk>u=1Fr(-JsJi>Ah6WaumNrZPSZ4e%bP+NjYGc=pEUT&A+bM*k(g=t z?~}QQ=jxve-uw1shrKXhy|?;m*2CftuWr$!9$4~3;{O98|93yNCQF_(0^-3JjzimT z_vhE&_c|^T2Vw~oJwKsQq+44>N&hpe5Y^T~P@lg{h5F6XDSFuERH8o5jkEr|cG06S zm;Yuu{s*`C>ump-`(klSJ+oAZY)cOu7ujBcBHObL4nwQR=2n+%YcR`{&yH6<@yzFL zd;L=!V_##nPLcbw|NGgWar~}O?)v{e*JrTkA>3c4l-sz}?|D!ndc4|1k9VBQJ=>F; zSZ&$l*(930O`>T~C6qv&X!7cW6Oay8&RW6`&jQl=D{hq=19@lYbk@45=d;a){=CuD zA&U84<$mUm`)2-bG<0ew_-u8(ubzn>W4iF+S*<BgG?_9vi>(48AOMm@lSd&CUzcCb zDpMEkDrI_q90;rAzfgSc-<{n9pA&A0xfnS%Ksv-jK6<Kj_1Kv(EWWsFl>Ytidi03= zv$2GuN9TasGyE;&jV0UlJL&BCInJs9&tlCH+%M2fzBK(h)<p9Aw~*#+|1Ivk>haHp zUJj)AtnUMV`@j9S3hQk8!>Dj}TYH?{p6MccP@uEhtIXNt70zce&+_?AxvLRHKZ(2h zT`%e>vmLn)z*<e7@|}DyR_}0wG<*GjBgME26oD3#sQRBN;$0u~>(?my>F?~8?C~T_ z_4)r26~@_EL!CVX+ZeAr5&KJ!rsQ*yN*mh9Z|d{EkuI#tjn(dQ%b5bnUWY~UplI5s z{4?*f0V>%uK;)V`$lKpaq$o50PdJbLZ|;B9|F(gCad1}IK+LVY`#<4q``_IEs{cLa zR%adl^_Xjg@|W*!f_YC-r#_us`rrM}&h^=!hFaV=0^e!#%PgB*f2;2U8FW~bDZJB> zN3r|296tY_;jH~{ej9s4qOlfwWSsgNJxfF`W#$G@pT9;C^3!ovn2R8f@8Ff=dTvP; z1(rflp#N4xy|04~yWS~teY)HlDn&K48Mv=fVf3MWt)vZhQQ`X3e^<{7-Os!DRPan| z;q#cw58W^PbGAeI8Ps!UnMbt#*=}rc>hP@6jdI`4?`<A-Xm@|womKjG^~^=6JFa%z zSJ1{=4c(&6R4L-P%c{y)pyH{MZOqU=AHiJ#+z1aC+IZIHv_l0{>+h*|^ylIQ{T-K^ z0)B4+`W4hy?9u%t`ekp<cI5%PQrgT_^aI^_@@t=--ncCzuLn|o*7t$G{r~>k+4!@t zRCojVKMaF@ec5^5y56?w<y)2UpHgOtpp05YnYCP0oBYW)9cKlfai(PQFq!g@+5cSs zT*GhWCVyumpNC!U_0K$>6_}!N^Z$|eE?_!U|KI;Q?!9gXQ%RD!=bm8@LQ<(zNDf7W zq=<1oGZ?3$Cd4>YDn+Flha94u4>>g<hZ1rY<#rZgl5#fB`*VM`^6mHiKhO91UH|`c zUH|LR_3FL$y4PNNt<U<beebpQJ!{249)E>JYlO#|$~dPo(sDd$8shrfY%%t~$!OH) zf`>7i!H;x}1M#A%ZHzI?1%w6ir}+OVxff!7LcT)m4p@Uh;q|!xJzem>Jn#B7Zq$fK z^=`oBh}3`vH-xXp#nk4A{x^c>+WuZL@*x%k`CUK$@8~|<{{rkeY;aBFh%EedSQdPi zkFUqypt%3fLYo}-8snKK4d3Z_&cb<VevY_;c-m%Qj2ja?bDl$6b<ADw*5|GdmBKSd z4xaBaK{}X%xa}YcYW%O$Tz>}iYvXyaMsAc8*DLB{d_-mO8squ82A(I<@N5Bm{Fulu ziZWIK{lRW7D~(mD-fjV&Eo<Oja|+L8`(#{<98$y`$B#yF4%VT%fcDA6^XUcjr3-nF zi^_3b5k{T!MxahKhEH9{n<HlV4Uk`sI1J|Cuja;h$Hf94-#6|so=H==pZe&p|0lrs zXSa=tRJ2Pf?&D(!5BCBbk488H;S97_#`Vwstz!K2Kf9OyzwsH$Gf-g%W_2pJV4y>x z*<cLf#)3?Q<DqLn6f6hlz;uuXI)bL8C=b<l0eqsZR~uu_zsMa5pB{twDYgRs5b8hp zp7>AFIBLr|@O9kp(B|X+zr-7j{yq?OD2sN*cbJVQ&CP%xSHfxo{Rx|f`vbl=eU!%@ z$9;~z7t=v1;+CMz2Z{!MaS$(PU%cJ!dYTLPJP6~n*m>}s3_Q<}zr^lA7*lb64WNes z6wmUzLzxH{#`%@t=g1S+EK1}lB{D)thkMEx*rg-NosPVA<1a*aL(8Hq#`?I1Gq?w# zpLkP5Dvh~f&!G-&gg#`dLI<L6&JrDcj2&<ul|3`AHu~KKtOmb6*4L|`kI$j|kar4g z&|g%I&DRL@#Q-`dANoP4Ey|3(4Zq(Khkj3rxt`@T&ag#sPn(YACqV4J5ih2Ab5Nf# zqB(550KSyztrs(6VWc<vke>QCrE4H2fhia}uJ<Ey7KleU3Md>0-44!yT|t=AEf2z! zCQmFKzNd9O%7FBwZi?7$$BMMPr9>LSX$X&|V^V!Zmep6pP-tC7q~e(9NSX(`y8mmM zfBg*ht>B*%Q}ii31B{pGL()+geziVE2Ifd3LgW>US3#x*OotxC^Xz(hru}Ey|EF}S zg2+@(lvQal)*G55LL4Yd;PX<dD}E@SE=HrxM`ONV^o_?VORO<f#2Q^tEZ0tK2>}mK zI0nbh`Rmw;1viA3>l~a1;6pL|suJHD|3YOP6LBDN1N8lJ6dXa^|Mod_J#!(Kz8ANU z|5~)A%@7j^e20w56EBVt6!zxe4^rO7_h=T{J0AU&`s_j6qw%9@L;2*02{J1B!*)aQ zEHEBi@Zyn2mDs&4ifiCISPG0M=w8>+&&2ukj8s-MHe>1eET&CO1JPJ5$MfH4JUgX} zp_HdQoV(k^BfJlJ55*r<;2NeQ(o=Y-XsXE9F5tX1qO$3VxUyL3q@bu|;*bx*&~&p~ zbk)6Zy)1mb9Oc|7I+`&E&k|it4dk@|$FoG5S}&G^+F%nXWwxWQGh6<DRo0H8xh^D9 z;g{*AHtN<?G&iLXu7Y?xU*P$=Il`&hV2QW?FfGz$#b_&*C_J-+s{bd>%~JvQjMlhE zE`g?rj(JvzbgzPF9?Hf&wkqOsL^_sZ=!kpysk|BDRK9c2Y|%LHbT9?LSCFQ$?XN13 zAKCtLiORvU7#WiMcMJS|JN$PF*hip0PuF-xi+z@Z`Qsds18~<7@8LJG@j_g|?`bbL z7UB}#*IdF&zsq=$bGb60-`+NdBF*Kppec?kK^q8h^*9vgU8w*ikp4<x=qBWm2}PPq z;Ih$(B@`}5ihd&KO%ci&yXWay_$*uCH7DN(aW9Fbl}JPX@?OBad{ivRn~v`Qnja3H zgVFDn=gE+T@@LCJ3DE0dXe#1T5tn*BoF_vy0p$Xk&^fnM<#S1qZY9Vj;@*~pKz@Y( zqrCr_S7?r`5SpWk=PfKo6S(JO=82c?-yB!Heovy$|4H64BJ*F!7U$px=kit+TmB+L zio8VC8!)o5%a&z{>+z^d)$8O-6{qv1B0Zk1Lm6U8o}H*aQ~0`uBC>Ga2+{y=3R9rA zh=8_eX9H>n#}xN}18u49|2IHmulyK``NwmY6X<7C0;c;kug+v4Zh?#9T~v;k9#;zA zk8~c!hw!QCc-~v?!|yS^D~fbkI36Fucn#qRL0)P2j_yzK4&zCL2O>NcpRK`j^BUwy z_peN^7oK}*{F{UKhXCnH(U=b5Sx7$t#~FaeA_I}0G!smqFrJOaAWiewGcP|sZb5hr zKF`3Iehg{AlpDenw*=Qe9LoduN?dEacMkPKp13EEr?d!fL7LIHkCwu7K_!Hn2k{+a z*Tg*><sMBSKAXl8D9>&@2ku6`l)e(;D}c6OAo8PfQ@&YHvUzL{5NR_to=>3|r{TE~ zpQ)VOY+ScF;>O3pW)kOv2#!bd*c@#~_eq)~xPmdYKzqc)miV58*2VV>#@D%9@ZCdo zs)RC5LA{2?<w%EX&!YJR8cXLNj5$?|^XVD=dLBN;1_aX2QO7Z6r?EfA`myo+1Uw&I zp9@IEH8IYo@?g9~?`!JfT0`l&mgfcOc|SIGa_eJbI;wYZ<eQCh&%yWGdeo;jjrVB0 zg7MdQOJlC$Xg7>yKq^6F8XAvz7$Z@86E}{}=o(-QLE|GzN7twE6Ry`EkT2!<72t<d zW^Qk4^V$g0*eM;5|D}-)z<7jnVsl%mF@Ga}nU1)aUm;A#)Q+SB!Fn(hd5s2itteQ6 zdeWRg79FFWP2rEbalC+ho8||+G|b!bSbeC6vePkv^TsRq9)WYocQ(Q&GQo7@lR<5Q z`M(Pe&#QTw!|rLKap;_A9Jf(4E`VnTypNBy1?F7p#$}^T&}U<9i}UDRDe@g}E5i=k zQP<_MxuymvSA)DwMJk>{Qr+lSTt{esjFm9=3_D&C@hD41Yz`T9LwE{&sx34f=bl5E zXHgvHZK#gRshprc;ydDcyFoVU(G_XNV9c?U;_-Qw9U~?n+}P&GvN(@y))DEJ+j!9& z{xsc-7hP2x`gwfJ*0oi<NTp+)Ba6Zwl?fUTs5Fdc(1y4U*{e7`Qy@QT2RcrJQdt@x zO<RzP&$ht-miSHaT*+k}3MHuAWbY`TcBl~x$LHoql%K$HI-vUx`R@WsN5=%x57jxU zip^1l0p&d!=Tt>J+2Jn`KP8|o&ZBEB|0nposmhU+a30;8vvAxWdDq49l7OYqSwVai zD5c4w>*JbZDE{v<tGX1&&udqf<GUS?XR+tGx*VqCYv=IH2UodT7N0Ev>x1Wj3@APS z&4D8BT2m<UxO5mSKs<d`8R1i)F~aenpb!^v-lgf#@kozw?0Ity;&$RV_I!abo;?N5 z7s&H6;w~2jS3ne>;aZnwar`A*`|@a{!@t-w>jWsSN%>sPftG?s_&OpLVO;mpcIXAf zRSDPv1(%Ko$8`}N3YsGw%6@qY$Od$cfdKi(@~DbBpq^Bp*frvzl$NLs==}a56Cm%) zQBV|6+#C*H+6bleX<#g_UjY<G*nr~58-LSw5#_y*ZQ`$v=kTQ~NQ3<8-=)-;h%3AC zS(KrA(Z7HD8S=amaM|G+DD#z?NE?NAh2N~X4*Z1tI0x(l<og2&gvVb8Y2n+lERN~> za1Hd|o^w4Nu5$s`EQo7$1lc%VBgA=J`#kD&8Fs&Jzu340_K4XD^^Vza4$^PId8i+? z?PZGJhvUi|e+lWY6Z<R;pA-E-TY%5641^vAhL0~(Ui3M|W#IF4=yU-4UhYWW3+Q7R zxTn!}a%r*ePkjH<bx>E7KXz^Mky4;|pq1!ejyfZczs6JrkskG<zCd=r(imD0Y3jnJ zOK>l$gwHM@E)En&+U-!f=WM_J8I<lr*Xs+V^s#dcK0Alc4pSQd*z-y$djCq}Ld2tO zWIV=Jv(VRvQW~8mx(W=k@=U+l6l;TJoR6M6SF|(5-1rYG_!+;P93wD%^h<aj;InP4 z`bVtmjboM9Dov-Ds8G2#i;1}i$8u@ds9q!3Cz6}X*T^$nO~*%{P-0KO0767!LqW8I zpe$fJfpEd%IHDX#^Tdu#K^)3~i1fuk@v^KfSF1BBSKp+I=&-J+tLeveYu!Wl)34}} zdbFOX515Zkt}S1%OTuFbbrR|&G)QQe&@`c0Lc4?s30Vot623~<kgzr3Si&y}e<T)2 zyfrZ)v1H<-iH|4NOMEu*xx{9PFC-319G*BkaY5pm#BGUt6VD``OS~4z8z~uS8R;1r z5P2msBJxILLS$v+=g6VR@kmaROwvhSQYh&btV&-jsYFtlq;g3WlO9T{msCHgQBupK zAxW7@?<akhG&gBs(z2v2Nn4Y)C+$x<l5{NTM6ycuk_#l?oLnS1oLnZkc5-TRyX0}j zbTM13aIuob$`l)3Y*ew$#fug1SbTHw6DgBY=BF%9S&{N}%DR;AQZ}a?O*vCSl<-Pa zDbcD#yAoL?Zz*|O$tES+mdq@9rsSp4mCJZ#ZYfi=?7*_G+?89d@YGMH<<2M<716xW zGSMfZ^`p(BsnJf+&e4qM@aS97_o9=d^P?-GN1{ig7Z0cdr4QVH;Nb%;{#<?Su4}ni zK9brjOFkjLQ8`-ZoAn(U%ggKPx~6V}W!+!YLt*_l^kki5vI6VZg7xde`b}W{)_++) zJK@`eZ3)K{P9$7Oj880_7)dOZST(VBVuQf?EfX^mGZW_|E=pXRxHIvm#IuQiMnoh& zQa+LvNsqi3$%u@MjEQ7L_6F7$u)d91zfe*b)-U}p)_*=JHEC$ln4}p=bCTvKEe)*y zbJF3!`WDu|=?3e!O@249eoA2dO|X7y@kOwHR?57TMJY>DvQyTieEXO6?SHX;2CV-_ z%=$O~8|$lRT(oTT$!LS<3(;1vewXO0(ah-E(Q(l!(FM_!u>SASO9%9UG6yOjcm&pu z1lC6bVD97xfWN4wkXOon@0Fo2zMXfo3X%KvZ>^8ND8%u;U;x11j~_2~Jo)$?Cq6of z`{BtA;5i|VR65Z1NI4-69Y2_G<lciL4rU%4F2v!PV952n4&#p-jt=9aBOP$g;On0p z?{&QU@eW6t92tYyQOD@+APz1((&b3!{m&ilal|{8diWg9y-M*%?2(7@|I&vy9^QO- z$B}|Z;tv0D`1eB#aP1i&>(IDEZyb93P{~7)!($KaJGA%ETEs3p)a(%MPKQSwoPV(T z!BPi;dQyHuL@({{bihV;M7KxBNBcy3MN93Uxqtfpm-eUce<zB+zu$Lm?~c98_wL!# zXGfpCU+%iS_34&Bw(M78TV!lxZ&D7{5gZMEFQ$u6Exxq)M%-?2FHLEglA7|85T%Bc zT9ooz${!^P6!<i6<<K<zW%xi<S!Wv9)I(bDly{a(fm>3XUj(C8;g|Cp`&&Y#IX-j{ zt92d@IC?!Tjwv2jC+J^XJS+b{t|DT(f+ca=<4(t+MEQz<Qu&(XTLdLm=3ATZ=lqDh z{x5$a3gs`zdObE4hrII_&L4?|(aQh)N1yPg|K*eX9Fsrp?@#iZ{9b;SKY#xC|9Yjr zQ&A}2rGGvC=TSb`Bj2gNWAh!y@o7HJ@BJNrJ>(6xrEF_k-5m28*pjxDt!VGD6K$qV zv$btcTf<uGS>J|i9-G(Zv-xcS8*kHXUpv?ivqNlI`=C&wD!z*wi54PLj23vuA$@Ew zQAj3Xt^Y@4x_k-WE+gbPIa#ijYvuQHn>=AF*;@8Kn`J-po|k{9hg5a-lxnG7QA5=m zYNA@9zER(+6Y4iD^}YHb{iuFHch`gT$9j^Uspsgmwz7TPK49PQhS<{PxH(}TGr!oH zc7fO2PO)Fu3_HQrv6H=)_H#Sh?y*05P4%mGo_)z{Z+F|-_EoR8SJSIw%j3J(!}p%S zx1keN#1o>WNENL_Yq3FmE4~xo_-V4Vd`#AmkISm^Q9Dvrlb_0|a+;hjcj&FUtI8w) zl-JZP%3{6X!m6fft=g!zDpk!;pQ)K@vI(oR+S9&vT5DxTX`_eWKK`11SuZe&daekm zd?JrLFY@BA1fEp2L|s)|)KhiDQ|bxvjB4!nRP{w0)lRfk?L|A)L9|yLMF-VMys3uc z87Nb{rACOi)krZ(y^neJ@tA}DRD7>y;hn_iVzZi!@$4jjfLbn{%9i=mI)5%Um)Ri; ztDW+8waXu<w#%^EB+IA^vaGr&?^2gA@;N7K>X59Z^T^sduk5Ywk%M$C`LeDpU(t2s zNS!K2=~i;IZY^KeZRB{}N4~H7`WZT1&d{&O&-5@kQxBK3bf)}VkC0#Jk#e2>RPNLZ z<t}+re<x4rjq+Fhz5gZtBI0qsyL#Av)n6f=7Ijn&(M&Y<d#Q&02V#K#ir-s|Fek+u z`kWf;zbMwpf@-bW?9cOu;*ZYH%QRVCTmNN$f$XpE_lL-Lbr<X!^RezBKk=8zEICDI z=p28r+^*-zvwEH1$M5HV>QA-K#`(+r&-~5)c7KPz)88syP|f_8WJIm;2g!%=xD+R^ ziXYS*Sx3jo*?N?mqesiR`gJ)^zai)AH`U7`pYlY06%r4t+r{f@n0QBxlKIto`Gn3V zpVayNzW!wOwckTDSI=W{snOzd|20`oT^0q@P2v$%M7Gu`_&tCTH(^}eQFRub@RuQ5 z)m*Vn&BMFU0<xZtml^sA`Ko?WF3@A-Lj9I}L${S5=ze~G{|hl$ec%sMjr`&MSMoQ# zUfisF(OGqsPwAUveSNcRpl^{+V}<+OdXd_u{?I+uB0t;z8jJEbP>;z(IZ?ON8~jXv zm8c<C`6Dpj&`O5X68(sNSZ3;K@@G9?74+Zp$EipBMe1?YKt1h$=x6y8O;uCPJZ7q^ z#p*SGn*X}_!XK~RRd1_z)O3Hg|EAiawyGce`RbJVRh?9Ss^9&$bOrxyf1$rvSNE6b z$8;n8G@dDI`0w~*bqjxjKga*X|48@7GtOZDdw-MuL{HJH{CD-Y`UiiKKgFN!f9}un z-}lD(tIebS8~z&gmD=c!(N+8z{!I0}dO<b!SNcnJFEzw9^}q4gy64n;YMfo;Z?kLd zCV!WE-v7aFvpZ~e+rvd|s@v~=amU>uci7Ey^W6eB*L%Th>yEku?u0w&PP$|6h}-9W zc0al0ZkhYREplJFC0?r6+H2!=@LG7!xuvd|+iTa`&33!L&uw+v-A=c|ZF4K!O81q^ zb_?BNuZ!2o>*b|;-P|d!hu6pJ?)CKgdY!%A?pJr(>*~&UX<k2f(Oq(v-4%D$8|c04 zz2Xh>26+A5HJ2L_A?dy7y%bX3t0C=WxYF(cSH?Z)cDp_DA@`7b)>Uy2yGQIGna6A9 zwG&Bppm<5l7Q19r|76<ZX|k5yrnie1<?rGPy+iDjw~B{k30Xz&lx_4bIY950ljMHA zM{d<W>b+vOJYbT{Bc`6|U<R9rdC)v$s+fn(lcug|YudT{Oh0$Ot8Dt~5@vvT(Y$P4 zF&XAn^NyZv-Zk%;j%J81X@;6{?r!(EtEqp}XZ7!9tgGc}xU%k1cbBW`%DHN;ysPdi zxW`;=R|oSdt7KRAgxlpRx+g_LS<=-tgWNr?lDk*DCWe^=|7UZX94n59qcX3FH#eC) z{(gC%f7nP-UjHF#`aj`~?ofSEUzYdE`}Gx_D@%!`vb!Jk5BLZDLq-|xA2G&wCa;Mz z`AmLOz}#$ZF$LAFX11AQ=9&fSMYGT>HcOmumYT23Dzir4Z8qo!RXy{qUSYn|Uzkm1 zi`izjo1JEt*<tpXpUltdB{fjJ<vephcUDW)GPT<LX!fcxF65kEX*T;uT^|3K+2bE~ zdHr8poPWaQ^G~|`{wY_$|JB9&r`=8X;wf*6nxp<1ce9_PD~0BT=7$!97KRpu7CYna zbU%iMg@%VRLnA^XL!&~Y<%8x+XU%TePv58iH1qu5+%4u-vtM5_i~O^$puEK_*H_I7 zd9zt5J~Q87o@%WrWTK|9Iq3iHZuQT(LjE7_HvdmoSZ*=j8*2_3-~3>l*=j;6%iL}b z`{&*5{ssS{E26KNW&S0TEoPW?Vy0Q|Uv_uM6ti0WA}Xjt;%;@DcvRgXhO0rQh%4$} zaS8ra7xu5YM01BZA}hOye845i9=f9J>5^qHSIp0K#r10am0qK>^*8!!CtQk?u7p#r zq&{!v%OA`}r(G$#(q`MQ?K-=`ZnRtM4>reMv{&ra(1)Q1Ll1?jgdPq(5_&XLHB>EB zJv28|Khz-fbf}@YMcpi_sYgY1RaHEus)_pQNzp_-Bc4@FFst{hcuqAH&#UJ|7u5|j zoZUq?)kAbwX`+YfDMqTn;$8K+n4&%upJMiPs`^MwRBwuD>SO%1&s4EiO_2rE2J9L5 zt-MKnCyS`v@(#5}7F9pW1hrQttJAWWIwOl?HlVz^A}gq?@@{oaR#dsNp}ti%(uHJW zeVcqn7nXf=W!YChAP4KZa)_=chw7)~Yr4LCQ@4|2bbI-h?jYaR9pyW^ll)NkmlN~= z`H_B6eym@Ti}c%av3^G`(PQP8`dztHzbDt|EcuO|DA(($a)X{GztxlFcY3<qsAtGc zdY0U*KbL#-VtHC`l4ta0nWML;PxL_jnO>p_s8>~nzfQjAug8WsPTZ>Uib671+@`Q{ zlTzY#OhFV;SUO!^5JlxBEQ@|wgyj{HC@<ny5WPfC)m!vZ>7uvlBhpo0(MR<YeN}(a zPYn>;)qJr-Es#z0?ebY&L^joT$meuX`Mge$&2(5c*NO549g!_`l5DAyWvVVFTj}C* znI0#X;}<<E^!xbp>JQ{s`a_wmC&;h$NAgGgrQEBR%6)p7{7EnONBSH5QU167X#YEt zY>JuUCdHI6B~2-Fr$`pXFw>DDN?_N=QsPcAN6Z!T#C)+pEEJ2xV$;cVHeIk)TsPC* z^e|~+kN8pS#T?L2;%Bj6MCB1P-n?%<Fdv!;nDhA9e4>BW`*l<w&<FJ)eOMncrCmMq zgnP;~arI44*FcwZ4bAJWk;!t;h>GGK%sbsH?i2Tm%Hjd@tZ8bVGtZl5rnz~+v=Gb0 za{SHcO7WG*7GH~1W}=y7CYvedQ!~{}Gt>1keO&*dPw12Sl>SwpHkHi-qO>R@%8GL0 z!B7v^B-A}LFf_olG^wVQX>Hnw!D5J5BEA%>Lp?*iLw!sy)7zxGr%fNzR~!%ru?pAW zP_IyWsIPg$ylKX`#^xn6(7a{dmPh3=d0hS?UkvpN^$)#hW|_}r7uii8)%*0B&`Y5- z_le80PueGJU0ct6>?Yb4wxw<FCfPQ&t!?Kf+mG!h_9^?c8)<vnUT%ap)O*@%Xgk=B zwv+8*yV`DUn0?v4;zrv$Z3Q>Vz2P$5aQB*R;3e35?fv#HTh5koZ`%2`p>5>GxYylV z?rk^Az2nBZcinrovm58eyZ7A(?n5`hedN7nciC_35qs1gvj^-!`>hx6-Q?Zu#d-Pc zaeK=CX><ME_KZDif4ArCANIVxWG}mE=90N$jrqgmnhWMPtIRnoR10(3{BEwAYi_zZ zYc85U&3SX#&2XQY9COB;ax<+ozgit?6l(1K>7DnU@fv$?+o^K69A>B68FreR<2~Wk z^Xhv~d3C)f{f+KbH`onvL%sLCncm0VEN`0kxi{IH;(h8(#rXD&chWA?+wEdI!aLv{ z^NxEbykESpy>GnL-ntms-WqR}w>HKWZ>wGIed~STed0~`KJq4cpQ&l;3$;*vsaEQ$ zm>+7O8`?g$pY3l4*ca_2JIBtohwNedi#=ga+F$Kyd%<4wgeSfHUIBlL|D(S*)IQW9 z)G^d4HcKe#rt)~u!`v&L8)A5ap-GG_1x;pbYiKdXR)=DIPMBlR6vk@+Ey385&;Z*C zVan@H%&%ZP4u6heCqiik0@zGwS;nS8?_z9iD2=p%?FlW<*c#9Z0Qp(WrJ~mf^g3Zf z(EGpx2<L-7$k=q~LyYYUt-@HUFXmAQI}G{=V`;YLQN~iasxlb)3Q;XULCF?GRq$8< zm6^&%Gy;zYw1CzOp!(Jd7!9R!h%8ViU=8#MM*7ev85swy%Sf85sK-cZx2G5hTZ{UP zq&iXC07-Uu8Z<;0^D?pWp^S0_!i^a@4vMu72{{?sgpsSE&oXi?6f;SL{2q$AFG6mE zKF`P#&}NLS1SLNMmg@ZiW2vpkF2H6%TQc?|Xe#4Te&j<y{sC>xC~D6(jG{JZ%P6uz zJ4R7{?HTn7v;(7tLOU|*4QMAuO@wx4)Cy=9MtuYA$|$ODH%3t%yEE!HXb(n9DETqa z@Jm6y3pACv7o*9)dNZ2rL2Uyx<=2PNgP`QEKz|Iyyd$9}LHjd$CUgL!=RjX%G_?`6 zF~D1BEQrlm@<+-SSo9O|GGoaOS&TOXO11#DG?cCf%yH;s#!%g-F!nL%r;PapI+d|C zq0<<<06LwC-E(Fj3@qjG8DqbIQhk8UfX-s<1nB3CtplZWz*1ReGaj`y)gM?&JD0Je zq4U6e#P5MpIe<t0yO7bZLKiWX(k^B!*>4Hsksp1@*xk^jjHT-@V=U#fobjkXuV6f~ z!%D`Z@_)r(HcE(WunJhPI>12J1Ze0t0S>x0pbB(dz!T8*;5$He*a$X(4PbKs<*|kF z)1W^vlG=AG*pB{1?YM)HRiQf>Nxr&^u_K{We;}(t$)@`druP4dk(9^JjHbNzGn)EP zlu>!02N?M$^dKXzK@TzN7U*F{k$sLZDjs^2QH7z$7)5qI4o=`rT5IS@Mx{befzybG z&xtdPnhechOc?qbqs~H!bLeXx^bbaBD3ujx1wGH$QP2yFrnb8XE+fql=oLo4486)| z@<FO6Fp1Dy2D5`gB1ni36afM=hC<?c1iizPC`SxjU*dWM{rpqHMg(RSg+#dt`re@L z4+67|Lc)g#`VNtF4S*R)At_&KlX_6f3t(1K(DzO3JLf4Vl>=a=5(};}vG0ij2m{p^ zinbyAp3s{Z%xGe<Rfg`_675Q;cF=+h-M=wg5ks|y7GmgLE^lL02PoQ`p!+)3UWuVP zLW?kTkC%5asuQ#*Bi@82Fqlcj!lsNE14SDUm|Ybz!icw^NepIOg-m9!2b++^7|g;7 zS)8H!qfB8iGb>~XhVFT?B!k&nEHKJYAEobU0yDUR`eCdueh)3fU{+VivJCZSc^8A3 zULngd)W2nU2D86HR$!>F%exsxWvj^e1EBXXYB{u0fP>!4sBGwc0cdY|Kcm(`D>GQH z7mIBMQ2RW{DD?f<9&-;tsjaFoYA5vJ0BW~K7_|%fDB}-=R%O(7Xte-ptLls*J3JOZ zcB;Xs3(&^{$YwPebrD)Cfb3VBQJ0|P#{}8nNk*N6)(xOM>M=S5eJX(RtIy~>&;|jN z_tT6<|B(#?dP5sAnrz=VU=Z{fMpK?m0$zqb%V^58X}~Mc=NMfF`h37hXfsBqLYoJS zg1*4$R?rp!qoFMs-5N^g5U&GD2Q>9l`ivM4=osieQ0lA1`+)i<(0!rQCxM><?ZD`C zXvcsV&`yk|x^xcs4BCa!!=POQW<t9$dN{Ouz$|DFMrT6P0zQZKWb_DVuYfP0)CNG4 zt*A`IIzaUSn))Esf!GQ9F?u1ie*pRFfPj<G7a9E>^re7PQ1WA-H$th6iC+P=E70FV zss6zK5=!<0csqgxB^eqw@WPTvL-iiQsE46L8K2U=#^8Ml7L8=+8DEmU0F5Uk*$Cim zOYB)c_PkI2Lbd?(>@P<#SQ$ac(TpPheVxJk87$(+(DSBzlkuq@V;H=>!9tFVAp5_~ z;EfIzZ)61JF_zKipzkt@;@)HM<_C*7GW3iS^FQ*zf`EJusI^ck6HuF>A2R+t=mf?m zJAD*z5A<V3oriuBK)#a|P#rpv(H1(1@u}S=GyVeTlmPOJPZ@nbbSmRhTTKg~`b}qa z7w8PePluA<1E0z@lhNeY<lh99k$f8X%b?`P1eJGo0F`A9qcfm$8Jz<qTL6DBbbi2g zDAfUI`kefmI13grnryz9@%uoRFn&KM`Rh{TN%f{O0qda485;*BUj#nItz>+v>sO3V z?UfByAq|yhHRErEe#6ihk;a(>?Livrt0<TM62j{PBG3&C{=y3j4Kn^9=yw4RLpL&7 zK&kHmngx-Y0<J=-On{z?<rYSfUFn#henjO1n$F!8K<z+v0D2U3M*y8m_Yt5+Lw5yG z`|M`)>(D&`^PoR6n)=Az0BW~=jD8dP6N5LBLjKH%e9-+2-ckw~Wki1H0S0d_vG5;5 z^BwXKqi%;DW(1Yz2!l7ASOky}??8_+it2bgfb#!^QPd_U0-k`LWHgoSRKSzaUl~p3 zo@V^M&@+rb8A`qj)Ys787{3SfEJO1Mc*7L~Z(D^t$B41eKN!4$#e#thjUDmEJ%;}p z^g=*6=tV|dhF)Ss0Vo{<brbXoL-Rv;PaH!PfnEz}4b5eA3eJHW5h5Nc0}K?Agg~AO z&KLu}r06q1a}vr1bcUiW37Vh4+g_#%)G;(}qjrq}zoIc!Y+ME3#GF0@zrq{m81!62 zV+lf|jx<h)jTP!a=@@7#bAZf1n92$?+7$0vV$gRKjm-#+HdO@~x~HgH89fGCh@txn z-W<iyZ$S$)^nF79N9eXt*feI>51@B28uuJkl=1sR6Bz#sXqcfn36&T?=SCPGKCF@g z8bOm8e>k)l<Kv#BiZkSEirNoo__Hd(2&#9<0BVO)jG#8UGoUN9G(*pcstlvaeq|YY zW>j}En({2i(DS1z&uFr11%{p_)!mFH8&_oL{-N$+^de}bfNjuw1JDoDeT?o2y+44` zR|XH@UYZSkkfAwOno}cau8^KZVmt=L{UY|<K{iGoAn1D%@7-ePme8t<-T<w}_?gh^ zjK2y>c>z%a8bIFx>T$*&0j<f<{1%N@W3+<SW@6*jItUX>0Qn`*R4(#GpsBp%>mc^r zL;ejkm5bU2(7lt!rG%ajr8*O2XR>=k*ylZHqX5dkG2@dBp9!F{Hv!KAI=?C7e+YdJ zJdb>`pv?g8TZZiP0%OP?Ef_;~B0Ca`L2AHjP^v%hr$JjY{_D^-jQIje{RjBtq3r_R zg|-JBz}ui>z;tLQ#-9x(KL-Ar&@KU6pyboUR?scr2WWT3C%g3kY2XwfKO;_pUIBkX zdk6duO=tYKpnVu!0os@Gshrg3fxi$+egJ&3$pA)^tzKmOCD4}`P4*nfXll1XjD8yW zGNY-zUSTx(U<P;<`M(1l%xG$pA&fr(I+XF}K&d@|{|R&$<9`Gl&S<JD`908N7xHPK zDesYB6wdt~I-1dx549!GQ=o4!dKHw~1NhXAV;KD{lxzp|4^Xlf@Fzj34#1~;#xg#& zHJuOq&!O)z{w(M?#(y75egiy;qhsK&hSL2Mm`9=H)4-=Pl3jqm21@lOz5*WyY=lzX zfKO$oJ^*wT=tRb!0i|*Se<pNt0Q!ugIuI{_PXn4mr!xLZ=rqP(3Z;4iO@2rD64ZvD zF<4nqD7psf?SBI$TLAYQl={zXK(?Va2A1kIm+`kj=P`CIbUtG@K^HLoE-3jca8&+9 zj8APuz6&hnL;ecv4(OKvWw+g-%NQ4hE@x~ibOqz~L#fWd{Q@Oh1FVK3R5th;;X_dJ z8<gEqd8wYjQ9Y>Kz$1IEW3VEWpyxKi9ffXS+yN;08E_|{-!bkWl-d=zlhE%OcMM9l z0PYBMGlNx2gxbQmpP^(6;C_N`W$>4ALXlm7BO7dI9Hk{20Y|pn$vCnZ*&1MvD4}*U z9@%OS_z`t%4c*Il9iUVW;I)9#wSh<NPGtg){D<rdTr()O0dSND*#cOy6V)GBsvp@J zSSs&f#;5C0{eh$Ukxc<sDi(@t1RT|a+6*|VAGIZLWCODGuL!Szo@U%a=o!Y5ALW2t zq(OUY!FXMulJPo0731}SA`#)GLk;6~gIdO&f_jYC1L`wgAE;wI_@@prUQcKq#_J2s z%Xpokag5g+nh)ehIZ+>7fbqIQ;~9rK(X$cZr9p27MUWo#(|0glKWI@9M*Kx+5=ch) z3bYvG4TKg4Dahw#XbHv}1TD#U1E8fCuRruoP!{=IgWd(o;oKLY<(UxKuL9$}3cZ^N zku57SUIz3YP)&#s*|j=&9AUCUP0$+QTcB+K>>7FlO7#S;G?eTJ+yhW*C*aCJ$#%d! z2&FbdJ2?0_&9BAg*dBtCU4Wx7l^eKcp)Uc{+f{**Er2CEyu#4@z8=bW&7iL_UOVV8 zMv#q%GnQ<d$q34O1ViuKXdaEA_d+zjjg4{JLaClWqW{o%HO5cSF^p*seTxwh=-Z5_ z1$_sM1>3;8jG+3x$IzHhk7L9a(D4B~pzkvR{fqvRilFxi`a_1^|LX~iECKz9q4{b3 zaR8nB2_vc9vI3}%6B$XqG&z9UWeP)cMKp&<NZd#C)POzEX-sVHU^>Dxz>nZFMo>O8 z85*zaS&X5!_?$71K&j5a)PqtR0Mh|FhcSbpa~VUnod*^o&4bWIU@^i^LYIIq5vKN9 z%DDTW%NWxSx*V*4FWnDa$(a7ouL8)1+2CvBLpE6jzCjp1r`Ixu+H+k1m3KYZfHc&$ z-!i5n^gG53fo=>a3H_ci<g=R?GY-0$ad$(vfFDrK$DvyR?zQ?iKt2Hs`4hD(aJ8Vj z7_6WtG}#?s<t?G9oq&53N^Jui-5d5Yt}1jN<H|wFr-7>m{h7hug+f!?16LgyWn2X) zwJUIsL304gh*hwKKFiQpMU#I6`VQCU7@7;$e=zO|=%0+EdYormMd$?vzo`}aA|o0? zFENtrd6{u^tt*Tf1icD!QJ#BXF99U}_Zn0MBtSLeqpk*tV%!F`49)2p5BNCu2-Gq3 z9?^tA9uN=cn4sf0#)rL3K1RY1O@78d3@s3VGR4kE9`rjPa}z_hF*h@!CiE7@N86Z! z4B5uq%7~%RLJZl-P#pl-#uR4cz0lhkc|WuWLpC#aFk~-Nl%aQehHM1HQfQcw-Jyvf z8PA+iXfaS6kX=(4pKOJ`6oc%Fc8Wo^q<qT)vKM^OP`#<H@F#-iTFl*m+HE$d6tDnF zbpSL@GGtR?A)xwH28+Q1j6<8zJt78`|Dk}dpj86kljh-oHPA;G`fjALK@95KRT)kB zRby!EP2<}b-$Ky`2)e(R8Uf!yA7^6sIBH7*{od3H*aAhLF?GN;fc`-DGqV%Gr(^7Z z()H?teV_q&8g_UIivB>**vT|v9ND>X0Q#qShS8m&O~A8APc~`FsMXNt!1Lfo(2Sw+ znQ6{A^7j`QNBy7$L-$eBGGH?_mGO^4TQQFMMQg@C25l3t2ilhLk3-urj(np%<NpHf zz+ja}LHAa|KLPE;IO=CqH{hRyc3~X#xvq?V3fhfv1)$`|!2cE6gTYE~!lW_&X=qQz z-309wAfUY&dbTp@0n~5$1W?`kGX5E8KgN-t_h<YZ=m18OkG}|B!86r7Xa<ATON4oq zad$!oGmhG52pEC%!=NJ>`5<&uz?aa`jI+?!19n5-U?i36O-55+qV@)KKQ`n~1eND) z#{Uia4&!ctjt#gK`fk8}=z9#^r|G*b#v<r=#y<;vpK%4DA29U$3G-pVa_9tx?&apA z0P0^^4E_GXObj4DoWzLFppyf>flgt>Ea;~JYoSvE3PGm@M4{6I3PWcE9E4K7!2caO zlW|mc$`|<Opr132^8AAF|A5YB+-=Y~jQ=NeF5~E0WNRR|K<5X14_y#I?YJ=D5R`06 z_+W9s56~q64oZGNYz0dLLeOQ5B3qKJiQ56$mpBZ_rocZB{fcpvCL4T>_zTcgj4J|N z&FE{;H34)TY6IYtKakxC%3~cP$j0jfC~gBI$j09W(C6PV{$=P!#@zuW8v&UD-4sA& z+04*b)R0Yqr~oA!0h-H?*#)){cO$%wp?Q6?oe_^hcQ7=6AG<H_MEc<f?_y|9&g>2# zJM3XxQRt71e+9aiaS70UjDHpS6XU|rpBbNQNPP*oL})aC%5Wgy2$cL1&~I1_`2=tg zD77D;-?JD>2V4^LC?k77k1;g<GshX(6H4|1E*W})k-eZN8CMK?it%%yzcSdxMi{zp z0=*h~hN0(clf!88i{BWX4L!@mp0lZ6f!Omm^(6oo7KZu{a4FC~87HCSm%x>Pk}m?M zpcffe5=uS?=(*Bd4xqk8?ExhGz)%|y<WppK;53x#2CyTDu;m%@`+{8<3!h7PjIk@A zwHUh+S{u~CIjCboJ;uT&3H2Gf4%&dR8=wst3m?aR)-f!~lkhBPihSU^3C$S0722Ay zuxmma#-iQ{Z9zMvN8J)8Fc$Vt$YSg&=rYE_j}z!K;F?3JtSC#U0u=p@2z>~pGU1w` z2chUA3GkZ`<qe-9Le-)07a}wl3ZF?pdxgk0R1VlMgnEb3mV~$kiuNPa%}}%%A*w+& zL+@e2h7rg=Y#CJ*>M;WOhkb_L&x9Q#=vpC$-kpT=FamZB=VjD0&^SiG#^HR7qBQv# z0lS9_FpAQ|GvYbuO^l*6R0klQhf>`Dy(bA5WJDL}tqi?$2p3{RSLkgFy?+Q7W(1|V zouPLT;UbKnG<Pucej;3y5tJr@Q9YqBG9gAn6B&9B5RNe7U1$<RbM)b4MofVgV`xr1 zT$~Y~LecjKnja6NzY$_8v?QZGf|g>$MChFi&Gm;%Gh!OF3`2A3;j)Za550?_x$tl~ zMy!RFXK3C$48J5~0qETf&F_XQGLqWo9){+5!<86GZFMh0^S<Hx82Wu>_<n}wT*H+a zNj83fQG1~9dqR@EA7W@uHe7{~Wc!C1wHNvbBgwAh$AIQO!&MncHYR@s>I{_p8%VM@ z`7}^D&>D;+8$8aaE6|#ZBzx3i)KzG0Mv`snFzOog2}Y8go@7)mv@RnXLhCV_{PZbC zHiFh?H2G`;MmB~%&1mvpIv>bqpmYw<<ja%}$Uac|3}~`3^)Vp(LaAQ?P4;fe$idL( z7)@n-o{>YK%@|GPZO+J{&=(j@WpBaA*PtyKP4!4+<eSh|jBW>Q&B!s(HjHi$ZOh2F zpp*{i4p90G&~L%QbPRMyXaM~#EZm9Fq@5Z1Jy)3S1wi+QQvU~X0<;^W2SB?s@*`*u zhUP=VX^f=4)03h3(Qq$DE`s)EXbv)*&d9}3Y6C#?kzukskV~Nb7@DIDQ<;GL5=!L& zG-ny6YXi9yO4kE4e;FRg$TiSGjLw3-%*bz`uP`*%7|viM_4ij9no|r@djm;*nc5i8 z{9>5e6-esKuQ4=F86L*a@1erO89g1E$;ge+5e&^shDS1T6Lb_q^O51vjNA-;ouRqO z@EeTW10`PpG|w0&{{Zqdlzalv9A21g4dfXp*%#1!UYKkOWDb<<2=o>x*$&Wq#PB#q zlP$+HG=~~~pP_lp@CS@403{y+itI;z0~Fbnd<FRHpyVGw(sk$>z$cqi+W=7zO6>vY zcQ@fljJOp__677?oA4Ay6oP)r$XqD384$NYshxn5P-+7p3PZ{6Kq)BI2Z-CDGa03! z)CNElfs)+;l8f*cjJN|jn~@iwa~M$+I+u}`pz|1!0G-ds%g_Z3J#>T@GV%&^5hD_z z)UH5Ygp#d+NP~XKs9w;ejG#WZj8VOz%NapwRxtEifbdF2P@1n8`dvUcn-P@eYleO! z5MIRyO0$}w-wK4OUjadBs4oHf-9UIPBPb2EH=y4UgsF{z*bd#m(0kqRw~W{Ur9K1b z9dCFeBbz|KXEe1D`81HvLdlN-y~hfZ?*iEr`U6AnwZdB&`5bf`L+`l4R1P4Yhtjoy zrZ(Ei$Y#)8480Ev?`C9k=pIH>8~w=07od9?9f9s+WDDp|j8207%*d9|{S3Wh3r885 z3O&H+V$g$(Yy~~U(EGRWVMbD$A7SWSP53Aysr`>J^xh_XoRQ=UzcBO;CwzjD<QFFy zdY2PE#YpmzUm1G06F$wzZ0H$=-t&ZW7)ieK8$<7W!e<#tcKV&6cOl_(4E^ph{0Bqt zM#6tG^c&3Zd4}GTgfB3XY<ZEPcP8OWj6V{3neoZLt}y;6=vBriU%STmqoKKs51&gE zz{i*hZIS3e0Yp$Z9^8uf8qmTZ0r3N&5l|BGPeDt8@`#@UtpM&vd?#pS@F?Q<K&yhs z;bWAxHmC>RYX@xro<;iCpwBT9ZJyYSk!bV87Z`~)Pi)CZw0UAG=zw$4zKI<{59AO3 zN=#!+X(;Xsgv0d`aW5cf9ybyFpKw(EbcW_%6Z<f(KC~}GbB&4p7)Ryp&(M3^!~u+J z2z`;E`MkuJ7}p3&_5vmgI*7qv?~B9?FdXq^pqb!JAw)&!7%&d;uuCG<?GwZ=gVOoa z5I+$*9n403k3r{v1&F@~x(F;rm~6iUz^4S+fzAhl%DkKr4?<TkA=oYveSmOehp(7W zcW5?)opnVb{F?|-y;p%XxE8eu?t6*r5N-`!58#7hFcdzQ2>%jP7TnVaf%|CU_e_Xv zyNL-=n`~x6)DBy~R^*8`OWek|r=dH+F2qxs-C!^BJOKTP2~m3T0}!IN-Oq%mJ);2r zMf0qQ<XZr%n2SX6E%=mq35t72BJ4_Ytcl0LN%$(o;hsQ9YUf|UY2@DzdWH#6{8?}g z=gxrs3C<%t3wnW(U7!~k*$sM$(UkvXhTi)nUSTxleU%AO*{(66G$A6Wb|f!;OE3%? z4{kzT;46`v8T%yk7RJKoBL%^&n184XEyTEwp|>$^BD65L9cfxXi!io5^bW>Nf))i~ zq-g_9WZYyZ3KU5~Jms0p*r%Yy7>l-x6ldH>XbNM|?vWCVMcYM6GHwL4JmU?8RseUS zzLZZzPzl%S0KFI7k1%W-smwUC$phd)#J>!Eh{5XDB2tC1cS0X#Eam+O<3>RrWgPq@ zQkB6@(;`xhal@h28HamU<T1uJfYxBV1nA?8r83rJEXo?G1!|*gcR}kgwhWZo3Ai_* z)PBIuhf<r>L%1Q7Yzna2yNJ|h-0M)fHf-bGf>JtwU4ccU5om(&JJ4qt+Zoyvv_Skg zXiLVUx}-677qlm1zk#L$^n1G(3SW;5MED4F5P*&C0q84?{T7-5(1xCZjs&p17Z0Vf z!meH%l<EL1`dNg^4eTlC+l)mYicq<M&4rF-{N2!Z8H+v=c@MmgG-sh7F!nt3LofmH zm!MgUn+Bc8(EMX$5@W7F$&Z0G&?yYfZ$>_443%LjLvxxD$_voEX=FNMslGE9nkS8X z##jNJ$tc)8LTv+RPB!v6LvyT=FTiZXqu)p7Fplz`%h0@RWFABFsge0$0n(GL7BV!S z8d(GuqkX9?OBkB#jeN<_9B^bQLvzNFWsIXTE@!NSu3%^`IkJ+m8cKe!5BI=E(4WB1 zXj3Y~Atv+;l-d=9nn3B=AoQ#dNw7r{^7as)guDn3ZJY!n5gyJ>@))l%G{ksl|D;<O z2b(1oVm$au(rt`I`y_=KIUJhE$YIb3z{M@>nUu^}v|my&#=+i6B^ZtRC6xwcP@Z?8 z<rr@~v?3!$K_6l)+BvBn<H06L=$nK?TPD#l@M=MyXS^q%%@~i)YtDFd-V2QP6coNf zcy*yI8ShDGDj0(NDeX|k-w1sTWFr1m=orRB-$+8=B0RKj64?uQA4A_^JosV~*%x?Z z6RI=t$j<Kp<l#+*!k>~*R}X%gMD~JTdcQ*FGah^?X#wM%fi7gclTi3u(lVq!4Bf(b z6QEleZz6O%<I#0~2K$lzER@bUf^ZJ>7~{eJl1?xld@vb(kFblN@FT+Fddcu5LL=|w z0*pnUNWO`&=qJfHGj;^D2xC#+<S=7VkK{6pMITD8&DhtWXbZv)hqeXn5JsCM!+y!* zFvh=Ah+^<@!b5$Ep<IL)g%)N!)U{X&;~jv)4~vyV8q~EIu1k1Fp|}R&9fXczyhG4U zj0bxZ+st^s2vNKk<IrBkQO4pOQJ!0%uu*ZO^DaPf4&lQFDOq3=;=hB=W4y1SXkWtn z28weDZ#5LQBfND`*o%n8!|o~6))c>-@v@;S7;g=fj)AudO4k71S}1)6V)1l65R0eJ zfCt}6p?rb275Xh>={i(y;87gvM|c~cupMFRL(eeYIU!02#@hk4z(e{y&?<}vpDEFb z@peMnG2Sj{7UQiKq9klecq^f|G2U0uCXDw3v@PT9hGqctQ)-KnC=Y=h3`NO581Dn< zpN#hj^gQEDhoa30?<43%#zUKxyu^5);jzCm<Chhp4C+L9d!cCKGE`3ZOPQjKhijG{ z$k^YYuQ2qtZg=G}*!4`5E6mut@rnUuB0}*0X-Gr(u-lAsjQdoGC~Ql3+n@^M#ld^f zI8X-RV^EYY`UK`ZP?qSEjG6|m%P9D5v>u}tLZ4z3`d}1oNhsJn+5j|1cpnt^L_$}E zwqSHkXiG*@+Ehk2gra?;oshmUv@@f-LA!tq_zdn7(O1E6gdc*U-J)+HOzGcdY#-=5 z0QIx|pyR+~gx`dsT?vaeijrNXB0L95^+bExL(myuCc@~m(OKYggnxxn{eeY$M`ttk z8kEkT3){ks(RpA#!ug>K0Q#7RHja{AjvyR{9%VeL>+g)e1&TI~qW%3Jq42jT{3+BP z3ST;aHVX}cYET+s*!%$6?ZEv=kM=qMA3N|c!goO*0WA=&C&Ztt8Ly)d*CGI)hnhm+ zBZQkO<ZtC;2>U6t0;q#??}1_`10d`yXqOnm&J^-OgBZg55ZW+?@UoW3+}GyNkQ<X3 zvb(_U115y<7{FmE^5BTWO5_(G1Ys>Ii0MJth$3Ql5VqnzaW)8hqKGUNgniLiRu95h zRuVI~oM%WxFze6Zya=xh!f`i*^T~+(If&0MDyeH6mO?~y#ULz2QFvl3AE`tG9n?i) zbwnNHDUG;U2j!Gj#Oo75dJplJg0PRhLgRw46Axl0fb$HAil$c(&Lhg1DM2`|C~CF_ z;W+Fwc{&K^!;9TQK{&sNvz3By0g=a66_1G?n2Q~N`PELMgXoBP(}<`DixLsh7%}aj zb?{y^9id(bH^BdUh|V}_6H8qUbE@e`*8}mr@p-9OF6qd%H)bZv<G&8bn^N=<tub%W z1}VECmQr^E<?-qD-0!^MN)2&^4mhW)Xoa-*;LHm61xZEmkZ33xiF)9n8&cne*c;Q| zSmJ;Eq)DtEy>V5n>51~*m@}93E?nt8l=W`>|2|O}|2^;@Ug;U6i=e#SMO)~NW#|;M zO)JEvqc*MZc{|jeYSsmDJw$u_|GGuW{S|766lA47v9qhi@}*YjjsK|h)Lu7Q`tKaN zA^-BR_TwwpjHRd6Z4|p&I%c-%>eRZNqARowj=LjotRs&2K7U=0O3j|o2(_w<&+4On zDQ!gjoparP$)PMh`=_n%Mk^BRS^w_3*Gtn1GxDvFAGLmK{GZC%564svvM^n@ns_=E zPKTdG{;~!8TyNBcd^!zh^u`sby}LrmOFO_a^>KdP|JFIxkPr8T?kER^BCr<q9x7dL zQ3Ki=pL9dHDYj266?<D<T)!XApnlW_@nmUgcRK0=?TcgTL);^J#6Ig0v_k~eZWqh3 zBXZ{6_V3C<e$noR_Gyo_l=t5)Mz-jG!*Tc6_1eK6*GqTT4W*;j{@e2DC^cE~@3c48 zq~X7${ku-&P1n!-cV(`H68DPr2lA-4u@YZz_1@wcv?6)izgn%+zg&wfN$uSp?MgmF zo}P|;(qh^XpS8n(kKq&Y2x_TTNEgB1kK9<h-uRSk)hK3>4oE@m(>s<E<uCw8t?*lx z>+O(^lyo(^f8iUJ{#~mevSWdN{13bMV9HS{sil!tdf5BG$&kz=^I{K#d@{c*Amimt z@@9F9EGTcqt_Ziu!t!=mMBX8b$^;q4TI>;-B$H(^SzM;b60)Q$g?$rB%QCX8yi1mo z<z)qVx2!1dk(IE*^?mYwSy?_HACwQtD)M3Zh<sF5#jXU^gIx-0%39dLrw(?Jc~aJu z_2g5szHA_$mJMYi*;qa!o5*KnQ~8{HUN)1><qNWfY$;P^E7@AMk!@u=*<N;#9c3rk z8LPi`mEB}_*#qnB_msV4Z|vmINA{KdWPdq8z8KrpVUT=Tz9KW^t8%a$B8SS?<S?v> znu*;WM#@ogw0vE@A>Wi^<XiG>`Hmbb-<9uSABgeteffd>P)?8^$&ckHSgC&^)*+uP zr~I|I!VLMDoGE9?&*c|#wwxp9%6W3WTp$<9MRKuRBEOVN<ubWku8=F`S2A0EEmvW0 zi8b;Y>@TrSu9q9+xAHr=5qnK+lAGlg`GeewT_?879dal3AlNPU$RFiixljHif0p}Y zR34BA<so@k9{Fn>+!OMoJSBgXr{x)$BY%@;<?r&G`~$l^oR=5mMR`eHmRICec}?aj zyunpUX=Shrgr|JvR7mAfc~zXsr}ATc<#=_Ix*2;!V8wA&NZp2gB5qej)E%m*N>E{y zs3Iy!C97hpxJpqaR7q7z-Kk2eGODb)OO;dQRRwjos;KT!mDIiJK6SsUtR7GgV$X~! z>S6VWdQ??a)l_x$n5v;3S2a~FRa@0jPpBucwt7A6s!^ZT?pF;}Bh^?vqnfB^Ra5mG zR?Tmwnq%es7T9$oRkc#BWBXRLQ|(m;)lqd)omCgrRdrL{RS%V>da7Qkw@O!iRA1Fk z^;ZMbi?P+u2dS5_XGaEBkRGgtVDFCC)G#$%WvUTsq#C70tJkrc$D3-5dMma+z*y`P zfOYxRc=f*eKz*ntsE^dg>JycP9X}?i$!d!FR87Uow$p>%GiIsJvC8diHAl@=^VEE` z04v@u!oCAbu<k9bx4s<vgsj9~0NLtmwMwm4Yp`p`TD4BCR~yu~>N~X&dx&gOo3SIo z57?7no7%2+sGVw;+O77eAJty9PyM8RR{K>{9Z(0=A$3?CQAgD=bzJ>|y+=;Qb}%@N zy|8lBZ|bc2U7b^ZU_XQN>VmqcE~(4vin^+<VW%vCT}l-8_b}Q9dwqm-9-UXm>3llB zE}-MFm)6bt7F|%^stf7cbYXov_SU*X7u5;aVJlHbbdpZi*epk<=n}f5E~W3(rF9uy zR^O$|>GHaQz8m{+-J>gEFO>WA{kpP#KtG6GQL5;Nv0uZZSOK^ic8PjS*U*n+E#O+Z zwyvX}&`;{Rx}JVY*T?F>PwR%*b)m6-MmN#V>ZbZR{k(3bn`2j%7T5<ORkzZubsODQ zx6|!)2i;M3(w%h|-BowPt}8vTFGEk=3;Q#q>pr?K_5tg!2Vgg<m#}{LAnarE3U=9e zRSypKH5sOd>r6cYJB5wHZdI>izp6L&82y%hTfd{n>UZ^fdYm4w-`5}L5A_875q5O> zL}y`7u}Rq1YKs0;Pu0`(bUgzrlh4$%^ym5uJsbPI%+>Sse7!&~)Qj|Dy+nVhm+EDD zxn7}HVlDY>{k2|&Rpr-UMftUQonEguVDE_U^hW)?-lR9{E&2!S)q<T6^bWmK@6x;V z9@_5!D_j5c&-J{q-t|9L-PUJx4)0{3&*?w(pZdJMfZYZz>C4z_;Hti+a}EANigp>m z9s?G;4ER_}KV<UUu-gFbS`gcD04wzuGPhwZ{@YCvtdL*SB$%*C#7+xI|6IA;ls08d zS#y^uXUdxj=5ABb++!-4d(D03ep(0oue})_F^|S}<fx84Ick{4O-)nF)HZd@6R}+z z>Y1lZebc}^Z5o<Jrm=a(H2LQ$<bSUoPJ29<_SpNO<3HC$H$8bJaau>*^fUcqyFbv% z;e-C#17fflVuqU6%rG<DWSS9Xq#0#Ko7Z`raav*Ayko}3c8M7G&o$Le)<0K1H#5v< zW+tscZoUZiXqac_n+4dVVG-@qV7|mY4a>}Ov%;*zeht~?YwX#u+N?3(V7G&HX1&>9 zzBS*Ojo9B{li7@&8-Bp<2HUWE!;aYg4ZF=A+NpteaWMN$)EqDeu@AywbHp4q$FQH! zFXn_fX-=76u`c)-tO5QTRt5jvoWrW&v@-aG|H0mSz{^pU5C5~fQ+9?V1PGypaDhM; z2w`?+(x^Ad-KAMBh=>?qAq7%^A%xzpNDDv3D_{~@SV9ZE7byWD6lq38K$<8;MAV?6 zsCe)Do%1|%lR!XFc>n(1zuC`sPtM-inKS1+=Xsv<oU?OyLipe8zuO;VPYh=#Jj*FK zMW^K0x>r(JcT4KkvtQCG&Z^F8PCsXLr@ym?v!=6_v$nI2Gr(Eb8R)F%v^eWK8#o&} zgPe_=jh(^l$FzyFsk0e-D`n3_-@*x<s@at(;W_EUc~bgFc0StD*~;14*~Zz{+0JQo zMmuAivFz1UV-KY5*|%v&p3}Z__M~>6$o^HQ-Pt93rn<A6^L1x;=Nrx*&NrPsoo_kg zoW0Cb+#Brrw4Xf3-I=6MY@Z^}Y|nN~VJE0VoasE(eFo1MKfLED<HtD1I>%*CbZ57v zZ#yTl7t~44$?OSrDtkxGbWV5vhn<(c=bYhu-|2LI;GF54<(%#O(3$1TcIG(eIOjU& zu}9Md=Be>b=OSmGbFp)YbE$KgbGdn<`jyUA&eiPRbS=9zWluA|k>|qS?A+qq$}`h% zcYfsD;oRxm<=o9a5%-v#BJOkUcNX$I_y^fD;$e1;c*J>>og*H1o?wTTMeHB(B>TTS z#SSn}^VIlfvmK$F=baav7oC@!UpX(E=f}Uo^U_~4Pm_N`pPT-cd2;%n*n8sbo~Ova z%X8%4XD^DsIe*Xgqj0*MZu4CAqFZup*Ku9fbIY#p2JTAPljyVOt?P5?S9ANhtGoT( zHQY7bwcNGcb=(2&y6!-CJ-5YO-`&97&>iG%<ZkQ^b}Q~C?xyZ$?hto#cc{CC8@g3D za$`4fhq=Su5$;HLl)I(7mAkdOjk~S8o!jb;cE`A5-8Q$zu2$Q#x7Ch3jecjIME_-; zNB>o~J$n+r`!$|f|8;kF_Z#jW?l;{%*)e7uyWZ^W?&CJxeck=s@$Lk7qC3gm-#vhR zYo@S!%{2Ew_N+OWU1|<tADctj$>uQjuQ|d!k{xP}c8_7dn&ZrlH68A^-4osK@WlU< z-Ba9C-P7Ee?&<FTxZicZ=bqty-|cjN;GXH8<(}>S(4FPZcIWU^{Bzy&-1B*s{al`D z-*hkHnf4d!)A29o8TVJXSMuEat9fSrweEH9_3jPsjqXkE&F(Gkt?q5^?e34<JKQ_n zyWG3o1@1lWz3z|Q``r88h3*6HgYHA_!|qRbTK=Q%WA5Yb6YfvlMefhsC*7aBPr1Kv zpLU;dpLKufKIcB~zTm#-zU2POeVJ$Q|HggAebs%<ece2b|93o(|Mxtn{}1jTc~<{o z_igvj?qA#`?mOn`|L?nhb^qr6-TlD*huh_LdzM%5ieAaHJ;!rB&ntVr7kDeNcU^C< zkGHbd*IUI~)mzQ$=dJGb_tx;%^wwfmyLG$)-n!mEZ#}QYTi@Hj+t3^2ZRBn24fZPD zCf=srX5J8QbN0R4!VA5s7kROlc*DHm-Ux4`H_F@6+sfP8+s510+s<qCMtfttv0j^3 z^XlIA-VWZ5-cH`m-j}>DdtdRs>a}~jc)NOEV+Xyjd%Jtz@b>V&>Fw!#%NytI<?Ze5 z<2Af}z5TrL-UM%=H_6-IJHVUlP4T9B)4T({gS>;i)H}qR?j6eBT8FW()e-DJb(D9s zcZ_$ecO1KKo#1t_f7glLcf6Cllf6^ek?J&WCcAR{4?A*vkKLNS?{#`VVE?DHytBO@ zW;;rGbG&oBbJ<PmeD4BpE_**Uy^GlA>0)+zy41UjJ*DQeuhW&@RqXk6jdv}3N?q^W zz@A?>c{j6%)UDoa-tFFxygR%**~9B@_I<i1+t<pw&%57SnC<=KJ>)&ijv$YCk9v<~ zyGgOj*COv{-jm+Xb+@l)yl1^%W;-BwFL*C{FL}T6UiN<N{l<I6d)0f*d)@o3_lEa7 z?@jOb-do-uygzz>@)mn<dw=%+;w|ys@!s{`%l5|c{^tGN`@s8$*Ts%D1RL4Ero?_d zPT4Jc<uW?}1?82>y~@4Ieab7B`<7QJuUcNM+^@WPxqo?$@|xwf%4?U`DGw;GTOL?m zuiR2zzq~<t!}1{Z!r8byxLjf1pH126X$U(e4Q02ako}Y**%PTeJllbZeW12v|0lEi z6Z<N)vZvA*_ET!hc3on(CH7TfN2Q(1Un+mO{FU-o%kAY|%Da}oR^F}r_44lQ8ns9H zo8>*(ziM20ukzmIeaemUzUBSO<I5Au6U&pz`<D+WPcBa>Pc2U?A6P!9d~i7}A5xxP zKD0ced|3JL@)6}D%SV-uE+11qwtQUq`0@$mj(oR^^2y~>*wf>*^33w-?C|kj_V75P z{C)QF$aeD3eLR-m#e<zh*jJ={QF&hZ;_@ZsOUsv)FE7u}cHSso#oiy+l&>vcSH8Y{ zgYMIH3p;V$R=%Cxxb9$IuDg16+xl_&zViL$h2;mz50)P)KV1Gv`H}LY<;Tj8m!ByA zw7iHNyq;uluBX_c>uGkHV*jl2bLHpR;p;{A`1)1(W%m90joA(CHTD4eEjxq#j(xy> zUw$jwr>p#@^5XK_<v*AIQeIMir~GdDz4H4@_w35|>GF$y$+vyScYV(<`@SFeEBU?r z-hLl{Wxub#iodGAn%~b~-S6+O;jihh<*)6p;}7uH^#}Uv`7QqX{s#Vr{vdxNe`9~J zU-38bH}yC3hxnWOL;WrM(69QDANz?v%pdNL@JITiR<H}wSijA$`E`GLe+Pd@e<y!u z|4aUt{jcPEBV~J+_+R&T_rKxq;eXTL)Blz~&fm-5+uz4;`1|_%`Q!Zw{=|G&5Pyn4 z)t}}c$PP0H`zgE0OlLot8UA7Z;bwoBqx_@U6Xsa|IRAM61i!=owtu4k9seZ%<mGp( z^1tVw;eX%n^nc)=>7V7F?f=l9<<Iu#_~-cNvXj&K{ssPA|3bg%U*ym8FZM6-FZD0; zFZbvBSNK=@SNT`_*Z9}^*ZJ48!_zYRV)?iGKl1PJ@AU8T@Aen?_xShvKlbnQ@Ant_ z5BLxI5BU%KKk*;&AN3#eANQZ|f9fytf95~w|J;Ad|Aqgw|BU~v|4aWl|9Srf|3&{L z|5yIY{;&Pt_^<e{`mgz~`@i+y@PFsO>Hpq;%m0J_NB>X$V*hRb&;DQhCH_19yZ(Fr z`~F}3zxjXnKk)zIclq6c6%>MEPzvn83EaR7%7GsQ!Ae1|pm)$GSUKn$tP-pmtQPbO zRuB3IYXoZsYXxfu>jVRWb%TMydO=IDey~BXVK6AzDA+g{98`i$f=z?Xf+4}?!O&of zAPlNO6vROi3=4(_BZ85^s9?)rt6=M3n_$~uyP!1~9gGRa25mtts0Z5zI|MrhI|Vxj zUkbh)d?om5&>rj(>>7M6*e&>auzT>0V2|LN!Jfglf^orK!QR0>K_l2V*e@6#Ob8|h zlY;$&1A@uHlwfKwEjTbZC^$GsgF}Mp!J)y7;IQEE;E3SJ;Hco};F#dp;JD!U;Dn$f z_;zq&@SWhK;N;+x;MCx>U}kW7@IS$KgYN}r1m6!jgC7KE24@9l2R{sE1+#-W!8yUX z!Fj>?!3DwG;KHC8TolX;E)FgUE)6aVE)V7hR|HoER|QuG*96xF*9F%HHv~5ZHw8Bb zw*<Eaw*|KcKML*$?hNh<?hY0N_XPI_KMw8-?hh6Q4+IYe4+RegKM5WQ9t|D~9uJ-f zei|$aeil3#{5*In_(kw^@J#S*@XO%2;Q8Q%;Kksj;8(%R!LNhg1g`|I2CoIL2fq#8 z@D4j@au_C6NyjFd#+rtj=6{bgKg^`nQ2t&G<@0JN->;5slfSpsB(3Rj_0c9D>GvZ2 zUK9<_KCjl~^J;ChIiA#<m(-k>RF4}yQjQ;O&d29tHE*t0t&P#|kJ0as(eIBj-w)$a zazF7%IbS`L^VP#q<~;RK&RY-1XkN}=56yj5>!Dny9@gdWWAyuD^!sD<K4U%47;_$~ zS?VEa%Tp;|eWZSWq<(*-es82+f23Z2q+WldUVmhMee-wjORf`+mHVvLv|Z}*_oPkR zH_>{L`lsHtenR<Plt}wmM@l^<RXI;QO46|B?<2DN55uvNR`vT;sn4(~^++n^uj%Je zEaj|9{fBYg+&6M4<*Um5^F3`(ZI4>ddGvFs&uUG}SC#q;t98Bau_C8oqUl&u&y-iM zH&)sy9INHY&r{WMRJ9(f^1W(R+g0T}%IhnV_N&SFD38chO}<B7+Ot;GdQf?(iJTy> z`R96HV^w~~YCB24tk$$$qeROkazMMw{l&Q)>2>Q>l_$M^y{hu0<)~Lxo}^x?b*-1A zD(6eIpC+NoL7QAR%&#x~i2F0;j;e7kA1V(ut;bO+PjcO`D#wS_NcurF()Oz9ebrSS z<?pBk^LJ8n9#ZpnQd3S+^F2~?T~gB?q~`ufP5qN<|46jGq}?colrxfY(QUNe<#Y1p z{G`(URmq2m%6%ewioD2GBKitBl>3QCNqxk5|5kaaN7_DeUc^@0ClYz6N7_D-$VENU z{w{J$d&&JIc{`4h`>K!9^Ntd^tBYQ!)<@}iN9lP->3K)x=QV$)dgVUCvGRFMt{+zA zKBxyhZfu^K^U<%&`KV7*4${27<hW{no><#m&L2j(Uda2o%41FYXDs&<#;O-$)%*2W z?>E-_&GoJ5mufvW*Co~ajrD$Gz27$dew%*3P3x;o>s9-A(j!;qe8`bGAE`MXsX1Q_ zg_Y?;Qqx~ZRc@;)x1t~TT;*2u1bLNP>9^!fc}PupNKJW2_4?A@<n{Vhy?)+bBE7!I zaV?DW`XbNd_4*>m<n{W}Zshg)(thNnKEg!XGuHkh{V0q_nCpj$wquyc@zq%Sb4}{G zChcCWiJl;p^CV*L5j#D<$T50N@^ma?XR6Z9VKuiO+O9RZ4(H3SW4=dTzo-69B*#;Y z<~ZbB<f5i>QA<?sihbm=BG<9>8|;*nCs92XCL-@)o9M@CZWq<wR1@j<)w-5T>|3=a z`ja&OJil+bURCQkQM;3fyx~8XcHy$3FB8?TN!~9;<oZU>qxvCCq<z9%f47N#4@LeF zGwF|Eo18an6MY@3zfjHVU+=d%O#5$s-!(0NP3k)mJ59fq`iWEzM5;F;)dNv34|0C$ z!yI3&$@xiD4~w3`{^{Svf2!7`yrf2sNL5d3zpiP2uC<wR@pt{c_@mWYkAEcPj71Nk zXXN<0wohI3VYM#pLTcojG(S${s4ne{oJe`<>enP{?~>fU>2(t67qq9=pWH8IP33Ty z-q$c`*F^1oRqabW+Q?nCuJs-c6FCg^JXQHV>PGwFFp>L6{m@AC6ZNceILwrTyw=w+ zQ!i0f^b4ufQ>5|`4vVE<%J;aA`5vhpR~;^L66Jo7<g2ye+OEU({)TJ24VUsIV%L#N z(|@Wpu~XEGIX<k)_emum<^I)hE&p&W|8P@|YHftdtBeykuil6B`!Lq}42Ma-sE*G2 zi}bfp<OsE|dQjwv>q~j7a$WLz|8iZ_x?XpL^sB1&tE%?DYMbf@u|Hu|&mXD(8RdSY ze4qQ!`jUQ5UfXSi-nZxj<XY=9*SjKLe6ICU(|W0E`>5X?ikxs>J#K{5Q#ee^nO}dT z`Chd)QrmH)mP^M|HSt4fFD;*rXKK=~Fb`56iH=KRX=jd?`mT-<xeD7<kEvd(%Kc)_ z^6TdJq50fz6u+xl)BYH#-ik*{`^LH7JX-s)T(?>umgluU4>NK=|C0Nt*G&1yOMTaC zDi68*kI?dukk6|l<UF-eT3<4LLY`G_)$gp1(Q=J4?G}wydm0Yc{#;Z0BK|MqT`5;> zjP{Q)q6cbHZ;U5Yj@2IvMbGg)^Es)OEAL;U<-TfCAL#A;x>C++oAifB`~<$Q<<WA5 zxjr2&?G{NnafkA9>veN`Ueof`rM_`wWE@%3@2h=D)K5&*&Lp{A%JrxoU#*LrB+{?& zSB(6Un$Jm%oRFIPsD*M~QgdBWBR{0(`lRN*NliIPO}mhq`X)8<Kx*oZ)W`#=xgS!! zzW5vD_4?v>GTb)uK&sale}lYUU-~0?y}tMh<n{Vum&oh&)n5p6y(s-Ntd7j<)1-{^ z^HkeI{91-jdR*==Y5$E>ABHvU|9X5?##OYh%DKixYWjJs<L@MoE2*BYieHTTY3h?y z&#QVN62F!6s(fqwrPgzP{ant&xKqlFgD-X{A0KN!k3_FWp~`hQT;wj)ejBNO7OEW% zrGMkroA$(w*YoOlxGH`+pKCuCznuOe=Z&;}A|2P&<a?CU+$X8_GaZN5blg_cxKT~w zMD$<npJViX#~L}o|1squ)%MHBpCYG-i|F%2{eYy$zDWNFb33c!z?zN=YhvHSNb~^m zAng*V{jTQyRm#i#n{qL{k$zj1@dd*XZO^)vPyZf@on+i7^_Ga9rMzMXA}LoG>2<3b z&xo|WqTF7qU9O5A;`&<eQXbB)^&_8?*Lv4+bxrgu_DAG49IovsdVqG<`W8LG`L(@r zJ)+~9NbP=@`&~7yU$LuvPy0pB_l!Q^bFDXx|3%tgqFl};en9)G9+B}0_D<VF>`@r? z^n0<-N$wx!{Ymr~?WlT5{r^bD{fwWa-UwuxeiLi|Nz^~A=JB!NdLKIeu4*|jVA=N= zU#VQm^~h^`<o;Uj59xTSs`@|D^G3s^-Xc9N((zzb^dsfeewgc}e4Hlb;CgvEOgYJ$ z^VQUUCRP0^az@_B2dV0J(Qo8czf1j*SN$%20eP)Qkz4Z8o{7dU!u&o)NPmciOFKv6 z|4}ZLE76CHb4)pCA8Eg^uHPFWpI37^%j+}uAGKbqd7Lzl!|6Dj0FShb*bT0u@~`8U zT0Sn%<2G8(P~%jg^jpFi=Kk@MrCg!v^HAj^6hAAhs(e;ezN)HssydEDk%?Z`{t@Xo zHWK|FM%qsz=_lm1-g3Fh$LDhXYE8~hYU+u8V%nQj>sR~)#(Da8@dwCjJ!qV#CVn=b z>-WXK!9O<lOKQGP{7%Xfiyp?Tn|5d1C;nVr+q16m^t!Ywe>d_)DtfT4eruxb8;y{D zlIZv&$@Qj=rxNueLmfXP66dEq<a{-eTk1jdLZba8%;itV^HDBGxql#Xg#2p1%k_(1 zFH*lXQvWj&|DWSUPOJI&DAzOUuQM<-?Mwhz<WK$Sn)=f<xgO;;^~w3Poy9+{h50y9 z#vSC%`AF3-6aR#~_FJ(F<h9?5-5{_1R`q(A*SCyQ(4%tQs@k8b)<adxQ&oRE%I&n| z8L#Sn$#u!AJn40kyk2BpigBXer;ZCl88^{>(jTJSztZ?vq~D81N_$pwJE(qHHMfJg z-yr&va;W@DKgRs3{A#>8R5=TEoK?;3oy?CB4p)7x^Cn?F52JCeQ2cYowc3wW&s9}# zR8`)px!%g-MH*+S=J6#RKUa0!9H~DQ<$jTTpYp4GkI?!Rx#V-z@8XBhKlSgq9TGjm z=UN}R9ap^?>i99zxK1q}m*wNE{CXqv`jGxn&C4V6V2pQF4y2vPtG?BEWtfl0b>6Nf z^L0#X$$4Y-*TTGitA44<{3Lc=<TKRqOjXK-+~(Icf5-glbwwXD&eeLWYrW)gC$%$) z_TNP6g>azs*F^Psq~qa4{g*`jf+W}b5?>$;t$us%cd6cqWPD4!L+!QnWAa9CP~KcF z^*(a{PUm4mohOcUo+#GwaoiIJ7X1^eUW;{{8OwYy!!NN{v5sTwDz|YyKdSy`UF4r> z8B<PDk*m7Ki|X2+>KZqyYrm>Xf2h`V-npJ%C!dGN;|MwrUDxq-UHn$QC+$|x`=N~2 z!&vQPtmE1^?>~C~iR#rv>o@6%KN~&B@KM?$$^BX#pCl3wV|bYRDW={zUixvO{zRg7 zI8pyS(Rf6X&u^(*ChBh|+Wv{mU*bngdnY;$OmyBS$^Cqd`zC7V6OI2S>h~r(Kb)xj zPc$x^s2`Bz^`PyX=)7y9?VZTHDPeqR|3udrB&u%{U0)EYJco6aqrCp}_R@6+b%{gZ z7E3&-uH)sp&X3n+oX6iyy^~6RtE-<^*SJ_+^+jFdV|CRVb&ZqNMb9uTpyyG2QrC40 zb&coLb^StJ<2rR+$55AcsMaO^L)byaQL)C&Vr{oLk1L5hGOZ)+6{-D;)Q^o+KSXM$ z>pHJq*Ku}T<HU8f?{%H$uIu==uJRP;>nrkd<?=7{Qp|6P9;oa5YF)=`b)9Fe>$t71 z^R0CqztuJFSl2jOUG+&_<7su#8@RvfcZq%>ul1|*mUW#^uj@FvuJh`3i5oDVYTA?f zRDB}ja>6`1PSWvboX@}N{6$^;)w;$d>#DEoGM*tkA^N$l^Hg;mztlC3Ti5xFy85qm zo!6-Ad`4Zz@v)9u<1s47vQ7ZMRO?mazjYn2$9a5J?wjL855&68C)V{5vHAnC&KJe% zSJ!nOqON{-UB*$wNkxy>b$+6*{%Bp}$93@!t96Y_*EK#^SO2=M@yNQysq5<h#u~qg zH9j8eJYB5g(OBny>N<~5SO2W8^9gnJ)9N~}P*;Dgu5pjL`fYWMZ`Z}1pr5t=biGks z><gc(JgMJZ*Y!trjhoiRJ~B+u`skVWQMnU)Ok6|tuf`b?krTpw`nlS{MD13R&x@*m zn5aLO==eL4_+yxepTT)l4{N(7x(+DO@qVJ?xJ2ho6RqcAxt`VWb)xa&MCVBpwX=!F z(-LiuMCaub^{113zEsDhNgj_?eU#`pHBmo1(YRZpdLq$&mgqP(k?|^iliII*K2*mu zN$ywW^{V5LMC0&@j%yN)`zPvGCF*x1I^U4!d{UxuuSE4_BJnro<&0bqb`}2(2hEIg zLS63>YQ2P7PgT)JgmX-LkQzUd_@lWG^2To?HTs3mjlLmou0uXQUXRP;mr{=$XRgn2 zM!v`!y-!}hFXbnne{S>ydE<YOn)@Kl%ctcMd&>Fp<3%ntPR_C$sRxad;|Gc#q49G5 zuJw|~mDDb4+?>CQUe&lcd2@e^*UWiHO*u%-{gaw<keYJWbR0}-%0X)GkJOZt)Lfs` z+#ji_A5wE&Qd8fgdR@^=<n{XbxHBJ*=Hp%&AFymq^gkV0+F$G#(;On7J@v10%LH(C zT%_@OQn4qY#1Z(Ow6m@cV|kPQUHzs|>@(j}xf8ogUazm?^iaynItEid%Bl4ueg}E2 zAMrcLYyF7dL0;QM{l74u2UWcgsvocN#{8V#ulj2}@+y7-(?%+H;vbM#c@uk&o1t<m z;}VvCn(}d9Du>c<DVMgFjIYQWc_r2Ml5re)l@}SmkXLyTd(Cu&_6wPRraY$oS<aH* zM}Gf#xwRY;=jM3r58_YLZhD;PZSvY5#om$E`xU)SUd|t>ev8y!*7+&gUC%FaMSW;{ z=krPVddqxXN&Fn{OXXek8F`h9-0#wMjYW<rhw2lx6Fu`GJ^fhgL*h{^$Cdla<L|k= z=JNr%9*F50Qy;`Tv>w&o<@zkj<v#CEV#g@2wy%t1$!q(H-6C(wL#pj7aZmEvzGAm) z+V4nBIY?D+={&#2nW$IOKS))NslUsFN7hgIp1EI^37YmG9pxTY#p{)BtkXW+tu((b zTO=>y#d0Kb_;5|kNeQl~(hy}!KEIfDLLJnE;xy9<RH>>_(8Y)mr8de$gKXrbR2n>` z5zKk{T!e3!I;U+KjBL{&aGOs0v`M2>+cYTLrcO{>9t@OnGnXZm73m<PO^s-zPF$Nf zha6|h!<q(DPEsR`)kK3vkvaj9I(=>G9JcA=fVO-vqQV>Hlfb%2woQbT>u7yyXHw%w zy{ZxsBfxRm3B?GIH^ND3gpO1@gD6k(YHXz55r+IYsaG`;9H+`5S1#f-aD3jbru~@H zG4;mhD#W=_*Fi~}IIB$Z=jT(UD4m1v>-D8`keBxF=_ER+Y7^&&zvtIA=c~4<^V^mO zNps_$D-UVkTHZ<3xUmLPq&XDhL0;=sj0btGS1}&swO&P8lh=9`rA%JyRpf{3<>%9S z6=lwGT2JEKaGdrJ=@;b9bx2idNc)r5dKdX1pPxsJt1c$z^Sm6YJY>+sT5@eC861%} z<s(()DuW~PrhQ1Y9O49%H}^}b<;W-PWRif-Re6b1O<u1rgFWW7_4+c{Bd^z&AP{-2 z2N?vC*Xv8LgS=i}f~(}!I7*O}yk1{|YvlF%G8n6c(vL}ve39z)We`c;T%Z0W^;y-) z39V;7H}y&^SK3p9=;ZUyMJ_eS&gZ6mFnTISGI+)~iQMUA5^K|>KU7tp>5okK+#JVs zMOjC>I4GYyh(%6O-bPNavF1Fi0nzK~;62LgPZyCyy7(&6AT=G+IC-%KgH`T0kGUUW zg{GeHjio%X2ESrmEFWu7E!JRFtczk}T}&KnP%q9GOK6ZP)<u)CE}n^XF+r?BvRH$6 zu{w*f2IXRP#$pYg#p=w(>a4~(Xo=Oih;{H8>*5+Uj?`OjB#oS4PBfMK;x{|ePP!PM zyr~CLaTf3n#p%`d)5ZAwUG<sVFL~|%Qg4{GyuIaoy0`@Es&bW25~^O*MdZxUNO?!p zhh^4xq?)6ubT6i`<g)pgY(#yy946XSrzkm0v{qGID9&nHpEUn^{&%^{5#r)Rm``)J zBP1lt@hZilWywozRK-eQq(}G4h6I#)==gmPooF%?-DFmh@-O`ZH?cIsoh;38J4-X% zlgY^Um-z#Cx9m})mS(D>lBF4LW@&~yTAJauOh)ce-TM&|UM6~IdK9VD<uK8x<fX-i zsm2(l8h@B*TK+C|J6t7gm}o?zkfJGu<)7<tc9;%h$LO$PjFw}V^lWBPMRJDanpq`v zxD3niRz*UFOPGjNCekJ&)V&?95<6Tac1*5$BpihUCifHT@IiYftCr<F+M}XaEn%z{ zt*Z5)!(>(~%Kd4NBro+4>-}q3E7E0vq}mhI6RT=?K&Oc)m&jgKG&HiP8do$t!z}5! zV`P~+v+imU^*9aVGdnEh&~QF^k#!C0lb3R9n4P?wCsECx=x{dC_D{5(6S1hQ5|;Z& z#G;aydeiU|dA+_^RPuU#v8d$r`eISZ>-EK=l9&6`<<tBsO4>Wo_D<B(O4L(IwA~Yx zgG9oDd{4`v)5p4ehhLS6M^P0GOI|z)9T%|r%v_Ju^nFsXq*WOnk~i%{YVMoV$SbK_ zFH#*5sqTqX_l(x%h1H(a)C=D;@<?jxg;dKIx0&%r97<A^B$6bSB$34EN(v&m5t59Q zL<T+aXvxW7CLSv}y+TcLb(17=y+p2;$n_GrULw~^<a&u*FOlmday=P;a2Hyu5;WmA zbXu#rII^mXBlX7>{9S8Rf+OU$R%M(+-e_)8tu+ZAlGj?3U;}w0O{98#8Q-vGPp_|w zhpM`GNc|uFuGg2KH+j9jjBCj2^>y)(`bT`O*B3vCyk1`iqg7p`z_`=+AE7Q1tLkF? zss?-2?y;D_+#kU>BTd9A%zcnI*JrFNeoy|RJhgEA_(hM;7ei<;x2i$SYQES+7v)ve zud0f+=ep*8iKXcI^B}hjvRTAqdI7OgxsON}!PImTwI-7iOj=8MBk83~PMP{4#%83P zyy@NiHp$dCsa!YGU|b~Q6=G+4oGw0!q`f$geouN8i(yTD<9=%W$oPiu>;33BJyO3f z$``k2P&v|I3<Wps6N#m#zVmj_a*Ot%{Ca&2zE?$S5_8b{(V&fL0oqH;r;9YAyxuj~ zU+t+MZD$R3MEN2C4Q5BW*eL3mjMwsN(7URWfmPAw)QizhluPSZ{YT82+@}POXwSUd za=($bf0Qq3(aF@B{<yFv?MHQ)b|p3UM{3H$Z$h+w^v6q4F5hBRYWZTbNGBDm>UT#v zd0W-Vuc}xb<XX#@2g#-WM~{%}aXxcBQoUcXqD+b#D@6=7Pera1UF4dmTqhblN>siR z{c%&GezjU3Vzg2YwKn9XUK3q3ny7pyy67>{MSw}Z*h}R+(RNH!?h^4x`3;`PS)xI% zM1xj|#+%eS6H_&Mj8yueT3_<g&k`N)BsxAybP-0Pi&_(1<d^8;z(f}XCfaWk?T3jj z-cItxYbq~^F0M*++^1HV`jGlpt4m(wDUTN=y2vQeMMjCnp%RtzMEhN$i&7JnzeMFa z(Q#g)a-v=e_pR-siyspm_a*wHn?%QxiFi>={-`|YI5^S8;fXG4OmtCWqVkle9!&DZ zH@dhdQ9TuF`^NcXxh`6WwH;z@hgjPo*2N6!B{HWVdOOz1wK!kQpx24T>tym;<Tuv- z9c%r?x=1V5dW>}vA=V&&tdj|`)=RAQ5^KH0S}(CqipBY)wA!gyf20xX;;*=8ak0o> zto0PD9L1^^>QX)?i%or#8u=hK_sefP^3RQ&@VV%dy0i~@(JOV;ud(XADA(sI7m-fx zMRFhf)=Kq|dX2jHkKbGwIUzOIB{h13)Rc?V^k-62K2oD6Nlp1kP5&e{<s>!jL2AlL zYV<d$USH&bwQ#1MNcH+sALRA=8rQ69TvHc^@prwx#x->j7N6_&HO{C%M&fh5zQk?O z!$wb#>h(4Lr#~LybG>hk`&D(cQ_bUkVi$<xNICRJEyT0sy!zu}!q{>j`ePW5m-6Y4 zQ^`wx=#Px~zLZlJa}$q{^64UO@_Ih;!^lg!=;Cei(jK~ao4j66^bvV!4_(~KWRHGd z{4Mf&JstI8=*?)DRlz15A%4K<Q8Fs1=XR|pj*`dG^Z34ww!%D~Eq)5|50NungvD?F zR4#Hmtcx_F+`i>@P$&B%_4^`SG!f<giT2M(`(vbfCDO%akuE-q^v5ny{-YSx|53hZ zTl;a8FH+Y<fl<DAQ0+vN+f}s}QSKLNe~(lzM!JYC(!~yuE>ek9uSL0EsCq5ZMRAep z;Yjsxl*jM%$2(EJh(xa+tKS$)zhMqm^nav!HImUIaXr({q+%~4)tiyD6LCbbPm$Wq zNbPE*b~VyP6Ok^Ch-8$?9GPiHQt{U!^|K?jKN>|Kj%ehNRE~?)-;Z^2Emr>|)*pq& zx_CC$#ecCb`qL-{;Z!Matcw6+9S6kfU&i_)-dO#aSpAt;C!b?ow5L%E<~-G&=%jG0 ziveO?yb|l=f2@=Lu`XJPb@DS-KQdOoAl6C9SQn$jI*A_ZqLWw`#m726iFJ`_tmBDT zC&6P~L=x*HW2}?!u`U*gbv`ZDMI^D#Z^ikKL3KVWmhm2Qw;~6zjvr$6XJYlMV_oDO zi=N?lwZ}T1h;_UW>v$p7d97IepIH5&SjT&@E{2J9@q4V}hFI(mYo(1mlA3an8u=v^ zd)OxZpS;-1Hi?Rn7rWUe@<`su1F6wpq^3Pd#csBV{E|2FT5XelK&sc(#b|B%BiJ_m z@oSqdx^B}S6}Rc)>o(Cx1f}$RqCfZzsh&?4O||KwsWx3S)h7Cb<FtG_UT712z~`bD zb)JK$me^IDx8PX=YJcQ<tT8k7M{2H1YU+>F)E}v_AEc)KNR9p{HT6eo&PQtMkJQ+8 zQd5tl=K7?j9vN4d?~|JElbY|7YJb%6Q>=@!V_hs2>-e}X`hrEB(k^wq@4B{AUHmW> zDH=V2U!<wFTV4H{y0$}I+o7)VgMrHI7K_#N{32hJ&)gTOxi3<4U!?hcnd^`@->>O> z45_JCQqil4j;9kH*CjfxOmuvm=%R!~fBcZheBtQfI?j)EF;}eP_E^UWv5uc&{ZT}$ z<EU824Y7`=Vx4b}bzBweI3m{ZRjiA-+e8l|-`bDFzL3{`q>C=w#D4O*X-87+N8+!O z*Zw4ONM7o-P1~g{_cL|zV_QCNp3j4;|JtUDHrvF$(Qk|#k!nBA#~td|ChE5*>bEEA zw<qG)vF1qniS`%T$J{5Wk#|yapQIwMRhefZFY>DMFXTlo^hdtrMJ}phugHsB=#O=4 z8mA&vxe)tBUav3qi@aW6>=$|IFI9=MlQ;EDs@E64i@aW6>==2yzW8HAXH9=6)$2=t zC$HC+{!U)6Fa4doUSIk<dA)u#y4ODGzSCz6owUz>%-F1)P1{VEJaPKOLnj|Pbo@cR zviyw6(<V$Lv)cHB4?J++eaDZVc+iaLllMPh2A{1w{;27br%jtYe#X9&r;+c|lb<}E zY`>`oPe1se$%o39rcIobeJ7xY;)#qNdf51EuMMlv-EFN*YFX@3X8ske<JhO8$Y)#g zDp;+&3f2z13hd<ZHQx8;Rj^LuRj|IxtH3U2vv_avDp(7771-<Sw^o7uJ3QWd6?*aB zztEre^$Y9szDZ#d-iH;2@xE<gTi$0BX7GM&;aJ{h7H0B3yD*#giwhU?et+S9-XAPH z$ou1k$9eyG;pe=+S$LE8cM9)V1$Kg1llKAatX5z@hpl)YQyjzlSBhWZeV^h!ydPgY zf%lV&C-Htp@eJPQ73cAOE&Hw&*bm`W-WRaHT7lgU9^n1)BG+PXgBN&zv&gmBz2I$& z?MX`196Jb5e)bI5j`#KwJ9e=Lz+Su`#C}!<p67oY?{iCYd7oF}Dm;asoxOMh|Mk2- zSmIuIjy`w6Q}mzV{dxA_Ebw&v?EHT%{gwB>mp<UVyVT7-Uu=lMJCDWYy^l?c*{j;C z^3K!mc^_bN*F5{ah4-y&?%m$T-iG%t*<a#)qCJuKqwS-4|F->Y-e=jgmwkhM1Mj!k zxA4xB;Ca8pzJvFB?0a~>&%TfMg*L63Jp-QiMfM`ze_{WE_h)R%%v0Z~ne0jLR>AQc z+M1_<|I#Y(Z0^;0Uzc5h3p^ovPu|D5<9OfOMRIsXc7u1GjBS;8GWLPIAL`DqO5S1K zVOGJL<xw`CXM8^I*Lc@h1)dqZnD@8KZ(Ajv9Qzlm<g+OZ@2mN{@y=6NdEe9DoA-VF zsl1=!pU(UD{2O?`ot^wj*%MB!0#7)-oA-r2ca%Nbl=q+bKS5S4L%`;UtyZzE{-xcl zKGXJ{aS;2b*;$XEhh+QK6s+R-^w8AmGj-zhgRFk~t?YCD!p3wH%vlOnnKZDR?3;Yv z3)myino~I0`XRfyEGc}4eI~XoEM|{|2MTYOp0w93EOB}{y^5{b6TOS0c{cTzieq_J z^mmJUc`ufo;;}rxcv$gL&QpfqoPJgzn4Xo)NoM!`zyF#uL0+?muISbK#MyI~f9=)W z>npv_&cF4U(gmM%bnkh+@9O>JoCQ-K{^+as8*>&c`|8u5<N8ePGkg9{{O{Ah`kXZN zzNz>1xv|ev*>}wU`QKK>l{c7r-#<Icz+>~*YSU)Dy4snmojL1Od1e1w?fTXJ+;3XH z%T^ztuX+6|Q|9-dxBOZAFIqnTak(k;m#_2Bn(N<{zyA3*=S*0meT_+LOqyNH;5B#l z{56hTvupPJwclI|*12T%`~eHrJ#zN^fyZWi#upFq)z`5TZy$L2z(wngT(5u6Yvi0h z>+QMTo^$$Sui43Z)7JZA_L`lvJh$E-TlQ#quI0Je$%<d=k6Y1e&aNMQtv@CEY~#Kk z{mYzPcY18OPlsx5=p6H!b<js&a~5Qu$@@qDy6h)&=4Y>29g=>m>@V2#pv~+#3pRUw zsWi~Xcn#^5m137uq%8mJeaPN(hK9E<d(^Csy|&I?;S)POw$rrm)t#m-|H|*2zdknW zpz6^(P0K%7nogNNW&X%Js`I10NA5Od{?tBG=EsBL)039u>Fh7VrVZ~u{M8XxW`E26 zn!U2)vt0HXHDJ`6^1nGP%e{7;vtY}Iw;Y%C;w|TH`S4anxp&NbWxKz%-6grHeU{cp z&;Mx2o%$~==}yz9_W4j7a>NwsedmpL-dJniw8KC9|IR1Png7+7{@GE>{C((NTQ8F{ z*ZAs^UH02$zpVDM^pjuhm+i7&`@PvQ+55*zhtbZv94mFZ%PErP@4LLb`{7gj<TCfI zUgM6=|Nm*yaZPz4%~QrbIcEy*i<f_8pY65v<i%OK>}y=t(i{?oMKEOV@>;Ohg1!6h zy<cO{e(%nnJH9WRIPvz0_U!qy=TE$LVvy<0ISXbd6VIM_EiFrZ%~`<z=JS73kLaMG zd~*B5KWF(#egB2mxswJ?8aZjqq%m`bX74L_?J()wNtb+z*RA{Cwf}>8OCNB_<VQYD z-Dby6eqr*PpYgSL%CSg7YxbIQIRD?3KG4Fsr1y{`pM93mD*u~u;v9NjR)_g}CjGp2 z<xE5QY$$CtVd{XX`%S$Xi}!G5<>yQ|^pr!-p8DL>_ouCT#9N1^S?<tzhhBZ?jfWk3 z*eQqaaKx@hTyn&Xb0!>d?-5TN@vM3M=7_hBcz4c%Bfm6r;mn0|cAdHKs3}KXdF<R{ z=Vs|qC-J)Ss3k`&IeMo#ug-b(=q7)ef9#EhhmU=7=5OS6!ho3zPZ+})$amQJr`Z|h zvzZG!Ht49F_T*_#p8W2~@1A0xy8G<8r|y2*lc$c$KFeN6P4TSLPkUjeJ@d@delzn- z(&%%44Vf9t9J0c*p2-!LzS_+HPn>xg>HL}3E_)yO9cA{nM|X+;vtZ_1GvAssfzKzL zb^2K^AWa=vTh4lE&aPSiFg>G4E6v9y(jWL=<`rh?{LB*aSs%WO@5A5cXW!)2LR(Ma z|C#J$JvFmqA_IAv9hV)G^|4p+J5Pk|@MCwderRpWIPX!$eUJ0H-TJBZjP)bym)6VH zeby`1Yt|!X?DsfhzhkT?3bTt}EsV|f%qx7ro_Tu}x)_PgVpqR8>?GG(e3HH3jxsyJ zom+grWEan8&$q3MS1_g;Q+$~{*rpX<HM^$uX4kZf+5PQO_A(p82<kreWqXhv$iBt? zW6!a>+7d=g2bn#@W@O`~(&3Dk?9vh0c&T(WJBalw9m5!D=hCU{%XM7oHv0tomeSAI z`|45l7kh%etOjJ`8GA#<GcVbL*um;mdoa6Kz0SU2Z?J#W5caS7J-dhff!7x7T(yK< z#Qw^jRaJJXD%uG<RXO%BCvaA>hqFu7D)vZrr|NHS<FNab-OBD$o7!X8e`<(5mi?!; zu-lx-iR_w_IEh_n531qz_UuA6(%vE4hsxfOeW<pvcV;K5?d&gQdr{e6&i10Rzsg=z zJKEXaQ@h%`vHKMJk7fH!*}Jpf)FgWkXR<Td-kaT}rrZ0lx6}-KKX#Wo+#c_+vy?r7 zou$sOC$V$XEc*y{jJm`=F54T*KAk<GZnD42PEfblXR*7}6ZYBc==6;JLw0m}&Ys17 zPA}WDoj2Ux_POl4w3>Y{J1wnkFJ!N!0rmszwlvs&kX@CA+l$yYX*>J5Y}X|FdG<=$ z*?y6IlG^Q;*qi7A`&aBabhG^``w0Ei{#~{^kiFP_!+pd4bGHAF{g-S{9Qz$_x;Nc^ zk6mz%wclqCoDTc1-VeMV*neX`o2LDD_OiLu{=mD*yUG3sd)3@&cX<oFg?2Z4&b;DS z-fP~Qj_1AYz3ue&{^Gss^zqnL##uGnQ^x7Xeli88KRe2loHf`}#&_0aH<^{3b+cV$ zoPq2jvzF7s{xR!18?krHhE9cjV+K1z*fnMoXA5?R8R>-V{<5=EWe1mOPQ)%O-*aNi zcG|i-oeR1<UG@-n*{j|v6>qQ_JvleaIfryNo$1|uIj-6B7svc1?e5^Oo&2>U`>U0G znmQ?;$fw!4o7PI!1N5o~tq$v9D=l7UHL~v(pR+oO&s&YsIE(LQ*X*9*zSlj&TIbUo z?abvm7sBOmEnElJ1J`#qw>sRRumyxrg$QCuU>FRCHcIp<%T_GA?-kk9$9l+W6xP!_ zc!Kn&R$4mIYIrFeVl}MQteMo}GWT1E89wLtd@KLI4cO_ucrW}I?gMsS&+DT|OXPJ@ zSe21{gT0R%))*KIZBT<cY!6?9-Qer6JA4E7fN#Q{@GTezd%@nY4`i=>VLuoT6JR1t zg8ktDm<&^3Dold|;UG8|xK}H?*XeL5%z(q-a5w^vgrng*a1xvhr@*Oj8k`PIxCrLK z#c&B+3YWp<FdwdfE8!}*8m@tBf!egL2c*cl5pIH;;TE_RZiCz5M{oz+33tKWumJ9X zd*R1$AKVWM;bHg*JOV$3Mes9t3Vs1k!)x$cWS|%HhCZ+|^o3PmRag!B!RpW-)_^r( zEm#}YfdQ~C3<Tt<&;skj2CyLvf{kEf7z`EI1U7}uU<hmuLtzUDp$ZWsFbsyn2p9>Y zU`yBvwuWtB1{?;51MOCz-3mtmZAbjFQ8*5chZCR!z6~b=?OLE+i3BwYv}=KOEzquo zS@Z|aU*z1yZDBiTh0$;Zd>=aD2XH2w1!uz?I0w#!^Wc2A0OrDl(1aU+HX@qYC_V@e z0qs?!y^6F~@lkjjo&egeNZS>E20w?Vfc7gs4bQ-{@Jo0Oo`)CUMR*B*1;2*hz$@@7 zyaulW?OuEXeuvhgPm~UXCxL4a`D~Qlfp_5p_y=@Z4Z8p~_^=WnH#Rau<g;O~0?3NJ zI`oJ2(6vRV!A)pH>d583+{b`>bGbK{dvhs+$KO55>``WqGJ7ur<@PA2m;L<@@V?b3 z?+ldH@{5;H@=QA)gRVZYyVW_rJ9Xxv*>B0TdiS&F-e)aOkLp;_QC8o=FT0zC=ivo- zk?-!nIVN+iW4ha&Gtm0ycXc`!k!I(;rn>`cF#)SGp**j<qkM69hc$48XV{Um?ACLp z)H#j+&xHShGbrs3NoT<vI1jRtH~IV`$gecFJ9RIFCfowI!foAY&z1YE@XDW1_MUI9 zyMk|aIy-WgyMdgclQMTw=1$7oNtv6Jxsx(?Qsz#|OwGWuH8U5Rdm%L87Pu8|>+bBi z>iUJBQ^!xiFW_l-2A+ki4W5H#+M)0g`Cq}y@N0M-e%sx&i`qu}aP<i=8J2CU57nag zPIuFLx4Zd;m7+;0nv|mXp;9!do2Is9^CP8Lk<>J4fhH}Goh_5UCN0pU1)8)#Q(K@( z3p8ngCN0pU1)50BvRCSK=OX(T0-w_gO<JKzD=fYLPw5?Wo$tQab^fRICAxQ|ukC8} zD_%s8oClY|<!}XH?}}H!wQwC=4>#Z`(cg<V!!2+J+=YJm208=1llyR%;|%8hjJMXc zxW`ko&8N%wWY)e#r@OPb8Z^4o;t#u<#mBnai*J$sp}ScscBiEh9LN8UhZCR!z6~eB zci<#A8BT#yfx0hI_odT;`Y(MKz6bP#()XbgegJ2}S#UOR-4g9nnhms3=^Qv0XsZ%! zRl;*DT}(Ny>TWMx4cEZ6a08z|2oJ%-@Dq3h9)-u?ad-lL3X9-p^n+Wv+wEI{`?McN zR|UvSR#$D^E$FbNvXtiaM>+!zgTo<{uM;`;)5zEb-Hl9=KUTIX-R))S#@ck*R_Lkq z<GJG#x;xFCXLtG#|NjX*0!#0x^JDKI^V1JAz5H`4vDLn8N&hP^-610!?PsLR%Snro z6P1B--qu5xt3jl2h3);tOZsOod40K(XS(7)RZsj2df`L8=+oCu)_?z<HtjV1`r&2z z_M`m&F?bxFfS<x5_*r-7Kha)Usr#b!`<gNO4Elc7{|`4hU`5i{X$@MoO;;$1QkS2p zlq;yqPgkn%b$40=mMu}{rG9LwKAk>Xz9mH;E>{;4=Rp5Ah)|@~D(3pwSu9@%chbZ< zq|O%1O>fEn+vxGz^Z#Xg{V~+<iF|%K>G@cc3%Xl+&XU>BfBh`w%-%0ohBmI9U0<wF zXk|yFpLm3A&LN(DW{o<mmV(#aStx@K0jz}m=l+W|Xos)Co-hvff_>q5xU&2D>}(~! zyHVPvyJ5FbXJS9wmy7fqLya9rO`O>EGPTlSy!3h0&Mo+q&F+S^cERtu{@=dh^sdF! z!zW)UyVoZ7+T>oF+-sA2ZGMI;ryp~#OLhJiU4oCR?N46<6US4DX(w{fO4MdFjDfMx z1~sU|_V6{>4ZaS$!#7|L_$KTL--2<l7wirDKo$ww7xshkFaajQB-kGgfXOfgrouEh z5DtQaA$uJH)8SB<0f)iia0DC)N5gmEBsdvPfm7i$I31dB5zK>&;S#tME(2nN#F0|s zNGWlolsHmK94RG^loCfui6f=Nky7GFDRHEfI8sU+DJ71S5=TmjBc;TVQsPLdbqCxD zcfsAT0PcZ%;m2?v+z$(ZC?|2GlsHmK94RG^loCfui6f=Nky7GFDRHEfI8sU+DJ71S z5=TmjBc;TVQsPJ{aio+uQc4^tC61I5M@oq!rNog^;z%iRq?9;PN*pOAj+7EdN{J(- zg%(&JHh>Lb5Nre+!(gbuCa@`N218(T7z$fJ2vvw6fnhKlM!-lI1zW;aur+K0GvF{d z9B4P<NGWlolsHmK94RG^loCfui6f=Nky7GFDRHE<K)Vu0N{J(-#F0|sNGWlolsHma zXm*E%i{L3nD8x{RE2YGhQsPP}aiz4_3Zvn0{&oiG_n{Mh0B6Eka5l_=bKqP!56*`R zU@lw;P53v*VZ^0U;!-Jbsg$@>N?a->E|n6ON{LIQ#HCW=QfU?|CN7l{mr99CrNpID z;!-Jbsg$@>N?a->E|n6ON{LIQ#HCW=QYmq%l(<w%Tq-3ll@?!tR{<F!E|n6ON{LIQ z#HG?~Oh{}hB{r23n@WjIrNpMv(h|lJ?~uL=AHYAL%Swq)rNpOF;!`Q{sg(FsN_;9M zK9v%mN{LUU#HUi?Qz`MO)QmTaSf3i)M68{<CPtMKqe_WUrNpRGVpJ(Hs+1U2N{lKc zMwJqyN{LaW#HdnYR4Fm4lo(Y?j4CBYl@g=M<hvD}G8)FfSZD*HVgHSGQwKyq{~PTl z%De@HP=)`GqutACsV^?_-GPSr&xw4ChWTjpTQtmnZUlTIGfy@XJrhGVuaMb>T=!qh z|1W_{;iK`mw0Je2Ujxg><<jDfe70<C?pE@*K^CLClax3evwbPEeJQhjDYJcP@qR!L zXS&oHY0X5AW0-Aq6z0G=a4wt&=feds7cPV*Tm<uo2rggxhL(QC(L^czhog#}l)mwy z(l;o*L>TWRmC`pTeS^|BD1C#{Hz<7;eN1h~>aYXo4eJ7T<o?>~@H*fXSYmZB^N`K& zWHHUF;Tp(enp{1zS56;}>Q|WE&HckUa4wt&=feds7cK<i)p!Nme|LxNKmfgAUFI5o z-OY936<ESC{r_uCkT2@}zjLSbu1(-SeC`zKZb7<R@OeAGz<JdFENjBtB);nM^QcRR zd;KTRr8>*CR2Cbd#FkT>jb6`knNAB#Oz2r_qg=oLN9b$xv%}0fGgYio!RlE2Qt0DX zq_7gj9KE@*oO>~jJrb~>jHnkeie6+a=`hkQVJkQlmRi=7*{#f)rp#`o%x<O3Zlzd) z+?r<gC|eJdViB^M%j{8RO;cvNQf9hRN|#&H%#x<YlHNm1Y6aHR*ds<dPM?CssAQ2< z{ZOg1^(1+zv-J_5T<VLlQ<uP{u;M!qTekciG}WRmdk48iP4NdZi<;d<qbPT=Vyn9B zU94s9f?5BpUJLkvg$LkKU{!#TDJfw`kB5@gr_`m?Q@+j$sm#_Dm5yfC@OSWgSYqTM zMM`%=DzcW%uB86a3a_@bUb2?Wt}68)SIzvPMo+6{_q!tBM@y9XKv`e;*iwBFa@S!z zkmXzL^OZZP%e=QNmAOU#ROVJJXPMu*qMnj@ZcF9t4*s9Xmde#fdc&swRE{p<+gX2I z+QR>y<Yy(yB|ht~OXV=_X>la|BeEE)ESfb~#xq{A<TsY_-IwjFADv6jWpsJpz40%5 zzf0xU_^->{%}0CIhqchB)t$03K-%KtMkSd?olD2^s|7xJRFeDkxwL#_WRmqt6Y0ry z>8FvNWlNmv%}?7FA03Iv_(@8-y!0&97g)28NYB5jFGX{G+?778FV{3O`dRw;74%^l z14)a?7)b7WnLgWNz4Bi0&o%N#`|Zcs+!a}@?7lzV=H~tPvuN=Z_2~isRQs&hib)^+ zG_A5VzRI@HYJ8T|>f5shmxyc@k<X&zJ?n5;NoS(5xlYRL;)&uLv646S4&gh2HM6j0 zAug<0ycKSPyWw8=G293D!@ES}`z&*<Y!zvCE$NTM?6}TH);jmB7WR9-$+xt0OEsmg zj3y4>N_j@Z7#Is}Km_)G_3X_|JdV@3hbADy|4}nI4_H0x)t8yc$yNma@0`iW;!6o~ zIShuw2p9?fmu7Q5cJ}5Br2hNR3IE#6O+Lox_{bPzc^_|iZ|PS0<ZY09O_@iYX_;kX zTnouR01v|FGpm#R-r--G)iLq5Oh++G@{yUH&u3OA&Bg;xk?|9<;tU5?jGI{=#)}S~ z=I1)o6aGILn-h)JK%@QV{kCCwjrPC)Hx8oF{@?qZLp~oNW5G{a6a4X!`;Uv-6Foqq zeV)Ic$mgj)^Y15AqoruHkH-H#huELaQ)e2?M7+>opX=`_3cFiHbXW;&aKHr*%HTr) zD?u;l4Sir`=nJbr_S>%2pdYLb*?dJd1HLA#1#81PFaXwtfv_I5!1}NOYzTv3BiI-Q zLj|(+KAXa3Fa$P-p|AyHy0;1u#E^NH!(cd!fRQi?wuG%<YuE<1h3z1_!e|%+W1$Ud zP>1c|Yp@%99d?Irz#i~T*b}}5<6tk?8}@+)><jzBc$feaVG`^Q2f$>Q0#jic90&)& z!H~isFdYts8E_aJ4oAR|a5Q`ePJ)x+6gU-5gVUi27r{KZ7%qWJ;WD@!=ED_mC0qqp z!!>X%TnE>~4R9me1UJJia4Xyfx5JO%4!9HUg1cb>+ynQ*kKsPJ9~Qz=tzE$nt+1k~ zLWHh@A6j8WQH2#n6;>2gSW#55UW4CS6;>2gSW#4AMNx$nMHN;QRaj9}VMS4e6-5<R z6jfMJRAGLsQdk4lgtcI8SO*5cx-bydgBDmHHh>Lb5Nre+!(gbuCa@`N218(T7z$fJ z2vvw6fnhKlM!-lI1zW;aur+K0GvF{d9B4mQ6jfMJRAEI?g%w2=RuolOQB+|?QH2#n z6;>2g3MT^XTcCYeQB+|?QKdlpvZAO`m}OP)K`X2vs<48n!V01aD~KwrAgZu>sDck# z!3VA2gH~8QRAKc{h1Ek9;;R)_4^>z_RAKc{h1Ek9Ru5HJJyc=!P=(b)m7Xyr?S*!& z;4fD27b~m+suXE6RsmI51yo@bP=!@M6~==VRsmI51yo@bP=!@M6;=UNSOrvJ6;Op$ zKowR2Rfv~W@E0qr0;;eIsKP3s3afxBtOBYOe*>?;t3bQ63aC<~?O6p>VHHrtM5O2= z_==U%eO4<gfhw#7s<0BM!pusARX`P10aaK9RACiR#V&viKCA@D5UYSHtOBaA3aG*= zpbD#iDy#ykunMT+6rl#<8&>cQE35*lxQ_w%$ts`<tAHvl<zW?2#iRVJ0;+hFpH)B= zRsmI51yo@bP{sQLyl+)l1yo@bP{msPv(5w)?PezWlV^i@q8DYg`g87-S>}{k=9F3H zlv(DKS>}{k=9F1x)&&5+A!8mcWtKV3)}Li-r#OGgY;($NbINRU%4~DWY;($NbINRU zYR(*T=8!XooH^voA!iObbI6%P&Kz>)(5z~kN4oUPA!iObbI6%P&Kz>)kTZvzIkeXJ zSISJCWOEGJEcqvv`@gf~|NVtAv4O#W75-l~OP+p#v*cN9{6BxD{B!V>(dFVLf6gAV zwd^dpnHSG$)EZKFhZ$!ycJVPQ%;p(*u4Gth!bNZuTn*R2wQwC=4>!P#a1-1Nx4^A% z8{7^*f;-?&xC`!v1#l1C3qQ6(oAcRx&*pnJ*R~&r*R7CWcQpAWNA@d@W_eXBEUy7; zLIp<RExTG`nb%+DXlxZDeltUr-BY&8DIdKpKZo{dXF+5Rf_CWnrRUN(RkoTepSQ_k zRV$h~&VLQsb3Z+0H}kB^-l<h|_U1Q`<GUGmU|(9WTrF6x7A#i_ma7HJ)q>?}!E&`= zxmtQwJj~<&tWU{SJYZW|uv{%zt`;m;3zn+|%hiJAYQb`~;3c+Vb4SA%7z=H{i0r@X zd8vc{uIK&#&(kaNIl2|i@Gl>y?pWch{^uXL`PZL(k?HJl#G${q$W7W~lTPfu{&SDq z{8uaSjDi2I3;o~Y@qS@mZtksobiMnBSBhs^X{lGD&!6~@`&Gg?G}9NSm6kr6=L^tE z*0)UDK;r|Me_X=LG9KFwq&vcHus6Sa9S`#B<4&GOvFuZ4I#=-2nP&C`Hs=jiqy7%w zgx|wk@CW!K{0SDr+wf=j3oL<m;9YnR-iN=!-{9}?0sI5Hpu4->wV(h+D1i+QxZpt< zd|*|qyAt$*-q06TfmLB3tOqTyK5PIR!XVfPHip6Q8J_OOGdNcGlpW&p57GZwrSaif z&sJ;ntj_4Q++Q>Qep$ahUsaZkWwKR|_~({qFX%qaegvL|*Sb&h=E23?r<MD6pJtWt zqT7l8v}eyAVJ?6*mt3LUtnAdkm=&F}Lbk)ia9Ev#6g2p4f5<4M$tWdzre2F>=c|o- zRvKr&py<Sp@8q1F`0<_i@tyebo%r#c`0<_i@tu^QlM-|i+sgd+PW<;y{P#}$_fGux zPW<;y{P#}$_fE^>Y)#J6<Sb3j(&Q}7{0!z78%=WrPj2CQA3c&M>u_|F>ovJv7DH=t zy(ZUda=j+kYjV9N*K2aUW_HG|#f3R=4x9_;!TE3j%!Lb~2^Yb<uEn+k0rZA-dFIWp zyB2#L@Cq#HTFksak^3ltovqLY7x*Q-#V_G4ehF`J{}#W5w|W8fVD*8Op)afgs{&8T zvHHR4&>z--HDN7S8`gmVKpk2GVLfPp^<e|p5C*|UurUmV3Ty(K!e%f8Hix0G1%yzA z2x3TJ7z~FIFcL<=mar9U4coxBupRBYf?xY19}U_oleq@%)u3e?v}}WxZP2o;Foq^v z1oPm2;-3rQ0eF*h{vO_fKfoX1Pp}x?hCjn!0J$mM2hZ`e0OF#wd4o1@(B=($fQg+p z&|(d=Si_mv)pRC7$`b<)f$3c@BNGi|qJc~_kO`jax9pQ4KJrwEZ+E?1%AOJQ2~WyF zk6NW)aLg~^c_eSEu9xktyZ5lS>FTt%h3%jfM#C5w3vEz?I&2R+z>csJ?96C)0!-xp zlVE>104Bo}m<rS2KsX2vh7=Bg>2N5_fWzQ$ID#@AN$ng(xsE1(JZ0<P|3BpavtTyN zfpg$oI1hNvntcJxg$tnx7r{KZ7%qWJ;WD@!=ED_mC0qqp!!>Xn8iihIp65WXbXEa$ z6B;Mwxkw>)C&ccAwAA&q)b%{?q3_2$X(V)x=h<&3@VySwZ<C&Ao|hEzOs<e;a)msT zE5ts9Jd-QrnOq^y<O<PdA=<3fK|5fh8rY}?HmZS*YG9)p*r*0Js)3DaV51t?s0KEw zfsJZlqZ-(#1~#gJjcQ<{8rY}?HmZS*YG9)p*r*0Js)3DaV51t?s0KEwfsJZlqZ-(# z1~#gJjcQ<{8rY}?HmZS*YG9)p*r*0Js)3DaV51t?s0KEwfsJZlqZ-(#1~#hU4uXwf zV;Bs?`+2Td=wi=#u2{%(#X_Dd7V_LDG!T8G9VzNWiaKrP5S$@c1Zuz<fSh(Br=9jz z^pvBJiw^2$S^J;aeV)nXpf#!A_D^B|&9nGE)($p!s#k*^(vAc+?W5u2B_b<H3lh<S zM6@iG2%g7r2xQOYm_dmSgTvt%jyb;jNF?H6N_!*{@h~N}C~*rC(Sk&@AQ3G{L<<tp zf<&|+5iLkWi*o_G;Ek?(o!`Nm@OyX*{s4c3Kfz*n8~zM`fhF(`ybJHa`|wxz8~hzU zfPX+2ba&nBT2O!@l)wfDT=1X_J_N85^n%{d7gm8)VIZsrEwDap02{&}*a$X;!9WZR zX=p(jT9Aeoq@e|AXh9m<*>wgPvo^>)KD$J#yR-pz$mS_@_z~^)A~eI(%;UUAi({*t z9l91dJHl>o4Dh@#>No3csdF0nnUFnw<$p-ca|lUi!5lab<f)Gx?%b|L?uF2VTi{l> zt!oj|`BzGj>4G+{Hj%SV0z3$-SmJq-%%WXy_0gy0eDZT{GFkob6LLK4OAGcTiy>w) zVKYbNaL>-F$bX4zE)%;fJxd)N!C991R7>()cVGHOlU;?LBc|Lhd$%WY^d#6H4uHup z1*XC@I1mnkgMlac*oVM$I22|8&w8`7XT4p(Onep{ZO2BnTiFwmd&<Pso7QN0X9Ek; zX>Y?fwuSAW6-L7t7z=#Mu0b8P2c9x#?+81=QQfU*dHOzH3s2OtXYqf0Hyhv0#&@&v z-R$!K-_54xZG1Ny-_6E%v+>>R%y+vOE`dwoGPoS(!xeBPTm@IdHPjy(j{0rTf0sV# zE_;^HX)qIbVxPzgDNp2E_KAteZWGyUBD>2yArWby&P`p7pe~x+Pm?<9q|Q1Wo<HjR z4&H=pRpDE}^GKaP!k=I<ybXVbzd-gYBxW)kepT$e2brAx75)Z)hY#Q%!1Gj{Zr1Tw zPyl8w+!EN}fD0a!!G{1=f?m)Y`ob!(Dhvetf42qJhYesu7z7)^#xNL<e#dMah7`6M zImqvDB3fz^q}Z=RAoI@gk+ES-Y*-T;*2IQ2v0+VYSQ8u8#D+DoVNGmU6C2jVhBdKa zO>9^b8`i{zHL+n$Y*-T;*2IQ2v0+VYSkvi(Zd$;C0u-SHHekQ8VNGmU6C2jVhBdKa zO>9^b8`i{zHL+n$Y*-T;*2IQ2v0+VYSQ8u8#D+DoVNG`sYy=wvKEP)gXIZxC56E;b z16B$7=xcg!AMK}oS+$gncxLm<yE$+UoD1i{`EUWug$tnx7r{Jh(T7Gv=JyJ=V=dx$ z=ZpB=`67OIzKFJIEY}t%QeX5Q`YW>%)*`P1UV$a7oXJ)Kbf9;ird)pM!tT@1av`l5 zat4dmv}jGsv?gl;oFAc`|JC1=eEMH6T);>rdt#H!F%e(jSz`ZYbS7H?XRZ40o+bY! z5YHCP>_JLDI0U9c_T02WX15rNW@BSYpN)gFZ&)5j-pr9VbL7p=#oddY%eoi4?^+wU z@3C&prN%p`@eYn^a#WL}(7XI*{~4=@4Jv^RPG*n5gEIIKz)H{ydP5&r8T!I1uqvzu z{a|(I4{N}huokQh>%ah57Y4$5&;skj2CyLvf{kEf7z`EI1U7}uU<hmuLtzUDp$ZYi zkiak)4kKVBjDjs;E7%&gfo);Cu0;ip5p5ZKU_@J33ErVb-i7zzefTT<4gL-vz(1f1 zx|ylApa4ZEfeoyQDZ1c68GPW03B{G57x3hSVjox;`ob!(Dy#;pLw{HU)`YcSZCD2w z#}!!vQyd8EK?|%88^DGz2sVO^f#>KIE3gS{3Y)<Y*c^t!77#)eB8VY@VK5v<z(^Pc zTf!I^3ye^THK@b(z>}zpJV&~?6YLCMf-l2Y;H%IMyTGpSHP{Wl4!grQU=R2v><Qn3 zaj+K<aVzcv4cHg<gYhr{@Q#XjN5%c&0GJF@U@A<51K}Vz7*aR{ro*8y0}g}3;RrYq zj)J3sr^^+Oh2sGKu6Poh45z@U!1J+-JRiGwI`E9_;&<VDa4{SBT>_WF)o=~m2zRo{ zV1dzZ5%4ifHaOsd2W9XffR&&Z^oBmLGW3O2U{zQR`oZeZAJ%|1VJ%o2)&b<BgnX2c zj}r1xLOx2!M+x~TAs?kdun}wwgP{VOz^1Sn41vu7`6wYDCFG-oe3X!n67o?(K1zEr zQo<US_JIcM3;O~7M~QJ&X(CL5{ow$Z3{zk#+}E|J^i<a(oAU8A!_=m1Hf6IZn@!nl z%4SnGo3h!I&8BQNWwR-pP1$V9W>Yqsve}f)rffE4vniWR*=%IjMs{sv*G6`2WEcCG z;xDH7iz)tMioclRFQ)j5DgI)LznI!JsKfTK1MCPp!Ok!NCc-4x9}a-YFa@T<G&m5D zH$2=F4>!fbP4RG3JlqryH^swE@o-bdc`5!{YM%gS!PzhiX2Tpf2hN4_;C#3M=E8;0 zgo|JvTnv}MrEnQs4)fs(xDu{{tKk~BuKOwbQPM?t>OX@g;Uzq)U-Q{-;0<i8)BTj= zcQ0a`GtOD5`&Fkm`99W2XXWl^oxbE(A-@_Pc|Uyf)v*|Zu^?lx{1dwvr9j$+HG%1X zN6%PPp5A;s$9F(B{=A^86)(-gOSACOEW9)eFU`VBv+&X^yfh0h&B9Bw@X{>2Gz%}y z!b`L8(k#3*3op&WOSACOEW9)eFU`VB!(0KIY~rO^cxe`18pA2D0ms#K!GkjR5Wq^% z3wlFeSOr#vfv_I5!1}NOYzTv3BiI-Q!@JgZv8mtX37ct|K3S&kmFatB<hhJImyzA_ zm*IPOI}Scs)9J%V99zpb`|-_w##8J0lrh*cHkok{@zCsOyrwKNI{jaWA+{G5661aV z9wa{a5IhV&fk)s`cnp4SRz!w8?LOpb_hI1~cou#M&%yKX0=x(>!LQ(DV6|l7b@**} zSYoVFVw_UC2<E}Xa4B2{m&1Ix0<Hw&9Hr~w2DlM!f}7zMxD^<Gmu`n2!5wfX+y!^T z0=Nh6g&zauBi7k&7g@o%e7v)rICsOomhsW+)coGa$G)Wd!Fc|EEa_b8m$4l_(!Kad z_d1N@oNM7axE^jGF26agH59gh5ULPC3<(T_;m`&jil2J#u!{6uB2Re!<uzbU7>O(t z-FFL1-1iDgtdKTsB`(}bT)4GxE}RGF1Ccr6!mY%GTN$^vGH!1rF5F67xJjEfY11a{ z*2-AEm3C{T-CAk4R@$wVc59{GT4}dd+O3s#Yo*;<X}4C|t(A6brQKR-w^r(})vU2C zGA?$2r^^z7XeC~p%@edzZ&`h1^98LQ5i#!-SOV|ByR2XuV=?b!G4Di_znw1FZZYpf zl)s%Qe>+kBcB1_4METo^^0yP^Zzsy%PL#i$D1SRq{&u4L?L_(8Ei{X@I`oG%U`<#H z)`oRp0IUnlRT1THx0tK4n5(jwt0KzZPL#i$D1SRq{&tJGDvRfTTbsb9uo)2PBg)@y z4TUWrgepW3LjuEKIE;XiFbcMWtzc`|2DXLmc#8Oa@Dy`m?^-X>mtSHA<$j*)vJf7C z2jL-j7=8kez@zY(Wl`r=X^Cao17IuaH;Z@dW9qrxnLs+(%+0iBqmn}3;&W!rN5hO! zvfn2+SOFrrj-DJ6)isWN5XM<W*2P);mXbF<+mp}s%>HLtl+j{+H!V}3WeT)RftD%I zGM~2G*?1!_H=3%G(L*PrhfZsIr1&$<ej&vvQY^C@pHx#=KD0`TR!PwsDOw{%Yout6 z6s?h>HBz)jiq=Tc8Yx;MMQfyJjTEhsqBT;qMvB%*(HbdQBSmYZv_Q6sE}J1)Itz#P z&Sv6F{P$Y^|2izjGVaYNbssF<c+v@^lObhh@DP~JZ!<qMb4PDa>Fp`KJ*Bs&mRowC zGu_O$sBi3MyMy#OZOoAQrRSJKdEe?J^4LzV<0$KT`w`Mb^uDLF89|O(l{j$+=g4M1 z+U*JS%_HGx{?9qF<HRP3|1xjJxm!7RE6>etIAgHGZCxo(gkDU1iD(h+v^tumiH2#S zVV2G*UdaD{3D3g|@FHhi39H`+Rz@rK?S9Lt@XP)o-L|`DSBHC|WuXb!r=yE~Wx6`P zkh5CW3Fwt3dZme8!FmF57W7IJy+Z5&h_j$qn&_1#dZme8VO|&d0`V5~N)x@p%qHNm zpjVpcl_q+niC$@<SDNS*BLA>13<P2`=#?gVrHNi?qF0*el_q+niC$@<SNPQlYyz9Y zW-tW)PkV0yCq;4o|5sIab<gZBhuk;AA)+F#h=^RSh=>S?h<HTJcmzQXmoo-8xS}y? zkl=-9$bv+TqOSL?h$~)*M9~;U9dBdQ;1Qxm+5h{go}QgOAb5R~yng?g*XQZ#r>m-~ z>w2C#`sv+3Z?HQk277=$U{BB&>;?LPy@5!8eZT-P5DWr?!4R-7`Ok=v7FtdVEvJQ+ z(?ZK>U*B$*=%cj9D)1P1hVbjavj7cHqXB9(K%_@0(xVjVQHt~^MS7GXJxZwyrAUub z>O(2@p_KYiiu5Q&dXyqPN|7F=NRLvaM=5=vNZ=H0X)$xx<=(%jy9VU%PVGR=Wrr&D zX%%fHMcqjuMN;8A&Q@RwNm7a=DGdo9GOA86f=2g=+6uCg_@!->I1Nf=XUI#4t(4ly z^<Z;#W$;gCZ7!nkQ_2HAuz<-*6@mmvf&x$oia;aK7&HM*!49AqXb$AN>@7hn&>FM> zZ9zNG9?);|R-odoK*d{uinjt4^J|LP6{Wg??qEmI1MCE7J-ijDcq>rxR-odoK*d{u zinjt4Zv`s$IV;`@RJ;|acq>rxR-odoK&ASDy+MDl4;TOjf<a&~z~+YHdz#;&b`(=P zim4sN=!M1Tg~jNF#ps2_=!M1Tg~jNF#nhZMH78BYNmFyu)SNUmCr!;!)Eq_4QPdno z%~8}GMa@yv97WAh)Eq_4QPdno%~8}GMa@yv97WAh`UB+7-@u39BfxGL*0jfJcEf0P z!)SKHu%<m$quY6_DOM*l(e=Kh)>YFxqTlD$vSOcBkIqsoHH}`onEFv{kE8E9QFbOU z6S-LpLIMuTt9@zo7^!`V+NY>}X=-1Z+Lu;I*H2!Nt=nHRVwHML9dva#mZhS*ecLtt znpXKdEpP*P0kFnI9+s1b<>X;Gd00*!mXn9&<Y75^SWX_6lZWNxVL5qNP9B!Wt&Y4X zCokf){$jrc=TWOmlNX}9rFrf&txldf%`=zt%;hTDW0HGLo;U9~-RfAWo~2j=RR@8Q zfR$4<3XBG0z#(8PI24Qn=kdnlLXNW5x(NIhECv^Xi@?R;5^yPC<pm2bP4y6tvTLFQ z><5N|{Q+~H`T#H-90*8<CLNk|XwsochbA4GbZF9{NrxsKnsn&Hz<6*tm;jCdM}nik z(cmZG7{E-1W+p>3lcAZ((9C3LW->H08Jd|4%}j=7CPOomp_$3h%w%Y0GBh(8dMcO( zrh`)f8j(gL(r83_7GMqTTi1|l2(tqG0bC0hk?8BeAHfaaMz9jx1a1bmfJ$&HxDDJ6 z?f`d!yTIMx9&j(X58MwP01twPz{6k_cm%8lk0NKPz+=@Z(}FqW?VShD-=TOHI_upa z-l_N??p5Fsuo^rH&|=L8#Q!(&G59;!1U><ug3kc<Yt28vX8iw?;}_sd@D=zM_!@iz zwt#A8?-k(dq8?ut_4Wq+!9HLB7zhS|!C+rd0`>z3fZ^akFajI|MuLOEC@>m~0f&IG z;7~9Q90taN!@&e_1UM2L1&#(k0kR{K)o1kA6#8ol{WXRDnnHg~p}(fkUsLF>DfHJA z`fCdPHHH3~LVr!6zoyV%Q|PZL^w$*nYYP1}h5njCe@(@E70@3y(F>%|AuCwHUhH}S zX6>`PeoFmj@LGco?k$nDPhOzkrhT5x=h=Lo&F9&Cp3Udke4fqc*?gYO=h=Lo&F9&C zp3Udke4fqc*?jDw_{W0dz)wLM91l(aCxUt4bWjF<3C;uyz**pI@GEc*_%%2eoCnSa zzX1!u1z-{QEm#aL1Q&sud5&A?-70b43T^{;fP28b;689acmO;IR)a@D6?hD+0gr<x zz>{DdcosYlHh>oZYd8KY9A5>mgA91rNtIInOY-t7@GrD&X80|6&8%5D+DaOoxtv+E za<r0ibmnqq%*rh*B9<9_%WS?SUoCwN<A}wU5xr$ZZ)xlHCXTl;d+;Q)4Ntk9fKhMF zGl!%*eADd;Ooy8|e&%)uro*rKT4A8d6Cp@|Bq#*U*xAq;v;is3yMP{GC(xgriykR0 zXPi*Mc%Xz8L4yYM|Duk48G5#ig^+#vO3=`lr~>{nupGI#8f-wrU>ri*+WIa^EqSYs zMP_D@nHgke2AP>bW@eC?8DwS#nVCUmW{{Z~WM&3=nL%D=ke3<cWd?bfL0)E%ml@<` z26>r5US^P&8DwP!S(!mrW{{N`WMu|fnL$=&kd+x^Wd>Q9K~`pvl^JAZ23eUwR%Vcu z8DwP!S(!mrW{{N`WMu|fnL$=&kd+x^Wd>Q9K~`pvl^JAZ23eUwR%Vcu8DwP!S(!mr zW{{N`WMu|fnL$=&kd+x^Wd>Q9K~`pvl^JAZ23eUwR%Vcu8DwP!S(!mrW{{N`WMu|f znL$=&=)W@bR~h=N4DBpKJIm0{GWj)@_L8BUWN0TD+C_#ok)cgwXb%~*|LYl%?}TQd z?Sp8>3H#^jx9rQr@JjW?c4PHN_EpYbQ#;z%tKYGi>W}dETJ<X`5j3xUH)vJ;R?xb7 zY0#$n??GG6+p%8SzWV*3L-pU44%$_}rhIzaRgBaZqX|}^PZcwExSqb@7xWk9?2eGV zYiRX+{aOWR^?dzWF<W?bfL0%%)${dh#n-PDU%yttFw&=kj@2(Qe(6}<KPaibnh{Ig z*knw=z0vnAJ%`K@mC$?098n3g5G8f>x7vEDGNiK{{co{3nRZ@9JFj9aUBy_sim`MR zW9cf!(p8M5s~AgHF_x}kEM3J|x{9%M6=Uft#?n=crK=cAS232ZVk}+7Sh|X_bQNRi zD#p@PjHRm>OII<Lu3{`*#aOzEv2+z<=_<z3Rg9&p7)w_%mabwfUBy_sim`MRV`;25 zfStiEU{}x!>;`&+-9a(f1M~rVg1%rc&=2el`h$JI05A{?0)xR2urDYf2loR*!Tvz< z{Qxi=90*3B?H>e2f`h>*FdB>jhk&u*P%sW0hX3*4a4-QJ0gePmfuq4sz%k%ha2)t4 zNQ2|S3E)I<68IUI2u=o*z+`X=m;$DPX<#}y6_kP*U?!LaW`j9kF8Db(4a@^)fwRG{ zz&YU8;9PJ%SV-D00E@tH!D4VBxCmSfE&-Q<C7^<Dz%L`ra&Qe;0sa841=oS=!5_g5 z;6|_#+yrh0w}47;E4U5Z4(<SVg1f-o;2v-<xDVV99str-9|8}9Rp1e@8axWB`0o1? z9G?VFfoH*b@F%bVyZ~MV?|}DcETK9{CqNPufI?7I{Sr1=6gF8DHdz!S6opL|g-sTP zO%|nFfR>;Yep_>F0~qb=cA!1z06KzBfYH8Afi7FF)?GPv1Kq)npa<9q^aMLskHaR5 z!X}H-jP^DC0ybHc-W?Q!JplazHdz!lSrj%|6gF9u?hhF0>j7XO7z74`A&fNl1tnlV zz{(r_ewuziO~0R}-%r!;r|I|8^!sW0{WSf4ntne`zn`YxPt)(G>G#w0`)T_9H2r>> zem_mWpQhhW)9<J0_tW(IY5M&%{eGH$KTW@%rr%G~@2Bba)Aajk`u#NhewuziO~0R} z-%r!;r|I|8^!sW0{WSf4ntne`zn`YxPt)(G>G#w0`)T_9H2r>B&*s_Y0P3@*K5ObT zwq}&3P9qc2x{Tv5!F+HAC<kYP1%NuQsq@&IQP`VN*qc$BI<L<I)O-CKun=4T7J=V_ z#o$745x5v!0xkthz-8caa0R#$Tm`NMzXMCb??DBieXuu5>1(PdGFF?&SZyL>wTb#V za6R}VxB=V<R)U+r&EOVL32p_qf!o0y;7)KCxEtI9?gjUO`@sX?LGTcG7_0)1fYsp9 z>UE6O)-hIFr`K#bT0ah+08fIaz|&wYc!spD1GF7Y+tIWg{Tz6{x{?`)N@gG`nSrQe z2BMM~h)QN4Dw%<(WCo&=8Hh?|AS#)GsALABk{O6fW*{n=fv98#qEdgrs>k2Jhu|ad zG59;!1U><ug3rL`d~uojj13@#4IqULAZ1Fxet@zul#QWm3}s^|8*Bh6L)oymNnry> z8Op;@9)|KTl!u`_4CP@c4^ssm18czJfc^s8KMLDF3fn&l+dm51KZ>`D6t;g9wtp11 ze-yTV6t;g9wtp11f0TI%`~|!WUI81ytKc>8I(P%T3El#4gLlBY;63m@_^VR90jIoa zV0CpBc7K$|eLUi3jJ6KjKMLDF%wU4A!8c$Fla(5n>PqyVwD04#APEXUAt(ZkKx5DZ z>;RhKw>ig_pcQD%d57wC?8j4nXK;&Z;gc@(s5E+18a*m)4U?i)wXlKOXhSu~au0Nt z4{f39Y8%0CtQK10w@q>H0Gi>Cd$8iq{J;L(_EN9e0b7o-`)xVe4uyXJ7@>RFgTP3& z$sP<wZAsYCU_89TIiCQI;QVyX*;!}L;=X4S-><+q;MagQYtQ5QZ@@y%F93^hU&!$y za51<9TncXqxEx%;`IX=*+*gAYJm2l;Rn(nesqPj09#nv3)gK1S!JWA80(XOZIKLM> ziN5v}ppFD<0rdpiNh+Y8uy0WXlxbKFh&QAjgw%tOdJtv+*U>-IA$1|79)#3`Q2c)c zHo@l|3HCcGX4)#5X{!vsz)iWMr!vckYU+W50riE}@jRmpMi;ci3R+?XEwO@@7_G{@ z%rS3ehL%`CORS(JR?reFXo(fHM41n*;BFP%tpcfSs?+FuY4p7``d%7+FO9yJM&C=L z@1@cA(&&3>^u09tUK)KbjlP#g-%F$KrP24&=zD4Oy)^n>8htN~zL!SdOQY|l(f88m zdujB&H2Pi|eJ_o^mqy=9qwl5B_tNNlY4p7``d%7+FO9yJM&C=L@1@cA(&&3>^u09t zUK)KbjlP#g-%F$KrP24&=zD4Oy)^n>8htN~zL!SdOQY|l(f88mdujB&H2Pi|eJ_o^ zmu4qlB|G^l*~wSQPQFTZ@>Lp(2HYH^nK@={!tKJE&+!b_EZ9ZEd`t!HHNBl*`_6wK zr^3!)Bs~+%VH_`ST`T}+bN(xE4)`@#2-pEaeXgKBS5TiTsLvJD;R@=ojKtG+Imh>z zvHmOg0Q?Pn2tEQIgTDir)&2y03O)nar($PJF*{?5*%?#J&X{6$#uT$NrkI^E#q5kJ zW@k(>J7bF38B-i6paDB20uT7Wf&hdd0g?cFSnP}`W@k(>J7bE2reFuq40HmWK?-yM zT|qa{9qb5tfStfHW}SH^+FAu|t%6QYEOS+WW!1%Q->O*FN-+jcaV5nVJjED1#kypQ zoewE?KBU<BkjlNWupBpQRfJ3%EZHy)PcaTpF~Uj7jK9qAGa^fcvaZB@n3BD*<tj8Q z+}_wy%BGyMDX~|xY9x7mCu#CztR9shtE{2SNqWrLS$QMqbgnF*U&1f(rWq;9`sug* zimvSJOMBE$pLRiCTf!a3y0>*sCmq}RUazste!F|N-LCrkICYu%Baitb?^){edhjP= zi0;WsF!fU2DXaNHeN<Pobq@i=+I}{dq&Ak5J2F$WfOpQ=jY9b`i<y0QWHHigF{5GG zz0Eo@sVuekl1Jjlm9LZ(ekzod0{sf3rZlBI77Z$mpRvR<7Rg?UP5x58qEIgPX7_R_ zsZ5i?V(wZ&%<}vxW%KTwD}Pq=9#a{`Y@S$r&shO{nYdnYrC4so;#{<jQ+IBsqIxkY zkhdhqQh!R7*?+A<?hc<jW)0)A!|9Q~-wz$*KlQ7ALhf8$&fLVm`SIN16WJTN8um^( zyI(c3t>#DV_|89Sh5Kbwy12S&Ls7As8~IvQy|%hk!bnQ$r?ff~`KzI|IqcN=fqtYS z{R2eRb8B(LIlZ;(+v3N08u_VSS^GQ=SvzNP+=|$hf2>`?)thQvqy6NcZ@;@HUDYM7 zOpz=SnsRMgH<oJ0z+W0a<#IwVl=q`&;3srThGPXM(CYrxZ`a&q%Xdtt#1;RdI=7`_ z%c_{uz_C(zQHsTdG}-E}#@AN8)Vff=&eyv9-A~qk_n_Z5WVO3DEvo6ZVq$JI+v26K z*q(fd;@wu9|MprcSJ%@ueyLX*qW+3>ydCpvx2HUQ&PVJsdmI}b<+*DAzH57`9m@}O zR?~hOiZN<;4PDtfuNv)#Ua9F8^aK9cd>}82qcGWCpgcc@>ameOt#IXC$WL9qJ8J9s zwPZ_WO)x#F#{HwX(!^ToVis5JU%K|`4|A4nLyNOd+y-q&iG*snE|F8^+_w$??9WNF zbbMBGB-gX0Qp2lpZL{>#HU9El)jg{h(}Ie(tfF^boV`*`N>fo!S-W4)4;i`hOSz`* z5Wb?uZ?zH)CI2dKc75w#RwpT;XUc^p<?cSYly5E8()j+hWv6&2hLhr8_2b!Qp2oqp z^H@LjY{2sEj6g{}YWSDmw(@;!^`7~j{!iWc_2B#6nBFSYPz>=Ms#j6ElsvRDb~WVR z`N%|9YU&mt#=0vN(UosY<5a}qwptS9v5$XyOmEG{4_Es65}={G<s`7GEnyq-8}VvF zY~9ayxgyf)--WB`h3dEa`eEygqj*oOzMt*m#3AD{-?FKpa5-NUaa|K%|E|ZaAJY#q z_NgE3wm9i+UHng8t5@6pPg7dWszu#jO&zRz<$w0P{QolL^-A>H+}x!!zfb=*fxoR^ zY3*@oqnU6P{kE)k{B(R?`<Rsz4fYVVeaacy4=Z9n#xL%TevG)kZ=88!jP$pQYs;#J z{Y#m|zqrieb2Q)58h4E=FBLV+oJ+Kgx<9A$m)GTf_w)beGRi*H*zf*yKS;<1OYR4W zc{?JhQwP$yAT=&n*&eNi=UjEd;_t`#IbAqYKW@=GeyIAznDWE=cla)x`fZa`F5Zrq zHf@I=yfhU5_JyqLi+nAQc+88glF>6VplMxS(fO9KF+bJ}YioT`Pb0G?PNtNLZ)RFL z#G%~zR^rb0Q{OG6FF#5s>yns3?ku{#ZCYQO&hH!Rm(P}O6?=5=sFl<o<JG@+!)c9k zw(iyXsmu4#aL=FbqrSVLmh19#75Q3G-~V^v{4nuUvy*}}mE@;j>utXNJ-0T}U?g^0 z9A^WeQt`FzKceSU@J};$RGJ@3&RpJB<a6u0#ZUapk0HNS)PI_MtsoBl?~YOFi@sM& z`|l99sG8c5b(Pe_s8i9?)NpEC`Kha4XUUgu-KL_F`S+vdnl#jQefN;H<B_v%##O7t zUD(A+u@Ni#t8qmPInT?jDD<Z6jBgw$|5%=X^55yMsWm)XMcp*!*XX*}ws!7XPDS-t z<WjBtb^Nj1rF?54;cu$FZkFG={!NQowT)P}>rzzDx4ge4)sXkN6;!Vhm(&8m7Ur94 z|6+f&xix3i{cGI+oi5&m_@Dn(ze@SAtF!j6`fs(*<*ebE-TMCObG8)M=Lx5}O~ZVy zlyCJ)YHL@7U!B?3)dsK8&i-Hd)$g^o|H;2c>C;m+^xIP25LbA0(wVOD_w7?5I!jIX zZMkac%lC-Sn)>N_v7+ibqVTnQ>utsP?XGQaO1B<swM%!y*V*M%+VGWc%l&rg%$H2t zcc-nl{%wUv+oPtTg{7qiZ7q#k;}fQ&;n?eIdinmo(>Q7@Z(6A><=it(>^<4#>v|zs z!*{9g$rZ=Rzf+BSONF?_2W6TTcl7%v6T7(56lBkdwN$BZHq~6Macx<QKkz+&W4~wk z|JvZP<8SM~?^aW{9^d!B67Q?Y&S-uA2x&|CkuVDUFDz@PX>JmYF|PBH=9<;RTHFTa z!T!Cj$$Ng^`B%NZZqPVSqvMvv`Ld_RFFjg?oJnt1)6Tb*|AZ))5cxmCHkI@Gz5mvG z$^7vDmF|)sBURr^pXcqAdcTr-oF9o%ztn4P6xX#HPUPAeKi^;eNHU-Q@7-I{;-~&> zYkf`}m(=S>vc;VE_-#@V)$%QywmxqZ$5zvh8g6~j__|@Opy6~%dh55c_$mH3j{m+V z{9&G>*38@w6XlN*OZ~q4N4d>^G}7v~8oqrky|6Urr^XetR^@iVmF8UWH94l;Nrj^- zUX!V_Z!+?e_41!SY^y*0&!#P3llk5$DN!ZgJC3bJATvw<8^8MVUq61jw=I2ZBvQk( z@{tUa*${EXTB4*{+>u_lrCiP%`ZX-m8uD4gtJN3Ouxna#KCb~c&yj9`?O2RYQw?4F zF6nG2o*#}^qci+)asD_lZ7rqSu6I99TBwobKUT~?biCV6>3=5ozRNRiwY`3q$o|j6 zRAVFH|NrIZz<)wU<==n%-PPFb;D@D+@Akva*#FEg`>a2DdcSY!*MzTe{Wyu<+F0Vp zx!eDJtp6*u^9LO#Y<D&{v~IlJf%n>OpFg_CA8yni&&U68Nv^+>;)jd-zc;38EC&7e z|FC`eKl5`vwZU4VQA<d41=c|>#X87)>H!^^F>0e3%fGuB$A3pN-W+3kn4g-@%<kqN zX0xd>UwBK+8n42;#+&B-!CURk@YZ;L@_y?*@4et%<^6^KQtxx`pWg4iFZo~NeeG3y zD}3X7-t~Uq2i^^S(l7LG^c(ptyqo>jerxY8zr8=myW8K_-`D$#|1*EC_p(3F|E2e# zf2M!4x5=;cZ}&U-clv+!yZSHtulWb~8UJm6r2nq}zJG}SrT;H~oHf??hg)kye}YZg z0{<x6*tYV2V*A*>{z<l<?dP9t``dl|Np_$e=$~Q-+rj=6yRY5XpK1@V2l&(M2z!t} z-Hx;4{8BsKj`wHS<LpoUnf7=)(VuN6+sXbsTWV+er`y?fwm;v_wR8P5?CG}5FSqmU znf?NMwmr{3$Nt7H@)z0*?Gk^nz1&{qFR@GQGXF|@ja}g{wb$C~{0e)Mz1?4KAG8nq zf3%O-$NiP|NxRO!-LALm{d?_m_C^0b`xpC)|FC_{zV5HKZ`e2dD*JxW$bT$o8Z`6& z7PJgn`X2|agVz4vgLXkXe^byg*vbDS*e&Sie;o`9hS?xEI5^mb!4<*pY$B)(?y!x5 zCxd5gv*3;3P1`njKX~7^4@<*R+aa72&aoZC^03@?3NH*Vw4K9>u)?OoKZJj<UBcVL z+ilmdDy*{I!dJt$Z1?b;@B_PR_)++g-6Q-Y+-&=VUxZ)U{^8f*H+DcmC$t@u@DiRK zoUn<c9g-+a6x#h0%@WP+utdv5TRS|_KGD%0oJb|Q*)fS76Fb{+iT;WH_K3v6iG%Ht ziK&UH_Nc@Ki3=>><k2TkQwJ)m2JsDtkeRCk`PRe;HI8?*55tzIua3czXj?3ap2pRA zSo93B=y|>xpcYcEyHdxO@GXiPu)>+d3g=3`YjG2Q54$*PR7yRq)<K_B&qH70pHgqA zH=*zHPw5c5m<75BtCt;gD{NZss8c$niu7P?O%~}PdM37fX6aejmz|9z#+~%(`gAyD zSXXSX&%<V7JAElu6I<&g*iCGwFT)yQp}t06L!2x03fzCt58!@KKZ1L;UXA-vzKOKE zUc=XqcG1sZeXyH;UjJ1!)*oOQu)F?;X^yR+7N&(tn3kqB?lz`9?ha;0<(ZvKPv|aY z0CXT01C1GKMq^Kq_N=<llE<rq&C%u<)t2`BOG3>z^YL?rS)g_`XPL8z?QC;_>Sz{W zRZyEtuq)`9OU)8Emzm3m;c_FEik6w>s>obp{-Aa<*P82aUvI8gN%Kdn6cw43<|g<z zW2vaf+=i{9-OTOgF5GvUd+~FhxexdK=6?7O7_8`;$FMxu*{n5diTO`h9PDDAGtc4k zc`OfhF&UG={ek%#S3fi#lAe#uN2KRt^C_Iq%xC!d2e$8%^h}%Ke1Uy??NPX@$kU#$ zjAuQoMtOnP2lt-do*0zt>-AN;dV6_;;0*SLs4m{VSh#QG4fTc+c7JbwIK#YQxDUX} zRU_|MZ;EQ^O~qPaYj3)DD&Hb1^-A$G!<&J7rZ<x~XL)6~f9d^>@6;{zmZ~Q7kISIT zy=&CY^pZC~Z}e_fJ-u7JTL@d}-OAP5yxU00?cO7}S9_~*ukoHxZM`SGr||Q%_b1%X zV;Ql~+u&_bW9eUCP{X8$g?`TWrFQrJfvv>W-e&Kg_@TE|-RNz<Q60Q3*h_3ppKDZO z-}61?)8`f_zT%F3MB_KXGNSRD`OV-r$2#KS(hIBc^up~`Z@+`zLGA8$#5$t!Q~o}x zsXxFUKx_m3f$C6ykUvO`lb%{7q^DNRrKiT{nOL3o{EPida9`?Qst)s)_&2E?{G0uo zRTFyfO4ZW8)xTZ!pdY_W_4M!d?^YB1d$32Z{d=)UpY-qZ?^B)q`>{%I{0IDpa6jxn z%+*!?BV1jLg?jBjijDfDUxk%=?LUT{`lP=GOZ7ec$FWtf{U@+ipY)%^UcL68@}GkL zwEr}*t@WSbj_a^tpY)&g`L?qEC;v~lpYxx`y}{pr`vw0+Rg8rBvnoQyyn=h9|0-c$ z^IyYH#?Qcc!+!&7k#G8M^6YQ<ZxQNk|83mw`tS0L?_o!>9TMq%{Cw$usk-`K`TtUF zL^7$CNG8iSA_Htn4z?jyB}X8olB%~Yumx%evZ}E<$Tq>MWN+IPo09!)GptGuw#~6C z+1s|TE%4LQwp7R4R<@No0jbtU^|gE2J=I>eukDK)35UDC?T>pOyAN*U95y78b5P_Q z6bYxAiG;&S8xjuparQWMfc>fcsTzo!J3;MfPqZh(Imw;`=Vx}J8ZMF#H<C~7Es{^| zBa#n#x;>p3%4`{-=G*ydUwei<L+xkFZ8`2U?U}gGw!c=b?78+leE!D%2D;E*0B4b1 zg!@8!3GPen65N;D%b{1;E8$#aufo05{$BO66?U2GhwQvYmDm+_1x7!wwb$al&R)mW zo9s<oz1iN1`*!SF?u1NzQ0-wKvJa^~_F?<5`U&#&5p{%JZC9&f?4$Nkb)>Db*vht# z*)?jYecV2-jzaoA1?Oq|458N9b!xOoVs$i<_&K$|ecnE=#@G#ZgF3{%U|)dqqJ2>v zi-i7*8fjm)FRM=W75j=h4r%?G`l)^0z77YOjg35HwhBaMLy_6)M3LF*B$3(bXUOc1 z>cF5=uoG92+^UmEZYYu){uKfF5L_MnPPIaQ--i44;11kR22Uwpq__%2ek)((w`wi& zTNR4@R-VXjWki0f9wNV$7WoZ*73-XZ;p^e+szdlj_=egkd^3DgwF%z}--7>6_>Srp zz8Ahn*!Qv1*(UsJ_<^z_>52c7@Du!e8h)yJho6O?arJX-cNT{K2sf+7;g{I$?1l#L zHR<^V+nwD+4^V}o2jI2|t4t!`TMRuCAzxz9i6q}*=q#FnYJq0ZOf^X~Pc-Lh%S1~! ztrD$pw@$Qy-!{=!CD0h!6RJa^Bk`p8UV~0_O?1P(V`4|#J@|S<kHk)Vzd<K@CfHe* z*g3H?@t{=@16l<>(I?bC=o9q(YG3pT(Im9fBz$!k+Ji=Wm<K%z{Xz3}!1K@>&gZYu zB$l9OR48)L=?$&Wm!HBd+CwXBZT|_k=n$>c2CQsrr%AMOnnWusZodOZ^odsJ*zYOr zw2H216@75;srN)z=!-s~(I;l1J%}cu^E3&KCUL21?sN$4bciCSC3JIILMNvsv_VUF zLp9Rx>i3|cE3|dGLg;h_4_%=>b{jjO85lH!fk?{1W<TiAtOjtF>h5H}LH1t)|59@) z`oR*UenFnp-@!@!l#}{9IH_OYr2Y<0>Zg$Um3$HYR%HMFPWGpe{ddB-%iOKnn|sgz z3Y-S8gVO*~XaEmz^+EF>S67)wp;c%D+GztF%rj^M+Gzs?Xald~7Hy!wyn+2-?Q{YU zo!}$V`7s)R=QM!!=AX!Z582-r>AsgY7`I4w&&hJn$?|4SmTP2rnJREHJK<z@(#h<k zli3}e%<kx9b_ZlO639ttD`OqBf#;CQ+DYXRPAc!?{Tb`U+R5g9oNVrfY(^t-Qn?LM zc?<4puUZZCHFk`(lg*8?vf1~2I2L=xMabzS?gFHBkzeQ+;TD-)<Tu8mvG$w#O^IO# z<hRa~-`a2Kw}K;5+;{T38}hp&p*s1UkYSyX<=V+|<7Bz#Wcfi(mJjd;`y-K9qxc$) z=OlU)C(%8BzCT|L@Xzr1zMx-Mx{q|yz0gVbq<;%mly^eL-$o3#BjXGFJNVYjj!x2d zK*m1^{~;uL7bN;4@K+<DJtv{N<VolbNa$zbulJu*q5nKznDP7<k;+}1RNeup{4!x* zK{k8-tH@^0$>wJM>&Rvg+59Hy6nR|WzvI6{D3QuZCzYE!scdAVi2M+FtergG$;snA zoIKvw$>Tmw9`9)z*+$5c9gxI3IZ3>Slf-?TB;M0W;zOMz?&l=&u1*s7a*}vgCy9GG zNxZ9*#J%hwJ4hAVA$AC~#FjvZ*<na@#+{@@q;g|B2C3ZI9>%wCJSUqQ+av8!a7026 zauRxXC!u$75_*u6(7QVcJ=jU;-JOK)jf9@7dOF$M*vaNyoNVsvWb@umHurV1d2c71 zn>yLt*q(3C=UyVA8#@Wz)Jf>ZPC_?z61uUy%wC2ck<*QxoNkJoz8bg4>CR40?`D_V z<@gb4y;q*J-pfhr&Q4nIW<^?ewzt{aklZ4(`34Cx`(P)tcSdGEqIx)KJ=#g@9!^^C zjI@3X8N3FWJq(%sgxVQt{WLzGwa=oXh_oK%q;-EMtq*Y0`ambG``ef7OXwmZr%Rli zKE%Fh-&A93$>tV83pFli6|_=^ISD;JPb#-_Qn_7lQE(Ao8Ce=ssK&vvV7Y1<+!@@B z`<~!l+>um%8mrhE+5Cnok`Xo%S0waMC!33E$>!zZHOh^xv$EMkHot*e<gxGMagme9 zZNhi4nysBY)=m=Za8tMmKO%#DCxeTe3~t0I{0n@FByN)@iQ70C+y?2JRGyQ#o|Cvq zCvgj$#5GRhc5xCnnI~nvtdvEz5>_N@2PavLldMT6S#@G`Vl?uVQ9IICq-@W`{fP(M z7a=t>Oa*E)V=FUk_^9!!@zmpIPgl)U3;0d3D7)YIlo~hskWncltm3oeo#1QaGh-Gc zbii0X!MMGEQG1bUq#9!*x2eMBI;%{rh`$Zv{&q;|4yq#|JG&U5+Yy7L;QPkWe5-M! zjz50tY_<ACp--K7{H&j;7f(EKTB+K2;@sjr)Z5TL>I>+ex(M1=r=WZ3zR-Sp1axnG z479&4h3=!zK5_0I#rksS9(v_TQzxISSDiHd)M<Ln#F@vRsMk(C^~9<A14lPapEGTy z-hArJlcwvhOF1$AO#GXIS){}?oi(d?57P?T$E2Wpnq6kiIboLRJ8MqqEHhxX_%E6F zvze!w;pCxl`KTy|op@7WSNAPWt#))*4BUWRo<LS3aC1N59RbhJaWD@L??`yI4(}*< zK^@-F@WML0pTJAh;T;1HtNgj2+~Zhyd<h`;6W(#~3Uiz&EkA`<REL*_*C>z2DSDyM z?$Acmz(QyfYF=|{O>1ghdum)~)y4Udr_j_^q1~WHjZy4r<!q#*JsdsM<-|B)JJ(^j zv~!Ke73KD5=W~>!2RV8$U&X2Nng&lK7A4lVozQlA+iTmdY5Q)6P96GmT+(3_m;}xN z*McX&haH!6Yz78^lfXTlRHt#B=6AZN)BBzKb$++!ngNLcCk$9H;D$lNhHW{ZxXwRw zK)wI)bBEto>%ZZ^P6tjs@a6;8kC=PVJtK=o_8vL*;0XucGwPHvT}MwHy=Zjh=%+@% zJ*MlJqsL4ivv$nKhe%1tV<*tPk5M<;n`!Hpsms+B>PmH$x?25CEmglqGk#zFRehlT zran|3sgKp)dDHb1^{M(yeXjnYHmiTCFVvUnEA=n+wfaVFQPrqXS{v=5P+1+=Tl6B+ z(R4DMP0DmJT}?OUuy<tcnprWkGqYm5nqFo%)0=thVzY<oWA-$C&0eOT+1vCt`<MY} z$FG{#%<E{GZ<sgDTjp)^j(OL-XWsV?^~QOJdE>pqy$RkC-jUu>-qGGqykoqRm;wJe zW9t&TU(nGWZYS6yL~7fk?N976_E`IKdzzIw;~65A?QiX3<mpBB5+v#6B0-U#%Y)`Y z3&w@5kU4FFwu}nf2OWanGTOT}xJ^cTNSM2VyG6nT_XYO{4+IZ}9m0-br?7LF3cG|y zghz%)g-3@!3FUj{J%H3BnfKI;RbHUh{)KTzM@AMy)UJA=UZ{HM&89iCZ!JwrW&_vp zosUsY$3Dbtz#{lquLN!RNN+gbaXi{P!8<{ngtk0Y{mh%=%~2E4Y6hs2o%V8y9cf3Y zDd;VSsp;~JN}h3^nrj!>U#ZjWuNnEDfnLG43GFhwO#Lb_L7_S)SP(2wiy7H2R~H7? z1vjXx!e(J}_4}}8*h(!6+k|b@HDUX(y;>2D4#%iJgk!@))pg-v;bH2J;e>Dk^SILA zNL|uiGc{UOz^PPssR#HH(i4ovHmHrv-@Z@X*{r^%W)$gWx{Xfh9=ezAqx+K+!}KUU zP9LdHVV-uOUaBkgD*c>(Q-5kEndwZKm6-+RTvKFPQIC33PX?J`W{jC&j&s*%XRj|Z zS27N|nX%0SX0>_3tn-S@i)N#F-+b;V<{OK!6yE_|=Ry0vGUzscg+6RQfUdHCgFa$E zgs!$9K_9grL#yoH(fumXbT)Ce#(n~Q+<wZ)W}|(=e#TiupR}Lj?^gSiWz=JDK@Zx@ zwYB!2&}ZxyjCIB`hw&w65q;Kvg?qjI7xYi|Yv^<K8|d?P3*j$AQ>y0d1v)WfUku1u z^xoF?&y-hlbfDdWL2#PcHuj~!qpo%idIy75XxrMqu$a>_NCmqGL-5huwzDrYz11q{ z5)=o_4x%f!x32^NHM?uDM^HkT4t8S@QqQ{ueS-b)(b9IbuQDajHs~Jg84SfoC;M8E zq!sKK^bPjsyt92hD4<RB2=-!LB!SMIvYDWeHnLODFF1hocayuK)mz&|(u^yxT_vqt z3nh(kk~v=mw!5T>YlV^)t~IhdN*dramV0w-BKPD<Q`<xCMXpG^CL3pScR%A|F)pNW zzAW=?Zv;ibuE7949y7?k88ixd1p|q1uzf3N%yEc)J7_{Y_=0jBDp!o-i`M^cz`U%T zV}C&m7u)xOX2CAOKDaNn?=XYZGuRvV67d!69Q4O0KA>EKN?5H%GwL76sD2DH{AVzt zU&L(vgUqo%L(Tlae8RYRFK-|t;<1c`r!)3l%4qjSW~0}Te*yW$${9J+h`eYb^N^H# zbIP$L`QDlu(3Uc8Pbqe!oH|nyUC8Nf<mis%R3FAbG7CA7XCKV7j^KG_+jC@m!L!I5 zpGe6fw6uOm$BAk_GT>%bU|v(3bz|Kl+p3$<rKflf#_&+q8uIwG_#B@RC2ox#scGpo z{Oj}hFXi!X$>Xoe<KL6Vzdn!ua2}tpB<AkBI*%{&MR7Xs%j2i>_>boCN96IZ&Eps6 z@$bmvx2fUVO8WIy!Q|lQd<o~n#AnQCeUbP=wN5rpHdk$ut&?q4r)0+@`be^Sa!1uY z*)!Qw^+*;c_fR_}_e>5}J10w$V^rVdp~*wl{>kyl@oHG|h$M5r$%)C6)#&6Y$*Jm) z<T=Un)wtvZ$t%?n$*YsMs-GqAN#3JQPd<@+LX{=gCSO&*bZK&uoRTau%guG+dV0Y- z&AqtqG7o{f!k6(?M9sUFIig$49nAdPM=gAqe)3VCM07*fe>YWXuqxM6^?ZFft6!hv ztIBNgioH9$hrIXwc79)0StlY}uCy!d$HAarZa6HQ&PeLEuqx3wQIc$z?3EmzEKM#i zIHq7?K}EsK1)mpAD4bMyQQ_LccZ-S}nMOs8`ZXHS==??-8n<fvQWMjpMUyd2(oJq_ z@^+KWXmI_R0UUv*cBD%4eZf*@0L%F%;6il~y2(<$4OCaMyv&z0-X)wDoEl7K%pu1q z=z(&a!Fa?AX2Ngnj#C($$oUL(MLEt!fAoUs!8FD&UNDDapbWlg@j)r$8xx#Lj+*3) zv=I}qq6nQSBcxy|b;1PG8EcrJj2dr(Ip~^(JGX?g1_hmlq&LB7Xs9OnwCGs@D>KkD z$Zf;D4R<S2LvT+f&&9uoug1j5xQtj?D<sza3GESg5qD`rYPu_CO$jjuItyLa1iw(D zpfi!X;y-XPv{NZGk6wHaM0~Mc8_eTNk0zMJNI>GA0G&e~OWdt__ai^oC-NP~ndJL? zbvE;?eCZr*aJjmkp5YF4pIXJ29HTs)?T%CUqGOb&bKP+^p}k-lDV*YtQz%DC?__AH zIth9z-;R`2odW#@nzW>S8gw4zEjfDvbUGz3>HHaV4&S1boSgxk!FMPnrzS#ABbAbm z)1hZldwo(XzC%i=6*ZwNBc$G{uVVF!`!9x`sxF4kQon`Hq((`8TnL?mZYHtT&b#a2 zoz8b8CB$;*Om!`Ey7~jOOukY@xTf4?7xJ$k>)=DFw`0_Jayw1>;yheKE==eCPvL(u zv(b_Z>!7DHKP@po4V}q6wLJM_(CKK1690PW9KIYWPrd;<Lp=kX#=N#X`C8~~bgS%> zGZvFr@@fg+isXJ8Nfyjv6vdNjskdc(J5tKL)|2wxNb$=YelSnBaemv0->?iiM|W@` zvUO8;g*RDum+QKlT&J$LV7wpIw?5RQA!?Z3)%n^DI!pI~&eOf1lXNk(Oz#ezqkBWA z=shHMy$kNy#L|z@!^y=7j1nhOvU910=cq-L;gx8W*Q%B3Hgyl*+pHo#*E6e+Ua5~H zhiT*bD0iHqk9Nly`X}x<TaV#L8`p<&q>bytIZ{9Mcz2wl$8ns^IkS8mr|U!9ahe`R znkMTJ&{OniXsJFBda6DMI!hk_ovEeeP1XBCr)z0bWqK@hj;7V}Y|_?d=;6?5dL;BT zT>_mgbyZ6(pIocNjunoU_E4segHF*>?`LRfKiS?w&%r%e&xOvRuZVkuYz@@EbXU)W zmh!zxN#%U#BwY@j$`>ak<^|9>`d6+@rQ|x$qvX|K%{mn_ZH{K;IGDvz+SFuy0d$sL z2>pfr4Roen1f8T8L(8<Zr+V#9UjcWPz8v}szCbB;;7aHueKoX<?^8-Te&^EBM)hH5 z$WVH-akQQj=m)0rtysRozO|lT-%9FeRhrptj<fXb?l?tPy5lsB?5B*U>w7s$*-Fcu ztZ#yzqVI&3>Kmb_>RX_*w6ye@`X1;!eFt=^z8*SV-w!R*cR}ZCY4ua|1JD`z2Iw?> zGxRjQ5;|A^5jtDn4L!3~nLY?-GCN5mo`;}Q^uy2@dKEM-Q)%^Oy2^2Cw@YdLll9}! znffW{batSKU+FXEXleaZA}PYxG3Dwy;WyYGrB9ixUxd!oe|COfftKmNK<DU}p;Pom z=nVZ5beet@I=fCU@i%yr^+(WI`eW!!{dZ^?U(b{{Ho1}%`Az$$B*)T*kJ+BGq&M84 zHxtKX{RMHX*Iz-O)Bl3LYKq{O>VHB{)nCJTQGbJbmafMAywT9L#z0@tTj0+$9`23C z$34{qxYrqrd%BVSvCJglyktV$b4&qriYbK7FpZ$oOk?P2M*7F6O;g-+O*7~jjLdUo zWu#x6Y}!I|(%MLyF5{b^66=11t|hIF^cj=QNa!phJ;5)G^e8ip^dfbnwHfbxiJYEi zj)YD!6QE`0aOfOEZ<3YPTbDNVB(^z`u#*k-Bq%i#p{JV3&{<|G^cRNygnXO}U2A?0 zeZiarf2KJVI?tR2XQMd<_f#Xj&~!5sT4qj$&N0%1O);~eGt6{+PBcHmJ<UwReX^N^ z`!qux4`!QEsFTpf^*(hav{}gY$>v<>spdD(S?1T!ndW?Add-}NdycsPI;~zv=~qrS z(yz?oORExw9))zeK8A0uO32?63nR}+w=&Yc%lPJ+q)vK;GtsQ-NUyl|nj5%2nf+3d zcQ-*#Wi3_ibR%>oE2@$&D~SEi<{I47%^#st%<a$_W+ikQ`>|^3j`RYvjI{h(lG;28 zXENV?m8*|KCz+?9WvseNy6b2&#<gT4ZFm-QLsCl8swWv~w`FDnbdI4t=RL8s)yanT z$#rS54fYU*wudj;9`Vw;i1$nABqQyu%+U6Tmo~}s(bAHh`4o2~vA3rA)T@h5dt9T| zdLdyadxg+bJZWjAUIFw}Pg?sduRZZU?{$E#^*Tad@DlK6dYy1@^qSzF>PahI=h5<m z>7KO4GOq=kmpo~!b39spFvXL$I>T!Nf1;PfJ<V&4`(#gA`)OWF+)sG)7r|_=748kp zT$$u6BAdM(;ntGPUP?G#cW9Z{1v<s+3Z3C~gHp%oDT}=RxF>u2KpT+F-huAw!O&9g zAm}V_1ay)&5<1lz4K4FVLFX{bC#BbbR^>?>oa~*<^*?#iu4j3Za6juwOZ<g*Jancf z?RSzVExpW>_SjHbXzzrj?Gx&B=sb_MPpC5Jbnh3?GVe6#9B-aWi^%71)vtSJ6MC|D z9`qFNLTIV?Yv`%oZ=kchUqNSjmq6!vi=k6JT6gl#-XiET-fy8Vd-MsE@O-XL_b!8$ zc^5(FduKtXc$Y(Gc;`T;c}wv57w>%Br+Mc>=X#gIdBQsb_iXQCXf2uST>)pZC;dd3 zcO`U+cNKJoCw)d-mdo7L3dgC_0=?_tPWGf_&txZ|r0<W=GVglm98da`Dc%iGa@o}j z={cr*(jUxWOqoA28NgU~I6EQl;M!#GPUuWedX?$keb6%RZs;8EUg#9>e&`JEF6cDx z9_U=}0caf=?a^lt(=*Un-a6<^?^$S>w-!3bTTi}t*;GmUo$P%Eoi26WoGCtV4d(hF zHwHfp9sFEI-t4epcT5p`ZM*25nXBDT4`n}0nLdwwFW0bV_EGk{Ji}g>518j_Q*WkA zW*}s)E1K!@+55Q{b6wF)S9#4`m-k6{FL%$*LXdCl2T-0~$b7d*NyT{qqhHUthA^_z z=o#Wa%<|YT!TCtXlUXUvPPp8x27SL~Tr9I!&p>_YCmX)5*|Wj=EiHlTa^IGW?#7{w zUZU<`l(va{=*jaRLk=v|%k@2at<Eq@?X#z+J1HsL{iXVUL!3Z<l76k(pSAm4_5bk2 zN5Srw)cxz{2*nq<-F1oKHhj&l`-dyxle>?v{YRJApeFQe{Pe5+*Rx&N#O`hK|BPug zs0lehC3=6I|JMUtxUhGo=C3;uO4f|y=k-<Cdri$>OSv!x?Y%Pov%;i7E(AFp^u8qi zb0+?6@3QER9MPc0_X@5}jQ)(v#joFcto#`{YEa{I9#;m*pV5W*^LqE}-II}~1~tAI z8TM|~yA@-3@nd@Xj7>DC@zF<_-9D#Rl<Ru8x4GIidz6*+{CfNs&NMqqq%O$}o2vo4 z-Qn_8{1w%?LXTwH=|L`z#&xdEr$ubxpK@25*10O*u+6x?v>nChu=YJm_!{l1=+A15 z_-IkbM<*4q_LBXpRnAYV+J1U{#lHG^HGk%YB&IfXd~Hw(JwE@h<~o1v>iAp9S8jXN z{xkZQ7(3MQxqyB6rtY7ytN5k9v25MZx!pWyud%9txyix7kYL}SB-k$)8tl)0kpo<u z(vvVxh;Kso>P2{oBjq?UcilC|5&fchuoFweviB2>fxF0xSrj6gaqX>IG`L>az#1aq zWle;&bmsI6S$$|^DMJ@yLEh8qUD@qN-O{c{Q=~`IAv;kO&vZbY`qc=3I#>XeR3-4+ zQ|-Wt)vbi+%{ovq>pXo}$?3~#O}}73FfiCB*qfCK%`U%X=93237-ZeRtrFzL+Fdna z<*74kOI=ty>c;BOj;sLf#M(+PRz!LRJL9*>cE@%CJE-GT3Aa|u%0&IO+Bv!P*^61B zWxb--D(yDbW66a=RzLcZ)3OKKb8DnN>mMUnzmhf5gj*vmbbF_FV0Bb>J}<M&RA;ve znsTe4yVS^!#L+S<&Wzy-RyChcFRKsL7Tr{LWsm6a?5gD=U7>H&Rr*E!zW$1p%1*3k z59R%iiRLtOzPXzD_*JawzHK(M^|qbY%NxRa&k5da?;P)P?*{JyZ=IL%K4sHkE59fE z0Y~}A`KA5>{}TV&pq!^czYM;h7Hkgpg#I(j`O<Ov=5fAqoW1fm|8ksud7Q5uXYV|Y z==ht%{&^g9d^r2$ab!%eIUG>Kp&AQkU>-+1&Y(Pw)ZEP>Iz^o3kp2|TkUWm>IQ!;t ztmBmAanJ_{wO<}5bey4i9O^!P_Rr&p*1tI%mdBBC#^&&VJdTV)HiyIWI7N<gU>>KD z<BZ7TG<F=(<FjQEHgO!$=3`D%#}SP`<}hmEUZe6jj5*+p&f~}kW^*_ukJG|&4$0%t z6X9oU9;cP#9Gb^z?KtD|IBgu~uslv%#~Gi;Y3DeH=W*IQ&V)Qp2gf-gkJHg{j?CjQ zRwIQ+<#9SY&e3_Cl;iv)kJH6*j>+TD_Yu#rd7N&Jb6g&$yW{*ckF%rWr1LmE9Ow8v z&Q6YVLLR55<D8hs+1YVU%H!<fI6uqd?CLlZ^Eka6=j1%j?v8Ux9;euGrsQ#UbDT+e zoZgNzIghi4<4n!tpofq@(;R2uHr8wyb)!YS#LDj5=vmP|48yAcjdcs%PS@UvF;b7! z6VPx^(3A9Z+4-)^^*Ly}7qKF^6n*~&c4yqBA7FLx3B68lkba5#(-*0dfc{5~2<UUz zu}MFpjtl5p)VTruiCP%Yho}naH&j(fU!lX0kh)0v1l=m6AJAPw+P>a7q`m7QA#GgG z4r$l=^pLi!%R<_(J};!r>dQjfslFzpZR$ru+M`}0ZBRcG((d&0A#F{65K<QQH=*Rx z!=dEfL!spA%8+_)n}n3OJtw4&*`6Wo#TJINBU=zsS8a=s8e-21sY`Z5C?$ABNG-Ap zLh67$FQnY<`Rs`?wrNPcwXH(xp&iP;8DoQx7Gax()E|3pNR73<LMid3AuY}}4rz(D zQAqu_EkoL}O@_1=+dQPD+c1=xaAQc%fT}=Svpevf<<?i@e#DX$S;IRg$QM--B2(0e z5ILd_3-^W|C-Op_D>6bY6uCgj4un>PgUI<%`g}@u2((qWFSJWo0^M2qa7uP4bhh-> zl<Y8Qne@q&>~QF1;epU=!V%C%!-Jq}!jaHt!h@mDOaDp9(!MBJsre6w(jp!TrKPM4 z>ANUdS}`R{zd^~;2U4>1jg&0?4<&m7^sJEnfs&P0c|}OiK*`dsDOuVwB}<P-$<h~5 zvh*#KEPXO1OD{*s(kD=|^g@)ZwBn^9y(lG1?@Gzir%|%>&y*~EASFxhP032@zEOI6 zN|yeUl8wgOvf{n9(e`ZKI*G^Hxsi5*dRG3<ke;@e9g4oQz@C-e?{U8U4Z6+J;KpEO z@KEq@uo<Z&<1o<~^$tCttr#gS(x;)pyy&g9Wz6_|z<9o^`kXy_{nQpyVn(9R$d0R- z?6*2cC(L4VrEX?cn45J6_7kk;9g!DJhLQf~W{d8Dp3_VhdmX$UdI0+e2Jrso820I% zz}~gFdYD)4ovTNAi@nSBc&~!FlbQOyU@3WW7WbWrE_H({(|53Dat^)Bb9y6h(QHDm z@|mSy>{4~5OVx_HsZuvfs?<G_Dz&<9s+fCBFypwvYvZNVMz5z=tlswe2Nme$qxhed zb&brqMr2*+j5$|)btL&4g&2{$Is)x7=E>FHqkBq7Im#6|MtphFUC}02v68uge))a% zDSP7rw90n6yY9`dxS{Nco1oL|iJQ%CjC1LKuh!Sn_uhw=xlX^t?zj)xH>gYja}S-^ zQ`wt+lS9mKb!2cS`M8+ak7WI2DkHCQbw2YKOIgFeO+CQ=p7m(3Z>x{h7mTN5UMHn@ zL6aS#N3aL)7(G#!(&wM87t!A@*DLAkS5XG*xnl<1b`!hnqug4Ybsb@2*T|gf@SJNz z&NV*kI)WUDLL9-~%-9u&J%VvS%!|Vw!RR33Njb!>IP6GrHs-})rEFqe9QN>B*u!)8 zJv<lo@Z5b5&xJiacV8*tD83Q7uu|4BFTO7$j2th%@AzEU@wu?$b75uN6U8z<7nb)x z;oarxPIQ!Km2<hH93_k#<!*9}{7b3#VEp{u%KapE{m8uD*~t1!sB1`i-gS)1dQ{Fe zGUpnRbx|fcSA3QH&+^E*oGZRcE;*i*W^`3t(N%H9SL2wagyXAm%u>Q}SV|%rUmUZP zS9Dce(N%FpF-ytCSL2w47hjEImeP#FO3YW1(-LoV9GN{ve2G!`5?^$T_!5_#OH9#G z_{MRK9f=xbvin+hVej)0GXe`A$1rLvHS^qF!OPjhwURm1Rpu#n1HWe8H=mlX*~{9L zQDk?ox7VL{<VUdwI_;gpXmY-Hu6L1lwRbK12k-N$7*)RHz3F}EZDz}5f!~66&wBcO zc*ktGKh{6mKgpl&pT;P2A#aXV_&4x|*h9Rp{Ty#uzw2-EzvAV$#_TZciY)2Je(I6b zvg7O|JCk>q&SBs5m39StY42gb>{{Nv&Dam@=XMJ_N1L$|v<LGJ1DJ6c6C4?w5KIl` z2Iax|!6nF_>)Av7K(HoQAG{pA9ef;o5t^_lY{ky$UBbTMkZ?peE<7fj$QwHI!n4Cg z;pO4-aAkN`xGH=q+z`IT8#kYZU-Q;X(?q*O_e5{rco~`)m6(u7Cr(MsPRvi7o46=( zb>iAYW#Ya>RbpM@rNo=;&fA<+$%15yWG8mv^+^t5huYZW(aDpN)03wq7bF)Zmn18a zHze;!KE%Gd=aL(f?<O}Tzv6|z#szH(x)$^*=vPouFtT8L!EptX3T76}D_Br)e!)cr zR~9TUxS`;-g8K?q7d%z)9DCi~Ecl?{(}J%GO<_S{v%+?TT?=<9>{B?PaA@Jk!f}O1 z7oJdfO5x1Hd4&rK&u2&6<%LTNR|L1^+F0y5BF7t<a~+;@jmWviXI=EDIaeI^2<lrD zA3bW$6^Erq&GMw)M*c?HIIPs+m=}kY))DjKu=J=|9zAN#6^Erq&GF)}^r%^$w2LS{ zdeodN4m%<jR@z<cFAhtOn&r`>=3H@Ddej^*4oi>fcp`hEu;PlsiYp2$t|+XyqOjtM z!{W}yg5=7%;;?Z(i&Trl#`#QX<if`JEW9}E$lQJ7e3q;6edBx<*%;p!$(2n(oX>JK zzHgk*q$GFWIG=?VhmG@DcyU;E31?%D^I7C?d|xD2?rNM@NUj_&3d_@G?<=k-thl1E z;)=qGD+(*FIBcBH<Ww$fT%Uy(hmG@DcyU<bacLoMb7A9r7G4}S&S&ApVdH$pS1xRv z&%%qt#`!F~IBcBH#G4Bn=d<wQuyH;MFAf{$v-GfWUd8z=yf|!}&%%qsicS+fuehSH z;)?QFTv1qYMfohQIBcBHq7TJk<9rrg95&8p;l*L&d=?EV4jbpQ@Zzv>J_|1nOB!4m ziOv;=jq_P}ao9MYg%^j7^I5dCIBcBH!i&Sk`7FFRY@E-;mJ1u_v+&}uaXt$#3d>!y zX%<&>UvWkGEUqZ5xT1U(R~$CZXHuLC8|Sm|;;?Z(3oi~E=QGck3mfON@Zzv>J_|1n z8|O2*k_#K>v+&}uaXt$#4omu78Ig~<uyH;MFAf{$v+&}uaX!Pzg^lxBcyZV`pM@8N zC1m!#;)?QFTv0xYD+(*FD4)d@hmG@@yXL~i`7FFRY@E-+i^InGOgeL6<9rrg95&8p z;l*L&eCEk=VdH!jUK}>gXW_+R<9sGBa$)0q7G4}S&S&ApVR?2}M&w>DY@E-+i^InG zEWGgndnd`WQQ6}W+2hFU@$l?%MD`f@mz<SwlCRM*@-KNBok#v9KjmC<Gdf28B`2fv z$iL)bbRPMa9F%j(zvvkGm%NM4Bma_baxS?R9V7peW6^o!UveutkNitc$+_fHbd3B< z9!2Mo|0sV%JHHhjUHqb>J9@axlyG03Omdc-SYUtaR_%Ot7Ib2#d5K%wHR>4iAuqkY z#@vdm^%_RteK}v3J0E1$x_K9+cFy`3$y%+<#3**+G9Zu=u9=&W`I%_%f|XT1Ke$4C zG<f~D+)Y-BD#eGRdGW9pSmt;}sfp}h7|tB;(RwPoCz`VRjXmr-P{q24cmI5J`*WD7 z_#L^0EBK>3o)ugtvs}UT(BHcqu5Ysj)<S(AEYDtF#y!vCIeM^PVgNfpYeu2b4(=t) zKU}MBX2yf}C3AO)U5m54i@9^eyO@!!xGr|7iCr--_IEMY7YDqd<I)-(FAg5arYSl` z*NMmZBZRvyG01fZe;-eEXK+81u{62VRJFl+fW*1j9mUT*JfFOE<V#NQq~ansa7B4e z9K<Meah5}TaD+;@1oh<{)-x|=7Vb)Um(<mr19&gt26nT*#v(3nnKSz`Q=iY+`(|d> zUu1sgYtxvwX8SVod((W1(Xcj*@y_;^cq@1xXAKq^hx!xzNq(7sk-q|)9_##ft>WFV zU6}hjhWA_-VV7wY@6=rxYzqCbUD$`wRhn_tMU1GP2sehGCJM0H(?2nevD4YEF3Os- z;k}PZq*$rlpxVynie?{vv>qL;&PP{9xGN`RuTaZ#VMe+uvKyxbXqR_o6mG-%@*sQ| zuJ+`-I9vNA*4l0CJiEmCpKWDtlE$`{v?<@*XK&EW><zf1R%Xrh_HsNZdwGr*m`m*0 z)KdN&W1pd!Z>RI-fcxW$`M#ORyTTJ#-L2t_x5HR*xGTr8W~l8Ge8GyxiacI#-n{D+ zyiG~QAp_G+Y?d&_U9ZY=T+jIa=Xv`~eE)4VL4OX7!&(11TFwCdCdZiP`w!`L*g1PB z$FcrO_Mx{>o&1%KXC#M`8Qj=3150pS&JO=enKS%cpT$b^5WUHJ)O$>S;yveg<c{yz zB2!|E>>fNb_D;+~vHW2!2!;nom_=ey!&C&9220GcU{&y_xh8lbc*<PI)4XJE2wn;P zYHknr3`d#Oy!W-xYz$vbSo5#ME{VO^A4yH&eG$!Shy0|3NAV7dW{o8}_t;xDi1)-b z>l+crr_Yl=eI_eI5zo?3MStEG3_f(+@35lJd(9VSSM+yxEBgCL`X~_{&?0Si_YVED zmNj<fIV6>?&t^~D(b=@X3DQBDep7y~_61|*&vV2a%RUqL=Q(0t5bP5Fc^3H@tU&$4 zU=e}$Kp#|-dH3!$^@PcIgVp=&h}%~W_xAG+&<A=4dgaWFFYs3DbHuWdew<mGcUkwi zfVcXtX69pOlS%YTOz?b{8f%vNCkc3eL4HyKD|ot_aZ7vb4afLHWDl6VI?LM~p6Bo4 z?}ALwR=%kyK8sy^+Rh|~sQvGe<)q1-v}P|DzWQW&qv#*eBJ@zljoPJVuZ1r=E^I&E z_YvQHv%Wj=mMpDA{#tqjrS{5R@s(lE<X4W@FXH)|NZlsyBHrs0pL=JyFJngudqeC} zUGMn)v;5V{iX}pSwRaNl21y7hXP(KwUIqU3(cc@1{E=$|vey>!PIhmvw-TAESYeAo z%;g?)>1EqUt$;7Rl*Du#_c+c!PPUP=D@?8o&R$_>mp@GY*d=(E*yY-g?6scQOzIi^ znZ0|;?Nd@%IWzQ#-?p6&wENLcrL|s+{&6gQ+Z;Q`EJdPRWq$A4?@HHxZ*uMTVb^|F zA!i;mt6eKz?^^K&*NXq_T5-m;;=fBP_L9<yy`HWWA4=+_XO=x^!{zBg37h~M0|&69 z<~Tcx`XKfO+t}aP->J5?!Y)(of@6bY*;hqx#Lk`)7cR)XfsB=3V%2&T76LAhj`-M3 z?j&mraL<Oz(cOz|W)6CxrtkNr!xtH#6c)p9Q)4~W=44I8afiA%j*}YekxK1c_}(AB zhn|C`!<C*~aW1Y5bMf?p%Uhpr?-_3Ryt$6A7VKQqW;0h>L~gDOm$;~-UumCf8*AKL zJCHNY>W$c!i;mgna_c%`@j<h`Bk@a1^w>pu2k)<GG$HMdN=a+f=ti2J+pwQbTv2N@ zy#FM+Lpl7&rIfTukN2advnH&O(<bYRTBcZ6(db?twRF65D<y4H_Vf<tinK84fyEuQ zP7m#`tCH5*fori_+9%@FyOEIWE~6h+;$q#g2QCRK>xR7ljJ&>#x631qcn<F6a>ct5 z2_Naov#~d^EH8xY{JQM>?fu%8B@WVr9c$99-2N|mB-ba2jSNeQMvz`Kc$IYfzlz-c zuV!xlSMypsziOT^t#<oSQ(N$D_o=ybc6Rbi_T1%{ME;JtY)Ry|z&a(tUW#mavEEG? z`PsOnmdKc<O}1Y6mc0vBYKU*yLt%G!F5!vp7V%`<;@h5Rp1s`lI92Q#Y|u(UH)n_C z7j_=u8s0V7Jhst-kNnsgvWZaR7aLXFFGcBhv)xnycbUgq*flXUCY3v2J-Y+z?~-C` zTVv~Fbd3)^44Y*SVjJXPH0MWH+jtZkAXp_8`)2&HNs4Wcwb=B)A}Myx)?>ZnIjnYU zz^d7c_Rm-~`wKSBUa=ePt70XbRT1F{=hduxTi4&f2HIQpZLE|=$9L_!_C5PfOlwk! zEE{Ftw|`~L<!|;w`;q<F{@re}pV&|BXZ%06|FE0wKkXOxOa5Qkf7!3?H+GAy4%pz! zep3%CA($e^?neU49tGI$5T0-nS@*WC7hy%CanJ<28qskFY+p3P7Dkqm%2EGS7BA}B zyO90APE&QgUOe(Xgmkl*uCd>Os~PC&>YY;T-Jn+0+qWzJ^$<@69dZ4X+I2J5UjSS* z8`jkSfsdM&CAv>St_8eb>uEa!x7PXXxPtfi8?eNLC8Y0TM``0$?I?}inkA(?J0o3b zP0peSInhLkB_l-%H^=VhGiJTo+1V=E&DkpI?JRrtbGC~1ah4qS7yBjH2Ndg#!?9n| zR2|5gVjFBNW48qR${lS-><p%8v!lq1I49Xv-O&B>tSr^oSYnLHoy1BC`>Bar?4fkT zPJVN-Uxf9d_Td<@ScJWz33+!o1x@yQTDQV}%hs$@P0RH%QrpDplTu>!shP{Ml*_U1 zF2{Csww!jOepw`AYuj4wPLI?~6=Q2@G}1oE=84lowdfd9H<eg+P(55JC}&BwGrNuF zs~)TwU!YEOwpOQMRc5eS=IpDka8^feclJi_VEuQbT8Bl^Gt{fjI`~`8a_BqG{`WV| z`sWtbfH$gYvF1r-eZzlKYq8g<J&Rc{%%WoVTHA0*xI_oxRpC|a_o@h&=>+Svcj*Gw zY9G;s;o5MmZj@+|XsH{E?Lyr&(K*psH^X*ecimj<73x-rL5ULGI&o0qAl(6bg=2I_ z<|4-F&WU-6({(DjD!EE`%_}o$U(NXHK}U9Bkt|BR4W%5-NJRRG_-Mhm5IVEdOMDdY zHHB8}{fcXr*miU6kGj&4x^Y;oD}f8sl)BTtR$WP{Mq4jy=^b3DDp#t!Tf}>rHW#l^ zgl4bzR_v7#+PF5OT$_=;Cu#$c6*|$ursjACy)E^9YpvWdzeeW8F1gp-cEgg{QE<gZ zQ#;xa`g2~I&>A>*eL#-y()B%iC>c4>`_ijOeQqgsBZ)sMRe4h-`k9}fJZUyF3ToEW K>9chcrT;%KY-A|_ diff --git a/lawnchair/res/font/inter_semi_bold.ttf b/lawnchair/res/font/inter_semi_bold.ttf deleted file mode 100644 index ddb279290ba32b02722d7bee592d7df22a16e86f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 293028 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%0D38jB&{w}Bz~6@Z)fNgB$^HtzXy?o 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 UK5^r`24)mgeNAnHZmsnH17|=S#Q*>R diff --git a/lawnchair/src/app/lawnchair/LauncherActivityCachingLogic.kt b/lawnchair/src/app/lawnchair/LauncherActivityCachingLogic.kt deleted file mode 100644 index a6fa0bbed5..0000000000 --- a/lawnchair/src/app/lawnchair/LauncherActivityCachingLogic.kt +++ /dev/null @@ -1,23 +0,0 @@ -package app.lawnchair - -import android.content.Context -import android.content.pm.LauncherActivityInfo -import androidx.annotation.Keep -import app.lawnchair.preferences.PreferenceManager -import com.android.launcher3.icons.LauncherActivityCachingLogic as BaseLogic -import com.android.launcher3.util.ComponentKey - -@Keep -class LauncherActivityCachingLogic(context: Context) : BaseLogic() { - - private val prefs = PreferenceManager.getInstance(context) - - override fun getLabel(info: LauncherActivityInfo): CharSequence { - val key = ComponentKey(info.componentName, info.user) - val customLabel = prefs.customAppName[key] - if (!customLabel.isNullOrEmpty()) { - return customLabel - } - return super.getLabel(info) - } -} diff --git a/lawnchair/src/app/lawnchair/qsb/providers/Mull.kt b/lawnchair/src/app/lawnchair/qsb/providers/Mull.kt deleted file mode 100644 index f4b6238efd..0000000000 --- a/lawnchair/src/app/lawnchair/qsb/providers/Mull.kt +++ /dev/null @@ -1,24 +0,0 @@ -package app.lawnchair.qsb.providers - -import android.content.Intent -import app.lawnchair.qsb.ThemingMethod -import com.android.launcher3.R - -data object Mull : QsbSearchProvider( - id = "Mull", - name = R.string.search_provider_mull, - icon = R.drawable.ic_mull, - themedIcon = R.drawable.ic_mull_tinted, - themingMethod = ThemingMethod.TINT, - packageName = "us.spotco.fennec_dos", - action = "org.mozilla.fenix.OPEN_TAB", - className = "org.mozilla.fenix.IntentReceiverActivity", - website = "https://gitlab.com/divested-mobile/mull-fenix", - type = QsbSearchProviderType.APP, - supportVoiceIntent = true, -) { - - override fun handleCreateVoiceIntent(): Intent = Intent(action) - .addFlags(INTENT_FLAGS) - .setClassName(packageName, "org.chromium.chrome.browser.VoiceSearchActivity") -} diff --git a/lawnchair/src/app/lawnchair/search/algorithms/engine/provider/AppSearchProvider.kt b/lawnchair/src/app/lawnchair/search/algorithms/engine/provider/AppSearchProvider.kt deleted file mode 100644 index 6d5680e025..0000000000 --- a/lawnchair/src/app/lawnchair/search/algorithms/engine/provider/AppSearchProvider.kt +++ /dev/null @@ -1,61 +0,0 @@ -package app.lawnchair.search.algorithms.engine.provider - -import android.content.Context -import app.lawnchair.preferences2.PreferenceManager2 -import app.lawnchair.search.algorithms.engine.SearchResult -import app.lawnchair.search.algorithms.filterHiddenApps -import com.android.launcher3.model.AllAppsList -import com.android.launcher3.model.data.AppInfo -import com.android.launcher3.search.StringMatcherUtility -import com.patrykmichalik.opto.core.firstBlocking -import java.util.Locale -import me.xdrop.fuzzywuzzy.FuzzySearch -import me.xdrop.fuzzywuzzy.algorithms.WeightedRatio - -object AppSearchProvider { - - fun search(context: Context, query: String, allApps: AllAppsList): List<SearchResult.App> { - val prefs = PreferenceManager2.getInstance(context) - val hiddenApps = prefs.hiddenApps.firstBlocking() - val hiddenAppsInSearch = prefs.hiddenAppsInSearch.firstBlocking() - val maxAppResults = prefs.maxAppSearchResultCount.firstBlocking() - val enableFuzzySearch = prefs.enableFuzzySearch.firstBlocking() - - val appResults = if (enableFuzzySearch) { - fuzzySearch(allApps.data, query, maxAppResults, hiddenApps, hiddenAppsInSearch) - } else { - normalSearch(allApps.data, query, maxAppResults, hiddenApps, hiddenAppsInSearch) - } - - return appResults.map { SearchResult.App(data = it) } - } - - private fun normalSearch(apps: List<AppInfo>, query: String, maxResultsCount: Int, hiddenApps: Set<String>, hiddenAppsInSearch: String): List<AppInfo> { - // Do an intersection of the words in the query and each title, and filter out all the - // apps that don't match all of the words in the query. - val queryTextLower = query.lowercase(Locale.getDefault()) - val matcher = StringMatcherUtility.StringMatcher.getInstance() - return apps.asSequence() - .filter { StringMatcherUtility.matches(queryTextLower, it.title.toString(), matcher) } - .filterHiddenApps(queryTextLower, hiddenApps, hiddenAppsInSearch) - .take(maxResultsCount) - .toList() - } - - private fun fuzzySearch(apps: List<AppInfo>, query: String, maxResultsCount: Int, hiddenApps: Set<String>, hiddenAppsInSearch: String): List<AppInfo> { - val queryTextLower = query.lowercase(Locale.getDefault()) - val filteredApps = apps.asSequence() - .filterHiddenApps(queryTextLower, hiddenApps, hiddenAppsInSearch) - .toList() - val matches = FuzzySearch.extractSorted( - queryTextLower, - filteredApps, - { it.sectionName + it.title }, - WeightedRatio(), - 65, - ) - - return matches.take(maxResultsCount) - .map { it.referent } - } -} diff --git a/lawnchair/src/app/lawnchair/ui/preferences/components/DraggablePreference.kt b/lawnchair/src/app/lawnchair/ui/preferences/components/DraggablePreference.kt deleted file mode 100644 index 67b21c5820..0000000000 --- a/lawnchair/src/app/lawnchair/ui/preferences/components/DraggablePreference.kt +++ /dev/null @@ -1,348 +0,0 @@ -package app.lawnchair.ui.preferences.components - -import android.view.HapticFeedbackConstants -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.animateColorAsState -import androidx.compose.animation.core.animateDpAsState -import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Check -import androidx.compose.material.icons.filled.Close -import androidx.compose.material.icons.rounded.DragHandle -import androidx.compose.material3.Card -import androidx.compose.material3.CardDefaults -import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi -import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.IconButtonDefaults -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface -import androidx.compose.material3.Switch -import androidx.compose.material3.SwitchDefaults -import androidx.compose.material3.Text -import androidx.compose.material3.ripple -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.key -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalView -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.semantics.CustomAccessibilityAction -import androidx.compose.ui.semantics.customActions -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.unit.dp -import app.lawnchair.ui.preferences.components.controls.ClickablePreference -import app.lawnchair.ui.preferences.components.layout.ExpandAndShrink -import app.lawnchair.ui.preferences.components.layout.PreferenceGroup -import app.lawnchair.ui.preferences.components.layout.PreferenceGroupHeading -import app.lawnchair.ui.preferences.components.layout.PreferenceTemplate -import app.lawnchair.ui.theme.preferenceGroupColor -import app.lawnchair.ui.util.addIf -import com.android.launcher3.R -import com.android.launcher3.Utilities -import sh.calvin.reorderable.ReorderableColumn -import sh.calvin.reorderable.ReorderableListItemScope - -@Composable -fun <T> DraggablePreferenceGroup( - label: String?, - items: List<T>, - defaultList: List<T>, - onOrderChange: (List<T>) -> Unit, - modifier: Modifier = Modifier, - onSettle: ((List<T>) -> Unit)? = null, - itemContent: @Composable ReorderableListItemScope.( - item: T, - index: Int, - isDragging: Boolean, - onDraggingChange: (Boolean) -> Unit, - ) -> Unit, -) { - var localItems by remember { mutableStateOf(items) } - - LaunchedEffect(items) { - if (localItems != items) { - localItems = items - } - } - - var isAnyDragging by remember { mutableStateOf(false) } - - LaunchedEffect(items) { - if (localItems != items) { - localItems = items - } - } - - val view = LocalView.current - - val color by animateColorAsState( - targetValue = if (!isAnyDragging) preferenceGroupColor() else MaterialTheme.colorScheme.surface, - label = "card background animation", - ) - - Column(modifier) { - PreferenceGroupHeading( - label, - ) - Surface( - modifier = Modifier.padding(horizontal = 16.dp), - shape = MaterialTheme.shapes.large, - color = color, - ) { - ReorderableColumn( - list = localItems, - onSettle = { fromIndex, toIndex -> - val newItems = localItems.toMutableList().apply { - add(toIndex, removeAt(fromIndex)) - }.toList() - localItems = newItems - onOrderChange(newItems) - if (onSettle != null) { - onSettle(newItems) - } - isAnyDragging = false - }, - onMove = { - isAnyDragging = true - if (Utilities.ATLEAST_U) { - view.performHapticFeedback(HapticFeedbackConstants.SEGMENT_FREQUENT_TICK) - } - }, - ) { index, item, isDragging -> - key(item.hashCode()) { - ReorderableItem { - Column { - DraggablePreferenceContainer( - isDragging = isDragging, - modifier = Modifier - .a11yDrag( - index = index, - items = items, - onMoveUp = { - localItems = it - onOrderChange(it) - if (onSettle != null) { - onSettle(it) - } - }, - onMoveDown = { - localItems = it - onOrderChange(it) - if (onSettle != null) { - onSettle(it) - } - }, - ), - ) { - itemContent( - item, - index, - isDragging, - ) { isAnyDragging = it } - } - - AnimatedVisibility(!isAnyDragging && index != localItems.lastIndex) { - HorizontalDivider( - Modifier.padding(start = 50.dp, end = 16.dp), - ) - } - } - } - } - } - } - - ExpandAndShrink(visible = localItems != defaultList) { - PreferenceGroup { - Item { - ClickablePreference(label = stringResource(id = R.string.action_reset)) { - val resetList = defaultList - onOrderChange(resetList) - if (onSettle != null) { - onSettle(resetList) - } - } - } - } - } - } -} - -@Composable -fun DraggablePreferenceContainer( - isDragging: Boolean, - modifier: Modifier = Modifier, - content: @Composable () -> Unit, -) { - val elevation by animateDpAsState(if (isDragging) 4.dp else 0.dp, label = "elevation") - - Card( - elevation = CardDefaults.cardElevation(defaultElevation = elevation), - colors = if (isDragging) { - CardDefaults.elevatedCardColors() - } else { - CardDefaults.cardColors(containerColor = Color.Transparent) - }, - modifier = modifier, - ) { - content() - } -} - -@Composable -fun DraggableSwitchPreference( - label: String, - checked: Boolean, - onCheckedChange: (Boolean) -> Unit, - dragIndicator: @Composable () -> Unit, - modifier: Modifier = Modifier, - interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, - enabled: Boolean = true, - description: String? = null, -) { - PreferenceTemplate( - modifier = modifier.clickable( - enabled = enabled, - onClick = { - onCheckedChange(!checked) - }, - interactionSource = interactionSource, - indication = ripple(), - ), - contentModifier = Modifier - .fillMaxHeight() - .padding(vertical = 16.dp) - .padding(start = 16.dp), - title = { Text(text = label) }, - description = { description?.let { Text(text = it) } }, - startWidget = { - dragIndicator() - }, - endWidget = { - Switch( - modifier = Modifier - .padding(all = 16.dp) - .height(24.dp), - checked = checked, - onCheckedChange = onCheckedChange, - enabled = enabled, - thumbContent = { - if (checked) { - Icon( - imageVector = Icons.Filled.Check, - contentDescription = null, - modifier = Modifier.size(SwitchDefaults.IconSize), - ) - } else { - Icon( - imageVector = Icons.Filled.Close, - contentDescription = null, - modifier = Modifier.size(SwitchDefaults.IconSize), - ) - } - }, - ) - }, - enabled = enabled, - applyPaddings = false, - ) -} - -@OptIn(ExperimentalMaterial3ExpressiveApi::class) -@Composable -fun DragHandle( - scope: ReorderableListItemScope, - interactionSource: MutableInteractionSource, - modifier: Modifier = Modifier, - isDraggable: Boolean = true, - onDragStop: () -> Unit = {}, -) { - val view = LocalView.current - IconButton( - modifier = with(scope) { - modifier.addIf(isDraggable) { - draggableHandle( - interactionSource = interactionSource, - onDragStarted = { - if (Utilities.ATLEAST_U) { - view.performHapticFeedback(HapticFeedbackConstants.DRAG_START) - } - }, - onDragStopped = { - if (Utilities.ATLEAST_R) { - view.performHapticFeedback(HapticFeedbackConstants.GESTURE_END) - } - onDragStop() - }, - ) - } - }, - enabled = isDraggable, - onClick = {}, - interactionSource = interactionSource, - shapes = IconButtonDefaults.shapes(), - ) { - Icon( - imageVector = Icons.Rounded.DragHandle, - contentDescription = "Drag indicator", - modifier = Modifier.width(24.dp), - ) - } -} - -fun <T> Modifier.a11yDrag( - index: Int, - items: List<T>, - onMoveUp: (List<T>) -> Unit, - onMoveDown: (List<T>) -> Unit, -) = this.semantics { - customActions = listOf( - CustomAccessibilityAction( - label = "Move up", - action = { - if (index > 0) { - onMoveUp( - items - .toMutableList() - .apply { - add(index - 1, removeAt(index)) - }, - ) - true - } else { - false - } - }, - ), - CustomAccessibilityAction( - label = "Move down", - action = { - if (index < items.size - 1) { - onMoveDown( - items.toMutableList() - .apply { - add(index + 1, removeAt(index)) - }, - ) - true - } else { - false - } - }, - ), - ) -} diff --git a/quickstep/res/drawable/app_chip_menu_item_fg.xml b/quickstep/res/drawable/app_chip_menu_item_fg.xml deleted file mode 100644 index 96d067d5b8..0000000000 --- a/quickstep/res/drawable/app_chip_menu_item_fg.xml +++ /dev/null @@ -1,20 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2025 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> -<shape xmlns:android="http://schemas.android.com/apk/res/android"> - <solid android:color="@color/app_chip_menu_item_color_fg" /> - <corners android:radius="@dimen/task_menu_item_corner_radius" /> -</shape> diff --git a/quickstep/res/drawable/bg_bubble_dismiss_circle.xml b/quickstep/res/drawable/bg_bubble_dismiss_circle.xml deleted file mode 100644 index b793eecdf9..0000000000 --- a/quickstep/res/drawable/bg_bubble_dismiss_circle.xml +++ /dev/null @@ -1,27 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2023 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> - -<shape - xmlns:android="http://schemas.android.com/apk/res/android" - android:shape="oval"> - - <stroke - android:width="2dp" - android:color="@android:color/system_accent1_600" /> - - <solid android:color="@android:color/system_accent1_600" /> -</shape> \ No newline at end of file diff --git a/quickstep/res/drawable/bg_circle.xml b/quickstep/res/drawable/bg_circle.xml deleted file mode 100644 index 506177b6a1..0000000000 --- a/quickstep/res/drawable/bg_circle.xml +++ /dev/null @@ -1,20 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2020 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<shape xmlns:android="http://schemas.android.com/apk/res/android" - android:shape="oval"> - <solid android:color="#FFFFFFFF" /> -</shape> \ No newline at end of file diff --git a/quickstep/res/drawable/bg_overview_add_desktop_button.xml b/quickstep/res/drawable/bg_overview_add_desktop_button.xml deleted file mode 100644 index 12581bf8cc..0000000000 --- a/quickstep/res/drawable/bg_overview_add_desktop_button.xml +++ /dev/null @@ -1,27 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2025 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<ripple android:color="?android:attr/colorControlHighlight" - xmlns:android="http://schemas.android.com/apk/res/android"> - <item> - <shape - android:shape="rectangle" - android:tint="?colorButtonNormal"> - <corners android:radius="@dimen/add_desktop_button_size" /> - <solid android:color="@color/materialColorSurfaceBright"/> - </shape> - </item> -</ripple> \ No newline at end of file diff --git a/quickstep/res/drawable/bg_overview_clear_all_button.xml b/quickstep/res/drawable/bg_overview_clear_all_button.xml deleted file mode 100644 index 3b83092e48..0000000000 --- a/quickstep/res/drawable/bg_overview_clear_all_button.xml +++ /dev/null @@ -1,27 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - 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. ---> -<ripple android:color="?android:attr/colorControlHighlight" - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> - <item> - <shape android:shape="rectangle" - android:tint="?colorButtonNormal"> - <corners android:radius="24dp" /> - <solid android:color="?attr/materialColorSurfaceBright"/> - </shape> - </item> -</ripple> diff --git a/quickstep/res/drawable/ic_bubble_dismiss_white.xml b/quickstep/res/drawable/ic_bubble_dismiss_white.xml deleted file mode 100644 index b15111b821..0000000000 --- a/quickstep/res/drawable/ic_bubble_dismiss_white.xml +++ /dev/null @@ -1,25 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2023 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="20dp" - android:height="20dp" - android:viewportWidth="24.0" - android:viewportHeight="24.0"> - <path - android:pathData="M19.000000,6.400000l-1.400000,-1.400000 -5.600000,5.600000 -5.600000,-5.600000 -1.400000,1.400000 5.600000,5.600000 -5.600000,5.600000 1.400000,1.400000 5.600000,-5.600000 5.600000,5.600000 1.400000,-1.400000 -5.600000,-5.600000z" - android:fillColor="@android:color/system_neutral1_50"/> -</vector> diff --git a/quickstep/res/drawable/ic_close_option.xml b/quickstep/res/drawable/ic_close_option.xml deleted file mode 100644 index 5681cb58e7..0000000000 --- a/quickstep/res/drawable/ic_close_option.xml +++ /dev/null @@ -1,26 +0,0 @@ -<!-- - ~ Copyright (C) 2024 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> - -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="960" - android:viewportHeight="960" - android:tint="?attr/colorControlNormal"> - <path - android:fillColor="@android:color/white" - android:pathData="M256,760L200,704L424,480L200,256L256,200L480,424L704,200L760,256L536,480L760,704L704,760L480,536L256,760Z" /> -</vector> diff --git a/quickstep/res/drawable/ic_empty_recents.xml b/quickstep/res/drawable/ic_empty_recents.xml deleted file mode 100644 index 5183733975..0000000000 --- a/quickstep/res/drawable/ic_empty_recents.xml +++ /dev/null @@ -1,30 +0,0 @@ -<!-- -Copyright (C) 2018 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="92dp" - android:height="80dp" - android:viewportWidth="92.0" - android:viewportHeight="80.0" - android:tint="?android:attr/textColorPrimary"> - <path - android:fillColor="#AAFFFFFF" - android:pathData="M18,6H2C0.9,6,0,6.9,0,8v64c0,1.1,0.9,2,2,2h16c1.1,0,2-0.9,2-2V8C20,6.9,19.1,6,18,6z M16,69.25 - c0,0.41-0.34,0.75-0.75,0.75H4.75C4.34,70,4,69.66,4,69.25v-58.5C4,10.34,4.34,10,4.75,10h10.5c0.41,0,0.75,0.34,0.75,0.75V69.25 - z M90,6H74c-1.1,0-2,0.9-2,2v64c0,1.1,0.9,2,2,2h16c1.1,0,2-0.9,2-2V8C92,6.9,91.1,6,90,6z M88,69.25c0,0.41-0.34,0.75-0.75,0.75 - h-10.5C76.34,70,76,69.66,76,69.25v-58.5c0-0.41,0.34-0.75,0.75-0.75h10.5c0.41,0,0.75,0.34,0.75,0.75V69.25z M64,0H28 - c-2.21,0-4,1.79-4,4v72c0,2.21,1.79,4,4,4h36c2.21,0,4-1.79,4-4V4C68,1.79,66.21,0,64,0z M64,75c0,0.55-0.45,1-1,1H29 - c-0.55,0-1-0.45-1-1V5c0-0.55,0.45-1,1-1h34c0.55,0,1,0.45,1,1V75z"/> -</vector> diff --git a/quickstep/res/drawable/keyboard_quick_switch_overview_button_background.xml b/quickstep/res/drawable/keyboard_quick_switch_overview_button_background.xml deleted file mode 100644 index 22ab5e5a62..0000000000 --- a/quickstep/res/drawable/keyboard_quick_switch_overview_button_background.xml +++ /dev/null @@ -1,21 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2023 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<shape - xmlns:android="http://schemas.android.com/apk/res/android" - android:shape="rectangle"> - <solid android:color="?attr/materialColorSurfaceBright" /> - <corners android:radius="@dimen/keyboard_quick_switch_task_view_radius" /> -</shape> diff --git a/quickstep/res/layout/gesture_tutorial_dialog.xml b/quickstep/res/layout/gesture_tutorial_dialog.xml deleted file mode 100644 index db6ec8533f..0000000000 --- a/quickstep/res/layout/gesture_tutorial_dialog.xml +++ /dev/null @@ -1,88 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- 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. ---> -<androidx.constraintlayout.widget.ConstraintLayout - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:background="@drawable/bg_sandbox_feedback" - android:paddingTop="24dp" - android:paddingBottom="24dp" - android:paddingStart="16dp" - android:paddingEnd="16dp"> - - <TextView - android:id="@+id/gesture_tutorial_dialog_title" - style="@style/TextAppearance.GestureTutorial.Dialog.Title" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:text="@string/skip_tutorial_dialog_title" - - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintTop_toTopOf="parent"/> - - <TextView - android:id="@+id/gesture_tutorial_dialog_subtitle" - style="@style/TextAppearance.GestureTutorial.Dialog.Subtitle" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_marginTop="16dp" - android:text="@string/skip_tutorial_dialog_subtitle" - - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintTop_toBottomOf="@id/gesture_tutorial_dialog_title"/> - - <!-- android:stateListAnimator="@null" removes shadow and normal on click behavior (increase - of elevation and shadow) which is replaced by ripple effect in android:foreground --> - <Button - android:id="@+id/gesture_tutorial_dialog_cancel_button" - style="@style/TextAppearance.GestureTutorial.CancelButtonLabel" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="46dp" - android:paddingTop="8dp" - android:paddingBottom="8dp" - android:paddingStart="16dp" - android:paddingEnd="16dp" - android:background="@drawable/gesture_tutorial_cancel_button_background" - android:foreground="?android:attr/selectableItemBackgroundBorderless" - android:stateListAnimator="@null" - android:text="@string/gesture_tutorial_action_button_label_cancel" - - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/gesture_tutorial_dialog_subtitle"/> - - <Button - android:id="@+id/gesture_tutorial_dialog_confirm_button" - style="@style/TextAppearance.GestureTutorial.ButtonLabel" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="46dp" - android:paddingTop="8dp" - android:paddingBottom="8dp" - android:paddingStart="16dp" - android:paddingEnd="16dp" - android:background="@drawable/gesture_tutorial_action_button_background" - android:foreground="?android:attr/selectableItemBackgroundBorderless" - android:stateListAnimator="@null" - android:text="@string/gesture_tutorial_action_button_label_skip" - - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintTop_toBottomOf="@id/gesture_tutorial_dialog_subtitle"/> - -</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file diff --git a/quickstep/res/layout/keyboard_quick_switch_textonly_taskview.xml b/quickstep/res/layout/keyboard_quick_switch_textonly_taskview.xml deleted file mode 100644 index f13d1cb396..0000000000 --- a/quickstep/res/layout/keyboard_quick_switch_textonly_taskview.xml +++ /dev/null @@ -1,64 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2023 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<com.android.launcher3.taskbar.KeyboardQuickSwitchTaskView - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - xmlns:launcher="http://schemas.android.com/apk/res-auto" - android:layout_width="@dimen/keyboard_quick_switch_taskview_width" - android:layout_height="@dimen/keyboard_quick_switch_taskview_height" - android:clipToOutline="true" - android:importantForAccessibility="yes" - launcher:borderColor="?attr/materialColorOutline"> - - <androidx.constraintlayout.widget.ConstraintLayout - android:id="@+id/content" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="@drawable/keyboard_quick_switch_overview_button_background" - - app:layout_constraintTop_toTopOf="parent" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toEndOf="parent"> - - <ImageView - android:id="@+id/icon" - android:layout_width="@dimen/keyboard_quick_switch_recents_icon_size" - android:layout_height="@dimen/keyboard_quick_switch_recents_icon_size" - android:layout_marginBottom="8dp" - android:src="@drawable/ic_empty_recents" - android:tint="?attr/materialColorOnSurface" - app:layout_constraintVertical_chainStyle="packed" - app:layout_constraintTop_toTopOf="parent" - app:layout_constraintBottom_toTopOf="@id/text" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toEndOf="parent"/> - - <TextView - style="@style/KeyboardQuickSwitchText" - android:id="@+id/text" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textAlignment="center" - app:layout_constraintTop_toBottomOf="@id/icon" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toEndOf="parent"/> - - </androidx.constraintlayout.widget.ConstraintLayout> - -</com.android.launcher3.taskbar.KeyboardQuickSwitchTaskView> diff --git a/quickstep/res/layout/task_thumbnail_view_header.xml b/quickstep/res/layout/task_thumbnail_view_header.xml deleted file mode 100644 index 70e4a42b41..0000000000 --- a/quickstep/res/layout/task_thumbnail_view_header.xml +++ /dev/null @@ -1,72 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?><!-- - Copyright (C) 2025 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<com.android.quickstep.views.TaskThumbnailViewHeader - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:id="@+id/task_thumbnail_view_header" - android:background="@drawable/task_thumbnail_header_bg"> - - <androidx.constraintlayout.widget.ConstraintLayout - android:layout_width="match_parent" - android:layout_height="@dimen/task_thumbnail_header_height" - android:layout_marginStart="@dimen/task_thumbnail_header_margin_edge" - android:layout_marginEnd="@dimen/task_thumbnail_header_margin_edge" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintTop_toTopOf="parent"> - <ImageView - android:id="@+id/header_app_icon" - android:contentDescription="@string/header_app_icon_description" - android:layout_width="@dimen/task_thumbnail_header_icon_size" - android:layout_height="@dimen/task_thumbnail_header_icon_size" - android:layout_marginEnd="@dimen/task_thumbnail_header_margin_between_views" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toStartOf="@id/header_app_title" - app:layout_constraintTop_toTopOf="parent" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintHorizontal_bias="0" - app:layout_constraintVertical_bias="0.5" - app:layout_constraintHorizontal_chainStyle="spread_inside" /> - <TextView - android:id="@+id/header_app_title" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginStart="@dimen/task_thumbnail_header_margin_between_views" - android:layout_marginEnd="@dimen/task_thumbnail_header_margin_between_views" - android:text="@string/header_default_app_title" - app:layout_constraintStart_toEndOf="@id/header_app_icon" - app:layout_constraintTop_toTopOf="parent" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintHorizontal_bias="0" - app:layout_constraintVertical_bias="0.5" /> - <ImageButton - android:id="@+id/header_close_button" - android:contentDescription="@string/header_close_icon_description" - android:layout_width="@dimen/task_thumbnail_header_icon_size" - android:layout_height="@dimen/task_thumbnail_header_icon_size" - android:layout_marginStart="@dimen/task_thumbnail_header_margin_between_views" - android:src="@drawable/task_header_close_button" - android:tint="@android:color/darker_gray" - android:background="@null" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintTop_toTopOf="parent" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintHorizontal_bias="1" - app:layout_constraintVertical_bias="0.5" /> - </androidx.constraintlayout.widget.ConstraintLayout> -</com.android.quickstep.views.TaskThumbnailViewHeader> diff --git a/quickstep/res/layout/widget_picker_activity.xml b/quickstep/res/layout/widget_picker_activity.xml deleted file mode 100644 index 3388e40f0c..0000000000 --- a/quickstep/res/layout/widget_picker_activity.xml +++ /dev/null @@ -1,24 +0,0 @@ -<!-- - ~ Copyright (C) 2023 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> - -<com.android.launcher3.dragndrop.SimpleDragLayer - xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/drag_layer" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:clipChildren="false" - android:clipToPadding="false" - android:importantForAccessibility="no" /> diff --git a/quickstep/res/raw-h480dp/all_set_page_bg.json b/quickstep/res/raw-h480dp/all_set_page_bg.json deleted file mode 100644 index f2998a038a..0000000000 --- a/quickstep/res/raw-h480dp/all_set_page_bg.json +++ /dev/null @@ -1 +0,0 @@ -{"v":"5.8.1","fr":60,"ip":0,"op":181,"w":701,"h":841,"nm":"SUW_WelcomeScreen_FoldableOpen_Portrait_Dynamic","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".primary","cl":"primary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":180,"s":[55]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.07,"y":0.949},"o":{"x":0.167,"y":0.167},"t":0,"s":[181.172,148.425,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.773,"y":0.038},"t":95,"s":[181.172,-68.377,0],"to":[0,0,0],"ti":[0,0,0]},{"t":180,"s":[181.172,148.425,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-3514.717,-358.642,0],"ix":1,"l":2},"s":{"a":0,"k":[21.6,21.6,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[100.594,-128.921],[118.85,-93.491],[148.984,-67.406],[148.684,-27.55],[163.244,9.552],[144.457,44.702],[140.106,84.321],[107.136,106.715],[84.872,139.773],[45.271,144.279],[10.194,163.205],[-26.964,148.792],[-66.818,149.249],[-93.023,119.218],[-128.524,101.101],[-137.771,62.332],[-160.786,29.793],[-150.957,-8.833],[-156.215,-48.341],[-129.561,-77.974],[-115.856,-115.401],[-78.484,-129.253],[-48.956,-156.023],[-9.427,-150.921],[29.159,-160.903],[61.789,-138.015]],"o":[[-100.594,128.921],[-118.85,93.491],[-148.984,67.406],[-148.684,27.55],[-163.244,-9.552],[-144.456,-44.702],[-140.106,-84.321],[-107.136,-106.715],[-84.872,-139.773],[-45.271,-144.279],[-10.194,-163.205],[26.964,-148.792],[66.818,-149.249],[93.023,-119.218],[128.524,-101.101],[137.771,-62.332],[160.786,-29.793],[150.957,8.833],[156.215,48.341],[129.561,77.974],[115.856,115.4],[78.484,129.253],[48.956,156.023],[9.427,150.921],[-29.159,160.903],[-61.789,138.015]],"v":[[975.226,761.299],[707.165,899.424],[509.8,1127.42],[208.253,1125.148],[-72.46,1235.308],[-338.41,1093.162],[-638.164,1060.25],[-807.592,810.792],[-1057.715,642.348],[-1091.808,342.727],[-1235.002,77.338],[-1125.948,-203.807],[-1129.407,-505.342],[-902.191,-703.604],[-765.123,-972.207],[-471.796,-1042.166],[-225.603,-1216.305],[66.637,-1141.935],[365.557,-1181.715],[589.761,-980.053],[872.928,-876.361],[977.734,-593.606],[1180.277,-370.197],[1141.675,-71.124],[1217.196,220.821],[1044.029,467.699]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[-3509.952,-363.731],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Polystar 1","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.713725490196,0.556862745098,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":9.3,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":181,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".tertiary","cl":"tertiary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.07],"y":[0.619]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[67]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.719],"y":[-0.263]},"t":95,"s":[82]},{"t":180,"s":[67]}],"ix":10},"p":{"s":true,"x":{"a":1,"k":[{"i":{"x":[0.07],"y":[0.927]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[458.803]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.719],"y":[-0.05]},"t":95,"s":[536.803]},{"t":180,"s":[458.803]}],"ix":3},"y":{"a":1,"k":[{"i":{"x":[0.07],"y":[1.1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[707.143]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.719],"y":[0.069]},"t":95,"s":[639.643]},{"t":180,"s":[707.143]}],"ix":4}},"a":{"k":[{"s":[164.438,1433.781,0],"t":0,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[164.438,1433.781,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"s":{"a":0,"k":[-30,30,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[2361.125,4541.989],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"tr","p":{"a":0,"k":[164.438,1481.781],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.901960784314,0.764705882353,0.423529411765,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":181,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".black","cl":"black","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":0,"k":[350.5,420.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[420.5,-350.5],[-420.5,-350.5],[-420.5,350.5],[420.5,350.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":181,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/quickstep/res/raw-land/all_set_page_bg.json b/quickstep/res/raw-land/all_set_page_bg.json deleted file mode 100644 index 9e94c47a63..0000000000 --- a/quickstep/res/raw-land/all_set_page_bg.json +++ /dev/null @@ -1 +0,0 @@ -{"v":"5.8.1","fr":60,"ip":0,"op":181,"w":796,"h":412,"nm":"SUW_Welcome_Handheld_Landscape_Dynamic","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".primary","cl":"primary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[-55]},{"t":180,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.07,"y":0.942},"o":{"x":0.167,"y":0.167},"t":0,"s":[700.266,85.857,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.773,"y":0.043},"t":95,"s":[700.266,-103.727,0],"to":[0,0,0],"ti":[0,0,0]},{"t":180,"s":[700.266,85.857,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-3514.717,-358.642,0],"ix":1,"l":2},"s":{"a":0,"k":[-18.2,18.2,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[100.594,-128.921],[118.85,-93.491],[148.984,-67.406],[148.684,-27.55],[163.244,9.552],[144.457,44.702],[140.106,84.321],[107.136,106.715],[84.872,139.773],[45.271,144.279],[10.194,163.205],[-26.964,148.792],[-66.818,149.249],[-93.023,119.218],[-128.524,101.101],[-137.771,62.332],[-160.786,29.793],[-150.957,-8.833],[-156.215,-48.341],[-129.561,-77.974],[-115.856,-115.401],[-78.484,-129.253],[-48.956,-156.023],[-9.427,-150.921],[29.159,-160.903],[61.789,-138.015]],"o":[[-100.594,128.921],[-118.85,93.491],[-148.984,67.406],[-148.684,27.55],[-163.244,-9.552],[-144.456,-44.702],[-140.106,-84.321],[-107.136,-106.715],[-84.872,-139.773],[-45.271,-144.279],[-10.194,-163.205],[26.964,-148.792],[66.818,-149.249],[93.023,-119.218],[128.524,-101.101],[137.771,-62.332],[160.786,-29.793],[150.957,8.833],[156.215,48.341],[129.561,77.974],[115.856,115.4],[78.484,129.253],[48.956,156.023],[9.427,150.921],[-29.159,160.903],[-61.789,138.015]],"v":[[975.226,761.299],[707.165,899.424],[509.8,1127.42],[208.253,1125.148],[-72.46,1235.308],[-338.41,1093.162],[-638.164,1060.25],[-807.592,810.792],[-1057.715,642.348],[-1091.808,342.727],[-1235.002,77.338],[-1125.948,-203.807],[-1129.407,-505.342],[-902.191,-703.604],[-765.123,-972.207],[-471.796,-1042.166],[-225.603,-1216.305],[66.637,-1141.935],[365.557,-1181.715],[589.761,-980.053],[872.928,-876.361],[977.734,-593.606],[1180.277,-370.197],[1141.675,-71.124],[1217.196,220.821],[1044.029,467.699]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[-3509.952,-363.731],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Polystar 1","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.713725490196,0.556862745098,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"k":[{"s":[10.989],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[10.989],"t":180,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":181,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".tertiary","cl":"tertiary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.07],"y":[1.047]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[-56]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.773],"y":[-0.035]},"t":95,"s":[-67]},{"t":180,"s":[-56]}],"ix":10},"p":{"s":true,"x":{"a":1,"k":[{"i":{"x":[0.07],"y":[1.073]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[319.023]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.773],"y":[-0.054]},"t":95,"s":[388.573]},{"t":180,"s":[319.023]}],"ix":3},"y":{"a":1,"k":[{"i":{"x":[0.07],"y":[0.927]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[565.531]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.773],"y":[0.054]},"t":95,"s":[482.331]},{"t":180,"s":[565.531]}],"ix":4}},"a":{"a":0,"k":[164.438,1433.781,0],"ix":1,"l":2},"s":{"a":0,"k":[65,65,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[1193.125,1815.766],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.901960784314,0.764705882353,0.423529411765,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[164.438,1481.781],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":181,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".black","cl":"black","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[398,206,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[2472,5352],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.125489994124,0.1294119891,0.141176006841,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":181,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/quickstep/res/raw-sw600dp-land/all_set_page_bg.json b/quickstep/res/raw-sw600dp-land/all_set_page_bg.json deleted file mode 100644 index 63b64da7fa..0000000000 --- a/quickstep/res/raw-sw600dp-land/all_set_page_bg.json +++ /dev/null @@ -1 +0,0 @@ -{"v":"5.8.1","fr":60,"ip":0,"op":181,"w":841,"h":701,"nm":"SUW_WelcomeScreen_FoldableOpen_Dynamic","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".primary","cl":"primary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":180,"s":[55]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.07,"y":0.939},"o":{"x":0.167,"y":0.167},"t":0,"s":[140.975,228.318,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.773,"y":0.045},"t":95,"s":[140.975,47.65,0],"to":[0,0,0],"ti":[0,0,0]},{"t":180,"s":[140.975,228.318,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-3514.717,-358.642,0],"ix":1,"l":2},"s":{"a":0,"k":[18,18,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[100.594,-128.921],[118.85,-93.491],[148.984,-67.406],[148.684,-27.55],[163.244,9.552],[144.457,44.702],[140.106,84.321],[107.136,106.715],[84.872,139.773],[45.271,144.279],[10.194,163.205],[-26.964,148.792],[-66.818,149.249],[-93.023,119.218],[-128.524,101.101],[-137.771,62.332],[-160.786,29.793],[-150.957,-8.833],[-156.215,-48.341],[-129.561,-77.974],[-115.856,-115.401],[-78.484,-129.253],[-48.956,-156.023],[-9.427,-150.921],[29.159,-160.903],[61.789,-138.015]],"o":[[-100.594,128.921],[-118.85,93.491],[-148.984,67.406],[-148.684,27.55],[-163.244,-9.552],[-144.456,-44.702],[-140.106,-84.321],[-107.136,-106.715],[-84.872,-139.773],[-45.271,-144.279],[-10.194,-163.205],[26.964,-148.792],[66.818,-149.249],[93.023,-119.218],[128.524,-101.101],[137.771,-62.332],[160.786,-29.793],[150.957,8.833],[156.215,48.341],[129.561,77.974],[115.856,115.4],[78.484,129.253],[48.956,156.023],[9.427,150.921],[-29.159,160.903],[-61.789,138.015]],"v":[[975.226,761.299],[707.165,899.424],[509.8,1127.42],[208.253,1125.148],[-72.46,1235.308],[-338.41,1093.162],[-638.164,1060.25],[-807.592,810.792],[-1057.715,642.348],[-1091.808,342.727],[-1235.002,77.338],[-1125.948,-203.807],[-1129.407,-505.342],[-902.191,-703.604],[-765.123,-972.207],[-471.796,-1042.166],[-225.603,-1216.305],[66.637,-1141.935],[365.557,-1181.715],[589.761,-980.053],[872.928,-876.361],[977.734,-593.606],[1180.277,-370.197],[1141.675,-71.124],[1217.196,220.821],[1044.029,467.699]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[-3509.952,-363.731],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Polystar 1","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.713725490196,0.556862745098,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"k":[{"s":[11.111],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[11.111],"t":180,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":181,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".tertiary","cl":"tertiary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.07],"y":[0.619]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[67]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.719],"y":[-0.263]},"t":95,"s":[82]},{"t":180,"s":[67]}],"ix":10},"p":{"s":true,"x":{"a":1,"k":[{"i":{"x":[0.07],"y":[0.913]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[639]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.719],"y":[-0.06]},"t":95,"s":[704]},{"t":180,"s":[639]}],"ix":3},"y":{"a":1,"k":[{"i":{"x":[0.07],"y":[1.12]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[527.25]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.719],"y":[0.083]},"t":95,"s":[471]},{"t":180,"s":[527.25]}],"ix":4}},"a":{"k":[{"s":[164.438,1433.781,0],"t":0,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[164.438,1433.781,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"s":{"a":0,"k":[-25,25,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[2361.125,4541.989],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"tr","p":{"a":0,"k":[164.438,1481.781],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.901960784314,0.764705882353,0.423529411765,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"k":[{"s":[6],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[6],"t":180,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":181,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".black","cl":"black","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[420.5,350.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[420.5,-350.5],[-420.5,-350.5],[-420.5,350.5],[420.5,350.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":181,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/quickstep/res/raw-sw600dp/all_set_page_bg.json b/quickstep/res/raw-sw600dp/all_set_page_bg.json deleted file mode 100644 index f2998a038a..0000000000 --- a/quickstep/res/raw-sw600dp/all_set_page_bg.json +++ /dev/null @@ -1 +0,0 @@ -{"v":"5.8.1","fr":60,"ip":0,"op":181,"w":701,"h":841,"nm":"SUW_WelcomeScreen_FoldableOpen_Portrait_Dynamic","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".primary","cl":"primary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":180,"s":[55]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.07,"y":0.949},"o":{"x":0.167,"y":0.167},"t":0,"s":[181.172,148.425,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.773,"y":0.038},"t":95,"s":[181.172,-68.377,0],"to":[0,0,0],"ti":[0,0,0]},{"t":180,"s":[181.172,148.425,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-3514.717,-358.642,0],"ix":1,"l":2},"s":{"a":0,"k":[21.6,21.6,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[100.594,-128.921],[118.85,-93.491],[148.984,-67.406],[148.684,-27.55],[163.244,9.552],[144.457,44.702],[140.106,84.321],[107.136,106.715],[84.872,139.773],[45.271,144.279],[10.194,163.205],[-26.964,148.792],[-66.818,149.249],[-93.023,119.218],[-128.524,101.101],[-137.771,62.332],[-160.786,29.793],[-150.957,-8.833],[-156.215,-48.341],[-129.561,-77.974],[-115.856,-115.401],[-78.484,-129.253],[-48.956,-156.023],[-9.427,-150.921],[29.159,-160.903],[61.789,-138.015]],"o":[[-100.594,128.921],[-118.85,93.491],[-148.984,67.406],[-148.684,27.55],[-163.244,-9.552],[-144.456,-44.702],[-140.106,-84.321],[-107.136,-106.715],[-84.872,-139.773],[-45.271,-144.279],[-10.194,-163.205],[26.964,-148.792],[66.818,-149.249],[93.023,-119.218],[128.524,-101.101],[137.771,-62.332],[160.786,-29.793],[150.957,8.833],[156.215,48.341],[129.561,77.974],[115.856,115.4],[78.484,129.253],[48.956,156.023],[9.427,150.921],[-29.159,160.903],[-61.789,138.015]],"v":[[975.226,761.299],[707.165,899.424],[509.8,1127.42],[208.253,1125.148],[-72.46,1235.308],[-338.41,1093.162],[-638.164,1060.25],[-807.592,810.792],[-1057.715,642.348],[-1091.808,342.727],[-1235.002,77.338],[-1125.948,-203.807],[-1129.407,-505.342],[-902.191,-703.604],[-765.123,-972.207],[-471.796,-1042.166],[-225.603,-1216.305],[66.637,-1141.935],[365.557,-1181.715],[589.761,-980.053],[872.928,-876.361],[977.734,-593.606],[1180.277,-370.197],[1141.675,-71.124],[1217.196,220.821],[1044.029,467.699]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[-3509.952,-363.731],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Polystar 1","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.713725490196,0.556862745098,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":9.3,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":181,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".tertiary","cl":"tertiary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.07],"y":[0.619]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[67]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.719],"y":[-0.263]},"t":95,"s":[82]},{"t":180,"s":[67]}],"ix":10},"p":{"s":true,"x":{"a":1,"k":[{"i":{"x":[0.07],"y":[0.927]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[458.803]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.719],"y":[-0.05]},"t":95,"s":[536.803]},{"t":180,"s":[458.803]}],"ix":3},"y":{"a":1,"k":[{"i":{"x":[0.07],"y":[1.1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[707.143]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.719],"y":[0.069]},"t":95,"s":[639.643]},{"t":180,"s":[707.143]}],"ix":4}},"a":{"k":[{"s":[164.438,1433.781,0],"t":0,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[164.438,1433.781,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"s":{"a":0,"k":[-30,30,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[2361.125,4541.989],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"tr","p":{"a":0,"k":[164.438,1481.781],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.901960784314,0.764705882353,0.423529411765,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":181,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".black","cl":"black","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":0,"k":[350.5,420.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[420.5,-350.5],[-420.5,-350.5],[-420.5,350.5],[420.5,350.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":181,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/quickstep/res/raw-sw720dp-land/all_set_page_bg.json b/quickstep/res/raw-sw720dp-land/all_set_page_bg.json deleted file mode 100644 index a994b0f658..0000000000 --- a/quickstep/res/raw-sw720dp-land/all_set_page_bg.json +++ /dev/null @@ -1 +0,0 @@ -{"v":"5.8.1","fr":60,"ip":0,"op":180,"w":1280,"h":800,"nm":"SUW_WelcomeScreen_Tablet_Landscape_Dynamic","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 5","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[288,540,0],"ix":2,"l":2},"a":{"a":0,"k":[50,50,0],"ix":1,"l":2},"s":{"a":0,"k":[25,25,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".primary","cl":"primary","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":180,"s":[56]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.07,"y":0.986},"o":{"x":0.167,"y":0.167},"t":0,"s":[375.832,-1006.545,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.773,"y":0.01},"t":95,"s":[375.832,-1811,0],"to":[0,0,0],"ti":[0,0,0]},{"t":180,"s":[375.832,-1006.545,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-3514.717,-358.642,0],"ix":1,"l":2},"s":{"a":0,"k":[110,110,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[75.615,-96.908],[89.338,-70.276],[111.99,-50.668],[111.764,-20.709],[122.709,7.18],[108.586,33.602],[105.316,63.383],[80.533,80.216],[63.797,105.066],[34.03,108.453],[7.663,122.679],[-20.269,111.845],[-50.226,112.189],[-69.924,89.614],[-96.61,75.997],[-103.56,46.854],[-120.861,22.395],[-113.472,-6.639],[-117.425,-36.337],[-97.389,-58.612],[-87.087,-86.745],[-58.996,-97.158],[-36.8,-117.281],[-7.086,-113.445],[21.918,-120.948],[46.446,-103.744]],"o":[[-75.615,96.909],[-89.338,70.276],[-111.99,50.668],[-111.764,20.709],[-122.709,-7.18],[-108.586,-33.602],[-105.316,-63.383],[-80.533,-80.216],[-63.797,-105.066],[-34.03,-108.453],[-7.663,-122.679],[20.269,-111.845],[50.226,-112.188],[69.924,-89.614],[96.61,-75.997],[103.56,-46.854],[120.861,-22.395],[113.472,6.64],[117.425,36.337],[97.389,58.612],[87.088,86.745],[58.995,97.158],[36.8,117.281],[7.087,113.445],[-21.918,120.948],[-46.446,103.744]],"v":[[733.209,572.105],[531.711,675.932],[383.354,847.313],[156.685,845.606],[-54.323,928.412],[-254.235,821.562],[-479.555,796.823],[-606.913,609.309],[-794.927,482.691],[-820.554,257.47],[-928.191,57.981],[-846.217,-153.353],[-848.817,-380.013],[-678.021,-529.044],[-574.99,-730.949],[-354.499,-783.537],[-169.439,-914.435],[50.234,-858.532],[274.928,-888.434],[443.46,-736.847],[656.313,-658.903],[735.094,-446.359],[887.344,-278.426],[858.327,-53.616],[915.095,165.835],[784.928,351.409]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.956862745098,0.729411764706,0.619607843137,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[-3509.952,-363.731],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Polystar 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":720,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".tertiary","cl":"tertiary","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.07],"y":[1.248]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[57]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.719],"y":[0.172]},"t":95,"s":[75]},{"t":180,"s":[57]}],"ix":10},"p":{"s":true,"x":{"a":1,"k":[{"i":{"x":[0.07],"y":[1.032]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[2618]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.719],"y":[0.022]},"t":95,"s":[2442]},{"t":180,"s":[2618]}],"ix":3},"y":{"a":1,"k":[{"i":{"x":[0.07],"y":[1.034]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[891]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.719],"y":[0.024]},"t":95,"s":[694]},{"t":180,"s":[891]}],"ix":4}},"a":{"a":0,"k":[164.438,1433.781,0],"ix":1,"l":2},"s":{"a":0,"k":[120,120,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[3079.125,4685.989],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.752941176471,0.788235294118,0.752941176471,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[164.438,1481.781],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/quickstep/res/raw-sw720dp/all_set_page_bg.json b/quickstep/res/raw-sw720dp/all_set_page_bg.json deleted file mode 100644 index 1030ffa543..0000000000 --- a/quickstep/res/raw-sw720dp/all_set_page_bg.json +++ /dev/null @@ -1 +0,0 @@ -{"v":"5.8.1","fr":60,"ip":0,"op":180,"w":800,"h":1280,"nm":"SUW_WelcomeScreen_Tablet_Portrait_Dynamic","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 5","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[288,528,0],"ix":2,"l":2},"a":{"a":0,"k":[50,50,0],"ix":1,"l":2},"s":{"a":0,"k":[25,25,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".primary","cl":"primary","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":180,"s":[56]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.07,"y":0.986},"o":{"x":0.167,"y":0.167},"t":0,"s":[999.832,-2238.545,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.773,"y":0.01},"t":95,"s":[999.832,-3043,0],"to":[0,0,0],"ti":[0,0,0]},{"t":180,"s":[999.832,-2238.545,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-3514.717,-358.642,0],"ix":1,"l":2},"s":{"a":0,"k":[200,200,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[75.615,-96.908],[89.338,-70.276],[111.99,-50.668],[111.764,-20.709],[122.709,7.18],[108.586,33.602],[105.316,63.383],[80.533,80.216],[63.797,105.066],[34.03,108.453],[7.663,122.679],[-20.269,111.845],[-50.226,112.189],[-69.924,89.614],[-96.61,75.997],[-103.56,46.854],[-120.861,22.395],[-113.472,-6.639],[-117.425,-36.337],[-97.389,-58.612],[-87.087,-86.745],[-58.996,-97.158],[-36.8,-117.281],[-7.086,-113.445],[21.918,-120.948],[46.446,-103.744]],"o":[[-75.615,96.909],[-89.338,70.276],[-111.99,50.668],[-111.764,20.709],[-122.709,-7.18],[-108.586,-33.602],[-105.316,-63.383],[-80.533,-80.216],[-63.797,-105.066],[-34.03,-108.453],[-7.663,-122.679],[20.269,-111.845],[50.226,-112.188],[69.924,-89.614],[96.61,-75.997],[103.56,-46.854],[120.861,-22.395],[113.472,6.64],[117.425,36.337],[97.389,58.612],[87.088,86.745],[58.995,97.158],[36.8,117.281],[7.087,113.445],[-21.918,120.948],[-46.446,103.744]],"v":[[733.209,572.105],[531.711,675.932],[383.354,847.313],[156.685,845.606],[-54.323,928.412],[-254.235,821.562],[-479.555,796.823],[-606.913,609.309],[-794.927,482.691],[-820.554,257.47],[-928.191,57.981],[-846.217,-153.353],[-848.817,-380.013],[-678.021,-529.044],[-574.99,-730.949],[-354.499,-783.537],[-169.439,-914.435],[50.234,-858.532],[274.928,-888.434],[443.46,-736.847],[656.313,-658.903],[735.094,-446.359],[887.344,-278.426],[858.327,-53.616],[915.095,165.835],[784.928,351.409]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.956862745098,0.729411764706,0.619607843137,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[-3509.952,-363.731],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Polystar 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":720,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".tertiary","cl":"tertiary","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.07],"y":[1.248]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[-39]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.719],"y":[0.172]},"t":95,"s":[-21]},{"t":180,"s":[-39]}],"ix":10},"p":{"s":true,"x":{"a":1,"k":[{"i":{"x":[0.07],"y":[1.032]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[1490]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.719],"y":[0.022]},"t":95,"s":[1314]},{"t":180,"s":[1490]}],"ix":3},"y":{"a":1,"k":[{"i":{"x":[0.07],"y":[1.034]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[2967]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.719],"y":[0.024]},"t":95,"s":[2770]},{"t":180,"s":[2967]}],"ix":4}},"a":{"a":0,"k":[164.438,1433.781,0],"ix":1,"l":2},"s":{"a":0,"k":[168,168,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[3079.125,4685.989],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.752941176471,0.788235294118,0.752941176471,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[164.438,1481.781],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java deleted file mode 100644 index a9f7c2a441..0000000000 --- a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java +++ /dev/null @@ -1,377 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher3; - -import static android.content.ClipDescription.MIMETYPE_TEXT_INTENT; -import static android.view.WindowInsets.Type.navigationBars; -import static android.view.WindowInsets.Type.statusBars; - -import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; -import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; - -import android.appwidget.AppWidgetManager; -import android.appwidget.AppWidgetProviderInfo; -import android.content.ClipData; -import android.content.ClipDescription; -import android.content.Intent; -import android.os.Bundle; -import android.util.Log; -import android.view.View; -import android.view.WindowInsetsController; -import android.view.WindowManager; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.android.launcher3.dragndrop.SimpleDragLayer; -import com.android.launcher3.model.WidgetItem; -import com.android.launcher3.model.WidgetPredictionsRequester; -import com.android.launcher3.model.WidgetsModel; -import com.android.launcher3.model.data.ItemInfo; -import com.android.launcher3.popup.PopupDataProvider; -import com.android.launcher3.util.PackageUserKey; -import com.android.launcher3.widget.BaseWidgetSheet; -import com.android.launcher3.widget.WidgetCell; -import com.android.launcher3.widget.model.WidgetsListBaseEntry; -import com.android.launcher3.widget.model.WidgetsListHeaderEntry; -import com.android.launcher3.widget.picker.WidgetsFullSheet; - -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - -/** An Activity that can host Launcher's widget picker. */ -public class WidgetPickerActivity extends BaseActivity { - private static final String TAG = "WidgetPickerActivity"; - /** - * Name of the extra that indicates that a widget being dragged. - * - * <p>When set to "true" in the result of startActivityForResult, the client that launched the - * picker knows that activity was closed due to pending drag. - */ - private static final String EXTRA_IS_PENDING_WIDGET_DRAG = "is_pending_widget_drag"; - - // Intent extras that specify the desired widget width and height. If these are not specified in - // the intent, then widgets will not be filtered for size. - private static final String EXTRA_DESIRED_WIDGET_WIDTH = "desired_widget_width"; - private static final String EXTRA_DESIRED_WIDGET_HEIGHT = "desired_widget_height"; - /** - * Widgets currently added by the user in the UI surface. - * <p>This allows widget picker to exclude existing widgets from suggestions.</p> - */ - private static final String EXTRA_ADDED_APP_WIDGETS = "added_app_widgets"; - /** - * A unique identifier of the surface hosting the widgets; - * <p>"widgets" is reserved for home screen surface.</p> - * <p>"widgets_hub" is reserved for glanceable hub surface.</p> - */ - private static final String EXTRA_UI_SURFACE = "ui_surface"; - private static final Pattern UI_SURFACE_PATTERN = - Pattern.compile("^(widgets|widgets_hub)$"); - private SimpleDragLayer<WidgetPickerActivity> mDragLayer; - private WidgetsModel mModel; - private LauncherAppState mApp; - private WidgetPredictionsRequester mWidgetPredictionsRequester; - private final PopupDataProvider mPopupDataProvider = new PopupDataProvider(i -> {}); - - private int mDesiredWidgetWidth; - private int mDesiredWidgetHeight; - private int mWidgetCategoryFilter; - @Nullable - private String mUiSurface; - // Widgets existing on the host surface. - @NonNull - private List<AppWidgetProviderInfo> mAddedWidgets = new ArrayList<>(); - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); - getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER); - - mApp = LauncherAppState.getInstance(this); - InvariantDeviceProfile idp = mApp.getInvariantDeviceProfile(); - mDeviceProfile = idp.getDeviceProfile(this); - mModel = new WidgetsModel(); - - setContentView(R.layout.widget_picker_activity); - mDragLayer = findViewById(R.id.drag_layer); - mDragLayer.recreateControllers(); - - WindowInsetsController wc = mDragLayer.getWindowInsetsController(); - wc.hide(navigationBars() + statusBars()); - - BaseWidgetSheet widgetSheet = WidgetsFullSheet.show(this, true); - widgetSheet.disableNavBarScrim(true); - widgetSheet.addOnCloseListener(this::finish); - - parseIntentExtras(); - refreshAndBindWidgets(); - } - - private void parseIntentExtras() { - // A value of 0 for either size means that no filtering will occur in that dimension. If - // both values are 0, then no size filtering will occur. - mDesiredWidgetWidth = - getIntent().getIntExtra(EXTRA_DESIRED_WIDGET_WIDTH, 0); - mDesiredWidgetHeight = - getIntent().getIntExtra(EXTRA_DESIRED_WIDGET_HEIGHT, 0); - - // Defaults to '0' to indicate that there isn't a category filter. - mWidgetCategoryFilter = - getIntent().getIntExtra(AppWidgetManager.EXTRA_CATEGORY_FILTER, 0); - - String uiSurfaceParam = getIntent().getStringExtra(EXTRA_UI_SURFACE); - if (uiSurfaceParam != null && UI_SURFACE_PATTERN.matcher(uiSurfaceParam).matches()) { - mUiSurface = uiSurfaceParam; - } - if (Utilities.ATLEAST_T) { - ArrayList<AppWidgetProviderInfo> addedWidgets = getIntent().getParcelableArrayListExtra( - EXTRA_ADDED_APP_WIDGETS, AppWidgetProviderInfo.class); - if (addedWidgets != null) { - mAddedWidgets = addedWidgets; - } - } - } - - @NonNull - @Override - public PopupDataProvider getPopupDataProvider() { - return mPopupDataProvider; - } - - @Override - public SimpleDragLayer<WidgetPickerActivity> getDragLayer() { - return mDragLayer; - } - - @Override - public View.OnClickListener getItemOnClickListener() { - return v -> { - final AppWidgetProviderInfo info = - (v instanceof WidgetCell) ? ((WidgetCell) v).getWidgetItem().widgetInfo : null; - if (info == null || info.provider == null) { - return; - } - - setResult(RESULT_OK, new Intent() - .putExtra(Intent.EXTRA_COMPONENT_NAME, info.provider) - .putExtra(Intent.EXTRA_USER, info.getProfile())); - - finish(); - }; - } - - @Override - public View.OnLongClickListener getAllAppsItemLongClickListener() { - return view -> { - if (!(view instanceof WidgetCell widgetCell)) return false; - - if (widgetCell.getWidgetView().getDrawable() == null - && widgetCell.getAppWidgetHostViewPreview() == null) { - // The widget preview hasn't been loaded; so, we abort the drag. - return false; - } - - final AppWidgetProviderInfo info = widgetCell.getWidgetItem().widgetInfo; - if (info == null || info.provider == null) { - return false; - } - - View dragView = widgetCell.getDragAndDropView(); - if (dragView == null) { - return false; - } - - ClipData clipData = new ClipData( - new ClipDescription( - /* label= */ "", // not displayed anywhere; so, set to empty. - new String[]{MIMETYPE_TEXT_INTENT} - ), - new ClipData.Item(new Intent() - .putExtra(Intent.EXTRA_USER, info.getProfile()) - .putExtra(Intent.EXTRA_COMPONENT_NAME, info.provider)) - ); - - // Set result indicating activity was closed due a widget being dragged. - setResult(RESULT_OK, new Intent() - .putExtra(EXTRA_IS_PENDING_WIDGET_DRAG, true)); - - // DRAG_FLAG_GLOBAL permits dragging data beyond app window. - return dragView.startDragAndDrop( - clipData, - new View.DragShadowBuilder(dragView), - /* myLocalState= */ null, - View.DRAG_FLAG_GLOBAL - ); - }; - } - - /** Updates the model with widgets and provides them after applying the provided filter. */ - private void refreshAndBindWidgets() { - MODEL_EXECUTOR.execute(() -> { - LauncherAppState app = LauncherAppState.getInstance(this); - mModel.update(app, null); - final List<WidgetsListBaseEntry> allWidgets = - mModel.getFilteredWidgetsListForPicker( - app.getContext(), - /*widgetItemFilter=*/ widget -> { - final WidgetAcceptabilityVerdict verdict = - isWidgetAcceptable(widget); - verdict.maybeLogVerdict(); - return verdict.isAcceptable; - } - ); - bindWidgets(allWidgets); - if (mUiSurface != null) { - Map<PackageUserKey, List<WidgetItem>> allWidgetsMap = allWidgets.stream() - .filter(WidgetsListHeaderEntry.class::isInstance) - .collect(Collectors.toMap( - entry -> PackageUserKey.fromPackageItemInfo(entry.mPkgItem), - entry -> entry.mWidgets) - ); - mWidgetPredictionsRequester = new WidgetPredictionsRequester(app.getContext(), - mUiSurface, allWidgetsMap); - mWidgetPredictionsRequester.request(mAddedWidgets, this::bindRecommendedWidgets); - } - }); - } - - private void bindWidgets(List<WidgetsListBaseEntry> widgets) { - MAIN_EXECUTOR.execute(() -> mPopupDataProvider.setAllWidgets(widgets)); - } - - private void bindRecommendedWidgets(List<ItemInfo> recommendedWidgets) { - MAIN_EXECUTOR.execute(() -> mPopupDataProvider.setRecommendedWidgets(recommendedWidgets)); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - if (mWidgetPredictionsRequester != null) { - mWidgetPredictionsRequester.clear(); - } - } - - private WidgetAcceptabilityVerdict isWidgetAcceptable(WidgetItem widget) { - final AppWidgetProviderInfo info = widget.widgetInfo; - if (info == null) { - return rejectWidget(widget, "shortcut"); - } - - if (mWidgetCategoryFilter > 0 && (info.widgetCategory & mWidgetCategoryFilter) == 0) { - return rejectWidget( - widget, - "doesn't match category filter [filter=%d, widget=%d]", - mWidgetCategoryFilter, - info.widgetCategory); - } - - if (mDesiredWidgetWidth == 0 && mDesiredWidgetHeight == 0) { - // Accept the widget if the desired dimensions are unspecified. - return acceptWidget(widget); - } - - final boolean isHorizontallyResizable = - (info.resizeMode & AppWidgetProviderInfo.RESIZE_HORIZONTAL) != 0; - if (mDesiredWidgetWidth > 0 && isHorizontallyResizable) { - if (info.maxResizeWidth > 0 - && info.maxResizeWidth >= info.minWidth - && info.maxResizeWidth < mDesiredWidgetWidth) { - return rejectWidget( - widget, - "maxResizeWidth[%d] < mDesiredWidgetWidth[%d]", - info.maxResizeWidth, - mDesiredWidgetWidth); - } - - final int minWidth = Math.min(info.minResizeWidth, info.minWidth); - if (minWidth > mDesiredWidgetWidth) { - return rejectWidget( - widget, - "min(minWidth[%d], minResizeWidth[%d]) > mDesiredWidgetWidth[%d]", - info.minWidth, - info.minResizeWidth, - mDesiredWidgetWidth); - } - } - - final boolean isVerticallyResizable = - (info.resizeMode & AppWidgetProviderInfo.RESIZE_VERTICAL) != 0; - if (mDesiredWidgetHeight > 0 && isVerticallyResizable) { - if (info.maxResizeHeight > 0 - && info.maxResizeHeight >= info.minHeight - && info.maxResizeHeight < mDesiredWidgetHeight) { - return rejectWidget( - widget, - "maxResizeHeight[%d] < mDesiredWidgetHeight[%d]", - info.maxResizeHeight, - mDesiredWidgetHeight); - } - - final int minHeight = Math.min(info.minResizeHeight, info.minHeight); - if (minHeight > mDesiredWidgetHeight) { - return rejectWidget( - widget, - "min(minHeight[%d], minResizeHeight[%d]) > mDesiredWidgetHeight[%d]", - info.minHeight, - info.minResizeHeight, - mDesiredWidgetHeight); - } - } - - if (!isHorizontallyResizable || !isVerticallyResizable) { - return rejectWidget(widget, "not resizeable"); - } - - return acceptWidget(widget); - } - - private static WidgetAcceptabilityVerdict rejectWidget( - WidgetItem widget, String rejectionReason, Object... args) { - return new WidgetAcceptabilityVerdict( - false, - widget.widgetInfo != null - ? widget.widgetInfo.provider.flattenToShortString() - : widget.label, - String.format(Locale.ENGLISH, rejectionReason, args)); - } - - private static WidgetAcceptabilityVerdict acceptWidget(WidgetItem widget) { - return new WidgetAcceptabilityVerdict( - true, widget.widgetInfo.provider.flattenToShortString(), ""); - } - - private record WidgetAcceptabilityVerdict( - boolean isAcceptable, String widgetLabel, String reason) { - void maybeLogVerdict() { - // Only log a verdict if a reason is specified. - if (Log.isLoggable(TAG, Log.DEBUG) && !reason.isEmpty()) { - Log.i(TAG, String.format( - Locale.ENGLISH, - "%s: %s because %s", - widgetLabel, - isAcceptable ? "accepted" : "rejected", - reason)); - } - } - } -} diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java deleted file mode 100644 index 56945ba0a6..0000000000 --- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.launcher3.hybridhotseat; - -import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION; -import static com.android.launcher3.model.PredictionHelper.getAppTargetFromItemInfo; -import static com.android.launcher3.model.PredictionHelper.isTrackedForHotseatPrediction; -import static com.android.launcher3.model.PredictionHelper.wrapAppTargetWithItemLocation; - -import android.app.prediction.AppTarget; -import android.app.prediction.AppTargetEvent; -import android.content.Context; -import android.os.Bundle; - -import com.android.launcher3.model.BgDataModel; -import com.android.launcher3.model.BgDataModel.FixedContainerItems; -import com.android.launcher3.model.data.ItemInfo; - -import java.util.ArrayList; - -/** - * Model helper for app predictions in workspace - */ -public class HotseatPredictionModel { - private static final String BUNDLE_KEY_PIN_EVENTS = "pin_events"; - private static final String BUNDLE_KEY_CURRENT_ITEMS = "current_items"; - - /** - * Creates and returns bundle using workspace items - */ - public static Bundle convertDataModelToAppTargetBundle(Context context, BgDataModel dataModel) { - Bundle bundle = new Bundle(); - ArrayList<AppTargetEvent> events = new ArrayList<>(); - ArrayList<ItemInfo> workspaceItems = dataModel.getAllWorkspaceItems(); - for (ItemInfo item : workspaceItems) { - AppTarget target = getAppTargetFromItemInfo(context, item); - if (target != null && !isTrackedForHotseatPrediction(item)) continue; - events.add(wrapAppTargetWithItemLocation(target, AppTargetEvent.ACTION_PIN, item)); - } - ArrayList<AppTarget> currentTargets = new ArrayList<>(); - FixedContainerItems hotseatItems = dataModel.extraItems.get(CONTAINER_HOTSEAT_PREDICTION); - if (hotseatItems != null) { - for (ItemInfo itemInfo : hotseatItems.items) { - AppTarget target = getAppTargetFromItemInfo(context, itemInfo); - if (target != null) currentTargets.add(target); - } - } - bundle.putParcelableArrayList(BUNDLE_KEY_PIN_EVENTS, events); - bundle.putParcelableArrayList(BUNDLE_KEY_CURRENT_ITEMS, currentTargets); - return bundle; - } -} diff --git a/quickstep/src/com/android/launcher3/model/PredictionHelper.java b/quickstep/src/com/android/launcher3/model/PredictionHelper.java deleted file mode 100644 index dbd99e1eab..0000000000 --- a/quickstep/src/com/android/launcher3/model/PredictionHelper.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.launcher3.model; - -import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.WORKSPACE; - -import android.app.prediction.AppTarget; -import android.app.prediction.AppTargetEvent; -import android.app.prediction.AppTargetId; -import android.content.ComponentName; -import android.content.Context; - -import androidx.annotation.Nullable; - -import com.android.launcher3.LauncherSettings; -import com.android.launcher3.Workspace; -import com.android.launcher3.logger.LauncherAtom; -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 java.util.Locale; - -/** Helper class with methods for converting launcher items to form usable by predictors */ -public final class PredictionHelper { - private static final String APP_LOCATION_HOTSEAT = "hotseat"; - private static final String APP_LOCATION_WORKSPACE = "workspace"; - - /** - * Creates and returns an {@link AppTarget} object for an {@link ItemInfo}. Returns null - * if item type is not supported in predictions - */ - @Nullable - public static AppTarget getAppTargetFromItemInfo(Context context, ItemInfo info) { - if (info == null) return null; - if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET - && info instanceof LauncherAppWidgetInfo - && ((LauncherAppWidgetInfo) info).providerName != null) { - ComponentName cn = ((LauncherAppWidgetInfo) info).providerName; - return new AppTarget.Builder(new AppTargetId("widget:" + cn.getPackageName()), - cn.getPackageName(), info.user).setClassName(cn.getClassName()).build(); - } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION - && info.getTargetComponent() != null) { - ComponentName cn = info.getTargetComponent(); - return new AppTarget.Builder(new AppTargetId("app:" + cn.getPackageName()), - cn.getPackageName(), info.user).setClassName(cn.getClassName()).build(); - } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT - && info instanceof WorkspaceItemInfo) { - ShortcutKey shortcutKey = ShortcutKey.fromItemInfo(info); - //TODO: switch to using full shortcut info - return new AppTarget.Builder(new AppTargetId("shortcut:" + shortcutKey.getId()), - shortcutKey.componentName.getPackageName(), shortcutKey.user).build(); - } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) { - return new AppTarget.Builder(new AppTargetId("folder:" + info.id), - context.getPackageName(), info.user).build(); - } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR) { - return new AppTarget.Builder(new AppTargetId("app_pair:" + info.id), - context.getPackageName(), info.user).build(); - } - return null; - } - - /** - * Creates and returns {@link AppTargetEvent} from an {@link AppTarget}, action, and item - * location using {@link ItemInfo} - */ - public static AppTargetEvent wrapAppTargetWithItemLocation( - AppTarget target, int action, ItemInfo info) { - String location = String.format(Locale.ENGLISH, "%s/%d/[%d,%d]/[%d,%d]", - info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT - ? APP_LOCATION_HOTSEAT : APP_LOCATION_WORKSPACE, - info.screenId, info.cellX, info.cellY, info.spanX, info.spanY); - return new AppTargetEvent.Builder(target, action).setLaunchLocation(location).build(); - } - - /** - * Helper method to determine if {@link ItemInfo} should be tracked and reported to hotseat - * predictors - */ - public static boolean isTrackedForHotseatPrediction(ItemInfo info) { - return info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT || ( - info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP - && info.screenId == Workspace.FIRST_SCREEN_ID); - } - - /** - * Helper method to determine if {@link LauncherAtom.ItemInfo} should be tracked and reported to - * hotseat predictors - */ - public static boolean isTrackedForHotseatPrediction(LauncherAtom.ItemInfo info) { - LauncherAtom.ContainerInfo ci = info.getContainerInfo(); - switch (ci.getContainerCase()) { - case HOTSEAT: - return true; - case WORKSPACE: - return ci.getWorkspace().getPageIndex() == 0; - default: - return false; - } - } - - /** - * Helper method to determine if {@link ItemInfo} should be tracked and reported to widget - * predictors - */ - public static boolean isTrackedForWidgetPrediction(ItemInfo info) { - return info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET - && info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP; - } - - /** - * Helper method to determine if {@link LauncherAtom.ItemInfo} should be tracked and reported - * to widget predictors - */ - public static boolean isTrackedForWidgetPrediction(LauncherAtom.ItemInfo info) { - return info.getItemCase() == LauncherAtom.ItemInfo.ItemCase.WIDGET - && info.getContainerInfo().getContainerCase() == WORKSPACE; - } -} diff --git a/quickstep/src/com/android/launcher3/secondarydisplay/SecondaryDisplayPredictionsImpl.java b/quickstep/src/com/android/launcher3/secondarydisplay/SecondaryDisplayPredictionsImpl.java deleted file mode 100644 index 8b71f01396..0000000000 --- a/quickstep/src/com/android/launcher3/secondarydisplay/SecondaryDisplayPredictionsImpl.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.launcher3.secondarydisplay; - -import static com.android.launcher3.util.OnboardingPrefs.ALL_APPS_VISITED_COUNT; - -import android.content.Context; - -import com.android.launcher3.appprediction.AppsDividerView; -import com.android.launcher3.appprediction.PredictionRowView; -import com.android.launcher3.model.BgDataModel; -import com.android.launcher3.views.ActivityContext; - -/** - * Implementation of SecondaryDisplayPredictions. - */ -@SuppressWarnings("unused") -public final class SecondaryDisplayPredictionsImpl extends SecondaryDisplayPredictions { - - private final ActivityContext mActivityContext; - private final Context mContext; - - public SecondaryDisplayPredictionsImpl(Context context) { - mContext = context; - mActivityContext = ActivityContext.lookupContext(context); - } - - @Override - void updateAppDivider() { - mActivityContext.getAppsView().getFloatingHeaderView() - .findFixedRowByType(AppsDividerView.class) - .setShowAllAppsLabel(!ALL_APPS_VISITED_COUNT.hasReachedMax(mContext)); - ALL_APPS_VISITED_COUNT.increment(mContext); - } - - @Override - public void setPredictedApps(BgDataModel.FixedContainerItems item) { - mActivityContext.getAppsView().getFloatingHeaderView() - .findFixedRowByType(PredictionRowView.class) - .setPredictedApps(item.items); - } -} diff --git a/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java deleted file mode 100644 index 4a8672fb6d..0000000000 --- a/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java +++ /dev/null @@ -1,358 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.launcher3.statehandlers; - -import static android.view.View.VISIBLE; - -import static com.android.launcher3.LauncherState.BACKGROUND_APP; -import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; -import static com.android.window.flags2.Flags.enableDesktopWindowingWallpaperActivity; - -import android.os.Debug; -import android.util.Log; -import android.view.View; - -import androidx.annotation.Nullable; - -import com.android.launcher3.Launcher; -import com.android.launcher3.LauncherState; -import com.android.launcher3.statemanager.StatefulActivity; -import com.android.launcher3.uioverrides.QuickstepLauncher; -import com.android.launcher3.util.DisplayController; -import com.android.quickstep.GestureState; -import com.android.quickstep.SystemUiProxy; -import com.android.wm.shell.desktopmode.IDesktopTaskListener; - -import java.util.HashSet; -import java.util.Set; - -/** - * Controls the visibility of the workspace and the resumed / paused state when desktop mode - * is enabled. - */ -public class DesktopVisibilityController { - - private static final String TAG = "DesktopVisController"; - private static final boolean DEBUG = false; - private final Launcher mLauncher; - private final Set<DesktopVisibilityListener> mDesktopVisibilityListeners = new HashSet<>(); - - private int mVisibleDesktopTasksCount; - private boolean mInOverviewState; - private boolean mBackgroundStateEnabled; - private boolean mGestureInProgress; - - @Nullable - private IDesktopTaskListener mDesktopTaskListener; - - public DesktopVisibilityController(Launcher launcher) { - mLauncher = launcher; - } - - /** - * Register a listener with System UI to receive updates about desktop tasks state - */ - public void registerSystemUiListener() { - mDesktopTaskListener = new IDesktopTaskListener.Stub() { - @Override - public void onTasksVisibilityChanged(int displayId, int visibleTasksCount) { - MAIN_EXECUTOR.execute(() -> { - if (displayId == mLauncher.getDisplayId()) { - if (DEBUG) { - Log.d(TAG, "desktop visible tasks count changed=" + visibleTasksCount); - } - setVisibleDesktopTasksCount(visibleTasksCount); - } - }); - } - - @Override - public void onStashedChanged(int displayId, boolean stashed) { - Log.w(TAG, "IDesktopTaskListener: onStashedChanged is deprecated"); - } - }; - SystemUiProxy.INSTANCE.get(mLauncher).setDesktopTaskListener(mDesktopTaskListener); - } - - /** - * Clear listener from System UI that was set with {@link #registerSystemUiListener()} - */ - public void unregisterSystemUiListener() { - SystemUiProxy.INSTANCE.get(mLauncher).setDesktopTaskListener(null); - mDesktopTaskListener = null; - } - - /** - * Whether desktop tasks are visible in desktop mode. - */ - public boolean areDesktopTasksVisible() { - boolean desktopTasksVisible = mVisibleDesktopTasksCount > 0; - if (DEBUG) { - Log.d(TAG, "areDesktopTasksVisible: desktopVisible=" + desktopTasksVisible - + " overview=" + mInOverviewState); - } - return desktopTasksVisible && !mInOverviewState; - } - - /** - * Number of visible desktop windows in desktop mode. - */ - public int getVisibleDesktopTasksCount() { - return mVisibleDesktopTasksCount; - } - - /** Registers a listener for Desktop Mode visibility updates. */ - public void registerDesktopVisibilityListener(DesktopVisibilityListener listener) { - mDesktopVisibilityListeners.add(listener); - } - - /** Removes a previously registered Desktop Mode visibility listener. */ - public void unregisterDesktopVisibilityListener(DesktopVisibilityListener listener) { - mDesktopVisibilityListeners.remove(listener); - } - - /** - * Sets the number of desktop windows that are visible and updates launcher visibility based on - * it. - */ - public void setVisibleDesktopTasksCount(int visibleTasksCount) { - if (DEBUG) { - Log.d(TAG, "setVisibleDesktopTasksCount: visibleTasksCount=" + visibleTasksCount - + " currentValue=" + mVisibleDesktopTasksCount); - } - - if (visibleTasksCount != mVisibleDesktopTasksCount) { - final boolean wasVisible = mVisibleDesktopTasksCount > 0; - final boolean isVisible = visibleTasksCount > 0; - final boolean wereDesktopTasksVisibleBefore = areDesktopTasksVisible(); - mVisibleDesktopTasksCount = visibleTasksCount; - final boolean areDesktopTasksVisibleNow = areDesktopTasksVisible(); - if (wereDesktopTasksVisibleBefore != areDesktopTasksVisibleNow) { - notifyDesktopVisibilityListeners(areDesktopTasksVisibleNow); - } - - if (!enableDesktopWindowingWallpaperActivity() && wasVisible != isVisible) { - // TODO: b/333533253 - Remove after flag rollout - if (mVisibleDesktopTasksCount > 0) { - setLauncherViewsVisibility(View.INVISIBLE); - if (!mInOverviewState) { - // When desktop tasks are visible & we're not in overview, we want launcher - // to appear paused, this ensures that taskbar displays. - markLauncherPaused(); - } - } else { - setLauncherViewsVisibility(View.VISIBLE); - // If desktop tasks aren't visible, ensure that launcher appears resumed to - // behave normally. - markLauncherResumed(); - } - } - } - } - - /** - * Process launcher state change and update launcher view visibility based on desktop state - */ - public void onLauncherStateChanged(LauncherState state) { - if (DEBUG) { - Log.d(TAG, "onLauncherStateChanged: newState=" + state); - } - setBackgroundStateEnabled(state == BACKGROUND_APP); - // Desktop visibility tracks overview and background state separately - setOverviewStateEnabled(state != BACKGROUND_APP && state.isRecentsViewVisible); - } - - private void setOverviewStateEnabled(boolean overviewStateEnabled) { - if (DEBUG) { - Log.d(TAG, "setOverviewStateEnabled: enabled=" + overviewStateEnabled - + " currentValue=" + mInOverviewState); - } - if (overviewStateEnabled != mInOverviewState) { - final boolean wereDesktopTasksVisibleBefore = areDesktopTasksVisible(); - mInOverviewState = overviewStateEnabled; - final boolean areDesktopTasksVisibleNow = areDesktopTasksVisible(); - if (wereDesktopTasksVisibleBefore != areDesktopTasksVisibleNow) { - notifyDesktopVisibilityListeners(areDesktopTasksVisibleNow); - } - - if (enableDesktopWindowingWallpaperActivity()) { - return; - } - // TODO: b/333533253 - Clean up after flag rollout - - if (mInOverviewState) { - setLauncherViewsVisibility(View.VISIBLE); - markLauncherResumed(); - } else if (areDesktopTasksVisibleNow && !mGestureInProgress) { - // Switching out of overview state and gesture finished. - // If desktop tasks are still visible, hide launcher again. - setLauncherViewsVisibility(View.INVISIBLE); - markLauncherPaused(); - } - } - } - - private void notifyDesktopVisibilityListeners(boolean areDesktopTasksVisible) { - if (DEBUG) { - Log.d(TAG, "notifyDesktopVisibilityListeners: visible=" + areDesktopTasksVisible); - } - for (DesktopVisibilityListener listener : mDesktopVisibilityListeners) { - listener.onDesktopVisibilityChanged(areDesktopTasksVisible); - } - DisplayController.handleInfoChangeForDesktopMode(mLauncher); - } - - /** - * TODO: b/333533253 - Remove after flag rollout - */ - private void setBackgroundStateEnabled(boolean backgroundStateEnabled) { - if (DEBUG) { - Log.d(TAG, "setBackgroundStateEnabled: enabled=" + backgroundStateEnabled - + " currentValue=" + mBackgroundStateEnabled); - } - if (backgroundStateEnabled != mBackgroundStateEnabled) { - mBackgroundStateEnabled = backgroundStateEnabled; - if (mBackgroundStateEnabled) { - setLauncherViewsVisibility(View.VISIBLE); - markLauncherResumed(); - } else if (areDesktopTasksVisible() && !mGestureInProgress) { - // Switching out of background state. If desktop tasks are visible, pause launcher. - setLauncherViewsVisibility(View.INVISIBLE); - markLauncherPaused(); - } - } - } - - /** - * Whether recents gesture is currently in progress. - * - * TODO: b/333533253 - Remove after flag rollout - */ - public boolean isRecentsGestureInProgress() { - return mGestureInProgress; - } - - /** - * Notify controller that recents gesture has started. - * - * TODO: b/333533253 - Remove after flag rollout - */ - public void setRecentsGestureStart() { - if (DEBUG) { - Log.d(TAG, "setRecentsGestureStart"); - } - setRecentsGestureInProgress(true); - } - - /** - * Notify controller that recents gesture finished with the given - * {@link com.android.quickstep.GestureState.GestureEndTarget} - * - * TODO: b/333533253 - Remove after flag rollout - */ - public void setRecentsGestureEnd(@Nullable GestureState.GestureEndTarget endTarget) { - if (DEBUG) { - Log.d(TAG, "setRecentsGestureEnd: endTarget=" + endTarget); - } - setRecentsGestureInProgress(false); - - if (endTarget == null) { - // Gesture did not result in a new end target. Ensure launchers gets paused again. - markLauncherPaused(); - } - } - - /** - * TODO: b/333533253 - Remove after flag rollout - */ - private void setRecentsGestureInProgress(boolean gestureInProgress) { - if (gestureInProgress != mGestureInProgress) { - mGestureInProgress = gestureInProgress; - } - } - - /** - * TODO: b/333533253 - Remove after flag rollout - */ - private void setLauncherViewsVisibility(int visibility) { - if (enableDesktopWindowingWallpaperActivity()) { - return; - } - if (DEBUG) { - Log.d(TAG, "setLauncherViewsVisibility: visibility=" + visibility + " " - + Debug.getCaller()); - } - View workspaceView = mLauncher.getWorkspace(); - if (workspaceView != null) { - workspaceView.setVisibility(visibility); - } - View dragLayer = mLauncher.getDragLayer(); - if (dragLayer != null) { - dragLayer.setVisibility(visibility); - } - if (mLauncher instanceof QuickstepLauncher ql && ql.getTaskbarUIController() != null - && mVisibleDesktopTasksCount != 0) { - ql.getTaskbarUIController().onLauncherVisibilityChanged(visibility == VISIBLE); - } - } - - /** - * TODO: b/333533253 - Remove after flag rollout - */ - private void markLauncherPaused() { - if (enableDesktopWindowingWallpaperActivity()) { - return; - } - if (DEBUG) { - Log.d(TAG, "markLauncherPaused " + Debug.getCaller()); - } - StatefulActivity<LauncherState> activity = - QuickstepLauncher.ACTIVITY_TRACKER.getCreatedActivity(); - if (activity != null) { - activity.setPaused(); - } - } - - /** - * TODO: b/333533253 - Remove after flag rollout - */ - private void markLauncherResumed() { - if (enableDesktopWindowingWallpaperActivity()) { - return; - } - if (DEBUG) { - Log.d(TAG, "markLauncherResumed " + Debug.getCaller()); - } - StatefulActivity<LauncherState> activity = - QuickstepLauncher.ACTIVITY_TRACKER.getCreatedActivity(); - // Check activity state before calling setResumed(). Launcher may have been actually - // paused (eg fullscreen task moved to front). - // In this case we should not mark the activity as resumed. - if (activity != null && activity.isResumed()) { - activity.setResumed(); - } - } - - /** A listener for when the user enters/exits Desktop Mode. */ - public interface DesktopVisibilityListener { - /** - * Callback for when the user enters or exits Desktop Mode - * - * @param visible whether Desktop Mode is now visible - */ - void onDesktopVisibilityChanged(boolean visible); - } -} diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java deleted file mode 100644 index 8e8ab93832..0000000000 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java +++ /dev/null @@ -1,686 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.launcher3.taskbar; - -import static android.content.Context.RECEIVER_NOT_EXPORTED; -import static android.view.Display.DEFAULT_DISPLAY; -import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; -import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL; - -import static com.android.launcher3.BaseActivity.EVENT_DESTROYED; -import static com.android.launcher3.Flags.enableUnfoldStateAnimation; -import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION; -import static com.android.launcher3.config.FeatureFlags.enableTaskbarNoRecreate; -import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY; -import static com.android.launcher3.util.DisplayController.CHANGE_DESKTOP_MODE; -import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE; -import static com.android.launcher3.util.DisplayController.CHANGE_TASKBAR_PINNING; -import static com.android.launcher3.util.DisplayController.TASKBAR_NOT_DESTROYED_TAG; -import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; -import static com.android.launcher3.util.FlagDebugUtils.formatFlagChange; -import static com.android.quickstep.util.SystemActionConstants.ACTION_SHOW_TASKBAR; -import static com.android.quickstep.util.SystemActionConstants.SYSTEM_ACTION_ID_TASKBAR; - -import android.annotation.SuppressLint; -import android.app.PendingIntent; -import android.content.ComponentCallbacks; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.ActivityInfo; -import android.content.res.Configuration; -import android.hardware.display.DisplayManager; -import android.net.Uri; -import android.os.Handler; -import android.os.Trace; -import android.provider.Settings; -import android.util.Log; -import android.view.Display; -import android.view.MotionEvent; -import android.view.WindowManager; -import android.widget.FrameLayout; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; - -import com.android.launcher3.DeviceProfile; -import com.android.launcher3.Launcher; -import com.android.launcher3.LauncherAppState; -import com.android.launcher3.anim.AnimatorPlaybackController; -import com.android.launcher3.statemanager.StatefulActivity; -import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarNavButtonCallbacks; -import com.android.launcher3.taskbar.unfold.NonDestroyableScopedUnfoldTransitionProgressProvider; -import com.android.launcher3.uioverrides.QuickstepLauncher; -import com.android.launcher3.util.DisplayController; -import com.android.launcher3.util.SettingsCache; -import com.android.launcher3.util.SimpleBroadcastReceiver; -import com.android.quickstep.AllAppsActionManager; -import com.android.quickstep.RecentsActivity; -import com.android.quickstep.SystemUiProxy; -import com.android.quickstep.util.AssistUtils; -import com.android.systemui.shared.system.QuickStepContract; -import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags; -import com.android.systemui.unfold.UnfoldTransitionProgressProvider; -import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider; - -import java.io.PrintWriter; -import java.util.StringJoiner; - -import app.lawnchair.LawnchairApp; - -/** - * Class to manage taskbar lifecycle - */ -public class TaskbarManager { - private static final String TAG = "TaskbarManager"; - private static final boolean DEBUG = false; - - /** - * All the configurations which do not initiate taskbar recreation. - * This includes all the configurations defined in Launcher's manifest entry and - * ActivityController#filterConfigChanges - */ - private static final int SKIP_RECREATE_CONFIG_CHANGES = ActivityInfo.CONFIG_WINDOW_CONFIGURATION - | ActivityInfo.CONFIG_KEYBOARD - | ActivityInfo.CONFIG_KEYBOARD_HIDDEN - | ActivityInfo.CONFIG_MCC - | ActivityInfo.CONFIG_MNC - | ActivityInfo.CONFIG_NAVIGATION - | ActivityInfo.CONFIG_ORIENTATION - | ActivityInfo.CONFIG_SCREEN_SIZE - | ActivityInfo.CONFIG_SCREEN_LAYOUT - | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE; - - - private final Context mContext; - private final @Nullable Context mNavigationBarPanelContext; - private WindowManager mWindowManager; - private FrameLayout mTaskbarRootLayout; - private boolean mAddedWindow; - private final TaskbarNavButtonController mNavButtonController; - private final ComponentCallbacks mComponentCallbacks; - - private final SimpleBroadcastReceiver mShutdownReceiver = - new SimpleBroadcastReceiver(i -> destroyExistingTaskbar()); - - // The source for this provider is set when Launcher is available - // We use 'non-destroyable' version here so the original provider won't be destroyed - // as it is tied to the activity lifecycle, not the taskbar lifecycle. - // It's destruction/creation will be managed by the activity. - private final ScopedUnfoldTransitionProgressProvider mUnfoldProgressProvider = - new NonDestroyableScopedUnfoldTransitionProgressProvider(); - - private TaskbarActivityContext mTaskbarActivityContext; - private StatefulActivity mActivity; - /** - * Cache a copy here so we can initialize state whenever taskbar is recreated, since - * this class does not get re-initialized w/ new taskbars. - */ - private final TaskbarSharedState mSharedState = new TaskbarSharedState(); - - /** - * We use WindowManager's ComponentCallbacks() for internal UI changes (similar to an Activity) - * which comes via a different channel - */ - private final RecreationListener mRecreationListener = new RecreationListener(); - - private class RecreationListener implements DisplayController.DisplayInfoChangeListener { - @Override - public void onDisplayInfoChanged(Context context, DisplayController.Info info, int flags) { - if ((flags & (CHANGE_DENSITY | CHANGE_NAVIGATION_MODE | CHANGE_DESKTOP_MODE - | CHANGE_TASKBAR_PINNING)) != 0) { - recreateTaskbar(); - } - } - } - private final SettingsCache.OnChangeListener mOnSettingsChangeListener = c -> recreateTaskbar(); - - private boolean mUserUnlocked = false; - - private final SimpleBroadcastReceiver mTaskbarBroadcastReceiver = - new SimpleBroadcastReceiver(this::showTaskbarFromBroadcast); - - private final AllAppsActionManager mAllAppsActionManager; - - private final Runnable mActivityOnDestroyCallback = new Runnable() { - @Override - public void run() { - if (mActivity != null) { - mActivity.removeOnDeviceProfileChangeListener( - mDebugActivityDeviceProfileChanged); - Log.d(TASKBAR_NOT_DESTROYED_TAG, - "unregistering activity lifecycle callbacks from " - + "onActivityDestroyed."); - mActivity.removeEventCallback(EVENT_DESTROYED, this); - } - mActivity = null; - debugWhyTaskbarNotDestroyed("clearActivity"); - if (mTaskbarActivityContext != null) { - mTaskbarActivityContext.setUIController(TaskbarUIController.DEFAULT); - } - mUnfoldProgressProvider.setSourceProvider(null); - } - }; - - UnfoldTransitionProgressProvider.TransitionProgressListener mUnfoldTransitionProgressListener = - new UnfoldTransitionProgressProvider.TransitionProgressListener() { - @Override - public void onTransitionStarted() { - Log.d(TASKBAR_NOT_DESTROYED_TAG, - "fold/unfold transition started getting called."); - } - - @Override - public void onTransitionProgress(float progress) { - Log.d(TASKBAR_NOT_DESTROYED_TAG, - "fold/unfold transition progress : " + progress); - } - - @Override - public void onTransitionFinishing() { - Log.d(TASKBAR_NOT_DESTROYED_TAG, - "fold/unfold transition finishing getting called."); - - } - - @Override - public void onTransitionFinished() { - Log.d(TASKBAR_NOT_DESTROYED_TAG, - "fold/unfold transition finished getting called."); - - } - }; - - @SuppressLint("WrongConstant") - public TaskbarManager( - Context context, - AllAppsActionManager allAppsActionManager, - TaskbarNavButtonCallbacks navCallbacks) { - - Display display = - context.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY); - mContext = context.createWindowContext(display, - ENABLE_TASKBAR_NAVBAR_UNIFICATION ? TYPE_NAVIGATION_BAR : TYPE_NAVIGATION_BAR_PANEL, - null); - mAllAppsActionManager = allAppsActionManager; - mNavigationBarPanelContext = ENABLE_TASKBAR_NAVBAR_UNIFICATION - ? context.createWindowContext(display, TYPE_NAVIGATION_BAR_PANEL, null) - : null; - if (enableTaskbarNoRecreate()) { - mWindowManager = mContext.getSystemService(WindowManager.class); - mTaskbarRootLayout = new FrameLayout(mContext) { - @Override - public boolean dispatchTouchEvent(MotionEvent ev) { - // The motion events can be outside the view bounds of task bar, and hence - // manually dispatching them to the drag layer here. - if (mTaskbarActivityContext != null - && mTaskbarActivityContext.getDragLayer().isAttachedToWindow()) { - return mTaskbarActivityContext.getDragLayer().dispatchTouchEvent(ev); - } - return super.dispatchTouchEvent(ev); - } - }; - } - mNavButtonController = new TaskbarNavButtonController( - context, - navCallbacks, - SystemUiProxy.INSTANCE.get(mContext), - new Handler(), - AssistUtils.newInstance(mContext)); - mComponentCallbacks = new ComponentCallbacks() { - private Configuration mOldConfig = mContext.getResources().getConfiguration(); - - @Override - public void onConfigurationChanged(Configuration newConfig) { - Trace.instantForTrack(Trace.TRACE_TAG_APP, "TaskbarManager", - "onConfigurationChanged: " + newConfig); - debugWhyTaskbarNotDestroyed( - "TaskbarManager#mComponentCallbacks.onConfigurationChanged: " + newConfig); - DeviceProfile dp = mUserUnlocked - ? LauncherAppState.getIDP(mContext).getDeviceProfile(mContext) - : null; - int configDiff = mOldConfig.diff(newConfig) & ~SKIP_RECREATE_CONFIG_CHANGES; - - if ((configDiff & ActivityInfo.CONFIG_UI_MODE) != 0) { - // Only recreate for theme changes, not other UI mode changes such as docking. - int oldUiNightMode = (mOldConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK); - int newUiNightMode = (newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK); - if (oldUiNightMode == newUiNightMode) { - configDiff &= ~ActivityInfo.CONFIG_UI_MODE; - } - } - - debugWhyTaskbarNotDestroyed("ComponentCallbacks#onConfigurationChanged() " - + "configDiff=" + Configuration.configurationDiffToString(configDiff)); - if (configDiff != 0 || mTaskbarActivityContext == null) { - recreateTaskbar(); - } else { - // Config change might be handled without re-creating the taskbar - if (dp != null && !isTaskbarEnabled(dp)) { - destroyExistingTaskbar(); - } else { - if (dp != null && isTaskbarEnabled(dp)) { - if (ENABLE_TASKBAR_NAVBAR_UNIFICATION) { - // Re-initialize for screen size change? Should this be done - // by looking at screen-size change flag in configDiff in the - // block above? - recreateTaskbar(); - } else { - mTaskbarActivityContext.updateDeviceProfile(dp); - } - } - mTaskbarActivityContext.onConfigurationChanged(configDiff); - } - } - mOldConfig = new Configuration(newConfig); - // reset taskbar was pinned value, so we don't automatically unstash taskbar upon - // user unfolding the device. - mSharedState.setTaskbarWasPinned(false); - } - - @Override - public void onLowMemory() { } - }; - - if (LawnchairApp.isRecentsEnabled ()) { - final Uri USER_SETUP_COMPLETE_URI = Settings.Secure.getUriFor( - Settings.Secure.USER_SETUP_COMPLETE); - - final Uri NAV_BAR_KIDS_MODE = Settings.Secure.getUriFor( - Settings.Secure.NAV_BAR_KIDS_MODE); - SettingsCache.INSTANCE.get(mContext) - .register(USER_SETUP_COMPLETE_URI, mOnSettingsChangeListener); - SettingsCache.INSTANCE.get(mContext) - .register(NAV_BAR_KIDS_MODE, mOnSettingsChangeListener); - } - - Log.d(TASKBAR_NOT_DESTROYED_TAG, "registering component callbacks from constructor."); - mContext.registerComponentCallbacks(mComponentCallbacks); - mShutdownReceiver.register(mContext, Intent.ACTION_SHUTDOWN); - UI_HELPER_EXECUTOR.execute(() -> { - mSharedState.taskbarSystemActionPendingIntent = PendingIntent.getBroadcast( - mContext, - SYSTEM_ACTION_ID_TASKBAR, - new Intent(ACTION_SHOW_TASKBAR).setPackage(mContext.getPackageName()), - PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); - mContext.registerReceiver( - mTaskbarBroadcastReceiver, - new IntentFilter(ACTION_SHOW_TASKBAR), - RECEIVER_NOT_EXPORTED); - }); - - debugWhyTaskbarNotDestroyed("TaskbarManager created"); - recreateTaskbar(); - } - - private void destroyExistingTaskbar() { - debugWhyTaskbarNotDestroyed("destroyExistingTaskbar: " + mTaskbarActivityContext); - if (mTaskbarActivityContext != null) { - mTaskbarActivityContext.onDestroy(); - if (!ENABLE_TASKBAR_NAVBAR_UNIFICATION || enableTaskbarNoRecreate()) { - mTaskbarActivityContext = null; - } - } - DeviceProfile dp = mUserUnlocked ? - LauncherAppState.getIDP(mContext).getDeviceProfile(mContext) : null; - if (dp == null || !isTaskbarEnabled(dp)) { - removeTaskbarRootViewFromWindow(); - } - } - - /** - * Show Taskbar upon receiving broadcast - */ - private void showTaskbarFromBroadcast(Intent intent) { - if (ACTION_SHOW_TASKBAR.equals(intent.getAction()) && mTaskbarActivityContext != null) { - mTaskbarActivityContext.showTaskbarFromBroadcast(); - } - } - - /** - * Toggles All Apps for Taskbar or Launcher depending on the current state. - */ - public void toggleAllApps() { - if (mTaskbarActivityContext == null || mTaskbarActivityContext.canToggleHomeAllApps()) { - // Home All Apps should be toggled from this class, because the controllers are not - // initialized when Taskbar is disabled (i.e. TaskbarActivityContext is null). - if (mActivity instanceof Launcher l) l.toggleAllAppsSearch(); - } else { - mTaskbarActivityContext.toggleAllAppsSearch(); - } - } - - /** - * Displays a frame of the first Launcher reveal animation. - * - * This should be used to run a first Launcher reveal animation whose progress matches a swipe - * progress. - */ - public AnimatorPlaybackController createLauncherStartFromSuwAnim(int duration) { - return mTaskbarActivityContext == null - ? null : mTaskbarActivityContext.createLauncherStartFromSuwAnim(duration); - } - - /** - * Called when the user is unlocked - */ - public void onUserUnlocked() { - mUserUnlocked = true; - DisplayController.INSTANCE.get(mContext).addChangeListener(mRecreationListener); - recreateTaskbar(); - addTaskbarRootViewToWindow(); - } - - /** - * Sets a {@link StatefulActivity} to act as taskbar callback - */ - public void setActivity(@NonNull StatefulActivity activity) { - if (mActivity == activity) { - return; - } - removeActivityCallbacksAndListeners(); - mActivity = activity; - debugWhyTaskbarNotDestroyed("Set mActivity=" + mActivity); - mActivity.addOnDeviceProfileChangeListener(mDebugActivityDeviceProfileChanged); - Log.d(TASKBAR_NOT_DESTROYED_TAG, - "registering activity lifecycle callbacks from setActivity()."); - mActivity.addEventCallback(EVENT_DESTROYED, mActivityOnDestroyCallback); - UnfoldTransitionProgressProvider unfoldTransitionProgressProvider = - getUnfoldTransitionProgressProviderForActivity(activity); - if (unfoldTransitionProgressProvider != null) { - unfoldTransitionProgressProvider.addCallback(mUnfoldTransitionProgressListener); - } - mUnfoldProgressProvider.setSourceProvider(unfoldTransitionProgressProvider); - - if (mTaskbarActivityContext != null) { - mTaskbarActivityContext.setUIController( - createTaskbarUIControllerForActivity(mActivity)); - } - } - - /** - * Returns an {@link UnfoldTransitionProgressProvider} to use while the given StatefulActivity - * is active. - */ - private UnfoldTransitionProgressProvider getUnfoldTransitionProgressProviderForActivity( - StatefulActivity activity) { - if (!enableUnfoldStateAnimation()) { - if (activity instanceof QuickstepLauncher ql) { - return ql.getUnfoldTransitionProgressProvider(); - } - } else { - return SystemUiProxy.INSTANCE.get(mContext).getUnfoldTransitionProvider(); - } - return null; - } - - /** - * Creates a {@link TaskbarUIController} to use while the given StatefulActivity is active. - */ - private TaskbarUIController createTaskbarUIControllerForActivity(StatefulActivity activity) { - if (activity instanceof QuickstepLauncher) { - return new LauncherTaskbarUIController((QuickstepLauncher) activity); - } - if (activity instanceof RecentsActivity) { - return new FallbackTaskbarUIController((RecentsActivity) activity); - } - return TaskbarUIController.DEFAULT; - } - - /** - * This method is called multiple times (ex. initial init, then when user unlocks) in which case - * we fully want to destroy an existing taskbar and create a new one. - * In other case (folding/unfolding) we don't need to remove and add window. - */ - @VisibleForTesting - public synchronized void recreateTaskbar() { - Trace.beginSection("recreateTaskbar"); - try { - DeviceProfile dp = mUserUnlocked ? - LauncherAppState.getIDP(mContext).getDeviceProfile(mContext) : null; - - // All Apps action is unrelated to navbar unification, so we only need to check DP. - final boolean isLargeScreenTaskbar = dp != null && dp.isTaskbarPresent; - mAllAppsActionManager.setTaskbarPresent(isLargeScreenTaskbar); - - destroyExistingTaskbar(); - - boolean isTaskbarEnabled = dp != null && isTaskbarEnabled(dp); - debugWhyTaskbarNotDestroyed("recreateTaskbar: isTaskbarEnabled=" + isTaskbarEnabled - + " [dp != null (i.e. mUserUnlocked)]=" + (dp != null) - + " FLAG_HIDE_NAVBAR_WINDOW=" + ENABLE_TASKBAR_NAVBAR_UNIFICATION - + " dp.isTaskbarPresent=" + (dp == null ? "null" : dp.isTaskbarPresent)); - if (!isTaskbarEnabled) { - SystemUiProxy.INSTANCE.get(mContext) - .notifyTaskbarStatus(/* visible */ false, /* stashed */ false); - return; - } - - if (enableTaskbarNoRecreate() || mTaskbarActivityContext == null) { - mTaskbarActivityContext = new TaskbarActivityContext(mContext, - mNavigationBarPanelContext, dp, mNavButtonController, - mUnfoldProgressProvider); - } else { - mTaskbarActivityContext.updateDeviceProfile(dp); - } - mSharedState.startTaskbarVariantIsTransient = - DisplayController.isTransientTaskbar(mTaskbarActivityContext); - mSharedState.allAppsVisible = mSharedState.allAppsVisible && isLargeScreenTaskbar; - mTaskbarActivityContext.init(mSharedState); - - if (mActivity != null) { - mTaskbarActivityContext.setUIController( - createTaskbarUIControllerForActivity(mActivity)); - } - - if (enableTaskbarNoRecreate()) { - addTaskbarRootViewToWindow(); - mTaskbarRootLayout.removeAllViews(); - mTaskbarRootLayout.addView(mTaskbarActivityContext.getDragLayer()); - mTaskbarActivityContext.notifyUpdateLayoutParams(); - } - } finally { - Trace.endSection(); - } - } - - public void onSystemUiFlagsChanged(@SystemUiStateFlags long systemUiStateFlags) { - if (DEBUG) { - Log.d(TAG, "SysUI flags changed: " + formatFlagChange(systemUiStateFlags, - mSharedState.sysuiStateFlags, QuickStepContract::getSystemUiStateString)); - } - mSharedState.sysuiStateFlags = systemUiStateFlags; - if (mTaskbarActivityContext != null) { - mTaskbarActivityContext.updateSysuiStateFlags(systemUiStateFlags, false /* fromInit */); - } - } - - public void onLongPressHomeEnabled(boolean assistantLongPressEnabled) { - if (mNavButtonController != null) { - mNavButtonController.setAssistantLongPressEnabled(assistantLongPressEnabled); - } - } - - /** - * Sets the flag indicating setup UI is visible - */ - public void setSetupUIVisible(boolean isVisible) { - mSharedState.setupUIVisible = isVisible; - if (mTaskbarActivityContext != null) { - mTaskbarActivityContext.setSetupUIVisible(isVisible); - } - } - - private boolean isTaskbarEnabled(DeviceProfile deviceProfile) { - return ENABLE_TASKBAR_NAVBAR_UNIFICATION || deviceProfile.isTaskbarPresent; - } - - public void onRotationProposal(int rotation, boolean isValid) { - if (mTaskbarActivityContext != null) { - mTaskbarActivityContext.onRotationProposal(rotation, isValid); - } - } - - public void disableNavBarElements(int displayId, int state1, int state2, boolean animate) { - mSharedState.disableNavBarDisplayId = displayId; - mSharedState.disableNavBarState1 = state1; - mSharedState.disableNavBarState2 = state2; - if (mTaskbarActivityContext != null) { - mTaskbarActivityContext.disableNavBarElements(displayId, state1, state2, animate); - } - } - - public void onSystemBarAttributesChanged(int displayId, int behavior) { - mSharedState.systemBarAttrsDisplayId = displayId; - mSharedState.systemBarAttrsBehavior = behavior; - if (mTaskbarActivityContext != null) { - mTaskbarActivityContext.onSystemBarAttributesChanged(displayId, behavior); - } - } - - public void onNavButtonsDarkIntensityChanged(float darkIntensity) { - mSharedState.navButtonsDarkIntensity = darkIntensity; - if (mTaskbarActivityContext != null) { - mTaskbarActivityContext.onNavButtonsDarkIntensityChanged(darkIntensity); - } - } - - public void onNavigationBarLumaSamplingEnabled(int displayId, boolean enable) { - mSharedState.mLumaSamplingDisplayId = displayId; - mSharedState.mIsLumaSamplingEnabled = enable; - if (mTaskbarActivityContext != null) { - mTaskbarActivityContext.onNavigationBarLumaSamplingEnabled(displayId, enable); - } - } - - private void removeActivityCallbacksAndListeners() { - if (mActivity != null) { - mActivity.removeOnDeviceProfileChangeListener(mDebugActivityDeviceProfileChanged); - Log.d(TASKBAR_NOT_DESTROYED_TAG, - "unregistering activity lifecycle callbacks from " - + "removeActivityCallbackAndListeners()."); - mActivity.removeEventCallback(EVENT_DESTROYED, mActivityOnDestroyCallback); - UnfoldTransitionProgressProvider unfoldTransitionProgressProvider = - getUnfoldTransitionProgressProviderForActivity(mActivity); - if (unfoldTransitionProgressProvider != null) { - unfoldTransitionProgressProvider.removeCallback(mUnfoldTransitionProgressListener); - } - } - } - - /** - * Called when the manager is no longer needed - */ - public void destroy() { - debugWhyTaskbarNotDestroyed("TaskbarManager#destroy()"); - removeActivityCallbacksAndListeners(); - UI_HELPER_EXECUTOR.execute( - () -> mTaskbarBroadcastReceiver.unregisterReceiverSafely(mContext)); - destroyExistingTaskbar(); - removeTaskbarRootViewFromWindow(); - if (mUserUnlocked) { - DisplayController.INSTANCE.get(mContext).removeChangeListener(mRecreationListener); - } - if (LawnchairApp.isRecentsEnabled()) { - final Uri USER_SETUP_COMPLETE_URI = Settings.Secure.getUriFor( - Settings.Secure.USER_SETUP_COMPLETE); - final Uri NAV_BAR_KIDS_MODE = Settings.Secure.getUriFor( - Settings.Secure.NAV_BAR_KIDS_MODE); - - SettingsCache.INSTANCE.get(mContext) - .unregister(USER_SETUP_COMPLETE_URI, mOnSettingsChangeListener); - SettingsCache.INSTANCE.get(mContext) - .unregister(NAV_BAR_KIDS_MODE, mOnSettingsChangeListener); - } - - Log.d(TASKBAR_NOT_DESTROYED_TAG, "unregistering component callbacks from destroy()."); - mContext.unregisterComponentCallbacks(mComponentCallbacks); - mContext.unregisterReceiver(mShutdownReceiver); - } - - public @Nullable TaskbarActivityContext getCurrentActivityContext() { - return mTaskbarActivityContext; - } - - public void dumpLogs(String prefix, PrintWriter pw) { - pw.println(prefix + "TaskbarManager:"); - if (mTaskbarActivityContext == null) { - pw.println(prefix + "\tTaskbarActivityContext: null"); - } else { - mTaskbarActivityContext.dumpLogs(prefix + "\t", pw); - } - } - - @VisibleForTesting - void addTaskbarRootViewToWindow() { - if (enableTaskbarNoRecreate() && !mAddedWindow && mTaskbarActivityContext != null) { - mWindowManager.addView(mTaskbarRootLayout, - mTaskbarActivityContext.getWindowLayoutParams()); - mAddedWindow = true; - } - } - - @VisibleForTesting - void removeTaskbarRootViewFromWindow() { - if (enableTaskbarNoRecreate() && mAddedWindow) { - mWindowManager.removeViewImmediate(mTaskbarRootLayout); - mAddedWindow = false; - } - } - - /** Temp logs for b/254119092. */ - public void debugWhyTaskbarNotDestroyed(String debugReason) { - StringJoiner log = new StringJoiner("\n"); - log.add(debugReason); - - boolean activityTaskbarPresent = mActivity != null - && mActivity.getDeviceProfile().isTaskbarPresent; - boolean contextTaskbarPresent = mUserUnlocked - && LauncherAppState.getIDP(mContext).getDeviceProfile(mContext).isTaskbarPresent; - if (activityTaskbarPresent == contextTaskbarPresent) { - log.add("mActivity and mContext agree taskbarIsPresent=" + contextTaskbarPresent); - Log.d(TASKBAR_NOT_DESTROYED_TAG, log.toString()); - return; - } - - log.add("mActivity and mContext device profiles have different values, add more logs."); - - log.add("\tmActivity logs:"); - log.add("\t\tmActivity=" + mActivity); - if (mActivity != null) { - log.add("\t\tmActivity.getResources().getConfiguration()=" - + mActivity.getResources().getConfiguration()); - log.add("\t\tmActivity.getDeviceProfile().isTaskbarPresent=" - + activityTaskbarPresent); - } - log.add("\tmContext logs:"); - log.add("\t\tmContext=" + mContext); - log.add("\t\tmContext.getResources().getConfiguration()=" - + mContext.getResources().getConfiguration()); - if (mUserUnlocked) { - log.add("\t\tLauncherAppState.getIDP().getDeviceProfile(mContext).isTaskbarPresent=" - + contextTaskbarPresent); - } else { - log.add("\t\tCouldn't get DeviceProfile because !mUserUnlocked"); - } - - Log.d(TASKBAR_NOT_DESTROYED_TAG, log.toString()); - } - - private final DeviceProfile.OnDeviceProfileChangeListener mDebugActivityDeviceProfileChanged = - dp -> debugWhyTaskbarNotDestroyed("mActivity onDeviceProfileChanged"); -} \ No newline at end of file diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsFallbackSearchContainer.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsFallbackSearchContainer.java deleted file mode 100644 index 53fe06d32c..0000000000 --- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsFallbackSearchContainer.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.launcher3.taskbar.allapps; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.View; - -import androidx.annotation.Nullable; - -import com.android.launcher3.ExtendedEditText; -import com.android.launcher3.allapps.ActivityAllAppsContainerView; -import com.android.launcher3.allapps.SearchUiManager; - -/** Empty search container for Taskbar All Apps used as a fallback if search is not supported. */ -public class TaskbarAllAppsFallbackSearchContainer extends View implements SearchUiManager { - public TaskbarAllAppsFallbackSearchContainer(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public TaskbarAllAppsFallbackSearchContainer( - Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - - @Override - public void initializeSearch(ActivityAllAppsContainerView<?> containerView) { - // Do nothing. - } - - @Override - public void resetSearch() { - // Do nothing. - } - - @Nullable - @Override - public ExtendedEditText getEditText() { - return null; - } -} diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java deleted file mode 100644 index 4f927f07c3..0000000000 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java +++ /dev/null @@ -1,481 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.launcher3.taskbar.bubbles; - -import static java.lang.Math.abs; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; -import android.annotation.Nullable; -import android.view.InsetsController; -import android.view.MotionEvent; -import android.view.View; - -import com.android.launcher3.R; -import com.android.launcher3.anim.AnimatedFloat; -import com.android.launcher3.taskbar.StashedHandleViewController; -import com.android.launcher3.taskbar.TaskbarActivityContext; -import com.android.launcher3.taskbar.TaskbarControllers; -import com.android.launcher3.taskbar.TaskbarInsetsController; -import com.android.launcher3.taskbar.TaskbarStashController; -import com.android.launcher3.util.MultiPropertyFactory; -import com.android.wm.shell.common.bubbles.BubbleBarLocation; -import com.android.wm.shell.shared.animation.PhysicsAnimator; - -/** - * Coordinates between controllers such as BubbleBarView and - * BubbleHandleViewController to - * create a cohesive animation between stashed/unstashed states. - */ -public class BubbleStashController { - - private static final String TAG = "BubbleStashController"; - - /** - * How long to stash/unstash. - */ - public static final long BAR_STASH_DURATION = InsetsController.ANIMATION_DURATION_RESIZE; - - /** - * The scale bubble bar animates to when being stashed. - */ - private static final float STASHED_BAR_SCALE = 0.5f; - - protected final TaskbarActivityContext mActivity; - - // Initialized in init. - private TaskbarControllers mControllers; - private TaskbarInsetsController mTaskbarInsetsController; - private BubbleBarViewController mBarViewController; - private BubbleStashedHandleViewController mHandleViewController; - private TaskbarStashController mTaskbarStashController; - - private MultiPropertyFactory.MultiProperty mIconAlphaForStash; - private AnimatedFloat mIconScaleForStash; - private AnimatedFloat mIconTranslationYForStash; - private MultiPropertyFactory.MultiProperty mBubbleStashedHandleAlpha; - - private boolean mRequestedStashState; - private boolean mRequestedExpandedState; - - private boolean mIsStashed; - private int mStashedHeight; - private int mUnstashedHeight; - private boolean mBubblesShowingOnHome; - private boolean mBubblesShowingOnOverview; - private boolean mIsSysuiLocked; - - private final float mHandleCenterFromScreenBottom; - - @Nullable - private AnimatorSet mAnimator; - - public BubbleStashController(TaskbarActivityContext activity) { - mActivity = activity; - // the handle is centered within the stashed taskbar area - mHandleCenterFromScreenBottom = mActivity.getResources().getDimensionPixelSize(R.dimen.bubblebar_stashed_size) - / 2f; - } - - public void init(TaskbarControllers controllers, BubbleControllers bubbleControllers) { - mControllers = controllers; - mTaskbarInsetsController = controllers.taskbarInsetsController; - mBarViewController = bubbleControllers.bubbleBarViewController; - mHandleViewController = bubbleControllers.bubbleStashedHandleViewController; - mTaskbarStashController = controllers.taskbarStashController; - - mIconAlphaForStash = mBarViewController.getBubbleBarAlpha().get(0); - mIconScaleForStash = mBarViewController.getBubbleBarScale(); - mIconTranslationYForStash = mBarViewController.getBubbleBarTranslationY(); - - mBubbleStashedHandleAlpha = mHandleViewController.getStashedHandleAlpha().get( - StashedHandleViewController.ALPHA_INDEX_STASHED); - - mStashedHeight = mHandleViewController.getStashedHeight(); - mUnstashedHeight = mHandleViewController.getUnstashedHeight(); - } - - /** - * Returns the touchable height of the bubble bar based on it's stashed state. - */ - public int getTouchableHeight() { - return mIsStashed ? mStashedHeight : mUnstashedHeight; - } - - /** - * Returns whether the bubble bar is currently stashed. - */ - public boolean isStashed() { - return mIsStashed; - } - - /** - * Animates the handle (or bubble bar depending on state) to be visible after - * the device is - * unlocked. - * - * <p> - * Normally either the bubble bar or the handle is visible, - * and {@link #showBubbleBar(boolean)} and {@link #stashBubbleBar()} are used to - * transition - * between these two states. But the transition from the state where both the - * bar and handle - * are invisible is slightly different. - */ - private void animateAfterUnlock() { - AnimatorSet animatorSet = new AnimatorSet(); - if (mBubblesShowingOnHome || mBubblesShowingOnOverview) { - mIsStashed = false; - animatorSet.playTogether(mIconScaleForStash.animateToValue(1), - mIconTranslationYForStash.animateToValue(getBubbleBarTranslationY()), - mIconAlphaForStash.animateToValue(1)); - } else { - mIsStashed = true; - animatorSet.playTogether(mBubbleStashedHandleAlpha.animateToValue(1)); - } - - animatorSet.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - onIsStashedChanged(); - } - }); - animatorSet.setDuration(BAR_STASH_DURATION).start(); - } - - /** - * Called when launcher enters or exits the home page. Bubbles are unstashed on - * home. - */ - public void setBubblesShowingOnHome(boolean onHome) { - if (mBubblesShowingOnHome != onHome) { - mBubblesShowingOnHome = onHome; - - if (!mBarViewController.hasBubbles()) { - // if there are no bubbles, there's nothing to show, so just return. - return; - } - - if (mBubblesShowingOnHome) { - showBubbleBar(/* expanded= */ false); - // When transitioning from app to home the stash animator may already have been - // created, so we need to animate the bubble bar here to align with hotseat. - if (!mIsStashed) { - mIconTranslationYForStash.animateToValue(getBubbleBarTranslationYForHotseat()) - .start(); - } - // If the bubble bar is already unstashed, the taskbar touchable region won't be - // updated correctly, so force an update here. - mControllers - .runAfterInit(() -> mTaskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged()); - } else if (!mBarViewController.isExpanded()) { - stashBubbleBar(); - } - } - } - - /** Whether bubbles are showing on the launcher home page. */ - public boolean isBubblesShowingOnHome() { - return mBubblesShowingOnHome; - } - - // TODO: when tapping on an app in overview, this is a bit delayed compared to - // taskbar stashing - /** - * Called when launcher enters or exits overview. Bubbles are unstashed on - * overview. - */ - public void setBubblesShowingOnOverview(boolean onOverview) { - if (mBubblesShowingOnOverview != onOverview) { - mBubblesShowingOnOverview = onOverview; - if (!mBubblesShowingOnOverview && !mBarViewController.isExpanded()) { - stashBubbleBar(); - } else { - // When transitioning to overview the stash animator may already have been - // created, so we need to animate the bubble bar here to align with taskbar. - mIconTranslationYForStash.animateToValue(getBubbleBarTranslationYForTaskbar()) - .start(); - } - } - } - - /** Whether bubbles are showing on Overview. */ - public boolean isBubblesShowingOnOverview() { - return mBubblesShowingOnOverview; - } - - /** - * Called when sysui locked state changes, when locked, bubble bar is stashed. - */ - public void onSysuiLockedStateChange(boolean isSysuiLocked) { - if (isSysuiLocked != mIsSysuiLocked) { - mIsSysuiLocked = isSysuiLocked; - if (!mIsSysuiLocked && mBarViewController.hasBubbles()) { - animateAfterUnlock(); - } - } - } - - /** - * Stashes the bubble bar if allowed based on other state (e.g. on home and - * overview the - * bar does not stash). - */ - public void stashBubbleBar() { - mRequestedStashState = true; - mRequestedExpandedState = false; - updateStashedAndExpandedState(); - } - - /** - * Shows the bubble bar, and expands bubbles depending on - * {@param expandBubbles}. - */ - public void showBubbleBar(boolean expandBubbles) { - mRequestedStashState = false; - mRequestedExpandedState = expandBubbles; - updateStashedAndExpandedState(); - } - - private void updateStashedAndExpandedState() { - if (mBarViewController.isHiddenForNoBubbles()) { - // If there are no bubbles the bar and handle are invisible, nothing to do here. - return; - } - boolean isStashed = mRequestedStashState - && !mBubblesShowingOnHome - && !mBubblesShowingOnOverview; - if (mIsStashed != isStashed) { - // notify the view controller that the stash state is about to change so that it - // can - // cancel an ongoing animation if there is one. - // note that this has to be called before updating mIsStashed with the new - // value, - // otherwise interrupting an ongoing animation may update it again with the - // wrong state - mBarViewController.onStashStateChanging(); - mIsStashed = isStashed; - if (mAnimator != null) { - mAnimator.cancel(); - } - mAnimator = createStashAnimator(mIsStashed, BAR_STASH_DURATION); - mAnimator.start(); - onIsStashedChanged(); - } - if (mBarViewController.isExpanded() != mRequestedExpandedState) { - mBarViewController.setExpanded(mRequestedExpandedState); - } - } - - /** - * Create a stash animation. - * - * @param isStashed whether it's a stash animation or an unstash animation - * @param duration duration of the animation - * @return the animation - */ - private AnimatorSet createStashAnimator(boolean isStashed, long duration) { - AnimatorSet animatorSet = new AnimatorSet(); - - AnimatorSet fullLengthAnimatorSet = new AnimatorSet(); - // Not exactly half and may overlap. See [first|second]HalfDurationScale below. - AnimatorSet firstHalfAnimatorSet = new AnimatorSet(); - AnimatorSet secondHalfAnimatorSet = new AnimatorSet(); - - final float firstHalfDurationScale; - final float secondHalfDurationScale; - - if (isStashed) { - firstHalfDurationScale = 0.75f; - secondHalfDurationScale = 0.5f; - - fullLengthAnimatorSet.play( - mIconTranslationYForStash.animateToValue(getStashTranslation())); - - firstHalfAnimatorSet.playTogether( - mIconAlphaForStash.animateToValue(0), - mIconScaleForStash.animateToValue(STASHED_BAR_SCALE)); - secondHalfAnimatorSet.playTogether( - mBubbleStashedHandleAlpha.animateToValue(1)); - } else { - firstHalfDurationScale = 0.5f; - secondHalfDurationScale = 0.75f; - - final float translationY = getBubbleBarTranslationY(); - - fullLengthAnimatorSet.playTogether( - mIconScaleForStash.animateToValue(1), - mIconTranslationYForStash.animateToValue(translationY)); - - firstHalfAnimatorSet.playTogether( - mBubbleStashedHandleAlpha.animateToValue(0)); - secondHalfAnimatorSet.playTogether( - mIconAlphaForStash.animateToValue(1)); - } - - fullLengthAnimatorSet.play(mHandleViewController.createRevealAnimToIsStashed(isStashed)); - - fullLengthAnimatorSet.setDuration(duration); - firstHalfAnimatorSet.setDuration((long) (duration * firstHalfDurationScale)); - secondHalfAnimatorSet.setDuration((long) (duration * secondHalfDurationScale)); - secondHalfAnimatorSet.setStartDelay((long) (duration * (1 - secondHalfDurationScale))); - - animatorSet.playTogether(fullLengthAnimatorSet, firstHalfAnimatorSet, - secondHalfAnimatorSet); - animatorSet.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mAnimator = null; - mControllers.runAfterInit(() -> { - if (isStashed) { - mBarViewController.setExpanded(false); - } - mTaskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged(); - }); - } - }); - return animatorSet; - } - - private float getStashTranslation() { - return (mUnstashedHeight - mStashedHeight) / 2f; - } - - private void onIsStashedChanged() { - mControllers.runAfterInit(() -> { - mHandleViewController.onIsStashedChanged(); - mTaskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged(); - }); - } - - public float getBubbleBarTranslationYForTaskbar() { - return -mActivity.getDeviceProfile().taskbarBottomMargin; - } - - private float getBubbleBarTranslationYForHotseat() { - final float hotseatBottomSpace = mActivity.getDeviceProfile().hotseatBarBottomSpacePx; - final float hotseatCellHeight = mActivity.getDeviceProfile().hotseatCellHeightPx; - return -hotseatBottomSpace - hotseatCellHeight + mUnstashedHeight - abs( - hotseatCellHeight - mUnstashedHeight) / 2; - } - - public float getBubbleBarTranslationY() { - // If we're on home, adjust the translation so the bubble bar aligns with - // hotseat. - // Otherwise we're either showing in an app or in overview. In either case - // adjust it so - // the bubble bar aligns with the taskbar. - return mBubblesShowingOnHome ? getBubbleBarTranslationYForHotseat() - : getBubbleBarTranslationYForTaskbar(); - } - - /** - * The difference on the Y axis between the center of the handle and the center - * of the bubble - * bar. - */ - public float getDiffBetweenHandleAndBarCenters() { - // the difference between the centers of the handle and the bubble bar is the - // difference - // between their distance from the bottom of the screen. - - float barCenter = mBarViewController.getBubbleBarCollapsedHeight() / 2f; - return mHandleCenterFromScreenBottom - barCenter; - } - - /** The distance the handle moves as part of the new bubble animation. */ - public float getStashedHandleTranslationForNewBubbleAnimation() { - // the should move up to the top of the stashed taskbar area. it is centered - // within it so - // it should move the same distance as it is away from the bottom. - return -mHandleCenterFromScreenBottom; - } - - /** Checks whether the motion event is over the stash handle. */ - public boolean isEventOverStashHandle(MotionEvent ev) { - return mHandleViewController.isEventOverHandle(ev); - } - - /** Set a bubble bar location */ - public void setBubbleBarLocation(BubbleBarLocation bubbleBarLocation) { - mHandleViewController.setBubbleBarLocation(bubbleBarLocation); - } - - /** Returns the [PhysicsAnimator] for the stashed handle view. */ - public PhysicsAnimator<View> getStashedHandlePhysicsAnimator() { - return mHandleViewController.getPhysicsAnimator(); - } - - /** Notifies taskbar that it should update its touchable region. */ - public void updateTaskbarTouchRegion() { - mTaskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged(); - } - - /** Shows the bubble bar immediately without animation. */ - public void showBubbleBarImmediate() { - mHandleViewController.setTranslationYForSwipe(0); - mIconTranslationYForStash.updateValue(getBubbleBarTranslationY()); - mIconAlphaForStash.setValue(1); - mIconScaleForStash.updateValue(1); - mIsStashed = false; - onIsStashedChanged(); - } - - /** Stashes the bubble bar immediately without animation. */ - public void stashBubbleBarImmediate() { - mHandleViewController.setTranslationYForSwipe(0); - mBubbleStashedHandleAlpha.setValue(1); - mIconAlphaForStash.setValue(0); - mIconTranslationYForStash.updateValue(getStashTranslation()); - mIconScaleForStash.updateValue(STASHED_BAR_SCALE); - mIsStashed = true; - onIsStashedChanged(); - } - - /** - * Updates the values of the internal animators after the new bubble animation - * was interrupted - * - * @param isStashed whether the current state should be stashed - * @param bubbleBarTranslationY the current bubble bar translation. this is only - * used if the - * bubble bar is showing to ensure that the stash - * animator runs - * smoothly. - */ - public void onNewBubbleAnimationInterrupted(boolean isStashed, float bubbleBarTranslationY) { - if (isStashed) { - mBubbleStashedHandleAlpha.setValue(1); - mIconAlphaForStash.setValue(0); - mIconScaleForStash.updateValue(STASHED_BAR_SCALE); - mIconTranslationYForStash.updateValue(getStashTranslation()); - } else { - mBubbleStashedHandleAlpha.setValue(0); - mHandleViewController.setTranslationYForSwipe(0); - mIconAlphaForStash.setValue(1); - mIconScaleForStash.updateValue(1); - mIconTranslationYForStash.updateValue(bubbleBarTranslationY); - } - mIsStashed = isStashed; - onIsStashedChanged(); - } - - /** Set the translation Y for the stashed handle. */ - public void setHandleTranslationY(float ty) { - mHandleViewController.setTranslationYForSwipe(ty); - } -} diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java deleted file mode 100644 index 14d391ba1c..0000000000 --- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher3.uioverrides; - -import static com.android.app.animation.Interpolators.ACCELERATE_DECELERATE; -import static com.android.app.animation.Interpolators.AGGRESSIVE_EASE_IN_OUT; -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.launcher3.LauncherState.QUICK_SWITCH_FROM_HOME; -import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTION_EXIT_HOME; -import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE; -import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_MODAL; -import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE; -import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SPLIT_SELECT_INSTRUCTIONS_FADE; -import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X; -import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y; -import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW; -import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET; -import static com.android.quickstep.views.RecentsView.RECENTS_GRID_PROGRESS; -import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY; -import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION; -import static com.android.quickstep.views.RecentsView.TASK_THUMBNAIL_SPLASH_ALPHA; - -import android.util.FloatProperty; -import android.view.animation.Interpolator; - -import androidx.annotation.NonNull; - -import com.android.launcher3.LauncherState; -import com.android.launcher3.anim.PendingAnimation; -import com.android.launcher3.config.FeatureFlags; -import com.android.launcher3.statemanager.StateManager.StateHandler; -import com.android.launcher3.states.StateAnimationConfig; -import com.android.quickstep.views.RecentsView; - -/** - * State handler for recents view. Manages UI changes and animations for recents view based off the - * current {@link LauncherState}. - * - * @param <T> the recents view - */ -public abstract class BaseRecentsViewStateController<T extends RecentsView> - implements StateHandler<LauncherState> { - protected final T mRecentsView; - protected final QuickstepLauncher mLauncher; - - public BaseRecentsViewStateController(@NonNull QuickstepLauncher launcher) { - mLauncher = launcher; - mRecentsView = launcher.getOverviewPanel(); - } - - @Override - public void setState(@NonNull LauncherState state) { - float[] scaleAndOffset = state.getOverviewScaleAndOffset(mLauncher); - RECENTS_SCALE_PROPERTY.set(mRecentsView, scaleAndOffset[0]); - ADJACENT_PAGE_HORIZONTAL_OFFSET.set(mRecentsView, scaleAndOffset[1]); - TASK_SECONDARY_TRANSLATION.set(mRecentsView, 0f); - - getContentAlphaProperty().set(mRecentsView, state.isRecentsViewVisible ? 1f : 0); - getTaskModalnessProperty().set(mRecentsView, state.getOverviewModalness()); - RECENTS_GRID_PROGRESS.set(mRecentsView, - state.displayOverviewTasksAsGrid(mLauncher.getDeviceProfile()) ? 1f : 0f); - TASK_THUMBNAIL_SPLASH_ALPHA.set(mRecentsView, state.showTaskThumbnailSplash() ? 1f : 0f); - } - - @Override - public void setStateWithAnimation(LauncherState toState, StateAnimationConfig config, - PendingAnimation builder) { - if (config.hasAnimationFlag(SKIP_OVERVIEW)) { - return; - } - setStateWithAnimationInternal(toState, config, builder); - builder.addEndListener(success -> { - if (!success) { - mRecentsView.reset(); - } - }); - } - - /** - * Core logic for animating the recents view UI. - * - * @param toState state to animate to - * @param config current animation config - * @param setter animator set builder - */ - void setStateWithAnimationInternal(@NonNull final LauncherState toState, - @NonNull StateAnimationConfig config, @NonNull PendingAnimation setter) { - float[] scaleAndOffset = toState.getOverviewScaleAndOffset(mLauncher); - setter.setFloat(mRecentsView, RECENTS_SCALE_PROPERTY, scaleAndOffset[0], - config.getInterpolator(ANIM_OVERVIEW_SCALE, LINEAR)); - setter.setFloat(mRecentsView, ADJACENT_PAGE_HORIZONTAL_OFFSET, scaleAndOffset[1], - config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_X, LINEAR)); - setter.setFloat(mRecentsView, TASK_SECONDARY_TRANSLATION, 0f, - config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, LINEAR)); - - boolean exitingOverview = - !FeatureFlags.enableSplitContextually() && !toState.isRecentsViewVisible; - if (mRecentsView.isSplitSelectionActive() && exitingOverview) { - setter.add(mRecentsView.getSplitSelectController().getSplitAnimationController() - .createPlaceholderDismissAnim(mLauncher, LAUNCHER_SPLIT_SELECTION_EXIT_HOME, - setter.getDuration())); - setter.setViewAlpha( - mRecentsView.getSplitInstructionsView(), - 0, - config.getInterpolator( - ANIM_OVERVIEW_SPLIT_SELECT_INSTRUCTIONS_FADE, - LINEAR - ) - ); - } - - setter.setFloat(mRecentsView, getContentAlphaProperty(), - toState.isRecentsViewVisible ? 1 : 0, - config.getInterpolator(ANIM_OVERVIEW_FADE, AGGRESSIVE_EASE_IN_OUT)); - - setter.setFloat( - mRecentsView, getTaskModalnessProperty(), - toState.getOverviewModalness(), - config.getInterpolator(ANIM_OVERVIEW_MODAL, LINEAR)); - - LauncherState fromState = mLauncher.getStateManager().getState(); - setter.setFloat(mRecentsView, TASK_THUMBNAIL_SPLASH_ALPHA, - toState.showTaskThumbnailSplash() ? 1f : 0f, - getOverviewInterpolator(fromState, toState)); - - setter.setFloat(mRecentsView, RECENTS_GRID_PROGRESS, - toState.displayOverviewTasksAsGrid(mLauncher.getDeviceProfile()) ? 1f : 0f, - getOverviewInterpolator(fromState, toState)); - } - - private Interpolator getOverviewInterpolator(LauncherState fromState, LauncherState toState) { - return fromState == QUICK_SWITCH_FROM_HOME - ? ACCELERATE_DECELERATE - : toState.isRecentsViewVisible ? INSTANT : FINAL_FRAME; - } - - abstract FloatProperty getTaskModalnessProperty(); - - /** - * Get property for content alpha for the recents view. - * - * @return the float property for the view's content alpha - */ - abstract FloatProperty getContentAlphaProperty(); -} diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepAppWidgetHost.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepAppWidgetHost.java deleted file mode 100644 index 45813ce52e..0000000000 --- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepAppWidgetHost.java +++ /dev/null @@ -1,74 +0,0 @@ -/** - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.launcher3.uioverrides; - -import static com.android.launcher3.widget.LauncherWidgetHolder.APPWIDGET_HOST_ID; - -import android.appwidget.AppWidgetHost; -import android.appwidget.AppWidgetProviderInfo; -import android.content.Context; -import android.os.Looper; - -import androidx.annotation.NonNull; - -import com.android.launcher3.LauncherAppState; -import com.android.launcher3.util.Executors; -import com.android.launcher3.widget.LauncherAppWidgetProviderInfo; -import com.android.launcher3.widget.LauncherWidgetHolder; - -import java.util.function.IntConsumer; - -/** - * {@link AppWidgetHost} that is used to receive the changes to the widgets without - * storing any {@code Activity} info like that of the launcher. - */ -final class QuickstepAppWidgetHost extends AppWidgetHost { - private final @NonNull Context mContext; - private final @NonNull IntConsumer mAppWidgetRemovedCallback; - private final @NonNull LauncherWidgetHolder.ProviderChangedListener mProvidersChangedListener; - - QuickstepAppWidgetHost(@NonNull Context context, @NonNull IntConsumer appWidgetRemovedCallback, - @NonNull LauncherWidgetHolder.ProviderChangedListener listener, - @NonNull Looper looper) { - super(context, APPWIDGET_HOST_ID, null, looper); - mContext = context; - mAppWidgetRemovedCallback = appWidgetRemovedCallback; - mProvidersChangedListener = listener; - } - - @Override - protected void onProvidersChanged() { - mProvidersChangedListener.notifyWidgetProvidersChanged(); - } - - @Override - public void onAppWidgetRemoved(int appWidgetId) { - // 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))); - } - - @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)); - } -} diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java deleted file mode 100644 index 235ec7b101..0000000000 --- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.launcher3.uioverrides; - -import static com.android.app.animation.Interpolators.LINEAR; -import static com.android.launcher3.LauncherState.CLEAR_ALL_BUTTON; -import static com.android.launcher3.LauncherState.OVERVIEW; -import static com.android.launcher3.LauncherState.OVERVIEW_ACTIONS; -import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT; -import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_ACTIONS_FADE; -import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA; -import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS; -import static com.android.quickstep.views.RecentsView.TASK_MODALNESS; -import static com.android.quickstep.views.RecentsView.TASK_PRIMARY_SPLIT_TRANSLATION; -import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_SPLIT_TRANSLATION; -import static com.android.quickstep.views.TaskView.FLAG_UPDATE_ALL; -import static com.android.wm.shell.Flags.enableSplitContextual; - -import android.animation.AnimatorSet; -import android.annotation.TargetApi; -import android.os.Build; -import android.util.FloatProperty; -import android.util.Log; -import android.util.Pair; - -import androidx.annotation.NonNull; - -import com.android.launcher3.LauncherState; -import com.android.launcher3.Utilities; -import com.android.launcher3.anim.AnimatedFloat; -import com.android.launcher3.anim.AnimatorListeners; -import com.android.launcher3.anim.PendingAnimation; -import com.android.launcher3.anim.PropertySetter; -import com.android.launcher3.states.StateAnimationConfig; -import com.android.quickstep.orientation.RecentsPagedOrientationHandler; -import com.android.quickstep.util.AnimUtils; -import com.android.quickstep.util.SplitAnimationTimings; -import com.android.quickstep.views.ClearAllButton; -import com.android.quickstep.views.LauncherRecentsView; -import com.android.quickstep.views.RecentsView; - -/** - * State handler for handling UI changes for {@link LauncherRecentsView}. In addition to managing - * the basic view properties, this class also manages changes in the task visuals. - */ -@TargetApi(Build.VERSION_CODES.O) -public final class RecentsViewStateController extends - BaseRecentsViewStateController<LauncherRecentsView> { - - public RecentsViewStateController(QuickstepLauncher launcher) { - super(launcher); - } - - @Override - public void setState(@NonNull LauncherState state) { - super.setState(state); - if (state.isRecentsViewVisible) { - mRecentsView.updateEmptyMessage(); - } else { - mRecentsView.resetTaskVisuals(); - } - setAlphas(PropertySetter.NO_ANIM_PROPERTY_SETTER, new StateAnimationConfig(), state); - mRecentsView.setFullscreenProgress(state.getOverviewFullscreenProgress()); - // In Overview, we may be layering app surfaces behind Launcher, so we need to notify - // DepthController to prevent optimizations which might occlude the layers behind - mLauncher.getDepthController().setHasContentBehindLauncher(state.isRecentsViewVisible); - - PendingAnimation builder = - new PendingAnimation(state.getTransitionDuration(mLauncher, true)); - - handleSplitSelectionState(state, builder, /* animate */false); - } - - @Override - void setStateWithAnimationInternal(@NonNull LauncherState toState, - @NonNull StateAnimationConfig config, @NonNull PendingAnimation builder) { - super.setStateWithAnimationInternal(toState, config, builder); - - if (toState.isRecentsViewVisible) { - // While animating into recents, update the visible task data as needed - builder.addOnFrameCallback(() -> mRecentsView.loadVisibleTaskData(FLAG_UPDATE_ALL)); - mRecentsView.updateEmptyMessage(); - // TODO(b/246283207): Remove logging once root cause of flake detected. - if (Utilities.isRunningInTestHarness()) { - Log.d("b/246283207", "RecentsView#setStateWithAnimationInternal getCurrentPage(): " - + mRecentsView.getCurrentPage() - + ", getScrollForPage(getCurrentPage())): " - + mRecentsView.getScrollForPage(mRecentsView.getCurrentPage())); - } - } else { - builder.addListener( - AnimatorListeners.forSuccessCallback(mRecentsView::resetTaskVisuals)); - } - // In Overview, we may be layering app surfaces behind Launcher, so we need to notify - // DepthController to prevent optimizations which might occlude the layers behind - builder.addListener(AnimatorListeners.forSuccessCallback(() -> - mLauncher.getDepthController().setHasContentBehindLauncher( - toState.isRecentsViewVisible))); - - handleSplitSelectionState(toState, builder, /* animate */true); - - setAlphas(builder, config, toState); - builder.setFloat(mRecentsView, FULLSCREEN_PROGRESS, - toState.getOverviewFullscreenProgress(), LINEAR); - } - - /** - * Create or dismiss split screen select animations. - * @param builder if null then this will run the split select animations right away, otherwise - * will add animations to builder. - */ - private void handleSplitSelectionState(@NonNull LauncherState toState, - @NonNull PendingAnimation builder, boolean animate) { - boolean goingToOverviewFromWorkspaceContextual = enableSplitContextual() && - toState == OVERVIEW && mLauncher.isSplitSelectionActive(); - if (toState != OVERVIEW_SPLIT_SELECT && !goingToOverviewFromWorkspaceContextual) { - // Not going to split - return; - } - - // Create transition animations to split select - RecentsPagedOrientationHandler orientationHandler = - ((RecentsView) mLauncher.getOverviewPanel()).getPagedOrientationHandler(); - Pair<FloatProperty<RecentsView>, FloatProperty<RecentsView>> taskViewsFloat = - orientationHandler.getSplitSelectTaskOffset( - TASK_PRIMARY_SPLIT_TRANSLATION, TASK_SECONDARY_SPLIT_TRANSLATION, - mLauncher.getDeviceProfile()); - - SplitAnimationTimings timings = - AnimUtils.getDeviceOverviewToSplitTimings(mLauncher.getDeviceProfile().isTablet); - if (!goingToOverviewFromWorkspaceContextual) { - // This animation is already done for the contextual case, don't redo it - mRecentsView.createSplitSelectInitAnimation(builder, - toState.getTransitionDuration(mLauncher, true /* isToState */)); - } - // Shift tasks vertically downward to get out of placeholder view - builder.setFloat(mRecentsView, taskViewsFloat.first, - toState.getSplitSelectTranslation(mLauncher), - timings.getGridSlidePrimaryInterpolator()); - // Zero out horizontal translation - builder.setFloat(mRecentsView, taskViewsFloat.second, - 0, - timings.getGridSlideSecondaryInterpolator()); - - if (!animate) { - AnimatorSet as = builder.buildAnim(); - as.start(); - as.end(); - } - } - - private void setAlphas(PropertySetter propertySetter, StateAnimationConfig config, - LauncherState state) { - float clearAllButtonAlpha = state.areElementsVisible(mLauncher, CLEAR_ALL_BUTTON) ? 1 : 0; - propertySetter.setFloat(mRecentsView.getClearAllButton(), ClearAllButton.VISIBILITY_ALPHA, - clearAllButtonAlpha, LINEAR); - float overviewButtonAlpha = state.areElementsVisible(mLauncher, OVERVIEW_ACTIONS) ? 1 : 0; - propertySetter.setFloat(mLauncher.getActionsView().getVisibilityAlpha(), - AnimatedFloat.VALUE, overviewButtonAlpha, config.getInterpolator( - ANIM_OVERVIEW_ACTIONS_FADE, LINEAR)); - } - - @Override - FloatProperty<RecentsView> getTaskModalnessProperty() { - return TASK_MODALNESS; - } - - @Override - FloatProperty<RecentsView> getContentAlphaProperty() { - return CONTENT_ALPHA; - } -} diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java deleted file mode 100644 index 205da70513..0000000000 --- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java +++ /dev/null @@ -1,395 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.launcher3.uioverrides.touchcontrollers; - -import static com.android.launcher3.AbstractFloatingView.TYPE_TOUCH_CONTROLLER_NO_INTERCEPT; -import static com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS; -import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_BOTH; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.content.Context; -import android.os.VibrationEffect; -import android.view.MotionEvent; -import android.view.View; -import android.view.animation.Interpolator; - -import com.android.app.animation.Interpolators; -import com.android.launcher3.AbstractFloatingView; -import com.android.launcher3.LauncherAnimUtils; -import com.android.launcher3.R; -import com.android.launcher3.Utilities; -import com.android.launcher3.anim.AnimatorPlaybackController; -import com.android.launcher3.anim.PendingAnimation; -import com.android.launcher3.touch.BaseSwipeDetector; -import com.android.launcher3.touch.SingleAxisSwipeDetector; -import com.android.launcher3.util.DisplayController; -import com.android.launcher3.util.FlingBlockCheck; -import com.android.launcher3.util.TouchController; -import com.android.launcher3.util.VibratorWrapper; -import com.android.launcher3.views.BaseDragLayer; -import com.android.quickstep.orientation.RecentsPagedOrientationHandler; -import com.android.quickstep.util.VibrationConstants; -import com.android.quickstep.views.RecentsView; -import com.android.quickstep.views.RecentsViewContainer; -import com.android.quickstep.views.TaskView; - -/** - * Touch controller for handling task view card swipes - */ -public abstract class TaskViewTouchController<CONTAINER extends Context & RecentsViewContainer> - extends AnimatorListenerAdapter implements TouchController, - SingleAxisSwipeDetector.Listener { - - private static final float ANIMATION_PROGRESS_FRACTION_MIDPOINT = 0.5f; - private static final long MIN_TASK_DISMISS_ANIMATION_DURATION = 300; - private static final long MAX_TASK_DISMISS_ANIMATION_DURATION = 600; - - public static final int TASK_DISMISS_VIBRATION_PRIMITIVE = - VibrationEffect.Composition.PRIMITIVE_TICK; - public static final float TASK_DISMISS_VIBRATION_PRIMITIVE_SCALE = 1f; - public static final VibrationEffect TASK_DISMISS_VIBRATION_FALLBACK = - VibrationConstants.EFFECT_TEXTURE_TICK; - - protected final CONTAINER mContainer; - private final SingleAxisSwipeDetector mDetector; - private final RecentsView mRecentsView; - private final int[] mTempCords = new int[2]; - private final boolean mIsRtl; - - private AnimatorPlaybackController mCurrentAnimation; - private boolean mCurrentAnimationIsGoingUp; - private boolean mAllowGoingUp; - private boolean mAllowGoingDown; - - private boolean mNoIntercept; - - private float mDisplacementShift; - private float mProgressMultiplier; - private float mEndDisplacement; - private boolean mDraggingEnabled = true; - private FlingBlockCheck mFlingBlockCheck = new FlingBlockCheck(); - private Float mOverrideVelocity = null; - - private TaskView mTaskBeingDragged; - - private boolean mIsDismissHapticRunning = false; - - public TaskViewTouchController(CONTAINER container) { - mContainer = container; - mRecentsView = container.getOverviewPanel(); - mIsRtl = Utilities.isRtl(container.getResources()); - SingleAxisSwipeDetector.Direction dir = - mRecentsView.getPagedOrientationHandler().getUpDownSwipeDirection(); - mDetector = new SingleAxisSwipeDetector(container, this, dir); - } - - private boolean canInterceptTouch(MotionEvent ev) { - if ((ev.getEdgeFlags() & Utilities.EDGE_NAV_BAR) != 0) { - // Don't intercept swipes on the nav bar, as user might be trying to go home - // during a task dismiss animation. - if (mCurrentAnimation != null) { - mCurrentAnimation.getAnimationPlayer().end(); - } - return false; - } - if (mCurrentAnimation != null) { - mCurrentAnimation.forceFinishIfCloseToEnd(); - } - if (mCurrentAnimation != null) { - // If we are already animating from a previous state, we can intercept. - return true; - } - if (AbstractFloatingView.getTopOpenViewWithType( - mContainer, TYPE_TOUCH_CONTROLLER_NO_INTERCEPT) != null) { - return false; - } - return isRecentsInteractive(); - } - - protected abstract boolean isRecentsInteractive(); - - /** Is recents view showing a single task in a modal way. */ - protected abstract boolean isRecentsModal(); - - protected void onUserControlledAnimationCreated(AnimatorPlaybackController animController) { - } - - @Override - public void onAnimationCancel(Animator animation) { - if (mCurrentAnimation != null && animation == mCurrentAnimation.getTarget()) { - clearState(); - } - } - - @Override - public boolean onControllerInterceptTouchEvent(MotionEvent ev) { - if ((ev.getAction() == MotionEvent.ACTION_UP || ev.getAction() == MotionEvent.ACTION_CANCEL) - && mCurrentAnimation == null) { - clearState(); - } - if (ev.getAction() == MotionEvent.ACTION_DOWN) { - mNoIntercept = !canInterceptTouch(ev); - if (mNoIntercept) { - return false; - } - - // Now figure out which direction scroll events the controller will start - // calling the callbacks. - int directionsToDetectScroll = 0; - boolean ignoreSlopWhenSettling = false; - if (mCurrentAnimation != null) { - directionsToDetectScroll = DIRECTION_BOTH; - ignoreSlopWhenSettling = true; - } else { - mTaskBeingDragged = null; - - for (int i = 0; i < mRecentsView.getTaskViewCount(); i++) { - TaskView view = mRecentsView.getTaskViewAt(i); - - if (mRecentsView.isTaskViewVisible(view) && mContainer.getDragLayer() - .isEventOverView(view, ev)) { - // Disable swiping up and down if the task overlay is modal. - if (isRecentsModal()) { - mTaskBeingDragged = null; - break; - } - mTaskBeingDragged = view; - int upDirection = mRecentsView.getPagedOrientationHandler() - .getUpDirection(mIsRtl); - - // The task can be dragged up to dismiss it - mAllowGoingUp = true; - - // The task can be dragged down to open it if: - // - It's the current page - // - We support gestures to enter overview - // - It's the focused task if in grid view - // - The task is snapped - mAllowGoingDown = i == mRecentsView.getCurrentPage() - && DisplayController.getNavigationMode(mContainer).hasGestures - && (!mRecentsView.showAsGrid() || mTaskBeingDragged.isFocusedTask()) - && mRecentsView.isTaskInExpectedScrollPosition(i); - - directionsToDetectScroll = mAllowGoingDown ? DIRECTION_BOTH : upDirection; - break; - } - } - if (mTaskBeingDragged == null) { - mNoIntercept = true; - return false; - } - } - - mDetector.setDetectableScrollConditions( - directionsToDetectScroll, ignoreSlopWhenSettling); - } - - if (mNoIntercept) { - return false; - } - - onControllerTouchEvent(ev); - return mDetector.isDraggingOrSettling(); - } - - @Override - public boolean onControllerTouchEvent(MotionEvent ev) { - return mDetector.onTouchEvent(ev); - } - - private void reInitAnimationController(boolean goingUp) { - if (mCurrentAnimation != null && mCurrentAnimationIsGoingUp == goingUp) { - // No need to init - return; - } - if ((goingUp && !mAllowGoingUp) || (!goingUp && !mAllowGoingDown)) { - // Trying to re-init in an unsupported direction. - return; - } - if (mCurrentAnimation != null) { - mCurrentAnimation.setPlayFraction(0); - mCurrentAnimation.getTarget().removeListener(this); - mCurrentAnimation.dispatchOnCancel(); - } - - RecentsPagedOrientationHandler orientationHandler = - mRecentsView.getPagedOrientationHandler(); - mCurrentAnimationIsGoingUp = goingUp; - BaseDragLayer dl = mContainer.getDragLayer(); - final int secondaryLayerDimension = orientationHandler.getSecondaryDimension(dl); - long maxDuration = 2 * secondaryLayerDimension; - int verticalFactor = orientationHandler.getTaskDragDisplacementFactor(mIsRtl); - int secondaryTaskDimension = orientationHandler.getSecondaryDimension(mTaskBeingDragged); - // The interpolator controlling the most prominent visual movement. We use this to determine - // whether we passed SUCCESS_TRANSITION_PROGRESS. - final Interpolator currentInterpolator; - PendingAnimation pa; - if (goingUp) { - currentInterpolator = Interpolators.LINEAR; - pa = new PendingAnimation(maxDuration); - mRecentsView.createTaskDismissAnimation(pa, mTaskBeingDragged, - true /* animateTaskView */, true /* removeTask */, maxDuration, - false /* dismissingForSplitSelection*/); - - mEndDisplacement = -secondaryTaskDimension; - } else { - currentInterpolator = Interpolators.ZOOM_IN; - pa = mRecentsView.createTaskLaunchAnimation( - mTaskBeingDragged, maxDuration, currentInterpolator); - - // Since the thumbnail is what is filling the screen, based the end displacement on it. - View thumbnailView = mTaskBeingDragged.getFirstThumbnailViewDeprecated(); - mTempCords[1] = orientationHandler.getSecondaryDimension(thumbnailView); - dl.getDescendantCoordRelativeToSelf(thumbnailView, mTempCords); - mEndDisplacement = secondaryLayerDimension - mTempCords[1]; - } - mEndDisplacement *= verticalFactor; - mCurrentAnimation = pa.createPlaybackController(); - - // Setting this interpolator doesn't affect the visual motion, but is used to determine - // whether we successfully reached the target state in onDragEnd(). - mCurrentAnimation.getTarget().setInterpolator(currentInterpolator); - onUserControlledAnimationCreated(mCurrentAnimation); - mCurrentAnimation.getTarget().addListener(this); - mCurrentAnimation.dispatchOnStart(); - mProgressMultiplier = 1 / mEndDisplacement; - } - - @Override - public void onDragStart(boolean start, float startDisplacement) { - if (!mDraggingEnabled) return; - - RecentsPagedOrientationHandler orientationHandler = - mRecentsView.getPagedOrientationHandler(); - if (mCurrentAnimation == null) { - reInitAnimationController(orientationHandler.isGoingUp(startDisplacement, mIsRtl)); - mDisplacementShift = 0; - } else { - mDisplacementShift = mCurrentAnimation.getProgressFraction() / mProgressMultiplier; - mCurrentAnimation.pause(); - } - mFlingBlockCheck.unblockFling(); - mOverrideVelocity = null; - } - - @Override - public boolean onDrag(float displacement) { - if (!mDraggingEnabled) return true; - - RecentsPagedOrientationHandler orientationHandler = - mRecentsView.getPagedOrientationHandler(); - float totalDisplacement = displacement + mDisplacementShift; - boolean isGoingUp = totalDisplacement == 0 ? mCurrentAnimationIsGoingUp : - orientationHandler.isGoingUp(totalDisplacement, mIsRtl); - if (isGoingUp != mCurrentAnimationIsGoingUp) { - reInitAnimationController(isGoingUp); - mFlingBlockCheck.blockFling(); - } else { - mFlingBlockCheck.onEvent(); - } - - if (isGoingUp) { - if (mCurrentAnimation.getProgressFraction() < ANIMATION_PROGRESS_FRACTION_MIDPOINT) { - // Halve the value when dismissing, as we are animating the drag across the full - // length for only the first half of the progress - mCurrentAnimation.setPlayFraction( - Utilities.boundToRange(totalDisplacement * mProgressMultiplier / 2, 0, 1)); - } else { - // Set mOverrideVelocity to control task dismiss velocity in onDragEnd - int velocityDimenId = R.dimen.default_task_dismiss_drag_velocity; - if (mRecentsView.showAsGrid()) { - if (mTaskBeingDragged.isFocusedTask()) { - velocityDimenId = - R.dimen.default_task_dismiss_drag_velocity_grid_focus_task; - } else { - velocityDimenId = R.dimen.default_task_dismiss_drag_velocity_grid; - } - } - mOverrideVelocity = -mTaskBeingDragged.getResources().getDimension(velocityDimenId); - - // Once halfway through task dismissal interpolation, switch from reversible - // dragging-task animation to playing the remaining task translation animations, - // while this is in progress disable dragging. - mDraggingEnabled = false; - } - } else { - mCurrentAnimation.setPlayFraction( - Utilities.boundToRange(totalDisplacement * mProgressMultiplier, 0, 1)); - } - - return true; - } - - @Override - public void onDragEnd(float velocity) { - if (mOverrideVelocity != null) { - velocity = mOverrideVelocity; - mOverrideVelocity = null; - } - // Limit velocity, as very large scalar values make animations play too quickly - float maxTaskDismissDragVelocity = mTaskBeingDragged.getResources().getDimension( - R.dimen.max_task_dismiss_drag_velocity); - velocity = Utilities.boundToRange(velocity, -maxTaskDismissDragVelocity, - maxTaskDismissDragVelocity); - boolean fling = mDraggingEnabled && mDetector.isFling(velocity); - final boolean goingToEnd; - boolean blockedFling = fling && mFlingBlockCheck.isBlocked(); - if (blockedFling) { - fling = false; - } - RecentsPagedOrientationHandler orientationHandler = - mRecentsView.getPagedOrientationHandler(); - boolean goingUp = orientationHandler.isGoingUp(velocity, mIsRtl); - float progress = mCurrentAnimation.getProgressFraction(); - float interpolatedProgress = mCurrentAnimation.getInterpolatedProgress(); - if (fling) { - goingToEnd = goingUp == mCurrentAnimationIsGoingUp; - } else { - goingToEnd = interpolatedProgress > SUCCESS_TRANSITION_PROGRESS; - } - long animationDuration = BaseSwipeDetector.calculateDuration( - velocity, goingToEnd ? (1 - progress) : progress); - if (blockedFling && !goingToEnd) { - animationDuration *= LauncherAnimUtils.blockedFlingDurationFactor(velocity); - } - // Due to very high or low velocity dismissals, animation durations can be inconsistently - // long or short. Bound the duration for animation of task translations for a more - // standardized feel. - animationDuration = Utilities.boundToRange(animationDuration, - MIN_TASK_DISMISS_ANIMATION_DURATION, MAX_TASK_DISMISS_ANIMATION_DURATION); - - mCurrentAnimation.setEndAction(this::clearState); - mCurrentAnimation.startWithVelocity(mContainer, goingToEnd, Math.abs(velocity), - mEndDisplacement, animationDuration); - if (goingUp && goingToEnd && !mIsDismissHapticRunning) { - VibratorWrapper.INSTANCE.get(mContainer).vibrate(TASK_DISMISS_VIBRATION_PRIMITIVE, - TASK_DISMISS_VIBRATION_PRIMITIVE_SCALE, TASK_DISMISS_VIBRATION_FALLBACK); - mIsDismissHapticRunning = true; - } - - mDraggingEnabled = true; - } - - private void clearState() { - mDetector.finishedScrolling(); - mDetector.setDetectableScrollConditions(0, false); - mDraggingEnabled = true; - mTaskBeingDragged = null; - mCurrentAnimation = null; - mIsDismissHapticRunning = false; - } -} \ No newline at end of file diff --git a/quickstep/src/com/android/quickstep/LauncherSearchIndexablesProvider.java b/quickstep/src/com/android/quickstep/LauncherSearchIndexablesProvider.java deleted file mode 100644 index 5d6fbe2ca6..0000000000 --- a/quickstep/src/com/android/quickstep/LauncherSearchIndexablesProvider.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.quickstep; - -import android.annotation.TargetApi; -import android.content.Intent; -import android.content.pm.LauncherApps; -import android.content.pm.ResolveInfo; -import android.content.res.TypedArray; -import android.content.res.XmlResourceParser; -import android.database.Cursor; -import android.database.MatrixCursor; -import android.os.Build; -import android.provider.SearchIndexablesContract.XmlResource; -import android.provider.SearchIndexablesProvider; -import android.util.Xml; - -import com.android.launcher3.R; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - -import java.io.IOException; - -import static android.provider.SearchIndexablesContract.INDEXABLES_RAW_COLUMNS; -import static android.provider.SearchIndexablesContract.INDEXABLES_XML_RES_COLUMNS; -import static android.provider.SearchIndexablesContract.NON_INDEXABLES_KEYS_COLUMNS; - -@TargetApi(Build.VERSION_CODES.O) -public class LauncherSearchIndexablesProvider extends SearchIndexablesProvider { - @Override - public boolean onCreate() { - return true; - } - - @Override - public Cursor queryXmlResources(String[] strings) { - MatrixCursor cursor = new MatrixCursor(INDEXABLES_XML_RES_COLUMNS); - ResolveInfo settingsActivity = getContext().getPackageManager().resolveActivity( - new Intent(Intent.ACTION_APPLICATION_PREFERENCES) - .setPackage(getContext().getPackageName()), 0); - cursor.newRow() - .add(XmlResource.COLUMN_XML_RESID, R.xml.indexable_launcher_prefs) - .add(XmlResource.COLUMN_INTENT_ACTION, Intent.ACTION_APPLICATION_PREFERENCES) - .add(XmlResource.COLUMN_INTENT_TARGET_PACKAGE, getContext().getPackageName()) - .add(XmlResource.COLUMN_INTENT_TARGET_CLASS, settingsActivity.activityInfo.name); - return cursor; - } - - @Override - public Cursor queryRawData(String[] projection) { - return new MatrixCursor(INDEXABLES_RAW_COLUMNS); - } - - @Override - public Cursor queryNonIndexableKeys(String[] projection) { - MatrixCursor cursor = new MatrixCursor(NON_INDEXABLES_KEYS_COLUMNS); - if (!getContext().getSystemService(LauncherApps.class).hasShortcutHostPermission()) { - // We are not the current launcher. Hide all preferences - try (XmlResourceParser parser = getContext().getResources() - .getXml(R.xml.indexable_launcher_prefs)) { - final int depth = parser.getDepth(); - final int[] attrs = new int[] { android.R.attr.key }; - int type; - while (((type = parser.next()) != XmlPullParser.END_TAG || - parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { - if (type == XmlPullParser.START_TAG) { - TypedArray a = getContext().obtainStyledAttributes( - Xml.asAttributeSet(parser), attrs); - cursor.addRow(new String[] {a.getString(0)}); - a.recycle(); - } - } - } catch (IOException |XmlPullParserException e) { - throw new RuntimeException(e); - } - } - return cursor; - } -} diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java deleted file mode 100644 index 7da92bce90..0000000000 --- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java +++ /dev/null @@ -1,525 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.quickstep; - -import static com.android.launcher3.PagedView.INVALID_PAGE; -import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_3_BUTTON; -import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_KEYBOARD_QUICK_SWITCH; -import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_KEYBOARD_SHORTCUT; -import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; -import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.content.Intent; -import android.graphics.PointF; -import android.os.SystemClock; -import android.os.Trace; -import android.util.Log; -import android.view.View; - -import androidx.annotation.BinderThread; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.UiThread; - -import com.android.internal.jank.Cuj; -import com.android.launcher3.DeviceProfile; -import com.android.launcher3.config.FeatureFlags; -import com.android.launcher3.logger.LauncherAtom; -import com.android.launcher3.logging.StatsLogManager; -import com.android.launcher3.statemanager.StatefulActivity; -import com.android.launcher3.taskbar.TaskbarUIController; -import com.android.launcher3.util.RunnableList; -import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener; -import com.android.quickstep.util.ActiveGestureLog; -import com.android.quickstep.views.RecentsView; -import com.android.quickstep.views.RecentsViewContainer; -import com.android.quickstep.views.TaskView; -import com.android.systemui.shared.recents.model.ThumbnailData; -import com.android.systemui.shared.system.InteractionJankMonitorWrapper; - -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.HashMap; - -/** - * Helper class to handle various atomic commands for switching between Overview. - */ -public class OverviewCommandHelper { - private static final String TAG = "OverviewCommandHelper"; - - public static final int TYPE_SHOW = 1; - public static final int TYPE_KEYBOARD_INPUT = 2; - public static final int TYPE_HIDE = 3; - public static final int TYPE_TOGGLE = 4; - public static final int TYPE_HOME = 5; - - /** - * Use case for needing a queue is double tapping recents button in 3 button nav. - * Size of 2 should be enough. We'll toss in one more because we're kind hearted. - */ - private final static int MAX_QUEUE_SIZE = 3; - - private static final String TRANSITION_NAME = "Transition:toOverview"; - - private final TouchInteractionService mService; - private final OverviewComponentObserver mOverviewComponentObserver; - private final TaskAnimationManager mTaskAnimationManager; - private final ArrayList<CommandInfo> mPendingCommands = new ArrayList<>(); - - /** - * Index of the TaskView that should be focused when launching Overview. Persisted so that we - * do not lose the focus across multiple calls of - * {@link OverviewCommandHelper#executeCommand(CommandInfo)} for the same command - */ - private int mKeyboardTaskFocusIndex = -1; - - /** - * Whether we should incoming toggle commands while a previous toggle command is still ongoing. - * This serves as a rate-limiter to prevent overlapping animations that can clobber each other - * and prevent clean-up callbacks from running. This thus prevents a recurring set of bugs with - * janky recents animations and unresponsive home and overview buttons. - */ - private boolean mWaitForToggleCommandComplete = false; - - public OverviewCommandHelper(TouchInteractionService service, - OverviewComponentObserver observer, - TaskAnimationManager taskAnimationManager) { - mService = service; - mOverviewComponentObserver = observer; - mTaskAnimationManager = taskAnimationManager; - } - - /** - * Called when the command finishes execution. - */ - private void scheduleNextTask(CommandInfo command) { - if (mPendingCommands.isEmpty()) { - Log.d(TAG, "no pending commands to schedule"); - return; - } - if (mPendingCommands.get(0) != command) { - Log.d(TAG, "next task not scheduled." - + " mPendingCommands[0] type is " + mPendingCommands.get(0) - + " - command type is: " + command); - return; - } - Log.d(TAG, "scheduleNextTask called: " + command); - mPendingCommands.remove(0); - executeNext(); - } - - /** - * Executes the next command from the queue. If the command finishes immediately (returns true), - * it continues to execute the next command, until the queue is empty of a command defer's its - * completion (returns false). - */ - @UiThread - private void executeNext() { - if (mPendingCommands.isEmpty()) { - Log.d(TAG, "executeNext - mPendingCommands is empty"); - return; - } - CommandInfo cmd = mPendingCommands.get(0); - - boolean result = executeCommand(cmd); - Log.d(TAG, "executeNext cmd type: " + cmd + ", result: " + result); - if (result) { - scheduleNextTask(cmd); - } - } - - @UiThread - private void addCommand(CommandInfo cmd) { - boolean wasEmpty = mPendingCommands.isEmpty(); - mPendingCommands.add(cmd); - if (wasEmpty) { - executeNext(); - } - } - - /** - * Adds a command to be executed next, after all pending tasks are completed. - * Max commands that can be queued is {@link #MAX_QUEUE_SIZE}. - * Requests after reaching that limit will be silently dropped. - */ - @BinderThread - public void addCommand(int type) { - if (mPendingCommands.size() >= MAX_QUEUE_SIZE) { - Log.d(TAG, "the pending command queue is full (" + mPendingCommands.size() + "). " - + "command not added: " + type); - return; - } - Log.d(TAG, "adding command type: " + type); - CommandInfo cmd = new CommandInfo(type); - MAIN_EXECUTOR.execute(() -> addCommand(cmd)); - } - - @UiThread - public void clearPendingCommands() { - Log.d(TAG, "clearing pending commands - size: " + mPendingCommands.size()); - mPendingCommands.clear(); - } - - @UiThread - public boolean canStartHomeSafely() { - return mPendingCommands.isEmpty() || mPendingCommands.get(0).type == TYPE_HOME; - } - - @Nullable - private TaskView getNextTask(RecentsView view) { - final TaskView runningTaskView = view.getRunningTaskView(); - - if (runningTaskView == null) { - return view.getTaskViewAt(0); - } else { - final TaskView nextTask = view.getNextTaskView(); - return nextTask != null ? nextTask : runningTaskView; - } - } - - private boolean launchTask(RecentsView recents, @Nullable TaskView taskView, CommandInfo cmd) { - RunnableList callbackList = null; - if (taskView != null) { - mWaitForToggleCommandComplete = true; - taskView.setEndQuickSwitchCuj(true); - callbackList = taskView.launchTasks(); - } - - if (callbackList != null) { - callbackList.add(() -> { - Log.d(TAG, "launching task callback: " + cmd); - scheduleNextTask(cmd); - mWaitForToggleCommandComplete = false; - }); - Log.d(TAG, "launching task - waiting for callback: " + cmd); - return false; - } else { - recents.startHome(); - mWaitForToggleCommandComplete = false; - return true; - } - } - - /** - * Executes the task and returns true if next task can be executed. If false, then the next - * task is deferred until {@link #scheduleNextTask} is called - */ - private <T extends StatefulActivity<?> & RecentsViewContainer> boolean executeCommand( - CommandInfo cmd) { - if (mWaitForToggleCommandComplete && cmd.type == TYPE_TOGGLE) { - Log.d(TAG, "executeCommand: " + cmd - + " - waiting for toggle command complete"); - return true; - } - BaseActivityInterface<?, T> activityInterface = - mOverviewComponentObserver.getActivityInterface(); - - RecentsView<?, ?> visibleRecentsView = activityInterface.getVisibleRecentsView(); - RecentsView<?, ?> createdRecentsView; - - Log.d(TAG, "executeCommand: " + cmd - + " - visibleRecentsView: " + visibleRecentsView); - if (visibleRecentsView == null) { - T activity = activityInterface.getCreatedContainer(); - createdRecentsView = activity == null ? null : activity.getOverviewPanel(); - DeviceProfile dp = activity == null ? null : activity.getDeviceProfile(); - TaskbarUIController uiController = activityInterface.getTaskbarController(); - boolean allowQuickSwitch = FeatureFlags.ENABLE_KEYBOARD_QUICK_SWITCH.get() - && uiController != null - && dp != null - && (dp.isTablet || dp.isTwoPanels); - - switch (cmd.type) { - case TYPE_HIDE: - if (!allowQuickSwitch) { - return true; - } - mKeyboardTaskFocusIndex = uiController.launchFocusedTask(); - if (mKeyboardTaskFocusIndex == -1) { - return true; - } - break; - case TYPE_KEYBOARD_INPUT: - if (allowQuickSwitch) { - uiController.openQuickSwitchView(); - return true; - } else { - mKeyboardTaskFocusIndex = 0; - break; - } - case TYPE_HOME: - ActiveGestureLog.INSTANCE.addLog( - "OverviewCommandHelper.executeCommand(TYPE_HOME)"); - mService.startActivity(mOverviewComponentObserver.getHomeIntent()); - return true; - case TYPE_SHOW: - // When Recents is not currently visible, the command's type is TYPE_SHOW - // when overview is triggered via the keyboard overview button or Action+Tab - // keys (Not Alt+Tab which is KQS). The overview button on-screen in 3-button - // nav is TYPE_TOGGLE. - mKeyboardTaskFocusIndex = 0; - break; - default: - // continue below to handle displaying Recents. - } - } else { - createdRecentsView = visibleRecentsView; - switch (cmd.type) { - case TYPE_SHOW: - // already visible - return true; - case TYPE_KEYBOARD_INPUT: { - if (visibleRecentsView.isHandlingTouch()) { - return true; - } - } - case TYPE_HIDE: { - if (visibleRecentsView.isHandlingTouch()) { - return true; - } - mKeyboardTaskFocusIndex = INVALID_PAGE; - int currentPage = visibleRecentsView.getNextPage(); - TaskView tv = (currentPage >= 0 - && currentPage < visibleRecentsView.getTaskViewCount()) - ? (TaskView) visibleRecentsView.getPageAt(currentPage) - : null; - return launchTask(visibleRecentsView, tv, cmd); - } - case TYPE_TOGGLE: - return launchTask(visibleRecentsView, getNextTask(visibleRecentsView), cmd); - case TYPE_HOME: - visibleRecentsView.startHome(); - return true; - } - } - - if (createdRecentsView != null) { - createdRecentsView.setKeyboardTaskFocusIndex(mKeyboardTaskFocusIndex); - } - // Handle recents view focus when launching from home - Animator.AnimatorListener animatorListener = new AnimatorListenerAdapter() { - - @Override - public void onAnimationStart(Animator animation) { - super.onAnimationStart(animation); - updateRecentsViewFocus(cmd); - logShowOverviewFrom(cmd.type); - } - - @Override - public void onAnimationEnd(Animator animation) { - Log.d(TAG, "switching to Overview state - onAnimationEnd: " + cmd); - super.onAnimationEnd(animation); - onRecentsViewFocusUpdated(cmd); - scheduleNextTask(cmd); - } - }; - if (activityInterface.switchToRecentsIfVisible(animatorListener)) { - Log.d(TAG, "switching to Overview state - waiting: " + cmd); - // If successfully switched, wait until animation finishes - return false; - } - - final T activity = activityInterface.getCreatedContainer(); - if (activity != null) { - InteractionJankMonitorWrapper.begin( - activity.getRootView(), - Cuj.CUJ_LAUNCHER_QUICK_SWITCH); - } - - GestureState gestureState = mService.createGestureState(GestureState.DEFAULT_STATE, - GestureState.TrackpadGestureType.NONE); - gestureState.setHandlingAtomicEvent(true); - AbsSwipeUpHandler interactionHandler = mService.getSwipeUpHandlerFactory() - .newHandler(gestureState, cmd.createTime); - interactionHandler.setGestureEndCallback( - () -> onTransitionComplete(cmd, interactionHandler)); - interactionHandler.initWhenReady("OverviewCommandHelper: cmd.type=" + cmd.type); - - RecentsAnimationListener recentAnimListener = new RecentsAnimationListener() { - @Override - public void onRecentsAnimationStart(RecentsAnimationController controller, - RecentsAnimationTargets targets) { - updateRecentsViewFocus(cmd); - logShowOverviewFrom(cmd.type); - activityInterface.runOnInitBackgroundStateUI(() -> - interactionHandler.onGestureEnded(0, new PointF())); - cmd.removeListener(this); - } - - @Override - public void onRecentsAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas) { - interactionHandler.onGestureCancelled(); - cmd.removeListener(this); - - T createdActivity = activityInterface.getCreatedContainer(); - if (createdActivity == null) { - return; - } - if (createdRecentsView != null) { - createdRecentsView.onRecentsAnimationComplete(); - } - } - }; - - if (visibleRecentsView != null) { - visibleRecentsView.moveRunningTaskToFront(); - } - if (mTaskAnimationManager.isRecentsAnimationRunning()) { - cmd.mActiveCallbacks = mTaskAnimationManager.continueRecentsAnimation(gestureState); - cmd.mActiveCallbacks.addListener(interactionHandler); - mTaskAnimationManager.notifyRecentsAnimationState(interactionHandler); - interactionHandler.onGestureStarted(true /*isLikelyToStartNewTask*/); - - cmd.mActiveCallbacks.addListener(recentAnimListener); - mTaskAnimationManager.notifyRecentsAnimationState(recentAnimListener); - } else { - Intent intent = new Intent(interactionHandler.getLaunchIntent()); - intent.putExtra(INTENT_EXTRA_LOG_TRACE_ID, gestureState.getGestureId()); - cmd.mActiveCallbacks = mTaskAnimationManager.startRecentsAnimation( - gestureState, intent, interactionHandler); - interactionHandler.onGestureStarted(false /*isLikelyToStartNewTask*/); - cmd.mActiveCallbacks.addListener(recentAnimListener); - } - Trace.beginAsyncSection(TRANSITION_NAME, 0); - Log.d(TAG, "switching via recents animation - onGestureStarted: " + cmd); - return false; - } - - private void onTransitionComplete(CommandInfo cmd, AbsSwipeUpHandler handler) { - Log.d(TAG, "switching via recents animation - onTransitionComplete: " + cmd); - cmd.removeListener(handler); - Trace.endAsyncSection(TRANSITION_NAME, 0); - onRecentsViewFocusUpdated(cmd); - scheduleNextTask(cmd); - } - - private void updateRecentsViewFocus(CommandInfo cmd) { - RecentsView recentsView = - mOverviewComponentObserver.getActivityInterface().getVisibleRecentsView(); - if (recentsView == null || (cmd.type != TYPE_KEYBOARD_INPUT && cmd.type != TYPE_HIDE - && cmd.type != TYPE_SHOW)) { - return; - } - // When the overview is launched via alt tab (cmd type is TYPE_KEYBOARD_INPUT), - // the touch mode somehow is not change to false by the Android framework. - // The subsequent tab to go through tasks in overview can only be dispatched to - // focuses views, while focus can only be requested in - // {@link View#requestFocusNoSearch(int, Rect)} when touch mode is false. To note, - // here we launch overview with live tile. - recentsView.getViewRootImpl().touchModeChanged(false); - // Ensure that recents view has focus so that it receives the followup key inputs - if (requestFocus(recentsView.getTaskViewAt(mKeyboardTaskFocusIndex))) { - return; - } - if (requestFocus(recentsView.getNextTaskView())) { - return; - } - if (requestFocus(recentsView.getTaskViewAt(0))) { - return; - } - requestFocus(recentsView); - } - - private void onRecentsViewFocusUpdated(CommandInfo cmd) { - RecentsView recentsView = - mOverviewComponentObserver.getActivityInterface().getVisibleRecentsView(); - if (recentsView == null - || cmd.type != TYPE_HIDE - || mKeyboardTaskFocusIndex == INVALID_PAGE) { - return; - } - recentsView.setKeyboardTaskFocusIndex(INVALID_PAGE); - recentsView.setCurrentPage(mKeyboardTaskFocusIndex); - mKeyboardTaskFocusIndex = INVALID_PAGE; - } - - private boolean requestFocus(@Nullable View taskView) { - if (taskView == null) { - return false; - } - taskView.post(() -> { - taskView.requestFocus(); - taskView.requestAccessibilityFocus(); - }); - return true; - } - - private <T extends StatefulActivity<?> & RecentsViewContainer> - void logShowOverviewFrom(int cmdType) { - BaseActivityInterface<?, T> activityInterface = - mOverviewComponentObserver.getActivityInterface(); - var container = activityInterface.getCreatedContainer(); - if (container != null) { - StatsLogManager.LauncherEvent event; - switch (cmdType) { - case TYPE_SHOW -> event = LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_KEYBOARD_SHORTCUT; - case TYPE_HIDE -> - event = LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_KEYBOARD_QUICK_SWITCH; - case TYPE_TOGGLE -> event = LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_3_BUTTON; - default -> { - return; - } - } - - StatsLogManager.newInstance(container.asContext()) - .logger() - .withContainerInfo(LauncherAtom.ContainerInfo.newBuilder() - .setTaskSwitcherContainer( - LauncherAtom.TaskSwitcherContainer.getDefaultInstance()) - .build()) - .log(event); - } - } - - public void dump(PrintWriter pw) { - pw.println("OverviewCommandHelper:"); - pw.println(" mPendingCommands=" + mPendingCommands.size()); - if (!mPendingCommands.isEmpty()) { - pw.println(" pendingCommandType=" + mPendingCommands.get(0).type); - } - pw.println(" mKeyboardTaskFocusIndex=" + mKeyboardTaskFocusIndex); - pw.println(" mWaitForToggleCommandComplete=" + mWaitForToggleCommandComplete); - } - - private static class CommandInfo { - public final long createTime = SystemClock.elapsedRealtime(); - public final int type; - RecentsAnimationCallbacks mActiveCallbacks; - - CommandInfo(int type) { - this.type = type; - } - - void removeListener(RecentsAnimationListener listener) { - if (mActiveCallbacks != null) { - mActiveCallbacks.removeListener(listener); - } - } - - @NonNull - @Override - public String toString() { - return "CommandInfo(" - + "type=" + type + ", " - + "createTime=" + createTime + ", " - + "mActiveCallbacks=" + mActiveCallbacks - + ")"; - } - } -} diff --git a/quickstep/src/com/android/quickstep/TaskIconCache.java b/quickstep/src/com/android/quickstep/TaskIconCache.java deleted file mode 100644 index f45bacb33b..0000000000 --- a/quickstep/src/com/android/quickstep/TaskIconCache.java +++ /dev/null @@ -1,296 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.quickstep; - -import static com.android.launcher3.Flags.enableOverviewIconMenu; -import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY; -import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; - -import android.annotation.Nullable; -import android.app.ActivityManager; -import android.app.ActivityManager.TaskDescription; -import android.content.Context; -import android.content.pm.ActivityInfo; -import android.content.pm.PackageManager; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.os.UserHandle; -import android.text.TextUtils; -import android.util.SparseArray; - -import androidx.annotation.WorkerThread; - -import com.android.launcher3.R; -import com.android.launcher3.Utilities; -import com.android.launcher3.icons.BaseIconFactory; -import com.android.launcher3.icons.BaseIconFactory.IconOptions; -import com.android.launcher3.icons.BitmapInfo; -import com.android.launcher3.icons.IconProvider; -import com.android.launcher3.pm.UserCache; -import com.android.launcher3.util.CancellableTask; -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.FlagOp; -import com.android.launcher3.util.Preconditions; -import com.android.quickstep.util.TaskKeyLruCache; -import com.android.quickstep.util.TaskVisualsChangeListener; -import com.android.systemui.shared.recents.model.Task; -import com.android.systemui.shared.recents.model.Task.TaskKey; -import com.android.systemui.shared.system.PackageManagerWrapper; - -import java.util.concurrent.Executor; -import java.util.function.Consumer; - -/** - * Manages the caching of task icons and related data. - */ -public class TaskIconCache implements DisplayInfoChangeListener { - - private final Executor mBgExecutor; - - private final Context mContext; - private final TaskKeyLruCache<TaskCacheEntry> mIconCache; - private final SparseArray<BitmapInfo> mDefaultIcons = new SparseArray<>(); - private BitmapInfo mDefaultIconBase = null; - - private final IconProvider mIconProvider; - - private BaseIconFactory mIconFactory; - - @Nullable - public TaskVisualsChangeListener mTaskVisualsChangeListener = null; - - public TaskIconCache(Context context, Executor bgExecutor, IconProvider iconProvider) { - mContext = context; - mBgExecutor = bgExecutor; - mIconProvider = iconProvider; - - Resources res = context.getResources(); - int cacheSize = res.getInteger(R.integer.recentsIconCacheSize); - - mIconCache = new TaskKeyLruCache<>(cacheSize); - - DisplayController.INSTANCE.get(mContext).addChangeListener(this); - } - - @Override - public void onDisplayInfoChanged(Context context, Info info, int flags) { - if ((flags & CHANGE_DENSITY) != 0) { - clearCache(); - } - } - - /** - * Asynchronously fetches the icon and other task data. - * - * @param task The task to fetch the data for - * @param callback The callback to receive the task after its data has been populated. - * @return A cancelable handle to the request - */ - public CancellableTask updateIconInBackground(Task task, Consumer<Task> callback) { - Preconditions.assertUIThread(); - if (task.icon != null) { - // Nothing to load, the icon is already loaded - callback.accept(task); - return null; - } - CancellableTask<TaskCacheEntry> request = new CancellableTask<>( - () -> getCacheEntry(task), - MAIN_EXECUTOR, - result -> { - task.icon = result.icon; - task.titleDescription = result.contentDescription; - task.title = result.title; - callback.accept(task); - dispatchIconUpdate(task.key.id); - } - ); - mBgExecutor.execute(request); - return request; - } - - /** - * Clears the icon cache - */ - public void clearCache() { - mBgExecutor.execute(this::resetFactory); - } - - void onTaskRemoved(TaskKey taskKey) { - mIconCache.remove(taskKey); - } - - void invalidateCacheEntries(String pkg, UserHandle handle) { - mBgExecutor.execute(() -> mIconCache.removeAll(key -> - pkg.equals(key.getPackageName()) && handle.getIdentifier() == key.userId)); - } - - @WorkerThread - private TaskCacheEntry getCacheEntry(Task task) { - TaskCacheEntry entry = mIconCache.getAndInvalidateIfModified(task.key); - if (entry != null) { - return entry; - } - - TaskDescription desc = task.taskDescription; - TaskKey key = task.key; - ActivityInfo activityInfo = null; - - // Create new cache entry - entry = new TaskCacheEntry(); - - // Load icon - // TODO: Load icon resource (b/143363444) - Bitmap icon = getIcon(desc, key.userId); - if (icon != null) { - entry.icon = getBitmapInfo( - new BitmapDrawable(mContext.getResources(), icon), - key.userId, - desc.getPrimaryColor(), - false /* isInstantApp */).newIcon(mContext); - } else { - activityInfo = PackageManagerWrapper.getInstance().getActivityInfo( - key.getComponent(), key.userId); - if (activityInfo != null) { - BitmapInfo bitmapInfo = getBitmapInfo( - mIconProvider.getIcon(activityInfo), - key.userId, - desc.getPrimaryColor(), - activityInfo.applicationInfo.isInstantApp()); - entry.icon = bitmapInfo.newIcon(mContext); - } else { - entry.icon = getDefaultIcon(key.userId); - } - } - - // Skip loading the content description if the activity no longer exists - if (activityInfo == null) { - activityInfo = PackageManagerWrapper.getInstance().getActivityInfo( - key.getComponent(), key.userId); - } - if (activityInfo != null) { - entry.contentDescription = getBadgedContentDescription( - activityInfo, task.key.userId, task.taskDescription); - if (enableOverviewIconMenu()) { - entry.title = Utilities.trim(activityInfo.loadLabel(mContext.getPackageManager())); - } - } - - mIconCache.put(task.key, entry); - return entry; - } - - private Bitmap getIcon(ActivityManager.TaskDescription desc, int userId) { - if (desc.getInMemoryIcon() != null) { - return desc.getInMemoryIcon(); - } - return ActivityManager.TaskDescription.loadTaskDescriptionIcon( - desc.getIconFilename(), userId); - } - - private String getBadgedContentDescription(ActivityInfo info, int userId, TaskDescription td) { - PackageManager pm = mContext.getPackageManager(); - String taskLabel = td == null ? null : Utilities.trim(td.getLabel()); - if (TextUtils.isEmpty(taskLabel)) { - taskLabel = Utilities.trim(info.loadLabel(pm)); - } - - String applicationLabel = Utilities.trim(info.applicationInfo.loadLabel(pm)); - String badgedApplicationLabel = userId != UserHandle.myUserId() - ? pm.getUserBadgedLabel(applicationLabel, UserHandle.of(userId)).toString() - : applicationLabel; - return applicationLabel.equals(taskLabel) - ? badgedApplicationLabel : badgedApplicationLabel + " " + taskLabel; - } - - @WorkerThread - private Drawable getDefaultIcon(int userId) { - synchronized (mDefaultIcons) { - if (mDefaultIconBase == null) { - try (BaseIconFactory bif = getIconFactory()) { - mDefaultIconBase = bif.makeDefaultIcon(); - } - } - - int index; - if ((index = mDefaultIcons.indexOfKey(userId)) >= 0) { - return mDefaultIcons.valueAt(index).newIcon(mContext); - } else { - BitmapInfo info = mDefaultIconBase.withFlags( - UserCache.INSTANCE.get(mContext).getUserInfo(UserHandle.of(userId)) - .applyBitmapInfoFlags(FlagOp.NO_OP)); - mDefaultIcons.put(userId, info); - return info.newIcon(mContext); - } - } - } - - @WorkerThread - private BitmapInfo getBitmapInfo(Drawable drawable, int userId, - int primaryColor, boolean isInstantApp) { - try (BaseIconFactory bif = getIconFactory()) { - bif.setWrapperBackgroundColor(primaryColor); - - // User version code O, so that the icon is always wrapped in an adaptive icon container - return bif.createBadgedIconBitmap(drawable, - new IconOptions() - .setUser(UserCache.INSTANCE.get(mContext) - .getUserInfo(UserHandle.of(userId))) - .setInstantApp(isInstantApp) - .setExtractedColor(0)); - } - } - - @WorkerThread - private BaseIconFactory getIconFactory() { - if (mIconFactory == null) { - mIconFactory = new BaseIconFactory(mContext, - DisplayController.INSTANCE.get(mContext).getInfo().getDensityDpi(), - mContext.getResources().getDimensionPixelSize( - R.dimen.task_icon_cache_default_icon_size)); - } - return mIconFactory; - } - - @WorkerThread - private void resetFactory() { - mIconFactory = null; - mIconCache.evictAll(); - } - - private static class TaskCacheEntry { - public Drawable icon; - public String contentDescription = ""; - public String title = ""; - } - - void registerTaskVisualsChangeListener(TaskVisualsChangeListener newListener) { - mTaskVisualsChangeListener = newListener; - } - - void removeTaskVisualsChangeListener() { - mTaskVisualsChangeListener = null; - } - - void dispatchIconUpdate(int taskId) { - if (mTaskVisualsChangeListener != null) { - mTaskVisualsChangeListener.onTaskIconChanged(taskId); - } - } -} \ No newline at end of file diff --git a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java deleted file mode 100644 index 38e927f95f..0000000000 --- a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java +++ /dev/null @@ -1,279 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.quickstep; - -import static com.android.launcher3.Flags.enableGridOnlyOverview; -import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; - -import android.content.Context; -import android.content.res.Resources; - -import androidx.annotation.NonNull; -import androidx.annotation.VisibleForTesting; - -import com.android.launcher3.R; -import com.android.launcher3.util.CancellableTask; -import com.android.launcher3.util.Preconditions; -import com.android.quickstep.task.thumbnail.data.TaskThumbnailDataSource; -import com.android.quickstep.util.TaskKeyByLastActiveTimeCache; -import com.android.quickstep.util.TaskKeyCache; -import com.android.quickstep.util.TaskKeyLruCache; -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 java.util.ArrayList; -import java.util.concurrent.Executor; -import java.util.function.Consumer; - -public class TaskThumbnailCache implements TaskThumbnailDataSource { - - private final Executor mBgExecutor; - private final TaskKeyCache<ThumbnailData> mCache; - private final HighResLoadingState mHighResLoadingState; - private final boolean mEnableTaskSnapshotPreloading; - private final Context mContext; - - public static class HighResLoadingState { - private boolean mForceHighResThumbnails; - private boolean mVisible; - private boolean mFlingingFast; - private boolean mHighResLoadingEnabled; - private ArrayList<HighResLoadingStateChangedCallback> mCallbacks = new ArrayList<>(); - - public interface HighResLoadingStateChangedCallback { - void onHighResLoadingStateChanged(boolean enabled); - } - - private HighResLoadingState(Context context) { - // If the device does not support low-res thumbnails, only attempt to load high-res - // thumbnails - mForceHighResThumbnails = !supportsLowResThumbnails(); - } - - public void addCallback(HighResLoadingStateChangedCallback callback) { - mCallbacks.add(callback); - } - - public void removeCallback(HighResLoadingStateChangedCallback callback) { - mCallbacks.remove(callback); - } - - public void setVisible(boolean visible) { - mVisible = visible; - updateState(); - } - - public void setFlingingFast(boolean flingingFast) { - mFlingingFast = flingingFast; - updateState(); - } - - public boolean isEnabled() { - return mHighResLoadingEnabled; - } - - private void updateState() { - boolean prevState = mHighResLoadingEnabled; - mHighResLoadingEnabled = mForceHighResThumbnails || (mVisible && !mFlingingFast); - if (prevState != mHighResLoadingEnabled) { - for (int i = mCallbacks.size() - 1; i >= 0; i--) { - mCallbacks.get(i).onHighResLoadingStateChanged(mHighResLoadingEnabled); - } - } - } - } - - public TaskThumbnailCache(Context context, Executor bgExecutor) { - this(context, bgExecutor, - context.getResources().getInteger(R.integer.recentsThumbnailCacheSize)); - } - - private TaskThumbnailCache(Context context, Executor bgExecutor, int cacheSize) { - this(context, bgExecutor, - enableGridOnlyOverview() ? new TaskKeyByLastActiveTimeCache<>(cacheSize) - : new TaskKeyLruCache<>(cacheSize)); - } - - @VisibleForTesting - TaskThumbnailCache(Context context, Executor bgExecutor, TaskKeyCache<ThumbnailData> cache) { - mBgExecutor = bgExecutor; - mHighResLoadingState = new HighResLoadingState(context); - mContext = context; - - Resources res = context.getResources(); - mEnableTaskSnapshotPreloading = res.getBoolean(R.bool.config_enableTaskSnapshotPreloading); - mCache = cache; - } - - /** - * Synchronously fetches the thumbnail for the given task at the specified resolution level, and - * puts it in the cache. - */ - public void updateThumbnailInCache(Task task, boolean lowResolution) { - if (task == null) { - return; - } - Preconditions.assertUIThread(); - // Fetch the thumbnail for this task and put it in the cache - if (task.thumbnail == null) { - updateThumbnailInBackground(task.key, lowResolution, - t -> task.thumbnail = t); - } - } - - /** - * Synchronously updates the thumbnail in the cache if it is already there. - */ - public void updateTaskSnapShot(int taskId, ThumbnailData thumbnail) { - Preconditions.assertUIThread(); - mCache.updateIfAlreadyInCache(taskId, thumbnail); - } - - /** - * Asynchronously fetches the icon and other task data for the given {@param task}. - * - * @param callback The callback to receive the task after its data has been populated. - * @return A cancelable handle to the request - */ - @Override - public CancellableTask<ThumbnailData> updateThumbnailInBackground( - Task task, @NonNull Consumer<ThumbnailData> callback) { - Preconditions.assertUIThread(); - - boolean lowResolution = !mHighResLoadingState.isEnabled(); - if (task.thumbnail != null && task.thumbnail.getThumbnail() != null - && (!task.thumbnail.reducedResolution || lowResolution)) { - // Nothing to load, the thumbnail is already high-resolution or matches what the - // request, so just callback - callback.accept(task.thumbnail); - return null; - } - - return updateThumbnailInBackground(task.key, !mHighResLoadingState.isEnabled(), t -> { - task.thumbnail = t; - callback.accept(t); - }); - } - - /** - * Updates cache size and remove excess entries if current size is more than new cache size. - * - * @return whether cache size has increased - */ - public boolean updateCacheSizeAndRemoveExcess() { - int newSize = mContext.getResources().getInteger(R.integer.recentsThumbnailCacheSize); - int oldSize = mCache.getMaxSize(); - if (newSize == oldSize) { - // Return if no change in size - return false; - } - - mCache.updateCacheSizeAndRemoveExcess(newSize); - return newSize > oldSize; - } - - private CancellableTask<ThumbnailData> updateThumbnailInBackground(TaskKey key, - boolean lowResolution, Consumer<ThumbnailData> callback) { - Preconditions.assertUIThread(); - - ThumbnailData cachedThumbnail = mCache.getAndInvalidateIfModified(key); - if (cachedThumbnail != null && cachedThumbnail.getThumbnail() != null - && (!cachedThumbnail.reducedResolution || lowResolution)) { - // Already cached, lets use that thumbnail - callback.accept(cachedThumbnail); - return null; - } - - CancellableTask<ThumbnailData> request = new CancellableTask<>( - () -> { - ThumbnailData thumbnailData = ActivityManagerWrapper.getInstance() - .getTaskThumbnail(key.id, lowResolution); - return thumbnailData.getThumbnail() != null ? thumbnailData - : ActivityManagerWrapper.getInstance().takeTaskThumbnail(key.id); - }, - MAIN_EXECUTOR, - result -> { - // Avoid an async timing issue that a low res entry replaces an existing high - // res entry in high res enabled state, so we check before putting it to cache - if (enableGridOnlyOverview() && result.reducedResolution - && getHighResLoadingState().isEnabled()) { - ThumbnailData newCachedThumbnail = mCache.getAndInvalidateIfModified(key); - if (newCachedThumbnail != null && newCachedThumbnail.getThumbnail() != null - && !newCachedThumbnail.reducedResolution) { - return; - } - } - mCache.put(key, result); - callback.accept(result); - } - ); - mBgExecutor.execute(request); - return request; - } - - /** - * Clears the cache. - */ - public void clear() { - mCache.evictAll(); - } - - /** - * Removes the cached thumbnail for the given task. - */ - public void remove(Task.TaskKey key) { - mCache.remove(key); - } - - /** - * @return The cache size. - */ - public int getCacheSize() { - return mCache.getMaxSize(); - } - - /** - * @return The mutable high-res loading state. - */ - public HighResLoadingState getHighResLoadingState() { - return mHighResLoadingState; - } - - /** - * @return Whether to enable background preloading of task thumbnails. - */ - public boolean isPreloadingEnabled() { - return mEnableTaskSnapshotPreloading && mHighResLoadingState.mVisible; - } - - /** - * @return Whether device supports low-res thumbnails. Low-res files are an optimization - * for faster load times of snapshots. Devices can optionally disable low-res files so that - * they only store snapshots at high-res scale. The actual scale can be configured in - * frameworks/base config overlay. - */ - private static boolean supportsLowResThumbnails() { - Resources res = Resources.getSystem(); - int resId = res.getIdentifier("config_lowResTaskSnapshotScale", "dimen", "android"); - if (resId != 0) { - return 0 < res.getFloat(resId); - } - return true; - } - -} diff --git a/quickstep/src/com/android/quickstep/fallback/RecentsTaskController.java b/quickstep/src/com/android/quickstep/fallback/RecentsTaskController.java deleted file mode 100644 index 2cb398cfff..0000000000 --- a/quickstep/src/com/android/quickstep/fallback/RecentsTaskController.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.quickstep.fallback; - -import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController; -import com.android.quickstep.RecentsActivity; - -public class RecentsTaskController extends TaskViewTouchController<RecentsActivity> { - - public RecentsTaskController(RecentsActivity activity) { - super(activity); - } - - @Override - protected boolean isRecentsInteractive() { - return mContainer.hasWindowFocus() || mContainer.getStateManager().getState().hasLiveTile(); - } - - @Override - protected boolean isRecentsModal() { - return false; - } -} diff --git a/quickstep/src/com/android/quickstep/fallback/window/RecentsDisplayModel.kt b/quickstep/src/com/android/quickstep/fallback/window/RecentsDisplayModel.kt deleted file mode 100644 index 5b88686cb8..0000000000 --- a/quickstep/src/com/android/quickstep/fallback/window/RecentsDisplayModel.kt +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.quickstep.fallback.window - -import android.content.Context -import android.util.Log -import android.view.Display -import android.view.Display.DEFAULT_DISPLAY -import androidx.core.util.valueIterator -import com.android.launcher3.dagger.ApplicationContext -import com.android.launcher3.dagger.LauncherAppSingleton -import com.android.launcher3.util.DaggerSingletonObject -import com.android.launcher3.util.DaggerSingletonTracker -import com.android.launcher3.util.WallpaperColorHints -import com.android.quickstep.DisplayModel -import com.android.quickstep.FallbackWindowInterface -import com.android.quickstep.RecentsAnimationDeviceState -import com.android.quickstep.TaskAnimationManager -import com.android.quickstep.dagger.QuickstepBaseAppComponent -import com.android.quickstep.fallback.window.RecentsDisplayModel.RecentsDisplayResource -import com.android.quickstep.fallback.window.RecentsWindowFlags.Companion.enableOverviewInWindow -import java.io.PrintWriter -import javax.inject.Inject - -@LauncherAppSingleton -class RecentsDisplayModel -@Inject -constructor( - @ApplicationContext context: Context, - private val wallpaperColorHints: WallpaperColorHints, - tracker: DaggerSingletonTracker, -) : DisplayModel<RecentsDisplayResource>(context) { - - companion object { - private const val TAG = "RecentsDisplayModel" - private const val DEBUG = false - - @JvmStatic - val INSTANCE: DaggerSingletonObject<RecentsDisplayModel> = - DaggerSingletonObject<RecentsDisplayModel>( - QuickstepBaseAppComponent::getRecentsDisplayModel - ) - } - - init { - if (enableOverviewInWindow) { - initializeDisplays() - } else { - // Always create resource for default display - storeDisplayResource(DEFAULT_DISPLAY) - } - tracker.addCloseable { destroy() } - } - - override fun createDisplayResource(display: Display): RecentsDisplayResource { - return RecentsDisplayResource( - display.displayId, - context.createDisplayContext(display), - wallpaperColorHints.hints, - ) - } - - fun getRecentsWindowManager(displayId: Int): RecentsWindowManager? { - if (DEBUG) Log.d(TAG, "getRecentsWindowManager for display $displayId") - return getDisplayResource(displayId)?.recentsWindowManager - } - - fun getFallbackWindowInterface(displayId: Int): FallbackWindowInterface? { - if (DEBUG) Log.d(TAG, "getFallbackWindowInterface for display $displayId") - return getDisplayResource(displayId)?.fallbackWindowInterface - } - - fun getTaskAnimationManager(displayId: Int): TaskAnimationManager? { - return getDisplayResource(displayId)?.taskAnimationManager - } - - val activeDisplayResources: Iterable<RecentsDisplayResource> - get() = - object : Iterable<RecentsDisplayResource> { - override fun iterator() = displayResourceArray.valueIterator() - } - - data class RecentsDisplayResource( - val displayId: Int, - val displayContext: Context, - val wallpaperColorHints: Int, - ) : DisplayResource() { - val recentsWindowManager = - if (enableOverviewInWindow) RecentsWindowManager(displayContext, wallpaperColorHints) - else null - val fallbackWindowInterface = - if (enableOverviewInWindow) FallbackWindowInterface(recentsWindowManager) else null - val taskAnimationManager = - TaskAnimationManager( - displayContext, - RecentsAnimationDeviceState.INSTANCE.get(displayContext), - displayId, - ) - - override fun cleanup() { - recentsWindowManager?.destroy() - } - - override fun dump(prefix: String, writer: PrintWriter) { - writer.println("${prefix}RecentsDisplayResource:") - - writer.println("${prefix}\tdisplayId=${displayId}") - writer.println("${prefix}\tdisplayContext=${displayContext}") - writer.println("${prefix}\twallpaperColorHints=${wallpaperColorHints}") - writer.println("${prefix}\trecentsWindowManager=${recentsWindowManager}") - writer.println("${prefix}\tfallbackWindowInterface=${fallbackWindowInterface}") - } - } -} diff --git a/quickstep/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java deleted file mode 100644 index 349f4d2f2f..0000000000 --- a/quickstep/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.quickstep.inputconsumers; - -import android.view.MotionEvent; - -import com.android.launcher3.taskbar.TaskbarActivityContext; -import com.android.quickstep.InputConsumer; -import com.android.quickstep.TaskAnimationManager; - -import java.util.function.Supplier; - -/** - * A NO_OP input consumer which also resets any pending gesture - */ -public class ResetGestureInputConsumer implements InputConsumer { - - private final TaskAnimationManager mTaskAnimationManager; - private final Supplier<TaskbarActivityContext> mActivityContextSupplier; - - public ResetGestureInputConsumer( - TaskAnimationManager taskAnimationManager, - Supplier<TaskbarActivityContext> activityContextSupplier) { - mTaskAnimationManager = taskAnimationManager; - mActivityContextSupplier = activityContextSupplier; - } - - @Override - public int getType() { - return TYPE_RESET_GESTURE; - } - - @Override - public void onMotionEvent(MotionEvent ev) { - if (ev.getAction() == MotionEvent.ACTION_DOWN - && mTaskAnimationManager.isRecentsAnimationRunning()) { - TaskbarActivityContext tac = mActivityContextSupplier.get(); - mTaskAnimationManager.finishRunningRecentsAnimation( - /* toHome= */ tac != null && !tac.isInApp()); - } - } -} diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialStepIndicator.java b/quickstep/src/com/android/quickstep/interaction/TutorialStepIndicator.java deleted file mode 100644 index ae0e725aad..0000000000 --- a/quickstep/src/com/android/quickstep/interaction/TutorialStepIndicator.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.quickstep.interaction; - -import android.content.Context; -import android.graphics.drawable.Drawable; -import android.util.AttributeSet; -import android.util.Log; -import android.widget.ImageView; -import android.widget.LinearLayout; - -import androidx.appcompat.content.res.AppCompatResources; - -import com.android.launcher3.R; -import com.android.launcher3.Utilities; -import com.android.launcher3.icons.GraphicsUtils; - -/** Indicator displaying the current progress through the gesture navigation tutorial. */ -public class TutorialStepIndicator extends LinearLayout { - - private static final String LOG_TAG = "TutorialStepIndicator"; - - private int mCurrentStep = -1; - private int mTotalSteps = -1; - - public TutorialStepIndicator(Context context) { - super(context); - } - - public TutorialStepIndicator(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public TutorialStepIndicator(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - - public TutorialStepIndicator(Context context, AttributeSet attrs, int defStyleAttr, - int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - } - - /** - * Updates this indicator to display totalSteps indicator pills, with the first currentStep - * pills highlighted. - */ - public void setTutorialProgress(int currentStep, int totalSteps) { - if (currentStep <= 0) { - Log.w(LOG_TAG, "Current step number invalid: " + currentStep + ". Assuming step 1."); - currentStep = 1; - } - if (totalSteps <= 0) { - Log.w(LOG_TAG, "Total number of steps invalid: " + totalSteps + ". Assuming 1 step."); - totalSteps = 1; - } - if (currentStep > totalSteps) { - Log.w(LOG_TAG, "Current step number greater than the total number of steps. Assuming" - + " final step."); - currentStep = totalSteps; - } - if (totalSteps < 2) { - setVisibility(GONE); - return; - } - setVisibility(VISIBLE); - mCurrentStep = currentStep; - mTotalSteps = totalSteps; - - initializeStepIndicators(); - } - - private void initializeStepIndicators() { - for (int i = mTotalSteps; i < getChildCount(); i++) { - removeViewAt(i); - } - int activeStepIndicatorColor = GraphicsUtils.getAttrColor( - getContext(), android.R.attr.textColorPrimary); - int inactiveStepIndicatorColor = GraphicsUtils.getAttrColor( - getContext(), android.R.attr.textColorSecondaryInverse); - for (int i = 0; i < mTotalSteps; i++) { - Drawable pageIndicatorPillDrawable = AppCompatResources.getDrawable( - getContext(), R.drawable.tutorial_step_indicator_pill); - - if (i >= getChildCount()) { - ImageView pageIndicatorPill = new ImageView(getContext()); - pageIndicatorPill.setImageDrawable(pageIndicatorPillDrawable); - - LinearLayout.LayoutParams lp = new LayoutParams( - LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); - - lp.setMarginStart(Utilities.dpToPx(3)); - lp.setMarginEnd(Utilities.dpToPx(3)); - - addView(pageIndicatorPill, lp); - } - if (pageIndicatorPillDrawable != null) { - if (i < mCurrentStep) { - pageIndicatorPillDrawable.setTint(activeStepIndicatorColor); - } else { - pageIndicatorPillDrawable.setTint(inactiveStepIndicatorColor); - } - } - } - } -} diff --git a/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java b/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java deleted file mode 100644 index eeacee1cf6..0000000000 --- a/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java +++ /dev/null @@ -1,820 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.quickstep.orientation; - -import static android.view.Gravity.BOTTOM; -import static android.view.Gravity.CENTER_HORIZONTAL; -import static android.view.Gravity.END; -import static android.view.Gravity.START; -import static android.view.Gravity.TOP; -import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; -import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; - -import static com.android.launcher3.Flags.enableOverviewIconMenu; -import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X; -import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y; -import static com.android.launcher3.touch.SingleAxisSwipeDetector.VERTICAL; -import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT; -import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT; -import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_MAIN; - -import android.graphics.Matrix; -import android.graphics.Point; -import android.graphics.PointF; -import android.graphics.Rect; -import android.graphics.RectF; -import android.graphics.drawable.ShapeDrawable; -import android.util.FloatProperty; -import android.util.Pair; -import android.view.Gravity; -import android.view.Surface; -import android.view.View; -import android.widget.FrameLayout; -import android.widget.LinearLayout; - -import androidx.annotation.NonNull; - -import com.android.launcher3.DeviceProfile; -import com.android.launcher3.R; -import com.android.launcher3.Utilities; -import com.android.launcher3.logger.LauncherAtom; -import com.android.launcher3.touch.DefaultPagedViewHandler; -import com.android.launcher3.touch.SingleAxisSwipeDetector; -import com.android.launcher3.util.SplitConfigurationOptions; -import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds; -import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption; -import com.android.launcher3.util.SplitConfigurationOptions.StagePosition; -import com.android.quickstep.views.IconAppChipView; - -import java.util.ArrayList; -import java.util.List; - -public class PortraitPagedViewHandler extends DefaultPagedViewHandler implements - RecentsPagedOrientationHandler { - - private final Matrix mTmpMatrix = new Matrix(); - private final RectF mTmpRectF = new RectF(); - - @Override - public <T> T getPrimaryValue(T x, T y) { - return x; - } - - @Override - public <T> T getSecondaryValue(T x, T y) { - return y; - } - - @Override - public boolean isLayoutNaturalToLauncher() { - return true; - } - - @Override - public void adjustFloatingIconStartVelocity(PointF velocity) { - //no-op - } - - @Override - public void fixBoundsForHomeAnimStartRect(RectF outStartRect, DeviceProfile deviceProfile) { - if (outStartRect.left > deviceProfile.widthPx) { - outStartRect.offsetTo(0, outStartRect.top); - } else if (outStartRect.left < -deviceProfile.widthPx) { - outStartRect.offsetTo(0, outStartRect.top); - } - } - - @Override - public <T> void setSecondary(T target, Float2DAction<T> action, float param) { - action.call(target, 0, param); - } - - @Override - public <T> void set(T target, Int2DAction<T> action, int primaryParam, - int secondaryParam) { - action.call(target, primaryParam, secondaryParam); - } - - @Override - public int getPrimarySize(View view) { - return view.getWidth(); - } - - @Override - public float getPrimarySize(RectF rect) { - return rect.width(); - } - - @Override - public float getStart(RectF rect) { - return rect.left; - } - - @Override - public float getEnd(RectF rect) { - return rect.right; - } - - @Override - public void rotateInsets(@NonNull Rect insets, @NonNull Rect outInsets) { - outInsets.set(insets); - } - - @Override - public int getClearAllSidePadding(View view, boolean isRtl) { - return (isRtl ? view.getPaddingRight() : - view.getPaddingLeft()) / 2; - } - - @Override - public int getSecondaryDimension(View view) { - return view.getHeight(); - } - - @Override - public FloatProperty<View> getPrimaryViewTranslate() { - return VIEW_TRANSLATE_X; - } - - @Override - public FloatProperty<View> getSecondaryViewTranslate() { - return VIEW_TRANSLATE_Y; - } - - @Override - public float getDegreesRotated() { - return 0; - } - - @Override - public int getRotation() { - return Surface.ROTATION_0; - } - - @Override - public void setPrimaryScale(View view, float scale) { - view.setScaleX(scale); - } - - @Override - public void setSecondaryScale(View view, float scale) { - view.setScaleY(scale); - } - - public int getSecondaryTranslationDirectionFactor() { - return -1; - } - - @Override - public int getSplitTranslationDirectionFactor(int stagePosition, DeviceProfile deviceProfile) { - if (deviceProfile.isLeftRightSplit && stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) { - return -1; - } else { - return 1; - } - } - - @Override - public float getTaskMenuX(float x, View thumbnailView, - DeviceProfile deviceProfile, float taskInsetMargin, View taskViewIcon) { - if (deviceProfile.isLandscape) { - return x + taskInsetMargin - + (thumbnailView.getMeasuredWidth() - thumbnailView.getMeasuredHeight()) / 2f; - } else { - return x + taskInsetMargin; - } - } - - @Override - public float getTaskMenuY(float y, View thumbnailView, int stagePosition, - View taskMenuView, float taskInsetMargin, View taskViewIcon) { - return y + taskInsetMargin; - } - - @Override - public int getTaskMenuWidth(View thumbnailView, DeviceProfile deviceProfile, - @StagePosition int stagePosition) { - if (enableOverviewIconMenu()) { - return thumbnailView.getResources().getDimensionPixelSize( - R.dimen.task_thumbnail_icon_menu_expanded_width); - } - int padding = thumbnailView.getResources() - .getDimensionPixelSize(R.dimen.task_menu_edge_padding); - return (deviceProfile.isLandscape && !deviceProfile.isTablet - ? thumbnailView.getMeasuredHeight() - : thumbnailView.getMeasuredWidth()) - (2 * padding); - } - - @Override - public int getTaskMenuHeight(float taskInsetMargin, DeviceProfile deviceProfile, - float taskMenuX, float taskMenuY) { - return (int) (deviceProfile.heightPx - deviceProfile.getInsets().top - taskMenuY - - deviceProfile.getOverviewActionsClaimedSpaceBelow()); - } - - @Override - public void setTaskOptionsMenuLayoutOrientation(DeviceProfile deviceProfile, - LinearLayout taskMenuLayout, int dividerSpacing, - ShapeDrawable dividerDrawable) { - taskMenuLayout.setOrientation(LinearLayout.VERTICAL); - dividerDrawable.setIntrinsicHeight(dividerSpacing); - taskMenuLayout.setDividerDrawable(dividerDrawable); - } - - @Override - public void setLayoutParamsForTaskMenuOptionItem(LinearLayout.LayoutParams lp, - LinearLayout viewGroup, DeviceProfile deviceProfile) { - viewGroup.setOrientation(LinearLayout.HORIZONTAL); - lp.width = LinearLayout.LayoutParams.MATCH_PARENT; - lp.height = WRAP_CONTENT; - } - - @Override - public Pair<Float, Float> getDwbLayoutTranslations(int taskViewWidth, - int taskViewHeight, SplitBounds splitBounds, DeviceProfile deviceProfile, - View[] thumbnailViews, int desiredTaskId, View banner) { - float translationX = 0; - float translationY = 0; - FrameLayout.LayoutParams bannerParams = (FrameLayout.LayoutParams) banner.getLayoutParams(); - banner.setPivotX(0); - banner.setPivotY(0); - banner.setRotation(getDegreesRotated()); - if (splitBounds == null) { - // Single, fullscreen case - bannerParams.width = MATCH_PARENT; - bannerParams.gravity = BOTTOM | CENTER_HORIZONTAL; - return new Pair<>(translationX, translationY); - } - - bannerParams.gravity = - BOTTOM | (deviceProfile.isLeftRightSplit ? START : CENTER_HORIZONTAL); - - // Set correct width - if (desiredTaskId == splitBounds.leftTopTaskId) { - bannerParams.width = thumbnailViews[0].getMeasuredWidth(); - } else { - bannerParams.width = thumbnailViews[1].getMeasuredWidth(); - } - - // Set translations - if (deviceProfile.isLeftRightSplit) { - if (desiredTaskId == splitBounds.rightBottomTaskId) { - float leftTopTaskPercent = splitBounds.appsStackedVertically - ? splitBounds.topTaskPercent - : splitBounds.leftTaskPercent; - float dividerThicknessPercent = splitBounds.appsStackedVertically - ? splitBounds.dividerHeightPercent - : splitBounds.dividerWidthPercent; - translationX = ((taskViewWidth * leftTopTaskPercent) - + (taskViewWidth * dividerThicknessPercent)); - } - } else { - if (desiredTaskId == splitBounds.leftTopTaskId) { - FrameLayout.LayoutParams snapshotParams = - (FrameLayout.LayoutParams) thumbnailViews[0] - .getLayoutParams(); - float bottomRightTaskPlusDividerPercent = splitBounds.appsStackedVertically - ? (1f - splitBounds.topTaskPercent) - : (1f - splitBounds.leftTaskPercent); - translationY = -((taskViewHeight - snapshotParams.topMargin) - * bottomRightTaskPlusDividerPercent); - } - } - return new Pair<>(translationX, translationY); - } - - /* ---------- The following are only used by TaskViewTouchHandler. ---------- */ - - @Override - public SingleAxisSwipeDetector.Direction getUpDownSwipeDirection() { - return VERTICAL; - } - - @Override - public int getUpDirection(boolean isRtl) { - // Ignore rtl since it only affects X value displacement, Y displacement doesn't change - return SingleAxisSwipeDetector.DIRECTION_POSITIVE; - } - - @Override - public boolean isGoingUp(float displacement, boolean isRtl) { - // Ignore rtl since it only affects X value displacement, Y displacement doesn't change - return displacement < 0; - } - - @Override - public int getTaskDragDisplacementFactor(boolean isRtl) { - // Ignore rtl since it only affects X value displacement, Y displacement doesn't change - return 1; - } - - /* -------------------- */ - @Override - public int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect) { - return dp.heightPx - rect.bottom; - } - - @Override - public List<SplitPositionOption> getSplitPositionOptions(DeviceProfile dp) { - if (dp.isTablet) { - return Utilities.getSplitPositionOptions(dp); - } - - List<SplitPositionOption> options = new ArrayList<>(); - if (dp.isSeascape()) { - options.add(new SplitPositionOption( - R.drawable.ic_split_horizontal, R.string.recent_task_option_split_screen, - STAGE_POSITION_BOTTOM_OR_RIGHT, STAGE_TYPE_MAIN)); - } else if (dp.isLeftRightSplit) { - options.add(new SplitPositionOption( - R.drawable.ic_split_horizontal, R.string.recent_task_option_split_screen, - STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN)); - } else { - // Only add top option - options.add(new SplitPositionOption( - R.drawable.ic_split_vertical, R.string.recent_task_option_split_screen, - STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN)); - } - return options; - } - - @Override - public void getInitialSplitPlaceholderBounds(int placeholderHeight, int placeholderInset, - DeviceProfile dp, @StagePosition int stagePosition, Rect out) { - int screenWidth = dp.widthPx; - int screenHeight = dp.heightPx; - boolean pinToRight = stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT; - int insetSizeAdjustment = getPlaceholderSizeAdjustment(dp, pinToRight); - - out.set(0, 0, screenWidth, placeholderHeight + insetSizeAdjustment); - if (!dp.isLeftRightSplit) { - // portrait, phone or tablet - spans width of screen, nothing else to do - out.inset(placeholderInset, 0); - - // Adjust the top to account for content off screen. This will help to animate the view - // in with rounded corners. - int totalHeight = (int) (1.0f * screenHeight / 2 * (screenWidth - 2 * placeholderInset) - / screenWidth); - out.top -= (totalHeight - placeholderHeight); - return; - } - - // Now we rotate the portrait rect depending on what side we want pinned - - float postRotateScale = (float) screenHeight / screenWidth; - mTmpMatrix.reset(); - mTmpMatrix.postRotate(pinToRight ? 90 : 270); - mTmpMatrix.postTranslate(pinToRight ? screenWidth : 0, pinToRight ? 0 : screenWidth); - // The placeholder height stays constant after rotation, so we don't change width scale - mTmpMatrix.postScale(1, postRotateScale); - - mTmpRectF.set(out); - mTmpMatrix.mapRect(mTmpRectF); - mTmpRectF.inset(0, placeholderInset); - mTmpRectF.roundOut(out); - - // Adjust the top to account for content off screen. This will help to animate the view in - // with rounded corners. - int totalWidth = (int) (1.0f * screenWidth / 2 * (screenHeight - 2 * placeholderInset) - / screenHeight); - int width = out.width(); - if (pinToRight) { - out.right += totalWidth - width; - } else { - out.left -= totalWidth - width; - } - } - - @Override - public void updateSplitIconParams(View out, float onScreenRectCenterX, - float onScreenRectCenterY, float fullscreenScaleX, float fullscreenScaleY, - int drawableWidth, int drawableHeight, DeviceProfile dp, - @StagePosition int stagePosition) { - boolean pinToRight = stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT; - float insetAdjustment = getPlaceholderSizeAdjustment(dp, pinToRight) / 2f; - if (!dp.isLeftRightSplit) { - out.setX(onScreenRectCenterX / fullscreenScaleX - - 1.0f * drawableWidth / 2); - out.setY((onScreenRectCenterY + insetAdjustment) / fullscreenScaleY - - 1.0f * drawableHeight / 2); - } else { - if (pinToRight) { - out.setX((onScreenRectCenterX - insetAdjustment) / fullscreenScaleX - - 1.0f * drawableWidth / 2); - } else { - out.setX((onScreenRectCenterX + insetAdjustment) / fullscreenScaleX - - 1.0f * drawableWidth / 2); - } - out.setY(onScreenRectCenterY / fullscreenScaleY - - 1.0f * drawableHeight / 2); - } - } - - /** - * The split placeholder comes with a default inset to buffer the icon from the top of the - * screen. But if the device already has a large inset (from cutouts etc), use that instead. - */ - private int getPlaceholderSizeAdjustment(DeviceProfile dp, boolean pinToRight) { - int insetThickness; - if (!dp.isLandscape) { - insetThickness = dp.getInsets().top; - } else { - insetThickness = pinToRight ? dp.getInsets().right : dp.getInsets().left; - } - return Math.max(insetThickness - dp.splitPlaceholderInset, 0); - } - - @Override - public void setSplitInstructionsParams(View out, DeviceProfile dp, int splitInstructionsHeight, - int splitInstructionsWidth) { - out.setPivotX(0); - out.setPivotY(splitInstructionsHeight); - out.setRotation(getDegreesRotated()); - int distanceToEdge; - if (dp.isPhone) { - if (dp.isLandscape) { - distanceToEdge = out.getResources().getDimensionPixelSize( - R.dimen.split_instructions_bottom_margin_phone_landscape); - } else { - distanceToEdge = out.getResources().getDimensionPixelSize( - R.dimen.split_instructions_bottom_margin_phone_portrait); - } - } else { - distanceToEdge = dp.getOverviewActionsClaimedSpaceBelow(); - } - - // Center the view in case of unbalanced insets on left or right of screen - int insetCorrectionX = (dp.getInsets().right - dp.getInsets().left) / 2; - // Adjust for any insets on the bottom edge - int insetCorrectionY = dp.getInsets().bottom; - out.setTranslationX(insetCorrectionX); - out.setTranslationY(-distanceToEdge + insetCorrectionY); - FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) out.getLayoutParams(); - lp.gravity = CENTER_HORIZONTAL | BOTTOM; - out.setLayoutParams(lp); - } - - @Override - public void getFinalSplitPlaceholderBounds(int splitDividerSize, DeviceProfile dp, - @StagePosition int stagePosition, Rect out1, Rect out2) { - int screenHeight = dp.heightPx; - int screenWidth = dp.widthPx; - out1.set(0, 0, screenWidth, screenHeight / 2 - splitDividerSize); - out2.set(0, screenHeight / 2 + splitDividerSize, screenWidth, screenHeight); - if (!dp.isLeftRightSplit) { - // Portrait - the window bounds are always top and bottom half - return; - } - - // Now we rotate the portrait rect depending on what side we want pinned - boolean pinToRight = stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT; - float postRotateScale = (float) screenHeight / screenWidth; - - mTmpMatrix.reset(); - mTmpMatrix.postRotate(pinToRight ? 90 : 270); - mTmpMatrix.postTranslate(pinToRight ? screenHeight : 0, pinToRight ? 0 : screenWidth); - mTmpMatrix.postScale(1 / postRotateScale, postRotateScale); - - mTmpRectF.set(out1); - mTmpMatrix.mapRect(mTmpRectF); - mTmpRectF.roundOut(out1); - - mTmpRectF.set(out2); - mTmpMatrix.mapRect(mTmpRectF); - mTmpRectF.roundOut(out2); - } - - @Override - public void setSplitTaskSwipeRect(DeviceProfile dp, Rect outRect, - SplitBounds splitInfo, int desiredStagePosition) { - float topLeftTaskPercent = splitInfo.appsStackedVertically - ? splitInfo.topTaskPercent - : splitInfo.leftTaskPercent; - float dividerBarPercent = splitInfo.appsStackedVertically - ? splitInfo.dividerHeightPercent - : splitInfo.dividerWidthPercent; - - int taskbarHeight = dp.isTransientTaskbar ? 0 : dp.taskbarHeight; - float scale = (float) outRect.height() / (dp.availableHeightPx - taskbarHeight); - float topTaskHeight = dp.availableHeightPx * topLeftTaskPercent; - float scaledTopTaskHeight = topTaskHeight * scale; - float dividerHeight = dp.availableHeightPx * dividerBarPercent; - float scaledDividerHeight = dividerHeight * scale; - - if (desiredStagePosition == SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT) { - if (dp.isLeftRightSplit) { - outRect.right = outRect.left + Math.round(outRect.width() * topLeftTaskPercent); - } else { - outRect.bottom = Math.round(outRect.top + scaledTopTaskHeight); - } - } else { - if (dp.isLeftRightSplit) { - outRect.left += Math.round(outRect.width() - * (topLeftTaskPercent + dividerBarPercent)); - } else { - outRect.top += Math.round(scaledTopTaskHeight + scaledDividerHeight); - } - } - } - - @Override - public void measureGroupedTaskViewThumbnailBounds(View primarySnapshot, View secondarySnapshot, - int parentWidth, int parentHeight, SplitBounds splitBoundsConfig, - DeviceProfile dp, boolean isRtl) { - int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx; - int totalThumbnailHeight = parentHeight - spaceAboveSnapshot; - float dividerScale = splitBoundsConfig.appsStackedVertically - ? splitBoundsConfig.dividerHeightPercent - : splitBoundsConfig.dividerWidthPercent; - Pair<Point, Point> taskViewSizes = - getGroupedTaskViewSizes(dp, splitBoundsConfig, parentWidth, parentHeight); - if (dp.isLeftRightSplit) { - int scaledDividerBar = Math.round(parentWidth * dividerScale); - if (isRtl) { - int translationX = taskViewSizes.second.x + scaledDividerBar; - primarySnapshot.setTranslationX(-translationX); - secondarySnapshot.setTranslationX(0); - } else { - int translationX = taskViewSizes.first.x + scaledDividerBar; - secondarySnapshot.setTranslationX(translationX); - primarySnapshot.setTranslationX(0); - } - secondarySnapshot.setTranslationY(spaceAboveSnapshot); - - // Reset unused translations - primarySnapshot.setTranslationY(0); - } else { - float finalDividerHeight = Math.round(totalThumbnailHeight * dividerScale); - float translationY = taskViewSizes.first.y + spaceAboveSnapshot + finalDividerHeight; - secondarySnapshot.setTranslationY(translationY); - - FrameLayout.LayoutParams primaryParams = - (FrameLayout.LayoutParams) primarySnapshot.getLayoutParams(); - FrameLayout.LayoutParams secondaryParams = - (FrameLayout.LayoutParams) secondarySnapshot.getLayoutParams(); - secondaryParams.topMargin = 0; - primaryParams.topMargin = spaceAboveSnapshot; - - // Reset unused translations - primarySnapshot.setTranslationY(0); - secondarySnapshot.setTranslationX(0); - primarySnapshot.setTranslationX(0); - } - primarySnapshot.measure( - View.MeasureSpec.makeMeasureSpec(taskViewSizes.first.x, View.MeasureSpec.EXACTLY), - View.MeasureSpec.makeMeasureSpec(taskViewSizes.first.y, View.MeasureSpec.EXACTLY)); - secondarySnapshot.measure( - View.MeasureSpec.makeMeasureSpec(taskViewSizes.second.x, View.MeasureSpec.EXACTLY), - View.MeasureSpec.makeMeasureSpec(taskViewSizes.second.y, - View.MeasureSpec.EXACTLY)); - primarySnapshot.setScaleX(1); - secondarySnapshot.setScaleX(1); - primarySnapshot.setScaleY(1); - secondarySnapshot.setScaleY(1); - } - - @Override - public Pair<Point, Point> getGroupedTaskViewSizes( - DeviceProfile dp, - SplitBounds splitBoundsConfig, - int parentWidth, - int parentHeight) { - int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx; - int totalThumbnailHeight = parentHeight - spaceAboveSnapshot; - float dividerScale = splitBoundsConfig.appsStackedVertically - ? splitBoundsConfig.dividerHeightPercent - : splitBoundsConfig.dividerWidthPercent; - float taskPercent = splitBoundsConfig.appsStackedVertically - ? splitBoundsConfig.topTaskPercent - : splitBoundsConfig.leftTaskPercent; - - Point firstTaskViewSize = new Point(); - Point secondTaskViewSize = new Point(); - - if (dp.isLeftRightSplit) { - int scaledDividerBar = Math.round(parentWidth * dividerScale); - firstTaskViewSize.x = Math.round(parentWidth * taskPercent); - firstTaskViewSize.y = totalThumbnailHeight; - - secondTaskViewSize.x = parentWidth - firstTaskViewSize.x - scaledDividerBar; - secondTaskViewSize.y = totalThumbnailHeight; - } else { - int taskbarHeight = dp.isTransientTaskbar ? 0 : dp.taskbarHeight; - float scale = (float) totalThumbnailHeight / (dp.availableHeightPx - taskbarHeight); - float topTaskHeight = dp.availableHeightPx * taskPercent; - float finalDividerHeight = Math.round(totalThumbnailHeight * dividerScale); - float scaledTopTaskHeight = topTaskHeight * scale; - firstTaskViewSize.x = parentWidth; - firstTaskViewSize.y = Math.round(scaledTopTaskHeight); - - secondTaskViewSize.x = parentWidth; - secondTaskViewSize.y = Math.round(totalThumbnailHeight - firstTaskViewSize.y - - finalDividerHeight); - } - - return new Pair<>(firstTaskViewSize, secondTaskViewSize); - } - - @Override - public void setTaskIconParams(FrameLayout.LayoutParams iconParams, int taskIconMargin, - int taskIconHeight, int thumbnailTopMargin, boolean isRtl) { - iconParams.gravity = TOP | CENTER_HORIZONTAL; - // Reset margins, since they may have been set on rotation - iconParams.leftMargin = iconParams.rightMargin = 0; - iconParams.topMargin = iconParams.bottomMargin = 0; - } - - @Override - public void setIconAppChipChildrenParams(FrameLayout.LayoutParams iconParams, - int chipChildMarginStart) { - iconParams.setMarginStart(chipChildMarginStart); - iconParams.gravity = Gravity.START | Gravity.CENTER_VERTICAL; - iconParams.topMargin = 0; - } - - @Override - public void setIconAppChipMenuParams(IconAppChipView iconAppChipView, - FrameLayout.LayoutParams iconMenuParams, int iconMenuMargin, int thumbnailTopMargin) { - iconMenuParams.gravity = TOP | START; - iconMenuParams.setMarginStart(iconMenuMargin); - iconMenuParams.topMargin = thumbnailTopMargin; - iconMenuParams.bottomMargin = 0; - iconMenuParams.setMarginEnd(0); - - iconAppChipView.setPivotX(0); - iconAppChipView.setPivotY(0); - iconAppChipView.setSplitTranslationY(0); - iconAppChipView.setRotation(getDegreesRotated()); - } - - @Override - public void setSplitIconParams(View primaryIconView, View secondaryIconView, - int taskIconHeight, int primarySnapshotWidth, int primarySnapshotHeight, - int groupedTaskViewHeight, int groupedTaskViewWidth, boolean isRtl, - DeviceProfile deviceProfile, SplitBounds splitConfig) { - FrameLayout.LayoutParams primaryIconParams = - (FrameLayout.LayoutParams) primaryIconView.getLayoutParams(); - FrameLayout.LayoutParams secondaryIconParams = enableOverviewIconMenu() - ? (FrameLayout.LayoutParams) secondaryIconView.getLayoutParams() - : new FrameLayout.LayoutParams(primaryIconParams); - - if (enableOverviewIconMenu()) { - IconAppChipView primaryAppChipView = (IconAppChipView) primaryIconView; - IconAppChipView secondaryAppChipView = (IconAppChipView) secondaryIconView; - primaryIconParams.gravity = TOP | START; - secondaryIconParams.gravity = TOP | START; - secondaryIconParams.topMargin = primaryIconParams.topMargin; - secondaryIconParams.setMarginStart(primaryIconParams.getMarginStart()); - if (deviceProfile.isLeftRightSplit) { - if (isRtl) { - int secondarySnapshotWidth = groupedTaskViewWidth - primarySnapshotWidth; - primaryAppChipView.setSplitTranslationX(-secondarySnapshotWidth); - } else { - secondaryAppChipView.setSplitTranslationX(primarySnapshotWidth); - } - } else { - primaryAppChipView.setSplitTranslationX(0); - secondaryAppChipView.setSplitTranslationX(0); - int dividerThickness = Math.min(splitConfig.visualDividerBounds.width(), - splitConfig.visualDividerBounds.height()); - secondaryAppChipView.setSplitTranslationY( - primarySnapshotHeight + (deviceProfile.isTablet ? 0 : dividerThickness)); - } - } else if (deviceProfile.isLeftRightSplit) { - // We calculate the "midpoint" of the thumbnail area, and place the icons there. - // This is the place where the thumbnail area splits by default, in a near-50/50 split. - // It is usually not exactly 50/50, due to insets/screen cutouts. - int fullscreenInsetThickness = deviceProfile.isSeascape() - ? deviceProfile.getInsets().right - : deviceProfile.getInsets().left; - int fullscreenMidpointFromBottom = ((deviceProfile.widthPx - - fullscreenInsetThickness) / 2); - float midpointFromEndPct = (float) fullscreenMidpointFromBottom - / deviceProfile.widthPx; - float insetPct = (float) fullscreenInsetThickness / deviceProfile.widthPx; - int spaceAboveSnapshots = 0; - int overviewThumbnailAreaThickness = groupedTaskViewWidth - spaceAboveSnapshots; - int bottomToMidpointOffset = (int) (overviewThumbnailAreaThickness - * midpointFromEndPct); - int insetOffset = (int) (overviewThumbnailAreaThickness * insetPct); - - if (deviceProfile.isSeascape()) { - primaryIconParams.gravity = TOP | (isRtl ? END : START); - secondaryIconParams.gravity = TOP | (isRtl ? END : START); - if (splitConfig.initiatedFromSeascape) { - // if the split was initiated from seascape, - // the task on the right (secondary) is slightly larger - primaryIconView.setTranslationX(bottomToMidpointOffset - taskIconHeight); - secondaryIconView.setTranslationX(bottomToMidpointOffset); - } else { - // if not, - // the task on the left (primary) is slightly larger - primaryIconView.setTranslationX(bottomToMidpointOffset + insetOffset - - taskIconHeight); - secondaryIconView.setTranslationX(bottomToMidpointOffset + insetOffset); - } - } else { - primaryIconParams.gravity = TOP | (isRtl ? START : END); - secondaryIconParams.gravity = TOP | (isRtl ? START : END); - if (!splitConfig.initiatedFromSeascape) { - // if the split was initiated from landscape, - // the task on the left (primary) is slightly larger - primaryIconView.setTranslationX(-bottomToMidpointOffset); - secondaryIconView.setTranslationX(-bottomToMidpointOffset + taskIconHeight); - } else { - // if not, - // the task on the right (secondary) is slightly larger - primaryIconView.setTranslationX(-bottomToMidpointOffset - insetOffset); - secondaryIconView.setTranslationX(-bottomToMidpointOffset - insetOffset - + taskIconHeight); - } - } - } else { - primaryIconParams.gravity = TOP | CENTER_HORIZONTAL; - // shifts icon half a width left (height is used here since icons are square) - primaryIconView.setTranslationX(-(taskIconHeight / 2f)); - secondaryIconParams.gravity = TOP | CENTER_HORIZONTAL; - secondaryIconView.setTranslationX(taskIconHeight / 2f); - } - if (!enableOverviewIconMenu()) { - primaryIconView.setTranslationY(0); - secondaryIconView.setTranslationY(0); - } - - primaryIconView.setLayoutParams(primaryIconParams); - secondaryIconView.setLayoutParams(secondaryIconParams); - } - - @Override - public int getDefaultSplitPosition(DeviceProfile deviceProfile) { - if (!deviceProfile.isTablet) { - throw new IllegalStateException("Default position available only for large screens"); - } - if (deviceProfile.isLeftRightSplit) { - return STAGE_POSITION_BOTTOM_OR_RIGHT; - } else { - return STAGE_POSITION_TOP_OR_LEFT; - } - } - - @Override - public Pair<FloatProperty, FloatProperty> getSplitSelectTaskOffset(FloatProperty primary, - FloatProperty secondary, DeviceProfile deviceProfile) { - if (deviceProfile.isLeftRightSplit) { // or seascape - return new Pair<>(primary, secondary); - } else { - return new Pair<>(secondary, primary); - } - } - - @Override - public float getFloatingTaskOffscreenTranslationTarget(View floatingTask, RectF onScreenRect, - @StagePosition int stagePosition, DeviceProfile dp) { - if (dp.isLeftRightSplit) { - float currentTranslationX = floatingTask.getTranslationX(); - return stagePosition == STAGE_POSITION_TOP_OR_LEFT - ? currentTranslationX - onScreenRect.width() - : currentTranslationX + onScreenRect.width(); - } else { - float currentTranslationY = floatingTask.getTranslationY(); - return currentTranslationY - onScreenRect.height(); - } - } - - @Override - public void setFloatingTaskPrimaryTranslation(View floatingTask, float translation, - DeviceProfile dp) { - if (dp.isLeftRightSplit) { - floatingTask.setTranslationX(translation); - } else { - floatingTask.setTranslationY(translation); - } - - } - - @Override - public float getFloatingTaskPrimaryTranslation(View floatingTask, DeviceProfile dp) { - return dp.isLeftRightSplit - ? floatingTask.getTranslationX() - : floatingTask.getTranslationY(); - } - - @NonNull - @Override - public LauncherAtom.TaskSwitcherContainer.OrientationHandler getHandlerTypeForLogging() { - return LauncherAtom.TaskSwitcherContainer.OrientationHandler.PORTRAIT; - } -} diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModel.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModel.kt deleted file mode 100644 index 4511ea714c..0000000000 --- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModel.kt +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.quickstep.task.thumbnail - -import android.annotation.ColorInt -import android.graphics.Rect -import androidx.core.graphics.ColorUtils -import com.android.quickstep.recents.data.RecentTasksRepository -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 kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.flow.map - -@OptIn(ExperimentalCoroutinesApi::class) -class TaskThumbnailViewModel( - recentsViewData: RecentsViewData, - taskViewData: TaskViewData, - private val tasksRepository: RecentTasksRepository, -) { - private val task = MutableStateFlow<Flow<Task?>>(flowOf(null)) - private var boundTaskIsRunning = false - - val recentsFullscreenProgress = recentsViewData.fullscreenProgress - val inheritedScale = - combine(recentsViewData.scale, taskViewData.scale) { recentsScale, taskScale -> - recentsScale * taskScale - } - val uiState: Flow<TaskThumbnailUiState> = - task - .flatMapLatest { taskFlow -> - taskFlow.map { taskVal -> - when { - taskVal == null -> Uninitialized - boundTaskIsRunning -> LiveTile - isBackgroundOnly(taskVal) -> - BackgroundOnly(taskVal.colorBackground.removeAlpha()) - isSnapshotState(taskVal) -> { - val bitmap = taskVal.thumbnail?.thumbnail!! - Snapshot( - bitmap, - Rect(0, 0, bitmap.width, bitmap.height), - taskVal.colorBackground.removeAlpha() - ) - } - else -> Uninitialized - } - } - } - .distinctUntilChanged() - - fun bind(taskThumbnail: TaskThumbnail) { - boundTaskIsRunning = taskThumbnail.isRunning - task.value = tasksRepository.getTaskDataById(taskThumbnail.taskId) - } - - private fun isBackgroundOnly(task: Task): Boolean = task.isLocked || task.thumbnail == null - - private fun isSnapshotState(task: Task): Boolean { - val thumbnailPresent = task.thumbnail?.thumbnail != null - val taskLocked = task.isLocked - - return thumbnailPresent && !taskLocked - } - - @ColorInt private fun Int.removeAlpha(): Int = ColorUtils.setAlphaComponent(this, 0xff) -} diff --git a/quickstep/src/com/android/quickstep/task/viewmodel/TaskViewData.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskViewData.kt deleted file mode 100644 index a8b5112860..0000000000 --- a/quickstep/src/com/android/quickstep/task/viewmodel/TaskViewData.kt +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.quickstep.task.viewmodel - -import kotlinx.coroutines.flow.MutableStateFlow - -class TaskViewData { - // This is typically a View concern but it is used to invalidate rendering in other Views - val scale = MutableStateFlow(1f) -} diff --git a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java b/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java deleted file mode 100644 index cfa6b9891f..0000000000 --- a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java +++ /dev/null @@ -1,454 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.quickstep.util; - -import android.util.ArraySet; - -import androidx.annotation.NonNull; - -import java.io.PrintWriter; -import java.util.Set; - -/** - * Utility class for tracking gesture navigation events as they happen, then detecting and reporting - * known issues at log dump time. - */ -public class ActiveGestureErrorDetector { - - /** - * Enums associated to gesture navigation events. - */ - public enum GestureEvent { - MOTION_DOWN, MOTION_UP, MOTION_MOVE, SET_END_TARGET, SET_END_TARGET_HOME, - SET_END_TARGET_NEW_TASK, SET_END_TARGET_ALL_APPS, ON_SETTLED_ON_END_TARGET, - ON_START_RECENTS_ANIMATION, ON_FINISH_RECENTS_ANIMATION, ON_CANCEL_RECENTS_ANIMATION, - START_RECENTS_ANIMATION, FINISH_RECENTS_ANIMATION, CANCEL_RECENTS_ANIMATION, - SET_ON_PAGE_TRANSITION_END_CALLBACK, CANCEL_CURRENT_ANIMATION, CLEANUP_SCREENSHOT, - SCROLLER_ANIMATION_ABORTED, TASK_APPEARED, EXPECTING_TASK_APPEARED, - FLAG_USING_OTHER_ACTIVITY_INPUT_CONSUMER, LAUNCHER_DESTROYED, RECENT_TASKS_MISSING, - INVALID_VELOCITY_ON_SWIPE_UP, RECENTS_ANIMATION_START_PENDING, - - /** - * These GestureEvents are specifically associated to state flags that get set in - * {@link com.android.quickstep.MultiStateCallback}. If a state flag needs to be tracked - * for error detection, an enum should be added here and that state flag-enum pair should - * be added to the state flag's container class' {@code getTrackedEventForState} method. - */ - STATE_GESTURE_STARTED, STATE_GESTURE_COMPLETED, STATE_GESTURE_CANCELLED, - STATE_END_TARGET_ANIMATION_FINISHED, STATE_RECENTS_SCROLLING_FINISHED, - STATE_CAPTURE_SCREENSHOT, STATE_SCREENSHOT_CAPTURED, STATE_HANDLER_INVALIDATED, - STATE_RECENTS_ANIMATION_CANCELED, STATE_LAUNCHER_DRAWN(true, false); - - public final boolean mLogEvent; - public final boolean mTrackEvent; - - GestureEvent() { - this(false, true); - } - - GestureEvent(boolean logEvent, boolean trackEvent) { - mLogEvent = logEvent; - mTrackEvent = trackEvent; - } - } - - private ActiveGestureErrorDetector() {} - - private static final long ON_START_RECENT_ANIMATION_TIME_LIMIT = 500; - - protected static void analyseAndDump( - @NonNull String prefix, - @NonNull PrintWriter writer, - @NonNull ActiveGestureLog.EventLog eventLog) { - writer.println(prefix + "Error messages for gesture ID: " + eventLog.logId); - if (!eventLog.mIsFullyGesturalNavMode) { - writer.println(prefix - + "\tSkipping gesture error detection because gesture navigation not enabled"); - return; - } - - boolean errorDetected = false; - // Use a Set since the order is inherently checked in the loop. - final Set<GestureEvent> encounteredEvents = new ArraySet<>(); - // Set flags and check order of operations. - long lastStartRecentAnimationEventEntryTime = 0; - for (ActiveGestureLog.EventEntry eventEntry : eventLog.eventEntries) { - GestureEvent gestureEvent = eventEntry.getGestureEvent(); - if (gestureEvent == null) { - continue; - } - encounteredEvents.add(gestureEvent); - - switch (gestureEvent) { - case MOTION_UP: - errorDetected |= printErrorIfTrue( - !encounteredEvents.contains(GestureEvent.MOTION_DOWN), - prefix, - /* errorMessage= */ "Motion up detected before/without" - + " motion down.", - writer); - break; - case ON_SETTLED_ON_END_TARGET: - errorDetected |= printErrorIfTrue( - !encounteredEvents.contains(GestureEvent.SET_END_TARGET), - prefix, - /* errorMessage= */ "onSettledOnEndTarget called " - + "before/without setEndTarget.", - writer); - break; - case FINISH_RECENTS_ANIMATION: - errorDetected |= printErrorIfTrue( - !encounteredEvents.contains(GestureEvent.START_RECENTS_ANIMATION), - prefix, - /* errorMessage= */ "finishRecentsAnimation called " - + "before/without startRecentsAnimation.", - writer); - break; - case CANCEL_RECENTS_ANIMATION: - errorDetected |= printErrorIfTrue( - !encounteredEvents.contains(GestureEvent.START_RECENTS_ANIMATION), - prefix, - /* errorMessage= */ "cancelRecentsAnimation called " - + "before/without startRecentsAnimation.", - writer); - break; - case CLEANUP_SCREENSHOT: - errorDetected |= printErrorIfTrue( - !encounteredEvents.contains(GestureEvent.STATE_SCREENSHOT_CAPTURED), - prefix, - /* errorMessage= */ "recents activity screenshot was " - + "cleaned up before/without STATE_SCREENSHOT_CAPTURED " - + "being set.", - writer); - break; - case SCROLLER_ANIMATION_ABORTED: - errorDetected |= printErrorIfTrue( - encounteredEvents.contains(GestureEvent.SET_END_TARGET_HOME) - && !encounteredEvents.contains( - GestureEvent.ON_SETTLED_ON_END_TARGET), - prefix, - /* errorMessage= */ "recents view scroller animation " - + "aborted after setting end target HOME, but before" - + " settling on end target.", - writer); - break; - case TASK_APPEARED: - errorDetected |= printErrorIfTrue( - !encounteredEvents.contains(GestureEvent.EXPECTING_TASK_APPEARED), - prefix, - /* errorMessage= */ "onTasksAppeared was not expected to be called", - writer); - if (encounteredEvents.contains(GestureEvent.EXPECTING_TASK_APPEARED)) { - // Remove both events so that we can properly detect following errors. - encounteredEvents.remove(GestureEvent.EXPECTING_TASK_APPEARED); - encounteredEvents.remove(GestureEvent.TASK_APPEARED); - } - break; - case LAUNCHER_DESTROYED: - errorDetected |= printErrorIfTrue( - true, - prefix, - /* errorMessage= */ "Launcher destroyed mid-gesture", - writer); - break; - case STATE_GESTURE_COMPLETED: - errorDetected |= printErrorIfTrue( - !encounteredEvents.contains(GestureEvent.MOTION_UP), - prefix, - /* errorMessage= */ "STATE_GESTURE_COMPLETED set " - + "before/without motion up.", - writer); - errorDetected |= printErrorIfTrue( - !encounteredEvents.contains(GestureEvent.STATE_GESTURE_STARTED), - prefix, - /* errorMessage= */ "STATE_GESTURE_COMPLETED set " - + "before/without STATE_GESTURE_STARTED.", - writer); - break; - case STATE_GESTURE_CANCELLED: - errorDetected |= printErrorIfTrue( - !encounteredEvents.contains(GestureEvent.MOTION_UP), - prefix, - /* errorMessage= */ "STATE_GESTURE_CANCELLED set " - + "before/without motion up.", - writer); - errorDetected |= printErrorIfTrue( - !encounteredEvents.contains(GestureEvent.STATE_GESTURE_STARTED), - prefix, - /* errorMessage= */ "STATE_GESTURE_CANCELLED set " - + "before/without STATE_GESTURE_STARTED.", - writer); - break; - case STATE_SCREENSHOT_CAPTURED: - errorDetected |= printErrorIfTrue( - !encounteredEvents.contains(GestureEvent.STATE_CAPTURE_SCREENSHOT), - prefix, - /* errorMessage= */ "STATE_SCREENSHOT_CAPTURED set " - + "before/without STATE_CAPTURE_SCREENSHOT.", - writer); - break; - case STATE_RECENTS_SCROLLING_FINISHED: - errorDetected |= printErrorIfTrue( - !encounteredEvents.contains( - GestureEvent.SET_ON_PAGE_TRANSITION_END_CALLBACK), - prefix, - /* errorMessage= */ "STATE_RECENTS_SCROLLING_FINISHED " - + "set before/without calling " - + "setOnPageTransitionEndCallback.", - writer); - break; - case STATE_RECENTS_ANIMATION_CANCELED: - errorDetected |= printErrorIfTrue( - !encounteredEvents.contains( - GestureEvent.START_RECENTS_ANIMATION), - prefix, - /* errorMessage= */ "STATE_RECENTS_ANIMATION_CANCELED " - + "set before/without startRecentsAnimation.", - writer); - break; - case RECENT_TASKS_MISSING: - errorDetected |= printErrorIfTrue( - true, - prefix, - /* errorMessage= */ "SystemUiProxy.mRecentTasks missing," - + " couldn't start the recents activity", - writer); - break; - case ON_START_RECENTS_ANIMATION: - errorDetected |= printErrorIfTrue( - !encounteredEvents.contains(GestureEvent.START_RECENTS_ANIMATION), - prefix, - /* errorMessage= */ "ON_START_RECENTS_ANIMATION " - + "onAnimationStart callback ran before startRecentsAnimation", - writer); - errorDetected |= printErrorIfTrue( - eventEntry.getTime() - lastStartRecentAnimationEventEntryTime - > ON_START_RECENT_ANIMATION_TIME_LIMIT, - prefix, - /* errorMessage= */"ON_START_RECENTS_ANIMATION " - + "startRecentsAnimation was never called or onAnimationStart " - + "callback was called more than 500 ms after " - + "startRecentsAnimation.", - writer); - lastStartRecentAnimationEventEntryTime = 0; - break; - case ON_CANCEL_RECENTS_ANIMATION: - errorDetected |= printErrorIfTrue( - !encounteredEvents.contains(GestureEvent.ON_START_RECENTS_ANIMATION), - prefix, - /* errorMessage= */ "ON_CANCEL_RECENTS_ANIMATION " - + "onAnimationCanceled callback ran before onAnimationStart " - + "callback", - writer); - break; - case ON_FINISH_RECENTS_ANIMATION: - errorDetected |= printErrorIfTrue( - !encounteredEvents.contains(GestureEvent.ON_START_RECENTS_ANIMATION), - prefix, - /* errorMessage= */ "ON_FINISH_RECENTS_ANIMATION " - + "onAnimationFinished callback ran before onAnimationStart " - + "callback", - writer); - break; - case INVALID_VELOCITY_ON_SWIPE_UP: - errorDetected |= printErrorIfTrue( - true, - prefix, - /* errorMessage= */ "invalid velocity on swipe up gesture.", - writer); - break; - case START_RECENTS_ANIMATION: - lastStartRecentAnimationEventEntryTime = eventEntry.getTime(); - break; - case RECENTS_ANIMATION_START_PENDING: - errorDetected |= printErrorIfTrue( - true, - prefix, - /* errorMessage= */ (eventEntry.getDuplicateCount() + 1) - + " gesture(s) attempted while a requested recents" - + " animation is still pending.", - writer); - break; - case EXPECTING_TASK_APPEARED: - case MOTION_DOWN: - case SET_END_TARGET: - case SET_END_TARGET_HOME: - case SET_END_TARGET_ALL_APPS: - case SET_END_TARGET_NEW_TASK: - case SET_ON_PAGE_TRANSITION_END_CALLBACK: - case CANCEL_CURRENT_ANIMATION: - case FLAG_USING_OTHER_ACTIVITY_INPUT_CONSUMER: - case STATE_GESTURE_STARTED: - case STATE_END_TARGET_ANIMATION_FINISHED: - case STATE_CAPTURE_SCREENSHOT: - case STATE_HANDLER_INVALIDATED: - case STATE_LAUNCHER_DRAWN: - default: - // No-Op - } - } - - // Check that all required events were found. - errorDetected |= printErrorIfTrue( - !encounteredEvents.contains(GestureEvent.MOTION_DOWN), - prefix, - /* errorMessage= */ "Motion down never detected.", - writer); - errorDetected |= printErrorIfTrue( - !encounteredEvents.contains(GestureEvent.MOTION_UP), - prefix, - /* errorMessage= */ "Motion up never detected.", - writer); - - errorDetected |= printErrorIfTrue( - /* condition= */ encounteredEvents.contains(GestureEvent.SET_END_TARGET) - && !encounteredEvents.contains(GestureEvent.ON_SETTLED_ON_END_TARGET), - prefix, - /* errorMessage= */ "setEndTarget was called, but " - + "onSettledOnEndTarget wasn't.", - writer); - errorDetected |= printErrorIfTrue( - /* condition= */ encounteredEvents.contains(GestureEvent.SET_END_TARGET) - && !encounteredEvents.contains( - GestureEvent.STATE_END_TARGET_ANIMATION_FINISHED), - prefix, - /* errorMessage= */ "setEndTarget was called, but " - + "STATE_END_TARGET_ANIMATION_FINISHED was never set.", - writer); - errorDetected |= printErrorIfTrue( - /* condition= */ encounteredEvents.contains(GestureEvent.SET_END_TARGET) - && !encounteredEvents.contains( - GestureEvent.STATE_RECENTS_SCROLLING_FINISHED), - prefix, - /* errorMessage= */ "setEndTarget was called, but " - + "STATE_RECENTS_SCROLLING_FINISHED was never set.", - writer); - errorDetected |= printErrorIfTrue( - /* condition= */ encounteredEvents.contains( - GestureEvent.STATE_END_TARGET_ANIMATION_FINISHED) - && encounteredEvents.contains( - GestureEvent.STATE_RECENTS_SCROLLING_FINISHED) - && !encounteredEvents.contains(GestureEvent.ON_SETTLED_ON_END_TARGET), - prefix, - /* errorMessage= */ "STATE_END_TARGET_ANIMATION_FINISHED and " - + "STATE_RECENTS_SCROLLING_FINISHED were set, but onSettledOnEndTarget " - + "wasn't called.", - writer); - - errorDetected |= printErrorIfTrue( - /* condition= */ encounteredEvents.contains( - GestureEvent.START_RECENTS_ANIMATION) - && !encounteredEvents.contains(GestureEvent.FINISH_RECENTS_ANIMATION) - && !encounteredEvents.contains(GestureEvent.CANCEL_RECENTS_ANIMATION), - prefix, - /* errorMessage= */ "startRecentsAnimation was called, but " - + "finishRecentsAnimation and cancelRecentsAnimation weren't.", - writer); - - errorDetected |= printErrorIfTrue( - /* condition= */ encounteredEvents.contains(GestureEvent.STATE_GESTURE_STARTED) - && !encounteredEvents.contains(GestureEvent.STATE_GESTURE_COMPLETED) - && !encounteredEvents.contains(GestureEvent.STATE_GESTURE_CANCELLED), - prefix, - /* errorMessage= */ "STATE_GESTURE_STARTED was set, but " - + "STATE_GESTURE_COMPLETED and STATE_GESTURE_CANCELLED weren't.", - writer); - - errorDetected |= printErrorIfTrue( - /* condition= */ encounteredEvents.contains( - GestureEvent.STATE_CAPTURE_SCREENSHOT) - && !encounteredEvents.contains(GestureEvent.STATE_SCREENSHOT_CAPTURED), - prefix, - /* errorMessage= */ "STATE_CAPTURE_SCREENSHOT was set, but " - + "STATE_SCREENSHOT_CAPTURED wasn't.", - writer); - - errorDetected |= printErrorIfTrue( - /* condition= */ encounteredEvents.contains( - GestureEvent.SET_ON_PAGE_TRANSITION_END_CALLBACK) - && !encounteredEvents.contains( - GestureEvent.STATE_RECENTS_SCROLLING_FINISHED), - prefix, - /* errorMessage= */ "setOnPageTransitionEndCallback called, but " - + "STATE_RECENTS_SCROLLING_FINISHED wasn't set.", - writer); - - errorDetected |= printErrorIfTrue( - /* condition= */ encounteredEvents.contains( - GestureEvent.FLAG_USING_OTHER_ACTIVITY_INPUT_CONSUMER) - && !encounteredEvents.contains(GestureEvent.CANCEL_CURRENT_ANIMATION) - && !encounteredEvents.contains(GestureEvent.STATE_HANDLER_INVALIDATED), - prefix, - /* errorMessage= */ "AbsSwipeUpHandler.cancelCurrentAnimation " - + "wasn't called and STATE_HANDLER_INVALIDATED wasn't set.", - writer); - - errorDetected |= printErrorIfTrue( - /* condition= */ encounteredEvents.contains( - GestureEvent.STATE_RECENTS_ANIMATION_CANCELED) - && !encounteredEvents.contains(GestureEvent.CLEANUP_SCREENSHOT), - prefix, - /* errorMessage= */ "STATE_RECENTS_ANIMATION_CANCELED was set but " - + "the task screenshot wasn't cleaned up.", - writer); - - errorDetected |= printErrorIfTrue( - /* condition= */ encounteredEvents.contains( - GestureEvent.EXPECTING_TASK_APPEARED) - && !encounteredEvents.contains(GestureEvent.TASK_APPEARED), - prefix, - /* errorMessage= */ "onTaskAppeared was expected to be called but wasn't.", - writer); - - errorDetected |= printErrorIfTrue( - /* condition= */ encounteredEvents.contains(GestureEvent.START_RECENTS_ANIMATION) - && !encounteredEvents.contains(GestureEvent.ON_START_RECENTS_ANIMATION), - prefix, - /* errorMessage= */ - "startRecentAnimation was called but onAnimationStart callback was not", - writer); - errorDetected |= printErrorIfTrue( - /* condition= */ - encounteredEvents.contains(GestureEvent.FINISH_RECENTS_ANIMATION) - && !encounteredEvents.contains(GestureEvent.ON_FINISH_RECENTS_ANIMATION), - prefix, - /* errorMessage= */ - "finishController was called but onAnimationFinished callback was not", - writer); - errorDetected |= printErrorIfTrue( - /* condition= */ - encounteredEvents.contains(GestureEvent.CANCEL_RECENTS_ANIMATION) - && !encounteredEvents.contains(GestureEvent.ON_CANCEL_RECENTS_ANIMATION), - prefix, - /* errorMessage= */ - "onRecentsAnimationCanceled was called but onAnimationCanceled was not", - writer); - - if (!errorDetected) { - writer.println(prefix + "\tNo errors detected."); - } - } - - private static boolean printErrorIfTrue( - boolean condition, String prefix, String errorMessage, PrintWriter writer) { - if (!condition) { - return false; - } - writer.println(prefix + "\t- " + errorMessage); - - return true; - } -} diff --git a/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java b/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java deleted file mode 100644 index c54862aba3..0000000000 --- a/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java +++ /dev/null @@ -1,376 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.quickstep.util; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.android.launcher3.util.Preconditions; - -import java.io.PrintWriter; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.Locale; -import java.util.Objects; - -/** - * A log to keep track of the active gesture. - */ -public class ActiveGestureLog { - - private static final int MAX_GESTURES_TRACKED = 15; - - public static final ActiveGestureLog INSTANCE = new ActiveGestureLog(); - - private boolean mIsFullyGesturalNavMode; - - /** - * NOTE: This value should be kept same as - * ActivityTaskManagerService#INTENT_EXTRA_LOG_TRACE_ID in platform - */ - public static final String INTENT_EXTRA_LOG_TRACE_ID = "INTENT_EXTRA_LOG_TRACE_ID"; - - private final EventLog[] logs; - private int nextIndex; - private int mCurrentLogId = 100; - - private ActiveGestureLog() { - this.logs = new EventLog[MAX_GESTURES_TRACKED]; - this.nextIndex = 0; - } - - /** - * Track the given event for error detection. - * - * @param gestureEvent GestureEvent representing an event during the current gesture's - * execution. - */ - public void trackEvent(@Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) { - addLog(CompoundString.NO_OP, gestureEvent); - } - - /** - * Adds a log to be printed at log-dump-time. - */ - public void addLog(@NonNull String event) { - addLog(event, null); - } - - public void addLog(@NonNull String event, int extras) { - addLog(event, extras, null); - } - - public void addLog(@NonNull String event, boolean extras) { - addLog(event, extras, null); - } - - /** - * Adds a log to be printed at log-dump-time and track the associated event for error detection. - * - * @param gestureEvent GestureEvent representing the event being logged. - */ - public void addLog( - @NonNull String event, @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) { - addLog(new CompoundString(event), gestureEvent); - } - - public void addLog( - @NonNull String event, - int extras, - @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) { - addLog(new CompoundString(event).append(": ").append(extras), gestureEvent); - } - - public void addLog( - @NonNull String event, - boolean extras, - @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) { - addLog(new CompoundString(event).append(": ").append(extras), gestureEvent); - } - - public void addLog(@NonNull CompoundString compoundString) { - addLog(compoundString, null); - } - - public void addLog( - @NonNull CompoundString compoundString, - @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) { - EventLog lastEventLog = logs[(nextIndex + logs.length - 1) % logs.length]; - if (lastEventLog == null || mCurrentLogId != lastEventLog.logId) { - EventLog eventLog = new EventLog(mCurrentLogId, mIsFullyGesturalNavMode); - EventEntry eventEntry = new EventEntry(); - - eventEntry.update(compoundString, gestureEvent); - eventLog.eventEntries.add(eventEntry); - logs[nextIndex] = eventLog; - nextIndex = (nextIndex + 1) % logs.length; - return; - } - - // Update the last EventLog - List<EventEntry> lastEventEntries = lastEventLog.eventEntries; - EventEntry lastEntry = !lastEventEntries.isEmpty() - ? lastEventEntries.get(lastEventEntries.size() - 1) : null; - - // Update the last EventEntry if it's a duplicate - if (isEntrySame(lastEntry, compoundString, gestureEvent)) { - lastEntry.duplicateCount++; - return; - } - EventEntry eventEntry = new EventEntry(); - - eventEntry.update(compoundString, gestureEvent); - lastEventEntries.add(eventEntry); - } - - public void dump(String prefix, PrintWriter writer) { - writer.println(prefix + "ActiveGestureErrorDetector:"); - for (int i = 0; i < logs.length; i++) { - EventLog eventLog = logs[(nextIndex + i) % logs.length]; - if (eventLog == null) { - continue; - } - ActiveGestureErrorDetector.analyseAndDump(prefix + '\t', writer, eventLog); - } - - writer.println(prefix + "ActiveGestureLog history:"); - SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSSZ ", Locale.US); - Date date = new Date(); - for (int i = 0; i < logs.length; i++) { - EventLog eventLog = logs[(nextIndex + i) % logs.length]; - if (eventLog == null) { - continue; - } - - writer.println(prefix + "\tLogs for logId: " + eventLog.logId); - for (EventEntry eventEntry : eventLog.eventEntries) { - if (eventEntry.mCompoundString.mIsNoOp) { - continue; - } - date.setTime(eventEntry.time); - - StringBuilder msg = new StringBuilder(prefix + "\t\t") - .append(sdf.format(date)) - .append(eventEntry.mCompoundString); - if (eventEntry.duplicateCount > 0) { - msg.append(" & ").append(eventEntry.duplicateCount).append(" similar events"); - } - writer.println(msg); - } - } - } - - /** - * Increments and returns the current log ID. This should be used every time a new log trace - * is started. - */ - public int incrementLogId() { - return mCurrentLogId++; - } - - public void setIsFullyGesturalNavMode(boolean isFullyGesturalNavMode) { - mIsFullyGesturalNavMode = isFullyGesturalNavMode; - } - - /** Returns the current log ID. This should be used when a log trace is being reused. */ - public int getLogId() { - return mCurrentLogId; - } - - private boolean isEntrySame( - EventEntry entry, - CompoundString compoundString, - ActiveGestureErrorDetector.GestureEvent gestureEvent) { - return entry != null - && entry.mCompoundString.equals(compoundString) - && entry.gestureEvent == gestureEvent; - } - - /** A single event entry. */ - protected static class EventEntry { - - @NonNull private CompoundString mCompoundString; - private ActiveGestureErrorDetector.GestureEvent gestureEvent; - private long time; - private int duplicateCount; - - private EventEntry() {} - - @Nullable - protected ActiveGestureErrorDetector.GestureEvent getGestureEvent() { - return gestureEvent; - } - - public int getDuplicateCount() { - return duplicateCount; - } - - private void update( - @NonNull CompoundString compoundString, - ActiveGestureErrorDetector.GestureEvent gestureEvent) { - this.mCompoundString = compoundString; - this.gestureEvent = gestureEvent; - time = System.currentTimeMillis(); - duplicateCount = 0; - } - - public long getTime() { - return time; - } - } - - /** An entire log of entries associated with a single log ID */ - protected static class EventLog { - - protected final List<EventEntry> eventEntries = new ArrayList<>(); - protected final int logId; - protected final boolean mIsFullyGesturalNavMode; - - private EventLog(int logId, boolean isFullyGesturalNavMode) { - this.logId = logId; - mIsFullyGesturalNavMode = isFullyGesturalNavMode; - } - } - - /** A buildable string stored as an array for memory efficiency. */ - public static class CompoundString { - - public static final CompoundString NO_OP = new CompoundString(); - - private final List<String> mSubstrings; - private final List<Object> mArgs; - - private final boolean mIsNoOp; - - private CompoundString() { - this(null); - } - - public CompoundString(String substring) { - mIsNoOp = substring == null; - mSubstrings = mIsNoOp ? null : new ArrayList<>(); - mArgs = mIsNoOp ? null : new ArrayList<>(); - - if (!mIsNoOp) { - mSubstrings.add(substring); - } - } - - public CompoundString append(CompoundString substring) { - if (mIsNoOp || substring.mIsNoOp) { - return this; - } - mSubstrings.addAll(substring.mSubstrings); - mArgs.addAll(substring.mArgs); - - return this; - } - - public CompoundString append(String substring) { - if (mIsNoOp) { - return this; - } - mSubstrings.add(substring); - - return this; - } - - public CompoundString append(int num) { - if (mIsNoOp) { - return this; - } - mArgs.add(num); - - return append("%d"); - } - - public CompoundString append(long num) { - if (mIsNoOp) { - return this; - } - mArgs.add(num); - - return append("%d"); - } - - public CompoundString append(float num) { - if (mIsNoOp) { - return this; - } - mArgs.add(num); - - return append("%.2f"); - } - - public CompoundString append(double num) { - if (mIsNoOp) { - return this; - } - mArgs.add(num); - - return append("%.2f"); - } - - public CompoundString append(boolean bool) { - if (mIsNoOp) { - return this; - } - mArgs.add(bool); - - return append("%b"); - } - - private Object[] getArgs() { - Preconditions.assertTrue(!mIsNoOp); - - return mArgs.toArray(); - } - - @Override - public String toString() { - return String.format(toUnformattedString(), getArgs()); - } - - private String toUnformattedString() { - Preconditions.assertTrue(!mIsNoOp); - - StringBuilder sb = new StringBuilder(); - for (String substring : mSubstrings) { - sb.append(substring); - } - - return sb.toString(); - } - - @Override - public int hashCode() { - return Objects.hash(mIsNoOp, mSubstrings, mArgs); - } - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof CompoundString)) { - return false; - } - CompoundString other = (CompoundString) obj; - return (mIsNoOp == other.mIsNoOp) - && Objects.equals(mSubstrings, other.mSubstrings) - && Objects.equals(mArgs, other.mArgs); - } - } -} diff --git a/quickstep/src/com/android/quickstep/util/ActivityInitListener.java b/quickstep/src/com/android/quickstep/util/ActivityInitListener.java deleted file mode 100644 index 5efbb4074a..0000000000 --- a/quickstep/src/com/android/quickstep/util/ActivityInitListener.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.quickstep.util; - -import com.android.launcher3.BaseActivity; -import com.android.launcher3.util.ActivityTracker; -import com.android.launcher3.util.ActivityTracker.SchedulerCallback; - -import java.util.function.BiPredicate; - -public class ActivityInitListener<T extends BaseActivity> implements - SchedulerCallback<T> { - - private BiPredicate<T, Boolean> mOnInitListener; - private final ActivityTracker<T> mActivityTracker; - - private boolean mIsRegistered = false; - - /** - * @param onInitListener a callback made when the activity is initialized. The callback should - * return true to continue receiving callbacks (ie. for if the activity is - * recreated). - */ - public ActivityInitListener(BiPredicate<T, Boolean> onInitListener, - ActivityTracker<T> tracker) { - mOnInitListener = onInitListener; - mActivityTracker = tracker; - } - - @Override - public final boolean init(T activity, boolean alreadyOnHome) { - if (!mIsRegistered) { - // Don't receive any more updates - return false; - } - return handleInit(activity, alreadyOnHome); - } - - protected boolean handleInit(T activity, boolean alreadyOnHome) { - return mOnInitListener.test(activity, alreadyOnHome); - } - - /** - * Registers the activity-created listener. If the activity is already created, then the - * callback provided in the constructor will be called synchronously. - */ - public void register(String reasonString) { - mIsRegistered = true; - mActivityTracker.registerCallback(this, reasonString); - } - - /** - * After calling this, we won't {@link #init} even when the activity is ready. - */ - public void unregister(String reasonString) { - mActivityTracker.unregisterCallback(this, reasonString); - mIsRegistered = false; - mOnInitListener = null; - } -} diff --git a/quickstep/src/com/android/quickstep/util/AssistStateManager.java b/quickstep/src/com/android/quickstep/util/AssistStateManager.java deleted file mode 100644 index 7acb28da11..0000000000 --- a/quickstep/src/com/android/quickstep/util/AssistStateManager.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.quickstep.util; - -import static com.android.launcher3.util.MainThreadInitializedObject.forOverride; - -import com.android.launcher3.R; -import com.android.launcher3.util.MainThreadInitializedObject; -import com.android.launcher3.util.ResourceBasedOverride; -import com.android.launcher3.util.SafeCloseable; - -import java.io.PrintWriter; -import java.util.Optional; - -/** Class to manage Assistant states. */ -public class AssistStateManager implements ResourceBasedOverride, SafeCloseable { - - public static final MainThreadInitializedObject<AssistStateManager> INSTANCE = - forOverride(AssistStateManager.class, R.string.assist_state_manager_class); - - public AssistStateManager() {} - - /** Return {@code true} if the Settings toggle is enabled. */ - public boolean isSettingsAllEntrypointsEnabled() { - return false; - } - - /** Whether search supports showing on the lockscreen. */ - public boolean supportsShowWhenLocked() { - return false; - } - - /** Whether ContextualSearchService invocation path is available. */ - public boolean isContextualSearchServiceAvailable() { - return false; - } - - /** Get the Launcher overridden long press nav handle duration to trigger Assistant. */ - public Optional<Long> getLPNHDurationMillis() { - return Optional.empty(); - } - - /** - * Get the Launcher overridden long press nav handle touch slop multiplier to trigger Assistant. - */ - public Optional<Float> getLPNHCustomSlopMultiplier() { - return Optional.empty(); - } - - /** Get the Launcher overridden long press home duration to trigger Assistant. */ - public Optional<Long> getLPHDurationMillis() { - return Optional.empty(); - } - - /** Get the Launcher overridden long press home touch slop multiplier to trigger Assistant. */ - public Optional<Float> getLPHCustomSlopMultiplier() { - return Optional.empty(); - } - - /** Get the long press duration data source. */ - public int getDurationDataSource() { - return 0; - } - - /** Get the long press touch slop multiplier data source. */ - public int getSlopDataSource() { - return 0; - } - - /** Get the haptic bit overridden by AGSA. */ - public Optional<Boolean> getShouldPlayHapticOverride() { - return Optional.empty(); - } - - /** Dump states. */ - public void dump(String prefix, PrintWriter writer) {} - - @Override - public void close() {} -} diff --git a/quickstep/src/com/android/quickstep/util/AssistUtils.java b/quickstep/src/com/android/quickstep/util/AssistUtils.java deleted file mode 100644 index 11b6ea7d21..0000000000 --- a/quickstep/src/com/android/quickstep/util/AssistUtils.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.quickstep.util; - -import android.content.Context; - -import com.android.launcher3.R; -import com.android.launcher3.util.ResourceBasedOverride; - -/** Utilities to work with Assistant functionality. */ -public class AssistUtils implements ResourceBasedOverride { - - public AssistUtils() {} - - /** Creates AssistUtils as specified by overrides */ - public static AssistUtils newInstance(Context context) { - return Overrides.getObject(AssistUtils.class, context, R.string.assist_utils_class); - } - - /** @return Array of AssistUtils.INVOCATION_TYPE_* that we want to handle instead of SysUI. */ - public int[] getSysUiAssistOverrideInvocationTypes() { - return new int[0]; - } - - /** - * @return {@code true} if the override was handled, i.e. an assist surface was shown or the - * request should be ignored. {@code false} means the caller should start assist another way. - */ - public boolean tryStartAssistOverride(int invocationType) { - return false; - } -} diff --git a/quickstep/src/com/android/quickstep/util/DesktopTask.java b/quickstep/src/com/android/quickstep/util/DesktopTask.java deleted file mode 100644 index 8d99069c19..0000000000 --- a/quickstep/src/com/android/quickstep/util/DesktopTask.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.quickstep.util; - -import androidx.annotation.NonNull; - -import com.android.quickstep.views.TaskView; -import com.android.systemui.shared.recents.model.Task; - -import java.util.List; - -/** - * A {@link Task} container that can contain N number of tasks that are part of the desktop in - * recent tasks list. - */ -public class DesktopTask extends GroupTask { - - @NonNull - public final List<Task> tasks; - - public DesktopTask(@NonNull List<Task> tasks) { - super(tasks.get(0), null, null, TaskView.Type.DESKTOP); - this.tasks = tasks; - } - - @Override - public boolean containsTask(int taskId) { - for (Task task : tasks) { - if (task.key.id == taskId) { - return true; - } - } - return false; - } - - @Override - public boolean hasMultipleTasks() { - return true; - } - - @Override - @NonNull - public List<Task> getTasks() { - return tasks; - } - - @Override - public DesktopTask copy() { - return new DesktopTask(tasks); - } - - @Override - public String toString() { - return "type=" + taskViewType + " tasks=" + tasks; - } - -} diff --git a/quickstep/src/com/android/quickstep/util/GroupTask.java b/quickstep/src/com/android/quickstep/util/GroupTask.java deleted file mode 100644 index 945ffe31f6..0000000000 --- a/quickstep/src/com/android/quickstep/util/GroupTask.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.quickstep.util; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds; -import com.android.quickstep.views.TaskView; -import com.android.systemui.shared.recents.model.Task; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -/** - * A {@link Task} container that can contain one or two tasks, depending on if the two tasks - * are represented as an app-pair in the recents task list. - */ -public class GroupTask { - @NonNull - public final Task task1; - @Nullable - public final Task task2; - @Nullable - public final SplitBounds mSplitBounds; - @TaskView.Type - public final int taskViewType; - - public GroupTask(@NonNull Task task) { - this(task, null, null); - } - - public GroupTask(@NonNull Task t1, @Nullable Task t2, @Nullable SplitBounds splitBounds) { - this(t1, t2, splitBounds, t2 != null ? TaskView.Type.GROUPED : TaskView.Type.SINGLE); - } - - protected GroupTask(@NonNull Task t1, @Nullable Task t2, @Nullable SplitBounds splitBounds, - @TaskView.Type int taskViewType) { - task1 = t1; - task2 = t2; - mSplitBounds = splitBounds; - this.taskViewType = taskViewType; - } - - public boolean containsTask(int taskId) { - return task1.key.id == taskId || (task2 != null && task2.key.id == taskId); - } - - public boolean hasMultipleTasks() { - return task2 != null; - } - - /** - * Returns a List of all the Tasks in this GroupTask - */ - public List<Task> getTasks() { - if (task2 == null) { - return Collections.singletonList(task1); - } else { - return Arrays.asList(task1, task2); - } - } - - /** - * Create a copy of this instance - */ - public GroupTask copy() { - return new GroupTask( - new Task(task1), - task2 != null ? new Task(task2) : null, - mSplitBounds); - } - - @Override - public String toString() { - return "type=" + taskViewType + " task1=" + task1 + " task2=" + task2; - } - -} diff --git a/quickstep/src/com/android/quickstep/util/TaskGridNavHelper.java b/quickstep/src/com/android/quickstep/util/TaskGridNavHelper.java deleted file mode 100644 index 98d363ef5c..0000000000 --- a/quickstep/src/com/android/quickstep/util/TaskGridNavHelper.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.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/views/ClearAllButton.java b/quickstep/src/com/android/quickstep/views/ClearAllButton.java deleted file mode 100644 index a5db439b4c..0000000000 --- a/quickstep/src/com/android/quickstep/views/ClearAllButton.java +++ /dev/null @@ -1,345 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.quickstep.views; - -import static com.android.launcher3.Flags.enableGridOnlyOverview; -import static com.android.quickstep.util.BorderAnimator.DEFAULT_BORDER_COLOR; - -import android.content.Context; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.Rect; -import android.util.AttributeSet; -import android.util.FloatProperty; -import android.widget.Button; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.android.launcher3.DeviceProfile; -import com.android.launcher3.Flags; -import com.android.launcher3.R; -import com.android.quickstep.orientation.RecentsPagedOrientationHandler; -import com.android.quickstep.util.BorderAnimator; - -import kotlin.Unit; - -import app.lawnchair.font.FontManager; -import app.lawnchair.theme.drawable.DrawableTokens; - -public class ClearAllButton extends Button { - - public static final FloatProperty<ClearAllButton> VISIBILITY_ALPHA = new FloatProperty<ClearAllButton>( - "visibilityAlpha") { - @Override - public Float get(ClearAllButton view) { - return view.mVisibilityAlpha; - } - - @Override - public void setValue(ClearAllButton view, float v) { - view.setVisibilityAlpha(v); - } - }; - - public static final FloatProperty<ClearAllButton> DISMISS_ALPHA = new FloatProperty<ClearAllButton>( - "dismissAlpha") { - @Override - public Float get(ClearAllButton view) { - return view.mDismissAlpha; - } - - @Override - public void setValue(ClearAllButton view, float v) { - view.setDismissAlpha(v); - } - }; - - private final RecentsViewContainer mContainer; - private float mScrollAlpha = 1; - private float mContentAlpha = 1; - private float mVisibilityAlpha = 1; - private float mDismissAlpha = 1; - private float mFullscreenProgress = 1; - private float mGridProgress = 1; - - private boolean mIsRtl; - private float mNormalTranslationPrimary; - private float mFullscreenTranslationPrimary; - private float mGridTranslationPrimary; - private float mGridScrollOffset; - private float mScrollOffsetPrimary; - - private int mSidePadding; - private int mOutlinePadding; - private boolean mBorderEnabled; - @Nullable - private final BorderAnimator mFocusBorderAnimator; - - public ClearAllButton(Context context, AttributeSet attrs) { - super(context, attrs); - mIsRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL; - mContainer = RecentsViewContainer.containerFromContext(context); - FontManager.INSTANCE.get(context).overrideFont(this, attrs); - setBackground(DrawableTokens.BgOverviewClearAllButton.resolve(context)); - if (Flags.enableFocusOutline()) { - TypedArray styledAttrs = context.obtainStyledAttributes(attrs, - R.styleable.ClearAllButton); - Resources resources = getResources(); - mOutlinePadding = resources.getDimensionPixelSize( - R.dimen.recents_clear_all_outline_padding); - mFocusBorderAnimator = BorderAnimator.createSimpleBorderAnimator( - /* borderRadiusPx= */ resources.getDimensionPixelSize( - R.dimen.recents_clear_all_outline_radius), - /* borderWidthPx= */ context.getResources().getDimensionPixelSize( - R.dimen.keyboard_quick_switch_border_width), - /* boundsBuilder= */ this::updateBorderBounds, - /* targetView= */ this, - /* borderColor= */ styledAttrs.getColor( - R.styleable.ClearAllButton_focusBorderColor, - DEFAULT_BORDER_COLOR)); - styledAttrs.recycle(); - } else { - mFocusBorderAnimator = null; - } - } - - private Unit updateBorderBounds(@NonNull Rect bounds) { - bounds.set(0, 0, getWidth(), getHeight()); - // Make the value negative to form a padding between button and outline - bounds.inset(-mOutlinePadding, -mOutlinePadding); - return Unit.INSTANCE; - } - - @Override - public void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) { - super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); - if (mFocusBorderAnimator != null && mBorderEnabled) { - mFocusBorderAnimator.setBorderVisibility(gainFocus, /* animated= */ true); - } - } - - /** - * Enable or disable showing border on focus change - */ - public void setBorderEnabled(boolean enabled) { - if (mBorderEnabled == enabled) { - return; - } - - mBorderEnabled = enabled; - if (mFocusBorderAnimator != null) { - mFocusBorderAnimator.setBorderVisibility(/* visible= */ - enabled && isFocused(), /* animated= */true); - } - } - - @Override - public void draw(Canvas canvas) { - if (mFocusBorderAnimator != null) { - mFocusBorderAnimator.drawBorder(canvas); - } - super.draw(canvas); - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - RecentsPagedOrientationHandler orientationHandler = getRecentsView().getPagedOrientationHandler(); - mSidePadding = orientationHandler.getClearAllSidePadding(getRecentsView(), mIsRtl); - } - - private RecentsView getRecentsView() { - return (RecentsView) getParent(); - } - - @Override - public void onRtlPropertiesChanged(int layoutDirection) { - super.onRtlPropertiesChanged(layoutDirection); - mIsRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL; - } - - @Override - public boolean hasOverlappingRendering() { - return false; - } - - public float getScrollAlpha() { - return mScrollAlpha; - } - - public void setContentAlpha(float alpha) { - if (mContentAlpha != alpha) { - mContentAlpha = alpha; - updateAlpha(); - } - } - - public void setVisibilityAlpha(float alpha) { - if (mVisibilityAlpha != alpha) { - mVisibilityAlpha = alpha; - updateAlpha(); - } - } - - public void setDismissAlpha(float alpha) { - if (mDismissAlpha != alpha) { - mDismissAlpha = alpha; - updateAlpha(); - } - } - - public void onRecentsViewScroll(int scroll, boolean gridEnabled) { - RecentsView recentsView = getRecentsView(); - if (recentsView == null) { - return; - } - - RecentsPagedOrientationHandler orientationHandler = recentsView.getPagedOrientationHandler(); - float orientationSize = orientationHandler.getPrimaryValue(getWidth(), getHeight()); - if (orientationSize == 0) { - return; - } - - int clearAllScroll = recentsView.getClearAllScroll(); - int adjustedScrollFromEdge = Math.abs(scroll - clearAllScroll); - float shift = Math.min(adjustedScrollFromEdge, orientationSize); - mNormalTranslationPrimary = mIsRtl ? -shift : shift; - if (!gridEnabled) { - mNormalTranslationPrimary += mSidePadding; - } - applyPrimaryTranslation(); - applySecondaryTranslation(); - float clearAllSpacing = recentsView.getPageSpacing() + recentsView.getClearAllExtraPageSpacing(); - clearAllSpacing = mIsRtl ? -clearAllSpacing : clearAllSpacing; - mScrollAlpha = Math.max((clearAllScroll + clearAllSpacing - scroll) / clearAllSpacing, 0); - updateAlpha(); - } - - private void updateAlpha() { - final float alpha = mScrollAlpha * mContentAlpha * mVisibilityAlpha * mDismissAlpha; - setAlpha(alpha); - setClickable(Math.min(alpha, 1) == 1); - } - - public void setFullscreenTranslationPrimary(float fullscreenTranslationPrimary) { - mFullscreenTranslationPrimary = fullscreenTranslationPrimary; - applyPrimaryTranslation(); - } - - public void setGridTranslationPrimary(float gridTranslationPrimary) { - mGridTranslationPrimary = gridTranslationPrimary; - applyPrimaryTranslation(); - } - - public void setGridScrollOffset(float gridScrollOffset) { - mGridScrollOffset = gridScrollOffset; - } - - public void setScrollOffsetPrimary(float scrollOffsetPrimary) { - mScrollOffsetPrimary = scrollOffsetPrimary; - } - - public float getScrollAdjustment(boolean fullscreenEnabled, boolean gridEnabled) { - float scrollAdjustment = 0; - if (fullscreenEnabled) { - scrollAdjustment += mFullscreenTranslationPrimary; - } - if (gridEnabled) { - scrollAdjustment += mGridTranslationPrimary + mGridScrollOffset; - } - scrollAdjustment += mScrollOffsetPrimary; - return scrollAdjustment; - } - - public float getOffsetAdjustment(boolean fullscreenEnabled, boolean gridEnabled) { - return getScrollAdjustment(fullscreenEnabled, gridEnabled); - } - - /** - * Adjust translation when this TaskView is about to be shown fullscreen. - * - * @param progress: 0 = no translation; 1 = translate according to TaskVIew - * translations. - */ - public void setFullscreenProgress(float progress) { - mFullscreenProgress = progress; - applyPrimaryTranslation(); - } - - /** - * Moves ClearAllButton between carousel and 2 row grid. - * - * @param gridProgress 0 = carousel; 1 = 2 row grid. - */ - public void setGridProgress(float gridProgress) { - mGridProgress = gridProgress; - applyPrimaryTranslation(); - } - - private void applyPrimaryTranslation() { - RecentsView recentsView = getRecentsView(); - if (recentsView == null) { - return; - } - - RecentsPagedOrientationHandler orientationHandler = recentsView.getPagedOrientationHandler(); - orientationHandler.getPrimaryViewTranslate().set(this, - orientationHandler.getPrimaryValue(0f, getOriginalTranslationY()) - + mNormalTranslationPrimary + getFullscreenTrans( - mFullscreenTranslationPrimary) - + getGridTrans(mGridTranslationPrimary)); - } - - private void applySecondaryTranslation() { - RecentsView recentsView = getRecentsView(); - if (recentsView == null) { - return; - } - - RecentsPagedOrientationHandler orientationHandler = recentsView.getPagedOrientationHandler(); - orientationHandler.getSecondaryViewTranslate().set(this, - orientationHandler.getSecondaryValue(0f, getOriginalTranslationY())); - } - - private float getFullscreenTrans(float endTranslation) { - return mFullscreenProgress > 0 ? endTranslation : 0; - } - - private float getGridTrans(float endTranslation) { - return mGridProgress > 0 ? endTranslation : 0; - } - - /** - * Get the Y translation that is set in the original layout position, before - * scrolling. - */ - private float getOriginalTranslationY() { - DeviceProfile deviceProfile = mContainer.getDeviceProfile(); - if (deviceProfile.isTablet) { - if (enableGridOnlyOverview()) { - return (getRecentsView().getLastComputedTaskSize().height() - + deviceProfile.overviewTaskThumbnailTopMarginPx) / 2.0f - + deviceProfile.overviewRowSpacing; - } else { - return deviceProfile.overviewRowSpacing; - } - } - return deviceProfile.overviewTaskThumbnailTopMarginPx / 2.0f; - } -} diff --git a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java deleted file mode 100644 index a9236197df..0000000000 --- a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java +++ /dev/null @@ -1,443 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.quickstep.views; - -import static android.provider.Settings.ACTION_APP_USAGE_SETTINGS; - -import static com.android.launcher3.Utilities.prefixTextWithIcon; -import static com.android.launcher3.util.Executors.ORDERED_BG_EXECUTOR; - -import android.app.ActivityOptions; -import android.content.ActivityNotFoundException; -import android.content.Context; -import android.content.Intent; -import android.content.pm.LauncherApps; -import android.content.pm.LauncherApps.AppUsageLimit; -import android.graphics.Outline; -import android.graphics.Paint; -import android.icu.text.MeasureFormat; -import android.icu.text.MeasureFormat.FormatWidth; -import android.icu.util.Measure; -import android.icu.util.MeasureUnit; -import android.os.UserHandle; -import android.util.Log; -import android.util.Pair; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewOutlineProvider; -import android.view.accessibility.AccessibilityNodeInfo; -import android.widget.FrameLayout; -import android.widget.TextView; - -import androidx.annotation.IntDef; -import androidx.annotation.Nullable; -import androidx.annotation.StringRes; - -import com.android.launcher3.DeviceProfile; -import com.android.launcher3.R; -import com.android.launcher3.Utilities; -import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds; -import com.android.quickstep.TaskUtils; -import com.android.quickstep.orientation.RecentsPagedOrientationHandler; -import com.android.systemui.shared.recents.model.Task; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.time.Duration; -import java.util.Locale; - -public final class DigitalWellBeingToast { - - private static final float THRESHOLD_LEFT_ICON_ONLY = 0.4f; - private static final float THRESHOLD_RIGHT_ICON_ONLY = 0.6f; - - /** Will span entire width of taskView with full text */ - private static final int SPLIT_BANNER_FULLSCREEN = 0; - /** Used for grid task view, only showing icon and time */ - private static final int SPLIT_GRID_BANNER_LARGE = 1; - /** Used for grid task view, only showing icon */ - private static final int SPLIT_GRID_BANNER_SMALL = 2; - - @IntDef(value = { - SPLIT_BANNER_FULLSCREEN, - SPLIT_GRID_BANNER_LARGE, - SPLIT_GRID_BANNER_SMALL, - }) - @Retention(RetentionPolicy.SOURCE) - @interface SplitBannerConfig { - } - - static final Intent OPEN_APP_USAGE_SETTINGS_TEMPLATE = new Intent(ACTION_APP_USAGE_SETTINGS); - static final int MINUTE_MS = 60000; - - private static final String TAG = "DigitalWellBeingToast"; - - private final RecentsViewContainer mContainer; - private final TaskView mTaskView; - private final LauncherApps mLauncherApps; - - private final int mBannerHeight; - - private Task mTask; - private boolean mHasLimit; - - private long mAppRemainingTimeMs; - @Nullable - private View mBanner; - private ViewOutlineProvider mOldBannerOutlineProvider; - private float mBannerOffsetPercentage; - @Nullable - private SplitBounds mSplitBounds; - private float mSplitOffsetTranslationY; - private float mSplitOffsetTranslationX; - - private boolean mIsDestroyed = false; - - public DigitalWellBeingToast(RecentsViewContainer container, TaskView taskView) { - mContainer = container; - mTaskView = taskView; - mLauncherApps = container.asContext().getSystemService(LauncherApps.class); - mBannerHeight = container.asContext().getResources().getDimensionPixelSize( - R.dimen.digital_wellbeing_toast_height); - } - - private void setNoLimit() { - mHasLimit = false; - mTaskView.setContentDescription(mTask.titleDescription); - replaceBanner(null); - mAppRemainingTimeMs = -1; - } - - private void setLimit(long appUsageLimitTimeMs, long appRemainingTimeMs) { - mAppRemainingTimeMs = appRemainingTimeMs; - mHasLimit = true; - TextView toast = mContainer.getViewCache().getView(R.layout.digital_wellbeing_toast, - mContainer.asContext(), mTaskView); - toast.setText(prefixTextWithIcon(mContainer.asContext(), R.drawable.ic_hourglass_top, - getText())); - toast.setOnClickListener(this::openAppUsageSettings); - replaceBanner(toast); - - mTaskView.setContentDescription( - getContentDescriptionForTask(mTask, appUsageLimitTimeMs, appRemainingTimeMs)); - } - - public String getText() { - return getText(mAppRemainingTimeMs, false /* forContentDesc */); - } - - public boolean hasLimit() { - return mHasLimit; - } - - public void initialize(Task task) { - if (mIsDestroyed) { - throw new IllegalStateException("Cannot re-initialize a destroyed toast"); - } - mTask = task; - ORDERED_BG_EXECUTOR.execute(() -> { - AppUsageLimit usageLimit = null; - try { - usageLimit = mLauncherApps.getAppUsageLimit( - mTask.getTopComponent().getPackageName(), - UserHandle.of(mTask.key.userId)); - } catch (Exception e) { - Log.e(TAG, "Error initializing digital well being toast", e); - } - final long appUsageLimitTimeMs = usageLimit != null ? usageLimit.getTotalUsageLimit() : -1; - final long appRemainingTimeMs = usageLimit != null ? usageLimit.getUsageRemaining() : -1; - - mTaskView.post(() -> { - if (mIsDestroyed) { - return; - } - if (appUsageLimitTimeMs < 0 || appRemainingTimeMs < 0) { - setNoLimit(); - } else { - setLimit(appUsageLimitTimeMs, appRemainingTimeMs); - } - }); - }); - } - - /** - * Mark the DWB toast as destroyed and remove banner from TaskView. - */ - public void destroy() { - mIsDestroyed = true; - mTaskView.post(() -> replaceBanner(null)); - } - - public void setSplitBounds(@Nullable SplitBounds splitBounds) { - mSplitBounds = splitBounds; - } - - private @SplitBannerConfig int getSplitBannerConfig() { - if (mSplitBounds == null - || !mContainer.getDeviceProfile().isTablet - || mTaskView.isFocusedTask()) { - return SPLIT_BANNER_FULLSCREEN; - } - - // For portrait grid only height of task changes, not width. So we keep the text - // the same - if (!mContainer.getDeviceProfile().isLeftRightSplit) { - return SPLIT_GRID_BANNER_LARGE; - } - - // For landscape grid, for 30% width we only show icon, otherwise show icon and - // time - if (mTask.key.id == mSplitBounds.leftTopTaskId) { - return mSplitBounds.leftTaskPercent < THRESHOLD_LEFT_ICON_ONLY - ? SPLIT_GRID_BANNER_SMALL - : SPLIT_GRID_BANNER_LARGE; - } else { - return mSplitBounds.leftTaskPercent > THRESHOLD_RIGHT_ICON_ONLY - ? SPLIT_GRID_BANNER_SMALL - : SPLIT_GRID_BANNER_LARGE; - } - } - - private String getReadableDuration( - Duration duration, - @StringRes int durationLessThanOneMinuteStringId) { - int hours = Math.toIntExact(duration.toHours()); - int minutes = Math.toIntExact(duration.minusHours(hours).toMinutes()); - - // Apply FormatWidth.WIDE if both the hour part and the minute part are - // non-zero. - if (hours > 0 && minutes > 0) { - return MeasureFormat.getInstance(Locale.getDefault(), FormatWidth.NARROW) - .formatMeasures( - new Measure(hours, MeasureUnit.HOUR), - new Measure(minutes, MeasureUnit.MINUTE)); - } - - // Apply FormatWidth.WIDE if only the hour part is non-zero (unless forced). - if (hours > 0) { - return MeasureFormat.getInstance(Locale.getDefault(), FormatWidth.WIDE).formatMeasures( - new Measure(hours, MeasureUnit.HOUR)); - } - - // Apply FormatWidth.WIDE if only the minute part is non-zero (unless forced). - if (minutes > 0) { - return MeasureFormat.getInstance(Locale.getDefault(), FormatWidth.WIDE).formatMeasures( - new Measure(minutes, MeasureUnit.MINUTE)); - } - - // Use a specific string for usage less than one minute but non-zero. - if (duration.compareTo(Duration.ZERO) > 0) { - return mContainer.asContext().getString(durationLessThanOneMinuteStringId); - } - - // Otherwise, return 0-minute string. - return MeasureFormat.getInstance(Locale.getDefault(), FormatWidth.WIDE).formatMeasures( - new Measure(0, MeasureUnit.MINUTE)); - } - - /** - * Returns text to show for the banner depending on - * {@link #getSplitBannerConfig()} - * If {@param forContentDesc} is {@code true}, this will always return the full - * string corresponding to {@link #SPLIT_BANNER_FULLSCREEN} - */ - private String getText(long remainingTime, boolean forContentDesc) { - final Duration duration = Duration.ofMillis( - remainingTime > MINUTE_MS ? (remainingTime + MINUTE_MS - 1) / MINUTE_MS * MINUTE_MS : remainingTime); - String readableDuration = getReadableDuration(duration, - R.string.shorter_duration_less_than_one_minute - /* forceFormatWidth */); - @SplitBannerConfig - int splitBannerConfig = getSplitBannerConfig(); - if (forContentDesc || splitBannerConfig == SPLIT_BANNER_FULLSCREEN) { - return mContainer.asContext().getString( - R.string.time_left_for_app, - readableDuration); - } - - if (splitBannerConfig == SPLIT_GRID_BANNER_SMALL) { - // show no text - return ""; - } else { // SPLIT_GRID_BANNER_LARGE - // only show time - return readableDuration; - } - } - - public void openAppUsageSettings(View view) { - final Intent intent = new Intent(OPEN_APP_USAGE_SETTINGS_TEMPLATE) - .putExtra(Intent.EXTRA_PACKAGE_NAME, - mTask.getTopComponent().getPackageName()) - .addFlags( - Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - try { - final RecentsViewContainer container = RecentsViewContainer.containerFromContext(view.getContext()); - final ActivityOptions options = ActivityOptions.makeScaleUpAnimation( - view, 0, 0, - view.getWidth(), view.getHeight()); - container.asContext().startActivity(intent, options.toBundle()); - - // TODO: add WW logging on the app usage settings click. - } catch (ActivityNotFoundException e) { - Log.e(TAG, "Failed to open app usage settings for task " - + mTask.getTopComponent().getPackageName(), e); - } - } - - private String getContentDescriptionForTask( - Task task, long appUsageLimitTimeMs, long appRemainingTimeMs) { - return appUsageLimitTimeMs >= 0 && appRemainingTimeMs >= 0 ? mContainer.asContext().getString( - R.string.task_contents_description_with_remaining_time, - task.titleDescription, - getText(appRemainingTimeMs, true /* forContentDesc */)) : task.titleDescription; - } - - private void replaceBanner(@Nullable View view) { - resetOldBanner(); - setBanner(view); - } - - private void resetOldBanner() { - if (mBanner != null) { - mBanner.setOutlineProvider(mOldBannerOutlineProvider); - mTaskView.removeView(mBanner); - mBanner.setOnClickListener(null); - mContainer.getViewCache().recycleView(R.layout.digital_wellbeing_toast, mBanner); - } - } - - private void setBanner(@Nullable View view) { - mBanner = view; - if (mBanner != null && mTaskView.getRecentsView() != null) { - setupAndAddBanner(); - setBannerOutline(); - } - } - - private void setupAndAddBanner() { - FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) mBanner.getLayoutParams(); - DeviceProfile deviceProfile = mContainer.getDeviceProfile(); - layoutParams.bottomMargin = ((ViewGroup.MarginLayoutParams) mTaskView.getFirstThumbnailViewDeprecated() - .getLayoutParams()).bottomMargin; - RecentsPagedOrientationHandler orientationHandler = mTaskView.getPagedOrientationHandler(); - Pair<Float, Float> translations = orientationHandler - .getDwbLayoutTranslations(mTaskView.getMeasuredWidth(), - mTaskView.getMeasuredHeight(), mSplitBounds, deviceProfile, - mTaskView.getThumbnailViews(), mTask.key.id, mBanner); - mSplitOffsetTranslationX = translations.first; - mSplitOffsetTranslationY = translations.second; - updateTranslationY(); - updateTranslationX(); - mTaskView.addView(mBanner); - } - - private void setBannerOutline() { - // TODO(b\273367585) to investigate why mBanner.getOutlineProvider() can be null - mOldBannerOutlineProvider = mBanner.getOutlineProvider() != null - ? mBanner.getOutlineProvider() - : ViewOutlineProvider.BACKGROUND; - - mBanner.setOutlineProvider(new ViewOutlineProvider() { - @Override - public void getOutline(View view, Outline outline) { - mOldBannerOutlineProvider.getOutline(view, outline); - float verticalTranslation = -view.getTranslationY() + mSplitOffsetTranslationY; - outline.offset(0, Math.round(verticalTranslation)); - } - }); - mBanner.setClipToOutline(true); - } - - void updateBannerOffset(float offsetPercentage) { - if (mBannerOffsetPercentage != offsetPercentage) { - mBannerOffsetPercentage = offsetPercentage; - if (mBanner != null) { - updateTranslationY(); - mBanner.invalidateOutline(); - } - } - } - - private void updateTranslationY() { - if (mBanner == null) { - return; - } - - mBanner.setTranslationY( - (mBannerOffsetPercentage * mBannerHeight) + mSplitOffsetTranslationY); - } - - private void updateTranslationX() { - if (mBanner == null) { - return; - } - - mBanner.setTranslationX(mSplitOffsetTranslationX); - } - - void setBannerColorTint(int color, float amount) { - if (mBanner == null) { - return; - } - if (amount == 0) { - mBanner.setLayerType(View.LAYER_TYPE_NONE, null); - } - Paint layerPaint = new Paint(); - layerPaint.setColorFilter(Utilities.makeColorTintingColorFilter(color, amount)); - mBanner.setLayerType(View.LAYER_TYPE_HARDWARE, layerPaint); - mBanner.setLayerPaint(layerPaint); - } - - void setBannerVisibility(int visibility) { - if (mBanner == null) { - return; - } - - mBanner.setVisibility(visibility); - } - - private int getAccessibilityActionId() { - return (mSplitBounds != null - && mSplitBounds.rightBottomTaskId == mTask.key.id) - ? R.id.action_digital_wellbeing_bottom_right - : R.id.action_digital_wellbeing_top_left; - } - - @Nullable - public AccessibilityNodeInfo.AccessibilityAction getDWBAccessibilityAction() { - if (!hasLimit()) { - return null; - } - - Context context = mContainer.asContext(); - String label = (mTaskView.containsMultipleTasks()) - ? context.getString( - R.string.split_app_usage_settings, - TaskUtils.getTitle(context, mTask)) - : context.getString(R.string.accessibility_app_usage_settings); - return new AccessibilityNodeInfo.AccessibilityAction(getAccessibilityActionId(), label); - } - - public boolean handleAccessibilityAction(int action) { - if (getAccessibilityActionId() == action) { - openAppUsageSettings(mTaskView); - return true; - } else { - return false; - } - } -} diff --git a/quickstep/src/com/android/quickstep/views/IconAppChipView.java b/quickstep/src/com/android/quickstep/views/IconAppChipView.java deleted file mode 100644 index ba42594e99..0000000000 --- a/quickstep/src/com/android/quickstep/views/IconAppChipView.java +++ /dev/null @@ -1,464 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.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<View> mViewTranslationX; - private final MultiPropertyFactory<View> mViewTranslationY; - - /** - * Gets the view split x-axis translation - */ - public MultiPropertyFactory<View>.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<View>.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<View>.MultiProperty getMenuTranslationX() { - return mViewTranslationX.get(INDEX_MENU_TRANSLATION); - } - - /** - * Gets the menu y-axis translation for split task - */ - public MultiPropertyFactory<View>.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 deleted file mode 100644 index bb4a7ecda6..0000000000 --- a/quickstep/src/com/android/quickstep/views/IconView.java +++ /dev/null @@ -1,209 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.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/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java deleted file mode 100644 index 1eba5bfe37..0000000000 --- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java +++ /dev/null @@ -1,435 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.quickstep.views; - -import static com.android.app.animation.Interpolators.EMPHASIZED; -import static com.android.launcher3.Flags.enableOverviewIconMenu; -import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE; -import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT; -import static com.android.quickstep.views.TaskThumbnailViewDeprecated.DIM_ALPHA; - -import android.animation.Animator; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.animation.ValueAnimator; -import android.content.Context; -import android.graphics.Outline; -import android.graphics.Rect; -import android.graphics.drawable.GradientDrawable; -import android.graphics.drawable.ShapeDrawable; -import android.graphics.drawable.shapes.RectShape; -import android.util.AttributeSet; -import android.view.Gravity; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewOutlineProvider; -import android.widget.LinearLayout; -import android.widget.TextView; - -import androidx.annotation.Nullable; - -import com.android.app.animation.Interpolators; -import com.android.launcher3.AbstractFloatingView; -import com.android.launcher3.DeviceProfile; -import com.android.launcher3.R; -import com.android.launcher3.anim.AnimationSuccessListener; -import com.android.launcher3.anim.RoundedRectRevealOutlineProvider; -import com.android.launcher3.popup.SystemShortcut; -import com.android.launcher3.views.BaseDragLayer; -import com.android.quickstep.TaskOverlayFactory; -import com.android.quickstep.TaskUtils; -import com.android.quickstep.orientation.RecentsPagedOrientationHandler; -import com.android.quickstep.util.TaskCornerRadius; -import com.android.quickstep.views.TaskView.TaskContainer; - -import app.lawnchair.theme.drawable.DrawableTokens; - -/** - * Contains options for a recent task when long-pressing its icon. - */ -public class TaskMenuView extends AbstractFloatingView { - - private static final Rect sTempRect = new Rect(); - - private static final int REVEAL_OPEN_DURATION = enableOverviewIconMenu() ? 417 : 150; - private static final int REVEAL_CLOSE_DURATION = enableOverviewIconMenu() ? 333 : 100; - - private RecentsViewContainer mContainer; - private TextView mTaskName; - @Nullable - private AnimatorSet mOpenCloseAnimator; - @Nullable - private ValueAnimator mRevealAnimator; - @Nullable - private Runnable mOnClosingStartCallback; - private TaskView mTaskView; - private TaskContainer mTaskContainer; - private LinearLayout mOptionLayout; - private float mMenuTranslationYBeforeOpen; - private float mMenuTranslationXBeforeOpen; - - public TaskMenuView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public TaskMenuView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - - mContainer = RecentsViewContainer.containerFromContext(context); - setClipToOutline(true); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mTaskName = findViewById(R.id.task_name); - mTaskName.setBackground(DrawableTokens.TaskMenuItemBg.resolve(getContext())); - mOptionLayout = findViewById(R.id.menu_option_layout); - } - - @Override - public boolean onControllerInterceptTouchEvent(MotionEvent ev) { - if (ev.getAction() == MotionEvent.ACTION_DOWN) { - BaseDragLayer dl = mContainer.getDragLayer(); - if (!dl.isEventOverView(this, ev)) { - // TODO: log this once we have a new container type for it? - close(true); - return true; - } - } - return false; - } - - @Override - protected void handleClose(boolean animate) { - animateClose(); - } - - @Override - protected boolean isOfType(int type) { - return (type & TYPE_TASK_MENU) != 0; - } - - @Override - public ViewOutlineProvider getOutlineProvider() { - return new ViewOutlineProvider() { - @Override - public void getOutline(View view, Outline outline) { - outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), - TaskCornerRadius.get(view.getContext())); - } - }; - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - if (!(enableOverviewIconMenu() - && ((RecentsView) mContainer.getOverviewPanel()).isOnGridBottomRow(mTaskView))) { - // TODO(b/326952853): Cap menu height for grid bottom row in a way that doesn't - // break - // additionalTranslationY. - int maxMenuHeight = calculateMaxHeight(); - if (MeasureSpec.getSize(heightMeasureSpec) > maxMenuHeight) { - heightMeasureSpec = MeasureSpec.makeMeasureSpec(maxMenuHeight, MeasureSpec.AT_MOST); - } - } - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } - - public void onRotationChanged() { - if (mOpenCloseAnimator != null && mOpenCloseAnimator.isRunning()) { - mOpenCloseAnimator.end(); - } - if (mIsOpen) { - mOptionLayout.removeAllViews(); - if (enableOverviewIconMenu() || !populateAndLayoutMenu()) { - close(false); - } - } - } - - /** - * Show a task menu for the given taskContainer. - */ - public static boolean showForTask(TaskContainer taskContainer, - @Nullable Runnable onClosingStartCallback) { - RecentsViewContainer container = RecentsViewContainer.containerFromContext( - taskContainer.getTaskView().getContext()); - final TaskMenuView taskMenuView = (TaskMenuView) container.getLayoutInflater().inflate( - R.layout.task_menu, container.getDragLayer(), false); - taskMenuView.setOnClosingStartCallback(onClosingStartCallback); - return taskMenuView.populateAndShowForTask(taskContainer); - } - - /** - * Show a task menu for the given taskContainer. - */ - public static boolean showForTask(TaskContainer taskContainer) { - return showForTask(taskContainer, null); - } - - private boolean populateAndShowForTask(TaskContainer taskContainer) { - if (isAttachedToWindow()) { - return false; - } - mContainer.getDragLayer().addView(this); - mTaskView = taskContainer.getTaskView(); - mTaskContainer = taskContainer; - if (!populateAndLayoutMenu()) { - return false; - } - post(this::animateOpen); - return true; - } - - /** - * @return true if successfully able to populate task view menu, false otherwise - */ - private boolean populateAndLayoutMenu() { - addMenuOptions(mTaskContainer); - orientAroundTaskView(mTaskContainer); - return true; - } - - private void addMenuOptions(TaskContainer taskContainer) { - if (enableOverviewIconMenu()) { - removeView(mTaskName); - } else { - mTaskName.setText(TaskUtils.getTitle(getContext(), taskContainer.getTask())); - mTaskName.setOnClickListener(v -> close(true)); - } - TaskOverlayFactory.getEnabledShortcuts(mTaskView, taskContainer) - .forEach(this::addMenuOption); - } - - private void addMenuOption(SystemShortcut menuOption) { - LinearLayout menuOptionView = (LinearLayout) mContainer.getLayoutInflater().inflate( - R.layout.task_view_menu_option, this, false); - if (enableOverviewIconMenu()) { - ((GradientDrawable) menuOptionView.getBackground()).setCornerRadius(0); - } - menuOption.setIconAndLabelFor( - menuOptionView.findViewById(R.id.icon), menuOptionView.findViewById(R.id.text)); - LayoutParams lp = (LayoutParams) menuOptionView.getLayoutParams(); - mTaskView.getPagedOrientationHandler().setLayoutParamsForTaskMenuOptionItem(lp, - menuOptionView, mContainer.getDeviceProfile()); - // Set an onClick listener on each menu option. The onClick method is - // responsible for - // ending LiveTile mode on the thumbnail if needed. - menuOptionView.setOnClickListener(menuOption::onClick); - mOptionLayout.addView(menuOptionView); - } - - private void orientAroundTaskView(TaskContainer taskContainer) { - RecentsView recentsView = mContainer.getOverviewPanel(); - RecentsPagedOrientationHandler orientationHandler = recentsView.getPagedOrientationHandler(); - measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); - - // Get Position - DeviceProfile deviceProfile = mContainer.getDeviceProfile(); - mContainer.getDragLayer().getDescendantRectRelativeToSelf( - enableOverviewIconMenu() - ? getIconView().findViewById(R.id.icon_view_menu_anchor) - : taskContainer.getThumbnailViewDeprecated(), - sTempRect); - Rect insets = mContainer.getDragLayer().getInsets(); - BaseDragLayer.LayoutParams params = (BaseDragLayer.LayoutParams) getLayoutParams(); - params.width = orientationHandler.getTaskMenuWidth( - taskContainer.getThumbnailViewDeprecated(), deviceProfile, - taskContainer.getStagePosition()); - // Gravity set to Left instead of Start as sTempRect.left measures Left distance - // not Start - params.gravity = Gravity.LEFT; - setLayoutParams(params); - setScaleX(mTaskView.getScaleX()); - setScaleY(mTaskView.getScaleY()); - - // Set divider spacing - ShapeDrawable divider = new ShapeDrawable(new RectShape()); - divider.getPaint().setColor(getResources().getColor(android.R.color.transparent)); - int dividerSpacing = (int) getResources().getDimension(R.dimen.task_menu_spacing); - mOptionLayout.setShowDividers( - enableOverviewIconMenu() ? SHOW_DIVIDER_NONE : SHOW_DIVIDER_MIDDLE); - - orientationHandler.setTaskOptionsMenuLayoutOrientation( - deviceProfile, mOptionLayout, dividerSpacing, divider); - float thumbnailAlignedX = sTempRect.left - insets.left; - float thumbnailAlignedY = sTempRect.top - insets.top; - - // Changing pivot to make computations easier - // NOTE: Changing the pivots means the rotated view gets rotated about the new - // pivots set, - // which would render the X and Y position set here incorrect - setPivotX(0); - setPivotY(0); - setRotation(orientationHandler.getDegreesRotated()); - - if (enableOverviewIconMenu()) { - setTranslationX(thumbnailAlignedX); - setTranslationY(thumbnailAlignedY); - } else { - // Margin that insets the menuView inside the taskView - float taskInsetMargin = getResources().getDimension(R.dimen.task_card_margin); - setTranslationX(orientationHandler.getTaskMenuX(thumbnailAlignedX, - mTaskContainer.getThumbnailViewDeprecated(), deviceProfile, taskInsetMargin, - getIconView())); - setTranslationY(orientationHandler.getTaskMenuY( - thumbnailAlignedY, mTaskContainer.getThumbnailViewDeprecated(), - mTaskContainer.getStagePosition(), this, taskInsetMargin, - getIconView())); - } - } - - private void animateOpen() { - mMenuTranslationYBeforeOpen = getTranslationY(); - mMenuTranslationXBeforeOpen = getTranslationX(); - animateOpenOrClosed(false); - mIsOpen = true; - } - - private View getIconView() { - return mTaskContainer.getIconView().asView(); - } - - private void animateClose() { - animateOpenOrClosed(true); - } - - private void animateOpenOrClosed(boolean closing) { - if (mOpenCloseAnimator != null && mOpenCloseAnimator.isRunning()) { - mOpenCloseAnimator.cancel(); - } - mOpenCloseAnimator = new AnimatorSet(); - // If we're opening, we just start from the beginning as a new `TaskMenuView` is - // created - // each time we do the open animation so there will never be a partial value - // here. - float revealAnimationStartProgress = 0f; - if (closing && mRevealAnimator != null) { - revealAnimationStartProgress = 1f - mRevealAnimator.getAnimatedFraction(); - } - mRevealAnimator = createOpenCloseOutlineProvider() - .createRevealAnimator(this, closing, revealAnimationStartProgress); - mRevealAnimator.setInterpolator(enableOverviewIconMenu() ? Interpolators.EMPHASIZED - : Interpolators.DECELERATE); - - if (enableOverviewIconMenu()) { - IconAppChipView iconAppChip = (IconAppChipView) mTaskContainer.getIconView().asView(); - - float additionalTranslationY = 0; - if (((RecentsView) mContainer.getOverviewPanel()).isOnGridBottomRow(mTaskView)) { - // Animate menu up for enough room to display full menu when task on bottom row. - float menuBottom = getHeight() + mMenuTranslationYBeforeOpen; - float taskBottom = mTaskView.getHeight() + mTaskView.getPersistentTranslationY(); - float taskbarTop = mContainer.getDeviceProfile().heightPx - - mContainer.getDeviceProfile().getOverviewActionsClaimedSpaceBelow(); - float midpoint = (taskBottom + taskbarTop) / 2f; - additionalTranslationY = -Math.max(menuBottom - midpoint, 0); - } - ObjectAnimator translationYAnim = ObjectAnimator.ofFloat(this, TRANSLATION_Y, - closing ? mMenuTranslationYBeforeOpen - : mMenuTranslationYBeforeOpen + additionalTranslationY); - translationYAnim.setInterpolator(EMPHASIZED); - - ObjectAnimator menuTranslationYAnim = ObjectAnimator.ofFloat( - iconAppChip.getMenuTranslationY(), - MULTI_PROPERTY_VALUE, closing ? 0 : additionalTranslationY); - menuTranslationYAnim.setInterpolator(EMPHASIZED); - - float additionalTranslationX = 0; - if (mContainer.getDeviceProfile().isLandscape - && mTaskContainer.getStagePosition() == STAGE_POSITION_BOTTOM_OR_RIGHT) { - // Animate menu and icon when split task would display off the side of the - // screen. - additionalTranslationX = Math.max( - getTranslationX() + getWidth() - (mContainer.getDeviceProfile().widthPx - - getResources().getDimensionPixelSize( - R.dimen.task_menu_edge_padding) * 2), - 0); - } - - ObjectAnimator translationXAnim = ObjectAnimator.ofFloat(this, TRANSLATION_X, - closing ? mMenuTranslationXBeforeOpen - : mMenuTranslationXBeforeOpen - additionalTranslationX); - translationXAnim.setInterpolator(EMPHASIZED); - - ObjectAnimator menuTranslationXAnim = ObjectAnimator.ofFloat( - iconAppChip.getMenuTranslationX(), - MULTI_PROPERTY_VALUE, closing ? 0 : -additionalTranslationX); - menuTranslationXAnim.setInterpolator(EMPHASIZED); - - mOpenCloseAnimator.playTogether(translationYAnim, translationXAnim, - menuTranslationXAnim, menuTranslationYAnim); - } - mOpenCloseAnimator.playTogether(mRevealAnimator, - ObjectAnimator.ofFloat( - mTaskContainer.getThumbnailViewDeprecated(), DIM_ALPHA, - closing ? 0 : TaskView.MAX_PAGE_SCRIM_ALPHA), - ObjectAnimator.ofFloat(this, ALPHA, closing ? 0 : 1)); - mOpenCloseAnimator.addListener(new AnimationSuccessListener() { - @Override - public void onAnimationStart(Animator animation) { - setVisibility(VISIBLE); - if (closing && mOnClosingStartCallback != null) { - mOnClosingStartCallback.run(); - } - } - - @Override - public void onAnimationSuccess(Animator animator) { - if (closing) { - closeComplete(); - } - } - }); - mOpenCloseAnimator.setDuration(closing ? REVEAL_CLOSE_DURATION : REVEAL_OPEN_DURATION); - mOpenCloseAnimator.start(); - } - - private void closeComplete() { - mIsOpen = false; - mContainer.getDragLayer().removeView(this); - mRevealAnimator = null; - } - - private RoundedRectRevealOutlineProvider createOpenCloseOutlineProvider() { - float radius = TaskCornerRadius.get(mContext); - Rect fromRect = new Rect( - enableOverviewIconMenu() && isLayoutRtl() ? getWidth() : 0, - 0, - enableOverviewIconMenu() && !isLayoutRtl() ? 0 : getWidth(), - 0); - Rect toRect = new Rect(0, 0, getWidth(), getHeight()); - return new RoundedRectRevealOutlineProvider(radius, radius, fromRect, toRect); - } - - /** - * Calculates max height based on how much space we have available. - * If not enough space then the view will scroll. The maximum menu size will sit - * inside the task - * with a margin on the top and bottom. - */ - private int calculateMaxHeight() { - float taskInsetMargin = getResources().getDimension(R.dimen.task_card_margin); - return mTaskView.getPagedOrientationHandler().getTaskMenuHeight(taskInsetMargin, - mContainer.getDeviceProfile(), getTranslationX(), getTranslationY()); - } - - private void setOnClosingStartCallback(Runnable onClosingStartCallback) { - mOnClosingStartCallback = onClosingStartCallback; - } -} diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailViewHeader.kt b/quickstep/src/com/android/quickstep/views/TaskThumbnailViewHeader.kt deleted file mode 100644 index 9a8805bf0f..0000000000 --- a/quickstep/src/com/android/quickstep/views/TaskThumbnailViewHeader.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2025 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS 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.util.AttributeSet -import android.widget.FrameLayout -import android.widget.ImageButton -import android.widget.ImageView -import android.widget.TextView -import com.android.launcher3.R -import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.ThumbnailHeader - -class TaskThumbnailViewHeader -@JvmOverloads -constructor(context: Context, attrs: AttributeSet? = null) : FrameLayout(context, attrs) { - - private val headerTitleView: TextView by lazy { findViewById(R.id.header_app_title) } - private val headerIconView: ImageView by lazy { findViewById(R.id.header_app_icon) } - private val headerCloseButton: ImageButton by lazy { findViewById(R.id.header_close_button) } - - fun setHeader(header: ThumbnailHeader) { - headerTitleView.setText(header.title) - headerIconView.setImageDrawable(header.icon) - headerCloseButton.setOnClickListener(header.clickCloseListener) - } -} diff --git a/quickstep/testing/com/android/launcher3/taskbar/bubbles/testing/FakeBubbleViewFactory.kt b/quickstep/testing/com/android/launcher3/taskbar/bubbles/testing/FakeBubbleViewFactory.kt deleted file mode 100644 index 2f1f0b5f6f..0000000000 --- a/quickstep/testing/com/android/launcher3/taskbar/bubbles/testing/FakeBubbleViewFactory.kt +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher3.taskbar.bubbles.testing - -import android.app.Notification -import android.content.Context -import android.graphics.Bitmap -import android.graphics.Canvas -import android.graphics.Color -import android.graphics.Paint -import android.util.PathParser -import android.view.LayoutInflater -import android.view.ViewGroup -import com.android.launcher3.R -import com.android.launcher3.taskbar.bubbles.BubbleBarBubble -import com.android.launcher3.taskbar.bubbles.BubbleView -import com.android.wm.shell.shared.bubbles.BubbleInfo - -object FakeBubbleViewFactory { - - /** Inflates a [BubbleView] and adds it to the [parent] view if it is present. */ - fun createBubble( - context: Context, - key: String, - parent: ViewGroup?, - iconSize: Int = 50, - iconColor: Int, - badgeColor: Int = Color.RED, - dotColor: Int = Color.BLUE, - suppressNotification: Boolean = false, - ): BubbleView { - val inflater = LayoutInflater.from(context) - // BubbleView uses launcher's badge to icon ratio and expects the badge image to already - // have the right size - val badgeToIconRatio = 0.444f - val badgeRadius = iconSize * badgeToIconRatio / 2 - val icon = createCircleBitmap(radius = iconSize / 2, color = iconColor) - val badge = createCircleBitmap(radius = badgeRadius.toInt(), color = badgeColor) - - val flags = - if (suppressNotification) Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION else 0 - val bubbleInfo = - BubbleInfo( - key, - flags, - null, - null, - 0, - context.packageName, - null, - null, - false, - true, - null, - ) - val bubbleView = inflater.inflate(R.layout.bubblebar_item_view, parent, false) as BubbleView - val dotPath = - PathParser.createPathFromPathData( - context.resources.getString(com.android.internal.R.string.config_icon_mask) - ) - val bubble = - BubbleBarBubble( - bubbleInfo, - bubbleView, - badge, - icon, - dotColor, - dotPath, - "test app", - null, - ) - bubbleView.setBubble(bubble) - return bubbleView - } - - private fun createCircleBitmap(radius: Int, color: Int): Bitmap { - val bitmap = Bitmap.createBitmap(radius * 2, radius * 2, Bitmap.Config.ARGB_8888) - val canvas = Canvas(bitmap) - canvas.drawARGB(0, 0, 0, 0) - val paint = Paint() - paint.color = color - canvas.drawCircle(radius.toFloat(), radius.toFloat(), radius.toFloat(), paint) - return bitmap - } -} diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarUnitTestRule.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarUnitTestRule.kt deleted file mode 100644 index a999e7f7de..0000000000 --- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarUnitTestRule.kt +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.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/bubbles/animation/BubbleBarViewAnimatorTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt deleted file mode 100644 index cc579abc9e..0000000000 --- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt +++ /dev/null @@ -1,454 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.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<Context>() - 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<BubbleStashController>() - 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 <T> PhysicsAnimator<T>.assertIsRunning() { - InstrumentationRegistry.getInstrumentation().runOnMainSync { - assertThat(isRunning()).isTrue() - } - } - - private fun <T> PhysicsAnimator<T>.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/quickstep/recents/window/RecentsDisplayModelTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/window/RecentsDisplayModelTest.kt deleted file mode 100644 index 0119679681..0000000000 --- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/window/RecentsDisplayModelTest.kt +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.quickstep.recents.window - -import android.graphics.Point -import android.hardware.display.DisplayManager -import android.platform.test.annotations.EnableFlags -import android.platform.test.flag.junit.SetFlagsRule -import android.view.Display -import android.view.DisplayAdjustments -import android.view.Surface -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.SmallTest -import androidx.test.platform.app.InstrumentationRegistry -import com.android.launcher3.Flags.FLAG_ENABLE_FALLBACK_OVERVIEW_IN_WINDOW -import com.android.launcher3.Flags.FLAG_ENABLE_LAUNCHER_OVERVIEW_IN_WINDOW -import com.android.launcher3.util.LauncherModelHelper -import com.android.launcher3.util.window.CachedDisplayInfo -import com.android.quickstep.fallback.window.RecentsDisplayModel -import org.junit.Assert -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.ArgumentMatchers.anyInt -import org.mockito.kotlin.mock -import org.mockito.kotlin.spy -import org.mockito.kotlin.whenever - -@SmallTest -@RunWith(AndroidJUnit4::class) -@EnableFlags(FLAG_ENABLE_LAUNCHER_OVERVIEW_IN_WINDOW, FLAG_ENABLE_FALLBACK_OVERVIEW_IN_WINDOW) -class RecentsDisplayModelTest { - @get:Rule val setFlagsRule = SetFlagsRule() - - // initiate dagger components for injection - private val launcherModelHelper = LauncherModelHelper() - private val context = spy(launcherModelHelper.sandboxContext) - private val displayManager: DisplayManager = context.spyService(DisplayManager::class.java) - private val display: Display = mock() - - private lateinit var recentsDisplayModel: RecentsDisplayModel - - private val width = 2208 - private val height = 1840 - - @Before - fun setup() { - // Mock display - val displayInfo = CachedDisplayInfo(Point(width, height), Surface.ROTATION_0) - whenever(display.rotation).thenReturn(displayInfo.rotation) - whenever(display.displayAdjustments).thenReturn(DisplayAdjustments()) - whenever(context.display).thenReturn(display) - - // Mock displayManager - whenever(displayManager.getDisplay(anyInt())).thenReturn(display) - - runOnMainSync { recentsDisplayModel = RecentsDisplayModel.INSTANCE.get(context) } - } - - @Test - fun testEnsureSingleton() { - val recentsDisplayModel2 = RecentsDisplayModel.INSTANCE.get(context) - assert(recentsDisplayModel == recentsDisplayModel2) - } - - @Test - fun testDefaultDisplayCreation() { - Assert.assertNotNull(recentsDisplayModel.getRecentsWindowManager(Display.DEFAULT_DISPLAY)) - Assert.assertNotNull( - recentsDisplayModel.getFallbackWindowInterface(Display.DEFAULT_DISPLAY) - ) - } - - @Test - fun testCreateSeparateInstances() { - val displayId = Display.DEFAULT_DISPLAY + 1 - runOnMainSync { recentsDisplayModel.storeDisplayResource(displayId) } - - val defaultManager = recentsDisplayModel.getRecentsWindowManager(Display.DEFAULT_DISPLAY) - val secondaryManager = recentsDisplayModel.getRecentsWindowManager(displayId) - Assert.assertNotSame(defaultManager, secondaryManager) - - val defaultInterface = - recentsDisplayModel.getFallbackWindowInterface(Display.DEFAULT_DISPLAY) - val secondInterface = recentsDisplayModel.getFallbackWindowInterface(displayId) - Assert.assertNotSame(defaultInterface, secondInterface) - } - - @Test - fun testDestroy() { - Assert.assertNotNull(recentsDisplayModel.getRecentsWindowManager(Display.DEFAULT_DISPLAY)) - Assert.assertNotNull( - recentsDisplayModel.getFallbackWindowInterface(Display.DEFAULT_DISPLAY) - ) - recentsDisplayModel.destroy() - Assert.assertNull(recentsDisplayModel.getRecentsWindowManager(Display.DEFAULT_DISPLAY)) - Assert.assertNull(recentsDisplayModel.getFallbackWindowInterface(Display.DEFAULT_DISPLAY)) - } - - private fun runOnMainSync(f: Runnable) { - InstrumentationRegistry.getInstrumentation().runOnMainSync { f.run() } - } -} 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 deleted file mode 100644 index 3b8754c200..0000000000 --- a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelTest.kt +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.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<Bitmap>() - 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/RecentOrientedStateHandlerTests.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/RecentOrientedStateHandlerTests.kt deleted file mode 100644 index 3cdf608059..0000000000 --- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/RecentOrientedStateHandlerTests.kt +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright (C) 2025 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS 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.view.Surface -import android.view.Surface.ROTATION_0 -import android.view.Surface.ROTATION_180 -import android.view.Surface.ROTATION_90 -import androidx.test.core.app.ApplicationProvider -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.SmallTest -import com.android.quickstep.FallbackActivityInterface -import com.android.quickstep.orientation.RecentsPagedOrientationHandler -import com.android.quickstep.orientation.RecentsPagedOrientationHandler.Companion.PORTRAIT -import com.google.common.truth.Truth.assertWithMessage -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.kotlin.spy -import org.mockito.kotlin.whenever - -/** - * Test all possible inputs to RecentsOrientedState.updateHandler. It tests all possible - * combinations of rotations and relevant methods (two methods that return boolean values) but it - * only provides the expected result when the final rotation is different from ROTATION_0 for - * simplicity. So any case not shown in resultMap you can assume results in ROTATION_0. - */ -@SmallTest -@RunWith(AndroidJUnit4::class) -class RecentOrientedStateHandlerTests { - - data class TestCase( - val recentsRotation: Int, - val displayRotation: Int, - val touchRotation: Int, - val isRotationAllowed: Boolean, - val isFixedLandscape: Boolean, - ) { - override fun toString(): String { - return "TestCase(recentsRotation=${Surface.rotationToString(recentsRotation)}, " + - "displayRotation=${Surface.rotationToString(displayRotation)}, " + - "touchRotation=${Surface.rotationToString(touchRotation)}, " + - "isRotationAllowed=$isRotationAllowed, " + - "isFixedLandscape=$isFixedLandscape)" - } - } - - private fun runTestCase(testCase: TestCase, expectedHandler: RecentsPagedOrientationHandler) { - val recentOrientedState = - spy( - RecentsOrientedState( - ApplicationProvider.getApplicationContext(), - FallbackActivityInterface.INSTANCE, - ) {} - ) - whenever(recentOrientedState.isRecentsActivityRotationAllowed).thenAnswer { - testCase.isRotationAllowed - } - whenever(recentOrientedState.isLauncherFixedLandscape).thenAnswer { - testCase.isFixedLandscape - } - - recentOrientedState.update(testCase.displayRotation, testCase.touchRotation) - val rotation = recentOrientedState.orientationHandler.rotation - assertWithMessage("$testCase to ${Surface.rotationToString(rotation)},") - .that(rotation) - .isEqualTo(expectedHandler.rotation) - } - - @Test - fun `test fixed landscape when device is portrait`() { - runTestCase( - TestCase( - recentsRotation = ROTATION_0, - displayRotation = -1, - touchRotation = ROTATION_0, - isRotationAllowed = false, - isFixedLandscape = true, - ), - PORTRAIT, - ) - } - - @Test - fun `test fixed landscape when device is landscape`() { - runTestCase( - TestCase( - recentsRotation = ROTATION_90, - displayRotation = -1, - touchRotation = ROTATION_0, - isRotationAllowed = false, - isFixedLandscape = true, - ), - PORTRAIT, - ) - } - - @Test - fun `test fixed landscape when device is seascape`() { - runTestCase( - TestCase( - recentsRotation = ROTATION_180, - displayRotation = -1, - touchRotation = ROTATION_0, - isRotationAllowed = false, - isFixedLandscape = true, - ), - PORTRAIT, - ) - } - - @Test - fun `test fixed landscape when device is portrait and display rotation is portrait`() { - runTestCase( - TestCase( - recentsRotation = ROTATION_0, - displayRotation = ROTATION_0, - touchRotation = ROTATION_0, - isRotationAllowed = false, - isFixedLandscape = true, - ), - PORTRAIT, - ) - } - - @Test - fun `test fixed landscape when device is landscape and display rotation is landscape `() { - runTestCase( - TestCase( - recentsRotation = ROTATION_90, - displayRotation = ROTATION_90, - touchRotation = ROTATION_0, - isRotationAllowed = false, - isFixedLandscape = true, - ), - PORTRAIT, - ) - } - - @Test - fun `test fixed landscape when device is seascape and display rotation is seascape`() { - runTestCase( - TestCase( - recentsRotation = ROTATION_180, - displayRotation = ROTATION_180, - touchRotation = ROTATION_0, - isRotationAllowed = false, - isFixedLandscape = true, - ), - PORTRAIT, - ) - } -} diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/RecentsOrientedStateTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/RecentsOrientedStateTest.java deleted file mode 100644 index 47ef13b42f..0000000000 --- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/RecentsOrientedStateTest.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.quickstep.util; - -import static android.view.Surface.ROTATION_0; -import static android.view.Surface.ROTATION_180; -import static android.view.Surface.ROTATION_90; - -import static androidx.test.core.app.ApplicationProvider.getApplicationContext; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; - -import android.content.Context; - -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.filters.SmallTest; - -import com.android.quickstep.FallbackActivityInterface; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -/** - * Tests for {@link RecentsOrientedState} - */ -@SmallTest -@RunWith(AndroidJUnit4.class) -public class RecentsOrientedStateTest { - - private RecentsOrientedState mR1, mR2; - - @Before - public void setup() { - Context context = getApplicationContext(); - mR1 = new RecentsOrientedState(context, FallbackActivityInterface.INSTANCE, i -> { }); - mR2 = new RecentsOrientedState(context, FallbackActivityInterface.INSTANCE, i -> { }); - assertEquals(mR1.getStateId(), mR2.getStateId()); - } - - @Test - public void stateId_changesWithFlags() { - mR1.setGestureActive(true); - mR2.setGestureActive(false); - assertNotEquals(mR1.getStateId(), mR2.getStateId()); - - mR2.setGestureActive(true); - assertEquals(mR1.getStateId(), mR2.getStateId()); - } - - @Test - public void stateId_changesWithRecentsRotation() { - mR1.setRecentsRotation(ROTATION_90); - mR2.setRecentsRotation(ROTATION_180); - assertNotEquals(mR1.getStateId(), mR2.getStateId()); - - mR2.setRecentsRotation(ROTATION_90); - assertEquals(mR1.getStateId(), mR2.getStateId()); - } - - @Test - public void stateId_changesWithDisplayRotation() { - mR1.update(ROTATION_0, ROTATION_90); - mR2.update(ROTATION_0, ROTATION_180); - assertNotEquals(mR1.getStateId(), mR2.getStateId()); - - mR2.update(ROTATION_90, ROTATION_90); - assertNotEquals(mR1.getStateId(), mR2.getStateId()); - - mR2.update(ROTATION_90, ROTATION_0); - assertNotEquals(mR1.getStateId(), mR2.getStateId()); - - mR2.update(ROTATION_0, ROTATION_90); - assertEquals(mR1.getStateId(), mR2.getStateId()); - } -} diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskGridNavHelperTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskGridNavHelperTest.java deleted file mode 100644 index 7ef4910ce5..0000000000 --- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskGridNavHelperTest.java +++ /dev/null @@ -1,510 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.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/src/com/android/launcher3/model/QuickstepModelDelegateTest.kt b/quickstep/tests/src/com/android/launcher3/model/QuickstepModelDelegateTest.kt deleted file mode 100644 index a5327628d1..0000000000 --- a/quickstep/tests/src/com/android/launcher3/model/QuickstepModelDelegateTest.kt +++ /dev/null @@ -1,127 +0,0 @@ -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/WidgetsPredictionsRequesterTest.kt b/quickstep/tests/src/com/android/launcher3/model/WidgetsPredictionsRequesterTest.kt deleted file mode 100644 index 5c7b4aba4d..0000000000 --- a/quickstep/tests/src/com/android/launcher3/model/WidgetsPredictionsRequesterTest.kt +++ /dev/null @@ -1,221 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.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<PackageUserKey, List<WidgetItem>> - - @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<WidgetItem> = 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<WidgetItem> = 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/statehandlers/DesktopVisibilityControllerTest.kt b/quickstep/tests/src/com/android/launcher3/statehandlers/DesktopVisibilityControllerTest.kt deleted file mode 100644 index 4b8f2a2348..0000000000 --- a/quickstep/tests/src/com/android/launcher3/statehandlers/DesktopVisibilityControllerTest.kt +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2025 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS 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.statehandlers - -import android.content.Context -import android.platform.test.annotations.EnableFlags -import android.platform.test.flag.junit.SetFlagsRule -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.SmallTest -import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession -import com.android.launcher3.util.DaggerSingletonTracker -import com.android.quickstep.SystemUiProxy -import com.android.window.flags.Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND -import com.android.window.flags.Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_FRONTEND -import com.android.wm.shell.shared.desktopmode.DesktopModeStatus -import org.junit.After -import org.junit.Assert.assertFalse -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.whenever -import org.mockito.quality.Strictness - -/** - * Tests the behavior of [DesktopVisibilityController] in regards to multiple desktops and multiple - * displays. - */ -@SmallTest -@RunWith(AndroidJUnit4::class) -class DesktopVisibilityControllerTest { - - @get:Rule val setFlagsRule = SetFlagsRule() - - private val mockitoSession = - mockitoSession() - .strictness(Strictness.LENIENT) - .mockStatic(DesktopModeStatus::class.java) - .startMocking() - - private val context = mock<Context>() - private val systemUiProxy = mock<SystemUiProxy>() - private val lifeCycleTracker = mock<DaggerSingletonTracker>() - private lateinit var desktopVisibilityController: DesktopVisibilityController - - @Before - fun setUp() { - whenever(context.resources).thenReturn(mock()) - whenever(DesktopModeStatus.enableMultipleDesktops(context)).thenReturn(true) - desktopVisibilityController = - DesktopVisibilityController(context, systemUiProxy, lifeCycleTracker) - } - - @After - fun tearDown() { - mockitoSession.finishMocking() - } - - @Test - @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, FLAG_ENABLE_MULTIPLE_DESKTOPS_FRONTEND) - fun noCrashWhenCheckingNonExistentDisplay() { - assertFalse(desktopVisibilityController.isInDesktopMode(displayId = 500)) - assertFalse(desktopVisibilityController.isInDesktopModeAndNotInOverview(displayId = 300)) - } -} diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/FallbackTaskbarUIControllerTest.kt b/quickstep/tests/src/com/android/launcher3/taskbar/FallbackTaskbarUIControllerTest.kt deleted file mode 100644 index 04012c027d..0000000000 --- a/quickstep/tests/src/com/android/launcher3/taskbar/FallbackTaskbarUIControllerTest.kt +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.android.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<RecentsState> - - private val recentsActivity: RecentsActivity = mock() - private val stateManager: StateManager<RecentsState, RecentsActivity> = 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<StateManager.StateListener<RecentsState>>() - 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 deleted file mode 100644 index 15b1e532bd..0000000000 --- a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.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<BubbleControllers> - - 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 deleted file mode 100644 index 9ed39066dd..0000000000 --- a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarHoverToolTipControllerTest.java +++ /dev/null @@ -1,219 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.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<Object>) 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<Void>) 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<Void>) 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 deleted file mode 100644 index e619e7cd38..0000000000 --- a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarKeyguardControllerTest.kt +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.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 deleted file mode 100644 index 104263af5b..0000000000 --- a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt +++ /dev/null @@ -1,242 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.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<String>): List<ItemInfo> { - return packageNames.map { createTestAppInfo(packageName = it) } - } - - private fun createDesktopTasksFromPackageNames( - packageNames: List<String> - ): ArrayList<RunningTaskInfo> { - 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/RecentTasksListTest.java b/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java deleted file mode 100644 index 03244eb0bf..0000000000 --- a/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.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<GroupTask> 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<GroupTask> 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 deleted file mode 100644 index 80fbce7265..0000000000 --- a/quickstep/tests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt +++ /dev/null @@ -1,205 +0,0 @@ -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<Long> { - // 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 deleted file mode 100644 index 648fa932a6..0000000000 --- a/quickstep/tests/src/com/android/quickstep/RecentsModelTest.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.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<ArrayList<GroupTask>> 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<Task> 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 deleted file mode 100644 index 07d8f61992..0000000000 --- a/quickstep/tests/src/com/android/quickstep/TaplDigitalWellBeingToastTest.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.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.<RecentsView>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/TaskThumbnailCacheTest.java b/quickstep/tests/src/com/android/quickstep/TaskThumbnailCacheTest.java deleted file mode 100644 index 4e04261e5c..0000000000 --- a/quickstep/tests/src/com/android/quickstep/TaskThumbnailCacheTest.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.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/taskbar/controllers/TaskbarPinningControllerTest.kt b/quickstep/tests/src/com/android/quickstep/taskbar/controllers/TaskbarPinningControllerTest.kt deleted file mode 100644 index 4d10f0f51e..0000000000 --- a/quickstep/tests/src/com/android/quickstep/taskbar/controllers/TaskbarPinningControllerTest.kt +++ /dev/null @@ -1,219 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.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<TaskbarDragLayer>() - private val taskbarSharedState = mock<TaskbarSharedState>() - private var isInDesktopMode = false - private val isInDesktopModeProvider = { isInDesktopMode } - private val launcherPrefs = - mock<LauncherPrefs> { - on { get(TASKBAR_PINNING) } doReturn false - on { get(TASKBAR_PINNING_IN_DESKTOP_MODE) } doReturn false - } - private val statsLogger = mock<StatsLogManager.StatsLogger>() - private val statsLogManager = mock<StatsLogManager> { 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<Runnable>() - 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<TaskbarDividerPopupView<TaskbarActivityContext>> { - on { requestFocus() } doReturn true - } - val view = mock<View>() - val argumentCaptor = argumentCaptor<Runnable>() - 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 deleted file mode 100644 index b637e7d4cf..0000000000 --- a/quickstep/tests/src/com/android/quickstep/taskbar/customization/TaskbarSpecsEvaluatorTest.kt +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.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<TaskbarFeatureEvaluator>() - 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 deleted file mode 100644 index c190cfebab..0000000000 --- a/quickstep/tests/src/com/android/quickstep/util/GestureExclusionManagerTest.kt +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.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<Any> { null }.get() - Executors.MAIN_EXECUTOR.submit<Any> { 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 deleted file mode 100644 index 3d7ad2bd60..0000000000 --- a/res/anim-v33/shared_x_axis_activity_close_enter.xml +++ /dev/null @@ -1,42 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2022 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> - -<set xmlns:android="http://schemas.android.com/apk/res/android" - android:shareInterpolator="false" - android:showBackdrop="true"> - - <alpha - android:fromAlpha="0.0" - android:toAlpha="1.0" - android:fillEnabled="true" - android:fillBefore="true" - android:fillAfter="true" - android:interpolator="@interpolator/standard_decelerate_interpolator" - android:startOffset="100" - android:duration="350" /> - - <translate - android:fromXDelta="-25%" - android:toXDelta="0" - android:fillEnabled="true" - android:fillBefore="true" - android:fillAfter="true" - android:interpolator="@interpolator/emphasized_interpolator" - android:startOffset="0" - android:duration="450" /> - -</set> \ 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 deleted file mode 100644 index fb63602d4e..0000000000 --- a/res/anim-v33/shared_x_axis_activity_close_exit.xml +++ /dev/null @@ -1,41 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2022 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> - -<set xmlns:android="http://schemas.android.com/apk/res/android" - android:shareInterpolator="false"> - - <alpha - android:fromAlpha="1.0" - android:toAlpha="0.0" - android:fillEnabled="true" - android:fillBefore="true" - android:fillAfter="true" - android:interpolator="@interpolator/standard_accelerate_interpolator" - android:startOffset="0" - android:duration="100" /> - - <translate - android:fromXDelta="0" - android:toXDelta="25%" - android:fillEnabled="true" - android:fillBefore="true" - android:fillAfter="true" - android:interpolator="@interpolator/emphasized_interpolator" - android:startOffset="0" - android:duration="450" /> - -</set> \ 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 deleted file mode 100644 index cba74ba0ec..0000000000 --- a/res/anim-v33/shared_x_axis_activity_open_enter.xml +++ /dev/null @@ -1,42 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2022 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> - -<set xmlns:android="http://schemas.android.com/apk/res/android" - android:shareInterpolator="false" - android:showBackdrop="true"> - - <alpha - android:fromAlpha="0.0" - android:toAlpha="1.0" - android:fillEnabled="true" - android:fillBefore="true" - android:fillAfter="true" - android:interpolator="@interpolator/standard_decelerate_interpolator" - android:startOffset="100" - android:duration="350" /> - - <translate - android:fromXDelta="25%" - android:toXDelta="0" - android:fillEnabled="true" - android:fillBefore="true" - android:fillAfter="true" - android:interpolator="@interpolator/emphasized_interpolator" - android:startOffset="0" - android:duration="450" /> - -</set> \ 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 deleted file mode 100644 index 22e878d7f1..0000000000 --- a/res/anim-v33/shared_x_axis_activity_open_exit.xml +++ /dev/null @@ -1,41 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2022 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> - -<set xmlns:android="http://schemas.android.com/apk/res/android" - android:shareInterpolator="false"> - - <alpha - android:fromAlpha="1.0" - android:toAlpha="0.0" - android:fillEnabled="true" - android:fillBefore="true" - android:fillAfter="true" - android:interpolator="@interpolator/standard_accelerate_interpolator" - android:startOffset="0" - android:duration="100" /> - - <translate - android:fromXDelta="0" - android:toXDelta="-25%" - android:fillEnabled="true" - android:fillBefore="true" - android:fillAfter="true" - android:interpolator="@interpolator/emphasized_interpolator" - android:startOffset="0" - android:duration="450" /> - -</set> \ 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 deleted file mode 100644 index a645f24dd9..0000000000 --- a/res/color-night-v31/material_color_surface.xml +++ /dev/null @@ -1,19 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2022 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:color="@android:color/system_neutral1_500" android:lStar="6" /> -</selector> \ 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 deleted file mode 100644 index f34ed6c548..0000000000 --- a/res/color-night-v31/material_color_surface_bright.xml +++ /dev/null @@ -1,19 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2022 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:color="@android:color/system_neutral1_500" android:lStar="24" /> -</selector> \ 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 deleted file mode 100644 index 002b88eba4..0000000000 --- a/res/color-night-v31/material_color_surface_container.xml +++ /dev/null @@ -1,19 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2022 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:color="@android:color/system_neutral1_500" android:lStar="12" /> -</selector> \ 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 deleted file mode 100644 index edd36fcd23..0000000000 --- a/res/color-night-v31/material_color_surface_container_high.xml +++ /dev/null @@ -1,19 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2022 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:color="@android:color/system_neutral1_500" android:lStar="17" /> -</selector> \ 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 deleted file mode 100644 index e54f95380e..0000000000 --- a/res/color-night-v31/material_color_surface_container_highest.xml +++ /dev/null @@ -1,19 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2022 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:color="@android:color/system_neutral1_500" android:lStar="22" /> -</selector> \ 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 deleted file mode 100644 index 40f0d4c9de..0000000000 --- a/res/color-night-v31/material_color_surface_container_low.xml +++ /dev/null @@ -1,19 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2022 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:color="@android:color/system_neutral1_500" android:lStar="10" /> -</selector> \ 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 deleted file mode 100644 index a645f24dd9..0000000000 --- a/res/color-night-v31/material_color_surface_dim.xml +++ /dev/null @@ -1,19 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2022 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:color="@android:color/system_neutral1_500" android:lStar="6" /> -</selector> \ 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 deleted file mode 100644 index ac63072a9e..0000000000 --- a/res/color-night-v31/material_color_surface_inverse.xml +++ /dev/null @@ -1,19 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2022 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:color="@android:color/system_neutral1_500" android:lStar="98" /> -</selector> \ 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 deleted file mode 100644 index a645f24dd9..0000000000 --- a/res/color-night-v31/material_color_surface_variant.xml +++ /dev/null @@ -1,19 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2022 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:color="@android:color/system_neutral1_500" android:lStar="6" /> -</selector> \ 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 deleted file mode 100644 index 13ceaa09e7..0000000000 --- a/res/color-night-v31/popup_color_background.xml +++ /dev/null @@ -1,20 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2023 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<selector xmlns:android="http://schemas.android.com/apk/res/android" > - <item - android:color="@android:color/system_neutral1_900" - android:lStar="12" /> -</selector> diff --git a/res/color-night-v31/widget_picker_primary_surface_color_dark.xml b/res/color-night-v31/widget_picker_primary_surface_color_dark.xml deleted file mode 100644 index 13ceaa09e7..0000000000 --- a/res/color-night-v31/widget_picker_primary_surface_color_dark.xml +++ /dev/null @@ -1,20 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2023 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<selector xmlns:android="http://schemas.android.com/apk/res/android" > - <item - android:color="@android:color/system_neutral1_900" - android:lStar="12" /> -</selector> diff --git a/res/color-v31/material_color_surface.xml b/res/color-v31/material_color_surface.xml deleted file mode 100644 index b049851ff4..0000000000 --- a/res/color-v31/material_color_surface.xml +++ /dev/null @@ -1,19 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2023 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:color="@android:color/system_neutral1_500" android:lStar="98" /> -</selector> \ 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 deleted file mode 100644 index b049851ff4..0000000000 --- a/res/color-v31/material_color_surface_bright.xml +++ /dev/null @@ -1,19 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2023 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:color="@android:color/system_neutral1_500" android:lStar="98" /> -</selector> \ 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 deleted file mode 100644 index b031c081a9..0000000000 --- a/res/color-v31/material_color_surface_container.xml +++ /dev/null @@ -1,19 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2023 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:color="@android:color/system_neutral1_500" android:lStar="94" /> -</selector> \ 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 deleted file mode 100644 index a996d51eba..0000000000 --- a/res/color-v31/material_color_surface_container_high.xml +++ /dev/null @@ -1,19 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2023 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:color="@android:color/system_neutral1_500" android:lStar="92" /> -</selector> \ 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 deleted file mode 100644 index e7a535af53..0000000000 --- a/res/color-v31/material_color_surface_container_highest.xml +++ /dev/null @@ -1,19 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2023 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:color="@android:color/system_neutral1_500" android:lStar="90" /> -</selector> \ 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 deleted file mode 100644 index b8fe01e484..0000000000 --- a/res/color-v31/material_color_surface_container_low.xml +++ /dev/null @@ -1,19 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2023 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:color="@android:color/system_neutral1_500" android:lStar="96" /> -</selector> \ 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 deleted file mode 100644 index e2d226fa89..0000000000 --- a/res/color-v31/material_color_surface_dim.xml +++ /dev/null @@ -1,19 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2022 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:color="@android:color/system_neutral1_500" android:lStar="87" /> -</selector> \ 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 deleted file mode 100644 index e189862856..0000000000 --- a/res/color-v31/material_color_surface_inverse.xml +++ /dev/null @@ -1,20 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2023 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> - -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:color="@android:color/system_neutral1_500" android:lStar="6" /> -</selector> \ 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 deleted file mode 100644 index e2d226fa89..0000000000 --- a/res/color-v31/material_color_surface_variant.xml +++ /dev/null @@ -1,19 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2022 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:color="@android:color/system_neutral1_500" android:lStar="87" /> -</selector> \ No newline at end of file diff --git a/res/color-v31/popup_color_background.xml b/res/color-v31/popup_color_background.xml deleted file mode 100644 index 99155d8972..0000000000 --- a/res/color-v31/popup_color_background.xml +++ /dev/null @@ -1,20 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2023 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<selector xmlns:android="http://schemas.android.com/apk/res/android" > - <item - android:color="@android:color/system_neutral1_50" - android:lStar="94" /> -</selector> diff --git a/res/color-v31/widget_picker_primary_surface_color_light.xml b/res/color-v31/widget_picker_primary_surface_color_light.xml deleted file mode 100644 index 02dffdddf7..0000000000 --- a/res/color-v31/widget_picker_primary_surface_color_light.xml +++ /dev/null @@ -1,20 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2023 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<selector xmlns:android="http://schemas.android.com/apk/res/android" > - <item - android:color="@android:color/system_accent2_50" - android:lStar="94" /> -</selector> diff --git a/res/color/popup_color_background.xml b/res/color/popup_color_background.xml deleted file mode 100644 index e87e77231c..0000000000 --- a/res/color/popup_color_background.xml +++ /dev/null @@ -1,18 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2023 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<selector xmlns:android="http://schemas.android.com/apk/res/android" > - <item android:color="?attr/popupColorBackground" /> -</selector> diff --git a/res/drawable-sw720dp/ic_transient_taskbar_all_apps_button.xml b/res/drawable-sw720dp/ic_transient_taskbar_all_apps_button.xml deleted file mode 100644 index 47f2a5d73a..0000000000 --- a/res/drawable-sw720dp/ic_transient_taskbar_all_apps_button.xml +++ /dev/null @@ -1,48 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2023 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="52dp" - android:height="52dp" - android:viewportWidth="52" - android:viewportHeight="52"> - <path - android:pathData="M15.5,19C14.538,19 13.715,18.65 13.033,17.968C12.35,17.285 12,16.462 12,15.5C12,14.538 12.35,13.715 13.033,13.033C13.715,12.35 14.538,12 15.5,12C16.462,12 17.285,12.35 17.968,13.033C18.65,13.715 19,14.538 19,15.5C19,16.462 18.65,17.285 17.968,17.968C17.285,18.65 16.462,19 15.5,19Z" - android:fillColor="#40484B"/> - <path - android:pathData="M26,19C25.038,19 24.215,18.65 23.532,17.968C22.85,17.285 22.5,16.462 22.5,15.5C22.5,14.538 22.85,13.715 23.532,13.033C24.215,12.35 25.038,12 26,12C26.962,12 27.785,12.35 28.468,13.033C29.15,13.715 29.5,14.538 29.5,15.5C29.5,16.462 29.15,17.285 28.468,17.968C27.785,18.65 26.962,19 26,19Z" - android:fillColor="#40484B"/> - <path - android:pathData="M36.5,19C35.537,19 34.715,18.65 34.033,17.968C33.35,17.285 33,16.462 33,15.5C33,14.538 33.35,13.715 34.033,13.033C34.715,12.35 35.537,12 36.5,12C37.463,12 38.285,12.35 38.967,13.033C39.65,13.715 40,14.538 40,15.5C40,16.462 39.65,17.285 38.967,17.968C38.285,18.65 37.463,19 36.5,19Z" - android:fillColor="#40484B"/> - <path - android:pathData="M15.5,29.5C14.538,29.5 13.715,29.15 13.033,28.468C12.35,27.785 12,26.962 12,26C12,25.038 12.35,24.215 13.033,23.532C13.715,22.85 14.538,22.5 15.5,22.5C16.462,22.5 17.285,22.85 17.968,23.532C18.65,24.215 19,25.038 19,26C19,26.962 18.65,27.785 17.968,28.468C17.285,29.15 16.462,29.5 15.5,29.5Z" - android:fillColor="#40484B"/> - <path - android:pathData="M26,29.5C25.038,29.5 24.215,29.15 23.532,28.468C22.85,27.785 22.5,26.962 22.5,26C22.5,25.038 22.85,24.215 23.532,23.532C24.215,22.85 25.038,22.5 26,22.5C26.962,22.5 27.785,22.85 28.468,23.532C29.15,24.215 29.5,25.038 29.5,26C29.5,26.962 29.15,27.785 28.468,28.468C27.785,29.15 26.962,29.5 26,29.5Z" - android:fillColor="#40484B"/> - <path - android:pathData="M36.5,29.5C35.537,29.5 34.715,29.15 34.033,28.468C33.35,27.785 33,26.962 33,26C33,25.038 33.35,24.215 34.033,23.532C34.715,22.85 35.537,22.5 36.5,22.5C37.463,22.5 38.285,22.85 38.967,23.532C39.65,24.215 40,25.038 40,26C40,26.962 39.65,27.785 38.967,28.468C38.285,29.15 37.463,29.5 36.5,29.5Z" - android:fillColor="#40484B"/> - <path - android:pathData="M15.5,40C14.538,40 13.715,39.65 13.033,38.967C12.35,38.285 12,37.463 12,36.5C12,35.537 12.35,34.715 13.033,34.033C13.715,33.35 14.538,33 15.5,33C16.462,33 17.285,33.35 17.968,34.033C18.65,34.715 19,35.537 19,36.5C19,37.463 18.65,38.285 17.968,38.967C17.285,39.65 16.462,40 15.5,40Z" - android:fillColor="#40484B"/> - <path - android:pathData="M26,40C25.038,40 24.215,39.65 23.532,38.967C22.85,38.285 22.5,37.463 22.5,36.5C22.5,35.537 22.85,34.715 23.532,34.033C24.215,33.35 25.038,33 26,33C26.962,33 27.785,33.35 28.468,34.033C29.15,34.715 29.5,35.537 29.5,36.5C29.5,37.463 29.15,38.285 28.468,38.967C27.785,39.65 26.962,40 26,40Z" - android:fillColor="#40484B"/> - <path - android:pathData="M36.5,40C35.537,40 34.715,39.65 34.033,38.967C33.35,38.285 33,37.463 33,36.5C33,35.537 33.35,34.715 34.033,34.033C34.715,33.35 35.537,33 36.5,33C37.463,33 38.285,33.35 38.967,34.033C39.65,34.715 40,35.537 40,36.5C40,37.463 39.65,38.285 38.967,38.967C38.285,39.65 37.463,40 36.5,40Z" - android:fillColor="#40484B"/> -</vector> diff --git a/res/drawable/bg_ps_header.xml b/res/drawable/bg_ps_header.xml deleted file mode 100644 index da314452ad..0000000000 --- a/res/drawable/bg_ps_header.xml +++ /dev/null @@ -1,26 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2023 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> -<ripple xmlns:android="http://schemas.android.com/apk/res/android" - android:color="@color/accent_ripple_color"> - <item> - <shape xmlns:android="http://schemas.android.com/apk/res/android" - android:shape="rectangle"> - <corners android:radius="@dimen/ps_container_corner_radius" /> - <solid android:color="?attr/materialColorSurfaceContainerHigh" /> - </shape> - </item> -</ripple> diff --git a/res/drawable/horizontal_ellipsis.xml b/res/drawable/horizontal_ellipsis.xml deleted file mode 100644 index 65e958d42b..0000000000 --- a/res/drawable/horizontal_ellipsis.xml +++ /dev/null @@ -1,27 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- 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. ---> - -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="@dimen/horizontal_ellipsis_size" - android:height="@dimen/horizontal_ellipsis_size" - android:viewportWidth="24.0" - android:viewportHeight="24.0" - android:tint="?android:attr/textColorSecondary" > - - <path - android:pathData="M6 10c-1.1 0-2 0.9-2 2s0.9 2 2 2 2-0.9 2-2-0.9-2-2-2zm12 0c-1.1 0-2 0.9-2 2s0.9 2 2 2 2-0.9 2-2-0.9-2-2-2zm-6 0c-1.1 0-2 0.9-2 2s0.9 2 2 2 2-0.9 2-2-0.9-2-2-2z" - android:fillColor="@android:color/white" /> -</vector> \ No newline at end of file diff --git a/res/drawable/ic_mull.xml b/res/drawable/ic_mull.xml deleted file mode 100644 index 079ee42123..0000000000 --- a/res/drawable/ic_mull.xml +++ /dev/null @@ -1,59 +0,0 @@ -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="108" - android:viewportHeight="108"> - <path - android:pathData="m71.12,25.5c0,0 1.73,-5.82 7.4,-8.81 5.67,-2.99 19.35,-2.99 21.4,-2.05 2.05,0.94 2.68,17.63 0,21.71 -2.68,4.09 -8.03,6.3 -12.28,6.3 -4.25,0 -17.63,-10.23 -17.63,-10.23z" - android:fillColor="#e91e63"/> - <path - android:pathData="m76.09,26.47c0,0 1.15,-3.86 4.91,-5.84 3.76,-1.99 12.84,-1.99 14.2,-1.36 1.36,0.62 1.78,11.69 0,14.41 -1.78,2.71 -5.32,4.18 -8.14,4.18 -2.82,0 -11.69,-6.79 -11.69,-6.79z" - android:fillColor="#424242"/> - <path - android:pathData="m36.33,25.5c0,0 -1.73,-5.82 -7.4,-8.81 -5.67,-2.99 -19.35,-2.99 -21.4,-2.05 -2.05,0.94 -2.68,17.63 0,21.71 2.68,4.09 8.03,6.3 12.28,6.3 4.25,0 17.63,-10.23 17.63,-10.23z" - android:fillColor="#e91e63"/> - <path - android:pathData="m31.36,26.47c0,0 -1.15,-3.86 -4.91,-5.84 -3.76,-1.99 -12.84,-1.99 -14.2,-1.36 -1.36,0.62 -1.78,11.69 0,14.41 1.78,2.71 5.32,4.18 8.14,4.18 2.82,0 11.69,-6.79 11.69,-6.79z" - android:fillColor="#424242"/> - <path - android:pathData="m28.32,25.85c0,0 3.25,0.03 5.89,0.77 0,0 -6.75,1.37 -12.02,4.85 0,0 2,0.11 4.64,0.53 0,0 -8.07,5.92 -14.37,14.92 21.23,4.12 25.12,26.8 25.12,26.8 0.87,-1.4 7.02,-5.61 7.02,-5.61 2.64,-30.47 -2.07,-43.39 -4.56,-47.92 -4.34,1.25 -8.58,3.1 -11.72,5.66zM80.62,31.99c2.64,-0.42 4.64,-0.53 4.64,-0.53 -5.27,-3.48 -12.02,-4.85 -12.02,-4.85 2.64,-0.74 5.89,-0.77 5.89,-0.77 -3.15,-2.56 -7.39,-4.41 -11.72,-5.66 -2.48,4.52 -7.19,17.44 -4.56,47.92 0,0 6.14,4.21 7.02,5.61 0,0 3.89,-22.68 25.12,-26.8 -6.29,-9 -14.37,-14.92 -14.37,-14.92z" - android:fillColor="#212121"/> - <path - android:pathData="m91.91,67.64c6.22,-0.42 10.55,2.85 10.55,2.85 -0.95,-9.81 -7.48,-12.97 -7.48,-12.97 4.22,0.42 6.54,3.17 6.54,3.17 -1.25,-5.21 -3.84,-10.02 -6.79,-14.16 0.09,0.13 0.19,0.26 0.28,0.39 -21.23,4.12 -25.12,26.8 -25.12,26.8C69.02,72.32 62.87,68.11 62.87,68.11 60.23,37.63 64.94,24.71 67.43,20.19 62.51,18.77 57.46,18.13 53.74,18.13c-4.12,0 -9.86,0.79 -15.26,2.54 0.52,-0.17 1.04,-0.33 1.57,-0.48 2.48,4.52 7.19,17.44 4.56,47.92 0,0 -6.14,4.21 -7.02,5.61 0,0 -3.89,-22.68 -25.12,-26.8 0.09,-0.14 0.19,-0.26 0.28,-0.39 -2.97,4.13 -5.57,8.94 -6.81,14.15 0,0 2.32,-2.74 6.54,-3.17 0,0 -6.54,3.17 -7.49,12.97 0,0 4.33,-3.27 10.55,-2.85 0,0 -6.64,0.63 -8.96,7.91 0,0 8.14,-5.23 19.78,-1.88 10.83,3.12 10.18,16.57 18.09,19.4 6.54,2.33 9.29,-0.84 9.29,-0.84 0,0 2.75,3.17 9.29,0.84 7.91,-2.82 7.25,-16.28 18.09,-19.4 11.63,-3.35 19.78,1.88 19.78,1.88C98.55,68.27 91.91,67.64 91.91,67.64Z" - android:fillColor="#e91e63"/> - <path - android:pathData="m53.73,74.28c-7.23,0 -10.58,3.19 -10.58,3.19 0,0 7.16,9.56 10.58,9.73 3.42,-0.17 10.58,-9.73 10.58,-9.73 0,0 -3.36,-3.19 -10.58,-3.19z" - android:fillColor="#212121"/> - <path - android:pathData="m53.73,75.88c6.21,0 9.63,1.19 10.58,1.58l0,-0.01c0,0 -3.36,-3.19 -10.58,-3.19 -7.23,0 -10.58,3.19 -10.58,3.19l0,0.01c0.96,-0.39 4.37,-1.58 10.59,-1.58z" - android:fillColor="#424242"/> - <path - android:pathData="m64.64,55.19a8.83,7.3 87.28,1 0,14.02 -4.08,8.83 7.3,87.28 1,0 -14.02,4.08z" - android:fillColor="#424242"/> - <path - android:pathData="m71.65,52.65m-4.62,0a4.62,4.62 0,1 1,9.23 0,4.62 4.62,0 1,1 -9.23,0" - android:fillColor="#332a24"/> - <path - android:pathData="m71.65,52.65m-2.97,0a2.97,2.97 0,1 1,5.93 0,2.97 2.97,0 1,1 -5.93,0" - android:fillColor="#4d4a47"/> - <path - android:pathData="m70.76,51.73m-1.19,0a1.19,1.19 0,1 1,2.39 0,1.19 1.19,0 1,1 -2.39,0" - android:fillColor="#ffffff"/> - <path - android:pathData="M42.81,55.19A7.3,8.83 55.96,1 0,28.79 51.1,7.3 8.83,55.96 1,0 42.81,55.19Z" - android:fillColor="#424242"/> - <path - android:pathData="m35.8,52.65m-4.62,0a4.62,4.62 0,1 1,9.23 0,4.62 4.62,0 1,1 -9.23,0" - android:fillColor="#332a24"/> - <path - android:pathData="m35.8,52.65m-2.97,0a2.97,2.97 0,1 1,5.93 0,2.97 2.97,0 1,1 -5.93,0" - android:fillColor="#4d4a47"/> - <path - android:pathData="m34.91,51.73m-1.19,0a1.19,1.19 0,1 1,2.39 0,1.19 1.19,0 1,1 -2.39,0" - android:fillColor="#ffffff"/> - <path - android:pathData="m91.51,67.64c6.22,-0.42 10.55,2.85 10.55,2.85 -0.95,-9.81 -7.48,-12.97 -7.48,-12.97 4.22,0.42 6.54,3.17 6.54,3.17 -1.21,-5.04 -3.69,-9.71 -6.52,-13.76 0,0 0.01,0 0.01,0 -0.09,-0.14 -0.19,-0.26 -0.28,-0.39 0,0 0,0 0,0 -1.11,-1.55 -2.26,-3.01 -3.41,-4.36 3.32,-0.85 6.68,-2.83 8.63,-5.8 2.68,-4.09 2.05,-20.77 0,-21.71 -2.05,-0.94 -15.73,-0.94 -21.4,2.05 -2.78,1.46 -4.6,3.61 -5.75,5.42C70.66,21.36 68.85,20.73 67.02,20.2 62.1,18.78 57.05,18.14 53.34,18.14l0,74.08c0,0 2.75,3.17 9.29,0.84 7.91,-2.82 7.25,-16.28 18.09,-19.4 11.63,-3.35 19.78,1.88 19.78,1.88C98.15,68.27 91.51,67.64 91.51,67.64Z" - android:strokeAlpha="0.1" - android:fillColor="#5c4337" - android:fillAlpha="0.1"/> -</vector> diff --git a/res/drawable/ic_mull_tinted.xml b/res/drawable/ic_mull_tinted.xml deleted file mode 100644 index 44768626f3..0000000000 --- a/res/drawable/ic_mull_tinted.xml +++ /dev/null @@ -1,46 +0,0 @@ -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="108" - android:viewportHeight="108"> - <path - android:pathData="m71.12,25.5c0,0 1.73,-5.82 7.4,-8.81 5.67,-2.99 19.35,-2.99 21.4,-2.05 2.05,0.94 2.68,17.63 0,21.71 -2.68,4.09 -8.03,6.3 -12.28,6.3 -4.25,0 -17.63,-10.23 -17.63,-10.23z" - android:strokeWidth="1.8" - android:fillColor="#000000" - android:strokeColor="#000000"/> - <path - android:pathData="m36.33,25.5c0,0 -1.73,-5.82 -7.4,-8.81 -5.67,-2.99 -19.35,-2.99 -21.4,-2.05 -2.05,0.94 -2.68,17.63 0,21.71 2.68,4.09 8.03,6.3 12.28,6.3 4.25,0 17.63,-10.23 17.63,-10.23z" - android:strokeWidth="1.8" - android:fillColor="#000000" - android:strokeColor="#000000"/> - <path - android:pathData="m28.32,25.85c0,0 3.25,0.03 5.89,0.77 0,0 -6.75,1.37 -12.02,4.85 0,0 2,0.11 4.64,0.53 0,0 -8.07,5.92 -14.37,14.92 21.23,4.12 25.12,26.8 25.12,26.8 0.87,-1.4 7.02,-5.61 7.02,-5.61 2.64,-30.47 -2.07,-43.39 -4.56,-47.92 -4.34,1.25 -8.58,3.1 -11.72,5.66zM80.62,31.99c2.64,-0.42 4.64,-0.53 4.64,-0.53 -5.27,-3.48 -12.02,-4.85 -12.02,-4.85 2.64,-0.74 5.89,-0.77 5.89,-0.77 -3.15,-2.56 -7.39,-4.41 -11.72,-5.66 -2.48,4.52 -7.19,17.44 -4.56,47.92 0,0 6.14,4.21 7.02,5.61 0,0 3.89,-22.68 25.12,-26.8 -6.29,-9 -14.37,-14.92 -14.37,-14.92z" - android:strokeWidth="1.8" - android:fillColor="#00000000" - android:strokeColor="#000000"/> - <path - android:pathData="M53.75,18.13C50.03,18.13 44.98,18.78 40.05,20.2C42.53,24.72 47.25,37.65 44.61,68.11C44.61,68.11 38.47,72.32 37.59,73.72C37.59,73.72 33.71,51.05 12.49,46.92C9.64,50.97 7.16,55.64 5.95,60.68C5.95,60.68 8.27,57.93 12.49,57.51C12.49,57.51 5.95,60.68 5,70.48C5,70.48 9.32,67.21 15.55,67.63C15.55,67.63 8.9,68.26 6.58,75.54C6.58,75.54 14.73,70.31 26.36,73.66C37.18,76.78 36.53,90.23 44.45,93.06C50.98,95.39 53.74,92.21 53.74,92.21C53.74,92.21 56.49,95.39 63.03,93.06C70.94,90.24 70.28,76.78 81.11,73.66C92.75,70.31 100.89,75.54 100.89,75.54C98.56,68.27 91.91,67.64 91.91,67.64C98.13,67.22 102.46,70.49 102.46,70.49C101.51,60.68 94.97,57.52 94.97,57.52C99.19,57.94 101.51,60.68 101.51,60.68C100.3,55.64 97.83,50.97 94.99,46.92C73.78,51.06 69.89,73.72 69.89,73.72C69.02,72.32 62.88,68.11 62.88,68.11C60.23,37.63 64.94,24.71 67.43,20.19C62.51,18.77 57.46,18.13 53.75,18.13zM94.99,46.92C95,46.92 95,46.93 95.01,46.92C94.91,46.8 94.81,46.66 94.72,46.53C94.81,46.66 94.9,46.79 94.99,46.92zM12.49,46.92C12.58,46.79 12.67,46.66 12.76,46.53C12.67,46.66 12.58,46.79 12.48,46.92L12.49,46.92zM40.05,20.2L40.05,20.19C39.52,20.34 39,20.5 38.48,20.67C39,20.5 39.53,20.35 40.05,20.2zM53.72,74.27C55.07,74.27 56.3,74.38 57.38,74.57C59.84,74.98 61.62,75.74 62.75,76.39C62.79,76.4 62.83,76.43 62.86,76.45C62.86,76.45 62.87,76.45 62.87,76.45C63.03,76.54 63.18,76.64 63.31,76.72C63.36,76.75 63.42,76.79 63.47,76.83C63.54,76.87 63.61,76.92 63.67,76.96C63.67,76.96 63.68,76.96 63.68,76.96C63.71,76.98 63.73,77 63.76,77.03C64.03,77.22 64.17,77.34 64.23,77.4C64.25,77.41 64.3,77.46 64.3,77.46C64.3,77.46 64.31,77.46 64.31,77.46C64.31,77.46 57.15,87.02 53.73,87.19C50.3,87.02 43.15,77.46 43.15,77.46C43.15,77.46 43.15,77.45 43.15,77.45C43.17,77.43 43.37,77.24 43.78,76.96C43.99,76.81 44.26,76.63 44.59,76.45C45.72,75.79 47.53,74.99 50.06,74.57C50.07,74.57 50.07,74.57 50.07,74.57C51.16,74.39 52.37,74.27 53.72,74.27z" - android:strokeWidth="1.8" - android:fillColor="#000000" - android:strokeColor="#000000"/> - <path - android:pathData="m64.64,55.19a8.83,7.3 87.28,1 0,14.02 -4.08,8.83 7.3,87.28 1,0 -14.02,4.08z" - android:strokeWidth="1.8" - android:fillColor="#00000000" - android:strokeColor="#000000"/> - <path - android:pathData="M71.31,48.05A4.62,4.62 0,0 0,67.03 52.65A4.62,4.62 0,0 0,76.27 52.65A4.62,4.62 0,0 0,71.31 48.05zM70.67,50.54A1.19,1.19 0,0 1,71.96 51.73A1.2,1.2 0,0 1,69.57 51.73A1.19,1.19 0,0 1,70.67 50.54z" - android:strokeWidth="1.8" - android:fillColor="#000000" - android:strokeColor="#00000000"/> - <path - android:pathData="M42.81,55.19A7.3,8.83 55.96,1 0,28.79 51.1,7.3 8.83,55.96 1,0 42.81,55.19Z" - android:strokeWidth="1.8" - android:fillColor="#00000000" - android:strokeColor="#000000"/> - <path - android:pathData="M35.46,48.05A4.62,4.62 0,0 0,31.19 52.65A4.62,4.62 0,0 0,40.42 52.65A4.62,4.62 0,0 0,35.46 48.05zM34.82,50.54A1.19,1.19 0,0 1,36.11 51.73A1.2,1.2 0,1 1,33.71 51.73A1.19,1.19 0,0 1,34.82 50.54z" - android:strokeWidth="1.8" - android:fillColor="#000000" - android:strokeColor="#00000000"/> -</vector> diff --git a/res/drawable/ic_taskbar_all_apps_button.xml b/res/drawable/ic_taskbar_all_apps_button.xml deleted file mode 100644 index 82fbbea617..0000000000 --- a/res/drawable/ic_taskbar_all_apps_button.xml +++ /dev/null @@ -1,48 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2023 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="44dp" - android:height="44dp" - android:viewportWidth="44" - android:viewportHeight="44"> - <path - android:pathData="M13,16C12.175,16 11.47,15.7 10.885,15.115C10.3,14.53 10,13.825 10,13C10,12.175 10.3,11.47 10.885,10.885C11.47,10.3 12.175,10 13,10C13.825,10 14.53,10.3 15.115,10.885C15.7,11.47 16,12.175 16,13C16,13.825 15.7,14.53 15.115,15.115C14.53,15.7 13.825,16 13,16Z" - android:fillColor="#40484B"/> - <path - android:pathData="M22,16C21.175,16 20.47,15.7 19.885,15.115C19.3,14.53 19,13.825 19,13C19,12.175 19.3,11.47 19.885,10.885C20.47,10.3 21.175,10 22,10C22.825,10 23.53,10.3 24.115,10.885C24.7,11.47 25,12.175 25,13C25,13.825 24.7,14.53 24.115,15.115C23.53,15.7 22.825,16 22,16Z" - android:fillColor="#40484B"/> - <path - android:pathData="M31,16C30.175,16 29.47,15.7 28.885,15.115C28.3,14.53 28,13.825 28,13C28,12.175 28.3,11.47 28.885,10.885C29.47,10.3 30.175,10 31,10C31.825,10 32.53,10.3 33.115,10.885C33.7,11.47 34,12.175 34,13C34,13.825 33.7,14.53 33.115,15.115C32.53,15.7 31.825,16 31,16Z" - android:fillColor="#40484B"/> - <path - android:pathData="M13,25C12.175,25 11.47,24.7 10.885,24.115C10.3,23.53 10,22.825 10,22C10,21.175 10.3,20.47 10.885,19.885C11.47,19.3 12.175,19 13,19C13.825,19 14.53,19.3 15.115,19.885C15.7,20.47 16,21.175 16,22C16,22.825 15.7,23.53 15.115,24.115C14.53,24.7 13.825,25 13,25Z" - android:fillColor="#40484B"/> - <path - android:pathData="M22,25C21.175,25 20.47,24.7 19.885,24.115C19.3,23.53 19,22.825 19,22C19,21.175 19.3,20.47 19.885,19.885C20.47,19.3 21.175,19 22,19C22.825,19 23.53,19.3 24.115,19.885C24.7,20.47 25,21.175 25,22C25,22.825 24.7,23.53 24.115,24.115C23.53,24.7 22.825,25 22,25Z" - android:fillColor="#40484B"/> - <path - android:pathData="M31,25C30.175,25 29.47,24.7 28.885,24.115C28.3,23.53 28,22.825 28,22C28,21.175 28.3,20.47 28.885,19.885C29.47,19.3 30.175,19 31,19C31.825,19 32.53,19.3 33.115,19.885C33.7,20.47 34,21.175 34,22C34,22.825 33.7,23.53 33.115,24.115C32.53,24.7 31.825,25 31,25Z" - android:fillColor="#40484B"/> - <path - android:pathData="M13,34C12.175,34 11.47,33.7 10.885,33.115C10.3,32.53 10,31.825 10,31C10,30.175 10.3,29.47 10.885,28.885C11.47,28.3 12.175,28 13,28C13.825,28 14.53,28.3 15.115,28.885C15.7,29.47 16,30.175 16,31C16,31.825 15.7,32.53 15.115,33.115C14.53,33.7 13.825,34 13,34Z" - android:fillColor="#40484B"/> - <path - android:pathData="M22,34C21.175,34 20.47,33.7 19.885,33.115C19.3,32.53 19,31.825 19,31C19,30.175 19.3,29.47 19.885,28.885C20.47,28.3 21.175,28 22,28C22.825,28 23.53,28.3 24.115,28.885C24.7,29.47 25,30.175 25,31C25,31.825 24.7,32.53 24.115,33.115C23.53,33.7 22.825,34 22,34Z" - android:fillColor="#40484B"/> - <path - android:pathData="M31,34C30.175,34 29.47,33.7 28.885,33.115C28.3,32.53 28,31.825 28,31C28,30.175 28.3,29.47 28.885,28.885C29.47,28.3 30.175,28 31,28C31.825,28 32.53,28.3 33.115,28.885C33.7,29.47 34,30.175 34,31C34,31.825 33.7,32.53 33.115,33.115C32.53,33.7 31.825,34 31,34Z" - android:fillColor="#40484B"/> -</vector> diff --git a/res/drawable/ic_transient_taskbar_all_apps_button.xml b/res/drawable/ic_transient_taskbar_all_apps_button.xml deleted file mode 100644 index 6e740aed4f..0000000000 --- a/res/drawable/ic_transient_taskbar_all_apps_button.xml +++ /dev/null @@ -1,48 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2023 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="48dp" - android:height="48dp" - android:viewportWidth="48" - android:viewportHeight="48"> - <path - android:pathData="M13.5,17C12.538,17 11.715,16.65 11.033,15.967C10.35,15.285 10,14.462 10,13.5C10,12.538 10.35,11.715 11.033,11.033C11.715,10.35 12.538,10 13.5,10C14.462,10 15.285,10.35 15.967,11.033C16.65,11.715 17,12.538 17,13.5C17,14.462 16.65,15.285 15.967,15.967C15.285,16.65 14.462,17 13.5,17Z" - android:fillColor="#40484B"/> - <path - android:pathData="M24,17C23.038,17 22.215,16.65 21.532,15.967C20.85,15.285 20.5,14.462 20.5,13.5C20.5,12.538 20.85,11.715 21.532,11.033C22.215,10.35 23.038,10 24,10C24.962,10 25.785,10.35 26.468,11.033C27.15,11.715 27.5,12.538 27.5,13.5C27.5,14.462 27.15,15.285 26.468,15.967C25.785,16.65 24.962,17 24,17Z" - android:fillColor="#40484B"/> - <path - android:pathData="M34.5,17C33.537,17 32.715,16.65 32.033,15.967C31.35,15.285 31,14.462 31,13.5C31,12.538 31.35,11.715 32.033,11.033C32.715,10.35 33.537,10 34.5,10C35.463,10 36.285,10.35 36.967,11.033C37.65,11.715 38,12.538 38,13.5C38,14.462 37.65,15.285 36.967,15.967C36.285,16.65 35.463,17 34.5,17Z" - android:fillColor="#40484B"/> - <path - android:pathData="M13.5,27.5C12.538,27.5 11.715,27.15 11.033,26.468C10.35,25.785 10,24.962 10,24C10,23.038 10.35,22.215 11.033,21.532C11.715,20.85 12.538,20.5 13.5,20.5C14.462,20.5 15.285,20.85 15.967,21.532C16.65,22.215 17,23.038 17,24C17,24.962 16.65,25.785 15.967,26.468C15.285,27.15 14.462,27.5 13.5,27.5Z" - android:fillColor="#40484B"/> - <path - android:pathData="M24,27.5C23.038,27.5 22.215,27.15 21.532,26.468C20.85,25.785 20.5,24.962 20.5,24C20.5,23.038 20.85,22.215 21.532,21.532C22.215,20.85 23.038,20.5 24,20.5C24.962,20.5 25.785,20.85 26.468,21.532C27.15,22.215 27.5,23.038 27.5,24C27.5,24.962 27.15,25.785 26.468,26.468C25.785,27.15 24.962,27.5 24,27.5Z" - android:fillColor="#40484B"/> - <path - android:pathData="M34.5,27.5C33.537,27.5 32.715,27.15 32.033,26.468C31.35,25.785 31,24.962 31,24C31,23.038 31.35,22.215 32.033,21.532C32.715,20.85 33.537,20.5 34.5,20.5C35.463,20.5 36.285,20.85 36.967,21.532C37.65,22.215 38,23.038 38,24C38,24.962 37.65,25.785 36.967,26.468C36.285,27.15 35.463,27.5 34.5,27.5Z" - android:fillColor="#40484B"/> - <path - android:pathData="M13.5,38C12.538,38 11.715,37.65 11.033,36.967C10.35,36.285 10,35.463 10,34.5C10,33.537 10.35,32.715 11.033,32.033C11.715,31.35 12.538,31 13.5,31C14.462,31 15.285,31.35 15.967,32.033C16.65,32.715 17,33.537 17,34.5C17,35.463 16.65,36.285 15.967,36.967C15.285,37.65 14.462,38 13.5,38Z" - android:fillColor="#40484B"/> - <path - android:pathData="M24,38C23.038,38 22.215,37.65 21.532,36.967C20.85,36.285 20.5,35.463 20.5,34.5C20.5,33.537 20.85,32.715 21.532,32.033C22.215,31.35 23.038,31 24,31C24.962,31 25.785,31.35 26.468,32.033C27.15,32.715 27.5,33.537 27.5,34.5C27.5,35.463 27.15,36.285 26.468,36.967C25.785,37.65 24.962,38 24,38Z" - android:fillColor="#40484B"/> - <path - android:pathData="M34.5,38C33.537,38 32.715,37.65 32.033,36.967C31.35,36.285 31,35.463 31,34.5C31,33.537 31.35,32.715 32.033,32.033C32.715,31.35 33.537,31 34.5,31C35.463,31 36.285,31.35 36.967,32.033C37.65,32.715 38,33.537 38,34.5C38,35.463 37.65,36.285 36.967,36.967C36.285,37.65 35.463,38 34.5,38Z" - android:fillColor="#40484B"/> -</vector> diff --git a/res/layout/page_indicator_dots.xml b/res/layout/page_indicator_dots.xml deleted file mode 100644 index d5fe51e734..0000000000 --- a/res/layout/page_indicator_dots.xml +++ /dev/null @@ -1,22 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2022 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> - -<com.android.launcher3.pageindicators.PageIndicatorDots xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/page_indicator" - android:layout_width="match_parent" - android:layout_height="@dimen/workspace_page_indicator_height" - android:layout_gravity="bottom | center_horizontal" - android:theme="@style/HomeScreenElementTheme" /> \ No newline at end of file diff --git a/res/values-v33/style.xml b/res/values-v33/style.xml deleted file mode 100644 index 1261b232cc..0000000000 --- a/res/values-v33/style.xml +++ /dev/null @@ -1,40 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* -* Copyright (C) 2022 The Android Open Source Project -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ ---> - -<resources> - <style name="HomeSettings.Theme" parent="@android:style/Theme.DeviceDefault.Settings"> - <item name="android:listPreferredItemPaddingEnd">16dp</item> - <item name="android:listPreferredItemPaddingStart">24dp</item> - <item name="android:navigationBarColor">@android:color/transparent</item> - <item name="android:statusBarColor">@android:color/transparent</item> - <item name="android:switchStyle">@style/SwitchStyle</item> - <item name="android:textAppearanceListItem">@style/HomeSettings.PreferenceTitle</item> - <item name="android:windowActionBar">false</item> - <item name="android:windowNoTitle">true</item> - <item name="preferenceTheme">@style/HomeSettings.PreferenceTheme</item> - <item name="android:windowAnimationStyle">@style/Animation.SharedBackground</item> - </style> - - <style name="Animation.SharedBackground" parent="@android:style/Animation.Activity"> - <item name="android:activityOpenEnterAnimation">@anim/shared_x_axis_activity_open_enter</item> - <item name="android:activityOpenExitAnimation">@anim/shared_x_axis_activity_open_exit</item> - <item name="android:activityCloseEnterAnimation">@anim/shared_x_axis_activity_close_enter</item> - <item name="android:activityCloseExitAnimation">@anim/shared_x_axis_activity_close_exit</item> - </style> -</resources> \ No newline at end of file diff --git a/res/xml/folder_shapes.xml b/res/xml/folder_shapes.xml deleted file mode 100644 index e60d333ba6..0000000000 --- a/res/xml/folder_shapes.xml +++ /dev/null @@ -1,33 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2019 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<shapes xmlns:launcher="http://schemas.android.com/apk/res-auto" > - - <Circle launcher:folderIconRadius="1" /> - - <!-- Default icon for AOSP --> - <RoundedSquare launcher:folderIconRadius="0.16" /> - - <!-- Rounded icon from RRO --> - <RoundedSquare launcher:folderIconRadius="0.6" /> - - <!-- Square icon --> - <RoundedSquare launcher:folderIconRadius="0" /> - - <TearDrop launcher:folderIconRadius="0.3" /> - <Squircle launcher:folderIconRadius="0.2" /> - -</shapes> \ No newline at end of file diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java deleted file mode 100644 index d7d6d7ad4c..0000000000 --- a/src/com/android/launcher3/BaseDraggingActivity.java +++ /dev/null @@ -1,192 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher3; - -import static com.android.launcher3.util.DisplayController.CHANGE_ROTATION; -import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; -import static com.android.launcher3.util.Executors.THREAD_POOL_EXECUTOR; - -import android.app.WallpaperManager; -import android.content.Context; -import android.content.res.Configuration; -import android.graphics.Point; -import android.graphics.Rect; -import android.os.Bundle; -import android.view.ActionMode; -import android.view.Display; -import android.view.View; - -import androidx.annotation.MainThread; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.android.launcher3.allapps.ActivityAllAppsContainerView; -import com.android.launcher3.allapps.search.DefaultSearchAdapterProvider; -import com.android.launcher3.allapps.search.SearchAdapterProvider; -import com.android.launcher3.model.data.ItemInfo; -import com.android.launcher3.touch.ItemClickHandler; -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.OnColorHintListener; -import com.android.launcher3.util.Themes; -import com.android.launcher3.util.WallpaperColorHints; -import com.android.launcher3.util.WindowBounds; - -import app.lawnchair.wallpaper.WallpaperManagerCompat; - -/** - * Extension of BaseActivity allowing support for drag-n-drop - */ -@SuppressWarnings("NewApi") -public abstract class BaseDraggingActivity extends BaseActivity - implements WallpaperManagerCompat.OnColorsChangedListener, DisplayInfoChangeListener { - - // 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(); - - private ActionMode mCurrentActionMode; - - private int mThemeRes = R.style.AppTheme; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - DisplayController.INSTANCE.get(this).addChangeListener(this); - - // Update theme - WallpaperManagerCompat.INSTANCE.get(this).addOnChangeListener(this); - int themeRes = Themes.getActivityThemeRes(this); - if (themeRes != mThemeRes) { - mThemeRes = themeRes; - setTheme(themeRes); - } - } - - @MainThread - @Override - public void onColorsChanged() { - updateTheme(); - } - - @Override - public void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - updateTheme(); - } - - protected void updateTheme() { - if (mThemeRes != Themes.getActivityThemeRes(this)) { - recreate(); - } - } - - @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; - } - - public abstract View getRootView(); - - public void returnToHomescreen() { - // no-op - } - - @Override - @NonNull - public ActivityOptionsWrapper getActivityLaunchOptions(View v, @Nullable ItemInfo item) { - ActivityOptionsWrapper wrapper = super.getActivityLaunchOptions(v, item); - addEventCallback(EVENT_RESUMED, wrapper.onEndCallback::executeAllAndDestroy); - return wrapper; - } - - @Override - public ActivityOptionsWrapper makeDefaultActivityOptions(int splashScreenStyle) { - ActivityOptionsWrapper wrapper = super.makeDefaultActivityOptions(splashScreenStyle); - addEventCallback(EVENT_RESUMED, wrapper.onEndCallback::executeAllAndDestroy); - return wrapper; - } - - @Override - protected void onDestroy() { - super.onDestroy(); - WallpaperManagerCompat.INSTANCE.get(this).removeOnChangeListener(this); - DisplayController.INSTANCE.get(this).removeChangeListener(this); - } - - protected void onDeviceProfileInitiated() { - if (mDeviceProfile.isVerticalBarLayout()) { - mDeviceProfile.updateIsSeascape(this); - } - } - - @Override - public void onDisplayInfoChanged(Context context, Info info, int flags) { - if ((flags & CHANGE_ROTATION) != 0 && mDeviceProfile.isVerticalBarLayout()) { - mDeviceProfile.updateIsSeascape(this); - reapplyUi(); - } - } - - @Override - public View.OnClickListener getItemOnClickListener() { - return ItemClickHandler.INSTANCE; - } - - protected abstract void reapplyUi(); - - protected WindowBounds getMultiWindowDisplaySize() { - if (Utilities.ATLEAST_R) { - return WindowBounds.fromWindowMetrics(getWindowManager().getCurrentWindowMetrics()); - } - // Note: Calls to getSize() can't rely on our cached DefaultDisplay since it can - // return - // the app window size - Display display = getWindowManager().getDefaultDisplay(); - Point mwSize = new Point(); - display.getSize(mwSize); - return new WindowBounds(new Rect(0, 0, mwSize.x, mwSize.y), new Rect ()); - } - - @Override - public boolean isAppBlockedForSafeMode() { - return LauncherAppState.getInstance(this).isSafeModeEnabled(); - } -} diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java deleted file mode 100644 index 70930a4a19..0000000000 --- a/src/com/android/launcher3/LauncherModel.java +++ /dev/null @@ -1,724 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT 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; - -import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED; - -import static com.android.launcher3.LauncherAppState.ACTION_FORCE_ROLOAD; -import static com.android.launcher3.LauncherPrefs.WORK_EDU_STEP; -import static com.android.launcher3.config.FeatureFlags.IS_STUDIO_BUILD; -import static com.android.launcher3.icons.cache.BaseIconCache.EMPTY_CLASS_NAME; -import static com.android.launcher3.model.PackageUpdatedTask.OP_UPDATE; -import static com.android.launcher3.pm.UserCache.ACTION_PROFILE_AVAILABLE; -import static com.android.launcher3.pm.UserCache.ACTION_PROFILE_UNAVAILABLE; -import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; -import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; - -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageInstaller; -import android.content.pm.ShortcutInfo; -import android.os.UserHandle; -import android.text.TextUtils; -import android.util.Log; -import android.util.Pair; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.WorkerThread; - -import com.android.launcher3.celllayout.CellPosMapper; -import com.android.launcher3.config.FeatureFlags; -import com.android.launcher3.icons.IconCache; -import com.android.launcher3.model.AddWorkspaceItemsTask; -import com.android.launcher3.model.AllAppsList; -import com.android.launcher3.model.BaseLauncherBinder; -import com.android.launcher3.model.BgDataModel; -import com.android.launcher3.model.BgDataModel.Callbacks; -import com.android.launcher3.model.CacheDataUpdatedTask; -import com.android.launcher3.model.ItemInstallQueue; -import com.android.launcher3.model.LoaderTask; -import com.android.launcher3.model.ModelDbController; -import com.android.launcher3.model.ModelDelegate; -import com.android.launcher3.model.ModelLauncherCallbacks; -import com.android.launcher3.model.ModelTaskController; -import com.android.launcher3.model.ModelWriter; -import com.android.launcher3.model.PackageInstallStateChangedTask; -import com.android.launcher3.model.PackageUpdatedTask; -import com.android.launcher3.model.ReloadStringCacheTask; -import com.android.launcher3.model.ShortcutsChangedTask; -import com.android.launcher3.model.UserLockStateChangedTask; -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.InstallSessionTracker; -import com.android.launcher3.pm.PackageInstallInfo; -import com.android.launcher3.pm.UserCache; -import com.android.launcher3.shortcuts.ShortcutRequest; -import com.android.launcher3.util.IntSet; -import com.android.launcher3.util.ItemInfoMatcher; -import com.android.launcher3.util.PackageManagerHelper; -import com.android.launcher3.util.PackageUserKey; -import com.android.launcher3.util.Preconditions; - -import java.io.FileDescriptor; -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.concurrent.CancellationException; -import java.util.function.Consumer; -import java.util.function.Supplier; -import java.util.stream.Collectors; - -/** - * Maintains in-memory state of the Launcher. It is expected that there should - * be only one - * LauncherModel object held in a static. Also provide APIs for updating the - * database state - * for the Launcher. - */ -public class LauncherModel implements InstallSessionTracker.Callback { - private static final boolean DEBUG_RECEIVER = false; - - static final String TAG = "Launcher.Model"; - - // Broadcast intent to track when the profile gets locked: - // ACTION_MANAGED_PROFILE_UNAVAILABLE can be used until Android U where profile - // no longer gets - // locked when paused. - // ACTION_PROFILE_INACCESSIBLE always means that the profile is getting locked - // but it only - // appeared in Android S. - private static final String ACTION_PROFILE_LOCKED = Utilities.ATLEAST_U - ? Intent.ACTION_PROFILE_INACCESSIBLE - : Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE; - - @NonNull - private final LauncherAppState mApp; - @NonNull - private final PackageManagerHelper mPmHelper; - @NonNull - private final ModelDbController mModelDbController; - @NonNull - private final Object mLock = new Object(); - @Nullable - private LoaderTask mLoaderTask; - private boolean mIsLoaderTaskRunning; - - // only allow this once per reboot to reload work apps - private boolean mShouldReloadWorkProfile = true; - - // Indicates whether the current model data is valid or not. - // We start off with everything not loaded. After that, we assume that - // our monitoring of the package manager provides all updates and we never - // need to do a requery. This is only ever touched from the loader thread. - private boolean mModelLoaded; - private boolean mModelDestroyed = false; - - public boolean isModelLoaded() { - synchronized (mLock) { - return mModelLoaded && mLoaderTask == null && !mModelDestroyed; - } - } - - @NonNull - private final ArrayList<Callbacks> mCallbacksList = new ArrayList<>(1); - - // < only access in worker thread > - @NonNull - private final AllAppsList mBgAllAppsList; - - /** - * 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. - */ - @NonNull - private final BgDataModel mBgDataModel = new BgDataModel(); - - @NonNull - private final ModelDelegate mModelDelegate; - - private int mLastLoadId = -1; - - // Runnable to check if the shortcuts permission has changed. - @NonNull - private final Runnable mDataValidationCheck = new Runnable() { - @Override - public void run() { - if (mModelLoaded) { - mModelDelegate.validateData(); - } - } - }; - - LauncherModel(@NonNull final Context context, @NonNull final LauncherAppState app, - @NonNull final IconCache iconCache, @NonNull final AppFilter appFilter, - @NonNull final PackageManagerHelper pmHelper, final boolean isPrimaryInstance) { - mApp = app; - mPmHelper = pmHelper; - mModelDbController = new ModelDbController(context); - mBgAllAppsList = new AllAppsList(iconCache, appFilter); - mModelDelegate = ModelDelegate.newInstance(context, app, mPmHelper, mBgAllAppsList, - mBgDataModel, isPrimaryInstance); - } - - @NonNull - public ModelDelegate getModelDelegate() { - return mModelDelegate; - } - - public ModelDbController getModelDbController() { - return mModelDbController; - } - - public ModelLauncherCallbacks newModelCallbacks() { - return new ModelLauncherCallbacks(this::enqueueModelUpdateTask); - } - - /** - * Adds the provided items to the workspace. - */ - public void addAndBindAddedWorkspaceItems( - @NonNull final List<Pair<ItemInfo, Object>> itemList) { - for (Callbacks cb : getCallbacks()) { - cb.preAddApps(); - } - enqueueModelUpdateTask(new AddWorkspaceItemsTask(itemList)); - } - - @NonNull - public ModelWriter getWriter(final boolean verifyChanges, CellPosMapper cellPosMapper, - @Nullable final Callbacks owner) { - return new ModelWriter(mApp.getContext(), this, mBgDataModel, verifyChanges, cellPosMapper, - owner); - } - - /** - * Called when the icon for an app changes, outside of package event - */ - @WorkerThread - public void onAppIconChanged(@NonNull final String packageName, - @NonNull final UserHandle user) { - // Update the icon for the calendar package - Context context = mApp.getContext(); - enqueueModelUpdateTask(new PackageUpdatedTask(OP_UPDATE, user, packageName)); - - List<ShortcutInfo> pinnedShortcuts = new ShortcutRequest(context, user) - .forPackage(packageName).query(ShortcutRequest.PINNED); - if (!pinnedShortcuts.isEmpty()) { - enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, pinnedShortcuts, user, - false)); - } - } - - /** - * Called when the workspace items have drastically changed - */ - public void onWorkspaceUiChanged() { - MODEL_EXECUTOR.execute(mModelDelegate::workspaceLoadComplete); - } - - /** - * Called when the model is destroyed - */ - public void destroy() { - mModelDestroyed = true; - MODEL_EXECUTOR.execute(mModelDelegate::destroy); - } - - public void onBroadcastIntent(@NonNull final Intent intent) { - if (DEBUG_RECEIVER) - Log.d(TAG, "onReceive intent=" + intent); - final String action = intent.getAction(); - if (Intent.ACTION_LOCALE_CHANGED.equals(action)) { - // If we have changed locale we need to clear out the labels in all - // apps/workspace. - forceReload(); - } else if (ACTION_DEVICE_POLICY_RESOURCE_UPDATED.equals(action)) { - enqueueModelUpdateTask(new ReloadStringCacheTask(mModelDelegate)); - } else if (IS_STUDIO_BUILD && ACTION_FORCE_ROLOAD.equals(action)) { - for (Callbacks cb : getCallbacks()) { - if (cb instanceof Launcher) { - ((Launcher) cb).recreate(); - } - } - } - } - - /** - * Called then there use a user event - * - * @see UserCache#addUserEventListener - */ - public void onUserEvent(UserHandle user, String action) { - if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) - && mShouldReloadWorkProfile) { - mShouldReloadWorkProfile = false; - forceReload(); - } else if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) - || Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)) { - mShouldReloadWorkProfile = false; - enqueueModelUpdateTask(new PackageUpdatedTask( - PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE, user)); - } else if (UserCache.ACTION_PROFILE_LOCKED.equals(action) - || UserCache.ACTION_PROFILE_UNLOCKED.equals(action)) { - enqueueModelUpdateTask(new UserLockStateChangedTask( - user, UserCache.ACTION_PROFILE_UNLOCKED.equals(action))); - } else if (UserCache.ACTION_PROFILE_ADDED.equals(action) - || UserCache.ACTION_PROFILE_REMOVED.equals(action)) { - forceReload(); - } else if (ACTION_PROFILE_AVAILABLE.equals(action) - || ACTION_PROFILE_UNAVAILABLE.equals(action)) { - /* - * This broadcast is only available when android.os.Flags.allowPrivateProfile() - * is set. - * For Work-profile this broadcast will be sent in addition to - * ACTION_MANAGED_PROFILE_AVAILABLE/UNAVAILABLE. - * So effectively, this if block only handles the non-work profile case. - */ - enqueueModelUpdateTask(new PackageUpdatedTask( - PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE, user)); - } - if (Intent.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) { - LauncherPrefs.get(mApp.getContext()).put(WORK_EDU_STEP, 0); - } - } - - /** - * Reloads the workspace items from the DB and re-binds the workspace. This - * should generally - * not be called as DB updates are automatically followed by UI update - */ - public void forceReload() { - synchronized (mLock) { - // Stop any existing loaders first, so they don't set mModelLoaded to true later - stopLoader(); - mModelLoaded = false; - } - - // Start the loader if launcher is already running, otherwise the loader will - // run, - // the next time launcher starts - if (hasCallbacks()) { - startLoader(); - } - } - - /** - * Rebinds all existing callbacks with already loaded model - */ - public void rebindCallbacks() { - if (hasCallbacks()) { - startLoader(); - } - } - - /** - * Removes an existing callback - */ - public void removeCallbacks(@NonNull final Callbacks callbacks) { - synchronized (mCallbacksList) { - Preconditions.assertUIThread(); - if (mCallbacksList.remove(callbacks)) { - if (stopLoader()) { - // Rebind existing callbacks - startLoader(); - } - } - } - } - - /** - * Adds a callbacks to receive model updates - * - * @return true if workspace load was performed synchronously - */ - public boolean addCallbacksAndLoad(@NonNull final Callbacks callbacks) { - synchronized (mLock) { - addCallbacks(callbacks); - return startLoader(new Callbacks[] { callbacks }); - - } - } - - /** - * Adds a callbacks to receive model updates - */ - public void addCallbacks(@NonNull final Callbacks callbacks) { - Preconditions.assertUIThread(); - synchronized (mCallbacksList) { - mCallbacksList.add(callbacks); - } - } - - /** - * Starts the loader. Tries to bind {@params synchronousBindPage} synchronously - * if possible. - * - * @return true if the page could be bound synchronously. - */ - public boolean startLoader() { - return startLoader(new Callbacks[0]); - } - - private boolean startLoader(@NonNull final Callbacks[] newCallbacks) { - // Enable queue before starting loader. It will get disabled in - // Launcher#finishBindingItems - ItemInstallQueue.INSTANCE.get(mApp.getContext()) - .pauseModelPush(ItemInstallQueue.FLAG_LOADER_RUNNING); - synchronized (mLock) { - // If there is already one running, tell it to stop. - boolean wasRunning = stopLoader(); - boolean bindDirectly = mModelLoaded && !mIsLoaderTaskRunning; - boolean bindAllCallbacks = wasRunning || !bindDirectly || newCallbacks.length == 0; - final Callbacks[] callbacksList = bindAllCallbacks ? getCallbacks() : newCallbacks; - - if (callbacksList.length > 0) { - // Clear any pending bind-runnables from the synchronized load process. - for (Callbacks cb : callbacksList) { - MAIN_EXECUTOR.execute(cb::clearPendingBinds); - } - - BaseLauncherBinder launcherBinder = new BaseLauncherBinder( - mApp, mBgDataModel, mBgAllAppsList, callbacksList); - if (bindDirectly) { - // Divide the set of loaded items into those that we are binding synchronously, - // and everything else that is to be bound normally (asynchronously). - launcherBinder.bindWorkspace(bindAllCallbacks, /* isBindSync= */ true); - // For now, continue posting the binding of AllApps as there are other - // issues that arise from that. - launcherBinder.bindAllApps(); - launcherBinder.bindDeepShortcuts(); - launcherBinder.bindWidgets(); - if (FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) { - mModelDelegate.bindAllModelExtras(callbacksList); - } - return true; - } else { - stopLoader(); - mLoaderTask = new LoaderTask( - mApp, mBgAllAppsList, mBgDataModel, mModelDelegate, launcherBinder); - - // Always post the loader task, instead of running directly - // (even on same thread) so that we exit any nested synchronized blocks - MODEL_EXECUTOR.post(mLoaderTask); - } - } - } - return false; - } - - /** - * If there is already a loader task running, tell it to stop. - * - * @return true if an existing loader was stopped. - */ - private boolean stopLoader() { - synchronized (mLock) { - LoaderTask oldTask = mLoaderTask; - mLoaderTask = null; - if (oldTask != null) { - oldTask.stopLocked(); - return true; - } - return false; - } - } - - /** - * Loads the model if not loaded - * - * @param callback called with the data model upon successful load or null on - * model thread. - */ - public void loadAsync(@NonNull final Consumer<BgDataModel> callback) { - synchronized (mLock) { - if (!mModelLoaded && !mIsLoaderTaskRunning) { - startLoader(); - } - } - MODEL_EXECUTOR.post(() -> callback.accept(isModelLoaded() ? mBgDataModel : null)); - } - - @Override - public void onInstallSessionCreated(@NonNull final PackageInstallInfo sessionInfo) { - if (FeatureFlags.PROMISE_APPS_IN_ALL_APPS.get()) { - enqueueModelUpdateTask((taskController, dataModel, apps) -> { - apps.addPromiseApp(mApp.getContext(), sessionInfo); - taskController.bindApplicationsIfNeeded(); - }); - } - } - - @Override - public void onSessionFailure(@NonNull final String packageName, - @NonNull final UserHandle user) { - enqueueModelUpdateTask((taskController, dataModel, apps) -> { - IconCache iconCache = mApp.getIconCache(); - final IntSet removedIds = new IntSet(); - HashSet<WorkspaceItemInfo> archivedWorkspaceItemsToCacheRefresh = new HashSet<>(); - boolean isAppArchived = PackageManagerHelper.INSTANCE.get(mApp.getContext()) - .isAppArchivedForUser(packageName, user); - synchronized (dataModel) { - if (isAppArchived) { - // Remove package icon cache entry for archived app in case of a session - // failure. - mApp.getIconCache().remove( - new ComponentName(packageName, packageName + EMPTY_CLASS_NAME), - user); - } - - for (ItemInfo info : dataModel.itemsIdMap) { - if (info instanceof WorkspaceItemInfo - && ((WorkspaceItemInfo) info).hasPromiseIconUi() - && user.equals(info.user) - && info.getIntent() != null) { - if (TextUtils.equals(packageName, info.getIntent().getPackage())) { - removedIds.add(info.id); - } - if (((WorkspaceItemInfo) info).isArchived()) { - WorkspaceItemInfo workspaceItem = (WorkspaceItemInfo) info; - // Refresh icons on the workspace for archived apps. - iconCache.getTitleAndIcon(workspaceItem, - workspaceItem.usingLowResIcon()); - archivedWorkspaceItemsToCacheRefresh.add(workspaceItem); - } - } - } - - if (isAppArchived) { - apps.updateIconsAndLabels(new HashSet<>(List.of(packageName)), user); - } - } - - if (!removedIds.isEmpty() && !isAppArchived) { - taskController.deleteAndBindComponentsRemoved( - ItemInfoMatcher.ofItemIds(removedIds), - "removed because install session failed"); - } - if (!archivedWorkspaceItemsToCacheRefresh.isEmpty()) { - taskController.bindUpdatedWorkspaceItems( - new ArrayList<>(archivedWorkspaceItemsToCacheRefresh)); - } - if (isAppArchived) { - taskController.bindApplicationsIfNeeded(); - } - }); - } - - @Override - public void onPackageStateChanged(@NonNull final PackageInstallInfo installInfo) { - enqueueModelUpdateTask(new PackageInstallStateChangedTask(installInfo)); - } - - /** - * Updates the icons and label of all pending icons for the provided package - * name. - */ - @Override - public void onUpdateSessionDisplay(@NonNull final PackageUserKey key, - @NonNull final PackageInstaller.SessionInfo info) { - mApp.getIconCache().updateSessionCache(key, info); - - HashSet<String> packages = new HashSet<>(); - packages.add(key.mPackageName); - enqueueModelUpdateTask(new CacheDataUpdatedTask( - CacheDataUpdatedTask.OP_SESSION_UPDATE, key.mUser, packages)); - } - - public class LoaderTransaction implements AutoCloseable { - - @NonNull - private final LoaderTask mTask; - - private LoaderTransaction(@NonNull final LoaderTask task) throws CancellationException { - synchronized (mLock) { - if (mLoaderTask != task) { - throw new CancellationException("Loader already stopped"); - } - mLastLoadId++; - mTask = task; - mIsLoaderTaskRunning = true; - mModelLoaded = false; - } - } - - public void commit() { - synchronized (mLock) { - // Everything loaded bind the data. - mModelLoaded = true; - } - } - - @Override - public void close() { - synchronized (mLock) { - // If we are still the last one to be scheduled, remove ourselves. - if (mLoaderTask == mTask) { - mLoaderTask = null; - } - mIsLoaderTaskRunning = false; - } - } - } - - public LoaderTransaction beginLoader(@NonNull final LoaderTask task) - throws CancellationException { - return new LoaderTransaction(task); - } - - /** - * Refreshes the cached shortcuts if the shortcut permission has changed. - * Current implementation simply reloads the workspace, but it can be optimized - * to - * use partial updates similar to {@link UserCache} - */ - public void validateModelDataOnResume() { - MODEL_EXECUTOR.getHandler().removeCallbacks(mDataValidationCheck); - MODEL_EXECUTOR.post(mDataValidationCheck); - } - - /** - * Called when the icons for packages have been updated in the icon cache. - */ - public void onPackageIconsUpdated(@NonNull final HashSet<String> updatedPackages, - @NonNull final UserHandle user) { - // If any package icon has changed (app was updated while launcher was dead), - // update the corresponding shortcuts. - enqueueModelUpdateTask(new CacheDataUpdatedTask( - CacheDataUpdatedTask.OP_CACHE_UPDATE, user, updatedPackages)); - } - - /** - * Called when the labels for the widgets has updated in the icon cache. - */ - public void onWidgetLabelsUpdated(@NonNull final HashSet<String> updatedPackages, - @NonNull final UserHandle user) { - enqueueModelUpdateTask((taskController, dataModel, apps) -> { - dataModel.widgetsModel.onPackageIconsUpdated(updatedPackages, user, mApp); - taskController.bindUpdatedWidgets(dataModel); - }); - } - - public void enqueueModelUpdateTask(@NonNull final ModelUpdateTask task) { - if (mModelDestroyed) { - return; - } - MODEL_EXECUTOR.execute(() -> { - if (!isModelLoaded()) { - // Loader has not yet run. - return; - } - ModelTaskController controller = new ModelTaskController( - mApp, mBgDataModel, mBgAllAppsList, this, MAIN_EXECUTOR); - task.execute(controller, mBgDataModel, mBgAllAppsList); - }); - } - - /** - * A task to be executed on the current callbacks on the UI thread. - * If there is no current callbacks, the task is ignored. - */ - public interface CallbackTask { - - void execute(@NonNull Callbacks callbacks); - } - - public interface ModelUpdateTask { - - void execute(@NonNull ModelTaskController taskController, - @NonNull BgDataModel dataModel, @NonNull AllAppsList apps); - } - - public void updateAndBindWorkspaceItem(@NonNull final WorkspaceItemInfo si, - @NonNull final ShortcutInfo info) { - updateAndBindWorkspaceItem(() -> { - si.updateFromDeepShortcutInfo(info, mApp.getContext()); - mApp.getIconCache().getShortcutIcon(si, info); - return si; - }); - } - - /** - * Utility method to update a shortcut on the background thread. - */ - public void updateAndBindWorkspaceItem( - @NonNull final Supplier<WorkspaceItemInfo> itemProvider) { - enqueueModelUpdateTask((taskController, dataModel, apps) -> { - WorkspaceItemInfo info = itemProvider.get(); - taskController.getModelWriter().updateItemInDatabase(info); - ArrayList<WorkspaceItemInfo> update = new ArrayList<>(); - update.add(info); - taskController.bindUpdatedWorkspaceItems(update); - }); - } - - public void refreshAndBindWidgetsAndShortcuts(@Nullable final PackageUserKey packageUser) { - enqueueModelUpdateTask((taskController, dataModel, apps) -> { - dataModel.widgetsModel.update(taskController.getApp(), packageUser); - taskController.bindUpdatedWidgets(dataModel); - }); - } - - public void dumpState(@Nullable final String prefix, @Nullable final FileDescriptor fd, - @NonNull final PrintWriter writer, @NonNull final String[] args) { - if (args.length > 0 && TextUtils.equals(args[0], "--all")) { - writer.println(prefix + "All apps list: size=" + mBgAllAppsList.data.size()); - for (AppInfo info : mBgAllAppsList.data) { - writer.println(prefix + " title=\"" + info.title - + "\" bitmapIcon=" + info.bitmap.icon - + " componentName=" + info.componentName.getPackageName()); - } - writer.println(); - } - mModelDelegate.dump(prefix, fd, writer, args); - mBgDataModel.dump(prefix, fd, writer, args); - } - - /** - * Returns true if there are any callbacks attached to the model - */ - public boolean hasCallbacks() { - synchronized (mCallbacksList) { - return !mCallbacksList.isEmpty(); - } - } - - /** - * Returns an array of currently attached callbacks - */ - @NonNull - public Callbacks[] getCallbacks() { - synchronized (mCallbacksList) { - return mCallbacksList.toArray(new Callbacks[mCallbacksList.size()]); - } - } - - /** - * Returns the ID for the last model load. If the load ID doesn't match for a - * transaction, the - * transaction should be ignored. - */ - public int getLastLoadId() { - return mLastLoadId; - } -} diff --git a/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.java b/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.java deleted file mode 100644 index 79b81871cc..0000000000 --- a/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * 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<CellLayout, DragAndDropAccessibilityDelegate> mDelegateFactory; - - /** - * @param parent the viewgroup containing all the children - * @param delegateFactory function to create no delegates - */ - public AccessibleDragListenerAdapter(ViewGroup parent, - Function<CellLayout, DragAndDropAccessibilityDelegate> 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/allapps/WorkModeSwitch.java b/src/com/android/launcher3/allapps/WorkModeSwitch.java deleted file mode 100644 index 20dfd8247f..0000000000 --- a/src/com/android/launcher3/allapps/WorkModeSwitch.java +++ /dev/null @@ -1,235 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.launcher3.allapps; - -import static com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip.getTabWidth; - -import android.animation.LayoutTransition; -import android.content.Context; -import android.graphics.Insets; -import android.graphics.Rect; -import android.os.Build; -import android.util.AttributeSet; -import android.view.View; -import android.view.WindowInsets; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.core.view.WindowInsetsCompat; - -import com.android.launcher3.DeviceProfile; -import com.android.launcher3.Insettable; -import com.android.launcher3.R; -import com.android.launcher3.Utilities; -import com.android.launcher3.anim.KeyboardInsetAnimationCallback; -import com.android.launcher3.model.StringCache; -import com.android.launcher3.views.ActivityContext; - -import app.lawnchair.font.FontManager; -import app.lawnchair.theme.color.tokens.ColorTokens; -import app.lawnchair.theme.drawable.DrawableTokens; - -/** - * Work profile toggle switch shown at the bottom of AllApps work tab - */ -public class WorkModeSwitch extends LinearLayout implements Insettable, - KeyboardInsetAnimationCallback.KeyboardInsetListener { - - private static final int FLAG_FADE_ONGOING = 1 << 1; - private static final int FLAG_TRANSLATION_ONGOING = 1 << 2; - private static final int FLAG_PROFILE_TOGGLE_ONGOING = 1 << 3; - private static final int SCROLL_THRESHOLD_DP = 10; - - private final Rect mInsets = new Rect(); - private final Rect mImeInsets = new Rect(); - private int mFlags; - private final ActivityContext mActivityContext; - private final Context mContext; - - // Threshold when user scrolls up/down to determine when should button - // extend/collapse - private final int mScrollThreshold; - private TextView mTextView; - private ImageView mIcon; - - public WorkModeSwitch(@NonNull Context context) { - this(context, null, 0); - } - - public WorkModeSwitch(@NonNull Context context, @NonNull AttributeSet attrs) { - this(context, attrs, 0); - } - - public WorkModeSwitch(@NonNull Context context, @NonNull AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - mContext = context; - mScrollThreshold = Utilities.dpToPx(SCROLL_THRESHOLD_DP); - mActivityContext = ActivityContext.lookupContext(getContext()); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mIcon = findViewById(R.id.work_icon); - - mTextView = findViewById(R.id.pause_text); - FontManager.INSTANCE.get(getContext()).setCustomFont(mTextView, R.id.font_body_medium); - - setSelected(true); - if (Utilities.ATLEAST_R) { - KeyboardInsetAnimationCallback keyboardInsetAnimationCallback = new KeyboardInsetAnimationCallback(this); - setWindowInsetsAnimationCallback(keyboardInsetAnimationCallback); - } - - setBackground(DrawableTokens.WorkAppsToggleBackground.resolve(getContext())); - var textColor = ColorTokens.ColorPrimary.resolveColor(getContext()); - mIcon.setColorFilter(textColor); - mTextView.setTextColor(textColor); - setInsets(mActivityContext.getDeviceProfile().getInsets()); - mTextView.setText(R.string.work_apps_pause_btn_text); - - if (Utilities.ATLEAST_U) { - getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING); - } - } - - @Override - public void setInsets(Rect insets) { - mInsets.set(insets); - updateTranslationY(); - MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams(); - if (lp != null) { - int bottomMargin = getResources().getDimensionPixelSize(R.dimen.work_fab_margin_bottom); - DeviceProfile dp = ActivityContext.lookupContext(getContext()).getDeviceProfile(); - if (mActivityContext.getAppsView().isSearchBarFloating()) { - bottomMargin += dp.hotseatQsbHeight; - } - - if (!dp.isGestureMode && dp.isTaskbarPresent) { - bottomMargin += dp.taskbarHeight; - } - - lp.bottomMargin = bottomMargin; - } - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - View parent = (View) getParent(); - var dp = mActivityContext.getDeviceProfile(); - int allAppsLeftRightPadding = dp.allAppsPadding.left + dp.allAppsPadding.right; - int size = parent.getWidth() - parent.getPaddingLeft() - parent.getPaddingRight() - - 2 * allAppsLeftRightPadding; - int tabWidth = getTabWidth(getContext(), size); - int shift = (size - tabWidth) / 2 + allAppsLeftRightPadding; - setTranslationX(Utilities.isRtl(getResources()) ? shift : -shift); - } - - @Override - public boolean isEnabled() { - return super.isEnabled() && getVisibility() == VISIBLE && mFlags == 0; - } - - public void animateVisibility(boolean visible) { - clearAnimation(); - if (visible) { - setFlag(FLAG_FADE_ONGOING); - setVisibility(VISIBLE); - extend(); - animate().alpha(1).withEndAction(() -> removeFlag(FLAG_FADE_ONGOING)).start(); - } else if (getVisibility() != GONE) { - setFlag(FLAG_FADE_ONGOING); - animate().alpha(0).withEndAction(() -> { - removeFlag(FLAG_FADE_ONGOING); - setVisibility(GONE); - }).start(); - } - } - - @Override - public WindowInsets onApplyWindowInsets(WindowInsets insets) { - WindowInsetsCompat windowInsetsCompat = WindowInsetsCompat.toWindowInsetsCompat(insets, this); - if (windowInsetsCompat.isVisible(WindowInsetsCompat.Type.ime())) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - setInsets(mImeInsets, windowInsetsCompat.getInsets(WindowInsetsCompat.Type.ime()).toPlatformInsets()); - } - } else { - mImeInsets.setEmpty(); - } - updateTranslationY(); - return super.onApplyWindowInsets(insets); - } - - void updateTranslationY() { - setTranslationY(-mImeInsets.bottom); - } - - @Override - public void setTranslationY(float translationY) { - // Always translate at least enough for nav bar insets. - super.setTranslationY(Math.min(translationY, -mInsets.bottom)); - } - - private void setInsets(Rect rect, Insets insets) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - rect.set(insets.left, insets.top, insets.right, insets.bottom); - } - } - - public Rect getImeInsets() { - return mImeInsets; - } - - @Override - public void onTranslationStart() { - setFlag(FLAG_TRANSLATION_ONGOING); - } - - @Override - public void onTranslationEnd() { - removeFlag(FLAG_TRANSLATION_ONGOING); - } - - private void setFlag(int flag) { - mFlags |= flag; - } - - private void removeFlag(int flag) { - mFlags &= ~flag; - } - - public void extend() { - mTextView.setVisibility(VISIBLE); - } - - public void shrink() { - mTextView.setVisibility(GONE); - } - - public int getScrollThreshold() { - return mScrollThreshold; - } - - public void updateStringFromCache() { - StringCache cache = mActivityContext.getStringCache(); - if (cache != null) { - mTextView.setText(cache.workProfilePauseButton); - } - } -} diff --git a/src/com/android/launcher3/dagger/ActivityContextScope.java b/src/com/android/launcher3/dagger/ActivityContextScope.java deleted file mode 100644 index 887f15cacf..0000000000 --- a/src/com/android/launcher3/dagger/ActivityContextScope.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher3.dagger; - -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -import javax.inject.Scope; - -/** - * Scope annotation for singletons associated with Launcher activity context. - */ -@Documented -@Retention(RetentionPolicy.RUNTIME) -@Scope -public @interface ActivityContextScope { -} diff --git a/src/com/android/launcher3/dagger/ApplicationContext.java b/src/com/android/launcher3/dagger/ApplicationContext.java deleted file mode 100644 index 9a5b08b51b..0000000000 --- a/src/com/android/launcher3/dagger/ApplicationContext.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher3.dagger; - -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -import javax.inject.Qualifier; - -/** - * Qualifier for Launcher application context. - */ -@Documented -@Retention(RetentionPolicy.RUNTIME) -@Qualifier -public @interface ApplicationContext { -} diff --git a/src/com/android/launcher3/dagger/LauncherAppSingleton.java b/src/com/android/launcher3/dagger/LauncherAppSingleton.java deleted file mode 100644 index 92c00b6d85..0000000000 --- a/src/com/android/launcher3/dagger/LauncherAppSingleton.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher3.dagger; - -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -import javax.inject.Scope; - -/** - * Scope annotation for singleton items within the LauncherAppComponent. - */ -@Documented -@Retention(RetentionPolicy.RUNTIME) -@Scope -public @interface LauncherAppSingleton { -} diff --git a/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java b/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java deleted file mode 100644 index fbe9e33d5a..0000000000 --- a/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * 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/graphics/GridCustomizationsProvider.java b/src/com/android/launcher3/graphics/GridCustomizationsProvider.java deleted file mode 100644 index eddc1f2faa..0000000000 --- a/src/com/android/launcher3/graphics/GridCustomizationsProvider.java +++ /dev/null @@ -1,270 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.launcher3.graphics; - -import static com.android.launcher3.LauncherPrefs.THEMED_ICONS; -import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; -import static com.android.launcher3.util.Themes.isThemedIconEnabled; - -import android.content.ContentProvider; -import android.content.ContentValues; -import android.content.pm.PackageManager; -import android.database.Cursor; -import android.database.MatrixCursor; -import android.net.Uri; -import android.os.Binder; -import android.os.Bundle; -import android.os.Handler; -import android.os.IBinder; -import android.os.IBinder.DeathRecipient; -import android.os.Message; -import android.os.Messenger; -import android.util.ArrayMap; -import android.util.Log; -import android.util.Pair; - -import com.android.launcher3.InvariantDeviceProfile; -import com.android.launcher3.InvariantDeviceProfile.GridOption; -import com.android.launcher3.LauncherPrefs; -import com.android.launcher3.util.Executors; - -/** - * Exposes various launcher grid options and allows the caller to change them. - * APIs: - * /list_options: List the various available grip options, has following columns - * name: name of the grid - * rows: number of rows in the grid - * cols: number of columns in the grid - * preview_count: number of previews available for this grid option. The preview uri - * looks like /preview/<grid-name>/<preview index starting with 0> - * is_default: true if this grid is currently active - * - * /preview: Opens a file stream for the grid preview - * - * /default_grid: Call update to set the current grid, with values - * name: name of the grid to apply - */ -public class GridCustomizationsProvider extends ContentProvider { - - private static final String TAG = "GridCustomizationsProvider"; - - private static final String KEY_NAME = "name"; - private static final String KEY_ROWS = "rows"; - private static final String KEY_COLS = "cols"; - private static final String KEY_PREVIEW_COUNT = "preview_count"; - private static final String KEY_IS_DEFAULT = "is_default"; - - private static final String KEY_LIST_OPTIONS = "/list_options"; - private static final String KEY_DEFAULT_GRID = "/default_grid"; - - private static final String METHOD_GET_PREVIEW = "get_preview"; - - private static final String GET_ICON_THEMED = "/get_icon_themed"; - private static final String SET_ICON_THEMED = "/set_icon_themed"; - private static final String ICON_THEMED = "/icon_themed"; - private static final String BOOLEAN_VALUE = "boolean_value"; - - private static final String KEY_SURFACE_PACKAGE = "surface_package"; - private static final String KEY_CALLBACK = "callback"; - public static final String KEY_HIDE_BOTTOM_ROW = "hide_bottom_row"; - - private static final int MESSAGE_ID_UPDATE_PREVIEW = 1337; - - /** - * Here we use the IBinder and the screen ID as the key of the active previews. - */ - private final ArrayMap<Pair<IBinder, Integer>, PreviewLifecycleObserver> mActivePreviews = - new ArrayMap<>(); - - @Override - public boolean onCreate() { - return true; - } - - @Override - public Cursor query(Uri uri, String[] projection, String selection, - String[] selectionArgs, String sortOrder) { - switch (uri.getPath()) { - case KEY_LIST_OPTIONS: { - MatrixCursor cursor = new MatrixCursor(new String[]{ - KEY_NAME, KEY_ROWS, KEY_COLS, KEY_PREVIEW_COUNT, KEY_IS_DEFAULT}); - InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(getContext()); - for (GridOption gridOption : InvariantDeviceProfile.parseAllGridOptions(getContext())) { - cursor.newRow() - .add(KEY_NAME, gridOption.name) - .add(KEY_ROWS, gridOption.numRows) - .add(KEY_COLS, gridOption.numColumns) - .add(KEY_PREVIEW_COUNT, 1) - .add(KEY_IS_DEFAULT, idp.numColumns == gridOption.numColumns - && idp.numRows == gridOption.numRows); - } - return cursor; - } - case GET_ICON_THEMED: - case ICON_THEMED: { - MatrixCursor cursor = new MatrixCursor(new String[]{BOOLEAN_VALUE}); - cursor.newRow().add(BOOLEAN_VALUE, isThemedIconEnabled(getContext()) ? 1 : 0); - return cursor; - } - default: - return null; - } - } - - @Override - public String getType(Uri uri) { - return "vnd.android.cursor.dir/launcher_grid"; - } - - @Override - public Uri insert(Uri uri, ContentValues initialValues) { - return null; - } - - @Override - public int delete(Uri uri, String selection, String[] selectionArgs) { - return 0; - } - - @Override - public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { - switch (uri.getPath()) { - case KEY_DEFAULT_GRID: { - String gridName = values.getAsString(KEY_NAME); - InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(getContext()); - // Verify that this is a valid grid option - GridOption match = null; - for (GridOption option : InvariantDeviceProfile.parseAllGridOptions(getContext())) { - if (option.name.equals(gridName)) { - match = option; - break; - } - } - if (match == null) { - return 0; - } - - idp.setCurrentGrid(getContext(), gridName); - getContext().getContentResolver().notifyChange(uri, null); - return 1; - } - case ICON_THEMED: - case SET_ICON_THEMED: { - LauncherPrefs.get(getContext()) - .put(THEMED_ICONS, values.getAsBoolean(BOOLEAN_VALUE)); - getContext().getContentResolver().notifyChange(uri, null); - return 1; - } - default: - return 0; - } - } - - @Override - public Bundle call(String method, String arg, Bundle extras) { - if (getContext().checkPermission("android.permission.BIND_WALLPAPER", - Binder.getCallingPid(), Binder.getCallingUid()) - != PackageManager.PERMISSION_GRANTED) { - return null; - } - - if (!METHOD_GET_PREVIEW.equals(method)) { - return null; - } - return getPreview(extras); - } - - private synchronized Bundle getPreview(Bundle request) { - PreviewLifecycleObserver observer = null; - try { - PreviewSurfaceRenderer renderer = new PreviewSurfaceRenderer(getContext(), request); - - observer = new PreviewLifecycleObserver(renderer); - // Destroy previous - destroyObserver(mActivePreviews.get(observer.getIdentifier())); - mActivePreviews.put(observer.getIdentifier(), observer); - - renderer.loadAsync(); - renderer.getHostToken().linkToDeath(observer, 0); - - Bundle result = new Bundle(); - result.putParcelable(KEY_SURFACE_PACKAGE, renderer.getSurfacePackage()); - - Messenger messenger = - new Messenger(new Handler(UI_HELPER_EXECUTOR.getLooper(), observer)); - Message msg = Message.obtain(); - msg.replyTo = messenger; - result.putParcelable(KEY_CALLBACK, msg); - return result; - } catch (Exception e) { - Log.e(TAG, "Unable to generate preview", e); - if (observer != null) { - destroyObserver(observer); - } - return null; - } - } - - private synchronized void destroyObserver(PreviewLifecycleObserver observer) { - if (observer == null || observer.destroyed) { - return; - } - observer.destroyed = true; - observer.renderer.getHostToken().unlinkToDeath(observer, 0); - Executors.MAIN_EXECUTOR.execute(observer.renderer::destroy); - PreviewLifecycleObserver cached = mActivePreviews.get(observer.getIdentifier()); - if (cached == observer) { - mActivePreviews.remove(observer.getIdentifier()); - } - } - - private class PreviewLifecycleObserver implements Handler.Callback, DeathRecipient { - - public final PreviewSurfaceRenderer renderer; - public boolean destroyed = false; - - PreviewLifecycleObserver(PreviewSurfaceRenderer renderer) { - this.renderer = renderer; - } - - @Override - public boolean handleMessage(Message message) { - if (destroyed) { - return true; - } - if (message.what == MESSAGE_ID_UPDATE_PREVIEW) { - renderer.hideBottomRow(message.getData().getBoolean(KEY_HIDE_BOTTOM_ROW)); - } else { - destroyObserver(this); - } - return true; - } - - @Override - public void binderDied() { - destroyObserver(this); - } - - /** - * Returns a key that should make the PreviewSurfaceRenderer unique and if two of them have - * the same key they will be treated as the same PreviewSurfaceRenderer. Primary this is - * used to prevent memory leaks by removing the old PreviewSurfaceRenderer. - */ - public Pair<IBinder, Integer> getIdentifier() { - return new Pair<>(renderer.getHostToken(), renderer.getDisplayId()); - } - } -} diff --git a/src/com/android/launcher3/graphics/IconShape.java b/src/com/android/launcher3/graphics/IconShape.java deleted file mode 100644 index f005c6320a..0000000000 --- a/src/com/android/launcher3/graphics/IconShape.java +++ /dev/null @@ -1,513 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.launcher3.graphics; - -import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.FloatArrayEvaluator; -import android.animation.ValueAnimator; -import android.animation.ValueAnimator.AnimatorUpdateListener; -import android.content.Context; -import android.content.res.TypedArray; -import android.content.res.XmlResourceParser; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.Path; -import android.graphics.Rect; -import android.graphics.Region; -import android.graphics.Region.Op; -import android.graphics.drawable.AdaptiveIconDrawable; -import android.graphics.drawable.ColorDrawable; -import android.util.AttributeSet; -import android.util.Xml; -import android.view.View; -import android.view.ViewOutlineProvider; - -import com.android.launcher3.R; -import com.android.launcher3.Utilities; -import com.android.launcher3.anim.RoundedRectRevealOutlineProvider; -import com.android.launcher3.icons.GraphicsUtils; -import com.android.launcher3.icons.IconNormalizer; -import com.android.launcher3.util.MainThreadInitializedObject; -import com.android.launcher3.util.SafeCloseable; -import com.android.launcher3.views.ClipPathView; -import com.patrykmichalik.opto.core.PreferenceExtensionsKt; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -import app.lawnchair.icons.CustomAdaptiveIconDrawable; -import app.lawnchair.preferences2.PreferenceManager2; - -/** - * Abstract representation of the shape of an icon shape - */ -public final class IconShape implements SafeCloseable { - - public static final MainThreadInitializedObject<IconShape> INSTANCE = new MainThreadInitializedObject<>( - IconShape::new); - - private static ShapeDelegate mDelegate = new Circle(); - private float mNormalizationScale = ICON_VISIBLE_AREA_FACTOR; - - private IconShape(Context context) { - pickBestShape(context); - } - - public ShapeDelegate getShape() { - return mDelegate; - } - - public float getNormalizationScale() { - return mNormalizationScale; - } - - @Override - public void close() { - } - - /** - * Initializes the shape which is closest to the {@link AdaptiveIconDrawable} - */ - public void pickBestShape(Context context) { - // Pick any large size - final int size = 200; - - Region full = new Region(0, 0, size, size); - Region iconR = new Region(); - AdaptiveIconDrawable drawable = new CustomAdaptiveIconDrawable( - new ColorDrawable(Color.BLACK), new ColorDrawable(Color.BLACK)); - drawable.setBounds(0, 0, size, size); - iconR.setPath(drawable.getIconMask(), full); - - Path shapePath = new Path(); - Region shapeR = new Region(); - - // Find the shape with minimum area of divergent region. - int minArea = Integer.MAX_VALUE; - ShapeDelegate closestShape = null; - for (ShapeDelegate shape : getAllShapes(context)) { - shapePath.reset(); - shape.addToPath(shapePath, 0, 0, size / 2f); - shapeR.setPath(shapePath, full); - shapeR.op(iconR, Op.XOR); - - int area = GraphicsUtils.getArea(shapeR); - if (area < minArea) { - minArea = area; - closestShape = shape; - } - } - - if (closestShape != null) { - mDelegate = closestShape; - } - - // Initialize shape properties - mNormalizationScale = IconNormalizer.normalizeAdaptiveIcon(drawable, size, null); - } - - public interface ShapeDelegate { - - default boolean enableShapeDetection() { - return false; - } - - void drawShape(Canvas canvas, float offsetX, float offsetY, float radius, Paint paint); - - void addToPath(Path path, float offsetX, float offsetY, float radius); - - <T extends View & ClipPathView> ValueAnimator createRevealAnimator(T target, - Rect startRect, Rect endRect, float endRadius, boolean isReversed); - } - - /** - * Abstract shape where the reveal animation is a derivative of a round rect - * animation - */ - private static abstract class SimpleRectShape implements ShapeDelegate { - - @Override - public final <T extends View & ClipPathView> ValueAnimator createRevealAnimator(T target, - Rect startRect, Rect endRect, float endRadius, boolean isReversed) { - return new RoundedRectRevealOutlineProvider( - getStartRadius(startRect), endRadius, startRect, endRect) { - @Override - public boolean shouldRemoveElevationDuringAnimation() { - return true; - } - }.createRevealAnimator(target, isReversed); - } - - protected abstract float getStartRadius(Rect startRect); - } - - /** - * Abstract shape which draws using {@link Path} - */ - private static abstract class PathShape implements ShapeDelegate { - - private final Path mTmpPath = new Path(); - - @Override - public final void drawShape(Canvas canvas, float offsetX, float offsetY, float radius, - Paint paint) { - mTmpPath.reset(); - addToPath(mTmpPath, offsetX, offsetY, radius); - canvas.drawPath(mTmpPath, paint); - } - - protected abstract AnimatorUpdateListener newUpdateListener( - Rect startRect, Rect endRect, float endRadius, Path outPath); - - @Override - public final <T extends View & ClipPathView> ValueAnimator createRevealAnimator(T target, - Rect startRect, Rect endRect, float endRadius, boolean isReversed) { - Path path = new Path(); - AnimatorUpdateListener listener = newUpdateListener(startRect, endRect, endRadius, path); - - ValueAnimator va = isReversed ? ValueAnimator.ofFloat(1f, 0f) : ValueAnimator.ofFloat(0f, 1f); - va.addListener(new AnimatorListenerAdapter() { - private ViewOutlineProvider mOldOutlineProvider; - - public void onAnimationStart(Animator animation) { - mOldOutlineProvider = target.getOutlineProvider(); - target.setOutlineProvider(null); - - target.setTranslationZ(-target.getElevation()); - } - - public void onAnimationEnd(Animator animation) { - target.setTranslationZ(0); - target.setClipPath(null); - target.setOutlineProvider(mOldOutlineProvider); - } - }); - - va.addUpdateListener((anim) -> { - path.reset(); - listener.onAnimationUpdate(anim); - target.setClipPath(path); - }); - - return va; - } - } - - public static final class AdaptiveIconShape extends PathShape { - - private final app.lawnchair.icons.shape.IconShape mIconShape; - - public AdaptiveIconShape(Context context) { - PreferenceManager2 preferenceManager2 = PreferenceManager2.getInstance(context); - mIconShape = PreferenceExtensionsKt.firstBlocking(preferenceManager2.getIconShape()); - } - - @Override - public void addToPath(Path path, float offsetX, float offsetY, float radius) { - mIconShape.addShape(path, offsetX, offsetY, radius); - } - - @Override - protected AnimatorUpdateListener newUpdateListener(Rect startRect, Rect endRect, float endRadius, - Path outPath) { - float startRadius = startRect.width() / 2f; - float[] start = new float[] { startRect.left, startRect.top, startRect.right, startRect.bottom }; - float[] end = new float[] { endRect.left, endRect.top, endRect.right, endRect.bottom }; - FloatArrayEvaluator evaluator = new FloatArrayEvaluator(); - return animation -> { - float progress = (float) animation.getAnimatedValue(); - float[] values = evaluator.evaluate(progress, start, end); - mIconShape.addToPath(outPath, - values[0], values[1], values[2], values[3], - startRadius, endRadius, progress); - }; - } - } - - public static final class Circle extends PathShape { - - private final float[] mTempRadii = new float[8]; - - protected AnimatorUpdateListener newUpdateListener(Rect startRect, Rect endRect, - float endRadius, Path outPath) { - float r1 = getStartRadius(startRect); - - float[] startValues = new float[] { - startRect.left, startRect.top, startRect.right, startRect.bottom, r1, r1 }; - float[] endValues = new float[] { - endRect.left, endRect.top, endRect.right, endRect.bottom, endRadius, endRadius }; - - FloatArrayEvaluator evaluator = new FloatArrayEvaluator(new float[6]); - - return (anim) -> { - float progress = (Float) anim.getAnimatedValue(); - float[] values = evaluator.evaluate(progress, startValues, endValues); - outPath.addRoundRect( - values[0], values[1], values[2], values[3], - getRadiiArray(values[4], values[5]), Path.Direction.CW); - }; - } - - private float[] getRadiiArray(float r1, float r2) { - mTempRadii[0] = mTempRadii[1] = mTempRadii[2] = mTempRadii[3] = mTempRadii[6] = mTempRadii[7] = r1; - mTempRadii[4] = mTempRadii[5] = r2; - return mTempRadii; - } - - @Override - public void addToPath(Path path, float offsetX, float offsetY, float radius) { - path.addCircle(radius + offsetX, radius + offsetY, radius, Path.Direction.CW); - } - - protected float getStartRadius(Rect startRect) { - return startRect.width() / 2f; - } - - @Override - public boolean enableShapeDetection() { - return true; - } - } - - private static class RoundedSquare extends SimpleRectShape { - - /** - * Ratio of corner radius to half size. - */ - private final float mRadiusRatio; - - public RoundedSquare(float radiusRatio) { - mRadiusRatio = radiusRatio; - } - - @Override - public void drawShape(Canvas canvas, float offsetX, float offsetY, float radius, Paint p) { - float cx = radius + offsetX; - float cy = radius + offsetY; - float cr = radius * mRadiusRatio; - canvas.drawRoundRect(cx - radius, cy - radius, cx + radius, cy + radius, cr, cr, p); - } - - @Override - public void addToPath(Path path, float offsetX, float offsetY, float radius) { - float cx = radius + offsetX; - float cy = radius + offsetY; - float cr = radius * mRadiusRatio; - path.addRoundRect(cx - radius, cy - radius, cx + radius, cy + radius, cr, cr, - Path.Direction.CW); - } - - @Override - protected float getStartRadius(Rect startRect) { - return (startRect.width() / 2f) * mRadiusRatio; - } - } - - private static class TearDrop extends PathShape { - - /** - * Radio of short radius to large radius, based on the shape options defined in - * the config. - */ - private final float mRadiusRatio; - private final float[] mTempRadii = new float[8]; - - public TearDrop(float radiusRatio) { - mRadiusRatio = radiusRatio; - } - - @Override - public void addToPath(Path p, float offsetX, float offsetY, float r1) { - float r2 = r1 * mRadiusRatio; - float cx = r1 + offsetX; - float cy = r1 + offsetY; - - p.addRoundRect(cx - r1, cy - r1, cx + r1, cy + r1, getRadiiArray(r1, r2), - Path.Direction.CW); - } - - private float[] getRadiiArray(float r1, float r2) { - mTempRadii[0] = mTempRadii[1] = mTempRadii[2] = mTempRadii[3] = mTempRadii[6] = mTempRadii[7] = r1; - mTempRadii[4] = mTempRadii[5] = r2; - return mTempRadii; - } - - @Override - protected AnimatorUpdateListener newUpdateListener(Rect startRect, Rect endRect, - float endRadius, Path outPath) { - float r1 = startRect.width() / 2f; - float r2 = r1 * mRadiusRatio; - - float[] startValues = new float[] { - startRect.left, startRect.top, startRect.right, startRect.bottom, r1, r2 }; - float[] endValues = new float[] { - endRect.left, endRect.top, endRect.right, endRect.bottom, endRadius, endRadius }; - - FloatArrayEvaluator evaluator = new FloatArrayEvaluator(new float[6]); - - return (anim) -> { - float progress = (Float) anim.getAnimatedValue(); - float[] values = evaluator.evaluate(progress, startValues, endValues); - outPath.addRoundRect( - values[0], values[1], values[2], values[3], - getRadiiArray(values[4], values[5]), Path.Direction.CW); - }; - } - } - - private static class Squircle extends PathShape { - - /** - * Radio of radius to circle radius, based on the shape options defined in the - * config. - */ - private final float mRadiusRatio; - - public Squircle(float radiusRatio) { - mRadiusRatio = radiusRatio; - } - - @Override - public void addToPath(Path p, float offsetX, float offsetY, float r) { - float cx = r + offsetX; - float cy = r + offsetY; - float control = r - r * mRadiusRatio; - - p.moveTo(cx, cy - r); - addLeftCurve(cx, cy, r, control, p); - addRightCurve(cx, cy, r, control, p); - addLeftCurve(cx, cy, -r, -control, p); - addRightCurve(cx, cy, -r, -control, p); - p.close(); - } - - private void addLeftCurve(float cx, float cy, float r, float control, Path path) { - path.cubicTo( - cx - control, cy - r, - cx - r, cy - control, - cx - r, cy); - } - - private void addRightCurve(float cx, float cy, float r, float control, Path path) { - path.cubicTo( - cx - r, cy + control, - cx - control, cy + r, - cx, cy + r); - } - - @Override - protected AnimatorUpdateListener newUpdateListener(Rect startRect, Rect endRect, - float endR, Path outPath) { - - float startCX = startRect.exactCenterX(); - float startCY = startRect.exactCenterY(); - float startR = startRect.width() / 2f; - float startControl = startR - startR * mRadiusRatio; - float startHShift = 0; - float startVShift = 0; - - float endCX = endRect.exactCenterX(); - float endCY = endRect.exactCenterY(); - // Approximate corner circle using bezier curves - // http://spencermortensen.com/articles/bezier-circle/ - float endControl = endR * 0.551915024494f; - float endHShift = endRect.width() / 2f - endR; - float endVShift = endRect.height() / 2f - endR; - - return (anim) -> { - float progress = (Float) anim.getAnimatedValue(); - - float cx = (1 - progress) * startCX + progress * endCX; - float cy = (1 - progress) * startCY + progress * endCY; - float r = (1 - progress) * startR + progress * endR; - float control = (1 - progress) * startControl + progress * endControl; - float hShift = (1 - progress) * startHShift + progress * endHShift; - float vShift = (1 - progress) * startVShift + progress * endVShift; - - outPath.moveTo(cx, cy - vShift - r); - outPath.rLineTo(-hShift, 0); - - addLeftCurve(cx - hShift, cy - vShift, r, control, outPath); - outPath.rLineTo(0, vShift + vShift); - - addRightCurve(cx - hShift, cy + vShift, r, control, outPath); - outPath.rLineTo(hShift + hShift, 0); - - addLeftCurve(cx + hShift, cy + vShift, -r, -control, outPath); - outPath.rLineTo(0, -vShift - vShift); - - addRightCurve(cx + hShift, cy - vShift, -r, -control, outPath); - outPath.close(); - }; - } - } - - private static ShapeDelegate getShapeDefinition(String type, float radius) { - switch (type) { - case "Circle": - return new Circle(); - case "RoundedSquare": - return new RoundedSquare(radius); - case "TearDrop": - return new TearDrop(radius); - case "Squircle": - return new Squircle(radius); - default: - throw new IllegalArgumentException("Invalid shape type: " + type); - } - } - - private static List<ShapeDelegate> getAllShapes(Context context) { - ArrayList<ShapeDelegate> result = new ArrayList<>(); - try (XmlResourceParser parser = context.getResources().getXml(R.xml.folder_shapes)) { - - // Find the root tag - int type; - while ((type = parser.next()) != XmlPullParser.END_TAG - && type != XmlPullParser.END_DOCUMENT - && !"shapes".equals(parser.getName())) - ; - - final int depth = parser.getDepth(); - int[] radiusAttr = new int[] { R.attr.folderIconRadius }; - - while (((type = parser.next()) != XmlPullParser.END_TAG || - parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { - - if (type == XmlPullParser.START_TAG) { - AttributeSet attrs = Xml.asAttributeSet(parser); - TypedArray a = context.obtainStyledAttributes(attrs, radiusAttr); - ShapeDelegate shape = getShapeDefinition(parser.getName(), a.getFloat(0, 1)); - a.recycle(); - - result.add(shape); - } - } - } catch (IOException | XmlPullParserException e) { - throw new RuntimeException(e); - } - return result; - } - -} diff --git a/src/com/android/launcher3/icons/ComponentWithLabel.java b/src/com/android/launcher3/icons/ComponentWithLabel.java deleted file mode 100644 index 30575fcbe5..0000000000 --- a/src/com/android/launcher3/icons/ComponentWithLabel.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.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<T extends ComponentWithLabel> implements CachingLogic<T> { - - 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/ComponentWithLabelAndIcon.java b/src/com/android/launcher3/icons/ComponentWithLabelAndIcon.java deleted file mode 100644 index 0f87632130..0000000000 --- a/src/com/android/launcher3/icons/ComponentWithLabelAndIcon.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Modifications copyright 2021, Lawnchair - */ -package com.android.launcher3.icons; - -import android.content.Context; -import android.graphics.drawable.Drawable; -import android.os.Process; - -import androidx.annotation.NonNull; - -import com.android.launcher3.LauncherAppState; - -/** - * Extension of ComponentWithLabel to also support loading icons - */ -public interface ComponentWithLabelAndIcon extends ComponentWithLabel { - - /** - * Provide an icon for this object - */ - Drawable getFullResIcon(IconCache cache); - - class ComponentWithIconCachingLogic extends ComponentCachingLogic<ComponentWithLabelAndIcon> { - - public ComponentWithIconCachingLogic(Context context, boolean addToMemCache) { - super(context, addToMemCache); - } - - @NonNull - @Override - public BitmapInfo loadIcon(@NonNull Context context, - @NonNull ComponentWithLabelAndIcon object) { - Drawable d = object.getFullResIcon(LauncherAppState.getInstance(context) - .getIconCache()); - if (d == null) { - return super.loadIcon(context, object); - } - try (LauncherIcons li = LauncherIcons.obtain(context)) { - return li.createBadgedIconBitmap(d, new BaseIconFactory.IconOptions().setUser(object.getUser())); - } - } - } -} diff --git a/src/com/android/launcher3/icons/LauncherActivityCachingLogic.java b/src/com/android/launcher3/icons/LauncherActivityCachingLogic.java deleted file mode 100644 index 0f17fdb6f9..0000000000 --- a/src/com/android/launcher3/icons/LauncherActivityCachingLogic.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Modifications copyright 2021, Lawnchair - */ -package com.android.launcher3.icons; - -import android.content.ComponentName; -import android.content.Context; -import android.content.pm.LauncherActivityInfo; -import android.graphics.drawable.Drawable; -import android.os.UserHandle; - -import androidx.annotation.NonNull; - -import com.android.launcher3.LauncherAppState; -import com.android.launcher3.R; -import com.android.launcher3.icons.cache.CachingLogic; -import com.android.launcher3.util.ResourceBasedOverride; - -/** - * Caching logic for LauncherActivityInfo. - */ -public class LauncherActivityCachingLogic - implements CachingLogic<LauncherActivityInfo>, ResourceBasedOverride { - - /** - * Creates and returns a new instance - */ - public static LauncherActivityCachingLogic newInstance(Context context) { - return Overrides.getObject(LauncherActivityCachingLogic.class, context, - R.string.launcher_activity_logic_class); - } - - @NonNull - @Override - public ComponentName getComponent(@NonNull LauncherActivityInfo object) { - return object.getComponentName(); - } - - @NonNull - @Override - public UserHandle getUser(@NonNull LauncherActivityInfo object) { - return object.getUser(); - } - - @NonNull - @Override - public CharSequence getLabel(@NonNull LauncherActivityInfo object) { - return object.getLabel(); - } - - @NonNull - @Override - public BitmapInfo loadIcon(@NonNull Context context, @NonNull LauncherActivityInfo object) { - try (LauncherIcons li = LauncherIcons.obtain(context)) { - return li.createBadgedIconBitmap(LauncherAppState.getInstance(context) - .getIconProvider().getIcon(object, li.mFillResIconDpi), - new BaseIconFactory.IconOptions().setUser(object.getUser())); - } - } -} diff --git a/src/com/android/launcher3/icons/LauncherIcons.java b/src/com/android/launcher3/icons/LauncherIcons.java deleted file mode 100644 index 542f9100da..0000000000 --- a/src/com/android/launcher3/icons/LauncherIcons.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * 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.icons; - -import android.content.Context; -import android.graphics.drawable.AdaptiveIconDrawable; -import android.graphics.drawable.Drawable; -import android.os.UserHandle; - -import androidx.annotation.NonNull; - -import com.android.launcher3.Flags; -import com.android.launcher3.InvariantDeviceProfile; -import com.android.launcher3.graphics.IconShape; -import com.android.launcher3.graphics.LauncherPreviewRenderer; -import com.android.launcher3.pm.UserCache; -import com.android.launcher3.util.MainThreadInitializedObject; -import com.android.launcher3.util.SafeCloseable; -import com.android.launcher3.util.Themes; -import com.android.launcher3.util.UserIconInfo; - -import java.util.concurrent.ConcurrentLinkedQueue; - -/** - * Wrapper class to provide access to {@link BaseIconFactory} and also to - * provide pool of this class - * that are threadsafe. - */ -public class LauncherIcons extends BaseIconFactory implements AutoCloseable { - - private static final MainThreadInitializedObject<Pool> POOL = new MainThreadInitializedObject<>(Pool::new); - - /** - * Return a new Message instance from the global pool. Allows us to - * avoid allocating new objects in many cases. - */ - public static LauncherIcons obtain(Context context) { - return POOL.get(context).obtain(); - } - - public static void clearPool(Context context) { - POOL.get(context).close(); - } - - private final ConcurrentLinkedQueue<LauncherIcons> mPool; - - private MonochromeIconFactory mMonochromeIconFactory; - - public boolean mMonoIconEnabled = false; - - protected LauncherIcons(Context context, int fillResIconDpi, int iconBitmapSize, - ConcurrentLinkedQueue<LauncherIcons> pool) { - super(context, fillResIconDpi, iconBitmapSize, - IconShape.INSTANCE.get(context).getShape().enableShapeDetection()); - mMonoIconEnabled = Themes.isThemedIconEnabled(context); - mPool = pool; - } - - /** - * Recycles a LauncherIcons that may be in-use. - */ - public void recycle() { - clear(); - mPool.add(this); - } - - @Override - protected Drawable getMonochromeDrawable(AdaptiveIconDrawable base) { - Drawable mono = super.getMonochromeDrawable(base); - if (mono != null || !Flags.forceMonochromeAppIcons()) { - return mono; - } - if (mMonochromeIconFactory == null) { - mMonochromeIconFactory = new MonochromeIconFactory(mIconBitmapSize); - } - return mMonochromeIconFactory.wrap(base); - } - - @NonNull - @Override - protected UserIconInfo getUserInfo(@NonNull UserHandle user) { - return UserCache.INSTANCE.get(mContext).getUserInfo(user); - } - - @Override - public void close() { - recycle(); - } - - private static class Pool implements SafeCloseable { - - private final Context mContext; - - @NonNull - private ConcurrentLinkedQueue<LauncherIcons> mPool = new ConcurrentLinkedQueue<>(); - - private Pool(Context context) { - mContext = context; - } - - public LauncherIcons obtain() { - ConcurrentLinkedQueue<LauncherIcons> pool = mPool; - LauncherIcons m = pool.poll(); - - if (m == null) { - InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(mContext); - return new LauncherIcons(mContext, idp.fillResIconDpi, idp.iconBitmapSize, pool); - } else { - return m; - } - } - - @Override - public void close() { - mPool = new ConcurrentLinkedQueue<>(); - } - } -} diff --git a/src/com/android/launcher3/icons/MonochromeIconFactory.java b/src/com/android/launcher3/icons/MonochromeIconFactory.java deleted file mode 100644 index 2854d51631..0000000000 --- a/src/com/android/launcher3/icons/MonochromeIconFactory.java +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -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/icons/ShortcutCachingLogic.java b/src/com/android/launcher3/icons/ShortcutCachingLogic.java deleted file mode 100644 index 1b8ddaf0a7..0000000000 --- a/src/com/android/launcher3/icons/ShortcutCachingLogic.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher3.icons; - -import static com.android.launcher3.BuildConfigs.WIDGETS_ENABLED; - -import android.content.ComponentName; -import android.content.Context; -import android.content.pm.LauncherApps; -import android.content.pm.PackageInfo; -import android.content.pm.ShortcutInfo; -import android.graphics.drawable.Drawable; -import android.os.Process; -import android.os.UserHandle; -import android.text.TextUtils; -import android.util.Log; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.android.launcher3.LauncherAppState; -import com.android.launcher3.icons.cache.CachingLogic; -import com.android.launcher3.shortcuts.ShortcutKey; -import com.android.launcher3.util.Themes; - -import app.lawnchair.icons.CustomAdaptiveIconDrawable; - -/** - * Caching logic for shortcuts. - */ -public class ShortcutCachingLogic implements CachingLogic<ShortcutInfo> { - - private static final String TAG = "ShortcutCachingLogic"; - - @Override - @NonNull - public ComponentName getComponent(@NonNull ShortcutInfo info) { - return ShortcutKey.fromInfo(info).componentName; - } - - @NonNull - @Override - public UserHandle getUser(@NonNull ShortcutInfo info) { - return info.getUserHandle(); - } - - @NonNull - @Override - public CharSequence getLabel(@NonNull ShortcutInfo info) { - return info.getShortLabel(); - } - - @Override - @NonNull - public CharSequence getDescription(@NonNull ShortcutInfo object, - @NonNull CharSequence fallback) { - CharSequence label = object.getLongLabel(); - return TextUtils.isEmpty(label) ? fallback : label; - } - - @NonNull - @Override - public BitmapInfo loadIcon(@NonNull Context context, @NonNull ShortcutInfo info) { - try (LauncherIcons li = LauncherIcons.obtain(context)) { - Drawable unbadgedDrawable = ShortcutCachingLogic.getIcon( - context, info, LauncherAppState.getIDP(context).fillResIconDpi); - if (unbadgedDrawable == null) - return BitmapInfo.LOW_RES_INFO; - return li.createBadgedIconBitmap(unbadgedDrawable); - } - } - - @Override - public long getLastUpdatedTime(@Nullable ShortcutInfo shortcutInfo, - @NonNull PackageInfo info) { - if (shortcutInfo == null) { - return info.lastUpdateTime; - } - return Math.max(shortcutInfo.getLastChangedTimestamp(), info.lastUpdateTime); - } - - @Override - public boolean addToMemCache() { - return false; - } - - /** - * Similar to {@link LauncherApps#getShortcutIconDrawable(ShortcutInfo, int)} - * with additional - * Launcher specific checks - */ - public static Drawable getIcon(Context context, ShortcutInfo shortcutInfo, int density) { - try { - Drawable icon = context.getSystemService(LauncherApps.class) - .getShortcutIconDrawable(shortcutInfo, density); - return CustomAdaptiveIconDrawable.wrap(icon); - } catch (SecurityException | IllegalStateException | NullPointerException e) { - Log.e(TAG, "Failed to get shortcut icon", e); - return null; - } - } -} diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java deleted file mode 100644 index 186194eb7f..0000000000 --- a/src/com/android/launcher3/logging/StatsLogManager.java +++ /dev/null @@ -1,1064 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.launcher3.logging; - -import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_CLOSE_DOWN; -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; -import com.android.launcher3.logger.LauncherAtom.FromState; -import com.android.launcher3.logger.LauncherAtom.ToState; -import com.android.launcher3.model.data.ItemInfo; -import com.android.launcher3.util.ResourceBasedOverride; -import com.android.launcher3.views.ActivityContext; - -/** - * Handles the user event logging in R+. - * - * <pre> - * All of the event ids are defined here. - * Most of the methods are placeholder methods for Launcher3 - * Actual call happens only for Launcher variant that implements QuickStep. - * </pre> - */ -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; - public static final int LAUNCHER_STATE_OVERVIEW = 3; - public static final int LAUNCHER_STATE_ALLAPPS = 4; - public static final int LAUNCHER_STATE_UNCHANGED = 5; - - @NonNull - protected final Context mContext; - @Nullable - protected final ActivityContext mActivityContext; - - private KeyboardStateManager mKeyboardStateManager; - private InstanceId mInstanceId; - - public StatsLogManager(@NonNull Context context) { - mContext = context; - mActivityContext = ActivityContext.lookupContextNoThrow(context); - } - - /** - * Returns event enum based on the two state transition information when swipe - * gesture happens(to be removed during UserEventDispatcher cleanup). - */ - public static EventEnum getLauncherAtomEvent(int startState, - int targetState, EventEnum fallbackEvent) { - if (startState == LAUNCHER_STATE_HOME - && targetState == LAUNCHER_STATE_HOME) { - return LAUNCHER_HOME_GESTURE; - } else if (startState != LAUNCHER_STATE_OVERVIEW - && targetState == LAUNCHER_STATE_OVERVIEW) { - return LAUNCHER_OVERVIEW_GESTURE; - } else if (startState != LAUNCHER_STATE_ALLAPPS - && targetState == LAUNCHER_STATE_ALLAPPS) { - return LAUNCHER_ALLAPPS_OPEN_UP; - } else if (startState == LAUNCHER_STATE_ALLAPPS - && targetState != LAUNCHER_STATE_ALLAPPS) { - return LAUNCHER_ALLAPPS_CLOSE_DOWN; - } - return fallbackEvent; // TODO fix - } - - public interface EventEnum { - /** - * Tag used to request new UI Event IDs via presubmit analysis. - * - * <p> - * 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. - * - * <pre> - * &#064;UiEvent(doc = "Briefly describe the interaction when this event will be logged") - * UNIQUE_EVENT_NAME(RESERVE_NEW_UI_EVENT_ID); - * </pre> - */ - int RESERVE_NEW_UI_EVENT_ID = Integer.MIN_VALUE; // Negative IDs are ignored by the logger. - - int getId(); - } - - 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), - - @UiEvent(doc = "App launched by dragging and dropping, probably from taskbar") - LAUNCHER_APP_LAUNCH_DRAGDROP(1552), - - @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 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 = "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), - - /** - * @deprecated Use {@link #LAUNCHER_APP_ICON_MENU_SPLIT_LEFT_TOP} or - * {@link #LAUNCHER_APP_ICON_MENU_SPLIT_RIGHT_BOTTOM} - */ - @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 pause app system shortcut.") - LAUNCHER_SYSTEM_SHORTCUT_PAUSE_TAP(521), - @UiEvent(doc = "User tapped on pin system shortcut.") - LAUNCHER_SYSTEM_SHORTCUT_PIN_TAP(522), - - @UiEvent(doc = "User tapped on don't suggest app system shortcut.") - LAUNCHER_SYSTEM_SHORTCUT_DONT_SUGGEST_APP_TAP(1603), - - @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 - @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 in DOWN direction on the bottom bazel area.") - LAUNCHER_SWIPEDOWN_NAVBAR(573), - - @UiEvent(doc = "User deep presses on the bottom bezel area.") - LAUNCHER_DEEP_PRESS_NAVBAR(1543), - - @UiEvent(doc = "User long presses on the bottom bezel area.") - LAUNCHER_LONG_PRESS_NAVBAR(1544), - - @UiEvent(doc = "User deep presses on the stashed taskbar") - LAUNCHER_DEEP_PRESS_STASHED_TASKBAR(1602), - - @UiEvent(doc = "User long presses on the stashed taskbar") - LAUNCHER_LONG_PRESS_STASHED_TASKBAR(1592), - - @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), - - @UiEvent(doc = "User tapped on barcode content in Overview Select mode.") - LAUNCHER_SELECT_MODE_BARCODE(1531), - - @UiEvent(doc = "Highlight gleams for barcode content in Overview Select mode.") - LAUNCHER_SELECT_MODE_SHOW_BARCODE_REGIONS(1532), - - @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 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), - - @UiEvent(doc = "Show Barode indicator for overview sharing") - LAUNCHER_OVERVIEW_SHARING_SHOW_BARCODE_INDICATOR(1533), - - @UiEvent(doc = "User taps barcode indicator in overview") - LAUNCHER_OVERVIEW_SHARING_BARCODE_INDICATOR_TAP(1534), - - @UiEvent(doc = "Configure barcode region for long_press action for overview sharing") - LAUNCHER_OVERVIEW_SHARING_CONFIGURE_BARCODE_REGION_LONG_PRESS(1535), - - @UiEvent(doc = "User long presses a barcode region in overview") - LAUNCHER_OVERVIEW_SHARING_BARCODE_REGION_LONG_PRESS(1536), - - @UiEvent(doc = "User drags a barcode region in overview") - LAUNCHER_OVERVIEW_SHARING_BARCODE_REGION_DRAG(1537), - - @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), - - @UiEvent(doc = "User long pressed taskbar All Apps button.") - LAUNCHER_TASKBAR_ALLAPPS_BUTTON_LONG_PRESS(1607), - - @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), - - @UiEvent(doc = "User long pressed on taskbar divider icon to open popup menu") - LAUNCHER_TASKBAR_DIVIDER_MENU_OPEN(1488), - - @UiEvent(doc = "User long pressed on taskbar divider icon to close popup menu") - LAUNCHER_TASKBAR_DIVIDER_MENU_CLOSE(1489), - - @UiEvent(doc = "User has pinned taskbar using taskbar divider menu") - LAUNCHER_TASKBAR_PINNED(1490), - - @UiEvent(doc = "User has unpinned taskbar using taskbar divider menu") - LAUNCHER_TASKBAR_UNPINNED(1491), - - @UiEvent(doc = "User tapped private space lock button") - LAUNCHER_PRIVATE_SPACE_LOCK_TAP(1548), - - @UiEvent(doc = "User tapped private space unlock button") - LAUNCHER_PRIVATE_SPACE_UNLOCK_TAP(1549), - - @UiEvent(doc = "User tapped private space settings button") - LAUNCHER_PRIVATE_SPACE_SETTINGS_TAP(1550), - - @UiEvent(doc = "User tapped on install to private space system shortcut.") - LAUNCHER_PRIVATE_SPACE_INSTALL_SYSTEM_SHORTCUT_TAP(1565), - - @UiEvent(doc = "User tapped private space install app button.") - LAUNCHER_PRIVATE_SPACE_INSTALL_APP_BUTTON_TAP(1605), - - @UiEvent(doc = "User attempted to create split screen with a widget") - LAUNCHER_SPLIT_WIDGET_ATTEMPT(1604), - - @UiEvent(doc = "User tapped on private space uninstall system shortcut.") - LAUNCHER_PRIVATE_SPACE_UNINSTALL_SYSTEM_SHORTCUT_TAP(1608), - - @UiEvent(doc = "User initiated split selection") - LAUNCHER_SPLIT_SELECTION_INITIATED(1618), - - @UiEvent(doc = "User finished a split selection session") - LAUNCHER_SPLIT_SELECTION_COMPLETE(1619), - - @UiEvent(doc = "User selected both apps for split screen") - LAUNCHER_SPLIT_SELECTED_SECOND_APP(1609), - - @UiEvent(doc = "User exited split selection by going home via swipe, button, or state " - + "transition") - LAUNCHER_SPLIT_SELECTION_EXIT_HOME(1610), - - @UiEvent(doc = "User exited split selection by tapping cancel in split instructions view") - LAUNCHER_SPLIT_SELECTION_EXIT_CANCEL_BUTTON(1611), - - @UiEvent(doc = "User exited split selection when another activity/app came to foreground" - + " after first app had been selected OR if user long-pressed on home. Default exit" - + " metric.") - LAUNCHER_SPLIT_SELECTION_EXIT_INTERRUPTED(1612), - - @UiEvent(doc = "User tapped add widget button in widget sheet.") - LAUNCHER_WIDGET_ADD_BUTTON_TAP(1622), - - @UiEvent(doc = "Number of user installed Private profile apps, shown above separator line") - LAUNCHER_PRIVATE_SPACE_USER_INSTALLED_APPS_COUNT(1672), - - @UiEvent(doc = "Number of preinstalled Private profile apps, shown under separator line") - LAUNCHER_PRIVATE_SPACE_PREINSTALLED_APPS_COUNT(1673), - - @UiEvent(doc = "Private space lock animation started") - LAUNCHER_PRIVATE_SPACE_LOCK_ANIMATION_BEGIN(1725), - - @UiEvent(doc = "Private space lock animation finished") - LAUNCHER_PRIVATE_SPACE_LOCK_ANIMATION_END(1726), - - @UiEvent(doc = "Private space unlock animation started") - LAUNCHER_PRIVATE_SPACE_UNLOCK_ANIMATION_BEGIN(1727), - - @UiEvent(doc = "Private space unlock animation finished") - LAUNCHER_PRIVATE_SPACE_UNLOCK_ANIMATION_END(1728), - - @UiEvent(doc = "User rotates whilst in Overview / RecentsView") - LAUNCHER_OVERVIEW_ORIENTATION_CHANGED(1762), - - @UiEvent(doc = "User launches Overview from 3 button navigation") - LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_3_BUTTON(1763), - - @UiEvent(doc = "User launches Overview from alt+tab keyboard quick switch") - LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_KEYBOARD_QUICK_SWITCH(1764), - - @UiEvent(doc = "User launches Overview from meta+tab keyboard shortcut") - LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_KEYBOARD_SHORTCUT(1765), - - // ADD MORE - ; - - private final int mId; - - LauncherEvent(int id) { - mId = id; - } - - public int getId() { - return mId; - } - } - - /** Launcher's latency events. */ - public enum LauncherLatencyEvent implements EventEnum { - // 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().") - LAUNCHER_LATENCY_STARTUP_VIEW_INFLATION(1364), - @UiEvent(doc = "The duration of asynchronous loading workspace") - LAUNCHER_LATENCY_STARTUP_WORKSPACE_LOADER_ASYNC(1367), - ; - - private final int mId; - - LauncherLatencyEvent(int id) { - mId = id; - } - - @Override - public int getId() { - return mId; - } - } - - /** - * Launcher specific ranking related events. - */ - public enum LauncherRankingEvent implements EventEnum { - UNKNOWN(0); - - // ADD MORE - private final int mId; - - LauncherRankingEvent(int id) { - mId = id; - } - - public int getId() { - return mId; - } - } - - /** - * Helps to construct and log launcher event. - */ - public interface StatsLogger { - /** - * Sets log fields from provided {@link ItemInfo}. - */ - default StatsLogger withItemInfo(ItemInfo itemInfo) { - return this; - } - - /** - * Sets {@link InstanceId} of log message. - */ - default StatsLogger withInstanceId(InstanceId instanceId) { - return this; - } - - /** - * Sets rank field of log message. - */ - default StatsLogger withRank(int rank) { - return this; - } - - /** - * Sets source launcher state field of log message. - */ - default StatsLogger withSrcState(int srcState) { - return this; - } - - /** - * Sets destination launcher state field of log message. - */ - default StatsLogger withDstState(int dstState) { - return this; - } - - /** - * Sets FromState field of log message. - */ - default StatsLogger withFromState(FromState fromState) { - return this; - } - - /** - * Sets ToState field of log message. - */ - default StatsLogger withToState(ToState toState) { - return this; - } - - /** - * Sets editText field of log message. - */ - default StatsLogger withEditText(String editText) { - return this; - } - - /** - * Sets the final value for container related fields of log message. - * - * By default container related fields are derived from {@link ItemInfo}, this - * method would - * override those values. - */ - default StatsLogger withContainerInfo(ContainerInfo containerInfo) { - return this; - } - - /** - * Sets logging fields from provided {@link SliceItem}. - */ - default StatsLogger withSliceItem(SliceItem sliceItem) { - return this; - } - - /** - * Sets logging fields from provided {@link LauncherAtom.Slice}. - */ - default StatsLogger withSlice(LauncherAtom.Slice slice) { - return this; - } - - /** - * Sets cardinality of log message. - */ - default StatsLogger withCardinality(int cardinality) { - return this; - } - - /** - * Sets the input type of the log message. - */ - default StatsLogger withInputType(int inputType) { - return this; - } - - /** - * Set the features of the log message. - */ - default StatsLogger withFeatures(int feature) { - return this; - } - - /** - * Set the package name of the log message. - */ - default StatsLogger withPackageName(@Nullable String packageName) { - return this; - } - - /** - * Builds the final message and logs it as {@link EventEnum}. - */ - default void log(EventEnum event) { - } - - /** - * Builds the final message and logs it to two different atoms, one for - * event tracking and the other for jank tracking. - */ - default void sendToInteractionJankMonitor(EventEnum event, View v) { - } - } - - /** - * 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 - */ - enum LatencyType { - UNKNOWN(0), - // example: launcher restart that happens via daily backup and restore - COLD(1), - HOT(2), - TIMEOUT(3), - FAIL(4), - COLD_USERWAITING(5), - ATOMIC(6), - CONTROLLED(7), - CACHED(8), - // example: device is rebooting via power key or shell command `adb reboot` - COLD_DEVICE_REBOOTING(9), - // Tracking warm startup latency: - // https://developer.android.com/topic/performance/vitals/launch-time#warm - WARM(10); - - private final int mId; - - LatencyType(int id) { - this.mId = id; - } - - public int getId() { - return mId; - } - } - - /** - * Sets {@link InstanceId} of log message. - */ - default StatsLatencyLogger withInstanceId(InstanceId instanceId) { - return this; - } - - /** - * Sets latency of the event. - */ - default StatsLatencyLogger withLatency(long latencyInMillis) { - return this; - } - - /** - * Sets {@link LatencyType} of log message. - */ - default StatsLatencyLogger withType(LatencyType type) { - return this; - } - - /** - * Sets query length of the event. - */ - default StatsLatencyLogger withQueryLength(int queryLength) { - return this; - } - - /** - * Sets sub event type. - */ - default StatsLatencyLogger withSubEventType(int type) { - return this; - } - - /** Sets cardinality of the event. */ - default StatsLatencyLogger withCardinality(int cardinality) { - return this; - } - - /** - * Sets packageId of log message. - */ - default StatsLatencyLogger withPackageId(int packageId) { - return this; - } - - /** - * Builds the final message and logs it as {@link EventEnum}. - */ - default void log(EventEnum event) { - } - } - - /** - * 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) { - this.mLauncherState = id; - } - - public int getLauncherState() { - return mLauncherState; - } - } - - /** - * Sets {@link InstanceId} of log message. - */ - default StatsImpressionLogger withInstanceId(InstanceId instanceId) { - return this; - } - - /** - * Sets {@link State} of impression event. - */ - default StatsImpressionLogger withState(State state) { - return this; - } - - /** - * Sets query length of the event. - */ - default StatsImpressionLogger withQueryLength(int queryLength) { - return this; - } - - /** - * Sets {@link com.android.app.search.ResultType} for the impression event. - */ - default StatsImpressionLogger withResultType(int resultType) { - return this; - } - - /** - * 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) { - return this; - } - - /** - * Sets uid for each of {@link com.android.app.search.ResultType} that indicates - * package name for the impression event. - */ - default StatsImpressionLogger withUid(int uid) { - return this; - } - - /** - * Sets result source that indicates the origin of the result for the impression - * event. - */ - default StatsImpressionLogger withResultSource(int resultSource) { - return this; - } - - /** - * Builds the final message and logs it as {@link EventEnum}. - */ - default void log(EventEnum event) { - } - } - - /** - * Returns new logger object. - */ - public StatsLogger logger() { - StatsLogger logger = createLogger(); - if (mInstanceId != null) { - logger.withInstanceId(mInstanceId); - } - return logger; - } - - /** - * Returns new latency logger object. - */ - public StatsLatencyLogger latencyLogger() { - StatsLatencyLogger logger = createLatencyLogger(); - if (mInstanceId != null) { - logger.withInstanceId(mInstanceId); - } - return logger; - } - - /** - * Returns new impression logger object. - */ - public StatsImpressionLogger impressionLogger() { - StatsImpressionLogger logger = createImpressionLogger(); - if (mInstanceId != null) { - logger.withInstanceId(mInstanceId); - } - return logger; - } - - /** - * Returns a singleton KeyboardStateManager. - */ - public KeyboardStateManager keyboardStateManager() { - if (mKeyboardStateManager == null) { - mKeyboardStateManager = new KeyboardStateManager( - mContext != null ? mContext.getResources().getDimensionPixelSize( - R.dimen.default_ime_height) : 0); - } - return mKeyboardStateManager; - } - - protected StatsLogger createLogger() { - return new StatsLogger() { - }; - } - - protected StatsLatencyLogger createLatencyLogger() { - return new StatsLatencyLogger() { - }; - } - - protected StatsImpressionLogger createImpressionLogger() { - return new StatsImpressionLogger() { - }; - } - - /** - * Sets InstanceId to every new {@link StatsLogger} object returned by - * {@link #logger()} when - * not-null. - */ - public StatsLogManager withDefaultInstanceId(@Nullable InstanceId instanceId) { - this.mInstanceId = instanceId; - return this; - } - - /** - * Creates a new instance of {@link StatsLogManager} based on provided context. - */ - public static StatsLogManager newInstance(Context context) { - 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/GridSizeMigrationUtil.java b/src/com/android/launcher3/model/GridSizeMigrationUtil.java deleted file mode 100644 index d23b1ef64b..0000000000 --- a/src/com/android/launcher3/model/GridSizeMigrationUtil.java +++ /dev/null @@ -1,815 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.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<DbEntry> readAllEntries(SQLiteDatabase db, String tableName, - Context context) { - DbReader dbReader = new DbReader(db, tableName, context, getValidPackages(context)); - List<DbEntry> 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<String> 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<DbEntry> workspaceToBeAdded = new ArrayList<>(); - final IntArray toBeRemoved = new IntArray(); - - // Process each screen separately - for (int screenId : srcReader.mWorkspaceEntriesByScreenId.keySet()) { - List<DbEntry> srcScreenItems = srcReader.mWorkspaceEntriesByScreenId.get(screenId); - List<DbEntry> destScreenItems = destReader.mWorkspaceEntriesByScreenId.get(screenId); - - if (destScreenItems == null) { - destScreenItems = new ArrayList<>(); - } - - List<DbEntry> 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<DbEntry> srcHotseatEntries = srcReader.loadHotseatEntries(); - List<DbEntry> destHotseatEntries = destReader.loadHotseatEntries(); - final ArrayList<DbEntry> 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<DbEntry> src, - @NonNull final List<DbEntry> dest, @NonNull final List<DbEntry> 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<Integer> 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<String> 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<String> 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<DbEntry> 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<DbEntry> existedEntries = destReader.mWorkspaceEntriesByScreenId.get(screenId); - if (existedEntries != null) { - for (DbEntry entry : existedEntries) { - occupied.markCells(entry, true); - } - } - Iterator<DbEntry> 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<DbEntry> placedHotseatItems, - @NonNull final List<DbEntry> 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<String> mValidPackages; - private int mLastScreenId = -1; - - private final Map<Integer, ArrayList<DbEntry>> mWorkspaceEntriesByScreenId = new ArrayMap<>(); - - public DbReader(SQLiteDatabase db, String tableName, Context context, - Set<String> validPackages) { - mDb = db; - mTableName = tableName; - mContext = context; - mValidPackages = validPackages; - } - - protected List<DbEntry> loadHotseatEntries() { - final List<DbEntry> 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<DbEntry> loadAllWorkspaceEntries() { - final List<DbEntry> 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<DbEntry> { - - private String mIntent; - private String mProvider; - private Map<String, Set<Integer>> 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/ShortcutsChangedTask.java b/src/com/android/launcher3/model/ShortcutsChangedTask.java deleted file mode 100644 index 1916d23b67..0000000000 --- a/src/com/android/launcher3/model/ShortcutsChangedTask.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * 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<ShortcutInfo> mShortcuts; - - @NonNull - private final UserHandle mUser; - - private final boolean mUpdateIdMap; - - public ShortcutsChangedTask(@NonNull final String packageName, - @NonNull final List<ShortcutInfo> 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<WorkspaceItemInfo> 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<String> allLauncherKnownIds = matchingWorkspaceItems.stream() - .map(WorkspaceItemInfo::getDeepShortcutId) - .distinct() - .collect(Collectors.toList()); - List<ShortcutInfo> shortcuts = new ShortcutRequest(context, mUser) - .forPackage(mPackageName, allLauncherKnownIds) - .query(ShortcutRequest.ALL); - - Set<String> nonPinnedIds = new HashSet<>(allLauncherKnownIds); - ArrayList<WorkspaceItemInfo> 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/WidgetsFilterDataProvider.kt b/src/com/android/launcher3/model/WidgetsFilterDataProvider.kt deleted file mode 100644 index 90d6fb2594..0000000000 --- a/src/com/android/launcher3/model/WidgetsFilterDataProvider.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher3.model - -import com.android.launcher3.dagger.LauncherAppSingleton -import java.util.function.Predicate -import javax.inject.Inject - -/** Helper for the widgets model to load the filters that can be applied to available widgets. */ -@LauncherAppSingleton -open class WidgetsFilterDataProvider @Inject constructor() { - - /** Filter that should be applied to the widget predictions */ - open val predictedWidgetsFilter: Predicate<WidgetItem>? = null - - /** - * Filter that should be applied to the widgets list to see which widgets can be shown by - * default. - */ - open val defaultWidgetsFilter: Predicate<WidgetItem>? = null - - protected val listeners = mutableListOf<WidgetsFilterLoadedCallback>() - - /** Adds a callback for listening to filter changes */ - fun addFilterChangeCallback(callback: WidgetsFilterLoadedCallback) { - listeners.add(callback) - } - - /** Removes a previously added callback */ - fun removeFilterChangeCallback(callback: WidgetsFilterLoadedCallback) { - listeners.remove(callback) - } - - /** Interface for the model callback to be invoked when filters are loaded. */ - interface WidgetsFilterLoadedCallback { - /** Method called back when widget filters are loaded */ - fun onWidgetsFilterLoaded() - } -} diff --git a/src/com/android/launcher3/pageindicators/WorkspacePageIndicator.java b/src/com/android/launcher3/pageindicators/WorkspacePageIndicator.java deleted file mode 100644 index bde4e525a1..0000000000 --- a/src/com/android/launcher3/pageindicators/WorkspacePageIndicator.java +++ /dev/null @@ -1,265 +0,0 @@ -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<WorkspacePageIndicator, Integer> PAINT_ALPHA - = new Property<WorkspacePageIndicator, Integer>(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<WorkspacePageIndicator, Float> NUM_PAGES - = new Property<WorkspacePageIndicator, Float>(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<WorkspacePageIndicator, Integer> TOTAL_SCROLL - = new Property<WorkspacePageIndicator, Integer>(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/provider/LauncherDbUtils.java b/src/com/android/launcher3/provider/LauncherDbUtils.java deleted file mode 100644 index b27d9f459d..0000000000 --- a/src/com/android/launcher3/provider/LauncherDbUtils.java +++ /dev/null @@ -1,246 +0,0 @@ -/* - * 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.provider; - -import static com.android.launcher3.LauncherSettings.Favorites.getColumns; -import static com.android.launcher3.icons.IconCache.EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE; - -import android.content.ContentValues; -import android.content.Context; -import android.content.Intent; -import android.content.pm.ResolveInfo; -import android.content.pm.ShortcutInfo; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteException; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.drawable.Icon; -import android.os.PersistableBundle; -import android.os.Process; -import android.os.UserManager; -import android.text.TextUtils; - -import com.android.launcher3.LauncherAppState; -import com.android.launcher3.LauncherSettings.Favorites; -import com.android.launcher3.Utilities; -import com.android.launcher3.model.LoaderCursor; -import com.android.launcher3.model.UserManagerState; -import com.android.launcher3.pm.PinRequestHelper; -import com.android.launcher3.pm.UserCache; -import com.android.launcher3.shortcuts.ShortcutKey; -import com.android.launcher3.util.IntArray; -import com.android.launcher3.util.IntSet; -import com.android.launcher3.util.PackageManagerHelper; - -import java.util.ArrayList; - -/** - * A set of utility methods for Launcher DB used for DB updates and migration. - */ -public class LauncherDbUtils { - /** - * Returns a string which can be used as a where clause for DB query to match - * the given itemId - */ - public static String itemIdMatch(int itemId) { - return "_id=" + itemId; - } - - public static IntArray queryIntArray(boolean distinct, SQLiteDatabase db, String tableName, - String columnName, String selection, String groupBy, String orderBy) { - IntArray out = new IntArray(); - try (Cursor c = db.query(distinct, tableName, new String[] { columnName }, selection, null, - groupBy, null, orderBy, null)) { - while (c.moveToNext()) { - out.add(c.getInt(0)); - } - } - return out; - } - - public static boolean tableExists(SQLiteDatabase db, String tableName) { - try (Cursor c = db.query(true, "sqlite_master", new String[] { "tbl_name" }, - "tbl_name = ?", new String[] { tableName }, - null, null, null, null, null)) { - return c.getCount() > 0; - } - } - - public static void dropTable(SQLiteDatabase db, String tableName) { - db.execSQL("DROP TABLE IF EXISTS " + tableName); - } - - /** Copy fromTable in fromDb to toTable in toDb. */ - public static void copyTable(SQLiteDatabase fromDb, String fromTable, SQLiteDatabase toDb, - String toTable, Context context) { - long userSerial = UserCache.INSTANCE.get(context).getSerialNumberForUser( - Process.myUserHandle()); - dropTable(toDb, toTable); - Favorites.addTableToDb(toDb, userSerial, false, toTable); - if (fromDb != toDb) { - toDb.execSQL("ATTACH DATABASE '" + fromDb.getPath() + "' AS from_db"); - toDb.execSQL( - "INSERT INTO " + toTable + " SELECT " + getColumns(userSerial) - + " FROM from_db." + fromTable); - toDb.execSQL("DETACH DATABASE 'from_db'"); - } else { - toDb.execSQL("INSERT INTO " + toTable + " SELECT " + getColumns(userSerial) + " FROM " - + fromTable); - } - } - - /** - * Migrates the legacy shortcuts to deep shortcuts pinned under Launcher. - * Removes any invalid shortcut or any shortcut which requires some permission - * to launch - */ - public static void migrateLegacyShortcuts(Context context, SQLiteDatabase db) { - Cursor c = db.query( - Favorites.TABLE_NAME, null, "itemType = 1", null, null, null, null); - UserManagerState ums = new UserManagerState(); - PackageManagerHelper pmHelper = PackageManagerHelper.INSTANCE.get(context); - ums.init(UserCache.INSTANCE.get(context), - context.getSystemService(UserManager.class)); - LoaderCursor lc = new LoaderCursor(c, LauncherAppState.getInstance(context), ums, pmHelper, - null); - IntSet deletedShortcuts = new IntSet(); - - while (lc.moveToNext()) { - if (lc.user != Process.myUserHandle()) { - deletedShortcuts.add(lc.id); - continue; - } - Intent intent = lc.parseIntent(); - if (intent == null) { - deletedShortcuts.add(lc.id); - continue; - } - if (TextUtils.isEmpty(lc.getTitle())) { - deletedShortcuts.add(lc.id); - continue; - } - - // Make sure the target intent can be launched without any permissions. - // Otherwise remove - // the shortcut - ResolveInfo ri = context.getPackageManager().resolveActivity(intent, 0); - if (ri == null || !TextUtils.isEmpty(ri.activityInfo.permission)) { - deletedShortcuts.add(lc.id); - continue; - } - PersistableBundle extras = new PersistableBundle(); - extras.putString(EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE, ri.activityInfo.packageName); - ShortcutInfo.Builder infoBuilder = new ShortcutInfo.Builder( - context, "migrated_shortcut-" + lc.id) - .setIntent(intent) - .setExtras(extras) - .setShortLabel(lc.getTitle()); - - Bitmap bitmap = null; - byte[] iconData = lc.getIconBlob(); - if (iconData != null) { - bitmap = BitmapFactory.decodeByteArray(iconData, 0, iconData.length); - } - if (bitmap != null) { - infoBuilder.setIcon(Icon.createWithBitmap(bitmap)); - } - - ShortcutInfo info = infoBuilder.build(); - try { - if (!PinRequestHelper.createRequestForShortcut(context, info).accept()) { - deletedShortcuts.add(lc.id); - continue; - } - } catch (Exception e) { - deletedShortcuts.add(lc.id); - continue; - } - ContentValues update = new ContentValues(); - update.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_DEEP_SHORTCUT); - update.put(Favorites.INTENT, - ShortcutKey.makeIntent(info.getId(), context.getPackageName()).toUri(0)); - db.update(Favorites.TABLE_NAME, update, "_id = ?", - new String[] { Integer.toString(lc.id) }); - } - lc.close(); - if (!deletedShortcuts.isEmpty()) { - db.delete(Favorites.TABLE_NAME, - Utilities.createDbSelectionQuery(Favorites._ID, deletedShortcuts.getArray()), - null); - } - - // Drop the unused columns - try { - db.execSQL("ALTER TABLE " + Favorites.TABLE_NAME + " DROP COLUMN iconPackage;"); - db.execSQL("ALTER TABLE " + Favorites.TABLE_NAME + " DROP COLUMN iconResource;"); - } catch (SQLiteException ignored) { - // Compat users upgrade from Lawnchair 13, see https://github.com/LawnchairLauncher/lawnchair/issues/3881. - removeColumn(db, Favorites.TABLE_NAME, "iconPackage"); - removeColumn(db, Favorites.TABLE_NAME, "iconResource"); - } - } - - /** - * Utility class to simplify managing sqlite transactions - */ - public static class SQLiteTransaction implements AutoCloseable { - private final SQLiteDatabase mDb; - - public SQLiteTransaction(SQLiteDatabase db) { - mDb = db; - db.beginTransaction(); - } - - public void commit() { - mDb.setTransactionSuccessful(); - } - - @Override - public void close() { - mDb.endTransaction(); - } - - public SQLiteDatabase getDb() { - return mDb; - } - } - - private static void removeColumn(SQLiteDatabase db, String tableName, String columnName) { - final var columns = new ArrayList<>(); - // Get all column names from the table - try (Cursor c = db.rawQuery("PRAGMA table_info(" + tableName + ")", null)) { - while (c.moveToNext()) { - String column = c.getString(c.getColumnIndex("name")); - if (!column.equals(columnName)) { - columns.add(column); - } - } - } - - // Create a new table without the column - String newTable = tableName + "_new"; - String columnsSeparated = TextUtils.join(",", columns); - db.execSQL("CREATE TABLE " + newTable + " AS SELECT " + columnsSeparated + " FROM " + tableName); - - // Drop the old table - db.execSQL("DROP TABLE " + tableName); - - // Rename the new table to the old table's name - db.execSQL("ALTER TABLE " + newTable + " RENAME TO " + tableName); - } -} diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayPredictions.java b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayPredictions.java deleted file mode 100644 index a58916ad80..0000000000 --- a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayPredictions.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.launcher3.secondarydisplay; - -import android.content.Context; - -import com.android.launcher3.R; -import com.android.launcher3.model.BgDataModel; -import com.android.launcher3.util.ResourceBasedOverride; - -/** - * Exposes Quickstep app prediction row APIs to {@link SecondaryDisplayLauncher}. - */ -public class SecondaryDisplayPredictions implements ResourceBasedOverride { - /** - * Creates a {@link SecondaryDisplayPredictions} instance. - */ - static SecondaryDisplayPredictions newInstance(Context context) { - return Overrides.getObject( - SecondaryDisplayPredictions.class, context, - R.string.secondary_display_predictions_class); - } - - /** - * Setup/update app divider separating app predictions from All Apps. - */ - void updateAppDivider() { - } - - /** - * Set predicted apps in top of app drawer. - */ - public void setPredictedApps(BgDataModel.FixedContainerItems item) { - } -} diff --git a/src/com/android/launcher3/states/HintState.java b/src/com/android/launcher3/states/HintState.java deleted file mode 100644 index 61a4a395e3..0000000000 --- a/src/com/android/launcher3/states/HintState.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.launcher3.states; - -import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation; -import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME; - -import android.content.Context; - -import androidx.core.graphics.ColorUtils; - -import com.android.launcher3.Launcher; -import com.android.launcher3.LauncherState; - -import app.lawnchair.theme.color.tokens.ColorTokens; - -/** - * Scale down workspace/hotseat to hint at going to either overview (on pause) or first home screen. - */ -public class HintState extends LauncherState { - - private static final int STATE_FLAGS = FLAG_WORKSPACE_INACCESSIBLE | FLAG_DISABLE_RESTORE - | FLAG_HAS_SYS_UI_SCRIM; - - public static final float DEPTH_5_PERCENT = 0.05f; - - public HintState(int id) { - this(id, LAUNCHER_STATE_HOME); - } - - public HintState(int id, int statsLogOrdinal) { - super(id, statsLogOrdinal, STATE_FLAGS); - } - - @Override - public int getTransitionDuration(Context context, boolean isToState) { - return 80; - } - - @Override - protected float getDepthUnchecked(Context context) { - if (enableScalingRevealHomeAnimation()) { - return DEPTH_5_PERCENT; - } else { - return 0.15f; - } - } - - @Override - public int getWorkspaceScrimColor(Launcher launcher) { - return ColorUtils.setAlphaComponent( - ColorTokens.OverviewScrim.resolveColor(launcher), 100); - } - - @Override - public ScaleAndTranslation getWorkspaceScaleAndTranslation(Launcher launcher) { - return new ScaleAndTranslation(0.92f, 0, 0); - } -} diff --git a/src/com/android/launcher3/util/ActivityTracker.java b/src/com/android/launcher3/util/ActivityTracker.java deleted file mode 100644 index b2d0d75dc4..0000000000 --- a/src/com/android/launcher3/util/ActivityTracker.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.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 <T> The activity type to track - */ -public final class ActivityTracker<T extends BaseActivity> { - - private static final String TAG = "ActivityTracker"; - - private WeakReference<T> mCurrentActivity = new WeakReference<>(null); - private CopyOnWriteArrayList<SchedulerCallback<T>> mCallbacks = new CopyOnWriteArrayList<>(); - - @Nullable - public <R extends T> 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<T> 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<T> 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<T> 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<T extends BaseActivity> { - - /** - * 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/LauncherBindableItemsContainer.java b/src/com/android/launcher3/util/LauncherBindableItemsContainer.java deleted file mode 100644 index 02779ce596..0000000000 --- a/src/com/android/launcher3/util/LauncherBindableItemsContainer.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package 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<WorkspaceItemInfo> shortcuts, ActivityContext context) { - final HashSet<WorkspaceItemInfo> 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<ItemInfo> 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/LooperExecutor.java b/src/com/android/launcher3/util/LooperExecutor.java deleted file mode 100644 index 3a8a13c8bb..0000000000 --- a/src/com/android/launcher3/util/LooperExecutor.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ -package com.android.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<Runnable> 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/window/RefreshRateTracker.java b/src/com/android/launcher3/util/window/RefreshRateTracker.java deleted file mode 100644 index 7814617b9e..0000000000 --- a/src/com/android/launcher3/util/window/RefreshRateTracker.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -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<RefreshRateTracker> 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/views/ComposeInitializer.java b/src/com/android/launcher3/views/ComposeInitializer.java deleted file mode 100644 index 092988591e..0000000000 --- a/src/com/android/launcher3/views/ComposeInitializer.java +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.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/systemUI/anim/.gitignore b/systemUI/anim/.gitignore deleted file mode 100644 index f9a33dbbcc..0000000000 --- a/systemUI/anim/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -.idea/ -.gradle/ -gradle/ -build/ -gradlew* -local.properties -*.iml -android.properties -buildSrc \ No newline at end of file diff --git a/systemUI/anim/Android.bp b/systemUI/anim/Android.bp deleted file mode 100644 index 6f53b42371..0000000000 --- a/systemUI/anim/Android.bp +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright (C) 2021 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package { - // 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" - // to get the below license kinds: - // SPDX-license-identifier-Apache-2.0 - default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"], -} - -android_library { - - name: "SystemUIAnimationLib", - - srcs: [ - "src/**/*.java", - "src/**/*.kt", - ], - exclude_srcs: [ - "src/com/android/systemui/surfaceeffects/**/*.java", - "src/com/android/systemui/surfaceeffects/**/*.kt", - ], - - resource_dirs: [ - "res", - ], - - static_libs: [ - "androidx.core_core-animation-nodeps", - "androidx.core_core-ktx", - "androidx.annotation_annotation", - "SystemUIShaderLib", - "animationlib", - ], - - manifest: "AndroidManifest.xml", - kotlincflags: ["-Xjvm-default=all"], -} - -android_library { - name: "SystemUIShaderLib", - - srcs: [ - "src/com/android/systemui/surfaceeffects/**/*.java", - "src/com/android/systemui/surfaceeffects/**/*.kt", - ], - - static_libs: [ - "androidx.core_core-animation-nodeps", - "androidx.core_core-ktx", - "androidx.annotation_annotation", - ], - - manifest: "AndroidManifest.xml", - kotlincflags: ["-Xjvm-default=all"], - - // sdk_version must be specified, otherwise it compiles against private APIs. - min_sdk_version: "33", - sdk_version: "current", -} diff --git a/systemUI/anim/AndroidManifest.xml b/systemUI/anim/AndroidManifest.xml deleted file mode 100644 index 321cc53142..0000000000 --- a/systemUI/anim/AndroidManifest.xml +++ /dev/null @@ -1,22 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - 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. ---> - -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.systemui.animation"> - - -</manifest> diff --git a/systemUI/anim/build.gradle b/systemUI/anim/build.gradle deleted file mode 100644 index 41de7d5500..0000000000 --- a/systemUI/anim/build.gradle +++ /dev/null @@ -1,30 +0,0 @@ -plugins { - alias(libs.plugins.android.library) - alias(libs.plugins.kotlin.android) -} - -android { - namespace "com.android.systemui.animation" - buildFeatures { - aidl true - } - sourceSets { - main { - java.srcDirs = ['src'] - aidl.srcDirs = ['src'] - manifest.srcFile 'AndroidManifest.xml' - res.srcDirs = ['res'] - } - } -} - -addFrameworkJar('framework-15.jar') -compileOnlyCommonJars() - -dependencies { - implementation libs.androidx.core.animation - compileOnly projects.animationlib - compileOnly projects.wmshell - compileOnly projects.flags - -} diff --git a/systemUI/anim/lib/Android.bp b/systemUI/anim/lib/Android.bp deleted file mode 100644 index 7d7302309a..0000000000 --- a/systemUI/anim/lib/Android.bp +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (C) 2024 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package { - 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" - // to get the below license kinds: - // SPDX-license-identifier-Apache-2.0 - default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"], -} - -java_library { - name: "PlatformAnimationLib-server", - srcs: [ - "src/com/android/systemui/animation/server/*.java", - ":PlatformAnimationLib-aidl", - ], - static_libs: [ - "WindowManager-Shell-shared", - ], -} - -// This is the core animation library written in java and can be depended by java sdk libraries. -// Please don't introduce kotlin code in this target since kotlin is incompatible with sdk -// libraries. -java_library { - name: "PlatformAnimationLib-core", - srcs: [ - "src/com/android/systemui/animation/*.java", - ":PlatformAnimationLib-aidl", - ], - static_libs: [ - "WindowManager-Shell-shared", - ], -} - -filegroup { - name: "PlatformAnimationLib-client-srcs", - srcs: [ - "src/com/android/systemui/animation/OriginRemoteTransition.java", - "src/com/android/systemui/animation/OriginTransitionSession.java", - "src/com/android/systemui/animation/SurfaceUIComponent.java", - "src/com/android/systemui/animation/Transactions.java", - "src/com/android/systemui/animation/UIComponent.java", - "src/com/android/systemui/animation/ViewUIComponent.java", - ":PlatformAnimationLib-aidl", - ], -} - -filegroup { - name: "PlatformAnimationLib-aidl", - srcs: [ - "src/**/*.aidl", - ], -} diff --git a/systemUI/anim/lib/OWNERS b/systemUI/anim/lib/OWNERS deleted file mode 100644 index 7569419bf4..0000000000 --- a/systemUI/anim/lib/OWNERS +++ /dev/null @@ -1,3 +0,0 @@ -#inherits OWNERS from SystemUI in addition to WEAR framework owners below -file:platform/frameworks/base:/WEAR_OWNERS - diff --git a/systemUI/anim/lib/src/com/android/systemui/animation/OriginRemoteTransition.java b/systemUI/anim/lib/src/com/android/systemui/animation/OriginRemoteTransition.java deleted file mode 100644 index 56d85ab8ac..0000000000 --- a/systemUI/anim/lib/src/com/android/systemui/animation/OriginRemoteTransition.java +++ /dev/null @@ -1,516 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.animation; - -import static android.view.WindowManager.TRANSIT_CHANGE; - -import android.animation.Animator; -import android.animation.Animator.AnimatorListener; -import android.animation.ValueAnimator; -import android.annotation.Nullable; -import android.content.Context; -import android.graphics.Rect; -import android.hardware.display.DisplayManager; -import android.os.Handler; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.DisplayMetrics; -import android.util.Log; -import android.view.SurfaceControl; -import android.window.IRemoteTransition; -import android.window.IRemoteTransitionFinishedCallback; -import android.window.TransitionInfo; -import android.window.TransitionInfo.Change; -import android.window.WindowAnimationState; - -import com.android.internal.policy.ScreenDecorationsUtils; -import com.android.wm.shell.shared.TransitionUtil; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * An implementation of {@link IRemoteTransition} that accepts a {@link UIComponent} as the origin - * and automatically attaches it to the transition leash before the transition starts. - * - * @hide - */ -public class OriginRemoteTransition extends IRemoteTransition.Stub { - private static final String TAG = "OriginRemoteTransition"; - private static final long FINISH_ANIMATION_TIMEOUT_MS = 100; - - private final Context mContext; - private final boolean mIsEntry; - private final UIComponent mOrigin; - private final TransitionPlayer mPlayer; - private final long mDuration; - private final Handler mHandler; - - @Nullable private SurfaceControl.Transaction mStartTransaction; - @Nullable private IRemoteTransitionFinishedCallback mFinishCallback; - @Nullable private UIComponent.Transaction mOriginTransaction; - @Nullable private ValueAnimator mAnimator; - @Nullable private SurfaceControl mOriginLeash; - private boolean mCancelled; - - OriginRemoteTransition( - Context context, - boolean isEntry, - UIComponent origin, - TransitionPlayer player, - long duration, - Handler handler) { - mContext = context; - mIsEntry = isEntry; - mOrigin = origin; - mPlayer = player; - mDuration = duration; - mHandler = handler; - } - - @Override - public void startAnimation( - IBinder token, - TransitionInfo info, - SurfaceControl.Transaction t, - IRemoteTransitionFinishedCallback finishCallback) { - logD("startAnimation - " + info); - mHandler.post( - () -> { - mStartTransaction = t; - mFinishCallback = finishCallback; - startAnimationInternal(info, /* states= */ null); - }); - } - - @Override - public void mergeAnimation( - IBinder transition, - TransitionInfo info, - SurfaceControl.Transaction t, - IBinder mergeTarget, - IRemoteTransitionFinishedCallback finishCallback) { - logD("mergeAnimation - " + info); - cancel(); - } - - @Override - public void takeOverAnimation( - IBinder transition, - TransitionInfo info, - SurfaceControl.Transaction t, - IRemoteTransitionFinishedCallback finishCallback, - WindowAnimationState[] states) { - logD("takeOverAnimation - info=" + info + ", states=" + Arrays.toString(states)); - mHandler.post( - () -> { - mStartTransaction = t; - mFinishCallback = finishCallback; - startAnimationInternal(info, states); - }); - } - - @Override - public void onTransitionConsumed(IBinder transition, boolean aborted) { - logD("onTransitionConsumed - aborted: " + aborted); - cancel(); - } - - private void startAnimationInternal( - TransitionInfo info, @Nullable WindowAnimationState[] states) { - if (!prepareUIs(info)) { - logE("Unable to prepare UI!"); - finishAnimation(/* finished= */ false); - return; - } - // Notify player that we are starting. - mPlayer.onStart(info, states, mStartTransaction, mOrigin, mOriginTransaction); - - // Apply the initial transactions in case the player forgot to apply them. - mOriginTransaction.commit(); - mStartTransaction.apply(); - - // Start the animator. - mAnimator = ValueAnimator.ofFloat(0.0f, 1.0f); - mAnimator.setDuration(mDuration); - mAnimator.addListener( - new AnimatorListener() { - @Override - public void onAnimationStart(Animator a) {} - - @Override - public void onAnimationEnd(Animator a) { - finishAnimation(/* finished= */ !mCancelled); - } - - @Override - public void onAnimationCancel(Animator a) { - mCancelled = true; - } - - @Override - public void onAnimationRepeat(Animator a) {} - }); - mAnimator.addUpdateListener( - a -> { - mPlayer.onProgress((float) a.getAnimatedValue()); - }); - mAnimator.start(); - } - - private boolean prepareUIs(TransitionInfo info) { - if (info.getRootCount() == 0) { - logE("prepareUIs: no root leash!"); - return false; - } - if (info.getRootCount() > 1) { - logE("prepareUIs: multi-display transition is not supported yet!"); - return false; - } - if (info.getChanges().isEmpty()) { - logE("prepareUIs: no changes!"); - return false; - } - - SurfaceControl rootLeash = info.getRoot(0).getLeash(); - int displayId = info.getChanges().get(0).getEndDisplayId(); - Rect displayBounds = getDisplayBounds(displayId); - float windowRadius = ScreenDecorationsUtils.getWindowCornerRadius(mContext); - logD("prepareUIs: windowRadius=" + windowRadius + ", displayBounds=" + displayBounds); - - // Create the origin leash and add to the transition root leash. - mOriginLeash = - new SurfaceControl.Builder().setName("OriginTransition-origin-leash").build(); - - // Create temporary transaction to build - final SurfaceControl.Transaction tmpTransaction = new SurfaceControl.Transaction(); - tmpTransaction - .reparent(mOriginLeash, rootLeash) - .show(mOriginLeash) - .setCornerRadius(mOriginLeash, windowRadius) - .setWindowCrop(mOriginLeash, displayBounds.width(), displayBounds.height()); - - // Process surfaces - List<SurfaceControl> openingSurfaces = new ArrayList<>(); - List<SurfaceControl> closingSurfaces = new ArrayList<>(); - for (Change change : info.getChanges()) { - int mode = change.getMode(); - SurfaceControl leash = change.getLeash(); - // Reparent leash to the transition root. - tmpTransaction.reparent(leash, rootLeash); - if (TransitionUtil.isOpeningMode(mode)) { - openingSurfaces.add(change.getLeash()); - // For opening surfaces, ending bounds are base bound. Apply corner radius if - // it's full screen. - Rect bounds = change.getEndAbsBounds(); - if (displayBounds.equals(bounds)) { - tmpTransaction - .setCornerRadius(leash, windowRadius) - .setWindowCrop(leash, bounds.width(), bounds.height()); - } - } else if (TransitionUtil.isClosingMode(mode) || mode == TRANSIT_CHANGE) { - // TRANSIT_CHANGE refers to the closing window in predictive back animation. - closingSurfaces.add(change.getLeash()); - // For closing surfaces, starting bounds are base bounds. Apply corner radius if - // it's full screen. - Rect bounds = change.getStartAbsBounds(); - if (displayBounds.equals(bounds)) { - tmpTransaction - .setCornerRadius(leash, windowRadius) - .setWindowCrop(leash, bounds.width(), bounds.height()); - } - } - } - - if (openingSurfaces.isEmpty() && closingSurfaces.isEmpty()) { - logD("prepareUIs: no opening/closing surfaces available, nothing to prepare."); - return false; - } - - // Set relative order: - // ---- App1 ---- - // ---- origin ---- - // ---- App2 ---- - - if (mIsEntry) { - if (!closingSurfaces.isEmpty()) { - tmpTransaction.setRelativeLayer(mOriginLeash, closingSurfaces.get(0), 1); - } else { - logW("Missing closing surface is entry transition"); - } - if (!openingSurfaces.isEmpty()) { - tmpTransaction.setRelativeLayer( - openingSurfaces.get(openingSurfaces.size() - 1), mOriginLeash, 1); - } else { - logW("Missing opening surface is entry transition"); - } - - } else { - if (!openingSurfaces.isEmpty()) { - tmpTransaction.setRelativeLayer(mOriginLeash, openingSurfaces.get(0), 1); - } else { - logW("Missing opening surface is exit transition"); - } - if (!closingSurfaces.isEmpty()) { - tmpTransaction.setRelativeLayer( - closingSurfaces.get(closingSurfaces.size() - 1), mOriginLeash, 1); - } else { - logW("Missing closing surface is exit transition"); - } - } - mStartTransaction.merge(tmpTransaction); - - // Attach origin UIComponent to origin leash. - mOriginTransaction = mOrigin.newTransaction(); - mOriginTransaction.attachToTransitionLeash( - mOrigin, mOriginLeash, displayBounds.width(), displayBounds.height()); - return true; - } - - private Rect getDisplayBounds(int displayId) { - DisplayManager dm = mContext.getSystemService(DisplayManager.class); - DisplayMetrics metrics = new DisplayMetrics(); - dm.getDisplay(displayId).getMetrics(metrics); - return new Rect(0, 0, metrics.widthPixels, metrics.heightPixels); - } - - private void finishAnimation(boolean finished) { - logD("finishAnimation: finished=" + finished); - OneShotRunnable finishInternalRunnable = new OneShotRunnable(this::finishInternal); - Runnable timeoutRunnable = - () -> { - Log.w(TAG, "Timeout waiting for surface transaction!"); - finishInternalRunnable.run(); - }; - Runnable committedRunnable = - () -> { - // Remove the timeout runnable. - mHandler.removeCallbacks(timeoutRunnable); - finishInternalRunnable.run(); - }; - if (mAnimator == null) { - // The transition didn't start. Ensure we apply the start transaction and report - // finish afterwards. - mStartTransaction - .addTransactionCommittedListener(mHandler::post, committedRunnable::run) - .apply(); - // Call finishInternal() anyway after the timeout. - mHandler.postDelayed(timeoutRunnable, FINISH_ANIMATION_TIMEOUT_MS); - return; - } - mAnimator = null; - // Notify client that we have ended. - mPlayer.onEnd(finished); - // Detach the origin from the transition leash and report finish after it's done. - mOriginTransaction - .detachFromTransitionLeash(mOrigin, mHandler::post, committedRunnable) - .commit(); - // Call finishInternal() anyway after the timeout. - mHandler.postDelayed(timeoutRunnable, FINISH_ANIMATION_TIMEOUT_MS); - } - - private void finishInternal() { - logD("finishInternal"); - if (mOriginLeash != null) { - // Release origin leash. - mOriginLeash.release(); - mOriginLeash = null; - } - try { - mFinishCallback.onTransitionFinished(null, null); - } catch (RemoteException e) { - logE("Unable to report transition finish!", e); - } - mStartTransaction = null; - mOriginTransaction = null; - mFinishCallback = null; - } - - public void cancel() { - logD("cancel()"); - mHandler.post( - () -> { - if (mAnimator != null) { - mAnimator.cancel(); - } - }); - } - - private static void logD(String msg) { - if (OriginTransitionSession.DEBUG) { - Log.d(TAG, msg); - } - } - - private static void logW(String msg) { - Log.w(TAG, msg); - } - - private static void logE(String msg) { - Log.e(TAG, msg); - } - - private static void logE(String msg, Throwable e) { - Log.e(TAG, msg, e); - } - - private static UIComponent wrapSurfaces(TransitionInfo info, boolean isOpening) { - List<SurfaceControl> surfaces = new ArrayList<>(); - Rect maxBounds = new Rect(); - for (Change change : info.getChanges()) { - int mode = change.getMode(); - if (TransitionUtil.isOpeningMode(mode) == isOpening) { - surfaces.add(change.getLeash()); - Rect bounds = isOpening ? change.getEndAbsBounds() : change.getStartAbsBounds(); - maxBounds.union(bounds); - } - } - return new SurfaceUIComponent( - surfaces, - /* alpha= */ 1.0f, - /* visible= */ true, - /* bounds= */ maxBounds, - /* baseBounds= */ maxBounds); - } - - private static void applyWindowAnimationStates( - TransitionInfo info, - @Nullable WindowAnimationState[] states, - UIComponent closingApp, - UIComponent openingApp) { - if (states == null) { - // Nothing to apply. - return; - } - // Calculate bounds. - Rect maxClosingBounds = new Rect(); - Rect maxOpeningBounds = new Rect(); - for (int i = 0; i < info.getChanges().size(); i++) { - Rect bound = getBounds(states[i]); - if (bound == null) { - continue; - } - int mode = info.getChanges().get(i).getMode(); - if (TransitionUtil.isOpeningMode(mode)) { - maxOpeningBounds.union(bound); - } else if (TransitionUtil.isClosingMode(mode) || mode == TRANSIT_CHANGE) { - // TRANSIT_CHANGE refers to the closing window in predictive back animation. - maxClosingBounds.union(bound); - } - } - - // Intentionally use a new transaction instead of reusing the existing transaction since we - // want to apply window animation states first without committing any other pending changes - // in the existing transaction. The existing transaction is expected to be committed by the - // onStart() client callback together with client's custom transformation. - UIComponent.Transaction transaction = closingApp.newTransaction(); - if (!maxClosingBounds.isEmpty()) { - logD("Applying closing window bounds: " + maxClosingBounds); - transaction.setBounds(closingApp, maxClosingBounds); - } - if (!maxOpeningBounds.isEmpty()) { - logD("Applying opening window bounds: " + maxOpeningBounds); - transaction.setBounds(openingApp, maxOpeningBounds); - } - transaction.commit(); - } - - @Nullable - private static Rect getBounds(@Nullable WindowAnimationState state) { - if (state == null || state.bounds == null) { - return null; - } - Rect out = new Rect(); - state.bounds.roundOut(out); - return out; - } - - /** A {@link Runnable} that will only run once. */ - private static class OneShotRunnable implements Runnable { - private final AtomicBoolean mDone = new AtomicBoolean(); - private final Runnable mRunnable; - - OneShotRunnable(Runnable runnable) { - this.mRunnable = runnable; - } - - @Override - public void run() { - if (!mDone.getAndSet(true)) { - mRunnable.run(); - } - } - } - - /** - * An interface that represents an origin transitions. - * - * @hide - */ - public interface TransitionPlayer { - - /** - * Called when an origin transition starts. This method exposes the raw {@link - * TransitionInfo} so that clients can extract more information from it. - * - * <p>Note: if this transition is taking over a predictive back animation, the {@link - * WindowAnimationState} will be passed to this method. The concrete implementation is - * expected to apply the {@link WindowAnimationState} before continuing the transition. - */ - default void onStart( - TransitionInfo transitionInfo, - @Nullable WindowAnimationState[] states, - SurfaceControl.Transaction sfTransaction, - UIComponent origin, - UIComponent.Transaction uiTransaction) { - // Wrap transactions. - Transactions transactions = - new Transactions() - .registerTransactionForClass(origin.getClass(), uiTransaction) - .registerTransactionForClass( - SurfaceUIComponent.class, - new SurfaceUIComponent.Transaction(sfTransaction)); - // Wrap surfaces. - UIComponent closingApp = wrapSurfaces(transitionInfo, /* isOpening= */ false); - UIComponent openingApp = wrapSurfaces(transitionInfo, /* isOpening= */ true); - - // Restore the pending animation states coming from predictive back transition. - applyWindowAnimationStates(transitionInfo, states, closingApp, openingApp); - - // Start. - onStart(transactions, origin, closingApp, openingApp); - } - - /** - * Called when an origin transition starts. This method exposes the opening and closing - * windows as wrapped {@link UIComponent} to provide simplified interface to clients. - */ - void onStart( - UIComponent.Transaction transaction, - UIComponent origin, - UIComponent closingApp, - UIComponent openingApp); - - /** Called to update the transition frame. */ - void onProgress(float progress); - - /** Called when the transition ended. */ - void onEnd(boolean finished); - } -} diff --git a/systemUI/anim/lib/src/com/android/systemui/animation/OriginTransitionSession.java b/systemUI/anim/lib/src/com/android/systemui/animation/OriginTransitionSession.java deleted file mode 100644 index cb3dfb9e78..0000000000 --- a/systemUI/anim/lib/src/com/android/systemui/animation/OriginTransitionSession.java +++ /dev/null @@ -1,333 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.animation; - -import android.annotation.IntDef; -import android.annotation.Nullable; -import android.app.ActivityOptions; -import android.app.ActivityOptions.LaunchCookie; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.os.Build; -import android.os.Handler; -import android.os.Looper; -import android.os.RemoteException; -import android.util.Log; -import android.window.IRemoteTransition; -import android.window.RemoteTransition; - -import com.android.systemui.animation.OriginRemoteTransition.TransitionPlayer; -import com.android.systemui.animation.shared.IOriginTransitions; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Predicate; -import java.util.function.Supplier; - -/** - * A session object that holds origin transition states for starting an activity from an on-screen - * UI component and smoothly transitioning back from the activity to the same UI component. - * - * @hide - */ -public class OriginTransitionSession { - private static final String TAG = "OriginTransitionSession"; - static final boolean DEBUG = Build.IS_USERDEBUG || Log.isLoggable(TAG, Log.DEBUG); - - @Retention(RetentionPolicy.SOURCE) - @IntDef(value = {NOT_STARTED, STARTED, CANCELLED}) - private @interface State {} - - @State private static final int NOT_STARTED = 0; - @State private static final int STARTED = 1; - @State private static final int CANCELLED = 5; - - private final String mName; - @Nullable private final IOriginTransitions mOriginTransitions; - private final Predicate<RemoteTransition> mIntentStarter; - @Nullable private final IRemoteTransition mEntryTransition; - @Nullable private final IRemoteTransition mExitTransition; - private final AtomicInteger mState = new AtomicInteger(NOT_STARTED); - - @Nullable private RemoteTransition mOriginTransition; - - private OriginTransitionSession( - String name, - @Nullable IOriginTransitions originTransitions, - Predicate<RemoteTransition> intentStarter, - @Nullable IRemoteTransition entryTransition, - @Nullable IRemoteTransition exitTransition) { - mName = name; - mOriginTransitions = originTransitions; - mIntentStarter = intentStarter; - mEntryTransition = entryTransition; - mExitTransition = exitTransition; - if (hasExitTransition() && !hasEntryTransition()) { - throw new IllegalArgumentException( - "Entry transition must be supplied if you want to play an exit transition!"); - } - } - - /** - * Launch the target intent with the supplied entry transition. After this method, the entry - * transition is expected to receive callbacks. The exit transition will be registered and - * triggered when the system detects a return from the launched activity to the launching - * activity. - */ - public boolean start() { - if (!mState.compareAndSet(NOT_STARTED, STARTED)) { - logE("start: illegal state - " + stateToString(mState.get())); - return false; - } - - RemoteTransition remoteTransition = null; - if (hasEntryTransition() && hasExitTransition()) { - logD("start: starting with entry and exit transition."); - try { - remoteTransition = - mOriginTransition = - mOriginTransitions.makeOriginTransition( - new RemoteTransition(mEntryTransition, mName + "-entry"), - new RemoteTransition(mExitTransition, mName + "-exit")); - } catch (RemoteException e) { - logE("Unable to create origin transition!", e); - } - } else if (hasEntryTransition()) { - logD("start: starting with entry transition."); - remoteTransition = new RemoteTransition(mEntryTransition, mName + "-entry"); - - } else { - logD("start: starting without transition."); - } - if (mIntentStarter.test(remoteTransition)) { - return true; - } else { - // Animation is cancelled by intent starter. - logD("start: cancelled by intent starter!"); - cancel(); - return false; - } - } - - /** - * Cancel the current transition and the registered exit transition if it exists. After this - * method, this session object can no longer be used. Clients need to create a new session - * object if they want to launch another intent with origin transition. - */ - public void cancel() { - final int lastState = mState.getAndSet(CANCELLED); - if (lastState == CANCELLED || lastState == NOT_STARTED) { - return; - } - logD("cancel: cancelled transition. Last state: " + stateToString(lastState)); - if (mOriginTransition != null) { - try { - mOriginTransitions.cancelOriginTransition(mOriginTransition); - mOriginTransition = null; - } catch (RemoteException e) { - logE("Unable to cancel origin transition!", e); - } - } - if (mEntryTransition instanceof OriginRemoteTransition) { - ((OriginRemoteTransition) mEntryTransition).cancel(); - } - if (mExitTransition instanceof OriginRemoteTransition) { - ((OriginRemoteTransition) mExitTransition).cancel(); - } - } - - private boolean hasEntryTransition() { - return mEntryTransition != null; - } - - private boolean hasExitTransition() { - return mOriginTransitions != null && mExitTransition != null; - } - - private void logD(String msg) { - if (DEBUG) { - Log.d(TAG, "Session[" + mName + "] - " + msg); - } - } - - private void logE(String msg) { - Log.e(TAG, "Session[" + mName + "] - " + msg); - } - - private void logE(String msg, Throwable e) { - Log.e(TAG, "Session[" + mName + "] - " + msg, e); - } - - private static String stateToString(@State int state) { - switch (state) { - case NOT_STARTED: - return "NOT_STARTED"; - case STARTED: - return "STARTED"; - case CANCELLED: - return "CANCELLED"; - default: - return "UNKNOWN(" + state + ")"; - } - } - - /** - * A builder to build a {@link OriginTransitionSession}. - * - * @hide - */ - public static class Builder { - private final Context mContext; - @Nullable private final IOriginTransitions mOriginTransitions; - @Nullable private Supplier<IRemoteTransition> mEntryTransitionSupplier; - @Nullable private Supplier<IRemoteTransition> mExitTransitionSupplier; - private Handler mHandler = new Handler(Looper.getMainLooper()); - private String mName; - @Nullable private Predicate<RemoteTransition> mIntentStarter; - - /** Create a builder that only supports entry transition. */ - public Builder(Context context) { - this(context, /* originTransitions= */ null); - } - - /** Create a builder that supports both entry and return transition. */ - public Builder(Context context, @Nullable IOriginTransitions originTransitions) { - mContext = context; - mOriginTransitions = originTransitions; - mName = context.getPackageName(); - } - - /** Specify a name that is used in logging. */ - public Builder withName(String name) { - mName = name; - return this; - } - - /** Specify an intent that will be launched when the session started. */ - public Builder withIntent(Intent intent) { - return withIntentStarter( - transition -> { - mContext.startActivity( - intent, createDefaultActivityOptions(transition).toBundle()); - return true; - }); - } - - /** Specify a pending intent that will be launched when the session started. */ - public Builder withPendingIntent(PendingIntent pendingIntent) { - return withIntentStarter( - transition -> { - try { - pendingIntent.send(createDefaultActivityOptions(transition).toBundle()); - return true; - } catch (PendingIntent.CanceledException e) { - Log.e(TAG, "Failed to launch pending intent!", e); - return false; - } - }); - } - - private static ActivityOptions createDefaultActivityOptions( - @Nullable RemoteTransition transition) { - ActivityOptions options = - transition == null - ? ActivityOptions.makeBasic() - : ActivityOptions.makeRemoteTransition(transition); - LaunchCookie cookie = new LaunchCookie(); - options.setLaunchCookie(cookie); - return options; - } - - /** - * Specify an intent starter function that will be called to start an activity. The function - * accepts an optional {@link RemoteTransition} object which can be used to create an {@link - * ActivityOptions} for the activity launch. The function can also return a {@code false} - * result to cancel the session. - * - * <p>Note: it's encouraged to use {@link #withIntent(Intent)} or {@link - * #withPendingIntent(PendingIntent)} instead of this method. Use it only if the default - * activity launch code doesn't satisfy your requirement. - */ - public Builder withIntentStarter(Predicate<RemoteTransition> intentStarter) { - mIntentStarter = intentStarter; - return this; - } - - /** Add an entry transition to the builder. */ - public Builder withEntryTransition(IRemoteTransition transition) { - mEntryTransitionSupplier = () -> transition; - return this; - } - - /** Add an origin entry transition to the builder. */ - public Builder withEntryTransition( - UIComponent entryOrigin, TransitionPlayer entryPlayer, long entryDuration) { - mEntryTransitionSupplier = - () -> - new OriginRemoteTransition( - mContext, - /* isEntry= */ true, - entryOrigin, - entryPlayer, - entryDuration, - mHandler); - return this; - } - - /** Add an exit transition to the builder. */ - public Builder withExitTransition(IRemoteTransition transition) { - mExitTransitionSupplier = () -> transition; - return this; - } - - /** Add an origin exit transition to the builder. */ - public Builder withExitTransition( - UIComponent exitTarget, TransitionPlayer exitPlayer, long exitDuration) { - mExitTransitionSupplier = - () -> - new OriginRemoteTransition( - mContext, - /* isEntry= */ false, - exitTarget, - exitPlayer, - exitDuration, - mHandler); - return this; - } - - /** Supply a handler where transition callbacks will run. */ - public Builder withHandler(Handler handler) { - mHandler = handler; - return this; - } - - /** Build an {@link OriginTransitionSession}. */ - public OriginTransitionSession build() { - if (mIntentStarter == null) { - throw new IllegalArgumentException("No intent, pending intent, or intent starter!"); - } - return new OriginTransitionSession( - mName, - mOriginTransitions, - mIntentStarter, - mEntryTransitionSupplier == null ? null : mEntryTransitionSupplier.get(), - mExitTransitionSupplier == null ? null : mExitTransitionSupplier.get()); - } - } -} diff --git a/systemUI/anim/lib/src/com/android/systemui/animation/SurfaceUIComponent.java b/systemUI/anim/lib/src/com/android/systemui/animation/SurfaceUIComponent.java deleted file mode 100644 index e1ff2a4c82..0000000000 --- a/systemUI/anim/lib/src/com/android/systemui/animation/SurfaceUIComponent.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.animation; - -import android.graphics.Matrix; -import android.graphics.Rect; -import android.graphics.RectF; -import android.view.SurfaceControl; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.concurrent.Executor; - -/** - * A {@link UIComponent} representing a {@link SurfaceControl}. - * @hide - */ -public class SurfaceUIComponent implements UIComponent { - private final Collection<SurfaceControl> mSurfaces; - private final Rect mBaseBounds; - private final float[] mFloat9 = new float[9]; - - private float mAlpha; - private boolean mVisible; - private Rect mBounds; - - public SurfaceUIComponent( - SurfaceControl sc, float alpha, boolean visible, Rect bounds, Rect baseBounds) { - this(Arrays.asList(sc), alpha, visible, bounds, baseBounds); - } - - public SurfaceUIComponent( - Collection<SurfaceControl> surfaces, - float alpha, - boolean visible, - Rect bounds, - Rect baseBounds) { - mSurfaces = surfaces; - mAlpha = alpha; - mVisible = visible; - mBounds = bounds; - mBaseBounds = baseBounds; - } - - @Override - public float getAlpha() { - return mAlpha; - } - - @Override - public boolean isVisible() { - return mVisible; - } - - @Override - public Rect getBounds() { - return mBounds; - } - - @Override - public Transaction newTransaction() { - return new Transaction(new SurfaceControl.Transaction()); - } - - @Override - public String toString() { - return "SurfaceUIComponent{mSurfaces=" - + mSurfaces - + ", mAlpha=" - + mAlpha - + ", mVisible=" - + mVisible - + ", mBounds=" - + mBounds - + ", mBaseBounds=" - + mBaseBounds - + "}"; - } - - /** - * A {@link Transaction} wrapping a {@link SurfaceControl.Transaction}. - * @hide - */ - public static class Transaction implements UIComponent.Transaction<SurfaceUIComponent> { - private final SurfaceControl.Transaction mTransaction; - private final ArrayList<Runnable> mChanges = new ArrayList<>(); - - public Transaction(SurfaceControl.Transaction transaction) { - mTransaction = transaction; - } - - @Override - public Transaction setAlpha(SurfaceUIComponent ui, float alpha) { - mChanges.add( - () -> { - ui.mAlpha = alpha; - ui.mSurfaces.forEach(s -> mTransaction.setAlpha(s, alpha)); - }); - return this; - } - - @Override - public Transaction setVisible(SurfaceUIComponent ui, boolean visible) { - mChanges.add( - () -> { - ui.mVisible = visible; - if (visible) { - ui.mSurfaces.forEach(s -> mTransaction.show(s)); - } else { - ui.mSurfaces.forEach(s -> mTransaction.hide(s)); - } - }); - return this; - } - - @Override - public Transaction setBounds(SurfaceUIComponent ui, Rect bounds) { - mChanges.add( - () -> { - if (ui.mBounds.equals(bounds)) { - return; - } - ui.mBounds = bounds; - Matrix matrix = new Matrix(); - matrix.setRectToRect( - new RectF(ui.mBaseBounds), - new RectF(ui.mBounds), - Matrix.ScaleToFit.FILL); - ui.mSurfaces.forEach(s -> mTransaction.setMatrix(s, matrix, ui.mFloat9)); - }); - return this; - } - - @Override - public Transaction attachToTransitionLeash( - SurfaceUIComponent ui, SurfaceControl transitionLeash, int w, int h) { - mChanges.add( - () -> ui.mSurfaces.forEach(s -> mTransaction.reparent(s, transitionLeash))); - return this; - } - - @Override - public Transaction detachFromTransitionLeash( - SurfaceUIComponent ui, Executor executor, Runnable onDone) { - mChanges.add( - () -> { - ui.mSurfaces.forEach(s -> mTransaction.reparent(s, null)); - mTransaction.addTransactionCommittedListener(executor, onDone::run); - }); - return this; - } - - @Override - public void commit() { - mChanges.forEach(Runnable::run); - mChanges.clear(); - mTransaction.apply(); - } - } -} diff --git a/systemUI/anim/lib/src/com/android/systemui/animation/Transactions.java b/systemUI/anim/lib/src/com/android/systemui/animation/Transactions.java deleted file mode 100644 index 64d21724ba..0000000000 --- a/systemUI/anim/lib/src/com/android/systemui/animation/Transactions.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.animation; - -import android.annotation.FloatRange; -import android.graphics.Rect; -import android.util.ArrayMap; -import android.view.SurfaceControl; - -import java.util.Map; -import java.util.concurrent.Executor; - -/** - * A composite {@link UIComponent.Transaction} that combines multiple other transactions for each ui - * type. - * @hide - */ -public class Transactions implements UIComponent.Transaction<UIComponent> { - private final Map<Class, UIComponent.Transaction> mTransactions = new ArrayMap<>(); - - /** Register a transaction object for updating a certain {@link UIComponent} type. */ - public <T extends UIComponent> Transactions registerTransactionForClass( - Class<T> clazz, UIComponent.Transaction transaction) { - mTransactions.put(clazz, transaction); - return this; - } - - private UIComponent.Transaction getTransactionFor(UIComponent ui) { - UIComponent.Transaction transaction = mTransactions.get(ui.getClass()); - if (transaction == null) { - transaction = ui.newTransaction(); - mTransactions.put(ui.getClass(), transaction); - } - return transaction; - } - - @Override - public Transactions setAlpha(UIComponent ui, @FloatRange(from = 0.0, to = 1.0) float alpha) { - getTransactionFor(ui).setAlpha(ui, alpha); - return this; - } - - @Override - public Transactions setVisible(UIComponent ui, boolean visible) { - getTransactionFor(ui).setVisible(ui, visible); - return this; - } - - @Override - public Transactions setBounds(UIComponent ui, Rect bounds) { - getTransactionFor(ui).setBounds(ui, bounds); - return this; - } - - @Override - public Transactions attachToTransitionLeash( - UIComponent ui, SurfaceControl transitionLeash, int w, int h) { - getTransactionFor(ui).attachToTransitionLeash(ui, transitionLeash, w, h); - return this; - } - - @Override - public Transactions detachFromTransitionLeash( - UIComponent ui, Executor executor, Runnable onDone) { - getTransactionFor(ui).detachFromTransitionLeash(ui, executor, onDone); - return this; - } - - @Override - public void commit() { - mTransactions.values().forEach(UIComponent.Transaction::commit); - } -} diff --git a/systemUI/anim/lib/src/com/android/systemui/animation/UIComponent.java b/systemUI/anim/lib/src/com/android/systemui/animation/UIComponent.java deleted file mode 100644 index 8aec2f42a5..0000000000 --- a/systemUI/anim/lib/src/com/android/systemui/animation/UIComponent.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.animation; - -import android.annotation.FloatRange; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.graphics.Rect; -import android.view.SurfaceControl; - -import java.util.concurrent.Executor; - -/** - * An interface representing an UI component on the display. - * @hide - */ -public interface UIComponent { - - /** Get the current alpha of this UI. */ - float getAlpha(); - - /** Check if this UI is visible. */ - boolean isVisible(); - - /** Get the bounds of this UI in its display. */ - @NonNull - Rect getBounds(); - - /** Create a new {@link Transaction} that can update this UI. */ - @NonNull - Transaction newTransaction(); - - /** - * A transaction class for updating {@link UIComponent}. - * - * @param <T> the subtype of {@link UIComponent} that this {@link Transaction} can handle. - * @hide - */ - interface Transaction<T extends UIComponent> { - /** Update alpha of an UI. Execution will be delayed until {@link #commit()} is called. */ - Transaction setAlpha(@NonNull T ui, @FloatRange(from = 0.0, to = 1.0) float alpha); - - /** - * Update visibility of an UI. Execution will be delayed until {@link #commit()} is called. - */ - @NonNull - Transaction setVisible(@NonNull T ui, boolean visible); - - /** Update bounds of an UI. Execution will be delayed until {@link #commit()} is called. */ - @NonNull - Transaction setBounds(@NonNull T ui, @NonNull Rect bounds); - - /** - * Attach a ui to the transition leash. Execution will be delayed until {@link #commit()} is - * called. - */ - @NonNull - Transaction attachToTransitionLeash( - @NonNull T ui, @NonNull SurfaceControl transitionLeash, int w, int h); - - /** - * Detach a ui from the transition leash. Execution will be delayed until {@link #commit} is - * called. - */ - @NonNull - Transaction detachFromTransitionLeash( - @NonNull T ui, @NonNull Executor executor, @Nullable Runnable onDone); - - /** Commit any pending changes added to this transaction. */ - void commit(); - } -} diff --git a/systemUI/anim/lib/src/com/android/systemui/animation/ViewUIComponent.java b/systemUI/anim/lib/src/com/android/systemui/animation/ViewUIComponent.java deleted file mode 100644 index 4b8610884b..0000000000 --- a/systemUI/anim/lib/src/com/android/systemui/animation/ViewUIComponent.java +++ /dev/null @@ -1,388 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.animation; - -import android.annotation.Nullable; -import android.graphics.Canvas; -import android.graphics.Outline; -import android.graphics.Path; -import android.graphics.PorterDuff; -import android.graphics.Rect; -import android.graphics.RectF; -import android.os.Build; -import android.os.Handler; -import android.os.Looper; -import android.util.Log; -import android.view.Surface; -import android.view.SurfaceControl; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewRootImpl; -import android.view.ViewTreeObserver; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.Executor; - -/** - * A {@link UIComponent} wrapping a {@link View}. After being attached to the transition leash, this - * class will draw the content of the {@link View} directly into the leash, and the actual View will - * be changed to INVISIBLE in its view tree. This allows the {@link View} to transform in the - * full-screen size leash without being constrained by the view tree's boundary or inheriting its - * parent's alpha and transformation. - * - * @hide - */ -public class ViewUIComponent implements UIComponent { - private static final String TAG = "ViewUIComponent"; - private static final boolean DEBUG = Build.IS_USERDEBUG || Log.isLoggable(TAG, Log.DEBUG); - private final Path mClippingPath = new Path(); - private final Outline mClippingOutline = new Outline(); - - private final LifecycleListener mLifecycleListener = new LifecycleListener(); - private final View mView; - private final Handler mMainHandler; - - @Nullable private SurfaceControl mSurfaceControl; - @Nullable private Surface mSurface; - @Nullable private Rect mViewBoundsOverride; - private boolean mVisibleOverride; - private boolean mDirty; - - public ViewUIComponent(View view) { - mView = view; - mMainHandler = new Handler(Looper.getMainLooper()); - } - - /** - * @return the view wrapped by this UI component. - * @hide - */ - public View getView() { - return mView; - } - - @Override - public float getAlpha() { - return mView.getAlpha(); - } - - @Override - public boolean isVisible() { - return isAttachedToLeash() ? mVisibleOverride : mView.getVisibility() == View.VISIBLE; - } - - @Override - public Rect getBounds() { - if (isAttachedToLeash() && mViewBoundsOverride != null) { - return mViewBoundsOverride; - } - return getRealBounds(); - } - - @Override - public Transaction newTransaction() { - return new Transaction(); - } - - private void attachToTransitionLeash(SurfaceControl transitionLeash, int w, int h) { - logD("attachToTransitionLeash"); - // Remember current visibility. - mVisibleOverride = mView.getVisibility() == View.VISIBLE; - - // Create the surface - mSurfaceControl = - new SurfaceControl.Builder().setName("ViewUIComponent").setBufferSize(w, h).build(); - mSurface = new Surface(mSurfaceControl); - - // Attach surface to transition leash - SurfaceControl.Transaction t = new SurfaceControl.Transaction(); - t.reparent(mSurfaceControl, transitionLeash).show(mSurfaceControl); - - // Make sure view draw triggers surface draw. - mLifecycleListener.register(); - - // Make the view invisible AFTER the surface is shown. - t.addTransactionCommittedListener( - this::post, - () -> { - logD("Surface attached!"); - forceDraw(); - mView.setVisibility(View.INVISIBLE); - }) - .apply(); - } - - private void detachFromTransitionLeash(Executor executor, Runnable onDone) { - logD("detachFromTransitionLeash"); - Surface s = mSurface; - SurfaceControl sc = mSurfaceControl; - mSurface = null; - mSurfaceControl = null; - mLifecycleListener.unregister(); - // Restore view visibility - mView.setVisibility(mVisibleOverride ? View.VISIBLE : View.INVISIBLE); - // Clean up surfaces. - SurfaceControl.Transaction t = new SurfaceControl.Transaction(); - t.reparent(sc, null) - .addTransactionCommittedListener( - this::post, - () -> { - s.release(); - sc.release(); - executor.execute(onDone); - }); - ViewRootImpl viewRoot = mView.getViewRootImpl(); - if (viewRoot == null) { - t.apply(); - } else { - // Apply transaction AFTER the view is drawn. - viewRoot.applyTransactionOnDraw(t); - // Request layout to force redrawing the entire view tree, so that the transaction is - // guaranteed to be applied. - viewRoot.requestLayout(); - } - } - - @Override - public String toString() { - return "ViewUIComponent{" - + "alpha=" - + getAlpha() - + ", visible=" - + isVisible() - + ", bounds=" - + getBounds() - + ", attached=" - + isAttachedToLeash() - + "}"; - } - - private void draw() { - if (!mDirty) { - // No need to draw. This is probably a duplicate call. - logD("draw: skipped - clean"); - return; - } - mDirty = false; - if (!isAttachedToLeash()) { - // Not attached. - logD("draw: skipped - not attached"); - return; - } - ViewGroup.LayoutParams params = mView.getLayoutParams(); - if (params == null) { - // layout pass didn't happen. - logD("draw: skipped - no layout"); - return; - } - - final Rect realBounds = getRealBounds(); - if (realBounds.width() == 0 || realBounds.height() == 0) { - // bad bounds. - logD("draw: skipped - zero bounds"); - return; - } - - - Canvas canvas = mSurface.lockHardwareCanvas(); - // Clear the canvas first. - canvas.drawColor(0, PorterDuff.Mode.CLEAR); - if (mVisibleOverride) { - Rect renderBounds = getBounds(); - canvas.translate(renderBounds.left, renderBounds.top); - canvas.scale( - (float) renderBounds.width() / realBounds.width(), - (float) renderBounds.height() / realBounds.height()); - - if (mView.getClipToOutline()) { - mView.getOutlineProvider().getOutline(mView, mClippingOutline); - mClippingPath.reset(); - RectF rect = new RectF(0, 0, mView.getWidth(), mView.getHeight()); - final float cornerRadius = mClippingOutline.getRadius(); - mClippingPath.addRoundRect(rect, cornerRadius, cornerRadius, Path.Direction.CW); - mClippingPath.close(); - canvas.clipPath(mClippingPath); - } - - canvas.saveLayerAlpha(null, (int) (255 * mView.getAlpha())); - mView.draw(canvas); - canvas.restore(); - } - mSurface.unlockCanvasAndPost(canvas); - logD("draw: done"); - } - - private void forceDraw() { - mDirty = true; - draw(); - } - - private Rect getRealBounds() { - Rect output = new Rect(); - mView.getBoundsOnScreen(output); - return output; - } - - private boolean isAttachedToLeash() { - return mSurfaceControl != null && mSurface != null; - } - - private void logD(String msg) { - if (DEBUG) { - Log.d(TAG, msg); - } - } - - private void setVisible(boolean visible) { - logD("setVisibility: " + visible); - if (isAttachedToLeash()) { - mVisibleOverride = visible; - postDraw(); - } else { - mView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); - } - } - - private void setBounds(Rect bounds) { - logD("setBounds: " + bounds); - mViewBoundsOverride = bounds; - if (isAttachedToLeash()) { - postDraw(); - } else { - Log.w(TAG, "setBounds: not attached to leash!"); - } - } - - private void setAlpha(float alpha) { - logD("setAlpha: " + alpha); - mView.setAlpha(alpha); - if (isAttachedToLeash()) { - postDraw(); - } - } - - private void postDraw() { - if (mDirty) { - return; - } - mDirty = true; - post(this::draw); - } - - private void post(Runnable r) { - if (mView.isAttachedToWindow()) { - mView.post(r); - } else { - // If the view is detached from window, {@code View.post()} will postpone the action - // until the view is attached again. However, we don't know if the view will be attached - // again, so we post the action to the main thread in this case. This could lead to race - // condition if the attachment change caused a thread switching, and it's the caller's - // responsibility to ensure the window attachment state doesn't change unexpectedly. - if (DEBUG) { - Log.w(TAG, mView + " is not attached. Posting action to main thread!"); - } - mMainHandler.post(r); - } - } - - /** A listener for monitoring view life cycles. */ - private class LifecycleListener - implements ViewTreeObserver.OnDrawListener, View.OnAttachStateChangeListener { - private boolean mRegistered; - - @Override - public void onDraw() { - // View draw should trigger surface draw. - postDraw(); - } - - @Override - public void onViewAttachedToWindow(View v) { - // empty - } - - @Override - public void onViewDetachedFromWindow(View v) { - Log.w( - TAG, - v + " is detached from the window. Unregistering the life cycle listener ..."); - unregister(); - } - - public void register() { - if (mRegistered) { - return; - } - mRegistered = true; - mView.getViewTreeObserver().addOnDrawListener(this); - mView.addOnAttachStateChangeListener(this); - } - - public void unregister() { - if (!mRegistered) { - return; - } - mRegistered = false; - mView.getViewTreeObserver().removeOnDrawListener(this); - mView.removeOnAttachStateChangeListener(this); - } - } - - /** @hide */ - public static class Transaction implements UIComponent.Transaction<ViewUIComponent> { - private final List<Runnable> mChanges = new ArrayList<>(); - - @Override - public Transaction setAlpha(ViewUIComponent ui, float alpha) { - mChanges.add(() -> ui.post(() -> ui.setAlpha(alpha))); - return this; - } - - @Override - public Transaction setVisible(ViewUIComponent ui, boolean visible) { - mChanges.add(() -> ui.post(() -> ui.setVisible(visible))); - return this; - } - - @Override - public Transaction setBounds(ViewUIComponent ui, Rect bounds) { - mChanges.add(() -> ui.post(() -> ui.setBounds(bounds))); - return this; - } - - @Override - public Transaction attachToTransitionLeash( - ViewUIComponent ui, SurfaceControl transitionLeash, int w, int h) { - mChanges.add(() -> ui.post(() -> ui.attachToTransitionLeash(transitionLeash, w, h))); - return this; - } - - @Override - public Transaction detachFromTransitionLeash( - ViewUIComponent ui, Executor executor, Runnable onDone) { - mChanges.add(() -> ui.post(() -> ui.detachFromTransitionLeash(executor, onDone))); - return this; - } - - @Override - public void commit() { - mChanges.forEach(Runnable::run); - mChanges.clear(); - } - } -} diff --git a/systemUI/anim/lib/src/com/android/systemui/animation/server/IOriginTransitionsImpl.java b/systemUI/anim/lib/src/com/android/systemui/animation/server/IOriginTransitionsImpl.java deleted file mode 100644 index 3b66460140..0000000000 --- a/systemUI/anim/lib/src/com/android/systemui/animation/server/IOriginTransitionsImpl.java +++ /dev/null @@ -1,471 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.animation.server; - -import static android.view.WindowManager.TRANSIT_CHANGE; -import static android.view.WindowManager.TRANSIT_CLOSE; -import static android.view.WindowManager.TRANSIT_OPEN; -import static android.view.WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION; -import static android.view.WindowManager.TRANSIT_TO_BACK; -import static android.view.WindowManager.TRANSIT_TO_FRONT; - -import android.Manifest; -import android.annotation.Nullable; -import android.app.TaskInfo; -import android.content.ComponentName; -import android.content.Context; -import android.os.Build; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.ArrayMap; -import android.util.Log; -import android.view.SurfaceControl; -import android.window.IRemoteTransition; -import android.window.IRemoteTransitionFinishedCallback; -import android.window.RemoteTransition; -import android.window.TransitionFilter; -import android.window.TransitionInfo; -import android.window.TransitionInfo.Change; -import android.window.WindowAnimationState; - -import com.android.internal.annotations.GuardedBy; -import com.android.internal.util.IndentingPrintWriter; -import com.android.systemui.animation.shared.IOriginTransitions; -import com.android.wm.shell.shared.ShellTransitions; -import com.android.wm.shell.shared.TransitionUtil; - -import java.util.Map; -import java.util.concurrent.Executor; -import java.util.function.Predicate; - -/** An implementation of the {@link IOriginTransitions}. */ -public class IOriginTransitionsImpl extends IOriginTransitions.Stub { - private static final String TAG = "OriginTransitions"; - private static final boolean DEBUG = Build.IS_USERDEBUG || Log.isLoggable(TAG, Log.DEBUG); - - private final Object mLock = new Object(); - private final ShellTransitions mShellTransitions; - private final Context mContext; - - @GuardedBy("mLock") - private final Map<IBinder, OriginTransitionRecord> mRecords = new ArrayMap<>(); - - public IOriginTransitionsImpl(Context context, ShellTransitions shellTransitions) { - mShellTransitions = shellTransitions; - mContext = context; - } - - @Override - public RemoteTransition makeOriginTransition( - RemoteTransition launchTransition, RemoteTransition returnTransition) - throws RemoteException { - if (DEBUG) { - Log.d( - TAG, - "makeOriginTransition: (" + launchTransition + ", " + returnTransition + ")"); - } - enforceRemoteTransitionPermission(); - synchronized (mLock) { - OriginTransitionRecord record = - new OriginTransitionRecord(launchTransition, returnTransition); - mRecords.put(record.getToken(), record); - return record.asLaunchableTransition(); - } - } - - @Override - public void cancelOriginTransition(RemoteTransition originTransition) { - if (DEBUG) { - Log.d(TAG, "cancelOriginTransition: " + originTransition); - } - enforceRemoteTransitionPermission(); - synchronized (mLock) { - if (!mRecords.containsKey(originTransition.asBinder())) { - return; - } - mRecords.get(originTransition.asBinder()).destroy(); - } - } - - private void enforceRemoteTransitionPermission() { - mContext.enforceCallingPermission( - Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS, - "Missing permission " - + Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS); - } - - public void dump(IndentingPrintWriter ipw) { - ipw.println("IOriginTransitionsImpl"); - ipw.println("Active records:"); - ipw.increaseIndent(); - synchronized (mLock) { - if (mRecords.isEmpty()) { - ipw.println("none"); - } else { - for (OriginTransitionRecord record : mRecords.values()) { - record.dump(ipw); - } - } - } - ipw.decreaseIndent(); - } - - /** - * An {@link IRemoteTransition} that delegates animation to another {@link IRemoteTransition} - * and notify callbacks when the transition starts. - */ - private static class RemoteTransitionDelegate extends IRemoteTransition.Stub { - private final IRemoteTransition mTransition; - private final Predicate<TransitionInfo> mOnStarting; - private final Executor mExecutor; - - RemoteTransitionDelegate( - Executor executor, - IRemoteTransition transition, - Predicate<TransitionInfo> onStarting) { - mExecutor = executor; - mTransition = transition; - mOnStarting = onStarting; - } - - @Override - public void startAnimation( - IBinder token, - TransitionInfo info, - SurfaceControl.Transaction t, - IRemoteTransitionFinishedCallback finishCallback) - throws RemoteException { - if (DEBUG) { - Log.d(TAG, "startAnimation: " + info); - } - if (maybeInterceptTransition(info, t, finishCallback)) { - return; - } - mTransition.startAnimation(token, info, t, finishCallback); - } - - @Override - public void mergeAnimation( - IBinder transition, - TransitionInfo info, - SurfaceControl.Transaction t, - IBinder mergeTarget, - IRemoteTransitionFinishedCallback finishCallback) - throws RemoteException { - if (DEBUG) { - Log.d(TAG, "mergeAnimation: " + info); - } - mTransition.mergeAnimation(transition, info, t, mergeTarget, finishCallback); - } - - @Override - public void takeOverAnimation( - IBinder transition, - TransitionInfo info, - SurfaceControl.Transaction t, - IRemoteTransitionFinishedCallback finishCallback, - WindowAnimationState[] states) - throws RemoteException { - if (DEBUG) { - Log.d(TAG, "takeOverAnimation: " + info); - } - if (maybeInterceptTransition(info, t, finishCallback)) { - return; - } - mTransition.takeOverAnimation(transition, info, t, finishCallback, states); - } - - @Override - public void onTransitionConsumed(IBinder transition, boolean aborted) - throws RemoteException { - if (DEBUG) { - Log.d(TAG, "onTransitionConsumed: aborted=" + aborted); - } - mTransition.onTransitionConsumed(transition, aborted); - } - - @Override - public String toString() { - return "RemoteTransitionDelegate{transition=" + mTransition + "}"; - } - - private boolean maybeInterceptTransition( - TransitionInfo info, - SurfaceControl.Transaction t, - IRemoteTransitionFinishedCallback finishCallback) { - if (!mOnStarting.test(info)) { - Log.w(TAG, "Intercepting cancelled transition " + mTransition); - t.addTransactionCommittedListener( - mExecutor, - () -> { - try { - finishCallback.onTransitionFinished(null, null); - } catch (RemoteException e) { - Log.e(TAG, "Unable to report finish.", e); - } - }) - .apply(); - return true; - } - return false; - } - } - - /** A data record containing the origin transition pieces. */ - private class OriginTransitionRecord implements IBinder.DeathRecipient { - private final RemoteTransition mWrappedLaunchTransition; - private final RemoteTransition mWrappedReturnTransition; - - @GuardedBy("mLock") - private boolean mDestroyed; - - OriginTransitionRecord(RemoteTransition launchTransition, RemoteTransition returnTransition) - throws RemoteException { - mWrappedLaunchTransition = wrap(launchTransition, this::onLaunchTransitionStarting); - mWrappedReturnTransition = wrap(returnTransition, this::onReturnTransitionStarting); - linkToDeath(); - } - - private boolean onLaunchTransitionStarting(TransitionInfo info) { - synchronized (mLock) { - if (mDestroyed) { - return false; - } - TransitionFilter filter = - createFilterForReverseTransition( - info, /* forPredictiveBackTakeover= */ false); - if (filter != null) { - if (DEBUG) { - Log.d(TAG, "Registering filter " + filter); - } - mShellTransitions.registerRemote(filter, mWrappedReturnTransition); - } - TransitionFilter takeoverFilter = - createFilterForReverseTransition( - info, /* forPredictiveBackTakeover= */ true); - if (takeoverFilter != null) { - if (DEBUG) { - Log.d(TAG, "Registering filter for takeover " + takeoverFilter); - } - mShellTransitions.registerRemoteForTakeover( - takeoverFilter, mWrappedReturnTransition); - } - return true; - } - } - - private boolean onReturnTransitionStarting(TransitionInfo info) { - synchronized (mLock) { - if (mDestroyed) { - return false; - } - // Clean up stuff. - destroy(); - return true; - } - } - - public void destroy() { - synchronized (mLock) { - if (mDestroyed) { - // Already destroyed. - return; - } - if (DEBUG) { - Log.d(TAG, "Destroying origin transition record " + this); - } - mDestroyed = true; - unlinkToDeath(); - mShellTransitions.unregisterRemote(mWrappedReturnTransition); - mRecords.remove(getToken()); - } - } - - private void linkToDeath() throws RemoteException { - asDelegate(mWrappedLaunchTransition).mTransition.asBinder().linkToDeath(this, 0); - asDelegate(mWrappedReturnTransition).mTransition.asBinder().linkToDeath(this, 0); - } - - private void unlinkToDeath() { - asDelegate(mWrappedLaunchTransition).mTransition.asBinder().unlinkToDeath(this, 0); - asDelegate(mWrappedReturnTransition).mTransition.asBinder().unlinkToDeath(this, 0); - } - - public IBinder getToken() { - return asLaunchableTransition().asBinder(); - } - - public RemoteTransition asLaunchableTransition() { - return mWrappedLaunchTransition; - } - - @Override - public void binderDied() { - destroy(); - } - - @Override - public String toString() { - return "OriginTransitionRecord{launch=" - + mWrappedReturnTransition - + ", return=" - + mWrappedReturnTransition - + "}"; - } - - public void dump(IndentingPrintWriter ipw) { - synchronized (mLock) { - ipw.println("OriginTransitionRecord"); - ipw.increaseIndent(); - ipw.println("mDestroyed: " + mDestroyed); - ipw.println("Launch transition:"); - ipw.increaseIndent(); - ipw.println(mWrappedLaunchTransition); - ipw.decreaseIndent(); - ipw.println("Return transition:"); - ipw.increaseIndent(); - ipw.println(mWrappedReturnTransition); - ipw.decreaseIndent(); - ipw.decreaseIndent(); - } - } - - private static RemoteTransitionDelegate asDelegate(RemoteTransition transition) { - return (RemoteTransitionDelegate) transition.getRemoteTransition(); - } - - private RemoteTransition wrap( - RemoteTransition transition, Predicate<TransitionInfo> onStarting) { - return new RemoteTransition( - new RemoteTransitionDelegate( - mContext.getMainExecutor(), - transition.getRemoteTransition(), - onStarting), - transition.getDebugName()); - } - - @Nullable - private static TransitionFilter createFilterForReverseTransition( - TransitionInfo info, boolean forPredictiveBackTakeover) { - TaskInfo launchingTaskInfo = null; - TaskInfo launchedTaskInfo = null; - ComponentName launchingActivity = null; - ComponentName launchedActivity = null; - for (Change change : info.getChanges()) { - int mode = change.getMode(); - TaskInfo taskInfo = change.getTaskInfo(); - ComponentName activity = change.getActivityComponent(); - if (TransitionUtil.isClosingMode(mode) - && launchingTaskInfo == null - && taskInfo != null) { - // Found the launching task! - launchingTaskInfo = taskInfo; - } else if (TransitionUtil.isOpeningMode(mode) - && launchedTaskInfo == null - && taskInfo != null) { - // Found the launched task! - launchedTaskInfo = taskInfo; - } else if (TransitionUtil.isClosingMode(mode) - && launchingActivity == null - && activity != null) { - // Found the launching activity - launchingActivity = activity; - } else if (TransitionUtil.isOpeningMode(mode) - && launchedActivity == null - && activity != null) { - // Found the launched activity! - launchedActivity = activity; - } - } - if (DEBUG) { - Log.d( - TAG, - "createFilterForReverseTransition: forPredictiveBackTakeover=" - + forPredictiveBackTakeover - + ", launchingTaskInfo=" - + launchingTaskInfo - + ", launchedTaskInfo=" - + launchedTaskInfo - + ", launchingActivity=" - + launchedActivity - + ", launchedActivity=" - + launchedActivity); - } - if (launchingTaskInfo == null && launchingActivity == null) { - Log.w( - TAG, - "createFilterForReverseTransition: unable to find launching task or" - + " launching activity!"); - return null; - } - if (launchedTaskInfo == null && launchedActivity == null) { - Log.w( - TAG, - "createFilterForReverseTransition: unable to find launched task or launched" - + " activity!"); - return null; - } - if (launchedTaskInfo != null && launchedTaskInfo.launchCookies.isEmpty()) { - Log.w( - TAG, - "createFilterForReverseTransition: skipped - launched task has no launch" - + " cookie!"); - return null; - } - if (forPredictiveBackTakeover && launchedTaskInfo == null) { - // Predictive back take over currently only support cross-task transition. - Log.d( - TAG, - "createFilterForReverseTransition: skipped - unable to find launched task" - + " for predictive back takeover"); - return null; - } - TransitionFilter filter = new TransitionFilter(); - if (forPredictiveBackTakeover) { - filter.mTypeSet = new int[] {TRANSIT_PREPARE_BACK_NAVIGATION}; - } else { - filter.mTypeSet = - new int[] {TRANSIT_CLOSE, TRANSIT_TO_BACK, TRANSIT_OPEN, TRANSIT_TO_FRONT}; - } - - // The opening activity of the return transition must match the activity we just closed. - TransitionFilter.Requirement req1 = new TransitionFilter.Requirement(); - req1.mModes = new int[] {TRANSIT_OPEN, TRANSIT_TO_FRONT}; - req1.mTopActivity = - launchingActivity == null ? launchingTaskInfo.topActivity : launchingActivity; - - TransitionFilter.Requirement req2 = new TransitionFilter.Requirement(); - if (forPredictiveBackTakeover) { - req2.mModes = new int[] {TRANSIT_CHANGE}; - } else { - req2.mModes = new int[] {TRANSIT_CLOSE, TRANSIT_TO_BACK}; - } - if (launchedTaskInfo != null) { - // For task transitions, the closing task's cookie must match the task we just - // launched. - req2.mLaunchCookie = launchedTaskInfo.launchCookies.get(0); - } else { - // For activity transitions, the closing activity of the return transition must - // match the activity we just launched. - req2.mTopActivity = launchedActivity; - } - - filter.mRequirements = new TransitionFilter.Requirement[] {req1, req2}; - return filter; - } - } -} diff --git a/systemUI/anim/lib/src/com/android/systemui/animation/shared/IOriginTransitions.aidl b/systemUI/anim/lib/src/com/android/systemui/animation/shared/IOriginTransitions.aidl deleted file mode 100644 index 31cca701ca..0000000000 --- a/systemUI/anim/lib/src/com/android/systemui/animation/shared/IOriginTransitions.aidl +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.animation.shared; - -import android.window.RemoteTransition; - -/** - * An interface for an app to link a launch transition and a return transition together into an - * origin transition. - */ -interface IOriginTransitions { - - /** - * Create a new "origin transition" which wraps a launch transition and a return transition. - * The returned {@link RemoteTransition} is expected to be passed to - * {@link ActivityOptions#makeRemoteTransition(RemoteTransition)} to create an - * {@link ActivityOptions} and being used to launch an intent. When being used with - * {@link ActivityOptions}, the launch transition will be triggered for launching the intent, - * and the return transition will be remembered and triggered for returning from the launched - * activity. - */ - RemoteTransition makeOriginTransition(in RemoteTransition launchTransition, - in RemoteTransition returnTransition) = 1; - - /** - * Cancels an origin transition. Any parts not yet played will no longer be triggered, and the - * origin transition object will reset to a single frame animation. - */ - void cancelOriginTransition(in RemoteTransition originTransition) = 2; -} diff --git a/systemUI/anim/lib/tests/Android.bp b/systemUI/anim/lib/tests/Android.bp deleted file mode 100644 index c1a3e84e1d..0000000000 --- a/systemUI/anim/lib/tests/Android.bp +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (C) 2024 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package { - default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_", - default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"], -} - -android_test { - name: "PlatformAnimationLibCoreTests", - - defaults: [ - "platform_app_defaults", - ], - srcs: [ - "src/**/*.java", - ], - - dxflags: ["--multi-dex"], - platform_apis: true, - test_suites: ["device-tests"], - static_libs: [ - "PlatformAnimationLib-core", - "platform-test-rules", - "testables", - ], - compile_multilib: "both", - libs: [ - "android.test.runner.stubs.system", - "android.test.base.stubs.system", - ], - - certificate: "platform", - - manifest: "AndroidManifest.xml", -} diff --git a/systemUI/anim/lib/tests/AndroidManifest.xml b/systemUI/anim/lib/tests/AndroidManifest.xml deleted file mode 100644 index 1788abff44..0000000000 --- a/systemUI/anim/lib/tests/AndroidManifest.xml +++ /dev/null @@ -1,28 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2024 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" - android:sharedUserId="android.uid.system" - package="com.android.systemui.animation.core.tests" > - - <application android:debuggable="true" android:testOnly="true"> - <uses-library android:name="android.test.runner" /> - </application> - - <instrumentation android:name="android.testing.TestableInstrumentation" - android:targetPackage="com.android.systemui.animation.core.tests" - android:label="Tests for PlatformAnimationLib-core" /> -</manifest> diff --git a/systemUI/anim/lib/tests/AndroidTest.xml b/systemUI/anim/lib/tests/AndroidTest.xml deleted file mode 100644 index 0f37d7af22..0000000000 --- a/systemUI/anim/lib/tests/AndroidTest.xml +++ /dev/null @@ -1,39 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2024 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> -<configuration description="Runs Tests for PlatformAnimationLib-core."> - <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup"> - <option name="test-file-name" value="PlatformAnimationLibCoreTests.apk" /> - <option name="install-arg" value="-t" /> - </target_preparer> - - <!-- Among other reasons, root access is needed for screen recording artifacts. --> - <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"> - <option name="force-root" value="true" /> - </target_preparer> - - <test class="com.android.tradefed.testtype.AndroidJUnitTest" > - <option name="package" value="com.android.systemui.animation.core.tests" /> - <option name="runner" value="android.testing.TestableInstrumentation" /> - <option name="test-filter-dir" value="/data/data/com.android.systemui.animation.core.tests" /> - <option name="hidden-api-checks" value="false"/> - </test> - - <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> - <option name="directory-keys" value="/data/user/0/com.android.systemui.animation.core.tests/files" /> - <option name="collect-on-run-ended-only" value="false" /> - </metrics_collector> -</configuration> diff --git a/systemUI/anim/lib/tests/src/com/android/systemui/animation/OriginTransitionSessionTest.java b/systemUI/anim/lib/tests/src/com/android/systemui/animation/OriginTransitionSessionTest.java deleted file mode 100644 index 287e53b906..0000000000 --- a/systemUI/anim/lib/tests/src/com/android/systemui/animation/OriginTransitionSessionTest.java +++ /dev/null @@ -1,406 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.animation; - -import static com.google.common.truth.Truth.assertThat; - -import android.annotation.Nullable; -import android.app.Instrumentation; -import android.content.ComponentName; -import android.content.Context; -import android.os.Binder; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.ArrayMap; -import android.view.SurfaceControl; -import android.view.WindowManager; -import android.window.IRemoteTransition; -import android.window.IRemoteTransitionFinishedCallback; -import android.window.RemoteTransition; -import android.window.TransitionInfo; -import android.window.WindowAnimationState; -import android.window.WindowContainerTransaction; - -import androidx.test.filters.SmallTest; -import androidx.test.platform.app.InstrumentationRegistry; - -import com.android.systemui.animation.shared.IOriginTransitions; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -import java.util.Map; -import java.util.function.Predicate; - -/** Unit tests for {@link OriginTransitionSession}. */ -@SmallTest -@RunWith(JUnit4.class) -public final class OriginTransitionSessionTest { - private static final ComponentName TEST_ACTIVITY_1 = new ComponentName("test", "Activity1"); - private static final ComponentName TEST_ACTIVITY_2 = new ComponentName("test", "Activity2"); - private static final ComponentName TEST_ACTIVITY_3 = new ComponentName("test", "Activity3"); - - private FakeIOriginTransitions mIOriginTransitions; - private Instrumentation mInstrumentation; - private FakeIntentStarter mIntentStarter; - private Context mContext; - - @Before - public void setUp() { - mInstrumentation = InstrumentationRegistry.getInstrumentation(); - mContext = mInstrumentation.getTargetContext(); - mIOriginTransitions = new FakeIOriginTransitions(); - mIntentStarter = new FakeIntentStarter(TEST_ACTIVITY_1, TEST_ACTIVITY_2); - } - - @Test - public void sessionStart_withEntryAndExitTransition_transitionsPlayed() { - FakeRemoteTransition entry = new FakeRemoteTransition(); - FakeRemoteTransition exit = new FakeRemoteTransition(); - OriginTransitionSession session = - new OriginTransitionSession.Builder(mContext, mIOriginTransitions) - .withIntentStarter(mIntentStarter) - .withEntryTransition(entry) - .withExitTransition(exit) - .build(); - - session.start(); - - assertThat(mIntentStarter.hasLaunched()).isTrue(); - assertThat(entry.started()).isTrue(); - - runReturnTransition(mIntentStarter); - - assertThat(exit.started()).isTrue(); - } - - @Test - public void sessionStart_withEntryTransition_transitionPlayed() { - FakeRemoteTransition entry = new FakeRemoteTransition(); - OriginTransitionSession session = - new OriginTransitionSession.Builder(mContext, mIOriginTransitions) - .withIntentStarter(mIntentStarter) - .withEntryTransition(entry) - .build(); - - session.start(); - - assertThat(mIntentStarter.hasLaunched()).isTrue(); - assertThat(entry.started()).isTrue(); - assertThat(mIOriginTransitions.hasPendingReturnTransitions()).isFalse(); - } - - @Test - public void sessionStart_withoutTransition_launchedIntent() { - OriginTransitionSession session = - new OriginTransitionSession.Builder(mContext, mIOriginTransitions) - .withIntentStarter(mIntentStarter) - .build(); - - session.start(); - - assertThat(mIntentStarter.hasLaunched()).isTrue(); - assertThat(mIOriginTransitions.hasPendingReturnTransitions()).isFalse(); - } - - @Test - public void sessionStart_cancelledByIntentStarter_transitionNotPlayed() { - FakeRemoteTransition entry = new FakeRemoteTransition(); - FakeRemoteTransition exit = new FakeRemoteTransition(); - mIntentStarter = - new FakeIntentStarter(TEST_ACTIVITY_1, TEST_ACTIVITY_2, /* result= */ false); - OriginTransitionSession session = - new OriginTransitionSession.Builder(mContext, mIOriginTransitions) - .withIntentStarter(mIntentStarter) - .withEntryTransition(entry) - .withExitTransition(exit) - .build(); - - session.start(); - - assertThat(mIntentStarter.hasLaunched()).isFalse(); - assertThat(entry.started()).isFalse(); - assertThat(exit.started()).isFalse(); - assertThat(mIOriginTransitions.hasPendingReturnTransitions()).isFalse(); - } - - @Test - public void sessionStart_alreadyStarted_noOp() { - FakeRemoteTransition entry = new FakeRemoteTransition(); - FakeRemoteTransition exit = new FakeRemoteTransition(); - OriginTransitionSession session = - new OriginTransitionSession.Builder(mContext, mIOriginTransitions) - .withIntentStarter(mIntentStarter) - .withEntryTransition(entry) - .withExitTransition(exit) - .build(); - session.start(); - entry.reset(); - mIntentStarter.reset(); - - session.start(); - - assertThat(mIntentStarter.hasLaunched()).isFalse(); - assertThat(entry.started()).isFalse(); - } - - @Test - public void sessionStart_alreadyCancelled_noOp() { - FakeRemoteTransition entry = new FakeRemoteTransition(); - FakeRemoteTransition exit = new FakeRemoteTransition(); - OriginTransitionSession session = - new OriginTransitionSession.Builder(mContext, mIOriginTransitions) - .withIntentStarter(mIntentStarter) - .withEntryTransition(entry) - .withExitTransition(exit) - .build(); - session.cancel(); - - session.start(); - - assertThat(mIntentStarter.hasLaunched()).isFalse(); - assertThat(entry.started()).isFalse(); - assertThat(mIOriginTransitions.hasPendingReturnTransitions()).isFalse(); - } - - @Test - public void sessionCancelled_returnTransitionNotPlayed() { - FakeRemoteTransition entry = new FakeRemoteTransition(); - FakeRemoteTransition exit = new FakeRemoteTransition(); - OriginTransitionSession session = - new OriginTransitionSession.Builder(mContext, mIOriginTransitions) - .withIntentStarter(mIntentStarter) - .withEntryTransition(entry) - .withExitTransition(exit) - .build(); - - session.start(); - session.cancel(); - - assertThat(mIOriginTransitions.hasPendingReturnTransitions()).isFalse(); - } - - @Test - public void multipleSessionsStarted_allTransitionsPlayed() { - FakeRemoteTransition entry1 = new FakeRemoteTransition(); - FakeRemoteTransition exit1 = new FakeRemoteTransition(); - FakeIntentStarter starter1 = mIntentStarter; - OriginTransitionSession session1 = - new OriginTransitionSession.Builder(mContext, mIOriginTransitions) - .withIntentStarter(starter1) - .withEntryTransition(entry1) - .withExitTransition(exit1) - .build(); - FakeRemoteTransition entry2 = new FakeRemoteTransition(); - FakeRemoteTransition exit2 = new FakeRemoteTransition(); - FakeIntentStarter starter2 = new FakeIntentStarter(TEST_ACTIVITY_2, TEST_ACTIVITY_3); - OriginTransitionSession session2 = - new OriginTransitionSession.Builder(mContext, mIOriginTransitions) - .withIntentStarter(starter2) - .withEntryTransition(entry2) - .withExitTransition(exit2) - .build(); - - session1.start(); - - assertThat(starter1.hasLaunched()).isTrue(); - assertThat(entry1.started()).isTrue(); - - session2.start(); - - assertThat(starter2.hasLaunched()).isTrue(); - assertThat(entry2.started()).isTrue(); - - runReturnTransition(starter2); - - assertThat(exit2.started()).isTrue(); - - runReturnTransition(starter1); - - assertThat(exit1.started()).isTrue(); - } - - private void runReturnTransition(FakeIntentStarter intentStarter) { - TransitionInfo info = - buildTransitionInfo(intentStarter.getToActivity(), intentStarter.getFromActivity()); - mIOriginTransitions.runReturnTransition(intentStarter.getTransitionOfLastLaunch(), info); - } - - private static TransitionInfo buildTransitionInfo(ComponentName from, ComponentName to) { - TransitionInfo info = new TransitionInfo(WindowManager.TRANSIT_OPEN, /* flags= */ 0); - TransitionInfo.Change c1 = - new TransitionInfo.Change(/* container= */ null, /* leash= */ null); - c1.setMode(WindowManager.TRANSIT_OPEN); - c1.setActivityComponent(to); - TransitionInfo.Change c2 = - new TransitionInfo.Change(/* container= */ null, /* leash= */ null); - c2.setMode(WindowManager.TRANSIT_CLOSE); - c2.setActivityComponent(from); - info.addChange(c2); - info.addChange(c1); - return info; - } - - private static class FakeIntentStarter implements Predicate<RemoteTransition> { - private final ComponentName mFromActivity; - private final ComponentName mToActivity; - private final boolean mResult; - - @Nullable private RemoteTransition mTransition; - private boolean mLaunched; - - FakeIntentStarter(ComponentName from, ComponentName to) { - this(from, to, /* result= */ true); - } - - FakeIntentStarter(ComponentName from, ComponentName to, boolean result) { - mFromActivity = from; - mToActivity = to; - mResult = result; - } - - @Override - public boolean test(RemoteTransition transition) { - if (mResult) { - mLaunched = true; - mTransition = transition; - if (mTransition != null) { - TransitionInfo info = buildTransitionInfo(mFromActivity, mToActivity); - try { - transition - .getRemoteTransition() - .startAnimation( - new Binder(), - info, - new SurfaceControl.Transaction(), - new FakeFinishCallback()); - } catch (RemoteException e) { - - } - } - } - return mResult; - } - - @Nullable - public RemoteTransition getTransitionOfLastLaunch() { - return mTransition; - } - - public ComponentName getFromActivity() { - return mFromActivity; - } - - public ComponentName getToActivity() { - return mToActivity; - } - - public boolean hasLaunched() { - return mLaunched; - } - - public void reset() { - mTransition = null; - mLaunched = false; - } - } - - private static class FakeIOriginTransitions extends IOriginTransitions.Stub { - private final Map<RemoteTransition, RemoteTransition> mRecords = new ArrayMap<>(); - - @Override - public RemoteTransition makeOriginTransition( - RemoteTransition launchTransition, RemoteTransition returnTransition) { - mRecords.put(launchTransition, returnTransition); - return launchTransition; - } - - @Override - public void cancelOriginTransition(RemoteTransition originTransition) { - mRecords.remove(originTransition); - } - - public void runReturnTransition(RemoteTransition originTransition, TransitionInfo info) { - RemoteTransition transition = mRecords.remove(originTransition); - try { - transition - .getRemoteTransition() - .startAnimation( - new Binder(), - info, - new SurfaceControl.Transaction(), - new FakeFinishCallback()); - } catch (RemoteException e) { - - } - } - - public boolean hasPendingReturnTransitions() { - return !mRecords.isEmpty(); - } - } - - private static class FakeFinishCallback extends IRemoteTransitionFinishedCallback.Stub { - @Override - public void onTransitionFinished( - WindowContainerTransaction wct, SurfaceControl.Transaction sct) {} - } - - private static class FakeRemoteTransition extends IRemoteTransition.Stub { - private boolean mStarted; - - @Override - public void startAnimation( - IBinder token, - TransitionInfo info, - SurfaceControl.Transaction t, - IRemoteTransitionFinishedCallback finishCallback) - throws RemoteException { - mStarted = true; - finishCallback.onTransitionFinished(null, null); - } - - @Override - public void mergeAnimation( - IBinder transition, - TransitionInfo info, - SurfaceControl.Transaction t, - IBinder mergeTarget, - IRemoteTransitionFinishedCallback finishCallback) {} - - @Override - public void takeOverAnimation( - IBinder transition, - TransitionInfo info, - SurfaceControl.Transaction t, - IRemoteTransitionFinishedCallback finishCallback, - WindowAnimationState[] states) {} - - @Override - public void onTransitionConsumed(IBinder transition, boolean aborted) {} - - public boolean started() { - return mStarted; - } - - public void reset() { - mStarted = false; - } - } -} diff --git a/systemUI/anim/res/anim/launch_dialog_enter.xml b/systemUI/anim/res/anim/launch_dialog_enter.xml deleted file mode 100644 index c6b87d38f7..0000000000 --- a/systemUI/anim/res/anim/launch_dialog_enter.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - 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. ---> - -<!-- The enter animation of the host dialog is a translation of 0px that lasts 500ms so that the --> -<!-- host dialog is directly visible but the dim background still takes 500ms to fade in. --> -<translate xmlns:android="http://schemas.android.com/apk/res/android" - android:fromXDelta="0" - android:toXDelta="0" - android:duration="500" /> \ No newline at end of file diff --git a/systemUI/anim/res/anim/launch_dialog_exit.xml b/systemUI/anim/res/anim/launch_dialog_exit.xml deleted file mode 100644 index a0f441eaee..0000000000 --- a/systemUI/anim/res/anim/launch_dialog_exit.xml +++ /dev/null @@ -1,22 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - 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. ---> -<alpha - xmlns:android="http://schemas.android.com/apk/res/android" - android:interpolator="@android:interpolator/decelerate_cubic" - android:duration="150" - android:fromAlpha="1.0" - android:toAlpha="0.0" /> \ No newline at end of file diff --git a/systemUI/anim/res/values/ids.xml b/systemUI/anim/res/values/ids.xml deleted file mode 100644 index 1a224ac7c1..0000000000 --- a/systemUI/anim/res/values/ids.xml +++ /dev/null @@ -1,29 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2021 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<resources> - <!-- DialogTransitionAnimator --> - <item type="id" name="tag_dialog_background"/> - - <!-- ViewBoundsAnimator --> - <item type="id" name="tag_animator"/> - <item type="id" name="tag_alpha_animator"/> - <item type="id" name="tag_layout_listener"/> - <item type="id" name="tag_override_bottom"/> - <item type="id" name="tag_override_left"/> - <item type="id" name="tag_override_right"/> - <item type="id" name="tag_override_top"/> -</resources> \ No newline at end of file diff --git a/systemUI/anim/res/values/styles.xml b/systemUI/anim/res/values/styles.xml deleted file mode 100644 index 3019eeef23..0000000000 --- a/systemUI/anim/res/values/styles.xml +++ /dev/null @@ -1,24 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2021 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<resources> - <!-- An animation used by DialogTransitionAnimator to make a dialog appear instantly (to --> - <!-- animate in-window) and disappear by fading out (when the exit into view is disabled). --> - <style name="Animation.LaunchAnimation" parent="@android:style/Animation"> - <item name="android:windowEnterAnimation">@anim/launch_dialog_enter</item> - <item name="android:windowExitAnimation">@anim/launch_dialog_exit</item> - </style> -</resources> \ 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 deleted file mode 100644 index c14ee62081..0000000000 --- a/systemUI/anim/src/com/android/systemui/animation/ActivityTransitionAnimator.kt +++ /dev/null @@ -1,1370 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.animation - -import android.app.ActivityManager -import android.app.ActivityTaskManager -import android.app.PendingIntent -import android.app.TaskInfo -import android.app.WindowConfiguration -import android.content.ComponentName -import android.graphics.Color -import android.graphics.Matrix -import android.graphics.Rect -import android.graphics.RectF -import android.os.Binder -import android.os.Build -import android.os.Handler -import android.os.Looper -import android.os.RemoteException -import android.util.Log -import android.view.IRemoteAnimationFinishedCallback -import android.view.IRemoteAnimationRunner -import android.view.RemoteAnimationAdapter -import android.view.RemoteAnimationTarget -import android.view.SyncRtSurfaceTransactionApplier -import android.view.View -import android.view.ViewGroup -import android.view.WindowManager -import android.view.WindowManager.TRANSIT_CLOSE -import android.view.WindowManager.TRANSIT_OPEN -import android.view.WindowManager.TRANSIT_TO_BACK -import android.view.WindowManager.TRANSIT_TO_FRONT -import android.view.animation.PathInterpolator -import android.window.RemoteTransition -import android.window.TransitionFilter -import androidx.annotation.AnyThread -import androidx.annotation.BinderThread -import androidx.annotation.UiThread -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.translucentOccludingActivityFix -import com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary -import com.android.wm.shell.shared.IShellTransitions -import com.android.wm.shell.shared.ShellTransitions -import java.util.concurrent.Executor -import kotlin.math.roundToInt - -private const val TAG = "ActivityTransitionAnimator" - -/** - * A class that allows activities to be started in a seamless way from a view that is transforming - * nicely into the starting window. - */ -class ActivityTransitionAnimator -@JvmOverloads -constructor( - /** The executor that runs on the main thread. */ - private val mainExecutor: Executor, - - /** The object used to register ephemeral returns and long-lived transitions. */ - private val transitionRegister: TransitionRegister? = null, - - /** The animator used when animating a View into an app. */ - private val transitionAnimator: TransitionAnimator = defaultTransitionAnimator(mainExecutor), - - /** The animator used when animating a Dialog into an app. */ - // TODO(b/218989950): Remove this animator and instead set the duration of the dim fade out to - // TIMINGS.contentBeforeFadeOutDuration. - private val dialogToAppAnimator: TransitionAnimator = defaultDialogToAppAnimator(mainExecutor), - - /** - * Whether we should disable the WindowManager timeout. This should be set to true in tests - * only. - */ - // TODO(b/301385865): Remove this flag. - private val disableWmTimeout: Boolean = false, -) { - @JvmOverloads - constructor( - mainExecutor: Executor, - shellTransitions: ShellTransitions, - transitionAnimator: TransitionAnimator = defaultTransitionAnimator(mainExecutor), - dialogToAppAnimator: TransitionAnimator = defaultDialogToAppAnimator(mainExecutor), - disableWmTimeout: Boolean = false, - ) : this( - mainExecutor, - TransitionRegister.fromShellTransitions(shellTransitions), - transitionAnimator, - dialogToAppAnimator, - disableWmTimeout, - ) - - @JvmOverloads - constructor( - mainExecutor: Executor, - iShellTransitions: IShellTransitions, - transitionAnimator: TransitionAnimator = defaultTransitionAnimator(mainExecutor), - dialogToAppAnimator: TransitionAnimator = defaultDialogToAppAnimator(mainExecutor), - disableWmTimeout: Boolean = false, - ) : this( - mainExecutor, - TransitionRegister.fromIShellTransitions(iShellTransitions), - transitionAnimator, - dialogToAppAnimator, - disableWmTimeout, - ) - - companion object { - /** The timings when animating a View into an app. */ - @JvmField - val TIMINGS = - TransitionAnimator.Timings( - totalDuration = 500L, - contentBeforeFadeOutDelay = 0L, - contentBeforeFadeOutDuration = 150L, - contentAfterFadeInDelay = 150L, - contentAfterFadeInDuration = 183L - ) - - /** - * The timings when animating a Dialog into an app. We need to wait at least 200ms before - * showing the app (which is under the dialog window) so that the dialog window dim is fully - * faded out, to avoid flicker. - */ - val DIALOG_TIMINGS = - TIMINGS.copy(contentBeforeFadeOutDuration = 200L, contentAfterFadeInDelay = 200L) - - /** The interpolators when animating a View or a dialog into an app. */ - val INTERPOLATORS = - TransitionAnimator.Interpolators( - positionInterpolator = Interpolators.EMPHASIZED, - positionXInterpolator = Interpolators.EMPHASIZED_COMPLEMENT, - contentBeforeFadeOutInterpolator = Interpolators.LINEAR_OUT_SLOW_IN, - contentAfterFadeInInterpolator = PathInterpolator(0f, 0f, 0.6f, 1f) - ) - - // TODO(b/288507023): Remove this flag. - @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 - private const val ANIMATION_DURATION_NAV_FADE_OUT = 133L - private val ANIMATION_DELAY_NAV_FADE_IN = - TIMINGS.totalDuration - ANIMATION_DURATION_NAV_FADE_IN - - private val NAV_FADE_IN_INTERPOLATOR = Interpolators.STANDARD_DECELERATE - private val NAV_FADE_OUT_INTERPOLATOR = PathInterpolator(0.2f, 0f, 1f, 1f) - - /** The time we wait before timing out the remote animation after starting the intent. */ - private const val TRANSITION_TIMEOUT = 1_000L - - /** - * The time we wait before we Log.wtf because the remote animation was neither started or - * cancelled by WM. - */ - private const val LONG_TRANSITION_TIMEOUT = 5_000L - - private fun defaultTransitionAnimator(mainExecutor: Executor): TransitionAnimator { - return TransitionAnimator(mainExecutor, TIMINGS, INTERPOLATORS) - } - - private fun defaultDialogToAppAnimator(mainExecutor: Executor): TransitionAnimator { - return TransitionAnimator(mainExecutor, DIALOG_TIMINGS, INTERPOLATORS) - } - } - - /** - * The callback of this animator. This should be set before any call to - * [start(Pending)IntentWithAnimation]. - */ - var callback: Callback? = null - - /** The set of [Listener] that should be notified of any animation started by this animator. */ - private val listeners = LinkedHashSet<Listener>() - - /** Top-level listener that can be used to notify all registered [listeners]. */ - private val lifecycleListener = - object : Listener { - override fun onTransitionAnimationStart() { - listeners.forEach { it.onTransitionAnimationStart() } - } - - override fun onTransitionAnimationEnd() { - listeners.forEach { it.onTransitionAnimationEnd() } - } - - override fun onTransitionAnimationProgress(linearProgress: Float) { - listeners.forEach { it.onTransitionAnimationProgress(linearProgress) } - } - - override fun onTransitionAnimationCancelled() { - listeners.forEach { it.onTransitionAnimationCancelled() } - } - } - - /** Book-keeping for long-lived transitions that are currently registered. */ - private val longLivedTransitions = - HashMap<TransitionCookie, Pair<RemoteTransition, RemoteTransition>>() - - /** - * Start an intent and animate the opening window. The intent will be started by running - * [intentStarter], which should use the provided [RemoteAnimationAdapter] and return the launch - * result. [controller] is responsible from animating the view from which the intent was started - * in [Controller.onTransitionAnimationProgress]. No animation will start if there is no window - * opening. - * - * If [controller] is null or [animate] is false, then the intent will be started and no - * animation will run. - * - * If possible, you should pass the [packageName] of the intent that will be started so that - * trampoline activity launches will also be animated. - * - * If the device is currently locked, the user will have to unlock it before the intent is - * started unless [showOverLockscreen] is true. In that case, the activity will be started - * directly over the lockscreen. - * - * This method will throw any exception thrown by [intentStarter]. - */ - @JvmOverloads - fun startIntentWithAnimation( - controller: Controller?, - animate: Boolean = true, - packageName: String? = null, - showOverLockscreen: Boolean = false, - intentStarter: (RemoteAnimationAdapter?) -> Int - ) { - if (controller == null || !animate) { - Log.i(TAG, "Starting intent with no animation") - intentStarter(null) - controller?.callOnIntentStartedOnMainThread(willAnimate = false) - return - } - - val callback = - this.callback - ?: throw IllegalStateException( - "ActivityTransitionAnimator.callback must be set before using this animator" - ) - 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 - // keyguard with the animation - val animationAdapter = - if (!hideKeyguardWithAnimation) { - RemoteAnimationAdapter( - runner, - TIMINGS.totalDuration, - TIMINGS.totalDuration - 150 /* statusBarTransitionDelay */ - ) - } else { - null - } - - // Register the remote animation for the given package to also animate trampoline - // activity launches. - if (packageName != null && animationAdapter != null) { - try { - ActivityTaskManager.getService() - .registerRemoteAnimationForNextActivityStart( - packageName, - animationAdapter, - null /* launchCookie */ - ) - } catch (e: RemoteException) { - Log.w(TAG, "Unable to register the remote animation", e) - } - } - - if (animationAdapter != null && controller.transitionCookie != null) { - registerEphemeralReturnAnimation(controller, transitionRegister) - } - - val launchResult = intentStarter(animationAdapter) - - // Only animate if the app is not already on top and will be opened, unless we are on the - // keyguard. - val willAnimate = - launchResult == ActivityManager.START_TASK_TO_FRONT || - launchResult == ActivityManager.START_SUCCESS || - (launchResult == ActivityManager.START_DELIVERED_TO_TOP && - hideKeyguardWithAnimation) - - Log.i( - TAG, - "launchResult=$launchResult willAnimate=$willAnimate " + - "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) { - runnerDelegate.postTimeouts() - - // Hide the keyguard using the launch animation instead of the default unlock animation. - if (hideKeyguardWithAnimation) { - callback.hideKeyguardWithAnimation(runner) - } - } else { - // We need to make sure delegate references are dropped to avoid memory leaks. - runner.dispose() - } - } - - private fun Controller.callOnIntentStartedOnMainThread(willAnimate: Boolean) { - if (Looper.myLooper() != Looper.getMainLooper()) { - mainExecutor.execute { callOnIntentStartedOnMainThread(willAnimate) } - } else { - if (DEBUG_TRANSITION_ANIMATION) { - Log.d( - TAG, - "Calling controller.onIntentStarted(willAnimate=$willAnimate) " + - "[controller=$this]" - ) - } - this.onIntentStarted(willAnimate) - } - } - - /** - * Same as [startIntentWithAnimation] but allows [intentStarter] to throw a - * [PendingIntent.CanceledException] which must then be handled by the caller. This is useful - * for Java caller starting a [PendingIntent]. - * - * If possible, you should pass the [packageName] of the intent that will be started so that - * trampoline activity launches will also be animated. - */ - @Throws(PendingIntent.CanceledException::class) - @JvmOverloads - fun startPendingIntentWithAnimation( - controller: Controller?, - animate: Boolean = true, - packageName: String? = null, - showOverLockscreen: Boolean = false, - intentStarter: PendingIntentStarter - ) { - startIntentWithAnimation(controller, animate, packageName, showOverLockscreen) { - intentStarter.startPendingIntent(it) - } - } - - /** - * Uses [transitionRegister] to set up the return animation for the given [launchController]. - * - * De-registration is set up automatically once the return animation is run. - * - * TODO(b/339194555): automatically de-register when the launchable is detached. - */ - private fun registerEphemeralReturnAnimation( - launchController: Controller, - transitionRegister: TransitionRegister? - ) { - if (!returnAnimationFrameworkLibrary()) return - - var cleanUpRunnable: Runnable? = null - val returnRunner = - createRunner( - object : DelegateTransitionAnimatorController(launchController) { - override val isLaunching = false - - override fun onTransitionAnimationCancelled( - newKeyguardOccludedState: Boolean? - ) { - super.onTransitionAnimationCancelled(newKeyguardOccludedState) - cleanUp() - } - - override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) { - super.onTransitionAnimationEnd(isExpandingFullyAbove) - cleanUp() - } - - private fun cleanUp() { - cleanUpRunnable?.run() - } - } - ) - - // mTypeSet and mModes match back signals only, and not home. This is on purpose, because - // we only want ephemeral return animations triggered in these scenarios. - val filter = - TransitionFilter().apply { - mTypeSet = intArrayOf(TRANSIT_CLOSE, TRANSIT_TO_BACK) - mRequirements = - arrayOf( - TransitionFilter.Requirement().apply { - mLaunchCookie = launchController.transitionCookie - mModes = intArrayOf(TRANSIT_CLOSE, TRANSIT_TO_BACK) - } - ) - } - val transition = - RemoteTransition( - RemoteAnimationRunnerCompat.wrap(returnRunner), - "${launchController.transitionCookie}_returnTransition" - ) - - transitionRegister?.register(filter, transition) - cleanUpRunnable = Runnable { transitionRegister?.unregister(transition) } - } - - /** Add a [Listener] that can listen to transition animations. */ - fun addListener(listener: Listener) { - listeners.add(listener) - } - - /** Remove a [Listener]. */ - fun removeListener(listener: Listener) { - listeners.remove(listener) - } - - /** Create a new animation [Runner] controlled by [controller]. */ - @VisibleForTesting - fun createRunner(controller: Controller): Runner { - // Make sure we use the modified timings when animating a dialog into an app. - val transitionAnimator = - if (controller.isDialogLaunch) { - dialogToAppAnimator - } else { - transitionAnimator - } - - return Runner(controller, callback!!, transitionAnimator, lifecycleListener) - } - - interface PendingIntentStarter { - /** - * Start a pending intent using the provided [animationAdapter] and return the launch - * result. - */ - @Throws(PendingIntent.CanceledException::class) - fun startPendingIntent(animationAdapter: RemoteAnimationAdapter?): Int - } - - interface Callback { - /** Whether we are currently on the keyguard or not. */ - fun isOnKeyguard(): Boolean = false - - /** Hide the keyguard and animate using [runner]. */ - fun hideKeyguardWithAnimation(runner: IRemoteAnimationRunner) { - throw UnsupportedOperationException() - } - - /* Get the background color of [task]. */ - fun getBackgroundColor(task: TaskInfo): Int - } - - interface Listener { - /** Called when an activity transition animation started. */ - fun onTransitionAnimationStart() {} - - /** - * Called when an activity transition animation is finished. This will be called if and only - * if [onTransitionAnimationStart] was called earlier. - */ - fun onTransitionAnimationEnd() {} - - /** - * The animation was cancelled. Note that [onTransitionAnimationEnd] will still be called - * after this if the animation was already started, i.e. if [onTransitionAnimationStart] was - * called before the cancellation. - */ - fun onTransitionAnimationCancelled() {} - - /** Called when an activity transition animation made progress. */ - fun onTransitionAnimationProgress(linearProgress: Float) {} - } - - /** - * A controller that takes care of applying the animation to an expanding view. - * - * Note that all callbacks (onXXX methods) are all called on the main thread. - */ - interface Controller : TransitionAnimator.Controller { - companion object { - /** - * Return a [Controller] that will animate and expand [view] into the opening window. - * - * Important: The view must be attached to a [ViewGroup] when calling this function and - * during the animation. For safety, this method will return null when it is not. The - * view must also implement [LaunchableView], otherwise this method will throw. - * - * Note: The background of [view] should be a (rounded) rectangle so that it can be - * properly animated. - */ - @JvmOverloads - @JvmStatic - fun fromView( - view: View, - cujType: Int? = null, - cookie: TransitionCookie? = null, - component: ComponentName? = null, - returnCujType: Int? = null - ): Controller? { - // Make sure the View we launch from implements LaunchableView to avoid visibility - // issues. - if (view !is LaunchableView) { - throw IllegalArgumentException( - "An ActivityTransitionAnimator.Controller was created from a View that " + - "does not implement LaunchableView. This can lead to subtle bugs " + - "where the visibility of the View we are launching from is not what " + - "we expected." - ) - } - - if (view.parent !is ViewGroup) { - Log.e( - TAG, - "Skipping animation as view $view is not attached to a ViewGroup", - Exception() - ) - return null - } - - return GhostedViewTransitionAnimatorController( - view, - cujType, - cookie, - component, - returnCujType - ) - } - } - - /** - * Whether this controller is controlling a dialog launch. This will be used to adapt the - * timings, making sure we don't show the app until the dialog dim had the time to fade out. - */ - // TODO(b/218989950): Remove this. - val isDialogLaunch: Boolean - get() = false - - /** - * Whether the expandable controller by this [Controller] is below the window that is going - * to be animated. - * - * This should be `false` when animating an app from or to the shade or status bar, given - * that they are drawn above all apps. This is usually `true` when using this animator in a - * normal app or a launcher, that are drawn below the animating activity/window. - */ - val isBelowAnimatingWindow: Boolean - get() = false - - /** - * The cookie associated with the transition controlled by this [Controller]. - * - * This should be defined for all return [Controller] (when [isLaunching] is false) and for - * their associated launch [Controller]s. - * - * For the recommended format, see [TransitionCookie]. - */ - val transitionCookie: TransitionCookie? - get() = null - - /** - * The [ComponentName] of the activity whose window is tied to this [Controller]. - * - * This is used as a fallback when a cookie is defined but there is no match (e.g. when a - * matching activity was launched by a mean different from the launchable in this - * [Controller]), and should be defined for all long-lived registered [Controller]s. - */ - val component: ComponentName? - get() = null - - /** - * The intent was started. If [willAnimate] is false, nothing else will happen and the - * animation will not be started. - */ - fun onIntentStarted(willAnimate: Boolean) {} - - /** - * The animation was cancelled. Note that [onTransitionAnimationEnd] will still be called - * after this if the animation was already started, i.e. if [onTransitionAnimationStart] was - * called before the cancellation. - * - * If this transition animation affected the occlusion state of the keyguard, WM will - * provide us with [newKeyguardOccludedState] so that we can set the occluded state - * appropriately. - */ - fun onTransitionAnimationCancelled(newKeyguardOccludedState: Boolean? = null) {} - } - - /** - * Registers [controller] as a long-lived transition handler for launch and return animations. - * - * 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(controller: Controller) { - check(returnAnimationFrameworkLibrary()) { - "Long-lived registrations cannot be used when the returnAnimationFrameworkLibrary " + - "flag is disabled" - } - - if (transitionRegister == null) { - throw IllegalStateException( - "A RemoteTransitionRegister must be provided when creating this animator in " + - "order to use long-lived animations" - ) - } - - val cookie = - controller.transitionCookie - ?: throw IllegalStateException( - "A cookie must be defined in order to use long-lived animations" - ) - val component = - controller.component - ?: throw IllegalStateException( - "A component must be defined in order to use long-lived animations" - ) - - // Make sure that any previous registrations linked to the same cookie are gone. - unregister(cookie) - - val launchFilter = - TransitionFilter().apply { - mRequirements = - arrayOf( - TransitionFilter.Requirement().apply { - mActivityType = WindowConfiguration.ACTIVITY_TYPE_STANDARD - mModes = intArrayOf(TRANSIT_OPEN, TRANSIT_TO_FRONT) - mTopActivity = component - } - ) - } - val launchRemoteTransition = - RemoteTransition( - RemoteAnimationRunnerCompat.wrap(createRunner(controller)), - "${cookie}_launchTransition" - ) - transitionRegister.register(launchFilter, launchRemoteTransition) - - val returnController = - object : Controller by controller { - override val isLaunching: Boolean = false - } - val returnFilter = - TransitionFilter().apply { - mRequirements = - arrayOf( - TransitionFilter.Requirement().apply { - mActivityType = WindowConfiguration.ACTIVITY_TYPE_STANDARD - mModes = intArrayOf(TRANSIT_CLOSE, TRANSIT_TO_BACK) - mTopActivity = component - } - ) - } - val returnRemoteTransition = - RemoteTransition( - RemoteAnimationRunnerCompat.wrap(createRunner(returnController)), - "${cookie}_returnTransition" - ) - transitionRegister.register(returnFilter, returnRemoteTransition) - - longLivedTransitions[cookie] = Pair(launchRemoteTransition, returnRemoteTransition) - } - - /** Unregisters all controllers previously registered that contain [cookie]. */ - fun unregister(cookie: TransitionCookie) { - val transitions = longLivedTransitions[cookie] ?: return - transitionRegister?.unregister(transitions.first) - transitionRegister?.unregister(transitions.second) - longLivedTransitions.remove(cookie) - } - - /** - * Invokes [onAnimationComplete] when animation is either cancelled or completed. Delegates all - * events to the passed [delegate]. - */ - @VisibleForTesting - inner class DelegatingAnimationCompletionListener( - private val delegate: Listener?, - private val onAnimationComplete: () -> Unit - ) : Listener { - var cancelled = false - - override fun onTransitionAnimationStart() { - delegate?.onTransitionAnimationStart() - } - - override fun onTransitionAnimationProgress(linearProgress: Float) { - delegate?.onTransitionAnimationProgress(linearProgress) - } - - override fun onTransitionAnimationEnd() { - delegate?.onTransitionAnimationEnd() - if (!cancelled) { - onAnimationComplete.invoke() - } - } - - override fun onTransitionAnimationCancelled() { - cancelled = true - delegate?.onTransitionAnimationCancelled() - onAnimationComplete.invoke() - } - } - - @VisibleForTesting - inner class Runner( - controller: Controller, - callback: Callback, - /** The animator to use to animate the window transition. */ - transitionAnimator: TransitionAnimator, - /** Listener for animation lifecycle events. */ - listener: Listener? = null - ) : IRemoteAnimationRunner.Stub() { - // 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 { - delegate = - AnimationDelegate( - mainExecutor, - controller, - callback, - DelegatingAnimationCompletionListener(listener, this::dispose), - transitionAnimator, - disableWmTimeout, - ) - } - - @BinderThread - override fun onAnimationStart( - transit: Int, - apps: Array<out RemoteAnimationTarget>?, - wallpapers: Array<out RemoteAnimationTarget>?, - nonApps: Array<out RemoteAnimationTarget>?, - 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. - mainExecutor.execute { delegate = null } - } - } - - class AnimationDelegate - @JvmOverloads - constructor( - private val mainExecutor: Executor, - private val controller: Controller, - private val callback: Callback, - /** Listener for animation lifecycle events. */ - private val listener: Listener? = null, - /** The animator to use to animate the window transition. */ - private val transitionAnimator: TransitionAnimator = - defaultTransitionAnimator(mainExecutor), - - /** - * Whether we should disable the WindowManager timeout. This should be set to true in tests - * only. - */ - // TODO(b/301385865): Remove this flag. - disableWmTimeout: Boolean = false, - ) : RemoteAnimationDelegate<IRemoteAnimationFinishedCallback> { - private val transitionContainer = controller.transitionContainer - private val context = transitionContainer.context - private val transactionApplierView = - controller.openingWindowSyncView ?: controller.transitionContainer - private val transactionApplier = SyncRtSurfaceTransactionApplier(transactionApplierView) - private val timeoutHandler = - if (!disableWmTimeout) { - Handler(Looper.getMainLooper()) - } else { - null - } - - private val matrix = Matrix() - private val invertMatrix = Matrix() - private var windowCrop = Rect() - private var windowCropF = RectF() - private var timedOut = false - private var cancelled = false - private var animation: TransitionAnimator.Animation? = null - - /** - * 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. - * - * Note that this is important to keep this a Runnable (and not a Kotlin lambda), otherwise - * it will be automatically converted when posted and we wouldn't be able to remove it after - * posting it. - */ - private var onTimeout = Runnable { onAnimationTimedOut() } - - /** - * A long timeout to Log.wtf (signaling a bug in WM) when the remote animation wasn't - * started or cancelled within [LONG_TRANSITION_TIMEOUT] milliseconds after the intent was - * started. - */ - private var onLongTimeout = Runnable { - Log.wtf( - TAG, - "The remote animation was neither cancelled or started within " + - "$LONG_TRANSITION_TIMEOUT" - ) - } - - init { - // We do this check here to cover all entry points, including Launcher which doesn't - // call startIntentWithAnimation() - if (!controller.isLaunching) TransitionAnimator.checkReturnAnimationFrameworkFlag() - } - - @UiThread - internal fun postTimeouts() { - if (timeoutHandler != null) { - timeoutHandler.postDelayed(onTimeout, TRANSITION_TIMEOUT) - timeoutHandler.postDelayed(onLongTimeout, LONG_TRANSITION_TIMEOUT) - } - } - - private fun removeTimeouts() { - if (timeoutHandler != null) { - timeoutHandler.removeCallbacks(onTimeout) - timeoutHandler.removeCallbacks(onLongTimeout) - } - } - - @UiThread - override fun onAnimationStart( - @WindowManager.TransitionOldType transit: Int, - apps: Array<out RemoteAnimationTarget>?, - wallpapers: Array<out RemoteAnimationTarget>?, - nonApps: Array<out RemoteAnimationTarget>?, - callback: IRemoteAnimationFinishedCallback? - ) { - removeTimeouts() - - // The animation was started too late and we already notified the controller that it - // timed out. - if (timedOut) { - callback?.invoke() - 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 - } - - val window = findTargetWindowIfPossible(apps) - if (window == null) { - Log.i(TAG, "Aborting the animation as no window is opening") - callback?.invoke() - - if (DEBUG_TRANSITION_ANIMATION) { - Log.d( - TAG, - "Calling controller.onTransitionAnimationCancelled() [no window opening]" - ) - } - controller.onTransitionAnimationCancelled() - listener?.onTransitionAnimationCancelled() - return - } - - val navigationBar = - nonApps?.firstOrNull { - it.windowType == WindowManager.LayoutParams.TYPE_NAVIGATION_BAR - } - - startAnimation(window, navigationBar, callback) - } - - private fun findTargetWindowIfPossible( - apps: Array<out RemoteAnimationTarget>? - ): RemoteAnimationTarget? { - if (apps == null) { - return null - } - - val targetMode = - if (controller.isLaunching) { - RemoteAnimationTarget.MODE_OPENING - } else { - RemoteAnimationTarget.MODE_CLOSING - } - var candidate: RemoteAnimationTarget? = null - - for (it in apps) { - if (it.mode == targetMode) { - if (activityTransitionUseLargestWindow()) { - 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. - if ( - controller.transitionCookie != null && - it.taskInfo - ?.launchCookies - ?.contains(controller.transitionCookie) != true && - (controller.component == null || - it.taskInfo?.topActivity != controller.component) - ) { - continue - } - } - - if ( - candidate == null || - !it.hasAnimatingParent && candidate.hasAnimatingParent - ) { - candidate = it - continue - } - if ( - !it.hasAnimatingParent && - it.screenSpaceBounds.hasGreaterAreaThan(candidate.screenSpaceBounds) - ) { - candidate = it - } - } else { - if (!it.hasAnimatingParent) { - return it - } - if (candidate == null) { - candidate = it - } - } - } - } - - return candidate - } - - private fun startAnimation( - window: RemoteAnimationTarget, - navigationBar: RemoteAnimationTarget?, - iCallback: IRemoteAnimationFinishedCallback? - ) { - if (TransitionAnimator.DEBUG) { - Log.d(TAG, "Remote animation started") - } - - val windowBounds = window.screenSpaceBounds - val endState = - if (controller.isLaunching) { - TransitionAnimator.State( - top = windowBounds.top, - bottom = windowBounds.bottom, - left = windowBounds.left, - right = windowBounds.right - ) - } else { - controller.createAnimatorState() - } - val windowBackgroundColor = - if (translucentOccludingActivityFix() && window.isTranslucent) { - Color.TRANSPARENT - } else { - window.taskInfo?.let { callback.getBackgroundColor(it) } - ?: 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) - 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]. - val delegate = this.controller - val controller = - object : Controller by delegate { - override fun createAnimatorState(): TransitionAnimator.State { - if (isLaunching) return delegate.createAnimatorState() - val windowRadius = getWindowRadius(isExpandingFullyAbove) - return TransitionAnimator.State( - top = windowBounds.top, - bottom = windowBounds.bottom, - left = windowBounds.left, - right = windowBounds.right, - topCornerRadius = windowRadius, - bottomCornerRadius = windowRadius - ) - } - - override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) { - listener?.onTransitionAnimationStart() - - if (DEBUG_TRANSITION_ANIMATION) { - Log.d( - TAG, - "Calling controller.onTransitionAnimationStart(" + - "isExpandingFullyAbove=$isExpandingFullyAbove) " + - "[controller=$delegate]" - ) - } - delegate.onTransitionAnimationStart(isExpandingFullyAbove) - } - - override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) { - listener?.onTransitionAnimationEnd() - iCallback?.invoke() - - if (DEBUG_TRANSITION_ANIMATION) { - Log.d( - TAG, - "Calling controller.onTransitionAnimationEnd(" + - "isExpandingFullyAbove=$isExpandingFullyAbove) " + - "[controller=$delegate]" - ) - } - delegate.onTransitionAnimationEnd(isExpandingFullyAbove) - } - - override fun onTransitionAnimationProgress( - state: TransitionAnimator.State, - progress: Float, - linearProgress: Float - ) { - applyStateToWindow(window, state, linearProgress) - navigationBar?.let { applyStateToNavigationBar(it, state, linearProgress) } - - listener?.onTransitionAnimationProgress(linearProgress) - delegate.onTransitionAnimationProgress(state, progress, linearProgress) - } - } - - animation = - transitionAnimator.startAnimation( - controller, - endState, - windowBackgroundColor, - fadeWindowBackgroundLayer = !controller.isBelowAnimatingWindow, - drawHole = !controller.isBelowAnimatingWindow, - ) - } - - private fun getWindowRadius(isExpandingFullyAbove: Boolean): Float { - return if (isExpandingFullyAbove) { - // Most of the time, expanding fully above the root view means - // expanding in full screen. - ScreenDecorationsUtils.getWindowCornerRadius(context) - } else { - // This usually means we are in split screen mode, so 2 out of 4 - // corners will have a radius of 0. - 0f - } - } - - private fun applyStateToWindow( - window: RemoteAnimationTarget, - state: TransitionAnimator.State, - linearProgress: Float, - ) { - 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. - return - } - - val screenBounds = window.screenSpaceBounds - val centerX = (screenBounds.left + screenBounds.right) / 2f - val centerY = (screenBounds.top + screenBounds.bottom) / 2f - val width = screenBounds.right - screenBounds.left - val height = screenBounds.bottom - screenBounds.top - - // Scale the window. We use the max of (widthRatio, heightRatio) so that there is no - // blank space on any side. - val widthRatio = state.width.toFloat() / width - val heightRatio = state.height.toFloat() / height - val scale = maxOf(widthRatio, heightRatio) - matrix.reset() - matrix.setScale(scale, scale, centerX, centerY) - - // Align it to the top and center it in the x-axis. - val heightChange = height * scale - height - val translationX = state.centerX - centerX - val translationY = state.top - screenBounds.top + heightChange / 2f - matrix.postTranslate(translationX, translationY) - - // Crop it. The matrix will also be applied to the crop, so we apply the inverse - // operation. Given that we only scale (by factor > 0) then translate, we can assume - // that the matrix is invertible. - val cropX = state.left.toFloat() - screenBounds.left - val cropY = state.top.toFloat() - screenBounds.top - windowCropF.set(cropX, cropY, cropX + state.width, cropY + state.height) - matrix.invert(invertMatrix) - invertMatrix.mapRect(windowCropF) - windowCrop.set( - windowCropF.left.roundToInt(), - windowCropF.top.roundToInt(), - windowCropF.right.roundToInt(), - windowCropF.bottom.roundToInt() - ) - - val windowAnimationDelay = - if (controller.isLaunching) { - TIMINGS.contentAfterFadeInDelay - } else { - TIMINGS.contentBeforeFadeOutDelay - } - val windowAnimationDuration = - if (controller.isLaunching) { - TIMINGS.contentAfterFadeInDuration - } else { - TIMINGS.contentBeforeFadeOutDuration - } - 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 (controller.isBelowAnimatingWindow) { - if (controller.isLaunching) { - INTERPOLATORS.contentAfterFadeInInterpolator.getInterpolation( - windowProgress - ) - } else { - 1 - - INTERPOLATORS.contentBeforeFadeOutInterpolator.getInterpolation( - windowProgress - ) - } - } else { - 1f - } - - // The scale will also be applied to the corner radius, so we divide by the scale to - // keep the original radius. We use the max of (topCornerRadius, bottomCornerRadius) to - // make sure that the window does not draw itself behind the expanding view. This is - // 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) - .withMatrix(matrix) - .withWindowCrop(windowCrop) - .withCornerRadius(cornerRadius) - .withVisibility(true) - .build() - - transactionApplier.scheduleApply(params) - } - - private fun applyStateToNavigationBar( - navigationBar: RemoteAnimationTarget, - state: TransitionAnimator.State, - linearProgress: Float - ) { - 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. - return - } - - val fadeInProgress = - TransitionAnimator.getProgress( - TIMINGS, - linearProgress, - ANIMATION_DELAY_NAV_FADE_IN, - ANIMATION_DURATION_NAV_FADE_OUT - ) - - val params = SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(navigationBar.leash) - if (fadeInProgress > 0) { - matrix.reset() - matrix.setTranslate( - 0f, - (state.top - navigationBar.sourceContainerBounds.top).toFloat() - ) - windowCrop.set(state.left, 0, state.right, state.height) - params - .withAlpha(NAV_FADE_IN_INTERPOLATOR.getInterpolation(fadeInProgress)) - .withMatrix(matrix) - .withWindowCrop(windowCrop) - .withVisibility(true) - } else { - val fadeOutProgress = - TransitionAnimator.getProgress( - TIMINGS, - linearProgress, - 0, - ANIMATION_DURATION_NAV_FADE_OUT - ) - params.withAlpha(1f - NAV_FADE_OUT_INTERPOLATOR.getInterpolation(fadeOutProgress)) - } - - transactionApplier.scheduleApply(params.build()) - } - - private fun onAnimationTimedOut() { - // The remote animation was cancelled by WM, so we already cancelled the transition - // animation. - if (cancelled) { - return - } - - Log.w(TAG, "Remote animation timed out") - timedOut = true - - if (DEBUG_TRANSITION_ANIMATION) { - Log.d( - TAG, - "Calling controller.onTransitionAnimationCancelled() [animation timed out]" - ) - } - controller.onTransitionAnimationCancelled() - listener?.onTransitionAnimationCancelled() - } - - @UiThread - override fun onAnimationCancelled() { - removeTimeouts() - - // The short timeout happened, so we already cancelled the transition animation. - if (timedOut) { - return - } - - Log.i(TAG, "Remote animation was cancelled") - cancelled = true - - animation?.cancel() - - if (DEBUG_TRANSITION_ANIMATION) { - Log.d( - TAG, - "Calling controller.onTransitionAnimationCancelled() [remote animation " + - "cancelled]", - ) - } - controller.onTransitionAnimationCancelled() - listener?.onTransitionAnimationCancelled() - } - - private fun IRemoteAnimationFinishedCallback.invoke() { - try { - onAnimationFinished() - } catch (e: RemoteException) { - e.printStackTrace() - } - } - - private fun Rect.hasGreaterAreaThan(other: Rect): Boolean { - return (this.width() * this.height()) > (other.width() * other.height()) - } - } - - /** - * Wraps one of the two methods we have to register remote transitions with WM Shell: - * - for in-process registrations (e.g. System UI) we use [ShellTransitions] - * - for cross-process registrations (e.g. Launcher) we use [IShellTransitions] - * - * Important: each instance of this class must wrap exactly one of the two. - */ - class TransitionRegister - private constructor( - private val shellTransitions: ShellTransitions? = null, - private val iShellTransitions: IShellTransitions? = null, - ) { - init { - assert((shellTransitions != null).xor(iShellTransitions != null)) - } - - companion object { - /** Provides a [TransitionRegister] instance wrapping [ShellTransitions]. */ - fun fromShellTransitions(shellTransitions: ShellTransitions): TransitionRegister { - return TransitionRegister(shellTransitions = shellTransitions) - } - - /** Provides a [TransitionRegister] instance wrapping [IShellTransitions]. */ - fun fromIShellTransitions(iShellTransitions: IShellTransitions): TransitionRegister { - return TransitionRegister(iShellTransitions = iShellTransitions) - } - } - - /** Register [remoteTransition] with WM Shell using the given [filter]. */ - internal fun register( - filter: TransitionFilter, - remoteTransition: RemoteTransition, - ) { - shellTransitions?.registerRemote(filter, remoteTransition) - iShellTransitions?.registerRemote(filter, remoteTransition) - } - - /** Unregister [remoteTransition] from WM Shell. */ - internal fun unregister(remoteTransition: RemoteTransition) { - shellTransitions?.unregisterRemote(remoteTransition) - iShellTransitions?.unregisterRemote(remoteTransition) - } - } - - /** - * A cookie used to uniquely identify a task launched using an - * [ActivityTransitionAnimator.Controller]. - * - * The [String] encapsulated by this class should be formatted in such a way to be unique across - * the system, but reliably constant for the same associated launchable. - * - * Recommended naming scheme: - * - DO use the fully qualified name of the class that owns the instance of the launchable, - * along with a concise and precise description of the purpose of the launchable in question. - * - DO NOT introduce uniqueness through the use of timestamps or other runtime variables that - * will change if the instance is destroyed and re-created. - * - * Example: "com.not.the.real.class.name.ShadeController_openSettingsButton" - * - * Note that sometimes (e.g. in recycler views) there could be multiple instances of the same - * launchable, and no static knowledge to adequately differentiate between them using a single - * description. In this case, the recommendation is to append a unique identifier related to the - * contents of the launchable. - * - * Example: “com.not.the.real.class.name.ToastWebResult_launchAga_id143256” - */ - data class TransitionCookie(private val cookie: String) : Binder() -} diff --git a/systemUI/anim/src/com/android/systemui/animation/AnimationFeatureFlags.kt b/systemUI/anim/src/com/android/systemui/animation/AnimationFeatureFlags.kt deleted file mode 100644 index 1c9dabbb0e..0000000000 --- a/systemUI/anim/src/com/android/systemui/animation/AnimationFeatureFlags.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.android.systemui.animation - -interface AnimationFeatureFlags { - val isPredictiveBackQsDialogAnim: Boolean - get() = false -} diff --git a/systemUI/anim/src/com/android/systemui/animation/ComposableControllerFactory.kt b/systemUI/anim/src/com/android/systemui/animation/ComposableControllerFactory.kt deleted file mode 100644 index c842159af8..0000000000 --- a/systemUI/anim/src/com/android/systemui/animation/ComposableControllerFactory.kt +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2025 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS 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 android.content.ComponentName -import android.util.Log -import com.android.systemui.animation.ActivityTransitionAnimator.Controller -import com.android.systemui.animation.ActivityTransitionAnimator.ControllerFactory -import kotlinx.coroutines.flow.MutableStateFlow - -private const val TAG = "ComposableControllerFactory" - -/** - * [ControllerFactory] extension for Compose. Since composables are not guaranteed to be part of the - * composition when [ControllerFactory.createController] is called, this class provides a way for - * the composable to register itself at the time of composition, and deregister itself when - * disposed. - */ -abstract class ComposableControllerFactory( - cookie: ActivityTransitionAnimator.TransitionCookie, - component: ComponentName?, - launchCujType: Int? = null, - returnCujType: Int? = null, -) : ControllerFactory(cookie, component, launchCujType, returnCujType) { - /** - * The object to be used to create [Controller]s, when its associate composable is in the - * composition. - */ - protected val expandable = MutableStateFlow<Expandable?>(null) - - /** To be called when the composable to be animated enters composition. */ - fun onCompose(expandable: Expandable) { - if (TransitionAnimator.DEBUG) { - Log.d(TAG, "Composable entered composition (expandable=$expandable") - } - this.expandable.value = expandable - } - - /** To be called when the composable to be animated exits composition. */ - fun onDispose() { - if (TransitionAnimator.DEBUG) { - Log.d(TAG, "Composable left composition (expandable=${this.expandable.value}") - } - this.expandable.value = null - } -} diff --git a/systemUI/anim/src/com/android/systemui/animation/DelegateTransitionAnimatorController.kt b/systemUI/anim/src/com/android/systemui/animation/DelegateTransitionAnimatorController.kt deleted file mode 100644 index e246562761..0000000000 --- a/systemUI/anim/src/com/android/systemui/animation/DelegateTransitionAnimatorController.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.animation - -/** - * A base class to easily create an implementation of [ActivityTransitionAnimator.Controller] which - * delegates most of its call to [delegate]. This is mostly useful for Java code which can't easily - * create such a delegated class. - */ -open class DelegateTransitionAnimatorController( - protected val delegate: ActivityTransitionAnimator.Controller -) : ActivityTransitionAnimator.Controller by delegate diff --git a/systemUI/anim/src/com/android/systemui/animation/DialogTransitionAnimator.kt b/systemUI/anim/src/com/android/systemui/animation/DialogTransitionAnimator.kt deleted file mode 100644 index e9ec614c7c..0000000000 --- a/systemUI/anim/src/com/android/systemui/animation/DialogTransitionAnimator.kt +++ /dev/null @@ -1,1103 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.animation - -import android.animation.Animator -import android.animation.AnimatorListenerAdapter -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 -import android.view.View -import android.view.ViewGroup -import android.view.ViewGroup.LayoutParams.MATCH_PARENT -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.util.maybeForceFullscreen -import com.android.systemui.util.registerAnimationOnBackInvoked -import java.util.concurrent.Executor -import kotlin.math.roundToInt - -private const val TAG = "DialogTransitionAnimator" - -/** - * A class that allows dialogs to be started in a seamless way from a view that is transforming - * nicely into the starting dialog. - * - * This animator also allows to easily animate a dialog into an activity. - * - * @see show - * @see showFromView - * @see showFromDialog - * @see createActivityTransitionController - */ -class DialogTransitionAnimator -@JvmOverloads -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, - ), - private val isForTesting: Boolean = false, -) { - private companion object { - private val TIMINGS = ActivityTransitionAnimator.TIMINGS - - // We use the same interpolator for X and Y axis to make sure the dialog does not move out - // of the screen bounds during the animation. - private val INTERPOLATORS = - ActivityTransitionAnimator.INTERPOLATORS.copy( - positionXInterpolator = - ActivityTransitionAnimator.INTERPOLATORS.positionInterpolator - ) - } - - /** - * A controller that takes care of applying the dialog launch and exit animations to the source - * that triggered the animation. - */ - interface Controller { - /** The [ViewRootImpl] of this controller. */ - val viewRoot: ViewRootImpl? - - /** - * The identity object of the source animated by this controller. This animator will ensure - * that 2 animations with the same source identity are not going to run at the same time, to - * avoid flickers when a dialog is shown from the same source more or less at the same time - * (for instance if the user clicks an expandable button twice). - */ - val sourceIdentity: Any - - /** The CUJ associated to this controller. */ - val cuj: DialogCuj? - - /** - * Move the drawing of the source in the overlay of [viewGroup]. - * - * Once this method is called, and until [stopDrawingInOverlay] is called, the source - * controlled by this Controller should be drawn in the overlay of [viewGroup] so that it is - * drawn above all other elements in the same [viewRoot]. - */ - fun startDrawingInOverlayOf(viewGroup: ViewGroup) - - /** - * Move the drawing of the source back in its original location. - * - * @see startDrawingInOverlayOf - */ - fun stopDrawingInOverlay() - - /** - * Create the [TransitionAnimator.Controller] that will be called to animate the source - * controlled by this [Controller] during the dialog launch animation. - * - * At the end of this animation, the source should *not* be visible anymore (until the - * dialog is closed and is animated back into the source). - */ - fun createTransitionController(): TransitionAnimator.Controller - - /** - * Create the [TransitionAnimator.Controller] that will be called to animate the source - * controlled by this [Controller] during the dialog exit animation. - * - * At the end of this animation, the source should be visible again. - */ - fun createExitController(): TransitionAnimator.Controller - - /** - * Whether we should animate the dialog back into the source when it is dismissed. If this - * methods returns `false`, then the dialog will simply fade out and - * [onExitAnimationCancelled] will be called. - * - * Note that even when this returns `true`, the exit animation might still be cancelled (in - * which case [onExitAnimationCancelled] will also be called). - */ - fun shouldAnimateExit(): Boolean - - /** - * Called if we decided to *not* animate the dialog into the source for some reason. This - * means that [createExitController] will *not* be called and this implementation should - * make sure that the source is back in its original state, before it was animated into the - * dialog. In particular, the source should be visible again. - */ - fun onExitAnimationCancelled() - - /** - * Return the [InteractionJankMonitor.Configuration.Builder] to be used for animations - * controlled by this controller. - */ - // TODO(b/252723237): Make this non-nullable - fun jankConfigurationBuilder(): InteractionJankMonitor.Configuration.Builder? - - companion object { - /** - * Create a [Controller] that can animate [source] to and from a dialog. - * - * Important: The view must be attached to a [ViewGroup] when calling this function and - * during the animation. For safety, this method will return null when it is not. The - * view must also implement [LaunchableView], otherwise this method will throw. - * - * Note: The background of [view] should be a (rounded) rectangle so that it can be - * properly animated. - */ - fun fromView(source: View, cuj: DialogCuj? = null): Controller? { - // Make sure the View we launch from implements LaunchableView to avoid visibility - // issues. - if (source !is LaunchableView) { - throw IllegalArgumentException( - "A DialogTransitionAnimator.Controller was created from a View that does " + - "not implement LaunchableView. This can lead to subtle bugs where " + - "the visibility of the View we are launching from is not what we " + - "expected." - ) - } - - if (source.parent !is ViewGroup) { - Log.e( - TAG, - "Skipping animation as view $source is not attached to a ViewGroup", - Exception(), - ) - return null - } - - return ViewDialogTransitionAnimatorController(source, cuj) - } - } - } - - /** - * The set of dialogs that were animated using this animator and that are still opened (not - * dismissed, but can be hidden). - */ - // TODO(b/201264644): Remove this set. - private val openedDialogs = hashSetOf<AnimatedDialog>() - - /** - * Show [dialog] by expanding it from [view]. If [view] is a view inside another dialog that was - * shown using this method, then we will animate from that dialog instead. - * - * If [animateBackgroundBoundsChange] is true, then the background of the dialog will be - * animated when the dialog bounds change. - * - * Note: The background of [view] should be a (rounded) rectangle so that it can be properly - * animated. - * - * Caveats: When calling this function and [dialog] is not a fullscreen dialog, then it will be - * made fullscreen and 2 views will be inserted between the dialog DecorView and its children. - */ - @JvmOverloads - fun showFromView( - dialog: Dialog, - view: View, - cuj: DialogCuj? = null, - animateBackgroundBoundsChange: Boolean = false - ) { - val controller = Controller.fromView(view, cuj) - if (controller == null) { - dialog.show() - } else { - show(dialog, controller, animateBackgroundBoundsChange) - } - } - - /** - * Show [dialog] by expanding it from a source controlled by [controller]. - * - * If [animateBackgroundBoundsChange] is true, then the background of the dialog will be - * animated when the dialog bounds change. - * - * Note: The background of [view] should be a (rounded) rectangle so that it can be properly - * animated. - * - * Caveats: When calling this function and [dialog] is not a fullscreen dialog, then it will be - * made fullscreen and 2 views will be inserted between the dialog DecorView and its children. - */ - @JvmOverloads - fun show( - dialog: Dialog, - controller: Controller, - animateBackgroundBoundsChange: Boolean = false - ) { - if (Looper.myLooper() != Looper.getMainLooper()) { - throw IllegalStateException( - "showFromView must be called from the main thread and dialog must be created in " + - "the main thread" - ) - } - - // If the view we are launching from belongs to another dialog, then this means the caller - // intent is to launch a dialog from another dialog. - val animatedParent = - openedDialogs.firstOrNull { - it.dialog.window?.decorView?.viewRootImpl == controller.viewRoot - } - val controller = - animatedParent?.dialogContentWithBackground?.let { - Controller.fromView(it, controller.cuj) - } - ?: 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.show() - return - } - - val animatedDialog = - AnimatedDialog( - transitionAnimator = transitionAnimator, - callback = callback, - interactionJankMonitor = interactionJankMonitor, - controller = controller, - onDialogDismissed = { openedDialogs.remove(it) }, - dialog = dialog, - animateBackgroundBoundsChange = animateBackgroundBoundsChange, - parentAnimatedDialog = animatedParent, - forceDisableSynchronization = isForTesting, - featureFlags = featureFlags, - ) - - openedDialogs.add(animatedDialog) - animatedDialog.start() - } - - /** - * Launch [dialog] from [another dialog][animateFrom] that was shown using [show]. This will - * allow for dismissing the whole stack. - * - * @see dismissStack - */ - fun showFromDialog( - dialog: Dialog, - animateFrom: Dialog, - cuj: DialogCuj? = null, - animateBackgroundBoundsChange: Boolean = false - ) { - val view = - openedDialogs.firstOrNull { it.dialog == animateFrom }?.dialogContentWithBackground - if (view == null) { - Log.w( - TAG, - "Showing dialog $dialog normally as the dialog it is shown from was not shown " + - "using DialogTransitionAnimator" - ) - dialog.show() - return - } - - showFromView( - dialog, - view, - animateBackgroundBoundsChange = animateBackgroundBoundsChange, - cuj = cuj - ) - } - - /** - * Create an [ActivityTransitionAnimator.Controller] that can be used to launch an activity from - * the dialog that contains [View]. Note that the dialog must have been shown using this - * animator, otherwise this method will return null. - * - * The returned controller will take care of dismissing the dialog at the right time after the - * activity started, when the dialog to app animation is done (or when it is cancelled). If this - * method returns null, then the dialog won't be dismissed. - * - * @param view any view inside the dialog to animate. - */ - @JvmOverloads - fun createActivityTransitionController( - view: View, - cujType: Int? = null, - ): ActivityTransitionAnimator.Controller? { - val animatedDialog = - openedDialogs.firstOrNull { - it.dialog.window?.decorView?.viewRootImpl == view.viewRootImpl - } - ?: return null - return createActivityTransitionController(animatedDialog, cujType) - } - - /** - * Create an [ActivityTransitionAnimator.Controller] that can be used to launch an activity from - * [dialog]. Note that the dialog must have been shown using this animator, otherwise this - * method will return null. - * - * The returned controller will take care of dismissing the dialog at the right time after the - * activity started, when the dialog to app animation is done (or when it is cancelled). If this - * method returns null, then the dialog won't be dismissed. - * - * @param dialog the dialog to animate. - */ - @JvmOverloads - fun createActivityTransitionController( - dialog: Dialog, - cujType: Int? = null, - ): ActivityTransitionAnimator.Controller? { - val animatedDialog = openedDialogs.firstOrNull { it.dialog == dialog } ?: return null - return createActivityTransitionController(animatedDialog, cujType) - } - - private fun createActivityTransitionController( - animatedDialog: AnimatedDialog, - 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 - // run it anyways. - animatedDialog.exitAnimationDisabled = true - - val dialog = animatedDialog.dialog - - // Don't animate if the dialog is not showing or if we are locked and going to show the - // primary bouncer. - if ( - !dialog.isShowing || - (!callback.isUnlocked() && !callback.isShowingAlternateAuthOnUnlock()) - ) { - return null - } - - val dialogContentWithBackground = animatedDialog.dialogContentWithBackground ?: return null - val controller = - ActivityTransitionAnimator.Controller.fromView(dialogContentWithBackground, cujType) - ?: return null - - // Wrap the controller into one that will instantly dismiss the dialog when the animation is - // done or dismiss it normally (fading it out) if the animation is cancelled. - return object : ActivityTransitionAnimator.Controller by controller { - override val isDialogLaunch = true - - override fun onIntentStarted(willAnimate: Boolean) { - controller.onIntentStarted(willAnimate) - - if (!willAnimate) { - dialog.dismiss() - } - } - - override fun onTransitionAnimationCancelled(newKeyguardOccludedState: Boolean?) { - controller.onTransitionAnimationCancelled() - enableDialogDismiss() - dialog.dismiss() - } - - override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) { - controller.onTransitionAnimationStart(isExpandingFullyAbove) - - // Make sure the dialog is not dismissed during the animation. - disableDialogDismiss() - - // If this dialog was shown from a cascade of other dialogs, make sure those ones - // are dismissed too. - animatedDialog.prepareForStackDismiss() - - // Remove the dim. - dialog.window?.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND) - } - - override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) { - controller.onTransitionAnimationEnd(isExpandingFullyAbove) - - // Hide the dialog then dismiss it to instantly dismiss it without playing the - // animation. - dialog.hide() - enableDialogDismiss() - dialog.dismiss() - } - - private fun disableDialogDismiss() { - dialog.setDismissOverride { /* Do nothing */} - } - - private fun enableDialogDismiss() { - // We don't set the override to null given that [AnimatedDialog.OnDialogDismissed] - // will still properly dismiss the dialog but will also make sure to clean up - // everything (like making sure that the touched view that triggered the dialog is - // made VISIBLE again). - dialog.setDismissOverride(animatedDialog::onDialogDismissed) - } - } - } - - /** - * Ensure that all dialogs currently shown won't animate into their source when dismissed. - * - * This is a temporary API meant to be called right before we both dismiss a dialog and start an - * activity, which currently does not look good if we animate the dialog into their source at - * the same time as the activity starts. - * - * TODO(b/193634619): Remove this function and animate dialog into opening activity instead. - */ - fun disableAllCurrentDialogsExitAnimations() { - openedDialogs.forEach { it.exitAnimationDisabled = true } - } - - /** - * Dismiss [dialog]. If it was launched from another dialog using this animator, also dismiss - * the stack of dialogs and simply fade out [dialog]. - */ - fun dismissStack(dialog: Dialog) { - openedDialogs.firstOrNull { it.dialog == dialog }?.prepareForStackDismiss() - dialog.dismiss() - } - - interface Callback { - /** Whether the device is currently in dreaming (screensaver) mode. */ - fun isDreaming(): Boolean - - /** - * Whether the device is currently unlocked, i.e. if it is *not* on the keyguard or if the - * keyguard can be dismissed. - */ - fun isUnlocked(): Boolean - - /** - * Whether we are going to show alternate authentication (like UDFPS) instead of the - * traditional bouncer when unlocking the device. - */ - fun isShowingAlternateAuthOnUnlock(): Boolean - } -} - -/** - * The CUJ interaction associated with opening the dialog. - * - * The optional tag indicates the specific dialog being opened. - */ -data class DialogCuj(@CujType val cujType: Int, val tag: String? = null) - -private class AnimatedDialog( - private val transitionAnimator: TransitionAnimator, - private val callback: DialogTransitionAnimator.Callback, - private val interactionJankMonitor: InteractionJankMonitor, - - /** - * The controller of the source that triggered the dialog and that will animate into/from the - * dialog. - */ - val controller: DialogTransitionAnimator.Controller, - - /** - * A callback that will be called with this [AnimatedDialog] after the dialog was dismissed and - * the exit animation is done. - */ - private val onDialogDismissed: (AnimatedDialog) -> Unit, - - /** The dialog to show and animate. */ - val dialog: Dialog, - - /** Whether we should animate the dialog background when its bounds change. */ - animateBackgroundBoundsChange: Boolean, - - /** Launch animation corresponding to the parent [AnimatedDialog]. */ - private val parentAnimatedDialog: AnimatedDialog? = null, - - /** - * 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. - * - * Note that we access this DecorView lazily to avoid accessing it before the dialog is created, - * which can sometimes cause crashes (e.g. with the Cast dialog). - */ - private val decorView by lazy { dialog.window!!.decorView as ViewGroup } - - /** - * The dialog content with its background. When animating a fullscreen dialog, this is just the - * first ViewGroup of the dialog that has a background. When animating a normal (not fullscreen) - * dialog, this is an additional view that serves as a fake window that will have the same size - * as the dialog window initially had and to which we will set the dialog window background. - */ - var dialogContentWithBackground: ViewGroup? = null - - /** The background color of [dialog], taking into consideration its window background color. */ - private var originalDialogBackgroundColor = Color.BLACK - - /** - * Whether we are currently launching/showing the dialog by animating it from its source - * controlled by [controller]. - */ - private var isLaunching = true - - /** Whether we are currently dismissing/hiding the dialog by animating into its source. */ - private var isDismissing = false - - private var dismissRequested = false - var exitAnimationDisabled = false - - private var isSourceDrawnInDialog = false - private var isOriginalDialogViewLaidOut = false - - /** A layout listener to animate the dialog height change. */ - private val backgroundLayoutListener = - if (animateBackgroundBoundsChange) { - AnimatedBoundsLayoutListener() - } else { - null - } - - /* - * A layout listener in case the dialog (window) size changes (for instance because of a - * configuration change) to ensure that the dialog stays full width. - */ - private var decorViewLayoutListener: View.OnLayoutChangeListener? = null - - private var hasInstrumentedJank = false - - @RequiresApi(Build.VERSION_CODES.TIRAMISU) - fun start() { - val cuj = controller.cuj - if (cuj != null) { - val config = controller.jankConfigurationBuilder() - if (config != null) { - if (cuj.tag != null) { - config.setTag(cuj.tag) - } - - interactionJankMonitor.begin(config) - hasInstrumentedJank = true - } - } - - // Create the dialog so that its onCreate() method is called, which usually sets the dialog - // content. - dialog.create() - - val window = dialog.window!! - val isWindowFullScreen = - window.attributes.width == MATCH_PARENT && window.attributes.height == MATCH_PARENT - val dialogContentWithBackground = - if (isWindowFullScreen) { - // If the dialog window is already fullscreen, then we look for the first ViewGroup - // that has a background (and is not the DecorView, which always has a background) - // and animate towards that ViewGroup given that this is probably what represents - // the actual dialog view. - var viewGroupWithBackground: ViewGroup? = null - for (i in 0 until decorView.childCount) { - viewGroupWithBackground = - findFirstViewGroupWithBackground(decorView.getChildAt(i)) - if (viewGroupWithBackground != null) { - break - } - } - - // Animate that view with the background. Throw if we didn't find one, because - // otherwise it's not clear what we should animate. - if (viewGroupWithBackground == null) { - error("Unable to find ViewGroup with background") - } - - if (viewGroupWithBackground !is LaunchableView) { - error("The animated ViewGroup with background must implement LaunchableView") - } - - viewGroupWithBackground - } else { - val (dialogContentWithBackground, decorViewLayoutListener) = - dialog.maybeForceFullscreen()!! - this.decorViewLayoutListener = decorViewLayoutListener - dialogContentWithBackground - } - - this.dialogContentWithBackground = dialogContentWithBackground - dialogContentWithBackground.setTag(R.id.tag_dialog_background, true) - - val background = dialogContentWithBackground.background - originalDialogBackgroundColor = - GhostedViewTransitionAnimatorController.findGradientDrawable(background) - ?.color - ?.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 - // b/204944038#comment17). Given that this background implements LaunchableView, we call - // setShouldBlockVisibilityChanges() early so that the current visibility (VISIBLE) is - // restored at the end of the animation. - dialogContentWithBackground.setShouldBlockVisibilityChanges(true) - dialogContentWithBackground.setTransitionVisibility(View.INVISIBLE) - - // Make sure the dialog is visible instantly and does not do any window animation. - val attributes = window.attributes - attributes.windowAnimations = R.style.Animation_LaunchAnimation - - // Ensure that the animation is not clipped by the display cut-out when animating this - // dialog into an app. - attributes.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS - - // Ensure that the animation is not clipped by the navigation/task bars when animating this - // dialog into an app. - val wasFittingNavigationBars = - attributes.fitInsetsTypes and WindowInsets.Type.navigationBars() != 0 - attributes.fitInsetsTypes = - attributes.fitInsetsTypes and WindowInsets.Type.navigationBars().inv() - - window.attributes = window.attributes - - // We apply the insets ourselves to make sure that the paddings are set on the correct - // View. - window.setDecorFitsSystemWindows(false) - val viewWithInsets = (dialogContentWithBackground.parent as ViewGroup) - viewWithInsets.setOnApplyWindowInsetsListener { view, windowInsets -> - val type = - if (wasFittingNavigationBars) { - WindowInsets.Type.displayCutout() or WindowInsets.Type.navigationBars() - } else { - WindowInsets.Type.displayCutout() - } - - val insets = windowInsets.getInsets(type) - view.setPadding(insets.left, insets.top, insets.right, insets.bottom) - WindowInsets.CONSUMED - } - - // Start the animation once the background view is properly laid out. - dialogContentWithBackground.addOnLayoutChangeListener( - object : View.OnLayoutChangeListener { - override fun onLayoutChange( - v: View, - left: Int, - top: Int, - right: Int, - bottom: Int, - oldLeft: Int, - oldTop: Int, - oldRight: Int, - oldBottom: Int - ) { - dialogContentWithBackground.removeOnLayoutChangeListener(this) - - isOriginalDialogViewLaidOut = true - maybeStartLaunchAnimation() - } - } - ) - - // Disable the dim. We will enable it once we start the animation. - window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND) - - // Override the dialog dismiss() so that we can animate the exit before actually dismissing - // the dialog. - dialog.setDismissOverride(this::onDialogDismissed) - - if (featureFlags.isPredictiveBackQsDialogAnim && Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { - dialog.registerAnimationOnBackInvoked(targetView = dialogContentWithBackground) - } - - // Show the dialog. - dialog.show() - moveSourceDrawingToDialog() - } - - private fun moveSourceDrawingToDialog() { - if (decorView.viewRootImpl == null) { - // Make sure that we have access to the dialog view root to move the drawing to the - // dialog overlay. - decorView.post(::moveSourceDrawingToDialog) - return - } - - // Move the drawing of the source in the overlay of this dialog, then animate. We trigger a - // one-off synchronization to make sure that this is done in sync between the two different - // windows. - controller.startDrawingInOverlayOf(decorView) - synchronizeNextDraw( - then = { - isSourceDrawnInDialog = true - maybeStartLaunchAnimation() - } - ) - } - - /** - * Synchronize the next draw of the source and dialog view roots so that they are performed at - * the same time, in the same transaction. This is necessary to make sure that the source is - * drawn in the overlay at the same time as it is removed from its original position (or - * inversely, removed from the overlay when the source is moved back to its original position). - */ - private fun synchronizeNextDraw(then: () -> Unit) { - val controllerRootView = controller.viewRoot?.view - if (forceDisableSynchronization || controllerRootView == null) { - // Don't synchronize when inside an automated test or if the controller root view is - // detached. - then() - return - } - - ViewRootSync.synchronizeNextDraw(controllerRootView, decorView, then) - decorView.invalidate() - controllerRootView.invalidate() - } - - private fun findFirstViewGroupWithBackground(view: View): ViewGroup? { - if (view !is ViewGroup) { - return null - } - - if (view.background != null) { - return view - } - - for (i in 0 until view.childCount) { - val match = findFirstViewGroupWithBackground(view.getChildAt(i)) - if (match != null) { - return match - } - } - - return null - } - - private fun maybeStartLaunchAnimation() { - if (!isSourceDrawnInDialog || !isOriginalDialogViewLaidOut) { - return - } - - // Show the background dim. - dialog.window?.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND) - - startAnimation( - isLaunching = true, - onLaunchAnimationEnd = { - isLaunching = false - - // dismiss was called during the animation, dismiss again now to actually dismiss. - if (dismissRequested) { - dialog.dismiss() - } - - // If necessary, we animate the dialog background when its bounds change. We do it - // at the end of the launch animation, because the lauch animation already correctly - // handles bounds changes. - if (backgroundLayoutListener != null) { - dialogContentWithBackground!!.addOnLayoutChangeListener( - backgroundLayoutListener - ) - } - - if (hasInstrumentedJank) { - interactionJankMonitor.end(controller.cuj!!.cujType) - } - } - ) - } - - fun onDialogDismissed() { - if (Looper.myLooper() != Looper.getMainLooper()) { - dialog.context.mainExecutor.execute { onDialogDismissed() } - return - } - - // TODO(b/193634619): Support interrupting the launch animation in the middle. - if (isLaunching) { - dismissRequested = true - return - } - - if (isDismissing) { - return - } - - isDismissing = true - hideDialogIntoView { animationRan: Boolean -> - if (animationRan) { - // Instantly dismiss the dialog if we ran the animation into view. If it was - // skipped, dismiss() will run the window animation (which fades out the dialog). - dialog.hide() - } - - dialog.setDismissOverride(null) - dialog.dismiss() - } - } - - /** - * Hide the dialog into the source and call [onAnimationFinished] when the animation is done - * (passing animationRan=true) or if it's skipped (passing animationRan=false) to actually - * dismiss the dialog. - */ - private fun hideDialogIntoView(onAnimationFinished: (Boolean) -> Unit) { - // Remove the layout change listener we have added to the DecorView earlier. - if (decorViewLayoutListener != null) { - decorView.removeOnLayoutChangeListener(decorViewLayoutListener) - } - - if (!shouldAnimateDialogIntoSource()) { - Log.i(TAG, "Skipping animation of dialog into the source") - controller.onExitAnimationCancelled() - onAnimationFinished(false /* instantDismiss */) - onDialogDismissed(this@AnimatedDialog) - return - } - - startAnimation( - isLaunching = false, - onLaunchAnimationStart = { - // Remove the dim background as soon as we start the animation. - dialog.window?.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND) - }, - onLaunchAnimationEnd = { - val dialogContentWithBackground = this.dialogContentWithBackground!! - dialogContentWithBackground.visibility = View.INVISIBLE - - if (backgroundLayoutListener != null) { - dialogContentWithBackground.removeOnLayoutChangeListener( - backgroundLayoutListener - ) - } - - controller.stopDrawingInOverlay() - synchronizeNextDraw { - onAnimationFinished(true /* instantDismiss */) - onDialogDismissed(this@AnimatedDialog) - } - } - ) - } - - private fun startAnimation( - isLaunching: Boolean, - onLaunchAnimationStart: () -> Unit = {}, - onLaunchAnimationEnd: () -> Unit = {} - ) { - // Create 2 controllers to animate both the dialog and the source. - val startController = - if (isLaunching) { - controller.createTransitionController() - } else { - GhostedViewTransitionAnimatorController(dialogContentWithBackground!!) - } - val endController = - if (isLaunching) { - GhostedViewTransitionAnimatorController(dialogContentWithBackground!!) - } else { - controller.createExitController() - } - startController.transitionContainer = decorView - endController.transitionContainer = decorView - - val endState = endController.createAnimatorState() - val controller = - object : TransitionAnimator.Controller { - override var transitionContainer: ViewGroup - get() = startController.transitionContainer - set(value) { - startController.transitionContainer = value - endController.transitionContainer = value - } - - // We tell TransitionController that this is always a launch, and handle the launch - // vs return logic internally. - // TODO(b/323863002): maybe move the launch vs return logic out of this class and - // delegate it to TransitionController? - override val isLaunching: Boolean = true - - override fun createAnimatorState(): TransitionAnimator.State { - return startController.createAnimatorState() - } - - override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) { - // During launch, onLaunchAnimationStart will be used to remove the temporary - // touch surface ghost so it is important to call this before calling - // onLaunchAnimationStart on the controller (which will create its own ghost). - onLaunchAnimationStart() - - startController.onTransitionAnimationStart(isExpandingFullyAbove) - endController.onTransitionAnimationStart(isExpandingFullyAbove) - } - - override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) { - // 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() - } - } - - override fun onTransitionAnimationProgress( - state: TransitionAnimator.State, - progress: Float, - linearProgress: Float - ) { - startController.onTransitionAnimationProgress(state, progress, linearProgress) - - // The end view is visible only iff the starting view is not visible. - state.visible = !state.visible - endController.onTransitionAnimationProgress(state, progress, linearProgress) - - // If the dialog content is complex, its dimension might change during the - // launch animation. The animation end position might also change during the - // exit animation, for instance when locking the phone when the dialog is open. - // Therefore we update the end state to the new position/size. Usually the - // dialog dimension or position will change in the early frames, so changing the - // end state shouldn't really be noticeable. - if (endController is GhostedViewTransitionAnimatorController) { - endController.fillGhostedViewState(endState) - } - } - } - - transitionAnimator.startAnimation(controller, endState, originalDialogBackgroundColor) - } - - private fun shouldAnimateDialogIntoSource(): Boolean { - // Don't animate if the dialog was previously hidden using hide() or if we disabled the exit - // animation. - if (exitAnimationDisabled || !dialog.isShowing) { - return false - } - - // If we are dreaming, the dialog was probably closed because of that so we don't animate - // into the source. - if (callback.isDreaming()) { - return false - } - - return controller.shouldAnimateExit() - } - - /** A layout listener to animate the change of bounds of the dialog background. */ - class AnimatedBoundsLayoutListener : View.OnLayoutChangeListener { - companion object { - private const val ANIMATION_DURATION = 500L - } - - private var lastBounds: Rect? = null - private var currentAnimator: ValueAnimator? = null - - override fun onLayoutChange( - view: View, - left: Int, - top: Int, - right: Int, - bottom: Int, - oldLeft: Int, - oldTop: Int, - oldRight: Int, - oldBottom: Int - ) { - // Don't animate if bounds didn't actually change. - if (left == oldLeft && top == oldTop && right == oldRight && bottom == oldBottom) { - // Make sure that we that the last bounds set by the animator were not overridden. - lastBounds?.let { bounds -> - view.left = bounds.left - view.top = bounds.top - view.right = bounds.right - view.bottom = bounds.bottom - } - return - } - - if (lastBounds == null) { - lastBounds = Rect(oldLeft, oldTop, oldRight, oldBottom) - } - - val bounds = lastBounds!! - val startLeft = bounds.left - val startTop = bounds.top - val startRight = bounds.right - val startBottom = bounds.bottom - - currentAnimator?.cancel() - currentAnimator = null - - val animator = - ValueAnimator.ofFloat(0f, 1f).apply { - duration = ANIMATION_DURATION - interpolator = Interpolators.STANDARD - - addListener( - object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - currentAnimator = null - } - } - ) - - addUpdateListener { animatedValue -> - val progress = animatedValue.animatedFraction - - // Compute new bounds. - bounds.left = MathUtils.lerp(startLeft, left, progress).roundToInt() - bounds.top = MathUtils.lerp(startTop, top, progress).roundToInt() - bounds.right = MathUtils.lerp(startRight, right, progress).roundToInt() - bounds.bottom = MathUtils.lerp(startBottom, bottom, progress).roundToInt() - - // Set the new bounds. - view.left = bounds.left - view.top = bounds.top - view.right = bounds.right - view.bottom = bounds.bottom - } - } - - currentAnimator = animator - animator.start() - } - } - - fun prepareForStackDismiss() { - if (parentAnimatedDialog == null) { - return - } - parentAnimatedDialog.exitAnimationDisabled = true - parentAnimatedDialog.dialog.hide() - parentAnimatedDialog.prepareForStackDismiss() - parentAnimatedDialog.dialog.dismiss() - } -} diff --git a/systemUI/anim/src/com/android/systemui/animation/Expandable.kt b/systemUI/anim/src/com/android/systemui/animation/Expandable.kt deleted file mode 100644 index 3ba9a29748..0000000000 --- a/systemUI/anim/src/com/android/systemui/animation/Expandable.kt +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.animation - -import android.content.ComponentName -import android.view.View - -/** A piece of UI that can be expanded into a Dialog or an Activity. */ -interface Expandable { - /** - * Create an [ActivityTransitionAnimator.Controller] that can be used to expand this - * [Expandable] into an Activity, or return `null` if this [Expandable] should not be animated - * (e.g. if it is currently not attached or visible). - * - * @param launchCujType The CUJ type from the [com.android.internal.jank.InteractionJankMonitor] - * associated to the launch that will use this controller. - * @param cookie The unique cookie associated with the launch that will use this controller. - * This is required iff a return animation should be included. - * @param component The name of the activity that will be launched by this controller. This is - * required for long-lived registrations only. - * @param returnCujType The CUJ type from the [com.android.internal.jank.InteractionJankMonitor] - * associated to the return animation that will use this controller. - */ - fun activityTransitionController( - launchCujType: Int? = null, - cookie: ActivityTransitionAnimator.TransitionCookie? = null, - component: ComponentName? = null, - returnCujType: Int? = null - ): ActivityTransitionAnimator.Controller? - - /** - * See [activityTransitionController] above. - * - * Interfaces don't support [JvmOverloads], so this is a useful overload for Java usages that - * don't use the return-related parameters. - */ - fun activityTransitionController( - launchCujType: Int? = null - ): ActivityTransitionAnimator.Controller? { - return activityTransitionController( - launchCujType, - cookie = null, - component = null, - returnCujType = null - ) - } - - /** - * Create a [DialogTransitionAnimator.Controller] that can be used to expand this [Expandable] - * into a Dialog, or return `null` if this [Expandable] should not be animated (e.g. if it is - * currently not attached or visible). - */ - fun dialogTransitionController(cuj: DialogCuj? = null): DialogTransitionAnimator.Controller? - - companion object { - /** - * Create an [Expandable] that will animate [view] when expanded. - * - * Note: The background of [view] should be a (rounded) rectangle so that it can be properly - * animated. - */ - @JvmStatic - fun fromView(view: View): Expandable { - return object : Expandable { - override fun activityTransitionController( - launchCujType: Int?, - cookie: ActivityTransitionAnimator.TransitionCookie?, - component: ComponentName?, - returnCujType: Int? - ): ActivityTransitionAnimator.Controller? { - return ActivityTransitionAnimator.Controller.fromView( - view, - launchCujType, - cookie, - component, - returnCujType - ) - } - - override fun dialogTransitionController( - cuj: DialogCuj? - ): DialogTransitionAnimator.Controller? { - return DialogTransitionAnimator.Controller.fromView(view, cuj) - } - } - } - } -} diff --git a/systemUI/anim/src/com/android/systemui/animation/FontInterpolator.kt b/systemUI/anim/src/com/android/systemui/animation/FontInterpolator.kt deleted file mode 100644 index addabcc028..0000000000 --- a/systemUI/anim/src/com/android/systemui/animation/FontInterpolator.kt +++ /dev/null @@ -1,238 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.animation - -import android.graphics.fonts.Font -import android.graphics.fonts.FontVariationAxis -import android.util.Log -import android.util.LruCache -import android.util.MathUtils -import androidx.annotation.VisibleForTesting -import java.lang.Float.max -import java.lang.Float.min - -private const val TAG_WGHT = "wght" -private const val TAG_ITAL = "ital" - -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 - -// 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( - 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 - } - } - - /** - * 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<FontVariationAxis> - ) { - constructor( - font: Font, - axes: List<FontVariationAxis> - ) : this( - font.sourceIdentifier, - font.ttcIndex, - axes.toMutableList().apply { sortBy { it.tag } } - ) - - fun set(font: Font, axes: List<FontVariationAxis>) { - sourceId = font.sourceIdentifier - index = font.ttcIndex - sortedAxes.clear() - sortedAxes.addAll(axes) - sortedAxes.sortBy { it.tag } - } - } - - // 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 = numberOfAnimationSteps?.let { it * 2 } ?: DEFAULT_FONT_CACHE_MAX_ENTRIES - private val interpCache = LruCache<InterpKey, Font>(cacheMaxEntries) - private val verFontCache = LruCache<VarFontKey, Font>(cacheMaxEntries) - - // Mutable keys for recycling. - private val tmpInterpKey = InterpKey(null, null, 0f) - private val tmpVarFontKey = VarFontKey(0, 0, mutableListOf()) - - /** Linear interpolate the font variation settings. */ - 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 - - if (startAxes.isEmpty() && endAxes.isEmpty()) { - return start - } - - // Check we already know the result. This is commonly happens since we draws the different - // text chunks with the same font. - tmpInterpKey.set(start, end, progress) - val cachedFont = interpCache[tmpInterpKey] - if (cachedFont != null) { - if (DEBUG) { - Log.d(LOG_TAG, "[$progress] Interp. cache hit for $tmpInterpKey") - } - return cachedFont - } - - // General axes interpolation takes O(N log N), this is came from sorting the axes. Usually - // this doesn't take much time since the variation axes is usually up to 5. If we need to - // support more number of axes, we may want to preprocess the font and store the sorted axes - // and also pre-fill the missing axes value with default value from 'fvar' table. - val newAxes = - lerp(startAxes, endAxes) { tag, startValue, endValue -> - 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. - 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] Axis cache hit for $tmpVarFontKey") - } - 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() - 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] Cache MISS for $tmpInterpKey / $tmpVarFontKey") - return newFont - } - - private fun lerp( - start: Array<FontVariationAxis>, - end: Array<FontVariationAxis>, - filter: (tag: String, left: Float?, right: Float?) -> Float - ): List<FontVariationAxis> { - // Safe to modify result of Font#getAxes since it returns cloned object. - start.sortBy { axis -> axis.tag } - end.sortBy { axis -> axis.tag } - - val result = mutableListOf<FontVariationAxis>() - var i = 0 - var j = 0 - while (i < start.size || j < end.size) { - val tagA = if (i < start.size) start[i].tag else null - val tagB = if (j < end.size) end[j].tag else null - - val comp = - when { - tagA == null -> 1 - tagB == null -> -1 - else -> tagA.compareTo(tagB) - } - - val axis = - when { - 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) - } - } - - 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<FontVariationAxis>() - - // Returns true if given two font instance can be interpolated. - fun canInterpolate(start: Font, end: Font) = - start.ttcIndex == end.ttcIndex && start.sourceIdentifier == end.sourceIdentifier - } -} diff --git a/systemUI/anim/src/com/android/systemui/animation/FontVariationUtils.kt b/systemUI/anim/src/com/android/systemui/animation/FontVariationUtils.kt deleted file mode 100644 index 78ae4af258..0000000000 --- a/systemUI/anim/src/com/android/systemui/animation/FontVariationUtils.kt +++ /dev/null @@ -1,59 +0,0 @@ -package com.android.systemui.animation - -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 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 - ): String { - 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 - } - - if (roundness >= 0 && mRoundness != roundness) { - isUpdated = true - mRoundness = roundness - } - var resultString = "" - if (mWeight >= 0) { - resultString += "'$TAG_WGHT' $mWeight" - } - 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/GSFAxes.kt b/systemUI/anim/src/com/android/systemui/animation/GSFAxes.kt deleted file mode 100644 index e734dd26eb..0000000000 --- a/systemUI/anim/src/com/android/systemui/animation/GSFAxes.kt +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) 2025 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS 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 - -data class AxisDefinition( - val tag: String, - val minValue: Float, - val defaultValue: Float, - val maxValue: Float, - val animationStep: Float, -) - -object GSFAxes { - @JvmStatic - val WEIGHT = - AxisDefinition( - tag = "wght", - minValue = 1f, - defaultValue = 400f, - maxValue = 1000f, - animationStep = 10f, - ) - - val WIDTH = - AxisDefinition( - tag = "wdth", - minValue = 25f, - defaultValue = 100f, - maxValue = 151f, - animationStep = 1f, - ) - - val SLANT = - AxisDefinition( - tag = "slnt", - minValue = 0f, - defaultValue = 0f, - maxValue = -10f, - animationStep = 0.1f, - ) - - val ROUND = - AxisDefinition( - tag = "ROND", - minValue = 0f, - defaultValue = 0f, - maxValue = 100f, - animationStep = 1f, - ) - - val GRADE = - AxisDefinition( - tag = "GRAD", - minValue = 0f, - defaultValue = 0f, - maxValue = 100f, - animationStep = 1f, - ) - - val OPTICAL_SIZE = - AxisDefinition( - tag = "opsz", - minValue = 6f, - defaultValue = 18f, - maxValue = 144f, - animationStep = 1f, - ) - - // Not a GSF Axis, but present for FontInterpolator compatibility - val ITALIC = - AxisDefinition( - tag = "ITAL", - minValue = 0f, - defaultValue = 0f, - maxValue = 1f, - animationStep = 0.1f, - ) - - private val AXIS_MAP = - listOf(WEIGHT, WIDTH, SLANT, ROUND, GRADE, OPTICAL_SIZE, ITALIC) - .map { def -> def.tag.lowercase() to def } - .toMap() - - fun getAxis(axis: String): AxisDefinition? = AXIS_MAP[axis.lowercase()] -} diff --git a/systemUI/anim/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt b/systemUI/anim/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt deleted file mode 100644 index e626c04675..0000000000 --- a/systemUI/anim/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt +++ /dev/null @@ -1,532 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.animation - -import android.content.ComponentName -import android.graphics.Canvas -import android.graphics.ColorFilter -import android.graphics.Insets -import android.graphics.Matrix -import android.graphics.PixelFormat -import android.graphics.Rect -import android.graphics.drawable.Drawable -import android.graphics.drawable.GradientDrawable -import android.graphics.drawable.InsetDrawable -import android.graphics.drawable.LayerDrawable -import android.graphics.drawable.StateListDrawable -import android.util.Log -import android.view.GhostView -import android.view.View -import android.view.ViewGroup -import android.view.ViewGroupOverlay -import android.widget.FrameLayout -import com.android.internal.jank.Cuj.CujType -import com.android.internal.jank.InteractionJankMonitor -import java.util.LinkedList -import kotlin.math.min -import kotlin.math.roundToInt - -private const val TAG = "GhostedViewTransitionAnimatorController" - -/** - * A base implementation of [ActivityTransitionAnimator.Controller] which creates a - * [ghost][GhostView] of [ghostedView] as well as an expandable background view, which are drawn and - * animated instead of the ghosted view. - * - * Important: [ghostedView] must be attached to a [ViewGroup] when calling this function and during - * the animation. It must also implement [LaunchableView], otherwise an exception will be thrown - * during this controller instantiation. - * - * Note: Avoid instantiating this directly and call [ActivityTransitionAnimator.Controller.fromView] - * whenever possible instead. - */ -open class GhostedViewTransitionAnimatorController -@JvmOverloads -constructor( - /** The view that will be ghosted and from which the background will be extracted. */ - private val ghostedView: View, - - /** The [CujType] associated to this launch animation. */ - private val launchCujType: Int? = null, - override val transitionCookie: ActivityTransitionAnimator.TransitionCookie? = null, - override val component: ComponentName? = null, - - /** The [CujType] associated to this return animation. */ - private val returnCujType: Int? = null, - private var interactionJankMonitor: InteractionJankMonitor = - InteractionJankMonitor.getInstance(), -) : ActivityTransitionAnimator.Controller { - override val isLaunching: Boolean = true - - /** The container to which we will add the ghost view and expanding background. */ - override var transitionContainer = ghostedView.rootView as ViewGroup - private val transitionContainerOverlay: ViewGroupOverlay - get() = transitionContainer.overlay - - private val transitionContainerLocation = IntArray(2) - - /** The ghost view that is drawn and animated instead of the ghosted view. */ - private var ghostView: GhostView? = null - private val initialGhostViewMatrixValues = FloatArray(9) { 0f } - private val ghostViewMatrix = Matrix() - - /** - * The expanding background view that will be added to [transitionContainer] (below [ghostView]) - * and animate. - */ - private var backgroundView: FrameLayout? = null - - /** - * The drawable wrapping the [ghostedView] background and used as background for - * [backgroundView]. - */ - private var backgroundDrawable: WrappedDrawable? = null - private val backgroundInsets by lazy { background?.opticalInsets ?: Insets.NONE } - private var startBackgroundAlpha: Int = 0xFF - - private val ghostedViewLocation = IntArray(2) - private val ghostedViewState = TransitionAnimator.State() - - /** - * The background of the [ghostedView]. This background will be used to draw the background of - * the background view that is expanding up to the final animation position. - * - * Note that during the animation, the alpha value value of this background will be set to 0, - * then set back to its initial value at the end of the animation. - */ - private val background: Drawable? - - /** CUJ identifier accounting for whether this controller is for a launch or a return. */ - private val cujType: Int? - get() = - if (isLaunching) { - launchCujType - } else { - returnCujType - } - - init { - // Make sure the View we launch from implements LaunchableView to avoid visibility issues. - 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 " + - "of the View we are launching from is not what we expected." - ) - } - - /** Find the first view with a background in [view] and its children. */ - fun findBackground(view: View): Drawable? { - if (view.background != null) { - return view.background - } - - // Perform a BFS to find the largest View with background. - val views = LinkedList<View>().apply { add(view) } - - while (views.isNotEmpty()) { - val v = views.removeAt(0) - if (v.background != null) { - return v.background - } - - if (v is ViewGroup) { - for (i in 0 until v.childCount) { - views.add(v.getChildAt(i)) - } - } - } - - return null - } - - background = findBackground(ghostedView) - } - - /** - * Set the corner radius of [background]. The background is the one that was returned by - * [getBackground]. - */ - protected open fun setBackgroundCornerRadius( - background: Drawable, - topCornerRadius: Float, - bottomCornerRadius: Float - ) { - // By default, we rely on WrappedDrawable to set/restore the background radii before/after - // each draw. - backgroundDrawable?.setBackgroundRadius(topCornerRadius, bottomCornerRadius) - } - - /** Return the current top corner radius of the background. */ - protected open fun getCurrentTopCornerRadius(): Float { - val drawable = background ?: return 0f - val gradient = findGradientDrawable(drawable) ?: return 0f - - // TODO(b/184121838): Support more than symmetric top & bottom radius. - val radius = gradient.cornerRadii?.get(CORNER_RADIUS_TOP_INDEX) ?: gradient.cornerRadius - return radius * ghostedView.scaleX - } - - /** Return the current bottom corner radius of the background. */ - protected open fun getCurrentBottomCornerRadius(): Float { - val drawable = background ?: return 0f - val gradient = findGradientDrawable(drawable) ?: return 0f - - // TODO(b/184121838): Support more than symmetric top & bottom radius. - val radius = gradient.cornerRadii?.get(CORNER_RADIUS_BOTTOM_INDEX) ?: gradient.cornerRadius - return radius * ghostedView.scaleX - } - - override fun createAnimatorState(): TransitionAnimator.State { - val state = - TransitionAnimator.State( - topCornerRadius = getCurrentTopCornerRadius(), - bottomCornerRadius = getCurrentBottomCornerRadius() - ) - fillGhostedViewState(state) - return state - } - - fun fillGhostedViewState(state: TransitionAnimator.State) { - // For the animation we are interested in the area that has a non transparent background, - // so we have to take the optical insets into account. - ghostedView.getLocationOnScreen(ghostedViewLocation) - val insets = backgroundInsets - val boundCorrections: Rect = - if (ghostedView is LaunchableView) { - ghostedView.getPaddingForLaunchAnimation() - } else { - Rect() - } - state.top = ghostedViewLocation[1] + insets.top + boundCorrections.top - state.bottom = - ghostedViewLocation[1] + (ghostedView.height * ghostedView.scaleY).roundToInt() - - insets.bottom + boundCorrections.bottom - state.left = ghostedViewLocation[0] + insets.left + boundCorrections.left - state.right = - ghostedViewLocation[0] + (ghostedView.width * ghostedView.scaleX).roundToInt() - - insets.right + boundCorrections.right - } - - override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) { - if (ghostedView.parent !is ViewGroup) { - // This should usually not happen, but let's make sure we don't crash if the view was - // detached right before we started the animation. - Log.w(TAG, "Skipping animation as ghostedView is not attached to a ViewGroup") - return - } - - backgroundView = - FrameLayout(transitionContainer.context).also { transitionContainerOverlay.add(it) } - - // We wrap the ghosted view background and use it to draw the expandable background. Its - // alpha will be set to 0 as soon as we start drawing the expanding background. - startBackgroundAlpha = background?.alpha ?: 0xFF - backgroundDrawable = WrappedDrawable(background) - backgroundView?.background = backgroundDrawable - - // Delay the calls to `ghostedView.setVisibility()` during the animation. This must be - // called before `GhostView.addGhost()` is called because the latter will change the - // *transition* visibility, which won't be blocked and will affect the normal View - // visibility that is saved by `setShouldBlockVisibilityChanges()` for a later restoration. - (ghostedView as? LaunchableView)?.setShouldBlockVisibilityChanges(true) - - // 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 - // [OverlayViewGroup]. We need to turn off clipping for that container view. Currently, - // however, the only way to get a reference to that overlay is by going through our - // [ghostView]. The [OverlayViewGroup] will always be its grandparent view. - // TODO(b/306652954) reference the overlay view group directly if we can - (ghostView?.parent?.parent as? ViewGroup)?.let { - it.clipChildren = false - it.clipToPadding = false - } - - val matrix = ghostView?.animationMatrix ?: Matrix.IDENTITY_MATRIX - matrix.getValues(initialGhostViewMatrixValues) - - cujType?.let { interactionJankMonitor.begin(ghostedView, it) } - } - - override fun onTransitionAnimationProgress( - state: TransitionAnimator.State, - progress: Float, - linearProgress: Float - ) { - val ghostView = this.ghostView ?: return - val backgroundView = this.backgroundView!! - - if (!state.visible || !ghostedView.isAttachedToWindow) { - if (ghostView.visibility == View.VISIBLE) { - // Making the ghost view invisible will make the ghosted view visible, so order is - // important here. - ghostView.visibility = View.INVISIBLE - - // Make the ghosted view invisible again. We use the transition visibility like - // GhostView does so that we don't mess up with the accessibility tree (see - // b/204944038#comment17). - ghostedView.setTransitionVisibility(View.INVISIBLE) - backgroundView.visibility = View.INVISIBLE - } - return - } - - // The ghost and backgrounds views were made invisible earlier. That can for instance happen - // when animating a dialog into a view. - if (ghostView.visibility == View.INVISIBLE) { - ghostView.visibility = View.VISIBLE - backgroundView.visibility = View.VISIBLE - } - - fillGhostedViewState(ghostedViewState) - val leftChange = state.left - ghostedViewState.left - val rightChange = state.right - ghostedViewState.right - val topChange = state.top - ghostedViewState.top - val bottomChange = state.bottom - ghostedViewState.bottom - - val widthRatio = state.width.toFloat() / ghostedViewState.width - val heightRatio = state.height.toFloat() / ghostedViewState.height - val scale = min(widthRatio, heightRatio) - - if (ghostedView.parent is ViewGroup) { - // Recalculate the matrix in case the ghosted view moved. We ensure that the ghosted - // view is still attached to a ViewGroup, otherwise calculateMatrix will throw. - GhostView.calculateMatrix(ghostedView, transitionContainer, ghostViewMatrix) - } - - transitionContainer.getLocationOnScreen(transitionContainerLocation) - ghostViewMatrix.postScale( - scale, - scale, - ghostedViewState.centerX - transitionContainerLocation[0], - ghostedViewState.centerY - transitionContainerLocation[1] - ) - ghostViewMatrix.postTranslate( - (leftChange + rightChange) / 2f, - (topChange + bottomChange) / 2f - ) - ghostView.animationMatrix = ghostViewMatrix - - // We need to take into account the background insets for the background position. - val insets = backgroundInsets - val topWithInsets = state.top - insets.top - val leftWithInsets = state.left - insets.left - val rightWithInsets = state.right + insets.right - val bottomWithInsets = state.bottom + insets.bottom - - backgroundView.top = topWithInsets - transitionContainerLocation[1] - backgroundView.bottom = bottomWithInsets - transitionContainerLocation[1] - backgroundView.left = leftWithInsets - transitionContainerLocation[0] - backgroundView.right = rightWithInsets - transitionContainerLocation[0] - - val backgroundDrawable = backgroundDrawable!! - backgroundDrawable.wrapped?.let { - setBackgroundCornerRadius(it, state.topCornerRadius, state.bottomCornerRadius) - } - } - - override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) { - if (ghostView == null) { - // We didn't actually run the animation. - return - } - - cujType?.let { interactionJankMonitor.end(it) } - - backgroundDrawable?.wrapped?.alpha = startBackgroundAlpha - - GhostView.removeGhost(ghostedView) - backgroundView?.let { transitionContainerOverlay.remove(it) } - - if (ghostedView is LaunchableView) { - // Restore the ghosted view visibility. - 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 - // for more info). - ghostedView.visibility = View.INVISIBLE - ghostedView.visibility = View.VISIBLE - ghostedView.invalidate() - } - } - - companion object { - private const val CORNER_RADIUS_TOP_INDEX = 0 - private const val CORNER_RADIUS_BOTTOM_INDEX = 4 - - /** - * Return the first [GradientDrawable] found in [drawable], or null if none is found. If - * [drawable] is a [LayerDrawable], this will return the first layer that has a - * [GradientDrawable]. - */ - fun findGradientDrawable(drawable: Drawable): GradientDrawable? { - if (drawable is GradientDrawable) { - return drawable - } - - if (drawable is InsetDrawable) { - return drawable.drawable?.let { findGradientDrawable(it) } - } - - if (drawable is LayerDrawable) { - for (i in 0 until drawable.numberOfLayers) { - val maybeGradient = findGradientDrawable(drawable.getDrawable(i)) - if (maybeGradient != null) { - return maybeGradient - } - } - } - - if (drawable is StateListDrawable) { - return findGradientDrawable(drawable.current) - } - - return null - } - } - - private class WrappedDrawable(val wrapped: Drawable?) : Drawable() { - private var currentAlpha = 0xFF - private var previousBounds = Rect() - - private var cornerRadii = FloatArray(8) { -1f } - private var previousCornerRadii = FloatArray(8) - - override fun draw(canvas: Canvas) { - val wrapped = this.wrapped ?: return - - wrapped.copyBounds(previousBounds) - - wrapped.alpha = currentAlpha - wrapped.bounds = bounds - applyBackgroundRadii() - - wrapped.draw(canvas) - - // The background view (and therefore this drawable) is drawn before the ghost view, so - // the ghosted view background alpha should always be 0 when it is drawn above the - // background. - wrapped.alpha = 0 - wrapped.bounds = previousBounds - restoreBackgroundRadii() - } - - override fun setAlpha(alpha: Int) { - if (alpha != currentAlpha) { - currentAlpha = alpha - invalidateSelf() - } - } - - override fun getAlpha() = currentAlpha - - override fun getOpacity(): Int { - val wrapped = this.wrapped ?: return PixelFormat.TRANSPARENT - - val previousAlpha = wrapped.alpha - wrapped.alpha = currentAlpha - val opacity = wrapped.opacity - wrapped.alpha = previousAlpha - return opacity - } - - override fun setColorFilter(filter: ColorFilter?) { - wrapped?.colorFilter = filter - } - - fun setBackgroundRadius(topCornerRadius: Float, bottomCornerRadius: Float) { - updateRadii(cornerRadii, topCornerRadius, bottomCornerRadius) - invalidateSelf() - } - - private fun updateRadii( - radii: FloatArray, - topCornerRadius: Float, - bottomCornerRadius: Float - ) { - radii[0] = topCornerRadius - radii[1] = topCornerRadius - radii[2] = topCornerRadius - radii[3] = topCornerRadius - - radii[4] = bottomCornerRadius - radii[5] = bottomCornerRadius - radii[6] = bottomCornerRadius - radii[7] = bottomCornerRadius - } - - private fun applyBackgroundRadii() { - if (cornerRadii[0] < 0 || wrapped == null) { - return - } - - savePreviousBackgroundRadii(wrapped) - applyBackgroundRadii(wrapped, cornerRadii) - } - - private fun savePreviousBackgroundRadii(background: Drawable) { - // TODO(b/184121838): This method assumes that all GradientDrawable in background will - // have the same radius. Should we save/restore the radii for each layer instead? - val gradient = findGradientDrawable(background) ?: return - - // TODO(b/184121838): GradientDrawable#getCornerRadii clones its radii array. Should we - // try to avoid that? - val radii = gradient.cornerRadii - if (radii != null) { - radii.copyInto(previousCornerRadii) - } else { - // Copy the cornerRadius into previousCornerRadii. - val radius = gradient.cornerRadius - updateRadii(previousCornerRadii, radius, radius) - } - } - - private fun applyBackgroundRadii(drawable: Drawable, radii: FloatArray) { - if (drawable is GradientDrawable) { - drawable.cornerRadii = radii - return - } - - if (drawable is InsetDrawable) { - drawable.drawable?.let { applyBackgroundRadii(it, radii) } - return - } - - if (drawable !is LayerDrawable) { - return - } - - for (i in 0 until drawable.numberOfLayers) { - applyBackgroundRadii(drawable.getDrawable(i), radii) - } - } - - private fun restoreBackgroundRadii() { - if (cornerRadii[0] < 0 || wrapped == null) { - return - } - - applyBackgroundRadii(wrapped, previousCornerRadii) - } - } -} diff --git a/systemUI/anim/src/com/android/systemui/animation/IViewTransitionRegistry.kt b/systemUI/anim/src/com/android/systemui/animation/IViewTransitionRegistry.kt deleted file mode 100644 index 280d90de7a..0000000000 --- a/systemUI/anim/src/com/android/systemui/animation/IViewTransitionRegistry.kt +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2025 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS 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 android.view.View - -/** Represents a Registry for holding a transitioning view mapped to a token */ -interface IViewTransitionRegistry { - - /** - * Registers the transitioning [view] mapped to returned token - * - * @param view The view undergoing transition - * @return token mapped to the transitioning view - */ - fun register(view: View): ViewTransitionToken - - /** - * Unregisters the transitioned view from its corresponding [token] - * - * @param token The token corresponding to the transitioning view - */ - fun unregister(token: ViewTransitionToken) - - /** - * Extracts a transitioning view from registry using its corresponding [token] - * - * @param token The token corresponding to the transitioning view - */ - fun getView(token: ViewTransitionToken): View? - - /** - * Return token mapped to the [view], if it is present in the registry - * - * @param view the transitioning view whose token we are requesting - * @return token associated with the [view] if present, else null - */ - fun getViewToken(view: View): ViewTransitionToken? - - /** Event call to run on registry update (on both [register] and [unregister]) */ - fun onRegistryUpdate() -} diff --git a/systemUI/anim/src/com/android/systemui/animation/LaunchableView.kt b/systemUI/anim/src/com/android/systemui/animation/LaunchableView.kt deleted file mode 100644 index 330ab0f7fc..0000000000 --- a/systemUI/anim/src/com/android/systemui/animation/LaunchableView.kt +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.animation - -import android.graphics.Rect -import android.view.View - -/** A view that can expand/launch into an app or a dialog. */ -interface LaunchableView { - /** - * Set whether this view should block/postpone all calls to [View.setVisibility]. This ensures - * that this view: - * - remains invisible during the launch animation given that it is ghosted and already drawn - * somewhere else. - * - remains invisible as long as a dialog expanded from it is shown. - * - restores its expected visibility once the dialog expanded from it is dismissed. - * - * When `setShouldBlockVisibilityChanges(false)` is called, then visibility of the View should - * be restored to its expected value, i.e. it should have the visibility of the last call to - * `View.setVisibility()` that was made after `setShouldBlockVisibilityChanges(true)`, if any, - * or the original view visibility otherwise. - * - * Note that calls to [View.setTransitionVisibility] shouldn't be blocked. - * - * @param block whether we should block/postpone all calls to `setVisibility`. - */ - fun setShouldBlockVisibilityChanges(block: Boolean) - - /** Perform an action when the activity launch animation ends */ - fun onActivityLaunchAnimationEnd() {} - - /** Provide an optional correction applied to the visible area during a launch animation */ - fun getPaddingForLaunchAnimation(): Rect = Rect() -} - -/** A delegate that can be used by views to make the implementation of [LaunchableView] easier. */ -class LaunchableViewDelegate( - private val view: View, - - /** - * The lambda that should set the actual visibility of [view], usually by calling - * super.setVisibility(visibility). - */ - private val superSetVisibility: (Int) -> Unit, -) : LaunchableView { - private var blockVisibilityChanges = false - private var lastVisibility = view.visibility - - /** Call this when [LaunchableView.setShouldBlockVisibilityChanges] is called. */ - override fun setShouldBlockVisibilityChanges(block: Boolean) { - if (block == blockVisibilityChanges) { - return - } - - blockVisibilityChanges = block - if (block) { - // Save the current visibility for later. - lastVisibility = view.visibility - } else { - // Restore the visibility. To avoid accessibility issues, we change the visibility twice - // which makes sure that we trigger a visibility flag change (see b/204944038#comment17 - // for more info). - if (lastVisibility == View.VISIBLE) { - superSetVisibility(View.INVISIBLE) - superSetVisibility(View.VISIBLE) - } else { - superSetVisibility(View.VISIBLE) - superSetVisibility(lastVisibility) - } - } - } - - /** Call this when [View.setVisibility] is called. */ - fun setVisibility(visibility: Int) { - if (blockVisibilityChanges) { - lastVisibility = visibility - return - } - - superSetVisibility(visibility) - } -} diff --git a/systemUI/anim/src/com/android/systemui/animation/RemoteAnimationDelegate.kt b/systemUI/anim/src/com/android/systemui/animation/RemoteAnimationDelegate.kt deleted file mode 100644 index d465962d6e..0000000000 --- a/systemUI/anim/src/com/android/systemui/animation/RemoteAnimationDelegate.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.android.systemui.animation - -import android.annotation.UiThread -import android.view.IRemoteAnimationFinishedCallback -import android.view.RemoteAnimationTarget -import android.view.WindowManager - -/** - * A component capable of running remote animations. - * - * Expands the IRemoteAnimationRunner API by allowing for different types of more specialized - * callbacks. - */ -interface RemoteAnimationDelegate<in T : IRemoteAnimationFinishedCallback> { - /** - * Called on the UI thread when the animation targets are received. Sets up and kicks off the - * animation. - */ - @UiThread - fun onAnimationStart( - @WindowManager.TransitionOldType transit: Int, - apps: Array<out RemoteAnimationTarget>?, - wallpapers: Array<out RemoteAnimationTarget>?, - nonApps: Array<out RemoteAnimationTarget>?, - callback: T? - ) - - /** Called on the UI thread when a signal is received to cancel the animation. */ - @UiThread fun onAnimationCancelled() -} diff --git a/systemUI/anim/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java b/systemUI/anim/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java deleted file mode 100644 index abfc2c879b..0000000000 --- a/systemUI/anim/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java +++ /dev/null @@ -1,274 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.animation; - -import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; -import static android.view.WindowManager.TRANSIT_CLOSE; -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.TransitionInfo.FLAG_IS_WALLPAPER; - -import android.os.IBinder; -import android.os.RemoteException; -import android.util.ArrayMap; -import android.util.Log; -import android.view.IRemoteAnimationFinishedCallback; -import android.view.IRemoteAnimationRunner; -import android.view.RemoteAnimationTarget; -import android.view.SurfaceControl; -import android.view.WindowManager; -import android.view.WindowManager.TransitionOldType; -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; - -public abstract class RemoteAnimationRunnerCompat extends IRemoteAnimationRunner.Stub { - private static final String TAG = "RemoteAnimRunnerCompat"; - - public abstract void onAnimationStart(@WindowManager.TransitionOldType int transit, - 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) { - - onAnimationStart(transit, apps, wallpapers, - nonApps, () -> { - try { - finishedCallback.onAnimationFinished(); - } catch (RemoteException e) { - Log.e(TAG, "Failed to call app controlled animation finished callback", e); - } - }); - } - - // 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<IBinder, Runnable> mFinishRunnables = new ArrayMap<>(); - - public void onTransitionConsumed(IBinder transition, boolean aborted) {} - - @Override - public void startAnimation(IBinder token, TransitionInfo info, - SurfaceControl.Transaction t, - IRemoteTransitionFinishedCallback finishCallback) { - final ArrayMap<SurfaceControl, SurfaceControl> leashMap = new ArrayMap<>(); - final RemoteAnimationTarget[] apps = - RemoteAnimationTargetCompat.wrapApps(info, t, leashMap); - final RemoteAnimationTarget[] wallpapers = - RemoteAnimationTargetCompat.wrapNonApps( - info, true /* wallpapers */, t, leashMap); - final RemoteAnimationTarget[] nonApps = - RemoteAnimationTargetCompat.wrapNonApps( - info, false /* wallpapers */, t, leashMap); - - // TODO(b/177438007): Move this set-up logic into launcher's animation impl. - boolean isReturnToHome = false; - TransitionInfo.Change launcherTask = null; - TransitionInfo.Change wallpaper = null; - int launcherLayer = 0; - int rotateDelta = 0; - float displayW = 0; - float displayH = 0; - 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; - if (change.getTaskInfo() != null - && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME) { - isReturnToHome = change.getMode() == TRANSIT_OPEN - || change.getMode() == TRANSIT_TO_FRONT; - launcherTask = change; - launcherLayer = info.getChanges().size() - i; - } else if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) { - wallpaper = change; - } - if (change.getParent() == null && change.getEndRotation() >= 0 - && change.getEndRotation() != change.getStartRotation()) { - rotateDelta = change.getEndRotation() - change.getStartRotation(); - displayW = change.getEndAbsBounds().width(); - displayH = change.getEndAbsBounds().height(); - } - } - - // Prepare for rotation if there is one - final CounterRotator counterLauncher = new CounterRotator(); - final CounterRotator counterWallpaper = new CounterRotator(); - if (launcherTask != null && rotateDelta != 0 && launcherTask.getParent() != null) { - final TransitionInfo.Change parent = info.getChange(launcherTask.getParent()); - if (parent != null) { - counterLauncher.setup(t, parent.getLeash(), rotateDelta, displayW, - displayH); - } else { - Log.e(TAG, "Malformed: " + launcherTask + " has parent=" - + launcherTask.getParent() + " but it's not in info."); - } - if (counterLauncher.getSurface() != null) { - t.setLayer(counterLauncher.getSurface(), launcherLayer); - } - } - - if (isReturnToHome) { - if (counterLauncher.getSurface() != null) { - t.setLayer(counterLauncher.getSurface(), info.getChanges().size() * 3); - } - // Need to "boost" the closing things since that's what launcher expects. - for (int i = info.getChanges().size() - 1; i >= 0; --i) { - final TransitionInfo.Change change = info.getChanges().get(i); - final SurfaceControl leash = leashMap.get(change.getLeash()); - // skip changes that we didn't wrap - if (leash == null) continue; - final int mode = info.getChanges().get(i).getMode(); - // Only deal with independent layers - if (!TransitionInfo.isIndependent(change, info)) continue; - if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) { - t.setLayer(leash, info.getChanges().size() * 3 - i); - counterLauncher.addChild(t, leash); - } - } - // Make wallpaper visible immediately since launcher apparently won't do this. - for (int i = wallpapers.length - 1; i >= 0; --i) { - t.show(wallpapers[i].leash); - t.setAlpha(wallpapers[i].leash, 1.f); - } - } 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, - displayH); - } else { - Log.e(TAG, "Malformed: " + wallpaper + " has parent=" - + wallpaper.getParent() + " but it's not in info."); - } - if (counterWallpaper.getSurface() != null) { - t.setLayer(counterWallpaper.getSurface(), -1); - counterWallpaper.addChild(t, leashMap.get(wallpaper.getLeash())); - } - } - } - t.apply(); - - final Runnable animationFinishedCallback = () -> { - final SurfaceControl.Transaction finishTransaction = - new SurfaceControl.Transaction(); - counterLauncher.cleanUp(finishTransaction); - counterWallpaper.cleanUp(finishTransaction); - // Release surface references now. This is apparently to free GPU memory - // before GC would. - info.releaseAllSurfaces(); - // Don't release here since launcher might still be using them. Instead - // let launcher release them (eg. via RemoteAnimationTargets) - leashMap.clear(); - try { - finishCallback.onTransitionFinished(null /* wct */, finishTransaction); - finishTransaction.close(); - } catch (RemoteException e) { - Log.e(TAG, "Failed to call app controlled animation finished callback", e); - } - }; - synchronized (mFinishRunnables) { - mFinishRunnables.put(token, animationFinishedCallback); - } - // TODO(bc-unlcok): Pass correct transit type. - onAnimationStart(TRANSIT_OLD_NONE, - apps, wallpapers, nonApps, new IRemoteAnimationFinishedCallback() { - @Override - public void onAnimationFinished() { - synchronized (mFinishRunnables) { - if (mFinishRunnables.remove(token) == null) return; - } - animationFinishedCallback.run(); - } - - @Override - public IBinder asBinder() { - return null; - } - }); - } - - @Override - public void mergeAnimation(IBinder token, TransitionInfo info, - 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; - synchronized (mFinishRunnables) { - finishRunnable = mFinishRunnables.remove(mergeTarget); - } - // Since we're not actually animating, release native memory now - t.close(); - info.releaseAllSurfaces(); - if (finishRunnable == null) return; - onAnimationCancelled(false /* isKeyguardOccluded */); - finishRunnable.run(); - } - - @Override - public void takeOverAnimation(IBinder transition , TransitionInfo info , SurfaceControl.Transaction t , IRemoteTransitionFinishedCallback finishCallback , WindowAnimationState[] states) throws RemoteException { - - } - }; - } -} diff --git a/systemUI/anim/src/com/android/systemui/animation/RemoteAnimationTargetCompat.java b/systemUI/anim/src/com/android/systemui/animation/RemoteAnimationTargetCompat.java deleted file mode 100644 index e251af4472..0000000000 --- a/systemUI/anim/src/com/android/systemui/animation/RemoteAnimationTargetCompat.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.animation; - -import android.util.ArrayMap; -import android.view.RemoteAnimationTarget; -import android.view.SurfaceControl; -import android.window.TransitionInfo; -import android.window.TransitionInfo.Change; - -import com.android.wm.shell.shared.TransitionUtil; - -import java.util.ArrayList; -import java.util.function.Predicate; - -/** - * Some utility methods for creating {@link RemoteAnimationTarget} instances. - */ -public class RemoteAnimationTargetCompat { - - /** - * Represents a TransitionInfo object as an array of old-style app targets - * - * @param leashMap Temporary map of change leash -> launcher leash. Is an output, so should be - * populated by this function. If null, it is ignored. - */ - public static RemoteAnimationTarget[] wrapApps(TransitionInfo info, - SurfaceControl.Transaction t, ArrayMap<SurfaceControl, SurfaceControl> leashMap) { - // LeafTaskFilter is order-dependent, so the same object needs to be used for all Change - // objects. That's why it's constructed here and captured by the lambda instead of building - // a new one ad hoc every time. - TransitionUtil.LeafTaskFilter taskFilter = new TransitionUtil.LeafTaskFilter(); - return wrap(info, t, leashMap, (change) -> { - // Intra-task activity -> activity transitions should be categorized as apps. - if (change.getActivityComponent() != null) return true; - return taskFilter.test(change); - }); - } - - /** - * Represents a TransitionInfo object as an array of old-style non-app targets - * - * @param wallpapers If true, this will return wallpaper targets; otherwise it returns - * non-wallpaper targets. - * @param leashMap Temporary map of change leash -> launcher leash. Is an output, so should be - * populated by this function. If null, it is ignored. - */ - public static RemoteAnimationTarget[] wrapNonApps(TransitionInfo info, boolean wallpapers, - SurfaceControl.Transaction t, ArrayMap<SurfaceControl, SurfaceControl> leashMap) { - return wrap(info, t, leashMap, (change) -> { - // Intra-task activity -> activity transitions should be categorized as apps. - if (change.getActivityComponent() != null) return false; - return wallpapers - ? TransitionUtil.isWallpaper(change) : TransitionUtil.isNonApp(change); - }); - } - - private static RemoteAnimationTarget[] wrap(TransitionInfo info, - SurfaceControl.Transaction t, ArrayMap<SurfaceControl, SurfaceControl> leashMap, - Predicate<Change> filter) { - final ArrayList<RemoteAnimationTarget> out = new ArrayList<>(); - for (int i = 0; i < info.getChanges().size(); i++) { - TransitionInfo.Change change = info.getChanges().get(i); - if (TransitionUtil.isOrderOnly(change)) continue; - if (filter.test(change)) { - out.add(TransitionUtil.newTarget( - change, info.getChanges().size() - i, info, t, leashMap)); - } - } - return out.toArray(new RemoteAnimationTarget[out.size()]); - } -} diff --git a/systemUI/anim/src/com/android/systemui/animation/ShadeInterpolation.kt b/systemUI/anim/src/com/android/systemui/animation/ShadeInterpolation.kt deleted file mode 100644 index b89a8b0e02..0000000000 --- a/systemUI/anim/src/com/android/systemui/animation/ShadeInterpolation.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.android.systemui.animation - -import android.util.MathUtils - -object ShadeInterpolation { - - /** - * Interpolate alpha for notification background scrim during shade expansion. - * - * @param fraction Shade expansion fraction - */ - @JvmStatic - fun getNotificationScrimAlpha(fraction: Float): Float { - val mappedFraction = MathUtils.constrainedMap(0f, 1f, 0f, 0.5f, fraction) - return interpolateEaseInOut(mappedFraction) - } - - /** - * Interpolate alpha for shade content during shade expansion. - * - * @param fraction Shade expansion fraction - */ - @JvmStatic - fun getContentAlpha(fraction: Float): Float { - val mappedFraction = MathUtils.constrainedMap(0f, 1f, 0.3f, 1f, fraction) - return interpolateEaseInOut(mappedFraction) - } - - private fun interpolateEaseInOut(fraction: Float): Float { - val mappedFraction = fraction * 1.2f - 0.2f - return if (mappedFraction <= 0) { - 0f - } else { - val oneMinusFrac = 1f - mappedFraction - (1f - 0.5f * (1f - Math.cos((3.14159f * oneMinusFrac * oneMinusFrac).toDouble()))) - .toFloat() - } - } -} diff --git a/systemUI/anim/src/com/android/systemui/animation/TextAnimator.kt b/systemUI/anim/src/com/android/systemui/animation/TextAnimator.kt deleted file mode 100644 index 978943ae7f..0000000000 --- a/systemUI/anim/src/com/android/systemui/animation/TextAnimator.kt +++ /dev/null @@ -1,409 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.animation - -import android.animation.Animator -import android.animation.AnimatorListenerAdapter -import android.animation.TimeInterpolator -import android.animation.ValueAnimator -import android.graphics.Canvas -import android.graphics.Typeface -import android.graphics.fonts.Font -import android.graphics.fonts.FontVariationAxis -import android.text.Layout -import android.util.LruCache -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 { - fun getTypefaceForVariant(fvar: String?): Typeface? - - companion object { - fun createVariantTypeface(baseTypeface: Typeface, fVar: String?): Typeface { - if (fVar.isNullOrEmpty()) { - return baseTypeface - } - - val axes = FontVariationAxis.fromFontVariationSettings(fVar) - ?.toMutableList() - ?: mutableListOf() - axes.removeIf { !baseTypeface.isSupportedAxes(it.getOpenTypeTagValue()) } - if (axes.isEmpty()) { - return baseTypeface - } - return Typeface.createFromTypefaceWithVariation(baseTypeface, axes) - } - } -} - -class TypefaceVariantCacheImpl( - var baseTypeface: Typeface, -) : TypefaceVariantCache { - private val cache = LruCache<String, Typeface>(TYPEFACE_CACHE_MAX_ENTRIES) - override fun getTypefaceForVariant(fvar: String?): Typeface? { - if (fvar == null) { - return baseTypeface - } - cache.get(fvar)?.let { - return it - } - - return TypefaceVariantCache.createVariantTypeface(baseTypeface, fvar).also { - cache.put(fvar, it) - } - } -} - -/** - * This class provides text animation between two styles. - * - * Currently this class can provide text style animation for text weight and text size. For example - * the simple view that draws text with animating text size is like as follows: - * <pre> <code> - * ``` - * class SimpleTextAnimation : View { - * @JvmOverloads constructor(...) - * - * private val layout: Layout = ... // Text layout, e.g. StaticLayout. - * - * // TextAnimator tells us when needs to be invalidate. - * private val animator = TextAnimator(layout) { invalidate() } - * - * override fun onDraw(canvas: Canvas) = animator.draw(canvas) - * - * // Change the text size with animation. - * fun setTextSize(sizePx: Float, animate: Boolean) { - * animator.setTextStyle("" /* unchanged fvar... */, sizePx, animate) - * } - * } - * ``` - * </code> </pre> - */ -class TextAnimator( - layout: Layout, - numberOfAnimationSteps: Int? = null, // Only do this number of discrete animation steps. - private val invalidateCallback: () -> Unit, -) { - var typefaceCache: TypefaceVariantCache = TypefaceVariantCacheImpl(layout.paint.typeface) - get() = field - set(value) { - field = value - textInterpolator.typefaceCache = value - } - - // 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() - } - ) - } - - 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() - } - - return animProgress - } - - sealed class PositionedGlyph { - /** Mutable X coordinate of the glyph position relative from drawing offset. */ - var x: Float = 0f - - /** Mutable Y coordinate of the glyph position relative from the baseline. */ - var y: Float = 0f - - /** The current line of text being drawn, in a multi-line TextView. */ - var lineNo: Int = 0 - - /** Mutable text size of the glyph in pixels. */ - var textSize: Float = 0f - - /** Mutable color of the glyph. */ - var color: Int = 0 - - /** Immutable character offset in the text that the current font run start. */ - abstract var runStart: Int - protected set - - /** Immutable run length of the font run. */ - abstract var runLength: Int - protected set - - /** Immutable glyph index of the font run. */ - abstract var glyphIndex: Int - protected set - - /** Immutable font instance for this font run. */ - abstract var font: Font - protected set - - /** Immutable glyph ID for this glyph. */ - abstract var glyphId: Int - protected set - } - - private val fontVariationUtils = FontVariationUtils() - - fun updateLayout(layout: Layout, textSize: Float = -1f) { - textInterpolator.layout = layout - - if (textSize >= 0) { - textInterpolator.targetPaint.textSize = textSize - textInterpolator.basePaint.textSize = textSize - textInterpolator.onTargetPaintModified() - textInterpolator.onBasePaintModified() - } - } - - fun isRunning(): Boolean { - return animator.isRunning - } - - /** - * GlyphFilter applied just before drawing to canvas for tweaking positions and text size. - * - * This callback is called for each glyphs just before drawing the glyphs. This function will be - * called with the intrinsic position, size, color, glyph ID and font instance. You can mutate - * the position, size and color for tweaking animations. Do not keep the reference of passed - * glyph object. The interpolator reuses that object for avoiding object allocations. - * - * Details: The text is drawn with font run units. The font run is a text segment that draws - * with the same font. The {@code runStart} and {@code runLimit} is a range of the font run in - * the text that current glyph is in. Once the font run is determined, the system will convert - * characters into glyph IDs. The {@code glyphId} is the glyph identifier in the font and {@code - * glyphIndex} is the offset of the converted glyph array. Please note that the {@code - * glyphIndex} is not a character index, because the character will not be converted to glyph - * one-by-one. If there are ligatures including emoji sequence, etc, the glyph ID may be - * composed from multiple characters. - * - * 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. - * - * Example: - * ``` - * private val glyphFilter: GlyphCallback = { glyph, progress -> - * val index = glyph.runStart - * val i = glyph.glyphIndex - * val moveAmount = 1.3f - * val sign = (-1 + 2 * ((i + index) % 2)) - * val turnProgress = if (progress < .5f) progress / 0.5f else (1.0f - progress) / 0.5f - * - * // You can modify (x, y) coordinates, textSize and color during animation. - * glyph.textSize += glyph.textSize * sign * moveAmount * turnProgress - * glyph.y += glyph.y * sign * moveAmount * turnProgress - * glyph.x += glyph.x * sign * moveAmount * turnProgress - * } - * ``` - */ - var glyphFilter: GlyphCallback? - get() = textInterpolator.glyphFilter - set(value) { - textInterpolator.glyphFilter = value - } - - fun draw(c: Canvas) = textInterpolator.draw(c) - - /** - * 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( - fvar: String?, - textSize: Float, - color: Int?, - strokeWidth: Float, - animate: Boolean, - duration: Long, - interpolator: TimeInterpolator?, - delay: Long, - onAnimationEnd: Runnable?, - updateLayoutOnFailure: Boolean, - ) { - try { - 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) - updateLayout(textInterpolator.layout) - 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!! - } -} diff --git a/systemUI/anim/src/com/android/systemui/animation/TextInterpolator.kt b/systemUI/anim/src/com/android/systemui/animation/TextInterpolator.kt deleted file mode 100644 index 02caeeddd7..0000000000 --- a/systemUI/anim/src/com/android/systemui/animation/TextInterpolator.kt +++ /dev/null @@ -1,531 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.systemui.animation - -import android.graphics.Canvas -import android.graphics.Paint -import android.graphics.fonts.Font -import android.graphics.fonts.FontVariationAxis -import android.graphics.text.PositionedGlyphs -import android.text.Layout -import android.text.TextPaint -import android.text.TextShaper -import android.util.MathUtils -import com.android.internal.graphics.ColorUtils -import java.lang.Math.max - -/** Provide text style linear interpolation for plain text. */ -class TextInterpolator( - layout: Layout, - var typefaceCache: TypefaceVariantCache, - numberOfAnimationSteps: Int? = null, -) { - /** - * Returns base paint used for interpolation. - * - * Once you modified the style parameters, you have to call reshapeText to recalculate base text - * layout. - * - * Do not bypass the cache and update the typeface or font variation directly. - * - * @return a paint object - */ - val basePaint = TextPaint(layout.paint) - - /** - * Returns target paint used for interpolation. - * - * Once you modified the style parameters, you have to call reshapeText to recalculate target - * text layout. - * - * Do not bypass the cache and update the typeface or font variation directly. - * - * @return a paint object - */ - val targetPaint = TextPaint(layout.paint) - - /** - * A class represents a single font run. - * - * A font run is a range that will be drawn with the same font. - */ - private data class FontRun( - val start: Int, // inclusive - val end: Int, // exclusive - var baseFont: Font, - var targetFont: Font - ) { - val length: Int - get() = end - start - } - - /** A class represents text layout of a single run. */ - private class Run( - val glyphIds: IntArray, - val baseX: FloatArray, // same length as glyphIds - val baseY: FloatArray, // same length as glyphIds - val targetX: FloatArray, // same length as glyphIds - val targetY: FloatArray, // same length as glyphIds - val fontRuns: List<FontRun> - ) - - /** A class represents text layout of a single line. */ - private class Line(val runs: List<Run>) - - private var lines = listOf<Line>() - private val fontInterpolator = FontInterpolator(numberOfAnimationSteps) - - // Recycling object for glyph drawing and tweaking. - private val tmpPaint = TextPaint() - private val tmpPaintForGlyph by lazy { TextPaint() } - private val tmpGlyph by lazy { MutablePositionedGlyph() } - // Will be extended for the longest font run if needed. - private var tmpPositionArray = FloatArray(20) - - /** - * The progress position of the interpolation. - * - * The 0f means the start state, 1f means the end state. - */ - var progress: Float = 0f - - /** - * The layout used for drawing text. - * - * Only non-styled text is supported. Even if the given layout is created from Spanned, the span - * information is not used. - * - * The paint objects used for interpolation are not changed by this method call. - * - * Note: disabling ligature is strongly recommended if you give extra letter spacing since they - * may be disjointed based on letter spacing value and cannot be interpolated. Animator will - * throw runtime exception if they cannot be interpolated. - */ - var layout: Layout = layout - get() = field - set(value) { - field = value - shapeText(value) - } - - var shapedText: String = "" - private set - - init { - // shapeText needs to be called after all members are initialized. - shapeText(layout) - } - - /** - * Recalculate internal text layout for interpolation. - * - * Whenever the target paint is modified, call this method to recalculate internal text layout - * used for interpolation. - */ - fun onTargetPaintModified() { - updatePositionsAndFonts(shapeText(layout, targetPaint), updateBase = false) - } - - /** - * Recalculate internal text layout for interpolation. - * - * Whenever the base paint is modified, call this method to recalculate internal text layout - * used for interpolation. - */ - fun onBasePaintModified() { - updatePositionsAndFonts(shapeText(layout, basePaint), updateBase = true) - } - - /** - * Rebase the base state to the middle of the interpolation. - * - * The text interpolator does not calculate all the text position by text shaper due to - * performance reasons. Instead, the text interpolator shape the start and end state and - * calculate text position of the middle state by linear interpolation. Due to this trick, the - * text positions of the middle state is likely different from the text shaper result. So, if - * you want to start animation from the middle state, you will see the glyph jumps due to this - * trick, i.e. the progress 0.5 of interpolation between weight 400 and 700 is different from - * text shape result of weight 550. - * - * After calling this method, do not call onBasePaintModified() since it reshape the text and - * update the base state. As in above notice, the text shaping result at current progress is - * different shaped result. By calling onBasePaintModified(), you may see the glyph jump. - * - * By calling this method, the progress will be reset to 0. - * - * This API is useful to continue animation from the middle of the state. For example, if you - * animate weight from 200 to 400, then if you want to move back to 200 at the half of the - * animation, it will look like - * <pre> <code> - * ``` - * val interp = TextInterpolator(layout) - * - * // Interpolate between weight 200 to 400. - * interp.basePaint.fontVariationSettings = "'wght' 200" - * interp.onBasePaintModified() - * interp.targetPaint.fontVariationSettings = "'wght' 400" - * interp.onTargetPaintModified() - * - * // animate - * val animator = ValueAnimator.ofFloat(1f).apply { - * addUpdaterListener { - * interp.progress = it.animateValue as Float - * } - * }.start() - * - * // Here, assuming you receive some event and want to start new animation from current - * // state. - * OnSomeEvent { - * animator.cancel() - * - * // start another animation from the current state. - * interp.rebase() // Use current state as base state. - * interp.targetPaint.fontVariationSettings = "'wght' 200" // set new target - * interp.onTargetPaintModified() // reshape target - * - * // Here the textInterpolator interpolate from 'wght' from 300 to 200 if the current - * // progress is 0.5 - * animator.start() - * } - * ``` - * </code> </pre> - */ - fun rebase() { - if (progress == 0f) { - return - } else if (progress == 1f) { - basePaint.set(targetPaint) - } else { - lerp(basePaint, targetPaint, progress, tmpPaint) - basePaint.set(tmpPaint) - } - - lines.forEach { line -> - line.runs.forEach { run -> - for (i in run.baseX.indices) { - run.baseX[i] = MathUtils.lerp(run.baseX[i], run.targetX[i], progress) - run.baseY[i] = MathUtils.lerp(run.baseY[i], run.targetY[i], progress) - } - run.fontRuns.forEach { fontRun -> - fontRun.baseFont = - fontInterpolator.lerp(fontRun.baseFont, fontRun.targetFont, progress) - val fvar = FontVariationAxis.toFontVariationSettings(fontRun.baseFont.axes) - basePaint.typeface = typefaceCache.getTypefaceForVariant(fvar) - } - } - } - - progress = 0f - } - - /** - * Draws interpolated text at the given progress. - * - * @param canvas a canvas. - */ - fun draw(canvas: Canvas) { - lerp(basePaint, targetPaint, progress, tmpPaint) - lines.forEachIndexed { lineNo, line -> - line.runs.forEach { run -> - canvas.save() - try { - // Move to drawing origin. - val origin = layout.getDrawOrigin(lineNo) - canvas.translate(origin, layout.getLineBaseline(lineNo).toFloat()) - - run.fontRuns.forEach { fontRun -> - drawFontRun(canvas, run, fontRun, lineNo, tmpPaint) - } - } finally { - canvas.restore() - } - } - } - } - - // Shape text with current paint parameters. - private fun shapeText(layout: Layout) { - val baseLayout = shapeText(layout, basePaint) - val targetLayout = shapeText(layout, targetPaint) - - require(baseLayout.size == targetLayout.size) { - "The new layout result has different line count." - } - - var maxRunLength = 0 - lines = - baseLayout.zip(targetLayout) { baseLine, targetLine -> - val runs = - baseLine.zip(targetLine) { base, target -> - require(base.glyphCount() == target.glyphCount()) { - "Inconsistent glyph count at line ${lines.size}" - } - - val glyphCount = base.glyphCount() - - // Good to recycle the array if the existing array can hold the new layout - // result. - val glyphIds = - IntArray(glyphCount) { - base.getGlyphId(it).also { baseGlyphId -> - require(baseGlyphId == target.getGlyphId(it)) { - "Inconsistent glyph ID at $it in line ${lines.size}" - } - } - } - - val baseX = FloatArray(glyphCount) { base.getGlyphX(it) } - val baseY = FloatArray(glyphCount) { base.getGlyphY(it) } - val targetX = FloatArray(glyphCount) { target.getGlyphX(it) } - val targetY = FloatArray(glyphCount) { target.getGlyphY(it) } - - // Calculate font runs - val fontRun = mutableListOf<FontRun>() - if (glyphCount != 0) { - var start = 0 - var baseFont = base.getFont(start) - var targetFont = target.getFont(start) - require(FontInterpolator.canInterpolate(baseFont, targetFont)) { - "Cannot interpolate font at $start ($baseFont vs $targetFont)" - } - - for (i in 1 until glyphCount) { - val nextBaseFont = base.getFont(i) - val nextTargetFont = target.getFont(i) - - if (baseFont !== nextBaseFont) { - require(targetFont !== nextTargetFont) { - "Base font has changed at $i but target font is unchanged." - } - // Font transition point. push run and reset context. - fontRun.add(FontRun(start, i, baseFont, targetFont)) - maxRunLength = max(maxRunLength, i - start) - baseFont = nextBaseFont - targetFont = nextTargetFont - start = i - require(FontInterpolator.canInterpolate(baseFont, targetFont)) { - "Cannot interpolate font at $start" + - " ($baseFont vs $targetFont)" - } - } else { // baseFont === nextBaseFont - require(targetFont === nextTargetFont) { - "Base font is unchanged at $i but target font has changed." - } - } - } - fontRun.add(FontRun(start, glyphCount, baseFont, targetFont)) - maxRunLength = max(maxRunLength, glyphCount - start) - } - Run(glyphIds, baseX, baseY, targetX, targetY, fontRun) - } - Line(runs) - } - - // Update float array used for drawing. - if (tmpPositionArray.size < maxRunLength * 2) { - tmpPositionArray = FloatArray(maxRunLength * 2) - } - } - - 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 - } - - var glyphFilter: GlyphCallback? = null - - // 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) - - val glyphFilter = glyphFilter - if (glyphFilter == null) { - for (i in run.start until run.end) { - tmpPositionArray[arrayIndex++] = - MathUtils.lerp(line.baseX[i], line.targetX[i], progress) - tmpPositionArray[arrayIndex++] = - MathUtils.lerp(line.baseY[i], line.targetY[i], progress) - } - c.drawGlyphs(line.glyphIds, run.start, tmpPositionArray, 0, run.length, font, paint) - return - } - - tmpGlyph.font = font - tmpGlyph.runStart = run.start - tmpGlyph.runLength = run.end - run.start - tmpGlyph.lineNo = lineNo - - tmpPaintForGlyph.set(paint) - var prevStart = run.start - - for (i in run.start until run.end) { - tmpGlyph.glyphIndex = i - tmpGlyph.glyphId = line.glyphIds[i] - tmpGlyph.x = MathUtils.lerp(line.baseX[i], line.targetX[i], progress) - tmpGlyph.y = MathUtils.lerp(line.baseY[i], line.targetY[i], progress) - tmpGlyph.textSize = paint.textSize - tmpGlyph.color = paint.color - - glyphFilter(tmpGlyph, progress) - - if (tmpGlyph.textSize != paint.textSize || tmpGlyph.color != paint.color) { - tmpPaintForGlyph.textSize = tmpGlyph.textSize - tmpPaintForGlyph.color = tmpGlyph.color - - c.drawGlyphs( - line.glyphIds, - prevStart, - tmpPositionArray, - 0, - i - prevStart, - font, - tmpPaintForGlyph - ) - prevStart = i - arrayIndex = 0 - } - - tmpPositionArray[arrayIndex++] = tmpGlyph.x - tmpPositionArray[arrayIndex++] = tmpGlyph.y - } - - c.drawGlyphs( - line.glyphIds, - prevStart, - tmpPositionArray, - 0, - run.end - prevStart, - font, - tmpPaintForGlyph - ) - } - - private fun updatePositionsAndFonts( - layoutResult: List<List<PositionedGlyphs>>, - updateBase: Boolean - ) { - // Update target positions with newly calculated text layout. - check(layoutResult.size == lines.size) { "The new layout result has different line count." } - - lines.zip(layoutResult) { line, runs -> - line.runs.zip(runs) { lineRun, newGlyphs -> - require(newGlyphs.glyphCount() == lineRun.glyphIds.size) { - "The new layout has different glyph count." - } - - lineRun.fontRuns.forEach { run -> - val newFont = newGlyphs.getFont(run.start) - for (i in run.start until run.end) { - require(newGlyphs.getGlyphId(run.start) == lineRun.glyphIds[run.start]) { - "The new layout has different glyph ID at ${run.start}" - } - require(newFont === newGlyphs.getFont(i)) { - "The new layout has different font run." + - " $newFont vs ${newGlyphs.getFont(i)} at $i" - } - } - - // The passing base font and target font is already interpolatable, so just - // check new font can be interpolatable with base font. - require(FontInterpolator.canInterpolate(newFont, run.baseFont)) { - "New font cannot be interpolated with existing font. $newFont," + - " ${run.baseFont}" - } - - if (updateBase) { - run.baseFont = newFont - } else { - run.targetFont = newFont - } - } - - if (updateBase) { - for (i in lineRun.baseX.indices) { - lineRun.baseX[i] = newGlyphs.getGlyphX(i) - lineRun.baseY[i] = newGlyphs.getGlyphY(i) - } - } else { - for (i in lineRun.baseX.indices) { - lineRun.targetX[i] = newGlyphs.getGlyphX(i) - lineRun.targetY[i] = newGlyphs.getGlyphY(i) - } - } - } - } - } - - // Linear interpolate the paint. - private fun lerp(from: Paint, to: Paint, progress: Float, out: Paint) { - out.set(from) - - // Currently only font size & colors are interpolated. - // TODO(172943390): Add other interpolation or support custom interpolator. - out.textSize = MathUtils.lerp(from.textSize, to.textSize, progress) - out.color = ColorUtils.blendARGB(from.color, to.color, progress) - out.strokeWidth = MathUtils.lerp(from.strokeWidth, to.strokeWidth, progress) - } - - // Shape the text and stores the result to out argument. - private fun shapeText(layout: Layout, paint: TextPaint): List<List<PositionedGlyphs>> { - var text = StringBuilder() - val out = mutableListOf<List<PositionedGlyphs>>() - for (lineNo in 0 until layout.lineCount) { // Shape all lines. - val lineStart = layout.getLineStart(lineNo) - val lineEnd = layout.getLineEnd(lineNo) - var count = lineEnd - lineStart - // Do not render the last character in the line if it's a newline and unprintable - val last = lineStart + count - 1 - if (last > lineStart && last < layout.text.length && layout.text[last] == '\n') { - count-- - } - - val runs = mutableListOf<PositionedGlyphs>() - TextShaper.shapeText( - layout.text, - lineStart, - count, - layout.textDirectionHeuristic, - paint - ) { _, _, glyphs, _ -> - runs.add(glyphs) - } - out.add(runs) - - if (lineNo > 0) { - text.append("\n") - } - text.append(layout.text.substring(lineStart, lineEnd)) - } - shapedText = text.toString() - return out - } -} - -private fun Layout.getDrawOrigin(lineNo: Int) = - if (getParagraphDirection(lineNo) == Layout.DIR_LEFT_TO_RIGHT) { - getLineLeft(lineNo) - } else { - getLineRight(lineNo) - } diff --git a/systemUI/anim/src/com/android/systemui/animation/TransitionAnimator.kt b/systemUI/anim/src/com/android/systemui/animation/TransitionAnimator.kt deleted file mode 100644 index 8e824e60d4..0000000000 --- a/systemUI/anim/src/com/android/systemui/animation/TransitionAnimator.kt +++ /dev/null @@ -1,576 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.animation - -import android.animation.Animator -import android.animation.AnimatorListenerAdapter -import android.animation.ValueAnimator -import android.content.Context -import android.graphics.PorterDuff -import android.graphics.PorterDuffXfermode -import android.graphics.drawable.GradientDrawable -import android.util.Log -import android.util.MathUtils -import android.view.View -import android.view.ViewGroup -import android.view.animation.Interpolator -import androidx.annotation.VisibleForTesting -import com.android.app.animation.Interpolators.LINEAR -import com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary -import java.util.concurrent.Executor -import kotlin.math.roundToInt - -private const val TAG = "TransitionAnimator" - -/** A base class to animate a window (activity or dialog) launch to or return from a view . */ -class TransitionAnimator( - private val mainExecutor: Executor, - private val timings: Timings, - private val interpolators: Interpolators, -) { - companion object { - internal const val DEBUG = false - private val SRC_MODE = PorterDuffXfermode(PorterDuff.Mode.SRC) - - /** - * 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 - * [duration]. - */ - @JvmStatic - fun getProgress( - timings: Timings, - linearProgress: Float, - delay: Long, - duration: Long - ): Float { - return MathUtils.constrain( - (linearProgress * timings.totalDuration - delay) / duration, - 0.0f, - 1.0f - ) - } - - internal fun checkReturnAnimationFrameworkFlag() { - check(returnAnimationFrameworkLibrary()) { - "isLaunching cannot be false when the returnAnimationFrameworkLibrary flag is " + - "disabled" - } - } - } - - private val transitionContainerLocation = IntArray(2) - private val cornerRadii = FloatArray(8) - - /** - * A controller that takes care of applying the animation to an expanding view. - * - * Note that all callbacks (onXXX methods) are all called on the main thread. - */ - interface Controller { - /** - * The container in which the view that started the animation will be animating together - * with the opening or closing window. - * - * This will be used to: - * - Get the associated [Context]. - * - Compute whether we are expanding to or contracting from fully above the transition - * container. - * - Get the overlay into which we put the window background layer, while the animating - * window is not visible (see [openingWindowSyncView]). - * - * This container can be changed to force this [Controller] to animate the expanding view - * inside a different location, for instance to ensure correct layering during the - * animation. - */ - var transitionContainer: ViewGroup - - /** Whether the animation being controlled is a launch or a return. */ - val isLaunching: Boolean - - /** - * If [isLaunching], the [View] with which the opening app window should be synchronized - * once it starts to be visible. Otherwise, the [View] with which the closing app window - * should be synchronized until it stops being visible. - * - * We will also move the window background layer to this view's overlay once the opening - * window is visible (if [isLaunching]), or from this view's overlay once the closing window - * stop being visible (if ![isLaunching]). - * - * If null, this will default to [transitionContainer]. - */ - val openingWindowSyncView: View? - get() = null - - /** - * Return the [State] of the view that will be animated. We will animate from this state to - * the final window state. - * - * Note: This state will be mutated and passed to [onTransitionAnimationProgress] during the - * animation. - */ - fun createAnimatorState(): State - - /** - * The animation started. This is typically used to initialize any additional resource - * needed for the animation. [isExpandingFullyAbove] will be true if the window is expanding - * fully above the [transitionContainer]. - */ - fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {} - - /** The animation made progress and the expandable view [state] should be updated. */ - fun onTransitionAnimationProgress(state: State, progress: Float, linearProgress: Float) {} - - /** - * The animation ended. This will be called *if and only if* [onTransitionAnimationStart] - * was called previously. This is typically used to clean up the resources initialized when - * the animation was started. - */ - fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {} - } - - /** The state of an expandable view during a [TransitionAnimator] animation. */ - open class State( - /** The position of the view in screen space coordinates. */ - var top: Int = 0, - var bottom: Int = 0, - var left: Int = 0, - var right: Int = 0, - var topCornerRadius: Float = 0f, - var bottomCornerRadius: Float = 0f - ) { - private val startTop = top - - val width: Int - get() = right - left - - val height: Int - get() = bottom - top - - open val topChange: Int - get() = top - startTop - - val centerX: Float - get() = left + width / 2f - - val centerY: Float - get() = top + height / 2f - - /** Whether the expanding view should be visible or hidden. */ - var visible: Boolean = true - } - - interface Animation { - /** Cancel the animation. */ - fun cancel() - } - - /** The timings (durations and delays) used by this animator. */ - data class Timings( - /** The total duration of the animation. */ - val totalDuration: Long, - - /** The time to wait before fading out the expanding content. */ - val contentBeforeFadeOutDelay: Long, - - /** The duration of the expanding content fade out. */ - val contentBeforeFadeOutDuration: Long, - - /** - * The time to wait before fading in the expanded content (usually an activity or dialog - * window). - */ - val contentAfterFadeInDelay: Long, - - /** The duration of the expanded content fade in. */ - val contentAfterFadeInDuration: Long - ) - - /** The interpolators used by this animator. */ - data class Interpolators( - /** The interpolator used for the Y position, width, height and corner radius. */ - val positionInterpolator: Interpolator, - - /** - * The interpolator used for the X position. This can be different than - * [positionInterpolator] to create an arc-path during the animation. - */ - val positionXInterpolator: Interpolator = positionInterpolator, - - /** The interpolator used when fading out the expanding content. */ - val contentBeforeFadeOutInterpolator: Interpolator, - - /** The interpolator used when fading in the expanded content. */ - val contentAfterFadeInInterpolator: Interpolator - ) - - /** - * Start a transition animation controlled by [controller] towards [endState]. An intermediary - * layer with [windowBackgroundColor] will fade in then (optionally) fade out above the - * expanding view, and should be the same background color as the opening (or closing) window. - * - * If [fadeWindowBackgroundLayer] is true, then this intermediary layer will fade out during the - * second half of the animation (if [Controller.isLaunching] or fade in during the first half of - * 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. - */ - fun startAnimation( - controller: Controller, - endState: State, - windowBackgroundColor: Int, - fadeWindowBackgroundLayer: Boolean = true, - drawHole: Boolean = false, - ): Animation { - 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 - // to hide the expanding view, then we fade it out with SRC mode to draw a hole in the - // transition container and reveal the opening window. - val windowBackgroundLayer = - GradientDrawable().apply { - setColor(windowBackgroundColor) - alpha = 0 - } - - val animator = - createAnimator( - controller, - endState, - windowBackgroundLayer, - fadeWindowBackgroundLayer, - drawHole - ) - animator.start() - - return object : Animation { - override fun cancel() { - animator.cancel() - } - } - } - - @VisibleForTesting - fun createAnimator( - controller: Controller, - endState: State, - windowBackgroundLayer: GradientDrawable, - fadeWindowBackgroundLayer: Boolean = true, - drawHole: Boolean = false - ): ValueAnimator { - val state = controller.createAnimatorState() - - // Start state. - val startTop = state.top - val startBottom = state.bottom - val startLeft = state.left - val startRight = state.right - val startCenterX = (startLeft + startRight) / 2f - val startWidth = startRight - startLeft - val startTopCornerRadius = state.topCornerRadius - val startBottomCornerRadius = state.bottomCornerRadius - - // End state. - var endTop = endState.top - var endBottom = endState.bottom - var endLeft = endState.left - var endRight = endState.right - var endCenterX = (endLeft + endRight) / 2f - var endWidth = endRight - endLeft - val endTopCornerRadius = endState.topCornerRadius - val endBottomCornerRadius = endState.bottomCornerRadius - - fun maybeUpdateEndState() { - if ( - endTop != endState.top || - endBottom != endState.bottom || - endLeft != endState.left || - endRight != endState.right - ) { - endTop = endState.top - endBottom = endState.bottom - endLeft = endState.left - endRight = endState.right - endCenterX = (endLeft + endRight) / 2f - endWidth = endRight - endLeft - } - } - - val transitionContainer = controller.transitionContainer - val isExpandingFullyAbove = isExpandingFullyAbove(transitionContainer, endState) - - // 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) { - 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) { - 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) - } - } - } - ) - - animator.addUpdateListener { animation -> - maybeUpdateEndState() - - // TODO(b/184121838): Use reverse interpolators to get the same path/arc as the non - // reversed animation. - val linearProgress = animation.animatedFraction - val progress = interpolators.positionInterpolator.getInterpolation(linearProgress) - val xProgress = interpolators.positionXInterpolator.getInterpolation(linearProgress) - - val xCenter = MathUtils.lerp(startCenterX, endCenterX, xProgress) - val halfWidth = MathUtils.lerp(startWidth, endWidth, progress) / 2f - - state.top = MathUtils.lerp(startTop, endTop, progress).roundToInt() - state.bottom = MathUtils.lerp(startBottom, endBottom, progress).roundToInt() - state.left = (xCenter - halfWidth).roundToInt() - state.right = (xCenter + halfWidth).roundToInt() - - state.topCornerRadius = - MathUtils.lerp(startTopCornerRadius, endTopCornerRadius, progress) - state.bottomCornerRadius = - MathUtils.lerp(startBottomCornerRadius, endBottomCornerRadius, progress) - - 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 ( - 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 = - if (movedBackgroundLayer) { - openingWindowSyncView!! - } else { - controller.transitionContainer - } - - applyStateToWindowBackgroundLayer( - windowBackgroundLayer, - state, - linearProgress, - container, - fadeWindowBackgroundLayer, - drawHole, - controller.isLaunching - ) - controller.onTransitionAnimationProgress(state, progress, linearProgress) - } - - return animator - } - - /** Return whether we are expanding fully above the [transitionContainer]. */ - internal fun isExpandingFullyAbove(transitionContainer: View, endState: State): Boolean { - transitionContainer.getLocationOnScreen(transitionContainerLocation) - return endState.top <= transitionContainerLocation[1] && - endState.bottom >= transitionContainerLocation[1] + transitionContainer.height && - endState.left <= transitionContainerLocation[0] && - endState.right >= transitionContainerLocation[0] + transitionContainer.width - } - - private fun applyStateToWindowBackgroundLayer( - drawable: GradientDrawable, - state: State, - linearProgress: Float, - transitionContainer: View, - fadeWindowBackgroundLayer: Boolean, - drawHole: Boolean, - isLaunching: Boolean - ) { - // Update position. - transitionContainer.getLocationOnScreen(transitionContainerLocation) - drawable.setBounds( - state.left - transitionContainerLocation[0], - state.top - transitionContainerLocation[1], - state.right - transitionContainerLocation[0], - state.bottom - transitionContainerLocation[1] - ) - - // Update radius. - cornerRadii[0] = state.topCornerRadius - cornerRadii[1] = state.topCornerRadius - cornerRadii[2] = state.topCornerRadius - cornerRadii[3] = state.topCornerRadius - cornerRadii[4] = state.bottomCornerRadius - cornerRadii[5] = state.bottomCornerRadius - cornerRadii[6] = state.bottomCornerRadius - cornerRadii[7] = state.bottomCornerRadius - drawable.cornerRadii = cornerRadii - - // 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 - ) - - 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( - fadeOutProgress - ) - drawable.alpha = (alpha * 0xFF).roundToInt() - - if (drawHole) { - drawable.setXfermode(SRC_MODE) - } - } else { - drawable.alpha = 0xFF - } - } else { - if (fadeInProgress < 1 && fadeWindowBackgroundLayer) { - val alpha = - interpolators.contentBeforeFadeOutInterpolator.getInterpolation(fadeInProgress) - drawable.alpha = (alpha * 0xFF).roundToInt() - - if (drawHole) { - drawable.setXfermode(SRC_MODE) - } - } else { - val fadeOutProgress = - getProgress( - timings, - linearProgress, - timings.contentAfterFadeInDelay, - timings.contentAfterFadeInDuration - ) - val alpha = - 1 - - interpolators.contentAfterFadeInInterpolator.getInterpolation( - fadeOutProgress - ) - drawable.alpha = (alpha * 0xFF).roundToInt() - drawable.setXfermode(null) - } - } - } -} diff --git a/systemUI/anim/src/com/android/systemui/animation/ViewDialogTransitionAnimatorController.kt b/systemUI/anim/src/com/android/systemui/animation/ViewDialogTransitionAnimatorController.kt deleted file mode 100644 index 307bc95941..0000000000 --- a/systemUI/anim/src/com/android/systemui/animation/ViewDialogTransitionAnimatorController.kt +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.animation - -import android.util.Log -import android.view.GhostView -import android.view.View -import android.view.ViewGroup -import android.view.ViewRootImpl -import com.android.internal.jank.InteractionJankMonitor - -private const val TAG = "ViewDialogTransitionAnimatorController" - -/** A [DialogTransitionAnimator.Controller] that can animate a [View] from/to a dialog. */ -class ViewDialogTransitionAnimatorController -internal constructor( - private val source: View, - override val cuj: DialogCuj?, -) : DialogTransitionAnimator.Controller { - override val viewRoot: ViewRootImpl? - get() = source.viewRootImpl - - override val sourceIdentity: Any = source - - override fun startDrawingInOverlayOf(viewGroup: ViewGroup) { - // Delay the calls to `source.setVisibility()` during the animation. This must be called - // before `GhostView.addGhost()` is called because the latter will change the *transition* - // visibility, which won't be blocked and will affect the normal View visibility that is - // saved by `setShouldBlockVisibilityChanges()` for a later restoration. - (source as? LaunchableView)?.setShouldBlockVisibilityChanges(true) - - // Create a temporary ghost of the source (which will make it invisible) and add it - // to the host dialog. - if (source.parent !is ViewGroup) { - // This should usually not happen, but let's make sure we don't call GhostView.addGhost - // and crash if the view was detached right before we started the animation. - Log.w(TAG, "source was detached right before drawing was moved to overlay") - } else { - GhostView.addGhost(source, viewGroup) - } - } - - override fun stopDrawingInOverlay() { - // Note: here we should remove the ghost from the overlay, but in practice this is - // already done by the transition controller created below. - - if (source is LaunchableView) { - // Make sure we allow the source to change its visibility again and restore its previous - // value. - source.setShouldBlockVisibilityChanges(false) - } else { - // We made the source invisible earlier, so let's make it visible again. - source.visibility = View.VISIBLE - } - } - - override fun createTransitionController(): TransitionAnimator.Controller { - val delegate = GhostedViewTransitionAnimatorController(source) - return object : TransitionAnimator.Controller by delegate { - override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) { - // Remove the temporary ghost added by [startDrawingInOverlayOf]. Another - // ghost (that ghosts only the source content, and not its background) will - // be added right after this by the delegate and will be animated. - GhostView.removeGhost(source) - delegate.onTransitionAnimationStart(isExpandingFullyAbove) - } - - override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) { - delegate.onTransitionAnimationEnd(isExpandingFullyAbove) - - // At this point the view visibility is restored by the delegate, so we delay the - // visibility changes again and make it invisible while the dialog is shown. - if (source is LaunchableView) { - source.setShouldBlockVisibilityChanges(true) - source.setTransitionVisibility(View.INVISIBLE) - } else { - source.visibility = View.INVISIBLE - } - } - } - } - - override fun createExitController(): TransitionAnimator.Controller { - return GhostedViewTransitionAnimatorController(source) - } - - override fun shouldAnimateExit(): Boolean { - // The source should be invisible by now, if it's not then something else changed - // its visibility and we probably don't want to run the animation. - if (source.visibility != View.INVISIBLE) { - return false - } - - return source.isAttachedToWindow && ((source.parent as? View)?.isShown ?: true) - } - - override fun onExitAnimationCancelled() { - if (source is LaunchableView) { - // Make sure we allow the source to change its visibility again. - source.setShouldBlockVisibilityChanges(false) - } else { - // If the view is invisible it's probably because of us, so we make it visible - // again. - if (source.visibility == View.INVISIBLE) { - source.visibility = View.VISIBLE - } - } - } - - override fun jankConfigurationBuilder(): InteractionJankMonitor.Configuration.Builder? { - val type = cuj?.cujType ?: return null - return InteractionJankMonitor.Configuration.Builder.withView(type, source) - } -} diff --git a/systemUI/anim/src/com/android/systemui/animation/ViewHierarchyAnimator.kt b/systemUI/anim/src/com/android/systemui/animation/ViewHierarchyAnimator.kt deleted file mode 100644 index 00d9056529..0000000000 --- a/systemUI/anim/src/com/android/systemui/animation/ViewHierarchyAnimator.kt +++ /dev/null @@ -1,1160 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.animation - -import android.animation.Animator -import android.animation.AnimatorListenerAdapter -import android.animation.ObjectAnimator -import android.animation.PropertyValuesHolder -import android.animation.ValueAnimator -import android.util.IntProperty -import android.view.View -import android.view.ViewGroup -import android.view.animation.Interpolator -import com.android.app.animation.Interpolators -import kotlin.math.max -import kotlin.math.min - -/** - * A class that allows changes in bounds within a view hierarchy to animate seamlessly between the - * start and end state. - */ -class ViewHierarchyAnimator { - companion object { - /** Default values for the animation. These can all be overridden at call time. */ - private const val DEFAULT_DURATION = 500L - private val DEFAULT_INTERPOLATOR = Interpolators.STANDARD - private val DEFAULT_ADDITION_INTERPOLATOR = Interpolators.STANDARD_DECELERATE - private val DEFAULT_REMOVAL_INTERPOLATOR = Interpolators.STANDARD_ACCELERATE - private val DEFAULT_FADE_IN_INTERPOLATOR = Interpolators.ALPHA_IN - - /** The properties used to animate the view bounds. */ - private val PROPERTIES = - mapOf( - Bound.LEFT to createViewProperty(Bound.LEFT), - Bound.TOP to createViewProperty(Bound.TOP), - Bound.RIGHT to createViewProperty(Bound.RIGHT), - Bound.BOTTOM to createViewProperty(Bound.BOTTOM) - ) - - private fun createViewProperty(bound: Bound): IntProperty<View> { - return object : IntProperty<View>(bound.label) { - override fun setValue(view: View, value: Int) { - setBound(view, bound, value) - } - - override fun get(view: View): Int { - return getBound(view, bound) ?: bound.getValue(view) - } - } - } - - /** - * Instruct the animator to watch for changes to the layout of [rootView] and its children - * and animate them. It uses the given [interpolator] and [duration]. - * - * If a new layout change happens while an animation is already in progress, the animation - * is updated to continue from the current values to the new end state. - * - * By default, child views whole layout changes are animated as well. However, this can be - * controlled by [animateChildren]. If children are included, a set of [excludedViews] can - * be passed. If any dependent view from [rootView] matches an entry in this set, changes to - * that view will not be animated. - * - * The animator continues to respond to layout changes until [stopAnimating] is called. - * - * Successive calls to this method override the previous settings ([interpolator] and - * [duration]). The changes take effect on the next animation. - * - * Returns true if the [rootView] is already visible and will be animated, false otherwise. - * To animate the addition of a view, see [animateAddition]. - */ - @JvmOverloads - fun animate( - rootView: View, - interpolator: Interpolator = DEFAULT_INTERPOLATOR, - duration: Long = DEFAULT_DURATION, - animateChildren: Boolean = true, - excludedViews: Set<View> = emptySet() - ): Boolean { - return animate( - rootView, - interpolator, - duration, - ephemeral = false, - animateChildren = animateChildren, - excludedViews = excludedViews - ) - } - - /** - * Like [animate], but only takes effect on the next layout update, then unregisters itself - * once the first animation is complete. - */ - @JvmOverloads - fun animateNextUpdate( - rootView: View, - interpolator: Interpolator = DEFAULT_INTERPOLATOR, - duration: Long = DEFAULT_DURATION, - animateChildren: Boolean = true, - excludedViews: Set<View> = emptySet() - ): Boolean { - return animate( - rootView, - interpolator, - duration, - ephemeral = true, - animateChildren = animateChildren, - excludedViews = excludedViews - ) - } - - private fun animate( - rootView: View, - interpolator: Interpolator, - duration: Long, - ephemeral: Boolean, - animateChildren: Boolean, - excludedViews: Set<View> = emptySet() - ): Boolean { - if ( - !occupiesSpace( - rootView.visibility, - rootView.left, - rootView.top, - rootView.right, - rootView.bottom - ) - ) { - return false - } - - val listener = createUpdateListener(interpolator, duration, ephemeral) - addListener( - rootView, - listener, - recursive = true, - animateChildren = animateChildren, - excludedViews = excludedViews - ) - return true - } - - /** - * Returns a new [View.OnLayoutChangeListener] that when called triggers a layout animation - * using [interpolator] and [duration]. - * - * If [ephemeral] is true, the listener is unregistered after the first animation. Otherwise - * it keeps listening for further updates. - */ - private fun createUpdateListener( - interpolator: Interpolator, - duration: Long, - ephemeral: Boolean - ): View.OnLayoutChangeListener { - return createListener(interpolator, duration, ephemeral) - } - - /** - * Instruct the animator to stop watching for changes to the layout of [rootView] and its - * children. - * - * Any animations already in progress continue until their natural conclusion. - */ - fun stopAnimating(rootView: View) { - recursivelyRemoveListener(rootView) - } - - /** - * Instruct the animator to watch for changes to the layout of [rootView] and its children, - * and animate the next time the hierarchy appears after not being visible. It uses the - * given [interpolator] and [duration]. - * - * The start state of the animation is controlled by [origin]. This value can be any of the - * four corners, any of the four edges, or the center of the view. If any margins are added - * on the side(s) of the origin, the translation of those margins can be included by - * specifying [includeMargins]. - * - * Returns true if the [rootView] is invisible and will be animated, false otherwise. To - * animate an already visible view, see [animate] and [animateNextUpdate]. - * - * Then animator unregisters itself once the first addition animation is complete. - * - * @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 successfully. Will not be run if the animation is cancelled. - */ - @JvmOverloads - fun animateAddition( - rootView: View, - origin: Hotspot = Hotspot.CENTER, - interpolator: Interpolator = DEFAULT_ADDITION_INTERPOLATOR, - duration: Long = DEFAULT_DURATION, - includeMargins: Boolean = false, - includeFadeIn: Boolean = false, - fadeInInterpolator: Interpolator = DEFAULT_FADE_IN_INTERPOLATOR, - onAnimationEnd: Runnable? = null, - ): Boolean { - if ( - occupiesSpace( - rootView.visibility, - rootView.left, - rootView.top, - rootView.right, - rootView.bottom - ) - ) { - return false - } - - val listener = - createAdditionListener( - origin, - interpolator, - duration, - ignorePreviousValues = !includeMargins, - onAnimationEnd, - ) - addListener(rootView, listener, recursive = true) - - if (!includeFadeIn) { - return true - } - - if (rootView is ViewGroup) { - // First, fade in the container view - val containerDuration = duration / 6 - createAndStartFadeInAnimator( - rootView, containerDuration, startDelay = 0, interpolator = fadeInInterpolator - ) - - // Then, fade in the child views - val childDuration = duration / 3 - for (i in 0 until rootView.childCount) { - val view = rootView.getChildAt(i) - createAndStartFadeInAnimator( - view, - childDuration, - // Wait until the container fades in before fading in the children - startDelay = containerDuration, - interpolator = fadeInInterpolator - ) - } - // For now, we don't recursively fade in additional sub views (e.g. grandchild - // views) since it hasn't been necessary, but we could add that functionality. - } else { - // Fade in the view during the first half of the addition - createAndStartFadeInAnimator( - rootView, - duration / 2, - startDelay = 0, - interpolator = fadeInInterpolator - ) - } - - return true - } - - /** - * Returns a new [View.OnLayoutChangeListener] that on the next call triggers a layout - * addition animation from the given [origin], using [interpolator] and [duration]. - * - * If [ignorePreviousValues] is true, the animation will only span the area covered by the - * new bounds. Otherwise it will include the margins between the previous and new bounds. - */ - private fun createAdditionListener( - origin: Hotspot, - interpolator: Interpolator, - duration: Long, - ignorePreviousValues: Boolean, - onAnimationEnd: Runnable? = null, - ): View.OnLayoutChangeListener { - return createListener( - interpolator, - duration, - ephemeral = true, - origin = origin, - ignorePreviousValues = ignorePreviousValues, - onAnimationEnd, - ) - } - - /** - * Returns a new [View.OnLayoutChangeListener] that when called triggers a layout animation - * using [interpolator] and [duration]. - * - * If [ephemeral] is true, the listener is unregistered after the first animation. Otherwise - * it keeps listening for further updates. - * - * [origin] specifies whether the start values should be determined by a hotspot, and - * [ignorePreviousValues] controls whether the previous values should be taken into account. - */ - private fun createListener( - interpolator: Interpolator, - duration: Long, - ephemeral: Boolean, - origin: Hotspot? = null, - ignorePreviousValues: Boolean = false, - onAnimationEnd: Runnable? = null, - ): View.OnLayoutChangeListener { - return object : View.OnLayoutChangeListener { - override fun onLayoutChange( - view: View?, - left: Int, - top: Int, - right: Int, - bottom: Int, - previousLeft: Int, - previousTop: Int, - previousRight: Int, - previousBottom: Int - ) { - if (view == null) return - - val startLeft = getBound(view, Bound.LEFT) ?: previousLeft - val startTop = getBound(view, Bound.TOP) ?: previousTop - val startRight = getBound(view, Bound.RIGHT) ?: previousRight - val startBottom = getBound(view, Bound.BOTTOM) ?: previousBottom - - (view.getTag(R.id.tag_animator) as? ObjectAnimator)?.cancel() - - if (!occupiesSpace(view.visibility, left, top, right, bottom)) { - setBound(view, Bound.LEFT, left) - setBound(view, Bound.TOP, top) - setBound(view, Bound.RIGHT, right) - setBound(view, Bound.BOTTOM, bottom) - return - } - - val startValues = - processStartValues( - origin, - left, - top, - right, - bottom, - startLeft, - startTop, - startRight, - startBottom, - ignorePreviousValues - ) - val endValues = - mapOf( - Bound.LEFT to left, - Bound.TOP to top, - Bound.RIGHT to right, - Bound.BOTTOM to bottom - ) - - val boundsToAnimate = mutableSetOf<Bound>() - if (startValues.getValue(Bound.LEFT) != left) boundsToAnimate.add(Bound.LEFT) - if (startValues.getValue(Bound.TOP) != top) boundsToAnimate.add(Bound.TOP) - if (startValues.getValue(Bound.RIGHT) != right) boundsToAnimate.add(Bound.RIGHT) - if (startValues.getValue(Bound.BOTTOM) != bottom) { - boundsToAnimate.add(Bound.BOTTOM) - } - - if (boundsToAnimate.isNotEmpty()) { - startAnimation( - view, - boundsToAnimate, - startValues, - endValues, - interpolator, - duration, - ephemeral, - onAnimationEnd, - ) - } - } - } - } - - /** - * Animates the removal of [rootView] and its children from the hierarchy. It uses the given - * [interpolator] and [duration]. - * - * The end state of the animation is controlled by [destination]. This value can be any of - * the four corners, any of the four edges, or the center of the view. If any margins are - * 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 - * successfully. Will not be run if the animation is cancelled. - */ - @JvmOverloads - fun animateRemoval( - rootView: View, - destination: Hotspot = Hotspot.CENTER, - interpolator: Interpolator = DEFAULT_REMOVAL_INTERPOLATOR, - duration: Long = DEFAULT_DURATION, - includeMargins: Boolean = false, - onAnimationEnd: Runnable? = null, - ): Boolean { - if ( - !occupiesSpace( - rootView.visibility, - rootView.left, - rootView.top, - rootView.right, - rootView.bottom - ) - ) { - return false - } - - val parent = rootView.parent as ViewGroup - - // Ensure that rootView's siblings animate nicely around the removal. - val listener = createUpdateListener(interpolator, duration, ephemeral = true) - for (i in 0 until parent.childCount) { - val child = parent.getChildAt(i) - if (child == rootView) continue - addListener(child, listener, recursive = false) - } - - val viewHasSiblings = parent.childCount > 1 - if (viewHasSiblings) { - // Remove the view so that a layout update is triggered for the siblings and they - // animate to their next position while the view's removal is also animating. - parent.removeView(rootView) - // By adding the view to the overlay, we can animate it while it isn't part of the - // view hierarchy. It is correctly positioned because we have its previous bounds, - // and we set them manually during the animation. - parent.overlay.add(rootView) - } - // If this view has no siblings, the parent view may shrink to (0,0) size and mess - // up the animation if we immediately remove the view. So instead, we just leave the - // view in the real hierarchy until the animation finishes. - - val endRunnable = Runnable { - if (viewHasSiblings) { - parent.overlay.remove(rootView) - } else { - parent.removeView(rootView) - } - onAnimationEnd?.run() - } - - val startValues = - mapOf( - Bound.LEFT to rootView.left, - Bound.TOP to rootView.top, - Bound.RIGHT to rootView.right, - Bound.BOTTOM to rootView.bottom - ) - val endValues = - processEndValuesForRemoval( - destination, - rootView, - rootView.left, - rootView.top, - rootView.right, - rootView.bottom, - includeMargins, - ) - - val boundsToAnimate = mutableSetOf<Bound>() - if (rootView.left != endValues.getValue(Bound.LEFT)) boundsToAnimate.add(Bound.LEFT) - if (rootView.top != endValues.getValue(Bound.TOP)) boundsToAnimate.add(Bound.TOP) - if (rootView.right != endValues.getValue(Bound.RIGHT)) boundsToAnimate.add(Bound.RIGHT) - if (rootView.bottom != endValues.getValue(Bound.BOTTOM)) { - boundsToAnimate.add(Bound.BOTTOM) - } - - startAnimation( - rootView, - boundsToAnimate, - startValues, - endValues, - interpolator, - duration, - ephemeral = true, - endRunnable, - ) - - if (rootView is ViewGroup) { - // Shift the children so they maintain a consistent position within the shrinking - // view. - shiftChildrenForRemoval(rootView, destination, endValues, interpolator, duration) - - // Fade out the children during the first half of the removal, so they don't clutter - // too much once the view becomes very small. Then we fade out the view itself, in - // case it has its own content and/or background. - val startAlphas = FloatArray(rootView.childCount) - for (i in 0 until rootView.childCount) { - startAlphas[i] = rootView.getChildAt(i).alpha - } - - val animator = ValueAnimator.ofFloat(1f, 0f) - animator.interpolator = Interpolators.ALPHA_OUT - animator.duration = duration / 2 - animator.addUpdateListener { animation -> - for (i in 0 until rootView.childCount) { - rootView.getChildAt(i).alpha = - (animation.animatedValue as Float) * startAlphas[i] - } - } - animator.addListener( - object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - rootView - .animate() - .alpha(0f) - .setInterpolator(Interpolators.ALPHA_OUT) - .setDuration(duration / 2) - .start() - } - } - ) - animator.start() - } else { - // Fade out the view during the second half of the removal. - rootView - .animate() - .alpha(0f) - .setInterpolator(Interpolators.ALPHA_OUT) - .setDuration(duration / 2) - .setStartDelay(duration / 2) - .start() - } - - return true - } - - /** - * Animates the children of [rootView] so that its layout remains internally consistent as - * it shrinks towards [destination] and changes its bounds to [endValues]. - * - * Uses [interpolator] and [duration], which should match those of the removal animation. - */ - private fun shiftChildrenForRemoval( - rootView: ViewGroup, - destination: Hotspot, - endValues: Map<Bound, Int>, - interpolator: Interpolator, - duration: Long - ) { - for (i in 0 until rootView.childCount) { - val child = rootView.getChildAt(i) - val childStartValues = - mapOf( - Bound.LEFT to child.left, - Bound.TOP to child.top, - Bound.RIGHT to child.right, - Bound.BOTTOM to child.bottom - ) - val childEndValues = - processChildEndValuesForRemoval( - destination, - child.left, - child.top, - child.right, - child.bottom, - endValues.getValue(Bound.RIGHT) - endValues.getValue(Bound.LEFT), - endValues.getValue(Bound.BOTTOM) - endValues.getValue(Bound.TOP) - ) - - val boundsToAnimate = mutableSetOf<Bound>() - if (child.left != endValues.getValue(Bound.LEFT)) boundsToAnimate.add(Bound.LEFT) - if (child.top != endValues.getValue(Bound.TOP)) boundsToAnimate.add(Bound.TOP) - if (child.right != endValues.getValue(Bound.RIGHT)) boundsToAnimate.add(Bound.RIGHT) - if (child.bottom != endValues.getValue(Bound.BOTTOM)) { - boundsToAnimate.add(Bound.BOTTOM) - } - - startAnimation( - child, - boundsToAnimate, - childStartValues, - childEndValues, - interpolator, - duration, - ephemeral = true - ) - } - } - - /** - * Returns whether the given [visibility] and bounds are consistent with a view being a - * contributing part of the hierarchy. - */ - private fun occupiesSpace( - visibility: Int, - left: Int, - top: Int, - right: Int, - bottom: Int - ): Boolean { - return visibility != View.GONE && left != right && top != bottom - } - - /** - * Computes the actual starting values based on the requested [origin] and on - * [ignorePreviousValues]. - * - * If [origin] is null, the resolved start values will be the same as those passed in, or - * the same as the new values if [ignorePreviousValues] is true. If [origin] is not null, - * the start values are resolved based on it, and [ignorePreviousValues] controls whether or - * not newly introduced margins are included. - * - * Base case - * ``` - * 1) origin=TOP - * x---------x x---------x x---------x x---------x x---------x - * x---------x | | | | | | - * -> -> x---------x -> | | -> | | - * x---------x | | - * x---------x - * 2) origin=BOTTOM_LEFT - * x---------x - * x-------x | | - * -> -> x----x -> | | -> | | - * x--x | | | | | | - * x x--x x----x x-------x x---------x - * 3) origin=CENTER - * x---------x - * x-----x x-------x | | - * x -> x---x -> | | -> | | -> | | - * 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 - * x--x - * x--x x----x - * -> -> | | -> x------x - * x----x | | - * | | - * x------x - * ``` - */ - private fun processStartValues( - origin: Hotspot?, - newLeft: Int, - newTop: Int, - newRight: Int, - newBottom: Int, - previousLeft: Int, - previousTop: Int, - previousRight: Int, - previousBottom: Int, - ignorePreviousValues: Boolean - ): Map<Bound, Int> { - val startLeft = if (ignorePreviousValues) newLeft else previousLeft - val startTop = if (ignorePreviousValues) newTop else previousTop - val startRight = if (ignorePreviousValues) newRight else previousRight - val startBottom = if (ignorePreviousValues) newBottom else previousBottom - - var left = startLeft - var top = startTop - var right = startRight - var bottom = startBottom - - if (origin != null) { - left = - when (origin) { - Hotspot.CENTER -> (newLeft + newRight) / 2 - Hotspot.BOTTOM_LEFT, - Hotspot.LEFT, - Hotspot.TOP_LEFT -> min(startLeft, newLeft) - Hotspot.TOP, - Hotspot.BOTTOM -> newLeft - Hotspot.TOP_RIGHT, - Hotspot.RIGHT, - Hotspot.BOTTOM_RIGHT -> max(startRight, newRight) - } - top = - when (origin) { - Hotspot.CENTER -> (newTop + newBottom) / 2 - Hotspot.TOP_LEFT, - Hotspot.TOP, - Hotspot.TOP_RIGHT -> min(startTop, newTop) - Hotspot.LEFT, - Hotspot.RIGHT -> newTop - Hotspot.BOTTOM_RIGHT, - Hotspot.BOTTOM, - Hotspot.BOTTOM_LEFT -> max(startBottom, newBottom) - } - right = - when (origin) { - Hotspot.CENTER -> (newLeft + newRight) / 2 - Hotspot.TOP_RIGHT, - Hotspot.RIGHT, - Hotspot.BOTTOM_RIGHT -> max(startRight, newRight) - Hotspot.TOP, - Hotspot.BOTTOM -> newRight - Hotspot.BOTTOM_LEFT, - Hotspot.LEFT, - Hotspot.TOP_LEFT -> min(startLeft, newLeft) - } - bottom = - when (origin) { - Hotspot.CENTER -> (newTop + newBottom) / 2 - Hotspot.BOTTOM_RIGHT, - Hotspot.BOTTOM, - Hotspot.BOTTOM_LEFT -> max(startBottom, newBottom) - Hotspot.LEFT, - Hotspot.RIGHT -> newBottom - Hotspot.TOP_LEFT, - Hotspot.TOP, - Hotspot.TOP_RIGHT -> min(startTop, newTop) - } - } - - return mapOf( - Bound.LEFT to left, - Bound.TOP to top, - Bound.RIGHT to right, - Bound.BOTTOM to bottom - ) - } - - /** - * Computes a removal animation's end values based on the requested [destination] and the - * view's starting bounds. - * - * Examples: - * ``` - * 1) destination=TOP - * x---------x x---------x x---------x x---------x x---------x - * | | | | | | x---------x - * | | -> | | -> x---------x -> -> - * | | x---------x - * x---------x - * 2) destination=BOTTOM_LEFT - * x---------x - * | | x-------x - * | | -> | | -> x----x -> -> - * | | | | | | x--x - * x---------x x-------x x----x x--x x - * 3) destination=CENTER - * x---------x - * | | x-------x x-----x - * | | -> | | -> | | -> x---x -> x - * | | x-------x x-----x - * x---------x - * 4) destination=TOP, includeMargins=true (and view has large top margin) - * x---------x - * x---------x - * x---------x x---------x - * x---------x | | - * x---------x | | x---------x - * | | | | - * | | -> x---------x -> -> -> - * | | - * x---------x - * ``` - */ - private fun processEndValuesForRemoval( - destination: Hotspot, - rootView: View, - left: Int, - top: Int, - right: Int, - bottom: Int, - includeMargins: Boolean = false, - ): Map<Bound, Int> { - val marginAdjustment = - 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 - ) - } 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 - val endTop = top - marginAdjustment.top - val endRight = right + marginAdjustment.right - val endBottom = bottom + marginAdjustment.bottom - - // For the below calculations: We need to ensure that the destination bound and the - // bound *opposite* to the destination bound end at the same value, to ensure that the - // view has size 0 for that dimension. - // For example, - // - If destination=TOP, then endTop == endBottom. Left and right stay the same. - // - If destination=RIGHT, then endRight == endLeft. Top and bottom stay the same. - // - 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, - ) - } - } - - /** - * Computes the end values for the child of a view being removed, based on the child's - * starting bounds, the removal's [destination], and the [parentWidth] and [parentHeight]. - * - * The end values always represent the child's position after it has been translated so that - * its center is at the [destination]. - * - * Examples: - * ``` - * 1) destination=TOP - * The child maintains its left and right positions, but is shifted up so that its - * center is on the parent's end top edge. - * 2) destination=BOTTOM_LEFT - * The child shifts so that its center is on the parent's end bottom left corner. - * 3) destination=CENTER - * The child shifts so that its own center is on the parent's end center. - * ``` - */ - private fun processChildEndValuesForRemoval( - destination: Hotspot, - left: Int, - top: Int, - right: Int, - bottom: Int, - parentWidth: Int, - parentHeight: Int - ): Map<Bound, Int> { - val halfWidth = (right - left) / 2 - val halfHeight = (bottom - top) / 2 - - val endLeft = - when (destination) { - Hotspot.CENTER -> (parentWidth / 2) - halfWidth - Hotspot.BOTTOM_LEFT, - Hotspot.LEFT, - Hotspot.TOP_LEFT -> -halfWidth - Hotspot.TOP_RIGHT, - Hotspot.RIGHT, - Hotspot.BOTTOM_RIGHT -> parentWidth - halfWidth - Hotspot.TOP, - Hotspot.BOTTOM -> left - } - val endTop = - when (destination) { - Hotspot.CENTER -> (parentHeight / 2) - halfHeight - Hotspot.TOP_LEFT, - Hotspot.TOP, - Hotspot.TOP_RIGHT -> -halfHeight - Hotspot.BOTTOM_RIGHT, - Hotspot.BOTTOM, - Hotspot.BOTTOM_LEFT -> parentHeight - halfHeight - Hotspot.LEFT, - Hotspot.RIGHT -> top - } - val endRight = - when (destination) { - Hotspot.CENTER -> (parentWidth / 2) + halfWidth - Hotspot.TOP_RIGHT, - Hotspot.RIGHT, - Hotspot.BOTTOM_RIGHT -> parentWidth + halfWidth - Hotspot.BOTTOM_LEFT, - Hotspot.LEFT, - Hotspot.TOP_LEFT -> halfWidth - Hotspot.TOP, - Hotspot.BOTTOM -> right - } - val endBottom = - when (destination) { - Hotspot.CENTER -> (parentHeight / 2) + halfHeight - Hotspot.BOTTOM_RIGHT, - Hotspot.BOTTOM, - Hotspot.BOTTOM_LEFT -> parentHeight + halfHeight - Hotspot.TOP_LEFT, - Hotspot.TOP, - Hotspot.TOP_RIGHT -> halfHeight - Hotspot.LEFT, - Hotspot.RIGHT -> bottom - } - - return mapOf( - Bound.LEFT to endLeft, - Bound.TOP to endTop, - Bound.RIGHT to endRight, - Bound.BOTTOM to endBottom - ) - } - - private fun addListener( - view: View, - listener: View.OnLayoutChangeListener, - recursive: Boolean = false, - animateChildren: Boolean = true, - excludedViews: Set<View> = emptySet() - ) { - if (excludedViews.contains(view)) return - - // Make sure that only one listener is active at a time. - val previousListener = view.getTag(R.id.tag_layout_listener) - if (previousListener != null && previousListener is View.OnLayoutChangeListener) { - view.removeOnLayoutChangeListener(previousListener) - } - - view.addOnLayoutChangeListener(listener) - view.setTag(R.id.tag_layout_listener, listener) - if (animateChildren && view is ViewGroup && recursive) { - for (i in 0 until view.childCount) { - addListener( - view.getChildAt(i), - listener, - recursive = true, - animateChildren = animateChildren, - excludedViews = excludedViews - ) - } - } - } - - private fun recursivelyRemoveListener(view: View) { - val listener = view.getTag(R.id.tag_layout_listener) - if (listener != null && listener is View.OnLayoutChangeListener) { - view.setTag(R.id.tag_layout_listener, null /* tag */) - view.removeOnLayoutChangeListener(listener) - } - - if (view is ViewGroup) { - for (i in 0 until view.childCount) { - recursivelyRemoveListener(view.getChildAt(i)) - } - } - } - - private fun getBound(view: View, bound: Bound): Int? { - return view.getTag(bound.overrideTag) as? Int - } - - private fun setBound(view: View, bound: Bound, value: Int) { - view.setTag(bound.overrideTag, value) - bound.setValue(view, value) - } - - /** - * Initiates the animation of the requested [bounds] between [startValues] and [endValues] - * by creating the animator, registering it with the [view], and starting it using - * [interpolator] and [duration]. - * - * If [ephemeral] is true, the layout change listener is unregistered at the end of the - * animation, so no more animations happen. - */ - private fun startAnimation( - view: View, - bounds: Set<Bound>, - startValues: Map<Bound, Int>, - endValues: Map<Bound, Int>, - interpolator: Interpolator, - duration: Long, - ephemeral: Boolean, - onAnimationEnd: Runnable? = null, - ) { - val propertyValuesHolders = - buildList { - bounds.forEach { bound -> - add( - PropertyValuesHolder.ofInt( - PROPERTIES[bound], - startValues.getValue(bound), - endValues.getValue(bound) - ) - ) - } - } - .toTypedArray() - - (view.getTag(R.id.tag_animator) as? ObjectAnimator)?.cancel() - - val animator = ObjectAnimator.ofPropertyValuesHolder(view, *propertyValuesHolders) - animator.interpolator = interpolator - animator.duration = duration - animator.addListener( - object : AnimatorListenerAdapter() { - var cancelled = false - - override fun onAnimationEnd(animation: Animator) { - view.setTag(R.id.tag_animator, null /* tag */) - bounds.forEach { view.setTag(it.overrideTag, null /* tag */) } - - // When an animation is cancelled, a new one might be taking over. We - // shouldn't unregister the listener yet. - if (ephemeral && !cancelled) { - // The duration is the same for the whole hierarchy, so it's safe to - // remove the listener recursively. We do this because some descendant - // views might not change bounds, and therefore not animate and leak the - // listener. - recursivelyRemoveListener(view) - } - if (!cancelled) { - onAnimationEnd?.run() - } - } - - override fun onAnimationCancel(animation: Animator) { - cancelled = true - } - } - ) - - bounds.forEach { bound -> setBound(view, bound, startValues.getValue(bound)) } - - view.setTag(R.id.tag_animator, animator) - animator.start() - } - - private fun createAndStartFadeInAnimator( - view: View, - duration: Long, - startDelay: Long, - 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 */) - } - }) - - (view.getTag(R.id.tag_alpha_animator) as? ObjectAnimator)?.cancel() - view.setTag(R.id.tag_alpha_animator, animator) - animator.start() - } - } - - /** An enum used to determine the origin of addition animations. */ - enum class Hotspot { - CENTER, - LEFT, - TOP_LEFT, - TOP, - TOP_RIGHT, - RIGHT, - BOTTOM_RIGHT, - BOTTOM, - BOTTOM_LEFT - } - - private enum class Bound(val label: String, val overrideTag: Int) { - LEFT("left", R.id.tag_override_left) { - override fun setValue(view: View, value: Int) { - view.left = value - } - - override fun getValue(view: View): Int { - return view.left - } - }, - TOP("top", R.id.tag_override_top) { - override fun setValue(view: View, value: Int) { - view.top = value - } - - override fun getValue(view: View): Int { - return view.top - } - }, - RIGHT("right", R.id.tag_override_right) { - override fun setValue(view: View, value: Int) { - view.right = value - } - - override fun getValue(view: View): Int { - return view.right - } - }, - BOTTOM("bottom", R.id.tag_override_bottom) { - override fun setValue(view: View, value: Int) { - view.bottom = value - } - - override fun getValue(view: View): Int { - return view.bottom - } - }; - - 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, - ) -} diff --git a/systemUI/anim/src/com/android/systemui/animation/ViewRootSync.kt b/systemUI/anim/src/com/android/systemui/animation/ViewRootSync.kt deleted file mode 100644 index e4f6db57f6..0000000000 --- a/systemUI/anim/src/com/android/systemui/animation/ViewRootSync.kt +++ /dev/null @@ -1,43 +0,0 @@ -package com.android.systemui.animation - -import android.view.View -import android.window.SurfaceSyncGroup - -/** A util class to synchronize 2 view roots. */ -// TODO(b/200284684): Remove this class. -object ViewRootSync { - - /** - * Synchronize the next draw between the view roots of [view] and [otherView], then run [then]. - * - * Note that in some cases, the synchronization might not be possible (e.g. WM consumed the next - * transactions) or disabled (temporarily, on low ram devices). In this case, [then] will be - * called without synchronizing. - */ - fun synchronizeNextDraw(view: View, otherView: View, then: () -> Unit) { - if ( - !view.isAttachedToWindow || - view.viewRootImpl == null || - !otherView.isAttachedToWindow || - otherView.viewRootImpl == null || - view.viewRootImpl == otherView.viewRootImpl - ) { - // No need to synchronize if either the touch surface or dialog view is not attached - // to a window. - then() - return - } - - val syncGroup = SurfaceSyncGroup("SysUIAnimation") - syncGroup.addSyncCompleteCallback(view.context.mainExecutor) { then() } - syncGroup.add(view.rootSurfaceControl, null /* runnable */) - syncGroup.add(otherView.rootSurfaceControl, null /* runnable */) - syncGroup.markSyncReady() - } - - /** A Java-friendly API for [synchronizeNextDraw]. */ - @JvmStatic - fun synchronizeNextDraw(view: View, otherView: View, then: Runnable) { - synchronizeNextDraw(view, otherView, then::run) - } -} diff --git a/systemUI/anim/src/com/android/systemui/animation/ViewTransitionRegistry.kt b/systemUI/anim/src/com/android/systemui/animation/ViewTransitionRegistry.kt deleted file mode 100644 index 882ff3b61b..0000000000 --- a/systemUI/anim/src/com/android/systemui/animation/ViewTransitionRegistry.kt +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright (C) 2025 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS 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 android.os.Trace -import android.view.View -import java.lang.ref.WeakReference - -/** - * A registry to temporarily store the view being transitioned into a Dialog (using - * [DialogTransitionAnimator]) or an Activity (using [ActivityTransitionAnimator]). - */ -class ViewTransitionRegistry : IViewTransitionRegistry { - - /** - * A map of a unique token to a WeakReference of the View being transitioned. WeakReference - * ensures that Views are garbage collected whenever they become eligible and avoid any - * memory leaks. - */ - private val registry by lazy { mutableMapOf<ViewTransitionToken, ViewTransitionInfo>() } - - /** - * A [View.OnAttachStateChangeListener] to be attached to all views stored in the registry to - * ensure that views (and their corresponding entry) is automatically removed when the view is - * detached from the Window. - */ - private val listener by lazy { - object : View.OnAttachStateChangeListener { - override fun onViewAttachedToWindow(view: View) { - // empty - } - - override fun onViewDetachedFromWindow(view: View) { - // if view is detached from window, remove it from registry irrespective of number - // of reference held by clients/user of this registry - getViewToken(view)?.let { token -> remove(token) } - } - } - } - - /** - * Creates an entry of a unique token mapped to transitioning [view] in the registry. - * - * @param view view undergoing transitions - * @return unique token mapped to the view being registered - */ - override fun register(view: View): ViewTransitionToken { - // if view being registered is already present in the registry and has a unique token - // assigned to it, reuse that token - getViewToken(view)?.let { token -> - registry[token]?.let { info -> info.viewRefCount += 1 } - return token - } - - // token embedded as a view tag enables to use a single listener for all views - val token = ViewTransitionToken(view::class.java) - view.setTag(R.id.tag_view_transition_token, token) - view.addOnAttachStateChangeListener(listener) - registry[token] = ViewTransitionInfo(WeakReference(view)) - onRegistryUpdate() - - return token - } - - /** - * Unregisters a view mapped to the unique [token] in the registry. This will either remove the - * entry entirely from registry (if the reference count of the associated view reached zero) or - * will decrement the reference count of the associated view in the registry. - * - * @param token unique token associated with the transitioning view - */ - override fun unregister(token: ViewTransitionToken) { - registry[token]?.let { info -> - info.viewRefCount -= 1 - if (info.viewRefCount == 0) { - remove(token) - } - } - } - - /** - * Removes the entry associated with the unique [token] in the registry. - * - * @param token unique token associated with the transitioning view - */ - private fun remove(token: ViewTransitionToken) { - registry.remove(token)?.let { removedInfo -> - removedInfo.viewRef.get()?.let { view -> - view.removeOnAttachStateChangeListener(listener) - view.setTag(R.id.tag_view_transition_token, null) - } - removedInfo.viewRef.clear() - onRegistryUpdate() - } - } - - /** - * Access a view from registry using unique [token] associated with it. - * WARNING - this returns a StrongReference to the View stored in the registry - */ - override fun getView(token: ViewTransitionToken): View? { - return registry[token]?.viewRef?.get() - } - - /** - * Return token mapped to the [view], if it is present in the registry. - * - * @param view the transitioning view whose token we are requesting - * @return token associated with the [view] if present, else null - */ - override fun getViewToken(view: View): ViewTransitionToken? { - // extract token from the view if it is embedded inside it as a tag - val token = view.getTag(R.id.tag_view_transition_token) as? ViewTransitionToken - - // this should never really happen, but if token embedded inside the view as tag, doesn't - // point to a valid view in the registry, remove that token (tag) from the view and registry - if (token != null && getView(token) == null) { - view.setTag(R.id.tag_view_transition_token, null) - remove(token) - return null - } - - return token - } - - /** Event call to run on registry update (on both [register] and [unregister]). */ - override fun onRegistryUpdate() { - emitCountForTrace() - } - - /** - * Utility function to emit number of non-null views in the registry whenever the registry is - * updated (via [register] or [unregister]). - */ - private fun emitCountForTrace() { - Trace.setCounter("transition_registry_view_count", registry.count().toLong()) - } - - /** Information associated with each transitioning view in the registry. */ - private data class ViewTransitionInfo( - - /** View being transitioned */ - val viewRef: WeakReference<View>, - - /** Count of clients (users of this registry) referencing same transitioning view */ - var viewRefCount: Int = 1 - ) - - companion object { - val instance by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { ViewTransitionRegistry() } - } -} diff --git a/systemUI/anim/src/com/android/systemui/animation/ViewTransitionToken.kt b/systemUI/anim/src/com/android/systemui/animation/ViewTransitionToken.kt deleted file mode 100644 index e011df0150..0000000000 --- a/systemUI/anim/src/com/android/systemui/animation/ViewTransitionToken.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2025 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS 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 java.util.UUID - -/** - * A token uniquely mapped to a View in [ViewTransitionRegistry]. This token is guaranteed to be - * unique as timestamp is appended to the token string - * - * @constructor creates an instance of [ViewTransitionToken] with token as "UUID" or - * "ClassName_UUID" - * - * @property token String value of a unique token - */ -@JvmInline -value class ViewTransitionToken private constructor(val token: String) { - constructor() : this(token = UUID.randomUUID().toString()) - constructor(clazz: Class<*>) : this(token = clazz.simpleName + "_${UUID.randomUUID()}") -} diff --git a/systemUI/anim/src/com/android/systemui/animation/back/BackAnimationSpec.kt b/systemUI/anim/src/com/android/systemui/animation/back/BackAnimationSpec.kt deleted file mode 100644 index 6c982a0450..0000000000 --- a/systemUI/anim/src/com/android/systemui/animation/back/BackAnimationSpec.kt +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.animation.back - -import android.util.DisplayMetrics -import android.view.animation.Interpolator -import android.window.BackEvent -import com.android.app.animation.Interpolators -import com.android.systemui.util.dpToPx - -/** Used to convert [BackEvent] into a [BackTransformation]. */ -fun interface BackAnimationSpec { - - /** Computes transformation based on a [backEvent] and sets it to [result]. */ - fun getBackTransformation( - backEvent: BackEvent, - progressY: Float, // TODO(b/265060720): Remove progressY. Could be retrieved from backEvent - result: BackTransformation, - ) - - companion object -} - -/** Create a [BackAnimationSpec] from [displayMetrics] and design specs. */ -fun BackAnimationSpec.Companion.createFloatingSurfaceAnimationSpec( - displayMetricsProvider: () -> DisplayMetrics, - maxMarginXdp: Float, - maxMarginYdp: Float, - minScale: Float, - translateXEasing: Interpolator = Interpolators.BACK_GESTURE, - translateYEasing: Interpolator = Interpolators.LINEAR, - scaleEasing: Interpolator = Interpolators.BACK_GESTURE, -): BackAnimationSpec { - return BackAnimationSpec { backEvent, progressY, result -> - val displayMetrics = displayMetricsProvider() - val screenWidthPx = displayMetrics.widthPixels - val screenHeightPx = displayMetrics.heightPixels - - val maxMarginXPx = maxMarginXdp.dpToPx(displayMetrics) - val maxMarginYPx = maxMarginYdp.dpToPx(displayMetrics) - val maxTranslationXByScale = (screenWidthPx - screenWidthPx * minScale) / 2 - val maxTranslationX = maxTranslationXByScale - maxMarginXPx - val maxTranslationYByScale = (screenHeightPx - screenHeightPx * minScale) / 2 - val maxTranslationY = maxTranslationYByScale - maxMarginYPx - val minScaleReversed = 1f - minScale - - val direction = if (backEvent.swipeEdge == BackEvent.EDGE_LEFT) 1 else -1 - val progressX = backEvent.progress - - val ratioTranslateX = translateXEasing.getInterpolation(progressX) - val ratioTranslateY = translateYEasing.getInterpolation(progressY) - val ratioScale = scaleEasing.getInterpolation(progressX) - - result.apply { - translateX = ratioTranslateX * direction * maxTranslationX - translateY = ratioTranslateY * maxTranslationY - scale = 1f - (ratioScale * minScaleReversed) - } - } -} diff --git a/systemUI/anim/src/com/android/systemui/animation/back/BackAnimationSpecForSysUi.kt b/systemUI/anim/src/com/android/systemui/animation/back/BackAnimationSpecForSysUi.kt deleted file mode 100644 index 536f2972ab..0000000000 --- a/systemUI/anim/src/com/android/systemui/animation/back/BackAnimationSpecForSysUi.kt +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.animation.back - -import android.util.DisplayMetrics - -/** - * SysUI transitions - Dismiss app (ST1) Return to launching surface or place of origin - * https://carbon.googleplex.com/predictive-back-for-apps/pages/st-1-dismiss-app - */ -fun BackAnimationSpec.Companion.dismissAppForSysUi( - displayMetricsProvider: () -> DisplayMetrics, -): BackAnimationSpec = - BackAnimationSpec.createFloatingSurfaceAnimationSpec( - displayMetricsProvider = displayMetricsProvider, - maxMarginXdp = 8f, - maxMarginYdp = 8f, - minScale = 0.8f, - ) - -/** - * SysUI transitions - Cross task (ST2) Return to previous task/app, keeping the current one open - * https://carbon.googleplex.com/predictive-back-for-apps/pages/st-2-cross-task - */ -fun BackAnimationSpec.Companion.crossTaskForSysUi( - displayMetricsProvider: () -> DisplayMetrics, -): BackAnimationSpec = - BackAnimationSpec.createFloatingSurfaceAnimationSpec( - displayMetricsProvider = displayMetricsProvider, - maxMarginXdp = 8f, - maxMarginYdp = 8f, - minScale = 0.8f, - ) - -/** - * SysUI transitions - Inner area dismiss (ST3) Dismiss non-detachable surface - * https://carbon.googleplex.com/predictive-back-for-apps/pages/st-3-inner-area-dismiss - */ -fun BackAnimationSpec.Companion.innerAreaDismissForSysUi( - displayMetricsProvider: () -> DisplayMetrics, -): BackAnimationSpec = - BackAnimationSpec.createFloatingSurfaceAnimationSpec( - displayMetricsProvider = displayMetricsProvider, - maxMarginXdp = 0f, - maxMarginYdp = 0f, - minScale = 0.9f, - ) - -/** - * SysUI transitions - Floating system surfaces (ST4) - * https://carbon.googleplex.com/predictive-back-for-apps/pages/st-4-floating-system-surfaces - */ -fun BackAnimationSpec.Companion.floatingSystemSurfacesForSysUi( - displayMetricsProvider: () -> DisplayMetrics, -): BackAnimationSpec = - BackAnimationSpec.createFloatingSurfaceAnimationSpec( - displayMetricsProvider = displayMetricsProvider, - maxMarginXdp = 8f, - maxMarginYdp = 8f, - minScale = 0.9f, - ) - -/** - * SysUI transitions - Bottomsheet (AT3) - * https://carbon.googleplex.com/predictive-back-for-apps/pages/at-3-bottom-sheets - */ -fun BackAnimationSpec.Companion.bottomSheetForSysUi( - displayMetricsProvider: () -> DisplayMetrics, -): BackAnimationSpec = BackAnimationSpec.createBottomsheetAnimationSpec(displayMetricsProvider) diff --git a/systemUI/anim/src/com/android/systemui/animation/back/BackTransformation.kt b/systemUI/anim/src/com/android/systemui/animation/back/BackTransformation.kt deleted file mode 100644 index 029f62c6e4..0000000000 --- a/systemUI/anim/src/com/android/systemui/animation/back/BackTransformation.kt +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.animation.back - -import android.view.View - -/** - * This object that represents the transformation to apply to the target. The properties of this - * object are mutable for performance reasons (avoid recreating this object) - */ -data class BackTransformation( - var translateX: Float = Float.NaN, - var translateY: Float = Float.NaN, - var scale: Float = Float.NaN, - var scalePivotPosition: ScalePivotPosition? = null, -) - -/** Enum that describes the location of the scale pivot position */ -enum class ScalePivotPosition { - // more options may be added in the future - CENTER, - BOTTOM_CENTER; - - fun applyTo(view: View) { - val pivotX = - when (this) { - CENTER -> view.width / 2f - BOTTOM_CENTER -> view.width / 2f - } - val pivotY = - when (this) { - CENTER -> view.height / 2f - BOTTOM_CENTER -> view.height.toFloat() - } - view.pivotX = pivotX - view.pivotY = pivotY - } -} - -/** Apply the transformation to the [targetView] */ -fun BackTransformation.applyTo(targetView: View) { - if (translateX.isFinite()) targetView.translationX = translateX - if (translateY.isFinite()) targetView.translationY = translateY - scalePivotPosition?.applyTo(targetView) - if (scale.isFinite()) { - targetView.scaleX = scale - targetView.scaleY = scale - } -} diff --git a/systemUI/anim/src/com/android/systemui/animation/back/BottomsheetBackAnimationSpec.kt b/systemUI/anim/src/com/android/systemui/animation/back/BottomsheetBackAnimationSpec.kt deleted file mode 100644 index b1945a1c37..0000000000 --- a/systemUI/anim/src/com/android/systemui/animation/back/BottomsheetBackAnimationSpec.kt +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.animation.back - -import android.util.DisplayMetrics -import android.view.animation.Interpolator -import com.android.app.animation.Interpolators -import com.android.systemui.util.dpToPx - -private const val MAX_SCALE_DELTA_DP = 48 - -/** Create a [BackAnimationSpec] from [displayMetrics] and design specs. */ -fun BackAnimationSpec.Companion.createBottomsheetAnimationSpec( - displayMetricsProvider: () -> DisplayMetrics, - scaleEasing: Interpolator = Interpolators.BACK_GESTURE, -): BackAnimationSpec { - return BackAnimationSpec { backEvent, _, result -> - val displayMetrics = displayMetricsProvider() - val screenWidthPx = displayMetrics.widthPixels - val minScale = 1 - MAX_SCALE_DELTA_DP.dpToPx(displayMetrics) / screenWidthPx - val progressX = backEvent.progress - val ratioScale = scaleEasing.getInterpolation(progressX) - result.apply { - scale = 1f - ratioScale * (1f - minScale) - scalePivotPosition = ScalePivotPosition.BOTTOM_CENTER - } - } -} diff --git a/systemUI/anim/src/com/android/systemui/animation/back/FlingOnBackAnimationCallback.kt b/systemUI/anim/src/com/android/systemui/animation/back/FlingOnBackAnimationCallback.kt deleted file mode 100644 index 18454333c8..0000000000 --- a/systemUI/anim/src/com/android/systemui/animation/back/FlingOnBackAnimationCallback.kt +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.animation.back - -import android.os.Build -import android.util.TimeUtils -import android.view.Choreographer -import android.view.MotionEvent -import android.view.MotionEvent.ACTION_MOVE -import android.view.VelocityTracker -import android.view.animation.Interpolator -import android.window.BackEvent -import android.window.OnBackAnimationCallback -import com.android.app.animation.Interpolators -import com.android.internal.dynamicanimation.animation.DynamicAnimation -import com.android.internal.dynamicanimation.animation.FlingAnimation -import com.android.internal.dynamicanimation.animation.FloatValueHolder -import com.android.window.flags.Flags.predictiveBackTimestampApi - -private const val FLING_FRICTION = 6f -private const val SCALE_FACTOR = 100f - -/** - * Enhanced [OnBackAnimationCallback] with automatic fling animation and interpolated progress. - * - * Simplifies back gesture handling by animating flings and emitting processed events through - * `compat` functions. Customize progress interpolation with an optional [Interpolator]. - * - * @param progressInterpolator [Interpolator] for progress, defaults to - * [Interpolators.BACK_GESTURE]. - */ -abstract class FlingOnBackAnimationCallback( - val progressInterpolator: Interpolator = Interpolators.BACK_GESTURE -) : OnBackAnimationCallback { - - private val velocityTracker = VelocityTracker.obtain() - private var lastBackEvent: BackEvent? = null - private var downTime: Long? = null - - private var backInvokedFlingAnim: FlingAnimation? = null - private val backInvokedFlingUpdateListener = - DynamicAnimation.OnAnimationUpdateListener { _, progress: Float, _ -> - lastBackEvent?.let { - val backEvent = - BackEvent( - it.touchX, - it.touchY, - progress / SCALE_FACTOR, - it.swipeEdge, - it.frameTimeMillis, - ) - onBackProgressedCompat(backEvent) - } - } - private val backInvokedFlingEndListener = - DynamicAnimation.OnAnimationEndListener { _, _, _, _ -> - onBackInvokedCompat() - reset() - } - - abstract fun onBackStartedCompat(backEvent: BackEvent) - - abstract fun onBackProgressedCompat(backEvent: BackEvent) - - abstract fun onBackInvokedCompat() - - abstract fun onBackCancelledCompat() - - final override fun onBackStarted(backEvent: BackEvent) { - if (backInvokedFlingAnim != null) { - // This should never happen but let's call onBackInvokedCompat() just in case there is - // still a fling animation in progress - onBackInvokedCompat() - } - reset() - try { - if (predictiveBackTimestampApi()) { - downTime = backEvent.frameTimeMillis - } - } catch (t: Throwable) { - // LC-Ignored - } - onBackStartedCompat(backEvent) - } - - final override fun onBackProgressed(backEvent: BackEvent) { - val interpolatedProgress = progressInterpolator.getInterpolation(backEvent.progress) - val predictiveBackTimestampApi = try { - predictiveBackTimestampApi() - } catch (t: Throwable) { - // LC-Ignored - false - } - - if (predictiveBackTimestampApi) { - downTime?.let { downTime -> - velocityTracker.addMovement( - MotionEvent.obtain( - /* downTime */ downTime, - /* eventTime */ backEvent.frameTimeMillis, - /* action */ ACTION_MOVE, - /* x */ interpolatedProgress * SCALE_FACTOR, - /* y */ 0f, - /* metaState */ 0, - ) - ) - } - lastBackEvent = - BackEvent( - backEvent.touchX, - backEvent.touchY, - interpolatedProgress, - backEvent.swipeEdge, - backEvent.frameTimeMillis, - ) - } else { - lastBackEvent = - BackEvent( - backEvent.touchX, - backEvent.touchY, - interpolatedProgress, - backEvent.swipeEdge, - ) - } - lastBackEvent?.let { onBackProgressedCompat(it) } - } - - final override fun onBackInvoked() { - val predictiveBackTimestampApi = try { - predictiveBackTimestampApi() - } catch (t: Throwable) { - // LC-Ignored - false - } - if (predictiveBackTimestampApi && lastBackEvent != null) { - velocityTracker.computeCurrentVelocity(1000) - backInvokedFlingAnim = - FlingAnimation(FloatValueHolder()) - .setStartValue((lastBackEvent?.progress ?: 0f) * SCALE_FACTOR) - .setFriction(FLING_FRICTION) - .setStartVelocity(velocityTracker.xVelocity) - .setMinValue(0f) - .setMaxValue(SCALE_FACTOR) - .also { - it.addUpdateListener(backInvokedFlingUpdateListener) - it.addEndListener(backInvokedFlingEndListener) - it.start() - // do an animation-frame immediately to prevent idle frame - it.doAnimationFrame( - Choreographer.getInstance().lastFrameTimeNanos / TimeUtils.NANOS_PER_MS - ) - } - } else { - onBackInvokedCompat() - reset() - } - } - - final override fun onBackCancelled() { - onBackCancelledCompat() - reset() - } - - private fun reset() { - velocityTracker.clear() - backInvokedFlingAnim?.removeEndListener(backInvokedFlingEndListener) - backInvokedFlingAnim?.removeUpdateListener(backInvokedFlingUpdateListener) - lastBackEvent = null - backInvokedFlingAnim = null - downTime = null - } -} diff --git a/systemUI/anim/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtension.kt b/systemUI/anim/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtension.kt deleted file mode 100644 index f8f56bc0a8..0000000000 --- a/systemUI/anim/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtension.kt +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.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 android.window.OnBackInvokedDispatcher.Priority -import androidx.annotation.RequiresApi - -/** - * Generates an [OnBackAnimationCallback] given a [backAnimationSpec]. [onBackProgressed] will be - * called on each update passing the current [BackTransformation]. - * - * Optionally, you can specify [onBackStarted], [onBackInvoked], and [onBackCancelled] callbacks. - * - * @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 - onBackProgressed: (BackTransformation) -> Unit, - onBackStarted: (BackEvent) -> Unit = {}, - onBackInvoked: () -> Unit = {}, - onBackCancelled: () -> Unit = {}, -): OnBackAnimationCallback { - return object : OnBackAnimationCallback { - private var initialY = 0f - private val lastTransformation = BackTransformation() - - override fun onBackStarted(backEvent: BackEvent) { - initialY = backEvent.touchY - onBackStarted(backEvent) - } - - override fun onBackProgressed(backEvent: BackEvent) { - val progressY = (backEvent.touchY - initialY) / displayMetrics.heightPixels - - backAnimationSpec.getBackTransformation( - backEvent = backEvent, - progressY = progressY, - result = lastTransformation, - ) - - onBackProgressed(lastTransformation) - } - - override fun onBackInvoked() { - onBackInvoked() - } - - override fun onBackCancelled() { - onBackCancelled() - } - } -} - -/** - * Register [OnBackAnimationCallback] when View is attached and unregister it when View is detached - * - * @sample com.android.systemui.util.registerAnimationOnBackInvoked - */ -@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) -fun View.registerOnBackInvokedCallbackOnViewAttached( - onBackInvokedDispatcher: OnBackInvokedDispatcher, - onBackAnimationCallback: OnBackAnimationCallback, - @Priority @IntRange(from = 0) priority: Int = OnBackInvokedDispatcher.PRIORITY_DEFAULT, -) { - addOnAttachStateChangeListener( - object : View.OnAttachStateChangeListener { - override fun onViewAttachedToWindow(v: View) { - onBackInvokedDispatcher.registerOnBackInvokedCallback( - priority, - onBackAnimationCallback - ) - } - - override fun onViewDetachedFromWindow(v: View) { - removeOnAttachStateChangeListener(this) - onBackInvokedDispatcher.unregisterOnBackInvokedCallback(onBackAnimationCallback) - } - } - ) - - if (isAttachedToWindow) { - onBackInvokedDispatcher.registerOnBackInvokedCallback(priority, onBackAnimationCallback) - } -} diff --git a/systemUI/anim/src/com/android/systemui/animation/view/LaunchableFrameLayout.kt b/systemUI/anim/src/com/android/systemui/animation/view/LaunchableFrameLayout.kt deleted file mode 100644 index 7538f188fb..0000000000 --- a/systemUI/anim/src/com/android/systemui/animation/view/LaunchableFrameLayout.kt +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.animation.view - -import android.content.Context -import android.util.AttributeSet -import android.widget.FrameLayout -import com.android.systemui.animation.LaunchableView -import com.android.systemui.animation.LaunchableViewDelegate - -/** A [FrameLayout] that also implements [LaunchableView]. */ -open class LaunchableFrameLayout : FrameLayout, LaunchableView { - private val delegate = - LaunchableViewDelegate( - this, - superSetVisibility = { super.setVisibility(it) }, - ) - - constructor(context: Context) : super(context) - constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) - constructor( - context: Context, - attrs: AttributeSet?, - defStyleAttr: Int - ) : super(context, attrs, defStyleAttr) - - constructor( - context: Context, - attrs: AttributeSet?, - defStyleAttr: Int, - defStyleRes: Int - ) : super(context, attrs, defStyleAttr, defStyleRes) - - override fun setShouldBlockVisibilityChanges(block: Boolean) { - delegate.setShouldBlockVisibilityChanges(block) - } - - override fun setVisibility(visibility: Int) { - delegate.setVisibility(visibility) - } -} diff --git a/systemUI/anim/src/com/android/systemui/animation/view/LaunchableImageView.kt b/systemUI/anim/src/com/android/systemui/animation/view/LaunchableImageView.kt deleted file mode 100644 index e42b589f05..0000000000 --- a/systemUI/anim/src/com/android/systemui/animation/view/LaunchableImageView.kt +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.android.systemui.animation.view - -import android.content.Context -import android.util.AttributeSet -import android.widget.ImageView -import com.android.systemui.animation.LaunchableView -import com.android.systemui.animation.LaunchableViewDelegate - -/** An [ImageView] that also implements [LaunchableView]. */ -open class LaunchableImageView : ImageView, LaunchableView { - private val delegate = - LaunchableViewDelegate( - this, - superSetVisibility = { super.setVisibility(it) }, - ) - - constructor(context: Context?) : super(context) - constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) - constructor( - context: Context?, - attrs: AttributeSet?, - defStyleAttr: Int, - ) : super(context, attrs, defStyleAttr) - - constructor( - context: Context?, - attrs: AttributeSet?, - defStyleAttr: Int, - defStyleRes: Int, - ) : super(context, attrs, defStyleAttr, defStyleRes) - - override fun setShouldBlockVisibilityChanges(block: Boolean) { - delegate.setShouldBlockVisibilityChanges(block) - } - - override fun setVisibility(visibility: Int) { - delegate.setVisibility(visibility) - } -} diff --git a/systemUI/anim/src/com/android/systemui/animation/view/LaunchableLinearLayout.kt b/systemUI/anim/src/com/android/systemui/animation/view/LaunchableLinearLayout.kt deleted file mode 100644 index bce262291f..0000000000 --- a/systemUI/anim/src/com/android/systemui/animation/view/LaunchableLinearLayout.kt +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.animation.view - -import android.content.Context -import android.util.AttributeSet -import android.widget.LinearLayout -import com.android.systemui.animation.LaunchableView -import com.android.systemui.animation.LaunchableViewDelegate - -/** A [LinearLayout] that also implements [LaunchableView]. */ -open class LaunchableLinearLayout : LinearLayout, LaunchableView { - private val delegate = - LaunchableViewDelegate( - this, - superSetVisibility = { super.setVisibility(it) }, - ) - - constructor(context: Context?) : super(context) - constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) - constructor( - context: Context?, - attrs: AttributeSet?, - defStyleAttr: Int, - ) : super(context, attrs, defStyleAttr) - - constructor( - context: Context?, - attrs: AttributeSet?, - defStyleAttr: Int, - defStyleRes: Int, - ) : super(context, attrs, defStyleAttr, defStyleRes) - - override fun setShouldBlockVisibilityChanges(block: Boolean) { - delegate.setShouldBlockVisibilityChanges(block) - } - - override fun setVisibility(visibility: Int) { - delegate.setVisibility(visibility) - } -} diff --git a/systemUI/anim/src/com/android/systemui/animation/view/LaunchableTextView.kt b/systemUI/anim/src/com/android/systemui/animation/view/LaunchableTextView.kt deleted file mode 100644 index 147669528c..0000000000 --- a/systemUI/anim/src/com/android/systemui/animation/view/LaunchableTextView.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.android.systemui.animation.view - -import android.content.Context -import android.util.AttributeSet -import android.widget.TextView -import com.android.systemui.animation.LaunchableView -import com.android.systemui.animation.LaunchableViewDelegate - -/** A [TextView] that also implements [LaunchableView]. */ -open class LaunchableTextView : TextView, LaunchableView { - private val delegate = - LaunchableViewDelegate( - this, - superSetVisibility = { super.setVisibility(it) }, - ) - - constructor(context: Context?) : super(context) - constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) - constructor( - context: Context?, - attrs: AttributeSet?, - defStyleAttr: Int, - ) : super(context, attrs, defStyleAttr) - - override fun setShouldBlockVisibilityChanges(block: Boolean) { - delegate.setShouldBlockVisibilityChanges(block) - } - - override fun setVisibility(visibility: Int) { - delegate.setVisibility(visibility) - } -} diff --git a/systemUI/anim/src/com/android/systemui/surfaceeffects/PaintDrawCallback.kt b/systemUI/anim/src/com/android/systemui/surfaceeffects/PaintDrawCallback.kt deleted file mode 100644 index d50979ccd0..0000000000 --- a/systemUI/anim/src/com/android/systemui/surfaceeffects/PaintDrawCallback.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.surfaceeffects - -import android.graphics.Paint -import android.graphics.RenderEffect - -/** - * A callback with a [Paint] object that contains shader info, which is triggered every frame while - * animation is playing. Note that the [Paint] object here is always the same instance. - * - * This approach is more performant than other ones because [RenderEffect] forces an intermediate - * render pass of the View to a texture to feed into it. - * - * The usage of this callback is as follows: - * <pre>{@code - * private var paint: Paint? = null - * // Override [View.onDraw]. - * override fun onDraw(canvas: Canvas) { - * // RuntimeShader requires hardwareAcceleration. - * if (!canvas.isHardwareAccelerated) return - * - * paint?.let { canvas.drawPaint(it) } - * } - * - * // Given that this is called [PaintDrawCallback.onDraw] - * fun draw(paint: Paint) { - * this.paint = paint - * - * // Must call invalidate to trigger View#onDraw - * invalidate() - * } - * }</pre> - * - * Please refer to [RenderEffectDrawCallback] for alternative approach. - */ -interface PaintDrawCallback { - fun onDraw(paint: Paint) -} diff --git a/systemUI/anim/src/com/android/systemui/surfaceeffects/RenderEffectDrawCallback.kt b/systemUI/anim/src/com/android/systemui/surfaceeffects/RenderEffectDrawCallback.kt deleted file mode 100644 index db7ee58090..0000000000 --- a/systemUI/anim/src/com/android/systemui/surfaceeffects/RenderEffectDrawCallback.kt +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.surfaceeffects - -import android.graphics.RenderEffect - -/** - * A callback with a [RenderEffect] object that contains shader info, which is triggered every frame - * while animation is playing. Note that the [RenderEffect] instance is different each time to - * update shader uniforms. - * - * The usage of this callback is as follows: - * <pre>{@code - * private val xEffectDrawingCallback = RenderEffectDrawCallback() { - * val myOtherRenderEffect = createOtherRenderEffect() - * val chainEffect = RenderEffect.createChainEffect(renderEffect, myOtherRenderEffect) - * myView.setRenderEffect(chainEffect) - * } - * - * private val xEffect = XEffect(config, xEffectDrawingCallback) - * }</pre> - */ -interface RenderEffectDrawCallback { - fun onDraw(renderEffect: RenderEffect) -} diff --git a/systemUI/anim/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxConfig.kt b/systemUI/anim/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxConfig.kt deleted file mode 100644 index 72f0e86f9d..0000000000 --- a/systemUI/anim/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxConfig.kt +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.surfaceeffects.glowboxeffect - -/** Parameters used to play [GlowBoxEffect]. */ -data class GlowBoxConfig( - /** Start center position X in px. */ - val startCenterX: Float, - /** Start center position Y in px. */ - val startCenterY: Float, - /** End center position X in px. */ - val endCenterX: Float, - /** End center position Y in px. */ - val endCenterY: Float, - /** Width of the box in px. */ - val width: Float, - /** Height of the box in px. */ - val height: Float, - /** Color of the box in ARGB, Apply alpha value if needed. */ - val color: Int, - /** Amount of blur (or glow) of the box. */ - val blurAmount: Float, - /** - * Duration of the animation. Note that the full duration of the animation is - * [duration] + [easeInDuration] + [easeOutDuration]. - */ - val duration: Long, - /** Ease in duration of the animation. */ - val easeInDuration: Long, - /** Ease out duration of the animation. */ - val easeOutDuration: Long, -) diff --git a/systemUI/anim/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxEffect.kt b/systemUI/anim/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxEffect.kt deleted file mode 100644 index 5e590c1ca0..0000000000 --- a/systemUI/anim/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxEffect.kt +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.surfaceeffects.glowboxeffect - -import android.animation.ValueAnimator -import android.graphics.Paint -import androidx.annotation.VisibleForTesting -import androidx.core.animation.doOnEnd -import com.android.systemui.surfaceeffects.PaintDrawCallback -import com.android.systemui.surfaceeffects.utils.MathUtils.lerp - -/** Glow box effect where the box moves from start to end positions defined in the [config]. */ -class GlowBoxEffect( - private var config: GlowBoxConfig, - private val paintDrawCallback: PaintDrawCallback, - private val stateChangedCallback: AnimationStateChangedCallback? = null -) { - private val glowBoxShader = - GlowBoxShader().apply { - setSize(config.width, config.height) - setCenter(config.startCenterX, config.startCenterY) - setBlur(config.blurAmount) - setColor(config.color) - } - private var animator: ValueAnimator? = null - @VisibleForTesting var state: AnimationState = AnimationState.NOT_PLAYING - private val paint = Paint().apply { shader = glowBoxShader } - - fun updateConfig(newConfig: GlowBoxConfig) { - this.config = newConfig - - with(glowBoxShader) { - setSize(config.width, config.height) - setCenter(config.startCenterX, config.startCenterY) - setBlur(config.blurAmount) - setColor(config.color) - } - } - - fun play() { - if (state != AnimationState.NOT_PLAYING) { - return - } - - playEaseIn() - } - - /** Finishes the animation with ease out. */ - fun finish(force: Boolean = false) { - // If it's playing ease out, cancel immediately. - if (force && state == AnimationState.EASE_OUT) { - animator?.cancel() - return - } - - // If it's playing either ease in or main, fast-forward to ease out. - if (state == AnimationState.EASE_IN || state == AnimationState.MAIN) { - animator?.pause() - playEaseOut() - } - - // At this point, animation state should be ease out. Cancel it if force is true. - if (force) { - animator?.cancel() - } - } - - private fun playEaseIn() { - if (state == AnimationState.EASE_IN) { - return - } - state = AnimationState.EASE_IN - stateChangedCallback?.onStart() - - animator = - ValueAnimator.ofFloat(0f, 1f).apply { - duration = config.easeInDuration - addUpdateListener { - val progress = it.animatedValue as Float - glowBoxShader.setCenter( - lerp(config.startCenterX, config.endCenterX, progress), - lerp(config.startCenterY, config.endCenterY, progress) - ) - - draw() - } - - doOnEnd { - animator = null - playMain() - } - - start() - } - } - - private fun playMain() { - if (state == AnimationState.MAIN) { - return - } - state = AnimationState.MAIN - - animator = - ValueAnimator.ofFloat(0f, 1f).apply { - duration = config.duration - addUpdateListener { draw() } - - doOnEnd { - animator = null - playEaseOut() - } - - start() - } - } - - private fun playEaseOut() { - if (state == AnimationState.EASE_OUT) return - state = AnimationState.EASE_OUT - - animator = - ValueAnimator.ofFloat(0f, 1f).apply { - duration = config.easeOutDuration - addUpdateListener { - val progress = it.animatedValue as Float - glowBoxShader.setCenter( - lerp(config.endCenterX, config.startCenterX, progress), - lerp(config.endCenterY, config.startCenterY, progress) - ) - - draw() - } - - doOnEnd { - animator = null - state = AnimationState.NOT_PLAYING - stateChangedCallback?.onEnd() - } - - start() - } - } - - private fun draw() { - paintDrawCallback.onDraw(paint) - } - - /** - * The animation state of the effect. The animation state transitions as follows: [EASE_IN] -> - * [MAIN] -> [EASE_OUT] -> [NOT_PLAYING]. - */ - enum class AnimationState { - EASE_IN, - MAIN, - EASE_OUT, - NOT_PLAYING, - } - - interface AnimationStateChangedCallback { - /** - * Triggered when the animation starts, specifically when the states goes from - * [AnimationState.NOT_PLAYING] to [AnimationState.EASE_IN]. - */ - fun onStart() - /** - * Triggered when the animation ends, specifically when the states goes from - * [AnimationState.EASE_OUT] to [AnimationState.NOT_PLAYING]. - */ - fun onEnd() - } -} diff --git a/systemUI/anim/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxShader.kt b/systemUI/anim/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxShader.kt deleted file mode 100644 index 36934086cc..0000000000 --- a/systemUI/anim/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxShader.kt +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.surfaceeffects.glowboxeffect - -import android.graphics.RuntimeShader -import com.android.systemui.surfaceeffects.shaderutil.SdfShaderLibrary - -/** Soft box shader. */ -class GlowBoxShader : RuntimeShader(GLOW_SHADER) { - // language=AGSL - private companion object { - private const val SHADER = - """ - uniform half2 in_center; - uniform half2 in_size; - uniform half in_blur; - layout(color) uniform half4 in_color; - - float4 main(float2 fragcoord) { - half glow = soften(sdBox(fragcoord - in_center, in_size), in_blur); - return in_color * (1. - glow); - } - """ - - private const val GLOW_SHADER = - SdfShaderLibrary.BOX_SDF + SdfShaderLibrary.SHADER_SDF_OPERATION_LIB + SHADER - } - - fun setCenter(x: Float, y: Float) { - setFloatUniform("in_center", x, y) - } - - fun setSize(width: Float, height: Float) { - setFloatUniform("in_size", width, height) - } - - fun setBlur(blurAmount: Float) { - setFloatUniform("in_blur", blurAmount) - } - - fun setColor(color: Int) { - setColorUniform("in_color", color) - } -} diff --git a/systemUI/anim/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffect.kt b/systemUI/anim/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffect.kt deleted file mode 100644 index 211b84f253..0000000000 --- a/systemUI/anim/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffect.kt +++ /dev/null @@ -1,366 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.surfaceeffects.loadingeffect - -import android.animation.Animator -import android.animation.AnimatorListenerAdapter -import android.animation.ValueAnimator -import android.graphics.Paint -import android.graphics.RenderEffect -import android.view.View -import com.android.systemui.surfaceeffects.PaintDrawCallback -import com.android.systemui.surfaceeffects.RenderEffectDrawCallback -import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseAnimationConfig -import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseShader - -/** - * Plays loading effect with the given configuration. - * - * @param baseType immutable base shader type. This is used for constructing the shader. Reconstruct - * the [LoadingEffect] if the base type needs to be changed. - * @param config immutable parameters that are used for drawing the effect. - * @param paintCallback triggered every frame when animation is playing. Use this to draw the effect - * with [Canvas.drawPaint]. - * @param renderEffectCallback triggered every frame when animation is playing. Use this to draw the - * effect with [RenderEffect]. - * @param animationStateChangedCallback triggered when the [AnimationState] changes. Optional. - * - * The client is responsible to actually draw the [Paint] or [RenderEffect] returned in the - * callback. Note that [View.invalidate] must be called on each callback. There are a few ways to - * render the effect: - * 1) Use [Canvas.drawPaint]. (Preferred. Significantly cheaper!) - * 2) Set [RenderEffect] to the [View]. (Good for chaining effects.) - * 3) Use [RenderNode.setRenderEffect]. (This may be least preferred, as 2 should do what you want.) - * - * <p>First approach is more performant than other ones because [RenderEffect] forces an - * intermediate render pass of the View to a texture to feed into it. - * - * <p>If going with the first approach, your custom [View] would look like as follow: - * <pre>{@code - * private var paint: Paint? = null - * // Override [View.onDraw]. - * override fun onDraw(canvas: Canvas) { - * // RuntimeShader requires hardwareAcceleration. - * if (!canvas.isHardwareAccelerated) return - * - * paint?.let { canvas.drawPaint(it) } - * } - * - * // This is called [Callback.onDraw] - * fun draw(paint: Paint) { - * this.paint = paint - * - * // Must call invalidate to trigger View#onDraw - * invalidate() - * } - * }</pre> - * - * <p>If going with the second approach, it doesn't require an extra custom [View], and it is as - * simple as calling [View.setRenderEffect] followed by [View.invalidate]. You can also chain the - * effect with other [RenderEffect]. - * - * <p>Third approach is an option, but it's more of a boilerplate so you would like to stick with - * the second option. If you want to go with this option for some reason, below is the example: - * <pre>{@code - * // Initialize the shader and paint to use to pass into the [Canvas]. - * private val renderNode = RenderNode("LoadingEffect") - * - * // Override [View.onDraw]. - * override fun onDraw(canvas: Canvas) { - * // RuntimeShader requires hardwareAcceleration. - * if (!canvas.isHardwareAccelerated) return - * - * if (renderNode.hasDisplayList()) { - * canvas.drawRenderNode(renderNode) - * } - * } - * - * // This is called [Callback.onDraw] - * fun draw(renderEffect: RenderEffect) { - * renderNode.setPosition(0, 0, width, height) - * renderNode.setRenderEffect(renderEffect) - * - * val recordingCanvas = renderNode.beginRecording() - * // We need at least 1 drawing instruction. - * recordingCanvas.drawColor(Color.TRANSPARENT) - * renderNode.endRecording() - * - * // Must call invalidate to trigger View#onDraw - * invalidate() - * } - * }</pre> - */ -class LoadingEffect -private constructor( - baseType: TurbulenceNoiseShader.Companion.Type, - private val config: TurbulenceNoiseAnimationConfig, - private val paintCallback: PaintDrawCallback?, - private val renderEffectCallback: RenderEffectDrawCallback?, - private val animationStateChangedCallback: AnimationStateChangedCallback? = null -) { - constructor( - baseType: TurbulenceNoiseShader.Companion.Type, - config: TurbulenceNoiseAnimationConfig, - paintCallback: PaintDrawCallback, - animationStateChangedCallback: AnimationStateChangedCallback? = null - ) : this( - baseType, - config, - paintCallback, - renderEffectCallback = null, - animationStateChangedCallback - ) - constructor( - baseType: TurbulenceNoiseShader.Companion.Type, - config: TurbulenceNoiseAnimationConfig, - renderEffectCallback: RenderEffectDrawCallback, - animationStateChangedCallback: AnimationStateChangedCallback? = null - ) : this( - baseType, - config, - paintCallback = null, - renderEffectCallback, - animationStateChangedCallback - ) - - private val turbulenceNoiseShader: TurbulenceNoiseShader = - TurbulenceNoiseShader(baseType).apply { applyConfig(config) } - private var currentAnimator: ValueAnimator? = null - private var state: AnimationState = AnimationState.NOT_PLAYING - set(value) { - if (field != value) { - animationStateChangedCallback?.onStateChanged(field, value) - field = value - } - } - - // We create a paint instance only if the client renders it with Paint. - private val paint = - if (paintCallback != null) { - Paint().apply { this.shader = turbulenceNoiseShader } - } else { - null - } - - /** Plays LoadingEffect. */ - fun play() { - if (state != AnimationState.NOT_PLAYING) { - return // Ignore if any of the animation is playing. - } - - playEaseIn() - } - - // TODO(b/237282226): Support force finish. - /** Finishes the main animation, which triggers the ease-out animation. */ - fun finish() { - if (state == AnimationState.MAIN) { - // Calling Animator#end sets the animation state back to the initial state. Using pause - // to avoid visual artifacts. - currentAnimator?.pause() - currentAnimator = null - - playEaseOut() - } - } - - /** Updates the noise color dynamically. */ - fun updateColor(newColor: Int) { - turbulenceNoiseShader.setColor(newColor) - } - - /** Updates the noise color that's screen blended on top. */ - fun updateScreenColor(newColor: Int) { - turbulenceNoiseShader.setScreenColor(newColor) - } - - /** - * Retrieves the noise offset x, y, z values. This is useful for replaying the animation - * smoothly from the last animation, by passing in the last values to the next animation. - */ - fun getNoiseOffset(): Array<Float> { - return arrayOf( - turbulenceNoiseShader.noiseOffsetX, - turbulenceNoiseShader.noiseOffsetY, - turbulenceNoiseShader.noiseOffsetZ - ) - } - - private fun playEaseIn() { - if (state != AnimationState.NOT_PLAYING) { - return - } - state = AnimationState.EASE_IN - - val animator = ValueAnimator.ofFloat(0f, 1f) - animator.duration = config.easeInDuration.toLong() - - // Animation should start from the initial position to avoid abrupt transition. - val initialX = turbulenceNoiseShader.noiseOffsetX - val initialY = turbulenceNoiseShader.noiseOffsetY - val initialZ = turbulenceNoiseShader.noiseOffsetZ - - animator.addUpdateListener { updateListener -> - val timeInSec = updateListener.currentPlayTime * MS_TO_SEC - val progress = updateListener.animatedValue as Float - - turbulenceNoiseShader.setNoiseMove( - initialX + timeInSec * config.noiseMoveSpeedX, - initialY + timeInSec * config.noiseMoveSpeedY, - initialZ + timeInSec * config.noiseMoveSpeedZ - ) - - // TODO: Replace it with a better curve. - turbulenceNoiseShader.setOpacity(progress * config.luminosityMultiplier) - - draw() - } - - animator.addListener( - object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - currentAnimator = null - playMain() - } - } - ) - - animator.start() - this.currentAnimator = animator - } - - private fun playMain() { - if (state != AnimationState.EASE_IN) { - return - } - state = AnimationState.MAIN - - val animator = ValueAnimator.ofFloat(0f, 1f) - animator.duration = config.maxDuration.toLong() - - // Animation should start from the initial position to avoid abrupt transition. - val initialX = turbulenceNoiseShader.noiseOffsetX - val initialY = turbulenceNoiseShader.noiseOffsetY - val initialZ = turbulenceNoiseShader.noiseOffsetZ - - turbulenceNoiseShader.setOpacity(config.luminosityMultiplier) - - animator.addUpdateListener { updateListener -> - val timeInSec = updateListener.currentPlayTime * MS_TO_SEC - turbulenceNoiseShader.setNoiseMove( - initialX + timeInSec * config.noiseMoveSpeedX, - initialY + timeInSec * config.noiseMoveSpeedY, - initialZ + timeInSec * config.noiseMoveSpeedZ - ) - - draw() - } - - animator.addListener( - object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - currentAnimator = null - playEaseOut() - } - } - ) - - animator.start() - this.currentAnimator = animator - } - - private fun playEaseOut() { - if (state != AnimationState.MAIN) { - return - } - state = AnimationState.EASE_OUT - - val animator = ValueAnimator.ofFloat(0f, 1f) - animator.duration = config.easeOutDuration.toLong() - - // Animation should start from the initial position to avoid abrupt transition. - val initialX = turbulenceNoiseShader.noiseOffsetX - val initialY = turbulenceNoiseShader.noiseOffsetY - val initialZ = turbulenceNoiseShader.noiseOffsetZ - - animator.addUpdateListener { updateListener -> - val timeInSec = updateListener.currentPlayTime * MS_TO_SEC - val progress = updateListener.animatedValue as Float - - turbulenceNoiseShader.setNoiseMove( - initialX + timeInSec * config.noiseMoveSpeedX, - initialY + timeInSec * config.noiseMoveSpeedY, - initialZ + timeInSec * config.noiseMoveSpeedZ - ) - - // TODO: Replace it with a better curve. - turbulenceNoiseShader.setOpacity((1f - progress) * config.luminosityMultiplier) - - draw() - } - - animator.addListener( - object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - currentAnimator = null - state = AnimationState.NOT_PLAYING - } - } - ) - - animator.start() - this.currentAnimator = animator - } - - private fun draw() { - paintCallback?.onDraw(paint!!) - renderEffectCallback?.onDraw( - RenderEffect.createRuntimeShaderEffect( - turbulenceNoiseShader, - TurbulenceNoiseShader.BACKGROUND_UNIFORM - ) - ) - } - - /** - * States of the loading effect animation. - * - * <p>The state is designed to be follow the order below: [AnimationState.EASE_IN], - * [AnimationState.MAIN], [AnimationState.EASE_OUT]. Note that ease in and out don't necessarily - * mean the acceleration and deceleration in the animation curve. They simply mean each stage of - * the animation. (i.e. Intro, core, and rest) - */ - enum class AnimationState { - EASE_IN, - MAIN, - EASE_OUT, - NOT_PLAYING - } - - /** Optional callback that is triggered when the animation state changes. */ - interface AnimationStateChangedCallback { - /** - * A callback that's triggered when the [AnimationState] changes. Example usage is - * performing a cleanup when [AnimationState] becomes [NOT_PLAYING]. - */ - fun onStateChanged(oldState: AnimationState, newState: AnimationState) {} - } - - private companion object { - private const val MS_TO_SEC = 0.001f - } -} diff --git a/systemUI/anim/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectView.kt b/systemUI/anim/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectView.kt deleted file mode 100644 index aad593eb6a..0000000000 --- a/systemUI/anim/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectView.kt +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.surfaceeffects.loadingeffect - -import android.content.Context -import android.graphics.BlendMode -import android.graphics.Canvas -import android.graphics.Paint -import android.util.AttributeSet -import android.view.View - -/** Custom View for drawing the [LoadingEffect] with [Canvas.drawPaint]. */ -open class LoadingEffectView(context: Context?, attrs: AttributeSet?) : View(context, attrs) { - - private var paint: Paint? = null - private var blendMode: BlendMode = BlendMode.SRC_OVER - - override fun onDraw(canvas: Canvas) { - if (!canvas.isHardwareAccelerated) { - return - } - paint?.let { canvas.drawPaint(it) } - } - - /** Designed to be called on [LoadingEffect.PaintDrawCallback.onDraw]. */ - fun draw(paint: Paint) { - this.paint = paint - this.paint!!.blendMode = blendMode - - invalidate() - } - - /** Sets the blend mode of the [Paint]. */ - fun setBlendMode(blendMode: BlendMode) { - this.blendMode = blendMode - } -} diff --git a/systemUI/anim/src/com/android/systemui/surfaceeffects/ripple/MultiRippleController.kt b/systemUI/anim/src/com/android/systemui/surfaceeffects/ripple/MultiRippleController.kt deleted file mode 100644 index d8e17c9c82..0000000000 --- a/systemUI/anim/src/com/android/systemui/surfaceeffects/ripple/MultiRippleController.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.surfaceeffects.ripple - -import androidx.annotation.VisibleForTesting - -/** Controller that handles playing [RippleAnimation]. */ -class MultiRippleController(private val multipleRippleView: MultiRippleView) { - - companion object { - /** Max number of ripple animations at a time. */ - @VisibleForTesting const val MAX_RIPPLE_NUMBER = 10 - } - - /** Updates all the ripple colors during the animation. */ - fun updateColor(color: Int) { - multipleRippleView.ripples.forEach { anim -> anim.updateColor(color) } - } - - fun play(rippleAnimation: RippleAnimation) { - if (multipleRippleView.ripples.size >= MAX_RIPPLE_NUMBER) { - return - } - - multipleRippleView.ripples.add(rippleAnimation) - - rippleAnimation.play { - // Remove ripple once the animation is done - multipleRippleView.ripples.remove(rippleAnimation) - } - - // Trigger drawing - multipleRippleView.invalidate() - } -} diff --git a/systemUI/anim/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt b/systemUI/anim/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt deleted file mode 100644 index 6c175ddf1e..0000000000 --- a/systemUI/anim/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.surfaceeffects.ripple - -import android.content.Context -import android.graphics.Canvas -import android.graphics.Paint -import android.util.AttributeSet -import android.view.View -import androidx.annotation.VisibleForTesting - -/** - * A view that allows multiple ripples to play. - * - * Use [MultiRippleController] to play ripple animations. - */ -class MultiRippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) { - - @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) - val ripples = ArrayList<RippleAnimation>() - private val ripplePaint = Paint() - - companion object { - private const val TAG = "MultiRippleView" - } - - override fun onDraw(canvas: Canvas) { - if (!canvas.isHardwareAccelerated) { - // Drawing with the ripple shader requires hardware acceleration, so skip if it's - // unsupported. - return - } - - var shouldInvalidate = false - - ripples.forEach { anim -> - ripplePaint.shader = anim.rippleShader - canvas.drawPaint(ripplePaint) - - shouldInvalidate = shouldInvalidate || anim.isPlaying() - } - - if (shouldInvalidate) { - invalidate() - } - } -} diff --git a/systemUI/anim/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt b/systemUI/anim/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt deleted file mode 100644 index d4372507e2..0000000000 --- a/systemUI/anim/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.surfaceeffects.ripple - -import android.animation.Animator -import android.animation.AnimatorListenerAdapter -import android.animation.ValueAnimator -import androidx.annotation.VisibleForTesting -import androidx.core.graphics.ColorUtils - -/** A single ripple animation. */ -class RippleAnimation(private val config: RippleAnimationConfig) { - @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) - val rippleShader: RippleShader = RippleShader(config.rippleShape) - private val animator: ValueAnimator = ValueAnimator.ofFloat(0f, 1f) - - init { - applyConfigToShader() - } - - /** Updates the ripple color during the animation. */ - fun updateColor(color: Int) { - config.apply { config.color = color } - applyConfigToShader() - } - - @JvmOverloads - fun play(onAnimationEnd: Runnable? = null) { - if (isPlaying()) { - return // Ignore if ripple effect is already playing - } - - animator.duration = config.duration - animator.addUpdateListener { updateListener -> - val now = updateListener.currentPlayTime - val progress = updateListener.animatedValue as Float - rippleShader.rawProgress = progress - rippleShader.distortionStrength = if (config.shouldDistort) 1 - progress else 0f - rippleShader.time = now.toFloat() - } - animator.addListener( - object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - onAnimationEnd?.run() - } - } - ) - animator.start() - } - - /** Indicates whether the animation is playing. */ - fun isPlaying(): Boolean = animator.isRunning - - private fun applyConfigToShader() { - with(rippleShader) { - setCenter(config.centerX, config.centerY) - rippleSize.setMaxSize(config.maxWidth, config.maxHeight) - pixelDensity = config.pixelDensity - color = ColorUtils.setAlphaComponent(config.color, config.opacity) - sparkleStrength = config.sparkleStrength - - assignFadeParams(baseRingFadeParams, config.baseRingFadeParams) - assignFadeParams(sparkleRingFadeParams, config.sparkleRingFadeParams) - assignFadeParams(centerFillFadeParams, config.centerFillFadeParams) - } - } - - private fun assignFadeParams( - destFadeParams: RippleShader.FadeParams, - srcFadeParams: RippleShader.FadeParams? - ) { - srcFadeParams?.let { - destFadeParams.fadeInStart = it.fadeInStart - destFadeParams.fadeInEnd = it.fadeInEnd - destFadeParams.fadeOutStart = it.fadeOutStart - destFadeParams.fadeOutEnd = it.fadeOutEnd - } - } -} diff --git a/systemUI/anim/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt b/systemUI/anim/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt deleted file mode 100644 index 91c0a5b635..0000000000 --- a/systemUI/anim/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.android.systemui.surfaceeffects.ripple - -import android.graphics.Color - -/** - * A struct that holds the ripple animation configurations. - * - * <p>This configuration is designed to play a SINGLE animation. Do not reuse or modify the - * configuration parameters to play different animations, unless the value has to change within the - * single animation (e.g. Change color or opacity during the animation). Note that this data class - * is pulled out to make the [RippleAnimation] constructor succinct. - */ -data class RippleAnimationConfig( - val rippleShape: RippleShader.RippleShape = RippleShader.RippleShape.CIRCLE, - val duration: Long = 0L, - val centerX: Float = 0f, - val centerY: Float = 0f, - val maxWidth: Float = 0f, - val maxHeight: Float = 0f, - val pixelDensity: Float = 1f, - var color: Int = Color.WHITE, - val opacity: Int = RippleShader.RIPPLE_DEFAULT_ALPHA, - val sparkleStrength: Float = RippleShader.RIPPLE_SPARKLE_STRENGTH, - // Null means it uses default fade parameter values. - val baseRingFadeParams: RippleShader.FadeParams? = null, - val sparkleRingFadeParams: RippleShader.FadeParams? = null, - val centerFillFadeParams: RippleShader.FadeParams? = null, - val shouldDistort: Boolean = true -) diff --git a/systemUI/anim/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt b/systemUI/anim/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt deleted file mode 100644 index 7e56f4b3d2..0000000000 --- a/systemUI/anim/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt +++ /dev/null @@ -1,460 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.systemui.surfaceeffects.ripple - -import android.graphics.RuntimeShader -import android.util.Log -import android.view.animation.Interpolator -import android.view.animation.PathInterpolator -import androidx.annotation.VisibleForTesting -import com.android.systemui.surfaceeffects.shaderutil.SdfShaderLibrary -import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary - -/** - * Shader class that renders an expanding ripple effect. The ripple contains three elements: - * 1. an expanding filled [RippleShape] that appears in the beginning and quickly fades away - * 2. an expanding ring that appears throughout the effect - * 3. an expanding ring-shaped area that reveals noise over #2. - * - * The ripple shader will be default to the circle shape if not specified. - * - * Modeled after frameworks/base/graphics/java/android/graphics/drawable/RippleShader.java. - */ -class RippleShader(rippleShape: RippleShape = RippleShape.CIRCLE) : - RuntimeShader(buildShader(rippleShape)) { - - /** Shapes that the [RippleShader] supports. */ - enum class RippleShape { - CIRCLE, - ROUNDED_BOX, - ELLIPSE - } - // language=AGSL - companion object { - private val TAG = RippleShader::class.simpleName - - // Default fade in/ out values. The value range is [0,1]. - const val DEFAULT_FADE_IN_START = 0f - const val DEFAULT_FADE_OUT_END = 1f - - const val DEFAULT_BASE_RING_FADE_IN_END = 0.1f - const val DEFAULT_BASE_RING_FADE_OUT_START = 0.3f - - const val DEFAULT_SPARKLE_RING_FADE_IN_END = 0.1f - const val DEFAULT_SPARKLE_RING_FADE_OUT_START = 0.4f - - const val DEFAULT_CENTER_FILL_FADE_IN_END = 0f - const val DEFAULT_CENTER_FILL_FADE_OUT_START = 0f - const val DEFAULT_CENTER_FILL_FADE_OUT_END = 0.6f - - const val RIPPLE_SPARKLE_STRENGTH: Float = 0.3f - const val RIPPLE_DEFAULT_COLOR: Int = 0xffffffff.toInt() - const val RIPPLE_DEFAULT_ALPHA: Int = 115 // full opacity is 255. - - private const val SHADER_UNIFORMS = - """ - uniform vec2 in_center; - uniform vec2 in_size; - uniform float in_cornerRadius; - uniform float in_thickness; - uniform float in_time; - uniform float in_distort_radial; - uniform float in_distort_xy; - uniform float in_fadeSparkle; - uniform float in_fadeFill; - uniform float in_fadeRing; - uniform float in_blur; - uniform float in_pixelDensity; - layout(color) uniform vec4 in_color; - uniform float in_sparkle_strength; - """ - - private const val SHADER_CIRCLE_MAIN = - """ - vec4 main(vec2 p) { - vec2 p_distorted = distort(p, in_time, in_distort_radial, in_distort_xy); - float radius = in_size.x * 0.5; - float sparkleRing = soften(circleRing(p_distorted-in_center, radius), in_blur); - float inside = soften(sdCircle(p_distorted-in_center, radius * 1.25), in_blur); - float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time * 0.00175) - * (1.-sparkleRing) * in_fadeSparkle; - - float rippleInsideAlpha = (1.-inside) * in_fadeFill; - float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing; - float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * in_color.a; - vec4 ripple = vec4(in_color.rgb, 1.0) * rippleAlpha; - return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength); - } - """ - - private const val SHADER_ROUNDED_BOX_MAIN = - """ - vec4 main(vec2 p) { - float sparkleRing = soften(roundedBoxRing(p-in_center, in_size, in_cornerRadius, - in_thickness), in_blur); - float inside = soften(sdRoundedBox(p-in_center, in_size * 1.25, in_cornerRadius), - in_blur); - float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time * 0.00175) - * (1.-sparkleRing) * in_fadeSparkle; - - float rippleInsideAlpha = (1.-inside) * in_fadeFill; - float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing; - float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * in_color.a; - vec4 ripple = vec4(in_color.rgb, 1.0) * rippleAlpha; - return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength); - } - """ - - private const val SHADER_ELLIPSE_MAIN = - """ - vec4 main(vec2 p) { - vec2 p_distorted = distort(p, in_time, in_distort_radial, in_distort_xy); - - float sparkleRing = soften(ellipseRing(p_distorted-in_center, in_size), in_blur); - float inside = soften(sdEllipse(p_distorted-in_center, in_size * 1.2), in_blur); - float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time * 0.00175) - * (1.-sparkleRing) * in_fadeSparkle; - - float rippleInsideAlpha = (1.-inside) * in_fadeFill; - float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing; - float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * in_color.a; - vec4 ripple = vec4(in_color.rgb, 1.0) * rippleAlpha; - return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength); - } - """ - - private const val CIRCLE_SHADER = - SHADER_UNIFORMS + - ShaderUtilLibrary.SHADER_LIB + - SdfShaderLibrary.SHADER_SDF_OPERATION_LIB + - SdfShaderLibrary.CIRCLE_SDF + - SHADER_CIRCLE_MAIN - private const val ROUNDED_BOX_SHADER = - SHADER_UNIFORMS + - ShaderUtilLibrary.SHADER_LIB + - SdfShaderLibrary.SHADER_SDF_OPERATION_LIB + - SdfShaderLibrary.ROUNDED_BOX_SDF + - SHADER_ROUNDED_BOX_MAIN - private const val ELLIPSE_SHADER = - SHADER_UNIFORMS + - ShaderUtilLibrary.SHADER_LIB + - SdfShaderLibrary.SHADER_SDF_OPERATION_LIB + - SdfShaderLibrary.ELLIPSE_SDF + - SHADER_ELLIPSE_MAIN - - private fun buildShader(rippleShape: RippleShape): String = - when (rippleShape) { - RippleShape.CIRCLE -> CIRCLE_SHADER - RippleShape.ROUNDED_BOX -> ROUNDED_BOX_SHADER - RippleShape.ELLIPSE -> ELLIPSE_SHADER - } - - private fun subProgress(start: Float, end: Float, progress: Float): Float { - // Avoid division by 0. - if (start == end) { - // If start and end are the same and progress has exceeded the start/ end point, - // treat it as 1, otherwise 0. - return if (progress > start) 1f else 0f - } - - val min = Math.min(start, end) - val max = Math.max(start, end) - val sub = Math.min(Math.max(progress, min), max) - return (sub - start) / (end - start) - } - - private fun getFade(fadeParams: FadeParams, rawProgress: Float): Float { - val fadeIn = subProgress(fadeParams.fadeInStart, fadeParams.fadeInEnd, rawProgress) - val fadeOut = - 1f - subProgress(fadeParams.fadeOutStart, fadeParams.fadeOutEnd, rawProgress) - - return Math.min(fadeIn, fadeOut) - } - - private fun lerp(start: Float, stop: Float, amount: Float): Float { - return start + (stop - start) * amount - } - - // Copied from [Interpolators#STANDARD]. This is to remove dependency on AnimationLib. - private val STANDARD: Interpolator = PathInterpolator(0.2f, 0f, 0f, 1f) - } - - /** Sets the center position of the ripple. */ - fun setCenter(x: Float, y: Float) { - setFloatUniform("in_center", x, y) - } - - /** - * Blur multipliers for the ripple. - * - * <p>It interpolates from [blurStart] to [blurEnd] based on the [progress]. Increase number to - * add more blur. - */ - var blurStart: Float = 1.25f - var blurEnd: Float = 0.5f - - /** Size of the ripple. */ - val rippleSize = RippleSize() - - /** - * Linear progress of the ripple. Float value between [0, 1]. - * - * <p>Note that the progress here is expected to be linear without any curve applied. - */ - var rawProgress: Float = 0.0f - set(value) { - field = value - progress = STANDARD.getInterpolation(value) - - setFloatUniform("in_fadeSparkle", getFade(sparkleRingFadeParams, value)) - setFloatUniform("in_fadeRing", getFade(baseRingFadeParams, value)) - setFloatUniform("in_fadeFill", getFade(centerFillFadeParams, value)) - } - - /** Progress with Standard easing curve applied. */ - private var progress: Float = 0.0f - set(value) { - field = value - - rippleSize.update(value) - - setFloatUniform("in_size", rippleSize.currentWidth, rippleSize.currentHeight) - setFloatUniform("in_thickness", rippleSize.currentHeight * 0.5f) - // Corner radius is always max of the min between the current width and height. - setFloatUniform( - "in_cornerRadius", - Math.min(rippleSize.currentWidth, rippleSize.currentHeight) - ) - setFloatUniform("in_blur", lerp(1.25f, 0.5f, value)) - } - - /** Play time since the start of the effect. */ - var time: Float = 0.0f - set(value) { - field = value - setFloatUniform("in_time", value) - } - - /** A hex value representing the ripple color, in the format of ARGB */ - var color: Int = 0xffffff - set(value) { - field = value - setColorUniform("in_color", value) - } - - /** - * Noise sparkle intensity. Expected value between [0, 1]. The sparkle is white, and thus with - * strength 0 it's transparent, leaving the ripple fully smooth, while with strength 1 it's - * opaque white and looks the most grainy. - */ - var sparkleStrength: Float = 0.0f - set(value) { - field = value - setFloatUniform("in_sparkle_strength", value) - } - - /** Distortion strength of the ripple. Expected value between[0, 1]. */ - var distortionStrength: Float = 0.0f - set(value) { - field = value - setFloatUniform("in_distort_radial", 75 * rawProgress * value) - setFloatUniform("in_distort_xy", 75 * value) - } - - /** - * Pixel density of the screen that the effects are rendered to. - * - * <p>This value should come from [resources.displayMetrics.density]. - */ - var pixelDensity: Float = 1.0f - set(value) { - field = value - setFloatUniform("in_pixelDensity", value) - } - - /** Parameters that are used to fade in/ out of the sparkle ring. */ - val sparkleRingFadeParams = - FadeParams( - DEFAULT_FADE_IN_START, - DEFAULT_SPARKLE_RING_FADE_IN_END, - DEFAULT_SPARKLE_RING_FADE_OUT_START, - DEFAULT_FADE_OUT_END - ) - - /** - * Parameters that are used to fade in/ out of the base ring. - * - * <p>Note that the shader draws the sparkle ring on top of the base ring. - */ - val baseRingFadeParams = - FadeParams( - DEFAULT_FADE_IN_START, - DEFAULT_BASE_RING_FADE_IN_END, - DEFAULT_BASE_RING_FADE_OUT_START, - DEFAULT_FADE_OUT_END - ) - - /** Parameters that are used to fade in/ out of the center fill. */ - val centerFillFadeParams = - FadeParams( - DEFAULT_FADE_IN_START, - DEFAULT_CENTER_FILL_FADE_IN_END, - DEFAULT_CENTER_FILL_FADE_OUT_START, - DEFAULT_CENTER_FILL_FADE_OUT_END - ) - - /** - * Parameters used for fade in and outs of the ripple. - * - * <p>Note that all the fade in/ outs are "linear" progression. - * - * ``` - * (opacity) - * 1 - * │ - * maxAlpha ← ―――――――――――― - * │ / \ - * │ / \ - * minAlpha ←――――/ \―――― (alpha change) - * │ - * │ - * 0 ―――↑―――↑―――――――――↑―――↑――――1 (progress) - * fadeIn fadeOut - * Start & End Start & End - * ``` - * - * <p>If no fade in/ out is needed, set [fadeInStart] and [fadeInEnd] to 0; [fadeOutStart] and - * [fadeOutEnd] to 1. - */ - data class FadeParams( - /** - * The starting point of the fade out which ends at [fadeInEnd], given that the animation - * goes from 0 to 1. - */ - var fadeInStart: Float = DEFAULT_FADE_IN_START, - /** - * The endpoint of the fade in when the fade in starts at [fadeInStart], given that the - * animation goes from 0 to 1. - */ - var fadeInEnd: Float, - /** - * The starting point of the fade out which ends at 1, given that the animation goes from 0 - * to 1. - */ - var fadeOutStart: Float, - - /** The endpoint of the fade out, given that the animation goes from 0 to 1. */ - var fadeOutEnd: Float = DEFAULT_FADE_OUT_END, - ) - - /** - * Desired size of the ripple at a point t in [progress]. - * - * <p>Note that [progress] is curved and normalized. Below is an example usage: - * SizeAtProgress(t= 0f, width= 0f, height= 0f), SizeAtProgress(t= 0.2f, width= 500f, height= - * 700f), SizeAtProgress(t= 1f, width= 100f, height= 300f) - * - * <p>For simple ripple effects, you will want to use [setMaxSize] as it is translated into: - * SizeAtProgress(t= 0f, width= 0f, height= 0f), SizeAtProgress(t= 1f, width= maxWidth, height= - * maxHeight) - */ - data class SizeAtProgress( - /** Time t in [0,1] progress range. */ - var t: Float, - /** Target width size of the ripple at time [t]. */ - var width: Float, - /** Target height size of the ripple at time [t]. */ - var height: Float - ) - - /** Updates and stores the ripple size. */ - inner class RippleSize { - @VisibleForTesting var sizes = mutableListOf<SizeAtProgress>() - @VisibleForTesting var currentSizeIndex = 0 - @VisibleForTesting val initialSize = SizeAtProgress(0f, 0f, 0f) - - var currentWidth: Float = 0f - private set - var currentHeight: Float = 0f - private set - - /** - * Sets the max size of the ripple. - * - * <p>Use this if the ripple shape simply changes linearly. - */ - fun setMaxSize(width: Float, height: Float) { - setSizeAtProgresses(initialSize, SizeAtProgress(1f, width, height)) - } - - /** - * Sets the list of [sizes]. - * - * <p>Note that setting this clears the existing sizes. - */ - fun setSizeAtProgresses(vararg sizes: SizeAtProgress) { - // Reset everything. - this.sizes.clear() - currentSizeIndex = 0 - - this.sizes.addAll(sizes) - this.sizes.sortBy { it.t } - } - - /** - * Updates the current ripple size based on the progress. - * - * <p>Should be called when progress updates. - */ - fun update(progress: Float) { - val targetIndex = updateTargetIndex(progress) - val prevIndex = Math.max(targetIndex - 1, 0) - - val targetSize = sizes[targetIndex] - val prevSize = sizes[prevIndex] - - val subProgress = subProgress(prevSize.t, targetSize.t, progress) - - currentWidth = targetSize.width * subProgress + prevSize.width - currentHeight = targetSize.height * subProgress + prevSize.height - } - - private fun updateTargetIndex(progress: Float): Int { - if (sizes.isEmpty()) { - // It could be empty on init. - if (progress > 0f) { - Log.e( - TAG, - "Did you forget to set the ripple size? Use [setMaxSize] or " + - "[setSizeAtProgresses] before playing the animation." - ) - } - // If there's no size is set, we set everything to 0 and return early. - setSizeAtProgresses(initialSize) - return currentSizeIndex - } - - var candidate = sizes[currentSizeIndex] - - while (progress > candidate.t) { - currentSizeIndex = Math.min(currentSizeIndex + 1, sizes.size - 1) - candidate = sizes[currentSizeIndex] - } - - return currentSizeIndex - } - } -} diff --git a/systemUI/anim/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt b/systemUI/anim/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt deleted file mode 100644 index b899127095..0000000000 --- a/systemUI/anim/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt +++ /dev/null @@ -1,252 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.surfaceeffects.ripple - -import android.animation.Animator -import android.animation.AnimatorListenerAdapter -import android.animation.ValueAnimator -import android.content.Context -import android.content.res.Configuration -import android.graphics.Canvas -import android.graphics.Paint -import android.util.AttributeSet -import android.view.View -import androidx.core.graphics.ColorUtils -import com.android.systemui.surfaceeffects.ripple.RippleShader.RippleShape - -/** - * A generic expanding ripple effect. - * - * Set up the shader with a desired [RippleShape] using [setupShader], [setMaxSize] and [setCenter], - * then call [startRipple] to trigger the ripple expansion. - */ -open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) { - - protected lateinit var rippleShader: RippleShader - lateinit var rippleShape: RippleShape - private set - - private val ripplePaint = Paint() - protected val animator: ValueAnimator = ValueAnimator.ofFloat(0f, 1f) - - var duration: Long = 1750 - - fun setMaxSize(maxWidth: Float, maxHeight: Float) { - rippleShader.rippleSize.setMaxSize(maxWidth, maxHeight) - } - - private var centerX: Float = 0.0f - private var centerY: Float = 0.0f - fun setCenter(x: Float, y: Float) { - this.centerX = x - this.centerY = y - rippleShader.setCenter(x, y) - } - - override fun onConfigurationChanged(newConfig: Configuration?) { - rippleShader.pixelDensity = resources.displayMetrics.density - super.onConfigurationChanged(newConfig) - } - - override fun onAttachedToWindow() { - rippleShader.pixelDensity = resources.displayMetrics.density - super.onAttachedToWindow() - } - - /** Initializes the shader. Must be called before [startRipple]. */ - fun setupShader(rippleShape: RippleShape = RippleShape.CIRCLE) { - this.rippleShape = rippleShape - rippleShader = RippleShader(rippleShape) - - rippleShader.color = RippleShader.RIPPLE_DEFAULT_COLOR - rippleShader.rawProgress = 0f - rippleShader.sparkleStrength = RippleShader.RIPPLE_SPARKLE_STRENGTH - rippleShader.pixelDensity = resources.displayMetrics.density - - ripplePaint.shader = rippleShader - } - - /** - * Sets the fade parameters for the base ring. - * - * <p>Base ring indicates a blurred ring below the sparkle ring. See - * [RippleShader.baseRingFadeParams]. - */ - @JvmOverloads - fun setBaseRingFadeParams( - fadeInStart: Float = rippleShader.baseRingFadeParams.fadeInStart, - fadeInEnd: Float = rippleShader.baseRingFadeParams.fadeInEnd, - fadeOutStart: Float = rippleShader.baseRingFadeParams.fadeOutStart, - fadeOutEnd: Float = rippleShader.baseRingFadeParams.fadeOutEnd - ) { - setFadeParams( - rippleShader.baseRingFadeParams, - fadeInStart, - fadeInEnd, - fadeOutStart, - fadeOutEnd - ) - } - - /** - * Sets the fade parameters for the sparkle ring. - * - * <p>Sparkle ring refers to the ring that's drawn on top of the base ring. See - * [RippleShader.sparkleRingFadeParams]. - */ - @JvmOverloads - fun setSparkleRingFadeParams( - fadeInStart: Float = rippleShader.sparkleRingFadeParams.fadeInStart, - fadeInEnd: Float = rippleShader.sparkleRingFadeParams.fadeInEnd, - fadeOutStart: Float = rippleShader.sparkleRingFadeParams.fadeOutStart, - fadeOutEnd: Float = rippleShader.sparkleRingFadeParams.fadeOutEnd - ) { - setFadeParams( - rippleShader.sparkleRingFadeParams, - fadeInStart, - fadeInEnd, - fadeOutStart, - fadeOutEnd - ) - } - - /** - * Sets the fade parameters for the center fill. - * - * <p>One common use case is set all the params to 1, which completely removes the center fill. - * See [RippleShader.centerFillFadeParams]. - */ - @JvmOverloads - fun setCenterFillFadeParams( - fadeInStart: Float = rippleShader.centerFillFadeParams.fadeInStart, - fadeInEnd: Float = rippleShader.centerFillFadeParams.fadeInEnd, - fadeOutStart: Float = rippleShader.centerFillFadeParams.fadeOutStart, - fadeOutEnd: Float = rippleShader.centerFillFadeParams.fadeOutEnd - ) { - setFadeParams( - rippleShader.centerFillFadeParams, - fadeInStart, - fadeInEnd, - fadeOutStart, - fadeOutEnd - ) - } - - private fun setFadeParams( - fadeParams: RippleShader.FadeParams, - fadeInStart: Float, - fadeInEnd: Float, - fadeOutStart: Float, - fadeOutEnd: Float - ) { - with(fadeParams) { - this.fadeInStart = fadeInStart - this.fadeInEnd = fadeInEnd - this.fadeOutStart = fadeOutStart - this.fadeOutEnd = fadeOutEnd - } - } - - /** - * Sets blur multiplier at start and end of the progress. - * - * <p>It interpolates between [start] and [end]. No need to set it if using default blur. - */ - fun setBlur(start: Float, end: Float) { - rippleShader.blurStart = start - rippleShader.blurEnd = end - } - - /** - * Sets the list of [RippleShader.SizeAtProgress]. - * - * <p>Note that this clears the list before it sets with the new data. - */ - fun setSizeAtProgresses(vararg targetSizes: RippleShader.SizeAtProgress) { - rippleShader.rippleSize.setSizeAtProgresses(*targetSizes) - } - - @JvmOverloads - fun startRipple(onAnimationEnd: Runnable? = null) { - if (animator.isRunning) { - return // Ignore if ripple effect is already playing - } - animator.duration = duration - animator.addUpdateListener { updateListener -> - val now = updateListener.currentPlayTime - val progress = updateListener.animatedValue as Float - rippleShader.rawProgress = progress - rippleShader.distortionStrength = 1 - progress - rippleShader.time = now.toFloat() - invalidate() - } - animator.addListener( - object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - onAnimationEnd?.run() - } - } - ) - animator.start() - } - - /** - * Set the color to be used for the ripple. - * - * The alpha value of the color will be applied to the ripple. The alpha range is [0-255]. - */ - fun setColor(color: Int, alpha: Int = RippleShader.RIPPLE_DEFAULT_ALPHA) { - rippleShader.color = ColorUtils.setAlphaComponent(color, alpha) - } - - /** Set the intensity of the sparkles. */ - fun setSparkleStrength(strength: Float) { - rippleShader.sparkleStrength = strength - } - - /** Indicates whether the ripple animation is playing. */ - fun rippleInProgress(): Boolean = animator.isRunning - - override fun onDraw(canvas: Canvas) { - if (!canvas.isHardwareAccelerated) { - // Drawing with the ripple shader requires hardware acceleration, so skip if it's - // unsupported. - return - } - // To reduce overdraw, we mask the effect to a circle or a rectangle that's bigger than the - // active effect area. Values here should be kept in sync with the animation implementation - // in the ripple shader. - if (rippleShape == RippleShape.CIRCLE) { - val maskRadius = rippleShader.rippleSize.currentWidth - canvas.drawCircle(centerX, centerY, maskRadius, ripplePaint) - } else if (rippleShape == RippleShape.ELLIPSE) { - val maskWidth = rippleShader.rippleSize.currentWidth * 2 - val maskHeight = rippleShader.rippleSize.currentHeight * 2 - canvas.drawRect( - /* left= */ centerX - maskWidth, - /* top= */ centerY - maskHeight, - /* right= */ centerX + maskWidth, - /* bottom= */ centerY + maskHeight, - ripplePaint - ) - } else { // RippleShape.RoundedBox - // No masking for the rounded box, as it has more blur which requires larger bounds. - // Masking creates sharp bounds even when the masking is 4 times bigger. - canvas.drawPaint(ripplePaint) - } - } -} diff --git a/systemUI/anim/src/com/android/systemui/surfaceeffects/shaders/SolidColorShader.kt b/systemUI/anim/src/com/android/systemui/surfaceeffects/shaders/SolidColorShader.kt deleted file mode 100644 index c94fad7246..0000000000 --- a/systemUI/anim/src/com/android/systemui/surfaceeffects/shaders/SolidColorShader.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.systemui.surfaceeffects.shaders - -import android.graphics.RuntimeShader - -/** Simply renders a solid color. */ -class SolidColorShader(color: Int) : RuntimeShader(SHADER) { - // language=AGSL - private companion object { - private const val SHADER = - """ - layout(color) uniform vec4 in_color; - vec4 main(vec2 p) { - return in_color; - } - """ - } - - init { - setColorUniform("in_color", color) - } -} diff --git a/systemUI/anim/src/com/android/systemui/surfaceeffects/shaders/SparkleShader.kt b/systemUI/anim/src/com/android/systemui/surfaceeffects/shaders/SparkleShader.kt deleted file mode 100644 index df07856e32..0000000000 --- a/systemUI/anim/src/com/android/systemui/surfaceeffects/shaders/SparkleShader.kt +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.systemui.surfaceeffects.shaders - -import android.graphics.Color -import android.graphics.RuntimeShader -import android.graphics.Shader -import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary - -/** - * Renders sparkles based on the luma matte. - * - * For example, you can pass in simplex noise as the luma matte and have a cloud looking sparkles. - * - * You may want to utilize this shader by: (Preferred) 1. Create a RuntimeShaderEffect and set the - * [RenderEffect] to the target [View]. - * 2. Create a custom [View], set the shader to the [Paint] and use [Canvas.drawPaint] in [onDraw]. - */ -class SparkleShader : RuntimeShader(SPARKLE_SHADER) { - // language=AGSL - companion object { - private const val UNIFORMS = - """ - // Used it for RenderEffect. For example: - // myView.setRenderEffect( - // RenderEffect.createRuntimeShaderEffect(SparkleShader(), "in_src") - // ) - uniform shader in_src; - uniform half in_time; - uniform half in_pixelate; - uniform shader in_lumaMatte; - layout(color) uniform vec4 in_color; - """ - private const val MAIN_SHADER = - """vec4 main(vec2 p) { - half3 src = in_src.eval(p).rgb; - half luma = getLuminosity(in_lumaMatte.eval(p).rgb); - half sparkle = sparkles(p - mod(p, in_pixelate), in_time); - half3 mask = maskLuminosity(in_color.rgb * sparkle, luma); - - return vec4(src * mask * in_color.a, in_color.a); - } - """ - private const val SPARKLE_SHADER = UNIFORMS + ShaderUtilLibrary.SHADER_LIB + MAIN_SHADER - - /** Highly recommended to use this value unless specified by design spec. */ - const val DEFAULT_SPARKLE_PIXELATE_AMOUNT = 0.8f - } - - init { - // Initializes the src and luma matte to be white. - setInputShader("in_src", SolidColorShader(Color.WHITE)) - setLumaMatteColor(Color.WHITE) - } - - /** - * Sets the time of the sparkle animation. - * - * This is used for animating sparkles. Note that this only makes the sparkles sparkle in place. - * In order to move the sparkles in x, y directions, move the luma matte input instead. - */ - fun setTime(time: Float) { - setFloatUniform("in_time", time) - } - - /** - * Sets pixelated amount of the sparkle. - * - * This value *must* be based on [resources.displayMetrics.density]. Otherwise, this will result - * in having different sparkle sizes on different screens. - * - * Expected to be used as follows: - * <pre> - * {@code - * val pixelDensity = context.resources.displayMetrics.density - * // Sparkles will be 0.8 of the pixel size. - * val sparkleShader = SparkleShader().apply { setPixelateAmount(pixelDensity * 0.8f) } - * } - * </pre> - */ - fun setPixelateAmount(pixelateAmount: Float) { - setFloatUniform("in_pixelate", pixelateAmount) - } - - /** - * Sets the luma matte for the sparkles. The luminosity determines the sparkle's visibility. - * Useful for setting a complex mask (e.g. simplex noise, texture, etc.) - */ - fun setLumaMatte(lumaMatte: Shader) { - setInputShader("in_lumaMatte", lumaMatte) - } - - /** Sets the luma matte for the sparkles. Useful for setting a solid color. */ - fun setLumaMatteColor(color: Int) { - setInputShader("in_lumaMatte", SolidColorShader(color)) - } - - /** Sets the color of the sparkles. Expect to have the alpha value encoded. */ - fun setColor(color: Int) { - setColorUniform("in_color", color) - } -} diff --git a/systemUI/anim/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt b/systemUI/anim/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt deleted file mode 100644 index 4efab58347..0000000000 --- a/systemUI/anim/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.systemui.surfaceeffects.shaderutil - -/** Library class that contains 2D signed distance functions. */ -class SdfShaderLibrary { - // language=AGSL - companion object { - const val CIRCLE_SDF = - """ - float sdCircle(vec2 p, float r) { - return (length(p)-r) / r; - } - - float circleRing(vec2 p, float radius) { - float thicknessHalf = radius * 0.25; - - float outerCircle = sdCircle(p, radius + thicknessHalf); - float innerCircle = sdCircle(p, radius); - - return subtract(outerCircle, innerCircle); - } - """ - - const val BOX_SDF = - """ - float sdBox(vec2 p, vec2 size) { - size = size * 0.5; - vec2 d = abs(p) - size; - return length(max(d, 0.)) + min(max(d.x, d.y), 0.) / size.y; - } - """ - - const val ROUNDED_BOX_SDF = - """ - float sdRoundedBox(vec2 p, vec2 size, float cornerRadius) { - size *= 0.5; - cornerRadius *= 0.5; - vec2 d = abs(p) - size + cornerRadius; - - float outside = length(max(d, 0.0)); - float inside = min(max(d.x, d.y), 0.0); - - return (outside + inside - cornerRadius) / size.y; - } - - float roundedBoxRing(vec2 p, vec2 size, float cornerRadius, - float borderThickness) { - float outerRoundBox = sdRoundedBox(p, size + vec2(borderThickness), - cornerRadius + borderThickness); - float innerRoundBox = sdRoundedBox(p, size, cornerRadius); - return subtract(outerRoundBox, innerRoundBox); - } - """ - - // Used non-trigonometry parametrization and Halley's method (iterative) for root finding. - // This is more expensive than the regular circle SDF, recommend to use the circle SDF if - // possible. - const val ELLIPSE_SDF = - """float sdEllipse(vec2 p, vec2 wh) { - wh *= 0.5; - - // symmetry - (wh.x > wh.y) ? wh = wh.yx, p = abs(p.yx) : p = abs(p); - - vec2 u = wh*p, v = wh*wh; - - float U1 = u.y/2.0; - float U2 = v.y-v.x; - float U3 = u.x-U2; - float U4 = u.x+U2; - float U5 = 4.0*U1; - float U6 = 6.0*U1; - float U7 = 3.0*U3; - - float t = 0.5; - for (int i = 0; i < 3; i ++) { - float F1 = t*(t*t*(U1*t+U3)+U4)-U1; - float F2 = t*t*(U5*t+U7)+U4; - float F3 = t*(U6*t+U7); - - t += (F1*F2)/(F1*F3-F2*F2); - } - - t = clamp(t, 0.0, 1.0); - - float d = distance(p, wh*vec2(1.0-t*t,2.0*t)/(t*t+1.0)); - d /= wh.y; - - return (dot(p/wh,p/wh)>1.0) ? d : -d; - } - - float ellipseRing(vec2 p, vec2 wh) { - vec2 thicknessHalf = wh * 0.25; - - float outerEllipse = sdEllipse(p, wh + thicknessHalf); - float innerEllipse = sdEllipse(p, wh); - - return subtract(outerEllipse, innerEllipse); - } - """ - - const val SHADER_SDF_OPERATION_LIB = - """ - float soften(float d, float blur) { - float blurHalf = blur * 0.5; - return smoothstep(-blurHalf, blurHalf, d); - } - - float subtract(float outer, float inner) { - return max(outer, -inner); - } - """ - } -} diff --git a/systemUI/anim/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt b/systemUI/anim/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt deleted file mode 100644 index 867bbb7d74..0000000000 --- a/systemUI/anim/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt +++ /dev/null @@ -1,201 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.systemui.surfaceeffects.shaderutil - -/** Common utility functions that are used for computing shaders. */ -object ShaderUtilLibrary { - // language=AGSL - const val SHADER_LIB = - """ - float triangleNoise(vec2 n) { - n = fract(n * vec2(5.3987, 5.4421)); - n += dot(n.yx, n.xy + vec2(21.5351, 14.3137)); - float xy = n.x * n.y; - // compute in [0..2[ and remap to [-1.0..1.0[ - return fract(xy * 95.4307) + fract(xy * 75.04961) - 1.0; - } - - const float PI = 3.1415926535897932384626; - - float sparkles(vec2 uv, float t) { - float n = triangleNoise(uv); - float s = 0.0; - for (float i = 0; i < 4; i += 1) { - float l = i * 0.01; - float h = l + 0.1; - float o = smoothstep(n - l, h, n); - o *= abs(sin(PI * o * (t + 0.55 * i))); - s += o; - } - return s; - } - - vec2 distort(vec2 p, float time, float distort_amount_radial, - float distort_amount_xy) { - float angle = atan(p.y, p.x); - return p + vec2(sin(angle * 8 + time * 0.003 + 1.641), - cos(angle * 5 + 2.14 + time * 0.00412)) * distort_amount_radial - + vec2(sin(p.x * 0.01 + time * 0.00215 + 0.8123), - cos(p.y * 0.01 + time * 0.005931)) * distort_amount_xy; - } - - // Perceived luminosity (L′), not absolute luminosity. - half getLuminosity(vec3 c) { - return 0.3 * c.r + 0.59 * c.g + 0.11 * c.b; - } - - // Creates a luminosity mask and clamp to the legal range. - vec3 maskLuminosity(vec3 dest, float lum) { - dest.rgb *= vec3(lum); - // Clip back into the legal range - dest = clamp(dest, vec3(0.), vec3(1.0)); - return dest; - } - - // Integer mod. GLSL es 1.0 doesn't have integer mod :( - int imod(int a, int b) { - return a - (b * (a / b)); - } - - ivec3 imod(ivec3 a, int b) { - return ivec3(imod(a.x, b), imod(a.y, b), imod(a.z, b)); - } - - // Integer based hash function with the return range of [-1, 1]. - vec3 hash(vec3 p) { - ivec3 v = ivec3(p); - v = v * 1671731 + 10139267; - - v.x += v.y * v.z; - v.y += v.z * v.x; - v.z += v.x * v.y; - - ivec3 v2 = v / 65536; // v >> 16 - v = imod((10 - imod((v + v2), 10)), 10); // v ^ v2 - - v.x += v.y * v.z; - v.y += v.z * v.x; - v.z += v.x * v.y; - - // Use sin and cos to map the range to [-1, 1]. - return vec3(sin(float(v.x)), cos(float(v.y)), sin(float(v.z))); - } - - // Skew factors (non-uniform). - const half SKEW = 0.3333333; // 1/3 - const half UNSKEW = 0.1666667; // 1/6 - - // Return range roughly [-1,1]. - // It's because the hash function (that returns a random gradient vector) returns - // different magnitude of vectors. Noise doesn't have to be in the precise range thus - // skipped normalize. - half simplex3d(vec3 p) { - // Skew the input coordinate, so that we get squashed cubical grid - vec3 s = floor(p + (p.x + p.y + p.z) * SKEW); - - // Unskew back - vec3 u = s - (s.x + s.y + s.z) * UNSKEW; - - // Unskewed coordinate that is relative to p, to compute the noise contribution - // based on the distance. - vec3 c0 = p - u; - - // We have six simplices (in this case tetrahedron, since we are in 3D) that we - // could possibly in. - // Here, we are finding the correct tetrahedron (simplex shape), and traverse its - // four vertices (c0..3) when computing noise contribution. - // The way we find them is by comparing c0's x,y,z values. - // For example in 2D, we can find the triangle (simplex shape in 2D) that we are in - // by comparing x and y values. i.e. x>y lower, x<y, upper triangle. - // Same applies in 3D. - // - // Below indicates the offsets (or offset directions) when c0=(x0,y0,z0) - // x0>y0>z0: (1,0,0), (1,1,0), (1,1,1) - // x0>z0>y0: (1,0,0), (1,0,1), (1,1,1) - // z0>x0>y0: (0,0,1), (1,0,1), (1,1,1) - // z0>y0>x0: (0,0,1), (0,1,1), (1,1,1) - // y0>z0>x0: (0,1,0), (0,1,1), (1,1,1) - // y0>x0>z0: (0,1,0), (1,1,0), (1,1,1) - // - // The rule is: - // * For offset1, set 1 at the max component, otherwise 0. - // * For offset2, set 0 at the min component, otherwise 1. - // * For offset3, set 1 for all. - // - // Encode x0-y0, y0-z0, z0-x0 in a vec3 - vec3 en = c0 - c0.yzx; - // Each represents whether x0>y0, y0>z0, z0>x0 - en = step(vec3(0.), en); - // en.zxy encodes z0>x0, x0>y0, y0>x0 - vec3 offset1 = en * (1. - en.zxy); // find max - vec3 offset2 = 1. - en.zxy * (1. - en); // 1-(find min) - vec3 offset3 = vec3(1.); - - vec3 c1 = c0 - offset1 + UNSKEW; - vec3 c2 = c0 - offset2 + UNSKEW * 2.; - vec3 c3 = c0 - offset3 + UNSKEW * 3.; - - // Kernel summation: dot(max(0, r^2-d^2))^4, noise contribution) - // - // First compute d^2, squared distance to the point. - vec4 w; // w = max(0, r^2 - d^2)) - w.x = dot(c0, c0); - w.y = dot(c1, c1); - w.z = dot(c2, c2); - w.w = dot(c3, c3); - - // Noise contribution should decay to zero before they cross the simplex boundary. - // Usually r^2 is 0.5 or 0.6; - // 0.5 ensures continuity but 0.6 increases the visual quality for the application - // where discontinuity isn't noticeable. - w = max(0.6 - w, 0.); - - // Noise contribution from each point. - vec4 nc; - nc.x = dot(hash(s), c0); - nc.y = dot(hash(s + offset1), c1); - nc.z = dot(hash(s + offset2), c2); - nc.w = dot(hash(s + offset3), c3); - - nc *= w*w*w*w; - - // Add all the noise contributions. - // Should multiply by the possible max contribution to adjust the range in [-1,1]. - return dot(vec4(32.), nc); - } - - // Random rotations. - // The way you create fractal noise is layering simplex noise with some rotation. - // To make random cloud looking noise, the rotations should not align. (Otherwise it - // creates patterned noise). - // Below rotations only rotate in one axis. - const mat3 rot1 = mat3(1.0, 0. ,0., 0., 0.15, -0.98, 0., 0.98, 0.15); - const mat3 rot2 = mat3(-0.95, 0. ,-0.3, 0., 1., 0., 0.3, 0., -0.95); - const mat3 rot3 = mat3(1.0, 0. ,0., 0., -0.44, -0.89, 0., 0.89, -0.44); - - // Octave = 4 - // Divide each coefficient by 3 to produce more grainy noise. - half simplex3d_fractal(vec3 p) { - return 0.675 * simplex3d(p * rot1) + 0.225 * simplex3d(2.0 * p * rot2) - + 0.075 * simplex3d(4.0 * p * rot3) + 0.025 * simplex3d(8.0 * p); - } - - // Screen blend - vec3 screen(vec3 dest, vec3 src) { - return dest + src - dest * src; - } - """ -} diff --git a/systemUI/anim/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt b/systemUI/anim/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt deleted file mode 100644 index ba8f1ace02..0000000000 --- a/systemUI/anim/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.systemui.surfaceeffects.turbulencenoise - -import android.graphics.Color -import java.util.Random - -/** Turbulence noise animation configuration. */ -data class TurbulenceNoiseAnimationConfig( - /** The number of grids that is used to generate noise. */ - val gridCount: Float = DEFAULT_NOISE_GRID_COUNT, - - /** Multiplier for the noise luma matte. Increase this for brighter effects. */ - val luminosityMultiplier: Float = DEFAULT_LUMINOSITY_MULTIPLIER, - - /** Initial noise offsets. */ - val noiseOffsetX: Float = random.nextFloat(), - val noiseOffsetY: Float = random.nextFloat(), - val noiseOffsetZ: Float = random.nextFloat(), - - /** - * Noise move speed variables. - * - * Its sign determines the direction; magnitude determines the speed. <ul> - * - * ``` - * <li> [noiseMoveSpeedX] positive: right to left; negative: left to right. - * <li> [noiseMoveSpeedY] positive: bottom to top; negative: top to bottom. - * <li> [noiseMoveSpeedZ] its sign doesn't matter much, as it moves in Z direction. Use it - * to add turbulence in place. - * ``` - * - * </ul> - */ - val noiseMoveSpeedX: Float = 0f, - val noiseMoveSpeedY: Float = 0f, - val noiseMoveSpeedZ: Float = DEFAULT_NOISE_SPEED_Z, - - /** Color of the effect. */ - val color: Int = DEFAULT_COLOR, - /** Background color of the effect. */ - val screenColor: Int = DEFAULT_SCREEN_COLOR, - val width: Float = 0f, - val height: Float = 0f, - val maxDuration: Float = DEFAULT_MAX_DURATION_IN_MILLIS, - val easeInDuration: Float = DEFAULT_EASING_DURATION_IN_MILLIS, - val easeOutDuration: Float = DEFAULT_EASING_DURATION_IN_MILLIS, - val pixelDensity: Float = 1f, - /** - * Variants in noise. Higher number means more contrast; lower number means less contrast but - * make the noise dimmed. You may want to increase the [lumaMatteBlendFactor] to compensate. - * Expected range [0, 1]. - */ - val lumaMatteBlendFactor: Float = DEFAULT_LUMA_MATTE_BLEND_FACTOR, - /** - * Offset for the overall brightness in noise. Higher number makes the noise brighter. You may - * want to use this if you have made the noise softer using [lumaMatteBlendFactor]. Expected - * range [0, 1]. - */ - val lumaMatteOverallBrightness: Float = DEFAULT_LUMA_MATTE_OVERALL_BRIGHTNESS, - /** Whether to flip the luma mask. */ - val shouldInverseNoiseLuminosity: Boolean = false, -) { - companion object { - const val DEFAULT_MAX_DURATION_IN_MILLIS = 30_000f // Max 30 sec - const val DEFAULT_EASING_DURATION_IN_MILLIS = 750f - const val DEFAULT_LUMINOSITY_MULTIPLIER = 1f - const val DEFAULT_NOISE_GRID_COUNT = 1.2f - const val DEFAULT_NOISE_SPEED_Z = 0.3f - const val DEFAULT_COLOR = Color.WHITE - const val DEFAULT_LUMA_MATTE_BLEND_FACTOR = 1f - const val DEFAULT_LUMA_MATTE_OVERALL_BRIGHTNESS = 0f - const val DEFAULT_SCREEN_COLOR = Color.BLACK - private val random = Random() - } -} diff --git a/systemUI/anim/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt b/systemUI/anim/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt deleted file mode 100644 index e862f0c43a..0000000000 --- a/systemUI/anim/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.systemui.surfaceeffects.turbulencenoise - -import android.view.View -import androidx.annotation.VisibleForTesting - -/** Plays [TurbulenceNoiseView] in ease-in, main (no easing), and ease-out order. */ -class TurbulenceNoiseController(private val turbulenceNoiseView: TurbulenceNoiseView) { - - companion object { - /** - * States of the turbulence noise animation. - * - * <p>The state is designed to be follow the order below: [AnimationState.EASE_IN], - * [AnimationState.MAIN], [AnimationState.EASE_OUT]. - */ - enum class AnimationState { - EASE_IN, - MAIN, - EASE_OUT, - NOT_PLAYING - } - } - - /** Current state of the animation. */ - @VisibleForTesting - var state: AnimationState = AnimationState.NOT_PLAYING - set(value) { - field = value - if (state == AnimationState.NOT_PLAYING) { - turbulenceNoiseView.visibility = View.INVISIBLE - turbulenceNoiseView.clearConfig() - } else { - turbulenceNoiseView.visibility = View.VISIBLE - } - } - - init { - turbulenceNoiseView.visibility = View.INVISIBLE - } - - /** Updates the color of the noise. */ - fun updateNoiseColor(color: Int) { - if (state == AnimationState.NOT_PLAYING) { - return - } - turbulenceNoiseView.updateColor(color) - } - - /** - * Plays [TurbulenceNoiseView] with the given config. - * - * <p>It plays ease-in, main, and ease-out animations in sequence. - */ - fun play( - baseType: TurbulenceNoiseShader.Companion.Type, - config: TurbulenceNoiseAnimationConfig - ) { - if (state != AnimationState.NOT_PLAYING) { - return // Ignore if any of the animation is playing. - } - - turbulenceNoiseView.initShader(baseType, config) - playEaseInAnimation() - } - - // TODO(b/237282226): Support force finish. - /** Finishes the main animation, which triggers the ease-out animation. */ - fun finish() { - if (state == AnimationState.MAIN) { - turbulenceNoiseView.finish(nextAnimation = this::playEaseOutAnimation) - } - } - - private fun playEaseInAnimation() { - if (state != AnimationState.NOT_PLAYING) { - return - } - state = AnimationState.EASE_IN - - turbulenceNoiseView.playEaseIn(this::playMainAnimation) - } - - private fun playMainAnimation() { - if (state != AnimationState.EASE_IN) { - return - } - state = AnimationState.MAIN - - turbulenceNoiseView.play(this::playEaseOutAnimation) - } - - private fun playEaseOutAnimation() { - if (state != AnimationState.MAIN) { - return - } - state = AnimationState.EASE_OUT - - turbulenceNoiseView.playEaseOut(onAnimationEnd = { state = AnimationState.NOT_PLAYING }) - } -} diff --git a/systemUI/anim/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt b/systemUI/anim/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt deleted file mode 100644 index 025c8b9dce..0000000000 --- a/systemUI/anim/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt +++ /dev/null @@ -1,294 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.systemui.surfaceeffects.turbulencenoise - -import android.graphics.RuntimeShader -import com.android.systemui.surfaceeffects.shaders.SolidColorShader -import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary -import java.lang.Float.max - -/** - * Shader that renders turbulence simplex noise, by default no octave. - * - * @param baseType the base [Type] of the shader. - */ -class TurbulenceNoiseShader(val baseType: Type = Type.SIMPLEX_NOISE) : - RuntimeShader(getShader(baseType)) { - // language=AGSL - companion object { - /** Uniform name for the background buffer (e.g. image, solid color, etc.). */ - const val BACKGROUND_UNIFORM = "in_src" - private const val UNIFORMS = - """ - uniform shader ${BACKGROUND_UNIFORM}; - uniform float in_gridNum; - uniform vec3 in_noiseMove; - uniform vec2 in_size; - uniform float in_aspectRatio; - uniform float in_opacity; - uniform float in_pixelDensity; - uniform float in_inverseLuma; - uniform half in_lumaMatteBlendFactor; - uniform half in_lumaMatteOverallBrightness; - layout(color) uniform vec4 in_color; - layout(color) uniform vec4 in_screenColor; - """ - - private const val SIMPLEX_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 noiseP = vec3(uv + in_noiseMove.xy, in_noiseMove.z) * in_gridNum; - vec3 color = getColorTurbulenceMask(simplex3d(noiseP) * in_inverseLuma); - - // Blend the result with the background color. - color = in_src.eval(p).rgb + color * 0.6; - - // Add dither with triangle distribution to avoid color banding. Dither in the - // shader here as we are in gamma space. - float dither = triangleNoise(p * in_pixelDensity) / 255.; - color += dither.rrr; - - // Return the pre-multiplied alpha result, i.e. [R*A, G*A, B*A, A]. - return vec4(color * in_opacity, in_opacity); - } - """ - - private const val FRACTAL_SHADER = - """ - vec4 main(vec2 p) { - vec2 uv = p / in_size.xy; - uv.x *= in_aspectRatio; - - vec3 noiseP = vec3(uv + in_noiseMove.xy, in_noiseMove.z) * in_gridNum; - vec3 color = getColorTurbulenceMask(simplex3d_fractal(noiseP) * in_inverseLuma); - - // Blend the result with the background color. - color = in_src.eval(p).rgb + color * 0.6; - - // Skip dithering. - return vec4(color * in_opacity, in_opacity); - } - """ - - /** - * This effect has two layers: color turbulence effect with sparkles on top. - * 1. Gets the luma matte using Simplex noise. - * 2. Generate a colored turbulence layer with the luma matte. - * 3. Generate a colored sparkle layer with the same luma matter. - * 4. Apply a screen color to the background image. - * 5. Composite the previous result with the color turbulence. - * 6. Composite the latest result with the sparkles. - */ - private const val SIMPLEX_SPARKLE_SHADER = - """ - vec4 main(vec2 p) { - vec2 uv = p / in_size.xy; - uv.x *= in_aspectRatio; - - vec3 noiseP = vec3(uv + in_noiseMove.xy, in_noiseMove.z) * in_gridNum; - // Luma is used for both color and sparkle masks. - float luma = simplex3d(noiseP) * in_inverseLuma; - - // Get color layer (color mask with in_color applied) - vec3 colorLayer = getColorTurbulenceMask(simplex3d(noiseP) * in_inverseLuma); - float dither = triangleNoise(p * in_pixelDensity) / 255.; - colorLayer += dither.rrr; - - // Get sparkle layer (sparkle mask with particles & in_color applied) - vec3 sparkleLayer = getSparkleTurbulenceMask(luma, p); - - // Composite with the background. - half4 bgColor = in_src.eval(p); - half sparkleOpacity = smoothstep(0, 0.75, in_opacity); - - half3 effect = screen(bgColor.rgb, in_screenColor.rgb); - effect = screen(effect, colorLayer * 0.22); - effect += sparkleLayer * sparkleOpacity; - - return mix(bgColor, vec4(effect, 1.), in_opacity); - } - """ - - private const val COMMON_FUNCTIONS = - /** - * Below two functions generate turbulence layers (color or sparkles applied) with the - * given luma matte. They both return a mask with in_color applied. - */ - """ - vec3 getColorTurbulenceMask(float luma) { - // Bring it to [0, 1] range. - luma = luma * 0.5 + 0.5; - - half colorLuma = - saturate(luma * in_lumaMatteBlendFactor + in_lumaMatteOverallBrightness) - * in_opacity; - vec3 colorLayer = maskLuminosity(in_color.rgb, colorLuma); - - return colorLayer; - } - - vec3 getSparkleTurbulenceMask(float luma, vec2 p) { - half lumaIntensity = 1.75; - half lumaBrightness = -1.3; - half sparkleLuma = max(luma * lumaIntensity + lumaBrightness, 0.); - - float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_noiseMove.z); - vec3 sparkleLayer = maskLuminosity(in_color.rgb * sparkle, sparkleLuma); - - return sparkleLayer; - } - """ - private const val SIMPLEX_NOISE_SHADER = - ShaderUtilLibrary.SHADER_LIB + UNIFORMS + COMMON_FUNCTIONS + SIMPLEX_SHADER - private const val FRACTAL_NOISE_SHADER = - ShaderUtilLibrary.SHADER_LIB + UNIFORMS + COMMON_FUNCTIONS + FRACTAL_SHADER - private const val SPARKLE_NOISE_SHADER = - ShaderUtilLibrary.SHADER_LIB + UNIFORMS + COMMON_FUNCTIONS + SIMPLEX_SPARKLE_SHADER - - enum class Type { - /** Effect with a simple color noise turbulence. */ - SIMPLEX_NOISE, - /** Effect with a simple color noise turbulence, with fractal. */ - SIMPLEX_NOISE_FRACTAL, - /** Effect with color & sparkle turbulence with screen color layer. */ - SIMPLEX_NOISE_SPARKLE - } - - fun getShader(type: Type): String { - return when (type) { - Type.SIMPLEX_NOISE -> SIMPLEX_NOISE_SHADER - Type.SIMPLEX_NOISE_FRACTAL -> FRACTAL_NOISE_SHADER - Type.SIMPLEX_NOISE_SPARKLE -> SPARKLE_NOISE_SHADER - } - } - } - - /** Convenient way for updating multiple uniform values via config object. */ - fun applyConfig(config: TurbulenceNoiseAnimationConfig) { - setGridCount(config.gridCount) - setPixelDensity(config.pixelDensity) - setColor(config.color) - setScreenColor(config.screenColor) - setSize(config.width, config.height) - setLumaMatteFactors(config.lumaMatteBlendFactor, config.lumaMatteOverallBrightness) - setInverseNoiseLuminosity(config.shouldInverseNoiseLuminosity) - setNoiseMove(config.noiseOffsetX, config.noiseOffsetY, config.noiseOffsetZ) - } - - /** Sets the number of grid for generating noise. */ - fun setGridCount(gridNumber: Float = 1.0f) { - setFloatUniform("in_gridNum", gridNumber) - } - - /** - * Sets the pixel density of the screen. - * - * Used it for noise dithering. - */ - fun setPixelDensity(pixelDensity: Float) { - setFloatUniform("in_pixelDensity", pixelDensity) - } - - /** 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. Only relevant - * to [Type.SIMPLEX_NOISE_SPARKLE]. - */ - fun setScreenColor(color: Int) { - setColorUniform("in_screenColor", color) - } - - /** - * Sets the background color of the effect. Alpha is ignored. If you are using [RenderEffect], - * no need to call this function since the background image of the View will be used. - */ - fun setBackgroundColor(color: Int) { - setInputShader(BACKGROUND_UNIFORM, SolidColorShader(color)) - } - - /** - * Sets the opacity of the effect. Not intended to set by the client as it is used for - * ease-in/out animations. - * - * Expected value range is [1, 0]. - */ - fun setOpacity(opacity: Float) { - setFloatUniform("in_opacity", opacity) - } - - /** Sets the size of the shader. */ - fun setSize(width: Float, height: Float) { - setFloatUniform("in_size", width, height) - setFloatUniform("in_aspectRatio", width / max(height, 0.001f)) - } - - /** - * Sets blend and brightness factors of the luma matte. - * - * @param lumaMatteBlendFactor increases or decreases the amount of variance in noise. Setting - * this a lower number removes variations. I.e. the turbulence noise will look more blended. - * Expected input range is [0, 1]. - * @param lumaMatteOverallBrightness adds the overall brightness of the turbulence noise. - * Expected input range is [0, 1]. - * - * Example usage: You may want to apply a small number to [lumaMatteBlendFactor], such as 0.2, - * which makes the noise look softer. However it makes the overall noise look dim, so you want - * offset something like 0.3 for [lumaMatteOverallBrightness] to bring back its overall - * brightness. - */ - fun setLumaMatteFactors( - lumaMatteBlendFactor: Float = 1f, - lumaMatteOverallBrightness: Float = 0f - ) { - setFloatUniform("in_lumaMatteBlendFactor", lumaMatteBlendFactor) - setFloatUniform("in_lumaMatteOverallBrightness", lumaMatteOverallBrightness) - } - - /** - * Sets whether to inverse the luminosity of the noise. - * - * By default noise will be used as a luma matte as is. This means that you will see color in - * the brighter area. If you want to invert it, meaning blend color onto the darker side, set to - * true. - */ - fun setInverseNoiseLuminosity(inverse: Boolean) { - setFloatUniform("in_inverseLuma", if (inverse) -1f else 1f) - } - - /** 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 - - /** Sets noise move offset in x, y, and z direction. */ - fun setNoiseMove(x: Float, y: Float, z: Float) { - noiseOffsetX = x - noiseOffsetY = y - noiseOffsetZ = z - setFloatUniform("in_noiseMove", noiseOffsetX, noiseOffsetY, noiseOffsetZ) - } -} diff --git a/systemUI/anim/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt b/systemUI/anim/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt deleted file mode 100644 index 5e72e3bd1e..0000000000 --- a/systemUI/anim/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt +++ /dev/null @@ -1,236 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.systemui.surfaceeffects.turbulencenoise - -import android.animation.Animator -import android.animation.AnimatorListenerAdapter -import android.animation.ValueAnimator -import android.content.Context -import android.graphics.BlendMode -import android.graphics.Canvas -import android.graphics.Paint -import android.util.AttributeSet -import android.view.View -import androidx.annotation.VisibleForTesting - -/** - * View that renders turbulence noise effect. - * - * <p>Use [TurbulenceNoiseController] to control the turbulence animation. If you want to make some - * other turbulence noise effects, either add functionality to [TurbulenceNoiseController] or create - * another controller instead of extend or modify the [TurbulenceNoiseView]. - * - * <p>Please keep the [TurbulenceNoiseView] (or View in general) not aware of the state. - * - * <p>Please avoid inheriting the View if possible. Instead, reconsider adding a controller for a - * new case. - */ -class TurbulenceNoiseView(context: Context?, attrs: AttributeSet?) : View(context, attrs) { - - companion object { - private const val MS_TO_SEC = 0.001f - } - - private val paint = Paint() - @VisibleForTesting var turbulenceNoiseShader: TurbulenceNoiseShader? = null - @VisibleForTesting var noiseConfig: TurbulenceNoiseAnimationConfig? = null - @VisibleForTesting var currentAnimator: ValueAnimator? = null - - override fun onDraw(canvas: Canvas) { - if (!canvas.isHardwareAccelerated) { - // Drawing with the turbulence noise shader requires hardware acceleration, so skip - // if it's unsupported. - return - } - - canvas.drawPaint(paint) - } - - /** Updates the color during the animation. No-op if there's no animation playing. */ - internal fun updateColor(color: Int) { - turbulenceNoiseShader?.setColor(color) - } - - /** Plays the turbulence noise with no easing. */ - @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) - fun play(onAnimationEnd: Runnable? = null) { - if (noiseConfig == null) { - return - } - val config = noiseConfig!! - val shader = turbulenceNoiseShader!! - - val animator = ValueAnimator.ofFloat(0f, 1f) - animator.duration = config.maxDuration.toLong() - - // Animation should start from the initial position to avoid abrupt transition. - val initialX = shader.noiseOffsetX - val initialY = shader.noiseOffsetY - val initialZ = shader.noiseOffsetZ - - animator.addUpdateListener { updateListener -> - val timeInSec = updateListener.currentPlayTime * MS_TO_SEC - shader.setNoiseMove( - initialX + timeInSec * config.noiseMoveSpeedX, - initialY + timeInSec * config.noiseMoveSpeedY, - initialZ + timeInSec * config.noiseMoveSpeedZ - ) - - shader.setOpacity(config.luminosityMultiplier) - - invalidate() - } - - animator.addListener( - object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - currentAnimator = null - onAnimationEnd?.run() - } - } - ) - - animator.start() - currentAnimator = animator - } - - /** Plays the turbulence noise with linear ease-in. */ - @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) - fun playEaseIn(onAnimationEnd: Runnable? = null) { - if (noiseConfig == null) { - return - } - val config = noiseConfig!! - val shader = turbulenceNoiseShader!! - - val animator = ValueAnimator.ofFloat(0f, 1f) - animator.duration = config.easeInDuration.toLong() - - // Animation should start from the initial position to avoid abrupt transition. - val initialX = shader.noiseOffsetX - val initialY = shader.noiseOffsetY - val initialZ = shader.noiseOffsetZ - - animator.addUpdateListener { updateListener -> - val timeInSec = updateListener.currentPlayTime * MS_TO_SEC - val progress = updateListener.animatedValue as Float - - shader.setNoiseMove( - initialX + timeInSec * config.noiseMoveSpeedX, - initialY + timeInSec * config.noiseMoveSpeedY, - initialZ + timeInSec * config.noiseMoveSpeedZ - ) - - // TODO: Replace it with a better curve. - shader.setOpacity(progress * config.luminosityMultiplier) - - invalidate() - } - - animator.addListener( - object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - currentAnimator = null - onAnimationEnd?.run() - } - } - ) - - animator.start() - currentAnimator = animator - } - - /** Plays the turbulence noise with linear ease-out. */ - @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) - fun playEaseOut(onAnimationEnd: Runnable? = null) { - if (noiseConfig == null) { - return - } - val config = noiseConfig!! - val shader = turbulenceNoiseShader!! - - val animator = ValueAnimator.ofFloat(0f, 1f) - animator.duration = config.easeOutDuration.toLong() - - // Animation should start from the initial position to avoid abrupt transition. - val initialX = shader.noiseOffsetX - val initialY = shader.noiseOffsetY - val initialZ = shader.noiseOffsetZ - - animator.addUpdateListener { updateListener -> - val timeInSec = updateListener.currentPlayTime * MS_TO_SEC - val progress = updateListener.animatedValue as Float - - shader.setNoiseMove( - initialX + timeInSec * config.noiseMoveSpeedX, - initialY + timeInSec * config.noiseMoveSpeedY, - initialZ + timeInSec * config.noiseMoveSpeedZ - ) - - // TODO: Replace it with a better curve. - shader.setOpacity((1f - progress) * config.luminosityMultiplier) - - invalidate() - } - - animator.addListener( - object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - currentAnimator = null - onAnimationEnd?.run() - } - } - ) - - animator.start() - currentAnimator = animator - } - - /** Finishes the current animation if playing and plays the next animation if given. */ - @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) - fun finish(nextAnimation: Runnable? = null) { - // Calling Animator#end sets the animation state back to the initial state. Using pause to - // avoid visual artifacts. - currentAnimator?.pause() - currentAnimator = null - - nextAnimation?.run() - } - - /** Applies shader uniforms. Must be called before playing animation. */ - @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) - fun initShader( - baseType: TurbulenceNoiseShader.Companion.Type, - config: TurbulenceNoiseAnimationConfig - ) { - noiseConfig = config - if (turbulenceNoiseShader == null || turbulenceNoiseShader?.baseType != baseType) { - turbulenceNoiseShader = TurbulenceNoiseShader(baseType) - - paint.shader = turbulenceNoiseShader!! - } - turbulenceNoiseShader!!.applyConfig(config) - } - - /** Sets the blend mode of the View. */ - fun setBlendMode(blendMode: BlendMode) { - paint.blendMode = blendMode - } - - internal fun clearConfig() { - noiseConfig = null - } -} diff --git a/systemUI/anim/src/com/android/systemui/surfaceeffects/utils/MathUtils.kt b/systemUI/anim/src/com/android/systemui/surfaceeffects/utils/MathUtils.kt deleted file mode 100644 index 7ed3b87f68..0000000000 --- a/systemUI/anim/src/com/android/systemui/surfaceeffects/utils/MathUtils.kt +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.surfaceeffects.utils - -/** Copied from android.utils.MathUtils */ -object MathUtils { - fun lerp(start: Float, stop: Float, amount: Float): Float { - return start + (stop - start) * amount - } -} diff --git a/systemUI/anim/src/com/android/systemui/util/AnimatorExtensions.kt b/systemUI/anim/src/com/android/systemui/util/AnimatorExtensions.kt deleted file mode 100644 index 35dbb89ad8..0000000000 --- a/systemUI/anim/src/com/android/systemui/util/AnimatorExtensions.kt +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.util - -import androidx.core.animation.Animator - -/** - * Add an action which will be invoked when the animation has ended. - * - * @return the [Animator.AnimatorListener] added to the Animator - * @see Animator.end - */ -inline fun Animator.doOnEnd( - crossinline action: (animator: Animator) -> Unit -): Animator.AnimatorListener = addListener(onEnd = action) - -/** - * Add an action which will be invoked when the animation has started. - * - * @return the [Animator.AnimatorListener] added to the Animator - * @see Animator.start - */ -inline fun Animator.doOnStart( - crossinline action: (animator: Animator) -> Unit -): Animator.AnimatorListener = addListener(onStart = action) - -/** - * Add an action which will be invoked when the animation has been cancelled. - * - * @return the [Animator.AnimatorListener] added to the Animator - * @see Animator.cancel - */ -inline fun Animator.doOnCancel( - crossinline action: (animator: Animator) -> Unit -): Animator.AnimatorListener = addListener(onCancel = action) - -/** - * Add an action which will be invoked when the animation has repeated. - * - * @return the [Animator.AnimatorListener] added to the Animator - */ -inline fun Animator.doOnRepeat( - crossinline action: (animator: Animator) -> Unit -): Animator.AnimatorListener = addListener(onRepeat = action) - -/** - * Add a listener to this Animator using the provided actions. - * - * @return the [Animator.AnimatorListener] added to the Animator - */ -inline fun Animator.addListener( - crossinline onEnd: (animator: Animator) -> Unit = {}, - crossinline onStart: (animator: Animator) -> Unit = {}, - crossinline onCancel: (animator: Animator) -> Unit = {}, - crossinline onRepeat: (animator: Animator) -> Unit = {} -): Animator.AnimatorListener { - val listener = - object : Animator.AnimatorListener { - override fun onAnimationRepeat(animator: Animator) = onRepeat(animator) - override fun onAnimationEnd(animator: Animator) = onEnd(animator) - override fun onAnimationCancel(animator: Animator) = onCancel(animator) - override fun onAnimationStart(animator: Animator) = onStart(animator) - } - addListener(listener) - return listener -} diff --git a/systemUI/anim/src/com/android/systemui/util/Dialog.kt b/systemUI/anim/src/com/android/systemui/util/Dialog.kt deleted file mode 100644 index ce9e17c9e8..0000000000 --- a/systemUI/anim/src/com/android/systemui/util/Dialog.kt +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.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 -import com.android.systemui.animation.back.floatingSystemSurfacesForSysUi -import com.android.systemui.animation.back.onBackAnimationCallbackFrom -import com.android.systemui.animation.back.registerOnBackInvokedCallbackOnViewAttached -import com.android.systemui.animation.view.LaunchableFrameLayout - -/** - * Register on the Dialog's [OnBackInvokedDispatcher] an animation using the [BackAnimationSpec]. - * The [BackTransformation] will be applied on the [targetView]. - */ -@JvmOverloads -@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) -fun Dialog.registerAnimationOnBackInvoked( - targetView: View, - backAnimationSpec: BackAnimationSpec = - BackAnimationSpec.floatingSystemSurfacesForSysUi( - displayMetricsProvider = { targetView.resources.displayMetrics }, - ), -) { - targetView.registerOnBackInvokedCallbackOnViewAttached( - onBackInvokedDispatcher = onBackInvokedDispatcher, - onBackAnimationCallback = - onBackAnimationCallbackFrom( - backAnimationSpec = backAnimationSpec, - displayMetrics = targetView.resources.displayMetrics, - onBackProgressed = { backTransformation -> backTransformation.applyTo(targetView) }, - onBackInvoked = { dismiss() }, - ), - ) -} - -/** - * Make the dialog window (and therefore its DecorView) fullscreen to make it possible to animate - * outside its bounds. No-op if the dialog is already fullscreen. - * - * <p>Returns null if the dialog is already fullscreen. Otherwise, returns a pair containing a view - * and a layout listener. The new view matches the original dialog DecorView in size, position, and - * background. This new view will be a child of the modified, transparent, fullscreen DecorView. The - * layout listener is listening to changes to the modified DecorView. It is the responsibility of - * the caller to deregister the listener when the dialog is dismissed. - */ -fun Dialog.maybeForceFullscreen(): Pair<LaunchableFrameLayout, View.OnLayoutChangeListener>? { - // Create the dialog so that its onCreate() method is called, which usually sets the dialog - // content. - create() - - val window = window!! - val decorView = window.decorView as ViewGroup - - val isWindowFullscreen = - window.attributes.width == MATCH_PARENT && window.attributes.height == MATCH_PARENT - if (isWindowFullscreen) { - return null - } - - // We will make the dialog window (and therefore its DecorView) fullscreen to make it possible - // to animate outside its bounds. - // - // Before that, we add a new View as a child of the DecorView with the same size and gravity as - // that DecorView, then we add all original children of the DecorView to that new View. Finally - // we remove the background of the DecorView and add it to the new View, then we make the - // DecorView fullscreen. This new View now acts as a fake (non fullscreen) window. - // - // On top of that, we also add a fullscreen transparent background between the DecorView and the - // view that we added so that we can dismiss the dialog when this view is clicked. This is - // necessary because DecorView overrides onTouchEvent and therefore we can't set the click - // listener directly on the (now fullscreen) DecorView. - val fullscreenTransparentBackground = FrameLayout(context) - decorView.addView( - fullscreenTransparentBackground, - 0 /* index */, - FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT) - ) - - val dialogContentWithBackground = LaunchableFrameLayout(context) - dialogContentWithBackground.background = decorView.background - - // Make the window background transparent. Note that setting the window (or DecorView) - // background drawable to null leads to issues with background color (not being transparent) or - // with insets that are not refreshed. Therefore we need to set it to something not null, hence - // we are using android.R.color.transparent here. - window.setBackgroundDrawableResource(android.R.color.transparent) - - // Close the dialog when clicking outside of it. - fullscreenTransparentBackground.setOnClickListener { dismiss() } - dialogContentWithBackground.isClickable = true - - // Make sure the transparent and dialog backgrounds are not focusable by accessibility - // features. - fullscreenTransparentBackground.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO - dialogContentWithBackground.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO - - fullscreenTransparentBackground.addView( - dialogContentWithBackground, - FrameLayout.LayoutParams( - window.attributes.width, - window.attributes.height, - window.attributes.gravity - ) - ) - - // Move all original children of the DecorView to the new View we just added. - for (i in 1 until decorView.childCount) { - val view = decorView.getChildAt(1) - decorView.removeViewAt(1) - dialogContentWithBackground.addView(view) - } - - // Make the window fullscreen and add a layout listener to ensure it stays fullscreen. - window.setLayout(MATCH_PARENT, MATCH_PARENT) - val decorViewLayoutListener = - View.OnLayoutChangeListener { - v, - left, - top, - right, - bottom, - oldLeft, - oldTop, - oldRight, - oldBottom -> - if ( - window.attributes.width != MATCH_PARENT || window.attributes.height != MATCH_PARENT - ) { - // The dialog size changed, copy its size to dialogContentWithBackground and make - // the dialog window full screen again. - val layoutParams = dialogContentWithBackground.layoutParams - layoutParams.width = window.attributes.width - layoutParams.height = window.attributes.height - dialogContentWithBackground.layoutParams = layoutParams - window.setLayout(MATCH_PARENT, MATCH_PARENT) - } - } - decorView.addOnLayoutChangeListener(decorViewLayoutListener) - - return dialogContentWithBackground to decorViewLayoutListener -} diff --git a/systemUI/anim/src/com/android/systemui/util/Dimension.kt b/systemUI/anim/src/com/android/systemui/util/Dimension.kt deleted file mode 100644 index 4bc9972dd5..0000000000 --- a/systemUI/anim/src/com/android/systemui/util/Dimension.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.util - -import android.content.Context -import android.content.res.Resources -import android.util.DisplayMetrics -import android.util.TypedValue - -/** Convert [this] number of dps to device pixels. */ -fun Number.dpToPx(context: Context): Float = dpToPx(resources = context.resources) - -/** Convert [this] number of dps to device pixels. */ -fun Number.dpToPx(resources: Resources): Float = dpToPx(displayMetrics = resources.displayMetrics) - -/** Convert [this] number of dps to device pixels. */ -fun Number.dpToPx(displayMetrics: DisplayMetrics): Float = - TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, toFloat(), displayMetrics) diff --git a/systemUI/plugin/src/com/android/systemui/plugins/clocks/AlarmData.kt b/systemUI/plugin/src/com/android/systemui/plugins/clocks/AlarmData.kt deleted file mode 100644 index 837857bfa3..0000000000 --- a/systemUI/plugin/src/com/android/systemui/plugins/clocks/AlarmData.kt +++ /dev/null @@ -1,6 +0,0 @@ -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 deleted file mode 100644 index 8ba8e40768..0000000000 --- a/systemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file - * except in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the - * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ -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 deleted file mode 100644 index 789a47304e..0000000000 --- a/systemUI/plugin/src/com/android/systemui/plugins/clocks/WeatherData.kt +++ /dev/null @@ -1,123 +0,0 @@ -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 deleted file mode 100644 index e927ec3c85..0000000000 --- a/systemUI/plugin/src/com/android/systemui/plugins/clocks/ZenData.kt +++ /dev/null @@ -1,22 +0,0 @@ -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/shared/src/com/android/systemui/shared/condition/Condition.java b/systemUI/shared/src/com/android/systemui/shared/condition/Condition.java deleted file mode 100644 index 670feeba6e..0000000000 --- a/systemUI/shared/src/com/android/systemui/shared/condition/Condition.java +++ /dev/null @@ -1,306 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.shared.condition; - -import android.util.Log; - -import androidx.annotation.IntDef; -import androidx.annotation.NonNull; -import androidx.lifecycle.Lifecycle; -import androidx.lifecycle.LifecycleEventObserver; -import androidx.lifecycle.LifecycleOwner; - -import kotlinx.coroutines.CoroutineScope; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; - -/** - * Base class for a condition that needs to be fulfilled in order for {@link Monitor} to inform - * its callbacks. - */ -public abstract class Condition { - private final String mTag = getClass().getSimpleName(); - - private final ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>(); - private final boolean mOverriding; - private final CoroutineScope mScope; - private Boolean mIsConditionMet; - private boolean mStarted = false; - - /** - * By default, conditions have an initial value of false and are not overriding. - */ - public Condition(CoroutineScope scope) { - this(scope, false, false); - } - - /** - * Constructor for specifying initial state and overriding condition attribute. - * - * @param initialConditionMet Initial state of the condition. - * @param overriding Whether this condition overrides others. - */ - protected Condition(CoroutineScope scope, Boolean initialConditionMet, boolean overriding) { - mIsConditionMet = initialConditionMet; - mOverriding = overriding; - mScope = scope; - } - - /** - * Starts monitoring the condition. - */ - protected abstract void start(); - - /** - * Stops monitoring the condition. - */ - protected abstract void stop(); - - /** - * Condition should be started as soon as there is an active subscription. - */ - public static final int START_EAGERLY = 0; - /** - * Condition should be started lazily only if needed. But once started, it will not be cancelled - * unless there are no more active subscriptions. - */ - public static final int START_LAZILY = 1; - /** - * Condition should be started lazily only if needed, and can be stopped when not needed. This - * should be used for conditions which are expensive to keep running. - */ - public static final int START_WHEN_NEEDED = 2; - - @Retention(RetentionPolicy.SOURCE) - @IntDef({START_EAGERLY, START_LAZILY, START_WHEN_NEEDED}) - @interface StartStrategy { - } - - @StartStrategy - protected abstract int getStartStrategy(); - - /** - * Returns whether the current condition overrides - */ - public boolean isOverridingCondition() { - return mOverriding; - } - - /** - * Registers a callback to receive updates once started. This should be called before - * {@link #start()}. Also triggers the callback immediately if already started. - */ - public void addCallback(@NonNull Callback callback) { - if (shouldLog()) Log.d(mTag, "adding callback"); - mCallbacks.add(new WeakReference<>(callback)); - - if (mStarted) { - callback.onConditionChanged(this); - return; - } - - start(); - mStarted = true; - } - - /** - * Removes the provided callback from further receiving updates. - */ - public void removeCallback(@NonNull Callback callback) { - if (shouldLog()) Log.d(mTag, "removing callback"); - final Iterator<WeakReference<Callback>> iterator = mCallbacks.iterator(); - while (iterator.hasNext()) { - final Callback cb = iterator.next().get(); - if (cb == null || cb == callback) { - iterator.remove(); - } - } - - if (!mCallbacks.isEmpty() || !mStarted) { - return; - } - - stop(); - mStarted = false; - } - - /** - * Wrapper to {@link #addCallback(Callback)} when a lifecycle is in the resumed state - * and {@link #removeCallback(Callback)} when not resumed automatically. - */ - public Callback observe(LifecycleOwner owner, Callback listener) { - return observe(owner.getLifecycle(), listener); - } - - /** - * Wrapper to {@link #addCallback(Callback)} when a lifecycle is in the resumed state - * and {@link #removeCallback(Condition.Callback)} when not resumed automatically. - */ - public Callback observe(Lifecycle lifecycle, Callback listener) { - lifecycle.addObserver((LifecycleEventObserver) (lifecycleOwner, event) -> { - if (event == Lifecycle.Event.ON_RESUME) { - addCallback(listener); - } else if (event == Lifecycle.Event.ON_PAUSE) { - removeCallback(listener); - } - }); - return listener; - } - - /** - * Updates the value for whether the condition has been fulfilled, and sends an update if the - * value changes and any callback is registered. - * - * @param isConditionMet True if the condition has been fulfilled. False otherwise. - */ - protected void updateCondition(boolean isConditionMet) { - if (mIsConditionMet != null && mIsConditionMet == isConditionMet) { - return; - } - - if (shouldLog()) Log.d(mTag, "updating condition to " + isConditionMet); - mIsConditionMet = isConditionMet; - sendUpdate(); - } - - /** - * Clears the set condition value. This is purposefully separate from - * {@link #updateCondition(boolean)} to avoid confusion around {@code null} values. - */ - protected void clearCondition() { - if (mIsConditionMet == null) { - return; - } - - if (shouldLog()) Log.d(mTag, "clearing condition"); - - mIsConditionMet = null; - sendUpdate(); - } - - private void sendUpdate() { - final Iterator<WeakReference<Callback>> iterator = mCallbacks.iterator(); - while (iterator.hasNext()) { - final Callback cb = iterator.next().get(); - if (cb == null) { - iterator.remove(); - } else { - cb.onConditionChanged(this); - } - } - } - - /** - * Returns whether the condition is set. This method should be consulted to understand the - * value of {@link #isConditionMet()}. - * - * @return {@code true} if value is present, {@code false} otherwise. - */ - public boolean isConditionSet() { - return mIsConditionMet != null; - } - - /** - * Returns whether the condition has been met. Note that this method will return {@code false} - * if the condition is not set as well. - */ - public boolean isConditionMet() { - return Boolean.TRUE.equals(mIsConditionMet); - } - - protected final boolean shouldLog() { - return Log.isLoggable(mTag, Log.DEBUG); - } - - protected final String getTag() { - if (isOverridingCondition()) { - return mTag + "[OVRD]"; - } - - return mTag; - } - - /** - * Returns the state of the condition. - * - "Invalid", condition hasn't been set / not monitored - * - "True", condition has been met - * - "False", condition has not been met - */ - protected final String getState() { - if (!isConditionSet()) { - return "Invalid"; - } - return isConditionMet() ? "True" : "False"; - } - - /** - * Creates a new condition which will only be true when both this condition and all the provided - * conditions are true. - */ - public Condition and(@NonNull Collection<Condition> others) { - final List<Condition> conditions = new ArrayList<>(); - conditions.add(this); - conditions.addAll(others); - return new CombinedCondition(mScope, conditions, Evaluator.OP_AND); - } - - /** - * Creates a new condition which will only be true when both this condition and the provided - * condition is true. - */ - public Condition and(@NonNull Condition... others) { - return and(Arrays.asList(others)); - } - - /** - * Creates a new condition which will only be true when either this condition or any of the - * provided conditions are true. - */ - public Condition or(@NonNull Collection<Condition> others) { - final List<Condition> conditions = new ArrayList<>(); - conditions.add(this); - conditions.addAll(others); - return new CombinedCondition(mScope, conditions, Evaluator.OP_OR); - } - - /** - * Creates a new condition which will only be true when either this condition or the provided - * condition is true. - */ - public Condition or(@NonNull Condition... others) { - return or(Arrays.asList(others)); - } - - /** - * Callback that receives updates about whether the condition has been fulfilled. - */ - public interface Callback { - /** - * Called when the fulfillment of the condition changes. - * - * @param condition The condition in question. - */ - void onConditionChanged(Condition condition); - } -} diff --git a/tests/assets/databases/GridMigrationTest/flagged_result5x5to5x8.db b/tests/assets/databases/GridMigrationTest/flagged_result5x5to5x8.db deleted file mode 100644 index 8bea3ce70857de284cccf8b56073c0d31d09f49b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 77824 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 DPW@A$ diff --git a/tests/multivalentTests/shared/AndroidManifest.xml b/tests/multivalentTests/shared/AndroidManifest.xml deleted file mode 100644 index 1cd0cb318b..0000000000 --- a/tests/multivalentTests/shared/AndroidManifest.xml +++ /dev/null @@ -1,21 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* -** -** Copyright 2023, The Android Open Source Project -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -*/ ---> -<manifest package="com.android.launcher3.testing.shared"> -</manifest> diff --git a/tests/multivalentTests/shared/com/android/launcher3/testing/shared/HotseatCellCenterRequest.java b/tests/multivalentTests/shared/com/android/launcher3/testing/shared/HotseatCellCenterRequest.java deleted file mode 100644 index 7eb035a81f..0000000000 --- a/tests/multivalentTests/shared/com/android/launcher3/testing/shared/HotseatCellCenterRequest.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher3.testing.shared; - -import android.os.Parcel; -import android.os.Parcelable; - -/** - * Request object for querying a hotseat cell region in Rect. - */ -public class HotseatCellCenterRequest implements TestInformationRequest { - public final int cellInd; - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(cellInd); - } - - @Override - public String getRequestName() { - return TestProtocol.REQUEST_HOTSEAT_CELL_CENTER; - } - - public static final Parcelable.Creator<HotseatCellCenterRequest> CREATOR = - new Parcelable.Creator<HotseatCellCenterRequest>() { - - @Override - public HotseatCellCenterRequest createFromParcel(Parcel source) { - return new HotseatCellCenterRequest(source); - } - - @Override - public HotseatCellCenterRequest[] newArray(int size) { - return new HotseatCellCenterRequest[size]; - } - }; - - private HotseatCellCenterRequest(int cellInd) { - this.cellInd = cellInd; - } - - private HotseatCellCenterRequest(Parcel in) { - this(in.readInt()); - } - - /** - * Create a builder for HotseatCellCenterRequest. - * - * @return HotseatCellCenterRequest builder. - */ - public static HotseatCellCenterRequest.Builder builder() { - return new HotseatCellCenterRequest.Builder(); - } - - /** - * HotseatCellCenterRequest Builder. - */ - public static final class Builder { - private int mCellInd; - - private Builder() { - mCellInd = 0; - } - - /** - * Set the index of hotseat cells. - */ - public HotseatCellCenterRequest.Builder setCellInd(int i) { - this.mCellInd = i; - return this; - } - - /** - * build the HotseatCellCenterRequest. - */ - public HotseatCellCenterRequest build() { - return new HotseatCellCenterRequest(mCellInd); - } - } -} diff --git a/tests/multivalentTests/shared/com/android/launcher3/testing/shared/ResourceUtils.java b/tests/multivalentTests/shared/com/android/launcher3/testing/shared/ResourceUtils.java deleted file mode 100644 index 2efecd72e3..0000000000 --- a/tests/multivalentTests/shared/com/android/launcher3/testing/shared/ResourceUtils.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.launcher3.testing.shared; -import android.content.res.Resources; -import android.util.DisplayMetrics; -import android.util.TypedValue; -public class ResourceUtils { - private static final float EPSILON = 0.0001f; - public static final int DEFAULT_NAVBAR_VALUE = 48; - public static final int INVALID_RESOURCE_HANDLE = -1; - public static final String NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE = "navigation_bar_width"; - public static final String NAVBAR_BOTTOM_GESTURE_SIZE = "navigation_bar_gesture_height"; - public static final String NAVBAR_BOTTOM_GESTURE_LARGER_SIZE = - "navigation_bar_gesture_larger_height"; - public static final String NAVBAR_HEIGHT = "navigation_bar_height"; - public static final String NAVBAR_HEIGHT_LANDSCAPE = "navigation_bar_height_landscape"; - public static final String STATUS_BAR_HEIGHT = "status_bar_height"; - public static final String STATUS_BAR_HEIGHT_LANDSCAPE = "status_bar_height_landscape"; - public static final String STATUS_BAR_HEIGHT_PORTRAIT = "status_bar_height_portrait"; - public static final String NAV_BAR_INTERACTION_MODE_RES_NAME = "config_navBarInteractionMode"; - public static int getNavbarSize(String resName, Resources res) { - return getDimenByName(resName, res, DEFAULT_NAVBAR_VALUE); - } - public static int getDimenByName(String resName, Resources res, int defaultValue) { - final int frameSize; - final int frameSizeResID = res.getIdentifier(resName, "dimen", "android"); - if (frameSizeResID != 0) { - frameSize = res.getDimensionPixelSize(frameSizeResID); - } else { - frameSize = pxFromDp(defaultValue, res.getDisplayMetrics()); - } - return frameSize; - } - public static boolean getBoolByName(String resName, Resources res, boolean defaultValue) { - final boolean val; - final int resId = res.getIdentifier(resName, "bool", "android"); - if (resId != 0) { - val = res.getBoolean(resId); - } else { - val = defaultValue; - } - return val; - } - public static int getIntegerByName(String resName, Resources res, int defaultValue) { - int resId = res.getIdentifier(resName, "integer", "android"); - return resId != 0 ? res.getInteger(resId) : defaultValue; - } - public static int pxFromDp(float size, DisplayMetrics metrics) { - return pxFromDp(size, metrics, 1f); - } - public static int pxFromDp(float size, DisplayMetrics metrics, float scale) { - float value = scale * TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, size, metrics); - return size < 0 ? INVALID_RESOURCE_HANDLE : roundPxValueFromFloat(value); - } - /** - * Rounds a pixel value, taking into account floating point errors. - * - * <p>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 deleted file mode 100644 index 3828203280..0000000000 --- a/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestInformationRequest.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package 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 deleted file mode 100644 index 59d0de69ee..0000000000 --- a/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestProtocol.java +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.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 deleted file mode 100644 index e2cd8ea965..0000000000 --- a/tests/multivalentTests/shared/com/android/launcher3/testing/shared/WorkspaceCellCenterRequest.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -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<WorkspaceCellCenterRequest> CREATOR = - new Parcelable.Creator<WorkspaceCellCenterRequest>() { - - @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/celllayout/FavoriteItemsTransaction.java b/tests/multivalentTests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java deleted file mode 100644 index 0c3081fa83..0000000000 --- a/tests/multivalentTests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -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.LauncherAppState; -import com.android.launcher3.LauncherModel; -import com.android.launcher3.LauncherSettings; -import com.android.launcher3.model.ModelDbController; -import com.android.launcher3.model.data.CollectionInfo; -import com.android.launcher3.model.data.ItemInfo; -import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction; -import com.android.launcher3.util.ContentWriter; -import com.android.launcher3.util.ModelTestExtensions; - -import java.util.ArrayList; -import java.util.function.Supplier; - -public class FavoriteItemsTransaction { - private ArrayList<Supplier<ItemInfo>> mItemsToSubmit; - private Context mContext; - - public FavoriteItemsTransaction(Context context) { - mItemsToSubmit = new ArrayList<>(); - mContext = context; - } - - public FavoriteItemsTransaction addItem(Supplier<ItemInfo> itemInfo) { - this.mItemsToSubmit.add(itemInfo); - return this; - } - - /** - * Commits all the ItemInfo into the database of Favorites - **/ - public void commit() { - LauncherModel model = LauncherAppState.getInstance(mContext).getModel(); - // Load the model once so that there is no pending migration: - ModelTestExtensions.INSTANCE.loadModelSync(model); - runOnExecutorSync(MODEL_EXECUTOR, () -> { - ModelDbController controller = model.getModelDbController(); - // Migrate any previous data so that the DB state is correct - controller.tryMigrateDB(null /* restoreEventLogger */); - - // Create DB again to load fresh data - controller.createEmptyDB(); - controller.clearEmptyDbFlag(); - - // Add new data - try (SQLiteTransaction transaction = controller.newTransaction()) { - int count = mItemsToSubmit.size(); - ArrayList<ItemInfo> containerItems = new ArrayList<>(); - for (int i = 0; i < count; i++) { - ContentWriter writer = new ContentWriter(mContext); - ItemInfo item = mItemsToSubmit.get(i).get(); - - if (item instanceof CollectionInfo ci) { - for (ItemInfo itemInfo : ci.getContents()) { - itemInfo.container = i; - containerItems.add(itemInfo); - } - } - - item.onAddToDatabase(writer); - writer.put(LauncherSettings.Favorites._ID, i); - controller.insert(TABLE_NAME, writer.getValues(mContext)); - } - - for (int i = 0; i < containerItems.size(); i++) { - ContentWriter writer = new ContentWriter(mContext); - ItemInfo item = containerItems.get(i); - item.onAddToDatabase(writer); - writer.put(LauncherSettings.Favorites._ID, count + i); - controller.insert(TABLE_NAME, writer.getValues(mContext)); - } - transaction.commit(); - } - }); - - // Reload model - runOnExecutorSync(MAIN_EXECUTOR, model::forceReload); - ModelTestExtensions.INSTANCE.loadModelSync(model); - } -} diff --git a/tests/multivalentTests/src/com/android/launcher3/celllayout/board/CellType.java b/tests/multivalentTests/src/com/android/launcher3/celllayout/board/CellType.java deleted file mode 100644 index 49c146b32a..0000000000 --- a/tests/multivalentTests/src/com/android/launcher3/celllayout/board/CellType.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.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 deleted file mode 100644 index 39ba434dc0..0000000000 --- a/tests/multivalentTests/src/com/android/launcher3/celllayout/board/FolderPoint.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.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 deleted file mode 100644 index d3d297003d..0000000000 --- a/tests/multivalentTests/src/com/android/launcher3/celllayout/board/IconPoint.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.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/TestWorkspaceBuilder.java b/tests/multivalentTests/src/com/android/launcher3/celllayout/board/TestWorkspaceBuilder.java deleted file mode 100644 index 8a427dd81b..0000000000 --- a/tests/multivalentTests/src/com/android/launcher3/celllayout/board/TestWorkspaceBuilder.java +++ /dev/null @@ -1,192 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.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<ItemInfo> 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 deleted file mode 100644 index c90ce8504f..0000000000 --- a/tests/multivalentTests/src/com/android/launcher3/celllayout/board/WidgetRect.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.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/model/FactitiousDbController.kt b/tests/multivalentTests/src/com/android/launcher3/model/FactitiousDbController.kt deleted file mode 100644 index 711e1d2d65..0000000000 --- a/tests/multivalentTests/src/com/android/launcher3/model/FactitiousDbController.kt +++ /dev/null @@ -1,60 +0,0 @@ -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<out String>?, - selection: String?, - selectionArgs: Array<out String>?, - 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 deleted file mode 100644 index 761f06d514..0000000000 --- a/tests/multivalentTests/src/com/android/launcher3/model/GridSizeMigrationUtilTest.kt +++ /dev/null @@ -1,772 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -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<String> - 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<String?, Point>() - 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<String?>) { - 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<String?, Triple<Int, Int, Int>> { - // 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<String?, Triple<Int, Int, Int>>() - 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<String?, Int>() - 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<String?, Int>() - 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/settings/SettingsActivityTest.java b/tests/multivalentTests/src/com/android/launcher3/settings/SettingsActivityTest.java deleted file mode 100644 index ba148632ac..0000000000 --- a/tests/multivalentTests/src/com/android/launcher3/settings/SettingsActivityTest.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher3.settings; - -import static androidx.preference.PreferenceFragmentCompat.ARG_PREFERENCE_ROOT; -import static androidx.test.espresso.Espresso.onView; -import static androidx.test.espresso.action.ViewActions.click; -import static androidx.test.espresso.assertion.ViewAssertions.matches; -import static androidx.test.espresso.contrib.RecyclerViewActions.actionOnItem; -import static androidx.test.espresso.intent.Intents.intended; -import static androidx.test.espresso.intent.matcher.BundleMatchers.hasEntry; -import static androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent; -import static androidx.test.espresso.intent.matcher.IntentMatchers.hasExtra; -import static androidx.test.espresso.matcher.ViewMatchers.hasDescendant; -import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; -import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription; -import static androidx.test.espresso.matcher.ViewMatchers.withId; -import static androidx.test.espresso.matcher.ViewMatchers.withText; - -import static com.android.launcher3.settings.SettingsActivity.DEVELOPER_OPTIONS_KEY; -import static com.android.launcher3.settings.SettingsActivity.EXTRA_FRAGMENT_ARGS; -import static com.android.launcher3.settings.SettingsActivity.EXTRA_FRAGMENT_ROOT_KEY; - -import static com.google.common.truth.Truth.assertThat; - -import static org.hamcrest.Matchers.allOf; -import static org.hamcrest.Matchers.equalTo; - -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; - -import androidx.test.core.app.ActivityScenario; -import androidx.test.core.app.ApplicationProvider; -import androidx.test.espresso.intent.Intents; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import com.android.launcher3.R; -import com.android.systemui.shared.plugins.PluginPrefs; - -import org.junit.After; -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(AndroidJUnit4.class) -public class SettingsActivityTest { - - private Context mApplicationContext; - - @Before - public void setUp() { - mApplicationContext = ApplicationProvider.getApplicationContext(); - Intents.init(); - } - - @After - public void tearDown() { - Intents.release(); - } - - @Test - @Ignore // b/199309785 - public void testSettings_aboutTap_launchesActivity() { - ActivityScenario.launch(SettingsActivity.class); - onView(withId(androidx.preference.R.id.recycler_view)).perform( - actionOnItem(hasDescendant(withText("About")), click())); - - intended(allOf( - hasComponent(SettingsActivity.class.getName()), - hasExtra( - equalTo(EXTRA_FRAGMENT_ARGS), - hasEntry(ARG_PREFERENCE_ROOT, "about_screen")))); - } - - @Test - @Ignore // b/199309785 - public void testSettings_developerOptionsTap_launchesActivityWithFragment() { - PluginPrefs.setHasPlugins(mApplicationContext); - ActivityScenario.launch(SettingsActivity.class); - onView(withId(androidx.preference.R.id.recycler_view)).perform( - actionOnItem(hasDescendant(withText("Developer Options")), click())); - - intended(allOf( - hasComponent(SettingsActivity.class.getName()), - hasExtra(EXTRA_FRAGMENT_ROOT_KEY, DEVELOPER_OPTIONS_KEY))); - } - - @Test - @Ignore // b/199309785 - public void testSettings_aboutScreenIntent() { - Bundle fragmentArgs = new Bundle(); - fragmentArgs.putString(ARG_PREFERENCE_ROOT, "about_screen"); - - Intent intent = new Intent(mApplicationContext, SettingsActivity.class) - .putExtra(EXTRA_FRAGMENT_ARGS, fragmentArgs); - ActivityScenario.launch(intent); - - onView(withText("About")).check(matches(isDisplayed())); - onView(withText("Version")).check(matches(isDisplayed())); - onView(withContentDescription("Navigate up")).check(matches(isDisplayed())); - } - - @Test - @Ignore // b/199309785 - public void testSettings_developerOptionsFragmentIntent() { - Intent intent = new Intent(mApplicationContext, SettingsActivity.class) - .putExtra(EXTRA_FRAGMENT_ROOT_KEY, DEVELOPER_OPTIONS_KEY); - ActivityScenario.launch(intent); - - onView(withText("Developer Options")).check(matches(isDisplayed())); - onView(withId(R.id.filter_box)).check(matches(isDisplayed())); - onView(withContentDescription("Navigate up")).check(matches(isDisplayed())); - } - - @Test - @Ignore // b/199309785 - public void testSettings_backButtonFinishesActivity() { - Bundle fragmentArgs = new Bundle(); - fragmentArgs.putString(ARG_PREFERENCE_ROOT, "about_screen"); - Intent intent = new Intent(mApplicationContext, SettingsActivity.class) - .putExtra(EXTRA_FRAGMENT_ARGS, fragmentArgs); - ActivityScenario<SettingsActivity> scenario = ActivityScenario.launch(intent); - - onView(withContentDescription("Navigate up")).perform(click()); - scenario.onActivity(activity -> assertThat(activity.isFinishing()).isTrue()); - } -} diff --git a/tests/multivalentTests/src/com/android/launcher3/ui/TestViewHelpers.java b/tests/multivalentTests/src/com/android/launcher3/ui/TestViewHelpers.java deleted file mode 100644 index 4b2bade452..0000000000 --- a/tests/multivalentTests/src/com/android/launcher3/ui/TestViewHelpers.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.android.launcher3.ui; - -import static android.os.Process.myUserHandle; - -import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; - -import static com.android.launcher3.util.TestUtil.getOnUiThread; - -import android.app.Instrumentation; -import android.content.ComponentName; -import android.util.Log; -import android.view.View; -import android.view.ViewGroup; - -import com.android.launcher3.testcomponent.AppWidgetNoConfig; -import com.android.launcher3.testcomponent.AppWidgetWithConfig; -import com.android.launcher3.widget.LauncherAppWidgetProviderInfo; -import com.android.launcher3.widget.WidgetManagerHelper; - -import java.util.function.Function; - -public class TestViewHelpers { - private static final String TAG = "TestViewHelpers"; - - /** - * Finds a widget provider which can fit on the home screen. - * - * @param hasConfigureScreen if true, a provider with a config screen is returned. - */ - public static LauncherAppWidgetProviderInfo findWidgetProvider(boolean hasConfigureScreen) { - LauncherAppWidgetProviderInfo info = getOnUiThread(() -> { - Instrumentation i = getInstrumentation(); - ComponentName cn = new ComponentName(i.getContext(), - hasConfigureScreen ? AppWidgetWithConfig.class : AppWidgetNoConfig.class); - Log.d(TAG, "findWidgetProvider componentName=" + cn.flattenToString()); - return new WidgetManagerHelper(i.getTargetContext()).findProvider(cn, myUserHandle()); - }); - if (info == null) { - throw new IllegalArgumentException("No valid widget provider"); - } - return info; - } - - public static View findChildView(ViewGroup parent, Function<View, Boolean> condition) { - for (int i = 0; i < parent.getChildCount(); ++i) { - final View child = parent.getChildAt(i); - if (condition.apply(child)) return child; - } - return null; - } -} diff --git a/tests/multivalentTests/src/com/android/launcher3/util/PackageManagerHelperTest.java b/tests/multivalentTests/src/com/android/launcher3/util/PackageManagerHelperTest.java deleted file mode 100644 index b5e797ed39..0000000000 --- a/tests/multivalentTests/src/com/android/launcher3/util/PackageManagerHelperTest.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.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/shared/com/android/launcher3/testing/OWNERS b/tests/shared/com/android/launcher3/testing/OWNERS deleted file mode 100644 index 02e8ebcaba..0000000000 --- a/tests/shared/com/android/launcher3/testing/OWNERS +++ /dev/null @@ -1,4 +0,0 @@ -vadimt@google.com -sunnygoyal@google.com -winsonc@google.com -hyunyoungs@google.com diff --git a/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java b/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java deleted file mode 100644 index 57117cb1c6..0000000000 --- a/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.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<Intent> 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 deleted file mode 100644 index eac7f63fb9..0000000000 --- a/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewTest.java +++ /dev/null @@ -1,467 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.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<BaseAllAppsAdapter.AdapterItem> adapterItemList) { - BaseAllAppsAdapter.AdapterItem privateSpaceHeader = - new BaseAllAppsAdapter.AdapterItem(VIEW_TYPE_PRIVATE_SPACE_HEADER); - adapterItemList.add(privateSpaceHeader); - return adapterItemList.size(); - } - - private AppInfo[] createAppInfoList() { - List<AppInfo> 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<AppInfo> 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 deleted file mode 100644 index 9537e1c63f..0000000000 --- a/tests/src/com/android/launcher3/allapps/PrivateSpaceSettingsButtonTest.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.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 deleted file mode 100644 index 4e627a9d5b..0000000000 --- a/tests/src/com/android/launcher3/allapps/TaplKeyboardFocusTest.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.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<Launcher> { - - @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/celllayout/TaplReorderWidgetsTest.java b/tests/src/com/android/launcher3/celllayout/TaplReorderWidgetsTest.java deleted file mode 100644 index 28a1325944..0000000000 --- a/tests/src/com/android/launcher3/celllayout/TaplReorderWidgetsTest.java +++ /dev/null @@ -1,312 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -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<Launcher> { - - @Rule - public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind(); - - private static final String TAG = TaplReorderWidgetsTest.class.getSimpleName(); - - private static final List<String> 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<CellLayoutBoard> testBoards) { - ArrayList<CellLayoutBoard> 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<CellLayoutBoard> 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<CellLayoutBoard> 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<CellLayoutBoard> workspaceToBoards() { - return getFromLauncher(CellLayoutTestUtils::workspaceToBoards); - } - - private WidgetRect getWidgetClosestTo(Point point) { - ArrayList<CellLayoutBoard> 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<CellLayoutBoard> 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<Point, ReorderTestCase> 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<Point, ReorderTestCase> 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<CellLayoutTestCaseReader.TestSection> sections, - Map<Point, ReorderTestCase> 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<Point, ReorderTestCase> getTestMap(String testPath) throws IOException { - Map<Point, ReorderTestCase> testCaseMap = new HashMap<>(); - Iterator<CellLayoutTestCaseReader.TestSection> iterableSection = - CellLayoutTestCaseReader.readFromFile(testPath).parse().iterator(); - while (iterableSection.hasNext()) { - addTestCase(iterableSection, testCaseMap); - } - return testCaseMap; - } -} diff --git a/tests/src/com/android/launcher3/celllayout/integrationtest/TestUtils.kt b/tests/src/com/android/launcher3/celllayout/integrationtest/TestUtils.kt deleted file mode 100644 index bcb919162e..0000000000 --- a/tests/src/com/android/launcher3/celllayout/integrationtest/TestUtils.kt +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher3.celllayout.integrationtest - -import android.graphics.Point -import android.graphics.Rect -import android.view.View -import android.view.ViewGroup -import com.android.launcher3.CellLayout -import com.android.launcher3.Utilities -import com.android.launcher3.Workspace -import com.android.launcher3.util.CellAndSpan -import com.android.launcher3.widget.LauncherAppWidgetHostView - -object TestUtils { - fun <T> searchChildren(viewGroup: ViewGroup, type: Class<T>): T? where T : View { - for (i in 0..<viewGroup.childCount) { - val child = viewGroup.getChildAt(i) - if (type.isInstance(child)) { - return type.cast(child) - } - if (child is ViewGroup) { - val result = searchChildren(child, type) - if (result != null) { - return result - } - } - } - return null - } - - fun getWidgetAtCell( - workspace: Workspace<*>, - cellX: Int, - cellY: Int - ): LauncherAppWidgetHostView { - val view = - (workspace.getPageAt(workspace.currentPage) as CellLayout).getChildAt(cellX, cellY) - assert(view != null) { "There is no view at $cellX , $cellY" } - assert(view is LauncherAppWidgetHostView) { "The view at $cellX , $cellY is not a widget" } - return view as LauncherAppWidgetHostView - } - - fun getCellTopLeftRelativeToWorkspace( - workspace: Workspace<*>, - cellAndSpan: CellAndSpan - ): Point { - val target = Rect() - val cellLayout = workspace.getPageAt(workspace.currentPage) as CellLayout - cellLayout.cellToRect( - cellAndSpan.cellX, - cellAndSpan.cellY, - cellAndSpan.spanX, - cellAndSpan.spanY, - target - ) - val point = floatArrayOf(target.left.toFloat(), target.top.toFloat()) - Utilities.getDescendantCoordRelativeToAncestor(cellLayout, workspace, point, false) - return Point(point[0].toInt(), point[1].toInt()) - } -} diff --git a/tests/src/com/android/launcher3/celllayout/integrationtest/events/EventWaiter.kt b/tests/src/com/android/launcher3/celllayout/integrationtest/events/EventWaiter.kt deleted file mode 100644 index 20ad60f11b..0000000000 --- a/tests/src/com/android/launcher3/celllayout/integrationtest/events/EventWaiter.kt +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher3.celllayout.integrationtest.events - -import com.android.launcher3.debug.TestEventEmitter.TestEvent -import java.util.concurrent.TimeUnit -import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.withTimeoutOrNull - -enum class EventStatus() { - SUCCESS, - FAILURE, - TIMEOUT, -} - -class EventWaiter(val eventToWait: TestEvent) { - private val deferrable = CompletableDeferred<EventStatus>() - - companion object { - private const val TAG = "EventWaiter" - private val SIGNAL_TIMEOUT = TimeUnit.SECONDS.toMillis(5) - } - - fun waitForSignal(timeout: Long = SIGNAL_TIMEOUT) = runBlocking { - var status = withTimeoutOrNull(timeout) { deferrable.await() } - if (status == null) { - status = EventStatus.TIMEOUT - } - if (status != EventStatus.SUCCESS) { - throw Exception("Failure waiting for event $eventToWait, failure = $status") - } - } - - fun terminate() { - deferrable.complete(EventStatus.SUCCESS) - } -} diff --git a/tests/src/com/android/launcher3/celllayout/integrationtest/events/EventsRule.kt b/tests/src/com/android/launcher3/celllayout/integrationtest/events/EventsRule.kt deleted file mode 100644 index 45eb5e1889..0000000000 --- a/tests/src/com/android/launcher3/celllayout/integrationtest/events/EventsRule.kt +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher3.celllayout.integrationtest.events - -import android.content.Context -import android.util.Log -import com.android.dx.mockito.inline.extended.ExtendedMockito.* -import com.android.dx.mockito.inline.extended.StaticMockitoSession -import com.android.launcher3.debug.TestEventEmitter -import com.android.launcher3.debug.TestEventEmitter.TestEvent -import org.junit.rules.TestRule -import org.junit.runner.Description -import org.junit.runners.model.Statement -import org.mockito.quality.Strictness - -/** - * Rule to create EventWaiters to wait for events that happens on the Launcher. For reference look - * at [TestEvent] for existing events. - * - * Waiting for event should be used to prevent race conditions, it provides a more precise way of - * waiting for events compared to [AbstractLauncherUiTest#waitForLauncherCondition]. - * - * This class mocks the static method [TestEventEmitter.sendEvent] - */ -class EventsRule(val context: Context) : TestRule { - - private val expectedEvents: ArrayDeque<EventWaiter> = ArrayDeque() - - private lateinit var mockitoSession: StaticMockitoSession - - override fun apply(base: Statement, description: Description?): Statement { - return object : Statement() { - override fun evaluate() { - try { - beforeTest() - base.evaluate() - } finally { - afterTest() - } - } - } - } - - fun createEventWaiter(expectedEvent: TestEvent): EventWaiter { - val eventWaiter = EventWaiter(expectedEvent) - expectedEvents.add(eventWaiter) - return eventWaiter - } - - private fun beforeTest() { - mockitoSession = - mockitoSession() - .strictness(Strictness.LENIENT) - .spyStatic(TestEventEmitter::class.java) - .startMocking() - - doAnswer { invocation -> - val event = (invocation.arguments[0] as TestEvent) - Log.d(TAG, "Signal received $event") - Log.d(TAG, "Total expected events ${expectedEvents.size}") - if (!expectedEvents.isEmpty()) { - val eventWaiter = expectedEvents.last() - if (eventWaiter.eventToWait == event) { - Log.d(TAG, "Removing $event") - expectedEvents.removeLast() - eventWaiter.terminate() - } else { - Log.d(TAG, "Not matching $event") - } - } - null - } - .`when` { TestEventEmitter.sendEvent(any()) } - } - - private fun afterTest() { - mockitoSession.finishMocking() - expectedEvents.clear() - } - - companion object { - private const val TAG = "TestEvents" - } -} diff --git a/tests/src/com/android/launcher3/celllayout/testgenerator/ValidGridMigrationTestCaseGenerator.kt b/tests/src/com/android/launcher3/celllayout/testgenerator/ValidGridMigrationTestCaseGenerator.kt deleted file mode 100644 index a006fd7463..0000000000 --- a/tests/src/com/android/launcher3/celllayout/testgenerator/ValidGridMigrationTestCaseGenerator.kt +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.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<CellLayoutBoard>, - repeatAfterRange: Point -): List<WorkspaceItem> { - 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<CellLayoutBoard>, - val destBoards: List<CellLayoutBoard>, - 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<CellLayoutBoard> { - val boards = mutableListOf<CellLayoutBoard>() - 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 deleted file mode 100644 index 1500538633..0000000000 --- a/tests/src/com/android/launcher3/compat/TaplPromiseIconUiTest.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.android.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<Launcher> { - - @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/folder/FolderNameProviderTest.java b/tests/src/com/android/launcher3/folder/FolderNameProviderTest.java deleted file mode 100644 index 9c15309f82..0000000000 --- a/tests/src/com/android/launcher3/folder/FolderNameProviderTest.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.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<WorkspaceItemInfo> 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 deleted file mode 100644 index 7242e9c77a..0000000000 --- a/tests/src/com/android/launcher3/folder/PreviewBackgroundTest.java +++ /dev/null @@ -1,325 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.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/model/AddWorkspaceItemsTaskTest.kt b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt deleted file mode 100644 index 370af0cea5..0000000000 --- a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt +++ /dev/null @@ -1,225 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -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<Int>, 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<Int>, - vararg itemsToAdd: WorkspaceItemInfo - ): List<AddedItem> { - setupWorkspaces(nonEmptyScreenIds) - val task = newTask(*itemsToAdd) - - val addedItems = mutableListOf<AddedItem>() - - 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<AddedItem>() - - override fun bindAppsAdded( - newScreens: IntArray?, - addNotAnimated: ArrayList<ItemInfo>, - addAnimated: ArrayList<ItemInfo> - ) { - 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 deleted file mode 100644 index af367a814a..0000000000 --- a/tests/src/com/android/launcher3/model/AsyncBindingTest.kt +++ /dev/null @@ -1,212 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.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<Looper>() - - 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<ItemInfo>, forceAnimateIcons: Boolean) {} - - override fun bindInflatedItems(items: MutableList<Pair<ItemInfo, View>>) {} - - 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 deleted file mode 100644 index 328558d1a7..0000000000 --- a/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java +++ /dev/null @@ -1,161 +0,0 @@ -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<WorkspaceItemInfo> 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 deleted file mode 100644 index cea95e5a50..0000000000 --- a/tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java +++ /dev/null @@ -1,234 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.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 deleted file mode 100644 index 10785f7406..0000000000 --- a/tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.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 deleted file mode 100644 index aadf72e801..0000000000 --- a/tests/src/com/android/launcher3/model/FirstScreenBroadcastHelperTest.kt +++ /dev/null @@ -1,386 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.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<PackageManagerHelper>() - 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<PackageUserKey, SessionInfo> = - 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<PackageUserKey, SessionInfo> = - 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<String>().apply { repeat(20) { add(it.toString()) } }, - pendingWorkspaceItems = - mutableSetOf<String>().apply { repeat(20) { add(it.toString()) } }, - pendingHotseatItems = - mutableSetOf<String>().apply { repeat(20) { add(it.toString()) } }, - pendingWidgetItems = - mutableSetOf<String>().apply { repeat(20) { add(it.toString()) } }, - installedWorkspaceItems = - mutableSetOf<String>().apply { repeat(20) { add(it.toString()) } }, - installedHotseatItems = - mutableSetOf<String>().apply { repeat(20) { add(it.toString()) } }, - firstScreenInstalledWidgets = - mutableSetOf<String>().apply { repeat(20) { add(it.toString()) } }, - secondaryScreenInstalledWidgets = - mutableSetOf<String>().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<String>().apply { repeat(50) { add(it.toString()) } }, - firstScreenInstalledWidgets = - mutableSetOf<String>().apply { repeat(10) { add(it.toString()) } }, - secondaryScreenInstalledWidgets = - mutableSetOf<String>().apply { repeat(10) { add((it + 10).toString()) } }, - installedHotseatItems = - mutableSetOf<String>().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<String>().apply { repeat(70) { add(it.toString()) } }, - firstScreenInstalledWidgets = - mutableSetOf<String>().apply { repeat(20) { add(it.toString()) } }, - secondaryScreenInstalledWidgets = - mutableSetOf<String>().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<FirstScreenBroadcastModel> = - 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 deleted file mode 100644 index c4a4c9b7b6..0000000000 --- a/tests/src/com/android/launcher3/model/FolderIconLoadTest.kt +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.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<WorkspaceItemInfo> { - 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<WorkspaceItemInfo>, vararg indices: Int) { - for (index in indices) { - assertWithMessage("Index $index was not highRes") - .that(items[index].bitmap.isNullOrLowRes) - .isFalse() - } - } - - private fun verifyLowRes(items: ArrayList<WorkspaceItemInfo>, 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/LoaderCursorTest.java b/tests/src/com/android/launcher3/model/LoaderCursorTest.java deleted file mode 100644 index b4945d7129..0000000000 --- a/tests/src/com/android/launcher3/model/LoaderCursorTest.java +++ /dev/null @@ -1,236 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.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/PackageInstallStateChangedTaskTest.java b/tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java deleted file mode 100644 index 4ba61ac315..0000000000 --- a/tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java +++ /dev/null @@ -1,122 +0,0 @@ -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 deleted file mode 100644 index 6cf3b198bf..0000000000 --- a/tests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt +++ /dev/null @@ -1,847 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.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<WorkspaceItemInfo> - @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<IconRequestInfo<WorkspaceItemInfo>> = mutableListOf() - private var mComponentName: ComponentName = ComponentName("package", "class") - private var mUnlockedUsersArray: LongSparseArray<Boolean> = LongSparseArray() - private var mKeyToPinnedShortcutsMap: MutableMap<ShortcutKey, ShortcutInfo> = mutableMapOf() - private var mInstallingPkgs: HashMap<PackageUserKey, PackageInstaller.SessionInfo> = hashMapOf() - private var mAllDeepShortcuts: MutableList<ShortcutInfo> = mutableListOf() - private var mWidgetProvidersMap: MutableMap<ComponentKey, AppWidgetProviderInfo?> = - mutableMapOf() - private var mPendingPackages: MutableSet<PackageUserKey> = mutableSetOf() - - private lateinit var itemProcessorUnderTest: WorkspaceItemProcessor - - @Before - fun setup() { - mUserHandle = UserHandle(0) - mockIconRequestInfo = mock<IconRequestInfo<WorkspaceItemInfo>>() - mockWorkspaceInfo = mock<WorkspaceItemInfo>() - mockBgDataModel = mock<BgDataModel>() - mComponentName = ComponentName("package", "class") - mUnlockedUsersArray = LongSparseArray<Boolean>(1).apply { put(101, true) } - intent = - Intent().apply { - component = mComponentName - `package` = "pkg" - putExtra(ShortcutKey.EXTRA_SHORTCUT_ID, "") - } - mockContext = - mock<Context>().apply { - whenever(packageManager).thenReturn(mock()) - whenever(packageManager.getUserBadgedLabel(any(), any())).thenReturn("") - } - mockAppState = - mock<LauncherAppState>().apply { - whenever(context).thenReturn(mockContext) - whenever(iconCache).thenReturn(mock()) - whenever(iconCache.getShortcutIcon(any(), any(), any())).then {} - } - mockPmHelper = - mock<PackageManagerHelper>().apply { - whenever(getAppLaunchIntent(mComponentName.packageName, mUserHandle)) - .thenReturn(intent) - } - mockLauncherApps = - mock<LauncherApps>().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<UserCache>().apply { - val userIconInfo = - mock<UserIconInfo>().apply { whenever(isPrivate).thenReturn(false) } - whenever(getUserInfo(any())).thenReturn(userIconInfo) - } - - mockUserManagerState = mock<UserManagerState>() - mockWidgetInflater = mock<WidgetInflater>() - 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<ShortcutKey, ShortcutInfo> = mKeyToPinnedShortcutsMap, - app: LauncherAppState = mockAppState, - bgDataModel: BgDataModel = mockBgDataModel, - widgetProvidersMap: MutableMap<ComponentKey, AppWidgetProviderInfo?> = mWidgetProvidersMap, - widgetInflater: WidgetInflater = mockWidgetInflater, - pmHelper: PackageManagerHelper = mockPmHelper, - iconRequestInfos: MutableList<IconRequestInfo<WorkspaceItemInfo>> = mIconRequestInfos, - isSdCardReady: Boolean = false, - pendingPackages: MutableSet<PackageUserKey> = mPendingPackages, - unlockedUsers: LongSparseArray<Boolean> = mUnlockedUsersArray, - installingPkgs: HashMap<PackageUserKey, PackageInstaller.SessionInfo> = mInstallingPkgs, - allDeepShortcuts: MutableList<ShortcutInfo> = 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<LoaderCursor>().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<LauncherApps>().apply { - whenever(isPackageEnabled("package", mUserHandle)).thenReturn(true) - whenever(isActivityEnabled(mComponentName, mUserHandle)).thenReturn(false) - } - mockPmHelper = - mock<PackageManagerHelper>().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<LauncherApps>().apply { - whenever(isPackageEnabled("package", mUserHandle)).thenReturn(true) - whenever(isActivityEnabled(mComponentName, mUserHandle)).thenReturn(false) - } - mockPmHelper = - mock<PackageManagerHelper>().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<ShortcutInfo>().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<ShortcutInfo>().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<BgDataModel>().apply { whenever(findOrMakeFolder(1)).thenReturn(actualFolderInfo) } - mockCursor = - mock<LoaderCursor>().apply { - user = UserHandle(0) - itemType = ITEM_TYPE_FOLDER - id = 1 - container = 100 - restoreFlag = 1 - serialNumber = 101 - whenever(applyCommonProperties(any<ItemInfo>())).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<LauncherAppWidgetProviderInfo>().apply { - provider = ComponentName.unflattenFromString(expectedProvider) - whenever(user).thenReturn(mUserHandle) - } - val inflationResult = - WidgetInflater.InflationResult( - type = WidgetInflater.TYPE_REAL, - widgetInfo = expectedWidgetProviderInfo - ) - mockWidgetInflater = - mock<WidgetInflater>().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<LoaderCursor>().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<LauncherAppWidgetProviderInfo>().apply { - provider = mock() - whenever(user).thenReturn(UserHandle(1)) - } - val inflationResult = - WidgetInflater.InflationResult( - type = WidgetInflater.TYPE_PENDING, - widgetInfo = mockProviderInfo - ) - mockWidgetInflater = - mock<WidgetInflater>().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<LoaderCursor>().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<WidgetInflater>().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<WidgetInflater>().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<PackageManagerHelper>().apply { - whenever(isAppArchived(expectedPackage)).thenReturn(true) - } - mockCursor = - mock<LoaderCursor>().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<WidgetInflater>().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<PackageManagerHelper>().apply { - whenever(isAppArchived(expectedPackage)).thenReturn(true) - } - mockCursor = - mock<LoaderCursor>().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<WidgetInflater>().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 deleted file mode 100644 index b3d02be460..0000000000 --- a/tests/src/com/android/launcher3/model/WorkspaceItemSpaceFinderTest.kt +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -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 deleted file mode 100644 index cc8e61d726..0000000000 --- a/tests/src/com/android/launcher3/model/gridmigration/GridMigrationUtils.kt +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.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<String> { - override fun contains(element: String): Boolean = true - override fun containsAll(elements: Collection<String>): Boolean = true - override fun isEmpty(): Boolean = false - override fun iterator(): Iterator<String> = listOf<String>().iterator() -} - -fun itemListToBoard(itemsArg: List<WorkspaceItem>, boardSize: Point): List<CellLayoutBoard> { - 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<WorkspaceItem> { - val result = mutableListOf<WorkspaceItem>() - 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 deleted file mode 100644 index 58b915f0ca..0000000000 --- a/tests/src/com/android/launcher3/model/gridmigration/ValidGridMigrationUnitTest.kt +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.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<WorkspaceItem>) { - 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<WorkspaceItem>) { - // This returns a map with the number of repeated elements - // ex { calculatorIcon : 6, weatherWidget : 2 } - val itemsToMap = { it: List<WorkspaceItem> -> - 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<WorkspaceItem> { - 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/provider/RestoreDbTaskTest.java b/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java deleted file mode 100644 index b3675a6e0c..0000000000 --- a/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java +++ /dev/null @@ -1,417 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.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<UserHandle> 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 deleted file mode 100644 index 80d6341e70..0000000000 --- a/tests/src/com/android/launcher3/testcomponent/TouchEventGenerator.java +++ /dev/null @@ -1,275 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.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<Integer, Point> 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<Integer, Point>(); - } - - /** - * 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<PointerProperties[], PointerCoords[]> 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<PointerProperties[], PointerCoords[]> getFingerState() { - int nFingers = mFingers.size(); - PointerProperties[] properties = new PointerProperties[nFingers]; - PointerCoords[] coordinates = new PointerCoords[nFingers]; - - int index = 0; - for (Map.Entry<Integer, Point> 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<MotionEvent.PointerProperties[], MotionEvent.PointerCoords[]>( - 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 deleted file mode 100644 index 260f5568e0..0000000000 --- a/tests/src/com/android/launcher3/touch/SingleAxisSwipeDetectorTest.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.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 deleted file mode 100644 index a6f4441e2a..0000000000 --- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java +++ /dev/null @@ -1,709 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.android.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; -import java.util.function.Function; -import java.util.function.Supplier; - -/** - * Base class for all instrumentation tests providing various utility methods. - */ -public abstract class AbstractLauncherUiTest<LAUNCHER_TYPE extends Launcher> { - - 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); - } - - @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.reinitializeLauncherData(); - test.mDevice.pressHome(); - test.waitForLauncherCondition("Launcher didn't start", Objects::nonNull); - test.waitForState("Launcher internal state didn't switch to Home", - () -> LauncherState.NORMAL); - 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); - } - - protected TestRule getRulesInsideActivityMonitor() { - final ViewCaptureRule viewCaptureRule = new ViewCaptureRule( - Launcher.ACTIVITY_TRACKER::getCreatedActivity); - final RuleChain inner = RuleChain - .outerRule(new PortraitLandscapeRunner<LAUNCHER_TYPE>(this)) - .around(new FailureWatcher(mLauncher, viewCaptureRule::getViewCaptureData)) - // .around(viewCaptureRule) // b/315482167 - .around(new TestIsolationRule(mLauncher, true)); - - return TestHelpers.isInLauncherProcess() - ? RuleChain.outerRule(ShellCommandRule.setDefaultLauncher()).around(inner) - : 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> T getOnUiThread(final Callable<T> callback) { - try { - return mMainThreadExecutor.submit(callback).get(DEFAULT_UI_TIMEOUT, - TimeUnit.MILLISECONDS); - } catch (TimeoutException e) { - Log.e(TAG, "Timeout in getOnUiThread, sending SIGABRT", e); - Process.sendSignal(Process.myPid(), OsConstants.SIGABRT); - throw new RuntimeException(e); - } catch (Throwable e) { - throw new RuntimeException(e); - } - } - - protected <T> T getFromLauncher(Function<LAUNCHER_TYPE, T> f) { - if (!TestHelpers.isInLauncherProcess()) return null; - return getOnUiThread(() -> f.apply(Launcher.ACTIVITY_TRACKER.getCreatedActivity())); - } - - protected void executeOnLauncher(Consumer<LAUNCHER_TYPE> f) { - getFromLauncher(launcher -> { - f.accept(launcher); - return null; - }); - } - - // Execute an action on Launcher, but forgive it when launcher is null. - // Launcher can be null if teardown is happening after a failed setup step where launcher - // activity failed to be created. - protected void executeOnLauncherInTearDown(Consumer<LAUNCHER_TYPE> f) { - executeOnLauncher(launcher -> { - if (launcher != null) f.accept(launcher); - }); - } - - // Cannot be used in TaplTests between a Tapl call injecting a gesture and a tapl call - // expecting the results of that gesture because the wait can hide flakeness. - protected void waitForState(String message, Supplier<LauncherState> state) { - waitForLauncherCondition(message, - launcher -> launcher.getStateManager().getCurrentStableState() == state.get()); - } - - // Cannot be used in TaplTests between a Tapl call injecting a gesture and a tapl call - // expecting the results of that gesture because the wait can hide flakeness. - protected void waitForStateTransitionToEnd(String message, Supplier<LauncherState> state) { - waitForLauncherCondition(message, - launcher -> launcher.getStateManager().isInStableState(state.get()) - && !launcher.getStateManager().isInTransition()); - } - - protected void waitForResumed(String message) { - waitForLauncherCondition(message, launcher -> launcher.hasBeenResumed()); - } - - // Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide - // flakiness. - protected void waitForLauncherCondition(String - message, Function<LAUNCHER_TYPE, Boolean> condition) { - waitForLauncherCondition(message, condition, DEFAULT_ACTIVITY_TIMEOUT); - } - - // Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide - // flakiness. - protected <O> O getOnceNotNull(String message, Function<LAUNCHER_TYPE, O> f) { - return getOnceNotNull(message, f, DEFAULT_ACTIVITY_TIMEOUT); - } - - // Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide - // flakiness. - protected void waitForLauncherCondition( - String message, Function<LAUNCHER_TYPE, Boolean> condition, long timeout) { - verifyKeyguardInvisible(); - if (!TestHelpers.isInLauncherProcess()) return; - 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> T getOnceNotNull(String message, Function<LAUNCHER_TYPE, T> f, long timeout) { - if (!TestHelpers.isInLauncherProcess()) return null; - - final Object[] output = new Object[1]; - Wait.atMost(message, () -> { - final Object fromLauncher = getFromLauncher(f); - output[0] = fromLauncher; - return fromLauncher != null; - }, timeout, mLauncher); - return (T) output[0]; - } - - // Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide - // flakiness. - protected void waitForLauncherCondition( - String message, - Runnable testThreadAction, Function<LAUNCHER_TYPE, Boolean> condition, - long timeout) { - if (!TestHelpers.isInLauncherProcess()) return; - Wait.atMost(message, () -> { - testThreadAction.run(); - return getFromLauncher(condition); - }, 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) { - startIntent( - getInstrumentation().getContext().getPackageManager().getLaunchIntentForPackage( - packageName), - By.pkg(packageName).depth(0), - true /* newTask */); - } - - public static void startTestActivity(String activityName, String activityLabel) { - final String packageName = getAppPackageName(); - final Intent intent = getInstrumentation().getContext().getPackageManager(). - getLaunchIntentForPackage(packageName); - intent.setComponent(new ComponentName(packageName, - "com.android.launcher3.tests." + activityName)); - startIntent(intent, By.pkg(packageName).text(activityLabel), - false /* newTask */); - } - - public static void startTestActivity(int activityNumber) { - startTestActivity("Activity" + activityNumber, "TestActivity" + activityNumber); - } - - public static void startImeTestActivity() { - final String packageName = getAppPackageName(); - final Intent intent = getInstrumentation().getContext().getPackageManager(). - getLaunchIntentForPackage(packageName); - intent.setComponent(new ComponentName(packageName, - "com.android.launcher3.testcomponent.ImeTestActivity")); - startIntent(intent, By.pkg(packageName).text("ImeTestActivity"), - false /* newTask */); - } - - /** Starts ExcludeFromRecentsTestActivity, which has excludeFromRecents="true". */ - public static void startExcludeFromRecentsTestActivity() { - final String packageName = getAppPackageName(); - final Intent intent = getInstrumentation().getContext().getPackageManager() - .getLaunchIntentForPackage(packageName); - intent.setComponent(new ComponentName(packageName, - "com.android.launcher3.testcomponent.ExcludeFromRecentsTestActivity")); - startIntent(intent, By.pkg(packageName).text("ExcludeFromRecentsTestActivity"), - false /* newTask */); - } - - private static void startIntent(Intent intent, BySelector selector, boolean newTask) { - intent.addCategory(Intent.CATEGORY_LAUNCHER); - if (newTask) { - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - } else { - intent.addFlags( - Intent.FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT); - } - getInstrumentation().getTargetContext().startActivity(intent); - assertTrue("App didn't start: " + selector, - 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(), - 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; - } - - - public static String resolveSystemApp(String category) { - return resolveSystemAppInfo(category).packageName; - } - - protected void closeLauncherActivity() { - // Destroy Launcher activity. - executeOnLauncher(launcher -> { - if (launcher != null) { - onLauncherActivityClose(launcher); - launcher.finish(); - } - }); - waitForLauncherCondition( - "Launcher still active", launcher -> launcher == null, DEFAULT_UI_TIMEOUT); - } - - protected boolean isInLaunchedApp(LAUNCHER_TYPE launcher) { - return launcher == null || !launcher.hasBeenResumed(); - } - - protected boolean isInState(Supplier<LauncherState> state) { - if (!TestHelpers.isInLauncherProcess()) return true; - return getFromLauncher( - launcher -> launcher.getStateManager().getState() == state.get()); - } - - protected int getAllAppsScroll(LAUNCHER_TYPE launcher) { - return launcher.getAppsView().getActiveRecyclerView().computeVerticalScrollOffset(); - } - - 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/BaseLauncherTaplTest.java b/tests/src/com/android/launcher3/ui/BaseLauncherTaplTest.java deleted file mode 100644 index 9fa1500c68..0000000000 --- a/tests/src/com/android/launcher3/ui/BaseLauncherTaplTest.java +++ /dev/null @@ -1,531 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher3.ui; - -import static android.os.Process.myUserHandle; -import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT; - -import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; - -import static com.android.launcher3.testing.shared.TestProtocol.ICON_MISSING; -import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -import android.app.ActivityManager; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.pm.ActivityInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.graphics.Point; -import android.os.Debug; -import android.os.RemoteException; -import android.os.UserHandle; -import android.os.UserManager; -import android.platform.test.flag.junit.SetFlagsRule; -import android.platform.test.rule.LimitDevicesRule; -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.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.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 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.concurrent.TimeUnit; - -/** - * Base class for all TAPL tests in Launcher providing various utility methods. - */ -public abstract class BaseLauncherTaplTest { - - 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 = "BaseLauncherTaplTest"; - - private static final long BYTES_PER_MEGABYTE = 1 << 20; - - 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 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; - - private final ActivityManager.MemoryInfo mMemoryInfo = new ActivityManager.MemoryInfo(); - private final ActivityManager mActivityManager; - private long mMemoryBefore; - - /** 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(); - }, launcher, DEFAULT_UI_TIMEOUT); - } - - 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 BaseLauncherTaplTest() { - mActivityManager = InstrumentationRegistry.getContext() - .getSystemService(ActivityManager.class); - mLauncher.enableCheckEventsForSuccessfulGestures(); - mLauncher.setAnomalyChecker(BaseLauncherTaplTest::verifyKeyguardInvisible); - try { - mDevice.setOrientationNatural(); - } catch (RemoteException e) { - throw new RuntimeException(e); - } - mLauncher.enableDebugTracing(); - // Avoid double-reporting of Launcher crashes. - mLauncher.setOnLauncherCrashed(() -> mLauncherPid = 0); - } - - @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(); - - @Rule - public LimitDevicesRule mlimitDevicesRule = new LimitDevicesRule(); - - protected void performInitialization() { - reinitializeLauncherData(); - mDevice.pressHome(); - // Check that we switched to home. - mLauncher.getWorkspace(); - checkDetectedLeaks(mLauncher, true); - } - - protected void clearPackageData(String pkg) throws IOException, InterruptedException { - assertTrue("pm clear command failed", - mDevice.executeShellCommand( - String.format("pm clear --user %d %s", myUserHandle().getIdentifier(), pkg)) - .contains("Success")); - assertTrue("pm wait-for-handler command failed", - mDevice.executeShellCommand("pm wait-for-handler") - .contains("Success")); - } - - protected TestRule getRulesInsideActivityMonitor() { - final RuleChain inner = RuleChain - .outerRule(new FailureWatcher(mLauncher, null)) - .around(new TestIsolationRule(mLauncher, true)); - return TestHelpers.isInLauncherProcess() - ? RuleChain.outerRule(ShellCommandRule.setDefaultLauncher()).around(inner) - : 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(); - performInitialization(); - } - - private long getAvailableMemory() { - mActivityManager.getMemoryInfo(mMemoryInfo); - - return Math.divideExact(mMemoryInfo.availMem, BYTES_PER_MEGABYTE); - } - - @Before - public void saveMemoryBefore() { - mMemoryBefore = getAvailableMemory(); - } - - @After - public void logMemoryAfter() { - long memoryAfter = getAvailableMemory(); - - Log.d(TAG, "Available memory: before=" + mMemoryBefore - + "MB, after=" + memoryAfter - + "MB, delta=" + (memoryAfter - mMemoryBefore) + "MB"); - } - - /** 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 (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 resetFreezeRecentTaskList() { - try { - mDevice.executeShellCommand("wm reset-freeze-recent-tasks"); - } catch (IOException e) { - Log.e(TAG, "Failed to reset fozen recent tasks list", e); - } - } - - @After - public void verifyLauncherState() { - try { - // Limits UI tests affecting tests running after them. - mDevice.pressHome(); - 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(); - } - - public static void startAppFast(String packageName) { - startIntent( - getInstrumentation().getContext().getPackageManager().getLaunchIntentForPackage( - packageName), - By.pkg(packageName).depth(0), - true /* newTask */); - } - - public static void startTestActivity(String activityName, String activityLabel) { - final String packageName = getAppPackageName(); - final Intent intent = getInstrumentation().getContext().getPackageManager() - .getLaunchIntentForPackage(packageName); - intent.setComponent(new ComponentName(packageName, - "com.android.launcher3.tests." + activityName)); - startIntent(intent, By.pkg(packageName).text(activityLabel), - false /* newTask */); - } - - public static void startTestActivity(int activityNumber) { - startTestActivity("Activity" + activityNumber, "TestActivity" + activityNumber); - } - - public static void startImeTestActivity() { - final String packageName = getAppPackageName(); - final Intent intent = getInstrumentation().getContext().getPackageManager() - .getLaunchIntentForPackage(packageName); - intent.setComponent(new ComponentName(packageName, - "com.android.launcher3.testcomponent.ImeTestActivity")); - startIntent(intent, By.pkg(packageName).text("ImeTestActivity"), - false /* newTask */); - } - - /** Starts ExcludeFromRecentsTestActivity, which has excludeFromRecents="true". */ - public static void startExcludeFromRecentsTestActivity() { - final String packageName = getAppPackageName(); - final Intent intent = getInstrumentation().getContext().getPackageManager() - .getLaunchIntentForPackage(packageName); - intent.setComponent(new ComponentName(packageName, - "com.android.launcher3.testcomponent.ExcludeFromRecentsTestActivity")); - startIntent(intent, By.pkg(packageName).text("ExcludeFromRecentsTestActivity"), - false /* newTask */); - } - - private static void startIntent(Intent intent, BySelector selector, boolean newTask) { - intent.addCategory(Intent.CATEGORY_LAUNCHER); - if (newTask) { - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - } else { - intent.addFlags( - Intent.FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT); - } - getInstrumentation().getTargetContext().startActivity(intent); - assertTrue("App didn't start: " + selector, - 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); - } - - public static ActivityInfo resolveSystemAppInfo(String category) { - return getInstrumentation().getContext().getPackageManager().resolveActivity( - new Intent(Intent.ACTION_MAIN).addCategory(category), - PackageManager.MATCH_SYSTEM_ONLY) - .activityInfo; - } - - - public static String resolveSystemApp(String category) { - return resolveSystemAppInfo(category).packageName; - } - - 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/DoubleShadowIconDrawableTest.kt b/tests/src/com/android/launcher3/ui/DoubleShadowIconDrawableTest.kt deleted file mode 100644 index 1cee71c219..0000000000 --- a/tests/src/com/android/launcher3/ui/DoubleShadowIconDrawableTest.kt +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher3.ui - -import android.graphics.Color -import android.graphics.drawable.Drawable -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.SmallTest -import com.android.launcher3.views.DoubleShadowIconDrawable -import com.android.launcher3.views.ShadowInfo -import com.google.common.truth.Truth.assertThat -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.kotlin.mock -import org.mockito.kotlin.spy -import org.mockito.kotlin.verify - -@SmallTest -@RunWith(AndroidJUnit4::class) -class DoubleShadowIconDrawableTest { - - @Test - fun `DoubleShadowIconDrawable is setup correctly from given ShadowInfo`() { - // Given - val shadowInfo: ShadowInfo = mock() - val originalDrawable: Drawable = mock() - val iconSize = 2 - val iconInsetSize = 1 - // When - val drawableUnderTest = - DoubleShadowIconDrawable(shadowInfo, originalDrawable, iconSize, iconInsetSize) - // Then - assertThat(drawableUnderTest.intrinsicHeight).isEqualTo(iconSize) - assertThat(drawableUnderTest.intrinsicWidth).isEqualTo(iconSize) - } - - @Test - fun `createShadowRenderNode creates RenderNode for shadow effects`() { - // Given - val shadowInfo = - ShadowInfo( - ambientShadowBlur = 1f, - ambientShadowColor = 2, - keyShadowBlur = 3f, - keyShadowOffsetX = 4f, - keyShadowOffsetY = 5f, - keyShadowColor = 6 - ) - val originalDrawable: Drawable = mock() - val iconSize = 2 - val iconInsetSize = 1 - // When - val shadowDrawableUnderTest = - spy(DoubleShadowIconDrawable(shadowInfo, originalDrawable, iconSize, iconInsetSize)) - shadowDrawableUnderTest.createShadowRenderNode() - // Then - verify(shadowDrawableUnderTest) - .createShadowRenderEffect( - shadowInfo.ambientShadowBlur, - 0f, - 0f, - Color.alpha(shadowInfo.ambientShadowColor).toFloat() - ) - verify(shadowDrawableUnderTest) - .createShadowRenderEffect( - shadowInfo.keyShadowBlur, - shadowInfo.keyShadowOffsetX, - shadowInfo.keyShadowOffsetY, - Color.alpha(shadowInfo.keyShadowColor).toFloat() - ) - } -} diff --git a/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java b/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java deleted file mode 100644 index e5c5c196bf..0000000000 --- a/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java +++ /dev/null @@ -1,101 +0,0 @@ -package com.android.launcher3.ui; - -import android.util.Log; -import android.view.Surface; - -import com.android.launcher3.Launcher; -import com.android.launcher3.tapl.TestHelpers; -import com.android.launcher3.util.rule.FailureWatcher; - -import org.junit.rules.TestRule; -import org.junit.runner.Description; -import org.junit.runners.model.Statement; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.util.Objects; -import java.util.concurrent.TimeUnit; - -public class PortraitLandscapeRunner<LAUNCHER_TYPE extends Launcher> implements TestRule { - private static final String TAG = "PortraitLandscapeRunner"; - private AbstractLauncherUiTest<LAUNCHER_TYPE> mTest; - - // Annotation for tests that need to be run in portrait and landscape modes. - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.METHOD) - public @interface PortraitLandscape { - } - - public PortraitLandscapeRunner(AbstractLauncherUiTest<LAUNCHER_TYPE> test) { - mTest = test; - } - - @Override - public Statement apply(Statement base, Description description) { - if (!TestHelpers.isInLauncherProcess() - || description.getAnnotation(PortraitLandscape.class) == null) { - return base; - } - - return new Statement() { - @Override - public void evaluate() throws Throwable { - try { - try { - // we expect to begin unlocked... - AbstractLauncherUiTest.verifyKeyguardInvisible(); - - mTest.mDevice.pressHome(); - mTest.waitForLauncherCondition("Launcher activity wasn't created", - Objects::nonNull, - TimeUnit.SECONDS.toMillis(20)); - - mTest.executeOnLauncher(launcher -> - launcher.getRotationHelper().forceAllowRotationForTesting( - true)); - - } catch (Throwable e) { - FailureWatcher.onError(mTest.mLauncher, description); - throw e; - } - - evaluateInPortrait(); - evaluateInLandscape(); - } catch (Throwable e) { - Log.e(TAG, "Error", e); - throw e; - } finally { - mTest.mDevice.setOrientationNatural(); - mTest.executeOnLauncher(launcher -> - { - if (launcher != null) { - launcher.getRotationHelper().forceAllowRotationForTesting(false); - } - }); - mTest.mLauncher.setExpectedRotation(Surface.ROTATION_0); - - // and end unlocked... - AbstractLauncherUiTest.verifyKeyguardInvisible(); - } - } - - private void evaluateInPortrait() throws Throwable { - mTest.mDevice.setOrientationNatural(); - mTest.mLauncher.setExpectedRotation(Surface.ROTATION_0); - AbstractLauncherUiTest.checkDetectedLeaks(mTest.mLauncher, true); - base.evaluate(); - mTest.getDevice().pressHome(); - } - - private void evaluateInLandscape() throws Throwable { - mTest.mDevice.setOrientationLeft(); - mTest.mLauncher.setExpectedRotation(Surface.ROTATION_90); - AbstractLauncherUiTest.checkDetectedLeaks(mTest.mLauncher, true); - base.evaluate(); - mTest.getDevice().pressHome(); - } - }; - } -} diff --git a/tests/src/com/android/launcher3/ui/ShadowInfoTest.kt b/tests/src/com/android/launcher3/ui/ShadowInfoTest.kt deleted file mode 100644 index ef4dc1a8e7..0000000000 --- a/tests/src/com/android/launcher3/ui/ShadowInfoTest.kt +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher3.ui - -import android.content.Context -import android.content.res.TypedArray -import android.util.AttributeSet -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.SmallTest -import com.android.launcher3.R -import com.android.launcher3.views.ShadowInfo -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.whenever - -@SmallTest -@RunWith(AndroidJUnit4::class) -class ShadowInfoTest { - - @Test - fun `ShadowInfo is created correctly from context`() { - // Given - val mockContext: Context = mock() - val mockAttrs: AttributeSet = mock() - val styledAttrs: TypedArray = mock() - val expectedShadowInfo = - ShadowInfo( - ambientShadowBlur = 1f, - ambientShadowColor = 2, - keyShadowBlur = 3f, - keyShadowOffsetX = 4f, - keyShadowOffsetY = 5f, - keyShadowColor = 6 - ) - doReturn(styledAttrs) - .whenever(mockContext) - .obtainStyledAttributes(mockAttrs, R.styleable.ShadowInfo, 0, 0) - doReturn(1) - .whenever(styledAttrs) - .getDimensionPixelSize(R.styleable.ShadowInfo_ambientShadowBlur, 0) - doReturn(2).whenever(styledAttrs).getColor(R.styleable.ShadowInfo_ambientShadowColor, 0) - doReturn(3) - .whenever(styledAttrs) - .getDimensionPixelSize(R.styleable.ShadowInfo_keyShadowBlur, 0) - doReturn(4) - .whenever(styledAttrs) - .getDimensionPixelSize(R.styleable.ShadowInfo_keyShadowOffsetX, 0) - doReturn(5) - .whenever(styledAttrs) - .getDimensionPixelSize(R.styleable.ShadowInfo_keyShadowOffsetY, 0) - doReturn(6).whenever(styledAttrs).getColor(R.styleable.ShadowInfo_keyShadowColor, 0) - // When - val actualShadowInfo = ShadowInfo.fromContext(mockContext, mockAttrs, 0) - // Then - assertThat(actualShadowInfo.ambientShadowBlur).isEqualTo(1) - assertThat(actualShadowInfo.ambientShadowColor).isEqualTo(2) - assertThat(actualShadowInfo.keyShadowBlur).isEqualTo(3) - assertThat(actualShadowInfo.keyShadowOffsetX).isEqualTo(4) - assertThat(actualShadowInfo.keyShadowOffsetY).isEqualTo(5) - assertThat(actualShadowInfo.keyShadowColor).isEqualTo(6) - assertThat(actualShadowInfo).isEqualTo(expectedShadowInfo) - } -} diff --git a/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java b/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java deleted file mode 100644 index b2e413de20..0000000000 --- a/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java +++ /dev/null @@ -1,234 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package 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<Launcher> { - - 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<View> 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/WorkProfileTest.java b/tests/src/com/android/launcher3/ui/WorkProfileTest.java deleted file mode 100644 index b6c1135f87..0000000000 --- a/tests/src/com/android/launcher3/ui/WorkProfileTest.java +++ /dev/null @@ -1,212 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package 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.allapps.AllAppsStore.DEFER_UPDATES_TEST; -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.os.Process; -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.util.BaseLauncherActivityTest; -import com.android.launcher3.util.TestUtil; -import com.android.launcher3.util.rule.ScreenRecordRule; -import com.android.launcher3.util.rule.TestStabilityRule; -import com.android.launcher3.util.rule.TestStabilityRule.Stability; - -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.function.Predicate; - -@LargeTest -@RunWith(AndroidJUnit4.class) -public class WorkProfileTest extends BaseLauncherActivityTest<Launcher> { - - private static final int WORK_PAGE = ActivityAllAppsContainerView.AdapterHolder.WORK; - public static final int WAIT_TIME_MS = 30000; - - @Rule - public ScreenRecordRule mScreenRecordRule = new ScreenRecordRule(); - @Rule - public TestStabilityRule mTestStabilityRule = new TestStabilityRule(); - - private int mProfileUserId; - private boolean mWorkProfileSetupSuccessful; - private static final String TAG = "WorkProfileTest"; - - @Before - public void setUp() throws Exception { - String output = executeShellCommand(String.format( - "pm create-user --profileOf %d --managed TestProfile", - Process.myUserHandle().getIdentifier())); - 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. - } - - loadLauncherSync(); - goToState(ALL_APPS); - waitForState("Launcher internal state didn't switch to All Apps", () -> ALL_APPS); - } - - @After - public void removeWorkProfile() throws Exception { - TestUtil.uninstallDummyApp(); - executeShellCommand("pm remove-user --wait " + mProfileUserId); - } - - 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; - }, 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(), - WAIT_TIME_MS); - waitForLauncherCondition("Work tab is missing", - launcher -> launcher.getAppsView().isWorkTabVisible(), - WAIT_TIME_MS); - } - - // Staging; will be promoted to presubmit if stable - @Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) - @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.getWorkUtilityView() != null - && manager.getCurrentState() == WorkProfileManager.STATE_ENABLED - && manager.getWorkUtilityView().isEnabled(), - 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().getWorkUtilityView().getWorkFAB().performClick(); - }); - - waitForLauncherCondition("Work profile toggle OFF failed", launcher -> { - manager.reset(); // pulls current state from system - return manager.getCurrentState() == WorkProfileManager.STATE_DISABLED; - }, 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; - }, WAIT_TIME_MS); - - } - - @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<View> 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); - } - }, 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/AddConfigWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java deleted file mode 100644 index 64ad305599..0000000000 --- a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.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.Context; -import android.content.Intent; -import android.os.Process; -import android.view.View; - -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.filters.LargeTest; - -import com.android.launcher3.AbstractFloatingView; -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.PortraitLandscapeRunner.PortraitLandscape; -import com.android.launcher3.ui.TestViewHelpers; -import com.android.launcher3.util.BaseLauncherActivityTest; -import com.android.launcher3.util.BlockingBroadcastReceiver; -import com.android.launcher3.util.PackageUserKey; -import com.android.launcher3.util.Wait; -import com.android.launcher3.util.rule.ShellCommandRule; -import com.android.launcher3.views.OptionsPopupView; -import com.android.launcher3.widget.LauncherAppWidgetProviderInfo; -import com.android.launcher3.widget.PendingAddWidgetInfo; -import com.android.launcher3.widget.WidgetCell; -import com.android.launcher3.widget.picker.WidgetsFullSheet; -import com.android.launcher3.widget.picker.WidgetsListAdapter; -import com.android.launcher3.widget.picker.WidgetsRecyclerView; - -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 AddConfigWidgetTest extends BaseLauncherActivityTest<Launcher> { - - @Rule - public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind(); - - private LauncherAppWidgetProviderInfo mWidgetInfo; - private AppWidgetManager mAppWidgetManager; - - private int mWidgetId; - - @Before - public void setUp() throws Exception { - mWidgetInfo = TestViewHelpers.findWidgetProvider(true /* hasConfigureScreen */); - mAppWidgetManager = AppWidgetManager.getInstance(targetContext()); - } - - @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 { - new FavoriteItemsTransaction(targetContext()).commit(); - loadLauncherSync(); - - // Add widget to homescreen - WidgetConfigStartupMonitor monitor = new WidgetConfigStartupMonitor(); - executeOnLauncher(OptionsPopupView::openWidgets); - uiDevice.waitForIdle(); - - // Select the widget header - Context testContext = getInstrumentation().getContext(); - String packageName = testContext.getPackageName(); - executeOnLauncher(l -> { - WidgetsRecyclerView wrv = WidgetsFullSheet.getWidgetsView(l); - WidgetsListAdapter adapter = (WidgetsListAdapter) wrv.getAdapter(); - int pos = adapter.getItems().indexOf( - adapter.getItems().stream() - .filter(entry -> packageName.equals(entry.mPkgItem.packageName)) - .findFirst() - .get()); - wrv.getLayoutManager().scrollToPosition(pos); - adapter.onHeaderClicked(true, new PackageUserKey(packageName, Process.myUserHandle())); - }); - uiDevice.waitForIdle(); - - View widgetView = getOnceNotNull("Widget not found", l -> searchView(l.getDragLayer(), v -> - v instanceof WidgetCell - && v.getTag() instanceof PendingAddWidgetInfo pawi - && mWidgetInfo.provider.equals(pawi.componentName))); - addWidgetToWorkspace(widgetView); - - // 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)); - setResult(acceptConfig); - - if (acceptConfig) { - getOnceNotNull("Widget was not added", l -> { - // Close the resize frame before searching for widget - AbstractFloatingView.closeAllOpenViews(l); - return l.getWorkspace().getFirstMatch(new WidgetSearchCondition()); - }); - assertNotNull(mAppWidgetManager.getAppWidgetInfo(mWidgetId)); - } else { - // Verify that the widget id is deleted. - Wait.atMost("", () -> mAppWidgetManager.getAppWidgetInfo(mWidgetId) == null); - } - } - - private void setResult(boolean success) { - getInstrumentation().getTargetContext().sendBroadcast( - WidgetConfigActivity.getCommandIntent(WidgetConfigActivity.class, - success ? "clickOK" : "clickCancel")); - uiDevice.waitForIdle(); - } - - /** - * Condition for searching widget id - */ - private class WidgetSearchCondition implements ItemOperator { - - @Override - public boolean evaluate(ItemInfo info, View view) { - return info instanceof LauncherAppWidgetInfo lawi - && lawi.providerName.equals(mWidgetInfo.provider) - && lawi.appWidgetId == mWidgetId; - } - } - - /** - * Broadcast receiver for receiving widget config activity status. - */ - private static class WidgetConfigStartupMonitor extends BlockingBroadcastReceiver { - - 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/BindWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java deleted file mode 100644 index 67ee1ac789..0000000000 --- a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java +++ /dev/null @@ -1,325 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.launcher3.ui.widget; - -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.TestUtil.getOnUiThread; -import static com.android.launcher3.util.Wait.atMost; -import static com.android.launcher3.util.WidgetUtils.createWidgetInfo; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; - -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.text.TextUtils; -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.ui.TestViewHelpers; -import com.android.launcher3.util.BaseLauncherActivityTest; -import com.android.launcher3.util.rule.ShellCommandRule; -import com.android.launcher3.widget.LauncherAppWidgetHostView; -import com.android.launcher3.widget.LauncherAppWidgetProviderInfo; -import com.android.launcher3.widget.PendingAppWidgetHostView; -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; -import java.util.function.Function; - -/** - * Tests for bind widget flow. - * - * Note running these tests will clear the workspace on the device. - */ -@LargeTest -@RunWith(AndroidJUnit4.class) -public class BindWidgetTest extends BaseLauncherActivityTest<Launcher> { - - @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; - - @Before - public void setUp() throws Exception { - mModel = LauncherAppState.getInstance(targetContext()).getModel(); - } - - @After - public void tearDown() { - if (mCursor != null) { - mCursor.close(); - } - - if (mSessionId > -1) { - targetContext().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); - - // Item deleted from db - mCursor = queryItem(); - assertEquals(0, mCursor.getCount()); - - // The view does not exist - verifyItemEventuallyNull("Widget exists", widgetProvider(info)); - } - - @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 - 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(targetContext()) - .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(targetContext()); - - Bundle b = new Bundle(); - b.putBoolean(WidgetManagerHelper.WIDGET_OPTION_RESTORE_COMPLETED, true); - RemoteViews remoteViews = new RemoteViews( - targetContext().getPackageName(), 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); - verifyItemEventuallyNull("Pending widget exists", pendingWidgetProvider()); - } - - @Test - public void testPendingWidget_notRestored_removed() { - addPendingItemToScreen(getInvalidWidgetInfo(), FLAG_ID_NOT_VALID | FLAG_PROVIDER_NOT_READY); - - verifyItemEventuallyNull("Pending widget exists", pendingWidgetProvider()); - // Item deleted from db - mCursor = queryItem(); - assertEquals(0, mCursor.getCount()); - } - - @Test - 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 = targetContext().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) { - getOnceNotNull("Widget is not present", widgetProvider(info)); - } - - private void verifyPendingWidgetPresent() { - getOnceNotNull("Widget is not present", pendingWidgetProvider()); - } - - private Function<Launcher, Object> pendingWidgetProvider() { - return l -> l.getWorkspace().getFirstMatch( - (item, view) -> view instanceof PendingAppWidgetHostView); - } - - private Function<Launcher, Object> widgetProvider(LauncherAppWidgetProviderInfo info) { - return l -> l.getWorkspace().getFirstMatch((item, view) -> - view instanceof LauncherAppWidgetHostView - && TextUtils.equals(info.label, view.getContentDescription())); - } - - private void verifyItemEventuallyNull(String message, Function<Launcher, Object> provider) { - atMost(message, () -> getFromLauncher(provider) == null); - } - - private void addPendingItemToScreen(LauncherAppWidgetInfo item, int restoreStatus) { - item.restoreStatus = restoreStatus; - item.screenId = FIRST_SCREEN_ID; - new FavoriteItemsTransaction(targetContext()).addItem(() -> item).commit(); - loadLauncherSync(); - } - - private LauncherAppWidgetProviderInfo addWidgetToScreen(boolean hasConfigureScreen, - boolean bindWidget, Consumer<LauncherAppWidgetInfo> itemOverride) { - LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(hasConfigureScreen); - new FavoriteItemsTransaction(targetContext()) - .addItem(() -> { - LauncherAppWidgetInfo item = - createWidgetInfo(info, targetContext(), bindWidget); - item.screenId = FIRST_SCREEN_ID; - itemOverride.accept(item); - return item; - }).commit(); - loadLauncherSync(); - 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<String> activePackage = getOnUiThread(() -> { - Set<String> packages = new HashSet<>(); - InstallSessionHelper.INSTANCE.get(targetContext()).getActiveSessions() - .keySet().forEach(packageUserKey -> packages.add(packageUserKey.mPackageName)); - return packages; - }); - while (true) { - try { - targetContext().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( - null, itemIdMatch(0), null, null)).get(); - } catch (Exception e) { - throw new RuntimeException(e); - } - } -} diff --git a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java deleted file mode 100644 index 7e8d7593fc..0000000000 --- a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java +++ /dev/null @@ -1,192 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.launcher3.ui.widget; - -import static android.app.PendingIntent.FLAG_MUTABLE; -import static android.app.PendingIntent.FLAG_ONE_SHOT; - -import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; - -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNotSame; - -import static java.util.regex.Pattern.CASE_INSENSITIVE; - -import android.app.PendingIntent; -import android.appwidget.AppWidgetManager; -import android.content.Context; -import android.content.Intent; -import android.graphics.Color; -import android.view.View; - -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.filters.LargeTest; -import androidx.test.uiautomator.By; -import androidx.test.uiautomator.BySelector; - -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.testcomponent.AppWidgetNoConfig; -import com.android.launcher3.testcomponent.AppWidgetWithConfig; -import com.android.launcher3.testcomponent.RequestPinItemActivity; -import com.android.launcher3.util.BaseLauncherActivityTest; -import com.android.launcher3.util.BlockingBroadcastReceiver; -import com.android.launcher3.util.LauncherBindableItemsContainer.ItemOperator; -import com.android.launcher3.util.TestUtil; -import com.android.launcher3.util.rule.ScreenRecordRule; -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; -import java.util.regex.Pattern; - -/** - * Test to verify pin item request flow. - */ -@LargeTest -@RunWith(AndroidJUnit4.class) -public class RequestPinItemTest extends BaseLauncherActivityTest<Launcher> { - - @Rule - public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind(); - - @Rule - public ShellCommandRule mDefaultLauncherRule = ShellCommandRule.setDefaultLauncher(); - - @Rule - public ScreenRecordRule mScreenRecordRule = new ScreenRecordRule(); - - private String mCallbackAction; - private String mShortcutId; - private int mAppWidgetId; - - @Before - public void setUp() throws Exception { - mCallbackAction = UUID.randomUUID().toString(); - mShortcutId = UUID.randomUUID().toString(); - } - - @Test - public void testEmpty() throws Throwable { /* needed while the broken tests are being fixed */ } - - @ScreenRecordRule.ScreenRecord // b/386243192 - @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())); - } - - @ScreenRecordRule.ScreenRecord // b/386243192 - @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); - } - - @ScreenRecordRule.ScreenRecord // b/386243192 - @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())); - } - - @ScreenRecordRule.ScreenRecord // b/386243192 - @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 { - new FavoriteItemsTransaction(targetContext()).commit(); - loadLauncherSync(); - - // Open Pin item activity - BlockingBroadcastReceiver openMonitor = new BlockingBroadcastReceiver( - RequestPinItemActivity.class.getName()); - Context testContext = getInstrumentation().getContext(); - startAppFast( - testContext.getPackageName(), - new Intent(testContext, RequestPinItemActivity.class)); - assertNotNull(openMonitor.blockingGetExtraIntent()); - - // Set callback - PendingIntent callback = PendingIntent.getBroadcast(targetContext(), 0, - new Intent(mCallbackAction).setPackage(targetContext().getPackageName()), - FLAG_ONE_SHOT | FLAG_MUTABLE); - targetContext().sendBroadcast(RequestPinItemActivity.getCommandIntent( - RequestPinItemActivity.class, "setCallback").putExtra( - RequestPinItemActivity.EXTRA_PARAM + "0", callback)); - - for (Intent command : commandIntents) { - targetContext().sendBroadcast(command); - } - - // call the requested method to start the flow - targetContext().sendBroadcast(RequestPinItemActivity.getCommandIntent( - RequestPinItemActivity.class, activityMethod)); - - // Accept confirmation: - BlockingBroadcastReceiver resultReceiver = new BlockingBroadcastReceiver(mCallbackAction); - BySelector selector = By.text(Pattern.compile("^Add to home screen$", CASE_INSENSITIVE)) - .pkg(targetContext().getPackageName()); - uiDevice.wait(device -> device.findObject(selector), TestUtil.DEFAULT_UI_TIMEOUT).click(); - Intent result = resultReceiver.blockingGetIntent(); - assertNotNull(result); - mAppWidgetId = result.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1); - if (isWidget) { - assertNotSame(-1, mAppWidgetId); - } - - // Reload activity, so that the activity is focused - closeCurrentActivity(); - loadLauncherSync(); - getOnceNotNull("", l -> l.getWorkspace().getFirstMatch(itemMatcher)); - } -} diff --git a/tests/src/com/android/launcher3/ui/widget/TaplAddConfigWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/TaplAddConfigWidgetTest.java deleted file mode 100644 index e6e02b49ac..0000000000 --- a/tests/src/com/android/launcher3/ui/widget/TaplAddConfigWidgetTest.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.android.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<Launcher> { - - @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 deleted file mode 100644 index 9b184ae664..0000000000 --- a/tests/src/com/android/launcher3/ui/widget/TaplAddWidgetTest.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.android.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; - -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.filters.LargeTest; - -import com.android.launcher3.Launcher; -import com.android.launcher3.celllayout.FavoriteItemsTransaction; -import com.android.launcher3.tapl.Widget; -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.rule.ShellCommandRule; -import com.android.launcher3.util.rule.TestStabilityRule.Stability; -import com.android.launcher3.widget.LauncherAppWidgetProviderInfo; - -import org.junit.Assume; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; - -/** - * Test to add widget from widget tray - */ -@LargeTest -@RunWith(AndroidJUnit4.class) -public class TaplAddWidgetTest extends AbstractLauncherUiTest<Launcher> { - - @Rule - public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind(); - - @Test - @PortraitLandscape - public void testDragIcon() throws Throwable { - mLauncher.enableDebugTracing(); // b/289161193 - commitTransactionAndLoadHome(new FavoriteItemsTransaction(mTargetContext)); - - waitForLauncherCondition("Workspace didn't finish loading", l -> !l.isWorkspaceLoading()); - - final LauncherAppWidgetProviderInfo widgetInfo = - TestViewHelpers.findWidgetProvider(false /* hasConfigureScreen */); - - WidgetResizeFrame resizeFrame = mLauncher - .getWorkspace() - .openAllWidgets() - .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, - DEFAULT_UI_TIMEOUT); - assertNotNull("Widget not found on the workspace", widget); - widget.launch(getAppPackageName()); - mLauncher.disableDebugTracing(); // b/289161193 - } - - /** - * Test dragging a custom shortcut to the workspace and launch it. - * - * A custom shortcut is a 1x1 widget that launches a specific intent when user tap on it. - * Custom shortcuts are replaced by deep shortcuts after api 25. - */ - @Test - @PortraitLandscape - public void testDragCustomShortcut() throws Throwable { - // TODO(b/322820039): Enable test for tablets - the picker UI has changed and test needs to - // be updated to look for appropriate UI elements. - Assume.assumeFalse(mLauncher.isTablet()); - commitTransactionAndLoadHome(new FavoriteItemsTransaction(mTargetContext)); - - mLauncher.getWorkspace().openAllWidgets() - .getWidget("com.android.launcher3.testcomponent.CustomShortcutConfigActivity") - .dragToWorkspace(false, true); - mLauncher.getWorkspace().getWorkspaceAppIcon("Shortcut") - .launch(getAppPackageName()); - } - - /** - * 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 { - commitTransactionAndLoadHome(new FavoriteItemsTransaction(mTargetContext)); - - waitForLauncherCondition("Workspace didn't finish loading", l -> !l.isWorkspaceLoading()); - - final LauncherAppWidgetProviderInfo widgetInfo = - TestViewHelpers.findWidgetProvider(false /* hasConfigureScreen */); - - WidgetResizeFrame resizeFrame = mLauncher - .getWorkspace() - .openAllWidgets() - .getWidget(widgetInfo.getLabel(mTargetContext.getPackageManager())) - .dragWidgetToWorkspace(); - - assertNotNull("Widget resize frame not shown after widget add", resizeFrame); - resizeFrame.resize(); - } -} diff --git a/tests/src/com/android/launcher3/ui/widget/TaplBindWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/TaplBindWidgetTest.java deleted file mode 100644 index 28d1faa5e7..0000000000 --- a/tests/src/com/android/launcher3/ui/widget/TaplBindWidgetTest.java +++ /dev/null @@ -1,319 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.android.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<Launcher> { - - @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<LauncherAppWidgetInfo> 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<String> activePackage = getOnUiThread(() -> { - Set<String> 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 deleted file mode 100644 index 74047f0d01..0000000000 --- a/tests/src/com/android/launcher3/ui/widget/TaplRequestPinItemTest.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.android.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<Launcher> { - - @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 deleted file mode 100644 index 19c585085b..0000000000 --- a/tests/src/com/android/launcher3/ui/widget/TaplWidgetPickerTest.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.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<Launcher> { - - 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/widget/WidgetPickerTest.java b/tests/src/com/android/launcher3/ui/widget/WidgetPickerTest.java deleted file mode 100644 index caad1d98b7..0000000000 --- a/tests/src/com/android/launcher3/ui/widget/WidgetPickerTest.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.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.AbstractFloatingView; -import com.android.launcher3.Launcher; -import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape; -import com.android.launcher3.util.BaseLauncherActivityTest; -import com.android.launcher3.util.rule.ScreenRecordRule; -import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord; -import com.android.launcher3.views.OptionsPopupView; -import com.android.launcher3.widget.picker.WidgetsFullSheet; -import com.android.launcher3.widget.picker.WidgetsRecyclerView; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TestRule; -import org.junit.runner.RunWith; - -/** - * Make sure the basic interactions with the WidgetPicker works. - */ -@MediumTest -@RunWith(AndroidJUnit4.class) -public class WidgetPickerTest extends BaseLauncherActivityTest<Launcher> { - - @Rule - public TestRule screenRecordRule = new ScreenRecordRule(); - - 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() { - loadLauncherSync(); - // Test opening widgets. - executeOnLauncher(launcher -> - assertTrue("Widgets is initially opened", getWidgetsView(launcher) == null)); - assertNotNull("openAllWidgets() returned null", - getFromLauncher(OptionsPopupView::openWidgets)); - WidgetsRecyclerView widgets = getFromLauncher(this::getWidgetsView); - assertNotNull("getAllWidgets() returned null", widgets); - executeOnLauncher(launcher -> assertTrue("Widgets is not shown", widgets.isShown())); - executeOnLauncher(launcher -> assertEquals("Widgets is scrolled upon opening", - 0, getWidgetsScroll(launcher))); - - executeOnLauncher(AbstractFloatingView::closeAllOpenViews); - uiDevice.waitForIdle(); - - 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 deleted file mode 100644 index d6533171d3..0000000000 --- a/tests/src/com/android/launcher3/ui/workspace/TaplThemeIconsTest.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -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<Launcher> { - - 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<View> 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 deleted file mode 100644 index e92d6415f2..0000000000 --- a/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java +++ /dev/null @@ -1,381 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher3.ui.workspace; - -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; -import static org.junit.Assert.assertTrue; -import static org.junit.Assume.assumeTrue; - -import androidx.test.filters.LargeTest; -import androidx.test.runner.AndroidJUnit4; - -import com.android.launcher3.CellLayout; -import com.android.launcher3.Launcher; -import com.android.launcher3.model.data.ItemInfo; -import com.android.launcher3.tapl.Workspace; -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; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.Arrays; -import java.util.Set; -import java.util.stream.Collectors; - -/** - * Tests for two panel workspace. - * - * Note running these tests will clear the workspace on the device. - */ -@LargeTest -@RunWith(AndroidJUnit4.class) -public class TaplTwoPanelWorkspaceTest extends AbstractLauncherUiTest<Launcher> { - - private AutoCloseable mLauncherLayout; - - @Before - public void setUp() throws Exception { - super.setUp(); - - // Set layout that includes Maps/Play on workspace, and Messaging/Chrome on hotseat. - LauncherLayoutBuilder builder = new LauncherLayoutBuilder() - .atHotseat(0).putApp( - "com.google.android.apps.messaging", - "com.google.android.apps.messaging.ui.ConversationListActivity") - .atHotseat(1).putApp("com.android.chrome", "com.google.android.apps.chrome.Main") - .atWorkspace(0, -1, 0).putApp( - "com.google.android.apps.maps", "com.google.android.maps.MapsActivity") - .atWorkspace(3, -1, 0).putApp( - "com.android.vending", "com.android.vending.AssetBrowserActivity"); - mLauncherLayout = TestUtil.setLauncherDefaultLayout(mTargetContext, builder); - AbstractLauncherUiTest.initialize(this); - assumeTrue(mLauncher.isTwoPanels()); - - // Pre verifying the screens - executeOnLauncher(launcher -> { - launcher.enableHotseatEdu(false); - assertPagesExist(launcher, 0, 1); - assertItemsOnPage(launcher, 0, STORE_APP_NAME, MAPS_APP_NAME); - assertPageEmpty(launcher, 1); - }); - } - - @After - public void tearDown() throws Exception { - executeOnLauncherInTearDown(launcher -> launcher.enableHotseatEdu(true)); - if (mLauncherLayout != null) { - mLauncherLayout.close(); - } - } - - @Test - @PortraitLandscape - public void testDragIconToRightPanel() { - Workspace workspace = mLauncher.getWorkspace(); - - workspace.dragIcon(workspace.getHotseatAppIcon(CHROME_APP_NAME), 1); - - executeOnLauncher(launcher -> { - assertPagesExist(launcher, 0, 1); - assertItemsOnPage(launcher, 0, MAPS_APP_NAME, STORE_APP_NAME); - assertItemsOnPage(launcher, 1, CHROME_APP_NAME); - }); - } - - @Test - @PortraitLandscape - @ScreenRecordRule.ScreenRecord // b/329935119 - @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/329935119 - public void testSinglePageDragIconWhenMultiplePageScrollingIsPossible() { - Workspace workspace = mLauncher.getWorkspace(); - - workspace.dragIcon(workspace.getHotseatAppIcon(CHROME_APP_NAME), 2); - - workspace.flingBackward(); - - workspace.dragIcon(workspace.getWorkspaceAppIcon(MAPS_APP_NAME), 3); - - executeOnLauncher(launcher -> { - assertPagesExist(launcher, 0, 1, 2, 3); - assertItemsOnPage(launcher, 0, STORE_APP_NAME); - assertPageEmpty(launcher, 1); - assertItemsOnPage(launcher, 2, CHROME_APP_NAME); - assertItemsOnPage(launcher, 3, MAPS_APP_NAME); - }); - - workspace.dragIcon(workspace.getWorkspaceAppIcon(MAPS_APP_NAME), 3); - - executeOnLauncher(launcher -> { - assertPagesExist(launcher, 0, 1, 2, 3, 4, 5); - assertItemsOnPage(launcher, 0, STORE_APP_NAME); - assertPageEmpty(launcher, 1); - assertItemsOnPage(launcher, 2, CHROME_APP_NAME); - assertPageEmpty(launcher, 3); - assertPageEmpty(launcher, 4); - assertItemsOnPage(launcher, 5, MAPS_APP_NAME); - }); - - workspace.dragIcon(workspace.getWorkspaceAppIcon(MAPS_APP_NAME), -1); - - executeOnLauncher(launcher -> { - assertPagesExist(launcher, 0, 1, 2, 3); - assertItemsOnPage(launcher, 0, STORE_APP_NAME); - assertPageEmpty(launcher, 1); - assertItemsOnPage(launcher, 2, CHROME_APP_NAME); - assertItemsOnPage(launcher, 3, MAPS_APP_NAME); - }); - - workspace.dragIcon(workspace.getWorkspaceAppIcon(MAPS_APP_NAME), -1); - - workspace.flingForward(); - - workspace.dragIcon(workspace.getWorkspaceAppIcon(CHROME_APP_NAME), -2); - - executeOnLauncher(launcher -> { - assertPagesExist(launcher, 0, 1); - assertItemsOnPage(launcher, 0, CHROME_APP_NAME, STORE_APP_NAME); - assertItemsOnPage(launcher, 1, MAPS_APP_NAME); - }); - } - - @Test - @PortraitLandscape - public void testDragIconToPage2() { - Workspace workspace = mLauncher.getWorkspace(); - - workspace.dragIcon(workspace.getWorkspaceAppIcon(MAPS_APP_NAME), 2); - - executeOnLauncher(launcher -> { - assertPagesExist(launcher, 0, 1, 2, 3); - assertItemsOnPage(launcher, 0, STORE_APP_NAME); - assertPageEmpty(launcher, 1); - assertItemsOnPage(launcher, 2, MAPS_APP_NAME); - assertPageEmpty(launcher, 3); - }); - } - - @Test - @PortraitLandscape - public void testDragIconToPage3() { - Workspace workspace = mLauncher.getWorkspace(); - - // b/299522368 sometimes the phone app is not present in the hotseat. - workspace.dragIcon(workspace.getHotseatAppIcon(CHROME_APP_NAME), 3); - - executeOnLauncher(launcher -> { - assertPagesExist(launcher, 0, 1, 2, 3); - assertItemsOnPage(launcher, 0, STORE_APP_NAME, MAPS_APP_NAME); - assertPageEmpty(launcher, 1); - assertPageEmpty(launcher, 2); - assertItemsOnPage(launcher, 3, CHROME_APP_NAME); - }); - } - - @Test - @PortraitLandscape - public void testMultiplePageDragIcon() { - Workspace workspace = mLauncher.getWorkspace(); - - workspace.dragIcon(workspace.getHotseatAppIcon(MESSAGES_APP_NAME), 2); - - workspace.flingBackward(); - - workspace.dragIcon(workspace.getWorkspaceAppIcon(MAPS_APP_NAME), 5); - - executeOnLauncher(launcher -> { - assertPagesExist(launcher, 0, 1, 2, 3, 4, 5); - assertItemsOnPage(launcher, 0, STORE_APP_NAME); - assertPageEmpty(launcher, 1); - assertItemsOnPage(launcher, 2, MESSAGES_APP_NAME); - assertPageEmpty(launcher, 3); - assertPageEmpty(launcher, 4); - assertItemsOnPage(launcher, 5, MAPS_APP_NAME); - }); - - workspace.flingBackward(); - - workspace.dragIcon(workspace.getWorkspaceAppIcon(MESSAGES_APP_NAME), 4); - - executeOnLauncher(launcher -> { - assertPagesExist(launcher, 0, 1, 4, 5, 6, 7); - assertItemsOnPage(launcher, 0, STORE_APP_NAME); - assertPageEmpty(launcher, 1); - assertPageEmpty(launcher, 4); - assertItemsOnPage(launcher, 5, MAPS_APP_NAME); - assertItemsOnPage(launcher, 6, MESSAGES_APP_NAME); - assertPageEmpty(launcher, 7); - }); - - workspace.dragIcon(workspace.getWorkspaceAppIcon(MESSAGES_APP_NAME), -3); - - executeOnLauncher(launcher -> { - assertPagesExist(launcher, 0, 1, 4, 5); - assertItemsOnPage(launcher, 0, STORE_APP_NAME); - assertItemsOnPage(launcher, 1, MESSAGES_APP_NAME); - assertPageEmpty(launcher, 4); - assertItemsOnPage(launcher, 5, MAPS_APP_NAME); - }); - } - - @ScreenRecordRule.ScreenRecord // b/329935119 - @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/329935119 - @Test - @PortraitLandscape - public void testEmptyPageDoesNotGetRemovedIfPagePairIsNotEmpty() { - Workspace workspace = mLauncher.getWorkspace(); - - workspace.dragIcon(workspace.getWorkspaceAppIcon(MAPS_APP_NAME), 3); - workspace.dragIcon(workspace.getHotseatAppIcon(CHROME_APP_NAME), 0); - - executeOnLauncher(launcher -> { - assertPagesExist(launcher, 0, 1, 2, 3); - assertItemsOnPage(launcher, 0, STORE_APP_NAME); - assertPageEmpty(launcher, 1); - assertItemsOnPage(launcher, 2, CHROME_APP_NAME); - assertItemsOnPage(launcher, 3, MAPS_APP_NAME); - }); - - workspace.dragIcon(workspace.getWorkspaceAppIcon(MAPS_APP_NAME), -1); - - executeOnLauncher(launcher -> { - assertPagesExist(launcher, 0, 1, 2, 3); - assertItemsOnPage(launcher, 0, STORE_APP_NAME); - assertItemsOnPage(launcher, 1, MAPS_APP_NAME); - assertItemsOnPage(launcher, 2, CHROME_APP_NAME); - assertPageEmpty(launcher, 3); - }); - - // Move Chrome to the right panel as well, to make sure pages are not deleted whichever - // page is the empty one - workspace.flingForward(); - workspace.dragIcon(workspace.getWorkspaceAppIcon(CHROME_APP_NAME), 1); - - executeOnLauncher(launcher -> { - assertPagesExist(launcher, 0, 1, 2, 3); - assertItemsOnPage(launcher, 0, STORE_APP_NAME); - assertItemsOnPage(launcher, 1, MAPS_APP_NAME); - assertPageEmpty(launcher, 2); - assertItemsOnPage(launcher, 3, CHROME_APP_NAME); - }); - } - - @Test - @PortraitLandscape - public void testEmptyPagesGetRemovedIfBothPagesAreEmpty() { - Workspace workspace = mLauncher.getWorkspace(); - - workspace.dragIcon(workspace.getWorkspaceAppIcon(STORE_APP_NAME), 2); - workspace.dragIcon(workspace.getHotseatAppIcon(CHROME_APP_NAME), 1); - - executeOnLauncher(launcher -> { - assertPagesExist(launcher, 0, 1, 2, 3); - assertItemsOnPage(launcher, 0, MAPS_APP_NAME); - assertPageEmpty(launcher, 1); - assertItemsOnPage(launcher, 2, STORE_APP_NAME); - assertItemsOnPage(launcher, 3, CHROME_APP_NAME); - }); - - workspace.dragIcon(workspace.getWorkspaceAppIcon(CHROME_APP_NAME), -1); - workspace.flingForward(); - workspace.dragIcon(workspace.getWorkspaceAppIcon(STORE_APP_NAME), -2); - - executeOnLauncher(launcher -> { - assertPagesExist(launcher, 0, 1); - assertItemsOnPage(launcher, 0, STORE_APP_NAME, MAPS_APP_NAME); - assertItemsOnPage(launcher, 1, CHROME_APP_NAME); - }); - } - - @Test - @PortraitLandscape - public void testMiddleEmptyPagesGetRemoved() { - Workspace workspace = mLauncher.getWorkspace(); - - workspace.dragIcon(workspace.getWorkspaceAppIcon(MAPS_APP_NAME), 2); - workspace.dragIcon(workspace.getHotseatAppIcon(MESSAGES_APP_NAME), 3); - - executeOnLauncher(launcher -> { - assertPagesExist(launcher, 0, 1, 2, 3, 4, 5); - assertItemsOnPage(launcher, 0, STORE_APP_NAME); - assertPageEmpty(launcher, 1); - assertItemsOnPage(launcher, 2, MAPS_APP_NAME); - assertPageEmpty(launcher, 3); - assertPageEmpty(launcher, 4); - assertItemsOnPage(launcher, 5, MESSAGES_APP_NAME); - }); - - workspace.flingBackward(); - workspace.dragIcon(workspace.getWorkspaceAppIcon(MAPS_APP_NAME), 2); - - executeOnLauncher(launcher -> { - assertPagesExist(launcher, 0, 1, 4, 5); - assertItemsOnPage(launcher, 0, STORE_APP_NAME); - assertPageEmpty(launcher, 1); - assertItemsOnPage(launcher, 4, MAPS_APP_NAME); - assertItemsOnPage(launcher, 5, MESSAGES_APP_NAME); - }); - } - - private void assertPageEmpty(Launcher launcher, int pageId) { - CellLayout page = launcher.getWorkspace().getScreenWithId(pageId); - assertNotNull("Page " + pageId + " does NOT exist.", page); - assertEquals("Page " + pageId + " is NOT empty. Number of items on the page:", 0, - page.getShortcutsAndWidgets().getChildCount()); - } - - private void assertPagesExist(Launcher launcher, int... pageIds) { - int pageCount = launcher.getWorkspace().getPageCount(); - assertEquals("Existing page count does NOT match.", pageIds.length, pageCount); - for (int i = 0; i < pageCount; i++) { - CellLayout page = (CellLayout) launcher.getWorkspace().getPageAt(i); - int pageId = launcher.getWorkspace().getCellLayoutId(page); - assertEquals("The page's id at index " + i + " does NOT match.", pageId, - pageIds[i]); - } - } - - private void assertItemsOnPage(Launcher launcher, int pageId, String... itemTitles) { - Set<String> itemTitleSet = Arrays.stream(itemTitles).collect(Collectors.toSet()); - CellLayout page = launcher.getWorkspace().getScreenWithId(pageId); - int itemCount = page.getShortcutsAndWidgets().getChildCount(); - for (int i = 0; i < itemCount; i++) { - ItemInfo itemInfo = (ItemInfo) page.getShortcutsAndWidgets().getChildAt(i).getTag(); - if (itemInfo != null) { - assertTrue("There was an extra item on page " + pageId + ": " + itemInfo.title, - itemTitleSet.remove(itemInfo.title)); - } - } - assertTrue("Could NOT find some of the items on page " + pageId + ": " - + itemTitleSet.stream().collect(Collectors.joining(",")), - itemTitleSet.isEmpty()); - } -} \ No newline at end of file diff --git a/tests/src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java b/tests/src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java deleted file mode 100644 index 490cff20b4..0000000000 --- a/tests/src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.launcher3.ui.workspace; - -import static com.android.launcher3.util.TestConstants.AppNames.CHROME_APP_NAME; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -import com.android.launcher3.Launcher; -import com.android.launcher3.LauncherState; -import com.android.launcher3.tapl.HomeAppIcon; -import com.android.launcher3.tapl.Workspace; -import com.android.launcher3.ui.AbstractLauncherUiTest; -import com.android.launcher3.util.LauncherLayoutBuilder; -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; - -/** - * Test the basic interactions of the Workspace, adding pages, moving the pages and removing pages. - */ -public class TaplWorkspaceTest extends AbstractLauncherUiTest<Launcher> { - - private AutoCloseable mLauncherLayout; - - private static boolean isWorkspaceScrollable(Launcher launcher) { - return launcher.getWorkspace().getPageCount() > launcher.getWorkspace().getPanelCount(); - } - - private int getCurrentWorkspacePage(Launcher launcher) { - return launcher.getWorkspace().getCurrentPage(); - } - - @Before - public void setUp() throws Exception { - super.setUp(); - initialize(this); - } - - @After - public void tearDown() throws Exception { - if (mLauncherLayout != null) { - mLauncherLayout.close(); - } - } - - /** - * Add an icon and add a page to ensure the Workspace is scrollable and also make sure we can - * move between workspaces. After, make sure we can launch an app from the Workspace. - * @throws Exception if we can't set the defaults icons that will appear at the beginning. - */ - @ScreenRecord // b/331261431 - @Test - public void testWorkspace() 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(); - - final Workspace workspace = mLauncher.getWorkspace(); - - // Test that ensureWorkspaceIsScrollable adds a page by dragging an icon there. - executeOnLauncher(launcher -> assertFalse("Initial workspace state is scrollable", - isWorkspaceScrollable(launcher))); - assertEquals("Initial workspace doesn't have the correct page", workspace.pagesPerScreen(), - workspace.getPageCount()); - workspace.verifyWorkspaceAppIconIsGone("Chrome app was found on empty workspace", - CHROME_APP_NAME); - workspace.ensureWorkspaceIsScrollable(); - - executeOnLauncher( - launcher -> assertEquals( - "Ensuring workspace scrollable didn't switch to next screen", - workspace.pagesPerScreen(), getCurrentWorkspacePage(launcher))); - executeOnLauncher( - launcher -> assertTrue("ensureScrollable didn't make workspace scrollable", - isWorkspaceScrollable(launcher))); - assertNotNull("ensureScrollable didn't add Chrome app", - workspace.getWorkspaceAppIcon(CHROME_APP_NAME)); - - // Test flinging workspace. - workspace.flingBackward(); - assertTrue("Launcher internal state is not Home", isInState(() -> LauncherState.NORMAL)); - executeOnLauncher( - launcher -> assertEquals("Flinging back didn't switch workspace to page #0", - 0, getCurrentWorkspacePage(launcher))); - - workspace.flingForward(); - executeOnLauncher( - launcher -> assertEquals("Flinging forward didn't switch workspace to next screen", - workspace.pagesPerScreen(), getCurrentWorkspacePage(launcher))); - assertTrue("Launcher internal state is not Home", isInState(() -> LauncherState.NORMAL)); - - // Test starting a workspace app. - final HomeAppIcon app = workspace.getWorkspaceAppIcon(CHROME_APP_NAME); - assertNotNull("No Chrome app in workspace", app); - } - - - /** - * Similar to {@link TaplWorkspaceTest#testWorkspace} but here we also make sure we can delete - * the pages. - */ - @Test - public void testAddAndDeletePageAndFling() { - Workspace workspace = mLauncher.getWorkspace(); - // Get the first app from the hotseat - HomeAppIcon hotSeatIcon = workspace.getHotseatAppIcon(0); - String appName = hotSeatIcon.getIconName(); - - // Add one page by dragging app to page 1. - workspace.dragIcon(hotSeatIcon, workspace.pagesPerScreen()); - assertEquals("Incorrect Page count Number", - workspace.pagesPerScreen() * 2, - workspace.getPageCount()); - - // Delete one page by dragging app to hot seat. - workspace.getWorkspaceAppIcon(appName).dragToHotseat(0); - - // Refresh workspace to avoid using stale container error. - workspace = mLauncher.getWorkspace(); - assertEquals("Incorrect Page count Number", - workspace.pagesPerScreen(), - workspace.getPageCount()); - } -} diff --git a/tests/src/com/android/launcher3/ui/workspace/ThemeIconsTest.java b/tests/src/com/android/launcher3/ui/workspace/ThemeIconsTest.java deleted file mode 100644 index 38970fe31b..0000000000 --- a/tests/src/com/android/launcher3/ui/workspace/ThemeIconsTest.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.launcher3.ui.workspace; - -import static com.android.launcher3.AbstractFloatingView.TYPE_ACTION_POPUP; -import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; -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.ContentResolver; -import android.content.ContentValues; -import android.net.Uri; -import android.view.ViewGroup; - -import androidx.test.filters.LargeTest; - -import com.android.launcher3.AbstractFloatingView; -import com.android.launcher3.BubbleTextView; -import com.android.launcher3.Launcher; -import com.android.launcher3.LauncherState; -import com.android.launcher3.allapps.AllAppsRecyclerView; -import com.android.launcher3.celllayout.FavoriteItemsTransaction; -import com.android.launcher3.dagger.LauncherComponentProvider; -import com.android.launcher3.icons.mono.ThemedIconDrawable; -import com.android.launcher3.popup.ArrowPopup; -import com.android.launcher3.util.BaseLauncherActivityTest; -import com.android.launcher3.util.Executors; -import com.android.launcher3.util.TestUtil; - -import org.junit.Test; - -/** - * Tests for theme icon support in Launcher - * - * Note running these tests will clear the workspace on the device. - */ -@LargeTest -public class ThemeIconsTest extends BaseLauncherActivityTest<Launcher> { - - private static final String APP_NAME = "IconThemedActivity"; - private static final String SHORTCUT_NAME = "Shortcut 1"; - - @Test - public void testIconWithoutTheme() throws Exception { - setThemeEnabled(false); - new FavoriteItemsTransaction(targetContext()).commit(); - loadLauncherSync(); - switchToAllApps(); - - scrollToAppIcon(APP_NAME); - BubbleTextView btv = getFromLauncher( - l -> verifyIconTheme(APP_NAME, l.getAppsView(), false)); - addToWorkspace(btv); - executeOnLauncher(l -> verifyIconTheme(APP_NAME, l.getWorkspace(), false)); - } - - @Test - public void testShortcutIconWithoutTheme() throws Exception { - setThemeEnabled(false); - new FavoriteItemsTransaction(targetContext()).commit(); - loadLauncherSync(); - switchToAllApps(); - - scrollToAppIcon(TEST_APP_NAME); - BubbleTextView btv = getFromLauncher(l -> findBtv(TEST_APP_NAME, l.getAppsView())); - TestUtil.runOnExecutorSync(MAIN_EXECUTOR, btv::performLongClick); - - BubbleTextView menuItem = getOnceNotNull("Popup menu not open", l -> - (AbstractFloatingView.getOpenView(l, TYPE_ACTION_POPUP) instanceof ArrowPopup ap) - ? findBtv(SHORTCUT_NAME, ap) : null); - addToWorkspace(menuItem); - executeOnLauncher(l -> verifyIconTheme(SHORTCUT_NAME, l.getWorkspace(), false)); - } - - @Test - public void testIconWithTheme() throws Exception { - setThemeEnabled(true); - new FavoriteItemsTransaction(targetContext()).commit(); - loadLauncherSync(); - switchToAllApps(); - - scrollToAppIcon(APP_NAME); - BubbleTextView btv = getFromLauncher(l -> - verifyIconTheme(APP_NAME, l.getAppsView(), false)); - addToWorkspace(btv); - executeOnLauncher(l -> verifyIconTheme(APP_NAME, l.getWorkspace(), true)); - } - - @Test - public void testShortcutIconWithTheme() throws Exception { - setThemeEnabled(true); - loadLauncherSync(); - switchToAllApps(); - - scrollToAppIcon(TEST_APP_NAME); - BubbleTextView btv = getFromLauncher(l -> findBtv(TEST_APP_NAME, l.getAppsView())); - TestUtil.runOnExecutorSync(MAIN_EXECUTOR, btv::performLongClick); - - BubbleTextView menuItem = getOnceNotNull("Popup menu not open", l -> - (AbstractFloatingView.getOpenView(l, TYPE_ACTION_POPUP) instanceof ArrowPopup ap) - ? findBtv(SHORTCUT_NAME, ap) : null); - addToWorkspace(menuItem); - executeOnLauncher(l -> verifyIconTheme(SHORTCUT_NAME, l.getWorkspace(), true)); - } - - private BubbleTextView findBtv(String title, ViewGroup parent) { - // Wait for Launcher model to be completed - try { - Executors.MODEL_EXECUTOR.submit(() -> { }).get(); - } catch (Exception e) { - throw new RuntimeException(e); - } - return (BubbleTextView) searchView(parent, v -> - v instanceof BubbleTextView btv - && btv.getContentDescription() != null - && title.equals(btv.getContentDescription().toString())); - } - - private BubbleTextView verifyIconTheme(String title, ViewGroup parent, boolean isThemed) { - BubbleTextView icon = findBtv(title, parent); - assertNotNull(icon.getIcon()); - assertEquals(isThemed, icon.getIcon() instanceof ThemedIconDrawable); - return icon; - } - - private void setThemeEnabled(boolean isEnabled) { - Uri uri = new Uri.Builder() - .scheme(ContentResolver.SCHEME_CONTENT) - .authority(targetContext().getPackageName() + ".grid_control") - .appendPath("set_icon_themed") - .build(); - ContentValues values = new ContentValues(); - values.put("boolean_value", isEnabled); - - int result = LauncherComponentProvider.get(targetContext()).getGridCustomizationsProxy() - .update(uri, values, null, null); - assertTrue(result > 0); - } - - private void switchToAllApps() { - goToState(LauncherState.ALL_APPS); - waitForState("Launcher internal state didn't switch to All Apps", - () -> LauncherState.ALL_APPS); - freezeAllApps(); - } - - private void scrollToAppIcon(String appName) { - executeOnLauncher(l -> { - l.hideKeyboard(); - AllAppsRecyclerView rv = l.getAppsView().getActiveRecyclerView(); - int pos = rv.getApps().getAdapterItems().indexOf(rv.getApps().getAdapterItems().stream() - .filter(i -> i.itemInfo != null && appName.equals(i.itemInfo.title.toString())) - .findFirst() - .get()); - rv.getLayoutManager().scrollToPosition(pos); - }); - } -} diff --git a/tests/src/com/android/launcher3/util/Wait.java b/tests/src/com/android/launcher3/util/Wait.java deleted file mode 100644 index 50bc32e509..0000000000 --- a/tests/src/com/android/launcher3/util/Wait.java +++ /dev/null @@ -1,66 +0,0 @@ -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<String> message, Condition condition, long timeout, - LauncherInstrumentation launcher) { - atMost(message, condition, timeout, DEFAULT_SLEEP_MS, launcher); - } - - public static void atMost(Supplier<String> 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 deleted file mode 100644 index 702988c05b..0000000000 --- a/tests/src/com/android/launcher3/util/rule/ExtendedLongPressTimeoutRule.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher3.util.rule; - -import android.content.ContentResolver; -import android.provider.Settings; -import android.util.Log; -import android.view.ViewConfiguration; - -import androidx.test.InstrumentationRegistry; - -import org.junit.rules.TestRule; -import org.junit.runner.Description; -import org.junit.runners.model.Statement; - -public class ExtendedLongPressTimeoutRule implements TestRule { - - private static final String TAG = "ExtendedLongPressTimeoutRule"; - - private static final float LONG_PRESS_TIMEOUT_MULTIPLIER = 10f; - - @Override - public Statement apply(Statement base, Description description) { - return new Statement() { - @Override - public void evaluate() throws Throwable { - ContentResolver contentResolver = InstrumentationRegistry.getInstrumentation() - .getContext() - .getContentResolver(); - int prevLongPressTimeout = Settings.Secure.getInt( - contentResolver, - Settings.Secure.LONG_PRESS_TIMEOUT, - ViewConfiguration.getLongPressTimeout()); - int newLongPressTimeout = - (int) (prevLongPressTimeout * LONG_PRESS_TIMEOUT_MULTIPLIER); - - try { - Log.d(TAG, "In try-block: Setting long press timeout from " - + prevLongPressTimeout + "ms to " + newLongPressTimeout + "ms"); - Settings.Secure.putInt( - contentResolver, - Settings.Secure.LONG_PRESS_TIMEOUT, - (int) (prevLongPressTimeout * LONG_PRESS_TIMEOUT_MULTIPLIER)); - - base.evaluate(); - } catch (Exception e) { - Log.e(TAG, "Error", e); - throw e; - } finally { - Log.d(TAG, "In finally-block: resetting long press timeout to " - + prevLongPressTimeout + "ms"); - Settings.Secure.putInt( - contentResolver, - Settings.Secure.LONG_PRESS_TIMEOUT, - prevLongPressTimeout); - } - } - }; - } -} diff --git a/tests/src/com/android/launcher3/util/rule/ViewCaptureRule.kt b/tests/src/com/android/launcher3/util/rule/ViewCaptureRule.kt deleted file mode 100644 index e70ea18f7a..0000000000 --- a/tests/src/com/android/launcher3/util/rule/ViewCaptureRule.kt +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.android.launcher3.util.rule - -import android.app.Activity -import android.app.Application -import android.media.permission.SafeCloseable -import android.os.Bundle -import androidx.test.core.app.ApplicationProvider -import androidx.test.platform.app.InstrumentationRegistry -import com.android.app.viewcapture.SimpleViewCapture -import com.android.app.viewcapture.ViewCapture.MAIN_EXECUTOR -import com.android.app.viewcapture.data.ExportedData -import com.android.launcher3.tapl.TestHelpers -import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter -import com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT -import com.android.launcher3.util.viewcapture_analysis.ViewCaptureAnalyzer -import java.io.BufferedOutputStream -import java.io.FileOutputStream -import java.io.IOException -import java.io.OutputStreamWriter -import java.util.function.Supplier -import org.junit.Assert.assertTrue -import org.junit.Assert.fail -import org.junit.rules.TestRule -import org.junit.runner.Description -import org.junit.runners.model.Statement - -/** - * This JUnit TestRule registers a listener for activity lifecycle events to attach a ViewCapture - * instance that other test rules use to dump the timelapse hierarchy upon an error during a test. - * - * This rule will not work in OOP tests that don't have access to the activity under test. - */ -class ViewCaptureRule(var alreadyOpenActivitySupplier: Supplier<Activity?>) : TestRule { - private val viewCapture = SimpleViewCapture("test-view-capture") - var viewCaptureData: ExportedData? = null - private set - - override fun apply(base: Statement, description: Description): Statement { - // Skip view capture collection in Launcher3 tests to avoid hidden API check exception. - if ( - "com.android.launcher3.tests" == - InstrumentationRegistry.getInstrumentation().context.packageName - ) - return base - - return object : Statement() { - override fun evaluate() { - viewCaptureData = null - val windowListenerCloseables = mutableListOf<SafeCloseable>() - - startCapturingExistingActivity(windowListenerCloseables) - - val lifecycleCallbacks = - object : ActivityLifecycleCallbacksAdapter { - override fun onActivityCreated(activity: Activity, bundle: Bundle?) { - startCapture(windowListenerCloseables, activity) - } - - override fun onActivityDestroyed(activity: Activity) { - viewCapture.stopCapture(activity.window.decorView) - } - } - - val application = ApplicationProvider.getApplicationContext<Application>() - application.registerActivityLifecycleCallbacks(lifecycleCallbacks) - - try { - base.evaluate() - } finally { - application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks) - - viewCaptureData = - viewCapture.getExportedData(ApplicationProvider.getApplicationContext()) - - // Clean up ViewCapture references here rather than in onActivityDestroyed so - // test code can access view hierarchy capture. onActivityDestroyed would delete - // view capture data before FailureWatcher could output it as a test artifact. - // This is on the main thread to avoid a race condition where the onDrawListener - // is removed while onDraw is running, resulting in an IllegalStateException. - MAIN_EXECUTOR.execute { windowListenerCloseables.onEach(SafeCloseable::close) } - } - - analyzeViewCapture(description) - } - - private fun startCapturingExistingActivity( - windowListenerCloseables: MutableCollection<SafeCloseable> - ) { - val alreadyOpenActivity = alreadyOpenActivitySupplier.get() - if (alreadyOpenActivity != null) { - startCapture(windowListenerCloseables, alreadyOpenActivity) - } - } - - private fun startCapture( - windowListenerCloseables: MutableCollection<SafeCloseable>, - activity: Activity - ) { - windowListenerCloseables.add( - viewCapture.startCapture( - activity.window.decorView, - "${description.testClass?.simpleName}.${description.methodName}" - ) - ) - } - } - } - - private fun analyzeViewCapture(description: Description) { - // OOP tests don't produce ViewCapture data - if (!TestHelpers.isInLauncherProcess()) return - - // Due to flakiness of ViewCapture verifier, don't run the check in presubmit - if (TestStabilityRule.getRunFlavor() != PLATFORM_POSTSUBMIT) return - - var frameCount = 0 - for (i in 0 until viewCaptureData!!.windowDataCount) { - frameCount += viewCaptureData!!.getWindowData(i).frameDataCount - } - - val mayProduceNoFrames = description.getAnnotation(MayProduceNoFrames::class.java) != null - assertTrue("Empty ViewCapture data", mayProduceNoFrames || frameCount > 0) - - val anomalies: Map<String, String> = ViewCaptureAnalyzer.getAnomalies(viewCaptureData) - if (!anomalies.isEmpty()) { - val diagFile = FailureWatcher.diagFile(description, "ViewAnomalies", "txt") - try { - OutputStreamWriter(BufferedOutputStream(FileOutputStream(diagFile))).use { writer -> - writer.write("View animation anomalies detected.\r\n") - writer.write( - "To suppress an anomaly for a view, add its full path to the PATHS_TO_IGNORE list in the corresponding AnomalyDetector.\r\n" - ) - writer.write("List of views with animation anomalies:\r\n") - - for ((viewPath, message) in anomalies) { - writer.write("View: $viewPath\r\n $message\r\n") - } - } - } catch (ex: IOException) { - throw RuntimeException(ex) - } - - val (viewPath, message) = anomalies.entries.first() - fail( - "${anomalies.size} view(s) had animation anomalies during the test, including view: $viewPath: $message\r\nSee ${diagFile.name} for details." - ) - } - } - - @Retention(AnnotationRetention.RUNTIME) - @Target(AnnotationTarget.FUNCTION) - annotation class MayProduceNoFrames -} diff --git a/tests/src/com/android/launcher3/util/viewcapture_analysis/AlphaJumpDetector.java b/tests/src/com/android/launcher3/util/viewcapture_analysis/AlphaJumpDetector.java deleted file mode 100644 index dfccffc1f5..0000000000 --- a/tests/src/com/android/launcher3/util/viewcapture_analysis/AlphaJumpDetector.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.launcher3.util.viewcapture_analysis; - -import com.android.launcher3.util.viewcapture_analysis.ViewCaptureAnalyzer.AnalysisNode; - -import java.util.List; - -/** - * Anomaly detector that triggers an error when alpha of a view changes too rapidly. - * Invisible views are treated as if they had zero alpha. - */ -final class AlphaJumpDetector extends AnomalyDetector { - // Commonly used parts of the paths to ignore. - private static final String CONTENT = "DecorView|LinearLayout|FrameLayout:id/content|"; - private static final String DRAG_LAYER = - CONTENT + "LauncherRootView:id/launcher|DragLayer:id/drag_layer|"; - private static final String RECENTS_DRAG_LAYER = - CONTENT + "LauncherRootView:id/launcher|RecentsDragLayer:id/drag_layer|"; - - private static final IgnoreNode IGNORED_NODES_ROOT = buildIgnoreNodesTree(List.of( - CONTENT - + "SimpleDragLayer:id/add_item_drag_layer|AddItemWidgetsBottomSheet:id" - + "/add_item_bottom_sheet|LinearLayout:id/add_item_bottom_sheet_content" - + "|ScrollView:id/widget_preview_scroll_view|WidgetCell:id/widget_cell" - + "|WidgetCellPreview:id/widget_preview_container|ImageView:id/widget_badge", - CONTENT - + "SimpleDragLayer:id/add_item_drag_layer|AddItemWidgetsBottomSheet:id" - + "/add_item_bottom_sheet|LinearLayout:id/add_item_bottom_sheet_content" - + "|ScrollView:id/widget_preview_scroll_view|WidgetCell:id/widget_cell" - + "|WidgetCellPreview:id/widget_preview_container|WidgetCell$1|FrameLayout" - + "|ImageView:id/icon", - CONTENT + "SimpleDragLayer:id/add_item_drag_layer|View", - DRAG_LAYER - + "AppWidgetResizeFrame|FrameLayout|ImageButton:id/widget_reconfigure_button", - DRAG_LAYER - + "AppWidgetResizeFrame|FrameLayout|ImageView:id/widget_resize_bottom_handle", - DRAG_LAYER + "AppWidgetResizeFrame|FrameLayout|ImageView:id/widget_resize_frame", - DRAG_LAYER + "AppWidgetResizeFrame|FrameLayout|ImageView:id/widget_resize_left_handle", - DRAG_LAYER + "AppWidgetResizeFrame|FrameLayout|ImageView:id/widget_resize_right_handle", - DRAG_LAYER + "AppWidgetResizeFrame|FrameLayout|ImageView:id/widget_resize_top_handle", - DRAG_LAYER + "FloatingTaskView|FloatingTaskThumbnailView:id/thumbnail", - DRAG_LAYER + "FloatingTaskView|SplitPlaceholderView:id/split_placeholder", - DRAG_LAYER + "Folder|FolderPagedView:id/folder_content", - DRAG_LAYER + "LauncherAllAppsContainerView:id/apps_view", - DRAG_LAYER + "LauncherDragView", - DRAG_LAYER + "LauncherRecentsView:id/overview_panel", - DRAG_LAYER - + "NexusOverviewActionsView:id/overview_actions_view|FrameLayout:id" - + "/select_mode_buttons|ImageButton:id/close", - DRAG_LAYER - + "PopupContainerWithArrow:id/popup_container|LinearLayout:id" - + "/deep_shortcuts_container|DeepShortcutView:id/deep_shortcut_material" - + "|DeepShortcutTextView:id/bubble_text", - DRAG_LAYER - + "PopupContainerWithArrow:id/popup_container|LinearLayout:id" - + "/deep_shortcuts_container|DeepShortcutView:id/deep_shortcut_material|View" - + ":id/icon", - DRAG_LAYER - + "PopupContainerWithArrow:id/popup_container|LinearLayout:id" - + "/system_shortcuts_container|DeepShortcutView:id/system_shortcut" - + "|BubbleTextView:id/bubble_text", - DRAG_LAYER - + "PopupContainerWithArrow:id/popup_container|LinearLayout:id" - + "/system_shortcuts_container|DeepShortcutView:id/system_shortcut|View:id" - + "/icon", - DRAG_LAYER - + "PopupContainerWithArrow:id/popup_container|LinearLayout:id" - + "/system_shortcuts_container|ImageView", - DRAG_LAYER - + "PopupContainerWithArrow:id/popup_container|LinearLayout:id" - + "/widget_shortcut_container|DeepShortcutView:id/system_shortcut" - + "|BubbleTextView:id/bubble_text", - DRAG_LAYER - + "PopupContainerWithArrow:id/popup_container|LinearLayout:id" - + "/widget_shortcut_container|DeepShortcutView:id/system_shortcut|View:id/icon", - DRAG_LAYER + "SearchContainerView:id/apps_view", - DRAG_LAYER + "Snackbar|TextView:id/action", - DRAG_LAYER + "Snackbar|TextView:id/label", - DRAG_LAYER + "SplitInstructionsView|AppCompatTextView:id/split_instructions_text", - DRAG_LAYER + "TaskMenuView|LinearLayout:id/menu_option_layout", - DRAG_LAYER + "TaskMenuViewWithArrow|LinearLayout:id/menu_option_layout", - DRAG_LAYER + "TaskMenuView|TextView:id/task_name", - DRAG_LAYER + "View", - DRAG_LAYER + "WidgetsFullSheet|SpringRelativeLayout:id/container", - DRAG_LAYER + "WidgetsTwoPaneSheet|SpringRelativeLayout:id/container", - CONTENT + "LauncherRootView:id/launcher|FloatingIconView", - RECENTS_DRAG_LAYER + "ArrowTipView", - DRAG_LAYER + "ArrowTipView", - DRAG_LAYER + "FallbackRecentsView:id/overview_panel", - RECENTS_DRAG_LAYER + "FallbackRecentsView:id/overview_panel", - DRAG_LAYER - + "NexusOverviewActionsView:id/overview_actions_view" - + "|LinearLayout:id/action_buttons", - RECENTS_DRAG_LAYER - + "NexusOverviewActionsView:id/overview_actions_view" - + "|LinearLayout:id/action_buttons", - DRAG_LAYER + "IconView", - DRAG_LAYER - + "OptionsPopupView:id/popup_container|DeepShortcutView:id/system_shortcut" - + "|BubbleTextView:id/bubble_text" - )); - - // Minimal increase or decrease of view's alpha between frames that triggers the error. - private static final float ALPHA_JUMP_THRESHOLD = 1f; - - // Per-AnalysisNode data that's specific to this detector. - private static class NodeData { - public boolean ignoreAlphaJumps; - - // If ignoreNode is null, then this AnalysisNode node will be ignored if its parent is - // ignored. - // Otherwise, this AnalysisNode will be ignored if ignoreNode is a leaf i.e. has no - // children. - public IgnoreNode ignoreNode; - } - - private NodeData getNodeData(AnalysisNode info) { - return (NodeData) info.detectorsData[detectorOrdinal]; - } - - @Override - void initializeNode(AnalysisNode info) { - final NodeData nodeData = new NodeData(); - info.detectorsData[detectorOrdinal] = nodeData; - - // If the parent view ignores alpha jumps, its descendants will too. - final boolean parentIgnoresAlphaJumps = info.parent != null && getNodeData( - info.parent).ignoreAlphaJumps; - if (parentIgnoresAlphaJumps) { - nodeData.ignoreAlphaJumps = true; - return; - } - - // Parent view doesn't ignore alpha jumps. - // Initialize this AnalysisNode's ignore-node with the corresponding child of the - // ignore-node of the parent, if present. - final IgnoreNode parentIgnoreNode = info.parent != null - ? getNodeData(info.parent).ignoreNode - : IGNORED_NODES_ROOT; - nodeData.ignoreNode = parentIgnoreNode != null - ? parentIgnoreNode.children.get(info.nodeIdentity) : null; - // AnalysisNode will be ignored if the corresponding ignore-node is a leaf. - nodeData.ignoreAlphaJumps = - nodeData.ignoreNode != null && nodeData.ignoreNode.children.isEmpty(); - } - - @Override - String detectAnomalies(AnalysisNode oldInfo, AnalysisNode newInfo, int frameN, long timestamp, - int windowSizePx) { - // If the view was previously seen, proceed with analysis only if it was present in the - // view hierarchy in the previous frame. - if (oldInfo != null && oldInfo.frameN != frameN) return null; - - final AnalysisNode latestInfo = newInfo != null ? newInfo : oldInfo; - final NodeData nodeData = getNodeData(latestInfo); - if (nodeData.ignoreAlphaJumps) return null; - - final float oldAlpha = oldInfo != null ? oldInfo.alpha : 0; - final float newAlpha = newInfo != null ? newInfo.alpha : 0; - final float alphaDeltaAbs = Math.abs(newAlpha - oldAlpha); - - if (alphaDeltaAbs >= ALPHA_JUMP_THRESHOLD) { - nodeData.ignoreAlphaJumps = true; // No need to report alpha jump in children. - return String.format( - "Alpha jump detected: alpha change: %s (%s -> %s), threshold: %s", - alphaDeltaAbs, oldAlpha, newAlpha, ALPHA_JUMP_THRESHOLD); - } - return null; - } -} diff --git a/tests/src/com/android/launcher3/util/viewcapture_analysis/AnomalyDetector.java b/tests/src/com/android/launcher3/util/viewcapture_analysis/AnomalyDetector.java deleted file mode 100644 index 786791c22f..0000000000 --- a/tests/src/com/android/launcher3/util/viewcapture_analysis/AnomalyDetector.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.launcher3.util.viewcapture_analysis; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import java.util.HashMap; -import java.util.Map; - -/** - * Detector of one kind of anomaly. - */ -abstract class AnomalyDetector { - // Index of this detector in ViewCaptureAnalyzer.ANOMALY_DETECTORS - public int detectorOrdinal; - - /** - * Element of the tree of ignored nodes. - * If the "children" map is empty, then this node should be ignored, i.e. the analysis shouldn't - * run for it. - * I.e. ignored nodes correspond to the leaves in the ignored nodes tree. - */ - protected static class IgnoreNode { - // Map from child node identities to ignore-nodes for these children. - public final Map<String, IgnoreNode> children = new HashMap<>(); - } - - // Converts the list of full paths of nodes to ignore to a more efficient tree of ignore-nodes. - protected static IgnoreNode buildIgnoreNodesTree(Iterable<String> pathsToIgnore) { - final IgnoreNode root = new IgnoreNode(); - for (String pathToIgnore : pathsToIgnore) { - // Scan the diag path of an ignored node and add its elements into the tree. - IgnoreNode currentIgnoreNode = root; - for (String part : pathToIgnore.split("\\|")) { - // Ensure that the child of the node is added to the tree. - IgnoreNode child = currentIgnoreNode.children.get(part); - if (child == null) { - currentIgnoreNode.children.put(part, child = new IgnoreNode()); - } - currentIgnoreNode = child; - } - } - return root; - } - - /** - * Initializes fields of the node that are specific to the anomaly detected by this - * detector. - */ - abstract void initializeNode(@NonNull ViewCaptureAnalyzer.AnalysisNode info); - - /** - * Detects anomalies by looking at the last occurrence of a view, and the current one. - * null value means that the view. 'oldInfo' and 'newInfo' cannot be both null. - * If an anomaly is detected, an exception will be thrown. - * - * @param oldInfo the view, as seen in the last frame that contained it in the view - * hierarchy before 'currentFrame'. 'null' means that the view is first seen - * in the 'currentFrame'. - * @param newInfo the view in the view hierarchy of the 'currentFrame'. 'null' means that - * the view is not present in the 'currentFrame', but was present in the - * previous frame. - * @param frameN number of the current frame. - * @param windowSizePx maximum of the window width and height, in pixels. - * @return Anomaly diagnostic message if an anomaly has been detected; null otherwise. - */ - abstract String detectAnomalies( - @Nullable ViewCaptureAnalyzer.AnalysisNode oldInfo, - @Nullable ViewCaptureAnalyzer.AnalysisNode newInfo, int frameN, - long frameTimeNs, int windowSizePx); -} diff --git a/tests/src/com/android/launcher3/util/viewcapture_analysis/FlashDetector.java b/tests/src/com/android/launcher3/util/viewcapture_analysis/FlashDetector.java deleted file mode 100644 index fc8f818bf5..0000000000 --- a/tests/src/com/android/launcher3/util/viewcapture_analysis/FlashDetector.java +++ /dev/null @@ -1,180 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.launcher3.util.viewcapture_analysis; - -import static org.junit.Assert.assertTrue; - -import com.android.launcher3.util.viewcapture_analysis.ViewCaptureAnalyzer.AnalysisNode; - -import java.util.List; - -/** - * Anomaly detector that triggers an error when a view flashes, i.e. appears or disappears for a too - * short period of time. - */ -final class FlashDetector extends AnomalyDetector { - // Maximum time period of a view visibility or invisibility that is recognized as a flash. - private static final int FLASH_DURATION_MS = 300; - - // Commonly used parts of the paths to ignore. - private static final String CONTENT = "DecorView|LinearLayout|FrameLayout:id/content|"; - private static final String DRAG_LAYER = - CONTENT + "LauncherRootView:id/launcher|DragLayer:id/drag_layer|"; - private static final String RECENTS_DRAG_LAYER = - CONTENT + "LauncherRootView:id/launcher|RecentsDragLayer:id/drag_layer|"; - - private static final IgnoreNode IGNORED_NODES_ROOT = buildIgnoreNodesTree(List.of( - CONTENT + "LauncherRootView:id/launcher|FloatingIconView", - DRAG_LAYER + "LauncherRecentsView:id/overview_panel|TaskView", - DRAG_LAYER + "LauncherRecentsView:id/overview_panel|ClearAllButton:id/clear_all", - DRAG_LAYER - + "LauncherAllAppsContainerView:id/apps_view|AllAppsRecyclerView:id" - + "/apps_list_view|BubbleTextView:id/icon", - CONTENT - + "SimpleDragLayer:id/add_item_drag_layer|AddItemWidgetsBottomSheet:id" - + "/add_item_bottom_sheet|LinearLayout:id/add_item_bottom_sheet_content" - + "|ScrollView:id/widget_preview_scroll_view|WidgetCell:id/widget_cell" - + "|WidgetCellPreview:id/widget_preview_container|WidgetImageView:id" - + "/widget_preview", - CONTENT - + "SimpleDragLayer:id/add_item_drag_layer|AddItemWidgetsBottomSheet:id" - + "/add_item_bottom_sheet|LinearLayout:id/add_item_bottom_sheet_content" - + "|ScrollView:id/widget_preview_scroll_view|WidgetCell:id/widget_cell" - + "|WidgetCellPreview:id/widget_preview_container|ImageView:id/widget_badge", - RECENTS_DRAG_LAYER + "FallbackRecentsView:id/overview_panel|TaskView", - RECENTS_DRAG_LAYER - + "FallbackRecentsView:id/overview_panel|ClearAllButton:id/clear_all", - DRAG_LAYER + "SearchContainerView:id/apps_view", - DRAG_LAYER + "LauncherDragView", - DRAG_LAYER + "FloatingTaskView|FloatingTaskThumbnailView:id/thumbnail", - DRAG_LAYER - + "WidgetsFullSheet|SpringRelativeLayout:id/container|WidgetsRecyclerView:id" - + "/primary_widgets_list_view|WidgetsListHeader:id/widgets_list_header", - DRAG_LAYER - + "WidgetsTwoPaneSheet|SpringRelativeLayout:id/container|LinearLayout:id" - + "/linear_layout_container|FrameLayout:id/recycler_view_container" - + "|FrameLayout:id/widgets_two_pane_sheet_recyclerview|WidgetsRecyclerView:id" - + "/primary_widgets_list_view|WidgetsListHeader:id/widgets_list_header", - DRAG_LAYER + "NexusOverviewActionsView:id/overview_actions_view" - )); - - // Per-AnalysisNode data that's specific to this detector. - private static class NodeData { - public boolean ignoreFlashes; - - // If ignoreNode is null, then this AnalysisNode node will be ignored if its parent is - // ignored. - // Otherwise, this AnalysisNode will be ignored if ignoreNode is a leaf i.e. has no - // children. - public IgnoreNode ignoreNode; - } - - private NodeData getNodeData(AnalysisNode info) { - return (NodeData) info.detectorsData[detectorOrdinal]; - } - - @Override - void initializeNode(AnalysisNode info) { - final NodeData nodeData = new NodeData(); - info.detectorsData[detectorOrdinal] = nodeData; - - // If the parent view ignores flashes, its descendants will too. - final boolean parentIgnoresFlashes = info.parent != null && getNodeData( - info.parent).ignoreFlashes; - if (parentIgnoresFlashes) { - nodeData.ignoreFlashes = true; - return; - } - - // Parent view doesn't ignore flashes. - // Initialize this AnalysisNode's ignore-node with the corresponding child of the - // ignore-node of the parent, if present. - final IgnoreNode parentIgnoreNode = info.parent != null - ? getNodeData(info.parent).ignoreNode - : IGNORED_NODES_ROOT; - nodeData.ignoreNode = parentIgnoreNode != null - ? parentIgnoreNode.children.get(info.nodeIdentity) : null; - // AnalysisNode will be ignored if the corresponding ignore-node is a leaf. - nodeData.ignoreFlashes = - nodeData.ignoreNode != null && nodeData.ignoreNode.children.isEmpty(); - } - - @Override - String detectAnomalies(AnalysisNode oldInfo, AnalysisNode newInfo, int frameN, - long frameTimeNs, int windowSizePx) { - // Should we check when a view was visible for a short period, then its alpha became 0? - // Then 'lastVisible' time should be the last one still visible? - // Check only transitions of alpha between 0 and 1? - - // If this is the first time ever when we see the view, there have been no flashes yet. - if (oldInfo == null) return null; - - // A flash requires a view to go from the full visibility to no-visibility and then back, - // or vice versa. - // If the last time the view was seen before the current frame, it didn't have full - // visibility; no flash can possibly be detected at the current frame. - if (oldInfo.alpha < 1) return null; - - final AnalysisNode latestInfo = newInfo != null ? newInfo : oldInfo; - final NodeData nodeData = getNodeData(latestInfo); - if (nodeData.ignoreFlashes) return null; - - // Once the view becomes invisible, see for how long it was visible prior to that. If it - // was visible only for a short interval of time, it's a flash. - if ( - // View is invisible in the current frame - newInfo == null - // When the view became visible last time, it was a transition from - // no-visibility to full visibility. - && oldInfo.timeBecameVisibleNs != -1) { - final long wasVisibleTimeMs = (frameTimeNs - oldInfo.timeBecameVisibleNs) / 1000000; - - if (wasVisibleTimeMs <= FLASH_DURATION_MS) { - nodeData.ignoreFlashes = true; // No need to report flashes in children. - return - String.format( - "View was visible for a too short period of time %dms, which is a" - + " flash", - wasVisibleTimeMs - ); - } - } - - // Once a view becomes visible, see for how long it was invisible prior to that. If it - // was invisible only for a short interval of time, it's a flash. - if ( - // The view is fully visible now - newInfo != null && newInfo.alpha >= 1 - // The view wasn't visible in the previous frame - && frameN != oldInfo.frameN + 1) { - // We can assert the below condition because at this point, we know that - // oldInfo.alpha >= 1, i.e. it disappeared abruptly. - assertTrue("oldInfo.timeBecameInvisibleNs must not be -1", - oldInfo.timeBecameInvisibleNs != -1); - - final long wasInvisibleTimeMs = (frameTimeNs - oldInfo.timeBecameInvisibleNs) / 1000000; - if (wasInvisibleTimeMs <= FLASH_DURATION_MS) { - nodeData.ignoreFlashes = true; // No need to report flashes in children. - return - String.format( - "View was invisible for a too short period of time %dms, which " - + "is a flash", - wasInvisibleTimeMs); - } - } - return null; - } -} diff --git a/tests/src/com/android/launcher3/util/viewcapture_analysis/PositionJumpDetector.java b/tests/src/com/android/launcher3/util/viewcapture_analysis/PositionJumpDetector.java deleted file mode 100644 index 88ace68d53..0000000000 --- a/tests/src/com/android/launcher3/util/viewcapture_analysis/PositionJumpDetector.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.launcher3.util.viewcapture_analysis; - -import com.android.launcher3.util.viewcapture_analysis.ViewCaptureAnalyzer.AnalysisNode; - -import java.util.List; - -/** - * Anomaly detector that triggers an error when a view position jumps. - */ -final class PositionJumpDetector extends AnomalyDetector { - // Maximum allowed jump in "milliwindows", i.e. a 1/1000's of the maximum of the window - // dimensions. - private static final float JUMP_MIW = 250; - - private static final String[] BORDER_NAMES = {"left", "top", "right", "bottom"}; - - // Commonly used parts of the paths to ignore. - private static final String CONTENT = "DecorView|LinearLayout|FrameLayout:id/content|"; - private static final String DRAG_LAYER = - CONTENT + "LauncherRootView:id/launcher|DragLayer:id/drag_layer|"; - private static final String RECENTS_DRAG_LAYER = - CONTENT + "LauncherRootView:id/launcher|RecentsDragLayer:id/drag_layer|"; - - private static final IgnoreNode IGNORED_NODES_ROOT = buildIgnoreNodesTree(List.of( - DRAG_LAYER + "SearchContainerView:id/apps_view", - DRAG_LAYER + "AppWidgetResizeFrame", - DRAG_LAYER + "LauncherAllAppsContainerView:id/apps_view", - CONTENT - + "SimpleDragLayer:id/add_item_drag_layer|AddItemWidgetsBottomSheet:id" - + "/add_item_bottom_sheet|LinearLayout:id/add_item_bottom_sheet_content", - DRAG_LAYER + "WidgetsTwoPaneSheet|SpringRelativeLayout:id/container", - DRAG_LAYER + "WidgetsFullSheet|SpringRelativeLayout:id/container", - DRAG_LAYER + "LauncherDragView", - RECENTS_DRAG_LAYER + "FallbackRecentsView:id/overview_panel", - CONTENT + "LauncherRootView:id/launcher|FloatingIconView", - DRAG_LAYER + "FloatingTaskView", - DRAG_LAYER + "LauncherRecentsView:id/overview_panel" - )); - - // Per-AnalysisNode data that's specific to this detector. - private static class NodeData { - public boolean ignoreJumps; - - // If ignoreNode is null, then this AnalysisNode node will be ignored if its parent is - // ignored. - // Otherwise, this AnalysisNode will be ignored if ignoreNode is a leaf i.e. has no - // children. - public IgnoreNode ignoreNode; - } - - private NodeData getNodeData(AnalysisNode info) { - return (NodeData) info.detectorsData[detectorOrdinal]; - } - - @Override - void initializeNode(AnalysisNode info) { - final NodeData nodeData = new NodeData(); - info.detectorsData[detectorOrdinal] = nodeData; - - // If the parent view ignores jumps, its descendants will too. - final boolean parentIgnoresJumps = info.parent != null && getNodeData( - info.parent).ignoreJumps; - if (parentIgnoresJumps) { - nodeData.ignoreJumps = true; - return; - } - - // Parent view doesn't ignore jumps. - // Initialize this AnalysisNode's ignore-node with the corresponding child of the - // ignore-node of the parent, if present. - final IgnoreNode parentIgnoreNode = info.parent != null - ? getNodeData(info.parent).ignoreNode - : IGNORED_NODES_ROOT; - nodeData.ignoreNode = parentIgnoreNode != null - ? parentIgnoreNode.children.get(info.nodeIdentity) : null; - // AnalysisNode will be ignored if the corresponding ignore-node is a leaf. - nodeData.ignoreJumps = - nodeData.ignoreNode != null && nodeData.ignoreNode.children.isEmpty(); - } - - @Override - String detectAnomalies(AnalysisNode oldInfo, AnalysisNode newInfo, int frameN, - long frameTimeNs, int windowSizePx) { - // If the view is not present in the current frame, there can't be a jump detected in the - // current frame. - if (newInfo == null) return null; - - // We only detect position jumps if the view was visible in the previous frame. - if (oldInfo == null || frameN != oldInfo.frameN + 1) return null; - - final NodeData newNodeData = getNodeData(newInfo); - if (newNodeData.ignoreJumps) return null; - - final float[] positionDiffs = { - newInfo.left - oldInfo.left, - newInfo.top - oldInfo.top, - newInfo.right - oldInfo.right, - newInfo.bottom - oldInfo.bottom - }; - - for (int i = 0; i < 4; ++i) { - final float positionDiffAbs = Math.abs(positionDiffs[i]); - if (positionDiffAbs * 1000 > JUMP_MIW * windowSizePx) { - newNodeData.ignoreJumps = true; - return String.format("Position jump: %s jumped by %s", - BORDER_NAMES[i], positionDiffAbs); - } - } - return null; - } -} diff --git a/tests/src/com/android/launcher3/util/viewcapture_analysis/ViewCaptureAnalyzer.java b/tests/src/com/android/launcher3/util/viewcapture_analysis/ViewCaptureAnalyzer.java deleted file mode 100644 index b27ccbfd2f..0000000000 --- a/tests/src/com/android/launcher3/util/viewcapture_analysis/ViewCaptureAnalyzer.java +++ /dev/null @@ -1,315 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.launcher3.util.viewcapture_analysis; - -import static android.view.View.VISIBLE; - -import com.android.app.viewcapture.data.ExportedData; -import com.android.app.viewcapture.data.FrameData; -import com.android.app.viewcapture.data.ViewNode; -import com.android.app.viewcapture.data.WindowData; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; - -/** - * Utility that analyzes ViewCapture data and finds anomalies such as views appearing or - * disappearing without alpha-fading. - */ -public class ViewCaptureAnalyzer { - private static final String SCRIM_VIEW_CLASS = "com.android.launcher3.views.ScrimView"; - - // All detectors. They will be invoked in the order listed here. - private static final AnomalyDetector[] ANOMALY_DETECTORS = { -// new AlphaJumpDetector(), // b/309014345 -// new FlashDetector(), // b/309014345 - new PositionJumpDetector() - }; - - static { - for (int i = 0; i < ANOMALY_DETECTORS.length; ++i) ANOMALY_DETECTORS[i].detectorOrdinal = i; - } - - // A view from view capture data converted to a form that's convenient for detecting anomalies. - static class AnalysisNode { - public String className; - public String resourceId; - public AnalysisNode parent; - - // Window coordinates of the view. - public float left; - public float top; - public float right; - public float bottom; - - // Visible scale and alpha, build recursively from the ancestor list. - public float scaleX; - public float scaleY; - public float alpha; // Always > 0 - - public int frameN; - - // Timestamp of the frame when this view became abruptly visible, i.e. its alpha became 1 - // the next frame after it was 0 or the view wasn't visible. - // If the view is currently invisible or the last appearance wasn't abrupt, the value is -1. - public long timeBecameVisibleNs; - - // Timestamp of the frame when this view became abruptly invisible last time, i.e. its - // alpha became 0, or view disappeared, after being 1 in the previous frame. - // If the view is currently visible or the last disappearance wasn't abrupt, the value is - // -1. - public long timeBecameInvisibleNs; - - public ViewNode viewCaptureNode; - - // Class name + resource id - public String nodeIdentity; - - // Collection of detector-specific data for this node. - public final Object[] detectorsData = new Object[ANOMALY_DETECTORS.length]; - - @Override - public String toString() { - return String.format("view window coordinates: (%s, %s, %s, %s)", - left, top, right, bottom); - } - } - - /** - * Scans a view capture record and searches for view animation anomalies. Can find anomalies for - * multiple views. - * Returns a map from the view path to the anomaly message for the view. Non-empty map means - * that anomalies were detected. - */ - public static Map<String, String> getAnomalies(ExportedData viewCaptureData) { - final Map<String, String> anomalies = new HashMap<>(); - - final int scrimClassIndex = viewCaptureData.getClassnameList().indexOf(SCRIM_VIEW_CLASS); - - final int windowDataCount = viewCaptureData.getWindowDataCount(); - for (int i = 0; i < windowDataCount; ++i) { - analyzeWindowData( - viewCaptureData, viewCaptureData.getWindowData(i), scrimClassIndex, anomalies); - } - return anomalies; - } - - private static void analyzeWindowData(ExportedData viewCaptureData, WindowData windowData, - int scrimClassIndex, Map<String, String> anomalies) { - // View hash code => Last seen node with this hash code. - // The view is added when we analyze the first frame where it's visible. - // After that, it gets updated for every frame where it's visible. - // As we go though frames, if a view becomes invisible, it stays in the map. - final Map<Integer, AnalysisNode> lastSeenNodes = new HashMap<>(); - - int windowWidthPx = -1; - int windowHeightPx = -1; - - for (int frameN = 0; frameN < windowData.getFrameDataCount(); ++frameN) { - final FrameData frame = windowData.getFrameData(frameN); - final ViewNode rootNode = frame.getNode(); - - // If the rotation or window size has changed, reset the analyzer state. - final boolean isFirstFrame = windowWidthPx != rootNode.getWidth() - || windowHeightPx != rootNode.getHeight(); - if (isFirstFrame) { - windowWidthPx = rootNode.getWidth(); - windowHeightPx = rootNode.getHeight(); - lastSeenNodes.clear(); - } - - final int windowSizePx = Math.max(rootNode.getWidth(), rootNode.getHeight()); - - analyzeFrame(frameN, isFirstFrame, frame, viewCaptureData, lastSeenNodes, - scrimClassIndex, anomalies, windowSizePx); - } - } - - private static void analyzeFrame(int frameN, boolean isFirstFrame, FrameData frame, - ExportedData viewCaptureData, - Map<Integer, AnalysisNode> lastSeenNodes, int scrimClassIndex, - Map<String, String> anomalies, int windowSizePx) { - // Analyze the node tree starting from the root. - long frameTimeNs = frame.getTimestamp(); - analyzeView( - frameTimeNs, - frame.getNode(), - /* parent = */ null, - frameN, - isFirstFrame, - /* leftShift = */ 0, - /* topShift = */ 0, - viewCaptureData, - lastSeenNodes, - scrimClassIndex, - anomalies, - windowSizePx); - - // Analyze transitions when a view visible in the previous frame became invisible in the - // current one. - for (AnalysisNode info : lastSeenNodes.values()) { - if (info.frameN == frameN - 1) { - if (!info.viewCaptureNode.getWillNotDraw()) { - Arrays.stream(ANOMALY_DETECTORS).forEach( - detector -> - detectAnomaly( - detector, - frameN, - /* oldInfo = */ info, - /* newInfo = */ null, - anomalies, - frameTimeNs, - windowSizePx) - ); - } - info.timeBecameInvisibleNs = info.alpha == 1 ? frameTimeNs : -1; - info.timeBecameVisibleNs = -1; - } - } - } - - private static void analyzeView(long frameTimeNs, ViewNode viewCaptureNode, AnalysisNode parent, - int frameN, - boolean isFirstFrame, float leftShift, float topShift, ExportedData viewCaptureData, - Map<Integer, AnalysisNode> lastSeenNodes, int scrimClassIndex, - Map<String, String> anomalies, int windowSizePx) { - // Skip analysis of invisible views - final float parentAlpha = parent != null ? parent.alpha : 1; - final float alpha = getVisibleAlpha(viewCaptureNode, parentAlpha); - if (alpha <= 0.0) return; - - // Calculate analysis node parameters - final int hashcode = viewCaptureNode.getHashcode(); - final int classIndex = viewCaptureNode.getClassnameIndex(); - - final float parentScaleX = parent != null ? parent.scaleX : 1; - final float parentScaleY = parent != null ? parent.scaleY : 1; - final float scaleX = parentScaleX * viewCaptureNode.getScaleX(); - final float scaleY = parentScaleY * viewCaptureNode.getScaleY(); - - final float left = leftShift - + (viewCaptureNode.getLeft() + viewCaptureNode.getTranslationX()) * parentScaleX - + viewCaptureNode.getWidth() * (parentScaleX - scaleX) / 2; - final float top = topShift - + (viewCaptureNode.getTop() + viewCaptureNode.getTranslationY()) * parentScaleY - + viewCaptureNode.getHeight() * (parentScaleY - scaleY) / 2; - final float width = viewCaptureNode.getWidth() * scaleX; - final float height = viewCaptureNode.getHeight() * scaleY; - - // Initialize new analysis node - final AnalysisNode newAnalysisNode = new AnalysisNode(); - newAnalysisNode.className = viewCaptureData.getClassname(classIndex); - newAnalysisNode.resourceId = viewCaptureNode.getId(); - newAnalysisNode.nodeIdentity = - getNodeIdentity(newAnalysisNode.className, newAnalysisNode.resourceId); - newAnalysisNode.parent = parent; - newAnalysisNode.left = left; - newAnalysisNode.top = top; - newAnalysisNode.right = left + width; - newAnalysisNode.bottom = top + height; - newAnalysisNode.scaleX = scaleX; - newAnalysisNode.scaleY = scaleY; - newAnalysisNode.alpha = alpha; - newAnalysisNode.frameN = frameN; - newAnalysisNode.timeBecameInvisibleNs = -1; - newAnalysisNode.viewCaptureNode = viewCaptureNode; - Arrays.stream(ANOMALY_DETECTORS).forEach( - detector -> detector.initializeNode(newAnalysisNode)); - - final AnalysisNode oldAnalysisNode = lastSeenNodes.get(hashcode); // may be null - - if (oldAnalysisNode != null && oldAnalysisNode.frameN + 1 == frameN) { - // If this view was present in the previous frame, keep the time when it became visible. - newAnalysisNode.timeBecameVisibleNs = oldAnalysisNode.timeBecameVisibleNs; - } else { - // If the view is becoming visible after being invisible, initialize the time when it - // became visible with a new value. - // If the view became visible abruptly, i.e. alpha jumped from 0 to 1 between the - // previous and the current frames, then initialize with the time of the current - // frame. Otherwise, use -1. - newAnalysisNode.timeBecameVisibleNs = newAnalysisNode.alpha >= 1 ? frameTimeNs : -1; - } - - // Detect anomalies for the view. - if (!isFirstFrame && !viewCaptureNode.getWillNotDraw()) { - Arrays.stream(ANOMALY_DETECTORS).forEach( - detector -> - detectAnomaly(detector, frameN, oldAnalysisNode, newAnalysisNode, - anomalies, frameTimeNs, windowSizePx) - ); - } - lastSeenNodes.put(hashcode, newAnalysisNode); - - // Enumerate children starting from the topmost one. Stop at ScrimView, if present. - final float leftShiftForChildren = left - viewCaptureNode.getScrollX(); - final float topShiftForChildren = top - viewCaptureNode.getScrollY(); - for (int i = viewCaptureNode.getChildrenCount() - 1; i >= 0; --i) { - final ViewNode child = viewCaptureNode.getChildren(i); - - // Don't analyze anything under scrim view because we don't know whether it's - // transparent. - if (child.getClassnameIndex() == scrimClassIndex) break; - - analyzeView(frameTimeNs, child, newAnalysisNode, frameN, isFirstFrame, - leftShiftForChildren, - topShiftForChildren, - viewCaptureData, lastSeenNodes, scrimClassIndex, anomalies, windowSizePx); - } - } - - private static void detectAnomaly(AnomalyDetector detector, int frameN, - AnalysisNode oldAnalysisNode, AnalysisNode newAnalysisNode, - Map<String, String> anomalies, long frameTimeNs, int windowSizePx) { - final String maybeAnomaly = - detector.detectAnomalies(oldAnalysisNode, newAnalysisNode, frameN, frameTimeNs, - windowSizePx); - if (maybeAnomaly != null) { - AnalysisNode latestInfo = newAnalysisNode != null ? newAnalysisNode : oldAnalysisNode; - final String viewDiagPath = diagPathFromRoot(latestInfo); - if (!anomalies.containsKey(viewDiagPath)) { - anomalies.put(viewDiagPath, String.format("%s, %s", maybeAnomaly, latestInfo)); - } - } - } - - private static float getVisibleAlpha(ViewNode node, float parenVisibleAlpha) { - return node.getVisibility() == VISIBLE - ? parenVisibleAlpha * Math.max(0, Math.min(node.getAlpha(), 1)) - : 0f; - } - - private static String classNameToSimpleName(String className) { - return className.substring(className.lastIndexOf(".") + 1); - } - - private static String diagPathFromRoot(AnalysisNode analysisNode) { - final StringBuilder path = new StringBuilder(analysisNode.nodeIdentity); - for (AnalysisNode ancestor = analysisNode.parent; - ancestor != null; - ancestor = ancestor.parent) { - path.insert(0, ancestor.nodeIdentity + "|"); - } - return path.toString(); - } - - private static String getNodeIdentity(String className, String resourceId) { - final StringBuilder sb = new StringBuilder(); - sb.append(classNameToSimpleName(className)); - if (!"NO_ID".equals(resourceId)) sb.append(":" + resourceId); - return sb.toString(); - } -} diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenuItem.java b/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenuItem.java deleted file mode 100644 index e3035bf71b..0000000000 --- a/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenuItem.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.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/wmshell/shared/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.aidl b/wmshell/shared/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.aidl deleted file mode 100644 index c968e809bf..0000000000 --- a/wmshell/shared/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.aidl +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.common.desktopmode; - -parcelable DesktopModeTransitionSource; \ No newline at end of file diff --git a/wmshell/shared/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.kt b/wmshell/shared/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.kt deleted file mode 100644 index dbbf178613..0000000000 --- a/wmshell/shared/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.kt +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.common.desktopmode - -import android.os.Parcel -import android.os.Parcelable - -/** Transition source types for Desktop Mode. */ -enum class DesktopModeTransitionSource : Parcelable { - /** Transitions that originated as a consequence of task dragging. */ - TASK_DRAG, - /** Transitions that originated from an app from Overview. */ - APP_FROM_OVERVIEW, - /** Transitions that originated from app handle menu button */ - APP_HANDLE_MENU_BUTTON, - /** Transitions that originated as a result of keyboard shortcuts. */ - KEYBOARD_SHORTCUT, - /** Transitions with source unknown. */ - UNKNOWN; - - override fun describeContents(): Int { - return 0 - } - - override fun writeToParcel(dest: Parcel, flags: Int) { - dest.writeString(name) - } - - companion object { - @JvmField - val CREATOR = - object : Parcelable.Creator<DesktopModeTransitionSource> { - override fun createFromParcel(parcel: Parcel): DesktopModeTransitionSource { - return parcel.readString()?.let { valueOf(it) } ?: UNKNOWN - } - - override fun newArray(size: Int) = arrayOfNulls<DesktopModeTransitionSource>(size) - } - } -} diff --git a/wmshell/shared/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt b/wmshell/shared/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt deleted file mode 100644 index 123d4dc491..0000000000 --- a/wmshell/shared/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt +++ /dev/null @@ -1,701 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.wm.shell.common.magnetictarget - -import android.annotation.SuppressLint -import android.content.Context -import android.graphics.PointF -import android.os.VibrationAttributes -import android.os.VibrationEffect -import android.os.Vibrator -import android.view.MotionEvent -import android.view.VelocityTracker -import android.view.View -import android.view.ViewConfiguration -import androidx.dynamicanimation.animation.DynamicAnimation -import androidx.dynamicanimation.animation.FloatPropertyCompat -import androidx.dynamicanimation.animation.SpringForce -import com.android.wm.shell.shared.animation.PhysicsAnimator -import kotlin.math.abs -import kotlin.math.hypot - -/** - * Utility class for creating 'magnetized' objects that are attracted to one or more magnetic - * targets. Magnetic targets attract objects that are dragged near them, and hold them there unless - * they're moved away or released. Releasing objects inside a magnetic target typically performs an - * action on the object. - * - * MagnetizedObject also supports flinging to targets, which will result in the object being pulled - * into the target and released as if it was dragged into it. - * - * To use this class, either construct an instance with an object of arbitrary type, or use the - * [MagnetizedObject.magnetizeView] shortcut method if you're magnetizing a view. Then, set - * [magnetListener] to receive event callbacks. In your touch handler, pass all MotionEvents - * that move this object to [maybeConsumeMotionEvent]. If that method returns true, consider the - * event consumed by the MagnetizedObject and don't move the object unless it begins returning false - * again. - * - * @param context Context, used to retrieve a Vibrator instance for vibration effects. - * @param underlyingObject The actual object that we're magnetizing. - * @param xProperty Property that sets the x value of the object's position. - * @param yProperty Property that sets the y value of the object's position. - */ -abstract class MagnetizedObject<T : Any>( - val context: Context, - - /** The actual object that is animated. */ - val underlyingObject: T, - - /** Property that gets/sets the object's X value. */ - val xProperty: FloatPropertyCompat<in T>, - - /** Property that gets/sets the object's Y value. */ - val yProperty: FloatPropertyCompat<in T> -) { - - /** Return the width of the object. */ - abstract fun getWidth(underlyingObject: T): Float - - /** Return the height of the object. */ - abstract fun getHeight(underlyingObject: T): Float - - /** - * Fill the provided array with the location of the top-left of the object, relative to the - * entire screen. Compare to [View.getLocationOnScreen]. - */ - abstract fun getLocationOnScreen(underlyingObject: T, loc: IntArray) - - /** Methods for listening to events involving a magnetized object. */ - interface MagnetListener { - - /** - * Called when touch events move within the magnetic field of a target, causing the - * object to animate to the target and become 'stuck' there. The animation happens - * automatically here - you should not move the object. You can, however, change its state - * to indicate to the user that it's inside the target and releasing it will have an effect. - * - * [maybeConsumeMotionEvent] is now returning true and will continue to do so until a call - * to [onUnstuckFromTarget] or [onReleasedInTarget]. - * - * @param target The target that the object is now stuck to. - * @param draggedObject The object that is stuck to the target. - */ - fun onStuckToTarget(target: MagneticTarget, draggedObject: MagnetizedObject<*>) - - /** - * Called when the object is no longer stuck to a target. This means that either touch - * events moved outside of the magnetic field radius, or that a forceful fling out of the - * target was detected. - * - * The object won't be automatically animated out of the target, since you're responsible - * for moving the object again. You should move it (or animate it) using your own - * movement/animation logic. - * - * Reverse any effects applied in [onStuckToTarget] here. - * - * If [wasFlungOut] is true, [maybeConsumeMotionEvent] returned true for the ACTION_UP event - * that concluded the fling. If [wasFlungOut] is false, that means a drag gesture is ongoing - * and [maybeConsumeMotionEvent] is now returning false. - * - * @param target The target that this object was just unstuck from. - * @param draggedObject The object being unstuck from the target. - * @param velX The X velocity of the touch gesture when it exited the magnetic field. - * @param velY The Y velocity of the touch gesture when it exited the magnetic field. - * @param wasFlungOut Whether the object was unstuck via a fling gesture. This means that - * an ACTION_UP event was received, and that the gesture velocity was sufficient to conclude - * that the user wants to un-stick the object despite no touch events occurring outside of - * the magnetic field radius. - */ - fun onUnstuckFromTarget( - target: MagneticTarget, - draggedObject: MagnetizedObject<*>, - velX: Float, - velY: Float, - wasFlungOut: Boolean - ) - - /** - * Called when the object is released inside a target, or flung towards it with enough - * velocity to reach it. - * - * @param target The target that the object was released in. - * @param draggedObject The object released in the target. - */ - fun onReleasedInTarget(target: MagneticTarget, draggedObject: MagnetizedObject<*>) - } - - private val animator: PhysicsAnimator<T> = PhysicsAnimator.getInstance(underlyingObject) - private val objectLocationOnScreen = IntArray(2) - - /** - * Targets that have been added to this object. These will all be considered when determining - * magnetic fields and fling trajectories. - */ - private val associatedTargets = ArrayList<MagneticTarget>() - - private val velocityTracker: VelocityTracker = VelocityTracker.obtain() - private val vibrator: Vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator - private val vibrationAttributes: VibrationAttributes = VibrationAttributes.createForUsage( - VibrationAttributes.USAGE_TOUCH) - - private var touchDown = PointF() - private var touchSlop = 0 - private var movedBeyondSlop = false - - /** Whether touch events are presently occurring within the magnetic field area of a target. */ - val objectStuckToTarget: Boolean - get() = targetObjectIsStuckTo != null - - /** The target the object is stuck to, or null if the object is not stuck to any target. */ - private var targetObjectIsStuckTo: MagneticTarget? = null - - /** - * Sets the listener to receive events. This must be set, or [maybeConsumeMotionEvent] - * will always return false and no magnetic effects will occur. - */ - lateinit var magnetListener: MagnetizedObject.MagnetListener - - /** - * Optional update listener to provide to the PhysicsAnimator that is used to spring the object - * into the target. - */ - var physicsAnimatorUpdateListener: PhysicsAnimator.UpdateListener<T>? = null - - /** - * Optional end listener to provide to the PhysicsAnimator that is used to spring the object - * into the target. - */ - var physicsAnimatorEndListener: PhysicsAnimator.EndListener<T>? = null - - /** - * Method that is called when the object should be animated stuck to the target. The default - * implementation uses the object's x and y properties to animate the object centered inside the - * target. You can override this if you need custom animation. - * - * The method is invoked with the MagneticTarget that the object is sticking to, the X and Y - * velocities of the gesture that brought the object into the magnetic radius, whether or not it - * was flung, and a callback you must call after your animation completes. - */ - var animateStuckToTarget: (MagneticTarget, Float, Float, Boolean, (() -> Unit)?) -> Unit = - ::animateStuckToTargetInternal - - /** - * Sets whether forcefully flinging the object vertically towards a target causes it to be - * attracted to the target and then released immediately, despite never being dragged within the - * magnetic field. - */ - var flingToTargetEnabled = true - - /** - * If fling to target is enabled, forcefully flinging the object towards a target will cause - * it to be attracted to the target and then released immediately, despite never being dragged - * within the magnetic field. - * - * This sets the width of the area considered 'near' enough a target to be considered a fling, - * in terms of percent of the target view's width. For example, setting this to 3f means that - * flings towards a 100px-wide target will be considered 'near' enough if they're towards the - * 300px-wide area around the target. - * - * Flings whose trajectory intersects the area will be attracted and released - even if the - * target view itself isn't intersected: - * - * | | - * | 0 | - * | / | - * | / | - * | X / | - * |.....###.....| - * - * - * Flings towards the target whose trajectories do not intersect the area will be treated as - * normal flings and the magnet will leave the object alone: - * - * | | - * | | - * | 0 | - * | / | - * | / X | - * |.....###.....| - * - */ - var flingToTargetWidthPercent = 3f - - /** - * Sets the minimum velocity (in pixels per second) required to fling an object to the target - * without dragging it into the magnetic field. - */ - var flingToTargetMinVelocity = 4000f - - /** - * Sets the minimum velocity (in pixels per second) required to fling un-stuck an object stuck - * to the target. If this velocity is reached, the object will be freed even if it wasn't moved - * outside the magnetic field radius. - */ - var flingUnstuckFromTargetMinVelocity = 4000f - - /** - * Sets the maximum X velocity above which the object will not stick to the target. Even if the - * object is dragged through the magnetic field, it will not stick to the target until the - * horizontal velocity is below this value. - */ - var stickToTargetMaxXVelocity = 2000f - - /** - * Enable or disable haptic vibration effects when the object interacts with the magnetic field. - * - * If you're experiencing crashes when the object enters targets, ensure that you have the - * android.permission.VIBRATE permission! - */ - var hapticsEnabled = true - - /** Default spring configuration to use for animating the object into a target. */ - var springConfig = PhysicsAnimator.SpringConfig( - SpringForce.STIFFNESS_MEDIUM, SpringForce.DAMPING_RATIO_NO_BOUNCY) - - /** - * Spring configuration to use to spring the object into a target specifically when it's flung - * towards (rather than dragged near) it. - */ - var flungIntoTargetSpringConfig = springConfig - - /** - * Adds the provided MagneticTarget to this object. The object will now be attracted to the - * target if it strays within its magnetic field or is flung towards it. - * - * If this target (or its magnetic field) overlaps another target added to this object, the - * prior target will take priority. - */ - fun addTarget(target: MagneticTarget) { - associatedTargets.add(target) - target.updateLocationOnScreen() - } - - /** - * Shortcut that accepts a View and a magnetic field radius and adds it as a magnetic target. - * - * @return The MagneticTarget instance for the given View. This can be used to change the - * target's magnetic field radius after it's been added. It can also be added to other - * magnetized objects. - */ - fun addTarget(target: View, magneticFieldRadiusPx: Int): MagneticTarget { - return MagneticTarget(target, magneticFieldRadiusPx).also { addTarget(it) } - } - - /** - * Removes the given target from this object. The target will no longer attract the object. - */ - fun removeTarget(target: MagneticTarget) { - associatedTargets.remove(target) - } - - /** - * Removes all associated targets from this object. - */ - fun clearAllTargets() { - associatedTargets.clear() - } - - /** - * Provide this method with all motion events that move the magnetized object. If the - * location of the motion events moves within the magnetic field of a target, or indicate a - * fling-to-target gesture, this method will return true and you should not move the object - * yourself until it returns false again. - * - * Note that even when this method returns true, you should continue to pass along new motion - * events so that we know when the events move back outside the magnetic field area. - * - * This method will always return false if you haven't set a [magnetListener]. - */ - fun maybeConsumeMotionEvent(ev: MotionEvent): Boolean { - // Short-circuit if we don't have a listener or any targets, since those are required. - if (associatedTargets.size == 0) { - return false - } - - // When a gesture begins, recalculate target views' positions on the screen in case they - // have changed. Also, clear state. - if (ev.action == MotionEvent.ACTION_DOWN) { - updateTargetViews() - - // Clear the velocity tracker and stuck target. - velocityTracker.clear() - targetObjectIsStuckTo = null - - // Set the touch down coordinates and reset movedBeyondSlop. - touchDown.set(ev.rawX, ev.rawY) - movedBeyondSlop = false - } - - // Always pass events to the VelocityTracker. - addMovement(ev) - - // If we haven't yet moved beyond the slop distance, check if we have. - if (!movedBeyondSlop) { - val dragDistance = hypot(ev.rawX - touchDown.x, ev.rawY - touchDown.y) - if (dragDistance > touchSlop) { - // If we're beyond the slop distance, save that and continue. - movedBeyondSlop = true - } else { - // Otherwise, don't do anything yet. - return false - } - } - - val targetObjectIsInMagneticFieldOf = associatedTargets.firstOrNull { target -> - val distanceFromTargetCenter = hypot( - ev.rawX - target.centerOnDisplayX(), - ev.rawY - target.centerOnDisplayY()) - distanceFromTargetCenter < target.magneticFieldRadiusPx - } - - // If we aren't currently stuck to a target, and we're in the magnetic field of a target, - // we're newly stuck. - val objectNewlyStuckToTarget = - !objectStuckToTarget && targetObjectIsInMagneticFieldOf != null - - // If we are currently stuck to a target, we're in the magnetic field of a target, and that - // target isn't the one we're currently stuck to, then touch events have moved into a - // adjacent target's magnetic field. - val objectMovedIntoDifferentTarget = - objectStuckToTarget && - targetObjectIsInMagneticFieldOf != null && - targetObjectIsStuckTo != targetObjectIsInMagneticFieldOf - - if (objectNewlyStuckToTarget || objectMovedIntoDifferentTarget) { - velocityTracker.computeCurrentVelocity(1000) - val velX = velocityTracker.xVelocity - val velY = velocityTracker.yVelocity - - // If the object is moving too quickly within the magnetic field, do not stick it. This - // only applies to objects newly stuck to a target. If the object is moved into a new - // target, it wasn't moving at all (since it was stuck to the previous one). - if (objectNewlyStuckToTarget && abs(velX) > stickToTargetMaxXVelocity) { - return false - } - - // This touch event is newly within the magnetic field - let the listener know, and - // animate sticking to the magnet. - targetObjectIsStuckTo = targetObjectIsInMagneticFieldOf - cancelAnimations() - magnetListener.onStuckToTarget(targetObjectIsInMagneticFieldOf!!, this) - animateStuckToTarget(targetObjectIsInMagneticFieldOf, velX, velY, false, null) - - vibrateIfEnabled(VibrationEffect.EFFECT_HEAVY_CLICK) - } else if (targetObjectIsInMagneticFieldOf == null && objectStuckToTarget) { - velocityTracker.computeCurrentVelocity(1000) - - // This touch event is newly outside the magnetic field - let the listener know. It will - // move the object out of the target using its own movement logic. - cancelAnimations() - magnetListener.onUnstuckFromTarget( - targetObjectIsStuckTo!!, this, - velocityTracker.xVelocity, velocityTracker.yVelocity, - wasFlungOut = false) - targetObjectIsStuckTo = null - - vibrateIfEnabled(VibrationEffect.EFFECT_TICK) - } - - // First, check for relevant gestures concluding with an ACTION_UP. - if (ev.action == MotionEvent.ACTION_UP) { - velocityTracker.computeCurrentVelocity(1000 /* units */) - val velX = velocityTracker.xVelocity - val velY = velocityTracker.yVelocity - - // Cancel the magnetic animation since we might still be springing into the magnetic - // target, but we're about to fling away or release. - cancelAnimations() - - if (objectStuckToTarget) { - if (-velY > flingUnstuckFromTargetMinVelocity) { - // If the object is stuck, but it was forcefully flung away from the target in - // the upward direction, tell the listener so the object can be animated out of - // the target. - magnetListener.onUnstuckFromTarget( - targetObjectIsStuckTo!!, this, - velX, velY, wasFlungOut = true) - } else { - // If the object is stuck and not flung away, it was released inside the target. - magnetListener.onReleasedInTarget(targetObjectIsStuckTo!!, this) - vibrateIfEnabled(VibrationEffect.EFFECT_HEAVY_CLICK) - } - - // Either way, we're no longer stuck. - targetObjectIsStuckTo = null - return true - } - - // The target we're flinging towards, or null if we're not flinging towards any target. - val flungToTarget = associatedTargets.firstOrNull { target -> - isForcefulFlingTowardsTarget(target, ev.rawX, ev.rawY, velX, velY) - } - - if (flungToTarget != null) { - // If this is a fling-to-target, animate the object to the magnet and then release - // it. - magnetListener.onStuckToTarget(flungToTarget, this) - targetObjectIsStuckTo = flungToTarget - - animateStuckToTarget(flungToTarget, velX, velY, true) { - magnetListener.onReleasedInTarget(flungToTarget, this) - targetObjectIsStuckTo = null - vibrateIfEnabled(VibrationEffect.EFFECT_HEAVY_CLICK) - } - - return true - } - - // If it's not either of those things, we are not interested. - return false - } - - return objectStuckToTarget // Always consume touch events if the object is stuck. - } - - /** Plays the given vibration effect if haptics are enabled. */ - @SuppressLint("MissingPermission") - private fun vibrateIfEnabled(effectId: Int) { - if (hapticsEnabled) { - vibrator.vibrate(VibrationEffect.createPredefined(effectId), vibrationAttributes) - } - } - - /** Adds the movement to the velocity tracker using raw coordinates. */ - private fun addMovement(event: MotionEvent) { - // Add movement to velocity tracker using raw screen X and Y coordinates instead - // of window coordinates because the window frame may be moving at the same time. - val deltaX = event.rawX - event.x - val deltaY = event.rawY - event.y - event.offsetLocation(deltaX, deltaY) - velocityTracker.addMovement(event) - event.offsetLocation(-deltaX, -deltaY) - } - - /** Animates sticking the object to the provided target with the given start velocities. */ - private fun animateStuckToTargetInternal( - target: MagneticTarget, - velX: Float, - velY: Float, - flung: Boolean, - after: (() -> Unit)? = null - ) { - target.updateLocationOnScreen() - getLocationOnScreen(underlyingObject, objectLocationOnScreen) - - // Calculate the difference between the target's center coordinates and the object's. - // Animating the object's x/y properties by these values will center the object on top - // of the magnetic target. - val xDiff = target.centerOnScreen.x - - getWidth(underlyingObject) / 2f - objectLocationOnScreen[0] - val yDiff = target.centerOnScreen.y - - getHeight(underlyingObject) / 2f - objectLocationOnScreen[1] - - val springConfig = if (flung) flungIntoTargetSpringConfig else springConfig - - cancelAnimations() - - // Animate to the center of the target. - animator - .spring(xProperty, xProperty.getValue(underlyingObject) + xDiff, velX, - springConfig) - .spring(yProperty, yProperty.getValue(underlyingObject) + yDiff, velY, - springConfig) - - if (physicsAnimatorUpdateListener != null) { - animator.addUpdateListener(physicsAnimatorUpdateListener!!) - } - - if (physicsAnimatorEndListener != null) { - animator.addEndListener(physicsAnimatorEndListener!!) - } - - if (after != null) { - animator.withEndActions(after) - } - - animator.start() - } - - /** - * Whether or not the provided values match a 'fast fling' towards the provided target. If it - * does, we consider it a fling-to-target gesture. - */ - private fun isForcefulFlingTowardsTarget( - target: MagneticTarget, - rawX: Float, - rawY: Float, - velX: Float, - velY: Float - ): Boolean { - if (!flingToTargetEnabled) { - return false - } - - // Whether velocity is sufficient, depending on whether we're flinging into a target at the - // top or the bottom of the screen. - val velocitySufficient = - if (rawY < target.centerOnDisplayY()) velY > flingToTargetMinVelocity - else velY < flingToTargetMinVelocity - - if (!velocitySufficient) { - return false - } - - // Whether the trajectory of the fling intersects the target area. - var targetCenterXIntercept = rawX - - // Only do math if the X velocity is non-zero, otherwise X won't change. - if (velX != 0f) { - // Rise over run... - val slope = velY / velX - // ...y = mx + b, b = y / mx... - val yIntercept = rawY - slope * rawX - - // ...calculate the x value when y = the target's y-coordinate. - targetCenterXIntercept = (target.centerOnDisplayY() - yIntercept) / slope - } - - // The width of the area we're looking for a fling towards. - val targetAreaWidth = target.targetView.width * flingToTargetWidthPercent - - // Velocity was sufficient, so return true if the intercept is within the target area. - return targetCenterXIntercept > target.centerOnDisplayX() - targetAreaWidth / 2 && - targetCenterXIntercept < target.centerOnDisplayX() + targetAreaWidth / 2 - } - - /** Cancel animations on this object's x/y properties. */ - internal fun cancelAnimations() { - animator.cancel(xProperty, yProperty) - } - - /** Updates the locations on screen of all of the [associatedTargets]. */ - internal fun updateTargetViews() { - associatedTargets.forEach { it.updateLocationOnScreen() } - - // Update the touch slop, since the configuration may have changed. - if (associatedTargets.size > 0) { - touchSlop = - ViewConfiguration.get(associatedTargets[0].targetView.context).scaledTouchSlop - } - } - - /** - * Represents a target view with a magnetic field radius and cached center-on-screen - * coordinates. - * - * Instances of MagneticTarget are passed to a MagnetizedObject's [addTarget], and can then - * attract the object if it's dragged near or flung towards it. MagneticTargets can be added to - * multiple objects. - */ - class MagneticTarget( - val targetView: View, - var magneticFieldRadiusPx: Int - ) { - val centerOnScreen = PointF() - - /** - * Set screen vertical offset amount. - * - * Screen surface may be vertically shifted in some cases, for example when one-handed mode - * is enabled. [MagneticTarget] and [MagnetizedObject] set their location in screen - * coordinates (see [MagneticTarget.centerOnScreen] and - * [MagnetizedObject.getLocationOnScreen] respectively). - * - * When a [MagnetizedObject] is dragged, the touch location is determined by - * [MotionEvent.getRawX] and [MotionEvent.getRawY]. These work in display coordinates. When - * screen is shifted due to one-handed mode, display coordinates and screen coordinates do - * not match. To determine if a [MagnetizedObject] is dragged into a [MagneticTarget], view - * location on screen is translated to display coordinates using this offset value. - */ - var screenVerticalOffset: Int = 0 - - private val tempLoc = IntArray(2) - - fun updateLocationOnScreen() { - targetView.post { - targetView.getLocationOnScreen(tempLoc) - - // Add half of the target size to get the center, and subtract translation since the - // target could be animating in while we're doing this calculation. - centerOnScreen.set( - tempLoc[0] + targetView.width / 2f - targetView.translationX, - tempLoc[1] + targetView.height / 2f - targetView.translationY) - } - } - - /** - * Get target center coordinate on x-axis on display. [centerOnScreen] has to be up to date - * by calling [updateLocationOnScreen] first. - */ - fun centerOnDisplayX(): Float { - return centerOnScreen.x - } - - /** - * Get target center coordinate on y-axis on display. [centerOnScreen] has to be up to date - * by calling [updateLocationOnScreen] first. Use [screenVerticalOffset] to update the - * screen offset compared to the display. - */ - fun centerOnDisplayY(): Float { - return centerOnScreen.y + screenVerticalOffset - } - } - - companion object { - /** - * Magnetizes the given view. Magnetized views are attracted to one or more magnetic - * targets. Magnetic targets attract objects that are dragged near them, and hold them there - * unless they're moved away or released. Releasing objects inside a magnetic target - * typically performs an action on the object. - * - * Magnetized views can also be flung to targets, which will result in the view being pulled - * into the target and released as if it was dragged into it. - * - * To use the returned MagnetizedObject<View> instance, first set [magnetListener] to - * receive event callbacks. In your touch handler, pass all MotionEvents that move this view - * to [maybeConsumeMotionEvent]. If that method returns true, consider the event consumed by - * MagnetizedObject and don't move the view unless it begins returning false again. - * - * The view will be moved via translationX/Y properties, and its - * width/height will be determined via getWidth()/getHeight(). If you are animating - * something other than a view, or want to position your view using properties other than - * translationX/Y, implement an instance of [MagnetizedObject]. - * - * Note that the magnetic library can't re-order your view automatically. If the view - * renders on top of the target views, it will obscure the target when it sticks to it. - * You'll want to bring the view to the front in [MagnetListener.onStuckToTarget]. - */ - @JvmStatic - fun <T : View> magnetizeView(view: T): MagnetizedObject<T> { - return object : MagnetizedObject<T>( - view.context, - view, - DynamicAnimation.TRANSLATION_X, - DynamicAnimation.TRANSLATION_Y) { - override fun getWidth(underlyingObject: T): Float { - return underlyingObject.width.toFloat() - } - - override fun getHeight(underlyingObject: T): Float { - return underlyingObject.height.toFloat() } - - override fun getLocationOnScreen(underlyingObject: T, loc: IntArray) { - underlyingObject.getLocationOnScreen(loc) - } - } - } - } -} \ No newline at end of file diff --git a/wmshell/shared/src/com/android/wm/shell/shared/IHomeTransitionListener.aidl b/wmshell/shared/src/com/android/wm/shell/shared/IHomeTransitionListener.aidl deleted file mode 100644 index 8481c446c6..0000000000 --- a/wmshell/shared/src/com/android/wm/shell/shared/IHomeTransitionListener.aidl +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.shared; - -import android.window.RemoteTransition; -import android.window.TransitionFilter; - -/** - * Listener interface that Launcher attaches to SystemUI to get home activity transition callbacks - * on the default display. - */ -oneway interface IHomeTransitionListener { - - /** - * Called when a transition changes the visibility of the home activity on the default display. - */ - void onHomeVisibilityChanged(in boolean isVisible); -} - diff --git a/wmshell/shared/src/com/android/wm/shell/shared/IShellTransitions.aidl b/wmshell/shared/src/com/android/wm/shell/shared/IShellTransitions.aidl deleted file mode 100644 index 3256abf091..0000000000 --- a/wmshell/shared/src/com/android/wm/shell/shared/IShellTransitions.aidl +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.shared; - -import android.view.SurfaceControl; -import android.window.RemoteTransition; -import android.window.TransitionFilter; - -import com.android.wm.shell.shared.IHomeTransitionListener; - -/** - * Interface that is exposed to remote callers to manipulate the transitions feature. - */ -interface IShellTransitions { - - /** - * Registers a remote transition handler for all operations excluding takeovers (see - * registerRemoteForTakeover()). - */ - oneway void registerRemote(in TransitionFilter filter, - in RemoteTransition remoteTransition) = 1; - - /** - * Unregisters a remote transition handler for all operations. - */ - oneway void unregisterRemote(in RemoteTransition remoteTransition) = 2; - - /** - * Retrieves the apply-token used by transactions in Shell - */ - IBinder getShellApplyToken() = 3; - - /** - * Set listener that will receive callbacks about transitions involving home activity. - */ - oneway void setHomeTransitionListener(in IHomeTransitionListener listener) = 4; - - /** - * Returns a container surface for the home root task. - */ - SurfaceControl getHomeTaskOverlayContainer() = 5; - - /** - * Registers a remote transition for takeover operations only. - */ - oneway void registerRemoteForTakeover(in TransitionFilter filter, - in RemoteTransition remoteTransition) = 6; -}