From 6301570222d07a0381428e067aeb1db1e96a3ea8 Mon Sep 17 00:00:00 2001 From: Jon Miranda Date: Thu, 29 Aug 2019 11:41:53 -0700 Subject: [PATCH 01/12] [DO NOT MERGE] Fix NumberFormatException for launcher. Fix is already in ub-launcher3-master: I5094b22ddc77c45590cea1a5f5dead0dc7580abf Bug: 140076379 Change-Id: I01948cf71ef2e058dc1ef8c506f174856ee09e0d (cherry picked from commit d49fe3130ed2460022ba98b376543e1a7aa30605) (cherry picked from commit 241cd902035d05d9d7300c2f63b4f01af833d275) --- src/com/android/launcher3/Utilities.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java index 3bef5986d7..ba122f9441 100644 --- a/src/com/android/launcher3/Utilities.java +++ b/src/com/android/launcher3/Utilities.java @@ -732,7 +732,7 @@ public final class Utilities { int[] array = new int[tokenizer.countTokens()]; int count = 0; while (tokenizer.hasMoreTokens()) { - array[count] = Integer.parseInt(tokenizer.nextToken()); + array[count] = Integer.parseInt(tokenizer.nextToken().trim()); count++; } return array; From b56ecb154e5d3219bbe43eb7eae5cb16862da9eb Mon Sep 17 00:00:00 2001 From: Jon Miranda Date: Thu, 29 Aug 2019 11:41:53 -0700 Subject: [PATCH 02/12] [DO NOT MERGE] Fix NumberFormatException for launcher. Fix is already in ub-launcher3-master: I5094b22ddc77c45590cea1a5f5dead0dc7580abf Bug: 140076379 Change-Id: I01948cf71ef2e058dc1ef8c506f174856ee09e0d (cherry picked from commit d49fe3130ed2460022ba98b376543e1a7aa30605) (cherry picked from commit 241cd902035d05d9d7300c2f63b4f01af833d275) --- src/com/android/launcher3/Utilities.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java index 3bef5986d7..ba122f9441 100644 --- a/src/com/android/launcher3/Utilities.java +++ b/src/com/android/launcher3/Utilities.java @@ -732,7 +732,7 @@ public final class Utilities { int[] array = new int[tokenizer.countTokens()]; int count = 0; while (tokenizer.hasMoreTokens()) { - array[count] = Integer.parseInt(tokenizer.nextToken()); + array[count] = Integer.parseInt(tokenizer.nextToken().trim()); count++; } return array; From d1e003be6904a2a7d5701bb7fa0a892922fa0a6d Mon Sep 17 00:00:00 2001 From: Winson Chung Date: Fri, 25 Oct 2019 10:27:44 -0700 Subject: [PATCH 03/12] Skip handling input while the user is still locked - None of the gestures work when the user is locked so we can just skip them. Bug: 143107728 Test: Manual Change-Id: I2661d58269f7eccbfcd2214792c128b432e0347c (cherry picked from commit fc8c975880f2023ff3d0cc5caf284c704a6a37f9) --- .../src/com/android/quickstep/TouchInteractionService.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java index 0eafb44a53..5591e40b8d 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java @@ -416,6 +416,9 @@ public class TouchInteractionService extends Service implements Log.e(TAG, "Unknown event " + ev); return; } + if (!mDeviceState.isUserUnlocked()) { + return; + } Object traceToken = TraceHelper.INSTANCE.beginFlagsOverride( TraceHelper.FLAG_ALLOW_BINDER_TRACKING); From 504b0ba055dc521083523b603f51abef3a88c411 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Mon, 11 Nov 2019 11:08:05 -0800 Subject: [PATCH 04/12] Preventing dead lock in layout inflation Bug: 143353100 Change-Id: I52d0794aad26c1d1de8cb373f3398f626c15b4af (cherry picked from commit d1a67d0d7238cecf2c55ff095e2ec2b1621ac9a1) --- src/com/android/launcher3/util/ViewPool.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/com/android/launcher3/util/ViewPool.java b/src/com/android/launcher3/util/ViewPool.java index 8af048d9d1..5b33f1849e 100644 --- a/src/com/android/launcher3/util/ViewPool.java +++ b/src/com/android/launcher3/util/ViewPool.java @@ -21,12 +21,12 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import com.android.launcher3.util.ViewPool.Reusable; - import androidx.annotation.AnyThread; import androidx.annotation.Nullable; import androidx.annotation.UiThread; +import com.android.launcher3.util.ViewPool.Reusable; + /** * Utility class to maintain a pool of reusable views. * During initialization, views are inflated on the background thread. @@ -58,14 +58,18 @@ public class ViewPool { Preconditions.assertUIThread(); Handler handler = new Handler(); + // LayoutInflater is not thread save as it maintains a global variable 'mConstructorArgs'. + // Create a different copy to use on the background thread. + LayoutInflater inflater = mInflater.cloneInContext(mInflater.getContext()); + // Inflate views on a non looper thread. This allows us to catch errors like calling // "new Handler()" in constructor easily. new Thread(() -> { for (int i = 0; i < initialSize; i++) { - T view = inflateNewView(); + T view = inflateNewView(inflater); handler.post(() -> addToPool(view)); } - }).start(); + }, "ViewPool-init").start(); } @UiThread @@ -94,12 +98,12 @@ public class ViewPool { mCurrentSize--; return (T) mPool[mCurrentSize]; } - return inflateNewView(); + return inflateNewView(mInflater); } @AnyThread - private T inflateNewView() { - return (T) mInflater.inflate(mLayoutId, mParent, false); + private T inflateNewView(LayoutInflater inflater) { + return (T) inflater.inflate(mLayoutId, mParent, false); } /** From 3483020c9d8639d4578fee5566d3dd6c041eb6ca Mon Sep 17 00:00:00 2001 From: Winson Chung Date: Tue, 12 Nov 2019 16:29:21 -0800 Subject: [PATCH 05/12] Merging ub-launcher3-master, build 6003980 Bug:124967099 P2 Support 5 column layout for all apps (independent of what the workspace layout is) Bug:126596417 P2 [Gesture Nav] Polish quick switch from home screen Bug:136278866 P2 Temporary workarounds to make tests pass on Cuttlefish Bug:136282913 P1 Swipe up from Assistant Fulfillment Card Jank Bug:138473688 P4 Home settings can't be dismissed by swipe up Bug:138944598 P4 Merge TaskIconCache into IconCache Bug:139137636 P2 Create memory tests for Launcher Bug:139661510 P1 Quickswitch over NGA half shelf doesn't work Bug:139663018 P2 A home screen shortcut to the Files app keeps being added without my ok. Bug:140242324 P2 Cache shortcutInfo icons in Launcher Bug:140252765 P2 Audit Gesture Nav tests (including Back and Quick Switch) Bug:140337263 P2 Empty launcher (DecorView visibility==4) Bug:141265005 P4 Overview task should not be tiny if prediction list is shown instead of hotseat Bug:141484300 P4 Re-enable flicker tests Bug:141556707 P1 Non-badged work profile app shortcuts getting created on the launcher. Bug:141568904 P1 Launcher: Unable to add Directions widget Bug:141770616 P2 Flake: Can't find a launcher object; selector: BySelector [RES='\Qcom.google.android.apps.nexuslauncher:id/deep_shortcuts_container\E'] (visible state: AllApps), Bug:141886704 P2 Use app targets to determine input consumer instead of launcher state Bug:141899192 P3 [TalkBack] Page number that TalkBack read doesn't match the current screen on recent apps Bug:142088072 P2 Turning off the display and then back on takes user back to previous app in live tile mode Bug:142498588 P4 Feature req: Automatic folder names / creation Bug:142514365 P1 Failing test AddWidgetTest.testDragIcon Bug:142753423 P4 Prototype predictive hotseat Bug:142803200 P1 Broken binder tests Bug:142828227 P2 Temporary tweaks to make Launcher tests pass in Inproc presubmit Bug:143107728 P4 Crashes on boot, NPE in nexuslauncher Bug:143153963 P1 Launcher crashing while trying to tap and launch apps from home screen multiple times. Bug:143155020 P1 [Failing test] MomoryTests.testAppLaunchFromWorkspace* Bug:143190879 P1 Pop up "Pixel launcher keeps stopping" when both OTA and mainline are updated before restarting device. Bug:143249037 P1 Swipe up gesture does not work on wembley-eng Bug:143285809 P1 [Failing test] TaplTestsQuickstep.testQuickSwitchFromApp Bug:143307786 P1 Fix screenshot not getting cleaned up after recents animation cancelation Bug:143353100 P2 [RVC] Stuck in "All set!" after cable restore Bug:143361207 P3 Can't launch shortcut in live tile mode Bug:143377934 P4 Tapping on QSB leaves user in live tile mode with empty background Bug:143639898 P4 Notification dot should animate into popup on long press Bug:143702640 P2 icon badge is hidden when dragging Bug:143702700 P4 Drag and drop misbehave Bug:143714109 P2 Flashing when swipe up and right to launch the previous task Bug:143773683 P2 [Live tile] App open animation gets cancelled because of screenshot cleanup Bug:143792840 P4 Pixel launcher keeps stopping Bug:143803446 P2 "Files" app has one more icon appearing per day Bug:143857157 P3 Live tile does not follow translationX change of RecentsView Bug:143904998 P2 Launcher crashes when dragging now playing shortcut Bug:143991628 P2 Launcher3 App - system apps must target API level 29 or higher Bug:144081762 P2 Security Vulnerability in AOSP and Pixel Launchers Bug:144180777 P1 Inproc tests in git_master: getting app icon LauncherTestApp on all apps; Unable to scroll to a clickable icon: LauncherTestApp (visible state: AllApps) Bug:144290150 P2 Notification dot popups show the wrong notifications Bug:38268585 P2 Lightning bolt icon missing on recents in Vimeo for OC MR1 and Android P Test: Manual Change-Id: I0e329eea927c49217c0446a9e62ceaf3cf0099f0 (cherry picked from commit fa4ecb35adfcdcaf928572f5f0c089b42bb12218) --- Android.mk | 6 +- OWNERS | 1 + go/AndroidManifest.xml | 2 +- .../uioverrides/QuickstepLauncher.java | 46 +++ .../uioverrides/RecentsUiFactory.java | 93 ----- .../AppToOverviewAnimationProvider.java | 12 +- .../quickstep/FallbackActivityInterface.java | 12 +- .../quickstep/GoActivityInterface.java | 8 +- .../quickstep/LauncherActivityInterface.java | 21 +- .../android/quickstep/util/ShelfPeekAnim.java | 19 +- .../quickstep/views/IconRecentsView.java | 56 ++- .../launcher3/icons/BaseIconFactory.java | 11 +- .../android/launcher3/icons/BitmapInfo.java | 51 ++- .../launcher3/icons/cache/BaseIconCache.java | 50 +-- .../launcher3/icons/cache/CachingLogic.java | 4 +- .../icons/cache/IconCacheUpdateHandler.java | 3 +- quickstep/AndroidManifest-launcher.xml | 70 ++++ .../drawable/predicted_icon_background.xml | 8 + .../res/values/dimens.xml | 5 + .../HotseatPredictionController.java | 342 +++++++++++++++ .../LauncherAppTransitionManagerImpl.java | 39 +- .../appprediction/ComponentKeyMapper.java | 6 +- .../appprediction/DynamicItemCache.java | 40 +- .../PredictionUiStateManager.java | 36 +- ...sUiFactory.java => QuickstepLauncher.java} | 169 ++++---- .../states/BackgroundAppState.java | 4 +- .../uioverrides/states/OverviewState.java | 27 +- .../uioverrides/states/QuickSwitchState.java | 5 +- .../FlingAndHoldTouchController.java | 10 +- .../NavBarToHomeTouchController.java | 39 +- .../QuickSwitchTouchController.java | 10 +- .../TaskViewTouchController.java | 30 +- .../TransposedQuickSwitchTouchController.java | 4 +- .../AppToOverviewAnimationProvider.java | 4 +- .../android/quickstep/BaseSwipeUpHandler.java | 83 ++-- .../quickstep/FallbackActivityInterface.java | 34 +- ...onsumer.java => FallbackSwipeHandler.java} | 154 ++++--- .../quickstep/LauncherActivityInterface.java | 178 ++++---- ...Handler.java => LauncherSwipeHandler.java} | 389 ++++++++++-------- .../quickstep/OverviewCommandHelper.java | 8 +- .../QuickstepTestInformationHandler.java | 8 +- .../android/quickstep/SwipeSharedState.java | 183 -------- .../android/quickstep/TaskOverlayFactory.java | 34 +- .../quickstep/TaskShortcutFactory.java | 315 ++++++++++++++ .../android/quickstep/TaskSystemShortcut.java | 327 --------------- .../quickstep/TouchInteractionService.java | 301 +++++++------- .../inputconsumers/DelegateInputConsumer.java | 5 - .../DeviceLockedInputConsumer.java | 32 +- .../OtherActivityInputConsumer.java | 86 ++-- ...umer.java => OverscrollInputConsumer.java} | 79 ++-- .../OverviewWithoutFocusInputConsumer.java | 37 +- .../ResetGestureInputConsumer.java | 12 +- .../quickstep/util/ActiveGestureLog.java | 2 +- .../util/AppWindowAnimationHelper.java | 2 +- .../quickstep/util/AssistantUtilities.java | 47 +++ .../quickstep/util/NavBarPosition.java | 54 --- .../android/quickstep/util/ShelfPeekAnim.java | 104 +++++ .../quickstep/views/LauncherRecentsView.java | 15 +- .../quickstep/views/LiveTileOverlay.java | 30 +- .../android/quickstep/views/RecentsView.java | 74 ++-- .../android/quickstep/views/TaskMenuView.java | 20 +- .../com/android/quickstep/views/TaskView.java | 42 +- quickstep/res/values/config.xml | 2 + .../launcher3/BaseQuickstepLauncher.java | 280 +++++++++++++ .../launcher3/LauncherInitListener.java | 6 + .../launcher3/model/WellbeingModel.java | 342 +++++++++++++++ .../launcher3/uioverrides/ApiWrapper.java | 63 +++ .../uioverrides/BackButtonAlphaHandler.java | 29 +- .../uioverrides/DejankBinderTracker.java | 159 ------- .../launcher3/uioverrides/UiFactory.java | 267 ------------ .../LandscapeEdgeSwipeController.java | 8 +- .../PortraitStatesTouchController.java | 7 +- .../quickstep/BaseActivityInterface.java | 50 +-- .../quickstep/BaseRecentsActivity.java | 9 +- .../com/android/quickstep/GestureState.java | 258 +++++++++++- .../com/android/quickstep/InputConsumer.java | 10 +- .../android/quickstep/MultiStateCallback.java | 88 ++-- .../quickstep/NormalizedIconLoader.java | 99 ----- .../quickstep/RecentsAnimationCallbacks.java | 5 +- .../quickstep/RecentsAnimationController.java | 13 +- .../RecentsAnimationDeviceState.java | 98 ++++- .../quickstep/RecentsAnimationTargets.java | 9 - .../com/android/quickstep/RecentsModel.java | 65 ++- .../quickstep/TaskAnimationManager.java | 173 ++++++++ .../com/android/quickstep/TaskIconCache.java | 183 +++++--- .../android/quickstep/TaskThumbnailCache.java | 23 +- .../quickstep/util/ActivityInitListener.java | 9 + .../android/quickstep/util/LayoutUtils.java | 19 +- .../quickstep/util/NavBarPosition.java | 127 ++++++ .../quickstep/util/TaskKeyLruCache.java | 124 ++++++ .../quickstep/views/ShelfScrimView.java | 4 +- .../quickstep/FallbackRecentsTest.java | 27 +- .../quickstep/NavigationModeSwitchRule.java | 9 +- .../StartLauncherViaGestureTests.java | 4 + .../android/quickstep/TaplTestsQuickstep.java | 20 +- res/anim/slide_in_right.xml | 9 - res/values/attrs.xml | 8 - res/values/config.xml | 30 +- res/values/strings.xml | 4 +- .../model/BaseModelUpdateTaskTestCase.java | 5 +- .../model/CacheDataUpdatedTaskTest.java | 14 +- src/com/android/launcher3/BaseActivity.java | 73 +++- .../launcher3/BaseDraggingActivity.java | 20 +- src/com/android/launcher3/BubbleTextView.java | 17 +- src/com/android/launcher3/DeviceProfile.java | 44 +- .../android/launcher3/FastBitmapDrawable.java | 41 +- src/com/android/launcher3/IconProvider.java | 29 -- .../launcher3/InstallShortcutReceiver.java | 60 +-- .../launcher3/InvariantDeviceProfile.java | 176 ++++---- .../android/launcher3/ItemInfoWithIcon.java | 23 +- src/com/android/launcher3/Launcher.java | 124 +++--- .../android/launcher3/LauncherAppState.java | 8 + src/com/android/launcher3/LauncherModel.java | 30 +- .../android/launcher3/LauncherSettings.java | 1 + src/com/android/launcher3/LauncherState.java | 7 +- .../launcher3/LauncherStateManager.java | 16 +- .../launcher3/SessionCommitReceiver.java | 7 +- src/com/android/launcher3/Utilities.java | 14 +- .../launcher3/WidgetPreviewLoader.java | 53 ++- src/com/android/launcher3/Workspace.java | 3 - .../android/launcher3/WorkspaceItemInfo.java | 6 +- .../launcher3/WorkspaceLayoutManager.java | 6 +- .../WorkspaceStateTransitionAnimation.java | 16 +- .../allapps/AllAppsTransitionController.java | 11 - .../launcher3/config/FeatureFlags.java | 14 + .../launcher3/dragndrop/DragLayer.java | 7 +- .../android/launcher3/dragndrop/DragView.java | 3 +- .../dragndrop/PinItemDragListener.java | 3 +- src/com/android/launcher3/folder/Folder.java | 21 +- .../android/launcher3/folder/FolderIcon.java | 19 +- .../launcher3/folder/FolderNameProvider.java | 61 +++ .../launcher3/folder/FolderPagedView.java | 2 +- .../launcher3/folder/PreviewItemManager.java | 9 +- .../launcher3/graphics/DrawableFactory.java | 120 ------ .../graphics/LauncherPreviewRenderer.java | 4 +- .../graphics/PlaceHolderIconDrawable.java | 20 +- .../graphics/PreloadIconDrawable.java | 17 +- .../launcher3/icons/ClockDrawableWrapper.java | 328 +++++++++++++++ .../launcher3/icons/ComponentWithLabel.java | 6 +- .../android/launcher3/icons/IconCache.java | 25 +- .../android/launcher3/icons/IconProvider.java | 251 +++++++++++ .../icons/LauncherActivityCachingLogic.java | 15 +- .../launcher3/icons/LauncherIcons.java | 96 +++-- .../launcher3/icons/ShortcutCachingLogic.java | 23 +- .../model/AddWorkspaceItemsTask.java | 43 +- .../launcher3/model/BaseLoaderResults.java | 4 +- .../android/launcher3/model/LoaderCursor.java | 8 +- .../android/launcher3/model/LoaderTask.java | 36 +- .../launcher3/model/PackageUpdatedTask.java | 2 +- .../launcher3/model/ShortcutsChangedTask.java | 4 +- .../model/UserLockStateChangedTask.java | 2 +- .../notification/NotificationItemView.java | 10 +- .../notification/NotificationListener.java | 326 ++++++--------- .../notification/NotificationMainView.java | 17 +- .../launcher3/pm/PinRequestHelper.java | 12 +- .../android/launcher3/popup/ArrowPopup.java | 8 +- .../popup/PopupContainerWithArrow.java | 36 +- .../launcher3/popup/PopupDataProvider.java | 42 +- .../launcher3/popup/PopupPopulator.java | 26 +- .../launcher3/popup/RemoteActionShortcut.java | 104 +++-- .../launcher3/popup/SystemShortcut.java | 229 +++++------ .../popup/SystemShortcutFactory.java | 61 --- .../ShortcutDragPreviewProvider.java | 23 +- .../testing/TestInformationHandler.java | 32 +- .../launcher3/testing/TestProtocol.java | 3 - .../AbstractStateChangeTouchController.java | 23 +- .../touch}/AllAppsSwipeController.java | 25 +- ...peDetector.java => BaseSwipeDetector.java} | 229 +++-------- .../touch/BothAxesSwipeDetector.java | 99 +++++ .../touch/ItemLongClickListener.java | 9 - .../touch/SingleAxisSwipeDetector.java | 190 +++++++++ .../launcher3/util/ActivityTracker.java | 45 +- .../android/launcher3/util/ContentWriter.java | 7 +- .../launcher3/util/LooperIdleLock.java | 13 +- .../launcher3/util/PackageManagerHelper.java | 3 + .../android/launcher3/util/ShortcutUtil.java | 16 +- .../launcher3/util/UiThreadHelper.java | 1 - .../launcher3/util/VibratorWrapper.java | 84 ++++ .../launcher3/util/ViewOnDrawExecutor.java | 4 +- .../launcher3/views/AbstractSlideInView.java | 22 +- .../launcher3/views/BaseDragLayer.java | 4 +- .../launcher3/views/FloatingIconView.java | 4 +- .../widget/PendingAppWidgetHostView.java | 20 +- .../android/launcher3/widget/WidgetCell.java | 7 +- .../launcher3/widget/WidgetsDiffReporter.java | 8 +- .../systemui/plugins/OverscrollPlugin.java | 44 ++ .../systemui/plugins/RecentsExtraCard.java | 4 +- .../launcher3/uioverrides/ApiWrapper.java | 36 ++ .../launcher3/uioverrides/UiFactory.java | 105 ----- tests/AndroidManifest.xml | 2 +- tests/dummy_app/AndroidManifest.xml | 2 +- .../launcher3/model/LoaderCursorTest.java | 2 +- ....java => SingleAxisSwipeDetectorTest.java} | 44 +- .../launcher3/ui/AbstractLauncherUiTest.java | 20 +- .../launcher3/ui/TaplTestsLauncher3.java | 6 + .../ui/widget/AddConfigWidgetTest.java | 4 +- .../launcher3/ui/widget/AddWidgetTest.java | 2 + .../ui/widget/RequestPinItemTest.java | 10 +- .../src/com/android/launcher3/util/Wait.java | 11 +- .../util/rule/TestStabilityRule.java | 16 +- .../widget/WidgetsListAdapterTest.java | 11 +- .../com/android/launcher3/tapl/AllApps.java | 13 +- .../tapl/LauncherInstrumentation.java | 47 ++- .../com/android/launcher3/tapl/Overview.java | 2 +- .../com/android/launcher3/tapl/Workspace.java | 6 +- 205 files changed, 6496 insertions(+), 4118 deletions(-) create mode 100644 go/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java delete mode 100644 go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java rename quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherInitListenerEx.java => go/quickstep/src/com/android/quickstep/util/ShelfPeekAnim.java (50%) create mode 100644 quickstep/AndroidManifest-launcher.xml create mode 100644 quickstep/recents_ui_overrides/res/drawable/predicted_icon_background.xml create mode 100644 quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java rename quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/{RecentsUiFactory.java => QuickstepLauncher.java} (67%) rename quickstep/recents_ui_overrides/src/com/android/quickstep/{inputconsumers/FallbackNoButtonInputConsumer.java => FallbackSwipeHandler.java} (73%) rename quickstep/recents_ui_overrides/src/com/android/quickstep/{WindowTransformSwipeHandler.java => LauncherSwipeHandler.java} (78%) delete mode 100644 quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java create mode 100644 quickstep/recents_ui_overrides/src/com/android/quickstep/TaskShortcutFactory.java delete mode 100644 quickstep/recents_ui_overrides/src/com/android/quickstep/TaskSystemShortcut.java rename quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/{QuickCaptureInputConsumer.java => OverscrollInputConsumer.java} (71%) create mode 100644 quickstep/recents_ui_overrides/src/com/android/quickstep/util/AssistantUtilities.java delete mode 100644 quickstep/recents_ui_overrides/src/com/android/quickstep/util/NavBarPosition.java create mode 100644 quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java create mode 100644 quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java create mode 100644 quickstep/src/com/android/launcher3/model/WellbeingModel.java create mode 100644 quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java delete mode 100644 quickstep/src/com/android/launcher3/uioverrides/DejankBinderTracker.java delete mode 100644 quickstep/src/com/android/launcher3/uioverrides/UiFactory.java rename quickstep/{recents_ui_overrides => }/src/com/android/quickstep/MultiStateCallback.java (50%) delete mode 100644 quickstep/src/com/android/quickstep/NormalizedIconLoader.java create mode 100644 quickstep/src/com/android/quickstep/TaskAnimationManager.java create mode 100644 quickstep/src/com/android/quickstep/util/NavBarPosition.java create mode 100644 quickstep/src/com/android/quickstep/util/TaskKeyLruCache.java delete mode 100644 res/anim/slide_in_right.xml delete mode 100644 src/com/android/launcher3/IconProvider.java create mode 100644 src/com/android/launcher3/folder/FolderNameProvider.java delete mode 100644 src/com/android/launcher3/graphics/DrawableFactory.java create mode 100644 src/com/android/launcher3/icons/ClockDrawableWrapper.java create mode 100644 src/com/android/launcher3/icons/IconProvider.java delete mode 100644 src/com/android/launcher3/popup/SystemShortcutFactory.java rename {src_ui_overrides/com/android/launcher3/uioverrides => src/com/android/launcher3/touch}/AllAppsSwipeController.java (76%) rename src/com/android/launcher3/touch/{SwipeDetector.java => BaseSwipeDetector.java} (54%) create mode 100644 src/com/android/launcher3/touch/BothAxesSwipeDetector.java create mode 100644 src/com/android/launcher3/touch/SingleAxisSwipeDetector.java create mode 100644 src/com/android/launcher3/util/VibratorWrapper.java create mode 100644 src_plugins/com/android/systemui/plugins/OverscrollPlugin.java create mode 100644 src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java delete mode 100644 src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java rename tests/src/com/android/launcher3/touch/{SwipeDetectorTest.java => SingleAxisSwipeDetectorTest.java} (72%) diff --git a/Android.mk b/Android.mk index 3d1d996dfd..5def65f50c 100644 --- a/Android.mk +++ b/Android.mk @@ -200,7 +200,7 @@ LOCAL_RESOURCE_DIR := \ $(LOCAL_PATH)/quickstep/recents_ui_overrides/res LOCAL_FULL_LIBS_MANIFEST_FILES := \ - $(LOCAL_PATH)/AndroidManifest.xml \ + $(LOCAL_PATH)/quickstep/AndroidManifest-launcher.xml \ $(LOCAL_PATH)/AndroidManifest-common.xml LOCAL_MANIFEST_FILE := quickstep/AndroidManifest.xml @@ -247,7 +247,7 @@ LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.launcher3 LOCAL_FULL_LIBS_MANIFEST_FILES := \ $(LOCAL_PATH)/go/AndroidManifest.xml \ - $(LOCAL_PATH)/AndroidManifest.xml \ + $(LOCAL_PATH)/quickstep/AndroidManifest-launcher.xml \ $(LOCAL_PATH)/AndroidManifest-common.xml LOCAL_MANIFEST_FILE := quickstep/AndroidManifest.xml @@ -293,7 +293,7 @@ LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.launcher3 LOCAL_FULL_LIBS_MANIFEST_FILES := \ $(LOCAL_PATH)/go/AndroidManifest.xml \ - $(LOCAL_PATH)/AndroidManifest.xml \ + $(LOCAL_PATH)/quickstep/AndroidManifest-launcher.xml \ $(LOCAL_PATH)/AndroidManifest-common.xml LOCAL_MANIFEST_FILE := quickstep/AndroidManifest.xml diff --git a/OWNERS b/OWNERS index 538ca33d8f..7340e84883 100644 --- a/OWNERS +++ b/OWNERS @@ -10,6 +10,7 @@ mrcasey@google.com sunnygoyal@google.com twickham@google.com winsonc@google.com +zakcohen@google.com per-file FeatureFlags.java = sunnygoyal@google.com, adamcohen@google.com per-file BaseFlags.java = sunnygoyal@google.com, adamcohen@google.com diff --git a/go/AndroidManifest.xml b/go/AndroidManifest.xml index fae1efffe0..f84a82eaa2 100644 --- a/go/AndroidManifest.xml +++ b/go/AndroidManifest.xml @@ -22,7 +22,7 @@ xmlns:tools="http://schemas.android.com/tools" package="com.android.launcher3" > - + list = new ArrayList<>(); + list.add(getDragController()); + + if (getDeviceProfile().isVerticalBarLayout()) { + list.add(new LandscapeStatesTouchController(this)); + list.add(new LandscapeEdgeSwipeController(this)); + } else { + boolean allowDragToOverview = SysUINavigationMode.INSTANCE.get(this) + .getMode().hasGestures; + list.add(new PortraitStatesTouchController(this, allowDragToOverview)); + } + return list.toArray(new TouchController[list.size()]); + } +} diff --git a/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java b/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java deleted file mode 100644 index f2aa842d12..0000000000 --- a/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java +++ /dev/null @@ -1,93 +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 com.android.launcher3.DeviceProfile; -import com.android.launcher3.Launcher; -import com.android.launcher3.LauncherStateManager.StateHandler; -import com.android.launcher3.Utilities; -import com.android.launcher3.graphics.RotationMode; -import com.android.launcher3.uioverrides.touchcontrollers.LandscapeEdgeSwipeController; -import com.android.launcher3.uioverrides.touchcontrollers.LandscapeStatesTouchController; -import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController; -import com.android.launcher3.uioverrides.touchcontrollers.StatusBarTouchController; -import com.android.launcher3.util.TouchController; -import com.android.quickstep.SysUINavigationMode; -import com.android.quickstep.views.IconRecentsView; - -import java.util.ArrayList; - -/** - * Provides recents-related {@link UiFactory} logic and classes. - */ -public abstract class RecentsUiFactory { - - public static final boolean GO_LOW_RAM_RECENTS_ENABLED = true; - - public static TouchController[] createTouchControllers(Launcher launcher) { - ArrayList list = new ArrayList<>(); - list.add(launcher.getDragController()); - - if (launcher.getDeviceProfile().isVerticalBarLayout()) { - list.add(new LandscapeStatesTouchController(launcher)); - list.add(new LandscapeEdgeSwipeController(launcher)); - } else { - boolean allowDragToOverview = SysUINavigationMode.INSTANCE.get(launcher) - .getMode().hasGestures; - list.add(new PortraitStatesTouchController(launcher, allowDragToOverview)); - } - if (Utilities.IS_DEBUG_DEVICE - && !launcher.getDeviceProfile().isMultiWindowMode - && !launcher.getDeviceProfile().isVerticalBarLayout()) { - list.add(new StatusBarTouchController(launcher)); - } - return list.toArray(new TouchController[list.size()]); - } - - /** - * Creates and returns the controller responsible for recents view state transitions. - * - * @param launcher the launcher activity - * @return state handler for recents - */ - public static StateHandler createRecentsViewStateController(Launcher launcher) { - return new RecentsViewStateController(launcher); - } - - /** - * Clean-up logic that occurs when recents is no longer in use/visible. - * - * @param launcher the launcher activity - */ - public static void resetOverview(Launcher launcher) { - IconRecentsView recentsView = launcher.getOverviewPanel(); - recentsView.setTransitionedFromApp(false); - } - - /** - * Recents logic that triggers when launcher state changes or launcher activity stops/resumes. - * - * @param launcher the launcher activity - */ - public static void onLauncherStateOrResumeChanged(Launcher launcher) {} - - public static RotationMode getRotationMode(DeviceProfile dp) { - return RotationMode.NORMAL; - } - - public static void clearSwipeSharedState(Launcher launcher, boolean finishAnimation) { } -} diff --git a/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java b/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java index 6b50088221..04753d2527 100644 --- a/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java +++ b/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java @@ -46,13 +46,13 @@ final class AppToOverviewAnimationProvider imple RemoteAnimationProvider { private static final String TAG = "AppToOverviewAnimationProvider"; - private final BaseActivityInterface mHelper; + private final BaseActivityInterface mActivityInterface; private final int mTargetTaskId; private IconRecentsView mRecentsView; private AppToOverviewAnimationListener mAnimationReadyListener; - AppToOverviewAnimationProvider(BaseActivityInterface helper, int targetTaskId) { - mHelper = helper; + AppToOverviewAnimationProvider(BaseActivityInterface activityInterface, int targetTaskId) { + mActivityInterface = activityInterface; mTargetTaskId = targetTaskId; } @@ -68,15 +68,15 @@ final class AppToOverviewAnimationProvider imple /** * Callback for when the activity is ready/initialized. * - * @param activity the activity that is ready * @param wasVisible true if it was visible before */ - boolean onActivityReady(T activity, Boolean wasVisible) { + boolean onActivityReady(Boolean wasVisible) { + T activity = mActivityInterface.getCreatedActivity(); if (mAnimationReadyListener != null) { mAnimationReadyListener.onActivityReady(activity); } BaseActivityInterface.AnimationFactory factory = - mHelper.prepareRecentsUI(activity, wasVisible, + mActivityInterface.prepareRecentsUI(wasVisible, false /* animate activity */, (controller) -> { controller.dispatchOnStart(); ValueAnimator anim = controller.getAnimationPlayer() diff --git a/go/quickstep/src/com/android/quickstep/FallbackActivityInterface.java b/go/quickstep/src/com/android/quickstep/FallbackActivityInterface.java index 2af8441853..ecb9472c72 100644 --- a/go/quickstep/src/com/android/quickstep/FallbackActivityInterface.java +++ b/go/quickstep/src/com/android/quickstep/FallbackActivityInterface.java @@ -29,8 +29,8 @@ import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.quickstep.util.ActivityInitListener; import com.android.quickstep.views.IconRecentsView; -import java.util.function.BiPredicate; import java.util.function.Consumer; +import java.util.function.Predicate; /** * {@link BaseActivityInterface} for recents when the default launcher is different than the @@ -43,12 +43,13 @@ public final class FallbackActivityInterface extends public FallbackActivityInterface() { } @Override - public AnimationFactory prepareRecentsUI(RecentsActivity activity, boolean activityVisible, + public AnimationFactory prepareRecentsUI(boolean activityVisible, boolean animateActivity, Consumer callback) { if (activityVisible) { return (transitionLength) -> { }; } + RecentsActivity activity = getCreatedActivity(); IconRecentsView rv = activity.getOverviewPanel(); rv.setUsingRemoteAnimation(true); rv.setAlpha(0); @@ -84,8 +85,9 @@ public final class FallbackActivityInterface extends @Override public ActivityInitListener createActivityInitListener( - BiPredicate onInitListener) { - return new ActivityInitListener(onInitListener, RecentsActivity.ACTIVITY_TRACKER); + Predicate onInitListener) { + return new ActivityInitListener<>((activity, alreadyOnHome) -> + onInitListener.test(alreadyOnHome), RecentsActivity.ACTIVITY_TRACKER); } @Nullable @@ -115,5 +117,5 @@ public final class FallbackActivityInterface extends } @Override - public void onLaunchTaskSuccess(RecentsActivity activity) { } + public void onLaunchTaskSuccess() { } } diff --git a/go/quickstep/src/com/android/quickstep/GoActivityInterface.java b/go/quickstep/src/com/android/quickstep/GoActivityInterface.java index 5ce0f4cdf5..b62d17ce2f 100644 --- a/go/quickstep/src/com/android/quickstep/GoActivityInterface.java +++ b/go/quickstep/src/com/android/quickstep/GoActivityInterface.java @@ -17,7 +17,7 @@ public abstract class GoActivityInterface implem BaseActivityInterface { @Override - public void onTransitionCancelled(T activity, boolean activityVisible) { + public void onTransitionCancelled(boolean activityVisible) { // Go transitions to overview are all atomic. } @@ -29,7 +29,7 @@ public abstract class GoActivityInterface implem } @Override - public void onSwipeUpToRecentsComplete(T activity) { + public void onSwipeUpToRecentsComplete() { // Go does not support swipe up gesture. } @@ -39,7 +39,7 @@ public abstract class GoActivityInterface implem } @Override - public HomeAnimationFactory prepareHomeUI(T activity) { + public HomeAnimationFactory prepareHomeUI() { // Go does not support gestures from app to home. return null; } @@ -63,7 +63,7 @@ public abstract class GoActivityInterface implem } @Override - public void onLaunchTaskFailed(T activity) { + public void onLaunchTaskFailed() { // Go does not support gestures from one task to another. } } diff --git a/go/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/go/quickstep/src/com/android/quickstep/LauncherActivityInterface.java index 5bff8e8060..3e93480a95 100644 --- a/go/quickstep/src/com/android/quickstep/LauncherActivityInterface.java +++ b/go/quickstep/src/com/android/quickstep/LauncherActivityInterface.java @@ -26,8 +26,8 @@ import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.quickstep.views.IconRecentsView; -import java.util.function.BiPredicate; import java.util.function.Consumer; +import java.util.function.Predicate; /** * {@link BaseActivityInterface} for the in-launcher recents. @@ -36,15 +36,15 @@ import java.util.function.Consumer; public final class LauncherActivityInterface extends GoActivityInterface { @Override - public AnimationFactory prepareRecentsUI(Launcher activity, - boolean activityVisible, boolean animateActivity, + public AnimationFactory prepareRecentsUI(boolean activityVisible, boolean animateActivity, Consumer callback) { - LauncherState fromState = activity.getStateManager().getState(); - activity.getOverviewPanel().setUsingRemoteAnimation(true); + Launcher launcher = getCreatedActivity(); + LauncherState fromState = launcher.getStateManager().getState(); + launcher.getOverviewPanel().setUsingRemoteAnimation(true); //TODO: Implement this based off where the recents view needs to be for app => recents anim. return new AnimationFactory() { public void createActivityInterface(long transitionLength) { - callback.accept(activity.getStateManager().createAnimationToNewWorkspace( + callback.accept(launcher.getStateManager().createAnimationToNewWorkspace( fromState, OVERVIEW, transitionLength)); } @@ -54,9 +54,9 @@ public final class LauncherActivityInterface extends GoActivityInterface onInitListener) { - return new LauncherInitListener(onInitListener); + public LauncherInitListener createActivityInitListener(Predicate onInitListener) { + return new LauncherInitListener((activity, alreadyOnHome) -> + onInitListener.test(alreadyOnHome)); } @Override @@ -105,7 +105,8 @@ public final class LauncherActivityInterface extends GoActivityInterface onInitListener) { - super(onInitListener); +/** Empty class, only exists so that lowRamWithQuickstepIconRecentsDebug compiles. */ +public class ShelfPeekAnim { + public ShelfPeekAnim(Launcher launcher) { } - @Override - public boolean init(Launcher launcher, boolean alreadyOnHome) { - PredictionUiStateManager.INSTANCE.get(launcher).switchClient(Client.OVERVIEW); - return super.init(launcher, alreadyOnHome); + public enum ShelfAnimState { } } diff --git a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java index 87b4d4e2e5..e380698d38 100644 --- a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java +++ b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java @@ -40,6 +40,7 @@ import android.graphics.Matrix; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.Drawable; +import android.os.UserHandle; import android.util.ArraySet; import android.util.AttributeSet; import android.util.FloatProperty; @@ -66,6 +67,7 @@ import com.android.launcher3.R; import com.android.launcher3.util.Themes; import com.android.quickstep.ContentFillItemAnimator; import com.android.quickstep.RecentsModel; +import com.android.quickstep.RecentsModel.TaskVisualsChangeListener; import com.android.quickstep.RecentsToActivityHelper; import com.android.quickstep.TaskActionController; import com.android.quickstep.TaskAdapter; @@ -74,6 +76,7 @@ import com.android.quickstep.TaskListLoader; import com.android.quickstep.TaskSwipeCallback; import com.android.quickstep.util.MultiValueUpdateListener; import com.android.systemui.shared.recents.model.Task; +import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat; import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams; @@ -87,7 +90,8 @@ import java.util.Optional; * Root view for the icon recents view. Acts as the main interface to the rest of the Launcher code * base. */ -public final class IconRecentsView extends FrameLayout implements Insettable { +public final class IconRecentsView extends FrameLayout + implements Insettable, TaskVisualsChangeListener { public static final FloatProperty CONTENT_ALPHA = new FloatProperty("contentAlpha") { @@ -159,22 +163,6 @@ public final class IconRecentsView extends FrameLayout implements Insettable { private AnimatorSet mLayoutAnimation; private final ArraySet mLayingOutViews = new ArraySet<>(); private Rect mInsets; - private final RecentsModel.TaskThumbnailChangeListener listener = (taskId, thumbnailData) -> { - ArrayList itemViews = getTaskViews(); - for (int i = 0, size = itemViews.size(); i < size; i++) { - TaskItemView taskView = itemViews.get(i); - TaskHolder taskHolder = (TaskHolder) mTaskRecyclerView.getChildViewHolder(taskView); - Optional optTask = taskHolder.getTask(); - if (optTask.filter(task -> task.key.id == taskId).isPresent()) { - Task task = optTask.get(); - // Update thumbnail on the task. - task.thumbnail = thumbnailData; - taskView.setThumbnail(thumbnailData); - return task; - } - } - return null; - }; public IconRecentsView(Context context, AttributeSet attrs) { super(context, attrs); @@ -189,9 +177,29 @@ public final class IconRecentsView extends FrameLayout implements Insettable { mActivity.getStatsLogManager()); mTaskAdapter.setActionController(mTaskActionController); mTaskLayoutManager = new LinearLayoutManager(mContext, VERTICAL, true /* reverseLayout */); - RecentsModel.INSTANCE.get(context).addThumbnailChangeListener(listener); } + @Override + public Task onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData) { + ArrayList itemViews = getTaskViews(); + for (int i = 0, size = itemViews.size(); i < size; i++) { + TaskItemView taskView = itemViews.get(i); + TaskHolder taskHolder = (TaskHolder) mTaskRecyclerView.getChildViewHolder(taskView); + Optional optTask = taskHolder.getTask(); + if (optTask.filter(task -> task.key.id == taskId).isPresent()) { + Task task = optTask.get(); + // Update thumbnail on the task. + task.thumbnail = thumbnailData; + taskView.setThumbnail(thumbnailData); + return task; + } + } + return null; + } + + @Override + public void onTaskIconChanged(String pkg, UserHandle user) { } + @Override protected void onFinishInflate() { super.onFinishInflate(); @@ -274,6 +282,18 @@ public final class IconRecentsView extends FrameLayout implements Insettable { } } + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + RecentsModel.INSTANCE.get(getContext()).addThumbnailChangeListener(this); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + RecentsModel.INSTANCE.get(getContext()).removeThumbnailChangeListener(this); + } + @Override public void setEnabled(boolean enabled) { super.setEnabled(enabled); diff --git a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java index 5c4f37ca98..a3c7c078a4 100644 --- a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java +++ b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java @@ -116,7 +116,7 @@ public class BaseIconFactory implements AutoCloseable { icon = createIconBitmap(new BitmapDrawable(mContext.getResources(), icon), 1f); } - return BitmapInfo.fromBitmap(icon, mDisableColorExtractor ? null : mColorExtractor); + return BitmapInfo.of(icon, extractColor(icon)); } public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user, @@ -183,7 +183,10 @@ public class BaseIconFactory implements AutoCloseable { bitmap = createIconBitmap(badged, 1f); } } - return BitmapInfo.fromBitmap(bitmap, mDisableColorExtractor ? null : mColorExtractor); + int color = extractColor(result); + return icon instanceof BitmapInfo.Extender + ? ((BitmapInfo.Extender) icon).getExtendedInfo(result, color, this) + : BitmapInfo.of(result, color); } public Bitmap createScaledBitmapWithoutShadow(Drawable icon, boolean shrinkNonAdaptiveIcons) { @@ -334,6 +337,10 @@ public class BaseIconFactory implements AutoCloseable { iconDpi); } + private int extractColor(Bitmap bitmap) { + return mDisableColorExtractor ? 0 : mColorExtractor.findDominantColorByHue(bitmap); + } + /** * Returns the correct badge size given an icon size */ diff --git a/iconloaderlib/src/com/android/launcher3/icons/BitmapInfo.java b/iconloaderlib/src/com/android/launcher3/icons/BitmapInfo.java index 245561ea53..d33f9b1112 100644 --- a/iconloaderlib/src/com/android/launcher3/icons/BitmapInfo.java +++ b/iconloaderlib/src/com/android/launcher3/icons/BitmapInfo.java @@ -18,32 +18,55 @@ package com.android.launcher3.icons; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; +import androidx.annotation.NonNull; + public class BitmapInfo { public static final Bitmap LOW_RES_ICON = Bitmap.createBitmap(1, 1, Config.ALPHA_8); + public static final BitmapInfo LOW_RES_INFO = fromBitmap(LOW_RES_ICON); - public Bitmap icon; - public int color; + public final Bitmap icon; + public final int color; - public void applyTo(BitmapInfo info) { - info.icon = icon; - info.color = color; + public BitmapInfo(Bitmap icon, int color) { + this.icon = icon; + this.color = color; + } + + /** + * Ideally icon should not be null, except in cases when generating hardware bitmap failed + */ + public final boolean isNullOrLowRes() { + return icon == null || icon == LOW_RES_ICON; } public final boolean isLowRes() { return LOW_RES_ICON == icon; } - public static BitmapInfo fromBitmap(Bitmap bitmap) { - return fromBitmap(bitmap, null); + public static BitmapInfo fromBitmap(@NonNull Bitmap bitmap) { + return of(bitmap, 0); } - public static BitmapInfo fromBitmap(Bitmap bitmap, ColorExtractor dominantColorExtractor) { - BitmapInfo info = new BitmapInfo(); - info.icon = bitmap; - info.color = dominantColorExtractor != null - ? dominantColorExtractor.findDominantColorByHue(bitmap) - : 0; - return info; + public static BitmapInfo of(@NonNull Bitmap bitmap, int color) { + return new BitmapInfo(bitmap, color); + } + + /** + * Interface to be implemented by drawables to provide a custom BitmapInfo + */ + public interface Extender { + + /** + * Called for creating a custom BitmapInfo + */ + default BitmapInfo getExtendedInfo(Bitmap bitmap, int color, BaseIconFactory iconFactory) { + return BitmapInfo.of(bitmap, color); + } + + /** + * Notifies the drawable that it will be drawn directly in the UI, without any preprocessing + */ + default void prepareToDrawOnUi() { } } } diff --git a/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java b/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java index 93f0538bd5..6f63d88722 100644 --- a/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java +++ b/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java @@ -71,7 +71,10 @@ public abstract class BaseIconCache { // Empty class name is used for storing package default entry. public static final String EMPTY_CLASS_NAME = "."; - public static class CacheEntry extends BitmapInfo { + public static class CacheEntry { + + @NonNull + public BitmapInfo bitmap = BitmapInfo.LOW_RES_INFO; public CharSequence title = ""; public CharSequence contentDescription = ""; } @@ -259,23 +262,23 @@ public abstract class BaseIconCache { if (!replaceExisting) { entry = mCache.get(key); // We can't reuse the entry if the high-res icon is not present. - if (entry == null || entry.icon == null || entry.isLowRes()) { + if (entry == null || entry.bitmap.isNullOrLowRes()) { entry = null; } } if (entry == null) { entry = new CacheEntry(); - cachingLogic.loadIcon(mContext, object, entry); + entry.bitmap = cachingLogic.loadIcon(mContext, object); } // Icon can't be loaded from cachingLogic, which implies alternative icon was loaded // (e.g. fallback icon, default icon). So we drop here since there's no point in caching // an empty entry. - if (entry.icon == null) return; + if (entry.bitmap.isNullOrLowRes()) return; entry.title = cachingLogic.getLabel(object); entry.contentDescription = mPackageManager.getUserBadgedLabel(entry.title, user); if (cachingLogic.addToMemCache()) mCache.put(key, entry); - ContentValues values = newContentValues(entry, entry.title.toString(), + ContentValues values = newContentValues(entry.bitmap, entry.title.toString(), componentName.getPackageName(), cachingLogic.getKeywords(object, mLocaleList)); addIconToDB(values, componentName, info, userSerial); } @@ -300,8 +303,8 @@ public abstract class BaseIconCache { return mDefaultIcons.get(user); } - public boolean isDefaultIcon(Bitmap icon, UserHandle user) { - return getDefaultIcon(user).icon == icon; + public boolean isDefaultIcon(BitmapInfo icon, UserHandle user) { + return getDefaultIcon(user).icon == icon.icon; } /** @@ -315,7 +318,7 @@ public abstract class BaseIconCache { assertWorkerThread(); ComponentKey cacheKey = new ComponentKey(componentName, user); CacheEntry entry = mCache.get(cacheKey); - if (entry == null || (entry.isLowRes() && !useLowResIcon)) { + if (entry == null || (entry.bitmap.isLowRes() && !useLowResIcon)) { entry = new CacheEntry(); if (cachingLogic.addToMemCache()) { mCache.put(cacheKey, entry); @@ -330,7 +333,7 @@ public abstract class BaseIconCache { providerFetchedOnce = true; if (object != null) { - cachingLogic.loadIcon(mContext, object, entry); + entry.bitmap = cachingLogic.loadIcon(mContext, object); } else { if (usePackageIcon) { CacheEntry packageEntry = getEntryForPackageLocked( @@ -338,15 +341,15 @@ public abstract class BaseIconCache { if (packageEntry != null) { if (DEBUG) Log.d(TAG, "using package default icon for " + componentName.toShortString()); - packageEntry.applyTo(entry); + entry.bitmap = packageEntry.bitmap; entry.title = packageEntry.title; entry.contentDescription = packageEntry.contentDescription; } } - if (entry.icon == null) { + if (entry.bitmap == null) { if (DEBUG) Log.d(TAG, "using default icon for " + componentName.toShortString()); - getDefaultIcon(user).applyTo(entry); + entry.bitmap = getDefaultIcon(user); } } } @@ -390,10 +393,10 @@ public abstract class BaseIconCache { } if (icon != null) { BaseIconFactory li = getIconFactory(); - li.createIconBitmap(icon).applyTo(entry); + entry.bitmap = li.createIconBitmap(icon); li.close(); } - if (!TextUtils.isEmpty(title) && entry.icon != null) { + if (!TextUtils.isEmpty(title) && entry.bitmap.icon != null) { mCache.put(cacheKey, entry); } } @@ -413,7 +416,7 @@ public abstract class BaseIconCache { ComponentKey cacheKey = getPackageKey(packageName, user); CacheEntry entry = mCache.get(cacheKey); - if (entry == null || (entry.isLowRes() && !useLowResIcon)) { + if (entry == null || (entry.bitmap.isLowRes() && !useLowResIcon)) { entry = new CacheEntry(); boolean entryUpdated = true; @@ -438,8 +441,8 @@ public abstract class BaseIconCache { entry.title = appInfo.loadLabel(mPackageManager); entry.contentDescription = mPackageManager.getUserBadgedLabel(entry.title, user); - entry.icon = useLowResIcon ? LOW_RES_ICON : iconInfo.icon; - entry.color = iconInfo.color; + entry.bitmap = BitmapInfo.of( + useLowResIcon ? LOW_RES_ICON : iconInfo.icon, iconInfo.color); // Add the icon in the DB here, since these do not get written during // package updates. @@ -461,7 +464,7 @@ public abstract class BaseIconCache { return entry; } - private boolean getEntryFromDB(ComponentKey cacheKey, CacheEntry entry, boolean lowRes) { + protected boolean getEntryFromDB(ComponentKey cacheKey, CacheEntry entry, boolean lowRes) { Cursor c = null; try { c = mIconDb.query( @@ -472,7 +475,7 @@ public abstract class BaseIconCache { Long.toString(getSerialNumberForUser(cacheKey.user))}); if (c.moveToNext()) { // Set the alpha to be 255, so that we never have a wrong color - entry.color = setColorAlphaBound(c.getInt(0), 255); + entry.bitmap = BitmapInfo.of(LOW_RES_ICON, setColorAlphaBound(c.getInt(0), 255)); entry.title = c.getString(1); if (entry.title == null) { entry.title = ""; @@ -482,13 +485,12 @@ public abstract class BaseIconCache { entry.title, cacheKey.user); } - if (lowRes) { - entry.icon = LOW_RES_ICON; - } else { + if (!lowRes) { byte[] data = c.getBlob(2); try { - entry.icon = BitmapFactory.decodeByteArray(data, 0, data.length, - mDecodeOptions); + entry.bitmap = BitmapInfo.of( + BitmapFactory.decodeByteArray(data, 0, data.length, mDecodeOptions), + entry.bitmap.color); } catch (Exception e) { } } return true; diff --git a/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java b/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java index 3aa783a14c..a89ede7b34 100644 --- a/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java +++ b/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java @@ -21,6 +21,7 @@ import android.content.pm.PackageInfo; import android.os.LocaleList; import android.os.UserHandle; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.launcher3.icons.BitmapInfo; @@ -33,7 +34,8 @@ public interface CachingLogic { CharSequence getLabel(T object); - void loadIcon(Context context, T object, BitmapInfo target); + @NonNull + BitmapInfo loadIcon(Context context, T object); /** * Provides a option list of keywords to associate with this object diff --git a/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java b/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java index d0db15728a..bcdbce5e29 100644 --- a/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java +++ b/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java @@ -171,7 +171,8 @@ public class IconCacheUpdateHandler { long updateTime = c.getLong(indexLastUpdate); int version = c.getInt(indexVersion); T app = componentMap.remove(component); - if (version == info.versionCode && updateTime == info.lastUpdateTime + if (version == info.versionCode + && updateTime == cachingLogic.getLastUpdatedTime(app, info) && TextUtils.equals(c.getString(systemStateIndex), mIconCache.getIconSystemState(info.packageName))) { diff --git a/quickstep/AndroidManifest-launcher.xml b/quickstep/AndroidManifest-launcher.xml new file mode 100644 index 0000000000..60afddb0a8 --- /dev/null +++ b/quickstep/AndroidManifest-launcher.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/quickstep/recents_ui_overrides/res/drawable/predicted_icon_background.xml b/quickstep/recents_ui_overrides/res/drawable/predicted_icon_background.xml new file mode 100644 index 0000000000..cfc6d4801d --- /dev/null +++ b/quickstep/recents_ui_overrides/res/drawable/predicted_icon_background.xml @@ -0,0 +1,8 @@ + + + + + + + diff --git a/quickstep/recents_ui_overrides/res/values/dimens.xml b/quickstep/recents_ui_overrides/res/values/dimens.xml index 863a8ba528..ee672d47cc 100644 --- a/quickstep/recents_ui_overrides/res/values/dimens.xml +++ b/quickstep/recents_ui_overrides/res/values/dimens.xml @@ -28,4 +28,9 @@ 18dp 10dp -60dp + + + 15dp + 8dp + \ No newline at end of file diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java new file mode 100644 index 0000000000..424333c971 --- /dev/null +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java @@ -0,0 +1,342 @@ +/* + * 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; + +import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; + +import android.animation.Animator; +import android.animation.ObjectAnimator; +import android.app.prediction.AppPredictionContext; +import android.app.prediction.AppPredictionManager; +import android.app.prediction.AppPredictor; +import android.app.prediction.AppTarget; +import android.content.ComponentName; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.Nullable; + +import com.android.launcher3.allapps.AllAppsStore; +import com.android.launcher3.anim.AnimationSuccessListener; +import com.android.launcher3.appprediction.ComponentKeyMapper; +import com.android.launcher3.appprediction.DynamicItemCache; +import com.android.launcher3.dragndrop.DragController; +import com.android.launcher3.dragndrop.DragOptions; +import com.android.launcher3.icons.IconCache; +import com.android.launcher3.popup.PopupContainerWithArrow; +import com.android.launcher3.popup.SystemShortcut; +import com.android.launcher3.shortcuts.ShortcutKey; +import com.android.launcher3.touch.ItemLongClickListener; +import com.android.launcher3.uioverrides.QuickstepLauncher; +import com.android.launcher3.util.ComponentKey; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Provides prediction ability for the hotseat. Fills gaps in hotseat with predicted items, allows + * pinning of predicted apps and manages replacement of predicted apps with user drag. + */ +public class HotseatPredictionController implements DragController.DragListener, + View.OnAttachStateChangeListener, SystemShortcut.Factory, + InvariantDeviceProfile.OnIDPChangeListener, AllAppsStore.OnUpdateListener, + IconCache.ItemInfoUpdateReceiver { + + private static final String TAG = "PredictiveHotseat"; + private static final boolean DEBUG = false; + + private static final String PREDICTION_CLIENT = "hotseat"; + + private boolean mDragStarted = false; + private int mHotSeatItemsCount; + + private Launcher mLauncher; + private Hotseat mHotseat; + + private List mComponentKeyMappers = new ArrayList<>(); + + private DynamicItemCache mDynamicItemCache; + + private AppPredictor mAppPredictor; + private AllAppsStore mAllAppsStore; + + public HotseatPredictionController(Launcher launcher) { + mLauncher = launcher; + mHotseat = launcher.getHotseat(); + mAllAppsStore = mLauncher.getAppsView().getAppsStore(); + mAllAppsStore.addUpdateListener(this); + mDynamicItemCache = new DynamicItemCache(mLauncher, () -> fillGapsWithPrediction(false)); + mHotSeatItemsCount = mLauncher.getDeviceProfile().inv.numHotseatIcons; + launcher.getDeviceProfile().inv.addOnChangeListener(this); + mHotseat.addOnAttachStateChangeListener(this); + createPredictor(); + } + + @Override + public void onViewAttachedToWindow(View view) { + mLauncher.getDragController().addDragListener(this); + } + + @Override + public void onViewDetachedFromWindow(View view) { + mLauncher.getDragController().removeDragListener(this); + } + + /** + * Fills gaps in the hotseat with predictions + */ + public void fillGapsWithPrediction(boolean animate) { + if (mDragStarted) { + return; + } + List predictedApps = mapToWorkspaceItemInfo(mComponentKeyMappers); + int predictionIndex = 0; + ArrayList newItemsToAdd = new ArrayList<>(); + for (int rank = 0; rank < mHotSeatItemsCount; rank++) { + View child = mHotseat.getChildAt( + mHotseat.getCellXFromOrder(rank), + mHotseat.getCellYFromOrder(rank)); + + if (child != null && !isPredictedIcon(child)) { + continue; + } + if (predictedApps.size() <= predictionIndex) { + // Remove predicted apps from the past + if (isPredictedIcon(child)) { + mHotseat.removeView(child); + } + continue; + } + + WorkspaceItemInfo predictedItem = predictedApps.get(predictionIndex++); + if (isPredictedIcon(child)) { + BubbleTextView icon = (BubbleTextView) child; + icon.applyFromWorkspaceItem(predictedItem); + } else { + newItemsToAdd.add(predictedItem); + } + preparePredictionInfo(predictedItem, rank); + } + mLauncher.bindItems(newItemsToAdd, animate); + for (BubbleTextView icon : getPredictedIcons()) { + icon.verifyHighRes(); + icon.setOnLongClickListener((v) -> { + PopupContainerWithArrow.showForIcon((BubbleTextView) v); + return true; + }); + icon.setBackgroundResource(R.drawable.predicted_icon_background); + } + } + + /** + * Unregisters callbacks and frees resources + */ + public void destroy() { + mAllAppsStore.removeUpdateListener(this); + mLauncher.getDeviceProfile().inv.removeOnChangeListener(this); + mHotseat.removeOnAttachStateChangeListener(this); + if (mAppPredictor != null) { + mAppPredictor.destroy(); + } + } + + private void createPredictor() { + AppPredictionManager apm = mLauncher.getSystemService(AppPredictionManager.class); + if (apm == null) { + return; + } + if (mAppPredictor != null) { + mAppPredictor.destroy(); + } + mAppPredictor = apm.createAppPredictionSession( + new AppPredictionContext.Builder(mLauncher) + .setUiSurface(PREDICTION_CLIENT) + .setPredictedTargetCount(mHotSeatItemsCount) + .build()); + mAppPredictor.registerPredictionUpdates(mLauncher.getMainExecutor(), + this::setPredictedApps); + mAppPredictor.requestPredictionUpdate(); + } + + private void setPredictedApps(List appTargets) { + mComponentKeyMappers.clear(); + for (AppTarget appTarget : appTargets) { + ComponentKey key; + if (appTarget.getShortcutInfo() != null) { + key = ShortcutKey.fromInfo(appTarget.getShortcutInfo()); + } else { + key = new ComponentKey(new ComponentName(appTarget.getPackageName(), + appTarget.getClassName()), appTarget.getUser()); + } + mComponentKeyMappers.add(new ComponentKeyMapper(key, mDynamicItemCache)); + } + updateDependencies(); + fillGapsWithPrediction(false); + } + + private void updateDependencies() { + mDynamicItemCache.updateDependencies(mComponentKeyMappers, mAllAppsStore, this, + mHotSeatItemsCount); + } + + private void pinPrediction(ItemInfo info) { + BubbleTextView icon = (BubbleTextView) mHotseat.getChildAt( + mHotseat.getCellXFromOrder(info.rank), + mHotseat.getCellYFromOrder(info.rank)); + if (icon == null) { + return; + } + WorkspaceItemInfo workspaceItemInfo = new WorkspaceItemInfo((WorkspaceItemInfo) info); + mLauncher.getModelWriter().addItemToDatabase(workspaceItemInfo, + LauncherSettings.Favorites.CONTAINER_HOTSEAT, workspaceItemInfo.screenId, + workspaceItemInfo.cellX, workspaceItemInfo.cellY); + ObjectAnimator.ofFloat(icon, SCALE_PROPERTY, 1, 0.8f, 1).start(); + icon.reset(); + icon.applyFromWorkspaceItem(workspaceItemInfo); + icon.setOnLongClickListener(ItemLongClickListener.INSTANCE_WORKSPACE); + } + + private List mapToWorkspaceItemInfo( + List components) { + AllAppsStore allAppsStore = mLauncher.getAppsView().getAppsStore(); + if (allAppsStore.getApps().length == 0) { + return Collections.emptyList(); + } + + List predictedApps = new ArrayList<>(); + for (ComponentKeyMapper mapper : components) { + ItemInfoWithIcon info = mapper.getApp(allAppsStore); + if (info instanceof AppInfo) { + WorkspaceItemInfo predictedApp = new WorkspaceItemInfo((AppInfo) info); + predictedApps.add(predictedApp); + } else if (info instanceof WorkspaceItemInfo) { + predictedApps.add(new WorkspaceItemInfo((WorkspaceItemInfo) info)); + } else { + if (DEBUG) { + Log.e(TAG, "Predicted app not found: " + mapper); + } + } + // Stop at the number of hotseat items + if (predictedApps.size() == mHotSeatItemsCount) { + break; + } + } + return predictedApps; + } + + private List getPredictedIcons() { + List icons = new ArrayList<>(); + ViewGroup vg = mHotseat.getShortcutsAndWidgets(); + for (int i = 0; i < vg.getChildCount(); i++) { + View child = vg.getChildAt(i); + if (isPredictedIcon(child)) { + icons.add((BubbleTextView) child); + } + } + return icons; + } + + private void removePredictedApps(boolean animate) { + for (BubbleTextView icon : getPredictedIcons()) { + if (animate) { + icon.animate().scaleY(0).scaleX(0).setListener(new AnimationSuccessListener() { + @Override + public void onAnimationSuccess(Animator animator) { + if (icon.getParent() != null) { + mHotseat.removeView(icon); + } + } + }); + } else { + if (icon.getParent() != null) { + mHotseat.removeView(icon); + } + } + } + } + + @Override + public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) { + removePredictedApps(true); + mDragStarted = true; + } + + @Override + public void onDragEnd() { + if (!mDragStarted) { + return; + } + mDragStarted = false; + fillGapsWithPrediction(true); + } + + @Nullable + @Override + public SystemShortcut getShortcut(QuickstepLauncher activity, + ItemInfo itemInfo) { + if (itemInfo.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) { + return null; + } + return new PinPrediction(activity, itemInfo); + } + + private void preparePredictionInfo(WorkspaceItemInfo itemInfo, int rank) { + itemInfo.container = LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION; + itemInfo.rank = rank; + itemInfo.cellX = rank; + itemInfo.cellY = mHotSeatItemsCount - rank - 1; + itemInfo.screenId = rank; + } + + @Override + public void onIdpChanged(int changeFlags, InvariantDeviceProfile profile) { + this.mHotSeatItemsCount = profile.numHotseatIcons; + createPredictor(); + } + + @Override + public void onAppsUpdated() { + updateDependencies(); + fillGapsWithPrediction(false); + } + + @Override + public void reapplyItemInfo(ItemInfoWithIcon info) { + + } + + private class PinPrediction extends SystemShortcut { + + private PinPrediction(QuickstepLauncher target, ItemInfo itemInfo) { + super(R.drawable.ic_pin, R.string.pin_prediction, target, + itemInfo); + } + + @Override + public void onClick(View view) { + dismissTaskMenuView(mTarget); + pinPrediction(mItemInfo); + } + } + + private static boolean isPredictedIcon(View view) { + return view instanceof BubbleTextView && view.getTag() instanceof WorkspaceItemInfo + && ((WorkspaceItemInfo) view.getTag()).container + == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION; + } +} diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java index d84248419e..6946508fc7 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java @@ -17,7 +17,10 @@ package com.android.launcher3; import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X; +import static com.android.launcher3.LauncherState.BACKGROUND_APP; +import static com.android.launcher3.LauncherState.HOTSEAT_ICONS; import static com.android.launcher3.LauncherState.NORMAL; +import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE; import static com.android.launcher3.anim.Interpolators.LINEAR; import static com.android.quickstep.TaskViewUtils.findTaskViewToLaunch; @@ -27,12 +30,15 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; import android.content.Context; import android.view.View; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.launcher3.LauncherState.ScaleAndTranslation; +import com.android.launcher3.allapps.AllAppsTransitionController; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.anim.SpringAnimationBuilder; @@ -145,8 +151,37 @@ public final class LauncherAppTransitionManagerImpl extends QuickstepAppTransiti @Override public Animator createStateElementAnimation(int index, float... values) { switch (index) { - case INDEX_SHELF_ANIM: - return mLauncher.getAllAppsController().createSpringAnimation(values); + case INDEX_SHELF_ANIM: { + AllAppsTransitionController aatc = mLauncher.getAllAppsController(); + Animator springAnim = aatc.createSpringAnimation(values); + + if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) { + // Translate hotseat with the shelf until reaching overview. + float overviewProgress = OVERVIEW.getVerticalProgress(mLauncher); + ScaleAndTranslation sat = OVERVIEW.getHotseatScaleAndTranslation(mLauncher); + float shiftRange = aatc.getShiftRange(); + if (values.length == 1) { + values = new float[] {aatc.getProgress(), values[0]}; + } + ValueAnimator hotseatAnim = ValueAnimator.ofFloat(values); + hotseatAnim.addUpdateListener(anim -> { + float progress = (Float) anim.getAnimatedValue(); + if (progress >= overviewProgress || mLauncher.isInState(BACKGROUND_APP)) { + float hotseatShift = (progress - overviewProgress) * shiftRange; + mLauncher.getHotseat().setTranslationY(hotseatShift + sat.translationY); + } + }); + hotseatAnim.setInterpolator(LINEAR); + hotseatAnim.setDuration(springAnim.getDuration()); + + AnimatorSet anim = new AnimatorSet(); + anim.play(hotseatAnim); + anim.play(springAnim); + return anim; + } + + return springAnim; + } case INDEX_RECENTS_FADE_ANIM: return ObjectAnimator.ofFloat(mLauncher.getOverviewPanel(), RecentsView.CONTENT_ALPHA, values); diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/ComponentKeyMapper.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/ComponentKeyMapper.java index b9f4147f85..0712285aec 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/ComponentKeyMapper.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/ComponentKeyMapper.java @@ -18,8 +18,6 @@ package com.android.launcher3.appprediction; import static com.android.quickstep.InstantAppResolverImpl.COMPONENT_CLASS_MARKER; -import android.content.Context; - import com.android.launcher3.AppInfo; import com.android.launcher3.ItemInfoWithIcon; import com.android.launcher3.allapps.AllAppsStore; @@ -29,11 +27,9 @@ import com.android.launcher3.util.ComponentKey; public class ComponentKeyMapper { protected final ComponentKey componentKey; - private final Context mContext; private final DynamicItemCache mCache; - public ComponentKeyMapper(Context context, ComponentKey key, DynamicItemCache cache) { - mContext = context; + public ComponentKeyMapper(ComponentKey key, DynamicItemCache cache) { componentKey = key; mCache = cache; } diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java index 65e69b6046..38bb180ecb 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java @@ -18,6 +18,7 @@ package com.android.launcher3.appprediction; import static android.content.pm.PackageManager.MATCH_INSTANT; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; +import static com.android.quickstep.InstantAppResolverImpl.COMPONENT_CLASS_MARKER; import android.content.Context; import android.content.Intent; @@ -37,8 +38,10 @@ import androidx.annotation.Nullable; import androidx.annotation.UiThread; import androidx.annotation.WorkerThread; +import com.android.launcher3.AppInfo; import com.android.launcher3.LauncherAppState; import com.android.launcher3.WorkspaceItemInfo; +import com.android.launcher3.allapps.AllAppsStore; import com.android.launcher3.icons.IconCache; import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.shortcuts.DeepShortcutManager; @@ -72,6 +75,7 @@ public class DynamicItemCache { private final Handler mUiHandler; private final InstantAppResolver mInstantAppResolver; private final Runnable mOnUpdateCallback; + private final IconCache mIconCache; private final Map mShortcuts; private final Map mInstantApps; @@ -82,6 +86,7 @@ public class DynamicItemCache { mUiHandler = new Handler(Looper.getMainLooper(), this::handleUiMessage); mInstantAppResolver = InstantAppResolver.newInstance(context); mOnUpdateCallback = onUpdateCallback; + mIconCache = LauncherAppState.getInstance(mContext).getIconCache(); mShortcuts = new HashMap<>(); mInstantApps = new HashMap<>(); @@ -170,7 +175,7 @@ public class DynamicItemCache { if (!details.isEmpty()) { WorkspaceItemInfo si = new WorkspaceItemInfo(details.get(0), mContext); try (LauncherIcons li = LauncherIcons.obtain(mContext)) { - si.applyFrom(li.createShortcutIcon(details.get(0), true /* badged */, null)); + si.bitmap = li.createShortcutIcon(details.get(0), true /* badged */, null); } catch (Exception e) { if (DEBUG) { Log.e(TAG, "Error loading shortcut icon for " + shortcutKey.toString()); @@ -209,7 +214,7 @@ public class DynamicItemCache { InstantAppItemInfo info = new InstantAppItemInfo(intent, pkgName); IconCache iconCache = LauncherAppState.getInstance(mContext).getIconCache(); iconCache.getTitleAndIcon(info, false); - if (info.iconBitmap == null || iconCache.isDefaultIcon(info.iconBitmap, info.user)) { + if (info.bitmap.icon == null || iconCache.isDefaultIcon(info.bitmap, info.user)) { return null; } return info; @@ -240,4 +245,35 @@ public class DynamicItemCache { public WorkspaceItemInfo getShortcutInfo(ShortcutKey key) { return mShortcuts.get(key); } + + /** + * requests and caches icons for app targets + */ + public void updateDependencies(List componentKeyMappers, + AllAppsStore appsStore, IconCache.ItemInfoUpdateReceiver callback, int itemCount) { + List instantAppsToLoad = new ArrayList<>(); + List shortcutsToLoad = new ArrayList<>(); + int total = componentKeyMappers.size(); + for (int i = 0, count = 0; i < total && count < itemCount; i++) { + ComponentKeyMapper mapper = componentKeyMappers.get(i); + // Update instant apps + if (COMPONENT_CLASS_MARKER.equals(mapper.getComponentClass())) { + instantAppsToLoad.add(mapper.getPackage()); + count++; + } else if (mapper.getComponentKey() instanceof ShortcutKey) { + shortcutsToLoad.add((ShortcutKey) mapper.getComponentKey()); + count++; + } else { + // Reload high res icon + AppInfo info = (AppInfo) mapper.getApp(appsStore); + if (info != null) { + if (info.usingLowResIcon()) { + mIconCache.updateIconInBackground(callback, info); + } + count++; + } + } + } + cacheItems(shortcutsToLoad, instantAppsToLoad); + } } diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java index 1a59770a02..8338c2e437 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,14 +18,12 @@ package com.android.launcher3.appprediction; import static com.android.launcher3.LauncherState.BACKGROUND_APP; import static com.android.launcher3.LauncherState.OVERVIEW; -import static com.android.quickstep.InstantAppResolverImpl.COMPONENT_CLASS_MARKER; import android.app.prediction.AppPredictor; import android.app.prediction.AppTarget; import android.content.ComponentName; import android.content.Context; -import com.android.launcher3.AppInfo; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.InvariantDeviceProfile.OnIDPChangeListener; import com.android.launcher3.ItemInfoWithIcon; @@ -36,7 +34,6 @@ import com.android.launcher3.LauncherStateManager.StateListener; import com.android.launcher3.Utilities; import com.android.launcher3.allapps.AllAppsContainerView; import com.android.launcher3.allapps.AllAppsStore.OnUpdateListener; -import com.android.launcher3.icons.IconCache; import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver; import com.android.launcher3.shortcuts.ShortcutKey; import com.android.launcher3.util.ComponentKey; @@ -239,7 +236,7 @@ public class PredictionUiStateManager implements StateListener, ItemInfoUpdateRe key = new ComponentKey(new ComponentName(appTarget.getPackageName(), appTarget.getClassName()), appTarget.getUser()); } - state.apps.add(new ComponentKeyMapper(mContext, key, mDynamicItemCache)); + state.apps.add(new ComponentKeyMapper(key, mDynamicItemCache)); } } updateDependencies(state); @@ -250,33 +247,8 @@ public class PredictionUiStateManager implements StateListener, ItemInfoUpdateRe if (!state.isEnabled || mAppsView == null) { return; } - - IconCache iconCache = LauncherAppState.getInstance(mContext).getIconCache(); - List instantAppsToLoad = new ArrayList<>(); - List shortcutsToLoad = new ArrayList<>(); - int total = state.apps.size(); - for (int i = 0, count = 0; i < total && count < mMaxIconsPerRow; i++) { - ComponentKeyMapper mapper = state.apps.get(i); - // Update instant apps - if (COMPONENT_CLASS_MARKER.equals(mapper.getComponentClass())) { - instantAppsToLoad.add(mapper.getPackage()); - count++; - } else if (mapper.getComponentKey() instanceof ShortcutKey) { - shortcutsToLoad.add((ShortcutKey) mapper.getComponentKey()); - count++; - } else { - // Reload high res icon - AppInfo info = (AppInfo) mapper.getApp(mAppsView.getAppsStore()); - if (info != null) { - if (info.usingLowResIcon()) { - // TODO: Update icon cache to support null callbacks. - iconCache.updateIconInBackground(this, info); - } - count++; - } - } - } - mDynamicItemCache.cacheItems(shortcutsToLoad, instantAppsToLoad); + mDynamicItemCache.updateDependencies(state.apps, mAppsView.getAppsStore(), this, + mMaxIconsPerRow); } @Override diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java similarity index 67% rename from quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java rename to quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java index 4c04b29598..7dc561677a 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java @@ -13,26 +13,27 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.android.launcher3.uioverrides; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.OVERVIEW; -import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON; import android.content.Context; +import android.content.res.Configuration; import android.graphics.Rect; -import android.os.RemoteException; -import android.util.Log; +import android.os.Bundle; import android.view.Gravity; +import com.android.launcher3.BaseQuickstepLauncher; import com.android.launcher3.DeviceProfile; +import com.android.launcher3.HotseatPredictionController; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; -import com.android.launcher3.LauncherStateManager.StateHandler; import com.android.launcher3.anim.AnimatorPlaybackController; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.graphics.RotationMode; +import com.android.launcher3.popup.SystemShortcut; import com.android.launcher3.uioverrides.touchcontrollers.FlingAndHoldTouchController; import com.android.launcher3.uioverrides.touchcontrollers.LandscapeEdgeSwipeController; import com.android.launcher3.uioverrides.touchcontrollers.NavBarToHomeTouchController; @@ -48,28 +49,20 @@ import com.android.launcher3.util.UiThreadHelper.AsyncCommand; import com.android.quickstep.RecentsModel; import com.android.quickstep.SysUINavigationMode; import com.android.quickstep.SysUINavigationMode.Mode; -import com.android.quickstep.TouchInteractionService; import com.android.quickstep.SystemUiProxy; import com.android.quickstep.views.RecentsView; import java.util.ArrayList; +import java.util.stream.Stream; -/** - * Provides recents-related {@link UiFactory} logic and classes. - */ -public abstract class RecentsUiFactory { - - private static final String TAG = RecentsUiFactory.class.getSimpleName(); +public class QuickstepLauncher extends BaseQuickstepLauncher { public static final boolean GO_LOW_RAM_RECENTS_ENABLED = false; - /** * Reusable command for applying the shelf height on the background thread. */ - public static final AsyncCommand SET_SHELF_HEIGHT = (context, arg1, arg2) -> { - SystemUiProxy.INSTANCE.get(context).setShelfHeight(arg1 != 0, arg2); - }; - + public static final AsyncCommand SET_SHELF_HEIGHT = (context, arg1, arg2) -> + SystemUiProxy.INSTANCE.get(context).setShelfHeight(arg1 != 0, arg2); public static RotationMode ROTATION_LANDSCAPE = new RotationMode(-90) { @Override public void mapRect(int left, int top, int right, int bottom, Rect out) { @@ -96,7 +89,6 @@ public abstract class RecentsUiFactory { } } }; - public static RotationMode ROTATION_SEASCAPE = new RotationMode(90) { @Override public void mapRect(int left, int top, int right, int bottom, Rect out) { @@ -142,83 +134,114 @@ public abstract class RecentsUiFactory { | horizontalGravity | verticalGravity; } }; + private HotseatPredictionController mHotseatPredictionController; - public static RotationMode getRotationMode(DeviceProfile dp) { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (FeatureFlags.ENABLE_HYBRID_HOTSEAT.get()) { + mHotseatPredictionController = new HotseatPredictionController(this); + } + } + + @Override + protected RotationMode getFakeRotationMode(DeviceProfile dp) { return !dp.isVerticalBarLayout() ? RotationMode.NORMAL : (dp.isSeascape() ? ROTATION_SEASCAPE : ROTATION_LANDSCAPE); } - public static TouchController[] createTouchControllers(Launcher launcher) { - Mode mode = SysUINavigationMode.getMode(launcher); - - ArrayList list = new ArrayList<>(); - list.add(launcher.getDragController()); - if (mode == NO_BUTTON) { - list.add(new QuickSwitchTouchController(launcher)); - list.add(new NavBarToHomeTouchController(launcher)); - list.add(new FlingAndHoldTouchController(launcher)); - } else { - if (launcher.getDeviceProfile().isVerticalBarLayout()) { - list.add(new OverviewToAllAppsTouchController(launcher)); - list.add(new LandscapeEdgeSwipeController(launcher)); - if (mode.hasGestures) { - list.add(new TransposedQuickSwitchTouchController(launcher)); - } - } else { - list.add(new PortraitStatesTouchController(launcher, - mode.hasGestures /* allowDragToOverview */)); - if (mode.hasGestures) { - list.add(new QuickSwitchTouchController(launcher)); - } - } - } - - if (!launcher.getDeviceProfile().isMultiWindowMode) { - list.add(new StatusBarTouchController(launcher)); - } - - list.add(new LauncherTaskViewController(launcher)); - return list.toArray(new TouchController[list.size()]); + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + onStateOrResumeChanged(); } - /** - * Creates and returns the controller responsible for recents view state transitions. - * - * @param launcher the launcher activity - * @return state handler for recents - */ - public static StateHandler createRecentsViewStateController(Launcher launcher) { - return new RecentsViewStateController(launcher); + @Override + protected void onActivityFlagsChanged(int changeBits) { + super.onActivityFlagsChanged(changeBits); + + if ((changeBits & (ACTIVITY_STATE_DEFERRED_RESUMED | ACTIVITY_STATE_STARTED + | ACTIVITY_STATE_USER_ACTIVE | ACTIVITY_STATE_TRANSITION_ACTIVE)) != 0 + && (getActivityFlags() & ACTIVITY_STATE_TRANSITION_ACTIVE) == 0) { + onStateOrResumeChanged(); + } } - /** Clears the swipe shared state for the current swipe gesture. */ - public static void clearSwipeSharedState(Launcher launcher, boolean finishAnimation) { - if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { - launcher.getOverviewPanel().switchToScreenshot( - () -> TouchInteractionService.getSwipeSharedState().clearAllState( - finishAnimation)); + @Override + public Stream getSupportedShortcuts() { + if (mHotseatPredictionController != null) { + return Stream.concat(super.getSupportedShortcuts(), + Stream.of(mHotseatPredictionController)); } else { - TouchInteractionService.getSwipeSharedState().clearAllState(finishAnimation); + return super.getSupportedShortcuts(); } } /** * Recents logic that triggers when launcher state changes or launcher activity stops/resumes. - * - * @param launcher the launcher activity */ - public static void onLauncherStateOrResumeChanged(Launcher launcher) { - LauncherState state = launcher.getStateManager().getState(); - DeviceProfile profile = launcher.getDeviceProfile(); - boolean visible = (state == NORMAL || state == OVERVIEW) && launcher.isUserActive() + private void onStateOrResumeChanged() { + LauncherState state = getStateManager().getState(); + DeviceProfile profile = getDeviceProfile(); + boolean visible = (state == NORMAL || state == OVERVIEW) && isUserActive() && !profile.isVerticalBarLayout(); - UiThreadHelper.runAsyncCommand(launcher, SET_SHELF_HEIGHT, visible ? 1 : 0, + UiThreadHelper.runAsyncCommand(this, SET_SHELF_HEIGHT, visible ? 1 : 0, profile.hotseatBarSizePx); if (state == NORMAL) { - launcher.getOverviewPanel().setSwipeDownShouldLaunchApp(false); + ((RecentsView) getOverviewPanel()).setSwipeDownShouldLaunchApp(false); } } + @Override + public void finishBindingItems(int pageBoundFirst) { + super.finishBindingItems(pageBoundFirst); + if (mHotseatPredictionController != null) { + mHotseatPredictionController.fillGapsWithPrediction(false); + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (mHotseatPredictionController != null) { + mHotseatPredictionController.destroy(); + } + } + + @Override + public TouchController[] createTouchControllers() { + Mode mode = SysUINavigationMode.getMode(this); + + ArrayList list = new ArrayList<>(); + list.add(getDragController()); + if (mode == NO_BUTTON) { + list.add(new QuickSwitchTouchController(this)); + list.add(new NavBarToHomeTouchController(this)); + list.add(new FlingAndHoldTouchController(this)); + } else { + if (getDeviceProfile().isVerticalBarLayout()) { + list.add(new OverviewToAllAppsTouchController(this)); + list.add(new LandscapeEdgeSwipeController(this)); + if (mode.hasGestures) { + list.add(new TransposedQuickSwitchTouchController(this)); + } + } else { + list.add(new PortraitStatesTouchController(this, + mode.hasGestures /* allowDragToOverview */)); + if (mode.hasGestures) { + list.add(new QuickSwitchTouchController(this)); + } + } + } + + if (!getDeviceProfile().isMultiWindowMode) { + list.add(new StatusBarTouchController(this)); + } + + list.add(new LauncherTaskViewController(this)); + return list.toArray(new TouchController[list.size()]); + } + private static final class LauncherTaskViewController extends TaskViewTouchController { diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java index e4e60a04b1..bb66ae1cf9 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java @@ -69,7 +69,7 @@ public class BackgroundAppState extends OverviewState { return super.getOverviewScaleAndTranslation(launcher); } TaskView dummyTask; - if (recentsView.getCurrentPage() >= 0) { + if (recentsView.getCurrentPage() >= recentsView.getTaskViewStartIndex()) { if (recentsView.getCurrentPage() <= taskCount - 1) { dummyTask = recentsView.getCurrentPageTaskView(); } else { @@ -98,7 +98,7 @@ public class BackgroundAppState extends OverviewState { if ((getVisibleElements(launcher) & HOTSEAT_ICONS) != 0) { // Translate hotseat offscreen if we show it in overview. ScaleAndTranslation scaleAndTranslation = super.getHotseatScaleAndTranslation(launcher); - scaleAndTranslation.translationY = LayoutUtils.getShelfTrackingDistance(launcher, + scaleAndTranslation.translationY += LayoutUtils.getShelfTrackingDistance(launcher, launcher.getDeviceProfile()); return scaleAndTranslation; } diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java index 93d4de17de..25eaab1879 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java @@ -32,7 +32,6 @@ import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_7; import static com.android.launcher3.logging.LoggerUtils.newContainerTarget; import static com.android.launcher3.states.RotationHelper.REQUEST_ROTATE; -import android.content.Context; import android.graphics.Rect; import android.view.View; @@ -47,6 +46,7 @@ import com.android.launcher3.anim.AnimatorSetBuilder; import com.android.launcher3.userevent.nano.LauncherLogProto.Action; import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; import com.android.quickstep.SysUINavigationMode; +import com.android.quickstep.util.LayoutUtils; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.TaskView; @@ -91,8 +91,19 @@ public class OverviewState extends LauncherState { @Override public ScaleAndTranslation getHotseatScaleAndTranslation(Launcher launcher) { if ((getVisibleElements(launcher) & HOTSEAT_ICONS) != 0) { - // If the hotseat icons are visible in overview, keep them in their normal position. - return super.getWorkspaceScaleAndTranslation(launcher); + DeviceProfile dp = launcher.getDeviceProfile(); + if (dp.allAppsIconSizePx >= dp.iconSizePx) { + return new ScaleAndTranslation(1, 0, 0); + } else { + float scale = ((float) dp.allAppsIconSizePx) / dp.iconSizePx; + // Distance between the screen center (which is the pivotY for hotseat) and the + // bottom of the hotseat (which we want to preserve) + float distanceFromBottom = dp.heightPx / 2 - dp.hotseatBarBottomPaddingPx; + // On scaling, the bottom edge is moved closer to the pivotY. We move the + // hotseat back down so that the bottom edge's position is preserved. + float translationY = distanceFromBottom * (1 - scale); + return new ScaleAndTranslation(scale, 0, translationY); + } } return getWorkspaceScaleAndTranslation(launcher); } @@ -160,15 +171,7 @@ public class OverviewState extends LauncherState { } public static float getDefaultSwipeHeight(Launcher launcher) { - return getDefaultSwipeHeight(launcher, launcher.getDeviceProfile()); - } - - public static float getDefaultSwipeHeight(Context context, DeviceProfile dp) { - float swipeHeight = dp.allAppsCellHeightPx - dp.allAppsIconTextSizePx; - if (SysUINavigationMode.getMode(context) == SysUINavigationMode.Mode.NO_BUTTON) { - swipeHeight -= dp.getInsets().bottom; - } - return swipeHeight; + return LayoutUtils.getDefaultSwipeHeight(launcher, launcher.getDeviceProfile()); } @Override diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java index 6c9f46fc47..7b4bb0274a 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java @@ -20,13 +20,14 @@ import android.os.Looper; import com.android.launcher3.Launcher; import com.android.launcher3.userevent.nano.LauncherLogProto; +import com.android.quickstep.GestureState; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.TaskView; /** * State to indicate we are about to launch a recent task. Note that this state is only used when - * quick switching from launcher; quick switching from an app uses WindowTransformSwipeHelper. - * @see com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget#NEW_TASK + * quick switching from launcher; quick switching from an app uses LauncherSwipeHandler. + * @see GestureState.GestureEndTarget#NEW_TASK */ public class QuickSwitchState extends BackgroundAppState { diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java index ee2e9519e5..626292e867 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java @@ -35,12 +35,12 @@ import static com.android.launcher3.anim.Interpolators.ACCEL; import static com.android.launcher3.anim.Interpolators.DEACCEL; import static com.android.launcher3.anim.Interpolators.DEACCEL_3; import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2; +import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; -import android.view.HapticFeedbackConstants; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; @@ -51,6 +51,7 @@ import com.android.launcher3.anim.AnimatorSetBuilder; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; import com.android.quickstep.SystemUiProxy; +import com.android.launcher3.util.VibratorWrapper; import com.android.quickstep.util.MotionPauseDetector; import com.android.quickstep.views.RecentsView; @@ -106,8 +107,7 @@ public class FlingAndHoldTouchController extends PortraitStatesTouchController { } }); mPeekAnim.start(); - recentsView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, - HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); + VibratorWrapper.INSTANCE.get(mLauncher).vibrate(OVERVIEW_HAPTIC); mLauncher.getDragLayer().getScrim().animateToSysuiMultiplier(isPaused ? 0 : 1, peekDuration, 0); @@ -173,7 +173,7 @@ public class FlingAndHoldTouchController extends PortraitStatesTouchController { } @Override - public void onDragEnd(float velocity, boolean fling) { + public void onDragEnd(float velocity) { if (mMotionPauseDetector.isPaused() && handlingOverviewAnim()) { if (mPeekAnim != null) { mPeekAnim.cancel(); @@ -196,7 +196,7 @@ public class FlingAndHoldTouchController extends PortraitStatesTouchController { }); overviewAnim.start(); } else { - super.onDragEnd(velocity, fling); + super.onDragEnd(velocity); } View searchView = mLauncher.getAppsView().getSearchView(); diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java index d66af1ae24..ad4a343cb4 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java @@ -22,7 +22,9 @@ import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS; import static com.android.launcher3.anim.Interpolators.DEACCEL_3; +import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; import static com.android.launcher3.touch.AbstractStateChangeTouchController.SUCCESS_TRANSITION_PROGRESS; +import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS; import android.animation.Animator; import android.animation.AnimatorSet; @@ -43,21 +45,25 @@ import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.AnimatorSetBuilder; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.compat.AccessibilityManagerCompat; -import com.android.launcher3.touch.SwipeDetector; +import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.touch.SingleAxisSwipeDetector; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; import com.android.launcher3.util.TouchController; +import com.android.quickstep.util.AssistantUtilities; import com.android.quickstep.views.RecentsView; +import com.android.systemui.shared.system.ActivityManagerWrapper; /** * Handles swiping up on the nav bar to go home from launcher, e.g. overview or all apps. */ -public class NavBarToHomeTouchController implements TouchController, SwipeDetector.Listener { +public class NavBarToHomeTouchController implements TouchController, + SingleAxisSwipeDetector.Listener { private static final Interpolator PULLBACK_INTERPOLATOR = DEACCEL_3; private final Launcher mLauncher; - private final SwipeDetector mSwipeDetector; + private final SingleAxisSwipeDetector mSwipeDetector; private final float mPullbackDistance; private boolean mNoIntercept; @@ -67,7 +73,8 @@ public class NavBarToHomeTouchController implements TouchController, SwipeDetect public NavBarToHomeTouchController(Launcher launcher) { mLauncher = launcher; - mSwipeDetector = new SwipeDetector(mLauncher, this, SwipeDetector.VERTICAL); + mSwipeDetector = new SingleAxisSwipeDetector(mLauncher, this, + SingleAxisSwipeDetector.VERTICAL); mPullbackDistance = mLauncher.getResources().getDimension(R.dimen.home_pullback_distance); } @@ -79,7 +86,8 @@ public class NavBarToHomeTouchController implements TouchController, SwipeDetect if (mNoIntercept) { return false; } - mSwipeDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_POSITIVE, false); + mSwipeDetector.setDetectableScrollConditions(SingleAxisSwipeDetector.DIRECTION_POSITIVE, + false /* ignoreSlop */); } if (mNoIntercept) { @@ -101,6 +109,10 @@ public class NavBarToHomeTouchController implements TouchController, SwipeDetect if (AbstractFloatingView.getTopOpenView(mLauncher) != null) { return true; } + if (FeatureFlags.ASSISTANT_GIVES_LAUNCHER_FOCUS.get() + && AssistantUtilities.isExcludedAssistantRunning()) { + return true; + } return false; } @@ -127,8 +139,13 @@ public class NavBarToHomeTouchController implements TouchController, SwipeDetect if (!recentsView.isRtl()) { pullbackDist = -pullbackDist; } - Animator pullback = ObjectAnimator.ofFloat(recentsView, TRANSLATION_X, pullbackDist); + ObjectAnimator pullback = ObjectAnimator.ofFloat(recentsView, TRANSLATION_X, + pullbackDist); pullback.setInterpolator(PULLBACK_INTERPOLATOR); + if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { + pullback.addUpdateListener( + valueAnimator -> recentsView.redrawLiveTile(false /* mightNeedToRefill */)); + } anim.play(pullback); } else if (mStartState == ALL_APPS) { AnimatorSetBuilder builder = new AnimatorSetBuilder(); @@ -173,13 +190,19 @@ public class NavBarToHomeTouchController implements TouchController, SwipeDetect } @Override - public void onDragEnd(float velocity, boolean fling) { + public void onDragEnd(float velocity) { + boolean fling = mSwipeDetector.isFling(velocity); final int logAction = fling ? Touch.FLING : Touch.SWIPE; float progress = mCurrentAnimation.getProgressFraction(); float interpolatedProgress = PULLBACK_INTERPOLATOR.getInterpolation(progress); boolean success = interpolatedProgress >= SUCCESS_TRANSITION_PROGRESS || (velocity < 0 && fling); if (success) { + if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { + RecentsView recentsView = mLauncher.getOverviewPanel(); + recentsView.switchToScreenshot(null, + () -> recentsView.finishRecentsAnimation(true /* toRecents */, null)); + } mLauncher.getStateManager().goToState(mEndState, true, () -> onSwipeInteractionCompleted(mEndState)); if (mStartState != mEndState) { @@ -190,6 +213,8 @@ public class NavBarToHomeTouchController implements TouchController, SwipeDetect AbstractFloatingView.closeAllOpenViews(mLauncher); logStateChange(topOpenView.getLogContainerType(), logAction); } + ActivityManagerWrapper.getInstance() + .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS); } else { // Quickly return to the state we came from (we didn't move far). ValueAnimator anim = mCurrentAnimation.getAnimationPlayer(); diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java index 5c3b55d6c4..912be983f7 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java @@ -30,6 +30,7 @@ import static com.android.launcher3.anim.Interpolators.INSTANT; import static com.android.launcher3.anim.Interpolators.LINEAR; import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW; import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD; +import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED; import android.view.MotionEvent; @@ -42,7 +43,7 @@ import com.android.launcher3.LauncherStateManager; import com.android.launcher3.Utilities; import com.android.launcher3.anim.AnimatorSetBuilder; import com.android.launcher3.touch.AbstractStateChangeTouchController; -import com.android.launcher3.touch.SwipeDetector; +import com.android.launcher3.touch.SingleAxisSwipeDetector; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction; import com.android.quickstep.SysUINavigationMode; @@ -50,6 +51,7 @@ import com.android.quickstep.SysUINavigationMode.Mode; import com.android.quickstep.SystemUiProxy; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.TaskView; +import com.android.systemui.shared.system.ActivityManagerWrapper; /** * Handles quick switching to a recent task from the home screen. @@ -59,10 +61,10 @@ public class QuickSwitchTouchController extends AbstractStateChangeTouchControll private @Nullable TaskView mTaskToLaunch; public QuickSwitchTouchController(Launcher launcher) { - this(launcher, SwipeDetector.HORIZONTAL); + this(launcher, SingleAxisSwipeDetector.HORIZONTAL); } - protected QuickSwitchTouchController(Launcher l, SwipeDetector.Direction dir) { + protected QuickSwitchTouchController(Launcher l, SingleAxisSwipeDetector.Direction dir) { super(l, dir); } @@ -94,6 +96,8 @@ public class QuickSwitchTouchController extends AbstractStateChangeTouchControll super.onDragStart(start); mStartContainerType = LauncherLogProto.ContainerType.NAVBAR; mTaskToLaunch = mLauncher.getOverviewPanel().getTaskViewAt(0); + ActivityManagerWrapper.getInstance() + .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS); } @Override diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java index 00e4f58e92..ad02de1091 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java @@ -19,6 +19,9 @@ import static com.android.launcher3.AbstractFloatingView.TYPE_ACCESSIBLE; import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity; import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS; +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.util.DefaultDisplay.getSingleFrameMs; import android.animation.Animator; @@ -32,7 +35,8 @@ import com.android.launcher3.LauncherAnimUtils; import com.android.launcher3.Utilities; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.Interpolators; -import com.android.launcher3.touch.SwipeDetector; +import com.android.launcher3.touch.BaseSwipeDetector; +import com.android.launcher3.touch.SingleAxisSwipeDetector; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; import com.android.launcher3.util.FlingBlockCheck; import com.android.launcher3.util.PendingAnimation; @@ -46,15 +50,14 @@ import com.android.quickstep.views.TaskView; * Touch controller for handling task view card swipes */ public abstract class TaskViewTouchController - extends AnimatorListenerAdapter implements TouchController, SwipeDetector.Listener { - - private static final String TAG = "OverviewSwipeController"; + extends AnimatorListenerAdapter implements TouchController, + SingleAxisSwipeDetector.Listener { // Progress after which the transition is assumed to be a success in case user does not fling public static final float SUCCESS_TRANSITION_PROGRESS = 0.5f; protected final T mActivity; - private final SwipeDetector mDetector; + private final SingleAxisSwipeDetector mDetector; private final RecentsView mRecentsView; private final int[] mTempCords = new int[2]; @@ -74,7 +77,7 @@ public abstract class TaskViewTouchController public TaskViewTouchController(T activity) { mActivity = activity; mRecentsView = activity.getOverviewPanel(); - mDetector = new SwipeDetector(activity, this, SwipeDetector.VERTICAL); + mDetector = new SingleAxisSwipeDetector(activity, this, SingleAxisSwipeDetector.VERTICAL); } private boolean canInterceptTouch() { @@ -113,7 +116,7 @@ public abstract class TaskViewTouchController int directionsToDetectScroll = 0; boolean ignoreSlopWhenSettling = false; if (mCurrentAnimation != null) { - directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH; + directionsToDetectScroll = DIRECTION_BOTH; ignoreSlopWhenSettling = true; } else { mTaskBeingDragged = null; @@ -126,12 +129,12 @@ public abstract class TaskViewTouchController if (!SysUINavigationMode.getMode(mActivity).hasGestures) { // Don't allow swipe down to open if we don't support swipe up // to enter overview. - directionsToDetectScroll = SwipeDetector.DIRECTION_POSITIVE; + directionsToDetectScroll = DIRECTION_POSITIVE; } else { // The task can be dragged up to dismiss it, // and down to open if it's the current page. directionsToDetectScroll = i == mRecentsView.getCurrentPage() - ? SwipeDetector.DIRECTION_BOTH : SwipeDetector.DIRECTION_POSITIVE; + ? DIRECTION_BOTH : DIRECTION_POSITIVE; } break; } @@ -165,8 +168,8 @@ public abstract class TaskViewTouchController return; } int scrollDirections = mDetector.getScrollDirections(); - if (goingUp && ((scrollDirections & SwipeDetector.DIRECTION_POSITIVE) == 0) - || !goingUp && ((scrollDirections & SwipeDetector.DIRECTION_NEGATIVE) == 0)) { + if (goingUp && ((scrollDirections & DIRECTION_POSITIVE) == 0) + || !goingUp && ((scrollDirections & DIRECTION_NEGATIVE) == 0)) { // Trying to re-init in an unsupported direction. return; } @@ -243,7 +246,8 @@ public abstract class TaskViewTouchController } @Override - public void onDragEnd(float velocity, boolean fling) { + public void onDragEnd(float velocity) { + boolean fling = mDetector.isFling(velocity); final boolean goingToEnd; final int logAction; boolean blockedFling = fling && mFlingBlockCheck.isBlocked(); @@ -260,7 +264,7 @@ public abstract class TaskViewTouchController logAction = Touch.SWIPE; goingToEnd = interpolatedProgress > SUCCESS_TRANSITION_PROGRESS; } - long animationDuration = SwipeDetector.calculateDuration( + long animationDuration = BaseSwipeDetector.calculateDuration( velocity, goingToEnd ? (1 - progress) : progress); if (blockedFling && !goingToEnd) { animationDuration *= LauncherAnimUtils.blockedFlingDurationFactor(velocity); diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TransposedQuickSwitchTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TransposedQuickSwitchTouchController.java index f1e4041eb2..0ed529184e 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TransposedQuickSwitchTouchController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TransposedQuickSwitchTouchController.java @@ -17,12 +17,12 @@ package com.android.launcher3.uioverrides.touchcontrollers; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; -import com.android.launcher3.touch.SwipeDetector; +import com.android.launcher3.touch.SingleAxisSwipeDetector; public class TransposedQuickSwitchTouchController extends QuickSwitchTouchController { public TransposedQuickSwitchTouchController(Launcher launcher) { - super(launcher, SwipeDetector.VERTICAL); + super(launcher, SingleAxisSwipeDetector.VERTICAL); } @Override diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java index 8a11ac80aa..59b117f2e8 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java @@ -70,7 +70,7 @@ final class AppToOverviewAnimationProvider imple activity.getOverviewPanel().showCurrentTask(mTargetTaskId); AbstractFloatingView.closeAllOpenViews(activity, wasVisible); BaseActivityInterface.AnimationFactory factory = - mHelper.prepareRecentsUI(activity, wasVisible, + mHelper.prepareRecentsUI(wasVisible, false /* animate activity */, (controller) -> { controller.dispatchOnStart(); ValueAnimator anim = controller.getAnimationPlayer() @@ -102,7 +102,7 @@ final class AppToOverviewAnimationProvider imple anim.addListener(new AnimationSuccessListener() { @Override public void onAnimationSuccess(Animator animator) { - mHelper.onSwipeUpToRecentsComplete(mActivity); + mHelper.onSwipeUpToRecentsComplete(); if (mRecentsView != null) { mRecentsView.animateUpRunningTaskIconScale(); } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java index e1e994c6a6..4f50e33b00 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java @@ -15,20 +15,15 @@ */ package com.android.quickstep; -import static android.os.VibrationEffect.EFFECT_CLICK; -import static android.os.VibrationEffect.createPredefined; - -import static com.android.launcher3.Utilities.postAsyncCallback; import static com.android.launcher3.anim.Interpolators.ACCEL_1_5; import static com.android.launcher3.anim.Interpolators.DEACCEL; import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; -import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; +import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC; import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION; import android.animation.Animator; import android.annotation.TargetApi; -import android.app.ActivityManager.RunningTaskInfo; import android.content.Context; import android.content.Intent; import android.graphics.Point; @@ -36,11 +31,6 @@ import android.graphics.PointF; import android.graphics.Rect; import android.graphics.RectF; import android.os.Build; -import android.os.Handler; -import android.os.Looper; -import android.os.VibrationEffect; -import android.os.Vibrator; -import android.provider.Settings; import android.view.MotionEvent; import android.view.View; import android.view.animation.Interpolator; @@ -55,9 +45,9 @@ import com.android.launcher3.Utilities; import com.android.launcher3.anim.AnimationSuccessListener; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.graphics.RotationMode; +import com.android.launcher3.util.VibratorWrapper; import com.android.launcher3.views.FloatingIconView; import com.android.quickstep.BaseActivityInterface.HomeAnimationFactory; -import com.android.quickstep.SysUINavigationMode.Mode; import com.android.quickstep.util.ActiveGestureLog; import com.android.quickstep.util.ActivityInitListener; import com.android.quickstep.util.AppWindowAnimationHelper; @@ -96,17 +86,14 @@ public abstract class BaseSwipeUpHandler mActivityInterface; - protected final RecentsModel mRecentsModel; - protected final int mRunningTaskId; + protected final InputConsumerController mInputConsumer; protected final AppWindowAnimationHelper mAppWindowAnimationHelper; protected final TransformParams mTransformParams = new TransformParams(); - private final Vibrator mVibrator; - protected final Mode mMode; - // Shift in the range of [0, 1]. // 0 => preview snapShot is completely visible, and hotseat is completely translated down // 1 => preview snapShot is completely aligned with the recents view and hotseat is completely @@ -114,7 +101,6 @@ public abstract class BaseSwipeUpHandler mStateCallback.setState(stateFlag)); - } - } - protected void performHapticFeedback() { - if (!mVibrator.hasVibrator()) { - return; - } - if (Settings.System.getInt( - mContext.getContentResolver(), Settings.System.HAPTIC_FEEDBACK_ENABLED, 0) == 0) { - return; - } - - VibrationEffect effect = createPredefined(EFFECT_CLICK); - if (effect == null) { - return; - } - UI_HELPER_EXECUTOR.execute(() -> mVibrator.vibrate(effect)); + VibratorWrapper.INSTANCE.get(mContext).vibrate(OVERVIEW_HAPTIC); } public Consumer getRecentsViewDispatcher(RotationMode rotationMode) { @@ -246,14 +208,14 @@ public abstract class BaseSwipeUpHandler { resultCallback.accept(success); if (!success) { - mActivityInterface.onLaunchTaskFailed(mActivity); + mActivityInterface.onLaunchTaskFailed(); nextTask.notifyTaskLaunchFailed(TAG); } else { - mActivityInterface.onLaunchTaskSuccess(mActivity); + mActivityInterface.onLaunchTaskSuccess(); } - }, mMainThreadHandler); + }, MAIN_EXECUTOR.getHandler()); } - setStateOnUiThread(successStateFlag); + mStateCallback.setStateOnUiThread(successStateFlag); } mCanceled = false; mFinishingRecentsAnimationForNewTaskId = -1; @@ -288,7 +250,8 @@ public abstract class BaseSwipeUpHandler callback) { + RecentsActivity activity = getCreatedActivity(); if (activityVisible) { return (transitionLength) -> { }; } @@ -176,8 +183,9 @@ public final class FallbackActivityInterface implements @Override public ActivityInitListener createActivityInitListener( - BiPredicate onInitListener) { - return new ActivityInitListener(onInitListener, RecentsActivity.ACTIVITY_TRACKER); + Predicate onInitListener) { + return new ActivityInitListener<>((activity, alreadyOnHome) -> + onInitListener.test(alreadyOnHome), RecentsActivity.ACTIVITY_TRACKER); } @Nullable @@ -228,13 +236,21 @@ public final class FallbackActivityInterface implements } @Override - public void onLaunchTaskFailed(RecentsActivity activity) { + public void onLaunchTaskFailed() { // TODO: probably go back to overview instead. + RecentsActivity activity = getCreatedActivity(); + if (activity == null) { + return; + } activity.getOverviewPanel().startHome(); } @Override - public void onLaunchTaskSuccess(RecentsActivity activity) { + public void onLaunchTaskSuccess() { + RecentsActivity activity = getCreatedActivity(); + if (activity == null) { + return; + } activity.onTaskLaunched(); } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/FallbackNoButtonInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java similarity index 73% rename from quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/FallbackNoButtonInputConsumer.java rename to quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java index 370b48793c..24f247b2e0 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/FallbackNoButtonInputConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java @@ -13,21 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.quickstep.inputconsumers; +package com.android.quickstep; +import static com.android.quickstep.GestureState.GestureEndTarget.HOME; +import static com.android.quickstep.GestureState.GestureEndTarget.LAST_TASK; +import static com.android.quickstep.GestureState.GestureEndTarget.NEW_TASK; +import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS; import static com.android.quickstep.MultiStateCallback.DEBUG_STATES; import static com.android.quickstep.RecentsActivity.EXTRA_TASK_ID; import static com.android.quickstep.RecentsActivity.EXTRA_THUMBNAIL; -import static com.android.quickstep.WindowTransformSwipeHandler.MIN_PROGRESS_FOR_OVERVIEW; -import static com.android.quickstep.inputconsumers.FallbackNoButtonInputConsumer.GestureEndTarget.HOME; -import static com.android.quickstep.inputconsumers.FallbackNoButtonInputConsumer.GestureEndTarget.LAST_TASK; -import static com.android.quickstep.inputconsumers.FallbackNoButtonInputConsumer.GestureEndTarget.NEW_TASK; -import static com.android.quickstep.inputconsumers.FallbackNoButtonInputConsumer.GestureEndTarget.RECENTS; +import static com.android.quickstep.LauncherSwipeHandler.MIN_PROGRESS_FOR_OVERVIEW; import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD; import android.animation.Animator; import android.animation.AnimatorSet; -import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityOptions; import android.content.Context; import android.content.Intent; @@ -35,32 +34,25 @@ import android.graphics.PointF; import android.graphics.RectF; import android.os.Bundle; +import android.util.ArrayMap; import com.android.launcher3.R; import com.android.launcher3.anim.AnimationSuccessListener; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.util.ObjectWrapper; import com.android.quickstep.BaseActivityInterface.HomeAnimationFactory; -import com.android.quickstep.AnimatedFloat; -import com.android.quickstep.BaseSwipeUpHandler; -import com.android.quickstep.GestureState; -import com.android.quickstep.InputConsumer; -import com.android.quickstep.MultiStateCallback; -import com.android.quickstep.OverviewComponentObserver; -import com.android.quickstep.RecentsActivity; -import com.android.quickstep.RecentsAnimationController; -import com.android.quickstep.RecentsModel; -import com.android.quickstep.SwipeSharedState; +import com.android.quickstep.GestureState.GestureEndTarget; import com.android.quickstep.fallback.FallbackRecentsView; import com.android.quickstep.util.RectFSpringAnim; -import com.android.quickstep.RecentsAnimationTargets; import com.android.quickstep.views.TaskView; import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.ActivityOptionsCompat; import com.android.systemui.shared.system.InputConsumerController; -public class FallbackNoButtonInputConsumer extends - BaseSwipeUpHandler { +/** + * Handles the navigation gestures when a 3rd party launcher is the default home activity. + */ +public class FallbackSwipeHandler extends BaseSwipeUpHandler { private static final String[] STATE_NAMES = DEBUG_STATES ? new String[5] : null; @@ -83,51 +75,41 @@ public class FallbackNoButtonInputConsumer extends private static final int STATE_APP_CONTROLLER_RECEIVED = getFlagForIndex(4, "STATE_APP_CONTROLLER_RECEIVED"); - public enum GestureEndTarget { - HOME(3, 100, 1), - RECENTS(1, 300, 0), - LAST_TASK(0, 150, 1), - NEW_TASK(0, 150, 1); - + public static class EndTargetAnimationParams { private final float mEndProgress; private final long mDurationMultiplier; private final float mLauncherAlpha; - GestureEndTarget(float endProgress, long durationMultiplier, float launcherAlpha) { + EndTargetAnimationParams(float endProgress, long durationMultiplier, float launcherAlpha) { mEndProgress = endProgress; mDurationMultiplier = durationMultiplier; mLauncherAlpha = launcherAlpha; } } + private static ArrayMap + mEndTargetAnimationParams = new ArrayMap(); private final AnimatedFloat mLauncherAlpha = new AnimatedFloat(this::onLauncherAlphaChanged); private boolean mIsMotionPaused = false; - private GestureEndTarget mEndTarget; private final boolean mInQuickSwitchMode; private final boolean mContinuingLastGesture; private final boolean mRunningOverHome; private final boolean mSwipeUpOverHome; - private final RunningTaskInfo mRunningTaskInfo; - private final PointF mEndVelocityPxPerMs = new PointF(0, 0.5f); private RunningWindowAnim mFinishAnimation; - public FallbackNoButtonInputConsumer(Context context, GestureState gestureState, - OverviewComponentObserver overviewComponentObserver, - RunningTaskInfo runningTaskInfo, RecentsModel recentsModel, - InputConsumerController inputConsumer, + public FallbackSwipeHandler(Context context, RecentsAnimationDeviceState deviceState, + GestureState gestureState, InputConsumerController inputConsumer, boolean isLikelyToStartNewTask, boolean continuingLastGesture) { - super(context, gestureState, overviewComponentObserver, recentsModel, inputConsumer, - runningTaskInfo.id); + super(context, deviceState, gestureState, inputConsumer); mLauncherAlpha.value = 1; - mRunningTaskInfo = runningTaskInfo; mInQuickSwitchMode = isLikelyToStartNewTask || continuingLastGesture; mContinuingLastGesture = continuingLastGesture; - mRunningOverHome = ActivityManagerWrapper.isHomeTask(runningTaskInfo); + mRunningOverHome = ActivityManagerWrapper.isHomeTask(mGestureState.getRunningTask()); mSwipeUpOverHome = mRunningOverHome && !mInQuickSwitchMode; if (mSwipeUpOverHome) { @@ -136,40 +118,46 @@ public class FallbackNoButtonInputConsumer extends mAppWindowAnimationHelper.setBaseAlphaCallback((t, a) -> mLauncherAlpha.value); } + // Going home has an extra long progress to ensure that it animates into the screen + mEndTargetAnimationParams.put(HOME, new EndTargetAnimationParams(3, 100, 1)); + mEndTargetAnimationParams.put(RECENTS, new EndTargetAnimationParams(1, 300, 0)); + mEndTargetAnimationParams.put(LAST_TASK, new EndTargetAnimationParams(0, 150, 1)); + mEndTargetAnimationParams.put(NEW_TASK, new EndTargetAnimationParams(0, 150, 1)); + initStateCallbacks(); } private void initStateCallbacks() { mStateCallback = new MultiStateCallback(STATE_NAMES); - mStateCallback.addCallback(STATE_HANDLER_INVALIDATED, + mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED, this::onHandlerInvalidated); - mStateCallback.addCallback(STATE_RECENTS_PRESENT | STATE_HANDLER_INVALIDATED, + mStateCallback.runOnceAtState(STATE_RECENTS_PRESENT | STATE_HANDLER_INVALIDATED, this::onHandlerInvalidatedWithRecents); - mStateCallback.addCallback(STATE_GESTURE_CANCELLED | STATE_APP_CONTROLLER_RECEIVED, + mStateCallback.runOnceAtState(STATE_GESTURE_CANCELLED | STATE_APP_CONTROLLER_RECEIVED, this::finishAnimationTargetSetAnimationComplete); if (mInQuickSwitchMode) { - mStateCallback.addCallback(STATE_GESTURE_COMPLETED | STATE_APP_CONTROLLER_RECEIVED + mStateCallback.runOnceAtState(STATE_GESTURE_COMPLETED | STATE_APP_CONTROLLER_RECEIVED | STATE_RECENTS_PRESENT, this::finishAnimationTargetSet); } else { - mStateCallback.addCallback(STATE_GESTURE_COMPLETED | STATE_APP_CONTROLLER_RECEIVED, + mStateCallback.runOnceAtState(STATE_GESTURE_COMPLETED | STATE_APP_CONTROLLER_RECEIVED, this::finishAnimationTargetSet); } } private void onLauncherAlphaChanged() { - if (mRecentsAnimationTargets != null && mEndTarget == null) { + if (mRecentsAnimationTargets != null && mGestureState.getEndTarget() == null) { applyTransformUnchecked(); } } @Override - protected boolean onActivityInit(final RecentsActivity activity, Boolean alreadyOnHome) { - mActivity = activity; - mRecentsView = activity.getOverviewPanel(); + protected boolean onActivityInit(Boolean alreadyOnHome) { + mActivity = mActivityInterface.getCreatedActivity(); + mRecentsView = mActivity.getOverviewPanel(); linkRecentsViewScroll(); mRecentsView.setDisallowScrollToClearAll(true); mRecentsView.getClearAllButton().setVisibilityAlpha(0); @@ -178,12 +166,12 @@ public class FallbackNoButtonInputConsumer extends if (!mContinuingLastGesture) { if (mRunningOverHome) { - mRecentsView.onGestureAnimationStart(mRunningTaskInfo); + mRecentsView.onGestureAnimationStart(mGestureState.getRunningTask()); } else { - mRecentsView.onGestureAnimationStart(mRunningTaskId); + mRecentsView.onGestureAnimationStart(mGestureState.getRunningTaskId()); } } - setStateOnUiThread(STATE_RECENTS_PRESENT); + mStateCallback.setStateOnUiThread(STATE_RECENTS_PRESENT); return true; } @@ -226,9 +214,9 @@ public class FallbackNoButtonInputConsumer extends @Override public Intent getLaunchIntent() { if (mInQuickSwitchMode || mSwipeUpOverHome) { - return mOverviewComponentObserver.getOverviewIntent(); + return mGestureState.getOverviewIntent(); } else { - return mOverviewComponentObserver.getHomeIntent(); + return mGestureState.getHomeIntent(); } } @@ -247,8 +235,8 @@ public class FallbackNoButtonInputConsumer extends @Override public void onGestureCancelled() { updateDisplacement(0); - mEndTarget = LAST_TASK; - setStateOnUiThread(STATE_GESTURE_CANCELLED); + mGestureState.setEndTarget(LAST_TASK); + mStateCallback.setStateOnUiThread(STATE_GESTURE_CANCELLED); } @Override @@ -256,28 +244,29 @@ public class FallbackNoButtonInputConsumer extends mEndVelocityPxPerMs.set(0, velocity.y / 1000); if (mInQuickSwitchMode) { // For now set it to non-null, it will be reset before starting the animation - mEndTarget = LAST_TASK; + mGestureState.setEndTarget(LAST_TASK); } else { float flingThreshold = mContext.getResources() .getDimension(R.dimen.quickstep_fling_threshold_velocity); boolean isFling = Math.abs(endVelocity) > flingThreshold; if (isFling) { - mEndTarget = endVelocity < 0 ? HOME : LAST_TASK; + mGestureState.setEndTarget(endVelocity < 0 ? HOME : LAST_TASK); } else if (mIsMotionPaused) { - mEndTarget = RECENTS; + mGestureState.setEndTarget(RECENTS); } else { - mEndTarget = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW ? HOME : LAST_TASK; + mGestureState.setEndTarget(mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW + ? HOME + : LAST_TASK); } } - setStateOnUiThread(STATE_GESTURE_COMPLETED); + mStateCallback.setStateOnUiThread(STATE_GESTURE_COMPLETED); } @Override - public void onConsumerAboutToBeSwitched(SwipeSharedState sharedState) { - if (mInQuickSwitchMode && mEndTarget != null) { - sharedState.canGestureBeContinued = true; - sharedState.goingToLauncher = false; + public void onConsumerAboutToBeSwitched() { + if (mInQuickSwitchMode && mGestureState.getEndTarget() != null) { + mGestureState.setEndTarget(HOME); mCanceled = true; mCurrentShift.cancelAnimation(); @@ -293,12 +282,12 @@ public class FallbackNoButtonInputConsumer extends ? newRunningTaskView.getTask().key.id : -1; mRecentsView.setCurrentTask(newRunningTaskId); - sharedState.setRecentsAnimationFinishInterrupted(newRunningTaskId); + mGestureState.setFinishingRecentsAnimationTaskId(newRunningTaskId); } mRecentsView.setOnScrollChangeListener(null); } } else { - setStateOnUiThread(STATE_HANDLER_INVALIDATED); + mStateCallback.setStateOnUiThread(STATE_HANDLER_INVALIDATED); } } @@ -319,12 +308,12 @@ public class FallbackNoButtonInputConsumer extends } private void finishAnimationTargetSetAnimationComplete() { - switch (mEndTarget) { + switch (mGestureState.getEndTarget()) { case HOME: { if (mSwipeUpOverHome) { mRecentsAnimationController.finish(false, null, false); // Send a home intent to clear the task stack - mContext.startActivity(mOverviewComponentObserver.getHomeIntent()); + mContext.startActivity(mGestureState.getHomeIntent()); } else { mRecentsAnimationController.finish(true, null, true); } @@ -339,7 +328,8 @@ public class FallbackNoButtonInputConsumer extends break; } - ThumbnailData thumbnail = mRecentsAnimationController.screenshotTask(mRunningTaskId); + final int runningTaskId = mGestureState.getRunningTaskId(); + ThumbnailData thumbnail = mRecentsAnimationController.screenshotTask(runningTaskId); mRecentsAnimationController.setDeferCancelUntilNextTransition(true /* defer */, false /* screenshot */); @@ -348,9 +338,9 @@ public class FallbackNoButtonInputConsumer extends Bundle extras = new Bundle(); extras.putBinder(EXTRA_THUMBNAIL, new ObjectWrapper<>(thumbnail)); - extras.putInt(EXTRA_TASK_ID, mRunningTaskId); + extras.putInt(EXTRA_TASK_ID, runningTaskId); - Intent intent = new Intent(mOverviewComponentObserver.getOverviewIntent()) + Intent intent = new Intent(mGestureState.getOverviewIntent()) .putExtras(extras); mContext.startActivity(intent, options.toBundle()); mRecentsAnimationController.cleanupScreenshot(); @@ -362,7 +352,7 @@ public class FallbackNoButtonInputConsumer extends } } - setStateOnUiThread(STATE_HANDLER_INVALIDATED); + mStateCallback.setStateOnUiThread(STATE_HANDLER_INVALIDATED); } private void finishAnimationTargetSet() { @@ -370,17 +360,20 @@ public class FallbackNoButtonInputConsumer extends // Recalculate the end target, some views might have been initialized after // gesture has ended. if (mRecentsView == null || !hasTargets()) { - mEndTarget = LAST_TASK; + mGestureState.setEndTarget(LAST_TASK); } else { final int runningTaskIndex = mRecentsView.getRunningTaskIndex(); final int taskToLaunch = mRecentsView.getNextPage(); - mEndTarget = (runningTaskIndex >= 0 && taskToLaunch != runningTaskIndex) - ? NEW_TASK : LAST_TASK; + mGestureState.setEndTarget( + (runningTaskIndex >= 0 && taskToLaunch != runningTaskIndex) + ? NEW_TASK + : LAST_TASK); } } - float endProgress = mEndTarget.mEndProgress; - long duration = (long) (mEndTarget.mDurationMultiplier * + EndTargetAnimationParams params = mEndTargetAnimationParams.get(mGestureState.getEndTarget()); + float endProgress = params.mEndProgress; + long duration = (long) (params.mDurationMultiplier * Math.abs(endProgress - mCurrentShift.value)); if (mRecentsView != null) { duration = Math.max(duration, mRecentsView.getScroller().getDuration()); @@ -395,7 +388,7 @@ public class FallbackNoButtonInputConsumer extends } }; - if (mEndTarget == HOME && !mRunningOverHome) { + if (mGestureState.getEndTarget() == HOME && !mRunningOverHome) { RectFSpringAnim anim = createWindowAnimationToHome(mCurrentShift.value, duration); anim.addAnimatorListener(endListener); anim.start(mEndVelocityPxPerMs); @@ -404,7 +397,7 @@ public class FallbackNoButtonInputConsumer extends AnimatorSet anim = new AnimatorSet(); anim.play(mLauncherAlpha.animateToValue( - mLauncherAlpha.value, mEndTarget.mLauncherAlpha)); + mLauncherAlpha.value, params.mLauncherAlpha)); anim.play(mCurrentShift.animateToValue(mCurrentShift.value, endProgress)); anim.setDuration(duration); @@ -429,13 +422,14 @@ public class FallbackNoButtonInputConsumer extends } applyTransformUnchecked(); - setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED); + mStateCallback.setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED); } @Override public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) { + super.onRecentsAnimationCanceled(thumbnailData); mRecentsView.setRecentsAnimationTargets(null, null); - setStateOnUiThread(STATE_HANDLER_INVALIDATED); + mStateCallback.setStateOnUiThread(STATE_HANDLER_INVALIDATED); } /** diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java index f6b3654e69..844152be28 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java @@ -29,7 +29,7 @@ import static com.android.launcher3.anim.Interpolators.ACCEL_2; import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL; import static com.android.launcher3.anim.Interpolators.INSTANT; import static com.android.launcher3.anim.Interpolators.LINEAR; -import static com.android.quickstep.WindowTransformSwipeHandler.RECENTS_ATTACH_DURATION; +import static com.android.quickstep.LauncherSwipeHandler.RECENTS_ATTACH_DURATION; import android.animation.Animator; import android.animation.AnimatorSet; @@ -47,20 +47,23 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.UiThread; +import com.android.launcher3.BaseQuickstepLauncher; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; -import com.android.launcher3.LauncherInitListenerEx; +import com.android.launcher3.LauncherInitListener; import com.android.launcher3.LauncherState; import com.android.launcher3.LauncherStateManager; import com.android.launcher3.allapps.DiscoveryBounce; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.AnimatorSetBuilder; -import com.android.launcher3.uioverrides.states.OverviewState; +import com.android.launcher3.appprediction.PredictionUiStateManager; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.views.FloatingIconView; import com.android.quickstep.SysUINavigationMode.Mode; import com.android.quickstep.util.ActivityInitListener; import com.android.quickstep.util.LayoutUtils; +import com.android.quickstep.util.ShelfPeekAnim; +import com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState; import com.android.quickstep.util.StaggeredWorkspaceAnim; import com.android.quickstep.views.LauncherRecentsView; import com.android.quickstep.views.RecentsView; @@ -69,8 +72,8 @@ import com.android.systemui.plugins.shared.LauncherOverlayManager; import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; -import java.util.function.BiPredicate; import java.util.function.Consumer; +import java.util.function.Predicate; /** * {@link BaseActivityInterface} for the in-launcher recents. @@ -92,50 +95,65 @@ public final class LauncherActivityInterface implements BaseActivityInterface callback) { - final LauncherState startState = activity.getStateManager().getState(); + BaseQuickstepLauncher launcher = getCreatedActivity(); + final LauncherState startState = launcher.getStateManager().getState(); LauncherState resetState = startState; if (startState.disableRestore) { - resetState = activity.getStateManager().getRestState(); + resetState = launcher.getStateManager().getRestState(); } - activity.getStateManager().setRestState(resetState); + launcher.getStateManager().setRestState(resetState); final LauncherState fromState = animateActivity ? BACKGROUND_APP : OVERVIEW; - activity.getStateManager().goToState(fromState, false); + launcher.getStateManager().goToState(fromState, false); // Since all apps is not visible, we can safely reset the scroll position. // This ensures then the next swipe up to all-apps starts from scroll 0. - activity.getAppsView().reset(false /* animate */); + launcher.getAppsView().reset(false /* animate */); return new AnimationFactory() { - private ShelfAnimState mShelfState; + private final ShelfPeekAnim mShelfAnim = launcher.getShelfPeekAnim(); private boolean mIsAttachedToWindow; @Override public void createActivityInterface(long transitionLength) { - createActivityInterfaceInternal(activity, fromState, transitionLength, callback); + createActivityInterfaceInternal(launcher, fromState, transitionLength, callback); // Creating the activity controller animation sometimes reapplies the launcher state // (because we set the animation as the current state animation), so we reapply the // attached state here as well to ensure recents is shown/hidden appropriately. - if (SysUINavigationMode.getMode(activity) == Mode.NO_BUTTON) { + if (SysUINavigationMode.getMode(launcher) == Mode.NO_BUTTON) { setRecentsAttachedToAppWindow(mIsAttachedToWindow, false); } } @@ -233,36 +252,13 @@ public final class LauncherActivityInterface implements BaseActivityInterface onInitListener) { - return new LauncherInitListenerEx(onInitListener); + public ActivityInitListener createActivityInitListener(Predicate onInitListener) { + return new LauncherInitListener((activity, alreadyOnHome) -> + onInitListener.test(alreadyOnHome)); } @Nullable @Override - public Launcher getCreatedActivity() { - return Launcher.ACTIVITY_TRACKER.getCreatedActivity(); + public BaseQuickstepLauncher getCreatedActivity() { + return BaseQuickstepLauncher.ACTIVITY_TRACKER.getCreatedActivity(); } @Nullable @@ -469,12 +465,20 @@ public final class LauncherActivityInterface implements BaseActivityInterface +public class LauncherSwipeHandler extends BaseSwipeUpHandler implements OnApplyWindowInsetsListener { - private static final String TAG = WindowTransformSwipeHandler.class.getSimpleName(); + private static final String TAG = LauncherSwipeHandler.class.getSimpleName(); private static final String[] STATE_NAMES = DEBUG_STATES ? new String[16] : null; @@ -138,42 +144,6 @@ public class WindowTransformSwipeHandler private static final int LAUNCHER_UI_STATES = STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_LAUNCHER_STARTED; - public enum GestureEndTarget { - HOME(1, STATE_SCALED_CONTROLLER_HOME | STATE_CAPTURE_SCREENSHOT, true, false, - ContainerType.WORKSPACE, false), - - RECENTS(1, STATE_SCALED_CONTROLLER_RECENTS | STATE_CAPTURE_SCREENSHOT - | STATE_SCREENSHOT_VIEW_SHOWN, true, false, ContainerType.TASKSWITCHER, true), - - NEW_TASK(0, STATE_START_NEW_TASK | STATE_CAPTURE_SCREENSHOT, false, true, - ContainerType.APP, true), - - LAST_TASK(0, STATE_RESUME_LAST_TASK, false, true, ContainerType.APP, false); - - GestureEndTarget(float endShift, int endState, boolean isLauncher, boolean canBeContinued, - int containerType, boolean recentsAttachedToAppWindow) { - this.endShift = endShift; - this.endState = endState; - this.isLauncher = isLauncher; - this.canBeContinued = canBeContinued; - this.containerType = containerType; - this.recentsAttachedToAppWindow = recentsAttachedToAppWindow; - } - - /** 0 is app, 1 is overview */ - public final float endShift; - /** The state to apply when we reach this final target */ - public final int endState; - /** Whether the target is in the launcher activity */ - public final boolean isLauncher; - /** Whether the user can start a new gesture while this one is finishing */ - public final boolean canBeContinued; - /** Used to log where the user ended up after the gesture ends */ - public final int containerType; - /** Whether RecentsView should be attached to the window as we animate to this target */ - public final boolean recentsAttachedToAppWindow; - } - public static final long MAX_SWIPE_DURATION = 350; public static final long MIN_SWIPE_DURATION = 80; public static final long MIN_OVERSHOOT_DURATION = 120; @@ -183,7 +153,6 @@ public class WindowTransformSwipeHandler Math.min(1 / MIN_PROGRESS_FOR_OVERVIEW, 1 / (1 - MIN_PROGRESS_FOR_OVERVIEW)); private static final String SCREENSHOT_CAPTURED_EVT = "ScreenshotCaptured"; - private static final long SHELF_ANIM_DURATION = 240; public static final long RECENTS_ATTACH_DURATION = 300; /** @@ -191,10 +160,8 @@ public class WindowTransformSwipeHandler */ private static final int LOG_NO_OP_PAGE_INDEX = -1; - private final RecentsAnimationDeviceState mDeviceState; - private final GestureState mGestureState; + private final TaskAnimationManager mTaskAnimationManager; - private GestureEndTarget mGestureEndTarget; // Either RectFSpringAnim (if animating home) or ObjectAnimator (from mCurrentShift) otherwise private RunningWindowAnim mRunningWindowAnim; private boolean mIsShelfPeeking; @@ -210,8 +177,6 @@ public class WindowTransformSwipeHandler private boolean mHasLauncherTransitionControllerStarted; private AnimationFactory mAnimationFactory = (t) -> { }; - private LiveTileOverlay mLiveTileOverlay = new LiveTileOverlay(); - private boolean mLiveTileOverlayAttached = false; private boolean mWasLauncherAlreadyVisible; @@ -225,13 +190,14 @@ public class WindowTransformSwipeHandler private final long mTouchTimeMs; private long mLauncherFrameDrawnTime; - public WindowTransformSwipeHandler(Context context, RecentsAnimationDeviceState deviceState, - GestureState gestureState, RunningTaskInfo runningTaskInfo, long touchTimeMs, - OverviewComponentObserver overviewComponentObserver, boolean continuingLastGesture, - InputConsumerController inputConsumer, RecentsModel recentsModel) { - super(context, gestureState, overviewComponentObserver, recentsModel, inputConsumer, runningTaskInfo.id); - mDeviceState = deviceState; - mGestureState = gestureState; + private final Runnable mOnDeferredActivityLaunch = this::onDeferredActivityLaunch; + + public LauncherSwipeHandler(Context context, RecentsAnimationDeviceState deviceState, + TaskAnimationManager taskAnimationManager, GestureState gestureState, + long touchTimeMs, boolean continuingLastGesture, + InputConsumerController inputConsumer) { + super(context, deviceState, gestureState, inputConsumer); + mTaskAnimationManager = taskAnimationManager; mTouchTimeMs = touchTimeMs; mContinuingLastGesture = continuingLastGesture; initStateCallbacks(); @@ -240,62 +206,65 @@ public class WindowTransformSwipeHandler private void initStateCallbacks() { mStateCallback = new MultiStateCallback(STATE_NAMES); - mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_GESTURE_STARTED, + mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_GESTURE_STARTED, this::onLauncherPresentAndGestureStarted); - mStateCallback.addCallback(STATE_LAUNCHER_DRAWN | STATE_GESTURE_STARTED, + mStateCallback.runOnceAtState(STATE_LAUNCHER_DRAWN | STATE_GESTURE_STARTED, this::initializeLauncherAnimationController); - mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN, + mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN, this::launcherFrameDrawn); - mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_STARTED + mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_STARTED | STATE_GESTURE_CANCELLED, this::resetStateForAnimationCancel); - mStateCallback.addCallback(STATE_LAUNCHER_STARTED | STATE_APP_CONTROLLER_RECEIVED, + mStateCallback.runOnceAtState(STATE_LAUNCHER_STARTED | STATE_APP_CONTROLLER_RECEIVED, this::sendRemoteAnimationsToAnimationFactory); - mStateCallback.addCallback(STATE_RESUME_LAST_TASK | STATE_APP_CONTROLLER_RECEIVED, + mStateCallback.runOnceAtState(STATE_RESUME_LAST_TASK | STATE_APP_CONTROLLER_RECEIVED, this::resumeLastTask); - mStateCallback.addCallback(STATE_START_NEW_TASK | STATE_SCREENSHOT_CAPTURED, + mStateCallback.runOnceAtState(STATE_START_NEW_TASK | STATE_SCREENSHOT_CAPTURED, this::startNewTask); - mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED + mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED | STATE_LAUNCHER_DRAWN | STATE_CAPTURE_SCREENSHOT, this::switchToScreenshot); - mStateCallback.addCallback(STATE_SCREENSHOT_CAPTURED | STATE_GESTURE_COMPLETED + mStateCallback.runOnceAtState(STATE_SCREENSHOT_CAPTURED | STATE_GESTURE_COMPLETED | STATE_SCALED_CONTROLLER_RECENTS, this::finishCurrentTransitionToRecents); - mStateCallback.addCallback(STATE_SCREENSHOT_CAPTURED | STATE_GESTURE_COMPLETED + mStateCallback.runOnceAtState(STATE_SCREENSHOT_CAPTURED | STATE_GESTURE_COMPLETED | STATE_SCALED_CONTROLLER_HOME, this::finishCurrentTransitionToHome); - mStateCallback.addCallback(STATE_SCALED_CONTROLLER_HOME | STATE_CURRENT_TASK_FINISHED, + mStateCallback.runOnceAtState(STATE_SCALED_CONTROLLER_HOME | STATE_CURRENT_TASK_FINISHED, this::reset); - mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED + mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED | STATE_LAUNCHER_DRAWN | STATE_SCALED_CONTROLLER_RECENTS | STATE_CURRENT_TASK_FINISHED | STATE_GESTURE_COMPLETED | STATE_GESTURE_STARTED, this::setupLauncherUiAfterSwipeUpToRecentsAnimation); - mStateCallback.addCallback(STATE_HANDLER_INVALIDATED, this::invalidateHandler); - mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED, + mGestureState.runOnceAtState(STATE_END_TARGET_ANIMATION_FINISHED, this::onEndTargetSet); + + mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED, this::invalidateHandler); + mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED, this::invalidateHandlerWithLauncher); - mStateCallback.addCallback(STATE_HANDLER_INVALIDATED | STATE_RESUME_LAST_TASK, + mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED | STATE_RESUME_LAST_TASK, this::notifyTransitionCancelled); if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) { - mStateCallback.addChangeHandler(STATE_APP_CONTROLLER_RECEIVED | STATE_LAUNCHER_PRESENT + mStateCallback.addChangeListener(STATE_APP_CONTROLLER_RECEIVED | STATE_LAUNCHER_PRESENT | STATE_SCREENSHOT_VIEW_SHOWN | STATE_CAPTURE_SCREENSHOT, (b) -> mRecentsView.setRunningTaskHidden(!b)); } } @Override - protected boolean onActivityInit(final T activity, Boolean alreadyOnHome) { + protected boolean onActivityInit(Boolean alreadyOnHome) { + final T activity = mActivityInterface.getCreatedActivity(); if (mActivity == activity) { return true; } @@ -321,21 +290,28 @@ public class WindowTransformSwipeHandler mStateCallback.setState(STATE_LAUNCHER_PRESENT); if (alreadyOnHome) { - onLauncherStart(activity); + onLauncherStart(); } else { - activity.setOnStartCallback(this::onLauncherStart); + activity.runOnceOnStart(this::onLauncherStart); } setupRecentsViewUi(); + + if (mDeviceState.getNavMode() == TWO_BUTTONS) { + // If the device is in two button mode, swiping up will show overview with predictions + // so we need to kick off switching to the overview predictions as soon as possible + mActivityInterface.updateOverviewPredictionState(); + } return true; } @Override protected boolean moveWindowWithRecentsScroll() { - return mGestureEndTarget != HOME; + return mGestureState.getEndTarget() != HOME; } - private void onLauncherStart(final T activity) { + private void onLauncherStart() { + final T activity = mActivityInterface.getCreatedActivity(); if (mActivity != activity) { return; } @@ -345,9 +321,9 @@ public class WindowTransformSwipeHandler // If we've already ended the gesture and are going home, don't prepare recents UI, // as that will set the state as BACKGROUND_APP, overriding the animation to NORMAL. - if (mGestureEndTarget != HOME) { + if (mGestureState.getEndTarget() != HOME) { Runnable initAnimFactory = () -> { - mAnimationFactory = mActivityInterface.prepareRecentsUI(mActivity, + mAnimationFactory = mActivityInterface.prepareRecentsUI( mWasLauncherAlreadyVisible, true, this::onAnimatorPlaybackControllerCreated); maybeUpdateRecentsAttachedState(false /* animate */); @@ -356,7 +332,7 @@ public class WindowTransformSwipeHandler // Launcher is visible, but might be about to stop. Thus, if we prepare recents // now, it might get overridden by moveToRestState() in onStop(). To avoid this, // wait until the next gesture (and possibly launcher) starts. - mStateCallback.addCallback(STATE_GESTURE_STARTED, initAnimFactory); + mStateCallback.runOnceAtState(STATE_GESTURE_STARTED, initAnimFactory); } else { initAnimFactory.run(); } @@ -400,15 +376,31 @@ public class WindowTransformSwipeHandler // that time by a previous window transition. setupRecentsViewUi(); + // For the duration of the gesture, in cases where an activity is launched while the + // activity is not yet resumed, finish the animation to ensure we get resumed + mGestureState.getActivityInterface().setOnDeferredActivityLaunchCallback( + mOnDeferredActivityLaunch); + notifyGestureStartedAsync(); } + private void onDeferredActivityLaunch() { + if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { + mActivityInterface.switchRunningTaskViewToScreenshot( + null, () -> { + mTaskAnimationManager.finishRunningRecentsAnimation(true /* toHome */); + }); + } else { + mTaskAnimationManager.finishRunningRecentsAnimation(true /* toHome */); + } + } + private void setupRecentsViewUi() { if (mContinuingLastGesture) { updateSysUiFlags(mCurrentShift.value); return; } - mRecentsView.onGestureAnimationStart(mRunningTaskId); + mRecentsView.onGestureAnimationStart(mGestureState.getRunningTaskId()); } private void launcherFrameDrawn() { @@ -437,16 +429,15 @@ public class WindowTransformSwipeHandler .getHighResLoadingState().setVisible(true); } - private float getTaskCurveScaleForOffsetX(float offsetX, float taskWidth) { - float distanceToReachEdge = mDp.widthPx / 2 + taskWidth / 2 + - mContext.getResources().getDimensionPixelSize(R.dimen.recents_page_spacing); - float interpolation = Math.min(1, offsetX / distanceToReachEdge); - return TaskView.getCurveScaleForInterpolation(interpolation); - } - @Override public void onMotionPauseChanged(boolean isPaused) { - setShelfState(isPaused ? PEEK : HIDE, OVERSHOOT_1_2, SHELF_ANIM_DURATION); + setShelfState(isPaused ? PEEK : HIDE, ShelfPeekAnim.INTERPOLATOR, ShelfPeekAnim.DURATION); + + if (mDeviceState.isFullyGesturalNavMode() && isPaused) { + // In fully gestural nav mode, switch to overview predictions once the user has paused + // (this is a no-op if the predictions are already in that state) + mActivityInterface.updateOverviewPredictionState(); + } } public void maybeUpdateRecentsAttachedState() { @@ -461,16 +452,15 @@ public class WindowTransformSwipeHandler * Note this method has no effect unless the navigation mode is NO_BUTTON. */ private void maybeUpdateRecentsAttachedState(boolean animate) { - if (mMode != Mode.NO_BUTTON || mRecentsView == null) { + if (!mDeviceState.isFullyGesturalNavMode() || mRecentsView == null) { return; } - RemoteAnimationTargetCompat runningTaskTarget = mRecentsAnimationTargets == null - ? null - : mRecentsAnimationTargets.findTask(mRunningTaskId); + RemoteAnimationTargetCompat runningTaskTarget = mRecentsAnimationTargets != null + ? mRecentsAnimationTargets.findTask(mGestureState.getRunningTaskId()) + : null; final boolean recentsAttachedToAppWindow; - int runningTaskIndex = mRecentsView.getRunningTaskIndex(); - if (mGestureEndTarget != null) { - recentsAttachedToAppWindow = mGestureEndTarget.recentsAttachedToAppWindow; + if (mGestureState.getEndTarget() != null) { + recentsAttachedToAppWindow = mGestureState.getEndTarget().recentsAttachedToAppWindow; } else if (mContinuingLastGesture && mRecentsView.getRunningTaskIndex() != mRecentsView.getNextPage()) { recentsAttachedToAppWindow = true; @@ -517,9 +507,10 @@ public class WindowTransformSwipeHandler } private void buildAnimationController() { - if (mGestureEndTarget == HOME || mHasLauncherTransitionControllerStarted) { - // We don't want a new mLauncherTransitionController if mGestureEndTarget == HOME (it - // has its own animation) or if we're already animating the current controller. + if (mGestureState.getEndTarget() == HOME || mHasLauncherTransitionControllerStarted) { + // We don't want a new mLauncherTransitionController if + // mGestureState.getEndTarget() == HOME (it has its own animation) or if we're already + // animating the current controller. return; } initTransitionEndpoints(mActivity.getDeviceProfile()); @@ -543,7 +534,7 @@ public class WindowTransformSwipeHandler @Override public Intent getLaunchIntent() { - return mOverviewComponentObserver.getOverviewIntent(); + return mGestureState.getOverviewIntent(); } @Override @@ -555,7 +546,8 @@ public class WindowTransformSwipeHandler if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { if (mRecentsAnimationTargets != null) { - mLiveTileOverlay.update(mAppWindowAnimationHelper.getCurrentRectWithInsets(), + LiveTileOverlay.getInstance().update( + mAppWindowAnimationHelper.getCurrentRectWithInsets(), mAppWindowAnimationHelper.getCurrentCornerRadius()); } } @@ -563,7 +555,7 @@ public class WindowTransformSwipeHandler final boolean passed = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW; if (passed != mPassedOverviewThreshold) { mPassedOverviewThreshold = passed; - if (mMode != Mode.NO_BUTTON) { + if (!mDeviceState.isFullyGesturalNavMode()) { performHapticFeedback(); } } @@ -576,7 +568,7 @@ public class WindowTransformSwipeHandler } private void updateLauncherTransitionProgress() { - if (mGestureEndTarget == HOME) { + if (mGestureState.getEndTarget() == HOME) { return; } // Normalize the progress to 0 to 1, as the animation controller will clamp it to that @@ -613,9 +605,9 @@ public class WindowTransformSwipeHandler super.onRecentsAnimationStart(controller, targets); // Only add the callback to enable the input consumer after we actually have the controller - mStateCallback.addCallback(STATE_APP_CONTROLLER_RECEIVED | STATE_GESTURE_STARTED, + mStateCallback.runOnceAtState(STATE_APP_CONTROLLER_RECEIVED | STATE_GESTURE_STARTED, mRecentsAnimationController::enableInputConsumer); - setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED); + mStateCallback.setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED); mPassedOverviewThreshold = false; } @@ -623,9 +615,11 @@ public class WindowTransformSwipeHandler @Override public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) { super.onRecentsAnimationCanceled(thumbnailData); - mRecentsView.setRecentsAnimationTargets(null, null); + if (mRecentsView != null) { + mRecentsView.setRecentsAnimationTargets(null, null); + } mActivityInitListener.unregister(); - setStateOnUiThread(STATE_GESTURE_CANCELLED | STATE_HANDLER_INVALIDATED); + mStateCallback.setStateOnUiThread(STATE_GESTURE_CANCELLED | STATE_HANDLER_INVALIDATED); ActiveGestureLog.INSTANCE.addLog("cancelRecentsAnimation"); } @@ -633,7 +627,7 @@ public class WindowTransformSwipeHandler public void onGestureStarted() { notifyGestureStartedAsync(); mShiftAtGestureStart = mCurrentShift.value; - setStateOnUiThread(STATE_GESTURE_STARTED); + mStateCallback.setStateOnUiThread(STATE_GESTURE_STARTED); mGestureStarted = true; } @@ -656,7 +650,7 @@ public class WindowTransformSwipeHandler @Override public void onGestureCancelled() { updateDisplacement(0); - setStateOnUiThread(STATE_GESTURE_COMPLETED); + mStateCallback.setStateOnUiThread(STATE_GESTURE_COMPLETED); mLogAction = Touch.SWIPE_NOOP; handleNormalGestureEnd(0, false, new PointF(), true /* isCancel */); } @@ -671,7 +665,7 @@ public class WindowTransformSwipeHandler float flingThreshold = mContext.getResources() .getDimension(R.dimen.quickstep_fling_threshold_velocity); boolean isFling = mGestureStarted && Math.abs(endVelocity) > flingThreshold; - setStateOnUiThread(STATE_GESTURE_COMPLETED); + mStateCallback.setStateOnUiThread(STATE_GESTURE_COMPLETED); mLogAction = isFling ? Touch.FLING : Touch.SWIPE; boolean isVelocityVertical = Math.abs(velocity.y) > Math.abs(velocity.x); @@ -686,11 +680,11 @@ public class WindowTransformSwipeHandler @Override protected InputConsumer createNewInputProxyHandler() { - endRunningWindowAnim(mGestureEndTarget == HOME /* cancel */); + endRunningWindowAnim(mGestureState.getEndTarget() == HOME /* cancel */); endLauncherTransitionController(); if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) { // Hide the task view, if not already hidden - setTargetAlphaProvider(WindowTransformSwipeHandler::getHiddenTargetAlpha); + setTargetAlphaProvider(LauncherSwipeHandler::getHiddenTargetAlpha); } BaseDraggingActivity activity = mActivityInterface.getCreatedActivity(); @@ -708,6 +702,24 @@ public class WindowTransformSwipeHandler } } + private void onEndTargetSet() { + switch (mGestureState.getEndTarget()) { + case HOME: + mStateCallback.setState(STATE_SCALED_CONTROLLER_HOME | STATE_CAPTURE_SCREENSHOT); + break; + case RECENTS: + mStateCallback.setState(STATE_SCALED_CONTROLLER_RECENTS | STATE_CAPTURE_SCREENSHOT + | STATE_SCREENSHOT_VIEW_SHOWN); + break; + case NEW_TASK: + mStateCallback.setState(STATE_START_NEW_TASK | STATE_CAPTURE_SCREENSHOT); + break; + case LAST_TASK: + mStateCallback.setState(STATE_RESUME_LAST_TASK); + break; + } + } + private GestureEndTarget calculateEndTarget(PointF velocity, float endVelocity, boolean isFling, boolean isCancel) { final GestureEndTarget endTarget; @@ -729,7 +741,7 @@ public class WindowTransformSwipeHandler if (!isFling) { if (isCancel) { endTarget = LAST_TASK; - } else if (mMode == Mode.NO_BUTTON) { + } else if (mDeviceState.isFullyGesturalNavMode()) { if (mIsShelfPeeking) { endTarget = RECENTS; } else if (goingToNewTask) { @@ -750,9 +762,9 @@ public class WindowTransformSwipeHandler boolean willGoToNewTaskOnSwipeUp = goingToNewTask && Math.abs(velocity.x) > Math.abs(endVelocity); - if (mMode == Mode.NO_BUTTON && isSwipeUp && !willGoToNewTaskOnSwipeUp) { + if (mDeviceState.isFullyGesturalNavMode() && isSwipeUp && !willGoToNewTaskOnSwipeUp) { endTarget = HOME; - } else if (mMode == Mode.NO_BUTTON && isSwipeUp && !mIsShelfPeeking) { + } else if (mDeviceState.isFullyGesturalNavMode() && isSwipeUp && !mIsShelfPeeking) { // If swiping at a diagonal, base end target on the faster velocity. endTarget = NEW_TASK; } else if (isSwipeUp) { @@ -777,7 +789,7 @@ public class WindowTransformSwipeHandler float currentShift = mCurrentShift.value; final GestureEndTarget endTarget = calculateEndTarget(velocity, endVelocity, isFling, isCancel); - float endShift = endTarget.endShift; + float endShift = endTarget.isLauncher ? 1 : 0; final float startShift; Interpolator interpolator = DEACCEL; if (!isFling) { @@ -792,7 +804,7 @@ public class WindowTransformSwipeHandler float minFlingVelocity = mContext.getResources() .getDimension(R.dimen.quickstep_fling_min_velocity); if (Math.abs(endVelocity) > minFlingVelocity && mTransitionDragLength > 0) { - if (endTarget == RECENTS && mMode != Mode.NO_BUTTON) { + if (endTarget == RECENTS && !mDeviceState.isFullyGesturalNavMode()) { Interpolators.OvershootParams overshoot = new Interpolators.OvershootParams( startShift, endShift, endShift, endVelocity / 1000, mTransitionDragLength, mContext); @@ -825,7 +837,7 @@ public class WindowTransformSwipeHandler setShelfState(ShelfAnimState.CANCEL, LINEAR, 0); duration = Math.max(MIN_OVERSHOOT_DURATION, duration); } else if (endTarget == RECENTS) { - mLiveTileOverlay.startIconAnimation(); + LiveTileOverlay.getInstance().startIconAnimation(); if (mRecentsView != null) { int nearestPage = mRecentsView.getPageNearestToCenterOfScreen(); if (mRecentsView.getNextPage() != nearestPage) { @@ -838,7 +850,7 @@ public class WindowTransformSwipeHandler } duration = Math.max(duration, mRecentsView.getScroller().getDuration()); } - if (mMode == Mode.NO_BUTTON) { + if (mDeviceState.isFullyGesturalNavMode()) { setShelfState(ShelfAnimState.OVERVIEW, interpolator, duration); } } else if (endTarget == NEW_TASK || endTarget == LAST_TASK) { @@ -880,14 +892,15 @@ public class WindowTransformSwipeHandler @UiThread private void animateToProgressInternal(float start, float end, long duration, Interpolator interpolator, GestureEndTarget target, PointF velocityPxPerMs) { - mGestureEndTarget = target; + // Set the state, but don't notify until the animation completes + mGestureState.setEndTarget(target, false /* isAtomic */); maybeUpdateRecentsAttachedState(); - if (mGestureEndTarget == HOME) { + if (mGestureState.getEndTarget() == HOME) { HomeAnimationFactory homeAnimFactory; if (mActivity != null) { - homeAnimFactory = mActivityInterface.prepareHomeUI(mActivity); + homeAnimFactory = mActivityInterface.prepareHomeUI(); } else { homeAnimFactory = new HomeAnimationFactory() { @NonNull @@ -904,14 +917,15 @@ public class WindowTransformSwipeHandler return AnimatorPlaybackController.wrap(new AnimatorSet(), duration); } }; - mStateCallback.addChangeHandler(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED, + mStateCallback.addChangeListener(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED, isPresent -> mRecentsView.startHome()); } RectFSpringAnim windowAnim = createWindowAnimationToHome(start, homeAnimFactory); windowAnim.addAnimatorListener(new AnimationSuccessListener() { @Override public void onAnimationSuccess(Animator animator) { - setStateOnUiThread(target.endState); + // Finalize the state and notify of the change + mGestureState.setState(STATE_END_TARGET_ANIMATION_FINISHED); } }); windowAnim.start(velocityPxPerMs); @@ -936,10 +950,9 @@ public class WindowTransformSwipeHandler // We are about to launch the current running task, so use LAST_TASK state // instead of NEW_TASK. This could happen, for example, if our scroll is // aborted after we determined the target to be NEW_TASK. - setStateOnUiThread(LAST_TASK.endState); - } else { - setStateOnUiThread(target.endState); + mGestureState.setEndTarget(LAST_TASK); } + mGestureState.setState(STATE_END_TARGET_ANIMATION_FINISHED); } }); windowAnim.start(); @@ -947,7 +960,7 @@ public class WindowTransformSwipeHandler } // Always play the entire launcher animation when going home, since it is separate from // the animation that has been controlled thus far. - if (mGestureEndTarget == HOME) { + if (mGestureState.getEndTarget() == HOME) { start = 0; } @@ -999,21 +1012,22 @@ public class WindowTransformSwipeHandler } // Make sure recents is in its final state maybeUpdateRecentsAttachedState(false); - mActivityInterface.onSwipeUpToHomeComplete(mActivity); + mActivityInterface.onSwipeUpToHomeComplete(); } }); return anim; } @Override - public void onConsumerAboutToBeSwitched(SwipeSharedState sharedState) { - if (mGestureEndTarget != null) { - sharedState.canGestureBeContinued = mGestureEndTarget.canBeContinued; - sharedState.goingToLauncher = mGestureEndTarget.isLauncher; + public void onConsumerAboutToBeSwitched() { + if (mActivity != null) { + // In the off chance that the gesture ends before Launcher is started, we should clear + // the callback here so that it doesn't update with the wrong state + mActivity.clearRunOnceOnStartCallback(); + resetLauncherListenersAndOverlays(); } - - if (sharedState.canGestureBeContinued) { - cancelCurrentAnimation(sharedState); + if (mGestureState.getEndTarget() != null && !mGestureState.isRunningAnimationToLauncher()) { + cancelCurrentAnimation(); } else { reset(); } @@ -1033,6 +1047,15 @@ public class WindowTransformSwipeHandler @UiThread private void startNewTask() { + if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { + mRecentsAnimationController.finish(true /* toRecents */, this::startNewTaskInternal); + } else { + startNewTaskInternal(); + } + } + + @UiThread + private void startNewTaskInternal() { startNewTask(STATE_HANDLER_INVALIDATED, success -> { if (!success) { // We couldn't launch the task, so take user to overview so they can @@ -1045,14 +1068,14 @@ public class WindowTransformSwipeHandler } private void reset() { - setStateOnUiThread(STATE_HANDLER_INVALIDATED); + mStateCallback.setStateOnUiThread(STATE_HANDLER_INVALIDATED); } /** * Cancels any running animation so that the active target can be overriden by a new swipe * handle (in case of quick switch). */ - private void cancelCurrentAnimation(SwipeSharedState sharedState) { + private void cancelCurrentAnimation() { mCanceled = true; mCurrentShift.cancelAnimation(); if (mLauncherTransitionController != null && mLauncherTransitionController @@ -1070,7 +1093,7 @@ public class WindowTransformSwipeHandler ? newRunningTaskView.getTask().key.id : -1; mRecentsView.setCurrentTask(newRunningTaskId); - sharedState.setRecentsAnimationFinishInterrupted(newRunningTaskId); + mGestureState.setFinishingRecentsAnimationTaskId(newRunningTaskId); } } @@ -1089,9 +1112,7 @@ public class WindowTransformSwipeHandler endLauncherTransitionController(); mRecentsView.onGestureAnimationEnd(); - - mActivity.getRootView().setOnApplyWindowInsetsListener(null); - removeLiveTileOverlay(); + resetLauncherListenersAndOverlays(); } private void endLauncherTransitionController() { @@ -1102,58 +1123,71 @@ public class WindowTransformSwipeHandler } } + private void resetLauncherListenersAndOverlays() { + // Reset the callback for deferred activity launches + if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) { + mActivityInterface.setOnDeferredActivityLaunchCallback(null); + } + mActivity.getRootView().setOnApplyWindowInsetsListener(null); + removeLiveTileOverlay(); + } + private void notifyTransitionCancelled() { mAnimationFactory.onTransitionCancelled(); } private void resetStateForAnimationCancel() { boolean wasVisible = mWasLauncherAlreadyVisible || mGestureStarted; - mActivityInterface.onTransitionCancelled(mActivity, wasVisible); + mActivityInterface.onTransitionCancelled(wasVisible); // Leave the pending invisible flag, as it may be used by wallpaper open animation. mActivity.clearForceInvisibleFlag(INVISIBLE_BY_STATE_HANDLER); } private void switchToScreenshot() { + final int runningTaskId = mGestureState.getRunningTaskId(); if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { if (mRecentsAnimationController != null) { + SharedApiCompat.setWillFinishToHome(mRecentsAnimationController.getController(), + true /* willFinishToHome */); // Update the screenshot of the task if (mTaskSnapshot == null) { - mTaskSnapshot = mRecentsAnimationController.screenshotTask(mRunningTaskId); + mTaskSnapshot = mRecentsAnimationController.screenshotTask(runningTaskId); } - mRecentsView.updateThumbnail(mRunningTaskId, mTaskSnapshot, false /* refreshNow */); + mRecentsView.updateThumbnail(runningTaskId, mTaskSnapshot, false /* refreshNow */); } - setStateOnUiThread(STATE_SCREENSHOT_CAPTURED); + mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED); } else if (!hasTargets()) { // If there are no targets, then we don't need to capture anything - setStateOnUiThread(STATE_SCREENSHOT_CAPTURED); + mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED); } else { boolean finishTransitionPosted = false; if (mRecentsAnimationController != null) { // Update the screenshot of the task if (mTaskSnapshot == null) { - mTaskSnapshot = mRecentsAnimationController.screenshotTask(mRunningTaskId); + mTaskSnapshot = mRecentsAnimationController.screenshotTask(runningTaskId); } final TaskView taskView; - if (mGestureEndTarget == HOME) { + if (mGestureState.getEndTarget() == HOME) { // Capture the screenshot before finishing the transition to home to ensure it's // taken in the correct orientation, but no need to update the thumbnail. taskView = null; } else { - taskView = mRecentsView.updateThumbnail(mRunningTaskId, mTaskSnapshot); + taskView = mRecentsView.updateThumbnail(runningTaskId, mTaskSnapshot); } if (taskView != null && !mCanceled) { // Defer finishing the animation until the next launcher frame with the // new thumbnail finishTransitionPosted = ViewUtils.postDraw(taskView, - () -> setStateOnUiThread(STATE_SCREENSHOT_CAPTURED), this::isCanceled); + () -> mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED), + this::isCanceled); } } if (!finishTransitionPosted) { // If we haven't posted a draw callback, set the state immediately. Object traceToken = TraceHelper.INSTANCE.beginSection(SCREENSHOT_CAPTURED_EVT, TraceHelper.FLAG_CHECK_FOR_RACE_CONDITIONS); - setStateOnUiThread(STATE_SCREENSHOT_CAPTURED); + mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED); TraceHelper.INSTANCE.endSection(traceToken); } } @@ -1161,23 +1195,24 @@ public class WindowTransformSwipeHandler private void finishCurrentTransitionToRecents() { if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { - setStateOnUiThread(STATE_CURRENT_TASK_FINISHED); - } else if (!hasTargets()) { - // If there are no targets, then there is nothing to finish - setStateOnUiThread(STATE_CURRENT_TASK_FINISHED); + mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED); + } else if (!hasTargets() || mRecentsAnimationController == null) { + // If there are no targets or the animation not started, then there is nothing to finish + mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED); } else { - synchronized (mRecentsAnimationController) { - mRecentsAnimationController.finish(true /* toRecents */, - () -> setStateOnUiThread(STATE_CURRENT_TASK_FINISHED)); - } + mRecentsAnimationController.finish(true /* toRecents */, + () -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED)); } ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", true); } private void finishCurrentTransitionToHome() { - synchronized (mRecentsAnimationController) { + if (!hasTargets() || mRecentsAnimationController == null) { + // If there are no targets or the animation not started, then there is nothing to finish + mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED); + } else { mRecentsAnimationController.finish(true /* toRecents */, - () -> setStateOnUiThread(STATE_CURRENT_TASK_FINISHED), + () -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED), true /* sendUserLeaveHint */); } ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", true); @@ -1186,15 +1221,14 @@ public class WindowTransformSwipeHandler private void setupLauncherUiAfterSwipeUpToRecentsAnimation() { endLauncherTransitionController(); - mActivityInterface.onSwipeUpToRecentsComplete(mActivity); + mActivityInterface.onSwipeUpToRecentsComplete(); if (mRecentsAnimationController != null) { mRecentsAnimationController.setDeferCancelUntilNextTransition(true /* defer */, true /* screenshot */); } mRecentsView.onSwipeUpAnimationSuccess(); - RecentsModel.INSTANCE.get(mContext).onOverviewShown(false, TAG); - + SystemUiProxy.INSTANCE.get(mContext).onOverviewShown(false, TAG); doLogGesture(RECENTS); reset(); } @@ -1204,20 +1238,15 @@ public class WindowTransformSwipeHandler updateFinalShift(); } - private synchronized void addLiveTileOverlay() { - if (!mLiveTileOverlayAttached) { - mActivity.getRootView().getOverlay().add(mLiveTileOverlay); - mRecentsView.setLiveTileOverlay(mLiveTileOverlay); - mLiveTileOverlayAttached = true; + private void addLiveTileOverlay() { + if (LiveTileOverlay.getInstance().attach(mActivity.getRootView().getOverlay())) { + mRecentsView.setLiveTileOverlayAttached(true); } } - private synchronized void removeLiveTileOverlay() { - if (mLiveTileOverlayAttached) { - mActivity.getRootView().getOverlay().remove(mLiveTileOverlay); - mRecentsView.setLiveTileOverlay(null); - mLiveTileOverlayAttached = false; - } + private void removeLiveTileOverlay() { + LiveTileOverlay.getInstance().detach(mActivity.getRootView().getOverlay()); + mRecentsView.setLiveTileOverlayAttached(false); } public static float getHiddenTargetAlpha(RemoteAnimationTargetCompat app, float expectedAlpha) { diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java index 150c44d264..c4733bd35a 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java @@ -29,6 +29,7 @@ import android.view.ViewConfiguration; import androidx.annotation.BinderThread; import com.android.launcher3.BaseDraggingActivity; +import com.android.launcher3.appprediction.PredictionUiStateManager; import com.android.launcher3.logging.UserEventDispatcher; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.quickstep.util.ActivityInitListener; @@ -203,7 +204,8 @@ public class OverviewCommandHelper { return false; } - private boolean onActivityReady(T activity, Boolean wasVisible) { + private boolean onActivityReady(Boolean wasVisible) { + final T activity = mActivityInterface.getCreatedActivity(); if (!mUserEventLogged) { activity.getUserEventDispatcher().logActionCommand( LauncherLogProto.Action.Command.RECENTS_BUTTON, @@ -211,6 +213,10 @@ public class OverviewCommandHelper { LauncherLogProto.ContainerType.TASKSWITCHER); mUserEventLogged = true; } + + // Switch prediction client to overview + PredictionUiStateManager.INSTANCE.get(activity).switchClient( + PredictionUiStateManager.Client.OVERVIEW); return mAnimationProvider.onActivityReady(activity, wasVisible); } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java index ce533a699a..92c55da6da 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java @@ -10,7 +10,6 @@ import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppState; import com.android.launcher3.testing.TestInformationHandler; import com.android.launcher3.testing.TestProtocol; -import com.android.launcher3.uioverrides.states.OverviewState; import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController; import com.android.quickstep.util.LayoutUtils; import com.android.quickstep.views.RecentsView; @@ -34,7 +33,7 @@ public class QuickstepTestInformationHandler extends TestInformationHandler { switch (method) { case TestProtocol.REQUEST_HOME_TO_OVERVIEW_SWIPE_HEIGHT: { final float swipeHeight = - OverviewState.getDefaultSwipeHeight(mContext, mDeviceProfile); + LayoutUtils.getDefaultSwipeHeight(mContext, mDeviceProfile); response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) swipeHeight); return response; } @@ -112,11 +111,6 @@ public class QuickstepTestInformationHandler extends TestInformationHandler { @Override protected boolean isLauncherInitialized() { - if (TestProtocol.sDebugTracing) { - Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE, - "isLauncherInitialized.TouchInteractionService.isInitialized=" + - TouchInteractionService.isInitialized()); - } return super.isLauncherInitialized() && TouchInteractionService.isInitialized(); } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java deleted file mode 100644 index cd8e1a406b..0000000000 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java +++ /dev/null @@ -1,183 +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 com.android.launcher3.util.Executors.MAIN_EXECUTOR; - -import android.util.Log; - -import com.android.launcher3.Utilities; -import com.android.launcher3.config.FeatureFlags; -import com.android.launcher3.util.Preconditions; -import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener; - -import com.android.systemui.shared.recents.model.ThumbnailData; - -import java.io.PrintWriter; - -/** - * Utility class used to store state information shared across multiple transitions. - */ -public class SwipeSharedState implements RecentsAnimationListener { - - private OverviewComponentObserver mOverviewComponentObserver; - - private RecentsAnimationCallbacks mRecentsAnimationListener; - private RecentsAnimationController mLastRecentsAnimationController; - private RecentsAnimationTargets mLastAnimationTarget; - - private boolean mLastAnimationCancelled = false; - private boolean mLastAnimationRunning = false; - - public boolean canGestureBeContinued; - public boolean goingToLauncher; - public boolean recentsAnimationFinishInterrupted; - public int nextRunningTaskId = -1; - private int mLogId; - - public void setOverviewComponentObserver(OverviewComponentObserver observer) { - mOverviewComponentObserver = observer; - } - - @Override - public final void onRecentsAnimationStart(RecentsAnimationController controller, - RecentsAnimationTargets targets) { - mLastRecentsAnimationController = controller; - mLastAnimationTarget = targets; - - mLastAnimationCancelled = false; - mLastAnimationRunning = true; - } - - @Override - public final void onRecentsAnimationCanceled(ThumbnailData thumbnailData) { - if (thumbnailData != null) { - mOverviewComponentObserver.getActivityInterface().switchToScreenshot(thumbnailData, - () -> { - mLastRecentsAnimationController.cleanupScreenshot(); - clearAnimationState(); - }); - } else { - clearAnimationState(); - } - } - - @Override - public final void onRecentsAnimationFinished(RecentsAnimationController controller) { - if (mLastRecentsAnimationController == controller) { - mLastAnimationRunning = false; - } - } - - private void clearAnimationTarget() { - if (mLastAnimationTarget != null) { - mLastAnimationTarget.release(); - mLastAnimationTarget = null; - } - } - - private void clearAnimationState() { - clearAnimationTarget(); - - mLastAnimationCancelled = true; - mLastAnimationRunning = false; - } - - private void clearListenerState(boolean finishAnimation) { - if (mRecentsAnimationListener != null) { - mRecentsAnimationListener.removeListener(this); - mRecentsAnimationListener.notifyAnimationCanceled(); - if (mLastAnimationRunning && mLastRecentsAnimationController != null) { - Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), - finishAnimation - ? mLastRecentsAnimationController::finishAnimationToHome - : mLastRecentsAnimationController::finishAnimationToApp); - mLastRecentsAnimationController = null; - mLastAnimationTarget = null; - } - } - mRecentsAnimationListener = null; - clearAnimationTarget(); - mLastAnimationCancelled = false; - mLastAnimationRunning = false; - } - - public RecentsAnimationCallbacks newRecentsAnimationCallbacks() { - Preconditions.assertUIThread(); - - if (mLastAnimationRunning) { - String msg = "New animation started before completing old animation"; - if (FeatureFlags.IS_DOGFOOD_BUILD) { - throw new IllegalArgumentException(msg); - } else { - Log.e("SwipeSharedState", msg, new Exception()); - } - } - - clearListenerState(false /* finishAnimation */); - boolean shouldMinimiseSplitScreen = mOverviewComponentObserver == null ? false - : mOverviewComponentObserver.getActivityInterface().shouldMinimizeSplitScreen(); - mRecentsAnimationListener = new RecentsAnimationCallbacks(shouldMinimiseSplitScreen); - mRecentsAnimationListener.addListener(this); - return mRecentsAnimationListener; - } - - public RecentsAnimationCallbacks getActiveListener() { - return mRecentsAnimationListener; - } - - public void applyActiveRecentsAnimationState(RecentsAnimationListener listener) { - if (mLastRecentsAnimationController != null) { - listener.onRecentsAnimationStart(mLastRecentsAnimationController, - mLastAnimationTarget); - } else if (mLastAnimationCancelled) { - listener.onRecentsAnimationCanceled(null); - } - } - - /** - * Called when a recents animation has finished, but was interrupted before the next task was - * launched. The given {@param runningTaskId} should be used as the running task for the - * continuing input consumer. - */ - public void setRecentsAnimationFinishInterrupted(int runningTaskId) { - recentsAnimationFinishInterrupted = true; - nextRunningTaskId = runningTaskId; - mLastAnimationTarget = mLastAnimationTarget.cloneWithoutTargets(); - } - - public void clearAllState(boolean finishAnimation) { - clearListenerState(finishAnimation); - canGestureBeContinued = false; - recentsAnimationFinishInterrupted = false; - nextRunningTaskId = -1; - goingToLauncher = false; - } - - public void dump(String prefix, PrintWriter pw) { - pw.println(prefix + "goingToLauncher=" + goingToLauncher); - pw.println(prefix + "canGestureBeContinued=" + canGestureBeContinued); - pw.println(prefix + "recentsAnimationFinishInterrupted=" + recentsAnimationFinishInterrupted); - pw.println(prefix + "nextRunningTaskId=" + nextRunningTaskId); - pw.println(prefix + "lastAnimationCancelled=" + mLastAnimationCancelled); - pw.println(prefix + "lastAnimationRunning=" + mLastAnimationRunning); - pw.println(prefix + "logTraceId=" + mLogId); - } - - public void setLogTraceId(int logId) { - this.mLogId = logId; - } -} diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java index 17457aace6..b5441dfd14 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java @@ -19,11 +19,11 @@ package com.android.quickstep; import static com.android.launcher3.util.MainThreadInitializedObject.forOverride; import android.graphics.Matrix; -import android.view.View; import com.android.launcher3.BaseActivity; import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.R; +import com.android.launcher3.popup.SystemShortcut; import com.android.launcher3.util.MainThreadInitializedObject; import com.android.launcher3.util.ResourceBasedOverride; import com.android.quickstep.views.TaskThumbnailView; @@ -40,30 +40,30 @@ import java.util.List; public class TaskOverlayFactory implements ResourceBasedOverride { /** Note that these will be shown in order from top to bottom, if available for the task. */ - private static final TaskSystemShortcut[] MENU_OPTIONS = new TaskSystemShortcut[]{ - new TaskSystemShortcut.AppInfo(), - new TaskSystemShortcut.SplitScreen(), - new TaskSystemShortcut.Pin(), - new TaskSystemShortcut.Install(), - new TaskSystemShortcut.Freeform() + private static final TaskShortcutFactory[] MENU_OPTIONS = new TaskShortcutFactory[]{ + TaskShortcutFactory.APP_INFO, + TaskShortcutFactory.SPLIT_SCREEN, + TaskShortcutFactory.PIN, + TaskShortcutFactory.INSTALL, + TaskShortcutFactory.FREE_FORM, + TaskShortcutFactory.WELLBEING }; - public static final MainThreadInitializedObject INSTANCE = - forOverride(TaskOverlayFactory.class, R.string.task_overlay_factory_class); - - public List getEnabledShortcuts(TaskView taskView) { - final ArrayList shortcuts = new ArrayList<>(); + public static List getEnabledShortcuts(TaskView taskView) { + final ArrayList shortcuts = new ArrayList<>(); final BaseDraggingActivity activity = BaseActivity.fromContext(taskView.getContext()); - for (TaskSystemShortcut menuOption : MENU_OPTIONS) { - View.OnClickListener onClickListener = - menuOption.getOnClickListener(activity, taskView); - if (onClickListener != null) { - shortcuts.add(menuOption); + for (TaskShortcutFactory menuOption : MENU_OPTIONS) { + SystemShortcut shortcut = menuOption.getShortcut(activity, taskView); + if (shortcut != null) { + shortcuts.add(shortcut); } } return shortcuts; } + public static final MainThreadInitializedObject INSTANCE = + forOverride(TaskOverlayFactory.class, R.string.task_overlay_factory_class); + public TaskOverlay createOverlay(TaskThumbnailView thumbnailView) { return new TaskOverlay(); } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskShortcutFactory.java new file mode 100644 index 0000000000..9ba2e5a684 --- /dev/null +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskShortcutFactory.java @@ -0,0 +1,315 @@ +/* + * 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 android.view.Display.DEFAULT_DISPLAY; + +import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.TAP; + +import android.app.Activity; +import android.app.ActivityOptions; +import android.content.ComponentName; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.graphics.Rect; +import android.os.Handler; +import android.os.Looper; +import android.os.UserHandle; +import android.view.View; + +import com.android.launcher3.BaseDraggingActivity; +import com.android.launcher3.DeviceProfile; +import com.android.launcher3.R; +import com.android.launcher3.WorkspaceItemInfo; +import com.android.launcher3.model.WellbeingModel; +import com.android.launcher3.popup.SystemShortcut; +import com.android.launcher3.popup.SystemShortcut.AppInfo; +import com.android.launcher3.userevent.nano.LauncherLogProto; +import com.android.launcher3.util.Executors; +import com.android.launcher3.util.InstantAppResolver; +import com.android.quickstep.views.RecentsView; +import com.android.quickstep.views.TaskThumbnailView; +import com.android.quickstep.views.TaskView; +import com.android.systemui.shared.recents.model.Task; +import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat; +import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture; +import com.android.systemui.shared.recents.view.RecentsTransition; +import com.android.systemui.shared.system.ActivityCompat; +import com.android.systemui.shared.system.ActivityManagerWrapper; +import com.android.systemui.shared.system.ActivityOptionsCompat; +import com.android.systemui.shared.system.WindowManagerWrapper; + +import java.util.Collections; +import java.util.List; +import java.util.function.Consumer; + +/** + * Represents a system shortcut that can be shown for a recent task. + */ +public interface TaskShortcutFactory { + + SystemShortcut getShortcut(BaseDraggingActivity activity, TaskView view); + + static WorkspaceItemInfo dummyInfo(TaskView view) { + Task task = view.getTask(); + + WorkspaceItemInfo dummyInfo = new WorkspaceItemInfo(); + dummyInfo.intent = new Intent(); + ComponentName component = task.getTopComponent(); + dummyInfo.intent.setComponent(component); + dummyInfo.user = UserHandle.of(task.key.userId); + dummyInfo.title = TaskUtils.getTitle(view.getContext(), task); + return dummyInfo; + } + + TaskShortcutFactory APP_INFO = (activity, view) -> new AppInfo(activity, dummyInfo(view)); + + abstract class MultiWindowFactory implements TaskShortcutFactory { + + private final int mIconRes; + private final int mTextRes; + + MultiWindowFactory(int iconRes, int textRes) { + mIconRes = iconRes; + mTextRes = textRes; + } + + protected abstract boolean isAvailable(BaseDraggingActivity activity, int displayId); + protected abstract ActivityOptions makeLaunchOptions(Activity activity); + protected abstract boolean onActivityStarted(BaseDraggingActivity activity); + + @Override + public SystemShortcut getShortcut(BaseDraggingActivity activity, TaskView taskView) { + final Task task = taskView.getTask(); + if (!task.isDockable) { + return null; + } + if (!isAvailable(activity, task.key.displayId)) { + return null; + } + return new MultiWindowSystemShortcut(mIconRes, mTextRes, activity, taskView, this); + } + } + + class MultiWindowSystemShortcut extends SystemShortcut { + + private Handler mHandler; + + private final RecentsView mRecentsView; + private final TaskThumbnailView mThumbnailView; + private final TaskView mTaskView; + private final MultiWindowFactory mFactory; + + public MultiWindowSystemShortcut(int iconRes, int textRes, + BaseDraggingActivity activity, TaskView taskView, MultiWindowFactory factory) { + super(iconRes, textRes, activity, dummyInfo(taskView)); + + mHandler = new Handler(Looper.getMainLooper()); + mTaskView = taskView; + mRecentsView = activity.getOverviewPanel(); + mThumbnailView = taskView.getThumbnail(); + mFactory = factory; + } + + @Override + public void onClick(View view) { + Task.TaskKey taskKey = mTaskView.getTask().key; + final int taskId = taskKey.id; + + final View.OnLayoutChangeListener onLayoutChangeListener = + new View.OnLayoutChangeListener() { + @Override + public void onLayoutChange(View v, int l, int t, int r, int b, + int oldL, int oldT, int oldR, int oldB) { + mTaskView.getRootView().removeOnLayoutChangeListener(this); + mRecentsView.clearIgnoreResetTask(taskId); + + // Start animating in the side pages once launcher has been resized + mRecentsView.dismissTask(mTaskView, false, false); + } + }; + + final DeviceProfile.OnDeviceProfileChangeListener onDeviceProfileChangeListener = + new DeviceProfile.OnDeviceProfileChangeListener() { + @Override + public void onDeviceProfileChanged(DeviceProfile dp) { + mTarget.removeOnDeviceProfileChangeListener(this); + if (dp.isMultiWindowMode) { + mTaskView.getRootView().addOnLayoutChangeListener( + onLayoutChangeListener); + } + } + }; + + dismissTaskMenuView(mTarget); + + ActivityOptions options = mFactory.makeLaunchOptions(mTarget); + if (options != null + && ActivityManagerWrapper.getInstance().startActivityFromRecents(taskId, + options)) { + if (!mFactory.onActivityStarted(mTarget)) { + return; + } + // Add a device profile change listener to kick off animating the side tasks + // once we enter multiwindow mode and relayout + mTarget.addOnDeviceProfileChangeListener(onDeviceProfileChangeListener); + + final Runnable animStartedListener = () -> { + // Hide the task view and wait for the window to be resized + // TODO: Consider animating in launcher and do an in-place start activity + // afterwards + mRecentsView.setIgnoreResetTask(taskId); + mTaskView.setAlpha(0f); + }; + + final int[] position = new int[2]; + mThumbnailView.getLocationOnScreen(position); + final int width = (int) (mThumbnailView.getWidth() * mTaskView.getScaleX()); + final int height = (int) (mThumbnailView.getHeight() * mTaskView.getScaleY()); + final Rect taskBounds = new Rect(position[0], position[1], + position[0] + width, position[1] + height); + + // Take the thumbnail of the task without a scrim and apply it back after + float alpha = mThumbnailView.getDimAlpha(); + mThumbnailView.setDimAlpha(0); + Bitmap thumbnail = RecentsTransition.drawViewIntoHardwareBitmap( + taskBounds.width(), taskBounds.height(), mThumbnailView, 1f, + Color.BLACK); + mThumbnailView.setDimAlpha(alpha); + + AppTransitionAnimationSpecsFuture future = + new AppTransitionAnimationSpecsFuture(mHandler) { + @Override + public List composeSpecs() { + return Collections.singletonList(new AppTransitionAnimationSpecCompat( + taskId, thumbnail, taskBounds)); + } + }; + WindowManagerWrapper.getInstance().overridePendingAppTransitionMultiThumbFuture( + future, animStartedListener, mHandler, true /* scaleUp */, + taskKey.displayId); + } + } + } + + TaskShortcutFactory SPLIT_SCREEN = new MultiWindowFactory( + R.drawable.ic_split_screen, R.string.recent_task_option_split_screen) { + + @Override + protected boolean isAvailable(BaseDraggingActivity activity, int displayId) { + // Don't show menu-item if already in multi-window and the task is from + // the secondary display. + // TODO(b/118266305): Temporarily disable splitscreen for secondary display while new + // implementation is enabled + return !activity.getDeviceProfile().isMultiWindowMode + && (displayId == -1 || displayId == DEFAULT_DISPLAY); + } + + @Override + protected ActivityOptions makeLaunchOptions(Activity activity) { + final ActivityCompat act = new ActivityCompat(activity); + final int navBarPosition = WindowManagerWrapper.getInstance().getNavBarPosition( + act.getDisplayId()); + if (navBarPosition == WindowManagerWrapper.NAV_BAR_POS_INVALID) { + return null; + } + boolean dockTopOrLeft = navBarPosition != WindowManagerWrapper.NAV_BAR_POS_LEFT; + return ActivityOptionsCompat.makeSplitScreenOptions(dockTopOrLeft); + } + + @Override + protected boolean onActivityStarted(BaseDraggingActivity activity) { + SystemUiProxy.INSTANCE.get(activity).onSplitScreenInvoked(); + activity.getUserEventDispatcher().logActionOnControl(TAP, + LauncherLogProto.ControlType.SPLIT_SCREEN_TARGET); + return true; + } + }; + + TaskShortcutFactory FREE_FORM = new MultiWindowFactory( + R.drawable.ic_split_screen, R.string.recent_task_option_freeform) { + + @Override + protected boolean isAvailable(BaseDraggingActivity activity, int displayId) { + return ActivityManagerWrapper.getInstance().supportsFreeformMultiWindow(activity); + } + + @Override + protected ActivityOptions makeLaunchOptions(Activity activity) { + ActivityOptions activityOptions = ActivityOptionsCompat.makeFreeformOptions(); + // Arbitrary bounds only because freeform is in dev mode right now + Rect r = new Rect(50, 50, 200, 200); + activityOptions.setLaunchBounds(r); + return activityOptions; + } + + @Override + protected boolean onActivityStarted(BaseDraggingActivity activity) { + activity.returnToHomescreen(); + return true; + } + }; + + TaskShortcutFactory PIN = (activity, tv) -> { + if (!SystemUiProxy.INSTANCE.get(activity).isActive()) { + return null; + } + if (!ActivityManagerWrapper.getInstance().isScreenPinningEnabled()) { + return null; + } + if (ActivityManagerWrapper.getInstance().isLockToAppActive()) { + // We shouldn't be able to pin while an app is locked. + return null; + } + return new PinSystemShortcut(activity, tv); + }; + + class PinSystemShortcut extends SystemShortcut { + + private static final String TAG = "PinSystemShortcut"; + + private final TaskView mTaskView; + + public PinSystemShortcut(BaseDraggingActivity target, TaskView tv) { + super(R.drawable.ic_pin, R.string.recent_task_option_pin, target, dummyInfo(tv)); + mTaskView = tv; + } + + @Override + public void onClick(View view) { + Consumer resultCallback = success -> { + if (success) { + SystemUiProxy.INSTANCE.get(mTarget).startScreenPinning( + mTaskView.getTask().key.id); + } else { + mTaskView.notifyTaskLaunchFailed(TAG); + } + }; + mTaskView.launchTask(true, resultCallback, Executors.MAIN_EXECUTOR.getHandler()); + dismissTaskMenuView(mTarget); + } + } + + TaskShortcutFactory INSTALL = (activity, view) -> + InstantAppResolver.newInstance(activity).isInstantApp(activity, + view.getTask().getTopComponent().getPackageName()) + ? new SystemShortcut.Install(activity, dummyInfo(view)) : null; + + TaskShortcutFactory WELLBEING = (activity, view) -> + WellbeingModel.SHORTCUT_FACTORY.getShortcut(activity, dummyInfo(view)); +} diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskSystemShortcut.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskSystemShortcut.java deleted file mode 100644 index 5a2e3ff821..0000000000 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskSystemShortcut.java +++ /dev/null @@ -1,327 +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 android.view.Display.DEFAULT_DISPLAY; -import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.TAP; - -import android.app.Activity; -import android.app.ActivityOptions; -import android.content.ComponentName; -import android.content.Intent; -import android.graphics.Bitmap; -import android.graphics.Color; -import android.graphics.Rect; -import android.os.Handler; -import android.os.Looper; -import android.os.UserHandle; -import android.view.View; - -import com.android.launcher3.BaseDraggingActivity; -import com.android.launcher3.DeviceProfile; -import com.android.launcher3.ItemInfo; -import com.android.launcher3.R; -import com.android.launcher3.WorkspaceItemInfo; -import com.android.launcher3.popup.SystemShortcut; -import com.android.launcher3.userevent.nano.LauncherLogProto; -import com.android.launcher3.util.InstantAppResolver; -import com.android.quickstep.views.RecentsView; -import com.android.quickstep.views.TaskThumbnailView; -import com.android.quickstep.views.TaskView; -import com.android.systemui.shared.recents.model.Task; -import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat; -import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture; -import com.android.systemui.shared.recents.view.RecentsTransition; -import com.android.systemui.shared.system.ActivityCompat; -import com.android.systemui.shared.system.ActivityManagerWrapper; -import com.android.systemui.shared.system.ActivityOptionsCompat; -import com.android.systemui.shared.system.WindowManagerWrapper; - -import java.util.Collections; -import java.util.List; -import java.util.function.Consumer; - -/** - * Represents a system shortcut that can be shown for a recent task. - */ -public class TaskSystemShortcut extends SystemShortcut { - - private static final String TAG = "TaskSystemShortcut"; - - protected T mSystemShortcut; - - public TaskSystemShortcut(T systemShortcut) { - super(systemShortcut); - mSystemShortcut = systemShortcut; - } - - protected TaskSystemShortcut(int iconResId, int labelResId) { - super(iconResId, labelResId); - } - - @Override - public View.OnClickListener getOnClickListener( - BaseDraggingActivity activity, ItemInfo itemInfo) { - return null; - } - - public View.OnClickListener getOnClickListener(BaseDraggingActivity activity, TaskView view) { - Task task = view.getTask(); - - WorkspaceItemInfo dummyInfo = new WorkspaceItemInfo(); - dummyInfo.intent = new Intent(); - ComponentName component = task.getTopComponent(); - dummyInfo.intent.setComponent(component); - dummyInfo.user = UserHandle.of(task.key.userId); - dummyInfo.title = TaskUtils.getTitle(activity, task); - - return getOnClickListenerForTask(activity, task, dummyInfo); - } - - protected View.OnClickListener getOnClickListenerForTask( - BaseDraggingActivity activity, Task task, ItemInfo dummyInfo) { - return mSystemShortcut.getOnClickListener(activity, dummyInfo); - } - - public static class AppInfo extends TaskSystemShortcut { - public AppInfo() { - super(new SystemShortcut.AppInfo()); - } - } - - public static abstract class MultiWindow extends TaskSystemShortcut { - - private Handler mHandler; - - public MultiWindow(int iconRes, int textRes) { - super(iconRes, textRes); - mHandler = new Handler(Looper.getMainLooper()); - } - - protected abstract boolean isAvailable(BaseDraggingActivity activity, int displayId); - protected abstract ActivityOptions makeLaunchOptions(Activity activity); - protected abstract boolean onActivityStarted(BaseDraggingActivity activity); - - @Override - public View.OnClickListener getOnClickListener( - BaseDraggingActivity activity, TaskView taskView) { - final Task task = taskView.getTask(); - final int taskId = task.key.id; - final int displayId = task.key.displayId; - if (!task.isDockable) { - return null; - } - if (!isAvailable(activity, displayId)) { - return null; - } - final RecentsView recentsView = activity.getOverviewPanel(); - - final TaskThumbnailView thumbnailView = taskView.getThumbnail(); - return (v -> { - final View.OnLayoutChangeListener onLayoutChangeListener = - new View.OnLayoutChangeListener() { - @Override - public void onLayoutChange(View v, int l, int t, int r, int b, - int oldL, int oldT, int oldR, int oldB) { - taskView.getRootView().removeOnLayoutChangeListener(this); - recentsView.clearIgnoreResetTask(taskId); - - // Start animating in the side pages once launcher has been resized - recentsView.dismissTask(taskView, false, false); - } - }; - - final DeviceProfile.OnDeviceProfileChangeListener onDeviceProfileChangeListener = - new DeviceProfile.OnDeviceProfileChangeListener() { - @Override - public void onDeviceProfileChanged(DeviceProfile dp) { - activity.removeOnDeviceProfileChangeListener(this); - if (dp.isMultiWindowMode) { - taskView.getRootView().addOnLayoutChangeListener( - onLayoutChangeListener); - } - } - }; - - dismissTaskMenuView(activity); - - ActivityOptions options = makeLaunchOptions(activity); - if (options != null - && ActivityManagerWrapper.getInstance().startActivityFromRecents(taskId, - options)) { - if (!onActivityStarted(activity)) { - return; - } - // Add a device profile change listener to kick off animating the side tasks - // once we enter multiwindow mode and relayout - activity.addOnDeviceProfileChangeListener(onDeviceProfileChangeListener); - - final Runnable animStartedListener = () -> { - // Hide the task view and wait for the window to be resized - // TODO: Consider animating in launcher and do an in-place start activity - // afterwards - recentsView.setIgnoreResetTask(taskId); - taskView.setAlpha(0f); - }; - - final int[] position = new int[2]; - thumbnailView.getLocationOnScreen(position); - final int width = (int) (thumbnailView.getWidth() * taskView.getScaleX()); - final int height = (int) (thumbnailView.getHeight() * taskView.getScaleY()); - final Rect taskBounds = new Rect(position[0], position[1], - position[0] + width, position[1] + height); - - // Take the thumbnail of the task without a scrim and apply it back after - float alpha = thumbnailView.getDimAlpha(); - thumbnailView.setDimAlpha(0); - Bitmap thumbnail = RecentsTransition.drawViewIntoHardwareBitmap( - taskBounds.width(), taskBounds.height(), thumbnailView, 1f, - Color.BLACK); - thumbnailView.setDimAlpha(alpha); - - AppTransitionAnimationSpecsFuture future = - new AppTransitionAnimationSpecsFuture(mHandler) { - @Override - public List composeSpecs() { - return Collections.singletonList(new AppTransitionAnimationSpecCompat( - taskId, thumbnail, taskBounds)); - } - }; - WindowManagerWrapper.getInstance().overridePendingAppTransitionMultiThumbFuture( - future, animStartedListener, mHandler, true /* scaleUp */, displayId); - } - }); - } - } - - public static class SplitScreen extends MultiWindow { - public SplitScreen() { - super(R.drawable.ic_split_screen, R.string.recent_task_option_split_screen); - } - - @Override - protected boolean isAvailable(BaseDraggingActivity activity, int displayId) { - // Don't show menu-item if already in multi-window and the task is from - // the secondary display. - // TODO(b/118266305): Temporarily disable splitscreen for secondary display while new - // implementation is enabled - return !activity.getDeviceProfile().isMultiWindowMode - && (displayId == -1 || displayId == DEFAULT_DISPLAY); - } - - @Override - protected ActivityOptions makeLaunchOptions(Activity activity) { - final ActivityCompat act = new ActivityCompat(activity); - final int navBarPosition = WindowManagerWrapper.getInstance().getNavBarPosition( - act.getDisplayId()); - if (navBarPosition == WindowManagerWrapper.NAV_BAR_POS_INVALID) { - return null; - } - boolean dockTopOrLeft = navBarPosition != WindowManagerWrapper.NAV_BAR_POS_LEFT; - return ActivityOptionsCompat.makeSplitScreenOptions(dockTopOrLeft); - } - - @Override - protected boolean onActivityStarted(BaseDraggingActivity activity) { - SystemUiProxy.INSTANCE.get(activity).onSplitScreenInvoked(); - activity.getUserEventDispatcher().logActionOnControl(TAP, - LauncherLogProto.ControlType.SPLIT_SCREEN_TARGET); - return true; - } - } - - public static class Freeform extends MultiWindow { - public Freeform() { - super(R.drawable.ic_split_screen, R.string.recent_task_option_freeform); - } - - @Override - protected boolean isAvailable(BaseDraggingActivity activity, int displayId) { - return ActivityManagerWrapper.getInstance().supportsFreeformMultiWindow(activity); - } - - @Override - protected ActivityOptions makeLaunchOptions(Activity activity) { - ActivityOptions activityOptions = ActivityOptionsCompat.makeFreeformOptions(); - // Arbitrary bounds only because freeform is in dev mode right now - Rect r = new Rect(50, 50, 200, 200); - activityOptions.setLaunchBounds(r); - return activityOptions; - } - - @Override - protected boolean onActivityStarted(BaseDraggingActivity activity) { - activity.returnToHomescreen(); - return true; - } - } - - public static class Pin extends TaskSystemShortcut { - - private static final String TAG = Pin.class.getSimpleName(); - - private Handler mHandler; - - public Pin() { - super(R.drawable.ic_pin, R.string.recent_task_option_pin); - mHandler = new Handler(Looper.getMainLooper()); - } - - @Override - public View.OnClickListener getOnClickListener( - BaseDraggingActivity activity, TaskView taskView) { - if (!SystemUiProxy.INSTANCE.get(activity).isActive()) { - return null; - } - if (!ActivityManagerWrapper.getInstance().isScreenPinningEnabled()) { - return null; - } - if (ActivityManagerWrapper.getInstance().isLockToAppActive()) { - // We shouldn't be able to pin while an app is locked. - return null; - } - return view -> { - Consumer resultCallback = success -> { - if (success) { - SystemUiProxy.INSTANCE.get(activity).startScreenPinning( - taskView.getTask().key.id); - } else { - taskView.notifyTaskLaunchFailed(TAG); - } - }; - taskView.launchTask(true, resultCallback, mHandler); - dismissTaskMenuView(activity); - }; - } - } - - public static class Install extends TaskSystemShortcut { - public Install() { - super(new SystemShortcut.Install()); - } - - @Override - protected View.OnClickListener getOnClickListenerForTask( - BaseDraggingActivity activity, Task task, ItemInfo itemInfo) { - if (InstantAppResolver.newInstance(activity).isInstantApp(activity, - task.getTopComponent().getPackageName())) { - return mSystemShortcut.createOnClickListener(activity, itemInfo); - } - return null; - } - } -} diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java index 5591e40b8d..52ae1156dc 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java @@ -33,8 +33,8 @@ import android.annotation.TargetApi; import android.app.ActivityManager; import android.app.ActivityManager.RunningTaskInfo; import android.app.Service; -import android.app.TaskInfo; import android.content.ComponentName; +import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Configuration; @@ -49,12 +49,11 @@ import android.view.InputEvent; import android.view.MotionEvent; import androidx.annotation.BinderThread; +import androidx.annotation.Nullable; import androidx.annotation.UiThread; import androidx.annotation.WorkerThread; import com.android.launcher3.BaseDraggingActivity; -import com.android.launcher3.Launcher; -import com.android.launcher3.LauncherAppState; import com.android.launcher3.Utilities; import com.android.launcher3.allapps.DiscoveryBounce; import com.android.launcher3.config.FeatureFlags; @@ -62,20 +61,21 @@ import com.android.launcher3.logging.UserEventDispatcher; import com.android.launcher3.model.AppLaunchTracker; import com.android.launcher3.provider.RestoreDbTask; import com.android.launcher3.testing.TestProtocol; +import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper; import com.android.launcher3.util.TraceHelper; -import com.android.quickstep.SysUINavigationMode.Mode; -import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener; import com.android.quickstep.inputconsumers.AccessibilityInputConsumer; import com.android.quickstep.inputconsumers.AssistantInputConsumer; import com.android.quickstep.inputconsumers.DeviceLockedInputConsumer; -import com.android.quickstep.inputconsumers.FallbackNoButtonInputConsumer; import com.android.quickstep.inputconsumers.OtherActivityInputConsumer; +import com.android.quickstep.inputconsumers.OverscrollInputConsumer; import com.android.quickstep.inputconsumers.OverviewInputConsumer; import com.android.quickstep.inputconsumers.OverviewWithoutFocusInputConsumer; -import com.android.quickstep.inputconsumers.QuickCaptureInputConsumer; import com.android.quickstep.inputconsumers.ResetGestureInputConsumer; import com.android.quickstep.inputconsumers.ScreenPinnedInputConsumer; import com.android.quickstep.util.ActiveGestureLog; +import com.android.quickstep.util.AssistantUtilities; +import com.android.systemui.plugins.OverscrollPlugin; +import com.android.systemui.plugins.PluginListener; import com.android.systemui.shared.recents.IOverviewProxy; import com.android.systemui.shared.recents.ISystemUiProxy; import com.android.systemui.shared.system.ActivityManagerWrapper; @@ -83,7 +83,6 @@ import com.android.systemui.shared.system.InputChannelCompat.InputEventReceiver; import com.android.systemui.shared.system.InputConsumerController; import com.android.systemui.shared.system.InputMonitorCompat; import com.android.systemui.shared.system.RecentsAnimationListener; -import com.android.systemui.shared.system.TaskInfoCompat; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -112,8 +111,7 @@ class ArgList extends LinkedList { * Service connected by system-UI for handling touch interaction. */ @TargetApi(Build.VERSION_CODES.Q) -public class TouchInteractionService extends Service implements - NavigationModeChangeListener { +public class TouchInteractionService extends Service implements PluginListener { private static final String TAG = "TouchInteractionService"; @@ -122,6 +120,8 @@ public class TouchInteractionService extends Service implements private static final String HAS_ENABLED_QUICKSTEP_ONCE = "launcher.has_enabled_quickstep_once"; private static final int MAX_BACK_NOTIFICATION_COUNT = 3; private int mBackGestureNotificationCounter = -1; + @Nullable + private OverscrollPlugin mOverscrollPlugin; private final IBinder mMyBinder = new IOverviewProxy.Stub() { @@ -129,13 +129,11 @@ public class TouchInteractionService extends Service implements public void onInitialize(Bundle bundle) { ISystemUiProxy proxy = ISystemUiProxy.Stub.asInterface( bundle.getBinder(KEY_EXTRA_SYSUI_PROXY)); - MAIN_EXECUTOR.execute(() -> SystemUiProxy.INSTANCE.get(TouchInteractionService.this) - .setProxy(proxy)); - MAIN_EXECUTOR.execute(TouchInteractionService.this::initInputMonitor); - MAIN_EXECUTOR.execute(() -> preloadOverview(true /* fromInit */)); - if (TestProtocol.sDebugTracing) { - Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE, "TIS initialized"); - } + MAIN_EXECUTOR.execute(() -> { + SystemUiProxy.INSTANCE.get(TouchInteractionService.this).setProxy(proxy); + TouchInteractionService.this.initInputMonitor(); + preloadOverview(true /* fromInit */); + }); sIsInitialized = true; } @@ -169,15 +167,19 @@ public class TouchInteractionService extends Service implements @BinderThread @Override public void onAssistantAvailable(boolean available) { - MAIN_EXECUTOR.execute(() -> mDeviceState.setAssistantAvailable(available)); - MAIN_EXECUTOR.execute(TouchInteractionService.this::onAssistantVisibilityChanged); + MAIN_EXECUTOR.execute(() -> { + mDeviceState.setAssistantAvailable(available); + TouchInteractionService.this.onAssistantVisibilityChanged(); + }); } @BinderThread @Override public void onAssistantVisibilityChanged(float visibility) { - MAIN_EXECUTOR.execute(() -> mDeviceState.setAssistantVisibility(visibility)); - MAIN_EXECUTOR.execute(TouchInteractionService.this::onAssistantVisibilityChanged); + MAIN_EXECUTOR.execute(() -> { + mDeviceState.setAssistantVisibility(visibility); + TouchInteractionService.this.onAssistantVisibilityChanged(); + }); } @BinderThread @@ -199,8 +201,10 @@ public class TouchInteractionService extends Service implements @BinderThread public void onSystemUiStateChanged(int stateFlags) { - MAIN_EXECUTOR.execute(() -> mDeviceState.setSystemUiFlags(stateFlags)); - MAIN_EXECUTOR.execute(TouchInteractionService.this::onSystemUiFlagsChanged); + MAIN_EXECUTOR.execute(() -> { + mDeviceState.setSystemUiFlags(stateFlags); + TouchInteractionService.this.onSystemUiFlagsChanged(); + }); } @BinderThread @@ -228,8 +232,6 @@ public class TouchInteractionService extends Service implements private static boolean sConnected = false; private static boolean sIsInitialized = false; - private static final SwipeSharedState sSwipeSharedState = new SwipeSharedState(); - private int mLogId; public static boolean isConnected() { return sConnected; @@ -239,45 +241,38 @@ public class TouchInteractionService extends Service implements return sIsInitialized; } - public static SwipeSharedState getSwipeSharedState() { - return sSwipeSharedState; - } - - private final InputConsumer mResetGestureInputConsumer = - new ResetGestureInputConsumer(sSwipeSharedState); - - private final BaseSwipeUpHandler.Factory mWindowTreansformFactory = - this::createWindowTransformSwipeHandler; - private final BaseSwipeUpHandler.Factory mFallbackNoButtonFactory = - this::createFallbackNoButtonSwipeHandler; + private final BaseSwipeUpHandler.Factory mLauncherSwipeHandlerFactory = + this::createLauncherSwipeHandler; + private final BaseSwipeUpHandler.Factory mFallbackSwipeHandlerFactory = + this::createFallbackSwipeHandler; private ActivityManagerWrapper mAM; - private RecentsModel mRecentsModel; private OverviewCommandHelper mOverviewCommandHelper; private OverviewComponentObserver mOverviewComponentObserver; private InputConsumerController mInputConsumer; private RecentsAnimationDeviceState mDeviceState; + private TaskAnimationManager mTaskAnimationManager; private InputConsumer mUncheckedConsumer = InputConsumer.NO_OP; private InputConsumer mConsumer = InputConsumer.NO_OP; private Choreographer mMainChoreographer; + private InputConsumer mResetGestureInputConsumer; + private GestureState mGestureState = new GestureState(); private InputMonitorCompat mInputMonitorCompat; private InputEventReceiver mInputEventReceiver; - private Mode mMode = Mode.THREE_BUTTONS; @Override public void onCreate() { super.onCreate(); - mDeviceState = new RecentsAnimationDeviceState(this); - mDeviceState.runOnUserUnlocked(this::onUserUnlocked); - // Initialize anything here that is needed in direct boot mode. // Everything else should be initialized in onUserUnlocked() below. mMainChoreographer = Choreographer.getInstance(); mAM = ActivityManagerWrapper.getInstance(); + mDeviceState = new RecentsAnimationDeviceState(this); + mDeviceState.addNavigationModeChangedCallback(this::onNavigationModeChanged); + mDeviceState.runOnUserUnlocked(this::onUserUnlocked); - onNavigationModeChanged(SysUINavigationMode.INSTANCE.get(this).addModeChangeListener(this)); sConnected = true; } @@ -300,7 +295,7 @@ public class TouchInteractionService extends Service implements Log.d(TestProtocol.NO_BACKGROUND_TO_OVERVIEW_TAG, "initInputMonitor 1"); } disposeEventHandlers(); - if (!mMode.hasGestures || !SystemUiProxy.INSTANCE.get(this).isActive()) { + if (mDeviceState.isButtonNavMode() || !SystemUiProxy.INSTANCE.get(this).isActive()) { return; } if (TestProtocol.sDebugTracing) { @@ -319,25 +314,22 @@ public class TouchInteractionService extends Service implements mDeviceState.updateGestureTouchRegions(); } - @Override - public void onNavigationModeChanged(Mode newMode) { - if (TestProtocol.sDebugTracing) { - Log.d(TestProtocol.NO_BACKGROUND_TO_OVERVIEW_TAG, "onNavigationModeChanged " + newMode); - } - mMode = newMode; + /** + * Called when the navigation mode changes, guaranteed to be after the device state has updated. + */ + private void onNavigationModeChanged(SysUINavigationMode.Mode mode) { initInputMonitor(); resetHomeBounceSeenOnQuickstepEnabledFirstTime(); } @UiThread public void onUserUnlocked() { - mRecentsModel = RecentsModel.INSTANCE.get(this); + mTaskAnimationManager = new TaskAnimationManager(); mOverviewComponentObserver = new OverviewComponentObserver(this, mDeviceState); mOverviewCommandHelper = new OverviewCommandHelper(this, mDeviceState, mOverviewComponentObserver); + mResetGestureInputConsumer = new ResetGestureInputConsumer(mTaskAnimationManager); mInputConsumer = InputConsumerController.getRecentsAnimationInputConsumer(); - - sSwipeSharedState.setOverviewComponentObserver(mOverviewComponentObserver); mInputConsumer.registerInputConsumer(); onSystemUiFlagsChanged(); onAssistantVisibilityChanged(); @@ -347,10 +339,24 @@ public class TouchInteractionService extends Service implements mBackGestureNotificationCounter = Math.max(0, Utilities.getDevicePrefs(this) .getInt(KEY_BACK_NOTIFICATION_COUNT, MAX_BACK_NOTIFICATION_COUNT)); resetHomeBounceSeenOnQuickstepEnabledFirstTime(); + + PluginManagerWrapper.INSTANCE.get(getBaseContext()).addPluginListener(this, + OverscrollPlugin.class, false /* allowMultiple */); + } + + private void onDeferredActivityLaunch() { + if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { + mOverviewComponentObserver.getActivityInterface().switchRunningTaskViewToScreenshot( + null, () -> { + mTaskAnimationManager.finishRunningRecentsAnimation(true /* toHome */); + }); + } else { + mTaskAnimationManager.finishRunningRecentsAnimation(true /* toHome */); + } } private void resetHomeBounceSeenOnQuickstepEnabledFirstTime() { - if (!mDeviceState.isUserUnlocked() || !mMode.hasGestures) { + if (!mDeviceState.isUserUnlocked() || mDeviceState.isButtonNavMode()) { // Skip if not yet unlocked (can't read user shared prefs) or if the current navigation // mode doesn't have gestures return; @@ -385,9 +391,8 @@ public class TouchInteractionService extends Service implements @Override public void onDestroy() { - if (TestProtocol.sDebugTracing) { - Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE, "TIS destroyed"); - } + PluginManagerWrapper.INSTANCE.get(getBaseContext()).removePluginListener(this); + sIsInitialized = false; if (mDeviceState.isUserUnlocked()) { mInputConsumer.unregisterInputConsumer(); @@ -395,7 +400,6 @@ public class TouchInteractionService extends Service implements } disposeEventHandlers(); mDeviceState.destroy(); - SysUINavigationMode.INSTANCE.get(this).removeModeChangeListener(this); SystemUiProxy.INSTANCE.get(this).setProxy(null); sConnected = false; @@ -424,19 +428,19 @@ public class TouchInteractionService extends Service implements TraceHelper.FLAG_ALLOW_BINDER_TRACKING); MotionEvent event = (MotionEvent) ev; if (event.getAction() == ACTION_DOWN) { - GestureState newGestureState = new GestureState( - mOverviewComponentObserver.getActivityInterface()); - - mLogId = ActiveGestureLog.INSTANCE.generateAndSetLogId(); - sSwipeSharedState.setLogTraceId(mLogId); + GestureState newGestureState = new GestureState(mOverviewComponentObserver, + ActiveGestureLog.INSTANCE.generateAndSetLogId()); + newGestureState.updateRunningTask(TraceHelper.whitelistIpcs("getRunningTask.0", + () -> mAM.getRunningTask(0))); if (mDeviceState.isInSwipeUpTouchRegion(event)) { - boolean useSharedState = mConsumer.useSharedSwipeState(); mConsumer.onConsumerAboutToBeSwitched(); - mConsumer = newConsumer(newGestureState, useSharedState, event); + mConsumer = newConsumer(mGestureState, newGestureState, event); + ActiveGestureLog.INSTANCE.addLog("setInputConsumer", mConsumer.getType()); mUncheckedConsumer = mConsumer; - } else if (mDeviceState.isUserUnlocked() && mMode == Mode.NO_BUTTON + } else if (mDeviceState.isUserUnlocked() + && mDeviceState.isFullyGesturalNavMode() && mDeviceState.canTriggerAssistantAction(event)) { // Do not change mConsumer as if there is an ongoing QuickSwitch gesture, we should // not interrupt it. QuickSwitch assumes that interruption can only happen if the @@ -446,6 +450,9 @@ public class TouchInteractionService extends Service implements } else { mUncheckedConsumer = InputConsumer.NO_OP; } + + // Save the current gesture state + mGestureState = newGestureState; } ActiveGestureLog.INSTANCE.addLog("onMotionEvent", event.getActionMasked()); @@ -453,39 +460,41 @@ public class TouchInteractionService extends Service implements TraceHelper.INSTANCE.endFlagsOverride(traceToken); } - private InputConsumer newConsumer(GestureState gestureState, boolean useSharedState, - MotionEvent event) { + private InputConsumer newConsumer(GestureState previousGestureState, + GestureState newGestureState, MotionEvent event) { boolean canStartSystemGesture = mDeviceState.canStartSystemGesture(); if (!mDeviceState.isUserUnlocked()) { if (canStartSystemGesture) { // This handles apps launched in direct boot mode (e.g. dialer) as well as apps // launched while device is locked even after exiting direct boot mode (e.g. camera). - return createDeviceLockedInputConsumer(gestureState, - mAM.getRunningTask(ACTIVITY_TYPE_ASSISTANT)); + return createDeviceLockedInputConsumer(newGestureState); } else { return mResetGestureInputConsumer; } } - // When using sharedState, bypass systemState check as this is a followup gesture and the - // first gesture started in a valid system state. - InputConsumer base = canStartSystemGesture || useSharedState - ? newBaseConsumer(gestureState, useSharedState, event) : mResetGestureInputConsumer; - if (mMode == Mode.NO_BUTTON) { + // When there is an existing recents animation running, bypass systemState check as this is + // a followup gesture and the first gesture started in a valid system state. + InputConsumer base = canStartSystemGesture + || previousGestureState.isRecentsAnimationRunning() + ? newBaseConsumer(previousGestureState, newGestureState, event) + : mResetGestureInputConsumer; + if (mDeviceState.isFullyGesturalNavMode()) { if (mDeviceState.canTriggerAssistantAction(event)) { - base = new AssistantInputConsumer(this, gestureState, base, mInputMonitorCompat); + base = new AssistantInputConsumer(this, newGestureState, base, mInputMonitorCompat); } - if (FeatureFlags.ENABLE_QUICK_CAPTURE_GESTURE.get()) { - // Put the Compose gesture as higher priority than the Assistant or base gestures - base = new QuickCaptureInputConsumer(this, gestureState, base, mInputMonitorCompat); + if (mOverscrollPlugin != null) { + // Put the overscroll gesture as higher priority than the Assistant or base gestures + base = new OverscrollInputConsumer(this, newGestureState, base, mInputMonitorCompat, + mOverscrollPlugin); } if (mDeviceState.isScreenPinningActive()) { // Note: we only allow accessibility to wrap this, and it replaces the previous // base input consumer (which should be NO_OP anyway since topTaskLocked == true). - base = new ScreenPinnedInputConsumer(this, gestureState); + base = new ScreenPinnedInputConsumer(this, newGestureState); } if (mDeviceState.isAccessibilityMenuAvailable()) { @@ -500,6 +509,7 @@ public class TouchInteractionService extends Service implements return base; } +<<<<<<< HEAD private InputConsumer newBaseConsumer(GestureState gestureState, boolean useSharedState, MotionEvent event) { RunningTaskInfo runningTaskInfo = TraceHelper.whitelistIpcs("getRunningTask.0", @@ -507,15 +517,20 @@ public class TouchInteractionService extends Service implements if (!useSharedState) { sSwipeSharedState.clearAllState(false /* finishAnimation */); } +======= + private InputConsumer newBaseConsumer(GestureState previousGestureState, + GestureState gestureState, MotionEvent event) { +>>>>>>> ub-launcher3-master if (mDeviceState.isKeyguardShowingOccluded()) { // This handles apps showing over the lockscreen (e.g. camera) - return createDeviceLockedInputConsumer(gestureState, runningTaskInfo); + return createDeviceLockedInputConsumer(gestureState); } boolean forceOverviewInputConsumer = false; - if (isExcludedAssistant(runningTaskInfo)) { + if (AssistantUtilities.isExcludedAssistant(gestureState.getRunningTask())) { // In the case where we are in the excluded assistant state, ignore it and treat the // running activity as the task behind the assistant +<<<<<<< HEAD runningTaskInfo = TraceHelper.whitelistIpcs("getRunningTask.assistant", () -> mAM.getRunningTask(ACTIVITY_TYPE_ASSISTANT)); @@ -525,80 +540,90 @@ public class TouchInteractionService extends Service implements forceOverviewInputConsumer = runningTaskInfo.baseIntent.getComponent(). equals(homeComponent); } +======= + gestureState.updateRunningTask(TraceHelper.whitelistIpcs("getRunningTask.assistant", + () -> mAM.getRunningTask(ACTIVITY_TYPE_ASSISTANT /* ignoreActivityType */))); + ComponentName homeComponent = mOverviewComponentObserver.getHomeIntent().getComponent(); + ComponentName runningComponent = + gestureState.getRunningTask().baseIntent.getComponent(); + forceOverviewInputConsumer = + runningComponent != null && runningComponent.equals(homeComponent); +>>>>>>> ub-launcher3-master } - if (runningTaskInfo == null && !sSwipeSharedState.goingToLauncher - && !sSwipeSharedState.recentsAnimationFinishInterrupted) { - return mResetGestureInputConsumer; - } else if (sSwipeSharedState.recentsAnimationFinishInterrupted) { + if (previousGestureState.getFinishingRecentsAnimationTaskId() > 0) { // If the finish animation was interrupted, then continue using the other activity input // consumer but with the next task as the running task RunningTaskInfo info = new ActivityManager.RunningTaskInfo(); - info.id = sSwipeSharedState.nextRunningTaskId; - return createOtherActivityInputConsumer(gestureState, event, info); - } else if (sSwipeSharedState.goingToLauncher + info.id = previousGestureState.getFinishingRecentsAnimationTaskId(); + gestureState.updateRunningTask(info); + return createOtherActivityInputConsumer(previousGestureState, gestureState, event); + } else if (gestureState.getRunningTask() == null) { + return mResetGestureInputConsumer; + } else if (previousGestureState.isRunningAnimationToLauncher() || gestureState.getActivityInterface().isResumed() || forceOverviewInputConsumer) { - return createOverviewInputConsumer(gestureState, event); + return createOverviewInputConsumer( + previousGestureState, gestureState, event, forceOverviewInputConsumer); } else if (ENABLE_QUICKSTEP_LIVE_TILE.get() && gestureState.getActivityInterface().isInLiveTileMode()) { - return createOverviewInputConsumer(gestureState, event); - } else if (mDeviceState.isGestureBlockedActivity(runningTaskInfo)) { + return createOverviewInputConsumer( + previousGestureState, gestureState, event, forceOverviewInputConsumer); + } else if (mDeviceState.isGestureBlockedActivity(gestureState.getRunningTask())) { return mResetGestureInputConsumer; } else { - return createOtherActivityInputConsumer(gestureState, event, runningTaskInfo); + return createOtherActivityInputConsumer(previousGestureState, gestureState, event); } } - private boolean isExcludedAssistant(TaskInfo info) { - return info != null - && TaskInfoCompat.getActivityType(info) == ACTIVITY_TYPE_ASSISTANT - && (info.baseIntent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0; - } - - private InputConsumer createOtherActivityInputConsumer(GestureState gestureState, - MotionEvent event, RunningTaskInfo runningTaskInfo) { + private InputConsumer createOtherActivityInputConsumer(GestureState previousGestureState, + GestureState gestureState, MotionEvent event) { final boolean shouldDefer; final BaseSwipeUpHandler.Factory factory; - if (mMode == Mode.NO_BUTTON && !mOverviewComponentObserver.isHomeAndOverviewSame()) { - shouldDefer = !sSwipeSharedState.recentsAnimationFinishInterrupted; - factory = mFallbackNoButtonFactory; + if (mDeviceState.isFullyGesturalNavMode() + && !mOverviewComponentObserver.isHomeAndOverviewSame()) { + shouldDefer = previousGestureState.getFinishingRecentsAnimationTaskId() < 0; + factory = mFallbackSwipeHandlerFactory; } else { shouldDefer = gestureState.getActivityInterface().deferStartingActivity(mDeviceState, event); - factory = mWindowTreansformFactory; + factory = mLauncherSwipeHandlerFactory; } final boolean disableHorizontalSwipe = mDeviceState.isInExclusionRegion(event); - return new OtherActivityInputConsumer(this, mDeviceState, gestureState, runningTaskInfo, - shouldDefer, this::onConsumerInactive, sSwipeSharedState, mInputMonitorCompat, - disableHorizontalSwipe, factory, mLogId); + return new OtherActivityInputConsumer(this, mDeviceState, mTaskAnimationManager, + gestureState, shouldDefer, this::onConsumerInactive, + mInputMonitorCompat, disableHorizontalSwipe, factory); } - private InputConsumer createDeviceLockedInputConsumer(GestureState gestureState, - RunningTaskInfo taskInfo) { - if (mMode == Mode.NO_BUTTON && taskInfo != null) { - return new DeviceLockedInputConsumer(this, mDeviceState, gestureState, - sSwipeSharedState, mInputMonitorCompat, taskInfo.taskId, mLogId); + private InputConsumer createDeviceLockedInputConsumer(GestureState gestureState) { + if (mDeviceState.isFullyGesturalNavMode() && gestureState.getRunningTask() != null) { + return new DeviceLockedInputConsumer(this, mDeviceState, mTaskAnimationManager, + gestureState, mInputMonitorCompat); } else { return mResetGestureInputConsumer; } } - public InputConsumer createOverviewInputConsumer(GestureState gestureState, MotionEvent event) { + public InputConsumer createOverviewInputConsumer(GestureState previousGestureState, + GestureState gestureState, MotionEvent event, + boolean forceOverviewInputConsumer) { BaseDraggingActivity activity = gestureState.getActivityInterface().getCreatedActivity(); if (activity == null) { return mResetGestureInputConsumer; } - if (activity.getRootView().hasWindowFocus() || sSwipeSharedState.goingToLauncher) { + if (activity.getRootView().hasWindowFocus() + || previousGestureState.isRunningAnimationToLauncher() + || (FeatureFlags.ASSISTANT_GIVES_LAUNCHER_FOCUS.get() + && forceOverviewInputConsumer)) { return new OverviewInputConsumer(gestureState, activity, mInputMonitorCompat, false /* startingInActivityBounds */); } else { final boolean disableHorizontalSwipe = mDeviceState.isInExclusionRegion(event); - return new OverviewWithoutFocusInputConsumer(activity, gestureState, + return new OverviewWithoutFocusInputConsumer(activity, mDeviceState, gestureState, mInputMonitorCompat, disableHorizontalSwipe); } } @@ -617,7 +642,7 @@ public class TouchInteractionService extends Service implements if (!mDeviceState.isUserUnlocked()) { return; } - if (!mMode.hasGestures && !mOverviewComponentObserver.isHomeAndOverviewSame()) { + if (mDeviceState.isButtonNavMode() && !mOverviewComponentObserver.isHomeAndOverviewSame()) { // Prevent the overview from being started before the real home on first boot. return; } @@ -632,8 +657,8 @@ public class TouchInteractionService extends Service implements mOverviewComponentObserver.getActivityInterface(); if (activityInterface.getCreatedActivity() == null) { // Make sure that UI states will be initialized. - activityInterface.createActivityInitListener((activity, wasVisible) -> { - AppLaunchTracker.INSTANCE.get(activity); + activityInterface.createActivityInitListener((wasVisible) -> { + AppLaunchTracker.INSTANCE.get(TouchInteractionService.this); return false; }).register(); } else if (fromInit) { @@ -643,9 +668,8 @@ public class TouchInteractionService extends Service implements return; } - // Pass null animation handler to indicate this start is preload. - startRecentsActivityAsync(mOverviewComponentObserver.getOverviewIntentIgnoreSysUiState(), - null); + mTaskAnimationManager.preloadRecentsAnimation( + mOverviewComponentObserver.getOverviewIntentIgnoreSysUiState()); } @Override @@ -685,14 +709,9 @@ public class TouchInteractionService extends Service implements // Dump everything mDeviceState.dump(pw); pw.println("TouchState:"); - pw.println(" navMode=" + mMode); boolean resumed = mOverviewComponentObserver != null && mOverviewComponentObserver.getActivityInterface().isResumed(); pw.println(" resumed=" + resumed); - pw.println(" useSharedState=" + mConsumer.useSharedSwipeState()); - if (mConsumer.useSharedSwipeState()) { - sSwipeSharedState.dump(" ", pw); - } pw.println(" mConsumer=" + mConsumer.getName()); pw.println("FeatureFlags:"); pw.println(" APPLY_CONFIG_AT_RUNTIME=" + APPLY_CONFIG_AT_RUNTIME.get()); @@ -718,20 +737,16 @@ public class TouchInteractionService extends Service implements } } - private BaseSwipeUpHandler createWindowTransformSwipeHandler(GestureState gestureState, - RunningTaskInfo runningTask, long touchTimeMs, boolean continuingLastGesture, - boolean isLikelyToStartNewTask) { - return new WindowTransformSwipeHandler(this, mDeviceState, gestureState, runningTask, - touchTimeMs, mOverviewComponentObserver, continuingLastGesture, mInputConsumer, - mRecentsModel); + private BaseSwipeUpHandler createLauncherSwipeHandler(GestureState gestureState, + long touchTimeMs, boolean continuingLastGesture, boolean isLikelyToStartNewTask) { + return new LauncherSwipeHandler(this, mDeviceState, mTaskAnimationManager, + gestureState, touchTimeMs, continuingLastGesture, mInputConsumer); } - private BaseSwipeUpHandler createFallbackNoButtonSwipeHandler(GestureState gestureState, - RunningTaskInfo runningTask, long touchTimeMs, boolean continuingLastGesture, - boolean isLikelyToStartNewTask) { - return new FallbackNoButtonInputConsumer(this, gestureState, mOverviewComponentObserver, - runningTask, mRecentsModel, mInputConsumer, isLikelyToStartNewTask, - continuingLastGesture); + private BaseSwipeUpHandler createFallbackSwipeHandler(GestureState gestureState, + long touchTimeMs, boolean continuingLastGesture, boolean isLikelyToStartNewTask) { + return new FallbackSwipeHandler(this, mDeviceState, gestureState, + mInputConsumer, isLikelyToStartNewTask, continuingLastGesture); } protected boolean shouldNotifyBackGesture() { @@ -754,4 +769,14 @@ public class TouchInteractionService extends Service implements UI_HELPER_EXECUTOR.execute(() -> ActivityManagerWrapper.getInstance() .startRecentsActivity(intent, null, listener, null, null)); } + + @Override + public void onPluginConnected(OverscrollPlugin overscrollPlugin, Context context) { + mOverscrollPlugin = overscrollPlugin; + } + + @Override + public void onPluginDisconnected(OverscrollPlugin overscrollPlugin) { + mOverscrollPlugin = null; + } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java index 0b5129c079..2f73fc1c57 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java @@ -22,11 +22,6 @@ public abstract class DelegateInputConsumer implements InputConsumer { mState = STATE_INACTIVE; } - @Override - public boolean useSharedSwipeState() { - return mDelegate.useSharedSwipeState(); - } - @Override public boolean allowInterceptByParent() { return mDelegate.allowInterceptByParent() && mState != STATE_ACTIVE; diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java index 12b7c2622d..5a345201bf 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java @@ -22,8 +22,7 @@ import static android.view.MotionEvent.ACTION_UP; import static com.android.launcher3.Utilities.squaredHypot; import static com.android.launcher3.Utilities.squaredTouchSlop; import static com.android.quickstep.MultiStateCallback.DEBUG_STATES; -import static com.android.quickstep.TouchInteractionService.startRecentsActivityAsync; -import static com.android.quickstep.WindowTransformSwipeHandler.MIN_PROGRESS_FOR_OVERVIEW; +import static com.android.quickstep.LauncherSwipeHandler.MIN_PROGRESS_FOR_OVERVIEW; import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID; import android.content.ComponentName; @@ -45,9 +44,9 @@ import com.android.quickstep.LockScreenRecentsActivity; import com.android.quickstep.MultiStateCallback; import com.android.quickstep.RecentsAnimationController; import com.android.quickstep.RecentsAnimationDeviceState; -import com.android.quickstep.SwipeSharedState; import com.android.quickstep.RecentsAnimationCallbacks; import com.android.quickstep.RecentsAnimationTargets; +import com.android.quickstep.TaskAnimationManager; import com.android.quickstep.util.AppWindowAnimationHelper; import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.system.InputMonitorCompat; @@ -76,18 +75,16 @@ public class DeviceLockedInputConsumer implements InputConsumer, private final Context mContext; private final RecentsAnimationDeviceState mDeviceState; + private final TaskAnimationManager mTaskAnimationManager; private final GestureState mGestureState; private final float mTouchSlopSquared; - private final SwipeSharedState mSwipeSharedState; private final InputMonitorCompat mInputMonitorCompat; private final PointF mTouchDown = new PointF(); private final AppWindowAnimationHelper mAppWindowAnimationHelper; - private int mLogId; private final AppWindowAnimationHelper.TransformParams mTransformParams; private final Point mDisplaySize; private final MultiStateCallback mStateCallback; - public final int mRunningTaskId; private VelocityTracker mVelocityTracker; private float mProgress; @@ -98,25 +95,23 @@ public class DeviceLockedInputConsumer implements InputConsumer, private RecentsAnimationTargets mRecentsAnimationTargets; public DeviceLockedInputConsumer(Context context, RecentsAnimationDeviceState deviceState, - GestureState gestureState, SwipeSharedState swipeSharedState, - InputMonitorCompat inputMonitorCompat, int runningTaskId, int logId) { + TaskAnimationManager taskAnimationManager, GestureState gestureState, + InputMonitorCompat inputMonitorCompat) { mContext = context; mDeviceState = deviceState; + mTaskAnimationManager = taskAnimationManager; mGestureState = gestureState; mTouchSlopSquared = squaredTouchSlop(context); - mSwipeSharedState = swipeSharedState; mAppWindowAnimationHelper = new AppWindowAnimationHelper(context); - mLogId = logId; mTransformParams = new AppWindowAnimationHelper.TransformParams(); mInputMonitorCompat = inputMonitorCompat; - mRunningTaskId = runningTaskId; // Do not use DeviceProfile as the user data might be locked mDisplaySize = DefaultDisplay.INSTANCE.get(context).getInfo().realSize; // Init states mStateCallback = new MultiStateCallback(STATE_NAMES); - mStateCallback.addCallback(STATE_TARGET_RECEIVED | STATE_HANDLER_INVALIDATED, + mStateCallback.runOnceAtState(STATE_TARGET_RECEIVED | STATE_HANDLER_INVALIDATED, this::endRemoteAnimation); mVelocityTracker = VelocityTracker.obtain(); @@ -207,16 +202,14 @@ public class DeviceLockedInputConsumer implements InputConsumer, private void startRecentsTransition() { mThresholdCrossed = true; - RecentsAnimationCallbacks callbacks = mSwipeSharedState.newRecentsAnimationCallbacks(); - callbacks.addListener(this); + mInputMonitorCompat.pilferPointers(); + Intent intent = new Intent(Intent.ACTION_MAIN) .addCategory(Intent.CATEGORY_DEFAULT) .setComponent(new ComponentName(mContext, LockScreenRecentsActivity.class)) .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK) - .putExtra(INTENT_EXTRA_LOG_TRACE_ID, mLogId); - - mInputMonitorCompat.pilferPointers(); - startRecentsActivityAsync(intent, callbacks); + .putExtra(INTENT_EXTRA_LOG_TRACE_ID, mGestureState.getGestureId()); + mTaskAnimationManager.startRecentsAnimation(mGestureState, intent, this); } @Override @@ -226,7 +219,8 @@ public class DeviceLockedInputConsumer implements InputConsumer, mRecentsAnimationTargets = targets; Rect displaySize = new Rect(0, 0, mDisplaySize.x, mDisplaySize.y); - RemoteAnimationTargetCompat targetCompat = targets.findTask(mRunningTaskId); + RemoteAnimationTargetCompat targetCompat = targets.findTask( + mGestureState.getRunningTaskId()); if (targetCompat != null) { mAppWindowAnimationHelper.updateSource(displaySize, targetCompat); } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java index 02f4c4032d..bf2128dd11 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java @@ -26,12 +26,10 @@ import static android.view.MotionEvent.INVALID_POINTER_ID; import static com.android.launcher3.Utilities.EDGE_NAV_BAR; import static com.android.launcher3.Utilities.squaredHypot; import static com.android.launcher3.util.TraceHelper.FLAG_CHECK_FOR_RACE_CONDITIONS; -import static com.android.quickstep.TouchInteractionService.startRecentsActivityAsync; import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID; import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS; import android.annotation.TargetApi; -import android.app.ActivityManager.RunningTaskInfo; import android.content.Context; import android.content.ContextWrapper; import android.content.Intent; @@ -55,13 +53,10 @@ import com.android.quickstep.GestureState; import com.android.quickstep.InputConsumer; import com.android.quickstep.RecentsAnimationCallbacks; import com.android.quickstep.RecentsAnimationDeviceState; -import com.android.quickstep.SwipeSharedState; -import com.android.quickstep.SysUINavigationMode; -import com.android.quickstep.SysUINavigationMode.Mode; +import com.android.quickstep.TaskAnimationManager; import com.android.quickstep.util.ActiveGestureLog; import com.android.quickstep.util.CachedEventDispatcher; import com.android.quickstep.util.MotionPauseDetector; -import com.android.quickstep.util.NavBarPosition; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.InputMonitorCompat; @@ -80,21 +75,19 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC public static final float QUICKSTEP_TOUCH_SLOP_RATIO = 3; private final RecentsAnimationDeviceState mDeviceState; + private final TaskAnimationManager mTaskAnimationManager; private final GestureState mGestureState; + private RecentsAnimationCallbacks mActiveCallbacks; private final CachedEventDispatcher mRecentsViewDispatcher = new CachedEventDispatcher(); - private final RunningTaskInfo mRunningTask; - private final SwipeSharedState mSwipeSharedState; private final InputMonitorCompat mInputMonitorCompat; - private final SysUINavigationMode.Mode mMode; private final BaseActivityInterface mActivityInterface; private final BaseSwipeUpHandler.Factory mHandlerFactory; - private final NavBarPosition mNavBarPosition; - private final Consumer mOnCompleteCallback; private final MotionPauseDetector mMotionPauseDetector; private final float mMotionPauseMinDisplacement; + private VelocityTracker mVelocityTracker; private BaseSwipeUpHandler mInteractionHandler; @@ -123,20 +116,17 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC ActivityManagerWrapper.getInstance().cancelRecentsAnimation( true /* restoreHomeStackPosition */); }; - private int mLogId; public OtherActivityInputConsumer(Context base, RecentsAnimationDeviceState deviceState, - GestureState gestureState, RunningTaskInfo runningTaskInfo, + TaskAnimationManager taskAnimationManager, GestureState gestureState, boolean isDeferredDownTarget, Consumer onCompleteCallback, - SwipeSharedState swipeSharedState, InputMonitorCompat inputMonitorCompat, - boolean disableHorizontalSwipe, Factory handlerFactory, int logId) { + InputMonitorCompat inputMonitorCompat, boolean disableHorizontalSwipe, + Factory handlerFactory) { super(base); - mLogId = logId; mDeviceState = deviceState; + mTaskAnimationManager = taskAnimationManager; mGestureState = gestureState; mMainThreadHandler = new Handler(Looper.getMainLooper()); - mRunningTask = runningTaskInfo; - mMode = SysUINavigationMode.getMode(base); mHandlerFactory = handlerFactory; mActivityInterface = mGestureState.getActivityInterface(); @@ -147,11 +137,8 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC mVelocityTracker = VelocityTracker.obtain(); mInputMonitorCompat = inputMonitorCompat; - boolean continuingPreviousGesture = swipeSharedState.getActiveListener() != null; + boolean continuingPreviousGesture = mTaskAnimationManager.isRecentsAnimationRunning(); mIsDeferredDownTarget = !continuingPreviousGesture && isDeferredDownTarget; - mSwipeSharedState = swipeSharedState; - - mNavBarPosition = new NavBarPosition(base); mTouchSlop = ViewConfiguration.get(this).getScaledTouchSlop(); float slop = QUICKSTEP_TOUCH_SLOP_RATIO * mTouchSlop; @@ -183,7 +170,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC if (mPassedWindowMoveSlop && mInteractionHandler != null && !mRecentsViewDispatcher.hasConsumer()) { mRecentsViewDispatcher.setConsumer(mInteractionHandler.getRecentsViewDispatcher( - mNavBarPosition.getRotationMode())); + mDeviceState.getNavBarPosition().getRotationMode())); } int edgeFlags = ev.getEdgeFlags(); ev.setEdgeFlags(edgeFlags | EDGE_NAV_BAR); @@ -293,7 +280,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC mInteractionHandler.updateDisplacement(displacement - mStartDisplacement); } - if (mMode == Mode.NO_BUTTON) { + if (mDeviceState.isFullyGesturalNavMode()) { mMotionPauseDetector.setDisallowPause(upDist < mMotionPauseMinDisplacement || isLikelyToStartNewTask); mMotionPauseDetector.addPosition(displacement, ev.getEventTime()); @@ -329,25 +316,22 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC long touchTimeMs, boolean isLikelyToStartNewTask) { ActiveGestureLog.INSTANCE.addLog("startRecentsAnimation"); - RecentsAnimationCallbacks listenerSet = mSwipeSharedState.getActiveListener(); - final BaseSwipeUpHandler handler = mHandlerFactory.newHandler(mGestureState, mRunningTask, - touchTimeMs, listenerSet != null, isLikelyToStartNewTask); + mInteractionHandler = mHandlerFactory.newHandler(mGestureState, touchTimeMs, + mTaskAnimationManager.isRecentsAnimationRunning(), isLikelyToStartNewTask); + mInteractionHandler.setGestureEndCallback(this::onInteractionGestureFinished); + mMotionPauseDetector.setOnMotionPauseListener(mInteractionHandler::onMotionPauseChanged); + mInteractionHandler.initWhenReady(); - mInteractionHandler = handler; - handler.setGestureEndCallback(this::onInteractionGestureFinished); - mMotionPauseDetector.setOnMotionPauseListener(handler::onMotionPauseChanged); - handler.initWhenReady(); - - if (listenerSet != null) { - listenerSet.addListener(handler); - mSwipeSharedState.applyActiveRecentsAnimationState(handler); + if (mTaskAnimationManager.isRecentsAnimationRunning()) { + mActiveCallbacks = mTaskAnimationManager.continueRecentsAnimation(mGestureState); + mActiveCallbacks.addListener(mInteractionHandler); + mTaskAnimationManager.notifyRecentsAnimationState(mInteractionHandler); notifyGestureStarted(); } else { - RecentsAnimationCallbacks callbacks = mSwipeSharedState.newRecentsAnimationCallbacks(); - callbacks.addListener(handler); - Intent intent = handler.getLaunchIntent(); - intent.putExtra(INTENT_EXTRA_LOG_TRACE_ID, mLogId); - startRecentsActivityAsync(intent, callbacks); + Intent intent = mInteractionHandler.getLaunchIntent(); + intent.putExtra(INTENT_EXTRA_LOG_TRACE_ID, mGestureState.getGestureId()); + mActiveCallbacks = mTaskAnimationManager.startRecentsAnimation(mGestureState, intent, + mInteractionHandler); } } @@ -367,8 +351,10 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC ViewConfiguration.get(this).getScaledMaximumFlingVelocity()); float velocityX = mVelocityTracker.getXVelocity(mActivePointerId); float velocityY = mVelocityTracker.getYVelocity(mActivePointerId); - float velocity = mNavBarPosition.isRightEdge() ? velocityX - : mNavBarPosition.isLeftEdge() ? -velocityX + float velocity = mDeviceState.getNavBarPosition().isRightEdge() + ? velocityX + : mDeviceState.getNavBarPosition().isLeftEdge() + ? -velocityX : velocityY; mInteractionHandler.updateDisplacement(getDisplacement(ev) - mStartDisplacement); @@ -402,7 +388,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC // The consumer is being switched while we are active. Set up the shared state to be // used by the next animation removeListener(); - mInteractionHandler.onConsumerAboutToBeSwitched(mSwipeSharedState); + mInteractionHandler.onConsumerAboutToBeSwitched(); } } @@ -415,27 +401,21 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC } private void removeListener() { - RecentsAnimationCallbacks listenerSet = mSwipeSharedState.getActiveListener(); - if (listenerSet != null) { - listenerSet.removeListener(mInteractionHandler); + if (mActiveCallbacks != null) { + mActiveCallbacks.removeListener(mInteractionHandler); } } private float getDisplacement(MotionEvent ev) { - if (mNavBarPosition.isRightEdge()) { + if (mDeviceState.getNavBarPosition().isRightEdge()) { return ev.getX() - mDownPos.x; - } else if (mNavBarPosition.isLeftEdge()) { + } else if (mDeviceState.getNavBarPosition().isLeftEdge()) { return mDownPos.x - ev.getX(); } else { return ev.getY() - mDownPos.y; } } - @Override - public boolean useSharedSwipeState() { - return mInteractionHandler != null; - } - @Override public boolean allowInterceptByParent() { return !mPassedPilferInputSlop; diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/QuickCaptureInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverscrollInputConsumer.java similarity index 71% rename from quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/QuickCaptureInputConsumer.java rename to quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverscrollInputConsumer.java index 97ca730402..e3da98b5ea 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/QuickCaptureInputConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverscrollInputConsumer.java @@ -24,39 +24,28 @@ import static android.view.MotionEvent.ACTION_UP; import static com.android.launcher3.Utilities.squaredHypot; -import android.app.ActivityOptions; import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; import android.graphics.PointF; -import android.os.Bundle; import android.view.MotionEvent; import android.view.ViewConfiguration; +import androidx.annotation.Nullable; + import com.android.launcher3.BaseDraggingActivity; -import com.android.launcher3.R; import com.android.quickstep.GestureState; import com.android.quickstep.InputConsumer; import com.android.quickstep.views.RecentsView; +import com.android.systemui.plugins.OverscrollPlugin; import com.android.systemui.shared.system.InputMonitorCompat; /** - * Input consumer for handling events to launch quick capture from launcher + * Input consumer for handling events to pass to an {@code OverscrollPlugin}. + * * @param Draggable activity subclass used by RecentsView */ -public class QuickCaptureInputConsumer - extends DelegateInputConsumer { +public class OverscrollInputConsumer extends DelegateInputConsumer { - private static final String TAG = "QuickCaptureInputConsumer"; - - private static final String QUICK_CAPTURE_PACKAGE = "com.google.auxe.compose"; - private static final String QUICK_CAPTURE_PACKAGE_DEV = "com.google.auxe.compose.debug"; - - private static final String EXTRA_DEVICE_STATE = "deviceState"; - private static final String DEVICE_STATE_LOCKED = "Locked"; - private static final String DEVICE_STATE_LAUNCHER = "Launcher"; - private static final String DEVICE_STATE_APP = "App"; - private static final String DEVICE_STATE_UNKNOWN = "Unknown"; + private static final String TAG = "OverscrollInputConsumer"; private static final int ANGLE_THRESHOLD = 35; // Degrees @@ -69,14 +58,18 @@ public class QuickCaptureInputConsumer private final float mSquaredSlop; - private Context mContext; + private final Context mContext; + private final GestureState mGestureState; + @Nullable private final OverscrollPlugin mPlugin; private RecentsView mRecentsView; - public QuickCaptureInputConsumer(Context context, GestureState gestureState, - InputConsumer delegate, InputMonitorCompat inputMonitor) { + public OverscrollInputConsumer(Context context, GestureState gestureState, + InputConsumer delegate, InputMonitorCompat inputMonitor, OverscrollPlugin plugin) { super(delegate, inputMonitor); mContext = context; + mGestureState = gestureState; + mPlugin = plugin; float slop = ViewConfiguration.get(context).getScaledTouchSlop(); mSquaredSlop = slop * slop; @@ -87,11 +80,11 @@ public class QuickCaptureInputConsumer @Override public int getType() { - return TYPE_QUICK_CAPTURE | mDelegate.getType(); + return TYPE_OVERSCROLL | mDelegate.getType(); } - private boolean onActivityInit(final BaseDraggingActivity activity, Boolean alreadyOnHome) { - mRecentsView = activity.getOverviewPanel(); + private boolean onActivityInit(Boolean alreadyOnHome) { + mRecentsView = mGestureState.getActivityInterface().getCreatedActivity().getOverviewPanel(); return true; } @@ -147,7 +140,7 @@ public class QuickCaptureInputConsumer mPassedSlop = true; mStartDragPos.set(mLastPos.x, mLastPos.y); - if (isValidQuickCaptureGesture()) { + if (isOverscrolled()) { setActive(ev); } else { mState = STATE_DELEGATE_ACTIVE; @@ -159,8 +152,8 @@ public class QuickCaptureInputConsumer } case ACTION_CANCEL: case ACTION_UP: - if (mState != STATE_DELEGATE_ACTIVE && mPassedSlop) { - startQuickCapture(); + if (mState != STATE_DELEGATE_ACTIVE && mPassedSlop && mPlugin != null) { + mPlugin.onOverscroll(getDeviceState()); } mPassedSlop = false; @@ -173,7 +166,7 @@ public class QuickCaptureInputConsumer } } - private boolean isValidQuickCaptureGesture() { + private boolean isOverscrolled() { // Make sure there isn't an app to quick switch to on our right boolean atRightMostApp = (mRecentsView == null || mRecentsView.getRunningTaskIndex() <= 0); @@ -185,37 +178,19 @@ public class QuickCaptureInputConsumer return atRightMostApp && angleInBounds; } - private void startQuickCapture() { - // Inspect our delegate's type to figure out where the user invoked Compose - String deviceState = DEVICE_STATE_UNKNOWN; + private String getDeviceState() { + String deviceState = OverscrollPlugin.DEVICE_STATE_UNKNOWN; int consumerType = mDelegate.getType(); if (((consumerType & InputConsumer.TYPE_OVERVIEW) > 0) || ((consumerType & InputConsumer.TYPE_OVERVIEW_WITHOUT_FOCUS)) > 0) { - deviceState = DEVICE_STATE_LAUNCHER; + deviceState = OverscrollPlugin.DEVICE_STATE_LAUNCHER; } else if ((consumerType & InputConsumer.TYPE_OTHER_ACTIVITY) > 0) { - deviceState = DEVICE_STATE_APP; + deviceState = OverscrollPlugin.DEVICE_STATE_APP; } else if (((consumerType & InputConsumer.TYPE_RESET_GESTURE) > 0) || ((consumerType & InputConsumer.TYPE_DEVICE_LOCKED) > 0)) { - deviceState = DEVICE_STATE_LOCKED; + deviceState = OverscrollPlugin.DEVICE_STATE_LOCKED; } - // Then launch the app - PackageManager pm = mContext.getPackageManager(); - - Intent qcIntent = pm.getLaunchIntentForPackage(QUICK_CAPTURE_PACKAGE); - - if (qcIntent == null) { - // If we couldn't find the regular app, try the dev version - qcIntent = pm.getLaunchIntentForPackage(QUICK_CAPTURE_PACKAGE_DEV); - } - - if (qcIntent != null) { - qcIntent.putExtra(EXTRA_DEVICE_STATE, deviceState); - - Bundle options = ActivityOptions.makeCustomAnimation(mContext, R.anim.slide_in_right, - 0).toBundle(); - - mContext.startActivity(qcIntent, options); - } + return deviceState; } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java index 50069ea05c..875ec29530 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java @@ -21,9 +21,9 @@ import static android.view.MotionEvent.ACTION_MOVE; import static android.view.MotionEvent.ACTION_UP; import static com.android.launcher3.Utilities.squaredHypot; -import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS; import android.content.Context; +import android.content.Intent; import android.graphics.PointF; import android.view.MotionEvent; import android.view.VelocityTracker; @@ -35,36 +35,34 @@ import com.android.launcher3.Utilities; import com.android.launcher3.logging.StatsLogUtils; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; -import com.android.quickstep.BaseActivityInterface; -import com.android.quickstep.InputConsumer; import com.android.quickstep.GestureState; +import com.android.quickstep.InputConsumer; +import com.android.quickstep.RecentsAnimationDeviceState; import com.android.quickstep.util.ActiveGestureLog; -import com.android.quickstep.util.NavBarPosition; -import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.InputMonitorCompat; public class OverviewWithoutFocusInputConsumer implements InputConsumer { + private final Context mContext; + private final RecentsAnimationDeviceState mDeviceState; + private final GestureState mGestureState; private final InputMonitorCompat mInputMonitor; private final boolean mDisableHorizontalSwipe; private final PointF mDownPos = new PointF(); private final float mSquaredTouchSlop; - private final Context mContext; - private final NavBarPosition mNavBarPosition; - private final BaseActivityInterface mActivityInterface; private boolean mInterceptedTouch; private VelocityTracker mVelocityTracker; - public OverviewWithoutFocusInputConsumer(Context context, GestureState gestureState, + public OverviewWithoutFocusInputConsumer(Context context, + RecentsAnimationDeviceState deviceState, GestureState gestureState, InputMonitorCompat inputMonitor, boolean disableHorizontalSwipe) { + mContext = context; + mDeviceState = deviceState; + mGestureState = gestureState; mInputMonitor = inputMonitor; mDisableHorizontalSwipe = disableHorizontalSwipe; - mContext = context; - mActivityInterface = gestureState.getActivityInterface(); mSquaredTouchSlop = Utilities.squaredTouchSlop(context); - mNavBarPosition = new NavBarPosition(context); - mVelocityTracker = VelocityTracker.obtain(); } @@ -135,8 +133,11 @@ public class OverviewWithoutFocusInputConsumer implements InputConsumer { mVelocityTracker.computeCurrentVelocity(100); float velocityX = mVelocityTracker.getXVelocity(); float velocityY = mVelocityTracker.getYVelocity(); - float velocity = mNavBarPosition.isRightEdge() - ? -velocityX : (mNavBarPosition.isLeftEdge() ? velocityX : -velocityY); + float velocity = mDeviceState.getNavBarPosition().isRightEdge() + ? -velocityX + : mDeviceState.getNavBarPosition().isLeftEdge() + ? velocityX + : -velocityY; final boolean triggerQuickstep; int touch = Touch.FLING; @@ -150,9 +151,9 @@ public class OverviewWithoutFocusInputConsumer implements InputConsumer { } if (triggerQuickstep) { - mActivityInterface.closeOverlay(); - ActivityManagerWrapper.getInstance() - .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS); + mContext.startActivity(new Intent(Intent.ACTION_MAIN) + .addCategory(Intent.CATEGORY_HOME) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); ActiveGestureLog.INSTANCE.addLog("startQuickstep"); BaseActivity activity = BaseDraggingActivity.fromContext(mContext); int pageIndex = -1; // This number doesn't reflect workspace page index. diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java index e04c0c741c..d34b40bf0c 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java @@ -18,17 +18,17 @@ package com.android.quickstep.inputconsumers; import android.view.MotionEvent; import com.android.quickstep.InputConsumer; -import com.android.quickstep.SwipeSharedState; +import com.android.quickstep.TaskAnimationManager; /** * A NO_OP input consumer which also resets any pending gesture */ public class ResetGestureInputConsumer implements InputConsumer { - private final SwipeSharedState mSwipeSharedState; + private final TaskAnimationManager mTaskAnimationManager; - public ResetGestureInputConsumer(SwipeSharedState swipeSharedState) { - mSwipeSharedState = swipeSharedState; + public ResetGestureInputConsumer(TaskAnimationManager taskAnimationManager) { + mTaskAnimationManager = taskAnimationManager; } @Override @@ -39,8 +39,8 @@ public class ResetGestureInputConsumer implements InputConsumer { @Override public void onMotionEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN - && mSwipeSharedState.getActiveListener() != null) { - mSwipeSharedState.clearAllState(false /* finishAnimation */); + && mTaskAnimationManager.isRecentsAnimationRunning()) { + mTaskAnimationManager.finishRunningRecentsAnimation(false /* toHome */); } } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ActiveGestureLog.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ActiveGestureLog.java index 9a3bb760f1..fabfc4bb51 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ActiveGestureLog.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ActiveGestureLog.java @@ -33,7 +33,7 @@ public class ActiveGestureLog extends EventLogArray { */ public static final String INTENT_EXTRA_LOG_TRACE_ID = "INTENT_EXTRA_LOG_TRACE_ID"; - public ActiveGestureLog() { + private ActiveGestureLog() { super("touch_interaction_log", 40); } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java index 24e7f0ecea..4a39e73e41 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java @@ -169,7 +169,7 @@ public class AppWindowAnimationHelper { return null; } - float progress = params.progress; + float progress = Utilities.boundToRange(params.progress, 0, 1); updateCurrentRect(params); SurfaceParams[] surfaceParams = new SurfaceParams[params.targetSet.unfilteredApps.length]; diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AssistantUtilities.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AssistantUtilities.java new file mode 100644 index 0000000000..552db1f4dd --- /dev/null +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AssistantUtilities.java @@ -0,0 +1,47 @@ +/* + * 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 static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_ASSISTANT; + +import android.annotation.TargetApi; +import android.app.TaskInfo; +import android.content.Intent; +import android.os.Build; + +import com.android.systemui.shared.system.ActivityManagerWrapper; +import com.android.systemui.shared.system.TaskInfoCompat; + +/** + * Utility class for interacting with the Assistant. + */ +@TargetApi(Build.VERSION_CODES.Q) +public final class AssistantUtilities { + + /** Returns true if an Assistant activity that is excluded from recents is running. */ + public static boolean isExcludedAssistantRunning() { + return isExcludedAssistant(ActivityManagerWrapper.getInstance().getRunningTask()); + } + + /** Returns true if the given task holds an Assistant activity that is excluded from recents. */ + public static boolean isExcludedAssistant(TaskInfo info) { + return info != null + && TaskInfoCompat.getActivityType(info) == ACTIVITY_TYPE_ASSISTANT + && (info.baseIntent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0; + } + + private AssistantUtilities() {} +} diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/NavBarPosition.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/NavBarPosition.java deleted file mode 100644 index bbb318a027..0000000000 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/NavBarPosition.java +++ /dev/null @@ -1,54 +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 static com.android.launcher3.uioverrides.RecentsUiFactory.ROTATION_LANDSCAPE; -import static com.android.launcher3.uioverrides.RecentsUiFactory.ROTATION_SEASCAPE; -import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON; - -import android.content.Context; -import android.view.Surface; - -import com.android.launcher3.graphics.RotationMode; -import com.android.launcher3.util.DefaultDisplay; -import com.android.quickstep.SysUINavigationMode; - -/** - * Utility class to check nav bar position - */ -public class NavBarPosition { - - private final SysUINavigationMode.Mode mMode; - private final int mDisplayRotation; - - public NavBarPosition(Context context) { - mMode = SysUINavigationMode.getMode(context); - mDisplayRotation = DefaultDisplay.INSTANCE.get(context).getInfo().rotation; - } - - public boolean isRightEdge() { - return mMode != NO_BUTTON && mDisplayRotation == Surface.ROTATION_90; - } - - public boolean isLeftEdge() { - return mMode != NO_BUTTON && mDisplayRotation == Surface.ROTATION_270; - } - - public RotationMode getRotationMode() { - return isLeftEdge() ? ROTATION_SEASCAPE - : (isRightEdge() ? ROTATION_LANDSCAPE : RotationMode.NORMAL); - } -} diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java new file mode 100644 index 0000000000..41be6834f7 --- /dev/null +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java @@ -0,0 +1,104 @@ +/* + * 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 static com.android.launcher3.LauncherAppTransitionManagerImpl.INDEX_SHELF_ANIM; +import static com.android.launcher3.LauncherState.BACKGROUND_APP; +import static com.android.launcher3.LauncherState.OVERVIEW; +import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.view.animation.Interpolator; + +import com.android.launcher3.Launcher; +import com.android.launcher3.uioverrides.states.OverviewState; + +/** + * Animates the shelf between states HIDE, PEEK, and OVERVIEW. + */ +public class ShelfPeekAnim { + + public static final Interpolator INTERPOLATOR = OVERSHOOT_1_2; + public static final long DURATION = 240; + + private final Launcher mLauncher; + + private ShelfAnimState mShelfState; + private boolean mIsPeeking; + + public ShelfPeekAnim(Launcher launcher) { + mLauncher = launcher; + } + + /** + * Animates to the given state, canceling the previous animation if it was still running. + */ + public void setShelfState(ShelfAnimState shelfState, Interpolator interpolator, long duration) { + if (mShelfState == shelfState) { + return; + } + mLauncher.getStateManager().cancelStateElementAnimation(INDEX_SHELF_ANIM); + mShelfState = shelfState; + mIsPeeking = mShelfState == ShelfAnimState.PEEK || mShelfState == ShelfAnimState.HIDE; + if (mShelfState == ShelfAnimState.CANCEL) { + return; + } + float shelfHiddenProgress = BACKGROUND_APP.getVerticalProgress(mLauncher); + float shelfOverviewProgress = OVERVIEW.getVerticalProgress(mLauncher); + // Peek based on default overview progress so we can see hotseat if we're showing + // that instead of predictions in overview. + float defaultOverviewProgress = OverviewState.getDefaultVerticalProgress(mLauncher); + float shelfPeekingProgress = shelfHiddenProgress + - (shelfHiddenProgress - defaultOverviewProgress) * 0.25f; + float toProgress = mShelfState == ShelfAnimState.HIDE + ? shelfHiddenProgress + : mShelfState == ShelfAnimState.PEEK + ? shelfPeekingProgress + : shelfOverviewProgress; + Animator shelfAnim = mLauncher.getStateManager() + .createStateElementAnimation(INDEX_SHELF_ANIM, toProgress); + shelfAnim.setInterpolator(interpolator); + shelfAnim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationCancel(Animator animation) { + mShelfState = ShelfAnimState.CANCEL; + } + + @Override + public void onAnimationEnd(Animator animator) { + mIsPeeking = mShelfState == ShelfAnimState.PEEK; + } + }); + shelfAnim.setDuration(duration).start(); + } + + /** @return Whether the shelf is currently peeking or animating to or from peeking. */ + public boolean isPeeking() { + return mIsPeeking; + } + + /** The various shelf states we can animate to. */ + public enum ShelfAnimState { + HIDE(true), PEEK(true), OVERVIEW(false), CANCEL(false); + + ShelfAnimState(boolean shouldPreformHaptic) { + this.shouldPreformHaptic = shouldPreformHaptic; + } + + public final boolean shouldPreformHaptic; + } +} diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java index 0655c733ba..82fbbc6802 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java @@ -102,8 +102,9 @@ public class LauncherRecentsView extends RecentsView implements StateL @Override public void startHome() { if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { - switchToScreenshot(() -> finishRecentsAnimation(true /* toRecents */, - () -> mActivity.getStateManager().goToState(NORMAL))); + switchToScreenshot(null, + () -> finishRecentsAnimation(true /* toRecents */, + () -> mActivity.getStateManager().goToState(NORMAL))); } else { mActivity.getStateManager().goToState(NORMAL); } @@ -325,8 +326,8 @@ public class LauncherRecentsView extends RecentsView implements StateL @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); - PluginManagerWrapper.INSTANCE.get(getContext()) - .addPluginListener(mRecentsExtraCardPluginListener, RecentsExtraCard.class); + PluginManagerWrapper.INSTANCE.get(getContext()).addPluginListener( + mRecentsExtraCardPluginListener, RecentsExtraCard.class); } @Override @@ -377,10 +378,10 @@ public class LauncherRecentsView extends RecentsView implements StateL } @Override - public void resetTaskVisuals() { - super.resetTaskVisuals(); + public void setContentAlpha(float alpha) { + super.setContentAlpha(alpha); if (mRecentsExtraViewContainer != null) { - mRecentsExtraViewContainer.setAlpha(mContentAlpha); + mRecentsExtraViewContainer.setAlpha(alpha); } } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LiveTileOverlay.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LiveTileOverlay.java index a83879738d..18eda603fa 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LiveTileOverlay.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LiveTileOverlay.java @@ -16,6 +16,7 @@ import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.util.FloatProperty; +import android.view.ViewOverlay; import com.android.launcher3.anim.Interpolators; @@ -36,6 +37,15 @@ public class LiveTileOverlay extends Drawable { } }; + private static LiveTileOverlay sInstance; + + public static LiveTileOverlay getInstance() { + if (sInstance == null) { + sInstance = new LiveTileOverlay(); + } + return sInstance; + } + private final Paint mPaint = new Paint(); private Rect mBoundsRect = new Rect(); @@ -46,8 +56,9 @@ public class LiveTileOverlay extends Drawable { private boolean mDrawEnabled = true; private float mIconAnimationProgress = 0f; + private boolean mIsAttached; - public LiveTileOverlay() { + private LiveTileOverlay() { mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); } @@ -124,6 +135,23 @@ public class LiveTileOverlay extends Drawable { return PixelFormat.TRANSLUCENT; } + public boolean attach(ViewOverlay overlay) { + if (overlay != null && !mIsAttached) { + overlay.add(this); + mIsAttached = true; + return true; + } + + return false; + } + + public void detach(ViewOverlay overlay) { + if (overlay != null) { + overlay.remove(this); + mIsAttached = false; + } + } + private void setIconAnimationProgress(float progress) { mIconAnimationProgress = progress; invalidateSelf(); diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java index 434a0c27d4..eaa23a6b28 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java @@ -59,6 +59,7 @@ import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Handler; +import android.os.UserHandle; import android.text.Layout; import android.text.StaticLayout; import android.text.TextPaint; @@ -104,7 +105,7 @@ import com.android.launcher3.util.ViewPool; import com.android.quickstep.RecentsAnimationController; import com.android.quickstep.RecentsAnimationTargets; import com.android.quickstep.RecentsModel; -import com.android.quickstep.RecentsModel.TaskThumbnailChangeListener; +import com.android.quickstep.RecentsModel.TaskVisualsChangeListener; import com.android.quickstep.TaskThumbnailCache; import com.android.quickstep.TaskUtils; import com.android.quickstep.ViewUtils; @@ -126,7 +127,7 @@ import java.util.function.Consumer; @TargetApi(Build.VERSION_CODES.P) public abstract class RecentsView extends PagedView implements Insettable, TaskThumbnailCache.HighResLoadingState.HighResLoadingStateChangedCallback, - InvariantDeviceProfile.OnIDPChangeListener, TaskThumbnailChangeListener { + InvariantDeviceProfile.OnIDPChangeListener, TaskVisualsChangeListener { private static final String TAG = RecentsView.class.getSimpleName(); @@ -306,7 +307,7 @@ public abstract class RecentsView extends PagedView impl private final int mEmptyMessagePadding; private boolean mShowEmptyMessage; private Layout mEmptyTextLayout; - private LiveTileOverlay mLiveTileOverlay; + private boolean mLiveTileOverlayAttached; // Keeps track of the index where the first TaskView should be private int mTaskViewStartIndex = 0; @@ -382,6 +383,21 @@ public abstract class RecentsView extends PagedView impl return null; } + @Override + public void onTaskIconChanged(String pkg, UserHandle user) { + for (int i = 0; i < getTaskViewCount(); i++) { + TaskView tv = getTaskViewAt(i); + Task task = tv.getTask(); + if (task != null && task.key != null && pkg.equals(task.key.getPackageName()) + && task.key.userId == user.getIdentifier()) { + task.icon = null; + if (tv.getIconView().getDrawable() != null) { + tv.onTaskListVisibilityChanged(true /* visible */); + } + } + } + } + /** * Update the thumbnail of the task. * @param refreshNow Refresh immediately if it's true. @@ -859,8 +875,8 @@ public abstract class RecentsView extends PagedView impl */ public void onSwipeUpAnimationSuccess() { if (getRunningTaskView() != null) { - float startProgress = ENABLE_QUICKSTEP_LIVE_TILE.get() && mLiveTileOverlay != null - ? mLiveTileOverlay.cancelIconAnimation() + float startProgress = ENABLE_QUICKSTEP_LIVE_TILE.get() && mLiveTileOverlayAttached + ? LiveTileOverlay.getInstance().cancelIconAnimation() : 0f; animateUpRunningTaskIconScale(startProgress); } @@ -1672,8 +1688,8 @@ public abstract class RecentsView extends PagedView impl if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) { final int[] visibleTasks = getVisibleChildrenRange(); - event.setFromIndex(taskViewCount - visibleTasks[1] - 1); - event.setToIndex(taskViewCount - visibleTasks[0] - 1); + event.setFromIndex(taskViewCount - visibleTasks[1]); + event.setToIndex(taskViewCount - visibleTasks[0]); event.setItemCount(taskViewCount); } } @@ -1707,13 +1723,13 @@ public abstract class RecentsView extends PagedView impl mAppWindowAnimationHelper = appWindowAnimationHelper; } - public void setLiveTileOverlay(LiveTileOverlay liveTileOverlay) { - mLiveTileOverlay = liveTileOverlay; + public void setLiveTileOverlayAttached(boolean liveTileOverlayAttached) { + mLiveTileOverlayAttached = liveTileOverlayAttached; } public void updateLiveTileIcon(Drawable icon) { - if (mLiveTileOverlay != null) { - mLiveTileOverlay.setIcon(icon); + if (mLiveTileOverlayAttached) { + LiveTileOverlay.getInstance().setIcon(icon); } } @@ -1725,7 +1741,17 @@ public abstract class RecentsView extends PagedView impl return; } - mRecentsAnimationController.finish(toRecents, onFinishComplete); + mRecentsAnimationController.finish(toRecents, () -> { + if (onFinishComplete != null) { + onFinishComplete.run(); + // After we finish the recents animation, the current task id should be correctly + // reset so that when the task is launched from Overview later, it goes through the + // flow of starting a new task instead of finishing recents animation to app. A + // typical example of this is (1) user swipes up from app to Overview (2) user + // taps on QSB (3) user goes back to Overview and launch the most recent task. + setCurrentTask(-1); + } + }); } public void setDisallowScrollToClearAll(boolean disallowScrollToClearAll) { @@ -1828,8 +1854,8 @@ public abstract class RecentsView extends PagedView impl private void updateEnabledOverlays() { int overlayEnabledPage = mOverlayEnabled ? getNextPage() : -1; int taskCount = getTaskViewCount(); - for (int i = 0; i < taskCount; i++) { - getTaskViewAt(i).setOverlayEnabled(i == overlayEnabledPage); + for (int i = mTaskViewStartIndex; i < mTaskViewStartIndex + taskCount; i++) { + getTaskViewAtByAbsoluteIndex(i).setOverlayEnabled(i == overlayEnabledPage); } } @@ -1850,20 +1876,20 @@ public abstract class RecentsView extends PagedView impl return Math.max(insets.getSystemGestureInsets().right, insets.getSystemWindowInsetRight()); } - /** If it's in the live tile mode, switch the running task into screenshot mode. */ - public void switchToScreenshot(Runnable onFinishRunnable) { + public void switchToScreenshot(ThumbnailData thumbnailData, Runnable onFinishRunnable) { TaskView taskView = getRunningTaskView(); - if (taskView == null) { - if (onFinishRunnable != null) { - onFinishRunnable.run(); + if (taskView != null) { + taskView.setShowScreenshot(true); + if (thumbnailData != null) { + taskView.getThumbnail().setThumbnail(taskView.getTask(), thumbnailData); + } else { + taskView.getThumbnail().refresh(); } - return; + ViewUtils.postDraw(taskView, onFinishRunnable); + } else { + onFinishRunnable.run(); } - - taskView.setShowScreenshot(true); - taskView.getThumbnail().refresh(); - ViewUtils.postDraw(taskView, onFinishRunnable); } @Override diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java index 07d079664a..80022b4fe1 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java @@ -16,7 +16,6 @@ package com.android.quickstep.views; -import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; import static com.android.quickstep.views.TaskThumbnailView.DIM_ALPHA; import android.animation.Animator; @@ -26,7 +25,6 @@ import android.content.Context; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.util.AttributeSet; -import android.util.Log; import android.view.Gravity; import android.view.MotionEvent; import android.view.View; @@ -41,16 +39,13 @@ import com.android.launcher3.R; import com.android.launcher3.anim.AnimationSuccessListener; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.anim.RoundedRectRevealOutlineProvider; -import com.android.launcher3.testing.TestProtocol; +import com.android.launcher3.popup.SystemShortcut; import com.android.launcher3.util.Themes; import com.android.launcher3.views.BaseDragLayer; import com.android.quickstep.TaskOverlayFactory; -import com.android.quickstep.TaskSystemShortcut; import com.android.quickstep.TaskUtils; import com.android.quickstep.views.IconView.OnScaleUpdateListener; -import java.util.List; - /** * Contains options for a recent task when long-pressing its icon. */ @@ -197,22 +192,15 @@ public class TaskMenuView extends AbstractFloatingView { params.topMargin = (int) -mThumbnailTopMargin; mTaskIcon.setLayoutParams(params); - final BaseDraggingActivity activity = BaseDraggingActivity.fromContext(getContext()); - final List shortcuts = - TaskOverlayFactory.INSTANCE.get(getContext()).getEnabledShortcuts(taskView); - final int count = shortcuts.size(); - for (int i = 0; i < count; ++i) { - final TaskSystemShortcut menuOption = shortcuts.get(i); - addMenuOption(menuOption, menuOption.getOnClickListener(activity, taskView)); - } + TaskOverlayFactory.getEnabledShortcuts(taskView).forEach(this::addMenuOption); } - private void addMenuOption(TaskSystemShortcut menuOption, OnClickListener onClickListener) { + private void addMenuOption(SystemShortcut menuOption) { ViewGroup menuOptionView = (ViewGroup) mActivity.getLayoutInflater().inflate( R.layout.task_view_menu_option, this, false); menuOption.setIconAndLabelFor( menuOptionView.findViewById(R.id.icon), menuOptionView.findViewById(R.id.text)); - menuOptionView.setOnClickListener(onClickListener); + menuOptionView.setOnClickListener(menuOption); mOptionLayout.addView(menuOptionView); } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java index bfb961320d..a1775f4284 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java @@ -53,6 +53,7 @@ import com.android.launcher3.Utilities; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.logging.UserEventDispatcher; +import com.android.launcher3.popup.SystemShortcut; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; @@ -61,7 +62,6 @@ import com.android.launcher3.util.ViewPool.Reusable; import com.android.quickstep.RecentsModel; import com.android.quickstep.TaskIconCache; import com.android.quickstep.TaskOverlayFactory; -import com.android.quickstep.TaskSystemShortcut; import com.android.quickstep.TaskThumbnailCache; import com.android.quickstep.TaskUtils; import com.android.quickstep.util.TaskCornerRadius; @@ -287,11 +287,19 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { public void launchTask(boolean animate, boolean freezeTaskList, Consumer resultCallback, Handler resultCallbackHandler) { if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { + RecentsView recentsView = getRecentsView(); if (isRunningTask()) { - getRecentsView().finishRecentsAnimation(false /* toRecents */, + recentsView.finishRecentsAnimation(false /* toRecents */, () -> resultCallbackHandler.post(() -> resultCallback.accept(true))); } else { - launchTaskInternal(animate, freezeTaskList, resultCallback, resultCallbackHandler); + // This is a workaround against the WM issue that app open is not correctly animated + // when recents animation is being cleaned up (b/143774568). When that's possible, + // we should rely on the framework side to cancel the recents animation, and we will + // clean up the screenshot on the launcher side while we launch the next task. + recentsView.switchToScreenshot(null, + () -> recentsView.finishRecentsAnimation(true /* toRecents */, + () -> launchTaskInternal(animate, freezeTaskList, resultCallback, + resultCallbackHandler))); } } else { launchTaskInternal(animate, freezeTaskList, resultCallback, resultCallbackHandler); @@ -713,15 +721,8 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { getContext().getText(R.string.accessibility_close_task))); final Context context = getContext(); - final List shortcuts = - TaskOverlayFactory.INSTANCE.get(getContext()).getEnabledShortcuts(this); - final int count = shortcuts.size(); - for (int i = 0; i < count; ++i) { - final TaskSystemShortcut menuOption = shortcuts.get(i); - OnClickListener onClickListener = menuOption.getOnClickListener(mActivity, this); - if (onClickListener != null) { - info.addAction(menuOption.createAccessibilityAction(context)); - } + for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this)) { + info.addAction(s.createAccessibilityAction(context)); } if (mDigitalWellBeingToast.hasLimit()) { @@ -734,8 +735,8 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { final RecentsView recentsView = getRecentsView(); final AccessibilityNodeInfo.CollectionItemInfo itemInfo = AccessibilityNodeInfo.CollectionItemInfo.obtain( - 0, 1, recentsView.getChildCount() - recentsView.indexOfChild(this) - 1, 1, - false); + 0, 1, recentsView.getTaskViewCount() - recentsView.indexOfChild(this) - 1, + 1, false); info.setCollectionItemInfo(itemInfo); } @@ -752,16 +753,9 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { return true; } - final List shortcuts = - TaskOverlayFactory.INSTANCE.get(getContext()).getEnabledShortcuts(this); - final int count = shortcuts.size(); - for (int i = 0; i < count; ++i) { - final TaskSystemShortcut menuOption = shortcuts.get(i); - if (menuOption.hasHandlerForAction(action)) { - OnClickListener onClickListener = menuOption.getOnClickListener(mActivity, this); - if (onClickListener != null) { - onClickListener.onClick(this); - } + for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this)) { + if (s.hasHandlerForAction(action)) { + s.onClick(this); return true; } } diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml index 98aaceb0bf..5d9a0092c5 100644 --- a/quickstep/res/values/config.xml +++ b/quickstep/res/values/config.xml @@ -33,4 +33,6 @@ 200 20 + + diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java new file mode 100644 index 0000000000..9ea13c640c --- /dev/null +++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java @@ -0,0 +1,280 @@ +/* + * 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; + +import static com.android.launcher3.AbstractFloatingView.TYPE_ALL; +import static com.android.launcher3.AbstractFloatingView.TYPE_HIDE_BACK_BUTTON; +import static com.android.launcher3.LauncherState.ALL_APPS; +import static com.android.launcher3.LauncherState.NORMAL; +import static com.android.launcher3.LauncherState.OVERVIEW; +import static com.android.launcher3.allapps.DiscoveryBounce.BOUNCE_MAX_COUNT; +import static com.android.launcher3.allapps.DiscoveryBounce.HOME_BOUNCE_COUNT; +import static com.android.launcher3.allapps.DiscoveryBounce.HOME_BOUNCE_SEEN; +import static com.android.launcher3.allapps.DiscoveryBounce.SHELF_BOUNCE_COUNT; +import static com.android.launcher3.allapps.DiscoveryBounce.SHELF_BOUNCE_SEEN; + +import android.animation.AnimatorSet; +import android.animation.ValueAnimator; +import android.content.Intent; +import android.content.IntentSender; +import android.os.Bundle; +import android.os.CancellationSignal; + +import com.android.launcher3.LauncherState.ScaleAndTranslation; +import com.android.launcher3.LauncherStateManager.StateHandler; +import com.android.launcher3.model.WellbeingModel; +import com.android.launcher3.popup.SystemShortcut; +import com.android.launcher3.proxy.ProxyActivityStarter; +import com.android.launcher3.proxy.StartActivityParams; +import com.android.launcher3.uioverrides.BackButtonAlphaHandler; +import com.android.launcher3.uioverrides.RecentsViewStateController; +import com.android.launcher3.util.UiThreadHelper; +import com.android.quickstep.RecentsModel; +import com.android.quickstep.SysUINavigationMode; +import com.android.quickstep.SysUINavigationMode.Mode; +import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener; +import com.android.quickstep.SystemUiProxy; +import com.android.quickstep.util.RemoteFadeOutAnimationListener; +import com.android.quickstep.util.ShelfPeekAnim; + +import java.util.stream.Stream; + +/** + * Extension of Launcher activity to provide quickstep specific functionality + */ +public abstract class BaseQuickstepLauncher extends Launcher + implements NavigationModeChangeListener { + + /** + * Reusable command for applying the back button alpha on the background thread. + */ + public static final UiThreadHelper.AsyncCommand SET_BACK_BUTTON_ALPHA = + (context, arg1, arg2) -> SystemUiProxy.INSTANCE.get(context).setBackButtonAlpha( + Float.intBitsToFloat(arg1), arg2 != 0); + + private final ShelfPeekAnim mShelfPeekAnim = new ShelfPeekAnim(this); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + SysUINavigationMode.Mode mode = SysUINavigationMode.INSTANCE.get(this) + .addModeChangeListener(this); + getRotationHelper().setRotationHadDifferentUI(mode != Mode.NO_BUTTON); + + if (!getSharedPrefs().getBoolean(HOME_BOUNCE_SEEN, false)) { + getStateManager().addStateListener(new LauncherStateManager.StateListener() { + @Override + public void onStateTransitionStart(LauncherState toState) { } + + @Override + public void onStateTransitionComplete(LauncherState finalState) { + boolean swipeUpEnabled = SysUINavigationMode.INSTANCE + .get(BaseQuickstepLauncher.this).getMode().hasGestures; + LauncherState prevState = getStateManager().getLastState(); + + if (((swipeUpEnabled && finalState == OVERVIEW) || (!swipeUpEnabled + && finalState == ALL_APPS && prevState == NORMAL) || BOUNCE_MAX_COUNT + <= getSharedPrefs().getInt(HOME_BOUNCE_COUNT, 0))) { + getSharedPrefs().edit().putBoolean(HOME_BOUNCE_SEEN, true).apply(); + getStateManager().removeStateListener(this); + } + } + }); + } + + if (!getSharedPrefs().getBoolean(SHELF_BOUNCE_SEEN, false)) { + getStateManager().addStateListener(new LauncherStateManager.StateListener() { + @Override + public void onStateTransitionStart(LauncherState toState) { } + + @Override + public void onStateTransitionComplete(LauncherState finalState) { + LauncherState prevState = getStateManager().getLastState(); + + if ((finalState == ALL_APPS && prevState == OVERVIEW) || BOUNCE_MAX_COUNT + <= getSharedPrefs().getInt(SHELF_BOUNCE_COUNT, 0)) { + getSharedPrefs().edit().putBoolean(SHELF_BOUNCE_SEEN, true).apply(); + getStateManager().removeStateListener(this); + } + } + }); + } + } + + @Override + public void onDestroy() { + SysUINavigationMode.INSTANCE.get(this).removeModeChangeListener(this); + super.onDestroy(); + } + + @Override + public void onNavigationModeChanged(Mode newMode) { + getDragLayer().recreateControllers(); + getRotationHelper().setRotationHadDifferentUI(newMode != Mode.NO_BUTTON); + } + + @Override + public void onEnterAnimationComplete() { + super.onEnterAnimationComplete(); + // After the transition to home, enable the high-res thumbnail loader if it wasn't enabled + // as a part of quickstep, so that high-res thumbnails can load the next time we enter + // overview + RecentsModel.INSTANCE.get(this).getThumbnailCache() + .getHighResLoadingState().setVisible(true); + } + + @Override + public void onTrimMemory(int level) { + super.onTrimMemory(level); + RecentsModel.INSTANCE.get(this).onTrimMemory(level); + } + + @Override + public void startIntentSenderForResult(IntentSender intent, int requestCode, + Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, Bundle options) { + if (requestCode != -1) { + mPendingActivityRequestCode = requestCode; + StartActivityParams params = new StartActivityParams(this, requestCode); + params.intentSender = intent; + params.fillInIntent = fillInIntent; + params.flagsMask = flagsMask; + params.flagsValues = flagsValues; + params.extraFlags = extraFlags; + params.options = options; + startActivity(ProxyActivityStarter.getLaunchIntent(this, params)); + } else { + super.startIntentSenderForResult(intent, requestCode, fillInIntent, flagsMask, + flagsValues, extraFlags, options); + } + } + + @Override + public void startActivityForResult(Intent intent, int requestCode, Bundle options) { + if (requestCode != -1) { + mPendingActivityRequestCode = -1; + StartActivityParams params = new StartActivityParams(this, requestCode); + params.intent = intent; + params.options = options; + startActivity(ProxyActivityStarter.getLaunchIntent(this, params)); + } else { + super.startActivityForResult(intent, requestCode, options); + } + } + + @Override + protected void onDeferredResumed() { + if (mPendingActivityRequestCode != -1 && isInState(NORMAL)) { + // Remove any active ProxyActivityStarter task and send RESULT_CANCELED to Launcher. + onActivityResult(mPendingActivityRequestCode, RESULT_CANCELED, null); + // ProxyActivityStarter is started with clear task to reset the task after which it + // removes the task itself. + startActivity(ProxyActivityStarter.getLaunchIntent(this, null)); + } + } + + @Override + protected StateHandler[] createStateHandlers() { + return new StateHandler[] { + getAllAppsController(), + getWorkspace(), + new RecentsViewStateController(this), + new BackButtonAlphaHandler(this)}; + } + + @Override + protected ScaleAndTranslation getOverviewScaleAndTranslationForNormalState() { + if (SysUINavigationMode.getMode(this) == Mode.NO_BUTTON) { + float offscreenTranslationX = getDeviceProfile().widthPx + - getOverviewPanel().getPaddingStart(); + return new ScaleAndTranslation(1f, offscreenTranslationX, 0f); + } + return super.getOverviewScaleAndTranslationForNormalState(); + } + + @Override + public void useFadeOutAnimationForLauncherStart(CancellationSignal signal) { + QuickstepAppTransitionManagerImpl appTransitionManager = + (QuickstepAppTransitionManagerImpl) getAppTransitionManager(); + appTransitionManager.setRemoteAnimationProvider((appTargets, wallpaperTargets) -> { + + // On the first call clear the reference. + signal.cancel(); + + ValueAnimator fadeAnimation = ValueAnimator.ofFloat(1, 0); + fadeAnimation.addUpdateListener(new RemoteFadeOutAnimationListener(appTargets, + wallpaperTargets)); + AnimatorSet anim = new AnimatorSet(); + anim.play(fadeAnimation); + return anim; + }, signal); + } + + @Override + public void onDragLayerHierarchyChanged() { + onLauncherStateOrFocusChanged(); + } + + @Override + protected void onActivityFlagsChanged(int changeBits) { + if ((changeBits + & (ACTIVITY_STATE_WINDOW_FOCUSED | ACTIVITY_STATE_TRANSITION_ACTIVE)) != 0) { + onLauncherStateOrFocusChanged(); + } + + super.onActivityFlagsChanged(changeBits); + } + + /** + * Sets the back button visibility based on the current state/window focus. + */ + private void onLauncherStateOrFocusChanged() { + Mode mode = SysUINavigationMode.getMode(this); + boolean shouldBackButtonBeHidden = mode.hasGestures + && getStateManager().getState().hideBackButton + && hasWindowFocus() + && (getActivityFlags() & ACTIVITY_STATE_TRANSITION_ACTIVE) == 0; + if (shouldBackButtonBeHidden) { + // Show the back button if there is a floating view visible. + shouldBackButtonBeHidden = AbstractFloatingView.getTopOpenViewWithType(this, + TYPE_ALL & ~TYPE_HIDE_BACK_BUTTON) == null; + } + UiThreadHelper.setBackButtonAlphaAsync(this, SET_BACK_BUTTON_ALPHA, + shouldBackButtonBeHidden ? 0f : 1f, true /* animate */); + if (getDragLayer() != null) { + getRootView().setDisallowBackGesture(shouldBackButtonBeHidden); + } + } + + @Override + public void finishBindingItems(int pageBoundFirst) { + super.finishBindingItems(pageBoundFirst); + // Instantiate and initialize WellbeingModel now that its loading won't interfere with + // populating workspace. + // TODO: Find a better place for this + WellbeingModel.get(this); + } + + @Override + public Stream getSupportedShortcuts() { + return Stream.concat(super.getSupportedShortcuts(), + Stream.of(WellbeingModel.SHORTCUT_FACTORY)); + } + + public ShelfPeekAnim getShelfPeekAnim() { + return mShelfPeekAnim; + } +} diff --git a/quickstep/src/com/android/launcher3/LauncherInitListener.java b/quickstep/src/com/android/launcher3/LauncherInitListener.java index 663b1252d3..96340b22e6 100644 --- a/quickstep/src/com/android/launcher3/LauncherInitListener.java +++ b/quickstep/src/com/android/launcher3/LauncherInitListener.java @@ -22,6 +22,7 @@ import android.os.Build; import android.os.CancellationSignal; import android.os.Handler; +import com.android.launcher3.util.ActivityTracker; import com.android.quickstep.util.ActivityInitListener; import com.android.quickstep.util.RemoteAnimationProvider; @@ -32,6 +33,11 @@ public class LauncherInitListener extends ActivityInitListener { private RemoteAnimationProvider mRemoteAnimationProvider; + /** + * @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 LauncherInitListener(BiPredicate onInitListener) { super(onInitListener, Launcher.ACTIVITY_TRACKER); } diff --git a/quickstep/src/com/android/launcher3/model/WellbeingModel.java b/quickstep/src/com/android/launcher3/model/WellbeingModel.java new file mode 100644 index 0000000000..5aa43888b4 --- /dev/null +++ b/quickstep/src/com/android/launcher3/model/WellbeingModel.java @@ -0,0 +1,342 @@ +/* + * 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.model; + +import static android.content.ContentResolver.SCHEME_CONTENT; + +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; +import static com.android.launcher3.util.Executors.createAndStartNewLooper; + +import android.annotation.TargetApi; +import android.app.RemoteAction; +import android.content.ContentProviderClient; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.LauncherApps; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.DeadObjectException; +import android.os.Handler; +import android.os.Message; +import android.os.Process; +import android.os.UserHandle; +import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.Log; + +import androidx.annotation.MainThread; +import androidx.annotation.NonNull; + +import com.android.launcher3.BaseDraggingActivity; +import com.android.launcher3.ItemInfo; +import com.android.launcher3.R; +import com.android.launcher3.popup.RemoteActionShortcut; +import com.android.launcher3.popup.SystemShortcut; +import com.android.launcher3.util.PackageManagerHelper; +import com.android.launcher3.util.Preconditions; +import com.android.launcher3.util.SimpleBroadcastReceiver; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * Data model for digital wellbeing status of apps. + */ +@TargetApi(Build.VERSION_CODES.Q) +public final class WellbeingModel { + private static final String TAG = "WellbeingModel"; + private static final int[] RETRY_TIMES_MS = {5000, 15000, 30000}; + private static final boolean DEBUG = false; + + private static final int MSG_PACKAGE_ADDED = 1; + private static final int MSG_PACKAGE_REMOVED = 2; + private static final int MSG_FULL_REFRESH = 3; + + // Welbeing contract + private static final String METHOD_GET_ACTIONS = "get_actions"; + private static final String EXTRA_ACTIONS = "actions"; + private static final String EXTRA_ACTION = "action"; + private static final String EXTRA_MAX_NUM_ACTIONS_SHOWN = "max_num_actions_shown"; + private static final String EXTRA_PACKAGES = "packages"; + + private static WellbeingModel sInstance; + + private final Context mContext; + private final String mWellbeingProviderPkg; + private final Handler mWorkerHandler; + + private final ContentObserver mContentObserver; + + private final Object mModelLock = new Object(); + // Maps the action Id to the corresponding RemoteAction + private final Map mActionIdMap = new ArrayMap<>(); + private final Map mPackageToActionId = new HashMap<>(); + + private boolean mIsInTest; + + private WellbeingModel(final Context context) { + mContext = context; + mWorkerHandler = + new Handler(createAndStartNewLooper("WellbeingHandler"), this::handleMessage); + + mWellbeingProviderPkg = mContext.getString(R.string.wellbeing_provider_pkg); + mContentObserver = new ContentObserver(MAIN_EXECUTOR.getHandler()) { + @Override + public void onChange(boolean selfChange, Uri uri) { + // Wellbeing reports that app actions have changed. + if (DEBUG || mIsInTest) { + Log.d(TAG, "ContentObserver.onChange() called with: selfChange = [" + selfChange + + "], uri = [" + uri + "]"); + } + Preconditions.assertUIThread(); + updateWellbeingData(); + } + }; + + if (!TextUtils.isEmpty(mWellbeingProviderPkg)) { + context.registerReceiver( + new SimpleBroadcastReceiver(this::onWellbeingProviderChanged), + PackageManagerHelper.getPackageFilter(mWellbeingProviderPkg, + Intent.ACTION_PACKAGE_ADDED, Intent.ACTION_PACKAGE_CHANGED, + Intent.ACTION_PACKAGE_REMOVED, Intent.ACTION_PACKAGE_DATA_CLEARED, + Intent.ACTION_PACKAGE_RESTARTED)); + + IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); + filter.addAction(Intent.ACTION_PACKAGE_REMOVED); + filter.addDataScheme("package"); + context.registerReceiver(new SimpleBroadcastReceiver(this::onAppPackageChanged), + filter); + + restartObserver(); + } + } + + public void setInTest(boolean inTest) { + mIsInTest = inTest; + } + + protected void onWellbeingProviderChanged(Intent intent) { + if (DEBUG || mIsInTest) { + Log.d(TAG, "Changes to Wellbeing package: intent = [" + intent + "]"); + } + restartObserver(); + } + + private void restartObserver() { + final ContentResolver resolver = mContext.getContentResolver(); + resolver.unregisterContentObserver(mContentObserver); + Uri actionsUri = apiBuilder().path("actions").build(); + try { + resolver.registerContentObserver( + actionsUri, true /* notifyForDescendants */, mContentObserver); + } catch (Exception e) { + Log.e(TAG, "Failed to register content observer for " + actionsUri + ": " + e); + if (mIsInTest) throw new RuntimeException(e); + } + updateWellbeingData(); + } + + @MainThread + public static WellbeingModel get(@NonNull Context context) { + Preconditions.assertUIThread(); + if (sInstance == null) { + sInstance = new WellbeingModel(context.getApplicationContext()); + } + return sInstance; + } + + @MainThread + private SystemShortcut getShortcutForApp(String packageName, int userId, + BaseDraggingActivity activity, ItemInfo info) { + Preconditions.assertUIThread(); + // Work profile apps are not recognized by digital wellbeing. + if (userId != UserHandle.myUserId()) { + if (DEBUG || mIsInTest) { + Log.d(TAG, "getShortcutForApp [" + packageName + "]: not current user"); + } + return null; + } + + synchronized (mModelLock) { + String actionId = mPackageToActionId.get(packageName); + final RemoteAction action = actionId != null ? mActionIdMap.get(actionId) : null; + if (action == null) { + if (DEBUG || mIsInTest) { + Log.d(TAG, "getShortcutForApp [" + packageName + "]: no action"); + } + return null; + } + if (DEBUG || mIsInTest) { + Log.d(TAG, + "getShortcutForApp [" + packageName + "]: action: '" + action.getTitle() + + "'"); + } + return new RemoteActionShortcut(action, activity, info); + } + } + + private void updateWellbeingData() { + mWorkerHandler.sendEmptyMessage(MSG_FULL_REFRESH); + } + + private Uri.Builder apiBuilder() { + return new Uri.Builder() + .scheme(SCHEME_CONTENT) + .authority(mWellbeingProviderPkg + ".api"); + } + + private boolean updateActions(String... packageNames) { + if (packageNames.length == 0) { + return true; + } + if (DEBUG || mIsInTest) { + Log.d(TAG, "retrieveActions() called with: packageNames = [" + String.join(", ", + packageNames) + "]"); + } + Preconditions.assertNonUiThread(); + + Uri contentUri = apiBuilder().build(); + final Bundle remoteActionBundle; + try (ContentProviderClient client = mContext.getContentResolver() + .acquireUnstableContentProviderClient(contentUri)) { + if (client == null) { + if (DEBUG || mIsInTest) Log.i(TAG, "retrieveActions(): null provider"); + return false; + } + + // Prepare wellbeing call parameters. + final Bundle params = new Bundle(); + params.putStringArray(EXTRA_PACKAGES, packageNames); + params.putInt(EXTRA_MAX_NUM_ACTIONS_SHOWN, 1); + // Perform wellbeing call . + remoteActionBundle = client.call(METHOD_GET_ACTIONS, null, params); + } catch (DeadObjectException e) { + Log.i(TAG, "retrieveActions(): DeadObjectException"); + return false; + } catch (Exception e) { + Log.e(TAG, "Failed to retrieve data from " + contentUri + ": " + e); + if (mIsInTest) throw new RuntimeException(e); + return true; + } + + synchronized (mModelLock) { + // Remove the entries for requested packages, and then update the fist with what we + // got from service + Arrays.stream(packageNames).forEach(mPackageToActionId::remove); + + // The result consists of sub-bundles, each one is per a remote action. Each sub-bundle + // has a RemoteAction and a list of packages to which the action applies. + for (String actionId : + remoteActionBundle.getStringArray(EXTRA_ACTIONS)) { + final Bundle actionBundle = remoteActionBundle.getBundle(actionId); + mActionIdMap.put(actionId, + actionBundle.getParcelable(EXTRA_ACTION)); + + final String[] packagesForAction = + actionBundle.getStringArray(EXTRA_PACKAGES); + if (DEBUG || mIsInTest) { + Log.d(TAG, "....actionId: " + actionId + ", packages: " + String.join(", ", + packagesForAction)); + } + for (String packageName : packagesForAction) { + mPackageToActionId.put(packageName, actionId); + } + } + } + if (DEBUG || mIsInTest) Log.i(TAG, "retrieveActions(): finished"); + return true; + } + + private boolean handleMessage(Message msg) { + switch (msg.what) { + case MSG_PACKAGE_REMOVED: { + String packageName = (String) msg.obj; + mWorkerHandler.removeCallbacksAndMessages(packageName); + synchronized (mModelLock) { + mPackageToActionId.remove(packageName); + } + return true; + } + case MSG_PACKAGE_ADDED: { + String packageName = (String) msg.obj; + mWorkerHandler.removeCallbacksAndMessages(packageName); + if (!updateActions(packageName)) { + scheduleRefreshRetry(msg); + } + return true; + } + + case MSG_FULL_REFRESH: { + // Remove all existing messages + mWorkerHandler.removeCallbacksAndMessages(null); + final String[] packageNames = mContext.getSystemService(LauncherApps.class) + .getActivityList(null, Process.myUserHandle()).stream() + .map(li -> li.getApplicationInfo().packageName).distinct() + .toArray(String[]::new); + if (!updateActions(packageNames)) { + scheduleRefreshRetry(msg); + } + return true; + } + } + return false; + } + + private void scheduleRefreshRetry(Message originalMsg) { + int retryCount = originalMsg.arg1; + if (retryCount >= RETRY_TIMES_MS.length) { + // To many retries, skip + return; + } + + Message msg = Message.obtain(originalMsg); + msg.arg1 = retryCount + 1; + mWorkerHandler.sendMessageDelayed(msg, RETRY_TIMES_MS[retryCount]); + } + + private void onAppPackageChanged(Intent intent) { + if (DEBUG || mIsInTest) Log.d(TAG, "Changes in apps: intent = [" + intent + "]"); + Preconditions.assertUIThread(); + + final String packageName = intent.getData().getSchemeSpecificPart(); + if (packageName == null || packageName.length() == 0) { + // they sent us a bad intent + return; + } + + final String action = intent.getAction(); + if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) { + Message.obtain(mWorkerHandler, MSG_PACKAGE_REMOVED, packageName).sendToTarget(); + } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) { + Message.obtain(mWorkerHandler, MSG_PACKAGE_ADDED, packageName).sendToTarget(); + } + } + + /** + * Shortcut factory for generating wellbeing action + */ + public static final SystemShortcut.Factory SHORTCUT_FACTORY = (activity, info) -> + (info.getTargetComponent() == null) ? null : WellbeingModel.get(activity) + .getShortcutForApp( + info.getTargetComponent().getPackageName(), info.user.getIdentifier(), + activity, info); +} diff --git a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java new file mode 100644 index 0000000000..965b5f0ad7 --- /dev/null +++ b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java @@ -0,0 +1,63 @@ +/* + * 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 android.app.Activity; +import android.app.Person; +import android.content.pm.ShortcutInfo; +import android.util.Base64; + +import com.android.launcher3.Utilities; +import com.android.systemui.shared.system.ActivityCompat; + +import java.io.ByteArrayOutputStream; +import java.io.PrintWriter; +import java.util.zip.Deflater; + +public class ApiWrapper { + + public static boolean dumpActivity(Activity activity, PrintWriter writer) { + if (!Utilities.IS_DEBUG_DEVICE) { + return false; + } + ByteArrayOutputStream out = new ByteArrayOutputStream(); + if (!(new ActivityCompat(activity).encodeViewHierarchy(out))) { + return false; + } + + Deflater deflater = new Deflater(); + deflater.setInput(out.toByteArray()); + deflater.finish(); + + out.reset(); + byte[] buffer = new byte[1024]; + while (!deflater.finished()) { + int count = deflater.deflate(buffer); // returns the generated code... index + out.write(buffer, 0, count); + } + + writer.println("--encoded-view-dump-v0--"); + writer.println(Base64.encodeToString( + out.toByteArray(), Base64.NO_WRAP | Base64.NO_PADDING)); + return true; + } + + public static Person[] getPersons(ShortcutInfo si) { + Person[] persons = si.getPersons(); + return persons == null ? Utilities.EMPTY_PERSON_ARRAY : persons; + } +} diff --git a/quickstep/src/com/android/launcher3/uioverrides/BackButtonAlphaHandler.java b/quickstep/src/com/android/launcher3/uioverrides/BackButtonAlphaHandler.java index aa0dfc3d3b..43dc882400 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/BackButtonAlphaHandler.java +++ b/quickstep/src/com/android/launcher3/uioverrides/BackButtonAlphaHandler.java @@ -16,11 +16,9 @@ package com.android.launcher3.uioverrides; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; -import com.android.launcher3.Launcher; +import com.android.launcher3.BaseQuickstepLauncher; import com.android.launcher3.LauncherState; import com.android.launcher3.LauncherStateManager; import com.android.launcher3.anim.AnimatorSetBuilder; @@ -30,18 +28,14 @@ import com.android.quickstep.SystemUiProxy; public class BackButtonAlphaHandler implements LauncherStateManager.StateHandler { - private static final String TAG = "BackButtonAlphaHandler"; + private final BaseQuickstepLauncher mLauncher; - private final Launcher mLauncher; - - public BackButtonAlphaHandler(Launcher launcher) { + public BackButtonAlphaHandler(BaseQuickstepLauncher launcher) { mLauncher = launcher; } @Override - public void setState(LauncherState state) { - UiFactory.onLauncherStateOrFocusChanged(mLauncher); - } + public void setState(LauncherState state) { } @Override public void setStateWithAnimation(LauncherState toState, @@ -52,8 +46,8 @@ public class BackButtonAlphaHandler implements LauncherStateManager.StateHandler if (!SysUINavigationMode.getMode(mLauncher).hasGestures) { // If the nav mode is not gestural, then force back button alpha to be 1 - UiThreadHelper.setBackButtonAlphaAsync(mLauncher, UiFactory.SET_BACK_BUTTON_ALPHA, 1f, - true /* animate */); + UiThreadHelper.setBackButtonAlphaAsync(mLauncher, + BaseQuickstepLauncher.SET_BACK_BUTTON_ALPHA, 1f, true /* animate */); return; } @@ -64,15 +58,8 @@ public class BackButtonAlphaHandler implements LauncherStateManager.StateHandler anim.setDuration(config.duration); anim.addUpdateListener(valueAnimator -> { final float alpha = (float) valueAnimator.getAnimatedValue(); - UiThreadHelper.setBackButtonAlphaAsync(mLauncher, UiFactory.SET_BACK_BUTTON_ALPHA, - alpha, false /* animate */); - }); - anim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - // Reapply the final alpha in case some state (e.g. window focus) changed. - UiFactory.onLauncherStateOrFocusChanged(mLauncher); - } + UiThreadHelper.setBackButtonAlphaAsync(mLauncher, + BaseQuickstepLauncher.SET_BACK_BUTTON_ALPHA, alpha, false /* animate */); }); builder.play(anim); } diff --git a/quickstep/src/com/android/launcher3/uioverrides/DejankBinderTracker.java b/quickstep/src/com/android/launcher3/uioverrides/DejankBinderTracker.java deleted file mode 100644 index d8aa235823..0000000000 --- a/quickstep/src/com/android/launcher3/uioverrides/DejankBinderTracker.java +++ /dev/null @@ -1,159 +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 android.os.IBinder.FLAG_ONEWAY; - -import android.os.Binder; -import android.os.Build; -import android.os.IBinder; -import android.os.Looper; -import android.os.RemoteException; -import android.util.Log; - -import androidx.annotation.MainThread; - -import java.util.HashSet; -import java.util.Locale; -import java.util.function.BiConsumer; -import java.util.function.Supplier; - -/** - * A binder proxy transaction listener for tracking non-whitelisted binder calls. - */ -public class DejankBinderTracker implements Binder.ProxyTransactListener { - private static final String TAG = "DejankBinderTracker"; - - private static final Object sLock = new Object(); - private static final HashSet sWhitelistedFrameworkClasses = new HashSet<>(); - static { - // Common IPCs that are ok to block the main thread. - sWhitelistedFrameworkClasses.add("android.view.IWindowSession"); - sWhitelistedFrameworkClasses.add("android.os.IPowerManager"); - } - private static boolean sTemporarilyIgnoreTracking = false; - - // Used by the client to limit binder tracking to specific regions - private static boolean sTrackingAllowed = false; - - private BiConsumer mUnexpectedTransactionCallback; - private boolean mIsTracking = false; - - /** - * Temporarily ignore blocking binder calls for the duration of this {@link Runnable}. - */ - @MainThread - public static void whitelistIpcs(Runnable runnable) { - sTemporarilyIgnoreTracking = true; - runnable.run(); - sTemporarilyIgnoreTracking = false; - } - - /** - * Temporarily ignore blocking binder calls for the duration of this {@link Supplier}. - */ - @MainThread - public static T whitelistIpcs(Supplier supplier) { - sTemporarilyIgnoreTracking = true; - T value = supplier.get(); - sTemporarilyIgnoreTracking = false; - return value; - } - - /** - * Enables binder tracking during a test. - */ - @MainThread - public static void allowBinderTrackingInTests() { - sTrackingAllowed = true; - } - - /** - * Disables binder tracking during a test. - */ - @MainThread - public static void disallowBinderTrackingInTests() { - sTrackingAllowed = false; - } - - public DejankBinderTracker(BiConsumer unexpectedTransactionCallback) { - mUnexpectedTransactionCallback = unexpectedTransactionCallback; - } - - @MainThread - public void startTracking() { - if (!Build.TYPE.toLowerCase(Locale.ROOT).contains("debug") - && !Build.TYPE.toLowerCase(Locale.ROOT).equals("eng")) { - Log.wtf(TAG, "Unexpected use of binder tracker in non-debug build", new Exception()); - return; - } - if (mIsTracking) { - return; - } - mIsTracking = true; - Binder.setProxyTransactListener(this); - } - - @MainThread - public void stopTracking() { - if (!mIsTracking) { - return; - } - mIsTracking = false; - Binder.setProxyTransactListener(null); - } - - // Override the hidden Binder#onTransactStarted method - public synchronized Object onTransactStarted(IBinder binder, int transactionCode, int flags) { - if (!mIsTracking - || !sTrackingAllowed - || sTemporarilyIgnoreTracking - || (flags & FLAG_ONEWAY) == FLAG_ONEWAY - || !isMainThread()) { - return null; - } - - String descriptor; - try { - descriptor = binder.getInterfaceDescriptor(); - if (sWhitelistedFrameworkClasses.contains(descriptor)) { - return null; - } - } catch (RemoteException e) { - e.printStackTrace(); - descriptor = binder.getClass().getSimpleName(); - } - - mUnexpectedTransactionCallback.accept(descriptor, transactionCode); - return null; - } - - @Override - public Object onTransactStarted(IBinder binder, int transactionCode) { - // Do nothing - return null; - } - - @Override - public void onTransactEnded(Object session) { - // Do nothing - } - - public static boolean isMainThread() { - return Thread.currentThread() == Looper.getMainLooper().getThread(); - } -} diff --git a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java deleted file mode 100644 index 17c681b26f..0000000000 --- a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java +++ /dev/null @@ -1,267 +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 android.app.Activity.RESULT_CANCELED; - -import static com.android.launcher3.AbstractFloatingView.TYPE_ALL; -import static com.android.launcher3.AbstractFloatingView.TYPE_HIDE_BACK_BUTTON; -import static com.android.launcher3.LauncherState.ALL_APPS; -import static com.android.launcher3.LauncherState.NORMAL; -import static com.android.launcher3.LauncherState.OVERVIEW; -import static com.android.launcher3.allapps.DiscoveryBounce.BOUNCE_MAX_COUNT; -import static com.android.launcher3.allapps.DiscoveryBounce.HOME_BOUNCE_COUNT; -import static com.android.launcher3.allapps.DiscoveryBounce.HOME_BOUNCE_SEEN; -import static com.android.launcher3.allapps.DiscoveryBounce.SHELF_BOUNCE_COUNT; -import static com.android.launcher3.allapps.DiscoveryBounce.SHELF_BOUNCE_SEEN; - -import android.animation.AnimatorSet; -import android.animation.ValueAnimator; -import android.app.Activity; -import android.app.Person; -import android.content.Context; -import android.content.Intent; -import android.content.IntentSender; -import android.content.pm.ShortcutInfo; -import android.os.Bundle; -import android.os.CancellationSignal; -import android.util.Base64; - -import com.android.launcher3.AbstractFloatingView; -import com.android.launcher3.Launcher; -import com.android.launcher3.LauncherState; -import com.android.launcher3.LauncherState.ScaleAndTranslation; -import com.android.launcher3.LauncherStateManager; -import com.android.launcher3.LauncherStateManager.StateHandler; -import com.android.launcher3.QuickstepAppTransitionManagerImpl; -import com.android.launcher3.Utilities; -import com.android.launcher3.proxy.ProxyActivityStarter; -import com.android.launcher3.proxy.StartActivityParams; -import com.android.launcher3.util.UiThreadHelper; -import com.android.quickstep.RecentsModel; -import com.android.quickstep.SysUINavigationMode; -import com.android.quickstep.SysUINavigationMode.Mode; -import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener; -import com.android.quickstep.SystemUiProxy; -import com.android.quickstep.util.RemoteFadeOutAnimationListener; -import com.android.systemui.shared.system.ActivityCompat; - -import java.io.ByteArrayOutputStream; -import java.io.PrintWriter; -import java.util.zip.Deflater; - -public class UiFactory extends RecentsUiFactory { - - /** - * Reusable command for applying the back button alpha on the background thread. - */ - public static final UiThreadHelper.AsyncCommand SET_BACK_BUTTON_ALPHA = - (context, arg1, arg2) -> { - SystemUiProxy.INSTANCE.get(context).setBackButtonAlpha(Float.intBitsToFloat(arg1), - arg2 != 0); - }; - - public static Runnable enableLiveUIChanges(Launcher launcher) { - NavigationModeChangeListener listener = m -> { - launcher.getDragLayer().recreateControllers(); - launcher.getRotationHelper().setRotationHadDifferentUI(m != Mode.NO_BUTTON); - }; - SysUINavigationMode mode = SysUINavigationMode.INSTANCE.get(launcher); - SysUINavigationMode.Mode m = mode.addModeChangeListener(listener); - launcher.getRotationHelper().setRotationHadDifferentUI(m != Mode.NO_BUTTON); - return () -> mode.removeModeChangeListener(listener); - } - - public static StateHandler[] getStateHandler(Launcher launcher) { - return new StateHandler[] { - launcher.getAllAppsController(), - launcher.getWorkspace(), - createRecentsViewStateController(launcher), - new BackButtonAlphaHandler(launcher)}; - } - - /** - * Sets the back button visibility based on the current state/window focus. - */ - public static void onLauncherStateOrFocusChanged(Launcher launcher) { - Mode mode = SysUINavigationMode.getMode(launcher); - boolean shouldBackButtonBeHidden = mode.hasGestures - && launcher != null - && launcher.getStateManager().getState().hideBackButton - && launcher.hasWindowFocus(); - if (shouldBackButtonBeHidden) { - // Show the back button if there is a floating view visible. - shouldBackButtonBeHidden = AbstractFloatingView.getTopOpenViewWithType(launcher, - TYPE_ALL & ~TYPE_HIDE_BACK_BUTTON) == null; - } - UiThreadHelper.setBackButtonAlphaAsync(launcher, UiFactory.SET_BACK_BUTTON_ALPHA, - shouldBackButtonBeHidden ? 0f : 1f, true /* animate */); - if (launcher != null && launcher.getDragLayer() != null) { - launcher.getRootView().setDisallowBackGesture(shouldBackButtonBeHidden); - } - } - - public static void onCreate(Launcher launcher) { - if (!launcher.getSharedPrefs().getBoolean(HOME_BOUNCE_SEEN, false)) { - launcher.getStateManager().addStateListener(new LauncherStateManager.StateListener() { - @Override - public void onStateTransitionStart(LauncherState toState) { - } - - @Override - public void onStateTransitionComplete(LauncherState finalState) { - boolean swipeUpEnabled = SysUINavigationMode.INSTANCE.get(launcher).getMode() - .hasGestures; - LauncherState prevState = launcher.getStateManager().getLastState(); - - if (((swipeUpEnabled && finalState == OVERVIEW) || (!swipeUpEnabled - && finalState == ALL_APPS && prevState == NORMAL) || BOUNCE_MAX_COUNT <= - launcher.getSharedPrefs().getInt(HOME_BOUNCE_COUNT, 0))) { - launcher.getSharedPrefs().edit().putBoolean(HOME_BOUNCE_SEEN, true).apply(); - launcher.getStateManager().removeStateListener(this); - } - } - }); - } - - if (!launcher.getSharedPrefs().getBoolean(SHELF_BOUNCE_SEEN, false)) { - launcher.getStateManager().addStateListener(new LauncherStateManager.StateListener() { - @Override - public void onStateTransitionStart(LauncherState toState) { - } - - @Override - public void onStateTransitionComplete(LauncherState finalState) { - LauncherState prevState = launcher.getStateManager().getLastState(); - - if ((finalState == ALL_APPS && prevState == OVERVIEW) || BOUNCE_MAX_COUNT <= - launcher.getSharedPrefs().getInt(SHELF_BOUNCE_COUNT, 0)) { - launcher.getSharedPrefs().edit().putBoolean(SHELF_BOUNCE_SEEN, true).apply(); - launcher.getStateManager().removeStateListener(this); - } - } - }); - } - } - - public static void onEnterAnimationComplete(Context context) { - // After the transition to home, enable the high-res thumbnail loader if it wasn't enabled - // as a part of quickstep, so that high-res thumbnails can load the next time we enter - // overview - RecentsModel.INSTANCE.get(context).getThumbnailCache() - .getHighResLoadingState().setVisible(true); - } - - public static void onTrimMemory(Context context, int level) { - RecentsModel model = RecentsModel.INSTANCE.get(context); - if (model != null) { - model.onTrimMemory(level); - } - } - - public static void useFadeOutAnimationForLauncherStart(Launcher launcher, - CancellationSignal cancellationSignal) { - QuickstepAppTransitionManagerImpl appTransitionManager = - (QuickstepAppTransitionManagerImpl) launcher.getAppTransitionManager(); - appTransitionManager.setRemoteAnimationProvider((appTargets, wallpaperTargets) -> { - - // On the first call clear the reference. - cancellationSignal.cancel(); - - ValueAnimator fadeAnimation = ValueAnimator.ofFloat(1, 0); - fadeAnimation.addUpdateListener(new RemoteFadeOutAnimationListener(appTargets, - wallpaperTargets)); - AnimatorSet anim = new AnimatorSet(); - anim.play(fadeAnimation); - return anim; - }, cancellationSignal); - } - - public static boolean dumpActivity(Activity activity, PrintWriter writer) { - if (!Utilities.IS_DEBUG_DEVICE) { - return false; - } - ByteArrayOutputStream out = new ByteArrayOutputStream(); - if (!(new ActivityCompat(activity).encodeViewHierarchy(out))) { - return false; - } - - Deflater deflater = new Deflater(); - deflater.setInput(out.toByteArray()); - deflater.finish(); - - out.reset(); - byte[] buffer = new byte[1024]; - while (!deflater.finished()) { - int count = deflater.deflate(buffer); // returns the generated code... index - out.write(buffer, 0, count); - } - - writer.println("--encoded-view-dump-v0--"); - writer.println(Base64.encodeToString( - out.toByteArray(), Base64.NO_WRAP | Base64.NO_PADDING)); - return true; - } - - public static boolean startIntentSenderForResult(Activity activity, IntentSender intent, - int requestCode, Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, - Bundle options) { - StartActivityParams params = new StartActivityParams(activity, requestCode); - params.intentSender = intent; - params.fillInIntent = fillInIntent; - params.flagsMask = flagsMask; - params.flagsValues = flagsValues; - params.extraFlags = extraFlags; - params.options = options; - ((Context) activity).startActivity(ProxyActivityStarter.getLaunchIntent(activity, params)); - return true; - } - - public static boolean startActivityForResult(Activity activity, Intent intent, int requestCode, - Bundle options) { - StartActivityParams params = new StartActivityParams(activity, requestCode); - params.intent = intent; - params.options = options; - activity.startActivity(ProxyActivityStarter.getLaunchIntent(activity, params)); - return true; - } - - /** - * Removes any active ProxyActivityStarter task and sends RESULT_CANCELED to Launcher. - * - * ProxyActivityStarter is started with clear task to reset the task after which it removes the - * task itself. - */ - public static void resetPendingActivityResults(Launcher launcher, int requestCode) { - launcher.onActivityResult(requestCode, RESULT_CANCELED, null); - launcher.startActivity(ProxyActivityStarter.getLaunchIntent(launcher, null)); - } - - public static ScaleAndTranslation getOverviewScaleAndTranslationForNormalState(Launcher l) { - if (SysUINavigationMode.getMode(l) == Mode.NO_BUTTON) { - float offscreenTranslationX = l.getDeviceProfile().widthPx - - l.getOverviewPanel().getPaddingStart(); - return new ScaleAndTranslation(1f, offscreenTranslationX, 0f); - } - return new ScaleAndTranslation(1.1f, 0f, 0f); - } - - public static Person[] getPersons(ShortcutInfo si) { - Person[] persons = si.getPersons(); - return persons == null ? Utilities.EMPTY_PERSON_ARRAY : persons; - } -} diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeEdgeSwipeController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeEdgeSwipeController.java index bb72315d3b..3cb00885e8 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeEdgeSwipeController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeEdgeSwipeController.java @@ -11,10 +11,10 @@ import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; import com.android.launcher3.LauncherStateManager.AnimationComponents; import com.android.launcher3.touch.AbstractStateChangeTouchController; -import com.android.launcher3.touch.SwipeDetector; +import com.android.launcher3.touch.SingleAxisSwipeDetector; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction; -import com.android.quickstep.RecentsModel; +import com.android.quickstep.SystemUiProxy; /** * Touch controller for handling edge swipes in landscape/seascape UI @@ -24,7 +24,7 @@ public class LandscapeEdgeSwipeController extends AbstractStateChangeTouchContro private static final String TAG = "LandscapeEdgeSwipeCtrl"; public LandscapeEdgeSwipeController(Launcher l) { - super(l, SwipeDetector.HORIZONTAL); + super(l, SingleAxisSwipeDetector.HORIZONTAL); } @Override @@ -73,7 +73,7 @@ public class LandscapeEdgeSwipeController extends AbstractStateChangeTouchContro protected void onSwipeInteractionCompleted(LauncherState targetState, int logAction) { super.onSwipeInteractionCompleted(targetState, logAction); if (mStartState == NORMAL && targetState == OVERVIEW) { - RecentsModel.INSTANCE.get(mLauncher).onOverviewShown(true, TAG); + SystemUiProxy.INSTANCE.get(mLauncher).onOverviewShown(true, TAG); } } } diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java index ef6a5e23d6..99b2a81386 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java @@ -43,11 +43,10 @@ import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.AnimatorSetBuilder; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.touch.AbstractStateChangeTouchController; -import com.android.launcher3.touch.SwipeDetector; +import com.android.launcher3.touch.SingleAxisSwipeDetector; import com.android.launcher3.uioverrides.states.OverviewState; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; -import com.android.quickstep.RecentsModel; import com.android.quickstep.SystemUiProxy; import com.android.quickstep.TouchInteractionService; import com.android.quickstep.util.LayoutUtils; @@ -79,7 +78,7 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr private boolean mFinishFastOnSecondTouch; public PortraitStatesTouchController(Launcher l, boolean allowDragToOverview) { - super(l, SwipeDetector.VERTICAL); + super(l, SingleAxisSwipeDetector.VERTICAL); mOverviewPortraitStateTouchHelper = new PortraitOverviewStateTouchHelper(l); mAllowDragToOverview = allowDragToOverview; } @@ -300,7 +299,7 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr protected void onSwipeInteractionCompleted(LauncherState targetState, int logAction) { super.onSwipeInteractionCompleted(targetState, logAction); if (mStartState == NORMAL && targetState == OVERVIEW) { - RecentsModel.INSTANCE.get(mLauncher).onOverviewShown(true, TAG); + SystemUiProxy.INSTANCE.get(mLauncher).onOverviewShown(true, TAG); } } diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java index 409bec6c70..fd55e077fd 100644 --- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java +++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java @@ -32,11 +32,12 @@ import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.DeviceProfile; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.quickstep.util.ActivityInitListener; +import com.android.quickstep.util.ShelfPeekAnim; import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; -import java.util.function.BiPredicate; import java.util.function.Consumer; +import java.util.function.Predicate; /** * Utility class which abstracts out the logical differences between Launcher and RecentsActivity. @@ -44,21 +45,26 @@ import java.util.function.Consumer; @TargetApi(Build.VERSION_CODES.P) public interface BaseActivityInterface { - void onTransitionCancelled(T activity, boolean activityVisible); + void onTransitionCancelled(boolean activityVisible); int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect); - void onSwipeUpToRecentsComplete(T activity); + void onSwipeUpToRecentsComplete(); - default void onSwipeUpToHomeComplete(T activity) { } + default void onSwipeUpToHomeComplete() { } void onAssistantVisibilityChanged(float visibility); - @NonNull HomeAnimationFactory prepareHomeUI(T activity); + @NonNull HomeAnimationFactory prepareHomeUI(); - AnimationFactory prepareRecentsUI(T activity, boolean activityVisible, - boolean animateActivity, Consumer callback); + AnimationFactory prepareRecentsUI(boolean activityVisible, boolean animateActivity, + Consumer callback); - ActivityInitListener createActivityInitListener(BiPredicate onInitListener); + ActivityInitListener createActivityInitListener(Predicate onInitListener); + + /** + * Sets a callback to be run when an activity launch happens while launcher is not yet resumed. + */ + default void setOnDeferredActivityLaunchCallback(Runnable r) {} @Nullable T getCreatedActivity(); @@ -83,6 +89,13 @@ public interface BaseActivityInterface { return true; } + /** + * Updates the prediction state to the overview state. + */ + default void updateOverviewPredictionState() { + // By default overview predictions are not supported + } + /** * Used for containerType in {@link com.android.launcher3.logging.UserEventDispatcher} */ @@ -90,26 +103,17 @@ public interface BaseActivityInterface { boolean isInLiveTileMode(); - void onLaunchTaskFailed(T activity); + void onLaunchTaskFailed(); - void onLaunchTaskSuccess(T activity); + void onLaunchTaskSuccess(); default void closeOverlay() { } - default void switchToScreenshot(ThumbnailData thumbnailData, Runnable runnable) {} + default void switchRunningTaskViewToScreenshot(ThumbnailData thumbnailData, + Runnable runnable) {} interface AnimationFactory { - enum ShelfAnimState { - HIDE(true), PEEK(true), OVERVIEW(false), CANCEL(false); - - ShelfAnimState(boolean shouldPreformHaptic) { - this.shouldPreformHaptic = shouldPreformHaptic; - } - - public final boolean shouldPreformHaptic; - } - default void onRemoteAnimationReceived(RemoteAnimationTargets targets) { } void createActivityInterface(long transitionLength); @@ -118,8 +122,8 @@ public interface BaseActivityInterface { default void onTransitionCancelled() { } - default void setShelfState(ShelfAnimState animState, Interpolator interpolator, - long duration) { } + default void setShelfState(ShelfPeekAnim.ShelfAnimState animState, + Interpolator interpolator, long duration) { } /** * @param attached Whether to show RecentsView alongside the app window. If false, recents diff --git a/quickstep/src/com/android/quickstep/BaseRecentsActivity.java b/quickstep/src/com/android/quickstep/BaseRecentsActivity.java index 71833ad418..5fcdc191fe 100644 --- a/quickstep/src/com/android/quickstep/BaseRecentsActivity.java +++ b/quickstep/src/com/android/quickstep/BaseRecentsActivity.java @@ -27,7 +27,6 @@ import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.DeviceProfile; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.R; -import com.android.launcher3.uioverrides.UiFactory; import com.android.launcher3.util.ActivityTracker; import com.android.launcher3.util.SystemUiController; import com.android.launcher3.util.Themes; @@ -122,13 +121,17 @@ public abstract class BaseRecentsActivity extends BaseDraggingActivity { @Override public void onEnterAnimationComplete() { super.onEnterAnimationComplete(); - UiFactory.onEnterAnimationComplete(this); + // After the transition to home, enable the high-res thumbnail loader if it wasn't enabled + // as a part of quickstep, so that high-res thumbnails can load the next time we enter + // overview + RecentsModel.INSTANCE.get(this).getThumbnailCache() + .getHighResLoadingState().setVisible(true); } @Override public void onTrimMemory(int level) { super.onTrimMemory(level); - UiFactory.onTrimMemory(this, level); + RecentsModel.INSTANCE.get(this).onTrimMemory(level); } @Override diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java index de64227c26..ae0886b913 100644 --- a/quickstep/src/com/android/quickstep/GestureState.java +++ b/quickstep/src/com/android/quickstep/GestureState.java @@ -15,22 +15,270 @@ */ package com.android.quickstep; +import static com.android.quickstep.MultiStateCallback.DEBUG_STATES; + +import android.app.ActivityManager; +import android.content.Intent; import com.android.launcher3.BaseDraggingActivity; +import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; +import com.android.systemui.shared.recents.model.ThumbnailData; +import java.util.ArrayList; /** * Manages the state for an active system gesture, listens for events from the system and Launcher, * and fires events when the states change. */ -public class GestureState { +public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationListener { - // Needed to interact with the current activity - private BaseActivityInterface mActivityInterface; + /** + * Defines the end targets of a gesture and the associated state. + */ + public enum GestureEndTarget { + HOME(true, ContainerType.WORKSPACE, false), - public GestureState(BaseActivityInterface activityInterface) { - mActivityInterface = activityInterface; + RECENTS(true, ContainerType.TASKSWITCHER, true), + + NEW_TASK(false, ContainerType.APP, true), + + LAST_TASK(false, ContainerType.APP, false); + + GestureEndTarget(boolean isLauncher, int containerType, + boolean recentsAttachedToAppWindow) { + this.isLauncher = isLauncher; + this.containerType = containerType; + this.recentsAttachedToAppWindow = recentsAttachedToAppWindow; + } + + /** Whether the target is in the launcher activity. Implicitly, if the end target is going + to Launcher, then we can not interrupt the animation to start another gesture. */ + public final boolean isLauncher; + /** Used to log where the user ended up after the gesture ends */ + public final int containerType; + /** Whether RecentsView should be attached to the window as we animate to this target */ + public final boolean recentsAttachedToAppWindow; } + private static final String TAG = "GestureState"; + + private static final ArrayList STATE_NAMES = new ArrayList<>(); + private static int FLAG_COUNT = 0; + private static int getFlagForIndex(String name) { + if (DEBUG_STATES) { + STATE_NAMES.add(name); + } + int index = 1 << FLAG_COUNT; + FLAG_COUNT++; + return index; + } + + // Called when the end target as been set + public static final int STATE_END_TARGET_SET = + getFlagForIndex("STATE_END_TARGET_SET"); + + // Called when the end target animation has finished + public static final int STATE_END_TARGET_ANIMATION_FINISHED = + getFlagForIndex("STATE_END_TARGET_ANIMATION_FINISHED"); + + // Called when the recents animation has been requested to start + public static final int STATE_RECENTS_ANIMATION_INITIALIZED = + getFlagForIndex("STATE_RECENTS_ANIMATION_INITIALIZED"); + + // Called when the recents animation is started and the TaskAnimationManager has been updated + // with the controller and targets + public static final int STATE_RECENTS_ANIMATION_STARTED = + getFlagForIndex("STATE_RECENTS_ANIMATION_STARTED"); + + // Called when the recents animation is canceled + public static final int STATE_RECENTS_ANIMATION_CANCELED = + getFlagForIndex("STATE_RECENTS_ANIMATION_CANCELED"); + + // Called when the recents animation finishes + public static final int STATE_RECENTS_ANIMATION_FINISHED = + getFlagForIndex("STATE_RECENTS_ANIMATION_FINISHED"); + + // Always called when the recents animation ends (regardless of cancel or finish) + public static final int STATE_RECENTS_ANIMATION_ENDED = + getFlagForIndex("STATE_RECENTS_ANIMATION_ENDED"); + + + // Needed to interact with the current activity + private final Intent mHomeIntent; + private final Intent mOverviewIntent; + private final BaseActivityInterface mActivityInterface; + private final MultiStateCallback mStateCallback; + private final int mGestureId; + + private ActivityManager.RunningTaskInfo mRunningTask; + private GestureEndTarget mEndTarget; + // TODO: This can be removed once we stop finishing the animation when starting a new task + private int mFinishingRecentsAnimationTaskId = -1; + + public GestureState(OverviewComponentObserver componentObserver, int gestureId) { + mHomeIntent = componentObserver.getHomeIntent(); + mOverviewIntent = componentObserver.getOverviewIntent(); + mActivityInterface = componentObserver.getActivityInterface(); + mStateCallback = new MultiStateCallback(STATE_NAMES.toArray(new String[0])); + mGestureId = gestureId; + } + + public GestureState() { + // Do nothing, only used for initializing the gesture state prior to user unlock + mHomeIntent = new Intent(); + mOverviewIntent = new Intent(); + mActivityInterface = null; + mStateCallback = new MultiStateCallback(STATE_NAMES.toArray(new String[0])); + mGestureId = -1; + } + + /** + * @return whether the gesture state has the provided {@param stateMask} flags set. + */ + public boolean hasState(int stateMask) { + return mStateCallback.hasStates(stateMask); + } + + /** + * Sets the given {@param stateFlag}s. + */ + public void setState(int stateFlag) { + mStateCallback.setState(stateFlag); + } + + /** + * Adds a callback for when the states matching the given {@param stateMask} is set. + */ + public void runOnceAtState(int stateMask, Runnable callback) { + mStateCallback.runOnceAtState(stateMask, callback); + } + + /** + * @return the intent for the Home component. + */ + public Intent getHomeIntent() { + return mHomeIntent; + } + + /** + * @return the intent for the Overview component. + */ + public Intent getOverviewIntent() { + return mOverviewIntent; + } + + /** + * @return the interface to the activity handing the UI updates for this gesture. + */ public BaseActivityInterface getActivityInterface() { return mActivityInterface; } + + /** + * @return the id for this particular gesture. + */ + public int getGestureId() { + return mGestureId; + } + + /** + * @return the running task for this gesture. + */ + public ActivityManager.RunningTaskInfo getRunningTask() { + return mRunningTask; + } + + /** + * @return the running task id for this gesture. + */ + public int getRunningTaskId() { + return mRunningTask != null ? mRunningTask.taskId : -1; + } + + /** + * Updates the running task for the gesture to be the given {@param runningTask}. + */ + public void updateRunningTask(ActivityManager.RunningTaskInfo runningTask) { + mRunningTask = runningTask; + } + + /** + * @return the end target for this gesture (if known). + */ + public GestureEndTarget getEndTarget() { + return mEndTarget; + } + + /** + * Sets the end target of this gesture and immediately notifies the state changes. + */ + public void setEndTarget(GestureEndTarget target) { + setEndTarget(target, true /* isAtomic */); + } + + /** + * Sets the end target of this gesture, but if {@param isAtomic} is {@code false}, then the + * caller must explicitly set {@link #STATE_END_TARGET_ANIMATION_FINISHED} themselves. + */ + public void setEndTarget(GestureEndTarget target, boolean isAtomic) { + mEndTarget = target; + mStateCallback.setState(STATE_END_TARGET_SET); + if (isAtomic) { + mStateCallback.setState(STATE_END_TARGET_ANIMATION_FINISHED); + } + } + + /** + * @return the id for the task that was about to be launched following the finish of the recents + * animation. Only defined between when the finish-recents call was made and the launch + * activity call is made. + */ + public int getFinishingRecentsAnimationTaskId() { + return mFinishingRecentsAnimationTaskId; + } + + /** + * Sets the id for the task will be launched after the recents animation is finished. Once the + * animation has finished then the id will be reset to -1. + */ + public void setFinishingRecentsAnimationTaskId(int taskId) { + mFinishingRecentsAnimationTaskId = taskId; + mStateCallback.runOnceAtState(STATE_RECENTS_ANIMATION_FINISHED, () -> { + mFinishingRecentsAnimationTaskId = -1; + }); + } + + /** + * @return whether the current gesture is still running a recents animation to a state in the + * Launcher or Recents activity. + * Updates the running task for the gesture to be the given {@param runningTask}. + */ + public boolean isRunningAnimationToLauncher() { + return isRecentsAnimationRunning() && mEndTarget != null && mEndTarget.isLauncher; + } + + /** + * @return whether the recents animation is started but not yet ended + */ + public boolean isRecentsAnimationRunning() { + return mStateCallback.hasStates(STATE_RECENTS_ANIMATION_INITIALIZED) && + !mStateCallback.hasStates(STATE_RECENTS_ANIMATION_ENDED); + } + + @Override + public void onRecentsAnimationStart(RecentsAnimationController controller, + RecentsAnimationTargets targets) { + mStateCallback.setState(STATE_RECENTS_ANIMATION_STARTED); + } + + @Override + public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) { + mStateCallback.setState(STATE_RECENTS_ANIMATION_CANCELED); + mStateCallback.setState(STATE_RECENTS_ANIMATION_ENDED); + } + + @Override + public void onRecentsAnimationFinished(RecentsAnimationController controller) { + mStateCallback.setState(STATE_RECENTS_ANIMATION_FINISHED); + mStateCallback.setState(STATE_RECENTS_ANIMATION_ENDED); + } } diff --git a/quickstep/src/com/android/quickstep/InputConsumer.java b/quickstep/src/com/android/quickstep/InputConsumer.java index 62c0ded534..3e84e7d5ac 100644 --- a/quickstep/src/com/android/quickstep/InputConsumer.java +++ b/quickstep/src/com/android/quickstep/InputConsumer.java @@ -33,7 +33,7 @@ public interface InputConsumer { int TYPE_SCREEN_PINNED = 1 << 6; int TYPE_OVERVIEW_WITHOUT_FOCUS = 1 << 7; int TYPE_RESET_GESTURE = 1 << 8; - int TYPE_QUICK_CAPTURE = 1 << 9; + int TYPE_OVERSCROLL = 1 << 9; String[] NAMES = new String[] { "TYPE_NO_OP", // 0 @@ -45,17 +45,13 @@ public interface InputConsumer { "TYPE_SCREEN_PINNED", // 6 "TYPE_OVERVIEW_WITHOUT_FOCUS", // 7 "TYPE_RESET_GESTURE", // 8 - "TYPE_QUICK_CAPTURE", // 9 + "TYPE_OVERSCROLL", // 9 }; InputConsumer NO_OP = () -> TYPE_NO_OP; int getType(); - default boolean useSharedSwipeState() { - return false; - } - /** * Returns true if the user has crossed the threshold for it to be an explicit action. */ @@ -65,6 +61,8 @@ public interface InputConsumer { /** * Called by the event queue when the consumer is about to be switched to a new consumer. + * Consumers should update the state accordingly here before the state is passed to the new + * consumer. */ default void onConsumerAboutToBeSwitched() { } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/MultiStateCallback.java b/quickstep/src/com/android/quickstep/MultiStateCallback.java similarity index 50% rename from quickstep/recents_ui_overrides/src/com/android/quickstep/MultiStateCallback.java rename to quickstep/src/com/android/quickstep/MultiStateCallback.java index 357c9fc35e..6c65e01c20 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/MultiStateCallback.java +++ b/quickstep/src/com/android/quickstep/MultiStateCallback.java @@ -15,11 +15,17 @@ */ package com.android.quickstep; +import static com.android.launcher3.Utilities.postAsyncCallback; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; + +import android.os.Looper; import android.util.Log; import android.util.SparseArray; import com.android.launcher3.config.FeatureFlags; +import java.util.ArrayList; +import java.util.LinkedList; import java.util.StringJoiner; import java.util.function.Consumer; @@ -31,16 +37,29 @@ public class MultiStateCallback { private static final String TAG = "MultiStateCallback"; public static final boolean DEBUG_STATES = false; - private final SparseArray mCallbacks = new SparseArray<>(); - private final SparseArray> mStateChangeHandlers = new SparseArray<>(); + private final SparseArray> mCallbacks = new SparseArray<>(); + private final SparseArray>> mStateChangeListeners = + new SparseArray<>(); private final String[] mStateNames; + private int mState = 0; + public MultiStateCallback(String[] stateNames) { mStateNames = DEBUG_STATES ? stateNames : null; } - private int mState = 0; + /** + * Adds the provided state flags to the global state on the UI thread and executes any callbacks + * as a result. + */ + public void setStateOnUiThread(int stateFlag) { + if (Looper.myLooper() == Looper.getMainLooper()) { + setState(stateFlag); + } else { + postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> setState(stateFlag)); + } + } /** * Adds the provided state flags to the global state and executes any callbacks as a result. @@ -51,7 +70,7 @@ public class MultiStateCallback { + convertToFlagNames(stateFlag) + " to " + convertToFlagNames(mState)); } - int oldState = mState; + final int oldState = mState; mState = mState | stateFlag; int count = mCallbacks.size(); @@ -59,15 +78,13 @@ public class MultiStateCallback { int state = mCallbacks.keyAt(i); if ((mState & state) == state) { - Runnable callback = mCallbacks.valueAt(i); - if (callback != null) { - // Set the callback to null, so that it does not run again. - mCallbacks.setValueAt(i, null); - callback.run(); + LinkedList callbacks = mCallbacks.valueAt(i); + while (!callbacks.isEmpty()) { + callbacks.pollFirst().run(); } } } - notifyStateChangeHandlers(oldState); + notifyStateChangeListeners(oldState); } /** @@ -82,38 +99,61 @@ public class MultiStateCallback { int oldState = mState; mState = mState & ~stateFlag; - notifyStateChangeHandlers(oldState); + notifyStateChangeListeners(oldState); } - private void notifyStateChangeHandlers(int oldState) { - int count = mStateChangeHandlers.size(); + private void notifyStateChangeListeners(int oldState) { + int count = mStateChangeListeners.size(); for (int i = 0; i < count; i++) { - int state = mStateChangeHandlers.keyAt(i); + int state = mStateChangeListeners.keyAt(i); boolean wasOn = (state & oldState) == state; boolean isOn = (state & mState) == state; if (wasOn != isOn) { - mStateChangeHandlers.valueAt(i).accept(isOn); + ArrayList> listeners = mStateChangeListeners.valueAt(i); + for (Consumer listener : listeners) { + listener.accept(isOn); + } } } } /** - * Sets the callbacks to be run when the provided states are enabled. - * The callback is only run once. + * Sets a callback to be run when the provided states in the given {@param stateMask} is + * enabled. The callback is only run *once*, and if the states are already set at the time of + * this call then the callback will be made immediately. */ - public void addCallback(int stateMask, Runnable callback) { - if (FeatureFlags.IS_DOGFOOD_BUILD && mCallbacks.get(stateMask) != null) { - throw new IllegalStateException("Multiple callbacks on same state"); + public void runOnceAtState(int stateMask, Runnable callback) { + if ((mState & stateMask) == stateMask) { + callback.run(); + } else { + final LinkedList callbacks; + if (mCallbacks.indexOfKey(stateMask) >= 0) { + callbacks = mCallbacks.get(stateMask); + if (FeatureFlags.IS_DOGFOOD_BUILD && callbacks.contains(callback)) { + throw new IllegalStateException("Existing callback for state found"); + } + } else { + callbacks = new LinkedList<>(); + mCallbacks.put(stateMask, callbacks); + } + callbacks.add(callback); } - mCallbacks.put(stateMask, callback); } /** - * Sets the handler to be called when the provided states are enabled or disabled. + * Adds a persistent listener to be called states in the given {@param stateMask} are enabled + * or disabled. */ - public void addChangeHandler(int stateMask, Consumer handler) { - mStateChangeHandlers.put(stateMask, handler); + public void addChangeListener(int stateMask, Consumer listener) { + final ArrayList> listeners; + if (mStateChangeListeners.indexOfKey(stateMask) >= 0) { + listeners = mStateChangeListeners.get(stateMask); + } else { + listeners = new ArrayList<>(); + mStateChangeListeners.put(stateMask, listeners); + } + listeners.add(listener); } public int getState() { diff --git a/quickstep/src/com/android/quickstep/NormalizedIconLoader.java b/quickstep/src/com/android/quickstep/NormalizedIconLoader.java deleted file mode 100644 index bd6204aab1..0000000000 --- a/quickstep/src/com/android/quickstep/NormalizedIconLoader.java +++ /dev/null @@ -1,99 +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.app.ActivityManager.TaskDescription; -import android.content.ComponentName; -import android.content.Context; -import android.content.pm.ActivityInfo; -import android.content.res.Resources; -import android.graphics.drawable.Drawable; -import android.os.Build; -import android.os.UserHandle; -import android.util.LruCache; -import android.util.SparseArray; - -import com.android.launcher3.FastBitmapDrawable; -import com.android.launcher3.icons.BitmapInfo; -import com.android.launcher3.graphics.DrawableFactory; -import com.android.launcher3.icons.LauncherIcons; -import com.android.systemui.shared.recents.model.IconLoader; -import com.android.systemui.shared.recents.model.TaskKeyLruCache; - -/** - * Extension of {@link IconLoader} with icon normalization support - */ -@TargetApi(Build.VERSION_CODES.O) -public class NormalizedIconLoader extends IconLoader { - - private final SparseArray mDefaultIcons = new SparseArray<>(); - private final DrawableFactory mDrawableFactory; - private final boolean mDisableColorExtraction; - - public NormalizedIconLoader(Context context, TaskKeyLruCache iconCache, - LruCache activityInfoCache, - boolean disableColorExtraction) { - super(context, iconCache, activityInfoCache); - mDrawableFactory = DrawableFactory.INSTANCE.get(context); - mDisableColorExtraction = disableColorExtraction; - } - - @Override - public Drawable getDefaultIcon(int userId) { - synchronized (mDefaultIcons) { - BitmapInfo info = mDefaultIcons.get(userId); - if (info == null) { - info = getBitmapInfo(Resources.getSystem() - .getDrawable(android.R.drawable.sym_def_app_icon), userId, 0, false); - mDefaultIcons.put(userId, info); - } - - return new FastBitmapDrawable(info); - } - } - - @Override - protected Drawable createBadgedDrawable(Drawable drawable, int userId, TaskDescription desc) { - return new FastBitmapDrawable(getBitmapInfo(drawable, userId, desc.getPrimaryColor(), - false)); - } - - private BitmapInfo getBitmapInfo(Drawable drawable, int userId, - int primaryColor, boolean isInstantApp) { - try (LauncherIcons la = LauncherIcons.obtain(mContext)) { - if (mDisableColorExtraction) { - la.disableColorExtraction(); - } - la.setWrapperBackgroundColor(primaryColor); - - // User version code O, so that the icon is always wrapped in an adaptive icon container - return la.createBadgedIconBitmap(drawable, UserHandle.of(userId), - Build.VERSION_CODES.O, isInstantApp); - } - } - - @Override - protected Drawable getBadgedActivityIcon(ActivityInfo activityInfo, int userId, - TaskDescription desc) { - BitmapInfo bitmapInfo = getBitmapInfo( - activityInfo.loadUnbadgedIcon(mContext.getPackageManager()), - userId, - desc.getPrimaryColor(), - activityInfo.applicationInfo.isInstantApp()); - return mDrawableFactory.newIcon(mContext, bitmapInfo, activityInfo); - } -} diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java index 2918879d74..acf61b4142 100644 --- a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java +++ b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java @@ -127,7 +127,7 @@ public class RecentsAnimationCallbacks implements */ public interface RecentsAnimationListener { default void onRecentsAnimationStart(RecentsAnimationController controller, - RecentsAnimationTargets targetSet) {} + RecentsAnimationTargets targets) {} /** * Callback from the system when the recents animation is canceled. {@param thumbnailData} @@ -135,6 +135,9 @@ public class RecentsAnimationCallbacks implements */ default void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {} + /** + * Callback made whenever the recents animation is finished. + */ default void onRecentsAnimationFinished(RecentsAnimationController controller) {} } } diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java index d938dc5af8..46af8bfcd3 100644 --- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java +++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java @@ -18,6 +18,7 @@ package com.android.quickstep; import static android.view.MotionEvent.ACTION_CANCEL; import static android.view.MotionEvent.ACTION_DOWN; import static android.view.MotionEvent.ACTION_UP; + import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; @@ -48,11 +49,10 @@ public class RecentsAnimationController { private final Consumer mOnFinishedListener; private final boolean mShouldMinimizeSplitScreen; - private boolean mWindowThresholdCrossed = false; - private InputConsumerController mInputConsumerController; private Supplier mInputProxySupplier; private InputConsumer mInputConsumer; + private boolean mWindowThresholdCrossed = false; private boolean mTouchInProgress; private boolean mFinishPending; @@ -62,8 +62,6 @@ public class RecentsAnimationController { mController = controller; mOnFinishedListener = onFinishedListener; mShouldMinimizeSplitScreen = shouldMinimizeSplitScreen; - - setWindowThresholdCrossed(mWindowThresholdCrossed); } /** @@ -71,7 +69,7 @@ public class RecentsAnimationController { * currently being animated. */ public ThumbnailData screenshotTask(int taskId) { - return mController != null ? mController.screenshotTask(taskId) : null; + return mController.screenshotTask(taskId); } /** @@ -188,6 +186,11 @@ public class RecentsAnimationController { mInputConsumerController.setInputListener(this::onInputConsumerEvent); } + /** @return wrapper controller. */ + public RecentsAnimationControllerCompat getController() { + return mController; + } + private void disableInputProxy() { if (mInputConsumer != null && mTouchInProgress) { long now = SystemClock.uptimeMillis(); diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java index 9b094f6e2d..81f411e4e6 100644 --- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java +++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java @@ -16,10 +16,14 @@ package com.android.quickstep; import static android.content.Intent.ACTION_USER_UNLOCKED; + +import static android.provider.Settings.System.HAPTIC_FEEDBACK_ENABLED; import static com.android.launcher3.ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE; import static com.android.launcher3.ResourceUtils.NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON; import static com.android.quickstep.SysUINavigationMode.Mode.THREE_BUTTONS; +import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED; @@ -33,27 +37,37 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_S import android.app.ActivityManager; import android.content.BroadcastReceiver; import android.content.ComponentName; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.res.Resources; +import android.database.ContentObserver; import android.graphics.Point; import android.graphics.RectF; import android.graphics.Region; import android.os.Process; +import android.provider.Settings; import android.text.TextUtils; +import android.util.Log; import android.view.MotionEvent; import android.view.Surface; + import androidx.annotation.BinderThread; + import com.android.launcher3.R; import com.android.launcher3.ResourceUtils; import com.android.launcher3.Utilities; import com.android.launcher3.compat.UserManagerCompat; +import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.util.DefaultDisplay; +import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener; +import com.android.quickstep.util.NavBarPosition; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags; import com.android.systemui.shared.system.SystemGestureExclusionListenerCompat; + import java.io.PrintWriter; import java.util.ArrayList; @@ -61,17 +75,20 @@ import java.util.ArrayList; * Manages the state of the system during a swipe up gesture. */ public class RecentsAnimationDeviceState implements - SysUINavigationMode.NavigationModeChangeListener, + NavigationModeChangeListener, DefaultDisplay.DisplayInfoChangeListener { - private Context mContext; - private UserManagerCompat mUserManager; - private SysUINavigationMode mSysUiNavMode; - private DefaultDisplay mDefaultDisplay; - private int mDisplayId; + private final Context mContext; + private final UserManagerCompat mUserManager; + private final SysUINavigationMode mSysUiNavMode; + private final DefaultDisplay mDefaultDisplay; + private final int mDisplayId; + + private final ArrayList mOnDestroyActions = new ArrayList<>(); private @SystemUiStateFlags int mSystemUiStateFlags; private SysUINavigationMode.Mode mMode = THREE_BUTTONS; + private NavBarPosition mNavBarPosition; private final RectF mSwipeUpTouchRegion = new RectF(); private final Region mDeferredGestureRegion = new Region(); @@ -98,11 +115,13 @@ public class RecentsAnimationDeviceState implements private ComponentName mGestureBlockedActivity; public RecentsAnimationDeviceState(Context context) { + final ContentResolver resolver = context.getContentResolver(); mContext = context; mUserManager = UserManagerCompat.getInstance(context); mSysUiNavMode = SysUINavigationMode.INSTANCE.get(context); mDefaultDisplay = DefaultDisplay.INSTANCE.get(context); mDisplayId = mDefaultDisplay.getInfo().id; + runOnDestroy(() -> mDefaultDisplay.removeChangeListener(this)); // Register for user unlocked if necessary mIsUserUnlocked = mUserManager.isUserUnlocked(Process.myUserHandle()); @@ -110,6 +129,7 @@ public class RecentsAnimationDeviceState implements mContext.registerReceiver(mUserUnlockedReceiver, new IntentFilter(ACTION_USER_UNLOCKED)); } + runOnDestroy(() -> Utilities.unregisterReceiverSafely(mContext, mUserUnlockedReceiver)); // Register for exclusion updates mExclusionListener = new SystemGestureExclusionListenerCompat(mDisplayId) { @@ -120,7 +140,11 @@ public class RecentsAnimationDeviceState implements mExclusionRegion = region; } }; + runOnDestroy(mExclusionListener::unregister); + + // Register for navigation mode changes onNavigationModeChanged(mSysUiNavMode.addModeChangeListener(this)); + runOnDestroy(() -> mSysUiNavMode.removeModeChangeListener(this)); // Add any blocked activities String blockingActivity = context.getString(R.string.gesture_blocking_activity); @@ -129,18 +153,33 @@ public class RecentsAnimationDeviceState implements } } + private void runOnDestroy(Runnable action) { + mOnDestroyActions.add(action); + } + /** * Cleans up all the registered listeners and receivers. */ public void destroy() { - Utilities.unregisterReceiverSafely(mContext, mUserUnlockedReceiver); - mSysUiNavMode.removeModeChangeListener(this); - mDefaultDisplay.removeChangeListener(this); - mExclusionListener.unregister(); + for (Runnable r : mOnDestroyActions) { + r.run(); + } + } + + /** + * Adds a listener for the nav mode change, guaranteed to be called after the device state's + * mode has changed. + */ + public void addNavigationModeChangedCallback(NavigationModeChangeListener listener) { + listener.onNavigationModeChanged(mSysUiNavMode.addModeChangeListener(listener)); + runOnDestroy(() -> mSysUiNavMode.removeModeChangeListener(listener)); } @Override public void onNavigationModeChanged(SysUINavigationMode.Mode newMode) { + if (TestProtocol.sDebugTracing) { + Log.d(TestProtocol.NO_BACKGROUND_TO_OVERVIEW_TAG, "onNavigationModeChanged " + newMode); + } mDefaultDisplay.removeChangeListener(this); if (newMode.hasGestures) { mDefaultDisplay.addChangeListener(this); @@ -152,6 +191,7 @@ public class RecentsAnimationDeviceState implements mExclusionListener.unregister(); } mMode = newMode; + mNavBarPosition = new NavBarPosition(mMode, mDefaultDisplay.getInfo()); } @Override @@ -160,9 +200,45 @@ public class RecentsAnimationDeviceState implements return; } + mNavBarPosition = new NavBarPosition(mMode, info); updateGestureTouchRegions(); } + /** + * @return the current navigation mode for the device. + */ + public SysUINavigationMode.Mode getNavMode() { + return mMode; + } + + /** + * @return the nav bar position for the current nav bar mode and display rotation. + */ + public NavBarPosition getNavBarPosition() { + return mNavBarPosition; + } + + /** + * @return whether the current nav mode is fully gestural. + */ + public boolean isFullyGesturalNavMode() { + return mMode == NO_BUTTON; + } + + /** + * @return whether the current nav mode has some gestures (either 2 or 0 button mode). + */ + public boolean isGesturalNavMode() { + return mMode == TWO_BUTTONS || mMode == NO_BUTTON; + } + + /** + * @return whether the current nav mode is button-based. + */ + public boolean isButtonNavMode() { + return mMode == THREE_BUTTONS; + } + /** * @return the display id for the display that Launcher is running on. */ @@ -201,7 +277,7 @@ public class RecentsAnimationDeviceState implements * @return whether the given running task info matches the gesture-blocked activity. */ public boolean isGestureBlockedActivity(ActivityManager.RunningTaskInfo runningTaskInfo) { - return runningTaskInfo != null + return runningTaskInfo != null && mGestureBlockedActivity != null && mGestureBlockedActivity.equals(runningTaskInfo.topActivity); } diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java b/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java index 93537597d5..718c5baa2c 100644 --- a/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java +++ b/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java @@ -41,13 +41,4 @@ public class RecentsAnimationTargets extends RemoteAnimationTargets { public boolean hasTargets() { return unfilteredApps.length != 0; } - - /** - * Clones the target set without any actual targets. Used only when continuing a gesture after - * the actual recents animation has finished. - */ - public RecentsAnimationTargets cloneWithoutTargets() { - return new RecentsAnimationTargets(new RemoteAnimationTargetCompat[0], - new RemoteAnimationTargetCompat[0], homeContentInsets, minimizedHomeBounds); - } } diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java index 465d4648e6..517501ad9c 100644 --- a/quickstep/src/com/android/quickstep/RecentsModel.java +++ b/quickstep/src/com/android/quickstep/RecentsModel.java @@ -25,12 +25,12 @@ import android.annotation.TargetApi; import android.app.ActivityManager; import android.content.ComponentCallbacks2; import android.content.Context; -import android.content.pm.LauncherApps; import android.os.Build; import android.os.Looper; import android.os.Process; import android.os.UserHandle; +import com.android.launcher3.icons.IconProvider; import com.android.launcher3.util.MainThreadInitializedObject; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.model.ThumbnailData; @@ -48,13 +48,11 @@ import java.util.function.Consumer; @TargetApi(Build.VERSION_CODES.O) public class RecentsModel extends TaskStackChangeListener { - private static final String TAG = "RecentsModel"; - // We do not need any synchronization for this variable as its only written on UI thread. public static final MainThreadInitializedObject INSTANCE = new MainThreadInitializedObject<>(RecentsModel::new); - private final List mThumbnailChangeListeners = new ArrayList<>(); + private final List mThumbnailChangeListeners = new ArrayList<>(); private final Context mContext; private final RecentTasksList mTaskList; @@ -69,8 +67,10 @@ public class RecentsModel extends TaskStackChangeListener { new KeyguardManagerCompat(context), ActivityManagerWrapper.getInstance()); mIconCache = new TaskIconCache(context, looper); mThumbnailCache = new TaskThumbnailCache(context, looper); + ActivityManagerWrapper.getInstance().registerTaskStackListener(this); - setupPackageListener(); + IconProvider.registerIconChangeListener(context, + this::onPackageIconChanged, MAIN_EXECUTOR.getHandler()); } public TaskIconCache getIconCache() { @@ -183,45 +183,40 @@ public class RecentsModel extends TaskStackChangeListener { } } - public void onOverviewShown(boolean fromHome, String tag) { - SystemUiProxy.INSTANCE.get(mContext).onOverviewShown(fromHome, tag); + private void onPackageIconChanged(String pkg, UserHandle user) { + mIconCache.invalidateCacheEntries(pkg, user); + for (int i = mThumbnailChangeListeners.size() - 1; i >= 0; i--) { + mThumbnailChangeListeners.get(i).onTaskIconChanged(pkg, user); + } } - private void setupPackageListener() { - mContext.getSystemService(LauncherApps.class).registerCallback(new LauncherApps.Callback() { - @Override - public void onPackageRemoved(String packageName, UserHandle user) { - mIconCache.invalidatePackage(packageName); - } - - @Override - public void onPackageChanged(String packageName, UserHandle user) { - mIconCache.invalidatePackage(packageName); - } - - @Override - public void onPackageAdded(String packageName, UserHandle user) { } - - @Override - public void onPackagesAvailable( - String[] packageNames, UserHandle user, boolean replacing) { } - - @Override - public void onPackagesUnavailable( - String[] packageNames, UserHandle user, boolean replacing) { } - }); - } - - public void addThumbnailChangeListener(TaskThumbnailChangeListener listener) { + /** + * Adds a listener for visuals changes + */ + public void addThumbnailChangeListener(TaskVisualsChangeListener listener) { mThumbnailChangeListeners.add(listener); } - public void removeThumbnailChangeListener(TaskThumbnailChangeListener listener) { + /** + * Removes a previously added listener + */ + public void removeThumbnailChangeListener(TaskVisualsChangeListener listener) { mThumbnailChangeListeners.remove(listener); } - public interface TaskThumbnailChangeListener { + /** + * Listener for receiving various task properties changes + */ + public interface TaskVisualsChangeListener { + /** + * Called whn the task thumbnail changes + */ Task onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData); + + /** + * Called when the icon for a task changes + */ + void onTaskIconChanged(String pkg, UserHandle user); } } diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java new file mode 100644 index 0000000000..e3e8ace96b --- /dev/null +++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java @@ -0,0 +1,173 @@ +/* + * 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 com.android.launcher3.util.Executors.MAIN_EXECUTOR; +import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; +import static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_INITIALIZED; + +import android.content.Intent; +import android.util.Log; + +import androidx.annotation.UiThread; + +import com.android.launcher3.Utilities; +import com.android.launcher3.config.FeatureFlags; +import com.android.systemui.shared.recents.model.ThumbnailData; +import com.android.systemui.shared.system.ActivityManagerWrapper; + +public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAnimationListener { + + private RecentsAnimationController mController; + private RecentsAnimationCallbacks mCallbacks; + private RecentsAnimationTargets mTargets; + // Temporary until we can hook into gesture state events + private GestureState mLastGestureState; + + /** + * Preloads the recents animation. + */ + public void preloadRecentsAnimation(Intent intent) { + // Pass null animation handler to indicate this start is for preloading + UI_HELPER_EXECUTOR.execute(() -> ActivityManagerWrapper.getInstance() + .startRecentsActivity(intent, null, null, null, null)); + } + + /** + * Starts a new recents animation for the activity with the given {@param intent}. + */ + @UiThread + public RecentsAnimationCallbacks startRecentsAnimation(GestureState gestureState, + Intent intent, RecentsAnimationCallbacks.RecentsAnimationListener listener) { + // Notify if recents animation is still running + if (mController != null) { + String msg = "New recents animation started before old animation completed"; + if (FeatureFlags.IS_DOGFOOD_BUILD) { + throw new IllegalArgumentException(msg); + } else { + Log.e("TaskAnimationManager", msg, new Exception()); + } + } + // But force-finish it anyways + finishRunningRecentsAnimation(false /* toHome */); + + final BaseActivityInterface activityInterface = gestureState.getActivityInterface(); + mLastGestureState = gestureState; + mCallbacks = new RecentsAnimationCallbacks(activityInterface.shouldMinimizeSplitScreen()); + mCallbacks.addListener(new RecentsAnimationCallbacks.RecentsAnimationListener() { + @Override + public void onRecentsAnimationStart(RecentsAnimationController controller, + RecentsAnimationTargets targets) { + mController = controller; + mTargets = targets; + } + + @Override + public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) { + if (thumbnailData != null) { + // If a screenshot is provided, switch to the screenshot before cleaning up + activityInterface.switchRunningTaskViewToScreenshot(thumbnailData, + () -> cleanUpRecentsAnimation(thumbnailData)); + } else { + cleanUpRecentsAnimation(null /* canceledThumbnail */); + } + } + + @Override + public void onRecentsAnimationFinished(RecentsAnimationController controller) { + cleanUpRecentsAnimation(null /* canceledThumbnail */); + } + }); + mCallbacks.addListener(gestureState); + mCallbacks.addListener(listener); + UI_HELPER_EXECUTOR.execute(() -> ActivityManagerWrapper.getInstance() + .startRecentsActivity(intent, null, mCallbacks, null, null)); + gestureState.setState(STATE_RECENTS_ANIMATION_INITIALIZED); + return mCallbacks; + } + + /** + * Continues the existing running recents animation for a new gesture. + */ + public RecentsAnimationCallbacks continueRecentsAnimation(GestureState gestureState) { + mCallbacks.removeListener(mLastGestureState); + mLastGestureState = gestureState; + mCallbacks.addListener(gestureState); + return mCallbacks; + } + + /** + * Finishes the running recents animation. + */ + public void finishRunningRecentsAnimation(boolean toHome) { + if (mController != null) { + mCallbacks.notifyAnimationCanceled(); + Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), toHome + ? mController::finishAnimationToHome + : mController::finishAnimationToApp); + cleanUpRecentsAnimation(null /* canceledThumbnail */); + } + } + + /** + * Used to notify a listener of the current recents animation state (used if the listener was + * not yet added to the callbacks at the point that the listener callbacks would have been + * made). + */ + public void notifyRecentsAnimationState( + RecentsAnimationCallbacks.RecentsAnimationListener listener) { + if (isRecentsAnimationRunning()) { + listener.onRecentsAnimationStart(mController, mTargets); + } + // TODO: Do we actually need to report canceled/finished? + } + + /** + * @return whether there is a recents animation running. + */ + public boolean isRecentsAnimationRunning() { + return mController != null; + } + + /** + * Cleans up the recents animation entirely. + */ + private void cleanUpRecentsAnimation(ThumbnailData canceledThumbnail) { + // Clean up the screenshot if necessary + if (mController != null && canceledThumbnail != null) { + mController.cleanupScreenshot(); + } + + // Release all the target leashes + if (mTargets != null) { + mTargets.release(); + } + + // Remove gesture state from callbacks + if (mCallbacks != null && mLastGestureState != null) { + mCallbacks.removeListener(mLastGestureState); + } + + mController = null; + mCallbacks = null; + mTargets = null; + mLastGestureState = null; + } + + public void dump() { + // TODO + } +} diff --git a/quickstep/src/com/android/quickstep/TaskIconCache.java b/quickstep/src/com/android/quickstep/TaskIconCache.java index 289a129706..e590aea4f6 100644 --- a/quickstep/src/com/android/quickstep/TaskIconCache.java +++ b/quickstep/src/com/android/quickstep/TaskIconCache.java @@ -15,67 +15,64 @@ */ package com.android.quickstep; -import static com.android.launcher3.uioverrides.RecentsUiFactory.GO_LOW_RAM_RECENTS_ENABLED; +import static com.android.launcher3.FastBitmapDrawable.newIcon; +import static com.android.launcher3.uioverrides.QuickstepLauncher.GO_LOW_RAM_RECENTS_ENABLED; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; -import android.content.ComponentName; +import android.app.ActivityManager.TaskDescription; import android.content.Context; import android.content.pm.ActivityInfo; import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; +import android.os.Build; import android.os.Handler; import android.os.Looper; -import android.util.LruCache; +import android.os.UserHandle; +import android.util.SparseArray; import android.view.accessibility.AccessibilityManager; +import androidx.annotation.WorkerThread; + +import com.android.launcher3.FastBitmapDrawable; import com.android.launcher3.R; import com.android.launcher3.Utilities; +import com.android.launcher3.icons.BitmapInfo; +import com.android.launcher3.icons.IconProvider; +import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.icons.cache.HandlerRunnable; import com.android.launcher3.util.Preconditions; +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.TaskKeyLruCache; import com.android.systemui.shared.system.ActivityManagerWrapper; +import com.android.systemui.shared.system.PackageManagerWrapper; -import java.util.Map; import java.util.function.Consumer; /** * Manages the caching of task icons and related data. - * TODO(b/138944598): This class should later be merged into IconCache. */ public class TaskIconCache { private final Handler mBackgroundHandler; private final AccessibilityManager mAccessibilityManager; - private final NormalizedIconLoader mIconLoader; - - private final TaskKeyLruCache mIconCache; - private final TaskKeyLruCache mContentDescriptionCache; - private final LruCache mActivityInfoCache; - - private TaskKeyLruCache.EvictionCallback mClearActivityInfoOnEviction = - new TaskKeyLruCache.EvictionCallback() { - @Override - public void onEntryEvicted(Task.TaskKey key) { - if (key != null) { - mActivityInfoCache.remove(key.getComponent()); - } - } - }; + private final Context mContext; + private final TaskKeyLruCache mIconCache; + private final SparseArray mDefaultIcons = new SparseArray<>(); + private final IconProvider mIconProvider; public TaskIconCache(Context context, Looper backgroundLooper) { + mContext = context; mBackgroundHandler = new Handler(backgroundLooper); mAccessibilityManager = context.getSystemService(AccessibilityManager.class); Resources res = context.getResources(); int cacheSize = res.getInteger(R.integer.recentsIconCacheSize); - mIconCache = new TaskKeyLruCache<>(cacheSize, mClearActivityInfoOnEviction); - mContentDescriptionCache = new TaskKeyLruCache<>(cacheSize, mClearActivityInfoOnEviction); - mActivityInfoCache = new LruCache<>(cacheSize); - mIconLoader = new NormalizedIconLoader(context, mIconCache, mActivityInfoCache, - true /* disableColorExtraction */); + mIconCache = new TaskKeyLruCache<>(cacheSize); + mIconProvider = new IconProvider(context); } /** @@ -96,15 +93,14 @@ public class TaskIconCache { IconLoadRequest request = new IconLoadRequest(mBackgroundHandler) { @Override public void run() { - Drawable icon = mIconLoader.getIcon(task); - String contentDescription = loadContentDescriptionInBackground(task); + TaskCacheEntry entry = getCacheEntry(task); if (isCanceled()) { // We don't call back to the provided callback in this case return; } MAIN_EXECUTOR.execute(() -> { - task.icon = icon; - task.titleDescription = contentDescription; + task.icon = entry.icon; + task.titleDescription = entry.contentDescription; callback.accept(task); onEnd(); }); @@ -116,51 +112,99 @@ public class TaskIconCache { public void clear() { mIconCache.evictAll(); - mContentDescriptionCache.evictAll(); } - /** - * Loads the content description for the given {@param task}. - */ - private String loadContentDescriptionInBackground(Task task) { - // Return the cached content description if it exists - String label = mContentDescriptionCache.getAndInvalidateIfModified(task.key); - if (label != null) { - return label; - } - - // Skip loading content descriptions if accessibility is disabled unless low RAM recents - // is enabled. - if (!GO_LOW_RAM_RECENTS_ENABLED && !mAccessibilityManager.isEnabled()) { - return ""; - } - - // Skip loading the content description if the activity no longer exists - ActivityInfo activityInfo = mIconLoader.getAndUpdateActivityInfo(task.key); - if (activityInfo == null) { - return ""; - } - - // Load the label otherwise - label = ActivityManagerWrapper.getInstance().getBadgedContentDescription(activityInfo, - task.key.userId, task.taskDescription); - mContentDescriptionCache.put(task.key, label); - return label; - } - - void onTaskRemoved(TaskKey taskKey) { mIconCache.remove(taskKey); } - void invalidatePackage(String packageName) { - // TODO(b/138944598): Merge this class into IconCache so we can do this at the base level - Map activityInfoCache = mActivityInfoCache.snapshot(); - for (ComponentName cn : activityInfoCache.keySet()) { - if (cn.getPackageName().equals(packageName)) { - mActivityInfoCache.remove(cn); + void invalidateCacheEntries(String pkg, UserHandle handle) { + Utilities.postAsyncCallback(mBackgroundHandler, + () -> 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 = desc.getIcon(); + if (icon != null) { + entry.icon = new FastBitmapDrawable(getBitmapInfo( + new BitmapDrawable(mContext.getResources(), icon), + key.userId, + desc.getPrimaryColor(), + false /* isInstantApp */)); + } else { + activityInfo = PackageManagerWrapper.getInstance().getActivityInfo( + key.getComponent(), key.userId); + if (activityInfo != null) { + BitmapInfo bitmapInfo = getBitmapInfo( + mIconProvider.getIcon(activityInfo, UserHandle.of(key.userId)), + key.userId, + desc.getPrimaryColor(), + activityInfo.applicationInfo.isInstantApp()); + entry.icon = newIcon(mContext, bitmapInfo); + } else { + entry.icon = getDefaultIcon(key.userId); } } + + // Loading content descriptions if accessibility or low RAM recents is enabled. + if (GO_LOW_RAM_RECENTS_ENABLED || mAccessibilityManager.isEnabled()) { + // 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 = ActivityManagerWrapper.getInstance() + .getBadgedContentDescription(activityInfo, task.key.userId, + task.taskDescription); + } + } + + mIconCache.put(task.key, entry); + return entry; + } + + @WorkerThread + private Drawable getDefaultIcon(int userId) { + synchronized (mDefaultIcons) { + BitmapInfo info = mDefaultIcons.get(userId); + if (info == null) { + try (LauncherIcons la = LauncherIcons.obtain(mContext)) { + info = la.makeDefaultIcon(UserHandle.of(userId)); + } + mDefaultIcons.put(userId, info); + } + return new FastBitmapDrawable(info); + } + } + + @WorkerThread + private BitmapInfo getBitmapInfo(Drawable drawable, int userId, + int primaryColor, boolean isInstantApp) { + try (LauncherIcons la = LauncherIcons.obtain(mContext)) { + la.disableColorExtraction(); + la.setWrapperBackgroundColor(primaryColor); + + // User version code O, so that the icon is always wrapped in an adaptive icon container + return la.createBadgedIconBitmap(drawable, UserHandle.of(userId), + Build.VERSION_CODES.O, isInstantApp); + } } public static abstract class IconLoadRequest extends HandlerRunnable { @@ -168,4 +212,9 @@ public class TaskIconCache { super(handler, null); } } + + private static class TaskCacheEntry { + public Drawable icon; + public String contentDescription = ""; + } } diff --git a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java index 3b50c2623b..e47df6c673 100644 --- a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java +++ b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java @@ -27,9 +27,9 @@ import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.icons.cache.HandlerRunnable; import com.android.launcher3.util.Preconditions; +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.TaskKeyLruCache; import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.system.ActivityManagerWrapper; @@ -41,7 +41,7 @@ public class TaskThumbnailCache { private final Handler mBackgroundHandler; private final int mCacheSize; - private final ThumbnailCache mCache; + private final TaskKeyLruCache mCache; private final HighResLoadingState mHighResLoadingState; public static class HighResLoadingState { @@ -100,7 +100,7 @@ public class TaskThumbnailCache { Resources res = context.getResources(); mCacheSize = res.getInteger(R.integer.recentsThumbnailCacheSize); - mCache = new ThumbnailCache(mCacheSize); + mCache = new TaskKeyLruCache<>(mCacheSize); } /** @@ -223,21 +223,4 @@ public class TaskThumbnailCache { this.reducedResolution = reducedResolution; } } - - private static class ThumbnailCache extends TaskKeyLruCache { - - public ThumbnailCache(int cacheSize) { - super(cacheSize); - } - - /** - * Updates the cache entry if it is already present in the cache - */ - public void updateIfAlreadyInCache(int taskId, ThumbnailData thumbnailData) { - ThumbnailData oldData = getCacheEntry(taskId); - if (oldData != null) { - putCacheEntry(taskId, thumbnailData); - } - } - } } diff --git a/quickstep/src/com/android/quickstep/util/ActivityInitListener.java b/quickstep/src/com/android/quickstep/util/ActivityInitListener.java index fe37d609b4..b1c72ce729 100644 --- a/quickstep/src/com/android/quickstep/util/ActivityInitListener.java +++ b/quickstep/src/com/android/quickstep/util/ActivityInitListener.java @@ -31,6 +31,11 @@ public class ActivityInitListener implements SchedulerCa private final BiPredicate mOnInitListener; private final ActivityTracker mActivityTracker; + /** + * @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 onInitListener, ActivityTracker tracker) { mOnInitListener = onInitListener; @@ -42,6 +47,10 @@ public class ActivityInitListener implements SchedulerCa 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() { mActivityTracker.schedule(this); } diff --git a/quickstep/src/com/android/quickstep/util/LayoutUtils.java b/quickstep/src/com/android/quickstep/util/LayoutUtils.java index 050bdff09b..2e118b44df 100644 --- a/quickstep/src/com/android/quickstep/util/LayoutUtils.java +++ b/quickstep/src/com/android/quickstep/util/LayoutUtils.java @@ -26,7 +26,7 @@ import androidx.annotation.IntDef; import com.android.launcher3.DeviceProfile; import com.android.launcher3.R; -import com.android.launcher3.config.FeatureFlags; +import com.android.quickstep.SysUINavigationMode; import java.lang.annotation.Retention; @@ -39,12 +39,27 @@ public class LayoutUtils { @IntDef({MULTI_WINDOW_STRATEGY_HALF_SCREEN, MULTI_WINDOW_STRATEGY_DEVICE_PROFILE}) private @interface MultiWindowStrategy {} + /** + * The height for the swipe up motion + */ + public static float getDefaultSwipeHeight(Context context, DeviceProfile dp) { + float swipeHeight = dp.allAppsCellHeightPx - dp.allAppsIconTextSizePx; + if (SysUINavigationMode.getMode(context) == SysUINavigationMode.Mode.NO_BUTTON) { + swipeHeight -= dp.getInsets().bottom; + } + return swipeHeight; + } + public static void calculateLauncherTaskSize(Context context, DeviceProfile dp, Rect outRect) { float extraSpace; if (dp.isVerticalBarLayout()) { extraSpace = 0; } else { - extraSpace = dp.hotseatBarSizePx + dp.verticalDragHandleSizePx; + Resources res = context.getResources(); + + extraSpace = getDefaultSwipeHeight(context, dp) + dp.verticalDragHandleSizePx + + res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_extra_vertical_size) + + res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_bottom_padding); } calculateTaskSize(context, dp, extraSpace, MULTI_WINDOW_STRATEGY_HALF_SCREEN, outRect); } diff --git a/quickstep/src/com/android/quickstep/util/NavBarPosition.java b/quickstep/src/com/android/quickstep/util/NavBarPosition.java new file mode 100644 index 0000000000..a4614dec7f --- /dev/null +++ b/quickstep/src/com/android/quickstep/util/NavBarPosition.java @@ -0,0 +1,127 @@ +/* + * 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 static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON; + +import android.content.Context; +import android.graphics.Rect; +import android.view.Gravity; +import android.view.Surface; + +import com.android.launcher3.graphics.RotationMode; +import com.android.launcher3.util.DefaultDisplay; +import com.android.quickstep.SysUINavigationMode; + +/** + * Utility class to check nav bar position. + */ +public class NavBarPosition { + + public static RotationMode ROTATION_LANDSCAPE = new RotationMode(-90) { + @Override + public void mapRect(int left, int top, int right, int bottom, Rect out) { + out.left = top; + out.top = right; + out.right = bottom; + out.bottom = left; + } + + @Override + public void mapInsets(Context context, Rect insets, Rect out) { + // If there is a display cutout, the top insets in portrait would also include the + // cutout, which we will get as the left inset in landscape. Using the max of left and + // top allows us to cover both cases (with or without cutout). + if (SysUINavigationMode.getMode(context) == NO_BUTTON) { + out.top = Math.max(insets.top, insets.left); + out.bottom = Math.max(insets.right, insets.bottom); + out.left = out.right = 0; + } else { + out.top = Math.max(insets.top, insets.left); + out.bottom = insets.right; + out.left = insets.bottom; + out.right = 0; + } + } + }; + + public static RotationMode ROTATION_SEASCAPE = new RotationMode(90) { + @Override + public void mapRect(int left, int top, int right, int bottom, Rect out) { + out.left = bottom; + out.top = left; + out.right = top; + out.bottom = right; + } + + @Override + public void mapInsets(Context context, Rect insets, Rect out) { + if (SysUINavigationMode.getMode(context) == NO_BUTTON) { + out.top = Math.max(insets.top, insets.right); + out.bottom = Math.max(insets.left, insets.bottom); + out.left = out.right = 0; + } else { + out.top = Math.max(insets.top, insets.right); + out.bottom = insets.left; + out.right = insets.bottom; + out.left = 0; + } + } + + @Override + public int toNaturalGravity(int absoluteGravity) { + int horizontalGravity = absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK; + int verticalGravity = absoluteGravity & Gravity.VERTICAL_GRAVITY_MASK; + + if (horizontalGravity == Gravity.RIGHT) { + horizontalGravity = Gravity.LEFT; + } else if (horizontalGravity == Gravity.LEFT) { + horizontalGravity = Gravity.RIGHT; + } + + if (verticalGravity == Gravity.TOP) { + verticalGravity = Gravity.BOTTOM; + } else if (verticalGravity == Gravity.BOTTOM) { + verticalGravity = Gravity.TOP; + } + + return ((absoluteGravity & ~Gravity.HORIZONTAL_GRAVITY_MASK) + & ~Gravity.VERTICAL_GRAVITY_MASK) + | horizontalGravity | verticalGravity; + } + }; + + private final SysUINavigationMode.Mode mMode; + private final int mDisplayRotation; + + public NavBarPosition(SysUINavigationMode.Mode mode, DefaultDisplay.Info info) { + mMode = mode; + mDisplayRotation = info.rotation; + } + + public boolean isRightEdge() { + return mMode != NO_BUTTON && mDisplayRotation == Surface.ROTATION_90; + } + + public boolean isLeftEdge() { + return mMode != NO_BUTTON && mDisplayRotation == Surface.ROTATION_270; + } + + public RotationMode getRotationMode() { + return isLeftEdge() ? ROTATION_SEASCAPE + : (isRightEdge() ? ROTATION_LANDSCAPE : RotationMode.NORMAL); + } +} diff --git a/quickstep/src/com/android/quickstep/util/TaskKeyLruCache.java b/quickstep/src/com/android/quickstep/util/TaskKeyLruCache.java new file mode 100644 index 0000000000..d87feec069 --- /dev/null +++ b/quickstep/src/com/android/quickstep/util/TaskKeyLruCache.java @@ -0,0 +1,124 @@ +/* + * 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 android.util.Log; + +import com.android.systemui.shared.recents.model.Task.TaskKey; + +import java.util.LinkedHashMap; +import java.util.function.Predicate; + +/** + * A simple LRU cache for task key entries + * @param The type of the value + */ +public class TaskKeyLruCache { + + private final MyLinkedHashMap mMap; + + public TaskKeyLruCache(int maxSize) { + mMap = new MyLinkedHashMap<>(maxSize); + } + + /** + * Removes all entries from the cache + */ + public synchronized void evictAll() { + mMap.clear(); + } + + /** + * Removes a particular entry from the cache + */ + public synchronized void remove(TaskKey key) { + mMap.remove(key.id); + } + + /** + * Removes all entries matching keyCheck + */ + public synchronized void removeAll(Predicate keyCheck) { + mMap.entrySet().removeIf(e -> keyCheck.test(e.getValue().mKey)); + } + + /** + * Gets the entry if it is still valid + */ + public synchronized V getAndInvalidateIfModified(TaskKey key) { + Entry entry = mMap.get(key.id); + + if (entry != null && entry.mKey.windowingMode == key.windowingMode + && entry.mKey.lastActiveTime == key.lastActiveTime) { + return entry.mValue; + } else { + remove(key); + return null; + } + } + + /** + * Adds an entry to the cache, optionally evicting the last accessed entry + */ + public final synchronized void put(TaskKey key, V value) { + if (key != null && value != null) { + mMap.put(key.id, new Entry<>(key, value)); + } else { + Log.e("TaskKeyCache", "Unexpected null key or value: " + key + ", " + value); + } + } + + /** + * Updates the cache entry if it is already present in the cache + */ + public synchronized void updateIfAlreadyInCache(int taskId, V data) { + Entry entry = mMap.get(taskId); + if (entry != null) { + entry.mValue = data; + } + } + + private static class Entry { + + final TaskKey mKey; + V mValue; + + Entry(TaskKey key, V value) { + mKey = key; + mValue = value; + } + + @Override + public int hashCode() { + return mKey.id; + } + } + + private static class MyLinkedHashMap extends LinkedHashMap> { + + private final int mMaxSize; + + MyLinkedHashMap(int maxSize) { + super(0, 0.75f, true /* accessOrder */); + mMaxSize = maxSize; + } + + @Override + protected boolean removeEldestEntry(Entry> eldest) { + return size() > mMaxSize; + } + } +} diff --git a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java index 26e9eaf01e..0e591cac7a 100644 --- a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java +++ b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java @@ -38,12 +38,12 @@ import com.android.launcher3.DeviceProfile; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.anim.Interpolators; -import com.android.launcher3.uioverrides.states.OverviewState; import com.android.launcher3.util.Themes; import com.android.launcher3.views.ScrimView; import com.android.quickstep.SysUINavigationMode; import com.android.quickstep.SysUINavigationMode.Mode; import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener; +import com.android.quickstep.util.LayoutUtils; /** * Scrim used for all-apps and shelf in Overview @@ -163,7 +163,7 @@ public class ShelfScrimView extends ScrimView implements NavigationModeChangeLis int hotseatSize = dp.hotseatBarSizePx + dp.getInsets().bottom + hotseatPadding.bottom + hotseatPadding.top; float dragHandleTop = - Math.min(hotseatSize, OverviewState.getDefaultSwipeHeight(context, dp)); + Math.min(hotseatSize, LayoutUtils.getDefaultSwipeHeight(context, dp)); mDragHandleProgress = 1 - (dragHandleTop / mShiftRange); } mTopOffset = dp.getInsets().top - mShelfOffset; diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java index aa5fce19d9..ca813434a4 100644 --- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java +++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java @@ -31,6 +31,9 @@ import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.rule.ShellCommandRule.disableHeadsUpNotification; import static com.android.launcher3.util.rule.ShellCommandRule.getLauncherCommand; +import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_PRESUBMIT; +import static com.android.launcher3.util.rule.TestStabilityRule.RUN_FLAFOR; +import static com.android.launcher3.util.rule.TestStabilityRule.UNBUNDLED_PRESUBMIT; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -99,7 +102,7 @@ public class FallbackRecentsTest { } mOrderSensitiveRules = RuleChain.outerRule(new NavigationModeSwitchRule(mLauncher)) - .around(new FailureWatcher(mDevice)); + .around(new FailureWatcher(mDevice)); mOtherLauncherActivity = context.getPackageManager().queryIntentActivities( getHomeIntentInPackage(context), @@ -130,6 +133,11 @@ public class FallbackRecentsTest { @NavigationModeSwitch @Test public void goToOverviewFromHome() { + // b/142828227 + if (android.os.Build.MODEL.contains("Cuttlefish") && TestHelpers.isInLauncherProcess() && + (RUN_FLAFOR & (PLATFORM_PRESUBMIT | UNBUNDLED_PRESUBMIT)) != 0) { + return; + } mDevice.pressHome(); assertTrue("Fallback Launcher not visible", mDevice.wait(Until.hasObject(By.pkg( mOtherLauncherActivity.packageName)), WAIT_TIME_MS)); @@ -140,6 +148,11 @@ public class FallbackRecentsTest { @NavigationModeSwitch @Test public void goToOverviewFromApp() { + // b/142828227 + if (android.os.Build.MODEL.contains("Cuttlefish") && TestHelpers.isInLauncherProcess() && + (RUN_FLAFOR & (PLATFORM_PRESUBMIT | UNBUNDLED_PRESUBMIT)) != 0) { + return; + } startAppFastAndWaitForRecentTask(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR)); mLauncher.getBackground().switchToOverview(); @@ -162,7 +175,7 @@ public class FallbackRecentsTest { } result[0] = f.apply(activity); return true; - }).get(), DEFAULT_UI_TIMEOUT); + }).get(), DEFAULT_UI_TIMEOUT, mLauncher); return (T) result[0]; } @@ -174,11 +187,16 @@ public class FallbackRecentsTest { @NavigationModeSwitch @Test public void testOverview() { + // b/142828227 + if (android.os.Build.MODEL.contains("Cuttlefish") && TestHelpers.isInLauncherProcess() && + (RUN_FLAFOR & (PLATFORM_PRESUBMIT | UNBUNDLED_PRESUBMIT)) != 0) { + return; + } startAppFastAndWaitForRecentTask(getAppPackageName()); startAppFastAndWaitForRecentTask(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR)); startTestActivity(2); Wait.atMost("Expected three apps in the task list", - () -> mLauncher.getRecentTasks().size() >= 3, DEFAULT_ACTIVITY_TIMEOUT); + () -> mLauncher.getRecentTasks().size() >= 3, DEFAULT_ACTIVITY_TIMEOUT, mLauncher); BaseOverview overview = mLauncher.getBackground().switchToOverview(); executeOnRecents(recents -> @@ -237,7 +255,8 @@ public class FallbackRecentsTest { private void startAppFastAndWaitForRecentTask(String packageName) { startAppFast(packageName); Wait.atMost("Expected app in task list", - () -> containsRecentTaskWithPackage(packageName), DEFAULT_ACTIVITY_TIMEOUT); + () -> containsRecentTaskWithPackage(packageName), DEFAULT_ACTIVITY_TIMEOUT, + mLauncher); } private boolean containsRecentTaskWithPackage(String packageName) { diff --git a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java index c2197ab709..fa4c7b9fa1 100644 --- a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java +++ b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java @@ -30,6 +30,7 @@ import android.content.Context; import android.content.pm.PackageManager; import android.util.Log; +import androidx.test.uiautomator.By; import androidx.test.uiautomator.UiDevice; import com.android.launcher3.tapl.LauncherInstrumentation; @@ -80,7 +81,13 @@ public class NavigationModeSwitchRule implements TestRule { Mode mode = description.getAnnotation(NavigationModeSwitch.class).mode(); return new Statement() { private void assertTrue(String message, boolean condition) { - if(!condition) { + if (mLauncher.getDevice().hasObject(By.textStartsWith(""))) { + // The condition above is "screen is not empty". We are not treating + // "Screen is empty" as an anomaly here. It's an acceptable state when + // Launcher just starts under instrumentation. + mLauncher.checkForAnomaly(); + } + if (!condition) { final AssertionError assertionError = new AssertionError(message); FailureWatcher.onError(mLauncher.getDevice(), description, assertionError); throw assertionError; diff --git a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java index f5b9b7e880..d60fa1e3c3 100644 --- a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java +++ b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java @@ -18,6 +18,9 @@ package com.android.quickstep; import static com.android.launcher3.util.RaceConditionReproducer.enterEvt; import static com.android.launcher3.util.RaceConditionReproducer.exitEvt; +import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_PRESUBMIT; +import static com.android.launcher3.util.rule.TestStabilityRule.RUN_FLAFOR; +import static com.android.launcher3.util.rule.TestStabilityRule.UNBUNDLED_PRESUBMIT; import android.content.Intent; @@ -25,6 +28,7 @@ import androidx.test.filters.LargeTest; import androidx.test.runner.AndroidJUnit4; import com.android.launcher3.Launcher; +import com.android.launcher3.tapl.TestHelpers; import com.android.launcher3.util.RaceConditionReproducer; import com.android.quickstep.NavigationModeSwitchRule.Mode; import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch; diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java index d270d762d9..428e6475bd 100644 --- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java +++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java @@ -248,33 +248,33 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest { @Test @NavigationModeSwitch @PortraitLandscape - @Ignore("Temporarily disabled b/140252765") + @Ignore // b/143285809 public void testQuickSwitchFromApp() throws Exception { - startAppFast(getAppPackageName()); startTestActivity(2); - String calculatorPackage = resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR); - startAppFast(calculatorPackage); + startTestActivity(3); + startTestActivity(4); Background background = getAndAssertBackground(); background.quickSwitchToPreviousApp(); assertTrue("The first app we should have quick switched to is not running", - isTestActivityRunning("TestActivity2")); + isTestActivityRunning(3)); background = getAndAssertBackground(); background.quickSwitchToPreviousApp(); if (mLauncher.getNavigationModel() == NavigationModel.THREE_BUTTON) { // 3-button mode toggles between 2 apps, rather than going back further. assertTrue("Second quick switch should have returned to the first app.", - mDevice.wait(Until.hasObject(By.pkg(calculatorPackage)), DEFAULT_UI_TIMEOUT)); + isTestActivityRunning(4)); } else { assertTrue("The second app we should have quick switched to is not running", - isTestActivityRunning("Test Pin Item")); + isTestActivityRunning(2)); } getAndAssertBackground(); } - private boolean isTestActivityRunning(String activityLabel) { - return mDevice.wait(Until.hasObject(By.pkg(getAppPackageName()).text(activityLabel)), + private boolean isTestActivityRunning(int activityNumber) { + return mDevice.wait(Until.hasObject(By.pkg(getAppPackageName()) + .text("TestActivity" + activityNumber)), DEFAULT_UI_TIMEOUT); } @@ -285,7 +285,7 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest { startTestActivity(2); mLauncher.pressHome().quickSwitchToPreviousApp(); assertTrue("The most recent task is not running after quick switching from home", - isTestActivityRunning("TestActivity2")); + isTestActivityRunning(2)); getAndAssertBackground(); } } diff --git a/res/anim/slide_in_right.xml b/res/anim/slide_in_right.xml deleted file mode 100644 index 55d3e54025..0000000000 --- a/res/anim/slide_in_right.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - \ No newline at end of file diff --git a/res/values/attrs.xml b/res/values/attrs.xml index 7be584e932..de17eb7b4f 100644 --- a/res/values/attrs.xml +++ b/res/values/attrs.xml @@ -115,8 +115,6 @@ - - @@ -132,12 +130,6 @@ - - - - - - diff --git a/res/values/config.xml b/res/values/config.xml index 038718473b..2a1f6f7864 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -1,5 +1,5 @@ - + false false false @@ -21,10 +21,10 @@ com.android.launcher3.settings.SettingsActivity$LauncherSettingsFragment - + - + 90 @@ -34,7 +34,7 @@ - + 900 @@ -57,29 +57,20 @@ 200 - + true - - - - - - - - - @@ -97,7 +88,12 @@ 40 300 - + + + + + + @@ -112,10 +108,10 @@ - + - + diff --git a/res/values/strings.xml b/res/values/strings.xml index 9d9c2e8ded..dec8939faf 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -102,9 +102,11 @@ App info Install - Dismiss prediction + + Pin Prediction + diff --git a/robolectric_tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java b/robolectric_tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java index 32eb2ec5fe..5b6d94d732 100644 --- a/robolectric_tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java +++ b/robolectric_tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java @@ -202,15 +202,14 @@ public class BaseModelUpdateTaskTestCase { CacheEntry entry = mCache.get(new ComponentKey(componentName, user)); if (entry == null) { entry = new CacheEntry(); - getDefaultIcon(user).applyTo(entry); + entry.bitmap = getDefaultIcon(user); } return entry; } public void addCache(ComponentName key, String title) { CacheEntry entry = new CacheEntry(); - entry.icon = newIcon(); - entry.color = Color.RED; + entry.bitmap = BitmapInfo.of(newIcon(), Color.RED); entry.title = title; mCache.put(new ComponentKey(key, Process.myUserHandle()), entry); } diff --git a/robolectric_tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java b/robolectric_tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java index 81b90431a0..69c5b00c27 100644 --- a/robolectric_tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java +++ b/robolectric_tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java @@ -2,13 +2,13 @@ package com.android.launcher3.model; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotSame; -import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import com.android.launcher3.AppInfo; import com.android.launcher3.ItemInfo; import com.android.launcher3.WorkspaceItemInfo; +import com.android.launcher3.icons.BitmapInfo; import org.junit.Before; import org.junit.Test; @@ -43,7 +43,7 @@ public class CacheDataUpdatedTaskTest extends BaseModelUpdateTaskTestCase { public void testCacheUpdate_update_apps() throws Exception { // Clear all icons from apps list so that its easy to check what was updated for (AppInfo info : allAppsList.data) { - info.iconBitmap = null; + info.bitmap = BitmapInfo.LOW_RES_INFO; } executeTaskForTest(newTask(CacheDataUpdatedTask.OP_CACHE_UPDATE, "app1")); @@ -56,9 +56,9 @@ public class CacheDataUpdatedTaskTest extends BaseModelUpdateTaskTestCase { assertFalse(allAppsList.data.isEmpty()); for (AppInfo info : allAppsList.data) { if (info.componentName.getPackageName().equals("app1")) { - assertNotNull(info.iconBitmap); + assertFalse(info.bitmap.isNullOrLowRes()); } else { - assertNull(info.iconBitmap); + assertTrue(info.bitmap.isNullOrLowRes()); } } } @@ -85,10 +85,10 @@ public class CacheDataUpdatedTaskTest extends BaseModelUpdateTaskTestCase { for (ItemInfo info : bgDataModel.itemsIdMap) { if (updates.contains(info.id)) { assertEquals(NEW_LABEL_PREFIX + info.id, info.title); - assertNotNull(((WorkspaceItemInfo) info).iconBitmap); + assertFalse(((WorkspaceItemInfo) info).bitmap.isNullOrLowRes()); } else { assertNotSame(NEW_LABEL_PREFIX + info.id, info.title); - assertNull(((WorkspaceItemInfo) info).iconBitmap); + assertTrue(((WorkspaceItemInfo) info).bitmap.isNullOrLowRes()); } } } diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java index b28077f7cd..382bfdf513 100644 --- a/src/com/android/launcher3/BaseActivity.java +++ b/src/com/android/launcher3/BaseActivity.java @@ -35,7 +35,7 @@ import com.android.launcher3.logging.StatsLogUtils; import com.android.launcher3.logging.StatsLogUtils.LogStateProvider; import com.android.launcher3.logging.UserEventDispatcher; import com.android.launcher3.logging.UserEventDispatcher.UserEventDelegate; -import com.android.launcher3.uioverrides.UiFactory; +import com.android.launcher3.uioverrides.ApiWrapper; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.util.SystemUiController; import com.android.launcher3.util.ViewCache; @@ -81,19 +81,39 @@ public abstract class BaseActivity extends Activity protected StatsLogManager mStatsLogManager; protected SystemUiController mSystemUiController; - private static final int ACTIVITY_STATE_STARTED = 1 << 0; - private static final int ACTIVITY_STATE_RESUMED = 1 << 1; + + public static final int ACTIVITY_STATE_STARTED = 1 << 0; + public static final int ACTIVITY_STATE_RESUMED = 1 << 1; + /** - * State flag indicating if the user is active or the actitvity when to background as a result + * State flags indicating that the activity has received one frame after resume, and was + * not immediately paused. + */ + public static final int ACTIVITY_STATE_DEFERRED_RESUMED = 1 << 2; + + public static final int ACTIVITY_STATE_WINDOW_FOCUSED = 1 << 3; + + /** + * State flag indicating if the user is active or the activity when to background as a result * of user action. * @see #isUserActive() */ - private static final int ACTIVITY_STATE_USER_ACTIVE = 1 << 2; + public static final int ACTIVITY_STATE_USER_ACTIVE = 1 << 4; + + /** + * State flag indicating that a state transition is in progress + */ + public static final int ACTIVITY_STATE_TRANSITION_ACTIVE = 1 << 5; @Retention(SOURCE) @IntDef( flag = true, - value = {ACTIVITY_STATE_STARTED, ACTIVITY_STATE_RESUMED, ACTIVITY_STATE_USER_ACTIVE}) + value = {ACTIVITY_STATE_STARTED, + ACTIVITY_STATE_RESUMED, + ACTIVITY_STATE_DEFERRED_RESUMED, + ACTIVITY_STATE_WINDOW_FOCUSED, + ACTIVITY_STATE_USER_ACTIVE, + ACTIVITY_STATE_TRANSITION_ACTIVE}) public @interface ActivityFlags{} @ActivityFlags @@ -146,19 +166,19 @@ public abstract class BaseActivity extends Activity @Override protected void onStart() { - mActivityFlags |= ACTIVITY_STATE_STARTED; + addActivityFlags(ACTIVITY_STATE_STARTED); super.onStart(); } @Override protected void onResume() { - mActivityFlags |= ACTIVITY_STATE_RESUMED | ACTIVITY_STATE_USER_ACTIVE; + addActivityFlags(ACTIVITY_STATE_RESUMED | ACTIVITY_STATE_USER_ACTIVE); super.onResume(); } @Override protected void onUserLeaveHint() { - mActivityFlags &= ~ACTIVITY_STATE_USER_ACTIVE; + removeActivityFlags(ACTIVITY_STATE_USER_ACTIVE); super.onUserLeaveHint(); } @@ -172,7 +192,7 @@ public abstract class BaseActivity extends Activity @Override protected void onStop() { - mActivityFlags &= ~ACTIVITY_STATE_STARTED & ~ACTIVITY_STATE_USER_ACTIVE; + removeActivityFlags(ACTIVITY_STATE_STARTED | ACTIVITY_STATE_USER_ACTIVE); mForceInvisible = 0; super.onStop(); @@ -183,7 +203,7 @@ public abstract class BaseActivity extends Activity @Override protected void onPause() { - mActivityFlags &= ~ACTIVITY_STATE_RESUMED; + removeActivityFlags(ACTIVITY_STATE_RESUMED | ACTIVITY_STATE_DEFERRED_RESUMED); super.onPause(); // Reset the overridden sysui flags used for the task-swipe launch animation, we do this @@ -193,6 +213,17 @@ public abstract class BaseActivity extends Activity getSystemUiController().updateUiState(UI_STATE_OVERVIEW, 0); } + @Override + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + if (hasFocus) { + addActivityFlags(ACTIVITY_STATE_WINDOW_FOCUSED); + } else { + removeActivityFlags(ACTIVITY_STATE_WINDOW_FOCUSED); + } + + } + public boolean isStarted() { return (mActivityFlags & ACTIVITY_STATE_STARTED) != 0; } @@ -208,6 +239,22 @@ public abstract class BaseActivity extends Activity return (mActivityFlags & ACTIVITY_STATE_USER_ACTIVE) != 0; } + public int getActivityFlags() { + return mActivityFlags; + } + + protected void addActivityFlags(int flags) { + mActivityFlags |= flags; + onActivityFlagsChanged(flags); + } + + protected void removeActivityFlags(int flags) { + mActivityFlags &= ~flags; + onActivityFlagsChanged(flags); + } + + protected void onActivityFlagsChanged(int changeBits) { } + public void addOnDeviceProfileChangeListener(OnDeviceProfileChangeListener listener) { mDPChangeListeners.add(listener); } @@ -233,7 +280,7 @@ public abstract class BaseActivity extends Activity /** * Used to set the override visibility state, used only to handle the transition home with the * recents animation. - * @see QuickstepAppTransitionManagerImpl#getWallpaperOpenRunner() + * @see QuickstepAppTransitionManagerImpl#getWallpaperOpenRunner */ public void addForceInvisibleFlag(@InvisibilityFlags int flag) { mForceInvisible |= flag; @@ -260,7 +307,7 @@ public abstract class BaseActivity extends Activity @Override public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { - if (!UiFactory.dumpActivity(this, writer)) { + if (!ApiWrapper.dumpActivity(this, writer)) { super.dump(prefix, fd, writer, args); } } diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java index d24de8e30c..772eb00243 100644 --- a/src/com/android/launcher3/BaseDraggingActivity.java +++ b/src/com/android/launcher3/BaseDraggingActivity.java @@ -57,7 +57,7 @@ public abstract class BaseDraggingActivity extends BaseActivity private ActionMode mCurrentActionMode; protected boolean mIsSafeModeEnabled; - private OnStartCallback mOnStartCallback; + private Runnable mOnStartCallback; private int mThemeRes = R.style.AppTheme; @@ -226,7 +226,7 @@ public abstract class BaseDraggingActivity extends BaseActivity super.onStart(); if (mOnStartCallback != null) { - mOnStartCallback.onActivityStart(this); + mOnStartCallback.run(); mOnStartCallback = null; } } @@ -238,8 +238,12 @@ public abstract class BaseDraggingActivity extends BaseActivity mRotationListener.disable(); } - public void setOnStartCallback(OnStartCallback callback) { - mOnStartCallback = callback; + public void runOnceOnStart(Runnable action) { + mOnStartCallback = action; + } + + public void clearRunOnceOnStartCallback() { + mOnStartCallback = null; } protected void onDeviceProfileInitiated() { @@ -258,12 +262,4 @@ public abstract class BaseDraggingActivity extends BaseActivity } protected abstract void reapplyUi(); - - /** - * Callback for listening for onStart - */ - public interface OnStartCallback { - - void onActivityStart(T activity); - } } diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index 7adb6a4420..e6f8a85e52 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -16,6 +16,8 @@ package com.android.launcher3; +import static com.android.launcher3.FastBitmapDrawable.newIcon; +import static com.android.launcher3.graphics.PreloadIconDrawable.newPendingIcon; import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound; import android.animation.Animator; @@ -45,7 +47,6 @@ import com.android.launcher3.Launcher.OnResumeCallback; import com.android.launcher3.accessibility.LauncherAccessibilityDelegate; import com.android.launcher3.dot.DotInfo; import com.android.launcher3.folder.FolderIcon; -import com.android.launcher3.graphics.DrawableFactory; import com.android.launcher3.graphics.IconPalette; import com.android.launcher3.graphics.IconShape; import com.android.launcher3.graphics.PreloadIconDrawable; @@ -215,6 +216,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, cancelDotScaleAnim(); mDotParams.scale = 0f; mForceHideDot = false; + setBackground(null); } private void cancelDotScaleAnim() { @@ -287,9 +289,8 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, } private void applyIconAndLabel(ItemInfoWithIcon info) { - FastBitmapDrawable iconDrawable = DrawableFactory.INSTANCE.get(getContext()) - .newIcon(getContext(), info); - mDotParams.color = IconPalette.getMutedColor(info.iconColor, 0.54f); + FastBitmapDrawable iconDrawable = newIcon(getContext(), info); + mDotParams.color = IconPalette.getMutedColor(info.bitmap.color, 0.54f); setIcon(iconDrawable); setText(info.title); @@ -496,7 +497,8 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, // Text should be visible everywhere but the hotseat. Object tag = getParent() instanceof FolderIcon ? ((View) getParent()).getTag() : getTag(); ItemInfo info = tag instanceof ItemInfo ? (ItemInfo) tag : null; - return info == null || info.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT; + return info == null || (info.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT + && info.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION); } public void setTextVisibility(boolean visible) { @@ -567,8 +569,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, preloadDrawable = (PreloadIconDrawable) mIcon; preloadDrawable.setLevel(progressLevel); } else { - preloadDrawable = DrawableFactory.INSTANCE.get(getContext()) - .newPendingIcon(getContext(), info); + preloadDrawable = newPendingIcon(getContext(), info); preloadDrawable.setLevel(progressLevel); setIcon(preloadDrawable); } @@ -665,7 +666,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, mDisableRelayout = true; // Optimization: Starting in N, pre-uploads the bitmap to RenderThread. - info.iconBitmap.prepareToDraw(); + info.bitmap.icon.prepareToDraw(); if (info instanceof AppInfo) { applyFromApplicationInfo((AppInfo) info); diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java index 736142f190..a35f598686 100644 --- a/src/com/android/launcher3/DeviceProfile.java +++ b/src/com/android/launcher3/DeviceProfile.java @@ -25,6 +25,8 @@ import android.graphics.Rect; import android.util.DisplayMetrics; import android.view.Surface; +import androidx.annotation.Nullable; + import com.android.launcher3.CellLayout.ContainerType; import com.android.launcher3.graphics.IconShape; import com.android.launcher3.icons.DotRenderer; @@ -34,6 +36,8 @@ import com.android.launcher3.util.DefaultDisplay; public class DeviceProfile { public final InvariantDeviceProfile inv; + // IDP with no grid override values. + @Nullable private final InvariantDeviceProfile originalIdp; // Device properties public final boolean isTablet; @@ -134,10 +138,11 @@ public class DeviceProfile { public DotRenderer mDotRendererAllApps; public DeviceProfile(Context context, InvariantDeviceProfile inv, - Point minSize, Point maxSize, + InvariantDeviceProfile originalIDP, Point minSize, Point maxSize, int width, int height, boolean isLandscape, boolean isMultiWindowMode) { this.inv = inv; + this.originalIdp = inv; this.isLandscape = isLandscape; this.isMultiWindowMode = isMultiWindowMode; @@ -229,6 +234,19 @@ public class DeviceProfile { // Recalculate the available dimensions using the new hotseat size. updateAvailableDimensions(dm, res); } + + if (originalIDP != null) { + // Grid size change should not affect All Apps UI, so we use the original profile + // measurements here. + DeviceProfile originalProfile = isLandscape + ? originalIDP.landscapeProfile + : originalIDP.portraitProfile; + allAppsIconSizePx = originalProfile.iconSizePx; + allAppsIconTextSizePx = originalProfile.iconTextSizePx; + allAppsCellHeightPx = originalProfile.allAppsCellHeightPx; + allAppsIconDrawablePaddingPx = originalProfile.iconDrawablePaddingOriginalPx; + allAppsCellWidthPx = allAppsIconSizePx + allAppsIconDrawablePaddingPx; + } updateWorkspacePadding(); // This is done last, after iconSizePx is calculated above. @@ -241,8 +259,8 @@ public class DeviceProfile { public DeviceProfile copy(Context context) { Point size = new Point(availableWidthPx, availableHeightPx); - return new DeviceProfile(context, inv, size, size, widthPx, heightPx, isLandscape, - isMultiWindowMode); + return new DeviceProfile(context, inv, originalIdp, size, size, widthPx, heightPx, + isLandscape, isMultiWindowMode); } public DeviceProfile getMultiWindowProfile(Context context, Point mwSize) { @@ -253,8 +271,8 @@ public class DeviceProfile { // In multi-window mode, we can have widthPx = availableWidthPx // and heightPx = availableHeightPx because Launcher uses the InvariantDeviceProfiles' // widthPx and heightPx values where it's needed. - DeviceProfile profile = new DeviceProfile(context, inv, mwSize, mwSize, mwSize.x, mwSize.y, - isLandscape, true); + DeviceProfile profile = new DeviceProfile(context, inv, originalIdp, mwSize, mwSize, + mwSize.x, mwSize.y, isLandscape, true); // If there isn't enough vertical cell padding with the labels displayed, hide the labels. float workspaceCellPaddingY = profile.getCellSize().y - profile.iconSizePx @@ -338,18 +356,10 @@ public class DeviceProfile { } cellWidthPx = iconSizePx + iconDrawablePaddingPx; - // All apps - if (allAppsHasDifferentNumColumns()) { - allAppsIconSizePx = ResourceUtils.pxFromDp(inv.allAppsIconSize, dm); - allAppsIconTextSizePx = Utilities.pxFromSp(inv.allAppsIconTextSize, dm); - allAppsCellHeightPx = getCellSize(inv.numAllAppsColumns, inv.numAllAppsColumns).y; - allAppsIconDrawablePaddingPx = iconDrawablePaddingOriginalPx; - } else { - allAppsIconSizePx = iconSizePx; - allAppsIconTextSizePx = iconTextSizePx; - allAppsIconDrawablePaddingPx = iconDrawablePaddingPx; - allAppsCellHeightPx = getCellSize().y; - } + allAppsIconSizePx = iconSizePx; + allAppsIconTextSizePx = iconTextSizePx; + allAppsIconDrawablePaddingPx = iconDrawablePaddingPx; + allAppsCellHeightPx = getCellSize().y; allAppsCellWidthPx = allAppsIconSizePx + allAppsIconDrawablePaddingPx; if (isVerticalBarLayout()) { diff --git a/src/com/android/launcher3/FastBitmapDrawable.java b/src/com/android/launcher3/FastBitmapDrawable.java index a90025e97f..50916840d9 100644 --- a/src/com/android/launcher3/FastBitmapDrawable.java +++ b/src/com/android/launcher3/FastBitmapDrawable.java @@ -20,6 +20,7 @@ import static com.android.launcher3.anim.Interpolators.ACCEL; import static com.android.launcher3.anim.Interpolators.DEACCEL; import android.animation.ObjectAnimator; +import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; @@ -35,6 +36,7 @@ import android.graphics.drawable.Drawable; import android.util.Property; import android.util.SparseArray; +import com.android.launcher3.graphics.PlaceHolderIconDrawable; import com.android.launcher3.icons.BitmapInfo; public class FastBitmapDrawable extends Drawable { @@ -98,10 +100,6 @@ public class FastBitmapDrawable extends Drawable { this(info.icon, info.color); } - public FastBitmapDrawable(ItemInfoWithIcon info) { - this(info.iconBitmap, info.iconColor); - } - protected FastBitmapDrawable(Bitmap b, int iconColor) { this(b, iconColor, false); } @@ -365,7 +363,7 @@ public class FastBitmapDrawable extends Drawable { } @Override - public Drawable newDrawable() { + public FastBitmapDrawable newDrawable() { return new FastBitmapDrawable(mBitmap, mIconColor, mIsDisabled); } @@ -374,4 +372,37 @@ public class FastBitmapDrawable extends Drawable { return 0; } } + + /** + * Interface to be implemented by custom {@link BitmapInfo} to handle drawable construction + */ + public interface Factory { + + /** + * Called to create a new drawable + */ + FastBitmapDrawable newDrawable(); + } + + /** + * Returns a FastBitmapDrawable with the icon. + */ + public static FastBitmapDrawable newIcon(Context context, ItemInfoWithIcon info) { + FastBitmapDrawable drawable = newIcon(context, info.bitmap); + drawable.setIsDisabled(info.isDisabled()); + return drawable; + } + + /** + * Creates a drawable for the provided BitmapInfo + */ + public static FastBitmapDrawable newIcon(Context context, BitmapInfo info) { + if (info instanceof Factory) { + return ((Factory) info).newDrawable(); + } else if (info.isLowRes()) { + return new PlaceHolderIconDrawable(info, context); + } else { + return new FastBitmapDrawable(info); + } + } } diff --git a/src/com/android/launcher3/IconProvider.java b/src/com/android/launcher3/IconProvider.java deleted file mode 100644 index 0f006f7c19..0000000000 --- a/src/com/android/launcher3/IconProvider.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.android.launcher3; - -import static com.android.launcher3.util.MainThreadInitializedObject.forOverride; - -import android.content.pm.LauncherActivityInfo; -import android.graphics.drawable.Drawable; - -import com.android.launcher3.util.MainThreadInitializedObject; -import com.android.launcher3.util.ResourceBasedOverride; - -public class IconProvider implements ResourceBasedOverride { - - public static MainThreadInitializedObject INSTANCE = - forOverride(IconProvider.class, R.string.icon_provider_class); - - public IconProvider() { } - - public String getSystemStateForPackage(String systemState, String packageName) { - return systemState; - } - - /** - * @param flattenDrawable true if the caller does not care about the specification of the - * original icon as long as the flattened version looks the same. - */ - public Drawable getIcon(LauncherActivityInfo info, int iconDpi, boolean flattenDrawable) { - return info.getIcon(iconDpi); - } -} diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java index 0b79dd2835..21359f1dc3 100644 --- a/src/com/android/launcher3/InstallShortcutReceiver.java +++ b/src/com/android/launcher3/InstallShortcutReceiver.java @@ -22,6 +22,7 @@ import static com.android.launcher3.util.ShortcutUtil.fetchAndUpdateShortcutIcon import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProviderInfo; import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; @@ -40,9 +41,11 @@ import android.util.Base64; import android.util.Log; import android.util.Pair; +import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; import com.android.launcher3.compat.UserManagerCompat; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.icons.BitmapInfo; import com.android.launcher3.icons.GraphicsUtils; import com.android.launcher3.icons.LauncherIcons; @@ -239,11 +242,6 @@ public class InstallShortcutReceiver extends BroadcastReceiver { return info == null ? null : (WorkspaceItemInfo) info.getItemInfo().first; } - public static WorkspaceItemInfo fromActivityInfo(LauncherActivityInfo info, Context context) { - return (WorkspaceItemInfo) - new PendingInstallShortcutInfo(info, context).getItemInfo().first; - } - public static void queueShortcut(ShortcutInfo info, Context context) { queuePendingShortcutInfo(new PendingInstallShortcutInfo(info, context), context); } @@ -319,10 +317,10 @@ public class InstallShortcutReceiver extends BroadcastReceiver { private static class PendingInstallShortcutInfo { final boolean isActivity; - final ShortcutInfo shortcutInfo; - final AppWidgetProviderInfo providerInfo; + @Nullable final ShortcutInfo shortcutInfo; + @Nullable final AppWidgetProviderInfo providerInfo; - final Intent data; + @Nullable final Intent data; final Context mContext; final Intent launchIntent; final String label; @@ -352,7 +350,12 @@ public class InstallShortcutReceiver extends BroadcastReceiver { shortcutInfo = null; providerInfo = null; - data = null; + String packageName = info.getComponentName().getPackageName(); + data = new Intent(); + data.putExtra(Intent.EXTRA_SHORTCUT_INTENT, new Intent().setComponent( + new ComponentName(packageName, "")).setPackage(packageName)); + data.putExtra(Intent.EXTRA_SHORTCUT_NAME, info.getLabel()); + user = info.getUser(); mContext = context; @@ -446,21 +449,26 @@ public class InstallShortcutReceiver extends BroadcastReceiver { // This name is only used for comparisons and notifications, so fall back to activity // name if not supplied String name = ensureValidName(mContext, launchIntent, label).toString(); - Bitmap icon = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON); - Intent.ShortcutIconResource iconResource = - data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE); + Bitmap icon = data == null ? null + : data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON); + Intent.ShortcutIconResource iconResource = data == null ? null + : data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE); // Only encode the parameters which are supported by the API. JSONStringer json = new JSONStringer() .object() .key(LAUNCH_INTENT_KEY).value(launchIntent.toUri(0)) .key(NAME_KEY).value(name) + .key(USER_HANDLE_KEY).value( + UserManagerCompat.getInstance(mContext).getSerialNumberForUser(user)) .key(APP_SHORTCUT_TYPE_KEY).value(isActivity); if (icon != null) { byte[] iconByteArray = GraphicsUtils.flattenBitmap(icon); - json = json.key(ICON_KEY).value( - Base64.encodeToString( - iconByteArray, 0, iconByteArray.length, Base64.DEFAULT)); + if (iconByteArray != null) { + json = json.key(ICON_KEY).value( + Base64.encodeToString( + iconByteArray, 0, iconByteArray.length, Base64.DEFAULT)); + } } if (iconResource != null) { json = json.key(ICON_RESOURCE_NAME_KEY).value(iconResource.resourceName); @@ -476,14 +484,20 @@ public class InstallShortcutReceiver extends BroadcastReceiver { public Pair getItemInfo() { if (isActivity) { - WorkspaceItemInfo si = createWorkspaceItemInfo(data, + WorkspaceItemInfo si = createWorkspaceItemInfo(data, user, LauncherAppState.getInstance(mContext)); si.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; si.status |= WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON; return Pair.create(si, null); } else if (shortcutInfo != null) { WorkspaceItemInfo itemInfo = new WorkspaceItemInfo(shortcutInfo, mContext); - fetchAndUpdateShortcutIconAsync(mContext, itemInfo, shortcutInfo, true); + if (FeatureFlags.ENABLE_DEEP_SHORTCUT_ICON_CACHE.get()) { + fetchAndUpdateShortcutIconAsync(mContext, itemInfo, shortcutInfo, true); + } else { + LauncherIcons li = LauncherIcons.obtain(mContext); + itemInfo.bitmap = li.createShortcutIcon(shortcutInfo); + li.recycle(); + } return Pair.create(itemInfo, shortcutInfo); } else if (providerInfo != null) { LauncherAppWidgetProviderInfo info = LauncherAppWidgetProviderInfo @@ -499,7 +513,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver { return Pair.create(widgetInfo, providerInfo); } else { WorkspaceItemInfo itemInfo = - createWorkspaceItemInfo(data, LauncherAppState.getInstance(mContext)); + createWorkspaceItemInfo(data, user, LauncherAppState.getInstance(mContext)); return Pair.create(itemInfo, null); } } @@ -617,7 +631,8 @@ public class InstallShortcutReceiver extends BroadcastReceiver { return new PendingInstallShortcutInfo(info, original.mContext); } - private static WorkspaceItemInfo createWorkspaceItemInfo(Intent data, LauncherAppState app) { + private static WorkspaceItemInfo createWorkspaceItemInfo(Intent data, UserHandle user, + LauncherAppState app) { if (data == null) { Log.e(TAG, "Can't construct WorkspaceItemInfo with null data"); return null; @@ -634,10 +649,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver { } final WorkspaceItemInfo info = new WorkspaceItemInfo(); - - // Only support intents for current user for now. Intents sent from other - // users wouldn't get here without intent forwarding anyway. - info.user = Process.myUserHandle(); + info.user = user; BitmapInfo iconInfo = null; LauncherIcons li = LauncherIcons.obtain(app.getContext()); @@ -655,7 +667,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver { if (iconInfo == null) { iconInfo = app.getIconCache().getDefaultIcon(info.user); } - info.applyFrom(iconInfo); + info.bitmap = iconInfo; info.title = Utilities.trim(name); info.contentDescription = app.getContext().getPackageManager() diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java index 310a9e92ff..9d87152a87 100644 --- a/src/com/android/launcher3/InvariantDeviceProfile.java +++ b/src/com/android/launcher3/InvariantDeviceProfile.java @@ -58,6 +58,7 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; public class InvariantDeviceProfile { @@ -103,8 +104,6 @@ public class InvariantDeviceProfile { public int iconBitmapSize; public int fillResIconDpi; public float iconTextSize; - public float allAppsIconSize; - public float allAppsIconTextSize; private SparseArray mExtraAttrs; @@ -145,8 +144,6 @@ public class InvariantDeviceProfile { iconTextSize = p.iconTextSize; numHotseatIcons = p.numHotseatIcons; numAllAppsColumns = p.numAllAppsColumns; - allAppsIconSize = p.allAppsIconSize; - allAppsIconTextSize = p.allAppsIconTextSize; defaultLayoutId = p.defaultLayoutId; demoModeLayoutId = p.demoModeLayoutId; mExtraAttrs = p.mExtraAttrs; @@ -191,54 +188,11 @@ public class InvariantDeviceProfile { Point smallestSize = new Point(displayInfo.smallestSize); Point largestSize = new Point(displayInfo.largestSize); - ArrayList allOptions = getPredefinedDeviceProfiles(context, gridName); // This guarantees that width < height float minWidthDps = Utilities.dpiFromPx(Math.min(smallestSize.x, smallestSize.y), displayInfo.metrics); float minHeightDps = Utilities.dpiFromPx(Math.min(largestSize.x, largestSize.y), displayInfo.metrics); - // Sort the profiles based on the closeness to the device size - Collections.sort(allOptions, (a, b) -> - Float.compare(dist(minWidthDps, minHeightDps, a.minWidthDps, a.minHeightDps), - dist(minWidthDps, minHeightDps, b.minWidthDps, b.minHeightDps))); - DisplayOption interpolatedDisplayOption = - invDistWeightedInterpolate(minWidthDps, minHeightDps, allOptions); - - GridOption closestProfile = allOptions.get(0).grid; - numRows = closestProfile.numRows; - numColumns = closestProfile.numColumns; - numHotseatIcons = closestProfile.numHotseatIcons; - defaultLayoutId = closestProfile.defaultLayoutId; - demoModeLayoutId = closestProfile.demoModeLayoutId; - numFolderRows = closestProfile.numFolderRows; - numFolderColumns = closestProfile.numFolderColumns; - numAllAppsColumns = closestProfile.numAllAppsColumns; - - mExtraAttrs = closestProfile.extraAttrs; - - if (!closestProfile.name.equals(gridName)) { - Utilities.getPrefs(context).edit() - .putString(KEY_IDP_GRID_NAME, closestProfile.name).apply(); - } - - iconSize = interpolatedDisplayOption.iconSize; - iconShapePath = getIconShapePath(context); - landscapeIconSize = interpolatedDisplayOption.landscapeIconSize; - iconBitmapSize = ResourceUtils.pxFromDp(iconSize, displayInfo.metrics); - iconTextSize = interpolatedDisplayOption.iconTextSize; - fillResIconDpi = getLauncherIconDensity(iconBitmapSize); - - if (Utilities.getPrefs(context).getBoolean(GRID_OPTIONS_PREFERENCE_KEY, false)) { - allAppsIconSize = interpolatedDisplayOption.allAppsIconSize; - allAppsIconTextSize = interpolatedDisplayOption.allAppsIconTextSize; - } else { - allAppsIconSize = iconSize; - allAppsIconTextSize = iconTextSize; - } - - // If the partner customization apk contains any grid overrides, apply them - // Supported overrides: numRows, numColumns, iconSize - applyPartnerDeviceProfileOverrides(context, displayInfo.metrics); Point realSize = new Point(displayInfo.realSize); // The real size never changes. smallSide and largeSide will remain the @@ -246,10 +200,64 @@ public class InvariantDeviceProfile { int smallSide = Math.min(realSize.x, realSize.y); int largeSide = Math.max(realSize.x, realSize.y); - landscapeProfile = new DeviceProfile(context, this, smallestSize, largestSize, - largeSide, smallSide, true /* isLandscape */, false /* isMultiWindowMode */); - portraitProfile = new DeviceProfile(context, this, smallestSize, largestSize, - smallSide, largeSide, false /* isLandscape */, false /* isMultiWindowMode */); + // We want a list of all options as well as the list of filtered options. This allows us + // to have a consistent UI for areas that the grid size change should not affect + // ie. All Apps should be consistent between grid sizes. + ArrayList allOptions = new ArrayList<>(); + ArrayList filteredOptions = new ArrayList<>(); + getPredefinedDeviceProfiles(context, gridName, filteredOptions, allOptions); + + if (allOptions.isEmpty() && filteredOptions.isEmpty()) { + throw new RuntimeException("No display option with canBeDefault=true"); + } + + // Sort the profiles based on the closeness to the device size + Comparator comparator = (a, b) -> Float.compare(dist(minWidthDps, + minHeightDps, a.minWidthDps, a.minHeightDps), + dist(minWidthDps, minHeightDps, b.minWidthDps, b.minHeightDps)); + + // Calculate the device profiles as if there is no grid override. + Collections.sort(allOptions, comparator); + DisplayOption interpolatedDisplayOption = + invDistWeightedInterpolate(minWidthDps, minHeightDps, allOptions); + initGridOption(context, allOptions, interpolatedDisplayOption, displayInfo.metrics); + + // Create IDP with no grid override values. + InvariantDeviceProfile originalIDP = new InvariantDeviceProfile(this); + originalIDP.landscapeProfile = new DeviceProfile(context, this, null, smallestSize, + largestSize, largeSide, smallSide, true /* isLandscape */, + false /* isMultiWindowMode */); + originalIDP.portraitProfile = new DeviceProfile(context, this, null, smallestSize, + largestSize, smallSide, largeSide, false /* isLandscape */, + false /* isMultiWindowMode */); + + if (filteredOptions.isEmpty()) { + filteredOptions = allOptions; + + landscapeProfile = originalIDP.landscapeProfile; + portraitProfile = originalIDP.portraitProfile; + } else { + Collections.sort(filteredOptions, comparator); + interpolatedDisplayOption = + invDistWeightedInterpolate(minWidthDps, minHeightDps, filteredOptions); + + initGridOption(context, filteredOptions, interpolatedDisplayOption, + displayInfo.metrics); + numAllAppsColumns = originalIDP.numAllAppsColumns; + + landscapeProfile = new DeviceProfile(context, this, originalIDP, smallestSize, + largestSize, largeSide, smallSide, true /* isLandscape */, + false /* isMultiWindowMode */); + portraitProfile = new DeviceProfile(context, this, originalIDP, smallestSize, + largestSize, smallSide, largeSide, false /* isLandscape */, + false /* isMultiWindowMode */); + } + + GridOption closestProfile = filteredOptions.get(0).grid; + if (!closestProfile.name.equals(gridName)) { + Utilities.getPrefs(context).edit() + .putString(KEY_IDP_GRID_NAME, closestProfile.name).apply(); + } // We need to ensure that there is enough extra space in the wallpaper // for the intended parallax effects @@ -267,6 +275,33 @@ public class InvariantDeviceProfile { return closestProfile.name; } + private void initGridOption(Context context, ArrayList options, + DisplayOption displayOption, DisplayMetrics metrics) { + GridOption closestProfile = options.get(0).grid; + numRows = closestProfile.numRows; + numColumns = closestProfile.numColumns; + numHotseatIcons = closestProfile.numHotseatIcons; + defaultLayoutId = closestProfile.defaultLayoutId; + demoModeLayoutId = closestProfile.demoModeLayoutId; + numFolderRows = closestProfile.numFolderRows; + numFolderColumns = closestProfile.numFolderColumns; + numAllAppsColumns = numColumns; + + mExtraAttrs = closestProfile.extraAttrs; + + iconSize = displayOption.iconSize; + iconShapePath = getIconShapePath(context); + landscapeIconSize = displayOption.landscapeIconSize; + iconBitmapSize = ResourceUtils.pxFromDp(iconSize, metrics); + iconTextSize = displayOption.iconTextSize; + fillResIconDpi = getLauncherIconDensity(iconBitmapSize); + + // If the partner customization apk contains any grid overrides, apply them + // Supported overrides: numRows, numColumns, iconSize + applyPartnerDeviceProfileOverrides(context, metrics); + } + + @Nullable public TypedValue getAttrValue(int attr) { return mExtraAttrs == null ? null : mExtraAttrs.get(attr); @@ -344,7 +379,13 @@ public class InvariantDeviceProfile { } } - static ArrayList getPredefinedDeviceProfiles(Context context, String gridName) { + /** + * @param gridName The current grid name. + * @param filteredOptionsOut List filled with all the filtered options based on gridName. + * @param allOptionsOut List filled with all the options that can be the default option. + */ + static void getPredefinedDeviceProfiles(Context context, String gridName, + ArrayList filteredOptionsOut, ArrayList allOptionsOut) { ArrayList profiles = new ArrayList<>(); try (XmlResourceParser parser = context.getResources().getXml(R.xml.device_profiles)) { final int depth = parser.getDepth(); @@ -371,26 +412,19 @@ public class InvariantDeviceProfile { throw new RuntimeException(e); } - ArrayList filteredProfiles = new ArrayList<>(); if (!TextUtils.isEmpty(gridName)) { for (DisplayOption option : profiles) { if (gridName.equals(option.grid.name)) { - filteredProfiles.add(option); + filteredOptionsOut.add(option); } } } - if (filteredProfiles.isEmpty()) { - // No grid found, use the default options - for (DisplayOption option : profiles) { - if (option.canBeDefault) { - filteredProfiles.add(option); - } + + for (DisplayOption option : profiles) { + if (option.canBeDefault) { + allOptionsOut.add(option); } } - if (filteredProfiles.isEmpty()) { - throw new RuntimeException("No display option with canBeDefault=true"); - } - return filteredProfiles; } private int getLauncherIconDensity(int requiredSize) { @@ -514,8 +548,6 @@ public class InvariantDeviceProfile { private final int numHotseatIcons; - private final int numAllAppsColumns; - private final int defaultLayoutId; private final int demoModeLayoutId; @@ -538,8 +570,6 @@ public class InvariantDeviceProfile { R.styleable.GridDisplayOption_numFolderRows, numRows); numFolderColumns = a.getInt( R.styleable.GridDisplayOption_numFolderColumns, numColumns); - numAllAppsColumns = a.getInt( - R.styleable.GridDisplayOption_numAllAppsColumns, numColumns); a.recycle(); @@ -559,8 +589,6 @@ public class InvariantDeviceProfile { private float iconSize; private float iconTextSize; private float landscapeIconSize; - private float allAppsIconSize; - private float allAppsIconTextSize; DisplayOption(GridOption grid, Context context, AttributeSet attrs) { this.grid = grid; @@ -579,10 +607,6 @@ public class InvariantDeviceProfile { iconSize); iconTextSize = a.getFloat(R.styleable.ProfileDisplayOption_iconTextSize, 0); - allAppsIconSize = a.getFloat(R.styleable.ProfileDisplayOption_allAppsIconSize, - iconSize); - allAppsIconTextSize = a.getFloat(R.styleable.ProfileDisplayOption_allAppsIconTextSize, - iconTextSize); a.recycle(); } @@ -597,18 +621,14 @@ public class InvariantDeviceProfile { private DisplayOption multiply(float w) { iconSize *= w; landscapeIconSize *= w; - allAppsIconSize *= w; iconTextSize *= w; - allAppsIconTextSize *= w; return this; } private DisplayOption add(DisplayOption p) { iconSize += p.iconSize; landscapeIconSize += p.landscapeIconSize; - allAppsIconSize += p.allAppsIconSize; iconTextSize += p.iconTextSize; - allAppsIconTextSize += p.allAppsIconTextSize; return this; } } diff --git a/src/com/android/launcher3/ItemInfoWithIcon.java b/src/com/android/launcher3/ItemInfoWithIcon.java index 1550bb080d..19414550f8 100644 --- a/src/com/android/launcher3/ItemInfoWithIcon.java +++ b/src/com/android/launcher3/ItemInfoWithIcon.java @@ -16,10 +16,6 @@ package com.android.launcher3; -import static com.android.launcher3.icons.BitmapInfo.LOW_RES_ICON; - -import android.graphics.Bitmap; - import com.android.launcher3.icons.BitmapInfo; /** @@ -30,14 +26,9 @@ public abstract class ItemInfoWithIcon extends ItemInfo { public static final String TAG = "ItemInfoDebug"; /** - * A bitmap version of the application icon. + * The bitmap for the application icon */ - public Bitmap iconBitmap; - - /** - * Dominant color in the {@link #iconBitmap}. - */ - public int iconColor; + public BitmapInfo bitmap = BitmapInfo.LOW_RES_INFO; /** * Indicates that the icon is disabled due to safe mode restrictions. @@ -106,8 +97,7 @@ public abstract class ItemInfoWithIcon extends ItemInfo { protected ItemInfoWithIcon(ItemInfoWithIcon info) { super(info); - iconBitmap = info.iconBitmap; - iconColor = info.iconColor; + bitmap = info.bitmap; runtimeStatusFlags = info.runtimeStatusFlags; } @@ -120,12 +110,7 @@ public abstract class ItemInfoWithIcon extends ItemInfo { * Indicates whether we're using a low res icon */ public boolean usingLowResIcon() { - return iconBitmap == LOW_RES_ICON; - } - - public void applyFrom(BitmapInfo info) { - iconBitmap = info.icon; - iconColor = info.color; + return bitmap.isLowRes(); } /** diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 4b4d7939e6..5a5f7c347e 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -31,8 +31,11 @@ import static com.android.launcher3.Utilities.postAsyncCallback; import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_LAUNCHER_LOAD; import static com.android.launcher3.logging.LoggerUtils.newContainerTarget; import static com.android.launcher3.logging.LoggerUtils.newTarget; +import static com.android.launcher3.popup.SystemShortcut.APP_INFO; +import static com.android.launcher3.popup.SystemShortcut.DISMISS_PREDICTION; +import static com.android.launcher3.popup.SystemShortcut.INSTALL; +import static com.android.launcher3.popup.SystemShortcut.WIDGETS; import static com.android.launcher3.states.RotationHelper.REQUEST_NONE; -import static com.android.launcher3.testing.TestProtocol.CRASH_ADD_CUSTOM_SHORTCUT; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -58,6 +61,7 @@ import android.graphics.Point; import android.graphics.Rect; import android.os.Build; import android.os.Bundle; +import android.os.CancellationSignal; import android.os.Handler; import android.os.Parcelable; import android.os.Process; @@ -82,6 +86,8 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.android.launcher3.DropTarget.DragObject; +import com.android.launcher3.LauncherState.ScaleAndTranslation; +import com.android.launcher3.LauncherStateManager.StateHandler; import com.android.launcher3.accessibility.LauncherAccessibilityDelegate; import com.android.launcher3.allapps.AllAppsContainerView; import com.android.launcher3.allapps.AllAppsStore; @@ -96,6 +102,7 @@ import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.dragndrop.DragView; import com.android.launcher3.folder.FolderGridOrganizer; import com.android.launcher3.folder.FolderIcon; +import com.android.launcher3.folder.FolderNameProvider; import com.android.launcher3.graphics.RotationMode; import com.android.launcher3.icons.IconCache; import com.android.launcher3.keyboard.CustomActionsPopup; @@ -111,11 +118,11 @@ import com.android.launcher3.notification.NotificationListener; import com.android.launcher3.pm.PinRequestHelper; import com.android.launcher3.popup.PopupContainerWithArrow; import com.android.launcher3.popup.PopupDataProvider; +import com.android.launcher3.popup.SystemShortcut; import com.android.launcher3.qsb.QsbContainerView; import com.android.launcher3.states.RotationHelper; -import com.android.launcher3.testing.TestProtocol; +import com.android.launcher3.touch.AllAppsSwipeController; import com.android.launcher3.touch.ItemClickHandler; -import com.android.launcher3.uioverrides.UiFactory; import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.userevent.nano.LauncherLogProto.Action; @@ -135,6 +142,7 @@ import com.android.launcher3.util.ShortcutUtil; import com.android.launcher3.util.SystemUiController; import com.android.launcher3.util.Themes; import com.android.launcher3.util.Thunk; +import com.android.launcher3.util.TouchController; import com.android.launcher3.util.TraceHelper; import com.android.launcher3.util.UiThreadHelper; import com.android.launcher3.util.ViewOnDrawExecutor; @@ -166,6 +174,7 @@ import java.util.HashSet; import java.util.List; import java.util.function.Predicate; import java.util.function.Supplier; +import java.util.stream.Stream; /** * Default launcher application. @@ -267,6 +276,10 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, private ArrayList mOnResumeCallbacks = new ArrayList<>(); + // Used to notify when an activity launch has been deferred because launcher is not yet resumed + // TODO: See if we can remove this later + private Runnable mOnDeferredActivityLaunchCallback; + private ViewOnDrawExecutor mPendingExecutor; private LauncherModel mModel; @@ -290,12 +303,11 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, */ private PendingRequestArgs mPendingRequestArgs; // Request id for any pending activity result - private int mPendingActivityRequestCode = -1; + protected int mPendingActivityRequestCode = -1; public ViewGroupFocusHelper mFocusHandler; private RotationHelper mRotationHelper; - private Runnable mCancelTouchController; final Handler mHandler = new Handler(); private final Runnable mHandleDeferredResume = this::handleDeferredResume; @@ -346,7 +358,6 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, mDragController = new DragController(this); mAllAppsController = new AllAppsTransitionController(this); mStateManager = new LauncherStateManager(this); - UiFactory.onCreate(this); mAppWidgetManager = AppWidgetManagerCompat.getInstance(this); mAppWidgetHost = new LauncherAppWidgetHost(this, @@ -470,7 +481,6 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, @Override public void onEnterAnimationComplete() { super.onEnterAnimationComplete(); - UiFactory.onEnterAnimationComplete(this); mAllAppsController.highlightWorkTabIfNecessary(); mRotationHelper.setCurrentTransitionRequest(REQUEST_NONE); } @@ -484,7 +494,6 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, } mOldConfig.setTo(newConfig); - UiFactory.onLauncherStateOrResumeChanged(this); super.onConfigurationChanged(newConfig); } @@ -500,7 +509,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, public void reapplyUi() { if (supportsFakeLandscapeUI()) { mRotationMode = mStableDeviceProfile == null - ? RotationMode.NORMAL : UiFactory.getRotationMode(mDeviceProfile); + ? RotationMode.NORMAL : getFakeRotationMode(mDeviceProfile); } getRootView().dispatchInsets(); getStateManager().reapplyState(true /* cancelCurrentAnimation */); @@ -563,7 +572,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, if (supportsFakeLandscapeUI() && mDeviceProfile.isVerticalBarLayout()) { mStableDeviceProfile = mDeviceProfile.inv.portraitProfile; - mRotationMode = UiFactory.getRotationMode(mDeviceProfile); + mRotationMode = getFakeRotationMode(mDeviceProfile); } else { mStableDeviceProfile = null; mRotationMode = RotationMode.NORMAL; @@ -606,6 +615,10 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, return mStateManager; } + public FolderNameProvider getFolderNameProvider() { + return new FolderNameProvider(); + } + @Override public T findViewById(int id) { return mLauncherView.findViewById(id); @@ -929,8 +942,6 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, NotificationListener.removeNotificationsChangedListener(); getStateManager().moveToRestState(); - UiFactory.onLauncherStateOrResumeChanged(this); - // Workaround for b/78520668, explicitly trim memory once UI is hidden onTrimMemory(TRIM_MEMORY_UI_HIDDEN); } @@ -953,7 +964,6 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, logStopAndResume(Action.Command.RESUME); getUserEventDispatcher().startSession(); - UiFactory.onLauncherStateOrResumeChanged(this); AppLaunchTracker.INSTANCE.get(this).onReturnedToHome(); // Process any items that were added while Launcher was away. @@ -968,15 +978,17 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, DiscoveryBounce.showForHomeIfNeeded(this); - if (mPendingActivityRequestCode != -1 && isInState(NORMAL)) { - UiFactory.resetPendingActivityResults(this, mPendingActivityRequestCode); - } + onDeferredResumed(); + addActivityFlags(ACTIVITY_STATE_DEFERRED_RESUMED); + mDeferredResumePending = false; } else { mDeferredResumePending = true; } } + protected void onDeferredResumed() { } + private void logStopAndResume(int command) { int containerType = mStateManager.getState().containerType; if (containerType == ContainerType.WORKSPACE && mWorkspace != null) { @@ -1030,12 +1042,14 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, if (mDeferOverlayCallbacks) { scheduleDeferredCheck(); } + addActivityFlags(ACTIVITY_STATE_TRANSITION_ACTIVE); } public void onStateSetEnd(LauncherState state) { getAppWidgetHost().setResumed(state == LauncherState.NORMAL); getWorkspace().setClipChildren(!state.disablePageClipping); finishAutoCancelActionMode(); + removeActivityFlags(ACTIVITY_STATE_TRANSITION_ACTIVE); } @Override @@ -1080,18 +1094,6 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, } } - @Override - protected void onUserLeaveHint() { - super.onUserLeaveHint(); - UiFactory.onLauncherStateOrResumeChanged(this); - } - - @Override - public void onWindowFocusChanged(boolean hasFocus) { - super.onWindowFocusChanged(hasFocus); - mStateManager.onWindowFocusChanged(); - } - class LauncherOverlayCallbacksImpl implements LauncherOverlayCallbacks { public void onScrollChanged(float progress) { @@ -1155,7 +1157,6 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, // Setup the drag layer mDragLayer.setup(mDragController, mWorkspace); - mCancelTouchController = UiFactory.enableLiveUIChanges(this); mWorkspace.setup(mDragController); // Until the workspace is bound, ensure that we keep the wallpaper offset locked to the @@ -1533,11 +1534,6 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, mWorkspace.removeFolderListeners(); PluginManagerWrapper.INSTANCE.get(this).removePluginListener(this); - if (mCancelTouchController != null) { - mCancelTouchController.run(); - mCancelTouchController = null; - } - // Stop callbacks from LauncherModel // It's possible to receive onDestroy after a new Launcher activity has // been created. In this case, don't interfere with the new Launcher. @@ -1573,10 +1569,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, if (requestCode != -1) { mPendingActivityRequestCode = requestCode; } - if (requestCode == -1 - || !UiFactory.startActivityForResult(this, intent, requestCode, options)) { - super.startActivityForResult(intent, requestCode, options); - } + super.startActivityForResult(intent, requestCode, options); } @Override @@ -1585,14 +1578,11 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, if (requestCode != -1) { mPendingActivityRequestCode = requestCode; } - if (requestCode == -1 || !UiFactory.startIntentSenderForResult(this, intent, requestCode, - fillInIntent, flagsMask, flagsValues, extraFlags, options)) { - try { - super.startIntentSenderForResult(intent, requestCode, - fillInIntent, flagsMask, flagsValues, extraFlags, options); - } catch (IntentSender.SendIntentException e) { - throw new ActivityNotFoundException(); - } + try { + super.startIntentSenderForResult(intent, requestCode, + fillInIntent, flagsMask, flagsValues, extraFlags, options); + } catch (IntentSender.SendIntentException e) { + throw new ActivityNotFoundException(); } } @@ -1886,7 +1876,10 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, // recents animation into launcher. Defer launching the activity until Launcher is // next resumed. addOnResumeCallback(() -> startActivitySafely(v, intent, item, sourceContainer)); - UiFactory.clearSwipeSharedState(this, true /* finishAnimation */); + if (mOnDeferredActivityLaunchCallback != null) { + mOnDeferredActivityLaunchCallback.run(); + mOnDeferredActivityLaunchCallback = null; + } return true; } @@ -1927,7 +1920,6 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, // This clears all widget bitmaps from the widget tray // TODO(hyunyoungs) } - UiFactory.onTrimMemory(this, level); } @Override @@ -1947,6 +1939,14 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, mOnResumeCallbacks.add(callback); } + /** + * Persistant callback which notifies when an activity launch is deferred because the activity + * was not yet resumed. + */ + public void setOnDeferredActivityLaunchCallback(Runnable callback) { + mOnDeferredActivityLaunchCallback = callback; + } + /** * Implementation of the method from LauncherModel.Callbacks. */ @@ -2638,16 +2638,40 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, return super.onKeyUp(keyCode, event); } - public static Launcher getLauncher(Context context) { - return (Launcher) fromContext(context); + protected StateHandler[] createStateHandlers() { + return new StateHandler[] { getAllAppsController(), getWorkspace() }; } + public TouchController[] createTouchControllers() { + return new TouchController[] {getDragController(), new AllAppsSwipeController(this)}; + } + + protected RotationMode getFakeRotationMode(DeviceProfile deviceProfile) { + return RotationMode.NORMAL; + } + + protected ScaleAndTranslation getOverviewScaleAndTranslationForNormalState() { + return new ScaleAndTranslation(1.1f, 0f, 0f); + } + + public void useFadeOutAnimationForLauncherStart(CancellationSignal signal) { } + + public void onDragLayerHierarchyChanged() { } + @Override public void returnToHomescreen() { super.returnToHomescreen(); getStateManager().goToState(LauncherState.NORMAL); } + public Stream getSupportedShortcuts() { + return Stream.of(APP_INFO, WIDGETS, INSTALL, DISMISS_PREDICTION); + } + + public static Launcher getLauncher(Context context) { + return (Launcher) fromContext(context); + } + /** * Just a wrapper around the type cast to allow easier tracking of calls. */ diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java index c717d1a4a3..79f48215f9 100644 --- a/src/com/android/launcher3/LauncherAppState.java +++ b/src/com/android/launcher3/LauncherAppState.java @@ -30,12 +30,14 @@ import android.util.Log; import com.android.launcher3.compat.UserManagerCompat; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.icons.IconCache; +import com.android.launcher3.icons.IconProvider; import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.notification.NotificationListener; import com.android.launcher3.pm.InstallSessionTracker; import com.android.launcher3.pm.PackageInstallerCompat; import com.android.launcher3.util.MainThreadInitializedObject; import com.android.launcher3.util.Preconditions; +import com.android.launcher3.util.SafeCloseable; import com.android.launcher3.util.SecureSettingsObserver; import com.android.launcher3.util.SimpleBroadcastReceiver; import com.android.launcher3.widget.custom.CustomWidgetManager; @@ -57,6 +59,7 @@ public class LauncherAppState { private final InstallSessionTracker mInstallSessionTracker; private final SimpleBroadcastReceiver mModelChangeReceiver; + private final SafeCloseable mCalendarChangeTracker; public static LauncherAppState getInstance(final Context context) { return INSTANCE.get(context); @@ -92,6 +95,10 @@ public class LauncherAppState { if (FeatureFlags.IS_DOGFOOD_BUILD) { mModelChangeReceiver.register(mContext, ACTION_FORCE_ROLOAD); } + + mCalendarChangeTracker = IconProvider.registerIconChangeListener(mContext, + mModel::onAppIconChanged, MODEL_EXECUTOR.getHandler()); + // TODO: remove listener on terminate FeatureFlags.APP_SEARCH_IMPROVEMENTS.addChangeListener(context, mModel::forceReload); CustomWidgetManager.INSTANCE.get(mContext) @@ -143,6 +150,7 @@ public class LauncherAppState { mContext.unregisterReceiver(mModelChangeReceiver); mContext.getSystemService(LauncherApps.class).unregisterCallback(mModel); mInstallSessionTracker.unregister(); + mCalendarChangeTracker.close(); CustomWidgetManager.INSTANCE.get(mContext).setWidgetRefreshCallback(null); if (mNotificationDotsObserver != null) { diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index fc2e953b00..5ff5b04a5a 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -21,6 +21,7 @@ import static com.android.launcher3.config.FeatureFlags.IS_DOGFOOD_BUILD; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; +import android.content.Context; import android.content.Intent; import android.content.pm.LauncherApps; import android.content.pm.PackageInstaller; @@ -31,6 +32,7 @@ import android.util.Log; import android.util.Pair; import androidx.annotation.Nullable; +import androidx.annotation.WorkerThread; import com.android.launcher3.compat.UserManagerCompat; import com.android.launcher3.config.FeatureFlags; @@ -53,7 +55,6 @@ import com.android.launcher3.model.UserLockStateChangedTask; import com.android.launcher3.pm.InstallSessionTracker; import com.android.launcher3.pm.PackageInstallInfo; import com.android.launcher3.shortcuts.DeepShortcutManager; -import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.util.IntSparseArrayMap; import com.android.launcher3.util.ItemInfoMatcher; import com.android.launcher3.util.PackageUserKey; @@ -93,10 +94,6 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi private boolean mModelLoaded; public boolean isModelLoaded() { synchronized (mLock) { - if (TestProtocol.sDebugTracing) { - Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE, - "isModelLoaded: " + mModelLoaded + ", " + mLoaderTask); - } return mModelLoaded && mLoaderTask == null; } } @@ -214,9 +211,21 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, shortcuts, user, true)); } - public void updatePinnedShortcuts(String packageName, List shortcuts, - UserHandle user) { - enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, shortcuts, user, false)); + /** + * Called when the icon for an app changes, outside of package event + */ + @WorkerThread + public void onAppIconChanged(String packageName, UserHandle user) { + // Update the icon for the calendar package + Context context = mApp.getContext(); + onPackageChanged(packageName, user); + + List pinnedShortcuts = DeepShortcutManager.getInstance(context) + .queryForPinnedShortcuts(packageName, user); + if (!pinnedShortcuts.isEmpty()) { + enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, pinnedShortcuts, user, + false)); + } } public void onBroadcastIntent(Intent intent) { @@ -525,7 +534,7 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi updateAndBindWorkspaceItem(() -> { si.updateFromDeepShortcutInfo(info, mApp.getContext()); LauncherIcons li = LauncherIcons.obtain(mApp.getContext()); - si.applyFrom(li.createShortcutIcon(info)); + si.bitmap = li.createShortcutIcon(info); li.recycle(); return si; }); @@ -561,7 +570,8 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi 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 + "\" iconBitmap=" + info.iconBitmap + writer.println(prefix + " title=\"" + info.title + + "\" bitmapIcon=" + info.bitmap.icon + " componentName=" + info.componentName.getPackageName()); } } diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java index c509680981..ec307db521 100644 --- a/src/com/android/launcher3/LauncherSettings.java +++ b/src/com/android/launcher3/LauncherSettings.java @@ -127,6 +127,7 @@ public class LauncherSettings { public static final int CONTAINER_DESKTOP = -100; public static final int CONTAINER_HOTSEAT = -101; public static final int CONTAINER_PREDICTION = -102; + public static final int CONTAINER_HOTSEAT_PREDICTION = -103; static final String containerToString(int container) { switch (container) { diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java index 6e2626b615..d2b447b4b0 100644 --- a/src/com/android/launcher3/LauncherState.java +++ b/src/com/android/launcher3/LauncherState.java @@ -26,9 +26,11 @@ import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSL import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE; import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_SCALE; import static com.android.launcher3.anim.Interpolators.ACCEL; +import static com.android.launcher3.anim.Interpolators.ACCEL_2; import static com.android.launcher3.anim.Interpolators.DEACCEL; import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7; import static com.android.launcher3.anim.Interpolators.clampToProgress; +import static com.android.launcher3.states.RotationHelper.REQUEST_NONE; import static com.android.launcher3.testing.TestProtocol.ALL_APPS_STATE_ORDINAL; import static com.android.launcher3.testing.TestProtocol.BACKGROUND_APP_STATE_ORDINAL; import static com.android.launcher3.testing.TestProtocol.NORMAL_STATE_ORDINAL; @@ -36,14 +38,11 @@ import static com.android.launcher3.testing.TestProtocol.OVERVIEW_PEEK_STATE_ORD import static com.android.launcher3.testing.TestProtocol.OVERVIEW_STATE_ORDINAL; import static com.android.launcher3.testing.TestProtocol.QUICK_SWITCH_STATE_ORDINAL; import static com.android.launcher3.testing.TestProtocol.SPRING_LOADED_STATE_ORDINAL; -import static com.android.launcher3.anim.Interpolators.ACCEL_2; -import static com.android.launcher3.states.RotationHelper.REQUEST_NONE; import android.view.animation.Interpolator; import com.android.launcher3.anim.AnimatorSetBuilder; import com.android.launcher3.states.SpringLoadedState; -import com.android.launcher3.uioverrides.UiFactory; import com.android.launcher3.uioverrides.states.AllAppsState; import com.android.launcher3.uioverrides.states.OverviewState; import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; @@ -210,7 +209,7 @@ public class LauncherState { } public ScaleAndTranslation getOverviewScaleAndTranslation(Launcher launcher) { - return UiFactory.getOverviewScaleAndTranslationForNormalState(launcher); + return launcher.getOverviewScaleAndTranslationForNormalState(); } public float getOverviewFullscreenProgress() { diff --git a/src/com/android/launcher3/LauncherStateManager.java b/src/com/android/launcher3/LauncherStateManager.java index 848e19fb53..daf270bfa1 100644 --- a/src/com/android/launcher3/LauncherStateManager.java +++ b/src/com/android/launcher3/LauncherStateManager.java @@ -24,7 +24,8 @@ import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.os.Handler; import android.os.Looper; -import android.util.Log; + +import androidx.annotation.IntDef; import com.android.launcher3.anim.AnimationSuccessListener; import com.android.launcher3.anim.AnimatorPlaybackController; @@ -32,16 +33,12 @@ import com.android.launcher3.anim.AnimatorSetBuilder; import com.android.launcher3.anim.PropertySetter; import com.android.launcher3.anim.PropertySetter.AnimatedPropertySetter; import com.android.launcher3.compat.AccessibilityManagerCompat; -import com.android.launcher3.testing.TestProtocol; -import com.android.launcher3.uioverrides.UiFactory; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; -import androidx.annotation.IntDef; - /** * TODO: figure out what kind of tests we can write for this * @@ -146,7 +143,7 @@ public class LauncherStateManager { public StateHandler[] getStateHandlers() { if (mStateHandlers == null) { - mStateHandlers = UiFactory.getStateHandler(mLauncher); + mStateHandlers = mLauncher.createStateHandlers(); } return mStateHandlers; } @@ -414,7 +411,6 @@ public class LauncherStateManager { // Only disable clipping if needed, otherwise leave it as previous value. mLauncher.getWorkspace().setClipChildren(false); } - UiFactory.onLauncherStateOrResumeChanged(mLauncher); for (int i = mListeners.size() - 1; i >= 0; i--) { mListeners.get(i).onStateTransitionStart(state); @@ -435,8 +431,6 @@ public class LauncherStateManager { setRestState(null); } - UiFactory.onLauncherStateOrResumeChanged(mLauncher); - for (int i = mListeners.size() - 1; i >= 0; i--) { mListeners.get(i).onStateTransitionComplete(state); } @@ -444,10 +438,6 @@ public class LauncherStateManager { AccessibilityManagerCompat.sendStateEventToTest(mLauncher, state.ordinal); } - public void onWindowFocusChanged() { - UiFactory.onLauncherStateOrFocusChanged(mLauncher); - } - public LauncherState getLastState() { return mLastStableState; } diff --git a/src/com/android/launcher3/SessionCommitReceiver.java b/src/com/android/launcher3/SessionCommitReceiver.java index 8dedc6c498..e0c50e25f2 100644 --- a/src/com/android/launcher3/SessionCommitReceiver.java +++ b/src/com/android/launcher3/SessionCommitReceiver.java @@ -71,8 +71,13 @@ public class SessionCommitReceiver extends BroadcastReceiver { SessionInfo info = intent.getParcelableExtra(PackageInstaller.EXTRA_SESSION); UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER); - PackageInstallerCompat packageInstallerCompat = PackageInstallerCompat.getInstance(context); + if (!PackageInstaller.ACTION_SESSION_COMMITTED.equals(intent.getAction()) + || info == null || user == null) { + // Invalid intent. + return; + } + PackageInstallerCompat packageInstallerCompat = PackageInstallerCompat.getInstance(context); if (TextUtils.isEmpty(info.getAppPackageName()) || info.getInstallReason() != PackageManager.INSTALL_REASON_USER || packageInstallerCompat.promiseIconAddedForId(info.getSessionId())) { diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java index 92f8069064..2bec0ba3b7 100644 --- a/src/com/android/launcher3/Utilities.java +++ b/src/com/android/launcher3/Utilities.java @@ -63,6 +63,7 @@ import android.view.animation.Interpolator; import com.android.launcher3.dragndrop.FolderAdaptiveIcon; import com.android.launcher3.graphics.RotationMode; import com.android.launcher3.graphics.TintedDrawableSpan; +import com.android.launcher3.icons.IconProvider; import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.pm.ShortcutConfigActivityInfo; import com.android.launcher3.shortcuts.DeepShortcutManager; @@ -518,19 +519,20 @@ public final class Utilities { } /** - * Returns the full drawable for {@param info}. + * Returns the full drawable for info without any flattening or pre-processing. + * * @param outObj this is set to the internal data associated with {@param info}, * eg {@link LauncherActivityInfo} or {@link ShortcutInfo}. */ public static Drawable getFullDrawable(Launcher launcher, ItemInfo info, int width, int height, - boolean flattenDrawable, Object[] outObj) { + Object[] outObj) { LauncherAppState appState = LauncherAppState.getInstance(launcher); if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { LauncherActivityInfo activityInfo = launcher.getSystemService(LauncherApps.class) .resolveActivity(info.getIntent(), info.user); outObj[0] = activityInfo; - return (activityInfo != null) ? appState.getIconCache() - .getFullResIcon(activityInfo, flattenDrawable) : null; + return activityInfo == null ? null : new IconProvider(launcher).getIconForUI( + activityInfo, launcher.getDeviceProfile().inv.fillResIconDpi); } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) { if (info instanceof PendingAddShortcutInfo) { ShortcutConfigActivityInfo activityInfo = @@ -582,9 +584,9 @@ public final class Utilities { } ShortcutInfo si = (ShortcutInfo) obj; LauncherIcons li = LauncherIcons.obtain(appState.getContext()); - Bitmap badge = li.getShortcutInfoBadge(si, appState.getIconCache()).iconBitmap; + Bitmap badge = li.getShortcutInfoBadge(si, appState.getIconCache()).bitmap.icon; li.recycle(); - float badgeSize = iconSize * LauncherIcons.getBadgeSizeForIconSize(iconSize); + float badgeSize = LauncherIcons.getBadgeSizeForIconSize(iconSize); float insetFraction = (iconSize - badgeSize) / iconSize; return new InsetDrawable(new FastBitmapDrawable(badge), insetFraction, insetFraction, 0, 0); diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java index c5e74effa9..37b58d306d 100644 --- a/src/com/android/launcher3/WidgetPreviewLoader.java +++ b/src/com/android/launcher3/WidgetPreviewLoader.java @@ -23,16 +23,19 @@ import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.RectF; +import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.AsyncTask; import android.os.CancellationSignal; import android.os.Process; import android.os.UserHandle; +import android.util.ArrayMap; import android.util.Log; import android.util.LongSparseArray; import android.util.Pair; import androidx.annotation.Nullable; +import androidx.annotation.UiThread; import com.android.launcher3.compat.AppWidgetManagerCompat; import com.android.launcher3.compat.UserManagerCompat; @@ -78,6 +81,9 @@ public class WidgetPreviewLoader { private final UserManagerCompat mUserManager; private final CacheDb mDb; + private final UserHandle mMyUser = Process.myUserHandle(); + private final ArrayMap mUserBadges = new ArrayMap<>(); + public WidgetPreviewLoader(Context context, IconCache iconCache) { mContext = context; mIconCache = iconCache; @@ -85,6 +91,51 @@ public class WidgetPreviewLoader { mDb = new CacheDb(context); } + /** + * Returns a drawable that can be used as a badge for the user or null. + */ + @UiThread + public Drawable getBadgeForUser(UserHandle user, int badgeSize) { + if (mMyUser.equals(user)) { + return null; + } + + Bitmap badgeBitmap = getUserBadge(user, badgeSize); + FastBitmapDrawable d = new FastBitmapDrawable(badgeBitmap); + d.setFilterBitmap(true); + d.setBounds(0, 0, badgeBitmap.getWidth(), badgeBitmap.getHeight()); + return d; + } + + private Bitmap getUserBadge(UserHandle user, int badgeSize) { + synchronized (mUserBadges) { + Bitmap badgeBitmap = mUserBadges.get(user); + if (badgeBitmap != null) { + return badgeBitmap; + } + + final Resources res = mContext.getResources(); + badgeBitmap = Bitmap.createBitmap(badgeSize, badgeSize, Bitmap.Config.ARGB_8888); + + Drawable drawable = mContext.getPackageManager().getUserBadgedDrawableForDensity( + new BitmapDrawable(res, badgeBitmap), user, + new Rect(0, 0, badgeSize, badgeSize), + 0); + if (drawable instanceof BitmapDrawable) { + badgeBitmap = ((BitmapDrawable) drawable).getBitmap(); + } else { + badgeBitmap.eraseColor(Color.TRANSPARENT); + Canvas c = new Canvas(badgeBitmap); + drawable.setBounds(0, 0, badgeSize, badgeSize); + drawable.draw(c); + c.setBitmap(null); + } + + mUserBadges.put(user, badgeBitmap); + return badgeBitmap; + } + } + /** * Generates the widget preview on {@link AsyncTask#THREAD_POOL_EXECUTOR}. Must be * called on UI thread @@ -106,8 +157,8 @@ public class WidgetPreviewLoader { public void refresh() { mDb.clear(); - } + /** * The DB holds the generated previews for various components. Previews can also have different * sizes (landscape vs portrait). diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index eca5d12d19..431a149b95 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -1471,9 +1471,6 @@ public class Workspace extends PagedView public DragView beginDragShared(View child, DragSource source, ItemInfo dragObject, DragPreviewProvider previewProvider, DragOptions dragOptions) { - if (TestProtocol.sDebugTracing) { - Log.d(TestProtocol.NO_CONTEXT_MENU, "beginDragShared"); - } float iconScale = 1f; if (child instanceof BubbleTextView) { Drawable icon = ((BubbleTextView) child).getIcon(); diff --git a/src/com/android/launcher3/WorkspaceItemInfo.java b/src/com/android/launcher3/WorkspaceItemInfo.java index 23795c5c0e..be907e5aa8 100644 --- a/src/com/android/launcher3/WorkspaceItemInfo.java +++ b/src/com/android/launcher3/WorkspaceItemInfo.java @@ -28,7 +28,7 @@ import androidx.annotation.NonNull; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.icons.IconCache; import com.android.launcher3.shortcuts.ShortcutKey; -import com.android.launcher3.uioverrides.UiFactory; +import com.android.launcher3.uioverrides.ApiWrapper; import com.android.launcher3.util.ContentWriter; import java.util.Arrays; @@ -140,7 +140,7 @@ public class WorkspaceItemInfo extends ItemInfoWithIcon { .put(Favorites.RESTORED, status); if (!usingLowResIcon()) { - writer.putIcon(iconBitmap, user); + writer.putIcon(bitmap, user); } if (iconResource != null) { writer.put(Favorites.ICON_PACKAGE, iconResource.packageName) @@ -192,7 +192,7 @@ public class WorkspaceItemInfo extends ItemInfoWithIcon { } disabledMessage = shortcutInfo.getDisabledMessage(); - Person[] persons = UiFactory.getPersons(shortcutInfo); + Person[] persons = ApiWrapper.getPersons(shortcutInfo); personKeys = persons.length == 0 ? Utilities.EMPTY_STRING_ARRAY : Arrays.stream(persons).map(Person::getKey).sorted().toArray(String[]::new); } diff --git a/src/com/android/launcher3/WorkspaceLayoutManager.java b/src/com/android/launcher3/WorkspaceLayoutManager.java index ea2d4d0995..0b9d60249e 100644 --- a/src/com/android/launcher3/WorkspaceLayoutManager.java +++ b/src/com/android/launcher3/WorkspaceLayoutManager.java @@ -39,7 +39,8 @@ public interface WorkspaceLayoutManager { default void addInScreenFromBind(View child, ItemInfo info) { int x = info.cellX; int y = info.cellY; - if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { + if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT + || info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) { int screenId = info.screenId; x = getHotseat().getCellXFromOrder(screenId); y = getHotseat().getCellYFromOrder(screenId); @@ -83,7 +84,8 @@ public interface WorkspaceLayoutManager { } final CellLayout layout; - if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { + if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT + || container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) { layout = getHotseat(); // Hide folder title in the hotseat diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java index 40c6b5f1b4..7a7e1fee6c 100644 --- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java +++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java @@ -38,7 +38,6 @@ import com.android.launcher3.LauncherState.ScaleAndTranslation; import com.android.launcher3.LauncherStateManager.AnimationConfig; import com.android.launcher3.anim.AnimatorSetBuilder; import com.android.launcher3.anim.PropertySetter; -import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.graphics.WorkspaceAndHotseatScrim; /** @@ -96,14 +95,13 @@ public class WorkspaceStateTransitionAnimation { propertySetter.setFloat(mWorkspace, SCALE_PROPERTY, mNewScale, scaleInterpolator); if (!hotseat.getRotationMode().isTransposed) { - // Set the hotseat's pivot point to match the workspace's, so that it scales together. - DragLayer dragLayer = mLauncher.getDragLayer(); - float[] workspacePivot = - new float[]{ mWorkspace.getPivotX(), mWorkspace.getPivotY() }; - dragLayer.getDescendantCoordRelativeToSelf(mWorkspace, workspacePivot); - dragLayer.mapCoordInSelfToDescendant(hotseat, workspacePivot); - hotseat.setPivotX(workspacePivot[0]); - hotseat.setPivotY(workspacePivot[1]); + // Set the hotseat's pivot point to match the workspace's, so that it scales + // together. Since both hotseat and workspace can move, transform the point + // manually instead of using dragLayer.getDescendantCoordRelativeToSelf and + // related methods. + hotseat.setPivotY(mWorkspace.getPivotY() + mWorkspace.getTop() - hotseat.getTop()); + hotseat.setPivotX(mWorkspace.getPivotX() + + mWorkspace.getLeft() - hotseat.getLeft()); } float hotseatScale = hotseatScaleAndTranslation.scale; Interpolator hotseatScaleInterpolator = builder.getInterpolator(ANIM_HOTSEAT_SCALE, diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java index 3836c9fdb4..08ce9c24f8 100644 --- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java +++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java @@ -2,8 +2,6 @@ package com.android.launcher3.allapps; import static com.android.launcher3.LauncherState.ALL_APPS_CONTENT; import static com.android.launcher3.LauncherState.ALL_APPS_HEADER_EXTRA; -import static com.android.launcher3.LauncherState.BACKGROUND_APP; -import static com.android.launcher3.LauncherState.HOTSEAT_ICONS; import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.LauncherState.VERTICAL_SWIPE_INDICATOR; import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE; @@ -134,15 +132,6 @@ public class AllAppsTransitionController implements StateHandler, OnDeviceProfil } else { mLauncher.getSystemUiController().updateUiState(UI_STATE_ALL_APPS, 0); } - - if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) { - // Translate hotseat with the shelf until reaching overview. - float overviewProgress = OVERVIEW.getVerticalProgress(mLauncher); - if (progress >= overviewProgress || mLauncher.isInState(BACKGROUND_APP)) { - float hotseatShift = (progress - overviewProgress) * mShiftRange; - mLauncher.getHotseat().setTranslationY(hotseatShift); - } - } } public float getProgress() { diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java index 4abdbefc1b..f4b705e99d 100644 --- a/src/com/android/launcher3/config/FeatureFlags.java +++ b/src/com/android/launcher3/config/FeatureFlags.java @@ -110,6 +110,10 @@ public final class FeatureFlags { "FAKE_LANDSCAPE_UI", false, "Rotate launcher UI instead of using transposed layout"); + public static final TogglableFlag FOLDER_NAME_SUGGEST = new TogglableFlag( + "FOLDER_NAME_SUGGEST", true, + "Suggests folder names instead of blank text."); + public static final TogglableFlag APP_SEARCH_IMPROVEMENTS = new TogglableFlag( "APP_SEARCH_IMPROVEMENTS", false, "Adds localized title and keyword search and ranking"); @@ -120,6 +124,16 @@ public final class FeatureFlags { public static final TogglableFlag ENABLE_QUICK_CAPTURE_GESTURE = new TogglableFlag( "ENABLE_QUICK_CAPTURE_GESTURE", false, "Swipe from right to left to quick capture"); + public static final TogglableFlag ASSISTANT_GIVES_LAUNCHER_FOCUS = new TogglableFlag( + "ASSISTANT_GIVES_LAUNCHER_FOCUS", false, + "Allow Launcher to handle nav bar gestures while Assistant is running over it"); + + public static final TogglableFlag ENABLE_HYBRID_HOTSEAT = new TogglableFlag( + "ENABLE_HYBRID_HOTSEAT", false, "Fill gaps in hotseat with predicted apps"); + + public static final TogglableFlag ENABLE_DEEP_SHORTCUT_ICON_CACHE = new TogglableFlag( + "ENABLE_DEEP_SHORTCUT_ICON_CACHE", true, "R/W deep shortcut in IconCache"); + public static void initialize(Context context) { // Avoid the disk read for user builds if (Utilities.IS_DEBUG_DEVICE) { diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java index cdc70611dd..8823bde624 100644 --- a/src/com/android/launcher3/dragndrop/DragLayer.java +++ b/src/com/android/launcher3/dragndrop/DragLayer.java @@ -57,7 +57,6 @@ import com.android.launcher3.graphics.OverviewScrim; import com.android.launcher3.graphics.RotationMode; import com.android.launcher3.graphics.WorkspaceAndHotseatScrim; import com.android.launcher3.keyboard.ViewGroupFocusHelper; -import com.android.launcher3.uioverrides.UiFactory; import com.android.launcher3.util.Thunk; import com.android.launcher3.views.BaseDragLayer; import com.android.launcher3.views.Transposable; @@ -121,7 +120,7 @@ public class DragLayer extends BaseDragLayer { } public void recreateControllers() { - mControllers = UiFactory.createTouchControllers(mActivity); + mControllers = mActivity.createTouchControllers(); } public ViewGroupFocusHelper getFocusIndicatorHelper() { @@ -477,14 +476,14 @@ public class DragLayer extends BaseDragLayer { public void onViewAdded(View child) { super.onViewAdded(child); updateChildIndices(); - UiFactory.onLauncherStateOrFocusChanged(mActivity); + mActivity.onDragLayerHierarchyChanged(); } @Override public void onViewRemoved(View child) { super.onViewRemoved(child); updateChildIndices(); - UiFactory.onLauncherStateOrFocusChanged(mActivity); + mActivity.onDragLayerHierarchyChanged(); } @Override diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java index f66d07e3d8..145885a458 100644 --- a/src/com/android/launcher3/dragndrop/DragView.java +++ b/src/com/android/launcher3/dragndrop/DragView.java @@ -216,8 +216,7 @@ public class DragView extends View implements LauncherStateManager.StateListener Object[] outObj = new Object[1]; int w = mBitmap.getWidth(); int h = mBitmap.getHeight(); - Drawable dr = Utilities.getFullDrawable(mLauncher, info, w, h, - false /* flattenDrawable */, outObj); + Drawable dr = Utilities.getFullDrawable(mLauncher, info, w, h, outObj); if (dr instanceof AdaptiveIconDrawable) { int blurMargin = (int) mLauncher.getResources() diff --git a/src/com/android/launcher3/dragndrop/PinItemDragListener.java b/src/com/android/launcher3/dragndrop/PinItemDragListener.java index 07eb0d60bc..869dd948ba 100644 --- a/src/com/android/launcher3/dragndrop/PinItemDragListener.java +++ b/src/com/android/launcher3/dragndrop/PinItemDragListener.java @@ -32,7 +32,6 @@ import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppWidgetProviderInfo; import com.android.launcher3.PendingAddItemInfo; -import com.android.launcher3.uioverrides.UiFactory; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.widget.PendingAddShortcutInfo; import com.android.launcher3.widget.PendingAddWidgetInfo; @@ -68,7 +67,7 @@ public class PinItemDragListener extends BaseItemDragListener { public boolean init(Launcher launcher, boolean alreadyOnHome) { super.init(launcher, alreadyOnHome); if (!alreadyOnHome) { - UiFactory.useFadeOutAnimationForLauncherStart(launcher, mCancelSignal); + launcher.useFadeOutAnimationForLauncherStart(mCancelSignal); } return false; } diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java index 65d593cdc3..22dda4140b 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -401,7 +401,6 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo mFolderName.setText(""); mFolderName.setHint(R.string.folder_hint_text); } - // In case any children didn't come across during loading, clean up the folder accordingly mFolderIcon.post(() -> { if (getItemCount() <= 1) { @@ -410,6 +409,22 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo }); } + + /** + * Show suggested folder title. + */ + public void showSuggestedTitle(CharSequence suggestName) { + if (FeatureFlags.FOLDER_NAME_SUGGEST.get() && mInfo.contents.size() == 2) { + if (!TextUtils.isEmpty(suggestName)) { + mFolderName.setHint(suggestName); + mFolderName.setText(suggestName); + mFolderName.showKeyboard(); + mInfo.title = suggestName; + } + animateOpen(); + } + } + /** * Creates a new UserFolder, inflated from R.layout.user_folder. * @@ -532,8 +547,6 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo // dropping. One resulting issue is that replaceFolderWithFinalItem() can be called twice. mDeleteFolderOnDropCompleted = false; - centerAboutIcon(); - AnimatorSet anim = new FolderAnimationManager(this, true /* isOpening */).getAnimator(); anim.addListener(new AnimatorListenerAdapter() { @Override @@ -592,7 +605,6 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo if (mDragController.isDragging()) { mDragController.forceTouchMove(); } - mContent.verifyVisibleHighResIcons(mContent.getNextPage()); } @@ -877,7 +889,6 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo // Reordering may have occured, and we need to save the new item locations. We do this once // at the end to prevent unnecessary database operations. updateItemLocationsInDatabaseBatch(); - // Use the item count to check for multi-page as the folder UI may not have // been refreshed yet. if (getItemCount() <= mContent.itemsPerPage()) { diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java index 38406395ab..fd6d1e38a9 100644 --- a/src/com/android/launcher3/folder/FolderIcon.java +++ b/src/com/android/launcher3/folder/FolderIcon.java @@ -58,12 +58,14 @@ import com.android.launcher3.Utilities; import com.android.launcher3.Workspace; import com.android.launcher3.WorkspaceItemInfo; import com.android.launcher3.anim.Interpolators; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.dot.FolderDotInfo; import com.android.launcher3.dragndrop.BaseItemDragListener; import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.dragndrop.DragView; import com.android.launcher3.icons.DotRenderer; import com.android.launcher3.touch.ItemClickHandler; +import com.android.launcher3.util.Executors; import com.android.launcher3.util.Thunk; import com.android.launcher3.views.IconLabelDotView; import com.android.launcher3.widget.PendingAddShortcutInfo; @@ -368,12 +370,17 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel if (!itemAdded) mPreviewItemManager.hidePreviewItem(index, true); final int finalIndex = index; - postDelayed(new Runnable() { - public void run() { - mPreviewItemManager.hidePreviewItem(finalIndex, false); - mFolder.showItem(item); - invalidate(); - } + + String[] suggestedNameOut = new String[1]; + if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) { + Executors.UI_HELPER_EXECUTOR.post(() -> mLauncher.getFolderNameProvider() + .getSuggestedFolderName(getContext(), mInfo.contents, suggestedNameOut)); + } + postDelayed(() -> { + mPreviewItemManager.hidePreviewItem(finalIndex, false); + mFolder.showItem(item); + invalidate(); + mFolder.showSuggestedTitle(suggestedNameOut[0]); }, DROP_IN_ANIMATION_DURATION); } else { addItem(item); diff --git a/src/com/android/launcher3/folder/FolderNameProvider.java b/src/com/android/launcher3/folder/FolderNameProvider.java new file mode 100644 index 0000000000..0a1221e696 --- /dev/null +++ b/src/com/android/launcher3/folder/FolderNameProvider.java @@ -0,0 +1,61 @@ +/* + * 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.folder; + +import android.content.ComponentName; +import android.content.Context; + +import com.android.launcher3.LauncherSettings.Favorites; +import com.android.launcher3.WorkspaceItemInfo; + +import java.util.ArrayList; + +/** + * Locates provider for the folder name. + */ +public class FolderNameProvider { + + /** + * Returns suggested folder name. + */ + public CharSequence getSuggestedFolderName(Context context, + ArrayList workspaceItemInfos, CharSequence[] suggestName) { + // Currently only run the algorithm on initial folder creation. + // For more than 2 items in the folder, the ranking algorithm for finding + // candidate folder name should be rewritten. + if (workspaceItemInfos.size() == 2) { + ComponentName cmp1 = workspaceItemInfos.get(0).getTargetComponent(); + ComponentName cmp2 = workspaceItemInfos.get(1).getTargetComponent(); + + String pkgName0 = cmp1 == null ? "" : cmp1.getPackageName(); + String pkgName1 = cmp2 == null ? "" : cmp2.getPackageName(); + // If the two icons are from the same package, + // then assign the main icon's name + if (pkgName0.equals(pkgName1)) { + WorkspaceItemInfo wInfo0 = workspaceItemInfos.get(0); + WorkspaceItemInfo wInfo1 = workspaceItemInfos.get(1); + if (workspaceItemInfos.get(0).itemType == Favorites.ITEM_TYPE_APPLICATION) { + suggestName[0] = wInfo0.title; + } else if (wInfo1.itemType == Favorites.ITEM_TYPE_APPLICATION) { + suggestName[0] = wInfo1.title; + } + return suggestName[0]; + // two icons are all shortcuts. Don't assign title + } + } + return suggestName[0]; + } +} diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java index 54b363e909..3b5fd5902c 100644 --- a/src/com/android/launcher3/folder/FolderPagedView.java +++ b/src/com/android/launcher3/folder/FolderPagedView.java @@ -277,6 +277,7 @@ public class FolderPagedView extends PagedView { page.removeAllViews(); pages.add(page); } + mOrganizer.setFolderInfo(mFolder.getInfo()); setupContentDimensions(itemCount); Iterator pageItr = pages.iterator(); @@ -285,7 +286,6 @@ public class FolderPagedView extends PagedView { int position = 0; int rank = 0; - mOrganizer.setFolderInfo(mFolder.getInfo()); for (int i = 0; i < itemCount; i++) { View v = list.size() > i ? list.get(i) : null; if (currentPage == null || position >= mOrganizer.getMaxItemsPerPage()) { diff --git a/src/com/android/launcher3/folder/PreviewItemManager.java b/src/com/android/launcher3/folder/PreviewItemManager.java index 2d817e6d2d..5b3a05ef10 100644 --- a/src/com/android/launcher3/folder/PreviewItemManager.java +++ b/src/com/android/launcher3/folder/PreviewItemManager.java @@ -16,10 +16,12 @@ package com.android.launcher3.folder; +import static com.android.launcher3.FastBitmapDrawable.newIcon; import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ENTER_INDEX; import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.EXIT_INDEX; import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW; import static com.android.launcher3.folder.FolderIcon.DROP_IN_ANIMATION_DURATION; +import static com.android.launcher3.graphics.PreloadIconDrawable.newPendingIcon; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -38,7 +40,6 @@ import androidx.annotation.NonNull; import com.android.launcher3.Launcher; import com.android.launcher3.Utilities; import com.android.launcher3.WorkspaceItemInfo; -import com.android.launcher3.graphics.DrawableFactory; import com.android.launcher3.graphics.PreloadIconDrawable; import java.util.ArrayList; @@ -66,7 +67,6 @@ public class PreviewItemManager { private final Context mContext; private final FolderIcon mIcon; - private final DrawableFactory mDrawableFactory; private final int mIconSize; // These variables are all associated with the drawing of the preview; they are stored @@ -94,7 +94,6 @@ public class PreviewItemManager { public PreviewItemManager(FolderIcon icon) { mContext = icon.getContext(); mIcon = icon; - mDrawableFactory = DrawableFactory.INSTANCE.get(mContext); mIconSize = Launcher.getLauncher(mContext).getDeviceProfile().folderChildIconSizePx; } @@ -395,11 +394,11 @@ public class PreviewItemManager { private void setDrawable(PreviewItemDrawingParams p, WorkspaceItemInfo item) { if (item.hasPromiseIconUi()) { - PreloadIconDrawable drawable = mDrawableFactory.newPendingIcon(mContext, item); + PreloadIconDrawable drawable = newPendingIcon(mContext, item); drawable.setLevel(item.getInstallProgress()); p.drawable = drawable; } else { - p.drawable = mDrawableFactory.newIcon(mContext, item); + p.drawable = newIcon(mContext, item); } p.drawable.setBounds(0, 0, mIconSize, mIconSize); p.item = item; diff --git a/src/com/android/launcher3/graphics/DrawableFactory.java b/src/com/android/launcher3/graphics/DrawableFactory.java deleted file mode 100644 index 837301fb89..0000000000 --- a/src/com/android/launcher3/graphics/DrawableFactory.java +++ /dev/null @@ -1,120 +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.graphics; - -import static com.android.launcher3.graphics.IconShape.getShapePath; -import static com.android.launcher3.util.MainThreadInitializedObject.forOverride; - -import android.content.Context; -import android.content.pm.ActivityInfo; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Rect; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.os.Process; -import android.os.UserHandle; -import android.util.ArrayMap; - -import androidx.annotation.UiThread; - -import com.android.launcher3.FastBitmapDrawable; -import com.android.launcher3.ItemInfoWithIcon; -import com.android.launcher3.R; -import com.android.launcher3.icons.BitmapInfo; -import com.android.launcher3.util.MainThreadInitializedObject; -import com.android.launcher3.util.ResourceBasedOverride; - -/** - * Factory for creating new drawables. - */ -public class DrawableFactory implements ResourceBasedOverride { - - public static final MainThreadInitializedObject INSTANCE = - forOverride(DrawableFactory.class, R.string.drawable_factory_class); - - protected final UserHandle mMyUser = Process.myUserHandle(); - protected final ArrayMap mUserBadges = new ArrayMap<>(); - - /** - * Returns a FastBitmapDrawable with the icon. - */ - public FastBitmapDrawable newIcon(Context context, ItemInfoWithIcon info) { - FastBitmapDrawable drawable = info.usingLowResIcon() - ? new PlaceHolderIconDrawable(info, getShapePath(), context) - : new FastBitmapDrawable(info); - drawable.setIsDisabled(info.isDisabled()); - return drawable; - } - - public FastBitmapDrawable newIcon(Context context, BitmapInfo info, ActivityInfo target) { - return info.isLowRes() - ? new PlaceHolderIconDrawable(info, getShapePath(), context) - : new FastBitmapDrawable(info); - } - - /** - * Returns a FastBitmapDrawable with the icon. - */ - public PreloadIconDrawable newPendingIcon(Context context, ItemInfoWithIcon info) { - return new PreloadIconDrawable(info, getShapePath(), context); - } - - /** - * Returns a drawable that can be used as a badge for the user or null. - */ - @UiThread - public Drawable getBadgeForUser(UserHandle user, Context context, int badgeSize) { - if (mMyUser.equals(user)) { - return null; - } - - Bitmap badgeBitmap = getUserBadge(user, context, badgeSize); - FastBitmapDrawable d = new FastBitmapDrawable(badgeBitmap); - d.setFilterBitmap(true); - d.setBounds(0, 0, badgeBitmap.getWidth(), badgeBitmap.getHeight()); - return d; - } - - protected synchronized Bitmap getUserBadge(UserHandle user, Context context, int badgeSize) { - Bitmap badgeBitmap = mUserBadges.get(user); - if (badgeBitmap != null) { - return badgeBitmap; - } - - final Resources res = context.getApplicationContext().getResources(); - badgeBitmap = Bitmap.createBitmap(badgeSize, badgeSize, Bitmap.Config.ARGB_8888); - - Drawable drawable = context.getPackageManager().getUserBadgedDrawableForDensity( - new BitmapDrawable(res, badgeBitmap), user, new Rect(0, 0, badgeSize, badgeSize), - 0); - if (drawable instanceof BitmapDrawable) { - badgeBitmap = ((BitmapDrawable) drawable).getBitmap(); - } else { - badgeBitmap.eraseColor(Color.TRANSPARENT); - Canvas c = new Canvas(badgeBitmap); - drawable.setBounds(0, 0, badgeSize, badgeSize); - drawable.draw(c); - c.setBitmap(null); - } - - mUserBadges.put(user, badgeBitmap); - return badgeBitmap; - } -} diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java index d7b845b58c..2badb6e2f3 100644 --- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java +++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java @@ -50,8 +50,8 @@ import com.android.launcher3.InsettableFrameLayout; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.R; -import com.android.launcher3.WorkspaceItemInfo; import com.android.launcher3.Utilities; +import com.android.launcher3.WorkspaceItemInfo; import com.android.launcher3.WorkspaceLayoutManager; import com.android.launcher3.allapps.SearchUiManager; import com.android.launcher3.config.FeatureFlags; @@ -105,7 +105,7 @@ public class LauncherPreviewRenderer implements Callable { Build.VERSION.SDK_INT); mWorkspaceItemInfo = new WorkspaceItemInfo(); - mWorkspaceItemInfo.applyFrom(iconInfo); + mWorkspaceItemInfo.bitmap = iconInfo; mWorkspaceItemInfo.intent = new Intent(); mWorkspaceItemInfo.contentDescription = mWorkspaceItemInfo.title = context.getString(R.string.label_application); diff --git a/src/com/android/launcher3/graphics/PlaceHolderIconDrawable.java b/src/com/android/launcher3/graphics/PlaceHolderIconDrawable.java index 23745cbd96..d347e8fdeb 100644 --- a/src/com/android/launcher3/graphics/PlaceHolderIconDrawable.java +++ b/src/com/android/launcher3/graphics/PlaceHolderIconDrawable.java @@ -17,14 +17,14 @@ package com.android.launcher3.graphics; import static androidx.core.graphics.ColorUtils.compositeColors; +import static com.android.launcher3.graphics.IconShape.getShapePath; + import android.content.Context; -import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Path; import android.graphics.Rect; import com.android.launcher3.FastBitmapDrawable; -import com.android.launcher3.ItemInfoWithIcon; import com.android.launcher3.R; import com.android.launcher3.icons.BitmapInfo; import com.android.launcher3.util.Themes; @@ -37,20 +37,12 @@ public class PlaceHolderIconDrawable extends FastBitmapDrawable { // Path in [0, 100] bounds. private final Path mProgressPath; - public PlaceHolderIconDrawable(BitmapInfo info, Path progressPath, Context context) { - this(info.icon, info.color, progressPath, context); - } + public PlaceHolderIconDrawable(BitmapInfo info, Context context) { + super(info); - public PlaceHolderIconDrawable(ItemInfoWithIcon info, Path progressPath, Context context) { - this(info.iconBitmap, info.iconColor, progressPath, context); - } - - protected PlaceHolderIconDrawable(Bitmap b, int iconColor, Path progressPath, Context context) { - super(b, iconColor); - - mProgressPath = progressPath; + mProgressPath = getShapePath(); mPaint.setColor(compositeColors( - Themes.getAttrColor(context, R.attr.loadingIconColor), iconColor)); + Themes.getAttrColor(context, R.attr.loadingIconColor), info.color)); } @Override diff --git a/src/com/android/launcher3/graphics/PreloadIconDrawable.java b/src/com/android/launcher3/graphics/PreloadIconDrawable.java index cc4c2efafb..b0e1db1019 100644 --- a/src/com/android/launcher3/graphics/PreloadIconDrawable.java +++ b/src/com/android/launcher3/graphics/PreloadIconDrawable.java @@ -18,6 +18,7 @@ package com.android.launcher3.graphics; import static com.android.launcher3.graphics.IconShape.DEFAULT_PATH_SIZE; +import static com.android.launcher3.graphics.IconShape.getShapePath; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -101,13 +102,10 @@ public class PreloadIconDrawable extends FastBitmapDrawable { private ObjectAnimator mCurrentAnim; - /** - * @param progressPath fixed path in the bounds [0, 0, 100, 100] representing a progress bar. - */ - public PreloadIconDrawable(ItemInfoWithIcon info, Path progressPath, Context context) { - super(info); + public PreloadIconDrawable(ItemInfoWithIcon info, Context context) { + super(info.bitmap); mItem = info; - mProgressPath = progressPath; + mProgressPath = getShapePath(); mScaledTrackPath = new Path(); mScaledProgressPath = new Path(); @@ -289,4 +287,11 @@ public class PreloadIconDrawable extends FastBitmapDrawable { } invalidateSelf(); } + + /** + * Returns a FastBitmapDrawable with the icon. + */ + public static PreloadIconDrawable newPendingIcon(Context context, ItemInfoWithIcon info) { + return new PreloadIconDrawable(info, context); + } } diff --git a/src/com/android/launcher3/icons/ClockDrawableWrapper.java b/src/com/android/launcher3/icons/ClockDrawableWrapper.java new file mode 100644 index 0000000000..b7dd092ab4 --- /dev/null +++ b/src/com/android/launcher3/icons/ClockDrawableWrapper.java @@ -0,0 +1,328 @@ +/* + * 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 android.annotation.TargetApi; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.drawable.AdaptiveIconDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; +import android.os.Build; +import android.os.Bundle; +import android.os.Process; +import android.os.SystemClock; +import android.util.Log; + +import com.android.launcher3.FastBitmapDrawable; + +import java.util.Calendar; +import java.util.concurrent.TimeUnit; + +/** + * Wrapper over {@link AdaptiveIconDrawable} to intercept icon flattening logic for dynamic + * clock icons + */ +@TargetApi(Build.VERSION_CODES.O) +public class ClockDrawableWrapper extends AdaptiveIconDrawable implements BitmapInfo.Extender { + + private static final String TAG = "ClockDrawableWrapper"; + + private static final boolean DISABLE_SECONDS = true; + + // Time after which the clock icon should check for an update. The actual invalidate + // will only happen in case of any change. + public static final long TICK_MS = DISABLE_SECONDS ? TimeUnit.MINUTES.toMillis(1) : 200L; + + private static final String LAUNCHER_PACKAGE = "com.android.launcher3"; + private static final String ROUND_ICON_METADATA_KEY = LAUNCHER_PACKAGE + + ".LEVEL_PER_TICK_ICON_ROUND"; + private static final String HOUR_INDEX_METADATA_KEY = LAUNCHER_PACKAGE + ".HOUR_LAYER_INDEX"; + private static final String MINUTE_INDEX_METADATA_KEY = LAUNCHER_PACKAGE + + ".MINUTE_LAYER_INDEX"; + private static final String SECOND_INDEX_METADATA_KEY = LAUNCHER_PACKAGE + + ".SECOND_LAYER_INDEX"; + private static final String DEFAULT_HOUR_METADATA_KEY = LAUNCHER_PACKAGE + + ".DEFAULT_HOUR"; + private static final String DEFAULT_MINUTE_METADATA_KEY = LAUNCHER_PACKAGE + + ".DEFAULT_MINUTE"; + private static final String DEFAULT_SECOND_METADATA_KEY = LAUNCHER_PACKAGE + + ".DEFAULT_SECOND"; + + /* Number of levels to jump per second for the second hand */ + private static final int LEVELS_PER_SECOND = 10; + + public static final int INVALID_VALUE = -1; + + private final AnimationInfo mAnimationInfo = new AnimationInfo(); + private int mTargetSdkVersion; + + public ClockDrawableWrapper(AdaptiveIconDrawable base) { + super(base.getBackground(), base.getForeground()); + } + + /** + * Loads and returns the wrapper from the provided package, or returns null + * if it is unable to load. + */ + public static ClockDrawableWrapper forPackage(Context context, String pkg, int iconDpi) { + try { + PackageManager pm = context.getPackageManager(); + ApplicationInfo appInfo = pm.getApplicationInfo(pkg, + PackageManager.MATCH_UNINSTALLED_PACKAGES | PackageManager.GET_META_DATA); + final Bundle metadata = appInfo.metaData; + if (metadata == null) { + return null; + } + int drawableId = metadata.getInt(ROUND_ICON_METADATA_KEY, 0); + if (drawableId == 0) { + return null; + } + + Drawable drawable = pm.getResourcesForApplication(appInfo).getDrawableForDensity( + drawableId, iconDpi).mutate(); + if (!(drawable instanceof AdaptiveIconDrawable)) { + return null; + } + + ClockDrawableWrapper wrapper = + new ClockDrawableWrapper((AdaptiveIconDrawable) drawable); + wrapper.mTargetSdkVersion = appInfo.targetSdkVersion; + AnimationInfo info = wrapper.mAnimationInfo; + + info.baseDrawableState = drawable.getConstantState(); + + info.hourLayerIndex = metadata.getInt(HOUR_INDEX_METADATA_KEY, INVALID_VALUE); + info.minuteLayerIndex = metadata.getInt(MINUTE_INDEX_METADATA_KEY, INVALID_VALUE); + info.secondLayerIndex = metadata.getInt(SECOND_INDEX_METADATA_KEY, INVALID_VALUE); + + info.defaultHour = metadata.getInt(DEFAULT_HOUR_METADATA_KEY, 0); + info.defaultMinute = metadata.getInt(DEFAULT_MINUTE_METADATA_KEY, 0); + info.defaultSecond = metadata.getInt(DEFAULT_SECOND_METADATA_KEY, 0); + + LayerDrawable foreground = (LayerDrawable) wrapper.getForeground(); + int layerCount = foreground.getNumberOfLayers(); + if (info.hourLayerIndex < 0 || info.hourLayerIndex >= layerCount) { + info.hourLayerIndex = INVALID_VALUE; + } + if (info.minuteLayerIndex < 0 || info.minuteLayerIndex >= layerCount) { + info.minuteLayerIndex = INVALID_VALUE; + } + if (info.secondLayerIndex < 0 || info.secondLayerIndex >= layerCount) { + info.secondLayerIndex = INVALID_VALUE; + } else if (DISABLE_SECONDS) { + foreground.setDrawable(info.secondLayerIndex, null); + info.secondLayerIndex = INVALID_VALUE; + } + return wrapper; + } catch (Exception e) { + Log.d(TAG, "Unable to load clock drawable info", e); + } + return null; + } + + @Override + public BitmapInfo getExtendedInfo(Bitmap bitmap, int color, BaseIconFactory iconFactory) { + iconFactory.disableColorExtraction(); + float [] scale = new float[1]; + AdaptiveIconDrawable background = new AdaptiveIconDrawable( + getBackground().getConstantState().newDrawable(), null); + BitmapInfo bitmapInfo = iconFactory.createBadgedIconBitmap(background, + Process.myUserHandle(), mTargetSdkVersion, false, scale); + + return new ClockBitmapInfo(bitmap, color, scale[0], mAnimationInfo, bitmapInfo.icon); + } + + @Override + public void prepareToDrawOnUi() { + mAnimationInfo.applyTime(Calendar.getInstance(), (LayerDrawable) getForeground()); + } + + private static class AnimationInfo { + + public ConstantState baseDrawableState; + + public int hourLayerIndex; + public int minuteLayerIndex; + public int secondLayerIndex; + public int defaultHour; + public int defaultMinute; + public int defaultSecond; + + boolean applyTime(Calendar time, LayerDrawable foregroundDrawable) { + time.setTimeInMillis(System.currentTimeMillis()); + + // We need to rotate by the difference from the default time if one is specified. + int convertedHour = (time.get(Calendar.HOUR) + (12 - defaultHour)) % 12; + int convertedMinute = (time.get(Calendar.MINUTE) + (60 - defaultMinute)) % 60; + int convertedSecond = (time.get(Calendar.SECOND) + (60 - defaultSecond)) % 60; + + boolean invalidate = false; + if (hourLayerIndex != INVALID_VALUE) { + final Drawable hour = foregroundDrawable.getDrawable(hourLayerIndex); + if (hour.setLevel(convertedHour * 60 + time.get(Calendar.MINUTE))) { + invalidate = true; + } + } + + if (minuteLayerIndex != INVALID_VALUE) { + final Drawable minute = foregroundDrawable.getDrawable(minuteLayerIndex); + if (minute.setLevel(time.get(Calendar.HOUR) * 60 + convertedMinute)) { + invalidate = true; + } + } + + if (secondLayerIndex != INVALID_VALUE) { + final Drawable second = foregroundDrawable.getDrawable(secondLayerIndex); + if (second.setLevel(convertedSecond * LEVELS_PER_SECOND)) { + invalidate = true; + } + } + + return invalidate; + } + } + + private static class ClockBitmapInfo extends BitmapInfo implements FastBitmapDrawable.Factory { + + public final float scale; + public final int offset; + public final AnimationInfo animInfo; + public final Bitmap mFlattenedBackground; + + ClockBitmapInfo(Bitmap icon, int color, float scale, AnimationInfo animInfo, + Bitmap background) { + super(icon, color); + this.scale = scale; + this.animInfo = animInfo; + this.offset = (int) Math.ceil(ShadowGenerator.BLUR_FACTOR * icon.getWidth()); + this.mFlattenedBackground = background; + } + + @Override + public FastBitmapDrawable newDrawable() { + return new ClockIconDrawable(this); + } + } + + private static class ClockIconDrawable extends FastBitmapDrawable implements Runnable { + + private final Calendar mTime = Calendar.getInstance(); + + private final ClockBitmapInfo mInfo; + + private final AdaptiveIconDrawable mFullDrawable; + private final LayerDrawable mForeground; + + ClockIconDrawable(ClockBitmapInfo clockInfo) { + super(clockInfo); + + mInfo = clockInfo; + + mFullDrawable = (AdaptiveIconDrawable) mInfo.animInfo.baseDrawableState.newDrawable(); + mForeground = (LayerDrawable) mFullDrawable.getForeground(); + } + + @Override + protected void onBoundsChange(Rect bounds) { + super.onBoundsChange(bounds); + mFullDrawable.setBounds(bounds); + } + + @Override + public void drawInternal(Canvas canvas, Rect bounds) { + if (mInfo == null) { + super.drawInternal(canvas, bounds); + return; + } + // draw the background that is already flattened to a bitmap + canvas.drawBitmap(mInfo.mFlattenedBackground, null, bounds, mPaint); + + // prepare and draw the foreground + mInfo.animInfo.applyTime(mTime, mForeground); + + canvas.scale(mInfo.scale, mInfo.scale, + bounds.exactCenterX() + mInfo.offset, bounds.exactCenterY() + mInfo.offset); + canvas.clipPath(mFullDrawable.getIconMask()); + mForeground.draw(canvas); + + reschedule(); + } + + @Override + protected void updateFilter() { + super.updateFilter(); + mFullDrawable.setColorFilter(mPaint.getColorFilter()); + } + + @Override + public void run() { + if (mInfo.animInfo.applyTime(mTime, mForeground)) { + invalidateSelf(); + } else { + reschedule(); + } + } + + @Override + public boolean setVisible(boolean visible, boolean restart) { + boolean result = super.setVisible(visible, restart); + if (visible) { + reschedule(); + } else { + unscheduleSelf(this); + } + return result; + } + + private void reschedule() { + if (!isVisible()) { + return; + } + + unscheduleSelf(this); + final long upTime = SystemClock.uptimeMillis(); + final long step = TICK_MS; /* tick every 200 ms */ + scheduleSelf(this, upTime - ((upTime % step)) + step); + } + + @Override + public ConstantState getConstantState() { + return new ClockConstantState(mInfo, isDisabled()); + } + + private static class ClockConstantState extends MyConstantState { + + private final ClockBitmapInfo mInfo; + + ClockConstantState(ClockBitmapInfo info, boolean isDisabled) { + super(info.icon, info.color, isDisabled); + mInfo = info; + } + + @Override + public FastBitmapDrawable newDrawable() { + ClockIconDrawable drawable = new ClockIconDrawable(mInfo); + drawable.setIsDisabled(mIsDisabled); + return drawable; + } + } + } +} diff --git a/src/com/android/launcher3/icons/ComponentWithLabel.java b/src/com/android/launcher3/icons/ComponentWithLabel.java index 832956d7ed..f7ee5f9329 100644 --- a/src/com/android/launcher3/icons/ComponentWithLabel.java +++ b/src/com/android/launcher3/icons/ComponentWithLabel.java @@ -57,10 +57,8 @@ public interface ComponentWithLabel { } @Override - public void loadIcon(Context context, - ComponentWithLabel object, BitmapInfo target) { - // Do not load icon. - target.icon = BitmapInfo.LOW_RES_ICON; + public BitmapInfo loadIcon(Context context, ComponentWithLabel object) { + return BitmapInfo.LOW_RES_INFO; } @Override diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java index ad01f9fa6a..4ac6ff4840 100644 --- a/src/com/android/launcher3/icons/IconCache.java +++ b/src/com/android/launcher3/icons/IconCache.java @@ -38,7 +38,6 @@ import android.util.Log; import androidx.annotation.NonNull; import com.android.launcher3.AppInfo; -import com.android.launcher3.IconProvider; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.ItemInfoWithIcon; import com.android.launcher3.LauncherFiles; @@ -52,6 +51,7 @@ import com.android.launcher3.icons.cache.CachingLogic; import com.android.launcher3.icons.cache.HandlerRunnable; import com.android.launcher3.model.PackageItemInfo; import com.android.launcher3.shortcuts.ShortcutKey; +import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.InstantAppResolver; import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.util.Preconditions; @@ -85,7 +85,7 @@ public class IconCache extends BaseIconCache { mLauncherApps = mContext.getSystemService(LauncherApps.class); mUserManager = UserManagerCompat.getInstance(mContext); mInstantAppResolver = InstantAppResolver.newInstance(mContext); - mIconProvider = IconProvider.INSTANCE.get(context); + mIconProvider = new IconProvider(context); } @Override @@ -165,7 +165,7 @@ public class IconCache extends BaseIconCache { CacheEntry entry = cacheLocked(application.componentName, application.user, () -> null, mLauncherActivityInfoCachingLogic, false, application.usingLowResIcon()); - if (entry.icon != null && !isDefaultIcon(entry.icon, application.user)) { + if (entry.bitmap != null && !isDefaultIcon(entry.bitmap, application.user)) { applyCacheEntry(entry, application); } } @@ -195,7 +195,7 @@ public class IconCache extends BaseIconCache { // null info means not installed, but if we have a component from the intent then // we should still look in the cache for restored app icons. if (info.getTargetComponent() == null) { - info.applyFrom(getDefaultIcon(info.user)); + info.bitmap = getDefaultIcon(info.user); info.title = ""; info.contentDescription = ""; } else { @@ -238,15 +238,11 @@ public class IconCache extends BaseIconCache { protected void applyCacheEntry(CacheEntry entry, ItemInfoWithIcon info) { info.title = Utilities.trim(entry.title); info.contentDescription = entry.contentDescription; - info.applyFrom((entry.icon == null) ? getDefaultIcon(info.user) : entry); + info.bitmap = (entry.bitmap == null) ? getDefaultIcon(info.user) : entry.bitmap; } public Drawable getFullResIcon(LauncherActivityInfo info) { - return getFullResIcon(info, true); - } - - public Drawable getFullResIcon(LauncherActivityInfo info, boolean flattenDrawable) { - return mIconProvider.getIcon(info, mIconDpi, flattenDrawable); + return mIconProvider.getIcon(info, mIconDpi); } public void updateSessionCache(PackageUserKey key, PackageInstaller.SessionInfo info) { @@ -259,6 +255,15 @@ public class IconCache extends BaseIconCache { + ",flags_asi:" + FeatureFlags.APP_SEARCH_IMPROVEMENTS.get(); } + @Override + protected boolean getEntryFromDB(ComponentKey cacheKey, CacheEntry entry, boolean lowRes) { + if (mIconProvider.isClockIcon(cacheKey)) { + // For clock icon, we always load the dynamic icon + return false; + } + return super.getEntryFromDB(cacheKey, entry, lowRes); + } + public static abstract class IconLoadRequest extends HandlerRunnable { IconLoadRequest(Handler handler, Runnable endRunnable) { super(handler, endRunnable); diff --git a/src/com/android/launcher3/icons/IconProvider.java b/src/com/android/launcher3/icons/IconProvider.java new file mode 100644 index 0000000000..26b7eae379 --- /dev/null +++ b/src/com/android/launcher3/icons/IconProvider.java @@ -0,0 +1,251 @@ +/* + * 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 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.PackageManager; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.os.Handler; +import android.os.Process; +import android.os.UserHandle; +import android.text.TextUtils; +import android.util.Log; + +import com.android.launcher3.R; +import com.android.launcher3.compat.UserManagerCompat; +import com.android.launcher3.icons.BitmapInfo.Extender; +import com.android.launcher3.util.ComponentKey; +import com.android.launcher3.util.SafeCloseable; + +import java.util.Calendar; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; + +/** + * Class to handle icon loading from different packages + */ +public class IconProvider { + + private static final String TAG = "IconProvider"; + private static final boolean DEBUG = false; + + private static final String ICON_METADATA_KEY_PREFIX = ".dynamic_icons"; + + private static final String SYSTEM_STATE_SEPARATOR = " "; + + // Default value returned if there are problems getting resources. + private static final int NO_ID = 0; + + private static final BiFunction LAI_LOADER = + LauncherActivityInfo::getIcon; + + private static final BiFunction AI_LOADER = + ActivityInfo::loadUnbadgedIcon; + + + private final Context mContext; + private final ComponentName mCalendar; + private final ComponentName mClock; + + public IconProvider(Context context) { + mContext = context; + mCalendar = parseComponentOrNull(context, R.string.calendar_component_name); + mClock = parseComponentOrNull(context, R.string.clock_component_name); + } + + /** + * Adds any modification to the provided systemState for dynamic icons. This system state + * is used by caches to check for icon invalidation. + */ + public String getSystemStateForPackage(String systemState, String packageName) { + if (mCalendar != null && mCalendar.getPackageName().equals(packageName)) { + return systemState + SYSTEM_STATE_SEPARATOR + getDay(); + } else { + return systemState; + } + } + + /** + * Loads the icon for the provided LauncherActivityInfo such that it can be drawn directly + * on the UI + */ + public Drawable getIconForUI(LauncherActivityInfo info, int iconDpi) { + Drawable icon = getIcon(info, iconDpi); + if (icon instanceof BitmapInfo.Extender) { + ((Extender) icon).prepareToDrawOnUi(); + } + return icon; + } + + /** + * Loads the icon for the provided LauncherActivityInfo + */ + public Drawable getIcon(LauncherActivityInfo info, int iconDpi) { + return getIcon(info.getApplicationInfo().packageName, info.getUser(), + info, iconDpi, LAI_LOADER); + } + + /** + * Loads the icon for the provided activity info + */ + public Drawable getIcon(ActivityInfo info, UserHandle user) { + return getIcon(info.applicationInfo.packageName, user, info, mContext.getPackageManager(), + AI_LOADER); + } + + private Drawable getIcon(String packageName, UserHandle user, T obj, P param, + BiFunction loader) { + Drawable icon = null; + if (mCalendar != null && mCalendar.getPackageName().equals(packageName)) { + icon = loadCalendarDrawable(0); + } else if (mClock != null + && mClock.getPackageName().equals(packageName) + && Process.myUserHandle().equals(user)) { + icon = loadClockDrawable(0); + } + return icon == null ? loader.apply(obj, param) : icon; + } + + private Drawable loadCalendarDrawable(int iconDpi) { + PackageManager pm = mContext.getPackageManager(); + try { + final Bundle metadata = pm.getActivityInfo( + mCalendar, + PackageManager.GET_UNINSTALLED_PACKAGES | PackageManager.GET_META_DATA) + .metaData; + final Resources resources = pm.getResourcesForApplication(mCalendar.getPackageName()); + final int id = getDynamicIconId(metadata, resources); + if (id != NO_ID) { + if (DEBUG) Log.d(TAG, "Got icon #" + id); + return resources.getDrawableForDensity(id, iconDpi, null /* theme */); + } + } catch (PackageManager.NameNotFoundException e) { + if (DEBUG) { + Log.d(TAG, "Could not get activityinfo or resources for package: " + + mCalendar.getPackageName()); + } + } + return null; + } + + private Drawable loadClockDrawable(int iconDpi) { + return ClockDrawableWrapper.forPackage(mContext, mClock.getPackageName(), iconDpi); + } + + protected boolean isClockIcon(ComponentKey key) { + return mClock != null && mClock.equals(key.componentName) + && Process.myUserHandle().equals(key.user); + } + + /** + * @param metadata metadata of the default activity of Calendar + * @param resources from the Calendar package + * @return the resource id for today's Calendar icon; 0 if resources cannot be found. + */ + private int getDynamicIconId(Bundle metadata, Resources resources) { + if (metadata == null) { + return NO_ID; + } + String key = mCalendar.getPackageName() + ICON_METADATA_KEY_PREFIX; + final int arrayId = metadata.getInt(key, NO_ID); + if (arrayId == NO_ID) { + return NO_ID; + } + try { + return resources.obtainTypedArray(arrayId).getResourceId(getDay(), NO_ID); + } catch (Resources.NotFoundException e) { + if (DEBUG) { + Log.d(TAG, "package defines '" + key + "' but corresponding array not found"); + } + return NO_ID; + } + } + + /** + * @return Today's day of the month, zero-indexed. + */ + private int getDay() { + return Calendar.getInstance().get(Calendar.DAY_OF_MONTH) - 1; + } + + + /** + * Registers a callback to listen for calendar icon changes. + * The callback receives the packageName for the calendar icon + */ + public static SafeCloseable registerIconChangeListener(Context context, + BiConsumer callback, Handler handler) { + ComponentName calendar = parseComponentOrNull(context, R.string.calendar_component_name); + ComponentName clock = parseComponentOrNull(context, R.string.clock_component_name); + + if (calendar == null && clock == null) { + return () -> { }; + } + + BroadcastReceiver receiver = new DateTimeChangeReceiver(callback); + final IntentFilter filter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED); + if (calendar != null) { + filter.addAction(Intent.ACTION_TIME_CHANGED); + filter.addAction(Intent.ACTION_DATE_CHANGED); + } + context.registerReceiver(receiver, filter, null, handler); + + return () -> context.unregisterReceiver(receiver); + } + + private static class DateTimeChangeReceiver extends BroadcastReceiver { + + private final BiConsumer mCallback; + + DateTimeChangeReceiver(BiConsumer callback) { + mCallback = callback; + } + + @Override + public void onReceive(Context context, Intent intent) { + if (Intent.ACTION_TIMEZONE_CHANGED.equals(intent.getAction())) { + ComponentName clock = parseComponentOrNull(context, R.string.clock_component_name); + if (clock != null) { + mCallback.accept(clock.getPackageName(), Process.myUserHandle()); + } + } + + ComponentName calendar = + parseComponentOrNull(context, R.string.calendar_component_name); + if (calendar != null) { + for (UserHandle user : UserManagerCompat.getInstance(context).getUserProfiles()) { + mCallback.accept(calendar.getPackageName(), user); + } + } + + } + } + + private static ComponentName parseComponentOrNull(Context context, int resId) { + String cn = context.getString(resId); + return TextUtils.isEmpty(cn) ? null : ComponentName.unflattenFromString(cn); + + } +} diff --git a/src/com/android/launcher3/icons/LauncherActivityCachingLogic.java b/src/com/android/launcher3/icons/LauncherActivityCachingLogic.java index f9a94daf53..93de35a88a 100644 --- a/src/com/android/launcher3/icons/LauncherActivityCachingLogic.java +++ b/src/com/android/launcher3/icons/LauncherActivityCachingLogic.java @@ -20,7 +20,6 @@ import android.content.Context; import android.content.pm.LauncherActivityInfo; import android.os.UserHandle; -import com.android.launcher3.IconProvider; import com.android.launcher3.R; import com.android.launcher3.icons.cache.CachingLogic; import com.android.launcher3.util.ResourceBasedOverride; @@ -55,13 +54,11 @@ public class LauncherActivityCachingLogic } @Override - public void loadIcon(Context context, LauncherActivityInfo object, - BitmapInfo target) { - LauncherIcons li = LauncherIcons.obtain(context); - li.createBadgedIconBitmap( - IconProvider.INSTANCE.get(context) - .getIcon(object, li.mFillResIconDpi, true /* flattenDrawable */), - object.getUser(), object.getApplicationInfo().targetSdkVersion).applyTo(target); - li.recycle(); + public BitmapInfo loadIcon(Context context, LauncherActivityInfo object) { + try (LauncherIcons li = LauncherIcons.obtain(context)) { + return li.createBadgedIconBitmap(new IconProvider(context) + .getIcon(object, li.mFillResIconDpi), + object.getUser(), object.getApplicationInfo().targetSdkVersion); + } } } diff --git a/src/com/android/launcher3/icons/LauncherIcons.java b/src/com/android/launcher3/icons/LauncherIcons.java index 44c67e9a7d..02e2d7052e 100644 --- a/src/com/android/launcher3/icons/LauncherIcons.java +++ b/src/com/android/launcher3/icons/LauncherIcons.java @@ -32,7 +32,7 @@ import com.android.launcher3.FastBitmapDrawable; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.ItemInfoWithIcon; import com.android.launcher3.LauncherAppState; -import com.android.launcher3.R; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.graphics.IconShape; import com.android.launcher3.model.PackageItemInfo; import com.android.launcher3.shortcuts.DeepShortcutManager; @@ -46,8 +46,6 @@ import java.util.function.Supplier; */ public class LauncherIcons extends BaseIconFactory implements AutoCloseable { - private static final String EXTRA_BADGEPKG = "badge_package"; - private static final Object sPoolSync = new Object(); private static LauncherIcons sPool; private static int sPoolId = 0; @@ -116,7 +114,6 @@ public class LauncherIcons extends BaseIconFactory implements AutoCloseable { } // below methods should also migrate to BaseIconFactory - @WorkerThread public BitmapInfo createShortcutIcon(ShortcutInfo shortcutInfo) { return createShortcutIcon(shortcutInfo, true /* badged */); } @@ -126,24 +123,20 @@ public class LauncherIcons extends BaseIconFactory implements AutoCloseable { return createShortcutIcon(shortcutInfo, badged, null); } - @WorkerThread public BitmapInfo createShortcutIcon(ShortcutInfo shortcutInfo, boolean badged, @Nullable Supplier fallbackIconProvider) { - return createShortcutIcon(shortcutInfo, badged, true, fallbackIconProvider); + if (FeatureFlags.ENABLE_DEEP_SHORTCUT_ICON_CACHE.get()) { + return createShortcutIconCached(shortcutInfo, badged, true, fallbackIconProvider); + } else { + return createShortcutIconLegacy(shortcutInfo, badged, fallbackIconProvider); + } } - @WorkerThread - public BitmapInfo createShortcutIcon(ShortcutInfo shortcutInfo, boolean badged, - boolean useCache, @Nullable Supplier fallbackIconProvider) { + public BitmapInfo createShortcutIconLegacy(ShortcutInfo shortcutInfo, boolean badged, + @Nullable Supplier fallbackIconProvider) { + Drawable unbadgedDrawable = DeepShortcutManager.getInstance(mContext) + .getShortcutIconDrawable(shortcutInfo, mFillResIconDpi); IconCache cache = LauncherAppState.getInstance(mContext).getIconCache(); - final BitmapInfo bitmapInfo; - if (useCache) { - bitmapInfo = cache.getDeepShortcutTitleAndIcon(shortcutInfo); - } else { - bitmapInfo = new BitmapInfo(); - new ShortcutCachingLogic().loadIcon(mContext, shortcutInfo, bitmapInfo); - } - final Bitmap unbadgedBitmap; if (bitmapInfo.icon != null) { unbadgedBitmap = bitmapInfo.icon; @@ -151,37 +144,68 @@ public class LauncherIcons extends BaseIconFactory implements AutoCloseable { if (fallbackIconProvider != null) { // Fallback icons are already badged and with appropriate shadow ItemInfoWithIcon fullIcon = fallbackIconProvider.get(); - if (fullIcon != null && fullIcon.iconBitmap != null) { - BitmapInfo result = new BitmapInfo(); - result.icon = fullIcon.iconBitmap; - result.color = fullIcon.iconColor; - return result; + if (fullIcon != null && fullIcon.bitmap != null) { + return fullIcon.bitmap; } } unbadgedBitmap = cache.getDefaultIcon(Process.myUserHandle()).icon; } - BitmapInfo result = new BitmapInfo(); if (!badged) { - result.color = Themes.getColorAccent(mContext); - result.icon = unbadgedBitmap; - return result; + return BitmapInfo.of(unbadgedBitmap, Themes.getColorAccent(mContext)); } final Bitmap unbadgedfinal = unbadgedBitmap; final ItemInfoWithIcon badge = getShortcutInfoBadge(shortcutInfo, cache); - result.color = badge.iconColor; - result.icon = BitmapRenderer.createHardwareBitmap(mIconBitmapSize, mIconBitmapSize, (c) -> { + Bitmap icon = BitmapRenderer.createHardwareBitmap(mIconBitmapSize, mIconBitmapSize, (c) -> { getShadowGenerator().recreateIcon(unbadgedfinal, c); - badgeWithDrawable(c, new FastBitmapDrawable(badge)); + badgeWithDrawable(c, new FastBitmapDrawable(badge.bitmap)); }); - return result; + return BitmapInfo.of(icon, badge.bitmap.color); + } + + @WorkerThread + public BitmapInfo createShortcutIconCached(ShortcutInfo shortcutInfo, boolean badged, + boolean useCache, @Nullable Supplier fallbackIconProvider) { + IconCache cache = LauncherAppState.getInstance(mContext).getIconCache(); + final BitmapInfo bitmapInfo; + if (useCache) { + bitmapInfo = cache.getDeepShortcutTitleAndIcon(shortcutInfo).bitmap; + } else { + bitmapInfo = new ShortcutCachingLogic().loadIcon(mContext, shortcutInfo); + } + final Bitmap unbadgedBitmap; + if (bitmapInfo.icon != null) { + unbadgedBitmap = bitmapInfo.icon; + } else { + if (fallbackIconProvider != null) { + // Fallback icons are already badged and with appropriate shadow + ItemInfoWithIcon fullIcon = fallbackIconProvider.get(); + if (fullIcon != null && fullIcon.bitmap != null) { + return fullIcon.bitmap; + } + } + unbadgedBitmap = cache.getDefaultIcon(Process.myUserHandle()).icon; + } + + if (!badged) { + return BitmapInfo.of(unbadgedBitmap, Themes.getColorAccent(mContext)); + } + + final Bitmap unbadgedfinal = unbadgedBitmap; + final ItemInfoWithIcon badge = getShortcutInfoBadge(shortcutInfo, cache); + + Bitmap icon = BitmapRenderer.createHardwareBitmap(mIconBitmapSize, mIconBitmapSize, (c) -> { + getShadowGenerator().recreateIcon(unbadgedfinal, c); + badgeWithDrawable(c, new FastBitmapDrawable(badge.bitmap)); + }); + return BitmapInfo.of(icon, badge.bitmap.color); } public ItemInfoWithIcon getShortcutInfoBadge(ShortcutInfo shortcutInfo, IconCache cache) { ComponentName cn = shortcutInfo.getActivity(); - String badgePkg = getBadgePackage(shortcutInfo); + String badgePkg = shortcutInfo.getPackage(); boolean hasBadgePkgSet = !badgePkg.equals(shortcutInfo.getPackage()); if (cn != null && !hasBadgePkgSet) { // Get the app info for the source activity. @@ -199,14 +223,4 @@ public class LauncherIcons extends BaseIconFactory implements AutoCloseable { return pkgInfo; } } - - private String getBadgePackage(ShortcutInfo si) { - String whitelistedPkg = mContext.getString(R.string.shortcutinfo_badgepkg_whitelist); - if (whitelistedPkg.equals(si.getPackage()) - && si.getExtras() != null - && si.getExtras().containsKey(EXTRA_BADGEPKG)) { - return si.getExtras().getString(EXTRA_BADGEPKG); - } - return si.getPackage(); - } } diff --git a/src/com/android/launcher3/icons/ShortcutCachingLogic.java b/src/com/android/launcher3/icons/ShortcutCachingLogic.java index 5d696fd6f3..5c214704a3 100644 --- a/src/com/android/launcher3/icons/ShortcutCachingLogic.java +++ b/src/com/android/launcher3/icons/ShortcutCachingLogic.java @@ -23,10 +23,14 @@ import android.content.pm.ShortcutInfo; import android.graphics.drawable.Drawable; import android.os.UserHandle; +import androidx.annotation.NonNull; + import com.android.launcher3.LauncherAppState; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.icons.cache.CachingLogic; import com.android.launcher3.shortcuts.DeepShortcutManager; import com.android.launcher3.shortcuts.ShortcutKey; +import com.android.launcher3.util.Themes; /** * Caching logic for shortcuts. @@ -48,20 +52,23 @@ public class ShortcutCachingLogic implements CachingLogic { return info.getShortLabel(); } + @NonNull @Override - public void loadIcon(Context context, ShortcutInfo info, BitmapInfo target) { - LauncherIcons li = LauncherIcons.obtain(context); - Drawable unbadgedDrawable = DeepShortcutManager.getInstance(context) - .getShortcutIconDrawable(info, LauncherAppState.getIDP(context).fillResIconDpi); - if (unbadgedDrawable != null) { - target.icon = li.createScaledBitmapWithoutShadow(unbadgedDrawable, 0); + public BitmapInfo loadIcon(Context context, ShortcutInfo info) { + try (LauncherIcons li = LauncherIcons.obtain(context)) { + Drawable unbadgedDrawable = DeepShortcutManager.getInstance(context) + .getShortcutIconDrawable(info, LauncherAppState.getIDP(context).fillResIconDpi); + if (unbadgedDrawable == null) return BitmapInfo.LOW_RES_INFO; + return new BitmapInfo(li.createScaledBitmapWithoutShadow( + unbadgedDrawable, 0), Themes.getColorAccent(context)); } - li.recycle(); } @Override public long getLastUpdatedTime(ShortcutInfo shortcutInfo, PackageInfo info) { - if (shortcutInfo == null) return info.lastUpdateTime; + if (shortcutInfo == null || !FeatureFlags.ENABLE_DEEP_SHORTCUT_ICON_CACHE.get()) { + return info.lastUpdateTime; + } return Math.max(shortcutInfo.getLastChangedTimestamp(), info.lastUpdateTime); } diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java index 227bb22e2e..fa0fe1be1f 100644 --- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java +++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java @@ -76,6 +76,11 @@ public class AddWorkspaceItemsTask extends BaseModelUpdateTask { if (shortcutExists(dataModel, item.getIntent(), item.user)) { continue; } + + // b/139663018 Short-circuit this logic if the icon is a system app + if (PackageManagerHelper.isSystemApp(app.getContext(), item.getIntent())) { + continue; + } } if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { @@ -117,25 +122,39 @@ public class AddWorkspaceItemsTask extends BaseModelUpdateTask { } SessionInfo sessionInfo = packageInstaller.getActiveSessionInfo(item.user, packageName); + List activities = launcherApps + .getActivityList(packageName, item.user); + boolean hasActivity = activities != null && !activities.isEmpty(); + if (sessionInfo == null) { - List activities = launcherApps - .getActivityList(packageName, item.user); - if (activities != null && !activities.isEmpty()) { - // App was installed while launcher was in the background. - itemInfo = new AppInfo(app.getContext(), activities.get(0), item.user) - .makeWorkspaceItem(); - WorkspaceItemInfo wii = (WorkspaceItemInfo) itemInfo; - wii.title = ""; - wii.applyFrom(app.getIconCache().getDefaultIcon(item.user)); - app.getIconCache().getTitleAndIcon(wii, - ((WorkspaceItemInfo) itemInfo).usingLowResIcon()); - } else { + if (!hasActivity) { // Session was cancelled, do not add. continue; } } else { workspaceInfo.setInstallProgress((int) sessionInfo.getProgress()); } + + if (hasActivity) { + // App was installed while launcher was in the background, + // or app was already installed for another user. + itemInfo = new AppInfo(app.getContext(), activities.get(0), item.user) + .makeWorkspaceItem(); + + if (shortcutExists(dataModel, itemInfo.getIntent(), itemInfo.user)) { + // We need this additional check here since we treat all auto added + // workspace items as promise icons. At this point we now have the + // correct intent to compare against existing workspace icons. + // Icon already exists on the workspace and should not be auto-added. + continue; + } + + WorkspaceItemInfo wii = (WorkspaceItemInfo) itemInfo; + wii.title = ""; + wii.bitmap = app.getIconCache().getDefaultIcon(item.user); + app.getIconCache().getTitleAndIcon(wii, + ((WorkspaceItemInfo) itemInfo).usingLowResIcon()); + } } // Add the shortcut to the db diff --git a/src/com/android/launcher3/model/BaseLoaderResults.java b/src/com/android/launcher3/model/BaseLoaderResults.java index 0a4f00582d..a00a6bd3a5 100644 --- a/src/com/android/launcher3/model/BaseLoaderResults.java +++ b/src/com/android/launcher3/model/BaseLoaderResults.java @@ -300,8 +300,8 @@ public abstract class BaseLoaderResults { public LooperIdleLock newIdleLock(Object lock) { LooperIdleLock idleLock = new LooperIdleLock(lock, Looper.getMainLooper()); - // If we are not binding, there is no reason to wait for idle. - if (mCallbacks.get() == null) { + // If we are not binding or if the main looper is already idle, there is no reason to wait + if (mCallbacks.get() == null || Looper.getMainLooper().getQueue().isIdle()) { idleLock.queueIdle(); } return idleLock; diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java index 6154e7ef84..95268d088e 100644 --- a/src/com/android/launcher3/model/LoaderCursor.java +++ b/src/com/android/launcher3/model/LoaderCursor.java @@ -153,7 +153,7 @@ public class LoaderCursor extends CursorWrapper { info.title = getTitle(); // the fallback icon if (!loadIcon(info)) { - info.applyFrom(mIconCache.getDefaultIcon(info.user)); + info.bitmap = mIconCache.getDefaultIcon(info.user); } // TODO: If there's an explicit component and we can't install that, delete it. @@ -183,7 +183,7 @@ public class LoaderCursor extends CursorWrapper { info.iconResource.resourceName = resourceName; BitmapInfo iconInfo = li.createIconBitmap(info.iconResource); if (iconInfo != null) { - info.applyFrom(iconInfo); + info.bitmap = iconInfo; return true; } } @@ -192,7 +192,7 @@ public class LoaderCursor extends CursorWrapper { // Failed to load from resource, try loading from DB. byte[] data = getBlob(iconIndex); try { - info.applyFrom(li.createIconBitmap(BitmapFactory.decodeByteArray(data, 0, data.length))); + info.bitmap = li.createIconBitmap(BitmapFactory.decodeByteArray(data, 0, data.length)); return true; } catch (Exception e) { Log.e(TAG, "Failed to decode byte array for info " + info, e); @@ -273,7 +273,7 @@ public class LoaderCursor extends CursorWrapper { info.intent = newIntent; mIconCache.getTitleAndIcon(info, lai, useLowResIcon); - if (mIconCache.isDefaultIcon(info.iconBitmap, user)) { + if (mIconCache.isDefaultIcon(info.bitmap, user)) { loadIcon(info); } diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java index 81b701d716..5893a08d30 100644 --- a/src/com/android/launcher3/model/LoaderTask.java +++ b/src/com/android/launcher3/model/LoaderTask.java @@ -181,16 +181,7 @@ public class LoaderTask implements Runnable { } Object traceToken = TraceHelper.INSTANCE.beginSection(TAG); - TimingLogger logger = TestProtocol.sDebugTracing ? - new TimingLogger(TAG, "run") { - @Override - public void addSplit(String splitLabel) { - super.addSplit(splitLabel); - Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE, - "LoaderTask.addSplit " + splitLabel); - } - } - : new TimingLogger(TAG, "run"); + TimingLogger logger = new TimingLogger(TAG, "run"); try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) { List allShortcuts = new ArrayList<>(); loadWorkspace(allShortcuts); @@ -225,10 +216,12 @@ public class LoaderTask implements Runnable { mApp.getModel()::onPackageIconsUpdated); logger.addSplit("update icon cache"); - verifyNotStopped(); - logger.addSplit("save shortcuts in icon cache"); - updateHandler.updateIcons(allShortcuts, new ShortcutCachingLogic(), - mApp.getModel()::onPackageIconsUpdated); + if (FeatureFlags.ENABLE_DEEP_SHORTCUT_ICON_CACHE.get()) { + verifyNotStopped(); + logger.addSplit("save shortcuts in icon cache"); + updateHandler.updateIcons(allShortcuts, new ShortcutCachingLogic(), + mApp.getModel()::onPackageIconsUpdated); + } // Take a break waitForIdle(); @@ -243,10 +236,12 @@ public class LoaderTask implements Runnable { mResults.bindDeepShortcuts(); logger.addSplit("bindDeepShortcuts"); - verifyNotStopped(); - logger.addSplit("save deep shortcuts in icon cache"); - updateHandler.updateIcons(allDeepShortcuts, - new ShortcutCachingLogic(), (pkgs, user) -> { }); + if (FeatureFlags.ENABLE_DEEP_SHORTCUT_ICON_CACHE.get()) { + verifyNotStopped(); + logger.addSplit("save deep shortcuts in icon cache"); + updateHandler.updateIcons(allDeepShortcuts, + new ShortcutCachingLogic(), (pkgs, user) -> { }); + } // Take a break waitForIdle(); @@ -536,8 +531,9 @@ public class LoaderTask implements Runnable { // use the last saved icon instead of the default. Supplier fallbackIconProvider = () -> c.loadIcon(finalInfo, li) ? finalInfo : null; - info.applyFrom(li.createShortcutIcon(pinnedShortcut, - true /* badged */, fallbackIconProvider)); + info.bitmap = li.createShortcutIcon( + pinnedShortcut, true /* badged */, + fallbackIconProvider); li.recycle(); if (pmHelper.isAppSuspended( pinnedShortcut.getPackage(), info.user)) { diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java index db63b7c251..1e614bda95 100644 --- a/src/com/android/launcher3/model/PackageUpdatedTask.java +++ b/src/com/android/launcher3/model/PackageUpdatedTask.java @@ -189,7 +189,7 @@ public class PackageUpdatedTask extends BaseModelUpdateTask { BitmapInfo iconInfo = li.createIconBitmap(si.iconResource); li.recycle(); if (iconInfo != null) { - si.applyFrom(iconInfo); + si.bitmap = iconInfo; infoUpdated = true; } } diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.java b/src/com/android/launcher3/model/ShortcutsChangedTask.java index 6c358b1709..05225d4fa0 100644 --- a/src/com/android/launcher3/model/ShortcutsChangedTask.java +++ b/src/com/android/launcher3/model/ShortcutsChangedTask.java @@ -93,8 +93,8 @@ public class ShortcutsChangedTask extends BaseModelUpdateTask { // If the shortcut is pinned but no longer has an icon in the system, // keep the current icon instead of reverting to the default icon. LauncherIcons li = LauncherIcons.obtain(context); - workspaceItemInfo.applyFrom(li.createShortcutIcon(fullDetails, true, - () -> workspaceItemInfo)); + workspaceItemInfo.bitmap = li.createShortcutIcon( + fullDetails, true, () -> workspaceItemInfo); li.recycle(); updatedWorkspaceItemInfos.add(workspaceItemInfo); } diff --git a/src/com/android/launcher3/model/UserLockStateChangedTask.java b/src/com/android/launcher3/model/UserLockStateChangedTask.java index 4b773d7204..db1c307480 100644 --- a/src/com/android/launcher3/model/UserLockStateChangedTask.java +++ b/src/com/android/launcher3/model/UserLockStateChangedTask.java @@ -92,7 +92,7 @@ public class UserLockStateChangedTask extends BaseModelUpdateTask { // If the shortcut is pinned but no longer has an icon in the system, // keep the current icon instead of reverting to the default icon. LauncherIcons li = LauncherIcons.obtain(context); - si.applyFrom(li.createShortcutIcon(shortcut, true, () -> si)); + si.bitmap = li.createShortcutIcon(shortcut, true, () -> si); li.recycle(); } else { si.runtimeStatusFlags |= FLAG_DISABLED_LOCKED_USER; diff --git a/src/com/android/launcher3/notification/NotificationItemView.java b/src/com/android/launcher3/notification/NotificationItemView.java index 717a7e93da..021fb30c5e 100644 --- a/src/com/android/launcher3/notification/NotificationItemView.java +++ b/src/com/android/launcher3/notification/NotificationItemView.java @@ -16,7 +16,7 @@ package com.android.launcher3.notification; -import static com.android.launcher3.touch.SwipeDetector.HORIZONTAL; +import static com.android.launcher3.touch.SingleAxisSwipeDetector.HORIZONTAL; import android.app.Notification; import android.content.Context; @@ -30,7 +30,7 @@ import android.widget.TextView; import com.android.launcher3.R; import com.android.launcher3.graphics.IconPalette; import com.android.launcher3.popup.PopupContainerWithArrow; -import com.android.launcher3.touch.SwipeDetector; +import com.android.launcher3.touch.SingleAxisSwipeDetector; import com.android.launcher3.util.Themes; import java.util.List; @@ -49,7 +49,7 @@ public class NotificationItemView { private final TextView mHeaderCount; private final NotificationMainView mMainView; private final NotificationFooterLayout mFooter; - private final SwipeDetector mSwipeDetector; + private final SingleAxisSwipeDetector mSwipeDetector; private final View mIconView; private final View mHeader; @@ -74,8 +74,8 @@ public class NotificationItemView { mHeader = container.findViewById(R.id.header); mDivider = container.findViewById(R.id.divider); - mSwipeDetector = new SwipeDetector(mContext, mMainView, HORIZONTAL); - mSwipeDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_BOTH, false); + mSwipeDetector = new SingleAxisSwipeDetector(mContext, mMainView, HORIZONTAL); + mSwipeDetector.setDetectableScrollConditions(SingleAxisSwipeDetector.DIRECTION_BOTH, false); mMainView.setSwipeDetector(mSwipeDetector); mFooter.setContainer(this); } diff --git a/src/com/android/launcher3/notification/NotificationListener.java b/src/com/android/launcher3/notification/NotificationListener.java index 10378ee437..059ad18971 100644 --- a/src/com/android/launcher3/notification/NotificationListener.java +++ b/src/com/android/launcher3/notification/NotificationListener.java @@ -16,6 +16,7 @@ package com.android.launcher3.notification; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; import static com.android.launcher3.util.SecureSettingsObserver.newNotificationSettingsObserver; @@ -32,9 +33,10 @@ import android.text.TextUtils; import android.util.Log; import android.util.Pair; +import androidx.annotation.AnyThread; import androidx.annotation.Nullable; +import androidx.annotation.WorkerThread; -import com.android.launcher3.util.IntSet; import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.util.SecureSettingsObserver; @@ -44,6 +46,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; /** * A {@link NotificationListenerService} that sends updates to its @@ -59,16 +62,17 @@ public class NotificationListener extends NotificationListenerService { private static final int MSG_NOTIFICATION_POSTED = 1; private static final int MSG_NOTIFICATION_REMOVED = 2; private static final int MSG_NOTIFICATION_FULL_REFRESH = 3; + private static final int MSG_CANCEL_NOTIFICATION = 4; + private static final int MSG_RANKING_UPDATE = 5; private static NotificationListener sNotificationListenerInstance = null; private static NotificationsChangedListener sNotificationsChangedListener; - private static StatusBarNotificationsChangedListener sStatusBarNotificationsChangedListener; private static boolean sIsConnected; - private static boolean sIsCreated; private final Handler mWorkerHandler; private final Handler mUiHandler; private final Ranking mTempRanking = new Ranking(); + /** Maps groupKey's to the corresponding group of notifications. */ private final Map mNotificationGroupMap = new HashMap<>(); /** Maps keys to their corresponding current group key */ @@ -79,85 +83,12 @@ public class NotificationListener extends NotificationListenerService { private SecureSettingsObserver mNotificationDotsObserver; - private final Handler.Callback mWorkerCallback = new Handler.Callback() { - @Override - public boolean handleMessage(Message message) { - switch (message.what) { - case MSG_NOTIFICATION_POSTED: - mUiHandler.obtainMessage(message.what, message.obj).sendToTarget(); - break; - case MSG_NOTIFICATION_REMOVED: - mUiHandler.obtainMessage(message.what, message.obj).sendToTarget(); - break; - case MSG_NOTIFICATION_FULL_REFRESH: - List activeNotifications; - if (sIsConnected) { - try { - activeNotifications = filterNotifications(getActiveNotifications()); - } catch (SecurityException ex) { - Log.e(TAG, "SecurityException: failed to fetch notifications"); - activeNotifications = new ArrayList(); - - } - } else { - activeNotifications = new ArrayList(); - } - - mUiHandler.obtainMessage(message.what, activeNotifications).sendToTarget(); - break; - } - return true; - } - }; - - private final Handler.Callback mUiCallback = new Handler.Callback() { - @Override - public boolean handleMessage(Message message) { - switch (message.what) { - case MSG_NOTIFICATION_POSTED: - if (sNotificationsChangedListener != null) { - NotificationPostedMsg msg = (NotificationPostedMsg) message.obj; - sNotificationsChangedListener.onNotificationPosted(msg.packageUserKey, - msg.notificationKey, msg.shouldBeFilteredOut); - } - break; - case MSG_NOTIFICATION_REMOVED: - if (sNotificationsChangedListener != null) { - Pair pair - = (Pair) message.obj; - sNotificationsChangedListener.onNotificationRemoved(pair.first, pair.second); - } - break; - case MSG_NOTIFICATION_FULL_REFRESH: - if (sNotificationsChangedListener != null) { - sNotificationsChangedListener.onNotificationFullRefresh( - (List) message.obj); - } - break; - } - return true; - } - }; - public NotificationListener() { - super(); - mWorkerHandler = new Handler(MODEL_EXECUTOR.getLooper(), mWorkerCallback); - mUiHandler = new Handler(Looper.getMainLooper(), mUiCallback); + mWorkerHandler = new Handler(MODEL_EXECUTOR.getLooper(), this::handleWorkerMessage); + mUiHandler = new Handler(Looper.getMainLooper(), this::handleUiMessage); sNotificationListenerInstance = this; } - @Override - public void onCreate() { - super.onCreate(); - sIsCreated = true; - } - - @Override - public void onDestroy() { - super.onDestroy(); - sIsCreated = false; - } - public static @Nullable NotificationListener getInstanceIfConnected() { return sIsConnected ? sNotificationListenerInstance : null; } @@ -168,25 +99,107 @@ public class NotificationListener extends NotificationListenerService { NotificationListener notificationListener = getInstanceIfConnected(); if (notificationListener != null) { notificationListener.onNotificationFullRefresh(); - } else if (!sIsCreated && sNotificationsChangedListener != null) { + } else { // User turned off dots globally, so we unbound this service; // tell the listener that there are no notifications to remove dots. - sNotificationsChangedListener.onNotificationFullRefresh( - Collections.emptyList()); + MODEL_EXECUTOR.submit(() -> MAIN_EXECUTOR.submit(() -> + listener.onNotificationFullRefresh(Collections.emptyList()))); } } - public static void setStatusBarNotificationsChangedListener - (StatusBarNotificationsChangedListener listener) { - sStatusBarNotificationsChangedListener = listener; - } - public static void removeNotificationsChangedListener() { sNotificationsChangedListener = null; } - public static void removeStatusBarNotificationsChangedListener() { - sStatusBarNotificationsChangedListener = null; + private boolean handleWorkerMessage(Message message) { + switch (message.what) { + case MSG_NOTIFICATION_POSTED: { + StatusBarNotification sbn = (StatusBarNotification) message.obj; + mUiHandler.obtainMessage(notificationIsValidForUI(sbn) + ? MSG_NOTIFICATION_POSTED : MSG_NOTIFICATION_REMOVED, + toKeyPair(sbn)).sendToTarget(); + return true; + } + case MSG_NOTIFICATION_REMOVED: { + StatusBarNotification sbn = (StatusBarNotification) message.obj; + mUiHandler.obtainMessage(MSG_NOTIFICATION_REMOVED, + toKeyPair(sbn)).sendToTarget(); + + NotificationGroup notificationGroup = mNotificationGroupMap.get(sbn.getGroupKey()); + String key = sbn.getKey(); + if (notificationGroup != null) { + notificationGroup.removeChildKey(key); + if (notificationGroup.isEmpty()) { + if (key.equals(mLastKeyDismissedByLauncher)) { + // Only cancel the group notification if launcher dismissed the + // last child. + cancelNotification(notificationGroup.getGroupSummaryKey()); + } + mNotificationGroupMap.remove(sbn.getGroupKey()); + } + } + if (key.equals(mLastKeyDismissedByLauncher)) { + mLastKeyDismissedByLauncher = null; + } + return true; + } + case MSG_NOTIFICATION_FULL_REFRESH: + List activeNotifications = null; + if (sIsConnected) { + try { + activeNotifications = Arrays.stream(getActiveNotifications()) + .filter(this::notificationIsValidForUI) + .collect(Collectors.toList()); + } catch (SecurityException ex) { + Log.e(TAG, "SecurityException: failed to fetch notifications"); + activeNotifications = new ArrayList<>(); + } + } else { + activeNotifications = new ArrayList<>(); + } + + mUiHandler.obtainMessage(message.what, activeNotifications).sendToTarget(); + return true; + case MSG_CANCEL_NOTIFICATION: { + mLastKeyDismissedByLauncher = (String) message.obj; + cancelNotification(mLastKeyDismissedByLauncher); + return true; + } + case MSG_RANKING_UPDATE: { + String[] keys = ((RankingMap) message.obj).getOrderedKeys(); + for (StatusBarNotification sbn : getActiveNotifications(keys)) { + updateGroupKeyIfNecessary(sbn); + } + return true; + } + } + return false; + } + + private boolean handleUiMessage(Message message) { + switch (message.what) { + case MSG_NOTIFICATION_POSTED: + if (sNotificationsChangedListener != null) { + Pair msg = (Pair) message.obj; + sNotificationsChangedListener.onNotificationPosted( + msg.first, msg.second); + } + break; + case MSG_NOTIFICATION_REMOVED: + if (sNotificationsChangedListener != null) { + Pair msg = (Pair) message.obj; + sNotificationsChangedListener.onNotificationRemoved( + msg.first, msg.second); + } + break; + case MSG_NOTIFICATION_FULL_REFRESH: + if (sNotificationsChangedListener != null) { + sNotificationsChangedListener.onNotificationFullRefresh( + (List) message.obj); + } + break; + } + return true; } @Override @@ -217,84 +230,37 @@ public class NotificationListener extends NotificationListenerService { super.onListenerDisconnected(); sIsConnected = false; mNotificationDotsObserver.unregister(); + onNotificationFullRefresh(); } @Override public void onNotificationPosted(final StatusBarNotification sbn) { - super.onNotificationPosted(sbn); - if (sbn == null) { - // There is a bug in platform where we can get a null notification; just ignore it. - return; - } - mWorkerHandler.obtainMessage(MSG_NOTIFICATION_POSTED, new NotificationPostedMsg(sbn)) - .sendToTarget(); - if (sStatusBarNotificationsChangedListener != null) { - sStatusBarNotificationsChangedListener.onNotificationPosted(sbn); - } - } - - /** - * An object containing data to send to MSG_NOTIFICATION_POSTED targets. - */ - private class NotificationPostedMsg { - final PackageUserKey packageUserKey; - final NotificationKeyData notificationKey; - final boolean shouldBeFilteredOut; - - NotificationPostedMsg(StatusBarNotification sbn) { - packageUserKey = PackageUserKey.fromNotification(sbn); - notificationKey = NotificationKeyData.fromNotification(sbn); - shouldBeFilteredOut = shouldBeFilteredOut(sbn); + if (sbn != null) { + mWorkerHandler.obtainMessage(MSG_NOTIFICATION_POSTED, sbn).sendToTarget(); } } @Override public void onNotificationRemoved(final StatusBarNotification sbn) { - super.onNotificationRemoved(sbn); - if (sbn == null) { - // There is a bug in platform where we can get a null notification; just ignore it. - return; + if (sbn != null) { + mWorkerHandler.obtainMessage(MSG_NOTIFICATION_REMOVED, sbn).sendToTarget(); } - Pair packageUserKeyAndNotificationKey - = new Pair<>(PackageUserKey.fromNotification(sbn), - NotificationKeyData.fromNotification(sbn)); - mWorkerHandler.obtainMessage(MSG_NOTIFICATION_REMOVED, packageUserKeyAndNotificationKey) - .sendToTarget(); - if (sStatusBarNotificationsChangedListener != null) { - sStatusBarNotificationsChangedListener.onNotificationRemoved(sbn); - } - - NotificationGroup notificationGroup = mNotificationGroupMap.get(sbn.getGroupKey()); - String key = sbn.getKey(); - if (notificationGroup != null) { - notificationGroup.removeChildKey(key); - if (notificationGroup.isEmpty()) { - if (key.equals(mLastKeyDismissedByLauncher)) { - // Only cancel the group notification if launcher dismissed the last child. - cancelNotification(notificationGroup.getGroupSummaryKey()); - } - mNotificationGroupMap.remove(sbn.getGroupKey()); - } - } - if (key.equals(mLastKeyDismissedByLauncher)) { - mLastKeyDismissedByLauncher = null; - } - } - - public void cancelNotificationFromLauncher(String key) { - mLastKeyDismissedByLauncher = key; - cancelNotification(key); } @Override public void onNotificationRankingUpdate(RankingMap rankingMap) { - super.onNotificationRankingUpdate(rankingMap); - String[] keys = rankingMap.getOrderedKeys(); - for (StatusBarNotification sbn : getActiveNotifications(keys)) { - updateGroupKeyIfNecessary(sbn); - } + mWorkerHandler.obtainMessage(MSG_RANKING_UPDATE, rankingMap).sendToTarget(); } + /** + * Cancels a notification + */ + @AnyThread + public void cancelNotificationFromLauncher(String key) { + mWorkerHandler.obtainMessage(MSG_CANCEL_NOTIFICATION, key).sendToTarget(); + } + + @WorkerThread private void updateGroupKeyIfNecessary(StatusBarNotification sbn) { String childKey = sbn.getKey(); String oldGroupKey = mNotificationGroupKeyMap.get(childKey); @@ -328,53 +294,33 @@ public class NotificationListener extends NotificationListenerService { } } - /** This makes a potentially expensive binder call and should be run on a background thread. */ + /** + * This makes a potentially expensive binder call and should be run on a background thread. + */ + @WorkerThread public List getNotificationsForKeys(List keys) { - StatusBarNotification[] notifications = NotificationListener.this - .getActiveNotifications(NotificationKeyData.extractKeysOnly(keys) - .toArray(new String[keys.size()])); - return notifications == null - ? Collections.emptyList() : Arrays.asList(notifications); + StatusBarNotification[] notifications = getActiveNotifications( + keys.stream().map(n -> n.notificationKey).toArray(String[]::new)); + return notifications == null ? Collections.emptyList() : Arrays.asList(notifications); } /** - * Filter out notifications that don't have an intent - * or are headers for grouped notifications. - * - * @see #shouldBeFilteredOut(StatusBarNotification) + * Returns true for notifications that have an intent and are not headers for grouped + * notifications and should be shown in the notification popup. */ - private List filterNotifications( - StatusBarNotification[] notifications) { - if (notifications == null) return null; - IntSet removedNotifications = new IntSet(); - for (int i = 0; i < notifications.length; i++) { - if (shouldBeFilteredOut(notifications[i])) { - removedNotifications.add(i); - } - } - List filteredNotifications = new ArrayList<>( - notifications.length - removedNotifications.size()); - for (int i = 0; i < notifications.length; i++) { - if (!removedNotifications.contains(i)) { - filteredNotifications.add(notifications[i]); - } - } - return filteredNotifications; - } - - private boolean shouldBeFilteredOut(StatusBarNotification sbn) { + @WorkerThread + private boolean notificationIsValidForUI(StatusBarNotification sbn) { Notification notification = sbn.getNotification(); - updateGroupKeyIfNecessary(sbn); getCurrentRanking().getRanking(sbn.getKey(), mTempRanking); if (!mTempRanking.canShowBadge()) { - return true; + return false; } if (mTempRanking.getChannel().getId().equals(NotificationChannel.DEFAULT_CHANNEL_ID)) { // Special filtering for the default, legacy "Miscellaneous" channel. if ((notification.flags & Notification.FLAG_ONGOING_EVENT) != 0) { - return true; + return false; } } @@ -382,19 +328,19 @@ public class NotificationListener extends NotificationListenerService { CharSequence text = notification.extras.getCharSequence(Notification.EXTRA_TEXT); boolean missingTitleAndText = TextUtils.isEmpty(title) && TextUtils.isEmpty(text); boolean isGroupHeader = (notification.flags & Notification.FLAG_GROUP_SUMMARY) != 0; - return (isGroupHeader || missingTitleAndText); + return !isGroupHeader && !missingTitleAndText; + } + + private static Pair toKeyPair(StatusBarNotification sbn) { + return Pair.create(PackageUserKey.fromNotification(sbn), + NotificationKeyData.fromNotification(sbn)); } public interface NotificationsChangedListener { void onNotificationPosted(PackageUserKey postedPackageUserKey, - NotificationKeyData notificationKey, boolean shouldBeFilteredOut); + NotificationKeyData notificationKey); void onNotificationRemoved(PackageUserKey removedPackageUserKey, NotificationKeyData notificationKey); void onNotificationFullRefresh(List activeNotifications); } - - public interface StatusBarNotificationsChangedListener { - void onNotificationPosted(StatusBarNotification sbn); - void onNotificationRemoved(StatusBarNotification sbn); - } } diff --git a/src/com/android/launcher3/notification/NotificationMainView.java b/src/com/android/launcher3/notification/NotificationMainView.java index 78627ecc10..b67adbb2cb 100644 --- a/src/com/android/launcher3/notification/NotificationMainView.java +++ b/src/com/android/launcher3/notification/NotificationMainView.java @@ -38,8 +38,9 @@ import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; import com.android.launcher3.R; import com.android.launcher3.anim.AnimationSuccessListener; +import com.android.launcher3.touch.BaseSwipeDetector; import com.android.launcher3.touch.OverScroll; -import com.android.launcher3.touch.SwipeDetector; +import com.android.launcher3.touch.SingleAxisSwipeDetector; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.util.Themes; @@ -48,7 +49,7 @@ import com.android.launcher3.util.Themes; * e.g. icon + title + text. */ @TargetApi(Build.VERSION_CODES.N) -public class NotificationMainView extends FrameLayout implements SwipeDetector.Listener { +public class NotificationMainView extends FrameLayout implements SingleAxisSwipeDetector.Listener { private static FloatProperty CONTENT_TRANSLATION = new FloatProperty("contentTranslation") { @@ -75,7 +76,7 @@ public class NotificationMainView extends FrameLayout implements SwipeDetector.L private TextView mTextView; private View mIconView; - private SwipeDetector mSwipeDetector; + private SingleAxisSwipeDetector mSwipeDetector; public NotificationMainView(Context context) { this(context, null, 0); @@ -107,7 +108,7 @@ public class NotificationMainView extends FrameLayout implements SwipeDetector.L mIconView = findViewById(R.id.popup_item_icon); } - public void setSwipeDetector(SwipeDetector swipeDetector) { + public void setSwipeDetector(SingleAxisSwipeDetector swipeDetector) { mSwipeDetector = swipeDetector; } @@ -173,7 +174,7 @@ public class NotificationMainView extends FrameLayout implements SwipeDetector.L LauncherLogProto.ItemType.NOTIFICATION); } - // SwipeDetector.Listener's + // SingleAxisSwipeDetector.Listener's @Override public void onDragStart(boolean start) { } @@ -187,7 +188,7 @@ public class NotificationMainView extends FrameLayout implements SwipeDetector.L } @Override - public void onDragEnd(float velocity, boolean fling) { + public void onDragEnd(float velocity) { final boolean willExit; final float endTranslation; final float startTranslation = mTextAndBackground.getTranslationX(); @@ -195,7 +196,7 @@ public class NotificationMainView extends FrameLayout implements SwipeDetector.L if (!canChildBeDismissed()) { willExit = false; endTranslation = 0; - } else if (fling) { + } else if (mSwipeDetector.isFling(velocity)) { willExit = true; endTranslation = velocity < 0 ? - getWidth() : getWidth(); } else if (Math.abs(startTranslation) > getWidth() / 2) { @@ -206,7 +207,7 @@ public class NotificationMainView extends FrameLayout implements SwipeDetector.L endTranslation = 0; } - long duration = SwipeDetector.calculateDuration(velocity, + long duration = BaseSwipeDetector.calculateDuration(velocity, (endTranslation - startTranslation) / getWidth()); mContentTranslateAnimator.removeAllListeners(); diff --git a/src/com/android/launcher3/pm/PinRequestHelper.java b/src/com/android/launcher3/pm/PinRequestHelper.java index 5b6b56d858..02f96a174f 100644 --- a/src/com/android/launcher3/pm/PinRequestHelper.java +++ b/src/com/android/launcher3/pm/PinRequestHelper.java @@ -31,6 +31,8 @@ import android.os.Parcelable; import androidx.annotation.Nullable; import com.android.launcher3.WorkspaceItemInfo; +import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.icons.LauncherIcons; public class PinRequestHelper { @@ -80,7 +82,15 @@ public class PinRequestHelper { ShortcutInfo si = request.getShortcutInfo(); WorkspaceItemInfo info = new WorkspaceItemInfo(si, context); // Apply the unbadged icon and fetch the actual icon asynchronously. - fetchAndUpdateShortcutIconAsync(context, info, si, false); + if (FeatureFlags.ENABLE_DEEP_SHORTCUT_ICON_CACHE.get()) { + fetchAndUpdateShortcutIconAsync(context, info, si, false); + } else { + LauncherIcons li = LauncherIcons.obtain(context); + info.bitmap = li.createShortcutIcon(si, false /* badged */); + li.recycle(); + LauncherAppState.getInstance(context).getModel() + .updateAndBindWorkspaceItem(info, si); + } return info; } else { return null; diff --git a/src/com/android/launcher3/popup/ArrowPopup.java b/src/com/android/launcher3/popup/ArrowPopup.java index 28000b977b..98f7fd853d 100644 --- a/src/com/android/launcher3/popup/ArrowPopup.java +++ b/src/com/android/launcher3/popup/ArrowPopup.java @@ -360,10 +360,14 @@ public abstract class ArrowPopup extends AbstractFloatingView { final TimeInterpolator revealInterpolator = ACCEL_DEACCEL; // Rectangular reveal. + mEndRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight()); final ValueAnimator revealAnim = createOpenCloseOutlineProvider() .createRevealAnimator(this, false); revealAnim.setDuration(revealDuration); revealAnim.setInterpolator(revealInterpolator); + // Clip the popup to the initial outline while the notification dot and arrow animate. + revealAnim.start(); + revealAnim.pause(); ValueAnimator fadeIn = ValueAnimator.ofFloat(0, 1); fadeIn.setDuration(revealDuration + arrowDuration); @@ -399,7 +403,6 @@ public abstract class ArrowPopup extends AbstractFloatingView { if (!mIsOpen) { return; } - mEndRect.setEmpty(); if (getOutlineProvider() instanceof RevealOutlineAnimation) { ((RevealOutlineAnimation) getOutlineProvider()).getOutline(mEndRect); } @@ -471,9 +474,6 @@ public abstract class ArrowPopup extends AbstractFloatingView { mStartRect.set(arrowCenterX - halfArrowWidth, arrowCenterY, arrowCenterX + halfArrowWidth, arrowCenterY); - if (mEndRect.isEmpty()) { - mEndRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight()); - } return new RoundedRectRevealOutlineProvider (arrowCornerRadius, mOutlineRadius, mStartRect, mEndRect); diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java index 4833c26d82..e70673a5a0 100644 --- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java +++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java @@ -77,6 +77,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.function.Predicate; +import java.util.stream.Collectors; /** * A container for shortcuts to deep links and notifications associated with an app. @@ -196,9 +197,6 @@ public class PopupContainerWithArrow extends ArrowPopup implements DragSource, * @return the container if shown or null. */ public static PopupContainerWithArrow showForIcon(BubbleTextView icon) { - if (TestProtocol.sDebugTracing) { - Log.d(TestProtocol.NO_CONTEXT_MENU, "showForIcon"); - } Launcher launcher = Launcher.getLauncher(icon.getContext()); if (getOpen(launcher) != null) { // There is already an items container open, so don't open this one. @@ -213,7 +211,7 @@ public class PopupContainerWithArrow extends ArrowPopup implements DragSource, final PopupContainerWithArrow container = (PopupContainerWithArrow) launcher.getLayoutInflater().inflate( R.layout.popup_container, launcher.getDragLayer(), false); - container.populateAndShow(icon, itemInfo, SystemShortcutFactory.INSTANCE.get(launcher)); + container.populateAndShow(icon, itemInfo); return container; } @@ -238,16 +236,15 @@ public class PopupContainerWithArrow extends ArrowPopup implements DragSource, } } - protected void populateAndShow( - BubbleTextView icon, ItemInfo item, SystemShortcutFactory factory) { - if (TestProtocol.sDebugTracing) { - Log.d(TestProtocol.NO_CONTEXT_MENU, "populateAndShow"); - } + protected void populateAndShow(BubbleTextView icon, ItemInfo item) { PopupDataProvider popupDataProvider = mLauncher.getPopupDataProvider(); populateAndShow(icon, popupDataProvider.getShortcutCountForItem(item), popupDataProvider.getNotificationKeysForItem(item), - factory.getEnabledShortcuts(mLauncher, item)); + mLauncher.getSupportedShortcuts() + .map(s -> s.getShortcut(mLauncher, item)) + .filter(s -> s != null) + .collect(Collectors.toList())); } public ViewGroup getSystemShortcutContainerForTesting() { @@ -382,8 +379,7 @@ public class PopupContainerWithArrow extends ArrowPopup implements DragSource, @Override public void onWidgetsBound() { ItemInfo itemInfo = (ItemInfo) mOriginalIcon.getTag(); - SystemShortcut widgetInfo = new SystemShortcut.Widgets(); - View.OnClickListener onClickListener = widgetInfo.getOnClickListener(mLauncher, itemInfo); + SystemShortcut widgetInfo = SystemShortcut.WIDGETS.getShortcut(mLauncher, itemInfo); View widgetsView = null; int count = mSystemShortcutContainer.getChildCount(); for (int i = 0; i < count; i++) { @@ -394,7 +390,7 @@ public class PopupContainerWithArrow extends ArrowPopup implements DragSource, } } - if (onClickListener != null && widgetsView == null) { + if (widgetInfo != null && widgetsView == null) { // We didn't have any widgets cached but now there are some, so enable the shortcut. if (mSystemShortcutContainer != this) { initializeSystemShortcut( @@ -407,7 +403,7 @@ public class PopupContainerWithArrow extends ArrowPopup implements DragSource, close(false); PopupContainerWithArrow.showForIcon(mOriginalIcon); } - } else if (onClickListener == null && widgetsView != null) { + } else if (widgetInfo == null && widgetsView != null) { // No widgets exist, but we previously added the shortcut so remove it. if (mSystemShortcutContainer != this) { mSystemShortcutContainer.removeView(widgetsView); @@ -430,8 +426,7 @@ public class PopupContainerWithArrow extends ArrowPopup implements DragSource, info.setIconAndContentDescriptionFor((ImageView) view); } view.setTag(info); - view.setOnClickListener(info.getOnClickListener(mLauncher, - (ItemInfo) mOriginalIcon.getTag())); + view.setOnClickListener(info); } /** @@ -506,7 +501,7 @@ public class PopupContainerWithArrow extends ArrowPopup implements DragSource, DotInfo dotInfo = mLauncher.getDotInfoForItem(itemInfo); if (mNotificationItemView != null && dotInfo != null) { mNotificationItemView.updateHeader( - dotInfo.getNotificationCount(), itemInfo.iconColor); + dotInfo.getNotificationCount(), itemInfo.bitmap.color); } } @@ -575,8 +570,11 @@ public class PopupContainerWithArrow extends ArrowPopup implements DragSource, @Override protected void closeComplete() { - mOriginalIcon.setTextVisibility(mOriginalIcon.shouldTextBeVisible()); - mOriginalIcon.setForceHideDot(false); + PopupContainerWithArrow openPopup = getOpen(mLauncher); + if (openPopup == null || openPopup.mOriginalIcon != mOriginalIcon) { + mOriginalIcon.setTextVisibility(mOriginalIcon.shouldTextBeVisible()); + mOriginalIcon.setForceHideDot(false); + } super.closeComplete(); } diff --git a/src/com/android/launcher3/popup/PopupDataProvider.java b/src/com/android/launcher3/popup/PopupDataProvider.java index 4612b2a474..c5aa836b06 100644 --- a/src/com/android/launcher3/popup/PopupDataProvider.java +++ b/src/com/android/launcher3/popup/PopupDataProvider.java @@ -20,13 +20,15 @@ import android.content.ComponentName; import android.service.notification.StatusBarNotification; import android.util.Log; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; import com.android.launcher3.dot.DotInfo; import com.android.launcher3.model.WidgetItem; import com.android.launcher3.notification.NotificationKeyData; import com.android.launcher3.notification.NotificationListener; -import com.android.launcher3.shortcuts.DeepShortcutManager; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.util.ShortcutUtil; @@ -39,13 +41,9 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.function.Predicate; import java.util.stream.Collectors; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - /** * Provides data for the popup menu that appears after long-clicking on apps. */ @@ -76,28 +74,14 @@ public class PopupDataProvider implements NotificationListener.NotificationsChan @Override public void onNotificationPosted(PackageUserKey postedPackageUserKey, - NotificationKeyData notificationKey, boolean shouldBeFilteredOut) { + NotificationKeyData notificationKey) { DotInfo dotInfo = mPackageUserToDotInfos.get(postedPackageUserKey); - boolean dotShouldBeRefreshed; if (dotInfo == null) { - if (!shouldBeFilteredOut) { - DotInfo newDotInfo = new DotInfo(); - newDotInfo.addOrUpdateNotificationKey(notificationKey); - mPackageUserToDotInfos.put(postedPackageUserKey, newDotInfo); - dotShouldBeRefreshed = true; - } else { - dotShouldBeRefreshed = false; - } - } else { - dotShouldBeRefreshed = shouldBeFilteredOut - ? dotInfo.removeNotificationKey(notificationKey) - : dotInfo.addOrUpdateNotificationKey(notificationKey); - if (dotInfo.getNotificationKeys().size() == 0) { - mPackageUserToDotInfos.remove(postedPackageUserKey); - } + dotInfo = new DotInfo(); + mPackageUserToDotInfos.put(postedPackageUserKey, dotInfo); } - if (dotShouldBeRefreshed) { - updateNotificationDots(t -> postedPackageUserKey.equals(t)); + if (dotInfo.addOrUpdateNotificationKey(notificationKey)) { + updateNotificationDots(postedPackageUserKey::equals); } } @@ -109,7 +93,7 @@ public class PopupDataProvider implements NotificationListener.NotificationsChan if (oldDotInfo.getNotificationKeys().size() == 0) { mPackageUserToDotInfos.remove(removedPackageUserKey); } - updateNotificationDots(t -> removedPackageUserKey.equals(t)); + updateNotificationDots(removedPackageUserKey::equals); trimNotifications(mPackageUserToDotInfos); } } @@ -195,14 +179,6 @@ public class PopupDataProvider implements NotificationListener.NotificationsChan : getNotificationsForItem(info, dotInfo.getNotificationKeys()); } - /** This makes a potentially expensive binder call and should be run on a background thread. */ - public @NonNull List getStatusBarNotificationsForKeys( - List notificationKeys) { - NotificationListener notificationListener = NotificationListener.getInstanceIfConnected(); - return notificationListener == null ? Collections.EMPTY_LIST - : notificationListener.getNotificationsForKeys(notificationKeys); - } - public void cancelNotification(String notificationKey) { NotificationListener notificationListener = NotificationListener.getInstanceIfConnected(); if (notificationListener == null) { diff --git a/src/com/android/launcher3/popup/PopupPopulator.java b/src/com/android/launcher3/popup/PopupPopulator.java index dbfe9882f7..80c6683d31 100644 --- a/src/com/android/launcher3/popup/PopupPopulator.java +++ b/src/com/android/launcher3/popup/PopupPopulator.java @@ -20,7 +20,9 @@ import android.content.ComponentName; import android.content.pm.ShortcutInfo; import android.os.Handler; import android.os.UserHandle; -import android.service.notification.StatusBarNotification; + +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; @@ -28,6 +30,7 @@ import com.android.launcher3.WorkspaceItemInfo; import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.notification.NotificationInfo; import com.android.launcher3.notification.NotificationKeyData; +import com.android.launcher3.notification.NotificationListener; import com.android.launcher3.shortcuts.DeepShortcutManager; import com.android.launcher3.shortcuts.DeepShortcutView; import com.android.launcher3.util.PackageUserKey; @@ -37,9 +40,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; - -import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; +import java.util.stream.Collectors; /** * Contains logic relevant to populating a {@link PopupContainerWithArrow}. In particular, @@ -130,12 +131,15 @@ public class PopupPopulator { final UserHandle user = originalInfo.user; return () -> { if (!notificationKeys.isEmpty()) { - List notifications = launcher.getPopupDataProvider() - .getStatusBarNotificationsForKeys(notificationKeys); - List infos = new ArrayList<>(notifications.size()); - for (int i = 0; i < notifications.size(); i++) { - StatusBarNotification notification = notifications.get(i); - infos.add(new NotificationInfo(launcher, notification)); + NotificationListener notificationListener = + NotificationListener.getInstanceIfConnected(); + final List infos; + if (notificationListener == null) { + infos = Collections.emptyList(); + } else { + infos = notificationListener.getNotificationsForKeys(notificationKeys).stream() + .map(sbn -> new NotificationInfo(launcher, sbn)) + .collect(Collectors.toList()); } uiHandler.post(() -> container.applyNotificationInfos(infos)); } @@ -150,7 +154,7 @@ public class PopupPopulator { final WorkspaceItemInfo si = new WorkspaceItemInfo(shortcut, launcher); // Use unbadged icon for the menu. LauncherIcons li = LauncherIcons.obtain(launcher); - si.applyFrom(li.createShortcutIcon(shortcut, false /* badged */)); + si.bitmap = li.createShortcutIcon(shortcut, false /* badged */); li.recycle(); si.rank = i; diff --git a/src/com/android/launcher3/popup/RemoteActionShortcut.java b/src/com/android/launcher3/popup/RemoteActionShortcut.java index 5a5fbabacf..8751202538 100644 --- a/src/com/android/launcher3/popup/RemoteActionShortcut.java +++ b/src/com/android/launcher3/popup/RemoteActionShortcut.java @@ -16,13 +16,19 @@ package com.android.launcher3.popup; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; + +import android.annotation.TargetApi; import android.app.PendingIntent; import android.app.RemoteAction; +import android.content.Context; import android.content.Intent; -import android.os.Handler; -import android.os.Looper; +import android.os.Build; import android.util.Log; import android.view.View; +import android.view.accessibility.AccessibilityNodeInfo; +import android.widget.ImageView; +import android.widget.TextView; import android.widget.Toast; import com.android.launcher3.AbstractFloatingView; @@ -32,55 +38,75 @@ import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.userevent.nano.LauncherLogProto; +@TargetApi(Build.VERSION_CODES.Q) public class RemoteActionShortcut extends SystemShortcut { private static final String TAG = "RemoteActionShortcut"; private static final boolean DEBUG = Utilities.IS_DEBUG_DEVICE; private final RemoteAction mAction; - public RemoteActionShortcut(RemoteAction action) { - super(action.getIcon(), action.getTitle(), action.getContentDescription(), - R.id.action_remote_action_shortcut); + public RemoteActionShortcut(RemoteAction action, + BaseDraggingActivity activity, ItemInfo itemInfo) { + super(0, R.id.action_remote_action_shortcut, activity, itemInfo); mAction = action; } @Override - public View.OnClickListener getOnClickListener( - final BaseDraggingActivity activity, final ItemInfo itemInfo) { - return view -> { - AbstractFloatingView.closeAllOpenViews(activity); + public void setIconAndLabelFor(View iconView, TextView labelView) { + mAction.getIcon().loadDrawableAsync(iconView.getContext(), + iconView::setBackground, + MAIN_EXECUTOR.getHandler()); + labelView.setText(mAction.getTitle()); + } - final String actionIdentity = mAction.getTitle() + ", " + - itemInfo.getTargetComponent().getPackageName(); - try { - if (DEBUG) Log.d(TAG, "Sending action: " + actionIdentity); - mAction.getActionIntent().send( - activity, - 0, - new Intent().putExtra( - Intent.EXTRA_PACKAGE_NAME, - itemInfo.getTargetComponent().getPackageName()), - (pendingIntent, intent, resultCode, resultData, resultExtras) -> { - if (DEBUG) Log.d(TAG, "Action is complete: " + actionIdentity); - if (resultData != null && !resultData.isEmpty()) { - Log.e(TAG, "Remote action returned result: " + actionIdentity - + " : " + resultData); - Toast.makeText(activity, resultData, Toast.LENGTH_SHORT).show(); - } - }, - new Handler(Looper.getMainLooper())); - } catch (PendingIntent.CanceledException e) { - Log.e(TAG, "Remote action canceled: " + actionIdentity, e); - Toast.makeText(activity, activity.getString( - R.string.remote_action_failed, - mAction.getTitle()), - Toast.LENGTH_SHORT) - .show(); - } + @Override + public void setIconAndContentDescriptionFor(ImageView view) { + mAction.getIcon().loadDrawableAsync(view.getContext(), + view::setImageDrawable, + MAIN_EXECUTOR.getHandler()); + view.setContentDescription(mAction.getContentDescription()); + } - activity.getUserEventDispatcher().logActionOnControl(LauncherLogProto.Action.Touch.TAP, - LauncherLogProto.ControlType.REMOTE_ACTION_SHORTCUT, view); - }; + @Override + public AccessibilityNodeInfo.AccessibilityAction createAccessibilityAction(Context context) { + return new AccessibilityNodeInfo.AccessibilityAction( + R.id.action_remote_action_shortcut, mAction.getContentDescription()); + } + + @Override + public void onClick(View view) { + AbstractFloatingView.closeAllOpenViews(mTarget); + + final String actionIdentity = mAction.getTitle() + ", " + + mItemInfo.getTargetComponent().getPackageName(); + try { + if (DEBUG) Log.d(TAG, "Sending action: " + actionIdentity); + mAction.getActionIntent().send( + mTarget, + 0, + new Intent().putExtra( + Intent.EXTRA_PACKAGE_NAME, + mItemInfo.getTargetComponent().getPackageName()), + (pendingIntent, intent, resultCode, resultData, resultExtras) -> { + if (DEBUG) Log.d(TAG, "Action is complete: " + actionIdentity); + if (resultData != null && !resultData.isEmpty()) { + Log.e(TAG, "Remote action returned result: " + actionIdentity + + " : " + resultData); + Toast.makeText(mTarget, resultData, Toast.LENGTH_SHORT).show(); + } + }, + MAIN_EXECUTOR.getHandler()); + } catch (PendingIntent.CanceledException e) { + Log.e(TAG, "Remote action canceled: " + actionIdentity, e); + Toast.makeText(mTarget, mTarget.getString( + R.string.remote_action_failed, + mAction.getTitle()), + Toast.LENGTH_SHORT) + .show(); + } + + mTarget.getUserEventDispatcher().logActionOnControl(LauncherLogProto.Action.Touch.TAP, + LauncherLogProto.ControlType.REMOTE_ACTION_SHORTCUT, view); } @Override diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java index a87b7b8971..222c6c962f 100644 --- a/src/com/android/launcher3/popup/SystemShortcut.java +++ b/src/com/android/launcher3/popup/SystemShortcut.java @@ -5,14 +5,13 @@ import android.app.ActivityOptions; import android.content.Context; import android.content.Intent; import android.graphics.Rect; -import android.graphics.drawable.Icon; -import android.os.Handler; -import android.os.Looper; import android.view.View; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.ImageView; import android.widget.TextView; +import androidx.annotation.Nullable; + import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.ItemInfo; @@ -39,41 +38,30 @@ import java.util.List; * Example system shortcuts, defined as inner classes, include Widgets and AppInfo. * @param */ -public abstract class SystemShortcut - extends ItemInfo { +public abstract class SystemShortcut extends ItemInfo + implements View.OnClickListener { + private final int mIconResId; private final int mLabelResId; - private final Icon mIcon; - private final CharSequence mLabel; - private final CharSequence mContentDescription; private final int mAccessibilityActionId; - public SystemShortcut(int iconResId, int labelResId) { + protected final T mTarget; + protected final ItemInfo mItemInfo; + + public SystemShortcut(int iconResId, int labelResId, T target, ItemInfo itemInfo) { mIconResId = iconResId; mLabelResId = labelResId; mAccessibilityActionId = labelResId; - mIcon = null; - mLabel = null; - mContentDescription = null; + mTarget = target; + mItemInfo = itemInfo; } - public SystemShortcut(Icon icon, CharSequence label, CharSequence contentDescription, - int accessibilityActionId) { - mIcon = icon; - mLabel = label; - mContentDescription = contentDescription; - mAccessibilityActionId = accessibilityActionId; - mIconResId = 0; - mLabelResId = 0; - } - - public SystemShortcut(SystemShortcut other) { + public SystemShortcut(SystemShortcut other) { mIconResId = other.mIconResId; mLabelResId = other.mLabelResId; - mIcon = other.mIcon; - mLabel = other.mLabel; - mContentDescription = other.mContentDescription; mAccessibilityActionId = other.mAccessibilityActionId; + mTarget = other.mTarget; + mItemInfo = other.mItemInfo; } /** @@ -84,150 +72,135 @@ public abstract class SystemShortcut } public void setIconAndLabelFor(View iconView, TextView labelView) { - if (mIcon != null) { - mIcon.loadDrawableAsync(iconView.getContext(), - iconView::setBackground, - new Handler(Looper.getMainLooper())); - } else { - iconView.setBackgroundResource(mIconResId); - } - - if (mLabel != null) { - labelView.setText(mLabel); - } else { - labelView.setText(mLabelResId); - } + iconView.setBackgroundResource(mIconResId); + labelView.setText(mLabelResId); } public void setIconAndContentDescriptionFor(ImageView view) { - if (mIcon != null) { - mIcon.loadDrawableAsync(view.getContext(), - view::setImageDrawable, - new Handler(Looper.getMainLooper())); - } else { - view.setImageResource(mIconResId); - } - - view.setContentDescription(getContentDescription(view.getContext())); - } - - private CharSequence getContentDescription(Context context) { - return mContentDescription != null ? mContentDescription : context.getText(mLabelResId); + view.setImageResource(mIconResId); + view.setContentDescription(view.getContext().getText(mLabelResId)); } public AccessibilityNodeInfo.AccessibilityAction createAccessibilityAction(Context context) { - return new AccessibilityNodeInfo.AccessibilityAction(mAccessibilityActionId, - getContentDescription(context)); + return new AccessibilityNodeInfo.AccessibilityAction( + mAccessibilityActionId, context.getText(mLabelResId)); } public boolean hasHandlerForAction(int action) { return mAccessibilityActionId == action; } - public abstract View.OnClickListener getOnClickListener(T activity, ItemInfo itemInfo); + public interface Factory { + + @Nullable SystemShortcut getShortcut(T activity, ItemInfo itemInfo); + } + + public static final Factory WIDGETS = (launcher, itemInfo) -> { + if (itemInfo.getTargetComponent() == null) return null; + final List widgets = + launcher.getPopupDataProvider().getWidgetsForPackageUser(new PackageUserKey( + itemInfo.getTargetComponent().getPackageName(), itemInfo.user)); + if (widgets == null) { + return null; + } + return new Widgets(launcher, itemInfo); + }; public static class Widgets extends SystemShortcut { - public Widgets() { - super(R.drawable.ic_widget, R.string.widget_button_text); + public Widgets(Launcher target, ItemInfo itemInfo) { + super(R.drawable.ic_widget, R.string.widget_button_text, target, itemInfo); } @Override - public View.OnClickListener getOnClickListener(final Launcher launcher, - final ItemInfo itemInfo) { - if (itemInfo.getTargetComponent() == null) return null; - final List widgets = - launcher.getPopupDataProvider().getWidgetsForPackageUser(new PackageUserKey( - itemInfo.getTargetComponent().getPackageName(), itemInfo.user)); - if (widgets == null) { - return null; - } - return (view) -> { - AbstractFloatingView.closeAllOpenViews(launcher); - WidgetsBottomSheet widgetsBottomSheet = - (WidgetsBottomSheet) launcher.getLayoutInflater().inflate( - R.layout.widgets_bottom_sheet, launcher.getDragLayer(), false); - widgetsBottomSheet.populateAndShow(itemInfo); - launcher.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP, - ControlType.WIDGETS_BUTTON, view); - }; + public void onClick(View view) { + AbstractFloatingView.closeAllOpenViews(mTarget); + WidgetsBottomSheet widgetsBottomSheet = + (WidgetsBottomSheet) mTarget.getLayoutInflater().inflate( + R.layout.widgets_bottom_sheet, mTarget.getDragLayer(), false); + widgetsBottomSheet.populateAndShow(mItemInfo); + mTarget.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP, + ControlType.WIDGETS_BUTTON, view); } } + public static final Factory APP_INFO = AppInfo::new; + public static class AppInfo extends SystemShortcut { - public AppInfo() { - super(R.drawable.ic_info_no_shadow, R.string.app_info_drop_target_label); + + public AppInfo(BaseDraggingActivity target, ItemInfo itemInfo) { + super(R.drawable.ic_info_no_shadow, R.string.app_info_drop_target_label, target, + itemInfo); } @Override - public View.OnClickListener getOnClickListener( - BaseDraggingActivity activity, ItemInfo itemInfo) { - return (view) -> { - dismissTaskMenuView(activity); - Rect sourceBounds = activity.getViewBounds(view); - new PackageManagerHelper(activity).startDetailsActivityForInfo( - itemInfo, sourceBounds, ActivityOptions.makeBasic().toBundle()); - activity.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP, - ControlType.APPINFO_TARGET, view); - }; + public void onClick(View view) { + dismissTaskMenuView(mTarget); + Rect sourceBounds = mTarget.getViewBounds(view); + new PackageManagerHelper(mTarget).startDetailsActivityForInfo( + mItemInfo, sourceBounds, ActivityOptions.makeBasic().toBundle()); + mTarget.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP, + ControlType.APPINFO_TARGET, view); } } + public static Factory INSTALL = (activity, itemInfo) -> { + boolean supportsWebUI = (itemInfo instanceof WorkspaceItemInfo) + && ((WorkspaceItemInfo) itemInfo).hasStatusFlag( + WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI); + boolean isInstantApp = false; + if (itemInfo instanceof com.android.launcher3.AppInfo) { + com.android.launcher3.AppInfo appInfo = (com.android.launcher3.AppInfo) itemInfo; + isInstantApp = InstantAppResolver.newInstance(activity).isInstantApp(appInfo); + } + boolean enabled = supportsWebUI || isInstantApp; + if (!enabled) { + return null; + } + return new Install(activity, itemInfo); + }; + public static class Install extends SystemShortcut { - public Install() { - super(R.drawable.ic_install_no_shadow, R.string.install_drop_target_label); + + public Install(BaseDraggingActivity target, ItemInfo itemInfo) { + super(R.drawable.ic_install_no_shadow, R.string.install_drop_target_label, + target, itemInfo); } @Override - public View.OnClickListener getOnClickListener( - BaseDraggingActivity activity, ItemInfo itemInfo) { - boolean supportsWebUI = (itemInfo instanceof WorkspaceItemInfo) && - ((WorkspaceItemInfo) itemInfo).hasStatusFlag(WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI); - boolean isInstantApp = false; - if (itemInfo instanceof com.android.launcher3.AppInfo) { - com.android.launcher3.AppInfo appInfo = (com.android.launcher3.AppInfo) itemInfo; - isInstantApp = InstantAppResolver.newInstance(activity).isInstantApp(appInfo); - } - boolean enabled = supportsWebUI || isInstantApp; - if (!enabled) { - return null; - } - return createOnClickListener(activity, itemInfo); - } - - public View.OnClickListener createOnClickListener( - BaseDraggingActivity activity, ItemInfo itemInfo) { - return view -> { - Intent intent = new PackageManagerHelper(view.getContext()).getMarketIntent( - itemInfo.getTargetComponent().getPackageName()); - activity.startActivitySafely(view, intent, itemInfo, null); - AbstractFloatingView.closeAllOpenViews(activity); - }; + public void onClick(View view) { + Intent intent = new PackageManagerHelper(view.getContext()).getMarketIntent( + mItemInfo.getTargetComponent().getPackageName()); + mTarget.startActivitySafely(view, intent, mItemInfo, null); + AbstractFloatingView.closeAllOpenViews(mTarget); } } + public static Factory DISMISS_PREDICTION = (launcher, itemInfo) -> { + if (!FeatureFlags.ENABLE_PREDICTION_DISMISS.get()) return null; + if (itemInfo.container != LauncherSettings.Favorites.CONTAINER_PREDICTION) return null; + return new DismissPrediction(launcher, itemInfo); + }; + public static class DismissPrediction extends SystemShortcut { - public DismissPrediction() { - super(R.drawable.ic_remove_no_shadow, R.string.dismiss_prediction_label); + public DismissPrediction(Launcher launcher, ItemInfo itemInfo) { + super(R.drawable.ic_remove_no_shadow, R.string.dismiss_prediction_label, launcher, + itemInfo); } @Override - public View.OnClickListener getOnClickListener(Launcher activity, ItemInfo itemInfo) { - if (!FeatureFlags.ENABLE_PREDICTION_DISMISS.get()) return null; - if (itemInfo.container != LauncherSettings.Favorites.CONTAINER_PREDICTION) return null; - return (view) -> { - PopupContainerWithArrow.closeAllOpenViews(activity); - activity.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP, - ControlType.DISMISS_PREDICTION, ContainerType.DEEPSHORTCUTS); - AppLaunchTracker.INSTANCE.get(view.getContext()) - .onDismissApp(itemInfo.getTargetComponent(), - itemInfo.user, - AppLaunchTracker.CONTAINER_PREDICTIONS); - }; + public void onClick(View view) { + PopupContainerWithArrow.closeAllOpenViews(mTarget); + mTarget.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP, + ControlType.DISMISS_PREDICTION, ContainerType.DEEPSHORTCUTS); + AppLaunchTracker.INSTANCE.get(view.getContext()).onDismissApp( + mItemInfo.getTargetComponent(), + mItemInfo.user, + AppLaunchTracker.CONTAINER_PREDICTIONS); } } - protected static void dismissTaskMenuView(BaseDraggingActivity activity) { + public static void dismissTaskMenuView(BaseDraggingActivity activity) { AbstractFloatingView.closeOpenViews(activity, true, AbstractFloatingView.TYPE_ALL & ~AbstractFloatingView.TYPE_REBIND_SAFE); } diff --git a/src/com/android/launcher3/popup/SystemShortcutFactory.java b/src/com/android/launcher3/popup/SystemShortcutFactory.java deleted file mode 100644 index dfcc2f8224..0000000000 --- a/src/com/android/launcher3/popup/SystemShortcutFactory.java +++ /dev/null @@ -1,61 +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.popup; - -import static com.android.launcher3.util.MainThreadInitializedObject.forOverride; - -import androidx.annotation.NonNull; - -import com.android.launcher3.ItemInfo; -import com.android.launcher3.Launcher; -import com.android.launcher3.R; -import com.android.launcher3.util.MainThreadInitializedObject; -import com.android.launcher3.util.ResourceBasedOverride; - -import java.util.ArrayList; -import java.util.List; - -public class SystemShortcutFactory implements ResourceBasedOverride { - - public static final MainThreadInitializedObject INSTANCE = - forOverride(SystemShortcutFactory.class, R.string.system_shortcut_factory_class); - - /** Note that these are in order of priority. */ - private final SystemShortcut[] mAllShortcuts; - - @SuppressWarnings("unused") - public SystemShortcutFactory() { - this(new SystemShortcut.AppInfo(), - new SystemShortcut.Widgets(), - new SystemShortcut.Install(), - new SystemShortcut.DismissPrediction()); - } - - protected SystemShortcutFactory(SystemShortcut... shortcuts) { - mAllShortcuts = shortcuts; - } - - public @NonNull List getEnabledShortcuts(Launcher launcher, ItemInfo info) { - List systemShortcuts = new ArrayList<>(); - for (SystemShortcut systemShortcut : mAllShortcuts) { - if (systemShortcut.getOnClickListener(launcher, info) != null) { - systemShortcuts.add(systemShortcut); - } - } - - return systemShortcuts; - } -} diff --git a/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java b/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java index 408ced20e2..8dd90e4ab1 100644 --- a/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java +++ b/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java @@ -25,6 +25,7 @@ import android.view.View; import com.android.launcher3.Launcher; import com.android.launcher3.Utilities; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.graphics.DragPreviewProvider; import com.android.launcher3.icons.BitmapRenderer; @@ -42,16 +43,36 @@ public class ShortcutDragPreviewProvider extends DragPreviewProvider { @Override public Bitmap createDragBitmap() { + if (FeatureFlags.ENABLE_DEEP_SHORTCUT_ICON_CACHE.get()) { + int size = Launcher.getLauncher(mView.getContext()).getDeviceProfile().iconSizePx; + return BitmapRenderer.createHardwareBitmap( + size + blurSizeOutline, + size + blurSizeOutline, + (c) -> drawDragViewOnBackground(c, size)); + } else { + return createDragBitmapLegacy(); + } + } + + private Bitmap createDragBitmapLegacy() { + Drawable d = mView.getBackground(); + Rect bounds = getDrawableBounds(d); int size = Launcher.getLauncher(mView.getContext()).getDeviceProfile().iconSizePx; return BitmapRenderer.createHardwareBitmap( size + blurSizeOutline, size + blurSizeOutline, - (c) -> drawDragViewOnBackground(c, size)); + Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(b); + canvas.translate(blurSizeOutline / 2, blurSizeOutline / 2); + canvas.scale(size / bounds.width(), size / bounds.height(), 0, 0); + canvas.translate(bounds.left, bounds.top); + d.draw(canvas); } private void drawDragViewOnBackground(Canvas canvas, float size) { Drawable d = mView.getBackground(); Rect bounds = getDrawableBounds(d); + canvas.translate(blurSizeOutline / 2, blurSizeOutline / 2); canvas.scale(size / bounds.width(), size / bounds.height(), 0, 0); canvas.translate(bounds.left, bounds.top); diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java index d0e648ffc9..64df384da1 100644 --- a/src/com/android/launcher3/testing/TestInformationHandler.java +++ b/src/com/android/launcher3/testing/TestInformationHandler.java @@ -24,9 +24,10 @@ import android.graphics.Bitmap; import android.graphics.Color; import android.os.Bundle; import android.os.Debug; -import android.util.Log; import android.view.View; +import androidx.annotation.Keep; + import com.android.launcher3.DeviceProfile; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.Launcher; @@ -177,11 +178,6 @@ public class TestInformationHandler implements ResourceBasedOverride { } protected boolean isLauncherInitialized() { - if (TestProtocol.sDebugTracing) { - Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE, - "isLauncherInitialized " + Launcher.ACTIVITY_TRACKER.getCreatedActivity() + ", " - + LauncherAppState.getInstance(mContext).getModel().isModelLoaded()); - } return Launcher.ACTIVITY_TRACKER.getCreatedActivity() == null || LauncherAppState.getInstance(mContext).getModel().isModelLoaded(); } @@ -191,6 +187,22 @@ public class TestInformationHandler implements ResourceBasedOverride { Runtime.getRuntime().runFinalization(); final CountDownLatch fence = new CountDownLatch(1); + createFinalizationObserver(fence); + try { + do { + Runtime.getRuntime().gc(); + Runtime.getRuntime().runFinalization(); + } while (!fence.await(100, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ex) { + throw new RuntimeException(ex); + } + } + + // Create the observer in the scope of a method to minimize the chance that + // it remains live in a DEX/machine register at the point of the fence guard. + // This must be kept to avoid R8 inlining it. + @Keep + private static void createFinalizationObserver(CountDownLatch fence) { new Object() { @Override protected void finalize() throws Throwable { @@ -201,13 +213,5 @@ public class TestInformationHandler implements ResourceBasedOverride { } } }; - try { - do { - Runtime.getRuntime().gc(); - Runtime.getRuntime().runFinalization(); - } while (!fence.await(100, TimeUnit.MILLISECONDS)); - } catch (InterruptedException ex) { - throw new RuntimeException(ex); - } } } diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java index 923c466d2e..1cfa4afc5d 100644 --- a/src/com/android/launcher3/testing/TestProtocol.java +++ b/src/com/android/launcher3/testing/TestProtocol.java @@ -84,7 +84,4 @@ public final class TestProtocol { public static final String NO_BACKGROUND_TO_OVERVIEW_TAG = "b/138251824"; public static final String NO_DRAG_TO_WORKSPACE = "b/138729456"; public static final String APP_NOT_DISABLED = "b/139891609"; - public static final String NO_CONTEXT_MENU = "b/141770616"; - public static final String LAUNCHER_DIDNT_INITIALIZE = "b/142514365"; - public static final String CRASH_ADD_CUSTOM_SHORTCUT = "b/141568904"; } diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java index c5ba5bab6a..60f6ee9c52 100644 --- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java +++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java @@ -53,7 +53,7 @@ import com.android.launcher3.util.TouchController; * TouchController for handling state changes */ public abstract class AbstractStateChangeTouchController - implements TouchController, SwipeDetector.Listener { + implements TouchController, SingleAxisSwipeDetector.Listener { // Progress after which the transition is assumed to be a success in case user does not fling public static final float SUCCESS_TRANSITION_PROGRESS = 0.5f; @@ -65,8 +65,8 @@ public abstract class AbstractStateChangeTouchController protected final long ATOMIC_DURATION = getAtomicDuration(); protected final Launcher mLauncher; - protected final SwipeDetector mDetector; - protected final SwipeDetector.Direction mSwipeDirection; + protected final SingleAxisSwipeDetector mDetector; + protected final SingleAxisSwipeDetector.Direction mSwipeDirection; private boolean mNoIntercept; private boolean mIsLogContainerSet; @@ -101,9 +101,9 @@ public abstract class AbstractStateChangeTouchController private float mAtomicComponentsStartProgress; - public AbstractStateChangeTouchController(Launcher l, SwipeDetector.Direction dir) { + public AbstractStateChangeTouchController(Launcher l, SingleAxisSwipeDetector.Direction dir) { mLauncher = l; - mDetector = new SwipeDetector(l, this, dir); + mDetector = new SingleAxisSwipeDetector(l, this, dir); mSwipeDirection = dir; } @@ -127,7 +127,7 @@ public abstract class AbstractStateChangeTouchController boolean ignoreSlopWhenSettling = false; if (mCurrentAnimation != null) { - directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH; + directionsToDetectScroll = SingleAxisSwipeDetector.DIRECTION_BOTH; ignoreSlopWhenSettling = true; } else { directionsToDetectScroll = getSwipeDirection(); @@ -152,10 +152,10 @@ public abstract class AbstractStateChangeTouchController LauncherState fromState = mLauncher.getStateManager().getState(); int swipeDirection = 0; if (getTargetState(fromState, true /* isDragTowardPositive */) != fromState) { - swipeDirection |= SwipeDetector.DIRECTION_POSITIVE; + swipeDirection |= SingleAxisSwipeDetector.DIRECTION_POSITIVE; } if (getTargetState(fromState, false /* isDragTowardPositive */) != fromState) { - swipeDirection |= SwipeDetector.DIRECTION_NEGATIVE; + swipeDirection |= SingleAxisSwipeDetector.DIRECTION_NEGATIVE; } return swipeDirection; } @@ -369,7 +369,8 @@ public abstract class AbstractStateChangeTouchController } @Override - public void onDragEnd(float velocity, boolean fling) { + public void onDragEnd(float velocity) { + boolean fling = mDetector.isFling(velocity); final int logAction = fling ? Touch.FLING : Touch.SWIPE; boolean blockedFling = fling && mFlingBlockCheck.isBlocked(); @@ -406,7 +407,7 @@ public abstract class AbstractStateChangeTouchController } else { startProgress = Utilities.boundToRange(progress + velocity * getSingleFrameMs(mLauncher) * mProgressMultiplier, 0f, 1f); - duration = SwipeDetector.calculateDuration(velocity, + duration = BaseSwipeDetector.calculateDuration(velocity, endProgress - Math.max(progress, 0)) * durationMultiplier; } } else { @@ -424,7 +425,7 @@ public abstract class AbstractStateChangeTouchController } else { startProgress = Utilities.boundToRange(progress + velocity * getSingleFrameMs(mLauncher) * mProgressMultiplier, 0f, 1f); - duration = SwipeDetector.calculateDuration(velocity, + duration = BaseSwipeDetector.calculateDuration(velocity, Math.min(progress, 1) - endProgress) * durationMultiplier; } } diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java b/src/com/android/launcher3/touch/AllAppsSwipeController.java similarity index 76% rename from src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java rename to src/com/android/launcher3/touch/AllAppsSwipeController.java index bd6ea502e4..31a5d79fc3 100644 --- a/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java +++ b/src/com/android/launcher3/touch/AllAppsSwipeController.java @@ -1,4 +1,19 @@ -package com.android.launcher3.uioverrides; +/** + * 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.touch; import static com.android.launcher3.LauncherState.ALL_APPS; import static com.android.launcher3.LauncherState.NORMAL; @@ -9,8 +24,6 @@ import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; import com.android.launcher3.LauncherStateManager.AnimationComponents; -import com.android.launcher3.touch.AbstractStateChangeTouchController; -import com.android.launcher3.touch.SwipeDetector; import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; /** @@ -21,7 +34,7 @@ public class AllAppsSwipeController extends AbstractStateChangeTouchController { private MotionEvent mTouchDownEvent; public AllAppsSwipeController(Launcher l) { - super(l, SwipeDetector.VERTICAL); + super(l, SingleAxisSwipeDetector.VERTICAL); } @Override @@ -58,8 +71,8 @@ public class AllAppsSwipeController extends AbstractStateChangeTouchController { @Override protected int getLogContainerTypeForNormalState(MotionEvent ev) { - return mLauncher.getDragLayer().isEventOverView(mLauncher.getHotseat(), mTouchDownEvent) ? - ContainerType.HOTSEAT : ContainerType.WORKSPACE; + return mLauncher.getDragLayer().isEventOverView(mLauncher.getHotseat(), mTouchDownEvent) + ? ContainerType.HOTSEAT : ContainerType.WORKSPACE; } @Override diff --git a/src/com/android/launcher3/touch/SwipeDetector.java b/src/com/android/launcher3/touch/BaseSwipeDetector.java similarity index 54% rename from src/com/android/launcher3/touch/SwipeDetector.java rename to src/com/android/launcher3/touch/BaseSwipeDetector.java index c38ca24d92..12ca5ee7b1 100644 --- a/src/com/android/launcher3/touch/SwipeDetector.java +++ b/src/com/android/launcher3/touch/BaseSwipeDetector.java @@ -17,7 +17,6 @@ package com.android.launcher3.touch; import static android.view.MotionEvent.INVALID_POINTER_ID; -import android.content.Context; import android.graphics.PointF; import android.util.Log; import android.view.MotionEvent; @@ -25,133 +24,51 @@ import android.view.VelocityTracker; import android.view.ViewConfiguration; import androidx.annotation.NonNull; -import androidx.annotation.VisibleForTesting; - -import com.android.launcher3.Utilities; /** - * One dimensional scroll/drag/swipe gesture detector. + * Scroll/drag/swipe gesture detector. * * Definition of swipe is different from android system in that this detector handles * 'swipe to dismiss', 'swiping up/down a container' but also keeps scrolling state before - * swipe action happens + * swipe action happens. + * + * @see SingleAxisSwipeDetector + * @see BothAxesSwipeDetector */ -public class SwipeDetector { +public abstract class BaseSwipeDetector { private static final boolean DBG = false; - private static final String TAG = "SwipeDetector"; + private static final String TAG = "BaseSwipeDetector"; private static final float ANIMATION_DURATION = 1200; /** The minimum release velocity in pixels per millisecond that triggers fling.*/ private static final float RELEASE_VELOCITY_PX_MS = 1.0f; - - public static final int DIRECTION_POSITIVE = 1 << 0; - public static final int DIRECTION_NEGATIVE = 1 << 1; - public static final int DIRECTION_BOTH = DIRECTION_NEGATIVE | DIRECTION_POSITIVE; - - public static final Direction VERTICAL = new Direction() { - - @Override - float getDisplacement(MotionEvent ev, int pointerIndex, PointF refPoint, boolean isRtl) { - return ev.getY(pointerIndex) - refPoint.y; - } - - @Override - float getActiveTouchSlop(MotionEvent ev, int pointerIndex, PointF downPos) { - return Math.abs(ev.getX(pointerIndex) - downPos.x); - } - - @Override - float getVelocity(VelocityTracker tracker, boolean isRtl) { - return tracker.getYVelocity(); - } - - @Override - boolean isPositive(float displacement) { - // Up - return displacement < 0; - } - - @Override - boolean isNegative(float displacement) { - // Down - return displacement > 0; - } - }; - - public static final Direction HORIZONTAL = new Direction() { - - @Override - float getDisplacement(MotionEvent ev, int pointerIndex, PointF refPoint, boolean isRtl) { - float displacement = ev.getX(pointerIndex) - refPoint.x; - if (isRtl) { - displacement = -displacement; - } - return displacement; - } - - @Override - float getActiveTouchSlop(MotionEvent ev, int pointerIndex, PointF downPos) { - return Math.abs(ev.getY(pointerIndex) - downPos.y); - } - - @Override - float getVelocity(VelocityTracker tracker, boolean isRtl) { - float velocity = tracker.getXVelocity(); - if (isRtl) { - velocity = -velocity; - } - return velocity; - } - - @Override - boolean isPositive(float displacement) { - // Right - return displacement > 0; - } - - @Override - boolean isNegative(float displacement) { - // Left - return displacement < 0; - } - }; + private static final PointF sTempPoint = new PointF(); private final PointF mDownPos = new PointF(); private final PointF mLastPos = new PointF(); - private final Direction mDir; - private final boolean mIsRtl; - private final float mTouchSlop; - private final float mMaxVelocity; - /* Client of this gesture detector can register a callback. */ - private final Listener mListener; + protected final boolean mIsRtl; + protected final float mTouchSlop; + protected final float mMaxVelocity; private int mActivePointerId = INVALID_POINTER_ID; private VelocityTracker mVelocityTracker; - private float mLastDisplacement; - private float mDisplacement; - private float mSubtractDisplacement; - private boolean mIgnoreSlopWhenSettling; - private int mScrollDirections; + private PointF mLastDisplacement = new PointF(); + private PointF mDisplacement = new PointF(); + protected PointF mSubtractDisplacement = new PointF(); private ScrollState mState = ScrollState.IDLE; + protected boolean mIgnoreSlopWhenSettling; + private enum ScrollState { IDLE, DRAGGING, // onDragStart, onDrag SETTLING // onDragEnd } - public SwipeDetector(@NonNull Context context, @NonNull Listener l, @NonNull Direction dir) { - this(ViewConfiguration.get(context), l, dir, Utilities.isRtl(context.getResources())); - } - - @VisibleForTesting - protected SwipeDetector(@NonNull ViewConfiguration config, @NonNull Listener l, - @NonNull Direction dir, boolean isRtl) { - mListener = l; - mDir = dir; - mIsRtl = isRtl; + protected BaseSwipeDetector(@NonNull ViewConfiguration config, boolean isRtl) { mTouchSlop = config.getScaledTouchSlop(); mMaxVelocity = config.getScaledMaximumFlingVelocity(); + mIsRtl = isRtl; } public static long calculateDuration(float velocity, float progressNeeded) { @@ -192,27 +109,12 @@ public class SwipeDetector { return mState == ScrollState.DRAGGING || mState == ScrollState.SETTLING; } - public void setDetectableScrollConditions(int scrollDirectionFlags, boolean ignoreSlop) { - mScrollDirections = scrollDirectionFlags; - mIgnoreSlopWhenSettling = ignoreSlop; - } - - public int getScrollDirections() { - return mScrollDirections; - } - public void finishedScrolling() { setState(ScrollState.IDLE); } - /** - * Returns if the start drag was towards the positive direction or negative. - * - * @see #setDetectableScrollConditions(int, boolean) - * @see #DIRECTION_BOTH - */ - public boolean wasInitialTouchPositive() { - return mDir.isPositive(mSubtractDisplacement); + public boolean isFling(float velocity) { + return Math.abs(velocity) > RELEASE_VELOCITY_PX_MS; } public boolean onTouchEvent(MotionEvent ev) { @@ -230,8 +132,8 @@ public class SwipeDetector { mActivePointerId = ev.getPointerId(0); mDownPos.set(ev.getX(), ev.getY()); mLastPos.set(mDownPos); - mLastDisplacement = 0; - mDisplacement = 0; + mLastDisplacement.set(0, 0); + mDisplacement.set(0, 0); if (mState == ScrollState.SETTLING && mIgnoreSlopWhenSettling) { setState(ScrollState.DRAGGING); @@ -255,10 +157,14 @@ public class SwipeDetector { if (pointerIndex == INVALID_POINTER_ID) { break; } - mDisplacement = mDir.getDisplacement(ev, pointerIndex, mDownPos, mIsRtl); + mDisplacement.set(ev.getX(pointerIndex) - mDownPos.x, + ev.getY(pointerIndex) - mDownPos.y); + if (mIsRtl) { + mDisplacement.x = -mDisplacement.x; + } // handle state and listener calls. - if (mState != ScrollState.DRAGGING && shouldScrollStart(ev, pointerIndex)) { + if (mState != ScrollState.DRAGGING && shouldScrollStart(mDisplacement)) { setState(ScrollState.DRAGGING); } if (mState == ScrollState.DRAGGING) { @@ -308,84 +214,55 @@ public class SwipeDetector { mState = newState; } - private boolean shouldScrollStart(MotionEvent ev, int pointerIndex) { - // reject cases where the angle or slop condition is not met. - if (Math.max(mDir.getActiveTouchSlop(ev, pointerIndex, mDownPos), mTouchSlop) - > Math.abs(mDisplacement)) { - return false; + private void initializeDragging() { + if (mState == ScrollState.SETTLING && mIgnoreSlopWhenSettling) { + mSubtractDisplacement.set(0, 0); + } else { + mSubtractDisplacement.x = mDisplacement.x > 0 ? mTouchSlop : -mTouchSlop; + mSubtractDisplacement.y = mDisplacement.y > 0 ? mTouchSlop : -mTouchSlop; } - - // Check if the client is interested in scroll in current direction. - return ((mScrollDirections & DIRECTION_NEGATIVE) > 0 && mDir.isNegative(mDisplacement)) - || ((mScrollDirections & DIRECTION_POSITIVE) > 0 && mDir.isPositive(mDisplacement)); } + protected abstract boolean shouldScrollStart(PointF displacement); + private void reportDragStart(boolean recatch) { - mListener.onDragStart(!recatch); + reportDragStartInternal(recatch); if (DBG) { Log.d(TAG, "onDragStart recatch:" + recatch); } } - private void initializeDragging() { - if (mState == ScrollState.SETTLING && mIgnoreSlopWhenSettling) { - mSubtractDisplacement = 0; - } else if (mDisplacement > 0) { - mSubtractDisplacement = mTouchSlop; - } else { - mSubtractDisplacement = -mTouchSlop; - } - } + protected abstract void reportDragStartInternal(boolean recatch); private void reportDragging(MotionEvent event) { if (mDisplacement != mLastDisplacement) { if (DBG) { - Log.d(TAG, String.format("onDrag disp=%.1f", mDisplacement)); + Log.d(TAG, String.format("onDrag disp=%s", mDisplacement)); } - mLastDisplacement = mDisplacement; - mListener.onDrag(mDisplacement - mSubtractDisplacement, event); + mLastDisplacement.set(mDisplacement); + sTempPoint.set(mDisplacement.x - mSubtractDisplacement.x, + mDisplacement.y - mSubtractDisplacement.y); + reportDraggingInternal(sTempPoint, event); } } + protected abstract void reportDraggingInternal(PointF displacement, MotionEvent event); + private void reportDragEnd() { mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity); - float velocity = mDir.getVelocity(mVelocityTracker, mIsRtl) / 1000; + PointF velocity = new PointF(mVelocityTracker.getXVelocity() / 1000, + mVelocityTracker.getYVelocity() / 1000); + if (mIsRtl) { + velocity.x = -velocity.x; + } if (DBG) { - Log.d(TAG, String.format("onScrollEnd disp=%.1f, velocity=%.1f", + Log.d(TAG, String.format("onScrollEnd disp=%.1s, velocity=%.1s", mDisplacement, velocity)); } - mListener.onDragEnd(velocity, Math.abs(velocity) > RELEASE_VELOCITY_PX_MS); + reportDragEndInternal(velocity); } - /** Listener to receive updates on the swipe. */ - public interface Listener { - void onDragStart(boolean start); - - boolean onDrag(float displacement); - - default boolean onDrag(float displacement, MotionEvent event) { - return onDrag(displacement); - } - - void onDragEnd(float velocity, boolean fling); - } - - public abstract static class Direction { - - abstract float getDisplacement(MotionEvent ev, int pointerIndex, PointF refPoint, - boolean isRtl); - - /** - * Distance in pixels a touch can wander before we think the user is scrolling. - */ - abstract float getActiveTouchSlop(MotionEvent ev, int pointerIndex, PointF downPos); - - abstract float getVelocity(VelocityTracker tracker, boolean isRtl); - - abstract boolean isPositive(float displacement); - - abstract boolean isNegative(float displacement); - } + protected abstract void reportDragEndInternal(PointF velocity); } diff --git a/src/com/android/launcher3/touch/BothAxesSwipeDetector.java b/src/com/android/launcher3/touch/BothAxesSwipeDetector.java new file mode 100644 index 0000000000..944391e9b4 --- /dev/null +++ b/src/com/android/launcher3/touch/BothAxesSwipeDetector.java @@ -0,0 +1,99 @@ +/* + * 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.touch; + +import android.content.Context; +import android.graphics.PointF; +import android.view.MotionEvent; +import android.view.ViewConfiguration; + +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; + +import com.android.launcher3.Utilities; + +/** + * Two dimensional scroll/drag/swipe gesture detector that reports x and y displacement/velocity. + */ +public class BothAxesSwipeDetector extends BaseSwipeDetector { + + public static final int DIRECTION_UP = 1 << 0; + // Note that this will track left instead of right in RTL. + public static final int DIRECTION_RIGHT = 1 << 1; + public static final int DIRECTION_DOWN = 1 << 2; + // Note that this will track right instead of left in RTL. + public static final int DIRECTION_LEFT = 1 << 3; + + /* Client of this gesture detector can register a callback. */ + private final Listener mListener; + + private int mScrollDirections; + + public BothAxesSwipeDetector(@NonNull Context context, @NonNull Listener l) { + this(ViewConfiguration.get(context), l, Utilities.isRtl(context.getResources())); + } + + @VisibleForTesting + protected BothAxesSwipeDetector(@NonNull ViewConfiguration config, @NonNull Listener l, + boolean isRtl) { + super(config, isRtl); + mListener = l; + } + + public void setDetectableScrollConditions(int scrollDirectionFlags, boolean ignoreSlop) { + mScrollDirections = scrollDirectionFlags; + mIgnoreSlopWhenSettling = ignoreSlop; + } + + @Override + protected boolean shouldScrollStart(PointF displacement) { + // Check if the client is interested in scroll in current direction. + boolean canScrollUp = (mScrollDirections & DIRECTION_UP) > 0 + && displacement.y <= -mTouchSlop; + boolean canScrollRight = (mScrollDirections & DIRECTION_RIGHT) > 0 + && displacement.x >= mTouchSlop; + boolean canScrollDown = (mScrollDirections & DIRECTION_DOWN) > 0 + && displacement.y >= mTouchSlop; + boolean canScrollLeft = (mScrollDirections & DIRECTION_LEFT) > 0 + && displacement.x <= -mTouchSlop; + return canScrollUp || canScrollRight || canScrollDown || canScrollLeft; + } + + @Override + protected void reportDragStartInternal(boolean recatch) { + mListener.onDragStart(!recatch); + } + + @Override + protected void reportDraggingInternal(PointF displacement, MotionEvent event) { + mListener.onDrag(displacement, event); + } + + @Override + protected void reportDragEndInternal(PointF velocity) { + mListener.onDragEnd(velocity); + } + + /** Listener to receive updates on the swipe. */ + public interface Listener { + /** @param start whether this was the original drag start, as opposed to a recatch. */ + void onDragStart(boolean start); + + boolean onDrag(PointF displacement, MotionEvent motionEvent); + + void onDragEnd(PointF velocity); + } +} diff --git a/src/com/android/launcher3/touch/ItemLongClickListener.java b/src/com/android/launcher3/touch/ItemLongClickListener.java index 86d2b39012..aa02d0aad3 100644 --- a/src/com/android/launcher3/touch/ItemLongClickListener.java +++ b/src/com/android/launcher3/touch/ItemLongClickListener.java @@ -79,19 +79,10 @@ public class ItemLongClickListener { } private static boolean onAllAppsItemLongClick(View v) { - if (TestProtocol.sDebugTracing) { - Log.d(TestProtocol.NO_CONTEXT_MENU, "onAllAppsItemLongClick1"); - } Launcher launcher = Launcher.getLauncher(v.getContext()); if (!canStartDrag(launcher)) return false; - if (TestProtocol.sDebugTracing) { - Log.d(TestProtocol.NO_CONTEXT_MENU, "onAllAppsItemLongClick2"); - } // When we have exited all apps or are in transition, disregard long clicks if (!launcher.isInState(ALL_APPS) && !launcher.isInState(OVERVIEW)) return false; - if (TestProtocol.sDebugTracing) { - Log.d(TestProtocol.NO_CONTEXT_MENU, "onAllAppsItemLongClick3"); - } if (launcher.getWorkspace().isSwitchingState()) return false; // Start the drag diff --git a/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java b/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java new file mode 100644 index 0000000000..f2ebc45193 --- /dev/null +++ b/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java @@ -0,0 +1,190 @@ +/* + * 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.touch; + +import android.content.Context; +import android.graphics.PointF; +import android.view.MotionEvent; +import android.view.ViewConfiguration; + +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; + +import com.android.launcher3.Utilities; + +/** + * One dimensional scroll/drag/swipe gesture detector (either HORIZONTAL or VERTICAL). + */ +public class SingleAxisSwipeDetector extends BaseSwipeDetector { + + public static final int DIRECTION_POSITIVE = 1 << 0; + public static final int DIRECTION_NEGATIVE = 1 << 1; + public static final int DIRECTION_BOTH = DIRECTION_NEGATIVE | DIRECTION_POSITIVE; + + public static final Direction VERTICAL = new Direction() { + + @Override + boolean isPositive(float displacement) { + // Up + return displacement < 0; + } + + @Override + boolean isNegative(float displacement) { + // Down + return displacement > 0; + } + + @Override + float extractDirection(PointF direction) { + return direction.y; + } + + @Override + boolean canScrollStart(PointF displacement, float touchSlop) { + return Math.abs(displacement.y) >= Math.max(Math.abs(displacement.x), touchSlop); + } + + }; + + public static final Direction HORIZONTAL = new Direction() { + + @Override + boolean isPositive(float displacement) { + // Right + return displacement > 0; + } + + @Override + boolean isNegative(float displacement) { + // Left + return displacement < 0; + } + + @Override + float extractDirection(PointF direction) { + return direction.x; + } + + @Override + boolean canScrollStart(PointF displacement, float touchSlop) { + return Math.abs(displacement.x) >= Math.max(Math.abs(displacement.y), touchSlop); + } + }; + + private final Direction mDir; + /* Client of this gesture detector can register a callback. */ + private final Listener mListener; + + private int mScrollDirections; + + public SingleAxisSwipeDetector(@NonNull Context context, @NonNull Listener l, + @NonNull Direction dir) { + this(ViewConfiguration.get(context), l, dir, Utilities.isRtl(context.getResources())); + } + + @VisibleForTesting + protected SingleAxisSwipeDetector(@NonNull ViewConfiguration config, @NonNull Listener l, + @NonNull Direction dir, boolean isRtl) { + super(config, isRtl); + mListener = l; + mDir = dir; + } + + public void setDetectableScrollConditions(int scrollDirectionFlags, boolean ignoreSlop) { + mScrollDirections = scrollDirectionFlags; + mIgnoreSlopWhenSettling = ignoreSlop; + } + + public int getScrollDirections() { + return mScrollDirections; + } + + /** + * Returns if the start drag was towards the positive direction or negative. + * + * @see #setDetectableScrollConditions(int, boolean) + * @see #DIRECTION_BOTH + */ + public boolean wasInitialTouchPositive() { + return mDir.isPositive(mDir.extractDirection(mSubtractDisplacement)); + } + + @Override + protected boolean shouldScrollStart(PointF displacement) { + // Reject cases where the angle or slop condition is not met. + if (!mDir.canScrollStart(displacement, mTouchSlop)) { + return false; + } + + // Check if the client is interested in scroll in current direction. + float displacementComponent = mDir.extractDirection(displacement); + return canScrollNegative(displacementComponent) || canScrollPositive(displacementComponent); + } + + private boolean canScrollNegative(float displacement) { + return (mScrollDirections & DIRECTION_NEGATIVE) > 0 && mDir.isNegative(displacement); + } + + private boolean canScrollPositive(float displacement) { + return (mScrollDirections & DIRECTION_POSITIVE) > 0 && mDir.isPositive(displacement); + } + + @Override + protected void reportDragStartInternal(boolean recatch) { + mListener.onDragStart(!recatch); + } + + @Override + protected void reportDraggingInternal(PointF displacement, MotionEvent event) { + mListener.onDrag(mDir.extractDirection(displacement), event); + } + + @Override + protected void reportDragEndInternal(PointF velocity) { + float velocityComponent = mDir.extractDirection(velocity); + mListener.onDragEnd(velocityComponent); + } + + /** Listener to receive updates on the swipe. */ + public interface Listener { + /** @param start whether this was the original drag start, as opposed to a recatch. */ + void onDragStart(boolean start); + + // TODO remove + boolean onDrag(float displacement); + + default boolean onDrag(float displacement, MotionEvent event) { + return onDrag(displacement); + } + + void onDragEnd(float velocity); + } + + public abstract static class Direction { + + abstract boolean isPositive(float displacement); + + abstract boolean isNegative(float displacement); + + /** Returns the part of the given {@link PointF} that is relevant to this direction. */ + abstract float extractDirection(PointF point); + + /** Reject cases where the angle or slop condition is not met. */ + abstract boolean canScrollStart(PointF displacement, float touchSlop); + + } +} diff --git a/src/com/android/launcher3/util/ActivityTracker.java b/src/com/android/launcher3/util/ActivityTracker.java index b4f361f36c..499f655df1 100644 --- a/src/com/android/launcher3/util/ActivityTracker.java +++ b/src/com/android/launcher3/util/ActivityTracker.java @@ -45,33 +45,46 @@ public final class ActivityTracker implements Runnable { } public void onActivityDestroyed(T activity) { - if (TestProtocol.sDebugTracing) { - Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE, "onActivityDestroyed"); - } if (mCurrentActivity.get() == activity) { - if (TestProtocol.sDebugTracing) { - Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE, "onActivityDestroyed: clear"); - } mCurrentActivity.clear(); } } - public void schedule(SchedulerCallback callback) { + /** + * Schedules the callback to be notified when the activity is created. + * @return true if the activity is already created, false otherwise + */ + public boolean schedule(SchedulerCallback callback) { synchronized (this) { mPendingCallback = new WeakReference<>((SchedulerCallback) callback); } - MAIN_EXECUTOR.execute(this); + if (!notifyInitIfPending()) { + // If the activity doesn't already exist, then post and wait for the activity to start + MAIN_EXECUTOR.execute(this); + return false; + } + return true; } @Override public void run() { - T activity = mCurrentActivity.get(); - if (activity != null) { - initIfPending(activity, activity.isStarted()); - } + notifyInitIfPending(); } - public boolean initIfPending(T activity, boolean alreadyOnHome) { + /** + * Notifies the pending callback if the activity is now created. + * @return true if the activity is now created. + */ + private boolean notifyInitIfPending() { + T activity = mCurrentActivity.get(); + if (activity != null) { + notifyInitIfPending(activity, activity.isStarted()); + return true; + } + return false; + } + + public boolean notifyInitIfPending(T activity, boolean alreadyOnHome) { SchedulerCallback pendingCallback = mPendingCallback.get(); if (pendingCallback != null) { if (!pendingCallback.init(activity, alreadyOnHome)) { @@ -97,10 +110,6 @@ public final class ActivityTracker implements Runnable { } public boolean handleCreate(T activity) { - if (TestProtocol.sDebugTracing) { - Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE, - "ActivityTracker.handleCreate " + mCurrentActivity.get() + " => " + activity); - } mCurrentActivity = new WeakReference<>(activity); return handleIntent(activity, activity.getIntent(), false, false); } @@ -124,7 +133,7 @@ public final class ActivityTracker implements Runnable { } } if (!result && !explicitIntent) { - result = initIfPending(activity, alreadyOnHome); + result = notifyInitIfPending(activity, alreadyOnHome); } return result; } diff --git a/src/com/android/launcher3/util/ContentWriter.java b/src/com/android/launcher3/util/ContentWriter.java index 00adf1069a..2d643531e8 100644 --- a/src/com/android/launcher3/util/ContentWriter.java +++ b/src/com/android/launcher3/util/ContentWriter.java @@ -26,6 +26,7 @@ import android.os.UserHandle; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherSettings; import com.android.launcher3.compat.UserManagerCompat; +import com.android.launcher3.icons.BitmapInfo; import com.android.launcher3.icons.GraphicsUtils; /** @@ -37,7 +38,7 @@ public class ContentWriter { private final Context mContext; private CommitParams mCommitParams; - private Bitmap mIcon; + private BitmapInfo mIcon; private UserHandle mUser; public ContentWriter(Context context, CommitParams commitParams) { @@ -79,7 +80,7 @@ public class ContentWriter { return this; } - public ContentWriter putIcon(Bitmap value, UserHandle user) { + public ContentWriter putIcon(BitmapInfo value, UserHandle user) { mIcon = value; mUser = user; return this; @@ -97,7 +98,7 @@ public class ContentWriter { Preconditions.assertNonUiThread(); if (mIcon != null && !LauncherAppState.getInstance(context).getIconCache() .isDefaultIcon(mIcon, mUser)) { - mValues.put(LauncherSettings.Favorites.ICON, GraphicsUtils.flattenBitmap(mIcon)); + mValues.put(LauncherSettings.Favorites.ICON, GraphicsUtils.flattenBitmap(mIcon.icon)); mIcon = null; } return mValues; diff --git a/src/com/android/launcher3/util/LooperIdleLock.java b/src/com/android/launcher3/util/LooperIdleLock.java index 2896535f8f..f4ccf42e34 100644 --- a/src/com/android/launcher3/util/LooperIdleLock.java +++ b/src/com/android/launcher3/util/LooperIdleLock.java @@ -22,29 +22,30 @@ import android.os.MessageQueue; /** * Utility class to block execution until the UI looper is idle. */ -public class LooperIdleLock implements MessageQueue.IdleHandler, Runnable { +public class LooperIdleLock implements MessageQueue.IdleHandler { private final Object mLock; private boolean mIsLocked; + private Looper mLooper; public LooperIdleLock(Object lock, Looper looper) { mLock = lock; + mLooper = looper; mIsLocked = true; looper.getQueue().addIdleHandler(this); } - @Override - public void run() { - Looper.myQueue().addIdleHandler(this); - } - @Override public boolean queueIdle() { synchronized (mLock) { mIsLocked = false; mLock.notify(); } + // Manually remove from the list in case we're calling this outside of the idle callbacks + // (this is Ok in the normal flow as well because MessageQueue makes a copy of all handlers + // before calling back) + mLooper.getQueue().removeIdleHandler(this); return false; } diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java index 7b4e0c6ede..91f687eb6c 100644 --- a/src/com/android/launcher3/util/PackageManagerHelper.java +++ b/src/com/android/launcher3/util/PackageManagerHelper.java @@ -274,6 +274,9 @@ public class PackageManagerHelper { } else { packageName = cn.getPackageName(); } + if (packageName == null) { + packageName = intent.getPackage(); + } if (packageName != null) { try { PackageInfo info = pm.getPackageInfo(packageName, 0); diff --git a/src/com/android/launcher3/util/ShortcutUtil.java b/src/com/android/launcher3/util/ShortcutUtil.java index a69cd6c085..a03b743233 100644 --- a/src/com/android/launcher3/util/ShortcutUtil.java +++ b/src/com/android/launcher3/util/ShortcutUtil.java @@ -27,7 +27,6 @@ import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherSettings; import com.android.launcher3.Utilities; import com.android.launcher3.WorkspaceItemInfo; -import com.android.launcher3.icons.BitmapInfo; import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.model.WidgetsModel; import com.android.launcher3.shortcuts.ShortcutKey; @@ -77,17 +76,12 @@ public class ShortcutUtil { public static void fetchAndUpdateShortcutIconAsync( @NonNull Context context, @NonNull WorkspaceItemInfo info, @NonNull ShortcutInfo si, boolean badged) { - if (info.iconBitmap == null) { - // use low res icon as placeholder while the actual icon is being fetched. - info.iconBitmap = BitmapInfo.LOW_RES_ICON; - info.iconColor = Themes.getColorAccent(context); - } MODEL_EXECUTOR.execute(() -> { - LauncherIcons li = LauncherIcons.obtain(context); - BitmapInfo bitmapInfo = li.createShortcutIcon(si, badged, true, null); - info.applyFrom(bitmapInfo); - li.recycle(); - LauncherAppState.getInstance(context).getModel().updateAndBindWorkspaceItem(info, si); + try (LauncherIcons li = LauncherIcons.obtain(context)) { + info.bitmap = li.createShortcutIcon(si, badged, null); + LauncherAppState.getInstance(context).getModel() + .updateAndBindWorkspaceItem(info, si); + } }); } diff --git a/src/com/android/launcher3/util/UiThreadHelper.java b/src/com/android/launcher3/util/UiThreadHelper.java index a133f01d4e..ec87e79cfb 100644 --- a/src/com/android/launcher3/util/UiThreadHelper.java +++ b/src/com/android/launcher3/util/UiThreadHelper.java @@ -23,7 +23,6 @@ import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.view.inputmethod.InputMethodManager; -import com.android.launcher3.uioverrides.UiFactory; /** * Utility class for offloading some class from UI thread diff --git a/src/com/android/launcher3/util/VibratorWrapper.java b/src/com/android/launcher3/util/VibratorWrapper.java new file mode 100644 index 0000000000..04741a165b --- /dev/null +++ b/src/com/android/launcher3/util/VibratorWrapper.java @@ -0,0 +1,84 @@ +/* + * 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 static android.os.VibrationEffect.createPredefined; +import static android.provider.Settings.System.HAPTIC_FEEDBACK_ENABLED; + +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; +import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; + +import android.annotation.TargetApi; +import android.content.ContentResolver; +import android.content.Context; +import android.database.ContentObserver; +import android.os.Build; +import android.os.VibrationEffect; +import android.os.Vibrator; +import android.provider.Settings; + +/** + * Wrapper around {@link Vibrator} to easily perform haptic feedback where necessary. + */ +@TargetApi(Build.VERSION_CODES.Q) +public class VibratorWrapper { + + public static final MainThreadInitializedObject INSTANCE = + new MainThreadInitializedObject<>(VibratorWrapper::new); + + private static final VibrationEffect EFFECT_CLICK = + createPredefined(VibrationEffect.EFFECT_CLICK); + + /** + * Haptic when entering overview. + */ + public static final VibrationEffect OVERVIEW_HAPTIC = EFFECT_CLICK; + + private final Vibrator mVibrator; + private final boolean mHasVibrator; + + private boolean mIsHapticFeedbackEnabled; + + public VibratorWrapper(Context context) { + mVibrator = context.getSystemService(Vibrator.class); + mHasVibrator = mVibrator.hasVibrator(); + if (mHasVibrator) { + final ContentResolver resolver = context.getContentResolver(); + mIsHapticFeedbackEnabled = isHapticFeedbackEnabled(resolver); + final ContentObserver observer = new ContentObserver(MAIN_EXECUTOR.getHandler()) { + @Override + public void onChange(boolean selfChange) { + mIsHapticFeedbackEnabled = isHapticFeedbackEnabled(resolver); + } + }; + resolver.registerContentObserver(Settings.System.getUriFor(HAPTIC_FEEDBACK_ENABLED), + false /* notifyForDescendents */, observer); + } else { + mIsHapticFeedbackEnabled = false; + } + } + + private boolean isHapticFeedbackEnabled(ContentResolver resolver) { + return Settings.System.getInt(resolver, HAPTIC_FEEDBACK_ENABLED, 0) == 1; + } + + /** Vibrates with the given effect if haptic feedback is available and enabled. */ + public void vibrate(VibrationEffect vibrationEffect) { + if (mHasVibrator && mIsHapticFeedbackEnabled) { + UI_HELPER_EXECUTOR.execute(() -> mVibrator.vibrate(vibrationEffect)); + } + } +} diff --git a/src/com/android/launcher3/util/ViewOnDrawExecutor.java b/src/com/android/launcher3/util/ViewOnDrawExecutor.java index 61ba4e5662..5a131c83f8 100644 --- a/src/com/android/launcher3/util/ViewOnDrawExecutor.java +++ b/src/com/android/launcher3/util/ViewOnDrawExecutor.java @@ -55,7 +55,9 @@ public class ViewOnDrawExecutor implements Executor, OnDrawListener, Runnable, mLoadAnimationCompleted = true; } - attachObserver(); + if (mAttachedView.isAttachedToWindow()) { + attachObserver(); + } } private void attachObserver() { diff --git a/src/com/android/launcher3/views/AbstractSlideInView.java b/src/com/android/launcher3/views/AbstractSlideInView.java index a4518bae3c..195a77ad5d 100644 --- a/src/com/android/launcher3/views/AbstractSlideInView.java +++ b/src/com/android/launcher3/views/AbstractSlideInView.java @@ -32,13 +32,14 @@ import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.Launcher; import com.android.launcher3.Utilities; import com.android.launcher3.anim.Interpolators; -import com.android.launcher3.touch.SwipeDetector; +import com.android.launcher3.touch.BaseSwipeDetector; +import com.android.launcher3.touch.SingleAxisSwipeDetector; /** * Extension of AbstractFloatingView with common methods for sliding in from bottom */ public abstract class AbstractSlideInView extends AbstractFloatingView - implements SwipeDetector.Listener { + implements SingleAxisSwipeDetector.Listener { protected static Property TRANSLATION_SHIFT = new Property(Float.class, "translationShift") { @@ -57,7 +58,7 @@ public abstract class AbstractSlideInView extends AbstractFloatingView protected static final float TRANSLATION_SHIFT_OPENED = 0f; protected final Launcher mLauncher; - protected final SwipeDetector mSwipeDetector; + protected final SingleAxisSwipeDetector mSwipeDetector; protected final ObjectAnimator mOpenCloseAnimator; protected View mContent; @@ -73,7 +74,8 @@ public abstract class AbstractSlideInView extends AbstractFloatingView mLauncher = Launcher.getLauncher(context); mScrollInterpolator = Interpolators.SCROLL_CUBIC; - mSwipeDetector = new SwipeDetector(context, this, SwipeDetector.VERTICAL); + mSwipeDetector = new SingleAxisSwipeDetector(context, this, + SingleAxisSwipeDetector.VERTICAL); mOpenCloseAnimator = ObjectAnimator.ofPropertyValuesHolder(this); mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() { @@ -97,7 +99,7 @@ public abstract class AbstractSlideInView extends AbstractFloatingView } int directionsToDetectScroll = mSwipeDetector.isIdleState() ? - SwipeDetector.DIRECTION_NEGATIVE : 0; + SingleAxisSwipeDetector.DIRECTION_NEGATIVE : 0; mSwipeDetector.setDetectableScrollConditions( directionsToDetectScroll, false); mSwipeDetector.onTouchEvent(ev); @@ -122,7 +124,7 @@ public abstract class AbstractSlideInView extends AbstractFloatingView return mIsOpen && mOpenCloseAnimator.isRunning(); } - /* SwipeDetector.Listener */ + /* SingleAxisSwipeDetector.Listener */ @Override public void onDragStart(boolean start) { } @@ -136,17 +138,17 @@ public abstract class AbstractSlideInView extends AbstractFloatingView } @Override - public void onDragEnd(float velocity, boolean fling) { - if ((fling && velocity > 0) || mTranslationShift > 0.5f) { + public void onDragEnd(float velocity) { + if ((mSwipeDetector.isFling(velocity) && velocity > 0) || mTranslationShift > 0.5f) { mScrollInterpolator = scrollInterpolatorForVelocity(velocity); - mOpenCloseAnimator.setDuration(SwipeDetector.calculateDuration( + mOpenCloseAnimator.setDuration(BaseSwipeDetector.calculateDuration( velocity, TRANSLATION_SHIFT_CLOSED - mTranslationShift)); close(true); } else { mOpenCloseAnimator.setValues(PropertyValuesHolder.ofFloat( TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED)); mOpenCloseAnimator.setDuration( - SwipeDetector.calculateDuration(velocity, mTranslationShift)) + BaseSwipeDetector.calculateDuration(velocity, mTranslationShift)) .setInterpolator(Interpolators.DEACCEL); mOpenCloseAnimator.start(); } diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java index 2a4c5a7bfe..e43fc8a01c 100644 --- a/src/com/android/launcher3/views/BaseDragLayer.java +++ b/src/com/android/launcher3/views/BaseDragLayer.java @@ -170,10 +170,8 @@ public abstract class BaseDragLayer // Only look for controllers if we are not dispatching from gesture area and proxy is // not active mActiveController = findControllerToHandleTouch(ev); - - if (mActiveController != null) return true; } - return false; + return mActiveController != null; } @Override diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java index 45c0d90971..c63d745e0d 100644 --- a/src/com/android/launcher3/views/FloatingIconView.java +++ b/src/com/android/launcher3/views/FloatingIconView.java @@ -415,7 +415,7 @@ public class FloatingIconView extends View implements int width = isFolderIcon ? originalView.getWidth() : (int) pos.width(); int height = isFolderIcon ? originalView.getHeight() : (int) pos.height(); if (supportsAdaptiveIcons) { - drawable = getFullDrawable(l, info, width, height, false, sTmpObjArray); + drawable = getFullDrawable(l, info, width, height, sTmpObjArray); if (drawable instanceof AdaptiveIconDrawable) { badge = getBadge(l, info, sTmpObjArray[0]); } else { @@ -428,7 +428,7 @@ public class FloatingIconView extends View implements // Similar to DragView, we simply use the BubbleTextView icon here. drawable = btvIcon; } else { - drawable = getFullDrawable(l, info, width, height, false, sTmpObjArray); + drawable = getFullDrawable(l, info, width, height, sTmpObjArray); } } } diff --git a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java index 50db40fde8..6038873c42 100644 --- a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java +++ b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java @@ -16,6 +16,9 @@ package com.android.launcher3.widget; +import static com.android.launcher3.FastBitmapDrawable.newIcon; +import static com.android.launcher3.graphics.PreloadIconDrawable.newPendingIcon; + import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; @@ -33,12 +36,11 @@ import android.view.View.OnClickListener; import com.android.launcher3.DeviceProfile; import com.android.launcher3.FastBitmapDrawable; -import com.android.launcher3.icons.IconCache; -import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver; import com.android.launcher3.ItemInfoWithIcon; import com.android.launcher3.LauncherAppWidgetInfo; import com.android.launcher3.R; -import com.android.launcher3.graphics.DrawableFactory; +import com.android.launcher3.icons.IconCache; +import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver; import com.android.launcher3.model.PackageItemInfo; import com.android.launcher3.touch.ItemClickHandler; import com.android.launcher3.util.Themes; @@ -128,24 +130,22 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView mCenterDrawable.setCallback(null); mCenterDrawable = null; } - if (info.iconBitmap != null) { + if (info.bitmap.icon != null) { // The view displays three modes, // 1) App icon in the center // 2) Preload icon in the center // 3) Setup icon in the center and app icon in the top right corner. - DrawableFactory drawableFactory = DrawableFactory.INSTANCE.get(getContext()); if (mDisabledForSafeMode) { - FastBitmapDrawable disabledIcon = drawableFactory.newIcon(getContext(), info); + FastBitmapDrawable disabledIcon = newIcon(getContext(), info); disabledIcon.setIsDisabled(true); mCenterDrawable = disabledIcon; mSettingIconDrawable = null; } else if (isReadyForClickSetup()) { - mCenterDrawable = drawableFactory.newIcon(getContext(), info); + mCenterDrawable = newIcon(getContext(), info); mSettingIconDrawable = getResources().getDrawable(R.drawable.ic_setting).mutate(); - updateSettingColor(info.iconColor); + updateSettingColor(info.bitmap.color); } else { - mCenterDrawable = DrawableFactory.INSTANCE.get(getContext()) - .newPendingIcon(getContext(), info); + mCenterDrawable = newPendingIcon(getContext(), info); mSettingIconDrawable = null; applyState(); } diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java index 6944879beb..f713b339bd 100644 --- a/src/com/android/launcher3/widget/WidgetCell.java +++ b/src/com/android/launcher3/widget/WidgetCell.java @@ -36,7 +36,6 @@ import com.android.launcher3.R; import com.android.launcher3.SimpleOnStylusPressListener; import com.android.launcher3.StylusEventHelper; import com.android.launcher3.WidgetPreviewLoader; -import com.android.launcher3.graphics.DrawableFactory; import com.android.launcher3.icons.BaseIconFactory; import com.android.launcher3.model.WidgetItem; @@ -182,10 +181,8 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { return; } if (bitmap != null) { - mWidgetImage.setBitmap(bitmap, - DrawableFactory.INSTANCE.get(getContext()).getBadgeForUser(mItem.user, - getContext(), BaseIconFactory.getBadgeSizeForIconSize( - mDeviceProfile.allAppsIconSizePx))); + mWidgetImage.setBitmap(bitmap, mWidgetPreviewLoader.getBadgeForUser(mItem.user, + BaseIconFactory.getBadgeSizeForIconSize(mDeviceProfile.allAppsIconSizePx))); if (mAnimatePreview) { mWidgetImage.setAlpha(0f); ViewPropertyAnimator anim = mWidgetImage.animate(); diff --git a/src/com/android/launcher3/widget/WidgetsDiffReporter.java b/src/com/android/launcher3/widget/WidgetsDiffReporter.java index 435125bd83..f3b325db58 100644 --- a/src/com/android/launcher3/widget/WidgetsDiffReporter.java +++ b/src/com/android/launcher3/widget/WidgetsDiffReporter.java @@ -18,6 +18,8 @@ package com.android.launcher3.widget; import android.util.Log; +import androidx.recyclerview.widget.RecyclerView; + import com.android.launcher3.icons.IconCache; import com.android.launcher3.model.PackageItemInfo; import com.android.launcher3.widget.WidgetsListAdapter.WidgetListRowEntryComparator; @@ -25,8 +27,6 @@ import com.android.launcher3.widget.WidgetsListAdapter.WidgetListRowEntryCompara import java.util.ArrayList; import java.util.Iterator; -import androidx.recyclerview.widget.RecyclerView; - /** * Do diff on widget's tray list items and call the {@link RecyclerView.Adapter} * methods accordingly. @@ -137,7 +137,7 @@ public class WidgetsDiffReporter { } private boolean isSamePackageItemInfo(PackageItemInfo curInfo, PackageItemInfo newInfo) { - return curInfo.iconBitmap.equals(newInfo.iconBitmap) && - !mIconCache.isDefaultIcon(curInfo.iconBitmap, curInfo.user); + return curInfo.bitmap.icon.equals(newInfo.bitmap.icon) + && !mIconCache.isDefaultIcon(curInfo.bitmap, curInfo.user); } } diff --git a/src_plugins/com/android/systemui/plugins/OverscrollPlugin.java b/src_plugins/com/android/systemui/plugins/OverscrollPlugin.java new file mode 100644 index 0000000000..60eb30418e --- /dev/null +++ b/src_plugins/com/android/systemui/plugins/OverscrollPlugin.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.plugins; + +import com.android.systemui.plugins.annotations.ProvidesInterface; + +/** + * Implement this interface to receive a callback when the user swipes right + * to left on the gesture area. It won't fire if the user has quick switched to a previous app + * (swiped right) and the current app isn't yet the active one (i.e., if swiping left would take + * the user to a more recent app). + */ +@ProvidesInterface(action = com.android.systemui.plugins.OverscrollPlugin.ACTION, + version = com.android.systemui.plugins.OverlayPlugin.VERSION) +public interface OverscrollPlugin extends Plugin { + + String ACTION = "com.android.systemui.action.PLUGIN_LAUNCHER_OVERSCROLL"; + int VERSION = 1; + + String DEVICE_STATE_LOCKED = "Locked"; + String DEVICE_STATE_LAUNCHER = "Launcher"; + String DEVICE_STATE_APP = "App"; + String DEVICE_STATE_UNKNOWN = "Unknown"; + + /** + * Called when the user completed a right to left swipe in the gesture area. + * + * @param deviceState One of the DEVICE_STATE_* constants. + */ + void onOverscroll(String deviceState); +} diff --git a/src_plugins/com/android/systemui/plugins/RecentsExtraCard.java b/src_plugins/com/android/systemui/plugins/RecentsExtraCard.java index 0ebea3d470..cd9f33dd05 100644 --- a/src_plugins/com/android/systemui/plugins/RecentsExtraCard.java +++ b/src_plugins/com/android/systemui/plugins/RecentsExtraCard.java @@ -34,9 +34,9 @@ public interface RecentsExtraCard extends Plugin { /** * Sets up the recents overview extra card and fills in data. * - * @param context Plugin context + * @param context Plugin context * @param frameLayout PlaceholderView - * @param activity Recents activity to hold extra view + * @param activity Recents activity to hold extra view */ void setupView(Context context, FrameLayout frameLayout, Activity activity); } diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java b/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java new file mode 100644 index 0000000000..5407ea3182 --- /dev/null +++ b/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java @@ -0,0 +1,36 @@ +/* + * 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 android.app.Activity; +import android.app.Person; +import android.content.pm.ShortcutInfo; + +import com.android.launcher3.Utilities; + +import java.io.PrintWriter; + +public class ApiWrapper { + + public static boolean dumpActivity(Activity activity, PrintWriter writer) { + return false; + } + + public static Person[] getPersons(ShortcutInfo si) { + return Utilities.EMPTY_PERSON_ARRAY; + } +} diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java deleted file mode 100644 index 6d9ed88e08..0000000000 --- a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java +++ /dev/null @@ -1,105 +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 android.app.Activity; -import android.app.Person; -import android.content.Context; -import android.content.Intent; -import android.content.IntentSender; -import android.content.pm.ShortcutInfo; -import android.os.Bundle; -import android.os.CancellationSignal; - -import com.android.launcher3.DeviceProfile; -import com.android.launcher3.Launcher; -import com.android.launcher3.LauncherState.ScaleAndTranslation; -import com.android.launcher3.LauncherStateManager.StateHandler; -import com.android.launcher3.Utilities; -import com.android.launcher3.graphics.RotationMode; -import com.android.launcher3.util.TouchController; - -import java.io.PrintWriter; - -public class UiFactory { - - public static TouchController[] createTouchControllers(Launcher launcher) { - return new TouchController[] { - launcher.getDragController(), new AllAppsSwipeController(launcher)}; - } - - public static Runnable enableLiveUIChanges(Launcher l) { - return null; - } - - public static StateHandler[] getStateHandler(Launcher launcher) { - return new StateHandler[] { - launcher.getAllAppsController(), launcher.getWorkspace() }; - } - - public static void resetOverview(Launcher launcher) { } - - public static void onLauncherStateOrFocusChanged(Launcher launcher) { } - - public static void onCreate(Launcher launcher) { } - - public static void onStart(Launcher launcher) { } - - public static void onEnterAnimationComplete(Context context) {} - - public static void onLauncherStateOrResumeChanged(Launcher launcher) { } - - public static void onTrimMemory(Launcher launcher, int level) { } - - public static void useFadeOutAnimationForLauncherStart(Launcher launcher, - CancellationSignal cancellationSignal) { } - - public static boolean dumpActivity(Activity activity, PrintWriter writer) { - return false; - } - - public static void setBackButtonAlpha(Launcher launcher, float alpha, boolean animate) { } - - - public static ScaleAndTranslation getOverviewScaleAndTranslationForNormalState(Launcher l) { - return new ScaleAndTranslation(1.1f, 0f, 0f); - } - - public static RotationMode getRotationMode(DeviceProfile dp) { - return RotationMode.NORMAL; - } - - public static boolean startIntentSenderForResult(Activity activity, IntentSender intent, - int requestCode, Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, - Bundle options) { - return false; - } - - public static boolean startActivityForResult(Activity activity, Intent intent, int requestCode, - Bundle options) { - return false; - } - - public static void resetPendingActivityResults(Launcher launcher, int requestCode) { } - - /** No-op. */ - public static void clearSwipeSharedState(Launcher launcher, boolean finishAnimation) { } - - public static Person[] getPersons(ShortcutInfo si) { - return Utilities.EMPTY_PERSON_ARRAY; - } -} diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml index 24b5b02fbd..5cf96c8554 100644 --- a/tests/AndroidManifest.xml +++ b/tests/AndroidManifest.xml @@ -18,7 +18,7 @@ xmlns:tools="http://schemas.android.com/tools" package="com.android.launcher3.tests"> - diff --git a/tests/dummy_app/AndroidManifest.xml b/tests/dummy_app/AndroidManifest.xml index 9d0a74a5ff..f00138c78e 100644 --- a/tests/dummy_app/AndroidManifest.xml +++ b/tests/dummy_app/AndroidManifest.xml @@ -21,7 +21,7 @@ to come from a domain that you own or have control over. --> - + condition, long timeout) { if (!TestHelpers.isInLauncherProcess()) return; - Wait.atMost(message, () -> getFromLauncher(condition), timeout); + Wait.atMost(message, () -> getFromLauncher(condition), timeout, mLauncher); } // Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide @@ -317,7 +317,7 @@ public abstract class AbstractLauncherUiTest { final Object fromLauncher = getFromLauncher(f); output[0] = fromLauncher; return fromLauncher != null; - }, timeout); + }, timeout, mLauncher); return (T) output[0]; } @@ -331,7 +331,7 @@ public abstract class AbstractLauncherUiTest { Wait.atMost(message, () -> { testThreadAction.run(); return getFromLauncher(condition); - }, timeout); + }, timeout, mLauncher); } protected LauncherActivityInfo getSettingsApp() { @@ -373,7 +373,8 @@ public abstract class AbstractLauncherUiTest { startIntent( getInstrumentation().getContext().getPackageManager().getLaunchIntentForPackage( packageName), - By.pkg(packageName).depth(0)); + By.pkg(packageName).depth(0), + true /* newTask */); } public static void startTestActivity(int activityNumber) { @@ -382,12 +383,17 @@ public abstract class AbstractLauncherUiTest { getLaunchIntentForPackage(packageName); intent.setComponent(new ComponentName(packageName, "com.android.launcher3.tests.Activity" + activityNumber)); - startIntent(intent, By.pkg(packageName).text("TestActivity" + activityNumber)); + startIntent(intent, By.pkg(packageName).text("TestActivity" + activityNumber), + false /* newTask */); } - private static void startIntent(Intent intent, BySelector selector) { + private static void startIntent(Intent intent, BySelector selector, boolean newTask) { intent.addCategory(Intent.CATEGORY_LAUNCHER); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + 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, UiDevice.getInstance(getInstrumentation()) diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java index 5e87612f43..0321bcda5b 100644 --- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java +++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java @@ -18,6 +18,10 @@ package com.android.launcher3.ui; import static androidx.test.InstrumentationRegistry.getInstrumentation; +import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_PRESUBMIT; +import static com.android.launcher3.util.rule.TestStabilityRule.RUN_FLAFOR; +import static com.android.launcher3.util.rule.TestStabilityRule.UNBUNDLED_PRESUBMIT; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -34,6 +38,7 @@ import com.android.launcher3.tapl.AllApps; import com.android.launcher3.tapl.AppIcon; import com.android.launcher3.tapl.AppIconMenu; import com.android.launcher3.tapl.AppIconMenuItem; +import com.android.launcher3.tapl.TestHelpers; import com.android.launcher3.tapl.Widgets; import com.android.launcher3.tapl.Workspace; import com.android.launcher3.views.OptionsPopupView; @@ -349,6 +354,7 @@ public class TaplTestsLauncher3 extends AbstractLauncherUiTest { @Ignore("Temporarily disabled to unblock merging to master") @PortraitLandscape public void testDragCustomShortcut() { + if (!TestHelpers.isInLauncherProcess()) return; // b/143725213 mLauncher.getWorkspace().openAllWidgets() .getWidget("com.android.launcher3.testcomponent.CustomShortcutConfigActivity") .dragToWorkspace(); diff --git a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java index e1b3edeb5a..0472ce1c6f 100644 --- a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java +++ b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java @@ -103,12 +103,12 @@ public class AddConfigWidgetTest extends AbstractLauncherUiTest { setResult(acceptConfig); if (acceptConfig) { - Wait.atMost(null, new WidgetSearchCondition(), DEFAULT_ACTIVITY_TIMEOUT); + Wait.atMost(null, new WidgetSearchCondition(), DEFAULT_ACTIVITY_TIMEOUT, mLauncher); assertNotNull(mAppWidgetManager.getAppWidgetInfo(mWidgetId)); } else { // Verify that the widget id is deleted. Wait.atMost(null, () -> mAppWidgetManager.getAppWidgetInfo(mWidgetId) == null, - DEFAULT_ACTIVITY_TIMEOUT); + DEFAULT_ACTIVITY_TIMEOUT, mLauncher); } } diff --git a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java index b8ca5ded38..259f9ed81e 100644 --- a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java +++ b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java @@ -16,6 +16,7 @@ package com.android.launcher3.ui.widget; import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName; +import static com.android.launcher3.util.rule.TestStabilityRule.UNBUNDLED_POSTSUBMIT; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -29,6 +30,7 @@ import com.android.launcher3.tapl.Widget; 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.Stability; import org.junit.Rule; import org.junit.Test; diff --git a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java index 07129ddd95..d909ad7158 100644 --- a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java +++ b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java @@ -59,7 +59,8 @@ import java.util.UUID; @RunWith(AndroidJUnit4.class) public class RequestPinItemTest extends AbstractLauncherUiTest { - @Rule public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind(); + @Rule + public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind(); private String mCallbackAction; private String mShortcutId; @@ -84,10 +85,10 @@ public class RequestPinItemTest extends AbstractLauncherUiTest { .equals(AppWidgetNoConfig.class.getName())); } - @Test + @Test public void testPinWidgetNoConfig_customPreview() throws Throwable { // Command to set custom preview - Intent command = RequestPinItemActivity.getCommandIntent( + Intent command = RequestPinItemActivity.getCommandIntent( RequestPinItemActivity.class, "setRemoteViewColor").putExtra( RequestPinItemActivity.EXTRA_PARAM + "0", Color.RED); @@ -169,7 +170,8 @@ public class RequestPinItemTest extends AbstractLauncherUiTest { // Go back to home mLauncher.pressHome(); - Wait.atMost(null, new ItemSearchCondition(itemMatcher), DEFAULT_ACTIVITY_TIMEOUT); + Wait.atMost(null, new ItemSearchCondition(itemMatcher), DEFAULT_ACTIVITY_TIMEOUT, + mLauncher); } /** diff --git a/tests/src/com/android/launcher3/util/Wait.java b/tests/src/com/android/launcher3/util/Wait.java index 899686bd61..2663d02760 100644 --- a/tests/src/com/android/launcher3/util/Wait.java +++ b/tests/src/com/android/launcher3/util/Wait.java @@ -3,6 +3,8 @@ package com.android.launcher3.util; import android.os.SystemClock; import android.util.Log; +import com.android.launcher3.tapl.LauncherInstrumentation; + import org.junit.Assert; /** @@ -12,11 +14,13 @@ public class Wait { private static final long DEFAULT_SLEEP_MS = 200; - public static void atMost(String message, Condition condition, long timeout) { - atMost(message, condition, timeout, DEFAULT_SLEEP_MS); + public static void atMost(String message, Condition condition, long timeout, + LauncherInstrumentation launcher) { + atMost(message, condition, timeout, DEFAULT_SLEEP_MS, launcher); } - public static void atMost(String message, Condition condition, long timeout, long sleepMillis) { + public static void atMost(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); @@ -40,6 +44,7 @@ public class Wait { throw new RuntimeException(t); } Log.d("Wait", "atMost: timed out: " + SystemClock.uptimeMillis()); + launcher.checkForAnomaly(); Assert.fail(message); } } diff --git a/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java b/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java index 69bf01d40c..858cb384d7 100644 --- a/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java +++ b/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java @@ -21,6 +21,7 @@ import android.content.pm.PackageManager; import android.os.Build; import android.util.Log; +import androidx.test.InstrumentationRegistry; import androidx.test.uiautomator.UiDevice; import org.junit.rules.TestRule; @@ -57,7 +58,7 @@ public class TestStabilityRule implements TestRule { public static final int PLATFORM_PRESUBMIT = 0x8; public static final int PLATFORM_POSTSUBMIT = 0x10; - private static final int RUN_FLAFOR = getRunFlavor(); + public static final int RUN_FLAFOR = getRunFlavor(); @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @@ -86,6 +87,19 @@ public class TestStabilityRule implements TestRule { } private static int getRunFlavor() { + final String flavorOverride = InstrumentationRegistry.getArguments().getString("flavor"); + + if (flavorOverride != null) { + Log.d(TAG, "Flavor override: " + flavorOverride); + try { + return (int) TestStabilityRule.class.getField(flavorOverride).get(null); + } catch (NoSuchFieldException e) { + throw new AssertionError("Unrecognized run flavor override: " + flavorOverride); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + final String launcherVersion; try { launcherVersion = getInstrumentation(). diff --git a/tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java b/tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java index a31d8a6561..c7f7cd6ad6 100644 --- a/tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java +++ b/tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java @@ -23,16 +23,19 @@ import static org.mockito.Mockito.verify; import android.appwidget.AppWidgetProviderInfo; import android.content.Context; import android.graphics.Bitmap; +import android.view.LayoutInflater; + +import androidx.recyclerview.widget.RecyclerView; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import android.view.LayoutInflater; -import com.android.launcher3.icons.IconCache; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.LauncherAppWidgetProviderInfo; import com.android.launcher3.WidgetPreviewLoader; import com.android.launcher3.compat.AppWidgetManagerCompat; +import com.android.launcher3.icons.BitmapInfo; +import com.android.launcher3.icons.IconCache; import com.android.launcher3.model.PackageItemInfo; import com.android.launcher3.model.WidgetItem; import com.android.launcher3.util.MultiHashMap; @@ -46,8 +49,6 @@ import org.mockito.MockitoAnnotations; import java.util.ArrayList; import java.util.Map; -import androidx.recyclerview.widget.RecyclerView; - @SmallTest @RunWith(AndroidJUnit4.class) public class WidgetsListAdapterTest { @@ -136,7 +137,7 @@ public class WidgetsListAdapterTest { PackageItemInfo pInfo = new PackageItemInfo(wi.componentName.getPackageName()); pInfo.title = pInfo.packageName; pInfo.user = wi.user; - pInfo.iconBitmap = Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8); + pInfo.bitmap = BitmapInfo.of(Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8), 0); newMap.addToList(pInfo, wi); if (newMap.size() == num) { break; diff --git a/tests/tapl/com/android/launcher3/tapl/AllApps.java b/tests/tapl/com/android/launcher3/tapl/AllApps.java index e1e9b8d851..1ecfff7d1d 100644 --- a/tests/tapl/com/android/launcher3/tapl/AllApps.java +++ b/tests/tapl/com/android/launcher3/tapl/AllApps.java @@ -29,6 +29,8 @@ import androidx.test.uiautomator.UiObject2; import com.android.launcher3.ResourceUtils; import com.android.launcher3.testing.TestProtocol; +import java.util.stream.Collectors; + /** * Operations on AllApps opened from Home. Also a parent for All Apps opened from Overview. */ @@ -67,7 +69,7 @@ public class AllApps extends LauncherInstrumentation.VisibleContainer { return false; } if (iconBounds.bottom > displayBottom) { - LauncherInstrumentation.log("hasClickableIcon: icon center bellow bottom offset"); + LauncherInstrumentation.log("hasClickableIcon: icon bottom below bottom offset"); return false; } LauncherInstrumentation.log("hasClickableIcon: icon is clickable"); @@ -116,7 +118,12 @@ public class AllApps extends LauncherInstrumentation.VisibleContainer { displayBottom)) { mLauncher.scrollToLastVisibleRow( allAppsContainer, - mLauncher.getObjectsInContainer(allAppsContainer, "icon"), + mLauncher.getObjectsInContainer(allAppsContainer, "icon") + .stream() + .filter(object -> + object.getVisibleBounds().bottom + <= displayBottom) + .collect(Collectors.toList()), searchBox.getVisibleBounds().bottom - allAppsContainer.getVisibleBounds().top); final int newScroll = getAllAppsScroll(); @@ -163,7 +170,7 @@ public class AllApps extends LauncherInstrumentation.VisibleContainer { "Exceeded max scroll attempts: " + MAX_SCROLL_ATTEMPTS, ++attempts <= MAX_SCROLL_ATTEMPTS); - mLauncher.scroll(allAppsContainer, Direction.UP, margins, 50); + mLauncher.scroll(allAppsContainer, Direction.UP, margins, 12); } try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("scrolled up")) { diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java index 0879404ca1..2650cf9fc1 100644 --- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java +++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java @@ -64,10 +64,12 @@ import com.android.launcher3.ResourceUtils; import com.android.launcher3.testing.TestProtocol; import com.android.systemui.shared.system.QuickStepContract; -import java.util.ArrayList; +import org.junit.Assert; + import java.io.ByteArrayOutputStream; import java.io.IOException; import java.lang.ref.WeakReference; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Deque; @@ -77,8 +79,6 @@ import java.util.concurrent.TimeoutException; import java.util.function.Consumer; import java.util.function.Function; -import org.junit.Assert; - /** * The main tapl object. The only object that can be explicitly constructed by the using code. It * produces all other objects. @@ -298,6 +298,14 @@ public final class LauncherInstrumentation { return null; } + public void checkForAnomaly() { + final String anomalyMessage = getAnomalyMessage(); + if (anomalyMessage != null) { + failWithSystemHealth( + "Tests are broken by a non-Launcher system error: " + anomalyMessage); + } + } + private String getVisibleStateMessage() { if (hasLauncherObject(WIDGETS_RES_ID)) return "Widgets"; if (hasLauncherObject(OVERVIEW_RES_ID)) return "Overview"; @@ -331,20 +339,17 @@ public final class LauncherInstrumentation { } private void fail(String message) { - message = "http://go/tapl : " + getContextDescription() + message; + checkForAnomaly(); - final String anomaly = getAnomalyMessage(); - if (anomaly != null) { - message = anomaly + ", which causes:\n" + message; - } else { - message = message + " (visible state: " + getVisibleStateMessage() + ")"; - } + failWithSystemHealth("http://go/tapl : " + getContextDescription() + message + + " (visible state: " + getVisibleStateMessage() + ")"); + } + private void failWithSystemHealth(String message) { final String systemHealth = getSystemHealthMessage(); if (systemHealth != null) { message = message - + ", which might be a consequence of system health " - + "problems:\n<<<<<<<<<<<<<<<<<<\n" + + ", perhaps because of system health problems:\n<<<<<<<<<<<<<<<<<<\n" + systemHealth + "\n>>>>>>>>>>>>>>>>>>"; } @@ -424,11 +429,7 @@ public final class LauncherInstrumentation { // b/136278866 for (int i = 0; i != 100; ++i) { if (getNavigationModeMismatchError() == null) break; - try { - Thread.sleep(100); - } catch (InterruptedException e) { - e.printStackTrace(); - } + sleep(100); } final String error = getNavigationModeMismatchError(); @@ -535,8 +536,7 @@ public final class LauncherInstrumentation { // accessibility events prior to pressing Home. final String action; if (getNavigationModel() == NavigationModel.ZERO_BUTTON) { - final String anomaly = getAnomalyMessage(); - if (anomaly != null) fail("Can't swipe up to Home: " + anomaly); + checkForAnomaly(); final Point displaySize = getRealDisplaySize(); @@ -795,15 +795,18 @@ public final class LauncherInstrumentation { final int distance = gestureStart - container.getVisibleBounds().top - topPadding; final int bottomMargin = container.getVisibleBounds().height() - distance; + // TODO: Make the gesture steps dependent on the distance so that it can run for various + // screen sizes + final int totalMargin = Math.max(bottomMargin, getBottomGestureMargin(container)); scroll( container, Direction.DOWN, new Rect( 0, + totalMargin / 2, 0, - 0, - Math.max(bottomMargin, getBottomGestureMargin(container))), - 150); + totalMargin / 2), + 80); } void scroll(UiObject2 container, Direction direction, Rect margins, int steps) { diff --git a/tests/tapl/com/android/launcher3/tapl/Overview.java b/tests/tapl/com/android/launcher3/tapl/Overview.java index 4f8aeb1123..16a64a7499 100644 --- a/tests/tapl/com/android/launcher3/tapl/Overview.java +++ b/tests/tapl/com/android/launcher3/tapl/Overview.java @@ -58,7 +58,7 @@ public final class Overview extends BaseOverview { getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD), mLauncher.getDevice().getDisplayWidth() / 2, 0, - 50, + 12, ALL_APPS_STATE_ORDINAL); try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer( diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java index d1261e07be..db3314e9b1 100644 --- a/tests/tapl/com/android/launcher3/tapl/Workspace.java +++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java @@ -38,7 +38,7 @@ import com.android.launcher3.testing.TestProtocol; * Operations on the workspace screen. */ public final class Workspace extends Home { - private static final int DRAG_DURACTION = 2000; + private static final int DRAG_DURATION = 500; private static final int FLING_STEPS = 10; private final UiObject2 mHotseat; @@ -72,7 +72,7 @@ public final class Workspace extends Home { start.y, start.x, start.y - swipeHeight - mLauncher.getTouchSlop(), - 60, + 12, ALL_APPS_STATE_ORDINAL); try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer( @@ -166,7 +166,7 @@ public final class Workspace extends Home { launcher.waitForLauncherObject(longPressIndicator); LauncherInstrumentation.log("dragIconToWorkspace: indicator"); launcher.movePointer( - downTime, SystemClock.uptimeMillis(), DRAG_DURACTION, launchableCenter, dest); + downTime, SystemClock.uptimeMillis(), DRAG_DURATION, launchableCenter, dest); LauncherInstrumentation.log("dragIconToWorkspace: moved pointer"); launcher.sendPointer( downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, dest); From 9a8c9aa6878638beb1c51d2cf8462eb06392fbfb Mon Sep 17 00:00:00 2001 From: Tony Wickham Date: Mon, 9 Mar 2020 13:20:04 -0700 Subject: [PATCH 06/12] Fix crash when dumping before user unlocks Bug: 150864182 Bug: 151050221 Change-Id: I29ba2ef66b4359a47f866d02306498537c45236e (cherry picked from commit b365cc43878e0cb4e6d4b998c10cd590adc4cb8d) --- .../src/com/android/quickstep/TouchInteractionService.java | 4 +++- .../com/android/quickstep/RecentsAnimationDeviceState.java | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java index 4598cdf459..0f6333693f 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java @@ -760,7 +760,9 @@ public class TouchInteractionService extends Service implements PluginListener Date: Mon, 11 May 2020 19:48:59 -0700 Subject: [PATCH 09/12] Switch to new protocol for hybrid hotseat - create predictor from items in bgModel instead of scanning views - Launcher no longer checks for duplicates before sending pin/unpin events - sending cached items from last prediction to reduce UI shuffle - Switch to using UserCache to persist and read ComponentKey Bug: 148814143 Bug: 156413231 Bug: 156200931 Change-Id: Ide6330bed8eb7f0c6fbec1d1ac21e7f67a9b2be2 (cherry picked from commit d12d6ab98a6f390c5b4c59b303e35a3f596645be) (cherry picked from commit fa369310e2108a6e5915b5873d3bab16482213fd) --- .../res/values/override.xml | 2 + .../HotseatPredictionController.java | 285 +++++------------- .../hybridhotseat/HotseatPredictionModel.java | 136 +++++++++ res/values/config.xml | 1 + src/com/android/launcher3/DropTarget.java | 12 + .../android/launcher3/LauncherAppState.java | 2 +- .../launcher3/model/BaseLoaderResults.java | 4 +- .../android/launcher3/model/LoaderTask.java | 8 +- .../launcher3/model/PredictionModel.java | 96 ++++-- 9 files changed, 292 insertions(+), 254 deletions(-) create mode 100644 quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java diff --git a/quickstep/recents_ui_overrides/res/values/override.xml b/quickstep/recents_ui_overrides/res/values/override.xml index ed3ba929a8..6aa9619cc9 100644 --- a/quickstep/recents_ui_overrides/res/values/override.xml +++ b/quickstep/recents_ui_overrides/res/values/override.xml @@ -26,5 +26,7 @@ com.android.quickstep.QuickstepProcessInitializer com.android.quickstep.logging.UserEventDispatcherAppPredictionExtension + + com.android.launcher3.hybridhotseat.HotseatPredictionModel diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java index e9f35345ca..7a73e50da8 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java @@ -25,10 +25,7 @@ import android.app.prediction.AppPredictionManager; import android.app.prediction.AppPredictor; import android.app.prediction.AppTarget; import android.app.prediction.AppTargetEvent; -import android.app.prediction.AppTargetId; import android.content.ComponentName; -import android.os.Bundle; -import android.os.Process; import android.util.Log; import android.view.View; import android.view.ViewGroup; @@ -36,8 +33,6 @@ import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.android.launcher3.BubbleTextView; -import com.android.launcher3.CellLayout; import com.android.launcher3.DragSource; import com.android.launcher3.DropTarget; import com.android.launcher3.Hotseat; @@ -48,7 +43,6 @@ import com.android.launcher3.LauncherSettings; import com.android.launcher3.LauncherState; import com.android.launcher3.R; import com.android.launcher3.Utilities; -import com.android.launcher3.Workspace; import com.android.launcher3.allapps.AllAppsStore; import com.android.launcher3.anim.AnimationSuccessListener; import com.android.launcher3.appprediction.ComponentKeyMapper; @@ -57,12 +51,10 @@ import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.dragndrop.DragOptions; import com.android.launcher3.icons.IconCache; import com.android.launcher3.logging.FileLog; -import com.android.launcher3.model.PredictionModel; 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.ItemInfoWithIcon; -import com.android.launcher3.model.data.LauncherAppWidgetInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.popup.SystemShortcut; import com.android.launcher3.shortcuts.ShortcutKey; @@ -77,7 +69,6 @@ import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Locale; import java.util.OptionalInt; import java.util.stream.IntStream; @@ -93,17 +84,6 @@ public class HotseatPredictionController implements DragController.DragListener, private static final String TAG = "PredictiveHotseat"; private static final boolean DEBUG = false; - //TODO: replace this with AppTargetEvent.ACTION_UNPIN (b/144119543) - private static final int APPTARGET_ACTION_UNPIN = 4; - - private static final String APP_LOCATION_HOTSEAT = "hotseat"; - private static final String APP_LOCATION_WORKSPACE = "workspace"; - - private static final String BUNDLE_KEY_HOTSEAT = "hotseat_apps"; - private static final String BUNDLE_KEY_WORKSPACE = "workspace_apps"; - - private static final String BUNDLE_KEY_PIN_EVENTS = "pin_events"; - private static final String PREDICTION_CLIENT = "hotseat"; private DropTarget.DragObject mDragObject; private int mHotSeatItemsCount; @@ -116,13 +96,14 @@ public class HotseatPredictionController implements DragController.DragListener, private DynamicItemCache mDynamicItemCache; - private final PredictionModel mPredictionModel; + private final HotseatPredictionModel mPredictionModel; private AppPredictor mAppPredictor; private AllAppsStore mAllAppsStore; private AnimatorSet mIconRemoveAnimators; private boolean mUIUpdatePaused = false; private boolean mRequiresCacheUpdate = true; private boolean mIsCacheEmpty; + private boolean mIsDestroyed = false; private HotseatEduController mHotseatEduController; @@ -141,13 +122,13 @@ public class HotseatPredictionController implements DragController.DragListener, mLauncher = launcher; mHotseat = launcher.getHotseat(); mAllAppsStore = mLauncher.getAppsView().getAppsStore(); - mPredictionModel = LauncherAppState.INSTANCE.get(launcher).getPredictionModel(); + LauncherAppState appState = LauncherAppState.getInstance(launcher); + mPredictionModel = (HotseatPredictionModel) appState.getPredictionModel(); mAllAppsStore.addUpdateListener(this); mDynamicItemCache = new DynamicItemCache(mLauncher, this::fillGapsWithPrediction); mHotSeatItemsCount = mLauncher.getDeviceProfile().inv.numHotseatIcons; launcher.getDeviceProfile().inv.addOnChangeListener(this); mHotseat.addOnAttachStateChangeListener(this); - mIsCacheEmpty = mPredictionModel.getPredictionComponentKeys().isEmpty(); if (mHotseat.isAttachedToWindow()) { onViewAttachedToWindow(mHotseat); } @@ -260,6 +241,7 @@ public class HotseatPredictionController implements DragController.DragListener, * Unregisters callbacks and frees resources */ public void destroy() { + mIsDestroyed = true; mAllAppsStore.removeUpdateListener(this); mLauncher.getDeviceProfile().inv.removeOnChangeListener(this); mHotseat.removeOnAttachStateChangeListener(this); @@ -293,99 +275,52 @@ public class HotseatPredictionController implements DragController.DragListener, if (mAppPredictor != null) { mAppPredictor.destroy(); } - mAppPredictor = apm.createAppPredictionSession( - new AppPredictionContext.Builder(mLauncher) - .setUiSurface(PREDICTION_CLIENT) - .setPredictedTargetCount(mHotSeatItemsCount) - .setExtras(getAppPredictionContextExtra()) - .build()); WeakReference controllerRef = new WeakReference<>(this); - mAppPredictor.registerPredictionUpdates(mLauncher.getApplicationContext().getMainExecutor(), - list -> { - if (controllerRef.get() != null) { - controllerRef.get().setPredictedApps(list); - } - }); + + mPredictionModel.createBundle(bundle -> { + if (mIsDestroyed) return; + mAppPredictor = apm.createAppPredictionSession( + new AppPredictionContext.Builder(mLauncher) + .setUiSurface(PREDICTION_CLIENT) + .setPredictedTargetCount(mHotSeatItemsCount) + .setExtras(bundle) + .build()); + mAppPredictor.registerPredictionUpdates( + mLauncher.getApplicationContext().getMainExecutor(), + list -> { + if (controllerRef.get() != null) { + controllerRef.get().setPredictedApps(list); + } + }); + mAppPredictor.requestPredictionUpdate(); + }); setPauseUIUpdate(false); if (!isEduSeen()) { mHotseatEduController = new HotseatEduController(mLauncher, this::createPredictor); } - mAppPredictor.requestPredictionUpdate(); } /** * Create WorkspaceItemInfo objects and binds PredictedAppIcon views for cached predicted items. */ - public void showCachedItems(List apps, IntArray ranks) { + public void showCachedItems(List apps, IntArray ranks) { + mIsCacheEmpty = apps.isEmpty(); int count = Math.min(ranks.size(), apps.size()); List items = new ArrayList<>(count); + mComponentKeyMappers.clear(); for (int i = 0; i < count; i++) { WorkspaceItemInfo item = new WorkspaceItemInfo(apps.get(i)); + ComponentKey componentKey = new ComponentKey(item.getTargetComponent(), item.user); preparePredictionInfo(item, ranks.get(i)); items.add(item); - } - mComponentKeyMappers.clear(); - for (ComponentKey key : mPredictionModel.getPredictionComponentKeys()) { - mComponentKeyMappers.add(new ComponentKeyMapper(key, mDynamicItemCache)); + + mComponentKeyMappers.add(new ComponentKeyMapper(componentKey, mDynamicItemCache)); } updateDependencies(); bindItems(items, false, null); } - private Bundle getAppPredictionContextExtra() { - Bundle bundle = new Bundle(); - - //TODO: remove this way of reporting items - bundle.putParcelableArrayList(BUNDLE_KEY_HOTSEAT, - getPinnedAppTargetsInViewGroup((mHotseat.getShortcutsAndWidgets()))); - bundle.putParcelableArrayList(BUNDLE_KEY_WORKSPACE, getPinnedAppTargetsInViewGroup( - mLauncher.getWorkspace().getScreenWithId( - Workspace.FIRST_SCREEN_ID).getShortcutsAndWidgets())); - - ArrayList pinEvents = new ArrayList<>(); - getPinEventsForViewGroup(pinEvents, mHotseat.getShortcutsAndWidgets(), - APP_LOCATION_HOTSEAT); - getPinEventsForViewGroup(pinEvents, mLauncher.getWorkspace().getScreenWithId( - Workspace.FIRST_SCREEN_ID).getShortcutsAndWidgets(), APP_LOCATION_WORKSPACE); - bundle.putParcelableArrayList(BUNDLE_KEY_PIN_EVENTS, pinEvents); - - return bundle; - } - - private ArrayList getPinEventsForViewGroup(ArrayList pinEvents, - ViewGroup views, String root) { - for (int i = 0; i < views.getChildCount(); i++) { - View child = views.getChildAt(i); - final AppTargetEvent event; - if (child.getTag() instanceof ItemInfo && getAppTargetFromInfo( - (ItemInfo) child.getTag()) != null) { - ItemInfo info = (ItemInfo) child.getTag(); - event = wrapAppTargetWithLocation(getAppTargetFromInfo(info), - AppTargetEvent.ACTION_PIN, info); - } else { - CellLayout.LayoutParams params = (CellLayout.LayoutParams) views.getLayoutParams(); - event = wrapAppTargetWithLocation(getBlockAppTarget(), AppTargetEvent.ACTION_PIN, - root, 0, params.cellX, params.cellY, params.cellHSpan, params.cellVSpan); - } - pinEvents.add(event); - } - return pinEvents; - } - - - private ArrayList getPinnedAppTargetsInViewGroup(ViewGroup viewGroup) { - ArrayList pinnedApps = new ArrayList<>(); - for (int i = 0; i < viewGroup.getChildCount(); i++) { - View child = viewGroup.getChildAt(i); - if (isPinnedIcon(child)) { - WorkspaceItemInfo itemInfo = (WorkspaceItemInfo) child.getTag(); - pinnedApps.add(getAppTargetFromItemInfo(itemInfo)); - } - } - return pinnedApps; - } - private void setPredictedApps(List appTargets) { mComponentKeyMappers.clear(); StringBuilder predictionLog = new StringBuilder("predictedApps: [\n"); @@ -443,8 +378,11 @@ public class HotseatPredictionController implements DragController.DragListener, workspaceItemInfo.cellX, workspaceItemInfo.cellY); ObjectAnimator.ofFloat(icon, SCALE_PROPERTY, 1, 0.8f, 1).start(); icon.pin(workspaceItemInfo); - AppTarget appTarget = getAppTargetFromItemInfo(workspaceItemInfo); - notifyItemAction(appTarget, APP_LOCATION_HOTSEAT, AppTargetEvent.ACTION_PIN); + AppTarget appTarget = mPredictionModel.getAppTargetFromInfo(workspaceItemInfo); + if (appTarget != null) { + notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(appTarget, + AppTargetEvent.ACTION_PIN, workspaceItemInfo)); + } mRequiresCacheUpdate = true; } @@ -524,10 +462,9 @@ public class HotseatPredictionController implements DragController.DragListener, mIconRemoveAnimators.start(); } - private void notifyItemAction(AppTarget target, String location, int action) { + private void notifyItemAction(AppTargetEvent event) { if (mAppPredictor != null) { - mAppPredictor.notifyAppTargetEvent(new AppTargetEvent.Builder(target, - action).setLaunchLocation(location).build()); + mAppPredictor.notifyAppTargetEvent(event); } } @@ -545,36 +482,35 @@ public class HotseatPredictionController implements DragController.DragListener, /** * Unpins pinned app when it's converted into a folder */ - public void folderCreatedFromWorkspaceItem(ItemInfo info, FolderInfo folderInfo) { - if (info.itemType != LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { - return; + public void folderCreatedFromWorkspaceItem(ItemInfo itemInfo, FolderInfo folderInfo) { + AppTarget folderTarget = mPredictionModel.getAppTargetFromInfo(folderInfo); + AppTarget itemTarget = mPredictionModel.getAppTargetFromInfo(itemInfo); + if (folderTarget != null && HotseatPredictionModel.isTrackedForPrediction(folderInfo)) { + notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(folderTarget, + AppTargetEvent.ACTION_PIN, folderInfo)); } - AppTarget target = getAppTargetFromItemInfo(info); - ViewGroup hotseatVG = mHotseat.getShortcutsAndWidgets(); - ViewGroup firstScreenVG = mLauncher.getWorkspace().getScreenWithId( - Workspace.FIRST_SCREEN_ID).getShortcutsAndWidgets(); - - if (isInHotseat(folderInfo) && !getPinnedAppTargetsInViewGroup(hotseatVG).contains( - target)) { - notifyItemAction(target, APP_LOCATION_HOTSEAT, APPTARGET_ACTION_UNPIN); - } else if (isInFirstPage(folderInfo) && !getPinnedAppTargetsInViewGroup( - firstScreenVG).contains(target)) { - notifyItemAction(target, APP_LOCATION_WORKSPACE, APPTARGET_ACTION_UNPIN); + // using folder info with isTrackedForPrediction as itemInfo.container is already changed + // to folder by this point + if (itemTarget != null && HotseatPredictionModel.isTrackedForPrediction(folderInfo)) { + notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(itemTarget, + AppTargetEvent.ACTION_UNPIN, folderInfo + )); } } /** * Pins workspace item created when all folder items are removed but one */ - public void folderConvertedToWorkspaceItem(ItemInfo info, FolderInfo folderInfo) { - if (info.itemType != LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { - return; + public void folderConvertedToWorkspaceItem(ItemInfo itemInfo, FolderInfo folderInfo) { + AppTarget folderTarget = mPredictionModel.getAppTargetFromInfo(folderInfo); + AppTarget itemTarget = mPredictionModel.getAppTargetFromInfo(itemInfo); + if (folderTarget != null && HotseatPredictionModel.isTrackedForPrediction(folderInfo)) { + notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(folderTarget, + AppTargetEvent.ACTION_UNPIN, folderInfo)); } - AppTarget target = getAppTargetFromItemInfo(info); - if (isInHotseat(info)) { - notifyItemAction(target, APP_LOCATION_HOTSEAT, AppTargetEvent.ACTION_PIN); - } else if (isInFirstPage(info)) { - notifyItemAction(target, APP_LOCATION_WORKSPACE, AppTargetEvent.ACTION_PIN); + if (itemTarget != null && HotseatPredictionModel.isTrackedForPrediction(itemInfo)) { + notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(itemTarget, + AppTargetEvent.ACTION_PIN, itemInfo)); } } @@ -585,29 +521,18 @@ public class HotseatPredictionController implements DragController.DragListener, } ItemInfo dragInfo = mDragObject.dragInfo; - ViewGroup hotseatVG = mHotseat.getShortcutsAndWidgets(); - ViewGroup firstScreenVG = mLauncher.getWorkspace().getScreenWithId( - Workspace.FIRST_SCREEN_ID).getShortcutsAndWidgets(); - - if (dragInfo instanceof WorkspaceItemInfo - && dragInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION - && dragInfo.getTargetComponent() != null) { - AppTarget appTarget = getAppTargetFromItemInfo(dragInfo); - if (!isInHotseat(dragInfo) && isInHotseat(mDragObject.originalDragInfo)) { - if (!getPinnedAppTargetsInViewGroup(hotseatVG).contains(appTarget)) { - notifyItemAction(appTarget, APP_LOCATION_HOTSEAT, APPTARGET_ACTION_UNPIN); - } + if (mDragObject.isMoved()) { + AppTarget appTarget = mPredictionModel.getAppTargetFromInfo(dragInfo); + //always send pin event first to prevent AiAi from predicting an item moved within + // the same page + if (appTarget != null && HotseatPredictionModel.isTrackedForPrediction(dragInfo)) { + notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(appTarget, + AppTargetEvent.ACTION_PIN, dragInfo)); } - if (!isInFirstPage(dragInfo) && isInFirstPage(mDragObject.originalDragInfo)) { - if (!getPinnedAppTargetsInViewGroup(firstScreenVG).contains(appTarget)) { - notifyItemAction(appTarget, APP_LOCATION_WORKSPACE, APPTARGET_ACTION_UNPIN); - } - } - if (isInHotseat(dragInfo) && !isInHotseat(mDragObject.originalDragInfo)) { - notifyItemAction(appTarget, APP_LOCATION_HOTSEAT, AppTargetEvent.ACTION_PIN); - } - if (isInFirstPage(dragInfo) && !isInFirstPage(mDragObject.originalDragInfo)) { - notifyItemAction(appTarget, APP_LOCATION_WORKSPACE, AppTargetEvent.ACTION_PIN); + if (appTarget != null && HotseatPredictionModel.isTrackedForPrediction( + mDragObject.originalDragInfo)) { + notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(appTarget, + AppTargetEvent.ACTION_UNPIN, mDragObject.originalDragInfo)); } } mDragObject = null; @@ -615,6 +540,7 @@ public class HotseatPredictionController implements DragController.DragListener, mRequiresCacheUpdate = true; } + @Nullable @Override public SystemShortcut getShortcut(QuickstepLauncher activity, @@ -711,77 +637,4 @@ public class HotseatPredictionController implements DragController.DragListener, && ((WorkspaceItemInfo) view.getTag()).container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION; } - - private static boolean isPinnedIcon(View view) { - if (!(view instanceof BubbleTextView && view.getTag() instanceof WorkspaceItemInfo)) { - return false; - } - ItemInfo info = (ItemInfo) view.getTag(); - return info.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION && ( - info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION); - } - - private static boolean isInHotseat(ItemInfo itemInfo) { - return itemInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT; - } - - private static boolean isInFirstPage(ItemInfo itemInfo) { - return itemInfo.container == LauncherSettings.Favorites.CONTAINER_DESKTOP - && itemInfo.screenId == Workspace.FIRST_SCREEN_ID; - } - - private static AppTarget getAppTargetFromItemInfo(ItemInfo info) { - if (info.getTargetComponent() == null) return null; - ComponentName cn = info.getTargetComponent(); - return new AppTarget.Builder(new AppTargetId("app:" + cn.getPackageName()), - cn.getPackageName(), info.user).setClassName(cn.getClassName()).build(); - } - - private AppTarget getAppTargetFromInfo(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), - mLauncher.getPackageName(), info.user).build(); - } - return null; - } - - private AppTargetEvent wrapAppTargetWithLocation(AppTarget target, int action, ItemInfo info) { - return wrapAppTargetWithLocation(target, action, - info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT - ? APP_LOCATION_HOTSEAT : APP_LOCATION_WORKSPACE, info.screenId, info.cellX, - info.cellY, info.spanX, info.spanY); - } - - private AppTargetEvent wrapAppTargetWithLocation(AppTarget target, int action, String root, - int screenId, int x, int y, int spanX, int spanY) { - return new AppTargetEvent.Builder(target, action).setLaunchLocation( - String.format(Locale.ENGLISH, "%s/%d/[%d,%d]/[%d,%d]", root, screenId, x, y, spanX, - spanY)).build(); - } - - /** - * A helper method to generate an AppTarget that's used to communicate workspace layout - */ - private AppTarget getBlockAppTarget() { - return new AppTarget.Builder(new AppTargetId("block"), - mLauncher.getPackageName(), Process.myUserHandle()).build(); - } } diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java new file mode 100644 index 0000000000..5a038d27af --- /dev/null +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java @@ -0,0 +1,136 @@ +/* + * 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.util.Executors.MAIN_EXECUTOR; + +import android.app.prediction.AppTarget; +import android.app.prediction.AppTargetEvent; +import android.app.prediction.AppTargetId; +import android.content.ComponentName; +import android.content.Context; +import android.os.Bundle; + +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.LauncherSettings; +import com.android.launcher3.Workspace; +import com.android.launcher3.model.AllAppsList; +import com.android.launcher3.model.BaseModelUpdateTask; +import com.android.launcher3.model.BgDataModel; +import com.android.launcher3.model.PredictionModel; +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.ArrayList; +import java.util.Locale; +import java.util.function.Consumer; + +/** + * Model helper for app predictions in workspace + */ +public class HotseatPredictionModel extends PredictionModel { + private static final String APP_LOCATION_HOTSEAT = "hotseat"; + private static final String APP_LOCATION_WORKSPACE = "workspace"; + + private static final String BUNDLE_KEY_PIN_EVENTS = "pin_events"; + private static final String BUNDLE_KEY_CURRENT_ITEMS = "current_items"; + + + public HotseatPredictionModel(Context context) { } + + /** + * Creates and returns bundle using workspace items and cached items + */ + public void createBundle(Consumer cb) { + LauncherAppState appState = LauncherAppState.getInstance(mContext); + appState.getModel().enqueueModelUpdateTask(new BaseModelUpdateTask() { + @Override + public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) { + Bundle bundle = new Bundle(); + ArrayList events = new ArrayList<>(); + ArrayList workspaceItems = new ArrayList<>(dataModel.workspaceItems); + workspaceItems.addAll(dataModel.appWidgets); + for (ItemInfo item : workspaceItems) { + AppTarget target = getAppTargetFromInfo(item); + if (target != null && !isTrackedForPrediction(item)) continue; + events.add(wrapAppTargetWithLocation(target, AppTargetEvent.ACTION_PIN, item)); + } + ArrayList currentTargets = new ArrayList<>(); + for (ItemInfo itemInfo : dataModel.cachedPredictedItems) { + AppTarget target = getAppTargetFromInfo(itemInfo); + if (target != null) currentTargets.add(target); + } + bundle.putParcelableArrayList(BUNDLE_KEY_PIN_EVENTS, events); + bundle.putParcelableArrayList(BUNDLE_KEY_CURRENT_ITEMS, currentTargets); + MAIN_EXECUTOR.execute(() -> cb.accept(bundle)); + } + }); + } + + /** + * Creates and returns for {@link AppTarget} object given an {@link ItemInfo}. Returns null + * if item is not supported prediction + */ + public AppTarget getAppTargetFromInfo(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), + mContext.getPackageName(), info.user).build(); + } + return null; + } + + + /** + * Creates and returns {@link AppTargetEvent} from an {@link AppTarget}, action, and item + * location using {@link ItemInfo} + */ + public AppTargetEvent wrapAppTargetWithLocation(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 predictors + */ + public static boolean isTrackedForPrediction(ItemInfo info) { + return info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT || ( + info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP + && info.screenId == Workspace.FIRST_SCREEN_ID); + } +} diff --git a/res/values/config.xml b/res/values/config.xml index 603dc91619..4cbc597f6f 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -69,6 +69,7 @@ + diff --git a/src/com/android/launcher3/DropTarget.java b/src/com/android/launcher3/DropTarget.java index 0b0983cbee..c1aed9812c 100644 --- a/src/com/android/launcher3/DropTarget.java +++ b/src/com/android/launcher3/DropTarget.java @@ -110,6 +110,18 @@ public interface DropTarget { return res; } + + + /** + * This is used to determine if an object is dropped at a different location than it was + * dragged from + */ + public boolean isMoved() { + return dragInfo.cellX != originalDragInfo.cellX + || dragInfo.cellY != originalDragInfo.cellY + || dragInfo.screenId != originalDragInfo.screenId + || dragInfo.container != originalDragInfo.container; + } } /** diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java index 14e604d9ed..53e5274c0c 100644 --- a/src/com/android/launcher3/LauncherAppState.java +++ b/src/com/android/launcher3/LauncherAppState.java @@ -129,7 +129,7 @@ public class LauncherAppState { mIconCache = new IconCache(mContext, mInvariantDeviceProfile, iconCacheFileName); mWidgetCache = new WidgetPreviewLoader(mContext, mIconCache); mModel = new LauncherModel(this, mIconCache, AppFilter.newInstance(mContext)); - mPredictionModel = new PredictionModel(mContext); + mPredictionModel = PredictionModel.newInstance(mContext); } protected void onNotificationSettingsChanged(boolean areNotificationDotsEnabled) { diff --git a/src/com/android/launcher3/model/BaseLoaderResults.java b/src/com/android/launcher3/model/BaseLoaderResults.java index 1465100b83..ab921eaad9 100644 --- a/src/com/android/launcher3/model/BaseLoaderResults.java +++ b/src/com/android/launcher3/model/BaseLoaderResults.java @@ -253,8 +253,8 @@ public abstract class BaseLoaderResults { } private void bindPredictedItems(IntArray ranks, final Executor executor) { - executeCallbacksTask( - c -> c.bindPredictedItems(mBgDataModel.cachedPredictedItems, ranks), executor); + ArrayList items = new ArrayList<>(mBgDataModel.cachedPredictedItems); + executeCallbacksTask(c -> c.bindPredictedItems(items, ranks), executor); } protected void executeCallbacksTask(CallbackTask task, Executor executor) { diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java index 9e6282e270..d05d70b044 100644 --- a/src/com/android/launcher3/model/LoaderTask.java +++ b/src/com/android/launcher3/model/LoaderTask.java @@ -45,6 +45,8 @@ import android.util.LongSparseArray; import android.util.MutableInt; import android.util.TimingLogger; +import androidx.annotation.WorkerThread; + import com.android.launcher3.InstallShortcutReceiver; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherModel; @@ -850,12 +852,11 @@ public class LoaderTask implements Runnable { } } - private List loadCachedPredictions() { + @WorkerThread + private void loadCachedPredictions() { synchronized (mBgDataModel) { List componentKeys = mApp.getPredictionModel().getPredictionComponentKeys(); - List results = new ArrayList<>(); - if (componentKeys == null) return results; List l; mBgDataModel.cachedPredictedItems.clear(); for (ComponentKey key : componentKeys) { @@ -866,7 +867,6 @@ public class LoaderTask implements Runnable { mBgDataModel.cachedPredictedItems.add(info); mIconCache.getTitleAndIcon(info, false); } - return results; } } diff --git a/src/com/android/launcher3/model/PredictionModel.java b/src/com/android/launcher3/model/PredictionModel.java index 6aa41eb760..f8140eb87a 100644 --- a/src/com/android/launcher3/model/PredictionModel.java +++ b/src/com/android/launcher3/model/PredictionModel.java @@ -14,60 +14,86 @@ * limitations under the License. */ package com.android.launcher3.model; +import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; +import android.content.ComponentName; import android.content.Context; import android.content.SharedPreferences; import android.os.UserHandle; +import androidx.annotation.AnyThread; +import androidx.annotation.WorkerThread; + +import com.android.launcher3.R; import com.android.launcher3.Utilities; -import com.android.launcher3.model.data.AppInfo; +import com.android.launcher3.pm.UserCache; import com.android.launcher3.util.ComponentKey; +import com.android.launcher3.util.Preconditions; +import com.android.launcher3.util.ResourceBasedOverride; import java.util.ArrayList; import java.util.List; -import java.util.stream.Collectors; /** - * Model helper for app predictions in workspace + * Model Helper for app predictions */ -public class PredictionModel { +public class PredictionModel implements ResourceBasedOverride { + private static final String CACHED_ITEMS_KEY = "predicted_item_keys"; private static final int MAX_CACHE_ITEMS = 5; - private final Context mContext; - private final SharedPreferences mDevicePrefs; + protected Context mContext; private ArrayList mCachedComponentKeys; + private SharedPreferences mDevicePrefs; + private UserCache mUserCache; - public PredictionModel(Context context) { - mContext = context; - mDevicePrefs = Utilities.getDevicePrefs(mContext); + + /** + * Retrieve instance of this object that can be overridden in runtime based on the build + * variant of the application. + */ + public static PredictionModel newInstance(Context context) { + PredictionModel model = Overrides.getObject(PredictionModel.class, context, + R.string.prediction_model_class); + model.init(context); + return model; } + protected void init(Context context) { + mContext = context; + mDevicePrefs = Utilities.getDevicePrefs(mContext); + mUserCache = UserCache.INSTANCE.get(mContext); + + } /** * Formats and stores a list of component key in device preferences. */ + @AnyThread public void cachePredictionComponentKeys(List componentKeys) { - StringBuilder builder = new StringBuilder(); - int count = Math.min(componentKeys.size(), MAX_CACHE_ITEMS); - for (int i = 0; i < count; i++) { - builder.append(componentKeys.get(i)); - builder.append("\n"); - } - mDevicePrefs.edit().putString(CACHED_ITEMS_KEY, builder.toString()).apply(); - mCachedComponentKeys = null; + MODEL_EXECUTOR.execute(() -> { + StringBuilder builder = new StringBuilder(); + int count = Math.min(componentKeys.size(), MAX_CACHE_ITEMS); + for (int i = 0; i < count; i++) { + builder.append(serializeComponentKeyToString(componentKeys.get(i))); + builder.append("\n"); + } + mDevicePrefs.edit().putString(CACHED_ITEMS_KEY, builder.toString()).apply(); + mCachedComponentKeys = null; + }); } /** * parses and returns ComponentKeys saved by * {@link PredictionModel#cachePredictionComponentKeys(List)} */ + @WorkerThread public List getPredictionComponentKeys() { + Preconditions.assertWorkerThread(); if (mCachedComponentKeys == null) { mCachedComponentKeys = new ArrayList<>(); - String cachedBlob = mDevicePrefs.getString(CACHED_ITEMS_KEY, ""); for (String line : cachedBlob.split("\n")) { - ComponentKey key = ComponentKey.fromString(line); + ComponentKey key = getComponentKeyFromSerializedString(line); if (key != null) { mCachedComponentKeys.add(key); } @@ -76,18 +102,26 @@ public class PredictionModel { return mCachedComponentKeys; } - /** - * Remove uninstalled applications from model - */ - public void removePackage(String pkgName, UserHandle user, ArrayList ids) { - for (int i = ids.size() - 1; i >= 0; i--) { - AppInfo info = ids.get(i); - if (info.user.equals(user) && pkgName.equals(info.componentName.getPackageName())) { - ids.remove(i); - } + private String serializeComponentKeyToString(ComponentKey componentKey) { + long userSerialNumber = mUserCache.getSerialNumberForUser(componentKey.user); + return componentKey.componentName.flattenToString() + "#" + userSerialNumber; + } + + private ComponentKey getComponentKeyFromSerializedString(String str) { + int sep = str.indexOf('#'); + if (sep < 0 || (sep + 1) >= str.length()) { + return null; + } + ComponentName componentName = ComponentName.unflattenFromString(str.substring(0, sep)); + if (componentName == null) { + return null; + } + try { + long serialNumber = Long.parseLong(str.substring(sep + 1)); + UserHandle userHandle = mUserCache.getUserForSerialNumber(serialNumber); + return userHandle != null ? new ComponentKey(componentName, userHandle) : null; + } catch (NumberFormatException ex) { + return null; } - cachePredictionComponentKeys(getPredictionComponentKeys().stream() - .filter(cn -> !(cn.user.equals(user) && cn.componentName.getPackageName().equals( - pkgName))).collect(Collectors.toList())); } } From a5252556401a7c8abfc7f66af45cc16bff232c5d Mon Sep 17 00:00:00 2001 From: thiruram Date: Mon, 29 Jun 2020 10:47:14 -0700 Subject: [PATCH 10/12] Fixes NPE with system shortcuts. Bug: 160109140 Change-Id: I35c00fc1792fcf11fc61e1876f9184bd5fb309ce (cherry picked from commit 53925ff9c6558e330aed20a2e406d68474a41247) --- .../PredictionUiStateManager.java | 25 +++++++++++-------- .../HotseatPredictionController.java | 8 ++++-- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java index 5e54cd292f..a0f6b044ba 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java @@ -52,7 +52,6 @@ import com.android.launcher3.util.MainThreadInitializedObject; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Optional; import java.util.OptionalInt; import java.util.stream.IntStream; @@ -315,16 +314,22 @@ public class PredictionUiStateManager implements StateListener, * {@link LauncherSettings.Favorites#ITEM_TYPE_DEEP_SHORTCUT} */ public OptionalInt getAllAppsRank(@Nullable ItemInfo itemInfo) { - Optional componentKey = Optional.ofNullable(itemInfo) - .filter(item -> item.itemType == ITEM_TYPE_APPLICATION - || item.itemType == ITEM_TYPE_SHORTCUT - || item.itemType == ITEM_TYPE_DEEP_SHORTCUT) - .map(ItemInfo::getTargetComponent) - .map(componentName -> new ComponentKey(componentName, itemInfo.user)); + if (itemInfo == null || itemInfo.getTargetComponent() == null || itemInfo.user == null) { + return OptionalInt.empty(); + } - return componentKey.map(key -> IntStream.range(0, getCurrentState().apps.size()) - .filter(index -> key.equals(getCurrentState().apps.get(index).getComponentKey())) - .findFirst()).orElseGet(OptionalInt::empty); + if (itemInfo.itemType == ITEM_TYPE_APPLICATION + || itemInfo.itemType == ITEM_TYPE_SHORTCUT + || itemInfo.itemType == ITEM_TYPE_DEEP_SHORTCUT) { + ComponentKey key = new ComponentKey(itemInfo.getTargetComponent(), + itemInfo.user); + final List apps = getCurrentState().apps; + return IntStream.range(0, apps.size()) + .filter(index -> key.equals(apps.get(index).getComponentKey())) + .findFirst(); + } + + return OptionalInt.empty(); } /** diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java index 1dbb9e2d6b..987cd0fcf6 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java @@ -655,11 +655,15 @@ public class HotseatPredictionController implements DragController.DragListener, + ",launchLocation:" + itemInfo.container); } - final ComponentKey k = new ComponentKey(itemInfo.getTargetComponent(), itemInfo.user); + if (itemInfo.getTargetComponent() == null || itemInfo.user == null) { + return; + } + + final ComponentKey key = new ComponentKey(itemInfo.getTargetComponent(), itemInfo.user); final List predictedApps = new ArrayList<>(mComponentKeyMappers); OptionalInt rank = IntStream.range(0, predictedApps.size()) - .filter((i) -> k.equals(predictedApps.get(i).getComponentKey())) + .filter(index -> key.equals(predictedApps.get(index).getComponentKey())) .findFirst(); if (!rank.isPresent()) { return; From 15bcf24af6bcc0836d0b4e998735a1ea4b7fcc1a Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Thu, 30 Jul 2020 14:40:29 -0700 Subject: [PATCH 11/12] Fixing wrong velocity state when there are too few samples Bug: 160568387 Change-Id: I32b3b7ee1bff5595941fc0c0b37c9f5a28d1a1fa (cherry picked from commit 48afb6b3b9f5a4d67e36a7c6cc994efeb8b9be38) (cherry picked from commit f58a2b9cd982a6a761deaa05b3d3e9802df040c3) --- .../quickstep/util/MotionPauseDetector.java | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java index a5d456878a..969fa5067d 100644 --- a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java +++ b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java @@ -358,18 +358,23 @@ public class MotionPauseDetector { if (count < 3) { // Too few samples - if (count == 2) { - int endPos = pointPos - 1; - if (endPos < 0) { - endPos += HISTORY_SIZE; - } - float denominator = eventTime - mHistoricTimes[endPos]; - if (denominator != 0) { - return (eventTime - mHistoricPos[endPos]) / denominator; - + switch (count) { + case 2: { + int endPos = pointPos - 1; + if (endPos < 0) { + endPos += HISTORY_SIZE; + } + float denominator = eventTime - mHistoricTimes[endPos]; + if (denominator != 0) { + return (mHistoricPos[pointPos] - mHistoricPos[endPos]) / denominator; + } } + // fall through + case 1: + return 0f; + default: + return null; } - return null; } float Sxx = sxi2 - sxi * sxi / count; From c7c9e80d58665e698521bc5571a139eab8f655d4 Mon Sep 17 00:00:00 2001 From: Tony Wickham Date: Wed, 12 Aug 2020 16:48:33 -0700 Subject: [PATCH 12/12] Store mHistoricTimes as longs intead of floats The loss of precision was causing us to miscalculate the age of events, and thus not detecting any pause due to denominator = 0. Test: have a device that hasn't been rebooted for a certain amount of time such that SystemClock#uptimeMillis can't be accurately converted to a float, then try to swipe up and hold Fixes: 160568387 Change-Id: Idef112187f34a18feea7e6a0b77258626f9d0ed4 (cherry picked from commit a14567096922f23d6e03c84fc930e3451e7471c2) (cherry picked from commit 31ef24c9342b8d7b09f4c2f655dc2517b11d578f) --- .../android/quickstep/util/MotionPauseDetector.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java index 969fa5067d..f60f7ad6d8 100644 --- a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java +++ b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java @@ -276,7 +276,7 @@ public class MotionPauseDetector { private static final int HISTORY_SIZE = 20; // Position history are stored in a circular array - private final float[] mHistoricTimes = new float[HISTORY_SIZE]; + private final long[] mHistoricTimes = new long[HISTORY_SIZE]; private final float[] mHistoricPos = new float[HISTORY_SIZE]; private int mHistoryCount = 0; private int mHistoryStart = 0; @@ -292,7 +292,7 @@ public class MotionPauseDetector { mHistoryCount = mHistoryStart = 0; } - private void addPositionAndTime(float eventTime, float eventPosition) { + private void addPositionAndTime(long eventTime, float eventPosition) { mHistoricTimes[mHistoryStart] = eventTime; mHistoricPos[mHistoryStart] = eventPosition; mHistoryStart++; @@ -322,7 +322,7 @@ public class MotionPauseDetector { * Based on solveUnweightedLeastSquaresDeg2 in VelocityTracker.cpp */ private Float solveUnweightedLeastSquaresDeg2(final int pointPos) { - final float eventTime = mHistoricTimes[pointPos]; + final long eventTime = mHistoricTimes[pointPos]; float sxi = 0, sxiyi = 0, syi = 0, sxi2 = 0, sxi3 = 0, sxi2yi = 0, sxi4 = 0; int count = 0; @@ -332,8 +332,8 @@ public class MotionPauseDetector { index += HISTORY_SIZE; } - float time = mHistoricTimes[index]; - float age = eventTime - time; + long time = mHistoricTimes[index]; + long age = eventTime - time; if (age > HORIZON_MS) { break; } @@ -364,7 +364,7 @@ public class MotionPauseDetector { if (endPos < 0) { endPos += HISTORY_SIZE; } - float denominator = eventTime - mHistoricTimes[endPos]; + long denominator = eventTime - mHistoricTimes[endPos]; if (denominator != 0) { return (mHistoricPos[pointPos] - mHistoricPos[endPos]) / denominator; }