From 6301570222d07a0381428e067aeb1db1e96a3ea8 Mon Sep 17 00:00:00 2001 From: Jon Miranda Date: Thu, 29 Aug 2019 11:41:53 -0700 Subject: [PATCH 0001/1707] [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 0002/1707] [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 0003/1707] 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 0004/1707] 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 0005/1707] 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 a93c2dcd564c1d3278f5f5e52674aba9938399be Mon Sep 17 00:00:00 2001 From: Hongwei Wang Date: Mon, 10 Feb 2020 14:35:35 -0800 Subject: [PATCH 0006/1707] Indicate PiP animation type from Launcher to SysUI Also, need callback from SysUI to Launcher once the pip animation is started to keep back compatibility. Bug: 146594635 Test: manually Change-Id: I617a17fd5f6049d9f1d49f9f834623a2bb49f6ca --- .../quickstep/FallbackSwipeHandler.java | 2 ++ .../quickstep/LauncherSwipeHandler.java | 2 ++ .../android/quickstep/views/RecentsView.java | 9 +++++ .../com/android/quickstep/SystemUiProxy.java | 33 +++++++++++++++++-- 4 files changed, 43 insertions(+), 3 deletions(-) diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java index 700feef790..cd001a128b 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java @@ -332,6 +332,8 @@ public class FallbackSwipeHandler extends BaseSwipeUpHandler { if (!mTouchedHomeDuringTransition) { // If the user hasn't interacted with the screen during the transition, diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java index 345a147ca5..646d01fb8f 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java @@ -699,6 +699,8 @@ public class LauncherSwipeHandler switch (mGestureState.getEndTarget()) { case HOME: mStateCallback.setState(STATE_SCALED_CONTROLLER_HOME | STATE_CAPTURE_SCREENSHOT); + // Notify swipe-to-home (recents animation) is finished + SystemUiProxy.INSTANCE.get(mContext).notifySwipeToHomeFinished(); break; case RECENTS: mStateCallback.setState(STATE_SCALED_CONTROLLER_RECENTS | STATE_CAPTURE_SCREENSHOT 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 a673ab61b7..a7968ca055 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 @@ -17,6 +17,7 @@ package com.android.quickstep.views; import static androidx.dynamicanimation.animation.DynamicAnimation.MIN_VISIBLE_CHANGE_PIXELS; + import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS; import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_ICON_PARAMS; import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; @@ -105,11 +106,13 @@ import com.android.quickstep.RecentsAnimationController; import com.android.quickstep.RecentsAnimationTargets; import com.android.quickstep.RecentsModel; import com.android.quickstep.RecentsModel.TaskVisualsChangeListener; +import com.android.quickstep.SystemUiProxy; import com.android.quickstep.TaskThumbnailCache; import com.android.quickstep.TaskUtils; import com.android.quickstep.ViewUtils; import com.android.quickstep.util.AppWindowAnimationHelper; import com.android.quickstep.util.LayoutUtils; +import com.android.systemui.shared.recents.IPinnedStackAnimationListener; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.system.ActivityManagerWrapper; @@ -261,7 +264,10 @@ public abstract class RecentsView extends PagedView impl } }); } + }; + private final IPinnedStackAnimationListener mIPinnedStackAnimationListener = + new IPinnedStackAnimationListener.Stub() { @Override public void onPinnedStackAnimationStarted() { // Needed for activities that auto-enter PiP, which will not trigger a remote @@ -443,6 +449,8 @@ public abstract class RecentsView extends PagedView impl mSyncTransactionApplier = new SyncRtSurfaceTransactionApplierCompat(this); RecentsModel.INSTANCE.get(getContext()).addThumbnailChangeListener(this); mIdp.addOnChangeListener(this); + SystemUiProxy.INSTANCE.get(getContext()).setPinnedStackAnimationListener( + mIPinnedStackAnimationListener); } @Override @@ -455,6 +463,7 @@ public abstract class RecentsView extends PagedView impl mSyncTransactionApplier = null; RecentsModel.INSTANCE.get(getContext()).removeThumbnailChangeListener(this); mIdp.removeOnChangeListener(this); + SystemUiProxy.INSTANCE.get(getContext()).setPinnedStackAnimationListener(null); } @Override diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java index 458d6a9553..eb60601c44 100644 --- a/quickstep/src/com/android/quickstep/SystemUiProxy.java +++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java @@ -29,6 +29,7 @@ import android.util.Log; import android.view.MotionEvent; import com.android.launcher3.util.MainThreadInitializedObject; +import com.android.systemui.shared.recents.IPinnedStackAnimationListener; import com.android.systemui.shared.recents.ISystemUiProxy; /** @@ -268,9 +269,7 @@ public class SystemUiProxy implements ISystemUiProxy { } } - /** - * See SharedApiCompat#setShelfHeight() - */ + @Override public void setShelfHeight(boolean visible, int shelfHeight) { boolean changed = visible != mLastShelfVisible || shelfHeight != mLastShelfHeight; if (mSystemUiProxy != null && changed) { @@ -306,4 +305,32 @@ public class SystemUiProxy implements ISystemUiProxy { } } } + + /** + * Notifies that swipe-to-home action is finished. + */ + @Override + public void notifySwipeToHomeFinished() { + if (mSystemUiProxy != null) { + try { + mSystemUiProxy.notifySwipeToHomeFinished(); + } catch (RemoteException e) { + Log.w(TAG, "Failed call setPinnedStackAnimationType", e); + } + } + } + + /** + * Sets listener to get pinned stack animation callbacks. + */ + @Override + public void setPinnedStackAnimationListener(IPinnedStackAnimationListener listener) { + if (mSystemUiProxy != null) { + try { + mSystemUiProxy.setPinnedStackAnimationListener(listener); + } catch (RemoteException e) { + Log.w(TAG, "Failed call setPinnedStackAnimationListener", e); + } + } + } } From e1aff298891495a422d3595f90749a457e7c4ae6 Mon Sep 17 00:00:00 2001 From: Vadim Caen Date: Tue, 25 Feb 2020 12:48:21 +0100 Subject: [PATCH 0007/1707] Fixed portrait orientation if fixed_rotation flag Use SCREEN_ORIENTATION_PORTRAIT for the launcher if the fixed_rotation is enabled Test: manual, check that the launcher doesn't rotates when the flag is enabled and we are coming from a landscape activity Bug: 147469164 Bug: 146416687 Change-Id: I0fe0d16ec1fbfe096fbdfcf2c315e12fb3423cf8 --- src/com/android/launcher3/PagedView.java | 23 +++++++++++++++++-- src/com/android/launcher3/Utilities.java | 9 ++++++++ .../launcher3/states/RotationHelper.java | 6 ++++- 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java index 4c6e1f3ebc..66dba98e0f 100644 --- a/src/com/android/launcher3/PagedView.java +++ b/src/com/android/launcher3/PagedView.java @@ -20,12 +20,15 @@ import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessib import static com.android.launcher3.compat.AccessibilityManagerCompat.isObservedEventType; import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS; import static com.android.launcher3.touch.OverScroll.OVERSCROLL_DAMP_FACTOR; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import android.animation.LayoutTransition; import android.animation.TimeInterpolator; import android.annotation.SuppressLint; +import android.content.ContentResolver; import android.content.Context; import android.content.res.TypedArray; +import android.database.ContentObserver; import android.graphics.Canvas; import android.graphics.Rect; import android.os.Bundle; @@ -64,6 +67,9 @@ public abstract class PagedView extends ViewGrou private static final String TAG = "PagedView"; private static final boolean DEBUG = false; + public static boolean sFlagForcedRotation = false; + public static final String FIXED_ROTATION_TRANSFORM_SETTING_NAME = "fixed_rotation_transform"; + public static final int INVALID_PAGE = -1; protected static final ComputePageScrollsLogic SIMPLE_SCROLL_LOGIC = (v) -> v.getVisibility() != GONE; @@ -166,11 +172,12 @@ public abstract class PagedView extends ViewGrou * Initializes various states for this workspace. */ protected void init() { - mScroller = new OverScroller(getContext()); + Context context = getContext(); + mScroller = new OverScroller(context); setDefaultInterpolator(Interpolators.SCROLL); mCurrentPage = 0; - final ViewConfiguration configuration = ViewConfiguration.get(getContext()); + final ViewConfiguration configuration = ViewConfiguration.get(context); mTouchSlop = configuration.getScaledPagingTouchSlop(); mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); @@ -182,6 +189,18 @@ public abstract class PagedView extends ViewGrou if (Utilities.ATLEAST_OREO) { setDefaultFocusHighlightEnabled(false); } + + sFlagForcedRotation = Utilities.isFixedRotationTransformEnabled(context); + final ContentResolver resolver = context.getContentResolver(); + final ContentObserver observer = new ContentObserver(MAIN_EXECUTOR.getHandler()) { + @Override + public void onChange(boolean selfChange) { + sFlagForcedRotation = Utilities.isFixedRotationTransformEnabled(context); + } + }; + resolver.registerContentObserver(Settings.Global.getUriFor( + FIXED_ROTATION_TRANSFORM_SETTING_NAME), + false, observer); } protected void setDefaultInterpolator(Interpolator interpolator) { diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java index e0e4cc0ce4..571f745080 100644 --- a/src/com/android/launcher3/Utilities.java +++ b/src/com/android/launcher3/Utilities.java @@ -17,6 +17,7 @@ package com.android.launcher3; import static com.android.launcher3.ItemInfoWithIcon.FLAG_ICON_BADGED; +import static com.android.launcher3.PagedView.FIXED_ROTATION_TRANSFORM_SETTING_NAME; import android.animation.ValueAnimator; import android.annotation.TargetApi; @@ -127,6 +128,14 @@ public final class Utilities { Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0; } + /** + * Checks whether the fixed_rotation_flag is enabled. + */ + public static boolean isFixedRotationTransformEnabled(Context context) { + return Settings.Global.getInt(context.getContentResolver(), + FIXED_ROTATION_TRANSFORM_SETTING_NAME, 0) != 0; + } + // An intent extra to indicate the horizontal scroll of the wallpaper. public static final String EXTRA_WALLPAPER_OFFSET = "com.android.launcher3.WALLPAPER_OFFSET"; public static final String EXTRA_WALLPAPER_FLAVOR = "com.android.launcher3.WALLPAPER_FLAVOR"; diff --git a/src/com/android/launcher3/states/RotationHelper.java b/src/com/android/launcher3/states/RotationHelper.java index 852928b416..3a64f9aeb8 100644 --- a/src/com/android/launcher3/states/RotationHelper.java +++ b/src/com/android/launcher3/states/RotationHelper.java @@ -17,6 +17,7 @@ package com.android.launcher3.states; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static android.util.DisplayMetrics.DENSITY_DEVICE_STABLE; @@ -26,6 +27,7 @@ import android.content.res.Resources; import android.view.WindowManager; import com.android.launcher3.Launcher; +import com.android.launcher3.PagedView; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.config.FeatureFlags; @@ -182,7 +184,9 @@ public class RotationHelper implements OnSharedPreferenceChangeListener { } final int activityFlags; - if (mStateHandlerRequest != REQUEST_NONE) { + if (PagedView.sFlagForcedRotation) { + activityFlags = SCREEN_ORIENTATION_PORTRAIT; + } else if (mStateHandlerRequest != REQUEST_NONE) { activityFlags = mStateHandlerRequest == REQUEST_LOCK ? SCREEN_ORIENTATION_LOCKED : SCREEN_ORIENTATION_UNSPECIFIED; } else if (mCurrentTransitionRequest != REQUEST_NONE) { From 38d2e3086ce3a6636bd7647c170d51e04a56186a Mon Sep 17 00:00:00 2001 From: Tony Wickham Date: Thu, 5 Mar 2020 18:04:41 +0000 Subject: [PATCH 0008/1707] Revert "Fixed portrait orientation if fixed_rotation flag" This reverts commit e1aff298891495a422d3595f90749a457e7c4ae6. Reason for revert: An equivalent change was made in ub-launcher3-master with slight variations, causing conflicts when merging from there to platform master. Once this is reverted, conflicts should go away and the new solution should be good to go. Change-Id: I19ba4af5f1e5f089fb771ada8b20c28a4b09b68c --- src/com/android/launcher3/PagedView.java | 23 ++----------------- src/com/android/launcher3/Utilities.java | 9 -------- .../launcher3/states/RotationHelper.java | 6 +---- 3 files changed, 3 insertions(+), 35 deletions(-) diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java index 66dba98e0f..4c6e1f3ebc 100644 --- a/src/com/android/launcher3/PagedView.java +++ b/src/com/android/launcher3/PagedView.java @@ -20,15 +20,12 @@ import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessib import static com.android.launcher3.compat.AccessibilityManagerCompat.isObservedEventType; import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS; import static com.android.launcher3.touch.OverScroll.OVERSCROLL_DAMP_FACTOR; -import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import android.animation.LayoutTransition; import android.animation.TimeInterpolator; import android.annotation.SuppressLint; -import android.content.ContentResolver; import android.content.Context; import android.content.res.TypedArray; -import android.database.ContentObserver; import android.graphics.Canvas; import android.graphics.Rect; import android.os.Bundle; @@ -67,9 +64,6 @@ public abstract class PagedView extends ViewGrou private static final String TAG = "PagedView"; private static final boolean DEBUG = false; - public static boolean sFlagForcedRotation = false; - public static final String FIXED_ROTATION_TRANSFORM_SETTING_NAME = "fixed_rotation_transform"; - public static final int INVALID_PAGE = -1; protected static final ComputePageScrollsLogic SIMPLE_SCROLL_LOGIC = (v) -> v.getVisibility() != GONE; @@ -172,12 +166,11 @@ public abstract class PagedView extends ViewGrou * Initializes various states for this workspace. */ protected void init() { - Context context = getContext(); - mScroller = new OverScroller(context); + mScroller = new OverScroller(getContext()); setDefaultInterpolator(Interpolators.SCROLL); mCurrentPage = 0; - final ViewConfiguration configuration = ViewConfiguration.get(context); + final ViewConfiguration configuration = ViewConfiguration.get(getContext()); mTouchSlop = configuration.getScaledPagingTouchSlop(); mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); @@ -189,18 +182,6 @@ public abstract class PagedView extends ViewGrou if (Utilities.ATLEAST_OREO) { setDefaultFocusHighlightEnabled(false); } - - sFlagForcedRotation = Utilities.isFixedRotationTransformEnabled(context); - final ContentResolver resolver = context.getContentResolver(); - final ContentObserver observer = new ContentObserver(MAIN_EXECUTOR.getHandler()) { - @Override - public void onChange(boolean selfChange) { - sFlagForcedRotation = Utilities.isFixedRotationTransformEnabled(context); - } - }; - resolver.registerContentObserver(Settings.Global.getUriFor( - FIXED_ROTATION_TRANSFORM_SETTING_NAME), - false, observer); } protected void setDefaultInterpolator(Interpolator interpolator) { diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java index 571f745080..e0e4cc0ce4 100644 --- a/src/com/android/launcher3/Utilities.java +++ b/src/com/android/launcher3/Utilities.java @@ -17,7 +17,6 @@ package com.android.launcher3; import static com.android.launcher3.ItemInfoWithIcon.FLAG_ICON_BADGED; -import static com.android.launcher3.PagedView.FIXED_ROTATION_TRANSFORM_SETTING_NAME; import android.animation.ValueAnimator; import android.annotation.TargetApi; @@ -128,14 +127,6 @@ public final class Utilities { Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0; } - /** - * Checks whether the fixed_rotation_flag is enabled. - */ - public static boolean isFixedRotationTransformEnabled(Context context) { - return Settings.Global.getInt(context.getContentResolver(), - FIXED_ROTATION_TRANSFORM_SETTING_NAME, 0) != 0; - } - // An intent extra to indicate the horizontal scroll of the wallpaper. public static final String EXTRA_WALLPAPER_OFFSET = "com.android.launcher3.WALLPAPER_OFFSET"; public static final String EXTRA_WALLPAPER_FLAVOR = "com.android.launcher3.WALLPAPER_FLAVOR"; diff --git a/src/com/android/launcher3/states/RotationHelper.java b/src/com/android/launcher3/states/RotationHelper.java index 3a64f9aeb8..852928b416 100644 --- a/src/com/android/launcher3/states/RotationHelper.java +++ b/src/com/android/launcher3/states/RotationHelper.java @@ -17,7 +17,6 @@ package com.android.launcher3.states; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static android.util.DisplayMetrics.DENSITY_DEVICE_STABLE; @@ -27,7 +26,6 @@ import android.content.res.Resources; import android.view.WindowManager; import com.android.launcher3.Launcher; -import com.android.launcher3.PagedView; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.config.FeatureFlags; @@ -184,9 +182,7 @@ public class RotationHelper implements OnSharedPreferenceChangeListener { } final int activityFlags; - if (PagedView.sFlagForcedRotation) { - activityFlags = SCREEN_ORIENTATION_PORTRAIT; - } else if (mStateHandlerRequest != REQUEST_NONE) { + if (mStateHandlerRequest != REQUEST_NONE) { activityFlags = mStateHandlerRequest == REQUEST_LOCK ? SCREEN_ORIENTATION_LOCKED : SCREEN_ORIENTATION_UNSPECIFIED; } else if (mCurrentTransitionRequest != REQUEST_NONE) { From 30c29bfc08c2429a382d9d34a7728f8f73e9b8fd Mon Sep 17 00:00:00 2001 From: Andy Wickham Date: Tue, 3 Mar 2020 01:15:27 +0000 Subject: [PATCH 0009/1707] Removes iconloaderlib from Launcher3. Bug: 138964382 Test: builds Change-Id: Ic60adfb2ebdcf1a72b440df26023b861fd6e62d5 --- iconloaderlib/Android.bp | 44 -- iconloaderlib/AndroidManifest.xml | 20 - iconloaderlib/build.gradle | 38 -- .../adaptive_icon_drawable_wrapper.xml | 22 - .../res/drawable/ic_instant_app_badge.xml | 39 -- iconloaderlib/res/values/colors.xml | 21 - iconloaderlib/res/values/config.xml | 27 - iconloaderlib/res/values/dimens.xml | 19 - .../launcher3/icons/BaseIconFactory.java | 396 ------------ .../android/launcher3/icons/BitmapInfo.java | 72 --- .../launcher3/icons/BitmapRenderer.java | 70 --- .../launcher3/icons/ColorExtractor.java | 127 ---- .../android/launcher3/icons/DotRenderer.java | 143 ----- .../launcher3/icons/FixedScaleDrawable.java | 53 -- .../launcher3/icons/GraphicsUtils.java | 85 --- .../launcher3/icons/IconNormalizer.java | 411 ------------- .../launcher3/icons/ShadowGenerator.java | 167 ----- .../launcher3/icons/cache/BaseIconCache.java | 582 ------------------ .../launcher3/icons/cache/CachingLogic.java | 65 -- .../icons/cache/HandlerRunnable.java | 67 -- .../icons/cache/IconCacheUpdateHandler.java | 313 ---------- .../android/launcher3/util/ComponentKey.java | 59 -- .../launcher3/util/NoLocaleSQLiteHelper.java | 58 -- .../launcher3/util/SQLiteCacheHelper.java | 125 ---- .../android/launcher3/icons/IconFactory.java | 89 --- .../launcher3/icons/SimpleIconCache.java | 114 ---- 26 files changed, 3226 deletions(-) delete mode 100644 iconloaderlib/Android.bp delete mode 100644 iconloaderlib/AndroidManifest.xml delete mode 100644 iconloaderlib/build.gradle delete mode 100644 iconloaderlib/res/drawable-v26/adaptive_icon_drawable_wrapper.xml delete mode 100644 iconloaderlib/res/drawable/ic_instant_app_badge.xml delete mode 100644 iconloaderlib/res/values/colors.xml delete mode 100644 iconloaderlib/res/values/config.xml delete mode 100644 iconloaderlib/res/values/dimens.xml delete mode 100644 iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java delete mode 100644 iconloaderlib/src/com/android/launcher3/icons/BitmapInfo.java delete mode 100644 iconloaderlib/src/com/android/launcher3/icons/BitmapRenderer.java delete mode 100644 iconloaderlib/src/com/android/launcher3/icons/ColorExtractor.java delete mode 100644 iconloaderlib/src/com/android/launcher3/icons/DotRenderer.java delete mode 100644 iconloaderlib/src/com/android/launcher3/icons/FixedScaleDrawable.java delete mode 100644 iconloaderlib/src/com/android/launcher3/icons/GraphicsUtils.java delete mode 100644 iconloaderlib/src/com/android/launcher3/icons/IconNormalizer.java delete mode 100644 iconloaderlib/src/com/android/launcher3/icons/ShadowGenerator.java delete mode 100644 iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java delete mode 100644 iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java delete mode 100644 iconloaderlib/src/com/android/launcher3/icons/cache/HandlerRunnable.java delete mode 100644 iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java delete mode 100644 iconloaderlib/src/com/android/launcher3/util/ComponentKey.java delete mode 100644 iconloaderlib/src/com/android/launcher3/util/NoLocaleSQLiteHelper.java delete mode 100644 iconloaderlib/src/com/android/launcher3/util/SQLiteCacheHelper.java delete mode 100644 iconloaderlib/src_full_lib/com/android/launcher3/icons/IconFactory.java delete mode 100644 iconloaderlib/src_full_lib/com/android/launcher3/icons/SimpleIconCache.java diff --git a/iconloaderlib/Android.bp b/iconloaderlib/Android.bp deleted file mode 100644 index f12d16e42a..0000000000 --- a/iconloaderlib/Android.bp +++ /dev/null @@ -1,44 +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. - -android_library { - name: "iconloader_base", - sdk_version: "28", - min_sdk_version: "21", - static_libs: [ - "androidx.core_core", - ], - resource_dirs: [ - "res", - ], - srcs: [ - "src/**/*.java", - ], -} - -android_library { - name: "iconloader", - sdk_version: "system_current", - min_sdk_version: "21", - static_libs: [ - "androidx.core_core", - ], - resource_dirs: [ - "res", - ], - srcs: [ - "src/**/*.java", - "src_full_lib/**/*.java", - ], -} diff --git a/iconloaderlib/AndroidManifest.xml b/iconloaderlib/AndroidManifest.xml deleted file mode 100644 index b30258da2a..0000000000 --- a/iconloaderlib/AndroidManifest.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - diff --git a/iconloaderlib/build.gradle b/iconloaderlib/build.gradle deleted file mode 100644 index d7a62e1114..0000000000 --- a/iconloaderlib/build.gradle +++ /dev/null @@ -1,38 +0,0 @@ -apply plugin: 'com.android.library' - -android { - compileSdkVersion COMPILE_SDK - buildToolsVersion BUILD_TOOLS_VERSION - - defaultConfig { - minSdkVersion 25 - targetSdkVersion 28 - versionCode 1 - versionName "1.0" - } - - sourceSets { - main { - java.srcDirs = ['src', 'src_full_lib'] - manifest.srcFile 'AndroidManifest.xml' - res.srcDirs = ['res'] - } - } - - lintOptions { - abortOnError false - } - - tasks.withType(JavaCompile) { - options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation" - } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } -} - -dependencies { - implementation "androidx.core:core:${ANDROID_X_VERSION}" -} diff --git a/iconloaderlib/res/drawable-v26/adaptive_icon_drawable_wrapper.xml b/iconloaderlib/res/drawable-v26/adaptive_icon_drawable_wrapper.xml deleted file mode 100644 index 9f13cf5719..0000000000 --- a/iconloaderlib/res/drawable-v26/adaptive_icon_drawable_wrapper.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - diff --git a/iconloaderlib/res/drawable/ic_instant_app_badge.xml b/iconloaderlib/res/drawable/ic_instant_app_badge.xml deleted file mode 100644 index b74317e5f2..0000000000 --- a/iconloaderlib/res/drawable/ic_instant_app_badge.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - diff --git a/iconloaderlib/res/values/colors.xml b/iconloaderlib/res/values/colors.xml deleted file mode 100644 index 873b2fc5fb..0000000000 --- a/iconloaderlib/res/values/colors.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - #FFFFFF - diff --git a/iconloaderlib/res/values/config.xml b/iconloaderlib/res/values/config.xml deleted file mode 100644 index 68c2d2e3aa..0000000000 --- a/iconloaderlib/res/values/config.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - 56dp - false - app_icons.db - - \ No newline at end of file diff --git a/iconloaderlib/res/values/dimens.xml b/iconloaderlib/res/values/dimens.xml deleted file mode 100644 index e8c0c44f72..0000000000 --- a/iconloaderlib/res/values/dimens.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - 24dp - diff --git a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java deleted file mode 100644 index 31a923e314..0000000000 --- a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java +++ /dev/null @@ -1,396 +0,0 @@ -package com.android.launcher3.icons; - -import static android.graphics.Paint.DITHER_FLAG; -import static android.graphics.Paint.FILTER_BITMAP_FLAG; - -import static com.android.launcher3.icons.ShadowGenerator.BLUR_FACTOR; - -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.PaintFlagsDrawFilter; -import android.graphics.Rect; -import android.graphics.RectF; -import android.graphics.drawable.AdaptiveIconDrawable; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.Drawable; -import android.os.Build; -import android.os.Process; -import android.os.UserHandle; - -import androidx.annotation.NonNull; - -/** - * This class will be moved to androidx library. There shouldn't be any dependency outside - * this package. - */ -public class BaseIconFactory implements AutoCloseable { - - private static final String TAG = "BaseIconFactory"; - private static final int DEFAULT_WRAPPER_BACKGROUND = Color.WHITE; - static final boolean ATLEAST_OREO = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O; - static final boolean ATLEAST_P = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P; - - private static final float ICON_BADGE_SCALE = 0.444f; - - private final Rect mOldBounds = new Rect(); - protected final Context mContext; - private final Canvas mCanvas; - private final PackageManager mPm; - private final ColorExtractor mColorExtractor; - private boolean mDisableColorExtractor; - private boolean mBadgeOnLeft = false; - - protected final int mFillResIconDpi; - protected final int mIconBitmapSize; - - private IconNormalizer mNormalizer; - private ShadowGenerator mShadowGenerator; - private final boolean mShapeDetection; - - private Drawable mWrapperIcon; - private int mWrapperBackgroundColor = DEFAULT_WRAPPER_BACKGROUND; - - protected BaseIconFactory(Context context, int fillResIconDpi, int iconBitmapSize, - boolean shapeDetection) { - mContext = context.getApplicationContext(); - mShapeDetection = shapeDetection; - mFillResIconDpi = fillResIconDpi; - mIconBitmapSize = iconBitmapSize; - - mPm = mContext.getPackageManager(); - mColorExtractor = new ColorExtractor(); - - mCanvas = new Canvas(); - mCanvas.setDrawFilter(new PaintFlagsDrawFilter(DITHER_FLAG, FILTER_BITMAP_FLAG)); - clear(); - } - - protected BaseIconFactory(Context context, int fillResIconDpi, int iconBitmapSize) { - this(context, fillResIconDpi, iconBitmapSize, false); - } - - protected void clear() { - mWrapperBackgroundColor = DEFAULT_WRAPPER_BACKGROUND; - mDisableColorExtractor = false; - mBadgeOnLeft = false; - } - - public ShadowGenerator getShadowGenerator() { - if (mShadowGenerator == null) { - mShadowGenerator = new ShadowGenerator(mIconBitmapSize); - } - return mShadowGenerator; - } - - public IconNormalizer getNormalizer() { - if (mNormalizer == null) { - mNormalizer = new IconNormalizer(mContext, mIconBitmapSize, mShapeDetection); - } - return mNormalizer; - } - - @SuppressWarnings("deprecation") - public BitmapInfo createIconBitmap(Intent.ShortcutIconResource iconRes) { - try { - Resources resources = mPm.getResourcesForApplication(iconRes.packageName); - if (resources != null) { - final int id = resources.getIdentifier(iconRes.resourceName, null, null); - // do not stamp old legacy shortcuts as the app may have already forgotten about it - return createBadgedIconBitmap( - resources.getDrawableForDensity(id, mFillResIconDpi), - Process.myUserHandle() /* only available on primary user */, - false /* do not apply legacy treatment */); - } - } catch (Exception e) { - // Icon not found. - } - return null; - } - - public BitmapInfo createIconBitmap(Bitmap icon) { - if (mIconBitmapSize != icon.getWidth() || mIconBitmapSize != icon.getHeight()) { - icon = createIconBitmap(new BitmapDrawable(mContext.getResources(), icon), 1f); - } - - return BitmapInfo.of(icon, extractColor(icon)); - } - - public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user, - boolean shrinkNonAdaptiveIcons) { - return createBadgedIconBitmap(icon, user, shrinkNonAdaptiveIcons, false, null); - } - - public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user, - int iconAppTargetSdk) { - return createBadgedIconBitmap(icon, user, iconAppTargetSdk, false); - } - - public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user, - int iconAppTargetSdk, boolean isInstantApp) { - return createBadgedIconBitmap(icon, user, iconAppTargetSdk, isInstantApp, null); - } - - public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user, - int iconAppTargetSdk, boolean isInstantApp, float[] scale) { - boolean shrinkNonAdaptiveIcons = ATLEAST_P || - (ATLEAST_OREO && iconAppTargetSdk >= Build.VERSION_CODES.O); - return createBadgedIconBitmap(icon, user, shrinkNonAdaptiveIcons, isInstantApp, scale); - } - - public Bitmap createScaledBitmapWithoutShadow(Drawable icon, int iconAppTargetSdk) { - boolean shrinkNonAdaptiveIcons = ATLEAST_P || - (ATLEAST_OREO && iconAppTargetSdk >= Build.VERSION_CODES.O); - return createScaledBitmapWithoutShadow(icon, shrinkNonAdaptiveIcons); - } - - /** - * Creates bitmap using the source drawable and various parameters. - * The bitmap is visually normalized with other icons and has enough spacing to add shadow. - * - * @param icon source of the icon - * @param user info can be used for a badge - * @param shrinkNonAdaptiveIcons {@code true} if non adaptive icons should be treated - * @param isInstantApp info can be used for a badge - * @param scale returns the scale result from normalization - * @return a bitmap suitable for disaplaying as an icon at various system UIs. - */ - public BitmapInfo createBadgedIconBitmap(@NonNull Drawable icon, UserHandle user, - boolean shrinkNonAdaptiveIcons, boolean isInstantApp, float[] scale) { - if (scale == null) { - scale = new float[1]; - } - icon = normalizeAndWrapToAdaptiveIcon(icon, shrinkNonAdaptiveIcons, null, scale); - Bitmap bitmap = createIconBitmap(icon, scale[0]); - if (ATLEAST_OREO && icon instanceof AdaptiveIconDrawable) { - mCanvas.setBitmap(bitmap); - getShadowGenerator().recreateIcon(Bitmap.createBitmap(bitmap), mCanvas); - mCanvas.setBitmap(null); - } - - if (isInstantApp) { - badgeWithDrawable(bitmap, mContext.getDrawable(R.drawable.ic_instant_app_badge)); - } - if (user != null) { - BitmapDrawable drawable = new FixedSizeBitmapDrawable(bitmap); - Drawable badged = mPm.getUserBadgedIcon(drawable, user); - if (badged instanceof BitmapDrawable) { - bitmap = ((BitmapDrawable) badged).getBitmap(); - } else { - bitmap = createIconBitmap(badged, 1f); - } - } - int color = extractColor(bitmap); - return icon instanceof BitmapInfo.Extender - ? ((BitmapInfo.Extender) icon).getExtendedInfo(bitmap, color, this) - : BitmapInfo.of(bitmap, color); - } - - public Bitmap createScaledBitmapWithoutShadow(Drawable icon, boolean shrinkNonAdaptiveIcons) { - RectF iconBounds = new RectF(); - float[] scale = new float[1]; - icon = normalizeAndWrapToAdaptiveIcon(icon, shrinkNonAdaptiveIcons, iconBounds, scale); - return createIconBitmap(icon, - Math.min(scale[0], ShadowGenerator.getScaleForBounds(iconBounds))); - } - - /** - * Switches badging to left/right - */ - public void setBadgeOnLeft(boolean badgeOnLeft) { - mBadgeOnLeft = badgeOnLeft; - } - - /** - * Sets the background color used for wrapped adaptive icon - */ - public void setWrapperBackgroundColor(int color) { - mWrapperBackgroundColor = (Color.alpha(color) < 255) ? DEFAULT_WRAPPER_BACKGROUND : color; - } - - /** - * Disables the dominant color extraction for all icons loaded. - */ - public void disableColorExtraction() { - mDisableColorExtractor = true; - } - - private Drawable normalizeAndWrapToAdaptiveIcon(@NonNull Drawable icon, - boolean shrinkNonAdaptiveIcons, RectF outIconBounds, float[] outScale) { - if (icon == null) { - return null; - } - float scale = 1f; - - if (shrinkNonAdaptiveIcons && ATLEAST_OREO) { - if (mWrapperIcon == null) { - mWrapperIcon = mContext.getDrawable(R.drawable.adaptive_icon_drawable_wrapper) - .mutate(); - } - AdaptiveIconDrawable dr = (AdaptiveIconDrawable) mWrapperIcon; - dr.setBounds(0, 0, 1, 1); - boolean[] outShape = new boolean[1]; - scale = getNormalizer().getScale(icon, outIconBounds, dr.getIconMask(), outShape); - if (!(icon instanceof AdaptiveIconDrawable) && !outShape[0]) { - FixedScaleDrawable fsd = ((FixedScaleDrawable) dr.getForeground()); - fsd.setDrawable(icon); - fsd.setScale(scale); - icon = dr; - scale = getNormalizer().getScale(icon, outIconBounds, null, null); - - ((ColorDrawable) dr.getBackground()).setColor(mWrapperBackgroundColor); - } - } else { - scale = getNormalizer().getScale(icon, outIconBounds, null, null); - } - - outScale[0] = scale; - return icon; - } - - /** - * Adds the {@param badge} on top of {@param target} using the badge dimensions. - */ - public void badgeWithDrawable(Bitmap target, Drawable badge) { - mCanvas.setBitmap(target); - badgeWithDrawable(mCanvas, badge); - mCanvas.setBitmap(null); - } - - /** - * Adds the {@param badge} on top of {@param target} using the badge dimensions. - */ - public void badgeWithDrawable(Canvas target, Drawable badge) { - int badgeSize = getBadgeSizeForIconSize(mIconBitmapSize); - if (mBadgeOnLeft) { - badge.setBounds(0, mIconBitmapSize - badgeSize, badgeSize, mIconBitmapSize); - } else { - badge.setBounds(mIconBitmapSize - badgeSize, mIconBitmapSize - badgeSize, - mIconBitmapSize, mIconBitmapSize); - } - badge.draw(target); - } - - private Bitmap createIconBitmap(Drawable icon, float scale) { - return createIconBitmap(icon, scale, mIconBitmapSize); - } - - /** - * @param icon drawable that should be flattened to a bitmap - * @param scale the scale to apply before drawing {@param icon} on the canvas - */ - public Bitmap createIconBitmap(@NonNull Drawable icon, float scale, int size) { - Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); - if (icon == null) { - return bitmap; - } - mCanvas.setBitmap(bitmap); - mOldBounds.set(icon.getBounds()); - - if (ATLEAST_OREO && icon instanceof AdaptiveIconDrawable) { - int offset = Math.max((int) Math.ceil(BLUR_FACTOR * size), - Math.round(size * (1 - scale) / 2 )); - icon.setBounds(offset, offset, size - offset, size - offset); - icon.draw(mCanvas); - } else { - if (icon instanceof BitmapDrawable) { - BitmapDrawable bitmapDrawable = (BitmapDrawable) icon; - Bitmap b = bitmapDrawable.getBitmap(); - if (bitmap != null && b.getDensity() == Bitmap.DENSITY_NONE) { - bitmapDrawable.setTargetDensity(mContext.getResources().getDisplayMetrics()); - } - } - int width = size; - int height = size; - - int intrinsicWidth = icon.getIntrinsicWidth(); - int intrinsicHeight = icon.getIntrinsicHeight(); - if (intrinsicWidth > 0 && intrinsicHeight > 0) { - // Scale the icon proportionally to the icon dimensions - final float ratio = (float) intrinsicWidth / intrinsicHeight; - if (intrinsicWidth > intrinsicHeight) { - height = (int) (width / ratio); - } else if (intrinsicHeight > intrinsicWidth) { - width = (int) (height * ratio); - } - } - final int left = (size - width) / 2; - final int top = (size - height) / 2; - icon.setBounds(left, top, left + width, top + height); - mCanvas.save(); - mCanvas.scale(scale, scale, size / 2, size / 2); - icon.draw(mCanvas); - mCanvas.restore(); - - } - icon.setBounds(mOldBounds); - mCanvas.setBitmap(null); - return bitmap; - } - - @Override - public void close() { - clear(); - } - - public BitmapInfo makeDefaultIcon(UserHandle user) { - return createBadgedIconBitmap(getFullResDefaultActivityIcon(mFillResIconDpi), - user, Build.VERSION.SDK_INT); - } - - public static Drawable getFullResDefaultActivityIcon(int iconDpi) { - return Resources.getSystem().getDrawableForDensity( - Build.VERSION.SDK_INT >= Build.VERSION_CODES.O - ? android.R.drawable.sym_def_app_icon : android.R.mipmap.sym_def_app_icon, - iconDpi); - } - - /** - * Badges the provided source with the badge info - */ - public BitmapInfo badgeBitmap(Bitmap source, BitmapInfo badgeInfo) { - Bitmap icon = BitmapRenderer.createHardwareBitmap(mIconBitmapSize, mIconBitmapSize, (c) -> { - getShadowGenerator().recreateIcon(source, c); - badgeWithDrawable(c, new FixedSizeBitmapDrawable(badgeInfo.icon)); - }); - return BitmapInfo.of(icon, badgeInfo.color); - } - - private int extractColor(Bitmap bitmap) { - return mDisableColorExtractor ? 0 : mColorExtractor.findDominantColorByHue(bitmap); - } - - /** - * Returns the correct badge size given an icon size - */ - public static int getBadgeSizeForIconSize(int iconSize) { - return (int) (ICON_BADGE_SCALE * iconSize); - } - - /** - * An extension of {@link BitmapDrawable} which returns the bitmap pixel size as intrinsic size. - * This allows the badging to be done based on the action bitmap size rather than - * the scaled bitmap size. - */ - private static class FixedSizeBitmapDrawable extends BitmapDrawable { - - public FixedSizeBitmapDrawable(Bitmap bitmap) { - super(null, bitmap); - } - - @Override - public int getIntrinsicHeight() { - return getBitmap().getWidth(); - } - - @Override - public int getIntrinsicWidth() { - return getBitmap().getWidth(); - } - } -} diff --git a/iconloaderlib/src/com/android/launcher3/icons/BitmapInfo.java b/iconloaderlib/src/com/android/launcher3/icons/BitmapInfo.java deleted file mode 100644 index d33f9b1112..0000000000 --- a/iconloaderlib/src/com/android/launcher3/icons/BitmapInfo.java +++ /dev/null @@ -1,72 +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.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 final Bitmap icon; - public final int 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(@NonNull Bitmap bitmap) { - return of(bitmap, 0); - } - - 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/BitmapRenderer.java b/iconloaderlib/src/com/android/launcher3/icons/BitmapRenderer.java deleted file mode 100644 index 5751ed95cb..0000000000 --- a/iconloaderlib/src/com/android/launcher3/icons/BitmapRenderer.java +++ /dev/null @@ -1,70 +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.icons; - -import android.annotation.TargetApi; -import android.graphics.Bitmap; -import android.graphics.Bitmap.Config; -import android.graphics.Canvas; -import android.graphics.Picture; -import android.graphics.Rect; -import android.graphics.RectF; -import android.os.Build; -import android.os.Build.VERSION_CODES; - -/** - * Interface representing a bitmap draw operation. - */ -public interface BitmapRenderer { - - boolean USE_HARDWARE_BITMAP = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P; - - static Bitmap createSoftwareBitmap(int width, int height, BitmapRenderer renderer) { - GraphicsUtils.noteNewBitmapCreated(); - Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - renderer.draw(new Canvas(result)); - return result; - } - - @TargetApi(Build.VERSION_CODES.P) - static Bitmap createHardwareBitmap(int width, int height, BitmapRenderer renderer) { - if (!USE_HARDWARE_BITMAP) { - return createSoftwareBitmap(width, height, renderer); - } - - GraphicsUtils.noteNewBitmapCreated(); - Picture picture = new Picture(); - renderer.draw(picture.beginRecording(width, height)); - picture.endRecording(); - return Bitmap.createBitmap(picture); - } - - /** - * Returns a bitmap from subset of the source bitmap. The new bitmap may be the - * same object as source, or a copy may have been made. - */ - static Bitmap createBitmap(Bitmap source, int x, int y, int width, int height) { - if (Build.VERSION.SDK_INT >= VERSION_CODES.O && source.getConfig() == Config.HARDWARE) { - return createHardwareBitmap(width, height, c -> c.drawBitmap(source, - new Rect(x, y, x + width, y + height), new RectF(0, 0, width, height), null)); - } else { - GraphicsUtils.noteNewBitmapCreated(); - return Bitmap.createBitmap(source, x, y, width, height); - } - } - - void draw(Canvas out); -} diff --git a/iconloaderlib/src/com/android/launcher3/icons/ColorExtractor.java b/iconloaderlib/src/com/android/launcher3/icons/ColorExtractor.java deleted file mode 100644 index 87bda825cc..0000000000 --- a/iconloaderlib/src/com/android/launcher3/icons/ColorExtractor.java +++ /dev/null @@ -1,127 +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.icons; - -import android.graphics.Bitmap; -import android.graphics.Color; -import android.util.SparseArray; -import java.util.Arrays; - -/** - * Utility class for extracting colors from a bitmap. - */ -public class ColorExtractor { - - private final int NUM_SAMPLES = 20; - private final float[] mTmpHsv = new float[3]; - private final float[] mTmpHueScoreHistogram = new float[360]; - private final int[] mTmpPixels = new int[NUM_SAMPLES]; - private final SparseArray mTmpRgbScores = new SparseArray<>(); - - /** - * This picks a dominant color, looking for high-saturation, high-value, repeated hues. - * @param bitmap The bitmap to scan - */ - public int findDominantColorByHue(Bitmap bitmap) { - return findDominantColorByHue(bitmap, NUM_SAMPLES); - } - - /** - * This picks a dominant color, looking for high-saturation, high-value, repeated hues. - * @param bitmap The bitmap to scan - */ - public int findDominantColorByHue(Bitmap bitmap, int samples) { - final int height = bitmap.getHeight(); - final int width = bitmap.getWidth(); - int sampleStride = (int) Math.sqrt((height * width) / samples); - if (sampleStride < 1) { - sampleStride = 1; - } - - // This is an out-param, for getting the hsv values for an rgb - float[] hsv = mTmpHsv; - Arrays.fill(hsv, 0); - - // First get the best hue, by creating a histogram over 360 hue buckets, - // where each pixel contributes a score weighted by saturation, value, and alpha. - float[] hueScoreHistogram = mTmpHueScoreHistogram; - Arrays.fill(hueScoreHistogram, 0); - float highScore = -1; - int bestHue = -1; - - int[] pixels = mTmpPixels; - Arrays.fill(pixels, 0); - int pixelCount = 0; - - for (int y = 0; y < height; y += sampleStride) { - for (int x = 0; x < width; x += sampleStride) { - int argb = bitmap.getPixel(x, y); - int alpha = 0xFF & (argb >> 24); - if (alpha < 0x80) { - // Drop mostly-transparent pixels. - continue; - } - // Remove the alpha channel. - int rgb = argb | 0xFF000000; - Color.colorToHSV(rgb, hsv); - // Bucket colors by the 360 integer hues. - int hue = (int) hsv[0]; - if (hue < 0 || hue >= hueScoreHistogram.length) { - // Defensively avoid array bounds violations. - continue; - } - if (pixelCount < samples) { - pixels[pixelCount++] = rgb; - } - float score = hsv[1] * hsv[2]; - hueScoreHistogram[hue] += score; - if (hueScoreHistogram[hue] > highScore) { - highScore = hueScoreHistogram[hue]; - bestHue = hue; - } - } - } - - SparseArray rgbScores = mTmpRgbScores; - rgbScores.clear(); - int bestColor = 0xff000000; - highScore = -1; - // Go back over the RGB colors that match the winning hue, - // creating a histogram of weighted s*v scores, for up to 100*100 [s,v] buckets. - // The highest-scoring RGB color wins. - for (int i = 0; i < pixelCount; i++) { - int rgb = pixels[i]; - Color.colorToHSV(rgb, hsv); - int hue = (int) hsv[0]; - if (hue == bestHue) { - float s = hsv[1]; - float v = hsv[2]; - int bucket = (int) (s * 100) + (int) (v * 10000); - // Score by cumulative saturation * value. - float score = s * v; - Float oldTotal = rgbScores.get(bucket); - float newTotal = oldTotal == null ? score : oldTotal + score; - rgbScores.put(bucket, newTotal); - if (newTotal > highScore) { - highScore = newTotal; - // All the colors in the winning bucket are very similar. Last in wins. - bestColor = rgb; - } - } - } - return bestColor; - } -} diff --git a/iconloaderlib/src/com/android/launcher3/icons/DotRenderer.java b/iconloaderlib/src/com/android/launcher3/icons/DotRenderer.java deleted file mode 100644 index 97a0fd3ffc..0000000000 --- a/iconloaderlib/src/com/android/launcher3/icons/DotRenderer.java +++ /dev/null @@ -1,143 +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.icons; - -import static android.graphics.Paint.ANTI_ALIAS_FLAG; -import static android.graphics.Paint.FILTER_BITMAP_FLAG; - -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.Path; -import android.graphics.PathMeasure; -import android.graphics.Rect; -import android.graphics.RectF; -import android.util.Log; -import android.view.ViewDebug; - -/** - * Used to draw a notification dot on top of an icon. - */ -public class DotRenderer { - - private static final String TAG = "DotRenderer"; - - // The dot size is defined as a percentage of the app icon size. - private static final float SIZE_PERCENTAGE = 0.228f; - - private final float mCircleRadius; - private final Paint mCirclePaint = new Paint(ANTI_ALIAS_FLAG | FILTER_BITMAP_FLAG); - - private final Bitmap mBackgroundWithShadow; - private final float mBitmapOffset; - - // Stores the center x and y position as a percentage (0 to 1) of the icon size - private final float[] mRightDotPosition; - private final float[] mLeftDotPosition; - - public DotRenderer(int iconSizePx, Path iconShapePath, int pathSize) { - int size = Math.round(SIZE_PERCENTAGE * iconSizePx); - ShadowGenerator.Builder builder = new ShadowGenerator.Builder(Color.TRANSPARENT); - builder.ambientShadowAlpha = 88; - mBackgroundWithShadow = builder.setupBlurForSize(size).createPill(size, size); - mCircleRadius = builder.radius; - - mBitmapOffset = -mBackgroundWithShadow.getHeight() * 0.5f; // Same as width. - - // Find the points on the path that are closest to the top left and right corners. - mLeftDotPosition = getPathPoint(iconShapePath, pathSize, -1); - mRightDotPosition = getPathPoint(iconShapePath, pathSize, 1); - } - - private static float[] getPathPoint(Path path, float size, float direction) { - float halfSize = size / 2; - // Small delta so that we don't get a zero size triangle - float delta = 1; - - float x = halfSize + direction * halfSize; - Path trianglePath = new Path(); - trianglePath.moveTo(halfSize, halfSize); - trianglePath.lineTo(x + delta * direction, 0); - trianglePath.lineTo(x, -delta); - trianglePath.close(); - - trianglePath.op(path, Path.Op.INTERSECT); - float[] pos = new float[2]; - new PathMeasure(trianglePath, false).getPosTan(0, pos, null); - - pos[0] = pos[0] / size; - pos[1] = pos[1] / size; - return pos; - } - - public float[] getLeftDotPosition() { - return mLeftDotPosition; - } - - public float[] getRightDotPosition() { - return mRightDotPosition; - } - - /** - * Draw a circle on top of the canvas according to the given params. - */ - public void draw(Canvas canvas, DrawParams params) { - if (params == null) { - Log.e(TAG, "Invalid null argument(s) passed in call to draw."); - return; - } - canvas.save(); - - Rect iconBounds = params.iconBounds; - float[] dotPosition = params.leftAlign ? mLeftDotPosition : mRightDotPosition; - float dotCenterX = iconBounds.left + iconBounds.width() * dotPosition[0]; - float dotCenterY = iconBounds.top + iconBounds.height() * dotPosition[1]; - - // Ensure dot fits entirely in canvas clip bounds. - Rect canvasBounds = canvas.getClipBounds(); - float offsetX = params.leftAlign - ? Math.max(0, canvasBounds.left - (dotCenterX + mBitmapOffset)) - : Math.min(0, canvasBounds.right - (dotCenterX - mBitmapOffset)); - float offsetY = Math.max(0, canvasBounds.top - (dotCenterY + mBitmapOffset)); - - // We draw the dot relative to its center. - canvas.translate(dotCenterX + offsetX, dotCenterY + offsetY); - canvas.scale(params.scale, params.scale); - - mCirclePaint.setColor(Color.BLACK); - canvas.drawBitmap(mBackgroundWithShadow, mBitmapOffset, mBitmapOffset, mCirclePaint); - mCirclePaint.setColor(params.color); - canvas.drawCircle(0, 0, mCircleRadius, mCirclePaint); - canvas.restore(); - } - - public static class DrawParams { - /** The color (possibly based on the icon) to use for the dot. */ - @ViewDebug.ExportedProperty(category = "notification dot", formatToHexString = true) - public int color; - /** The bounds of the icon that the dot is drawn on top of. */ - @ViewDebug.ExportedProperty(category = "notification dot") - public Rect iconBounds = new Rect(); - /** The progress of the animation, from 0 to 1. */ - @ViewDebug.ExportedProperty(category = "notification dot") - public float scale; - /** Whether the dot should align to the top left of the icon rather than the top right. */ - @ViewDebug.ExportedProperty(category = "notification dot") - public boolean leftAlign; - } -} diff --git a/iconloaderlib/src/com/android/launcher3/icons/FixedScaleDrawable.java b/iconloaderlib/src/com/android/launcher3/icons/FixedScaleDrawable.java deleted file mode 100644 index 516965ec2b..0000000000 --- a/iconloaderlib/src/com/android/launcher3/icons/FixedScaleDrawable.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.android.launcher3.icons; - -import android.content.res.Resources; -import android.content.res.Resources.Theme; -import android.graphics.Canvas; -import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.DrawableWrapper; -import android.util.AttributeSet; - -import org.xmlpull.v1.XmlPullParser; - -/** - * Extension of {@link DrawableWrapper} which scales the child drawables by a fixed amount. - */ -public class FixedScaleDrawable extends DrawableWrapper { - - // TODO b/33553066 use the constant defined in MaskableIconDrawable - private static final float LEGACY_ICON_SCALE = .7f * .6667f; - private float mScaleX, mScaleY; - - public FixedScaleDrawable() { - super(new ColorDrawable()); - mScaleX = LEGACY_ICON_SCALE; - mScaleY = LEGACY_ICON_SCALE; - } - - @Override - public void draw(Canvas canvas) { - int saveCount = canvas.save(); - canvas.scale(mScaleX, mScaleY, - getBounds().exactCenterX(), getBounds().exactCenterY()); - super.draw(canvas); - canvas.restoreToCount(saveCount); - } - - @Override - public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) { } - - @Override - public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) { } - - public void setScale(float scale) { - float h = getIntrinsicHeight(); - float w = getIntrinsicWidth(); - mScaleX = scale * LEGACY_ICON_SCALE; - mScaleY = scale * LEGACY_ICON_SCALE; - if (h > w && w > 0) { - mScaleX *= w / h; - } else if (w > h && h > 0) { - mScaleY *= h / w; - } - } -} diff --git a/iconloaderlib/src/com/android/launcher3/icons/GraphicsUtils.java b/iconloaderlib/src/com/android/launcher3/icons/GraphicsUtils.java deleted file mode 100644 index 22f1f2357e..0000000000 --- a/iconloaderlib/src/com/android/launcher3/icons/GraphicsUtils.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.launcher3.icons; - -import android.graphics.Bitmap; -import android.graphics.Rect; -import android.graphics.Region; -import android.graphics.RegionIterator; -import android.util.Log; - -import androidx.annotation.ColorInt; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; - -public class GraphicsUtils { - - private static final String TAG = "GraphicsUtils"; - - public static Runnable sOnNewBitmapRunnable = () -> { }; - - /** - * Set the alpha component of {@code color} to be {@code alpha}. Unlike the support lib version, - * it bounds the alpha in valid range instead of throwing an exception to allow for safer - * interpolation of color animations - */ - @ColorInt - public static int setColorAlphaBound(int color, int alpha) { - if (alpha < 0) { - alpha = 0; - } else if (alpha > 255) { - alpha = 255; - } - return (color & 0x00ffffff) | (alpha << 24); - } - - /** - * Compresses the bitmap to a byte array for serialization. - */ - public static byte[] flattenBitmap(Bitmap bitmap) { - // Try go guesstimate how much space the icon will take when serialized - // to avoid unnecessary allocations/copies during the write (4 bytes per pixel). - int size = bitmap.getWidth() * bitmap.getHeight() * 4; - ByteArrayOutputStream out = new ByteArrayOutputStream(size); - try { - bitmap.compress(Bitmap.CompressFormat.PNG, 100, out); - out.flush(); - out.close(); - return out.toByteArray(); - } catch (IOException e) { - Log.w(TAG, "Could not write bitmap"); - return null; - } - } - - public static int getArea(Region r) { - RegionIterator itr = new RegionIterator(r); - int area = 0; - Rect tempRect = new Rect(); - while (itr.next(tempRect)) { - area += tempRect.width() * tempRect.height(); - } - return area; - } - - /** - * Utility method to track new bitmap creation - */ - public static void noteNewBitmapCreated() { - sOnNewBitmapRunnable.run(); - } -} diff --git a/iconloaderlib/src/com/android/launcher3/icons/IconNormalizer.java b/iconloaderlib/src/com/android/launcher3/icons/IconNormalizer.java deleted file mode 100644 index de39e79fec..0000000000 --- a/iconloaderlib/src/com/android/launcher3/icons/IconNormalizer.java +++ /dev/null @@ -1,411 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS 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.res.Resources; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Matrix; -import android.graphics.Paint; -import android.graphics.Path; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffXfermode; -import android.graphics.Rect; -import android.graphics.RectF; -import android.graphics.Region; -import android.graphics.drawable.AdaptiveIconDrawable; -import android.graphics.drawable.Drawable; -import android.os.Build; -import android.util.Log; - -import java.nio.ByteBuffer; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -public class IconNormalizer { - - private static final String TAG = "IconNormalizer"; - private static final boolean DEBUG = false; - // Ratio of icon visible area to full icon size for a square shaped icon - private static final float MAX_SQUARE_AREA_FACTOR = 375.0f / 576; - // Ratio of icon visible area to full icon size for a circular shaped icon - private static final float MAX_CIRCLE_AREA_FACTOR = 380.0f / 576; - - private static final float CIRCLE_AREA_BY_RECT = (float) Math.PI / 4; - - // Slope used to calculate icon visible area to full icon size for any generic shaped icon. - private static final float LINEAR_SCALE_SLOPE = - (MAX_CIRCLE_AREA_FACTOR - MAX_SQUARE_AREA_FACTOR) / (1 - CIRCLE_AREA_BY_RECT); - - private static final int MIN_VISIBLE_ALPHA = 40; - - // Shape detection related constants - private static final float BOUND_RATIO_MARGIN = .05f; - private static final float PIXEL_DIFF_PERCENTAGE_THRESHOLD = 0.005f; - private static final float SCALE_NOT_INITIALIZED = 0; - - // Ratio of the diameter of an normalized circular icon to the actual icon size. - public static final float ICON_VISIBLE_AREA_FACTOR = 0.92f; - - private final int mMaxSize; - private final Bitmap mBitmap; - private final Canvas mCanvas; - private final Paint mPaintMaskShape; - private final Paint mPaintMaskShapeOutline; - private final byte[] mPixels; - - private final RectF mAdaptiveIconBounds; - private float mAdaptiveIconScale; - - private boolean mEnableShapeDetection; - - // for each y, stores the position of the leftmost x and the rightmost x - private final float[] mLeftBorder; - private final float[] mRightBorder; - private final Rect mBounds; - private final Path mShapePath; - private final Matrix mMatrix; - - /** package private **/ - IconNormalizer(Context context, int iconBitmapSize, boolean shapeDetection) { - // Use twice the icon size as maximum size to avoid scaling down twice. - mMaxSize = iconBitmapSize * 2; - mBitmap = Bitmap.createBitmap(mMaxSize, mMaxSize, Bitmap.Config.ALPHA_8); - mCanvas = new Canvas(mBitmap); - mPixels = new byte[mMaxSize * mMaxSize]; - mLeftBorder = new float[mMaxSize]; - mRightBorder = new float[mMaxSize]; - mBounds = new Rect(); - mAdaptiveIconBounds = new RectF(); - - mPaintMaskShape = new Paint(); - mPaintMaskShape.setColor(Color.RED); - mPaintMaskShape.setStyle(Paint.Style.FILL); - mPaintMaskShape.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.XOR)); - - mPaintMaskShapeOutline = new Paint(); - mPaintMaskShapeOutline.setStrokeWidth( - 2 * context.getResources().getDisplayMetrics().density); - mPaintMaskShapeOutline.setStyle(Paint.Style.STROKE); - mPaintMaskShapeOutline.setColor(Color.BLACK); - mPaintMaskShapeOutline.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); - - mShapePath = new Path(); - mMatrix = new Matrix(); - mAdaptiveIconScale = SCALE_NOT_INITIALIZED; - mEnableShapeDetection = shapeDetection; - } - - private static float getScale(float hullArea, float boundingArea, float fullArea) { - float hullByRect = hullArea / boundingArea; - float scaleRequired; - if (hullByRect < CIRCLE_AREA_BY_RECT) { - scaleRequired = MAX_CIRCLE_AREA_FACTOR; - } else { - scaleRequired = MAX_SQUARE_AREA_FACTOR + LINEAR_SCALE_SLOPE * (1 - hullByRect); - } - - float areaScale = hullArea / fullArea; - // Use sqrt of the final ratio as the images is scaled across both width and height. - return areaScale > scaleRequired ? (float) Math.sqrt(scaleRequired / areaScale) : 1; - } - - /** - * @param d Should be AdaptiveIconDrawable - * @param size Canvas size to use - */ - @TargetApi(Build.VERSION_CODES.O) - public static float normalizeAdaptiveIcon(Drawable d, int size, @Nullable RectF outBounds) { - Rect tmpBounds = new Rect(d.getBounds()); - d.setBounds(0, 0, size, size); - - Path path = ((AdaptiveIconDrawable) d).getIconMask(); - Region region = new Region(); - region.setPath(path, new Region(0, 0, size, size)); - - Rect hullBounds = region.getBounds(); - int hullArea = GraphicsUtils.getArea(region); - - if (outBounds != null) { - float sizeF = size; - outBounds.set( - hullBounds.left / sizeF, - hullBounds.top / sizeF, - 1 - (hullBounds.right / sizeF), - 1 - (hullBounds.bottom / sizeF)); - } - d.setBounds(tmpBounds); - return getScale(hullArea, hullArea, size * size); - } - - /** - * Returns if the shape of the icon is same as the path. - * For this method to work, the shape path bounds should be in [0,1]x[0,1] bounds. - */ - private boolean isShape(Path maskPath) { - // Condition1: - // If width and height of the path not close to a square, then the icon shape is - // not same as the mask shape. - float iconRatio = ((float) mBounds.width()) / mBounds.height(); - if (Math.abs(iconRatio - 1) > BOUND_RATIO_MARGIN) { - if (DEBUG) { - Log.d(TAG, "Not same as mask shape because width != height. " + iconRatio); - } - return false; - } - - // Condition 2: - // Actual icon (white) and the fitted shape (e.g., circle)(red) XOR operation - // should generate transparent image, if the actual icon is equivalent to the shape. - - // Fit the shape within the icon's bounding box - mMatrix.reset(); - mMatrix.setScale(mBounds.width(), mBounds.height()); - mMatrix.postTranslate(mBounds.left, mBounds.top); - maskPath.transform(mMatrix, mShapePath); - - // XOR operation - mCanvas.drawPath(mShapePath, mPaintMaskShape); - - // DST_OUT operation around the mask path outline - mCanvas.drawPath(mShapePath, mPaintMaskShapeOutline); - - // Check if the result is almost transparent - return isTransparentBitmap(); - } - - /** - * Used to determine if certain the bitmap is transparent. - */ - private boolean isTransparentBitmap() { - ByteBuffer buffer = ByteBuffer.wrap(mPixels); - buffer.rewind(); - mBitmap.copyPixelsToBuffer(buffer); - - int y = mBounds.top; - // buffer position - int index = y * mMaxSize; - // buffer shift after every row, width of buffer = mMaxSize - int rowSizeDiff = mMaxSize - mBounds.right; - - int sum = 0; - for (; y < mBounds.bottom; y++) { - index += mBounds.left; - for (int x = mBounds.left; x < mBounds.right; x++) { - if ((mPixels[index] & 0xFF) > MIN_VISIBLE_ALPHA) { - sum++; - } - index++; - } - index += rowSizeDiff; - } - - float percentageDiffPixels = ((float) sum) / (mBounds.width() * mBounds.height()); - return percentageDiffPixels < PIXEL_DIFF_PERCENTAGE_THRESHOLD; - } - - /** - * Returns the amount by which the {@param d} should be scaled (in both dimensions) so that it - * matches the design guidelines for a launcher icon. - * - * We first calculate the convex hull of the visible portion of the icon. - * This hull then compared with the bounding rectangle of the hull to find how closely it - * resembles a circle and a square, by comparing the ratio of the areas. Note that this is not an - * ideal solution but it gives satisfactory result without affecting the performance. - * - * This closeness is used to determine the ratio of hull area to the full icon size. - * Refer {@link #MAX_CIRCLE_AREA_FACTOR} and {@link #MAX_SQUARE_AREA_FACTOR} - * - * @param outBounds optional rect to receive the fraction distance from each edge. - */ - public synchronized float getScale(@NonNull Drawable d, @Nullable RectF outBounds, - @Nullable Path path, @Nullable boolean[] outMaskShape) { - if (BaseIconFactory.ATLEAST_OREO && d instanceof AdaptiveIconDrawable) { - if (mAdaptiveIconScale == SCALE_NOT_INITIALIZED) { - mAdaptiveIconScale = normalizeAdaptiveIcon(d, mMaxSize, mAdaptiveIconBounds); - } - if (outBounds != null) { - outBounds.set(mAdaptiveIconBounds); - } - return mAdaptiveIconScale; - } - int width = d.getIntrinsicWidth(); - int height = d.getIntrinsicHeight(); - if (width <= 0 || height <= 0) { - width = width <= 0 || width > mMaxSize ? mMaxSize : width; - height = height <= 0 || height > mMaxSize ? mMaxSize : height; - } else if (width > mMaxSize || height > mMaxSize) { - int max = Math.max(width, height); - width = mMaxSize * width / max; - height = mMaxSize * height / max; - } - - mBitmap.eraseColor(Color.TRANSPARENT); - d.setBounds(0, 0, width, height); - d.draw(mCanvas); - - ByteBuffer buffer = ByteBuffer.wrap(mPixels); - buffer.rewind(); - mBitmap.copyPixelsToBuffer(buffer); - - // Overall bounds of the visible icon. - int topY = -1; - int bottomY = -1; - int leftX = mMaxSize + 1; - int rightX = -1; - - // Create border by going through all pixels one row at a time and for each row find - // the first and the last non-transparent pixel. Set those values to mLeftBorder and - // mRightBorder and use -1 if there are no visible pixel in the row. - - // buffer position - int index = 0; - // buffer shift after every row, width of buffer = mMaxSize - int rowSizeDiff = mMaxSize - width; - // first and last position for any row. - int firstX, lastX; - - for (int y = 0; y < height; y++) { - firstX = lastX = -1; - for (int x = 0; x < width; x++) { - if ((mPixels[index] & 0xFF) > MIN_VISIBLE_ALPHA) { - if (firstX == -1) { - firstX = x; - } - lastX = x; - } - index++; - } - index += rowSizeDiff; - - mLeftBorder[y] = firstX; - mRightBorder[y] = lastX; - - // If there is at least one visible pixel, update the overall bounds. - if (firstX != -1) { - bottomY = y; - if (topY == -1) { - topY = y; - } - - leftX = Math.min(leftX, firstX); - rightX = Math.max(rightX, lastX); - } - } - - if (topY == -1 || rightX == -1) { - // No valid pixels found. Do not scale. - return 1; - } - - convertToConvexArray(mLeftBorder, 1, topY, bottomY); - convertToConvexArray(mRightBorder, -1, topY, bottomY); - - // Area of the convex hull - float area = 0; - for (int y = 0; y < height; y++) { - if (mLeftBorder[y] <= -1) { - continue; - } - area += mRightBorder[y] - mLeftBorder[y] + 1; - } - - mBounds.left = leftX; - mBounds.right = rightX; - - mBounds.top = topY; - mBounds.bottom = bottomY; - - if (outBounds != null) { - outBounds.set(((float) mBounds.left) / width, ((float) mBounds.top) / height, - 1 - ((float) mBounds.right) / width, - 1 - ((float) mBounds.bottom) / height); - } - if (outMaskShape != null && mEnableShapeDetection && outMaskShape.length > 0) { - outMaskShape[0] = isShape(path); - } - // Area of the rectangle required to fit the convex hull - float rectArea = (bottomY + 1 - topY) * (rightX + 1 - leftX); - return getScale(area, rectArea, width * height); - } - - /** - * Modifies {@param xCoordinates} to represent a convex border. Fills in all missing values - * (except on either ends) with appropriate values. - * @param xCoordinates map of x coordinate per y. - * @param direction 1 for left border and -1 for right border. - * @param topY the first Y position (inclusive) with a valid value. - * @param bottomY the last Y position (inclusive) with a valid value. - */ - private static void convertToConvexArray( - float[] xCoordinates, int direction, int topY, int bottomY) { - int total = xCoordinates.length; - // The tangent at each pixel. - float[] angles = new float[total - 1]; - - int first = topY; // First valid y coordinate - int last = -1; // Last valid y coordinate which didn't have a missing value - - float lastAngle = Float.MAX_VALUE; - - for (int i = topY + 1; i <= bottomY; i++) { - if (xCoordinates[i] <= -1) { - continue; - } - int start; - - if (lastAngle == Float.MAX_VALUE) { - start = first; - } else { - float currentAngle = (xCoordinates[i] - xCoordinates[last]) / (i - last); - start = last; - // If this position creates a concave angle, keep moving up until we find a - // position which creates a convex angle. - if ((currentAngle - lastAngle) * direction < 0) { - while (start > first) { - start --; - currentAngle = (xCoordinates[i] - xCoordinates[start]) / (i - start); - if ((currentAngle - angles[start]) * direction >= 0) { - break; - } - } - } - } - - // Reset from last check - lastAngle = (xCoordinates[i] - xCoordinates[start]) / (i - start); - // Update all the points from start. - for (int j = start; j < i; j++) { - angles[j] = lastAngle; - xCoordinates[j] = xCoordinates[start] + lastAngle * (j - start); - } - last = i; - } - } - - /** - * @return The diameter of the normalized circle that fits inside of the square (size x size). - */ - public static int getNormalizedCircleSize(int size) { - float area = size * size * MAX_CIRCLE_AREA_FACTOR; - return (int) Math.round(Math.sqrt((4 * area) / Math.PI)); - } -} diff --git a/iconloaderlib/src/com/android/launcher3/icons/ShadowGenerator.java b/iconloaderlib/src/com/android/launcher3/icons/ShadowGenerator.java deleted file mode 100644 index 770272728d..0000000000 --- a/iconloaderlib/src/com/android/launcher3/icons/ShadowGenerator.java +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher3.icons; - -import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound; - -import android.graphics.Bitmap; -import android.graphics.BlurMaskFilter; -import android.graphics.BlurMaskFilter.Blur; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffXfermode; -import android.graphics.RectF; - -/** - * Utility class to add shadows to bitmaps. - */ -public class ShadowGenerator { - public static final float BLUR_FACTOR = 0.5f/48; - - // Percent of actual icon size - public static final float KEY_SHADOW_DISTANCE = 1f/48; - private static final int KEY_SHADOW_ALPHA = 61; - // Percent of actual icon size - private static final float HALF_DISTANCE = 0.5f; - private static final int AMBIENT_SHADOW_ALPHA = 30; - - private final int mIconSize; - - private final Paint mBlurPaint; - private final Paint mDrawPaint; - private final BlurMaskFilter mDefaultBlurMaskFilter; - - public ShadowGenerator(int iconSize) { - mIconSize = iconSize; - mBlurPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); - mDrawPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); - mDefaultBlurMaskFilter = new BlurMaskFilter(mIconSize * BLUR_FACTOR, Blur.NORMAL); - } - - public synchronized void recreateIcon(Bitmap icon, Canvas out) { - recreateIcon(icon, mDefaultBlurMaskFilter, AMBIENT_SHADOW_ALPHA, KEY_SHADOW_ALPHA, out); - } - - public synchronized void recreateIcon(Bitmap icon, BlurMaskFilter blurMaskFilter, - int ambientAlpha, int keyAlpha, Canvas out) { - int[] offset = new int[2]; - mBlurPaint.setMaskFilter(blurMaskFilter); - Bitmap shadow = icon.extractAlpha(mBlurPaint, offset); - - // Draw ambient shadow - mDrawPaint.setAlpha(ambientAlpha); - out.drawBitmap(shadow, offset[0], offset[1], mDrawPaint); - - // Draw key shadow - mDrawPaint.setAlpha(keyAlpha); - out.drawBitmap(shadow, offset[0], offset[1] + KEY_SHADOW_DISTANCE * mIconSize, mDrawPaint); - - // Draw the icon - mDrawPaint.setAlpha(255); - out.drawBitmap(icon, 0, 0, mDrawPaint); - } - - /** - * Returns the minimum amount by which an icon with {@param bounds} should be scaled - * so that the shadows do not get clipped. - */ - public static float getScaleForBounds(RectF bounds) { - float scale = 1; - - // For top, left & right, we need same space. - float minSide = Math.min(Math.min(bounds.left, bounds.right), bounds.top); - if (minSide < BLUR_FACTOR) { - scale = (HALF_DISTANCE - BLUR_FACTOR) / (HALF_DISTANCE - minSide); - } - - float bottomSpace = BLUR_FACTOR + KEY_SHADOW_DISTANCE; - if (bounds.bottom < bottomSpace) { - scale = Math.min(scale, (HALF_DISTANCE - bottomSpace) / (HALF_DISTANCE - bounds.bottom)); - } - return scale; - } - - public static class Builder { - - public final RectF bounds = new RectF(); - public final int color; - - public int ambientShadowAlpha = AMBIENT_SHADOW_ALPHA; - - public float shadowBlur; - - public float keyShadowDistance; - public int keyShadowAlpha = KEY_SHADOW_ALPHA; - public float radius; - - public Builder(int color) { - this.color = color; - } - - public Builder setupBlurForSize(int height) { - shadowBlur = height * 1f / 24; - keyShadowDistance = height * 1f / 16; - return this; - } - - public Bitmap createPill(int width, int height) { - return createPill(width, height, height / 2f); - } - - public Bitmap createPill(int width, int height, float r) { - radius = r; - - int centerX = Math.round(width / 2f + shadowBlur); - int centerY = Math.round(radius + shadowBlur + keyShadowDistance); - int center = Math.max(centerX, centerY); - bounds.set(0, 0, width, height); - bounds.offsetTo(center - width / 2f, center - height / 2f); - - int size = center * 2; - return BitmapRenderer.createHardwareBitmap(size, size, this::drawShadow); - } - - public void drawShadow(Canvas c) { - Paint p = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); - p.setColor(color); - - // Key shadow - p.setShadowLayer(shadowBlur, 0, keyShadowDistance, - setColorAlphaBound(Color.BLACK, keyShadowAlpha)); - c.drawRoundRect(bounds, radius, radius, p); - - // Ambient shadow - p.setShadowLayer(shadowBlur, 0, 0, - setColorAlphaBound(Color.BLACK, ambientShadowAlpha)); - c.drawRoundRect(bounds, radius, radius, p); - - if (Color.alpha(color) < 255) { - // Clear any content inside the pill-rect for translucent fill. - p.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); - p.clearShadowLayer(); - p.setColor(Color.BLACK); - c.drawRoundRect(bounds, radius, radius, p); - - p.setXfermode(null); - p.setColor(color); - c.drawRoundRect(bounds, radius, radius, p); - } - } - } -} diff --git a/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java b/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java deleted file mode 100644 index 4c634cbc26..0000000000 --- a/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java +++ /dev/null @@ -1,582 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.launcher3.icons.cache; - -import static com.android.launcher3.icons.BaseIconFactory.getFullResDefaultActivityIcon; -import static com.android.launcher3.icons.BitmapInfo.LOW_RES_ICON; -import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound; - -import android.content.ComponentName; -import android.content.ContentValues; -import android.content.Context; -import android.content.pm.ActivityInfo; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.content.res.Resources; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteException; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.drawable.Drawable; -import android.os.Build; -import android.os.Handler; -import android.os.LocaleList; -import android.os.Looper; -import android.os.Process; -import android.os.UserHandle; -import android.text.TextUtils; -import android.util.Log; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; - -import com.android.launcher3.icons.BaseIconFactory; -import com.android.launcher3.icons.BitmapInfo; -import com.android.launcher3.icons.BitmapRenderer; -import com.android.launcher3.icons.GraphicsUtils; -import com.android.launcher3.util.ComponentKey; -import com.android.launcher3.util.SQLiteCacheHelper; - -import java.util.AbstractMap; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.function.Supplier; - -public abstract class BaseIconCache { - - private static final String TAG = "BaseIconCache"; - private static final boolean DEBUG = false; - - private static final int INITIAL_ICON_CACHE_CAPACITY = 50; - - // Empty class name is used for storing package default entry. - public static final String EMPTY_CLASS_NAME = "."; - - public static class CacheEntry { - - @NonNull - public BitmapInfo bitmap = BitmapInfo.LOW_RES_INFO; - public CharSequence title = ""; - public CharSequence contentDescription = ""; - } - - private final HashMap mDefaultIcons = new HashMap<>(); - - protected final Context mContext; - protected final PackageManager mPackageManager; - - private final Map mCache; - protected final Handler mWorkerHandler; - - protected int mIconDpi; - protected IconDB mIconDb; - protected LocaleList mLocaleList = LocaleList.getEmptyLocaleList(); - protected String mSystemState = ""; - - private final String mDbFileName; - private final BitmapFactory.Options mDecodeOptions; - private final Looper mBgLooper; - - public BaseIconCache(Context context, String dbFileName, Looper bgLooper, - int iconDpi, int iconPixelSize, boolean inMemoryCache) { - mContext = context; - mDbFileName = dbFileName; - mPackageManager = context.getPackageManager(); - mBgLooper = bgLooper; - mWorkerHandler = new Handler(mBgLooper); - - if (inMemoryCache) { - mCache = new HashMap<>(INITIAL_ICON_CACHE_CAPACITY); - } else { - // Use a dummy cache - mCache = new AbstractMap() { - @Override - public Set> entrySet() { - return Collections.emptySet(); - } - - @Override - public CacheEntry put(ComponentKey key, CacheEntry value) { - return value; - } - }; - } - - if (BitmapRenderer.USE_HARDWARE_BITMAP && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - mDecodeOptions = new BitmapFactory.Options(); - mDecodeOptions.inPreferredConfig = Bitmap.Config.HARDWARE; - } else { - mDecodeOptions = null; - } - - updateSystemState(); - mIconDpi = iconDpi; - mIconDb = new IconDB(context, dbFileName, iconPixelSize); - } - - /** - * Returns the persistable serial number for {@param user}. Subclass should implement proper - * caching strategy to avoid making binder call every time. - */ - protected abstract long getSerialNumberForUser(UserHandle user); - - /** - * Return true if the given app is an instant app and should be badged appropriately. - */ - protected abstract boolean isInstantApp(ApplicationInfo info); - - /** - * Opens and returns an icon factory. The factory is recycled by the caller. - */ - protected abstract BaseIconFactory getIconFactory(); - - public void updateIconParams(int iconDpi, int iconPixelSize) { - mWorkerHandler.post(() -> updateIconParamsBg(iconDpi, iconPixelSize)); - } - - private synchronized void updateIconParamsBg(int iconDpi, int iconPixelSize) { - mIconDpi = iconDpi; - mDefaultIcons.clear(); - mIconDb.clear(); - mIconDb.close(); - mIconDb = new IconDB(mContext, mDbFileName, iconPixelSize); - mCache.clear(); - } - - private Drawable getFullResIcon(Resources resources, int iconId) { - if (resources != null && iconId != 0) { - try { - return resources.getDrawableForDensity(iconId, mIconDpi); - } catch (Resources.NotFoundException e) { } - } - return getFullResDefaultActivityIcon(mIconDpi); - } - - public Drawable getFullResIcon(String packageName, int iconId) { - try { - return getFullResIcon(mPackageManager.getResourcesForApplication(packageName), iconId); - } catch (PackageManager.NameNotFoundException e) { } - return getFullResDefaultActivityIcon(mIconDpi); - } - - public Drawable getFullResIcon(ActivityInfo info) { - try { - return getFullResIcon(mPackageManager.getResourcesForApplication(info.applicationInfo), - info.getIconResource()); - } catch (PackageManager.NameNotFoundException e) { } - return getFullResDefaultActivityIcon(mIconDpi); - } - - private BitmapInfo makeDefaultIcon(UserHandle user) { - try (BaseIconFactory li = getIconFactory()) { - return li.makeDefaultIcon(user); - } - } - - /** - * Remove any records for the supplied ComponentName. - */ - public synchronized void remove(ComponentName componentName, UserHandle user) { - mCache.remove(new ComponentKey(componentName, user)); - } - - /** - * Remove any records for the supplied package name from memory. - */ - private void removeFromMemCacheLocked(String packageName, UserHandle user) { - HashSet forDeletion = new HashSet<>(); - for (ComponentKey key: mCache.keySet()) { - if (key.componentName.getPackageName().equals(packageName) - && key.user.equals(user)) { - forDeletion.add(key); - } - } - for (ComponentKey condemned: forDeletion) { - mCache.remove(condemned); - } - } - - /** - * Removes the entries related to the given package in memory and persistent DB. - */ - public synchronized void removeIconsForPkg(String packageName, UserHandle user) { - removeFromMemCacheLocked(packageName, user); - long userSerial = getSerialNumberForUser(user); - mIconDb.delete( - IconDB.COLUMN_COMPONENT + " LIKE ? AND " + IconDB.COLUMN_USER + " = ?", - new String[]{packageName + "/%", Long.toString(userSerial)}); - } - - public IconCacheUpdateHandler getUpdateHandler() { - updateSystemState(); - return new IconCacheUpdateHandler(this); - } - - /** - * Refreshes the system state definition used to check the validity of the cache. It - * incorporates all the properties that can affect the cache like the list of enabled locale - * and system-version. - */ - private void updateSystemState() { - mLocaleList = mContext.getResources().getConfiguration().getLocales(); - mSystemState = mLocaleList.toLanguageTags() + "," + Build.VERSION.SDK_INT; - } - - protected String getIconSystemState(String packageName) { - return mSystemState; - } - - /** - * Adds an entry into the DB and the in-memory cache. - * @param replaceExisting if true, it will recreate the bitmap even if it already exists in - * the memory. This is useful then the previous bitmap was created using - * old data. - */ - @VisibleForTesting - public synchronized void addIconToDBAndMemCache(T object, CachingLogic cachingLogic, - PackageInfo info, long userSerial, boolean replaceExisting) { - UserHandle user = cachingLogic.getUser(object); - ComponentName componentName = cachingLogic.getComponent(object); - - final ComponentKey key = new ComponentKey(componentName, user); - CacheEntry entry = null; - if (!replaceExisting) { - entry = mCache.get(key); - // We can't reuse the entry if the high-res icon is not present. - if (entry == null || entry.bitmap.isNullOrLowRes()) { - entry = null; - } - } - if (entry == null) { - entry = new CacheEntry(); - 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.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.bitmap, entry.title.toString(), - componentName.getPackageName(), cachingLogic.getKeywords(object, mLocaleList)); - addIconToDB(values, componentName, info, userSerial, - cachingLogic.getLastUpdatedTime(object, info)); - } - - /** - * Updates {@param values} to contain versioning information and adds it to the DB. - * @param values {@link ContentValues} containing icon & title - */ - private void addIconToDB(ContentValues values, ComponentName key, - PackageInfo info, long userSerial, long lastUpdateTime) { - values.put(IconDB.COLUMN_COMPONENT, key.flattenToString()); - values.put(IconDB.COLUMN_USER, userSerial); - values.put(IconDB.COLUMN_LAST_UPDATED, lastUpdateTime); - values.put(IconDB.COLUMN_VERSION, info.versionCode); - mIconDb.insertOrReplace(values); - } - - public synchronized BitmapInfo getDefaultIcon(UserHandle user) { - if (!mDefaultIcons.containsKey(user)) { - mDefaultIcons.put(user, makeDefaultIcon(user)); - } - return mDefaultIcons.get(user); - } - - public boolean isDefaultIcon(BitmapInfo icon, UserHandle user) { - return getDefaultIcon(user).icon == icon.icon; - } - - /** - * Retrieves the entry from the cache. If the entry is not present, it creates a new entry. - * This method is not thread safe, it must be called from a synchronized method. - */ - protected CacheEntry cacheLocked( - @NonNull ComponentName componentName, @NonNull UserHandle user, - @NonNull Supplier infoProvider, @NonNull CachingLogic cachingLogic, - boolean usePackageIcon, boolean useLowResIcon) { - assertWorkerThread(); - ComponentKey cacheKey = new ComponentKey(componentName, user); - CacheEntry entry = mCache.get(cacheKey); - if (entry == null || (entry.bitmap.isLowRes() && !useLowResIcon)) { - entry = new CacheEntry(); - if (cachingLogic.addToMemCache()) { - mCache.put(cacheKey, entry); - } - - // Check the DB first. - T object = null; - boolean providerFetchedOnce = false; - - if (!getEntryFromDB(cacheKey, entry, useLowResIcon)) { - object = infoProvider.get(); - providerFetchedOnce = true; - - if (object != null) { - entry.bitmap = cachingLogic.loadIcon(mContext, object); - } else { - if (usePackageIcon) { - CacheEntry packageEntry = getEntryForPackageLocked( - componentName.getPackageName(), user, false); - if (packageEntry != null) { - if (DEBUG) Log.d(TAG, "using package default icon for " + - componentName.toShortString()); - entry.bitmap = packageEntry.bitmap; - entry.title = packageEntry.title; - entry.contentDescription = packageEntry.contentDescription; - } - } - if (entry.bitmap == null) { - if (DEBUG) Log.d(TAG, "using default icon for " + - componentName.toShortString()); - entry.bitmap = getDefaultIcon(user); - } - } - } - - if (TextUtils.isEmpty(entry.title)) { - if (object == null && !providerFetchedOnce) { - object = infoProvider.get(); - providerFetchedOnce = true; - } - if (object != null) { - entry.title = cachingLogic.getLabel(object); - entry.contentDescription = mPackageManager.getUserBadgedLabel( - cachingLogic.getDescription(object, entry.title), user); - } - } - } - return entry; - } - - public synchronized void clear() { - assertWorkerThread(); - mIconDb.clear(); - } - - /** - * Adds a default package entry in the cache. This entry is not persisted and will be removed - * when the cache is flushed. - */ - protected synchronized void cachePackageInstallInfo(String packageName, UserHandle user, - Bitmap icon, CharSequence title) { - removeFromMemCacheLocked(packageName, user); - - ComponentKey cacheKey = getPackageKey(packageName, user); - CacheEntry entry = mCache.get(cacheKey); - - // For icon caching, do not go through DB. Just update the in-memory entry. - if (entry == null) { - entry = new CacheEntry(); - } - if (!TextUtils.isEmpty(title)) { - entry.title = title; - } - if (icon != null) { - BaseIconFactory li = getIconFactory(); - entry.bitmap = li.createIconBitmap(icon); - li.close(); - } - if (!TextUtils.isEmpty(title) && entry.bitmap.icon != null) { - mCache.put(cacheKey, entry); - } - } - - private static ComponentKey getPackageKey(String packageName, UserHandle user) { - ComponentName cn = new ComponentName(packageName, packageName + EMPTY_CLASS_NAME); - return new ComponentKey(cn, user); - } - - /** - * Gets an entry for the package, which can be used as a fallback entry for various components. - * This method is not thread safe, it must be called from a synchronized method. - */ - protected CacheEntry getEntryForPackageLocked(String packageName, UserHandle user, - boolean useLowResIcon) { - assertWorkerThread(); - ComponentKey cacheKey = getPackageKey(packageName, user); - CacheEntry entry = mCache.get(cacheKey); - - if (entry == null || (entry.bitmap.isLowRes() && !useLowResIcon)) { - entry = new CacheEntry(); - boolean entryUpdated = true; - - // Check the DB first. - if (!getEntryFromDB(cacheKey, entry, useLowResIcon)) { - try { - int flags = Process.myUserHandle().equals(user) ? 0 : - PackageManager.GET_UNINSTALLED_PACKAGES; - PackageInfo info = mPackageManager.getPackageInfo(packageName, flags); - ApplicationInfo appInfo = info.applicationInfo; - if (appInfo == null) { - throw new NameNotFoundException("ApplicationInfo is null"); - } - - BaseIconFactory li = getIconFactory(); - // Load the full res icon for the application, but if useLowResIcon is set, then - // only keep the low resolution icon instead of the larger full-sized icon - BitmapInfo iconInfo = li.createBadgedIconBitmap( - appInfo.loadIcon(mPackageManager), user, appInfo.targetSdkVersion, - isInstantApp(appInfo)); - li.close(); - - entry.title = appInfo.loadLabel(mPackageManager); - entry.contentDescription = mPackageManager.getUserBadgedLabel(entry.title, user); - 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. - ContentValues values = newContentValues( - iconInfo, entry.title.toString(), packageName, null); - addIconToDB(values, cacheKey.componentName, info, getSerialNumberForUser(user), - info.lastUpdateTime); - - } catch (NameNotFoundException e) { - if (DEBUG) Log.d(TAG, "Application not installed " + packageName); - entryUpdated = false; - } - } - - // Only add a filled-out entry to the cache - if (entryUpdated) { - mCache.put(cacheKey, entry); - } - } - return entry; - } - - protected boolean getEntryFromDB(ComponentKey cacheKey, CacheEntry entry, boolean lowRes) { - Cursor c = null; - try { - c = mIconDb.query( - lowRes ? IconDB.COLUMNS_LOW_RES : IconDB.COLUMNS_HIGH_RES, - IconDB.COLUMN_COMPONENT + " = ? AND " + IconDB.COLUMN_USER + " = ?", - new String[]{ - cacheKey.componentName.flattenToString(), - Long.toString(getSerialNumberForUser(cacheKey.user))}); - if (c.moveToNext()) { - // Set the alpha to be 255, so that we never have a wrong color - entry.bitmap = BitmapInfo.of(LOW_RES_ICON, setColorAlphaBound(c.getInt(0), 255)); - entry.title = c.getString(1); - if (entry.title == null) { - entry.title = ""; - entry.contentDescription = ""; - } else { - entry.contentDescription = mPackageManager.getUserBadgedLabel( - entry.title, cacheKey.user); - } - - if (!lowRes) { - byte[] data = c.getBlob(2); - try { - entry.bitmap = BitmapInfo.of( - BitmapFactory.decodeByteArray(data, 0, data.length, mDecodeOptions), - entry.bitmap.color); - } catch (Exception e) { } - } - return true; - } - } catch (SQLiteException e) { - Log.d(TAG, "Error reading icon cache", e); - } finally { - if (c != null) { - c.close(); - } - } - return false; - } - - /** - * Returns a cursor for an arbitrary query to the cache db - */ - public synchronized Cursor queryCacheDb(String[] columns, String selection, - String[] selectionArgs) { - return mIconDb.query(columns, selection, selectionArgs); - } - - /** - * Cache class to store the actual entries on disk - */ - public static final class IconDB extends SQLiteCacheHelper { - private static final int RELEASE_VERSION = 27; - - public static final String TABLE_NAME = "icons"; - public static final String COLUMN_ROWID = "rowid"; - public static final String COLUMN_COMPONENT = "componentName"; - public static final String COLUMN_USER = "profileId"; - public static final String COLUMN_LAST_UPDATED = "lastUpdated"; - public static final String COLUMN_VERSION = "version"; - public static final String COLUMN_ICON = "icon"; - public static final String COLUMN_ICON_COLOR = "icon_color"; - public static final String COLUMN_LABEL = "label"; - public static final String COLUMN_SYSTEM_STATE = "system_state"; - public static final String COLUMN_KEYWORDS = "keywords"; - - public static final String[] COLUMNS_HIGH_RES = new String[] { - IconDB.COLUMN_ICON_COLOR, IconDB.COLUMN_LABEL, IconDB.COLUMN_ICON }; - public static final String[] COLUMNS_LOW_RES = new String[] { - IconDB.COLUMN_ICON_COLOR, IconDB.COLUMN_LABEL }; - - public IconDB(Context context, String dbFileName, int iconPixelSize) { - super(context, dbFileName, (RELEASE_VERSION << 16) + iconPixelSize, TABLE_NAME); - } - - @Override - protected void onCreateTable(SQLiteDatabase db) { - db.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " (" - + COLUMN_COMPONENT + " TEXT NOT NULL, " - + COLUMN_USER + " INTEGER NOT NULL, " - + COLUMN_LAST_UPDATED + " INTEGER NOT NULL DEFAULT 0, " - + COLUMN_VERSION + " INTEGER NOT NULL DEFAULT 0, " - + COLUMN_ICON + " BLOB, " - + COLUMN_ICON_COLOR + " INTEGER NOT NULL DEFAULT 0, " - + COLUMN_LABEL + " TEXT, " - + COLUMN_SYSTEM_STATE + " TEXT, " - + COLUMN_KEYWORDS + " TEXT, " - + "PRIMARY KEY (" + COLUMN_COMPONENT + ", " + COLUMN_USER + ") " - + ");"); - } - } - - private ContentValues newContentValues(BitmapInfo bitmapInfo, String label, - String packageName, @Nullable String keywords) { - ContentValues values = new ContentValues(); - values.put(IconDB.COLUMN_ICON, - bitmapInfo.isLowRes() ? null : GraphicsUtils.flattenBitmap(bitmapInfo.icon)); - values.put(IconDB.COLUMN_ICON_COLOR, bitmapInfo.color); - - values.put(IconDB.COLUMN_LABEL, label); - values.put(IconDB.COLUMN_SYSTEM_STATE, getIconSystemState(packageName)); - values.put(IconDB.COLUMN_KEYWORDS, keywords); - return values; - } - - private void assertWorkerThread() { - if (Looper.myLooper() != mBgLooper) { - throw new IllegalStateException("Cache accessed on wrong thread " + Looper.myLooper()); - } - } -} diff --git a/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java b/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java deleted file mode 100644 index c12e9dcc15..0000000000 --- a/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.launcher3.icons.cache; - -import android.content.ComponentName; -import android.content.Context; -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; - -public interface CachingLogic { - - ComponentName getComponent(T object); - - UserHandle getUser(T object); - - CharSequence getLabel(T object); - - default CharSequence getDescription(T object, CharSequence fallback) { - return fallback; - } - - @NonNull - BitmapInfo loadIcon(Context context, T object); - - /** - * Provides a option list of keywords to associate with this object - */ - @Nullable - default String getKeywords(T object, LocaleList localeList) { - return null; - } - - /** - * Returns the timestamp the entry was last updated in cache. - */ - default long getLastUpdatedTime(T object, PackageInfo info) { - return info.lastUpdateTime; - } - - /** - * Returns true the object should be added to mem cache; otherwise returns false. - */ - default boolean addToMemCache() { - return true; - } -} diff --git a/iconloaderlib/src/com/android/launcher3/icons/cache/HandlerRunnable.java b/iconloaderlib/src/com/android/launcher3/icons/cache/HandlerRunnable.java deleted file mode 100644 index ee52934543..0000000000 --- a/iconloaderlib/src/com/android/launcher3/icons/cache/HandlerRunnable.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.launcher3.icons.cache; - -import android.os.Handler; - -/** - * A runnable that can be posted to a {@link Handler} which can be canceled. - */ -public abstract class HandlerRunnable implements Runnable { - - private final Handler mHandler; - private final Runnable mEndRunnable; - - private boolean mEnded = false; - private boolean mCanceled = false; - - public HandlerRunnable(Handler handler, Runnable endRunnable) { - mHandler = handler; - mEndRunnable = endRunnable; - } - - /** - * Cancels this runnable from being run, only if it has not already run. - */ - public void cancel() { - mHandler.removeCallbacks(this); - // TODO: This can actually cause onEnd to be called twice if the handler is already running - // this runnable - // NOTE: This is currently run on whichever thread the caller is run on. - mCanceled = true; - onEnd(); - } - - /** - * @return whether this runnable was canceled. - */ - protected boolean isCanceled() { - return mCanceled; - } - - /** - * To be called by the implemention of this runnable. The end callback is done on whichever - * thread the caller is calling from. - */ - public void onEnd() { - if (!mEnded) { - mEnded = true; - if (mEndRunnable != null) { - mEndRunnable.run(); - } - } - } -} diff --git a/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java b/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java deleted file mode 100644 index 9e1ad7b7b2..0000000000 --- a/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java +++ /dev/null @@ -1,313 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.launcher3.icons.cache; - -import android.content.ComponentName; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.database.Cursor; -import android.database.sqlite.SQLiteException; -import android.os.SystemClock; -import android.os.UserHandle; -import android.text.TextUtils; -import android.util.ArrayMap; -import android.util.Log; -import android.util.SparseBooleanArray; - -import com.android.launcher3.icons.cache.BaseIconCache.IconDB; - -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map.Entry; -import java.util.Set; -import java.util.Stack; - -/** - * Utility class to handle updating the Icon cache - */ -public class IconCacheUpdateHandler { - - private static final String TAG = "IconCacheUpdateHandler"; - - /** - * In this mode, all invalid icons are marked as to-be-deleted in {@link #mItemsToDelete}. - * This mode is used for the first run. - */ - private static final boolean MODE_SET_INVALID_ITEMS = true; - - /** - * In this mode, any valid icon is removed from {@link #mItemsToDelete}. This is used for all - * subsequent runs, which essentially acts as set-union of all valid items. - */ - private static final boolean MODE_CLEAR_VALID_ITEMS = false; - - private static final Object ICON_UPDATE_TOKEN = new Object(); - - private final HashMap mPkgInfoMap; - private final BaseIconCache mIconCache; - - private final ArrayMap> mPackagesToIgnore = new ArrayMap<>(); - - private final SparseBooleanArray mItemsToDelete = new SparseBooleanArray(); - private boolean mFilterMode = MODE_SET_INVALID_ITEMS; - - IconCacheUpdateHandler(BaseIconCache cache) { - mIconCache = cache; - - mPkgInfoMap = new HashMap<>(); - - // Remove all active icon update tasks. - mIconCache.mWorkerHandler.removeCallbacksAndMessages(ICON_UPDATE_TOKEN); - - createPackageInfoMap(); - } - - /** - * Sets a package to ignore for processing - */ - public void addPackagesToIgnore(UserHandle userHandle, String packageName) { - Set packages = mPackagesToIgnore.get(userHandle); - if (packages == null) { - packages = new HashSet<>(); - mPackagesToIgnore.put(userHandle, packages); - } - packages.add(packageName); - } - - private void createPackageInfoMap() { - PackageManager pm = mIconCache.mPackageManager; - for (PackageInfo info : - pm.getInstalledPackages(PackageManager.MATCH_UNINSTALLED_PACKAGES)) { - mPkgInfoMap.put(info.packageName, info); - } - } - - /** - * Updates the persistent DB, such that only entries corresponding to {@param apps} remain in - * the DB and are updated. - * @return The set of packages for which icons have updated. - */ - public void updateIcons(List apps, CachingLogic cachingLogic, - OnUpdateCallback onUpdateCallback) { - // Filter the list per user - HashMap> userComponentMap = new HashMap<>(); - int count = apps.size(); - for (int i = 0; i < count; i++) { - T app = apps.get(i); - UserHandle userHandle = cachingLogic.getUser(app); - HashMap componentMap = userComponentMap.get(userHandle); - if (componentMap == null) { - componentMap = new HashMap<>(); - userComponentMap.put(userHandle, componentMap); - } - componentMap.put(cachingLogic.getComponent(app), app); - } - - for (Entry> entry : userComponentMap.entrySet()) { - updateIconsPerUser(entry.getKey(), entry.getValue(), cachingLogic, onUpdateCallback); - } - - // From now on, clear every valid item from the global valid map. - mFilterMode = MODE_CLEAR_VALID_ITEMS; - } - - /** - * Updates the persistent DB, such that only entries corresponding to {@param apps} remain in - * the DB and are updated. - * @return The set of packages for which icons have updated. - */ - @SuppressWarnings("unchecked") - private void updateIconsPerUser(UserHandle user, HashMap componentMap, - CachingLogic cachingLogic, OnUpdateCallback onUpdateCallback) { - Set ignorePackages = mPackagesToIgnore.get(user); - if (ignorePackages == null) { - ignorePackages = Collections.emptySet(); - } - long userSerial = mIconCache.getSerialNumberForUser(user); - - Stack appsToUpdate = new Stack<>(); - - try (Cursor c = mIconCache.mIconDb.query( - new String[]{IconDB.COLUMN_ROWID, IconDB.COLUMN_COMPONENT, - IconDB.COLUMN_LAST_UPDATED, IconDB.COLUMN_VERSION, - IconDB.COLUMN_SYSTEM_STATE}, - IconDB.COLUMN_USER + " = ? ", - new String[]{Long.toString(userSerial)})) { - - final int indexComponent = c.getColumnIndex(IconDB.COLUMN_COMPONENT); - final int indexLastUpdate = c.getColumnIndex(IconDB.COLUMN_LAST_UPDATED); - final int indexVersion = c.getColumnIndex(IconDB.COLUMN_VERSION); - final int rowIndex = c.getColumnIndex(IconDB.COLUMN_ROWID); - final int systemStateIndex = c.getColumnIndex(IconDB.COLUMN_SYSTEM_STATE); - - while (c.moveToNext()) { - String cn = c.getString(indexComponent); - ComponentName component = ComponentName.unflattenFromString(cn); - PackageInfo info = mPkgInfoMap.get(component.getPackageName()); - - int rowId = c.getInt(rowIndex); - if (info == null) { - if (!ignorePackages.contains(component.getPackageName())) { - - if (mFilterMode == MODE_SET_INVALID_ITEMS) { - mIconCache.remove(component, user); - mItemsToDelete.put(rowId, true); - } - } - continue; - } - if ((info.applicationInfo.flags & ApplicationInfo.FLAG_IS_DATA_ONLY) != 0) { - // Application is not present - continue; - } - - long updateTime = c.getLong(indexLastUpdate); - int version = c.getInt(indexVersion); - T app = componentMap.remove(component); - if (version == info.versionCode && updateTime == info.lastUpdateTime - && TextUtils.equals(c.getString(systemStateIndex), - mIconCache.getIconSystemState(info.packageName))) { - - if (mFilterMode == MODE_CLEAR_VALID_ITEMS) { - mItemsToDelete.put(rowId, false); - } - continue; - } - - if (app == null) { - if (mFilterMode == MODE_SET_INVALID_ITEMS) { - mIconCache.remove(component, user); - mItemsToDelete.put(rowId, true); - } - } else { - appsToUpdate.add(app); - } - } - } catch (SQLiteException e) { - Log.d(TAG, "Error reading icon cache", e); - // Continue updating whatever we have read so far - } - - // Insert remaining apps. - if (!componentMap.isEmpty() || !appsToUpdate.isEmpty()) { - Stack appsToAdd = new Stack<>(); - appsToAdd.addAll(componentMap.values()); - new SerializedIconUpdateTask(userSerial, user, appsToAdd, appsToUpdate, cachingLogic, - onUpdateCallback).scheduleNext(); - } - } - - /** - * Commits all updates as part of the update handler to disk. Not more calls should be made - * to this class after this. - */ - public void finish() { - // Commit all deletes - int deleteCount = 0; - StringBuilder queryBuilder = new StringBuilder() - .append(IconDB.COLUMN_ROWID) - .append(" IN ("); - - int count = mItemsToDelete.size(); - for (int i = 0; i < count; i++) { - if (mItemsToDelete.valueAt(i)) { - if (deleteCount > 0) { - queryBuilder.append(", "); - } - queryBuilder.append(mItemsToDelete.keyAt(i)); - deleteCount++; - } - } - queryBuilder.append(')'); - - if (deleteCount > 0) { - mIconCache.mIconDb.delete(queryBuilder.toString(), null); - } - } - - /** - * A runnable that updates invalid icons and adds missing icons in the DB for the provided - * LauncherActivityInfo list. Items are updated/added one at a time, so that the - * worker thread doesn't get blocked. - */ - private class SerializedIconUpdateTask implements Runnable { - private final long mUserSerial; - private final UserHandle mUserHandle; - private final Stack mAppsToAdd; - private final Stack mAppsToUpdate; - private final CachingLogic mCachingLogic; - private final HashSet mUpdatedPackages = new HashSet<>(); - private final OnUpdateCallback mOnUpdateCallback; - - SerializedIconUpdateTask(long userSerial, UserHandle userHandle, - Stack appsToAdd, Stack appsToUpdate, CachingLogic cachingLogic, - OnUpdateCallback onUpdateCallback) { - mUserHandle = userHandle; - mUserSerial = userSerial; - mAppsToAdd = appsToAdd; - mAppsToUpdate = appsToUpdate; - mCachingLogic = cachingLogic; - mOnUpdateCallback = onUpdateCallback; - } - - @Override - public void run() { - if (!mAppsToUpdate.isEmpty()) { - T app = mAppsToUpdate.pop(); - String pkg = mCachingLogic.getComponent(app).getPackageName(); - PackageInfo info = mPkgInfoMap.get(pkg); - - mIconCache.addIconToDBAndMemCache( - app, mCachingLogic, info, mUserSerial, true /*replace existing*/); - mUpdatedPackages.add(pkg); - - if (mAppsToUpdate.isEmpty() && !mUpdatedPackages.isEmpty()) { - // No more app to update. Notify callback. - mOnUpdateCallback.onPackageIconsUpdated(mUpdatedPackages, mUserHandle); - } - - // Let it run one more time. - scheduleNext(); - } else if (!mAppsToAdd.isEmpty()) { - T app = mAppsToAdd.pop(); - PackageInfo info = mPkgInfoMap.get(mCachingLogic.getComponent(app).getPackageName()); - // We do not check the mPkgInfoMap when generating the mAppsToAdd. Although every - // app should have package info, this is not guaranteed by the api - if (info != null) { - mIconCache.addIconToDBAndMemCache(app, mCachingLogic, info, - mUserSerial, false /*replace existing*/); - } - - if (!mAppsToAdd.isEmpty()) { - scheduleNext(); - } - } - } - - public void scheduleNext() { - mIconCache.mWorkerHandler.postAtTime(this, ICON_UPDATE_TOKEN, - SystemClock.uptimeMillis() + 1); - } - } - - public interface OnUpdateCallback { - - void onPackageIconsUpdated(HashSet updatedPackages, UserHandle user); - } -} diff --git a/iconloaderlib/src/com/android/launcher3/util/ComponentKey.java b/iconloaderlib/src/com/android/launcher3/util/ComponentKey.java deleted file mode 100644 index 34bed94270..0000000000 --- a/iconloaderlib/src/com/android/launcher3/util/ComponentKey.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.android.launcher3.util; - -/** - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import android.content.ComponentName; -import android.os.UserHandle; - -import java.util.Arrays; - -public class ComponentKey { - - public final ComponentName componentName; - public final UserHandle user; - - private final int mHashCode; - - public ComponentKey(ComponentName componentName, UserHandle user) { - if (componentName == null || user == null) { - throw new NullPointerException(); - } - this.componentName = componentName; - this.user = user; - mHashCode = Arrays.hashCode(new Object[] {componentName, user}); - - } - - @Override - public int hashCode() { - return mHashCode; - } - - @Override - public boolean equals(Object o) { - ComponentKey other = (ComponentKey) o; - return other.componentName.equals(componentName) && other.user.equals(user); - } - - /** - * Encodes a component key as a string of the form [flattenedComponentString#userId]. - */ - @Override - public String toString() { - return componentName.flattenToString() + "#" + user; - } -} \ No newline at end of file diff --git a/iconloaderlib/src/com/android/launcher3/util/NoLocaleSQLiteHelper.java b/iconloaderlib/src/com/android/launcher3/util/NoLocaleSQLiteHelper.java deleted file mode 100644 index fe864a2847..0000000000 --- a/iconloaderlib/src/com/android/launcher3/util/NoLocaleSQLiteHelper.java +++ /dev/null @@ -1,58 +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.util; - -import static android.database.sqlite.SQLiteDatabase.NO_LOCALIZED_COLLATORS; - -import android.content.Context; -import android.content.ContextWrapper; -import android.database.DatabaseErrorHandler; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteDatabase.CursorFactory; -import android.database.sqlite.SQLiteDatabase.OpenParams; -import android.database.sqlite.SQLiteOpenHelper; -import android.os.Build; - -/** - * Extension of {@link SQLiteOpenHelper} which avoids creating default locale table by - * A context wrapper which creates databases without support for localized collators. - */ -public abstract class NoLocaleSQLiteHelper extends SQLiteOpenHelper { - - private static final boolean ATLEAST_P = - Build.VERSION.SDK_INT >= Build.VERSION_CODES.P; - - public NoLocaleSQLiteHelper(Context context, String name, int version) { - super(ATLEAST_P ? context : new NoLocalContext(context), name, null, version); - if (ATLEAST_P) { - setOpenParams(new OpenParams.Builder().addOpenFlags(NO_LOCALIZED_COLLATORS).build()); - } - } - - private static class NoLocalContext extends ContextWrapper { - public NoLocalContext(Context base) { - super(base); - } - - @Override - public SQLiteDatabase openOrCreateDatabase( - String name, int mode, CursorFactory factory, DatabaseErrorHandler errorHandler) { - return super.openOrCreateDatabase( - name, mode | Context.MODE_NO_LOCALIZED_COLLATORS, factory, errorHandler); - } - } -} diff --git a/iconloaderlib/src/com/android/launcher3/util/SQLiteCacheHelper.java b/iconloaderlib/src/com/android/launcher3/util/SQLiteCacheHelper.java deleted file mode 100644 index 49de4bd1bf..0000000000 --- a/iconloaderlib/src/com/android/launcher3/util/SQLiteCacheHelper.java +++ /dev/null @@ -1,125 +0,0 @@ -package com.android.launcher3.util; - -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteException; -import android.database.sqlite.SQLiteFullException; -import android.database.sqlite.SQLiteOpenHelper; -import android.util.Log; - -/** - * An extension of {@link SQLiteOpenHelper} with utility methods for a single table cache DB. - * Any exception during write operations are ignored, and any version change causes a DB reset. - */ -public abstract class SQLiteCacheHelper { - private static final String TAG = "SQLiteCacheHelper"; - - private static final boolean IN_MEMORY_CACHE = false; - - private final String mTableName; - private final MySQLiteOpenHelper mOpenHelper; - - private boolean mIgnoreWrites; - - public SQLiteCacheHelper(Context context, String name, int version, String tableName) { - if (IN_MEMORY_CACHE) { - name = null; - } - mTableName = tableName; - mOpenHelper = new MySQLiteOpenHelper(context, name, version); - - mIgnoreWrites = false; - } - - /** - * @see SQLiteDatabase#delete(String, String, String[]) - */ - public void delete(String whereClause, String[] whereArgs) { - if (mIgnoreWrites) { - return; - } - try { - mOpenHelper.getWritableDatabase().delete(mTableName, whereClause, whereArgs); - } catch (SQLiteFullException e) { - onDiskFull(e); - } catch (SQLiteException e) { - Log.d(TAG, "Ignoring sqlite exception", e); - } - } - - /** - * @see SQLiteDatabase#insertWithOnConflict(String, String, ContentValues, int) - */ - public void insertOrReplace(ContentValues values) { - if (mIgnoreWrites) { - return; - } - try { - mOpenHelper.getWritableDatabase().insertWithOnConflict( - mTableName, null, values, SQLiteDatabase.CONFLICT_REPLACE); - } catch (SQLiteFullException e) { - onDiskFull(e); - } catch (SQLiteException e) { - Log.d(TAG, "Ignoring sqlite exception", e); - } - } - - private void onDiskFull(SQLiteFullException e) { - Log.e(TAG, "Disk full, all write operations will be ignored", e); - mIgnoreWrites = true; - } - - /** - * @see SQLiteDatabase#query(String, String[], String, String[], String, String, String) - */ - public Cursor query(String[] columns, String selection, String[] selectionArgs) { - return mOpenHelper.getReadableDatabase().query( - mTableName, columns, selection, selectionArgs, null, null, null); - } - - public void clear() { - mOpenHelper.clearDB(mOpenHelper.getWritableDatabase()); - } - - public void close() { - mOpenHelper.close(); - } - - protected abstract void onCreateTable(SQLiteDatabase db); - - /** - * A private inner class to prevent direct DB access. - */ - private class MySQLiteOpenHelper extends NoLocaleSQLiteHelper { - - public MySQLiteOpenHelper(Context context, String name, int version) { - super(context, name, version); - } - - @Override - public void onCreate(SQLiteDatabase db) { - onCreateTable(db); - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - if (oldVersion != newVersion) { - clearDB(db); - } - } - - @Override - public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { - if (oldVersion != newVersion) { - clearDB(db); - } - } - - private void clearDB(SQLiteDatabase db) { - db.execSQL("DROP TABLE IF EXISTS " + mTableName); - onCreate(db); - } - } -} diff --git a/iconloaderlib/src_full_lib/com/android/launcher3/icons/IconFactory.java b/iconloaderlib/src_full_lib/com/android/launcher3/icons/IconFactory.java deleted file mode 100644 index 48f11fde3b..0000000000 --- a/iconloaderlib/src_full_lib/com/android/launcher3/icons/IconFactory.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher3.icons; - -import android.content.Context; - -/** - * Wrapper class to provide access to {@link BaseIconFactory} and also to provide pool of this class - * that are threadsafe. - */ -public class IconFactory extends BaseIconFactory { - - private static final Object sPoolSync = new Object(); - private static IconFactory sPool; - private static int sPoolId = 0; - - /** - * Return a new Message instance from the global pool. Allows us to - * avoid allocating new objects in many cases. - */ - public static IconFactory obtain(Context context) { - int poolId; - synchronized (sPoolSync) { - if (sPool != null) { - IconFactory m = sPool; - sPool = m.next; - m.next = null; - return m; - } - poolId = sPoolId; - } - - return new IconFactory(context, - context.getResources().getConfiguration().densityDpi, - context.getResources().getDimensionPixelSize(R.dimen.default_icon_bitmap_size), - poolId); - } - - public static void clearPool() { - synchronized (sPoolSync) { - sPool = null; - sPoolId++; - } - } - - private final int mPoolId; - - private IconFactory next; - - private IconFactory(Context context, int fillResIconDpi, int iconBitmapSize, int poolId) { - super(context, fillResIconDpi, iconBitmapSize); - mPoolId = poolId; - } - - /** - * Recycles a LauncherIcons that may be in-use. - */ - public void recycle() { - synchronized (sPoolSync) { - if (sPoolId != mPoolId) { - return; - } - // Clear any temporary state variables - clear(); - - next = sPool; - sPool = this; - } - } - - @Override - public void close() { - recycle(); - } -} diff --git a/iconloaderlib/src_full_lib/com/android/launcher3/icons/SimpleIconCache.java b/iconloaderlib/src_full_lib/com/android/launcher3/icons/SimpleIconCache.java deleted file mode 100644 index 1337975f19..0000000000 --- a/iconloaderlib/src_full_lib/com/android/launcher3/icons/SimpleIconCache.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.launcher3.icons; - -import static android.content.Intent.ACTION_MANAGED_PROFILE_ADDED; -import static android.content.Intent.ACTION_MANAGED_PROFILE_REMOVED; - -import android.annotation.TargetApi; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.ApplicationInfo; -import android.os.Build; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Looper; -import android.os.UserHandle; -import android.os.UserManager; -import android.util.SparseLongArray; - -import com.android.launcher3.icons.cache.BaseIconCache; - -/** - * Wrapper class to provide access to {@link BaseIconFactory} and also to provide pool of this class - * that are threadsafe. - */ -@TargetApi(Build.VERSION_CODES.P) -public class SimpleIconCache extends BaseIconCache { - - private static SimpleIconCache sIconCache = null; - private static final Object CACHE_LOCK = new Object(); - - private final SparseLongArray mUserSerialMap = new SparseLongArray(2); - private final UserManager mUserManager; - - public SimpleIconCache(Context context, String dbFileName, Looper bgLooper, int iconDpi, - int iconPixelSize, boolean inMemoryCache) { - super(context, dbFileName, bgLooper, iconDpi, iconPixelSize, inMemoryCache); - mUserManager = context.getSystemService(UserManager.class); - - // Listen for user cache changes. - IntentFilter filter = new IntentFilter(ACTION_MANAGED_PROFILE_ADDED); - filter.addAction(ACTION_MANAGED_PROFILE_REMOVED); - context.registerReceiver(new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - resetUserCache(); - } - }, filter, null, new Handler(bgLooper), 0); - } - - @Override - protected long getSerialNumberForUser(UserHandle user) { - synchronized (mUserSerialMap) { - int index = mUserSerialMap.indexOfKey(user.getIdentifier()); - if (index >= 0) { - return mUserSerialMap.valueAt(index); - } - long serial = mUserManager.getSerialNumberForUser(user); - mUserSerialMap.put(user.getIdentifier(), serial); - return serial; - } - } - - private void resetUserCache() { - synchronized (mUserSerialMap) { - mUserSerialMap.clear(); - } - } - - @Override - protected boolean isInstantApp(ApplicationInfo info) { - return info.isInstantApp(); - } - - @Override - protected BaseIconFactory getIconFactory() { - return IconFactory.obtain(mContext); - } - - public static SimpleIconCache getIconCache(Context context) { - synchronized (CACHE_LOCK) { - if (sIconCache != null) { - return sIconCache; - } - boolean inMemoryCache = - context.getResources().getBoolean(R.bool.simple_cache_enable_im_memory); - String dbFileName = context.getString(R.string.cache_db_name); - - HandlerThread bgThread = new HandlerThread("simple-icon-cache"); - bgThread.start(); - - sIconCache = new SimpleIconCache(context.getApplicationContext(), dbFileName, - bgThread.getLooper(), context.getResources().getConfiguration().densityDpi, - context.getResources().getDimensionPixelSize(R.dimen.default_icon_bitmap_size), - inMemoryCache); - return sIconCache; - } - } -} From b07e481436dfddb77d10b5520d5ae35281004f7d Mon Sep 17 00:00:00 2001 From: Andy Wickham Date: Tue, 3 Mar 2020 01:15:27 +0000 Subject: [PATCH 0010/1707] Removes iconloaderlib from Launcher3. Bug: 138964382 Test: builds Change-Id: Ic60adfb2ebdcf1a72b440df26023b861fd6e62d5 Merged-In: Ic60adfb2ebdcf1a72b440df26023b861fd6e62d5 --- iconloaderlib/Android.bp | 44 -- iconloaderlib/AndroidManifest.xml | 20 - iconloaderlib/build.gradle | 38 -- .../adaptive_icon_drawable_wrapper.xml | 22 - .../res/drawable/ic_instant_app_badge.xml | 39 -- iconloaderlib/res/values/colors.xml | 21 - iconloaderlib/res/values/config.xml | 27 - iconloaderlib/res/values/dimens.xml | 19 - .../launcher3/icons/BaseIconFactory.java | 396 ------------ .../android/launcher3/icons/BitmapInfo.java | 72 --- .../launcher3/icons/BitmapRenderer.java | 70 --- .../launcher3/icons/ColorExtractor.java | 127 ---- .../android/launcher3/icons/DotRenderer.java | 143 ----- .../launcher3/icons/FixedScaleDrawable.java | 53 -- .../launcher3/icons/GraphicsUtils.java | 85 --- .../launcher3/icons/IconNormalizer.java | 411 ------------- .../launcher3/icons/ShadowGenerator.java | 167 ----- .../launcher3/icons/cache/BaseIconCache.java | 582 ------------------ .../launcher3/icons/cache/CachingLogic.java | 65 -- .../icons/cache/HandlerRunnable.java | 67 -- .../icons/cache/IconCacheUpdateHandler.java | 313 ---------- .../android/launcher3/util/ComponentKey.java | 59 -- .../launcher3/util/NoLocaleSQLiteHelper.java | 58 -- .../launcher3/util/SQLiteCacheHelper.java | 125 ---- .../android/launcher3/icons/IconFactory.java | 89 --- .../launcher3/icons/SimpleIconCache.java | 114 ---- 26 files changed, 3226 deletions(-) delete mode 100644 iconloaderlib/Android.bp delete mode 100644 iconloaderlib/AndroidManifest.xml delete mode 100644 iconloaderlib/build.gradle delete mode 100644 iconloaderlib/res/drawable-v26/adaptive_icon_drawable_wrapper.xml delete mode 100644 iconloaderlib/res/drawable/ic_instant_app_badge.xml delete mode 100644 iconloaderlib/res/values/colors.xml delete mode 100644 iconloaderlib/res/values/config.xml delete mode 100644 iconloaderlib/res/values/dimens.xml delete mode 100644 iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java delete mode 100644 iconloaderlib/src/com/android/launcher3/icons/BitmapInfo.java delete mode 100644 iconloaderlib/src/com/android/launcher3/icons/BitmapRenderer.java delete mode 100644 iconloaderlib/src/com/android/launcher3/icons/ColorExtractor.java delete mode 100644 iconloaderlib/src/com/android/launcher3/icons/DotRenderer.java delete mode 100644 iconloaderlib/src/com/android/launcher3/icons/FixedScaleDrawable.java delete mode 100644 iconloaderlib/src/com/android/launcher3/icons/GraphicsUtils.java delete mode 100644 iconloaderlib/src/com/android/launcher3/icons/IconNormalizer.java delete mode 100644 iconloaderlib/src/com/android/launcher3/icons/ShadowGenerator.java delete mode 100644 iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java delete mode 100644 iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java delete mode 100644 iconloaderlib/src/com/android/launcher3/icons/cache/HandlerRunnable.java delete mode 100644 iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java delete mode 100644 iconloaderlib/src/com/android/launcher3/util/ComponentKey.java delete mode 100644 iconloaderlib/src/com/android/launcher3/util/NoLocaleSQLiteHelper.java delete mode 100644 iconloaderlib/src/com/android/launcher3/util/SQLiteCacheHelper.java delete mode 100644 iconloaderlib/src_full_lib/com/android/launcher3/icons/IconFactory.java delete mode 100644 iconloaderlib/src_full_lib/com/android/launcher3/icons/SimpleIconCache.java diff --git a/iconloaderlib/Android.bp b/iconloaderlib/Android.bp deleted file mode 100644 index f12d16e42a..0000000000 --- a/iconloaderlib/Android.bp +++ /dev/null @@ -1,44 +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. - -android_library { - name: "iconloader_base", - sdk_version: "28", - min_sdk_version: "21", - static_libs: [ - "androidx.core_core", - ], - resource_dirs: [ - "res", - ], - srcs: [ - "src/**/*.java", - ], -} - -android_library { - name: "iconloader", - sdk_version: "system_current", - min_sdk_version: "21", - static_libs: [ - "androidx.core_core", - ], - resource_dirs: [ - "res", - ], - srcs: [ - "src/**/*.java", - "src_full_lib/**/*.java", - ], -} diff --git a/iconloaderlib/AndroidManifest.xml b/iconloaderlib/AndroidManifest.xml deleted file mode 100644 index b30258da2a..0000000000 --- a/iconloaderlib/AndroidManifest.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - diff --git a/iconloaderlib/build.gradle b/iconloaderlib/build.gradle deleted file mode 100644 index d7a62e1114..0000000000 --- a/iconloaderlib/build.gradle +++ /dev/null @@ -1,38 +0,0 @@ -apply plugin: 'com.android.library' - -android { - compileSdkVersion COMPILE_SDK - buildToolsVersion BUILD_TOOLS_VERSION - - defaultConfig { - minSdkVersion 25 - targetSdkVersion 28 - versionCode 1 - versionName "1.0" - } - - sourceSets { - main { - java.srcDirs = ['src', 'src_full_lib'] - manifest.srcFile 'AndroidManifest.xml' - res.srcDirs = ['res'] - } - } - - lintOptions { - abortOnError false - } - - tasks.withType(JavaCompile) { - options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation" - } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } -} - -dependencies { - implementation "androidx.core:core:${ANDROID_X_VERSION}" -} diff --git a/iconloaderlib/res/drawable-v26/adaptive_icon_drawable_wrapper.xml b/iconloaderlib/res/drawable-v26/adaptive_icon_drawable_wrapper.xml deleted file mode 100644 index 9f13cf5719..0000000000 --- a/iconloaderlib/res/drawable-v26/adaptive_icon_drawable_wrapper.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - diff --git a/iconloaderlib/res/drawable/ic_instant_app_badge.xml b/iconloaderlib/res/drawable/ic_instant_app_badge.xml deleted file mode 100644 index b74317e5f2..0000000000 --- a/iconloaderlib/res/drawable/ic_instant_app_badge.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - diff --git a/iconloaderlib/res/values/colors.xml b/iconloaderlib/res/values/colors.xml deleted file mode 100644 index 873b2fc5fb..0000000000 --- a/iconloaderlib/res/values/colors.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - #FFFFFF - diff --git a/iconloaderlib/res/values/config.xml b/iconloaderlib/res/values/config.xml deleted file mode 100644 index 68c2d2e3aa..0000000000 --- a/iconloaderlib/res/values/config.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - 56dp - false - app_icons.db - - \ No newline at end of file diff --git a/iconloaderlib/res/values/dimens.xml b/iconloaderlib/res/values/dimens.xml deleted file mode 100644 index e8c0c44f72..0000000000 --- a/iconloaderlib/res/values/dimens.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - 24dp - diff --git a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java deleted file mode 100644 index 31a923e314..0000000000 --- a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java +++ /dev/null @@ -1,396 +0,0 @@ -package com.android.launcher3.icons; - -import static android.graphics.Paint.DITHER_FLAG; -import static android.graphics.Paint.FILTER_BITMAP_FLAG; - -import static com.android.launcher3.icons.ShadowGenerator.BLUR_FACTOR; - -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.PaintFlagsDrawFilter; -import android.graphics.Rect; -import android.graphics.RectF; -import android.graphics.drawable.AdaptiveIconDrawable; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.Drawable; -import android.os.Build; -import android.os.Process; -import android.os.UserHandle; - -import androidx.annotation.NonNull; - -/** - * This class will be moved to androidx library. There shouldn't be any dependency outside - * this package. - */ -public class BaseIconFactory implements AutoCloseable { - - private static final String TAG = "BaseIconFactory"; - private static final int DEFAULT_WRAPPER_BACKGROUND = Color.WHITE; - static final boolean ATLEAST_OREO = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O; - static final boolean ATLEAST_P = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P; - - private static final float ICON_BADGE_SCALE = 0.444f; - - private final Rect mOldBounds = new Rect(); - protected final Context mContext; - private final Canvas mCanvas; - private final PackageManager mPm; - private final ColorExtractor mColorExtractor; - private boolean mDisableColorExtractor; - private boolean mBadgeOnLeft = false; - - protected final int mFillResIconDpi; - protected final int mIconBitmapSize; - - private IconNormalizer mNormalizer; - private ShadowGenerator mShadowGenerator; - private final boolean mShapeDetection; - - private Drawable mWrapperIcon; - private int mWrapperBackgroundColor = DEFAULT_WRAPPER_BACKGROUND; - - protected BaseIconFactory(Context context, int fillResIconDpi, int iconBitmapSize, - boolean shapeDetection) { - mContext = context.getApplicationContext(); - mShapeDetection = shapeDetection; - mFillResIconDpi = fillResIconDpi; - mIconBitmapSize = iconBitmapSize; - - mPm = mContext.getPackageManager(); - mColorExtractor = new ColorExtractor(); - - mCanvas = new Canvas(); - mCanvas.setDrawFilter(new PaintFlagsDrawFilter(DITHER_FLAG, FILTER_BITMAP_FLAG)); - clear(); - } - - protected BaseIconFactory(Context context, int fillResIconDpi, int iconBitmapSize) { - this(context, fillResIconDpi, iconBitmapSize, false); - } - - protected void clear() { - mWrapperBackgroundColor = DEFAULT_WRAPPER_BACKGROUND; - mDisableColorExtractor = false; - mBadgeOnLeft = false; - } - - public ShadowGenerator getShadowGenerator() { - if (mShadowGenerator == null) { - mShadowGenerator = new ShadowGenerator(mIconBitmapSize); - } - return mShadowGenerator; - } - - public IconNormalizer getNormalizer() { - if (mNormalizer == null) { - mNormalizer = new IconNormalizer(mContext, mIconBitmapSize, mShapeDetection); - } - return mNormalizer; - } - - @SuppressWarnings("deprecation") - public BitmapInfo createIconBitmap(Intent.ShortcutIconResource iconRes) { - try { - Resources resources = mPm.getResourcesForApplication(iconRes.packageName); - if (resources != null) { - final int id = resources.getIdentifier(iconRes.resourceName, null, null); - // do not stamp old legacy shortcuts as the app may have already forgotten about it - return createBadgedIconBitmap( - resources.getDrawableForDensity(id, mFillResIconDpi), - Process.myUserHandle() /* only available on primary user */, - false /* do not apply legacy treatment */); - } - } catch (Exception e) { - // Icon not found. - } - return null; - } - - public BitmapInfo createIconBitmap(Bitmap icon) { - if (mIconBitmapSize != icon.getWidth() || mIconBitmapSize != icon.getHeight()) { - icon = createIconBitmap(new BitmapDrawable(mContext.getResources(), icon), 1f); - } - - return BitmapInfo.of(icon, extractColor(icon)); - } - - public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user, - boolean shrinkNonAdaptiveIcons) { - return createBadgedIconBitmap(icon, user, shrinkNonAdaptiveIcons, false, null); - } - - public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user, - int iconAppTargetSdk) { - return createBadgedIconBitmap(icon, user, iconAppTargetSdk, false); - } - - public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user, - int iconAppTargetSdk, boolean isInstantApp) { - return createBadgedIconBitmap(icon, user, iconAppTargetSdk, isInstantApp, null); - } - - public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user, - int iconAppTargetSdk, boolean isInstantApp, float[] scale) { - boolean shrinkNonAdaptiveIcons = ATLEAST_P || - (ATLEAST_OREO && iconAppTargetSdk >= Build.VERSION_CODES.O); - return createBadgedIconBitmap(icon, user, shrinkNonAdaptiveIcons, isInstantApp, scale); - } - - public Bitmap createScaledBitmapWithoutShadow(Drawable icon, int iconAppTargetSdk) { - boolean shrinkNonAdaptiveIcons = ATLEAST_P || - (ATLEAST_OREO && iconAppTargetSdk >= Build.VERSION_CODES.O); - return createScaledBitmapWithoutShadow(icon, shrinkNonAdaptiveIcons); - } - - /** - * Creates bitmap using the source drawable and various parameters. - * The bitmap is visually normalized with other icons and has enough spacing to add shadow. - * - * @param icon source of the icon - * @param user info can be used for a badge - * @param shrinkNonAdaptiveIcons {@code true} if non adaptive icons should be treated - * @param isInstantApp info can be used for a badge - * @param scale returns the scale result from normalization - * @return a bitmap suitable for disaplaying as an icon at various system UIs. - */ - public BitmapInfo createBadgedIconBitmap(@NonNull Drawable icon, UserHandle user, - boolean shrinkNonAdaptiveIcons, boolean isInstantApp, float[] scale) { - if (scale == null) { - scale = new float[1]; - } - icon = normalizeAndWrapToAdaptiveIcon(icon, shrinkNonAdaptiveIcons, null, scale); - Bitmap bitmap = createIconBitmap(icon, scale[0]); - if (ATLEAST_OREO && icon instanceof AdaptiveIconDrawable) { - mCanvas.setBitmap(bitmap); - getShadowGenerator().recreateIcon(Bitmap.createBitmap(bitmap), mCanvas); - mCanvas.setBitmap(null); - } - - if (isInstantApp) { - badgeWithDrawable(bitmap, mContext.getDrawable(R.drawable.ic_instant_app_badge)); - } - if (user != null) { - BitmapDrawable drawable = new FixedSizeBitmapDrawable(bitmap); - Drawable badged = mPm.getUserBadgedIcon(drawable, user); - if (badged instanceof BitmapDrawable) { - bitmap = ((BitmapDrawable) badged).getBitmap(); - } else { - bitmap = createIconBitmap(badged, 1f); - } - } - int color = extractColor(bitmap); - return icon instanceof BitmapInfo.Extender - ? ((BitmapInfo.Extender) icon).getExtendedInfo(bitmap, color, this) - : BitmapInfo.of(bitmap, color); - } - - public Bitmap createScaledBitmapWithoutShadow(Drawable icon, boolean shrinkNonAdaptiveIcons) { - RectF iconBounds = new RectF(); - float[] scale = new float[1]; - icon = normalizeAndWrapToAdaptiveIcon(icon, shrinkNonAdaptiveIcons, iconBounds, scale); - return createIconBitmap(icon, - Math.min(scale[0], ShadowGenerator.getScaleForBounds(iconBounds))); - } - - /** - * Switches badging to left/right - */ - public void setBadgeOnLeft(boolean badgeOnLeft) { - mBadgeOnLeft = badgeOnLeft; - } - - /** - * Sets the background color used for wrapped adaptive icon - */ - public void setWrapperBackgroundColor(int color) { - mWrapperBackgroundColor = (Color.alpha(color) < 255) ? DEFAULT_WRAPPER_BACKGROUND : color; - } - - /** - * Disables the dominant color extraction for all icons loaded. - */ - public void disableColorExtraction() { - mDisableColorExtractor = true; - } - - private Drawable normalizeAndWrapToAdaptiveIcon(@NonNull Drawable icon, - boolean shrinkNonAdaptiveIcons, RectF outIconBounds, float[] outScale) { - if (icon == null) { - return null; - } - float scale = 1f; - - if (shrinkNonAdaptiveIcons && ATLEAST_OREO) { - if (mWrapperIcon == null) { - mWrapperIcon = mContext.getDrawable(R.drawable.adaptive_icon_drawable_wrapper) - .mutate(); - } - AdaptiveIconDrawable dr = (AdaptiveIconDrawable) mWrapperIcon; - dr.setBounds(0, 0, 1, 1); - boolean[] outShape = new boolean[1]; - scale = getNormalizer().getScale(icon, outIconBounds, dr.getIconMask(), outShape); - if (!(icon instanceof AdaptiveIconDrawable) && !outShape[0]) { - FixedScaleDrawable fsd = ((FixedScaleDrawable) dr.getForeground()); - fsd.setDrawable(icon); - fsd.setScale(scale); - icon = dr; - scale = getNormalizer().getScale(icon, outIconBounds, null, null); - - ((ColorDrawable) dr.getBackground()).setColor(mWrapperBackgroundColor); - } - } else { - scale = getNormalizer().getScale(icon, outIconBounds, null, null); - } - - outScale[0] = scale; - return icon; - } - - /** - * Adds the {@param badge} on top of {@param target} using the badge dimensions. - */ - public void badgeWithDrawable(Bitmap target, Drawable badge) { - mCanvas.setBitmap(target); - badgeWithDrawable(mCanvas, badge); - mCanvas.setBitmap(null); - } - - /** - * Adds the {@param badge} on top of {@param target} using the badge dimensions. - */ - public void badgeWithDrawable(Canvas target, Drawable badge) { - int badgeSize = getBadgeSizeForIconSize(mIconBitmapSize); - if (mBadgeOnLeft) { - badge.setBounds(0, mIconBitmapSize - badgeSize, badgeSize, mIconBitmapSize); - } else { - badge.setBounds(mIconBitmapSize - badgeSize, mIconBitmapSize - badgeSize, - mIconBitmapSize, mIconBitmapSize); - } - badge.draw(target); - } - - private Bitmap createIconBitmap(Drawable icon, float scale) { - return createIconBitmap(icon, scale, mIconBitmapSize); - } - - /** - * @param icon drawable that should be flattened to a bitmap - * @param scale the scale to apply before drawing {@param icon} on the canvas - */ - public Bitmap createIconBitmap(@NonNull Drawable icon, float scale, int size) { - Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); - if (icon == null) { - return bitmap; - } - mCanvas.setBitmap(bitmap); - mOldBounds.set(icon.getBounds()); - - if (ATLEAST_OREO && icon instanceof AdaptiveIconDrawable) { - int offset = Math.max((int) Math.ceil(BLUR_FACTOR * size), - Math.round(size * (1 - scale) / 2 )); - icon.setBounds(offset, offset, size - offset, size - offset); - icon.draw(mCanvas); - } else { - if (icon instanceof BitmapDrawable) { - BitmapDrawable bitmapDrawable = (BitmapDrawable) icon; - Bitmap b = bitmapDrawable.getBitmap(); - if (bitmap != null && b.getDensity() == Bitmap.DENSITY_NONE) { - bitmapDrawable.setTargetDensity(mContext.getResources().getDisplayMetrics()); - } - } - int width = size; - int height = size; - - int intrinsicWidth = icon.getIntrinsicWidth(); - int intrinsicHeight = icon.getIntrinsicHeight(); - if (intrinsicWidth > 0 && intrinsicHeight > 0) { - // Scale the icon proportionally to the icon dimensions - final float ratio = (float) intrinsicWidth / intrinsicHeight; - if (intrinsicWidth > intrinsicHeight) { - height = (int) (width / ratio); - } else if (intrinsicHeight > intrinsicWidth) { - width = (int) (height * ratio); - } - } - final int left = (size - width) / 2; - final int top = (size - height) / 2; - icon.setBounds(left, top, left + width, top + height); - mCanvas.save(); - mCanvas.scale(scale, scale, size / 2, size / 2); - icon.draw(mCanvas); - mCanvas.restore(); - - } - icon.setBounds(mOldBounds); - mCanvas.setBitmap(null); - return bitmap; - } - - @Override - public void close() { - clear(); - } - - public BitmapInfo makeDefaultIcon(UserHandle user) { - return createBadgedIconBitmap(getFullResDefaultActivityIcon(mFillResIconDpi), - user, Build.VERSION.SDK_INT); - } - - public static Drawable getFullResDefaultActivityIcon(int iconDpi) { - return Resources.getSystem().getDrawableForDensity( - Build.VERSION.SDK_INT >= Build.VERSION_CODES.O - ? android.R.drawable.sym_def_app_icon : android.R.mipmap.sym_def_app_icon, - iconDpi); - } - - /** - * Badges the provided source with the badge info - */ - public BitmapInfo badgeBitmap(Bitmap source, BitmapInfo badgeInfo) { - Bitmap icon = BitmapRenderer.createHardwareBitmap(mIconBitmapSize, mIconBitmapSize, (c) -> { - getShadowGenerator().recreateIcon(source, c); - badgeWithDrawable(c, new FixedSizeBitmapDrawable(badgeInfo.icon)); - }); - return BitmapInfo.of(icon, badgeInfo.color); - } - - private int extractColor(Bitmap bitmap) { - return mDisableColorExtractor ? 0 : mColorExtractor.findDominantColorByHue(bitmap); - } - - /** - * Returns the correct badge size given an icon size - */ - public static int getBadgeSizeForIconSize(int iconSize) { - return (int) (ICON_BADGE_SCALE * iconSize); - } - - /** - * An extension of {@link BitmapDrawable} which returns the bitmap pixel size as intrinsic size. - * This allows the badging to be done based on the action bitmap size rather than - * the scaled bitmap size. - */ - private static class FixedSizeBitmapDrawable extends BitmapDrawable { - - public FixedSizeBitmapDrawable(Bitmap bitmap) { - super(null, bitmap); - } - - @Override - public int getIntrinsicHeight() { - return getBitmap().getWidth(); - } - - @Override - public int getIntrinsicWidth() { - return getBitmap().getWidth(); - } - } -} diff --git a/iconloaderlib/src/com/android/launcher3/icons/BitmapInfo.java b/iconloaderlib/src/com/android/launcher3/icons/BitmapInfo.java deleted file mode 100644 index d33f9b1112..0000000000 --- a/iconloaderlib/src/com/android/launcher3/icons/BitmapInfo.java +++ /dev/null @@ -1,72 +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.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 final Bitmap icon; - public final int 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(@NonNull Bitmap bitmap) { - return of(bitmap, 0); - } - - 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/BitmapRenderer.java b/iconloaderlib/src/com/android/launcher3/icons/BitmapRenderer.java deleted file mode 100644 index 5751ed95cb..0000000000 --- a/iconloaderlib/src/com/android/launcher3/icons/BitmapRenderer.java +++ /dev/null @@ -1,70 +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.icons; - -import android.annotation.TargetApi; -import android.graphics.Bitmap; -import android.graphics.Bitmap.Config; -import android.graphics.Canvas; -import android.graphics.Picture; -import android.graphics.Rect; -import android.graphics.RectF; -import android.os.Build; -import android.os.Build.VERSION_CODES; - -/** - * Interface representing a bitmap draw operation. - */ -public interface BitmapRenderer { - - boolean USE_HARDWARE_BITMAP = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P; - - static Bitmap createSoftwareBitmap(int width, int height, BitmapRenderer renderer) { - GraphicsUtils.noteNewBitmapCreated(); - Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - renderer.draw(new Canvas(result)); - return result; - } - - @TargetApi(Build.VERSION_CODES.P) - static Bitmap createHardwareBitmap(int width, int height, BitmapRenderer renderer) { - if (!USE_HARDWARE_BITMAP) { - return createSoftwareBitmap(width, height, renderer); - } - - GraphicsUtils.noteNewBitmapCreated(); - Picture picture = new Picture(); - renderer.draw(picture.beginRecording(width, height)); - picture.endRecording(); - return Bitmap.createBitmap(picture); - } - - /** - * Returns a bitmap from subset of the source bitmap. The new bitmap may be the - * same object as source, or a copy may have been made. - */ - static Bitmap createBitmap(Bitmap source, int x, int y, int width, int height) { - if (Build.VERSION.SDK_INT >= VERSION_CODES.O && source.getConfig() == Config.HARDWARE) { - return createHardwareBitmap(width, height, c -> c.drawBitmap(source, - new Rect(x, y, x + width, y + height), new RectF(0, 0, width, height), null)); - } else { - GraphicsUtils.noteNewBitmapCreated(); - return Bitmap.createBitmap(source, x, y, width, height); - } - } - - void draw(Canvas out); -} diff --git a/iconloaderlib/src/com/android/launcher3/icons/ColorExtractor.java b/iconloaderlib/src/com/android/launcher3/icons/ColorExtractor.java deleted file mode 100644 index 87bda825cc..0000000000 --- a/iconloaderlib/src/com/android/launcher3/icons/ColorExtractor.java +++ /dev/null @@ -1,127 +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.icons; - -import android.graphics.Bitmap; -import android.graphics.Color; -import android.util.SparseArray; -import java.util.Arrays; - -/** - * Utility class for extracting colors from a bitmap. - */ -public class ColorExtractor { - - private final int NUM_SAMPLES = 20; - private final float[] mTmpHsv = new float[3]; - private final float[] mTmpHueScoreHistogram = new float[360]; - private final int[] mTmpPixels = new int[NUM_SAMPLES]; - private final SparseArray mTmpRgbScores = new SparseArray<>(); - - /** - * This picks a dominant color, looking for high-saturation, high-value, repeated hues. - * @param bitmap The bitmap to scan - */ - public int findDominantColorByHue(Bitmap bitmap) { - return findDominantColorByHue(bitmap, NUM_SAMPLES); - } - - /** - * This picks a dominant color, looking for high-saturation, high-value, repeated hues. - * @param bitmap The bitmap to scan - */ - public int findDominantColorByHue(Bitmap bitmap, int samples) { - final int height = bitmap.getHeight(); - final int width = bitmap.getWidth(); - int sampleStride = (int) Math.sqrt((height * width) / samples); - if (sampleStride < 1) { - sampleStride = 1; - } - - // This is an out-param, for getting the hsv values for an rgb - float[] hsv = mTmpHsv; - Arrays.fill(hsv, 0); - - // First get the best hue, by creating a histogram over 360 hue buckets, - // where each pixel contributes a score weighted by saturation, value, and alpha. - float[] hueScoreHistogram = mTmpHueScoreHistogram; - Arrays.fill(hueScoreHistogram, 0); - float highScore = -1; - int bestHue = -1; - - int[] pixels = mTmpPixels; - Arrays.fill(pixels, 0); - int pixelCount = 0; - - for (int y = 0; y < height; y += sampleStride) { - for (int x = 0; x < width; x += sampleStride) { - int argb = bitmap.getPixel(x, y); - int alpha = 0xFF & (argb >> 24); - if (alpha < 0x80) { - // Drop mostly-transparent pixels. - continue; - } - // Remove the alpha channel. - int rgb = argb | 0xFF000000; - Color.colorToHSV(rgb, hsv); - // Bucket colors by the 360 integer hues. - int hue = (int) hsv[0]; - if (hue < 0 || hue >= hueScoreHistogram.length) { - // Defensively avoid array bounds violations. - continue; - } - if (pixelCount < samples) { - pixels[pixelCount++] = rgb; - } - float score = hsv[1] * hsv[2]; - hueScoreHistogram[hue] += score; - if (hueScoreHistogram[hue] > highScore) { - highScore = hueScoreHistogram[hue]; - bestHue = hue; - } - } - } - - SparseArray rgbScores = mTmpRgbScores; - rgbScores.clear(); - int bestColor = 0xff000000; - highScore = -1; - // Go back over the RGB colors that match the winning hue, - // creating a histogram of weighted s*v scores, for up to 100*100 [s,v] buckets. - // The highest-scoring RGB color wins. - for (int i = 0; i < pixelCount; i++) { - int rgb = pixels[i]; - Color.colorToHSV(rgb, hsv); - int hue = (int) hsv[0]; - if (hue == bestHue) { - float s = hsv[1]; - float v = hsv[2]; - int bucket = (int) (s * 100) + (int) (v * 10000); - // Score by cumulative saturation * value. - float score = s * v; - Float oldTotal = rgbScores.get(bucket); - float newTotal = oldTotal == null ? score : oldTotal + score; - rgbScores.put(bucket, newTotal); - if (newTotal > highScore) { - highScore = newTotal; - // All the colors in the winning bucket are very similar. Last in wins. - bestColor = rgb; - } - } - } - return bestColor; - } -} diff --git a/iconloaderlib/src/com/android/launcher3/icons/DotRenderer.java b/iconloaderlib/src/com/android/launcher3/icons/DotRenderer.java deleted file mode 100644 index 97a0fd3ffc..0000000000 --- a/iconloaderlib/src/com/android/launcher3/icons/DotRenderer.java +++ /dev/null @@ -1,143 +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.icons; - -import static android.graphics.Paint.ANTI_ALIAS_FLAG; -import static android.graphics.Paint.FILTER_BITMAP_FLAG; - -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.Path; -import android.graphics.PathMeasure; -import android.graphics.Rect; -import android.graphics.RectF; -import android.util.Log; -import android.view.ViewDebug; - -/** - * Used to draw a notification dot on top of an icon. - */ -public class DotRenderer { - - private static final String TAG = "DotRenderer"; - - // The dot size is defined as a percentage of the app icon size. - private static final float SIZE_PERCENTAGE = 0.228f; - - private final float mCircleRadius; - private final Paint mCirclePaint = new Paint(ANTI_ALIAS_FLAG | FILTER_BITMAP_FLAG); - - private final Bitmap mBackgroundWithShadow; - private final float mBitmapOffset; - - // Stores the center x and y position as a percentage (0 to 1) of the icon size - private final float[] mRightDotPosition; - private final float[] mLeftDotPosition; - - public DotRenderer(int iconSizePx, Path iconShapePath, int pathSize) { - int size = Math.round(SIZE_PERCENTAGE * iconSizePx); - ShadowGenerator.Builder builder = new ShadowGenerator.Builder(Color.TRANSPARENT); - builder.ambientShadowAlpha = 88; - mBackgroundWithShadow = builder.setupBlurForSize(size).createPill(size, size); - mCircleRadius = builder.radius; - - mBitmapOffset = -mBackgroundWithShadow.getHeight() * 0.5f; // Same as width. - - // Find the points on the path that are closest to the top left and right corners. - mLeftDotPosition = getPathPoint(iconShapePath, pathSize, -1); - mRightDotPosition = getPathPoint(iconShapePath, pathSize, 1); - } - - private static float[] getPathPoint(Path path, float size, float direction) { - float halfSize = size / 2; - // Small delta so that we don't get a zero size triangle - float delta = 1; - - float x = halfSize + direction * halfSize; - Path trianglePath = new Path(); - trianglePath.moveTo(halfSize, halfSize); - trianglePath.lineTo(x + delta * direction, 0); - trianglePath.lineTo(x, -delta); - trianglePath.close(); - - trianglePath.op(path, Path.Op.INTERSECT); - float[] pos = new float[2]; - new PathMeasure(trianglePath, false).getPosTan(0, pos, null); - - pos[0] = pos[0] / size; - pos[1] = pos[1] / size; - return pos; - } - - public float[] getLeftDotPosition() { - return mLeftDotPosition; - } - - public float[] getRightDotPosition() { - return mRightDotPosition; - } - - /** - * Draw a circle on top of the canvas according to the given params. - */ - public void draw(Canvas canvas, DrawParams params) { - if (params == null) { - Log.e(TAG, "Invalid null argument(s) passed in call to draw."); - return; - } - canvas.save(); - - Rect iconBounds = params.iconBounds; - float[] dotPosition = params.leftAlign ? mLeftDotPosition : mRightDotPosition; - float dotCenterX = iconBounds.left + iconBounds.width() * dotPosition[0]; - float dotCenterY = iconBounds.top + iconBounds.height() * dotPosition[1]; - - // Ensure dot fits entirely in canvas clip bounds. - Rect canvasBounds = canvas.getClipBounds(); - float offsetX = params.leftAlign - ? Math.max(0, canvasBounds.left - (dotCenterX + mBitmapOffset)) - : Math.min(0, canvasBounds.right - (dotCenterX - mBitmapOffset)); - float offsetY = Math.max(0, canvasBounds.top - (dotCenterY + mBitmapOffset)); - - // We draw the dot relative to its center. - canvas.translate(dotCenterX + offsetX, dotCenterY + offsetY); - canvas.scale(params.scale, params.scale); - - mCirclePaint.setColor(Color.BLACK); - canvas.drawBitmap(mBackgroundWithShadow, mBitmapOffset, mBitmapOffset, mCirclePaint); - mCirclePaint.setColor(params.color); - canvas.drawCircle(0, 0, mCircleRadius, mCirclePaint); - canvas.restore(); - } - - public static class DrawParams { - /** The color (possibly based on the icon) to use for the dot. */ - @ViewDebug.ExportedProperty(category = "notification dot", formatToHexString = true) - public int color; - /** The bounds of the icon that the dot is drawn on top of. */ - @ViewDebug.ExportedProperty(category = "notification dot") - public Rect iconBounds = new Rect(); - /** The progress of the animation, from 0 to 1. */ - @ViewDebug.ExportedProperty(category = "notification dot") - public float scale; - /** Whether the dot should align to the top left of the icon rather than the top right. */ - @ViewDebug.ExportedProperty(category = "notification dot") - public boolean leftAlign; - } -} diff --git a/iconloaderlib/src/com/android/launcher3/icons/FixedScaleDrawable.java b/iconloaderlib/src/com/android/launcher3/icons/FixedScaleDrawable.java deleted file mode 100644 index 516965ec2b..0000000000 --- a/iconloaderlib/src/com/android/launcher3/icons/FixedScaleDrawable.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.android.launcher3.icons; - -import android.content.res.Resources; -import android.content.res.Resources.Theme; -import android.graphics.Canvas; -import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.DrawableWrapper; -import android.util.AttributeSet; - -import org.xmlpull.v1.XmlPullParser; - -/** - * Extension of {@link DrawableWrapper} which scales the child drawables by a fixed amount. - */ -public class FixedScaleDrawable extends DrawableWrapper { - - // TODO b/33553066 use the constant defined in MaskableIconDrawable - private static final float LEGACY_ICON_SCALE = .7f * .6667f; - private float mScaleX, mScaleY; - - public FixedScaleDrawable() { - super(new ColorDrawable()); - mScaleX = LEGACY_ICON_SCALE; - mScaleY = LEGACY_ICON_SCALE; - } - - @Override - public void draw(Canvas canvas) { - int saveCount = canvas.save(); - canvas.scale(mScaleX, mScaleY, - getBounds().exactCenterX(), getBounds().exactCenterY()); - super.draw(canvas); - canvas.restoreToCount(saveCount); - } - - @Override - public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) { } - - @Override - public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) { } - - public void setScale(float scale) { - float h = getIntrinsicHeight(); - float w = getIntrinsicWidth(); - mScaleX = scale * LEGACY_ICON_SCALE; - mScaleY = scale * LEGACY_ICON_SCALE; - if (h > w && w > 0) { - mScaleX *= w / h; - } else if (w > h && h > 0) { - mScaleY *= h / w; - } - } -} diff --git a/iconloaderlib/src/com/android/launcher3/icons/GraphicsUtils.java b/iconloaderlib/src/com/android/launcher3/icons/GraphicsUtils.java deleted file mode 100644 index 22f1f2357e..0000000000 --- a/iconloaderlib/src/com/android/launcher3/icons/GraphicsUtils.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.launcher3.icons; - -import android.graphics.Bitmap; -import android.graphics.Rect; -import android.graphics.Region; -import android.graphics.RegionIterator; -import android.util.Log; - -import androidx.annotation.ColorInt; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; - -public class GraphicsUtils { - - private static final String TAG = "GraphicsUtils"; - - public static Runnable sOnNewBitmapRunnable = () -> { }; - - /** - * Set the alpha component of {@code color} to be {@code alpha}. Unlike the support lib version, - * it bounds the alpha in valid range instead of throwing an exception to allow for safer - * interpolation of color animations - */ - @ColorInt - public static int setColorAlphaBound(int color, int alpha) { - if (alpha < 0) { - alpha = 0; - } else if (alpha > 255) { - alpha = 255; - } - return (color & 0x00ffffff) | (alpha << 24); - } - - /** - * Compresses the bitmap to a byte array for serialization. - */ - public static byte[] flattenBitmap(Bitmap bitmap) { - // Try go guesstimate how much space the icon will take when serialized - // to avoid unnecessary allocations/copies during the write (4 bytes per pixel). - int size = bitmap.getWidth() * bitmap.getHeight() * 4; - ByteArrayOutputStream out = new ByteArrayOutputStream(size); - try { - bitmap.compress(Bitmap.CompressFormat.PNG, 100, out); - out.flush(); - out.close(); - return out.toByteArray(); - } catch (IOException e) { - Log.w(TAG, "Could not write bitmap"); - return null; - } - } - - public static int getArea(Region r) { - RegionIterator itr = new RegionIterator(r); - int area = 0; - Rect tempRect = new Rect(); - while (itr.next(tempRect)) { - area += tempRect.width() * tempRect.height(); - } - return area; - } - - /** - * Utility method to track new bitmap creation - */ - public static void noteNewBitmapCreated() { - sOnNewBitmapRunnable.run(); - } -} diff --git a/iconloaderlib/src/com/android/launcher3/icons/IconNormalizer.java b/iconloaderlib/src/com/android/launcher3/icons/IconNormalizer.java deleted file mode 100644 index de39e79fec..0000000000 --- a/iconloaderlib/src/com/android/launcher3/icons/IconNormalizer.java +++ /dev/null @@ -1,411 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS 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.res.Resources; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Matrix; -import android.graphics.Paint; -import android.graphics.Path; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffXfermode; -import android.graphics.Rect; -import android.graphics.RectF; -import android.graphics.Region; -import android.graphics.drawable.AdaptiveIconDrawable; -import android.graphics.drawable.Drawable; -import android.os.Build; -import android.util.Log; - -import java.nio.ByteBuffer; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -public class IconNormalizer { - - private static final String TAG = "IconNormalizer"; - private static final boolean DEBUG = false; - // Ratio of icon visible area to full icon size for a square shaped icon - private static final float MAX_SQUARE_AREA_FACTOR = 375.0f / 576; - // Ratio of icon visible area to full icon size for a circular shaped icon - private static final float MAX_CIRCLE_AREA_FACTOR = 380.0f / 576; - - private static final float CIRCLE_AREA_BY_RECT = (float) Math.PI / 4; - - // Slope used to calculate icon visible area to full icon size for any generic shaped icon. - private static final float LINEAR_SCALE_SLOPE = - (MAX_CIRCLE_AREA_FACTOR - MAX_SQUARE_AREA_FACTOR) / (1 - CIRCLE_AREA_BY_RECT); - - private static final int MIN_VISIBLE_ALPHA = 40; - - // Shape detection related constants - private static final float BOUND_RATIO_MARGIN = .05f; - private static final float PIXEL_DIFF_PERCENTAGE_THRESHOLD = 0.005f; - private static final float SCALE_NOT_INITIALIZED = 0; - - // Ratio of the diameter of an normalized circular icon to the actual icon size. - public static final float ICON_VISIBLE_AREA_FACTOR = 0.92f; - - private final int mMaxSize; - private final Bitmap mBitmap; - private final Canvas mCanvas; - private final Paint mPaintMaskShape; - private final Paint mPaintMaskShapeOutline; - private final byte[] mPixels; - - private final RectF mAdaptiveIconBounds; - private float mAdaptiveIconScale; - - private boolean mEnableShapeDetection; - - // for each y, stores the position of the leftmost x and the rightmost x - private final float[] mLeftBorder; - private final float[] mRightBorder; - private final Rect mBounds; - private final Path mShapePath; - private final Matrix mMatrix; - - /** package private **/ - IconNormalizer(Context context, int iconBitmapSize, boolean shapeDetection) { - // Use twice the icon size as maximum size to avoid scaling down twice. - mMaxSize = iconBitmapSize * 2; - mBitmap = Bitmap.createBitmap(mMaxSize, mMaxSize, Bitmap.Config.ALPHA_8); - mCanvas = new Canvas(mBitmap); - mPixels = new byte[mMaxSize * mMaxSize]; - mLeftBorder = new float[mMaxSize]; - mRightBorder = new float[mMaxSize]; - mBounds = new Rect(); - mAdaptiveIconBounds = new RectF(); - - mPaintMaskShape = new Paint(); - mPaintMaskShape.setColor(Color.RED); - mPaintMaskShape.setStyle(Paint.Style.FILL); - mPaintMaskShape.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.XOR)); - - mPaintMaskShapeOutline = new Paint(); - mPaintMaskShapeOutline.setStrokeWidth( - 2 * context.getResources().getDisplayMetrics().density); - mPaintMaskShapeOutline.setStyle(Paint.Style.STROKE); - mPaintMaskShapeOutline.setColor(Color.BLACK); - mPaintMaskShapeOutline.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); - - mShapePath = new Path(); - mMatrix = new Matrix(); - mAdaptiveIconScale = SCALE_NOT_INITIALIZED; - mEnableShapeDetection = shapeDetection; - } - - private static float getScale(float hullArea, float boundingArea, float fullArea) { - float hullByRect = hullArea / boundingArea; - float scaleRequired; - if (hullByRect < CIRCLE_AREA_BY_RECT) { - scaleRequired = MAX_CIRCLE_AREA_FACTOR; - } else { - scaleRequired = MAX_SQUARE_AREA_FACTOR + LINEAR_SCALE_SLOPE * (1 - hullByRect); - } - - float areaScale = hullArea / fullArea; - // Use sqrt of the final ratio as the images is scaled across both width and height. - return areaScale > scaleRequired ? (float) Math.sqrt(scaleRequired / areaScale) : 1; - } - - /** - * @param d Should be AdaptiveIconDrawable - * @param size Canvas size to use - */ - @TargetApi(Build.VERSION_CODES.O) - public static float normalizeAdaptiveIcon(Drawable d, int size, @Nullable RectF outBounds) { - Rect tmpBounds = new Rect(d.getBounds()); - d.setBounds(0, 0, size, size); - - Path path = ((AdaptiveIconDrawable) d).getIconMask(); - Region region = new Region(); - region.setPath(path, new Region(0, 0, size, size)); - - Rect hullBounds = region.getBounds(); - int hullArea = GraphicsUtils.getArea(region); - - if (outBounds != null) { - float sizeF = size; - outBounds.set( - hullBounds.left / sizeF, - hullBounds.top / sizeF, - 1 - (hullBounds.right / sizeF), - 1 - (hullBounds.bottom / sizeF)); - } - d.setBounds(tmpBounds); - return getScale(hullArea, hullArea, size * size); - } - - /** - * Returns if the shape of the icon is same as the path. - * For this method to work, the shape path bounds should be in [0,1]x[0,1] bounds. - */ - private boolean isShape(Path maskPath) { - // Condition1: - // If width and height of the path not close to a square, then the icon shape is - // not same as the mask shape. - float iconRatio = ((float) mBounds.width()) / mBounds.height(); - if (Math.abs(iconRatio - 1) > BOUND_RATIO_MARGIN) { - if (DEBUG) { - Log.d(TAG, "Not same as mask shape because width != height. " + iconRatio); - } - return false; - } - - // Condition 2: - // Actual icon (white) and the fitted shape (e.g., circle)(red) XOR operation - // should generate transparent image, if the actual icon is equivalent to the shape. - - // Fit the shape within the icon's bounding box - mMatrix.reset(); - mMatrix.setScale(mBounds.width(), mBounds.height()); - mMatrix.postTranslate(mBounds.left, mBounds.top); - maskPath.transform(mMatrix, mShapePath); - - // XOR operation - mCanvas.drawPath(mShapePath, mPaintMaskShape); - - // DST_OUT operation around the mask path outline - mCanvas.drawPath(mShapePath, mPaintMaskShapeOutline); - - // Check if the result is almost transparent - return isTransparentBitmap(); - } - - /** - * Used to determine if certain the bitmap is transparent. - */ - private boolean isTransparentBitmap() { - ByteBuffer buffer = ByteBuffer.wrap(mPixels); - buffer.rewind(); - mBitmap.copyPixelsToBuffer(buffer); - - int y = mBounds.top; - // buffer position - int index = y * mMaxSize; - // buffer shift after every row, width of buffer = mMaxSize - int rowSizeDiff = mMaxSize - mBounds.right; - - int sum = 0; - for (; y < mBounds.bottom; y++) { - index += mBounds.left; - for (int x = mBounds.left; x < mBounds.right; x++) { - if ((mPixels[index] & 0xFF) > MIN_VISIBLE_ALPHA) { - sum++; - } - index++; - } - index += rowSizeDiff; - } - - float percentageDiffPixels = ((float) sum) / (mBounds.width() * mBounds.height()); - return percentageDiffPixels < PIXEL_DIFF_PERCENTAGE_THRESHOLD; - } - - /** - * Returns the amount by which the {@param d} should be scaled (in both dimensions) so that it - * matches the design guidelines for a launcher icon. - * - * We first calculate the convex hull of the visible portion of the icon. - * This hull then compared with the bounding rectangle of the hull to find how closely it - * resembles a circle and a square, by comparing the ratio of the areas. Note that this is not an - * ideal solution but it gives satisfactory result without affecting the performance. - * - * This closeness is used to determine the ratio of hull area to the full icon size. - * Refer {@link #MAX_CIRCLE_AREA_FACTOR} and {@link #MAX_SQUARE_AREA_FACTOR} - * - * @param outBounds optional rect to receive the fraction distance from each edge. - */ - public synchronized float getScale(@NonNull Drawable d, @Nullable RectF outBounds, - @Nullable Path path, @Nullable boolean[] outMaskShape) { - if (BaseIconFactory.ATLEAST_OREO && d instanceof AdaptiveIconDrawable) { - if (mAdaptiveIconScale == SCALE_NOT_INITIALIZED) { - mAdaptiveIconScale = normalizeAdaptiveIcon(d, mMaxSize, mAdaptiveIconBounds); - } - if (outBounds != null) { - outBounds.set(mAdaptiveIconBounds); - } - return mAdaptiveIconScale; - } - int width = d.getIntrinsicWidth(); - int height = d.getIntrinsicHeight(); - if (width <= 0 || height <= 0) { - width = width <= 0 || width > mMaxSize ? mMaxSize : width; - height = height <= 0 || height > mMaxSize ? mMaxSize : height; - } else if (width > mMaxSize || height > mMaxSize) { - int max = Math.max(width, height); - width = mMaxSize * width / max; - height = mMaxSize * height / max; - } - - mBitmap.eraseColor(Color.TRANSPARENT); - d.setBounds(0, 0, width, height); - d.draw(mCanvas); - - ByteBuffer buffer = ByteBuffer.wrap(mPixels); - buffer.rewind(); - mBitmap.copyPixelsToBuffer(buffer); - - // Overall bounds of the visible icon. - int topY = -1; - int bottomY = -1; - int leftX = mMaxSize + 1; - int rightX = -1; - - // Create border by going through all pixels one row at a time and for each row find - // the first and the last non-transparent pixel. Set those values to mLeftBorder and - // mRightBorder and use -1 if there are no visible pixel in the row. - - // buffer position - int index = 0; - // buffer shift after every row, width of buffer = mMaxSize - int rowSizeDiff = mMaxSize - width; - // first and last position for any row. - int firstX, lastX; - - for (int y = 0; y < height; y++) { - firstX = lastX = -1; - for (int x = 0; x < width; x++) { - if ((mPixels[index] & 0xFF) > MIN_VISIBLE_ALPHA) { - if (firstX == -1) { - firstX = x; - } - lastX = x; - } - index++; - } - index += rowSizeDiff; - - mLeftBorder[y] = firstX; - mRightBorder[y] = lastX; - - // If there is at least one visible pixel, update the overall bounds. - if (firstX != -1) { - bottomY = y; - if (topY == -1) { - topY = y; - } - - leftX = Math.min(leftX, firstX); - rightX = Math.max(rightX, lastX); - } - } - - if (topY == -1 || rightX == -1) { - // No valid pixels found. Do not scale. - return 1; - } - - convertToConvexArray(mLeftBorder, 1, topY, bottomY); - convertToConvexArray(mRightBorder, -1, topY, bottomY); - - // Area of the convex hull - float area = 0; - for (int y = 0; y < height; y++) { - if (mLeftBorder[y] <= -1) { - continue; - } - area += mRightBorder[y] - mLeftBorder[y] + 1; - } - - mBounds.left = leftX; - mBounds.right = rightX; - - mBounds.top = topY; - mBounds.bottom = bottomY; - - if (outBounds != null) { - outBounds.set(((float) mBounds.left) / width, ((float) mBounds.top) / height, - 1 - ((float) mBounds.right) / width, - 1 - ((float) mBounds.bottom) / height); - } - if (outMaskShape != null && mEnableShapeDetection && outMaskShape.length > 0) { - outMaskShape[0] = isShape(path); - } - // Area of the rectangle required to fit the convex hull - float rectArea = (bottomY + 1 - topY) * (rightX + 1 - leftX); - return getScale(area, rectArea, width * height); - } - - /** - * Modifies {@param xCoordinates} to represent a convex border. Fills in all missing values - * (except on either ends) with appropriate values. - * @param xCoordinates map of x coordinate per y. - * @param direction 1 for left border and -1 for right border. - * @param topY the first Y position (inclusive) with a valid value. - * @param bottomY the last Y position (inclusive) with a valid value. - */ - private static void convertToConvexArray( - float[] xCoordinates, int direction, int topY, int bottomY) { - int total = xCoordinates.length; - // The tangent at each pixel. - float[] angles = new float[total - 1]; - - int first = topY; // First valid y coordinate - int last = -1; // Last valid y coordinate which didn't have a missing value - - float lastAngle = Float.MAX_VALUE; - - for (int i = topY + 1; i <= bottomY; i++) { - if (xCoordinates[i] <= -1) { - continue; - } - int start; - - if (lastAngle == Float.MAX_VALUE) { - start = first; - } else { - float currentAngle = (xCoordinates[i] - xCoordinates[last]) / (i - last); - start = last; - // If this position creates a concave angle, keep moving up until we find a - // position which creates a convex angle. - if ((currentAngle - lastAngle) * direction < 0) { - while (start > first) { - start --; - currentAngle = (xCoordinates[i] - xCoordinates[start]) / (i - start); - if ((currentAngle - angles[start]) * direction >= 0) { - break; - } - } - } - } - - // Reset from last check - lastAngle = (xCoordinates[i] - xCoordinates[start]) / (i - start); - // Update all the points from start. - for (int j = start; j < i; j++) { - angles[j] = lastAngle; - xCoordinates[j] = xCoordinates[start] + lastAngle * (j - start); - } - last = i; - } - } - - /** - * @return The diameter of the normalized circle that fits inside of the square (size x size). - */ - public static int getNormalizedCircleSize(int size) { - float area = size * size * MAX_CIRCLE_AREA_FACTOR; - return (int) Math.round(Math.sqrt((4 * area) / Math.PI)); - } -} diff --git a/iconloaderlib/src/com/android/launcher3/icons/ShadowGenerator.java b/iconloaderlib/src/com/android/launcher3/icons/ShadowGenerator.java deleted file mode 100644 index 770272728d..0000000000 --- a/iconloaderlib/src/com/android/launcher3/icons/ShadowGenerator.java +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher3.icons; - -import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound; - -import android.graphics.Bitmap; -import android.graphics.BlurMaskFilter; -import android.graphics.BlurMaskFilter.Blur; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffXfermode; -import android.graphics.RectF; - -/** - * Utility class to add shadows to bitmaps. - */ -public class ShadowGenerator { - public static final float BLUR_FACTOR = 0.5f/48; - - // Percent of actual icon size - public static final float KEY_SHADOW_DISTANCE = 1f/48; - private static final int KEY_SHADOW_ALPHA = 61; - // Percent of actual icon size - private static final float HALF_DISTANCE = 0.5f; - private static final int AMBIENT_SHADOW_ALPHA = 30; - - private final int mIconSize; - - private final Paint mBlurPaint; - private final Paint mDrawPaint; - private final BlurMaskFilter mDefaultBlurMaskFilter; - - public ShadowGenerator(int iconSize) { - mIconSize = iconSize; - mBlurPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); - mDrawPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); - mDefaultBlurMaskFilter = new BlurMaskFilter(mIconSize * BLUR_FACTOR, Blur.NORMAL); - } - - public synchronized void recreateIcon(Bitmap icon, Canvas out) { - recreateIcon(icon, mDefaultBlurMaskFilter, AMBIENT_SHADOW_ALPHA, KEY_SHADOW_ALPHA, out); - } - - public synchronized void recreateIcon(Bitmap icon, BlurMaskFilter blurMaskFilter, - int ambientAlpha, int keyAlpha, Canvas out) { - int[] offset = new int[2]; - mBlurPaint.setMaskFilter(blurMaskFilter); - Bitmap shadow = icon.extractAlpha(mBlurPaint, offset); - - // Draw ambient shadow - mDrawPaint.setAlpha(ambientAlpha); - out.drawBitmap(shadow, offset[0], offset[1], mDrawPaint); - - // Draw key shadow - mDrawPaint.setAlpha(keyAlpha); - out.drawBitmap(shadow, offset[0], offset[1] + KEY_SHADOW_DISTANCE * mIconSize, mDrawPaint); - - // Draw the icon - mDrawPaint.setAlpha(255); - out.drawBitmap(icon, 0, 0, mDrawPaint); - } - - /** - * Returns the minimum amount by which an icon with {@param bounds} should be scaled - * so that the shadows do not get clipped. - */ - public static float getScaleForBounds(RectF bounds) { - float scale = 1; - - // For top, left & right, we need same space. - float minSide = Math.min(Math.min(bounds.left, bounds.right), bounds.top); - if (minSide < BLUR_FACTOR) { - scale = (HALF_DISTANCE - BLUR_FACTOR) / (HALF_DISTANCE - minSide); - } - - float bottomSpace = BLUR_FACTOR + KEY_SHADOW_DISTANCE; - if (bounds.bottom < bottomSpace) { - scale = Math.min(scale, (HALF_DISTANCE - bottomSpace) / (HALF_DISTANCE - bounds.bottom)); - } - return scale; - } - - public static class Builder { - - public final RectF bounds = new RectF(); - public final int color; - - public int ambientShadowAlpha = AMBIENT_SHADOW_ALPHA; - - public float shadowBlur; - - public float keyShadowDistance; - public int keyShadowAlpha = KEY_SHADOW_ALPHA; - public float radius; - - public Builder(int color) { - this.color = color; - } - - public Builder setupBlurForSize(int height) { - shadowBlur = height * 1f / 24; - keyShadowDistance = height * 1f / 16; - return this; - } - - public Bitmap createPill(int width, int height) { - return createPill(width, height, height / 2f); - } - - public Bitmap createPill(int width, int height, float r) { - radius = r; - - int centerX = Math.round(width / 2f + shadowBlur); - int centerY = Math.round(radius + shadowBlur + keyShadowDistance); - int center = Math.max(centerX, centerY); - bounds.set(0, 0, width, height); - bounds.offsetTo(center - width / 2f, center - height / 2f); - - int size = center * 2; - return BitmapRenderer.createHardwareBitmap(size, size, this::drawShadow); - } - - public void drawShadow(Canvas c) { - Paint p = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); - p.setColor(color); - - // Key shadow - p.setShadowLayer(shadowBlur, 0, keyShadowDistance, - setColorAlphaBound(Color.BLACK, keyShadowAlpha)); - c.drawRoundRect(bounds, radius, radius, p); - - // Ambient shadow - p.setShadowLayer(shadowBlur, 0, 0, - setColorAlphaBound(Color.BLACK, ambientShadowAlpha)); - c.drawRoundRect(bounds, radius, radius, p); - - if (Color.alpha(color) < 255) { - // Clear any content inside the pill-rect for translucent fill. - p.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); - p.clearShadowLayer(); - p.setColor(Color.BLACK); - c.drawRoundRect(bounds, radius, radius, p); - - p.setXfermode(null); - p.setColor(color); - c.drawRoundRect(bounds, radius, radius, p); - } - } - } -} diff --git a/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java b/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java deleted file mode 100644 index 4c634cbc26..0000000000 --- a/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java +++ /dev/null @@ -1,582 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.launcher3.icons.cache; - -import static com.android.launcher3.icons.BaseIconFactory.getFullResDefaultActivityIcon; -import static com.android.launcher3.icons.BitmapInfo.LOW_RES_ICON; -import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound; - -import android.content.ComponentName; -import android.content.ContentValues; -import android.content.Context; -import android.content.pm.ActivityInfo; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.content.res.Resources; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteException; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.drawable.Drawable; -import android.os.Build; -import android.os.Handler; -import android.os.LocaleList; -import android.os.Looper; -import android.os.Process; -import android.os.UserHandle; -import android.text.TextUtils; -import android.util.Log; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; - -import com.android.launcher3.icons.BaseIconFactory; -import com.android.launcher3.icons.BitmapInfo; -import com.android.launcher3.icons.BitmapRenderer; -import com.android.launcher3.icons.GraphicsUtils; -import com.android.launcher3.util.ComponentKey; -import com.android.launcher3.util.SQLiteCacheHelper; - -import java.util.AbstractMap; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.function.Supplier; - -public abstract class BaseIconCache { - - private static final String TAG = "BaseIconCache"; - private static final boolean DEBUG = false; - - private static final int INITIAL_ICON_CACHE_CAPACITY = 50; - - // Empty class name is used for storing package default entry. - public static final String EMPTY_CLASS_NAME = "."; - - public static class CacheEntry { - - @NonNull - public BitmapInfo bitmap = BitmapInfo.LOW_RES_INFO; - public CharSequence title = ""; - public CharSequence contentDescription = ""; - } - - private final HashMap mDefaultIcons = new HashMap<>(); - - protected final Context mContext; - protected final PackageManager mPackageManager; - - private final Map mCache; - protected final Handler mWorkerHandler; - - protected int mIconDpi; - protected IconDB mIconDb; - protected LocaleList mLocaleList = LocaleList.getEmptyLocaleList(); - protected String mSystemState = ""; - - private final String mDbFileName; - private final BitmapFactory.Options mDecodeOptions; - private final Looper mBgLooper; - - public BaseIconCache(Context context, String dbFileName, Looper bgLooper, - int iconDpi, int iconPixelSize, boolean inMemoryCache) { - mContext = context; - mDbFileName = dbFileName; - mPackageManager = context.getPackageManager(); - mBgLooper = bgLooper; - mWorkerHandler = new Handler(mBgLooper); - - if (inMemoryCache) { - mCache = new HashMap<>(INITIAL_ICON_CACHE_CAPACITY); - } else { - // Use a dummy cache - mCache = new AbstractMap() { - @Override - public Set> entrySet() { - return Collections.emptySet(); - } - - @Override - public CacheEntry put(ComponentKey key, CacheEntry value) { - return value; - } - }; - } - - if (BitmapRenderer.USE_HARDWARE_BITMAP && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - mDecodeOptions = new BitmapFactory.Options(); - mDecodeOptions.inPreferredConfig = Bitmap.Config.HARDWARE; - } else { - mDecodeOptions = null; - } - - updateSystemState(); - mIconDpi = iconDpi; - mIconDb = new IconDB(context, dbFileName, iconPixelSize); - } - - /** - * Returns the persistable serial number for {@param user}. Subclass should implement proper - * caching strategy to avoid making binder call every time. - */ - protected abstract long getSerialNumberForUser(UserHandle user); - - /** - * Return true if the given app is an instant app and should be badged appropriately. - */ - protected abstract boolean isInstantApp(ApplicationInfo info); - - /** - * Opens and returns an icon factory. The factory is recycled by the caller. - */ - protected abstract BaseIconFactory getIconFactory(); - - public void updateIconParams(int iconDpi, int iconPixelSize) { - mWorkerHandler.post(() -> updateIconParamsBg(iconDpi, iconPixelSize)); - } - - private synchronized void updateIconParamsBg(int iconDpi, int iconPixelSize) { - mIconDpi = iconDpi; - mDefaultIcons.clear(); - mIconDb.clear(); - mIconDb.close(); - mIconDb = new IconDB(mContext, mDbFileName, iconPixelSize); - mCache.clear(); - } - - private Drawable getFullResIcon(Resources resources, int iconId) { - if (resources != null && iconId != 0) { - try { - return resources.getDrawableForDensity(iconId, mIconDpi); - } catch (Resources.NotFoundException e) { } - } - return getFullResDefaultActivityIcon(mIconDpi); - } - - public Drawable getFullResIcon(String packageName, int iconId) { - try { - return getFullResIcon(mPackageManager.getResourcesForApplication(packageName), iconId); - } catch (PackageManager.NameNotFoundException e) { } - return getFullResDefaultActivityIcon(mIconDpi); - } - - public Drawable getFullResIcon(ActivityInfo info) { - try { - return getFullResIcon(mPackageManager.getResourcesForApplication(info.applicationInfo), - info.getIconResource()); - } catch (PackageManager.NameNotFoundException e) { } - return getFullResDefaultActivityIcon(mIconDpi); - } - - private BitmapInfo makeDefaultIcon(UserHandle user) { - try (BaseIconFactory li = getIconFactory()) { - return li.makeDefaultIcon(user); - } - } - - /** - * Remove any records for the supplied ComponentName. - */ - public synchronized void remove(ComponentName componentName, UserHandle user) { - mCache.remove(new ComponentKey(componentName, user)); - } - - /** - * Remove any records for the supplied package name from memory. - */ - private void removeFromMemCacheLocked(String packageName, UserHandle user) { - HashSet forDeletion = new HashSet<>(); - for (ComponentKey key: mCache.keySet()) { - if (key.componentName.getPackageName().equals(packageName) - && key.user.equals(user)) { - forDeletion.add(key); - } - } - for (ComponentKey condemned: forDeletion) { - mCache.remove(condemned); - } - } - - /** - * Removes the entries related to the given package in memory and persistent DB. - */ - public synchronized void removeIconsForPkg(String packageName, UserHandle user) { - removeFromMemCacheLocked(packageName, user); - long userSerial = getSerialNumberForUser(user); - mIconDb.delete( - IconDB.COLUMN_COMPONENT + " LIKE ? AND " + IconDB.COLUMN_USER + " = ?", - new String[]{packageName + "/%", Long.toString(userSerial)}); - } - - public IconCacheUpdateHandler getUpdateHandler() { - updateSystemState(); - return new IconCacheUpdateHandler(this); - } - - /** - * Refreshes the system state definition used to check the validity of the cache. It - * incorporates all the properties that can affect the cache like the list of enabled locale - * and system-version. - */ - private void updateSystemState() { - mLocaleList = mContext.getResources().getConfiguration().getLocales(); - mSystemState = mLocaleList.toLanguageTags() + "," + Build.VERSION.SDK_INT; - } - - protected String getIconSystemState(String packageName) { - return mSystemState; - } - - /** - * Adds an entry into the DB and the in-memory cache. - * @param replaceExisting if true, it will recreate the bitmap even if it already exists in - * the memory. This is useful then the previous bitmap was created using - * old data. - */ - @VisibleForTesting - public synchronized void addIconToDBAndMemCache(T object, CachingLogic cachingLogic, - PackageInfo info, long userSerial, boolean replaceExisting) { - UserHandle user = cachingLogic.getUser(object); - ComponentName componentName = cachingLogic.getComponent(object); - - final ComponentKey key = new ComponentKey(componentName, user); - CacheEntry entry = null; - if (!replaceExisting) { - entry = mCache.get(key); - // We can't reuse the entry if the high-res icon is not present. - if (entry == null || entry.bitmap.isNullOrLowRes()) { - entry = null; - } - } - if (entry == null) { - entry = new CacheEntry(); - 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.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.bitmap, entry.title.toString(), - componentName.getPackageName(), cachingLogic.getKeywords(object, mLocaleList)); - addIconToDB(values, componentName, info, userSerial, - cachingLogic.getLastUpdatedTime(object, info)); - } - - /** - * Updates {@param values} to contain versioning information and adds it to the DB. - * @param values {@link ContentValues} containing icon & title - */ - private void addIconToDB(ContentValues values, ComponentName key, - PackageInfo info, long userSerial, long lastUpdateTime) { - values.put(IconDB.COLUMN_COMPONENT, key.flattenToString()); - values.put(IconDB.COLUMN_USER, userSerial); - values.put(IconDB.COLUMN_LAST_UPDATED, lastUpdateTime); - values.put(IconDB.COLUMN_VERSION, info.versionCode); - mIconDb.insertOrReplace(values); - } - - public synchronized BitmapInfo getDefaultIcon(UserHandle user) { - if (!mDefaultIcons.containsKey(user)) { - mDefaultIcons.put(user, makeDefaultIcon(user)); - } - return mDefaultIcons.get(user); - } - - public boolean isDefaultIcon(BitmapInfo icon, UserHandle user) { - return getDefaultIcon(user).icon == icon.icon; - } - - /** - * Retrieves the entry from the cache. If the entry is not present, it creates a new entry. - * This method is not thread safe, it must be called from a synchronized method. - */ - protected CacheEntry cacheLocked( - @NonNull ComponentName componentName, @NonNull UserHandle user, - @NonNull Supplier infoProvider, @NonNull CachingLogic cachingLogic, - boolean usePackageIcon, boolean useLowResIcon) { - assertWorkerThread(); - ComponentKey cacheKey = new ComponentKey(componentName, user); - CacheEntry entry = mCache.get(cacheKey); - if (entry == null || (entry.bitmap.isLowRes() && !useLowResIcon)) { - entry = new CacheEntry(); - if (cachingLogic.addToMemCache()) { - mCache.put(cacheKey, entry); - } - - // Check the DB first. - T object = null; - boolean providerFetchedOnce = false; - - if (!getEntryFromDB(cacheKey, entry, useLowResIcon)) { - object = infoProvider.get(); - providerFetchedOnce = true; - - if (object != null) { - entry.bitmap = cachingLogic.loadIcon(mContext, object); - } else { - if (usePackageIcon) { - CacheEntry packageEntry = getEntryForPackageLocked( - componentName.getPackageName(), user, false); - if (packageEntry != null) { - if (DEBUG) Log.d(TAG, "using package default icon for " + - componentName.toShortString()); - entry.bitmap = packageEntry.bitmap; - entry.title = packageEntry.title; - entry.contentDescription = packageEntry.contentDescription; - } - } - if (entry.bitmap == null) { - if (DEBUG) Log.d(TAG, "using default icon for " + - componentName.toShortString()); - entry.bitmap = getDefaultIcon(user); - } - } - } - - if (TextUtils.isEmpty(entry.title)) { - if (object == null && !providerFetchedOnce) { - object = infoProvider.get(); - providerFetchedOnce = true; - } - if (object != null) { - entry.title = cachingLogic.getLabel(object); - entry.contentDescription = mPackageManager.getUserBadgedLabel( - cachingLogic.getDescription(object, entry.title), user); - } - } - } - return entry; - } - - public synchronized void clear() { - assertWorkerThread(); - mIconDb.clear(); - } - - /** - * Adds a default package entry in the cache. This entry is not persisted and will be removed - * when the cache is flushed. - */ - protected synchronized void cachePackageInstallInfo(String packageName, UserHandle user, - Bitmap icon, CharSequence title) { - removeFromMemCacheLocked(packageName, user); - - ComponentKey cacheKey = getPackageKey(packageName, user); - CacheEntry entry = mCache.get(cacheKey); - - // For icon caching, do not go through DB. Just update the in-memory entry. - if (entry == null) { - entry = new CacheEntry(); - } - if (!TextUtils.isEmpty(title)) { - entry.title = title; - } - if (icon != null) { - BaseIconFactory li = getIconFactory(); - entry.bitmap = li.createIconBitmap(icon); - li.close(); - } - if (!TextUtils.isEmpty(title) && entry.bitmap.icon != null) { - mCache.put(cacheKey, entry); - } - } - - private static ComponentKey getPackageKey(String packageName, UserHandle user) { - ComponentName cn = new ComponentName(packageName, packageName + EMPTY_CLASS_NAME); - return new ComponentKey(cn, user); - } - - /** - * Gets an entry for the package, which can be used as a fallback entry for various components. - * This method is not thread safe, it must be called from a synchronized method. - */ - protected CacheEntry getEntryForPackageLocked(String packageName, UserHandle user, - boolean useLowResIcon) { - assertWorkerThread(); - ComponentKey cacheKey = getPackageKey(packageName, user); - CacheEntry entry = mCache.get(cacheKey); - - if (entry == null || (entry.bitmap.isLowRes() && !useLowResIcon)) { - entry = new CacheEntry(); - boolean entryUpdated = true; - - // Check the DB first. - if (!getEntryFromDB(cacheKey, entry, useLowResIcon)) { - try { - int flags = Process.myUserHandle().equals(user) ? 0 : - PackageManager.GET_UNINSTALLED_PACKAGES; - PackageInfo info = mPackageManager.getPackageInfo(packageName, flags); - ApplicationInfo appInfo = info.applicationInfo; - if (appInfo == null) { - throw new NameNotFoundException("ApplicationInfo is null"); - } - - BaseIconFactory li = getIconFactory(); - // Load the full res icon for the application, but if useLowResIcon is set, then - // only keep the low resolution icon instead of the larger full-sized icon - BitmapInfo iconInfo = li.createBadgedIconBitmap( - appInfo.loadIcon(mPackageManager), user, appInfo.targetSdkVersion, - isInstantApp(appInfo)); - li.close(); - - entry.title = appInfo.loadLabel(mPackageManager); - entry.contentDescription = mPackageManager.getUserBadgedLabel(entry.title, user); - 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. - ContentValues values = newContentValues( - iconInfo, entry.title.toString(), packageName, null); - addIconToDB(values, cacheKey.componentName, info, getSerialNumberForUser(user), - info.lastUpdateTime); - - } catch (NameNotFoundException e) { - if (DEBUG) Log.d(TAG, "Application not installed " + packageName); - entryUpdated = false; - } - } - - // Only add a filled-out entry to the cache - if (entryUpdated) { - mCache.put(cacheKey, entry); - } - } - return entry; - } - - protected boolean getEntryFromDB(ComponentKey cacheKey, CacheEntry entry, boolean lowRes) { - Cursor c = null; - try { - c = mIconDb.query( - lowRes ? IconDB.COLUMNS_LOW_RES : IconDB.COLUMNS_HIGH_RES, - IconDB.COLUMN_COMPONENT + " = ? AND " + IconDB.COLUMN_USER + " = ?", - new String[]{ - cacheKey.componentName.flattenToString(), - Long.toString(getSerialNumberForUser(cacheKey.user))}); - if (c.moveToNext()) { - // Set the alpha to be 255, so that we never have a wrong color - entry.bitmap = BitmapInfo.of(LOW_RES_ICON, setColorAlphaBound(c.getInt(0), 255)); - entry.title = c.getString(1); - if (entry.title == null) { - entry.title = ""; - entry.contentDescription = ""; - } else { - entry.contentDescription = mPackageManager.getUserBadgedLabel( - entry.title, cacheKey.user); - } - - if (!lowRes) { - byte[] data = c.getBlob(2); - try { - entry.bitmap = BitmapInfo.of( - BitmapFactory.decodeByteArray(data, 0, data.length, mDecodeOptions), - entry.bitmap.color); - } catch (Exception e) { } - } - return true; - } - } catch (SQLiteException e) { - Log.d(TAG, "Error reading icon cache", e); - } finally { - if (c != null) { - c.close(); - } - } - return false; - } - - /** - * Returns a cursor for an arbitrary query to the cache db - */ - public synchronized Cursor queryCacheDb(String[] columns, String selection, - String[] selectionArgs) { - return mIconDb.query(columns, selection, selectionArgs); - } - - /** - * Cache class to store the actual entries on disk - */ - public static final class IconDB extends SQLiteCacheHelper { - private static final int RELEASE_VERSION = 27; - - public static final String TABLE_NAME = "icons"; - public static final String COLUMN_ROWID = "rowid"; - public static final String COLUMN_COMPONENT = "componentName"; - public static final String COLUMN_USER = "profileId"; - public static final String COLUMN_LAST_UPDATED = "lastUpdated"; - public static final String COLUMN_VERSION = "version"; - public static final String COLUMN_ICON = "icon"; - public static final String COLUMN_ICON_COLOR = "icon_color"; - public static final String COLUMN_LABEL = "label"; - public static final String COLUMN_SYSTEM_STATE = "system_state"; - public static final String COLUMN_KEYWORDS = "keywords"; - - public static final String[] COLUMNS_HIGH_RES = new String[] { - IconDB.COLUMN_ICON_COLOR, IconDB.COLUMN_LABEL, IconDB.COLUMN_ICON }; - public static final String[] COLUMNS_LOW_RES = new String[] { - IconDB.COLUMN_ICON_COLOR, IconDB.COLUMN_LABEL }; - - public IconDB(Context context, String dbFileName, int iconPixelSize) { - super(context, dbFileName, (RELEASE_VERSION << 16) + iconPixelSize, TABLE_NAME); - } - - @Override - protected void onCreateTable(SQLiteDatabase db) { - db.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " (" - + COLUMN_COMPONENT + " TEXT NOT NULL, " - + COLUMN_USER + " INTEGER NOT NULL, " - + COLUMN_LAST_UPDATED + " INTEGER NOT NULL DEFAULT 0, " - + COLUMN_VERSION + " INTEGER NOT NULL DEFAULT 0, " - + COLUMN_ICON + " BLOB, " - + COLUMN_ICON_COLOR + " INTEGER NOT NULL DEFAULT 0, " - + COLUMN_LABEL + " TEXT, " - + COLUMN_SYSTEM_STATE + " TEXT, " - + COLUMN_KEYWORDS + " TEXT, " - + "PRIMARY KEY (" + COLUMN_COMPONENT + ", " + COLUMN_USER + ") " - + ");"); - } - } - - private ContentValues newContentValues(BitmapInfo bitmapInfo, String label, - String packageName, @Nullable String keywords) { - ContentValues values = new ContentValues(); - values.put(IconDB.COLUMN_ICON, - bitmapInfo.isLowRes() ? null : GraphicsUtils.flattenBitmap(bitmapInfo.icon)); - values.put(IconDB.COLUMN_ICON_COLOR, bitmapInfo.color); - - values.put(IconDB.COLUMN_LABEL, label); - values.put(IconDB.COLUMN_SYSTEM_STATE, getIconSystemState(packageName)); - values.put(IconDB.COLUMN_KEYWORDS, keywords); - return values; - } - - private void assertWorkerThread() { - if (Looper.myLooper() != mBgLooper) { - throw new IllegalStateException("Cache accessed on wrong thread " + Looper.myLooper()); - } - } -} diff --git a/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java b/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java deleted file mode 100644 index c12e9dcc15..0000000000 --- a/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.launcher3.icons.cache; - -import android.content.ComponentName; -import android.content.Context; -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; - -public interface CachingLogic { - - ComponentName getComponent(T object); - - UserHandle getUser(T object); - - CharSequence getLabel(T object); - - default CharSequence getDescription(T object, CharSequence fallback) { - return fallback; - } - - @NonNull - BitmapInfo loadIcon(Context context, T object); - - /** - * Provides a option list of keywords to associate with this object - */ - @Nullable - default String getKeywords(T object, LocaleList localeList) { - return null; - } - - /** - * Returns the timestamp the entry was last updated in cache. - */ - default long getLastUpdatedTime(T object, PackageInfo info) { - return info.lastUpdateTime; - } - - /** - * Returns true the object should be added to mem cache; otherwise returns false. - */ - default boolean addToMemCache() { - return true; - } -} diff --git a/iconloaderlib/src/com/android/launcher3/icons/cache/HandlerRunnable.java b/iconloaderlib/src/com/android/launcher3/icons/cache/HandlerRunnable.java deleted file mode 100644 index ee52934543..0000000000 --- a/iconloaderlib/src/com/android/launcher3/icons/cache/HandlerRunnable.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.launcher3.icons.cache; - -import android.os.Handler; - -/** - * A runnable that can be posted to a {@link Handler} which can be canceled. - */ -public abstract class HandlerRunnable implements Runnable { - - private final Handler mHandler; - private final Runnable mEndRunnable; - - private boolean mEnded = false; - private boolean mCanceled = false; - - public HandlerRunnable(Handler handler, Runnable endRunnable) { - mHandler = handler; - mEndRunnable = endRunnable; - } - - /** - * Cancels this runnable from being run, only if it has not already run. - */ - public void cancel() { - mHandler.removeCallbacks(this); - // TODO: This can actually cause onEnd to be called twice if the handler is already running - // this runnable - // NOTE: This is currently run on whichever thread the caller is run on. - mCanceled = true; - onEnd(); - } - - /** - * @return whether this runnable was canceled. - */ - protected boolean isCanceled() { - return mCanceled; - } - - /** - * To be called by the implemention of this runnable. The end callback is done on whichever - * thread the caller is calling from. - */ - public void onEnd() { - if (!mEnded) { - mEnded = true; - if (mEndRunnable != null) { - mEndRunnable.run(); - } - } - } -} diff --git a/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java b/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java deleted file mode 100644 index 9e1ad7b7b2..0000000000 --- a/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java +++ /dev/null @@ -1,313 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.launcher3.icons.cache; - -import android.content.ComponentName; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.database.Cursor; -import android.database.sqlite.SQLiteException; -import android.os.SystemClock; -import android.os.UserHandle; -import android.text.TextUtils; -import android.util.ArrayMap; -import android.util.Log; -import android.util.SparseBooleanArray; - -import com.android.launcher3.icons.cache.BaseIconCache.IconDB; - -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map.Entry; -import java.util.Set; -import java.util.Stack; - -/** - * Utility class to handle updating the Icon cache - */ -public class IconCacheUpdateHandler { - - private static final String TAG = "IconCacheUpdateHandler"; - - /** - * In this mode, all invalid icons are marked as to-be-deleted in {@link #mItemsToDelete}. - * This mode is used for the first run. - */ - private static final boolean MODE_SET_INVALID_ITEMS = true; - - /** - * In this mode, any valid icon is removed from {@link #mItemsToDelete}. This is used for all - * subsequent runs, which essentially acts as set-union of all valid items. - */ - private static final boolean MODE_CLEAR_VALID_ITEMS = false; - - private static final Object ICON_UPDATE_TOKEN = new Object(); - - private final HashMap mPkgInfoMap; - private final BaseIconCache mIconCache; - - private final ArrayMap> mPackagesToIgnore = new ArrayMap<>(); - - private final SparseBooleanArray mItemsToDelete = new SparseBooleanArray(); - private boolean mFilterMode = MODE_SET_INVALID_ITEMS; - - IconCacheUpdateHandler(BaseIconCache cache) { - mIconCache = cache; - - mPkgInfoMap = new HashMap<>(); - - // Remove all active icon update tasks. - mIconCache.mWorkerHandler.removeCallbacksAndMessages(ICON_UPDATE_TOKEN); - - createPackageInfoMap(); - } - - /** - * Sets a package to ignore for processing - */ - public void addPackagesToIgnore(UserHandle userHandle, String packageName) { - Set packages = mPackagesToIgnore.get(userHandle); - if (packages == null) { - packages = new HashSet<>(); - mPackagesToIgnore.put(userHandle, packages); - } - packages.add(packageName); - } - - private void createPackageInfoMap() { - PackageManager pm = mIconCache.mPackageManager; - for (PackageInfo info : - pm.getInstalledPackages(PackageManager.MATCH_UNINSTALLED_PACKAGES)) { - mPkgInfoMap.put(info.packageName, info); - } - } - - /** - * Updates the persistent DB, such that only entries corresponding to {@param apps} remain in - * the DB and are updated. - * @return The set of packages for which icons have updated. - */ - public void updateIcons(List apps, CachingLogic cachingLogic, - OnUpdateCallback onUpdateCallback) { - // Filter the list per user - HashMap> userComponentMap = new HashMap<>(); - int count = apps.size(); - for (int i = 0; i < count; i++) { - T app = apps.get(i); - UserHandle userHandle = cachingLogic.getUser(app); - HashMap componentMap = userComponentMap.get(userHandle); - if (componentMap == null) { - componentMap = new HashMap<>(); - userComponentMap.put(userHandle, componentMap); - } - componentMap.put(cachingLogic.getComponent(app), app); - } - - for (Entry> entry : userComponentMap.entrySet()) { - updateIconsPerUser(entry.getKey(), entry.getValue(), cachingLogic, onUpdateCallback); - } - - // From now on, clear every valid item from the global valid map. - mFilterMode = MODE_CLEAR_VALID_ITEMS; - } - - /** - * Updates the persistent DB, such that only entries corresponding to {@param apps} remain in - * the DB and are updated. - * @return The set of packages for which icons have updated. - */ - @SuppressWarnings("unchecked") - private void updateIconsPerUser(UserHandle user, HashMap componentMap, - CachingLogic cachingLogic, OnUpdateCallback onUpdateCallback) { - Set ignorePackages = mPackagesToIgnore.get(user); - if (ignorePackages == null) { - ignorePackages = Collections.emptySet(); - } - long userSerial = mIconCache.getSerialNumberForUser(user); - - Stack appsToUpdate = new Stack<>(); - - try (Cursor c = mIconCache.mIconDb.query( - new String[]{IconDB.COLUMN_ROWID, IconDB.COLUMN_COMPONENT, - IconDB.COLUMN_LAST_UPDATED, IconDB.COLUMN_VERSION, - IconDB.COLUMN_SYSTEM_STATE}, - IconDB.COLUMN_USER + " = ? ", - new String[]{Long.toString(userSerial)})) { - - final int indexComponent = c.getColumnIndex(IconDB.COLUMN_COMPONENT); - final int indexLastUpdate = c.getColumnIndex(IconDB.COLUMN_LAST_UPDATED); - final int indexVersion = c.getColumnIndex(IconDB.COLUMN_VERSION); - final int rowIndex = c.getColumnIndex(IconDB.COLUMN_ROWID); - final int systemStateIndex = c.getColumnIndex(IconDB.COLUMN_SYSTEM_STATE); - - while (c.moveToNext()) { - String cn = c.getString(indexComponent); - ComponentName component = ComponentName.unflattenFromString(cn); - PackageInfo info = mPkgInfoMap.get(component.getPackageName()); - - int rowId = c.getInt(rowIndex); - if (info == null) { - if (!ignorePackages.contains(component.getPackageName())) { - - if (mFilterMode == MODE_SET_INVALID_ITEMS) { - mIconCache.remove(component, user); - mItemsToDelete.put(rowId, true); - } - } - continue; - } - if ((info.applicationInfo.flags & ApplicationInfo.FLAG_IS_DATA_ONLY) != 0) { - // Application is not present - continue; - } - - long updateTime = c.getLong(indexLastUpdate); - int version = c.getInt(indexVersion); - T app = componentMap.remove(component); - if (version == info.versionCode && updateTime == info.lastUpdateTime - && TextUtils.equals(c.getString(systemStateIndex), - mIconCache.getIconSystemState(info.packageName))) { - - if (mFilterMode == MODE_CLEAR_VALID_ITEMS) { - mItemsToDelete.put(rowId, false); - } - continue; - } - - if (app == null) { - if (mFilterMode == MODE_SET_INVALID_ITEMS) { - mIconCache.remove(component, user); - mItemsToDelete.put(rowId, true); - } - } else { - appsToUpdate.add(app); - } - } - } catch (SQLiteException e) { - Log.d(TAG, "Error reading icon cache", e); - // Continue updating whatever we have read so far - } - - // Insert remaining apps. - if (!componentMap.isEmpty() || !appsToUpdate.isEmpty()) { - Stack appsToAdd = new Stack<>(); - appsToAdd.addAll(componentMap.values()); - new SerializedIconUpdateTask(userSerial, user, appsToAdd, appsToUpdate, cachingLogic, - onUpdateCallback).scheduleNext(); - } - } - - /** - * Commits all updates as part of the update handler to disk. Not more calls should be made - * to this class after this. - */ - public void finish() { - // Commit all deletes - int deleteCount = 0; - StringBuilder queryBuilder = new StringBuilder() - .append(IconDB.COLUMN_ROWID) - .append(" IN ("); - - int count = mItemsToDelete.size(); - for (int i = 0; i < count; i++) { - if (mItemsToDelete.valueAt(i)) { - if (deleteCount > 0) { - queryBuilder.append(", "); - } - queryBuilder.append(mItemsToDelete.keyAt(i)); - deleteCount++; - } - } - queryBuilder.append(')'); - - if (deleteCount > 0) { - mIconCache.mIconDb.delete(queryBuilder.toString(), null); - } - } - - /** - * A runnable that updates invalid icons and adds missing icons in the DB for the provided - * LauncherActivityInfo list. Items are updated/added one at a time, so that the - * worker thread doesn't get blocked. - */ - private class SerializedIconUpdateTask implements Runnable { - private final long mUserSerial; - private final UserHandle mUserHandle; - private final Stack mAppsToAdd; - private final Stack mAppsToUpdate; - private final CachingLogic mCachingLogic; - private final HashSet mUpdatedPackages = new HashSet<>(); - private final OnUpdateCallback mOnUpdateCallback; - - SerializedIconUpdateTask(long userSerial, UserHandle userHandle, - Stack appsToAdd, Stack appsToUpdate, CachingLogic cachingLogic, - OnUpdateCallback onUpdateCallback) { - mUserHandle = userHandle; - mUserSerial = userSerial; - mAppsToAdd = appsToAdd; - mAppsToUpdate = appsToUpdate; - mCachingLogic = cachingLogic; - mOnUpdateCallback = onUpdateCallback; - } - - @Override - public void run() { - if (!mAppsToUpdate.isEmpty()) { - T app = mAppsToUpdate.pop(); - String pkg = mCachingLogic.getComponent(app).getPackageName(); - PackageInfo info = mPkgInfoMap.get(pkg); - - mIconCache.addIconToDBAndMemCache( - app, mCachingLogic, info, mUserSerial, true /*replace existing*/); - mUpdatedPackages.add(pkg); - - if (mAppsToUpdate.isEmpty() && !mUpdatedPackages.isEmpty()) { - // No more app to update. Notify callback. - mOnUpdateCallback.onPackageIconsUpdated(mUpdatedPackages, mUserHandle); - } - - // Let it run one more time. - scheduleNext(); - } else if (!mAppsToAdd.isEmpty()) { - T app = mAppsToAdd.pop(); - PackageInfo info = mPkgInfoMap.get(mCachingLogic.getComponent(app).getPackageName()); - // We do not check the mPkgInfoMap when generating the mAppsToAdd. Although every - // app should have package info, this is not guaranteed by the api - if (info != null) { - mIconCache.addIconToDBAndMemCache(app, mCachingLogic, info, - mUserSerial, false /*replace existing*/); - } - - if (!mAppsToAdd.isEmpty()) { - scheduleNext(); - } - } - } - - public void scheduleNext() { - mIconCache.mWorkerHandler.postAtTime(this, ICON_UPDATE_TOKEN, - SystemClock.uptimeMillis() + 1); - } - } - - public interface OnUpdateCallback { - - void onPackageIconsUpdated(HashSet updatedPackages, UserHandle user); - } -} diff --git a/iconloaderlib/src/com/android/launcher3/util/ComponentKey.java b/iconloaderlib/src/com/android/launcher3/util/ComponentKey.java deleted file mode 100644 index 34bed94270..0000000000 --- a/iconloaderlib/src/com/android/launcher3/util/ComponentKey.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.android.launcher3.util; - -/** - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import android.content.ComponentName; -import android.os.UserHandle; - -import java.util.Arrays; - -public class ComponentKey { - - public final ComponentName componentName; - public final UserHandle user; - - private final int mHashCode; - - public ComponentKey(ComponentName componentName, UserHandle user) { - if (componentName == null || user == null) { - throw new NullPointerException(); - } - this.componentName = componentName; - this.user = user; - mHashCode = Arrays.hashCode(new Object[] {componentName, user}); - - } - - @Override - public int hashCode() { - return mHashCode; - } - - @Override - public boolean equals(Object o) { - ComponentKey other = (ComponentKey) o; - return other.componentName.equals(componentName) && other.user.equals(user); - } - - /** - * Encodes a component key as a string of the form [flattenedComponentString#userId]. - */ - @Override - public String toString() { - return componentName.flattenToString() + "#" + user; - } -} \ No newline at end of file diff --git a/iconloaderlib/src/com/android/launcher3/util/NoLocaleSQLiteHelper.java b/iconloaderlib/src/com/android/launcher3/util/NoLocaleSQLiteHelper.java deleted file mode 100644 index fe864a2847..0000000000 --- a/iconloaderlib/src/com/android/launcher3/util/NoLocaleSQLiteHelper.java +++ /dev/null @@ -1,58 +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.util; - -import static android.database.sqlite.SQLiteDatabase.NO_LOCALIZED_COLLATORS; - -import android.content.Context; -import android.content.ContextWrapper; -import android.database.DatabaseErrorHandler; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteDatabase.CursorFactory; -import android.database.sqlite.SQLiteDatabase.OpenParams; -import android.database.sqlite.SQLiteOpenHelper; -import android.os.Build; - -/** - * Extension of {@link SQLiteOpenHelper} which avoids creating default locale table by - * A context wrapper which creates databases without support for localized collators. - */ -public abstract class NoLocaleSQLiteHelper extends SQLiteOpenHelper { - - private static final boolean ATLEAST_P = - Build.VERSION.SDK_INT >= Build.VERSION_CODES.P; - - public NoLocaleSQLiteHelper(Context context, String name, int version) { - super(ATLEAST_P ? context : new NoLocalContext(context), name, null, version); - if (ATLEAST_P) { - setOpenParams(new OpenParams.Builder().addOpenFlags(NO_LOCALIZED_COLLATORS).build()); - } - } - - private static class NoLocalContext extends ContextWrapper { - public NoLocalContext(Context base) { - super(base); - } - - @Override - public SQLiteDatabase openOrCreateDatabase( - String name, int mode, CursorFactory factory, DatabaseErrorHandler errorHandler) { - return super.openOrCreateDatabase( - name, mode | Context.MODE_NO_LOCALIZED_COLLATORS, factory, errorHandler); - } - } -} diff --git a/iconloaderlib/src/com/android/launcher3/util/SQLiteCacheHelper.java b/iconloaderlib/src/com/android/launcher3/util/SQLiteCacheHelper.java deleted file mode 100644 index 49de4bd1bf..0000000000 --- a/iconloaderlib/src/com/android/launcher3/util/SQLiteCacheHelper.java +++ /dev/null @@ -1,125 +0,0 @@ -package com.android.launcher3.util; - -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteException; -import android.database.sqlite.SQLiteFullException; -import android.database.sqlite.SQLiteOpenHelper; -import android.util.Log; - -/** - * An extension of {@link SQLiteOpenHelper} with utility methods for a single table cache DB. - * Any exception during write operations are ignored, and any version change causes a DB reset. - */ -public abstract class SQLiteCacheHelper { - private static final String TAG = "SQLiteCacheHelper"; - - private static final boolean IN_MEMORY_CACHE = false; - - private final String mTableName; - private final MySQLiteOpenHelper mOpenHelper; - - private boolean mIgnoreWrites; - - public SQLiteCacheHelper(Context context, String name, int version, String tableName) { - if (IN_MEMORY_CACHE) { - name = null; - } - mTableName = tableName; - mOpenHelper = new MySQLiteOpenHelper(context, name, version); - - mIgnoreWrites = false; - } - - /** - * @see SQLiteDatabase#delete(String, String, String[]) - */ - public void delete(String whereClause, String[] whereArgs) { - if (mIgnoreWrites) { - return; - } - try { - mOpenHelper.getWritableDatabase().delete(mTableName, whereClause, whereArgs); - } catch (SQLiteFullException e) { - onDiskFull(e); - } catch (SQLiteException e) { - Log.d(TAG, "Ignoring sqlite exception", e); - } - } - - /** - * @see SQLiteDatabase#insertWithOnConflict(String, String, ContentValues, int) - */ - public void insertOrReplace(ContentValues values) { - if (mIgnoreWrites) { - return; - } - try { - mOpenHelper.getWritableDatabase().insertWithOnConflict( - mTableName, null, values, SQLiteDatabase.CONFLICT_REPLACE); - } catch (SQLiteFullException e) { - onDiskFull(e); - } catch (SQLiteException e) { - Log.d(TAG, "Ignoring sqlite exception", e); - } - } - - private void onDiskFull(SQLiteFullException e) { - Log.e(TAG, "Disk full, all write operations will be ignored", e); - mIgnoreWrites = true; - } - - /** - * @see SQLiteDatabase#query(String, String[], String, String[], String, String, String) - */ - public Cursor query(String[] columns, String selection, String[] selectionArgs) { - return mOpenHelper.getReadableDatabase().query( - mTableName, columns, selection, selectionArgs, null, null, null); - } - - public void clear() { - mOpenHelper.clearDB(mOpenHelper.getWritableDatabase()); - } - - public void close() { - mOpenHelper.close(); - } - - protected abstract void onCreateTable(SQLiteDatabase db); - - /** - * A private inner class to prevent direct DB access. - */ - private class MySQLiteOpenHelper extends NoLocaleSQLiteHelper { - - public MySQLiteOpenHelper(Context context, String name, int version) { - super(context, name, version); - } - - @Override - public void onCreate(SQLiteDatabase db) { - onCreateTable(db); - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - if (oldVersion != newVersion) { - clearDB(db); - } - } - - @Override - public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { - if (oldVersion != newVersion) { - clearDB(db); - } - } - - private void clearDB(SQLiteDatabase db) { - db.execSQL("DROP TABLE IF EXISTS " + mTableName); - onCreate(db); - } - } -} diff --git a/iconloaderlib/src_full_lib/com/android/launcher3/icons/IconFactory.java b/iconloaderlib/src_full_lib/com/android/launcher3/icons/IconFactory.java deleted file mode 100644 index 48f11fde3b..0000000000 --- a/iconloaderlib/src_full_lib/com/android/launcher3/icons/IconFactory.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher3.icons; - -import android.content.Context; - -/** - * Wrapper class to provide access to {@link BaseIconFactory} and also to provide pool of this class - * that are threadsafe. - */ -public class IconFactory extends BaseIconFactory { - - private static final Object sPoolSync = new Object(); - private static IconFactory sPool; - private static int sPoolId = 0; - - /** - * Return a new Message instance from the global pool. Allows us to - * avoid allocating new objects in many cases. - */ - public static IconFactory obtain(Context context) { - int poolId; - synchronized (sPoolSync) { - if (sPool != null) { - IconFactory m = sPool; - sPool = m.next; - m.next = null; - return m; - } - poolId = sPoolId; - } - - return new IconFactory(context, - context.getResources().getConfiguration().densityDpi, - context.getResources().getDimensionPixelSize(R.dimen.default_icon_bitmap_size), - poolId); - } - - public static void clearPool() { - synchronized (sPoolSync) { - sPool = null; - sPoolId++; - } - } - - private final int mPoolId; - - private IconFactory next; - - private IconFactory(Context context, int fillResIconDpi, int iconBitmapSize, int poolId) { - super(context, fillResIconDpi, iconBitmapSize); - mPoolId = poolId; - } - - /** - * Recycles a LauncherIcons that may be in-use. - */ - public void recycle() { - synchronized (sPoolSync) { - if (sPoolId != mPoolId) { - return; - } - // Clear any temporary state variables - clear(); - - next = sPool; - sPool = this; - } - } - - @Override - public void close() { - recycle(); - } -} diff --git a/iconloaderlib/src_full_lib/com/android/launcher3/icons/SimpleIconCache.java b/iconloaderlib/src_full_lib/com/android/launcher3/icons/SimpleIconCache.java deleted file mode 100644 index 1337975f19..0000000000 --- a/iconloaderlib/src_full_lib/com/android/launcher3/icons/SimpleIconCache.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.launcher3.icons; - -import static android.content.Intent.ACTION_MANAGED_PROFILE_ADDED; -import static android.content.Intent.ACTION_MANAGED_PROFILE_REMOVED; - -import android.annotation.TargetApi; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.ApplicationInfo; -import android.os.Build; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Looper; -import android.os.UserHandle; -import android.os.UserManager; -import android.util.SparseLongArray; - -import com.android.launcher3.icons.cache.BaseIconCache; - -/** - * Wrapper class to provide access to {@link BaseIconFactory} and also to provide pool of this class - * that are threadsafe. - */ -@TargetApi(Build.VERSION_CODES.P) -public class SimpleIconCache extends BaseIconCache { - - private static SimpleIconCache sIconCache = null; - private static final Object CACHE_LOCK = new Object(); - - private final SparseLongArray mUserSerialMap = new SparseLongArray(2); - private final UserManager mUserManager; - - public SimpleIconCache(Context context, String dbFileName, Looper bgLooper, int iconDpi, - int iconPixelSize, boolean inMemoryCache) { - super(context, dbFileName, bgLooper, iconDpi, iconPixelSize, inMemoryCache); - mUserManager = context.getSystemService(UserManager.class); - - // Listen for user cache changes. - IntentFilter filter = new IntentFilter(ACTION_MANAGED_PROFILE_ADDED); - filter.addAction(ACTION_MANAGED_PROFILE_REMOVED); - context.registerReceiver(new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - resetUserCache(); - } - }, filter, null, new Handler(bgLooper), 0); - } - - @Override - protected long getSerialNumberForUser(UserHandle user) { - synchronized (mUserSerialMap) { - int index = mUserSerialMap.indexOfKey(user.getIdentifier()); - if (index >= 0) { - return mUserSerialMap.valueAt(index); - } - long serial = mUserManager.getSerialNumberForUser(user); - mUserSerialMap.put(user.getIdentifier(), serial); - return serial; - } - } - - private void resetUserCache() { - synchronized (mUserSerialMap) { - mUserSerialMap.clear(); - } - } - - @Override - protected boolean isInstantApp(ApplicationInfo info) { - return info.isInstantApp(); - } - - @Override - protected BaseIconFactory getIconFactory() { - return IconFactory.obtain(mContext); - } - - public static SimpleIconCache getIconCache(Context context) { - synchronized (CACHE_LOCK) { - if (sIconCache != null) { - return sIconCache; - } - boolean inMemoryCache = - context.getResources().getBoolean(R.bool.simple_cache_enable_im_memory); - String dbFileName = context.getString(R.string.cache_db_name); - - HandlerThread bgThread = new HandlerThread("simple-icon-cache"); - bgThread.start(); - - sIconCache = new SimpleIconCache(context.getApplicationContext(), dbFileName, - bgThread.getLooper(), context.getResources().getConfiguration().densityDpi, - context.getResources().getDimensionPixelSize(R.dimen.default_icon_bitmap_size), - inMemoryCache); - return sIconCache; - } - } -} From 9a8c9aa6878638beb1c51d2cf8462eb06392fbfb Mon Sep 17 00:00:00 2001 From: Tony Wickham Date: Mon, 9 Mar 2020 13:20:04 -0700 Subject: [PATCH 0011/1707] 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: Wed, 18 Mar 2020 22:06:50 -0700 Subject: [PATCH 0014/1707] Import translations. DO NOT MERGE BUG: 151750123 Auto-generated-cl: translation import Change-Id: I5c7e8ffccb7830d2159bfe0de0d35fe011938e74 --- res/values-rm/strings.xml | 203 -------------------------------------- 1 file changed, 203 deletions(-) delete mode 100644 res/values-rm/strings.xml diff --git a/res/values-rm/strings.xml b/res/values-rm/strings.xml deleted file mode 100644 index 0758148f58..0000000000 --- a/res/values-rm/strings.xml +++ /dev/null @@ -1,203 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 0b65822f308902bb1dc98ea98e0362a830d9aa25 Mon Sep 17 00:00:00 2001 From: vadimt Date: Mon, 13 Apr 2020 18:30:39 -0700 Subject: [PATCH 0015/1707] Temporarily disabling events checking in git_master Bug: 153824894 Change-Id: I5f294d1a808b1ceefd0b753de6c9207cf92de06d --- .../com/android/launcher3/tapl/LauncherInstrumentation.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java index 975fe9c1e7..b8e55651ae 100644 --- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java +++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java @@ -250,7 +250,8 @@ public final class LauncherInstrumentation { } public void enableCheckEventsForSuccessfulGestures() { - mCheckEventsForSuccessfulGestures = true; + // b/153824894 + // mCheckEventsForSuccessfulGestures = true; } public void setOnLauncherCrashed(Runnable onLauncherCrashed) { From ef27e0abdceb9c94236432b48f10d7780b8aa047 Mon Sep 17 00:00:00 2001 From: Bob Badour Date: Mon, 27 Apr 2020 20:34:54 -0700 Subject: [PATCH 0016/1707] Remove MODULE_LICENSE_APACHE2 from Launcher3 Repositories containing third-party code need a METADATA and a license_type. Repositories containing only first-party code no longer need a MODULE_LICENSE_* file. Bug: 68860345 Bug: 69058154 Bug: 151953481 Test: no code changes Exempt-From-Owner-Approval: janitorial work Change-Id: I17f3c230dbe730c03a2d8727e26c32b3e7e37e52 --- MODULE_LICENSE_APACHE2 | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 MODULE_LICENSE_APACHE2 diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2 deleted file mode 100644 index e69de29bb2..0000000000 From a7a9154f24a4d419c2a36d97eeccd6159e4d84e8 Mon Sep 17 00:00:00 2001 From: Samuel Fufa Date: Mon, 11 May 2020 19:48:59 -0700 Subject: [PATCH 0017/1707] 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 7d375e31fe5d221750f9128c20121e34c1ab142f Mon Sep 17 00:00:00 2001 From: Tony Huang Date: Tue, 26 Nov 2019 14:00:14 +0800 Subject: [PATCH 0018/1707] Enable one-handed mode gestural in QuickStep Handling swipe-down/swipe-up gestural in device bottom area for one-handed mode 1) The regsion is larger than gesture navigationbar view 2) One handed gestural in quickstep only active on NO_BUTTON, TWO_BUTTONS mode 3) One handed gestural only support on portrait mode Bug: 150747547 Bug: 154189137 Bug: 156988988 Test: make and install Test: manual enable one handed mode and swipe down to trigger Test: manual start one handed and rotate device Change-Id: I7b2447bfb2fe4082c95176b62934b98077b84920 --- .../quickstep/TouchInteractionService.java | 18 ++- .../OneHandedModeInputConsumer.java | 143 ++++++++++++++++++ quickstep/res/values/dimens.xml | 4 + .../com/android/quickstep/InputConsumer.java | 2 + .../OrientationTouchTransformer.java | 14 +- .../RecentsAnimationDeviceState.java | 45 +++++- .../com/android/quickstep/SystemUiProxy.java | 22 +++ .../util/SecureSettingsObserver.java | 8 + 8 files changed, 250 insertions(+), 6 deletions(-) create mode 100644 quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java 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 8bffc7805c..a88e413c84 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java @@ -75,6 +75,7 @@ import com.android.launcher3.util.WindowBounds; import com.android.quickstep.inputconsumers.AccessibilityInputConsumer; import com.android.quickstep.inputconsumers.AssistantInputConsumer; import com.android.quickstep.inputconsumers.DeviceLockedInputConsumer; +import com.android.quickstep.inputconsumers.OneHandedModeInputConsumer; import com.android.quickstep.inputconsumers.OtherActivityInputConsumer; import com.android.quickstep.inputconsumers.OverscrollInputConsumer; import com.android.quickstep.inputconsumers.OverviewInputConsumer; @@ -85,7 +86,6 @@ import com.android.quickstep.util.ActiveGestureLog; import com.android.quickstep.util.AssistantUtilities; import com.android.quickstep.util.ProtoTracer; import com.android.quickstep.util.SplitScreenBounds; -import com.android.quickstep.views.RecentsView; import com.android.systemui.plugins.OverscrollPlugin; import com.android.systemui.plugins.PluginListener; import com.android.systemui.shared.recents.IOverviewProxy; @@ -492,6 +492,12 @@ public class TouchInteractionService extends Service implements PluginListener mSquaredSlop) { + mStartDragPos.set(mLastPos.x, mLastPos.y); + if ((!mDeviceState.isOneHandedModeActive() && isValidStartAngle( + mDownPos.x - mLastPos.x, mDownPos.y - mLastPos.y)) + || (mDeviceState.isOneHandedModeActive() && isValidExitAngle( + mDownPos.x - mLastPos.x, mDownPos.y - mLastPos.y))) { + mPassedSlop = true; + setActive(ev); + } else { + mState = STATE_DELEGATE_ACTIVE; + } + } + } else { + float distance = (float) Math.hypot(mLastPos.x - mStartDragPos.x, + mLastPos.y - mStartDragPos.y); + if (distance > mDragDistThreshold && mPassedSlop + && mDeviceState.isOneHandedModeActive()) { + SystemUiProxy.INSTANCE.get(mContext).stopOneHandedMode(); + } + } + break; + } + case ACTION_UP: + case ACTION_CANCEL: { + if (mLastPos.y >= mStartDragPos.y && mPassedSlop + && !mDeviceState.isOneHandedModeActive()) { + SystemUiProxy.INSTANCE.get(mContext).startOneHandedMode(); + } + + mPassedSlop = false; + mState = STATE_INACTIVE; + break; + } + } + + if (mState != STATE_ACTIVE) { + mDelegate.onMotionEvent(ev); + } + } + + private boolean isValidStartAngle(float deltaX, float deltaY) { + final float angle = (float) Math.toDegrees(Math.atan2(deltaY, deltaX)); + return angle > -(ANGLE_MAX) && angle < -(ANGLE_MIN); + } + + private boolean isValidExitAngle(float deltaX, float deltaY) { + final float angle = (float) Math.toDegrees(Math.atan2(deltaY, deltaX)); + return angle > ANGLE_MIN && angle < ANGLE_MAX; + } +} diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml index 9d703167d9..6737c5f61d 100644 --- a/quickstep/res/values/dimens.xml +++ b/quickstep/res/values/dimens.xml @@ -76,6 +76,10 @@ 55dp 55dp + + + 20dp + 28dp diff --git a/quickstep/src/com/android/quickstep/InputConsumer.java b/quickstep/src/com/android/quickstep/InputConsumer.java index a4861dce01..0eed3e322d 100644 --- a/quickstep/src/com/android/quickstep/InputConsumer.java +++ b/quickstep/src/com/android/quickstep/InputConsumer.java @@ -34,6 +34,7 @@ public interface InputConsumer { int TYPE_OVERVIEW_WITHOUT_FOCUS = 1 << 7; int TYPE_RESET_GESTURE = 1 << 8; int TYPE_OVERSCROLL = 1 << 9; + int TYPE_ONE_HANDED = 1 << 10; String[] NAMES = new String[] { "TYPE_NO_OP", // 0 @@ -46,6 +47,7 @@ public interface InputConsumer { "TYPE_OVERVIEW_WITHOUT_FOCUS", // 7 "TYPE_RESET_GESTURE", // 8 "TYPE_OVERSCROLL", // 9 + "TYPE_ONE_HANDED", // 10 }; InputConsumer NO_OP = () -> TYPE_NO_OP; diff --git a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java index 4cf7aab4ab..6b94a79000 100644 --- a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java +++ b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java @@ -37,7 +37,6 @@ import android.view.Surface; import com.android.launcher3.R; import com.android.launcher3.ResourceUtils; import com.android.launcher3.util.DefaultDisplay; -import com.android.quickstep.util.RecentsOrientedState.SurfaceRotation; import java.io.PrintWriter; @@ -63,6 +62,7 @@ class OrientationTouchTransformer { private SparseArray mSwipeTouchRegions = new SparseArray<>(MAX_ORIENTATIONS); private final RectF mAssistantLeftRegion = new RectF(); private final RectF mAssistantRightRegion = new RectF(); + private final RectF mOneHandedModeRegion = new RectF(); private int mCurrentDisplayRotation; private boolean mEnableMultipleRegions; private Resources mResources; @@ -185,10 +185,10 @@ class OrientationTouchTransformer { Point size = display.realSize; int rotation = display.rotation; + int touchHeight = getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE); OrientationRectF orientationRectF = new OrientationRectF(0, 0, size.x, size.y, rotation); if (mMode == SysUINavigationMode.Mode.NO_BUTTON) { - int touchHeight = getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE); orientationRectF.top = orientationRectF.bottom - touchHeight; final int assistantWidth = mResources @@ -217,10 +217,11 @@ class OrientationTouchTransformer { + getNavbarSize(ResourceUtils.NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE); break; default: - orientationRectF.top = orientationRectF.bottom - - getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE); + orientationRectF.top = orientationRectF.bottom - touchHeight; } } + // One handed gestural only active on portrait mode + mOneHandedModeRegion.set(0, orientationRectF.bottom - touchHeight, size.x, size.y); return orientationRectF; } @@ -231,6 +232,10 @@ class OrientationTouchTransformer { } + boolean touchInOneHandedModeRegion(MotionEvent ev) { + return mOneHandedModeRegion.contains(ev.getX(), ev.getY()); + } + private int getNavbarSize(String resName) { return ResourceUtils.getNavbarSize(resName, mResources); } @@ -313,6 +318,7 @@ class OrientationTouchTransformer { regions.append(rectF.mRotation).append(" "); } pw.println(regions.toString()); + pw.println(" mOneHandedModeRegion=" + mOneHandedModeRegion); } private class OrientationRectF extends RectF { diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java index a892ddcccd..c32abcf0d8 100644 --- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java +++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java @@ -29,6 +29,7 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_B import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ONE_HANDED_ACTIVE; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING; @@ -43,16 +44,19 @@ import android.content.IntentFilter; import android.content.res.Resources; import android.graphics.Region; import android.os.Process; +import android.os.SystemProperties; import android.os.UserManager; import android.text.TextUtils; -import android.util.Log; +import android.util.DisplayMetrics; import android.view.MotionEvent; +import android.view.Surface; import androidx.annotation.BinderThread; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.util.DefaultDisplay; +import com.android.launcher3.util.SecureSettingsObserver; import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener; import com.android.quickstep.util.NavBarPosition; import com.android.systemui.shared.system.ActivityManagerWrapper; @@ -73,6 +77,8 @@ public class RecentsAnimationDeviceState implements NavigationModeChangeListener, DefaultDisplay.DisplayInfoChangeListener { + private static boolean sIsOneHandedEnabled; + private final Context mContext; private final SysUINavigationMode mSysUiNavMode; private final DefaultDisplay mDefaultDisplay; @@ -84,6 +90,7 @@ public class RecentsAnimationDeviceState implements private @SystemUiStateFlags int mSystemUiStateFlags; private SysUINavigationMode.Mode mMode = THREE_BUTTONS; private NavBarPosition mNavBarPosition; + private SecureSettingsObserver mOneHandedEnabledObserver; private final Region mDeferredGestureRegion = new Region(); private boolean mAssistantAvailable; @@ -169,6 +176,13 @@ public class RecentsAnimationDeviceState implements ComponentName.unflattenFromString(blockingActivity)); } } + + if (SystemProperties.getBoolean("ro.support_one_handed_mode", false)) { + mOneHandedEnabledObserver = SecureSettingsObserver.newOneHandedSettingsObserver( + mContext, this::onOneHandedEnabledSettingsChanged); + mOneHandedEnabledObserver.register(); + mOneHandedEnabledObserver.dispatchOnChange(); + } } private void setupOrientationSwipeHandler() { @@ -436,6 +450,13 @@ public class RecentsAnimationDeviceState implements event.getY(pointerIndex)); } + /** + * @return whether screen pinning is enabled and active + */ + public boolean isOneHandedModeActive() { + return (mSystemUiStateFlags & SYSUI_STATE_ONE_HANDED_ACTIVE) != 0; + } + /** * Sets the region in screen space where the gestures should be deferred (ie. due to specific * nav bar ui). @@ -496,6 +517,28 @@ public class RecentsAnimationDeviceState implements && !isLockToAppActive(); } + /** + * One handed gestural in quickstep only active on NO_BUTTON, TWO_BUTTONS, and portrait mode + * + * @param ev The touch screen motion event. + * @return whether the given motion event can trigger the one handed mode. + */ + public boolean canTriggerOneHandedAction(MotionEvent ev) { + if (!sIsOneHandedEnabled) { + return false; + } + + final DefaultDisplay.Info displayInfo = mDefaultDisplay.getInfo(); + return (mOrientationTouchTransformer.touchInOneHandedModeRegion(ev) + && displayInfo.rotation != Surface.ROTATION_90 + && displayInfo.rotation != Surface.ROTATION_270 + && displayInfo.metrics.densityDpi < DisplayMetrics.DENSITY_600); + } + + private void onOneHandedEnabledSettingsChanged(boolean isOneHandedEnabled) { + sIsOneHandedEnabled = isOneHandedEnabled; + } + /** * *May* apply a transform on the motion event if it lies in the nav bar region for another * orientation that is currently being tracked as a part of quickstep diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java index 20d133c3c6..a3eabc966c 100644 --- a/quickstep/src/com/android/quickstep/SystemUiProxy.java +++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java @@ -344,4 +344,26 @@ public class SystemUiProxy implements ISystemUiProxy { } } } + + @Override + public void startOneHandedMode() { + if (mSystemUiProxy != null) { + try { + mSystemUiProxy.startOneHandedMode(); + } catch (RemoteException e) { + Log.w(TAG, "Failed call startOneHandedMode", e); + } + } + } + + @Override + public void stopOneHandedMode() { + if (mSystemUiProxy != null) { + try { + mSystemUiProxy.stopOneHandedMode(); + } catch (RemoteException e) { + Log.w(TAG, "Failed call stopOneHandedMode", e); + } + } + } } diff --git a/src/com/android/launcher3/util/SecureSettingsObserver.java b/src/com/android/launcher3/util/SecureSettingsObserver.java index 48aa02b799..08a8e6d255 100644 --- a/src/com/android/launcher3/util/SecureSettingsObserver.java +++ b/src/com/android/launcher3/util/SecureSettingsObserver.java @@ -29,6 +29,8 @@ public class SecureSettingsObserver extends ContentObserver { /** Hidden field Settings.Secure.NOTIFICATION_BADGING */ public static final String NOTIFICATION_BADGING = "notification_badging"; + /** Hidden field Settings.Secure.ONE_HANDED_MODE_ENABLED */ + public static final String ONE_HANDED_ENABLED = "one_handed_mode_enabled"; private final ContentResolver mResolver; private final String mKeySetting; @@ -79,4 +81,10 @@ public class SecureSettingsObserver extends ContentObserver { return new SecureSettingsObserver( context.getContentResolver(), listener, NOTIFICATION_BADGING, 1); } + + public static SecureSettingsObserver newOneHandedSettingsObserver(Context context, + OnChangeListener listener) { + return new SecureSettingsObserver( + context.getContentResolver(), listener, ONE_HANDED_ENABLED, 1); + } } From 5ca2980e54c2b1f50f5cb4262e79c30a9f180864 Mon Sep 17 00:00:00 2001 From: Adam Cohen Date: Tue, 2 Jun 2020 18:21:18 -0400 Subject: [PATCH 0019/1707] Merging from ub-launcher3-rvc-dev @ build 6552182 Bug:150504032 Test: manual, presubmit on the source branch x20/teams/android-launcher/merge/ub-launcher3-rvc-dev_rvc-dev_6552182.html Change-Id: I795aafd151c6ce53077e1aebb97567eedd84f274 --- protos/launcher_atom.proto | 6 + .../hybridhotseat/HotseatEduController.java | 73 +--- .../HotseatPredictionController.java | 4 +- .../uioverrides/QuickstepLauncher.java | 15 + .../QuickstepAtomicAnimationFactory.java | 3 +- .../NavBarToHomeTouchController.java | 3 +- .../android/quickstep/BaseSwipeUpHandler.java | 320 +--------------- .../quickstep/BaseSwipeUpHandlerV2.java | 9 +- .../quickstep/FallbackActivityInterface.java | 2 +- .../quickstep/LauncherActivityInterface.java | 16 +- .../QuickstepTestInformationHandler.java | 5 +- .../quickstep/SwipeUpAnimationLogic.java | 350 ++++++++++++++++++ .../quickstep/TouchInteractionService.java | 13 +- .../FallbackNavBarTouchController.java | 12 +- .../OtherActivityInputConsumer.java | 5 + .../OverviewWithoutFocusInputConsumer.java | 12 +- .../SysUiOverlayInputConsumer.java | 86 +++++ .../quickstep/util/TaskViewSimulator.java | 21 +- .../util/TriggerSwipeUpTouchTracker.java | 14 +- .../quickstep/views/OverviewActionsView.java | 2 - .../android/quickstep/views/RecentsView.java | 5 + .../res/layout/gesture_tutorial_fragment.xml | 7 + quickstep/res/values/strings.xml | 6 - .../statehandlers/DepthController.java | 21 +- .../quickstep/BaseActivityInterface.java | 2 +- .../com/android/quickstep/InputConsumer.java | 11 +- .../OrientationTouchTransformer.java | 27 +- .../android/quickstep/RecentTasksList.java | 83 +++-- .../quickstep/RecentsAnimationController.java | 31 +- .../RecentsAnimationDeviceState.java | 100 ++++- .../BackGestureTutorialController.java | 8 +- .../HomeGestureTutorialController.java | 227 +++++++++++- .../interaction/NavBarGestureHandler.java | 46 ++- .../interaction/TutorialController.java | 5 + .../interaction/TutorialFragment.java | 5 - .../logging/StatsLogCompatManager.java | 19 +- .../quickstep/util/RecentsOrientedState.java | 6 +- .../quickstep/RecentTasksListTest.java | 8 +- res/values/colors.xml | 1 + .../launcher3/BaseDraggingActivity.java | 14 +- src/com/android/launcher3/DeviceProfile.java | 16 +- .../launcher3/InvariantDeviceProfile.java | 2 +- .../android/launcher3/LauncherSettings.java | 3 +- .../allapps/AllAppsTransitionController.java | 6 +- .../android/launcher3/anim/Interpolators.java | 5 + src/com/android/launcher3/folder/Folder.java | 1 + .../launcher3/logging/StatsLogManager.java | 34 +- .../launcher3/model/data/ItemInfo.java | 6 + .../popup/PopupContainerWithArrow.java | 6 +- .../launcher3/popup/PopupPopulator.java | 3 + .../launcher3/popup/SystemShortcut.java | 9 +- .../touch/WorkspaceTouchListener.java | 7 +- .../launcher3/util/DefaultDisplay.java | 9 +- .../launcher3/views/BaseDragLayer.java | 5 + .../launcher3/views/FloatingIconView.java | 10 + .../android/launcher3/views/ListenerView.java | 25 +- .../launcher3/views/OptionsPopupView.java | 35 +- .../uioverrides/PreviewSurfaceRenderer.java | 27 -- 58 files changed, 1191 insertions(+), 621 deletions(-) create mode 100644 quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeUpAnimationLogic.java create mode 100644 quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/SysUiOverlayInputConsumer.java delete mode 100644 src_ui_overrides/com/android/launcher3/uioverrides/PreviewSurfaceRenderer.java diff --git a/protos/launcher_atom.proto b/protos/launcher_atom.proto index 5f5fab0f8c..d552daff43 100644 --- a/protos/launcher_atom.proto +++ b/protos/launcher_atom.proto @@ -51,6 +51,7 @@ message ContainerInfo { WidgetsContainer widgets_container = 5; PredictionContainer prediction_container = 6; SearchResultContainer search_result_container = 7; + ShortcutsContainer shortcuts_container = 8; } } @@ -69,6 +70,11 @@ message PredictionContainer { message SearchResultContainer { } +// Container for package specific shortcuts to deep links and notifications. +// Typically shown as popup window by longpressing on an icon. +message ShortcutsContainer { +} + enum Origin { UNKNOWN = 0; DEFAULT_LAYOUT = 1; // icon automatically placed in workspace, folder, hotseat diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java index e4d0adf0c0..7c4f3ecd01 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java @@ -15,17 +15,9 @@ */ package com.android.launcher3.hybridhotseat; -import android.app.Notification; -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.app.PendingIntent; import android.content.Intent; -import android.content.res.Configuration; -import android.os.Build; import android.view.View; -import androidx.core.app.NotificationCompat; - import com.android.launcher3.CellLayout; import com.android.launcher3.Hotseat; import com.android.launcher3.InvariantDeviceProfile; @@ -37,11 +29,8 @@ import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.model.data.FolderInfo; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; -import com.android.launcher3.uioverrides.QuickstepLauncher; -import com.android.launcher3.util.ActivityTracker; import com.android.launcher3.util.GridOccupancy; import com.android.launcher3.util.IntArray; -import com.android.launcher3.util.Themes; import com.android.launcher3.views.ArrowTipView; import com.android.launcher3.views.Snackbar; @@ -54,18 +43,15 @@ import java.util.stream.IntStream; * Controller class for managing user onboaridng flow for hybrid hotseat */ public class HotseatEduController { + public static final String KEY_HOTSEAT_EDU_SEEN = "hotseat_edu_seen"; - - private static final String NOTIFICATION_CHANNEL_ID = "launcher_onboarding"; - private static final int ONBOARDING_NOTIFICATION_ID = 7641; - + public static final String HOTSEAT_EDU_ACTION = + "com.android.launcher3.action.SHOW_HYBRID_HOTSEAT_EDU"; private static final String SETTINGS_ACTION = "android.settings.ACTION_CONTENT_SUGGESTIONS_SETTINGS"; private final Launcher mLauncher; private final Hotseat mHotseat; - private final NotificationManager mNotificationManager; - private final Notification mNotification; private List mPredictedApps; private HotseatEduDialog mActiveDialog; @@ -77,9 +63,6 @@ public class HotseatEduController { mLauncher = launcher; mHotseat = launcher.getHotseat(); mOnOnboardingComplete = runnable; - mNotificationManager = mLauncher.getSystemService(NotificationManager.class); - createNotificationChannel(); - mNotification = createNotification(); } /** @@ -216,11 +199,6 @@ public class HotseatEduController { return pageId; } - - void removeNotification() { - mNotificationManager.cancel(ONBOARDING_NOTIFICATION_ID); - } - void moveHotseatItems() { mHotseat.removeAllViewsInLayout(); if (!mNewItems.isEmpty()) { @@ -258,45 +236,9 @@ public class HotseatEduController { void setPredictedApps(List predictedApps) { mPredictedApps = predictedApps; - if (!mPredictedApps.isEmpty() - && mLauncher.getOrientation() == Configuration.ORIENTATION_PORTRAIT) { - mNotificationManager.notify(ONBOARDING_NOTIFICATION_ID, mNotification); - } - else { - removeNotification(); - } - } - - private void createNotificationChannel() { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return; - CharSequence name = mLauncher.getString(R.string.hotseat_edu_prompt_title); - int importance = NotificationManager.IMPORTANCE_LOW; - NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, name, - importance); - mNotificationManager.createNotificationChannel(channel); - } - - private Notification createNotification() { - Intent intent = new Intent(mLauncher.getApplicationContext(), mLauncher.getClass()); - intent = new NotificationHandler().addToIntent(intent); - - CharSequence name = mLauncher.getString(R.string.hotseat_edu_prompt_title); - String description = mLauncher.getString(R.string.hotseat_edu_prompt_content); - NotificationCompat.Builder builder = new NotificationCompat.Builder(mLauncher, - NOTIFICATION_CHANNEL_ID) - .setContentTitle(name) - .setOngoing(true) - .setColor(Themes.getColorAccent(mLauncher)) - .setContentIntent(PendingIntent.getActivity(mLauncher, 0, intent, - PendingIntent.FLAG_CANCEL_CURRENT)) - .setSmallIcon(R.drawable.hotseat_edu_notification_icon) - .setContentText(description); - return builder.build(); - } void destroy() { - removeNotification(); if (mActiveDialog != null) { mActiveDialog.setHotseatEduController(null); } @@ -334,14 +276,5 @@ public class HotseatEduController { mActiveDialog.setHotseatEduController(this); mActiveDialog.show(mPredictedApps); } - - static class NotificationHandler implements - ActivityTracker.SchedulerCallback { - @Override - public boolean init(QuickstepLauncher activity, boolean alreadyOnHome) { - activity.getHotseatPredictionController().showEdu(); - return true; - } - } } 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 e2acf9358d..05bcb57adc 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 @@ -41,7 +41,6 @@ import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherSettings; -import com.android.launcher3.LauncherState; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.allapps.AllAppsStore; @@ -148,8 +147,7 @@ public class HotseatPredictionController implements DragController.DragListener, */ public void showEdu() { if (mHotseatEduController == null) return; - mLauncher.getStateManager().goToState(LauncherState.NORMAL, true, - () -> mHotseatEduController.showEdu()); + mHotseatEduController.showEdu(); } @Override diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java index 1dc5be6141..39e0f88c40 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java @@ -44,6 +44,7 @@ import com.android.launcher3.allapps.DiscoveryBounce; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.folder.Folder; +import com.android.launcher3.hybridhotseat.HotseatEduController; import com.android.launcher3.hybridhotseat.HotseatPredictionController; import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.model.data.ItemInfo; @@ -98,6 +99,20 @@ public class QuickstepLauncher extends BaseQuickstepLauncher { } } + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + if (HotseatEduController.HOTSEAT_EDU_ACTION.equals(intent.getAction()) + && mHotseatPredictionController != null) { + boolean alreadyOnHome = hasWindowFocus() && ((intent.getFlags() + & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) + != Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT); + getStateManager().goToState(NORMAL, alreadyOnHome, () -> { + mHotseatPredictionController.showEdu(); + }); + } + } + @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java index 94c7771513..a487869316 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java @@ -29,6 +29,7 @@ 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.DEACCEL_3; import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN; +import static com.android.launcher3.anim.Interpolators.FINAL_FRAME; import static com.android.launcher3.anim.Interpolators.INSTANT; import static com.android.launcher3.anim.Interpolators.LINEAR; import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2; @@ -188,7 +189,7 @@ public class QuickstepAtomicAnimationFactory extends } } else if (toState == NORMAL && fromState == OVERVIEW_PEEK) { // Keep fully visible until the very end (when overview is offscreen) to make invisible. - config.setInterpolator(ANIM_OVERVIEW_FADE, t -> t < 1 ? 0 : 1); + config.setInterpolator(ANIM_OVERVIEW_FADE, FINAL_FRAME); } else if (toState == OVERVIEW_PEEK && fromState == NORMAL) { config.setInterpolator(ANIM_OVERVIEW_FADE, INSTANT); config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, OVERSHOOT_1_7); 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 bf0690c77d..39bbfb93bc 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 @@ -116,8 +116,7 @@ public class NavBarToHomeTouchController implements TouchController, if (TestProtocol.sDebugTracing) { Log.d(TestProtocol.PAUSE_NOT_DETECTED, "NavBarToHomeTouchController.canInterceptTouch true 2 " - + AbstractFloatingView.getTopOpenView(mLauncher).getClass() - .getSimpleName()); + + AbstractFloatingView.getTopOpenView(mLauncher), new Exception()); } return true; } 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 5b396dd547..737d837ac7 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java @@ -15,57 +15,38 @@ */ package com.android.quickstep; -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.VibratorWrapper.OVERVIEW_HAPTIC; -import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION; -import android.animation.Animator; import android.annotation.TargetApi; import android.content.Context; import android.content.Intent; -import android.graphics.Matrix; -import android.graphics.Matrix.ScaleToFit; import android.graphics.PointF; import android.graphics.Rect; -import android.graphics.RectF; import android.os.Build; import android.util.Log; import android.view.MotionEvent; -import android.view.animation.Interpolator; import androidx.annotation.CallSuper; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import androidx.annotation.UiThread; import com.android.launcher3.DeviceProfile; import com.android.launcher3.InvariantDeviceProfile; -import com.android.launcher3.Utilities; -import com.android.launcher3.anim.AnimationSuccessListener; -import com.android.launcher3.anim.AnimatorPlaybackController; -import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.statemanager.StatefulActivity; import com.android.launcher3.testing.TestProtocol; -import com.android.launcher3.touch.PagedOrientationHandler; import com.android.launcher3.util.VibratorWrapper; -import com.android.launcher3.views.FloatingIconView; +import com.android.launcher3.util.WindowBounds; import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener; import com.android.quickstep.util.ActiveGestureLog; import com.android.quickstep.util.ActivityInitListener; -import com.android.quickstep.util.RectFSpringAnim; -import com.android.quickstep.util.TaskViewSimulator; import com.android.quickstep.util.TransformParams; -import com.android.quickstep.util.TransformParams.BuilderProxy; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.TaskView; import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.system.InputConsumerController; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat; -import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams.Builder; import java.util.ArrayList; import java.util.function.Consumer; @@ -75,40 +56,13 @@ import java.util.function.Consumer; */ @TargetApi(Build.VERSION_CODES.Q) public abstract class BaseSwipeUpHandler, Q extends RecentsView> - implements RecentsAnimationListener { + extends SwipeUpAnimationLogic implements RecentsAnimationListener { private static final String TAG = "BaseSwipeUpHandler"; - protected static final Rect TEMP_RECT = new Rect(); - public static final float MIN_PROGRESS_FOR_OVERVIEW = 0.7f; - private static final Interpolator PULLBACK_INTERPOLATOR = DEACCEL; - - // The distance needed to drag to reach the task size in recents. - protected int mTransitionDragLength; - // How much further we can drag past recents, as a factor of mTransitionDragLength. - protected float mDragLengthFactor = 1; - // Start resisting when swiping past this factor of mTransitionDragLength. - private float mDragLengthFactorStartPullback = 1f; - // This is how far down we can scale down, where 0f is full screen and 1f is recents. - private float mDragLengthFactorMaxPullback = 1f; - - protected final Context mContext; - protected final RecentsAnimationDeviceState mDeviceState; - protected final GestureState mGestureState; protected final BaseActivityInterface mActivityInterface; protected final InputConsumerController mInputConsumer; - protected final TaskViewSimulator mTaskViewSimulator; - private AnimatorPlaybackController mWindowTransitionController; - - protected final TransformParams mTransformParams = new TransformParams(); - - // 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 - // visible. - protected final AnimatedFloat mCurrentShift = new AnimatedFloat(this::updateFinalShift); - protected final ActivityInitListener mActivityInitListener; protected RecentsAnimationController mRecentsAnimationController; @@ -119,7 +73,6 @@ public abstract class BaseSwipeUpHandler, Q extend protected T mActivity; protected Q mRecentsView; - protected DeviceProfile mDp; protected Runnable mGestureEndCallback; @@ -131,13 +84,10 @@ public abstract class BaseSwipeUpHandler, Q extend protected BaseSwipeUpHandler(Context context, RecentsAnimationDeviceState deviceState, GestureState gestureState, InputConsumerController inputConsumer) { - mContext = context; - mDeviceState = deviceState; - mGestureState = gestureState; + super(context, deviceState, gestureState, new TransformParams()); mActivityInterface = gestureState.getActivityInterface(); mActivityInitListener = mActivityInterface.createActivityInitListener(this::onActivityInit); mInputConsumer = inputConsumer; - mTaskViewSimulator = new TaskViewSimulator(context, gestureState.getActivityInterface()); } /** @@ -157,28 +107,6 @@ public abstract class BaseSwipeUpHandler, Q extend return mRecentsView != null ? mRecentsView.getEventDispatcher(navbarRotation) : null; } - @UiThread - public void updateDisplacement(float displacement) { - // We are moving in the negative x/y direction - displacement = -displacement; - float shift; - if (displacement > mTransitionDragLength * mDragLengthFactor && mTransitionDragLength > 0) { - shift = mDragLengthFactor; - } else { - float translation = Math.max(displacement, 0); - shift = mTransitionDragLength == 0 ? 0 : translation / mTransitionDragLength; - if (shift > mDragLengthFactorStartPullback) { - float pullbackProgress = Utilities.getProgress(shift, - mDragLengthFactorStartPullback, mDragLengthFactor); - pullbackProgress = PULLBACK_INTERPOLATOR.getInterpolation(pullbackProgress); - shift = mDragLengthFactorStartPullback + pullbackProgress - * (mDragLengthFactorMaxPullback - mDragLengthFactorStartPullback); - } - } - - mCurrentShift.updateValue(shift); - } - public void setGestureEndCallback(Runnable gestureEndCallback) { mGestureEndCallback = gestureEndCallback; } @@ -275,6 +203,7 @@ public abstract class BaseSwipeUpHandler, Q extend RecentsAnimationTargets targets) { mRecentsAnimationController = recentsAnimationController; mRecentsAnimationTargets = targets; + mTransformParams.setTargetSet(mRecentsAnimationTargets); DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(mContext).getDeviceProfile(mContext); RemoteAnimationTargetCompat runningTaskTarget = targets.findTask( mGestureState.getRunningTaskId()); @@ -282,7 +211,8 @@ public abstract class BaseSwipeUpHandler, Q extend if (targets.minimizedHomeBounds != null && runningTaskTarget != null) { Rect overviewStackBounds = mActivityInterface .getOverviewWindowBounds(targets.minimizedHomeBounds, runningTaskTarget); - dp = dp.getMultiWindowProfile(mContext, overviewStackBounds); + dp = dp.getMultiWindowProfile(mContext, + new WindowBounds(overviewStackBounds, targets.homeContentInsets)); } else { // If we are not in multi-window mode, home insets should be same as system insets. dp = dp.copy(mContext); @@ -355,35 +285,6 @@ public abstract class BaseSwipeUpHandler, Q extend return mGestureState.getLastStartedTaskId() != -1; } - protected void initTransitionEndpoints(DeviceProfile dp) { - mDp = dp; - - mTaskViewSimulator.setDp(dp); - mTaskViewSimulator.setLayoutRotation( - mDeviceState.getCurrentActiveRotation(), - mDeviceState.getDisplayRotation()); - mTransitionDragLength = mActivityInterface.getSwipeUpDestinationAndLength( - dp, mContext, TEMP_RECT, - mTaskViewSimulator.getOrientationState().getOrientationHandler()); - - if (mDeviceState.isFullyGesturalNavMode()) { - // We can drag all the way to the top of the screen. - mDragLengthFactor = (float) dp.heightPx / mTransitionDragLength; - - float startScale = mTaskViewSimulator.getFullScreenScale(); - // Start pulling back when RecentsView scale is 0.75f, and let it go down to 0.5f. - mDragLengthFactorStartPullback = (0.75f - startScale) / (1 - startScale); - mDragLengthFactorMaxPullback = (0.5f - startScale) / (1 - startScale); - } else { - mDragLengthFactor = 1; - mDragLengthFactorStartPullback = mDragLengthFactorMaxPullback = 1; - } - - PendingAnimation pa = new PendingAnimation(mTransitionDragLength * 2); - mTaskViewSimulator.addAppToOverviewAnim(pa, t -> t * mDragLengthFactor); - mWindowTransitionController = pa.createPlaybackController(); - } - /** * Return true if the window should be translated horizontally if the recents view scrolls */ @@ -455,7 +356,6 @@ public abstract class BaseSwipeUpHandler, Q extend if (mWindowTransitionController != null) { float progress = mCurrentShift.value / mDragLengthFactor; mWindowTransitionController.setPlayFraction(progress); - mTransformParams.setTargetSet(mRecentsAnimationTargets); if (mRecentsViewScrollLinked) { mTaskViewSimulator.setScroll(mRecentsView.getScrollOffset()); @@ -464,217 +364,9 @@ public abstract class BaseSwipeUpHandler, Q extend } } - protected PagedOrientationHandler getOrientationHandler() { - return mTaskViewSimulator.getOrientationState().getOrientationHandler(); - } - - /** - * Creates an animation that transforms the current app window into the home app. - * @param startProgress The progress of {@link #mCurrentShift} to start the window from. - * @param homeAnimationFactory The home animation factory. - */ - protected RectFSpringAnim createWindowAnimationToHome(float startProgress, - HomeAnimationFactory homeAnimationFactory) { - final RectF targetRect = homeAnimationFactory.getWindowTargetRect(); - final FloatingIconView fiv = homeAnimationFactory.mIconView; - final boolean isFloatingIconView = fiv != null; - - mWindowTransitionController.setPlayFraction(startProgress / mDragLengthFactor); - mTaskViewSimulator.apply(mTransformParams - .setProgress(startProgress) - .setTargetSet(mRecentsAnimationTargets)); - RectF cropRectF = new RectF(mTaskViewSimulator.getCurrentCropRect()); - - // Matrix to map a rect in Launcher space to window space - Matrix homeToWindowPositionMap = new Matrix(); - mTaskViewSimulator.applyWindowToHomeRotation(homeToWindowPositionMap); - - final RectF startRect = new RectF(cropRectF); - mTaskViewSimulator.getCurrentMatrix().mapRect(startRect); - // Move the startRect to Launcher space as floatingIconView runs in Launcher - Matrix windowToHomePositionMap = new Matrix(); - homeToWindowPositionMap.invert(windowToHomePositionMap); - windowToHomePositionMap.mapRect(startRect); - - RectFSpringAnim anim = new RectFSpringAnim(startRect, targetRect, mContext); - if (isFloatingIconView) { - anim.addAnimatorListener(fiv); - fiv.setOnTargetChangeListener(anim::onTargetPositionChanged); - fiv.setFastFinishRunnable(anim::end); - } - - SpringAnimationRunner runner = new SpringAnimationRunner( - homeAnimationFactory, cropRectF, homeToWindowPositionMap); - anim.addOnUpdateListener(runner); - anim.addAnimatorListener(runner); - return anim; - } - public interface Factory { BaseSwipeUpHandler newHandler( GestureState gestureState, long touchTimeMs, boolean continuingLastGesture); } - - protected interface RunningWindowAnim { - void end(); - - void cancel(); - - static RunningWindowAnim wrap(Animator animator) { - return new RunningWindowAnim() { - @Override - public void end() { - animator.end(); - } - - @Override - public void cancel() { - animator.cancel(); - } - }; - } - - static RunningWindowAnim wrap(RectFSpringAnim rectFSpringAnim) { - return new RunningWindowAnim() { - @Override - public void end() { - rectFSpringAnim.end(); - } - - @Override - public void cancel() { - rectFSpringAnim.cancel(); - } - }; - } - } - - /** - * @param progress The progress of the animation to the home screen. - * @return The current alpha to set on the animating app window. - */ - protected float getWindowAlpha(float progress) { - // Alpha interpolates between [1, 0] between progress values [start, end] - final float start = 0f; - final float end = 0.85f; - - if (progress <= start) { - return 1f; - } - if (progress >= end) { - return 0f; - } - return Utilities.mapToRange(progress, start, end, 1, 0, ACCEL_1_5); - } - - protected abstract class HomeAnimationFactory { - - private FloatingIconView mIconView; - - public HomeAnimationFactory(@Nullable FloatingIconView iconView) { - mIconView = iconView; - } - - public @NonNull RectF getWindowTargetRect() { - PagedOrientationHandler orientationHandler = getOrientationHandler(); - DeviceProfile dp = mDp; - final int halfIconSize = dp.iconSizePx / 2; - float primaryDimension = orientationHandler - .getPrimaryValue(dp.availableWidthPx, dp.availableHeightPx); - float secondaryDimension = orientationHandler - .getSecondaryValue(dp.availableWidthPx, dp.availableHeightPx); - final float targetX = primaryDimension / 2f; - final float targetY = secondaryDimension - dp.hotseatBarSizePx; - // Fallback to animate to center of screen. - return new RectF(targetX - halfIconSize, targetY - halfIconSize, - targetX + halfIconSize, targetY + halfIconSize); - } - - public abstract @NonNull AnimatorPlaybackController createActivityAnimationToHome(); - - public void playAtomicAnimation(float velocity) { - // No-op - } - } - - private class SpringAnimationRunner extends AnimationSuccessListener - implements RectFSpringAnim.OnUpdateListener, BuilderProxy { - - final Rect mCropRect = new Rect(); - final Matrix mMatrix = new Matrix(); - - final RectF mWindowCurrentRect = new RectF(); - final Matrix mHomeToWindowPositionMap; - - final FloatingIconView mFIV; - final AnimatorPlaybackController mHomeAnim; - final RectF mCropRectF; - - final float mStartRadius; - final float mEndRadius; - final float mWindowAlphaThreshold; - - SpringAnimationRunner(HomeAnimationFactory factory, RectF cropRectF, - Matrix homeToWindowPositionMap) { - mHomeAnim = factory.createActivityAnimationToHome(); - mCropRectF = cropRectF; - mHomeToWindowPositionMap = homeToWindowPositionMap; - - cropRectF.roundOut(mCropRect); - mFIV = factory.mIconView; - - // End on a "round-enough" radius so that the shape reveal doesn't have to do too much - // rounding at the end of the animation. - mStartRadius = mTaskViewSimulator.getCurrentCornerRadius(); - mEndRadius = cropRectF.width() / 2f; - - // We want the window alpha to be 0 once this threshold is met, so that the - // FolderIconView can be seen morphing into the icon shape. - mWindowAlphaThreshold = mFIV != null ? 1f - SHAPE_PROGRESS_DURATION : 1f; - } - - @Override - public void onUpdate(RectF currentRect, float progress) { - mHomeAnim.setPlayFraction(progress); - mHomeToWindowPositionMap.mapRect(mWindowCurrentRect, currentRect); - - mMatrix.setRectToRect(mCropRectF, mWindowCurrentRect, ScaleToFit.FILL); - float cornerRadius = Utilities.mapRange(progress, mStartRadius, mEndRadius); - mTransformParams - .setTargetAlpha(getWindowAlpha(progress)) - .setCornerRadius(cornerRadius); - - mTransformParams.applySurfaceParams(mTransformParams.createSurfaceParams(this)); - if (mFIV != null) { - mFIV.update(currentRect, 1f, progress, - mWindowAlphaThreshold, mMatrix.mapRadius(cornerRadius), false); - } - } - - @Override - public void onBuildTargetParams( - Builder builder, RemoteAnimationTargetCompat app, TransformParams params) { - builder.withMatrix(mMatrix) - .withWindowCrop(mCropRect) - .withCornerRadius(params.getCornerRadius()); - } - - @Override - public void onCancel() { - if (mFIV != null) { - mFIV.fastFinish(); - } - } - - @Override - public void onAnimationStart(Animator animation) { - mHomeAnim.dispatchOnStart(); - } - - @Override - public void onAnimationSuccess(Animator animator) { - mHomeAnim.getAnimationPlayer().end(); - } - } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java index 46799ff272..92e10b14f9 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java @@ -29,6 +29,7 @@ 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.GestureState.STATE_END_TARGET_ANIMATION_FINISHED; +import static com.android.quickstep.GestureState.STATE_END_TARGET_SET; import static com.android.quickstep.GestureState.STATE_RECENTS_SCROLLING_FINISHED; import static com.android.quickstep.MultiStateCallback.DEBUG_STATES; import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS; @@ -253,6 +254,10 @@ public abstract class BaseSwipeUpHandlerV2, Q exte mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED | STATE_RESUME_LAST_TASK, this::notifyTransitionCancelled); + mGestureState.runOnceAtState(STATE_END_TARGET_SET, + () -> mDeviceState.onEndTargetCalculated(mGestureState.getEndTarget(), + mActivityInterface)); + if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) { mStateCallback.addChangeListener(STATE_APP_CONTROLLER_RECEIVED | STATE_LAUNCHER_PRESENT | STATE_SCREENSHOT_VIEW_SHOWN | STATE_CAPTURE_SCREENSHOT, @@ -1035,7 +1040,7 @@ public abstract class BaseSwipeUpHandlerV2, Q exte } // Make sure recents is in its final state maybeUpdateRecentsAttachedState(false); - mActivityInterface.onSwipeUpToHomeComplete(); + mActivityInterface.onSwipeUpToHomeComplete(mDeviceState); } }); return anim; @@ -1232,7 +1237,6 @@ public abstract class BaseSwipeUpHandlerV2, Q exte } ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", true); doLogGesture(HOME); - mDeviceState.enableMultipleRegions(false); } protected abstract void finishRecentsControllerToHome(Runnable callback); @@ -1248,7 +1252,6 @@ public abstract class BaseSwipeUpHandlerV2, Q exte SystemUiProxy.INSTANCE.get(mContext).onOverviewShown(false, TAG); doLogGesture(RECENTS); - mDeviceState.onSwipeUpToOverview(mActivityInterface); reset(); } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java index c9ff88409a..70be3ab8af 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java @@ -69,7 +69,7 @@ public final class FallbackActivityInterface extends /** 4 */ @Override - public void onSwipeUpToHomeComplete() { + public void onSwipeUpToHomeComplete(RecentsAnimationDeviceState deviceState) { onSwipeUpToRecentsComplete(); } 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 13b84e036e..62eb235251 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java @@ -96,7 +96,7 @@ public final class LauncherActivityInterface extends } @Override - public void onSwipeUpToHomeComplete() { + public void onSwipeUpToHomeComplete(RecentsAnimationDeviceState deviceState) { Launcher launcher = getCreatedActivity(); if (launcher == null) { return; @@ -105,6 +105,7 @@ public final class LauncherActivityInterface extends // recents, we assume the first task is invisible, making translation off by one task. launcher.getStateManager().reapplyState(); launcher.getRootView().setForceHideBackArrow(false); + notifyRecentsOfOrientation(deviceState); } @Override @@ -235,17 +236,20 @@ public final class LauncherActivityInterface extends // Are we going from Recents to Workspace? if (toState == LauncherState.NORMAL) { exitRunnable.run(); - - // reset layout on swipe to home - RecentsView recentsView = getCreatedActivity().getOverviewPanel(); - recentsView.setLayoutRotation(deviceState.getCurrentActiveRotation(), - deviceState.getDisplayRotation()); + notifyRecentsOfOrientation(deviceState); stateManager.removeStateListener(this); } } }); } + private void notifyRecentsOfOrientation(RecentsAnimationDeviceState deviceState) { + // reset layout on swipe to home + RecentsView recentsView = getCreatedActivity().getOverviewPanel(); + recentsView.setLayoutRotation(deviceState.getCurrentActiveRotation(), + deviceState.getDisplayRotation()); + } + @Override public Rect getOverviewWindowBounds(Rect homeBounds, RemoteAnimationTargetCompat target) { return homeBounds; 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 0d49b2b500..a28dabc6a3 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java @@ -79,12 +79,13 @@ public class QuickstepTestInformationHandler extends TestInformationHandler { @Override protected Activity getCurrentActivity() { - OverviewComponentObserver observer = new OverviewComponentObserver(mContext, - new RecentsAnimationDeviceState(mContext)); + RecentsAnimationDeviceState rads = new RecentsAnimationDeviceState(mContext); + OverviewComponentObserver observer = new OverviewComponentObserver(mContext, rads); try { return observer.getActivityInterface().getCreatedActivity(); } finally { observer.onDestroy(); + rads.destroy(); } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeUpAnimationLogic.java new file mode 100644 index 0000000000..b17730ba6d --- /dev/null +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeUpAnimationLogic.java @@ -0,0 +1,350 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.quickstep; + +import static com.android.launcher3.anim.Interpolators.ACCEL_1_5; +import static com.android.launcher3.anim.Interpolators.DEACCEL; +import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION; + +import android.animation.Animator; +import android.content.Context; +import android.graphics.Matrix; +import android.graphics.Matrix.ScaleToFit; +import android.graphics.Rect; +import android.graphics.RectF; +import android.view.animation.Interpolator; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.UiThread; + +import com.android.launcher3.DeviceProfile; +import com.android.launcher3.Utilities; +import com.android.launcher3.anim.AnimationSuccessListener; +import com.android.launcher3.anim.AnimatorPlaybackController; +import com.android.launcher3.anim.PendingAnimation; +import com.android.launcher3.touch.PagedOrientationHandler; +import com.android.launcher3.views.FloatingIconView; +import com.android.quickstep.util.RectFSpringAnim; +import com.android.quickstep.util.TaskViewSimulator; +import com.android.quickstep.util.TransformParams; +import com.android.quickstep.util.TransformParams.BuilderProxy; +import com.android.systemui.shared.system.RemoteAnimationTargetCompat; +import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams.Builder; + +public abstract class SwipeUpAnimationLogic { + + protected static final Rect TEMP_RECT = new Rect(); + private static final Interpolator PULLBACK_INTERPOLATOR = DEACCEL; + + protected DeviceProfile mDp; + + protected final Context mContext; + protected final RecentsAnimationDeviceState mDeviceState; + protected final GestureState mGestureState; + protected final TaskViewSimulator mTaskViewSimulator; + + protected final TransformParams mTransformParams; + + // 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 + // visible. + protected final AnimatedFloat mCurrentShift = new AnimatedFloat(this::updateFinalShift); + + // The distance needed to drag to reach the task size in recents. + protected int mTransitionDragLength; + // How much further we can drag past recents, as a factor of mTransitionDragLength. + protected float mDragLengthFactor = 1; + // Start resisting when swiping past this factor of mTransitionDragLength. + private float mDragLengthFactorStartPullback = 1f; + // This is how far down we can scale down, where 0f is full screen and 1f is recents. + private float mDragLengthFactorMaxPullback = 1f; + + protected AnimatorPlaybackController mWindowTransitionController; + + public SwipeUpAnimationLogic(Context context, RecentsAnimationDeviceState deviceState, + GestureState gestureState, TransformParams transformParams) { + mContext = context; + mDeviceState = deviceState; + mGestureState = gestureState; + mTaskViewSimulator = new TaskViewSimulator(context, gestureState.getActivityInterface()); + mTransformParams = transformParams; + } + + protected void initTransitionEndpoints(DeviceProfile dp) { + mDp = dp; + + mTaskViewSimulator.setDp(dp); + mTaskViewSimulator.setLayoutRotation( + mDeviceState.getCurrentActiveRotation(), + mDeviceState.getDisplayRotation()); + mTransitionDragLength = mGestureState.getActivityInterface().getSwipeUpDestinationAndLength( + dp, mContext, TEMP_RECT, + mTaskViewSimulator.getOrientationState().getOrientationHandler()); + + if (mDeviceState.isFullyGesturalNavMode()) { + // We can drag all the way to the top of the screen. + mDragLengthFactor = (float) dp.heightPx / mTransitionDragLength; + + float startScale = mTaskViewSimulator.getFullScreenScale(); + // Start pulling back when RecentsView scale is 0.75f, and let it go down to 0.5f. + mDragLengthFactorStartPullback = (0.75f - startScale) / (1 - startScale); + mDragLengthFactorMaxPullback = (0.5f - startScale) / (1 - startScale); + } else { + mDragLengthFactor = 1; + mDragLengthFactorStartPullback = mDragLengthFactorMaxPullback = 1; + } + + PendingAnimation pa = new PendingAnimation(mTransitionDragLength * 2); + mTaskViewSimulator.addAppToOverviewAnim(pa, t -> t * mDragLengthFactor); + mWindowTransitionController = pa.createPlaybackController(); + } + + @UiThread + public void updateDisplacement(float displacement) { + // We are moving in the negative x/y direction + displacement = -displacement; + float shift; + if (displacement > mTransitionDragLength * mDragLengthFactor && mTransitionDragLength > 0) { + shift = mDragLengthFactor; + } else { + float translation = Math.max(displacement, 0); + shift = mTransitionDragLength == 0 ? 0 : translation / mTransitionDragLength; + if (shift > mDragLengthFactorStartPullback) { + float pullbackProgress = Utilities.getProgress(shift, + mDragLengthFactorStartPullback, mDragLengthFactor); + pullbackProgress = PULLBACK_INTERPOLATOR.getInterpolation(pullbackProgress); + shift = mDragLengthFactorStartPullback + pullbackProgress + * (mDragLengthFactorMaxPullback - mDragLengthFactorStartPullback); + } + } + + mCurrentShift.updateValue(shift); + } + + /** + * Called when the value of {@link #mCurrentShift} changes + */ + @UiThread + public abstract void updateFinalShift(); + + protected PagedOrientationHandler getOrientationHandler() { + return mTaskViewSimulator.getOrientationState().getOrientationHandler(); + } + + protected abstract class HomeAnimationFactory { + + public FloatingIconView mIconView; + + public HomeAnimationFactory(@Nullable FloatingIconView iconView) { + mIconView = iconView; + } + + public @NonNull RectF getWindowTargetRect() { + PagedOrientationHandler orientationHandler = getOrientationHandler(); + DeviceProfile dp = mDp; + final int halfIconSize = dp.iconSizePx / 2; + float primaryDimension = orientationHandler + .getPrimaryValue(dp.availableWidthPx, dp.availableHeightPx); + float secondaryDimension = orientationHandler + .getSecondaryValue(dp.availableWidthPx, dp.availableHeightPx); + final float targetX = primaryDimension / 2f; + final float targetY = secondaryDimension - dp.hotseatBarSizePx; + // Fallback to animate to center of screen. + return new RectF(targetX - halfIconSize, targetY - halfIconSize, + targetX + halfIconSize, targetY + halfIconSize); + } + + public abstract @NonNull AnimatorPlaybackController createActivityAnimationToHome(); + + public void playAtomicAnimation(float velocity) { + // No-op + } + } + + /** + * Creates an animation that transforms the current app window into the home app. + * @param startProgress The progress of {@link #mCurrentShift} to start the window from. + * @param homeAnimationFactory The home animation factory. + */ + protected RectFSpringAnim createWindowAnimationToHome(float startProgress, + HomeAnimationFactory homeAnimationFactory) { + final RectF targetRect = homeAnimationFactory.getWindowTargetRect(); + final FloatingIconView fiv = homeAnimationFactory.mIconView; + final boolean isFloatingIconView = fiv != null; + + mWindowTransitionController.setPlayFraction(startProgress / mDragLengthFactor); + mTaskViewSimulator.apply(mTransformParams.setProgress(startProgress)); + RectF cropRectF = new RectF(mTaskViewSimulator.getCurrentCropRect()); + + // Matrix to map a rect in Launcher space to window space + Matrix homeToWindowPositionMap = new Matrix(); + mTaskViewSimulator.applyWindowToHomeRotation(homeToWindowPositionMap); + + final RectF startRect = new RectF(cropRectF); + mTaskViewSimulator.getCurrentMatrix().mapRect(startRect); + // Move the startRect to Launcher space as floatingIconView runs in Launcher + Matrix windowToHomePositionMap = new Matrix(); + homeToWindowPositionMap.invert(windowToHomePositionMap); + windowToHomePositionMap.mapRect(startRect); + + RectFSpringAnim anim = new RectFSpringAnim(startRect, targetRect, mContext); + if (isFloatingIconView) { + anim.addAnimatorListener(fiv); + fiv.setOnTargetChangeListener(anim::onTargetPositionChanged); + fiv.setFastFinishRunnable(anim::end); + } + + SpringAnimationRunner runner = new SpringAnimationRunner( + homeAnimationFactory, cropRectF, homeToWindowPositionMap); + anim.addOnUpdateListener(runner); + anim.addAnimatorListener(runner); + return anim; + } + + /** + * @param progress The progress of the animation to the home screen. + * @return The current alpha to set on the animating app window. + */ + protected float getWindowAlpha(float progress) { + // Alpha interpolates between [1, 0] between progress values [start, end] + final float start = 0f; + final float end = 0.85f; + + if (progress <= start) { + return 1f; + } + if (progress >= end) { + return 0f; + } + return Utilities.mapToRange(progress, start, end, 1, 0, ACCEL_1_5); + } + + protected class SpringAnimationRunner extends AnimationSuccessListener + implements RectFSpringAnim.OnUpdateListener, BuilderProxy { + + final Rect mCropRect = new Rect(); + final Matrix mMatrix = new Matrix(); + + final RectF mWindowCurrentRect = new RectF(); + final Matrix mHomeToWindowPositionMap; + + final FloatingIconView mFIV; + final AnimatorPlaybackController mHomeAnim; + final RectF mCropRectF; + + final float mStartRadius; + final float mEndRadius; + final float mWindowAlphaThreshold; + + SpringAnimationRunner(HomeAnimationFactory factory, RectF cropRectF, + Matrix homeToWindowPositionMap) { + mHomeAnim = factory.createActivityAnimationToHome(); + mCropRectF = cropRectF; + mHomeToWindowPositionMap = homeToWindowPositionMap; + + cropRectF.roundOut(mCropRect); + mFIV = factory.mIconView; + + // End on a "round-enough" radius so that the shape reveal doesn't have to do too much + // rounding at the end of the animation. + mStartRadius = mTaskViewSimulator.getCurrentCornerRadius(); + mEndRadius = cropRectF.width() / 2f; + + // We want the window alpha to be 0 once this threshold is met, so that the + // FolderIconView can be seen morphing into the icon shape. + mWindowAlphaThreshold = mFIV != null ? 1f - SHAPE_PROGRESS_DURATION : 1f; + } + + @Override + public void onUpdate(RectF currentRect, float progress) { + mHomeAnim.setPlayFraction(progress); + mHomeToWindowPositionMap.mapRect(mWindowCurrentRect, currentRect); + + mMatrix.setRectToRect(mCropRectF, mWindowCurrentRect, ScaleToFit.FILL); + float cornerRadius = Utilities.mapRange(progress, mStartRadius, mEndRadius); + mTransformParams + .setTargetAlpha(getWindowAlpha(progress)) + .setCornerRadius(cornerRadius); + + mTransformParams.applySurfaceParams(mTransformParams.createSurfaceParams(this)); + if (mFIV != null) { + mFIV.update(currentRect, 1f, progress, + mWindowAlphaThreshold, mMatrix.mapRadius(cornerRadius), false); + } + } + + @Override + public void onBuildTargetParams( + Builder builder, RemoteAnimationTargetCompat app, TransformParams params) { + builder.withMatrix(mMatrix) + .withWindowCrop(mCropRect) + .withCornerRadius(params.getCornerRadius()); + } + + @Override + public void onCancel() { + if (mFIV != null) { + mFIV.fastFinish(); + } + } + + @Override + public void onAnimationStart(Animator animation) { + mHomeAnim.dispatchOnStart(); + } + + @Override + public void onAnimationSuccess(Animator animator) { + mHomeAnim.getAnimationPlayer().end(); + } + } + + public interface RunningWindowAnim { + void end(); + + void cancel(); + + static RunningWindowAnim wrap(Animator animator) { + return new RunningWindowAnim() { + @Override + public void end() { + animator.end(); + } + + @Override + public void cancel() { + animator.cancel(); + } + }; + } + + static RunningWindowAnim wrap(RectFSpringAnim rectFSpringAnim) { + return new RunningWindowAnim() { + @Override + public void end() { + rectFSpringAnim.end(); + } + + @Override + public void cancel() { + rectFSpringAnim.cancel(); + } + }; + } + } +} 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 c4d4ab850a..c8619bdd12 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java @@ -82,6 +82,7 @@ import com.android.quickstep.inputconsumers.OverviewInputConsumer; import com.android.quickstep.inputconsumers.OverviewWithoutFocusInputConsumer; import com.android.quickstep.inputconsumers.ResetGestureInputConsumer; import com.android.quickstep.inputconsumers.ScreenPinnedInputConsumer; +import com.android.quickstep.inputconsumers.SysUiOverlayInputConsumer; import com.android.quickstep.util.ActiveGestureLog; import com.android.quickstep.util.AssistantUtilities; import com.android.quickstep.util.ProtoTracer; @@ -595,6 +596,13 @@ public class TouchInteractionService extends Service implements PluginListenergetOverviewPanel().startHome(); } + + @Override + public void onSwipeUpCancelled() {} } 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 8b08ea76fe..14215a1f67 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 @@ -403,6 +403,11 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC TraceHelper.INSTANCE.endSection(traceToken); } + @Override + public void notifyOrientationSetup() { + mDeviceState.onStartGesture(); + } + @Override public void onConsumerAboutToBeSwitched() { Preconditions.assertUIThread(); 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 ac1c3a8742..4440a04bea 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 @@ -17,6 +17,7 @@ package com.android.quickstep.inputconsumers; import android.content.Context; import android.content.Intent; +import android.graphics.PointF; import android.view.MotionEvent; import com.android.launcher3.BaseActivity; @@ -33,7 +34,8 @@ import com.android.quickstep.util.ActiveGestureLog; import com.android.quickstep.util.TriggerSwipeUpTouchTracker; import com.android.systemui.shared.system.InputMonitorCompat; -public class OverviewWithoutFocusInputConsumer implements InputConsumer { +public class OverviewWithoutFocusInputConsumer implements InputConsumer, + TriggerSwipeUpTouchTracker.OnSwipeUpListener { private final Context mContext; private final InputMonitorCompat mInputMonitor; @@ -45,7 +47,7 @@ public class OverviewWithoutFocusInputConsumer implements InputConsumer { mContext = context; mInputMonitor = inputMonitor; mTriggerSwipeUpTracker = new TriggerSwipeUpTouchTracker(context, disableHorizontalSwipe, - deviceState.getNavBarPosition(), this::onInterceptTouch, this::onSwipeUp); + deviceState.getNavBarPosition(), this::onInterceptTouch, this); } @Override @@ -70,7 +72,8 @@ public class OverviewWithoutFocusInputConsumer implements InputConsumer { } } - private void onSwipeUp(boolean wasFling) { + @Override + public void onSwipeUp(boolean wasFling, PointF finalVelocity) { mContext.startActivity(new Intent(Intent.ACTION_MAIN) .addCategory(Intent.CATEGORY_HOME) .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); @@ -83,4 +86,7 @@ public class OverviewWithoutFocusInputConsumer implements InputConsumer { wasFling ? Touch.FLING : Touch.SWIPE, Direction.UP, containerType, pageIndex); activity.getUserEventDispatcher().setPreviousHomeGesture(true); } + + @Override + public void onSwipeUpCancelled() {} } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/SysUiOverlayInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/SysUiOverlayInputConsumer.java new file mode 100644 index 0000000000..3f833c0a6d --- /dev/null +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/SysUiOverlayInputConsumer.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.quickstep.inputconsumers; + +import android.content.Context; +import android.content.Intent; +import android.graphics.PointF; +import android.view.MotionEvent; + +import com.android.launcher3.testing.TestLogging; +import com.android.launcher3.testing.TestProtocol; +import com.android.quickstep.InputConsumer; +import com.android.quickstep.RecentsAnimationDeviceState; +import com.android.quickstep.util.TriggerSwipeUpTouchTracker; +import com.android.systemui.shared.system.InputMonitorCompat; + +/** + * Input consumer used when a fullscreen System UI overlay is showing (such as the expanded Bubbles + * UI). + * + * This responds to swipes up by sending a closeSystemDialogs broadcast (causing overlays to close) + * rather than closing the app behind the overlay and sending the user all the way home. + */ +public class SysUiOverlayInputConsumer implements InputConsumer, + TriggerSwipeUpTouchTracker.OnSwipeUpListener { + + private final Context mContext; + private final InputMonitorCompat mInputMonitor; + private final TriggerSwipeUpTouchTracker mTriggerSwipeUpTracker; + + public SysUiOverlayInputConsumer( + Context context, + RecentsAnimationDeviceState deviceState, + InputMonitorCompat inputMonitor) { + mContext = context; + mInputMonitor = inputMonitor; + mTriggerSwipeUpTracker = new TriggerSwipeUpTouchTracker(context, true, + deviceState.getNavBarPosition(), this::onInterceptTouch, this); + } + + @Override + public int getType() { + return TYPE_SYSUI_OVERLAY; + } + + @Override + public boolean allowInterceptByParent() { + return !mTriggerSwipeUpTracker.interceptedTouch(); + } + + @Override + public void onMotionEvent(MotionEvent ev) { + mTriggerSwipeUpTracker.onMotionEvent(ev); + } + + private void onInterceptTouch() { + if (mInputMonitor != null) { + TestLogging.recordEvent(TestProtocol.SEQUENCE_PILFER, "pilferPointers"); + mInputMonitor.pilferPointers(); + } + } + + @Override + public void onSwipeUp(boolean wasFling, PointF finalVelocity) { + // Close system dialogs when a swipe up is detected. + mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); + } + + @Override + public void onSwipeUpCancelled() { + + } +} diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java index 3b08675885..3c9762be6d 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java @@ -23,6 +23,7 @@ import static com.android.systemui.shared.system.WindowManagerWrapper.WINDOWING_ import android.animation.TimeInterpolator; import android.content.Context; import android.graphics.Matrix; +import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.RectF; @@ -74,7 +75,7 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { private DeviceProfile mDp; private final Matrix mMatrix = new Matrix(); - private RemoteAnimationTargetCompat mRunningTarget; + private final Point mRunningTargetWindowPosition = new Point(); // Thumbnail view properties private final Rect mThumbnailPosition = new Rect(); @@ -139,13 +140,19 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { * Sets the targets which the simulator will control */ public void setPreview(RemoteAnimationTargetCompat runningTarget) { - mRunningTarget = runningTarget; + setPreviewBounds(runningTarget.screenSpaceBounds, runningTarget.contentInsets); + mRunningTargetWindowPosition.set(runningTarget.position.x, runningTarget.position.y); + } - mThumbnailData.insets.set(mRunningTarget.contentInsets); + /** + * Sets the targets which the simulator will control + */ + public void setPreviewBounds(Rect bounds, Rect insets) { + mThumbnailData.insets.set(insets); // TODO: What is this? mThumbnailData.windowingMode = WINDOWING_MODE_FULLSCREEN; - mThumbnailPosition.set(runningTarget.screenSpaceBounds); + mThumbnailPosition.set(bounds); mLayoutValid = false; } @@ -199,16 +206,14 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { postDisplayRotation(deltaRotation( mOrientationState.getLauncherRotation(), mOrientationState.getDisplayRotation()), mDp.widthPx, mDp.heightPx, matrix); - if (mRunningTarget != null) { - matrix.postTranslate(-mRunningTarget.position.x, -mRunningTarget.position.y); - } + matrix.postTranslate(-mRunningTargetWindowPosition.x, -mRunningTargetWindowPosition.y); } /** * Applies the target to the previously set parameters */ public void apply(TransformParams params) { - if (mDp == null || mRunningTarget == null) { + if (mDp == null || mThumbnailPosition.isEmpty()) { return; } if (!mLayoutValid) { diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TriggerSwipeUpTouchTracker.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TriggerSwipeUpTouchTracker.java index c71258b87b..29b95589f3 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TriggerSwipeUpTouchTracker.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TriggerSwipeUpTouchTracker.java @@ -149,8 +149,12 @@ public class TriggerSwipeUpTouchTracker { isSwipeUp = squaredHypot(displacementX, displacementY) >= mSquaredTouchSlop; } - if (isSwipeUp && mOnSwipeUp != null) { - mOnSwipeUp.onSwipeUp(wasFling); + if (mOnSwipeUp != null) { + if (isSwipeUp) { + mOnSwipeUp.onSwipeUp(wasFling, new PointF(velocityX, velocityY)); + } else { + mOnSwipeUp.onSwipeUpCancelled(); + } } } @@ -161,7 +165,11 @@ public class TriggerSwipeUpTouchTracker { /** * Called on touch up if a swipe up was detected. * @param wasFling Whether the swipe was a fling, or just passed touch slop at low velocity. + * @param finalVelocity The final velocity of the swipe. */ - void onSwipeUp(boolean wasFling); + void onSwipeUp(boolean wasFling, PointF finalVelocity); + + /** Called on touch up if a swipe up was not detected. */ + void onSwipeUpCancelled(); } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java index ea33d007f4..83287c463a 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java @@ -45,8 +45,6 @@ import java.lang.annotation.RetentionPolicy; public class OverviewActionsView extends FrameLayout implements OnClickListener { - public static final long VISIBILITY_TRANSITION_DURATION_MS = 80; - @IntDef(flag = true, value = { HIDDEN_UNSUPPORTED_NAVIGATION, HIDDEN_DISABLED_FEATURE, 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 a506b7eb0c..324aaecf0e 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 @@ -1039,6 +1039,11 @@ public abstract class RecentsView extends PagedView impl } private void animateRecentsRotationInPlace(int newRotation) { + if (mOrientationState.canLauncherRotate()) { + // Update the rotation but let system take care of the rotation animation + setLayoutRotation(newRotation, mOrientationState.getDisplayRotation()); + return; + } AnimatorSet pa = setRecentsChangedOrientation(true); pa.addListener(AnimationSuccessListener.forRunnable(() -> { setLayoutRotation(newRotation, mOrientationState.getDisplayRotation()); diff --git a/quickstep/res/layout/gesture_tutorial_fragment.xml b/quickstep/res/layout/gesture_tutorial_fragment.xml index 190290ebb4..74197bef40 100644 --- a/quickstep/res/layout/gesture_tutorial_fragment.xml +++ b/quickstep/res/layout/gesture_tutorial_fragment.xml @@ -24,6 +24,13 @@ android:layout_height="match_parent" android:background="@drawable/gesture_tutorial_ripple"/> + + Close - - - Easily access your most-used apps - - Pixel predicts apps you\’ll need next, right on your Home screen. Tap to set up. - Get app suggestions on the bottom row of your Home screen diff --git a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java index 8292a92af6..fcffaedfd8 100644 --- a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java +++ b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java @@ -102,11 +102,30 @@ public class DepthController implements StateHandler { */ private float mDepth; + private View.OnAttachStateChangeListener mOnAttachListener; + public DepthController(Launcher l) { mLauncher = l; } private void ensureDependencies() { + if (mLauncher.getRootView() != null && mOnAttachListener == null) { + mOnAttachListener = new View.OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(View view) { + // To handle the case where window token is invalid during last setDepth call. + IBinder windowToken = mLauncher.getRootView().getWindowToken(); + if (windowToken != null) { + mWallpaperManager.setWallpaperZoomOut(windowToken, mDepth); + } + } + + @Override + public void onViewDetachedFromWindow(View view) { + } + }; + mLauncher.getRootView().addOnAttachStateChangeListener(mOnAttachListener); + } if (mWallpaperManager != null) { return; } @@ -184,10 +203,10 @@ public class DepthController implements StateHandler { return; } - mDepth = depthF; if (mSurface == null || !mSurface.isValid()) { return; } + mDepth = depthF; ensureDependencies(); IBinder windowToken = mLauncher.getRootView().getWindowToken(); if (windowToken != null) { diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java index 91249250f5..7122647e6f 100644 --- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java +++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java @@ -101,7 +101,7 @@ public abstract class BaseActivityInterface TYPE_NO_OP; @@ -71,6 +73,11 @@ public interface InputConsumer { return false; } + /** + * Handle and specific setup necessary based on the orientation of the device + */ + default void notifyOrientationSetup() {} + /** * Returns the active input consumer is in the hierarchy of this input consumer. */ diff --git a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java index 6b94a79000..b59f0118af 100644 --- a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java +++ b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java @@ -67,6 +67,14 @@ class OrientationTouchTransformer { private boolean mEnableMultipleRegions; private Resources mResources; private OrientationRectF mLastRectTouched; + /** + * The rotation of the last touched nav bar. Derived from {@link #mLastRectTouched}, but has a + * longer lifetime than the rect. Note this is different than {@link #mQuickStepStartingRotation} + * as it always updates its value on every touch whereas mQuickstepStartingRotation only + * updates when device rotation matches touch rotation. Maybe this will be only one necessary + * after TODO(b/154580671) is in. TBD. + */ + private int mLastRectRotation; private SysUINavigationMode.Mode mMode; private QuickStepContractInfo mContractInfo; @@ -143,15 +151,18 @@ class OrientationTouchTransformer { * ALSO, you BETTER call this with {@param enableMultipleRegions} set to false once you're done. * * @param enableMultipleRegions Set to true to start tracking multiple nav bar regions - * @param info The current displayInfo + * @param info The current displayInfo which will be the start of the quickswitch gesture */ void enableMultipleRegions(boolean enableMultipleRegions, DefaultDisplay.Info info) { mEnableMultipleRegions = enableMultipleRegions && mMode != SysUINavigationMode.Mode.TWO_BUTTONS; - if (!enableMultipleRegions) { + if (mEnableMultipleRegions) { + mQuickStepStartingRotation = info.rotation; + } else { + mLastRectRotation = 0; mQuickStepStartingRotation = QUICKSTEP_ROTATION_UNINITIALIZED; - resetSwipeRegions(info); } + resetSwipeRegions(info); } /** @@ -248,11 +259,7 @@ class OrientationTouchTransformer { } int getCurrentActiveRotation() { - if (mLastRectTouched == null) { - return 0; - } else { - return mLastRectTouched.mRotation; - } + return mLastRectRotation; } int getQuickStepStartingRotation() { @@ -291,7 +298,9 @@ class OrientationTouchTransformer { } if (rect.applyTransform(event, false)) { mLastRectTouched = rect; - if (mCurrentDisplayRotation == mLastRectTouched.mRotation) { + mLastRectRotation = rect.mRotation; + if (mEnableMultipleRegions && mCurrentDisplayRotation == mLastRectRotation) { + // TODO(b/154580671) might make this block unnecessary // Start a touch session for the default nav region for the display mQuickStepStartingRotation = mLastRectTouched.mRotation; resetSwipeRegions(); diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java index 2d9c56f673..70b4f20c2b 100644 --- a/quickstep/src/com/android/quickstep/RecentTasksList.java +++ b/quickstep/src/com/android/quickstep/RecentTasksList.java @@ -40,21 +40,20 @@ import java.util.function.Consumer; /** * Manages the recent task list from the system, caching it as necessary. */ -@TargetApi(Build.VERSION_CODES.P) +@TargetApi(Build.VERSION_CODES.R) public class RecentTasksList extends TaskStackChangeListener { + private static final TaskLoadResult INVALID_RESULT = new TaskLoadResult(-1, false, 0); + private final KeyguardManagerCompat mKeyguardManager; private final LooperExecutor mMainThreadExecutor; private final ActivityManagerWrapper mActivityManagerWrapper; // The list change id, increments as the task list changes in the system private int mChangeId; - // The last change id when the list was last loaded completely, must be <= the list change id - private int mLastLoadedId; - // The last change id was loaded with keysOnly = true - private boolean mLastLoadHadKeysOnly; - ArrayList mTasks = new ArrayList<>(); + private TaskLoadResult mResultsBg = INVALID_RESULT; + private TaskLoadResult mResultsUi = INVALID_RESULT; public RecentTasksList(LooperExecutor mainThreadExecutor, KeyguardManagerCompat keyguardManager, ActivityManagerWrapper activityManagerWrapper) { @@ -71,7 +70,7 @@ public class RecentTasksList extends TaskStackChangeListener { public void getTaskKeys(int numTasks, Consumer> callback) { // Kick off task loading in the background UI_HELPER_EXECUTOR.execute(() -> { - ArrayList tasks = loadTasksInBackground(numTasks, true /* loadKeysOnly */); + ArrayList tasks = loadTasksInBackground(numTasks, -1, true /* loadKeysOnly */); mMainThreadExecutor.execute(() -> callback.accept(tasks)); }); } @@ -85,26 +84,30 @@ public class RecentTasksList extends TaskStackChangeListener { */ public synchronized int getTasks(boolean loadKeysOnly, Consumer> callback) { final int requestLoadId = mChangeId; - Runnable resultCallback = callback == null - ? () -> { } - : () -> callback.accept(copyOf(mTasks)); - - if (mLastLoadedId == mChangeId && (!mLastLoadHadKeysOnly || loadKeysOnly)) { + if (mResultsUi.isValidForRequest(requestLoadId, loadKeysOnly)) { // The list is up to date, send the callback on the next frame, // so that requestID can be returned first. - mMainThreadExecutor.post(resultCallback); + if (callback != null) { + // Copy synchronously as the changeId might change by next frame + ArrayList result = copyOf(mResultsUi); + mMainThreadExecutor.post(() -> callback.accept(result)); + } + return requestLoadId; } // Kick off task loading in the background UI_HELPER_EXECUTOR.execute(() -> { - ArrayList tasks = loadTasksInBackground(Integer.MAX_VALUE, loadKeysOnly); - + if (!mResultsBg.isValidForRequest(requestLoadId, loadKeysOnly)) { + mResultsBg = loadTasksInBackground(Integer.MAX_VALUE, requestLoadId, loadKeysOnly); + } + TaskLoadResult loadResult = mResultsBg; mMainThreadExecutor.execute(() -> { - mTasks = tasks; - mLastLoadedId = requestLoadId; - mLastLoadHadKeysOnly = loadKeysOnly; - resultCallback.run(); + mResultsUi = loadResult; + if (callback != null) { + ArrayList result = copyOf(mResultsUi); + callback.accept(result); + } }); }); @@ -119,8 +122,8 @@ public class RecentTasksList extends TaskStackChangeListener { } @Override - public synchronized void onTaskStackChanged() { - mChangeId++; + public void onTaskStackChanged() { + invalidateLoadedTasks(); } @Override @@ -131,22 +134,28 @@ public class RecentTasksList extends TaskStackChangeListener { // callback (those are for changes to the active tasks), but the task list is still updated, // so we should also invalidate the change id to ensure we load a new list instead of // reusing a stale list. - mChangeId++; + invalidateLoadedTasks(); } @Override public void onTaskRemoved(int taskId) { - mTasks = loadTasksInBackground(Integer.MAX_VALUE, false); + invalidateLoadedTasks(); } + @Override - public synchronized void onActivityPinned(String packageName, int userId, int taskId, - int stackId) { - mChangeId++; + public void onActivityPinned(String packageName, int userId, int taskId, int stackId) { + invalidateLoadedTasks(); } @Override public synchronized void onActivityUnpinned() { + invalidateLoadedTasks(); + } + + private synchronized void invalidateLoadedTasks() { + UI_HELPER_EXECUTOR.execute(() -> mResultsBg = INVALID_RESULT); + mResultsUi = INVALID_RESULT; mChangeId++; } @@ -154,9 +163,8 @@ public class RecentTasksList extends TaskStackChangeListener { * Loads and creates a list of all the recent tasks. */ @VisibleForTesting - ArrayList loadTasksInBackground(int numTasks, boolean loadKeysOnly) { + TaskLoadResult loadTasksInBackground(int numTasks, int requestId, boolean loadKeysOnly) { int currentUserId = Process.myUserHandle().getIdentifier(); - ArrayList allTasks = new ArrayList<>(); List rawTasks = mActivityManagerWrapper.getRecentTasks(numTasks, currentUserId); // The raw tasks are given in most-recent to least-recent order, we need to reverse it @@ -173,6 +181,7 @@ public class RecentTasksList extends TaskStackChangeListener { } }; + TaskLoadResult allTasks = new TaskLoadResult(requestId, loadKeysOnly, rawTasks.size()); for (ActivityManager.RecentTaskInfo rawTask : rawTasks) { Task.TaskKey taskKey = new Task.TaskKey(rawTask); Task task; @@ -197,4 +206,22 @@ public class RecentTasksList extends TaskStackChangeListener { } return newTasks; } + + private static class TaskLoadResult extends ArrayList { + + final int mId; + + // If the result was loaded with keysOnly = true + final boolean mKeysOnly; + + TaskLoadResult(int id, boolean keysOnly, int size) { + super(size); + mId = id; + mKeysOnly = keysOnly; + } + + boolean isValidForRequest(int requestId, boolean loadKeysOnly) { + return mId == requestId && (!mKeysOnly || loadKeysOnly); + } + } } \ No newline at end of file diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java index b0a3cd2a1e..4e9aa6135e 100644 --- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java +++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java @@ -29,7 +29,6 @@ import android.view.KeyEvent; import android.view.MotionEvent; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import androidx.annotation.UiThread; import com.android.launcher3.util.Preconditions; @@ -58,8 +57,7 @@ public class RecentsAnimationController { private boolean mUseLauncherSysBarFlags = false; private boolean mSplitScreenMinimized = false; private boolean mTouchInProgress; - private boolean mFinishPending; - private @Nullable Runnable mFinishPendingCallback; + private boolean mDisableInputProxyPending; public RecentsAnimationController(RecentsAnimationControllerCompat controller, boolean allowMinimizeSplitScreen, @@ -138,12 +136,12 @@ public class RecentsAnimationController { @UiThread public void finishAnimationToHome() { - finishAndClear(true /* toRecents */, null, false /* sendUserLeaveHint */); + finishAndDisableInputProxy(true /* toRecents */, null, false /* sendUserLeaveHint */); } @UiThread public void finishAnimationToApp() { - finishAndClear(false /* toRecents */, null, false /* sendUserLeaveHint */); + finishAndDisableInputProxy(false /* toRecents */, null, false /* sendUserLeaveHint */); } /** See {@link #finish(boolean, Runnable, boolean)} */ @@ -162,19 +160,16 @@ public class RecentsAnimationController { @UiThread public void finish(boolean toRecents, Runnable onFinishComplete, boolean sendUserLeaveHint) { Preconditions.assertUIThread(); - if (!toRecents) { - finishAndClear(false, onFinishComplete, sendUserLeaveHint); + if (toRecents && mTouchInProgress) { + // Finish the controller as requested, but don't disable input proxy yet. + mDisableInputProxyPending = true; + finishController(toRecents, onFinishComplete, sendUserLeaveHint); } else { - if (mTouchInProgress) { - mFinishPending = true; - mFinishPendingCallback = onFinishComplete; - } else { - finishAndClear(true, onFinishComplete, sendUserLeaveHint); - } + finishAndDisableInputProxy(toRecents, onFinishComplete, sendUserLeaveHint); } } - private void finishAndClear(boolean toRecents, Runnable onFinishComplete, + private void finishAndDisableInputProxy(boolean toRecents, Runnable onFinishComplete, boolean sendUserLeaveHint) { disableInputProxy(); finishController(toRecents, onFinishComplete, sendUserLeaveHint); @@ -262,11 +257,9 @@ public class RecentsAnimationController { } else if (action == ACTION_CANCEL || action == ACTION_UP) { // Finish any pending actions mTouchInProgress = false; - if (mFinishPending) { - mFinishPending = false; - finishAndClear(true /* toRecents */, mFinishPendingCallback, - false /* sendUserLeaveHint */); - mFinishPendingCallback = null; + if (mDisableInputProxyPending) { + mDisableInputProxyPending = false; + disableInputProxy(); } } if (mInputConsumer != null) { diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java index f7c29851af..c83de13618 100644 --- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java +++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java @@ -18,6 +18,7 @@ package com.android.quickstep; import static android.content.Intent.ACTION_USER_UNLOCKED; import static com.android.launcher3.util.DefaultDisplay.CHANGE_ALL; +import static com.android.launcher3.util.DefaultDisplay.CHANGE_FRAME_DELAY; import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON; import static com.android.quickstep.SysUINavigationMode.Mode.THREE_BUTTONS; @@ -46,6 +47,7 @@ import android.graphics.Region; import android.os.Process; import android.os.SystemProperties; import android.os.UserManager; +import android.provider.Settings; import android.text.TextUtils; import android.util.DisplayMetrics; import android.view.MotionEvent; @@ -111,7 +113,8 @@ public class RecentsAnimationDeviceState implements private TaskStackChangeListener mFrozenTaskListener = new TaskStackChangeListener() { @Override public void onRecentTaskListFrozenChanged(boolean frozen) { - if (frozen) { + mTaskListFrozen = frozen; + if (frozen || mInOverview) { return; } enableMultipleRegions(false); @@ -132,6 +135,9 @@ public class RecentsAnimationDeviceState implements * TODO: (b/156984037) For when user rotates after entering overview */ private boolean mInOverview; + private boolean mTaskListFrozen; + + private boolean mIsUserSetupComplete; public RecentsAnimationDeviceState(Context context) { mContext = context; @@ -190,6 +196,17 @@ public class RecentsAnimationDeviceState implements mOneHandedEnabledObserver.register(); mOneHandedEnabledObserver.dispatchOnChange(); } + + SecureSettingsObserver userSetupObserver = new SecureSettingsObserver( + context.getContentResolver(), + e -> mIsUserSetupComplete = e, + Settings.Secure.USER_SETUP_COMPLETE, + 0); + mIsUserSetupComplete = userSetupObserver.getValue(); + if (!mIsUserSetupComplete) { + userSetupObserver.register(); + runOnDestroy(userSetupObserver::unregister); + } } private void setupOrientationSwipeHandler() { @@ -258,7 +275,8 @@ public class RecentsAnimationDeviceState implements @Override public void onDisplayInfoChanged(DefaultDisplay.Info info, int flags) { - if (info.id != getDisplayId()) { + if (info.id != getDisplayId() || (flags & CHANGE_FRAME_DELAY) == CHANGE_FRAME_DELAY) { + // ignore displays that aren't running launcher and frame refresh rate changes return; } @@ -329,6 +347,13 @@ public class RecentsAnimationDeviceState implements return mIsUserUnlocked; } + /** + * @return whether the user has completed setup wizard + */ + public boolean isUserSetupComplete() { + return mIsUserSetupComplete; + } + private void notifyUserUnlocked() { for (Runnable action : mUserUnlockedActions) { action.run(); @@ -375,7 +400,6 @@ public class RecentsAnimationDeviceState implements return (mSystemUiStateFlags & SYSUI_STATE_NAV_BAR_HIDDEN) == 0 && (mSystemUiStateFlags & SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED) == 0 && (mSystemUiStateFlags & SYSUI_STATE_QUICK_SETTINGS_EXPANDED) == 0 - && (mSystemUiStateFlags & SYSUI_STATE_BUBBLES_EXPANDED) == 0 && ((mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED) == 0 || (mSystemUiStateFlags & SYSUI_STATE_OVERVIEW_DISABLED) == 0); } @@ -395,6 +419,13 @@ public class RecentsAnimationDeviceState implements return (mSystemUiStateFlags & SYSUI_STATE_SCREEN_PINNING) != 0; } + /** + * @return whether the bubble stack is expanded + */ + public boolean isBubblesExpanded() { + return (mSystemUiStateFlags & SYSUI_STATE_BUBBLES_EXPANDED) != 0; + } + /** * @return whether lock-task mode is active */ @@ -550,7 +581,7 @@ public class RecentsAnimationDeviceState implements * *May* apply a transform on the motion event if it lies in the nav bar region for another * orientation that is currently being tracked as a part of quickstep */ - public void setOrientationTransformIfNeeded(MotionEvent event) { + void setOrientationTransformIfNeeded(MotionEvent event) { // negative coordinates bug b/143901881 if (event.getX() < 0 || event.getY() < 0) { event.setLocation(Math.max(0, event.getX()), Math.max(0, event.getY())); @@ -558,25 +589,54 @@ public class RecentsAnimationDeviceState implements mOrientationTouchTransformer.transform(event); } - void onSwipeUpToOverview(BaseActivityInterface activityInterface) { - mInOverview = true; - activityInterface.onExitOverview(this, () -> { - mInOverview = false; - enableMultipleRegions(false); - }); + void enableMultipleRegions(boolean enable) { + mOrientationTouchTransformer.enableMultipleRegions(enable, mDefaultDisplay.getInfo()); + notifySysuiForRotation(mOrientationTouchTransformer.getQuickStepStartingRotation()); } - void enableMultipleRegions(boolean enable) { - if (mInOverview) { - return; + private void notifySysuiForRotation(int rotation) { + UI_HELPER_EXECUTOR.execute(() -> + SystemUiProxy.INSTANCE.get(mContext).onQuickSwitchToNewTask(rotation)); + } + + public void onStartGesture() { + if (mTaskListFrozen) { + // Prioritize whatever nav bar user touches once in quickstep + // This case is specifically when user changes what nav bar they are using mid + // quickswitch session before tasks list is unfrozen + notifySysuiForRotation(mOrientationTouchTransformer.getCurrentActiveRotation()); + } + } + + + void onEndTargetCalculated(GestureState.GestureEndTarget endTarget, + BaseActivityInterface activityInterface) { + if (endTarget == GestureState.GestureEndTarget.RECENTS) { + mInOverview = true; + if (!mTaskListFrozen) { + // If we're in landscape w/o ever quickswitching, show the navbar in landscape + enableMultipleRegions(true); + } + activityInterface.onExitOverview(this, () -> { + mInOverview = false; + enableMultipleRegions(false); + }); + } else if (endTarget == GestureState.GestureEndTarget.HOME) { + enableMultipleRegions(false); + } else if (endTarget == GestureState.GestureEndTarget.NEW_TASK) { + if (mOrientationTouchTransformer.getQuickStepStartingRotation() == -1) { + // First gesture to start quickswitch + enableMultipleRegions(true); + } else { + notifySysuiForRotation(mOrientationTouchTransformer.getCurrentActiveRotation()); + } + } else if (endTarget == GestureState.GestureEndTarget.LAST_TASK) { + if (!mTaskListFrozen) { + // touched nav bar but didn't go anywhere and not quickswitching, do nothing + return; + } + notifySysuiForRotation(mOrientationTouchTransformer.getCurrentActiveRotation()); } - mOrientationTouchTransformer.enableMultipleRegions(enable, mDefaultDisplay.getInfo()); - UI_HELPER_EXECUTOR.execute(() -> { - int quickStepStartingRotation = - mOrientationTouchTransformer.getQuickStepStartingRotation(); - SystemUiProxy.INSTANCE.get(mContext) - .onQuickSwitchToNewTask(quickStepStartingRotation); - }); } public int getCurrentActiveRotation() { diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java index 58870edece..41e86e00b6 100644 --- a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java +++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java @@ -18,8 +18,11 @@ package com.android.quickstep.interaction; import static com.android.quickstep.interaction.TutorialController.TutorialType.BACK_NAVIGATION_COMPLETE; import static com.android.quickstep.interaction.TutorialController.TutorialType.LEFT_EDGE_BACK_NAVIGATION; +import android.graphics.PointF; import android.view.View; +import androidx.annotation.Nullable; + import com.android.launcher3.R; import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureResult; import com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult; @@ -154,11 +157,14 @@ final class BackGestureTutorialController extends TutorialController { } @Override - public void onNavBarGestureAttempted(NavBarGestureResult result) { + public void onNavBarGestureAttempted(NavBarGestureResult result, PointF finalVelocity) { if (mTutorialType == BACK_NAVIGATION_COMPLETE) { if (result == NavBarGestureResult.HOME_GESTURE_COMPLETED) { mTutorialFragment.closeTutorial(); } } } + + @Override + public void setNavBarGestureProgress(@Nullable Float displacement) {} } diff --git a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java index 524cbaf06f..65f41a4a6c 100644 --- a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java +++ b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java @@ -15,19 +15,135 @@ */ package com.android.quickstep.interaction; +import static com.android.launcher3.anim.Interpolators.ACCEL; +import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs; +import static com.android.quickstep.BaseSwipeUpHandlerV2.MAX_SWIPE_DURATION; import static com.android.quickstep.interaction.TutorialController.TutorialType.HOME_NAVIGATION_COMPLETE; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.annotation.TargetApi; +import android.content.Context; +import android.graphics.Insets; +import android.graphics.Outline; +import android.graphics.PointF; +import android.graphics.Rect; +import android.graphics.RectF; +import android.os.Build; +import android.view.SurfaceControl; import android.view.View; +import android.view.ViewOutlineProvider; +import android.view.WindowInsets.Type; +import android.view.WindowManager; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.launcher3.DeviceProfile; +import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.R; +import com.android.launcher3.Utilities; +import com.android.launcher3.anim.AnimationSuccessListener; +import com.android.launcher3.anim.AnimatorPlaybackController; +import com.android.launcher3.anim.PendingAnimation; +import com.android.quickstep.AnimatedFloat; +import com.android.quickstep.GestureState; +import com.android.quickstep.OverviewComponentObserver; +import com.android.quickstep.RecentsAnimationDeviceState; +import com.android.quickstep.SwipeUpAnimationLogic; +import com.android.quickstep.SwipeUpAnimationLogic.RunningWindowAnim; import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureResult; import com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult; +import com.android.quickstep.util.RectFSpringAnim; +import com.android.quickstep.util.TransformParams; +import com.android.systemui.shared.system.QuickStepContract; +import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams; /** A {@link TutorialController} for the Home tutorial. */ +@TargetApi(Build.VERSION_CODES.R) final class HomeGestureTutorialController extends TutorialController { + private float mFakeTaskViewRadius; + private Rect mFakeTaskViewRect = new Rect(); + + private final ViewSwipeUpAnimation mViewSwipeUpAnimation; + private RunningWindowAnim mRunningWindowAnim; + HomeGestureTutorialController(HomeGestureTutorialFragment fragment, TutorialType tutorialType) { super(fragment, tutorialType); + + RecentsAnimationDeviceState deviceState = new RecentsAnimationDeviceState(mContext); + OverviewComponentObserver observer = new OverviewComponentObserver(mContext, deviceState); + mViewSwipeUpAnimation = new ViewSwipeUpAnimation(mContext, deviceState, + new GestureState(observer, -1)); + observer.onDestroy(); + deviceState.destroy(); + + DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(mContext) + .getDeviceProfile(mContext) + .copy(mContext); + Insets insets = mContext.getSystemService(WindowManager.class) + .getCurrentWindowMetrics() + .getWindowInsets() + .getInsets(Type.systemBars()); + dp.updateInsets(new Rect(insets.left, insets.top, insets.right, insets.bottom)); + mViewSwipeUpAnimation.initDp(dp); + + mFakeTaskViewRadius = QuickStepContract.getWindowCornerRadius(mContext.getResources()); + + mFakeTaskView.setClipToOutline(true); + mFakeTaskView.setOutlineProvider(new ViewOutlineProvider() { + @Override + public void getOutline(View view, Outline outline) { + outline.setRoundRect(mFakeTaskViewRect, mFakeTaskViewRadius); + } + }); + } + + private void cancelRunningAnimation() { + if (mRunningWindowAnim != null) { + mRunningWindowAnim.cancel(); + } + mRunningWindowAnim = null; + } + + /** Fades the task view, optionally after animating to a fake Overview. */ + private void fadeOutFakeTaskView(boolean toOverviewFirst, @Nullable Runnable onEndRunnable) { + cancelRunningAnimation(); + PendingAnimation anim = new PendingAnimation(300); + AnimatorListenerAdapter resetTaskView = new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation, boolean isReverse) { + mFakeTaskView.setVisibility(View.INVISIBLE); + mFakeTaskView.setAlpha(1); + mRunningWindowAnim = null; + } + }; + if (toOverviewFirst) { + anim.setFloat(mViewSwipeUpAnimation.getCurrentShift(), AnimatedFloat.VALUE, 1, ACCEL); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation, boolean isReverse) { + PendingAnimation fadeAnim = new PendingAnimation(300); + fadeAnim.setViewAlpha(mFakeTaskView, 0, ACCEL); + fadeAnim.addListener(resetTaskView); + AnimatorSet animset = fadeAnim.buildAnim(); + animset.setStartDelay(100); + animset.start(); + mRunningWindowAnim = RunningWindowAnim.wrap(animset); + } + }); + } else { + anim.setViewAlpha(mFakeTaskView, 0, ACCEL); + anim.addListener(resetTaskView); + } + if (onEndRunnable != null) { + anim.addListener(AnimationSuccessListener.forRunnable(onEndRunnable)); + } + AnimatorSet animset = anim.buildAnim(); + animset.start(); + mRunningWindowAnim = RunningWindowAnim.wrap(animset); } @Override @@ -85,22 +201,35 @@ final class HomeGestureTutorialController extends TutorialController { } @Override - public void onNavBarGestureAttempted(NavBarGestureResult result) { + public void onNavBarGestureAttempted(NavBarGestureResult result, PointF finalVelocity) { switch (mTutorialType) { case HOME_NAVIGATION: switch (result) { - case HOME_GESTURE_COMPLETED: + case HOME_GESTURE_COMPLETED: { + hideFeedback(); + cancelRunningAnimation(); hideHandCoachingAnimation(); - mTutorialFragment.changeController(HOME_NAVIGATION_COMPLETE); + RectFSpringAnim rectAnim = + mViewSwipeUpAnimation.handleSwipeUpToHome(finalVelocity); + // After home animation finishes, fade out and then move to the next screen. + rectAnim.addAnimatorListener(AnimationSuccessListener.forRunnable( + () -> fadeOutFakeTaskView(false, + () -> mTutorialFragment.changeController( + HOME_NAVIGATION_COMPLETE)))); + mRunningWindowAnim = RunningWindowAnim.wrap(rectAnim); break; + } case HOME_NOT_STARTED_TOO_FAR_FROM_EDGE: case OVERVIEW_NOT_STARTED_TOO_FAR_FROM_EDGE: showFeedback(R.string.home_gesture_feedback_swipe_too_far_from_edge); break; case OVERVIEW_GESTURE_COMPLETED: - showFeedback(R.string.home_gesture_feedback_overview_detected); + fadeOutFakeTaskView(true, () -> + showFeedback(R.string.home_gesture_feedback_overview_detected)); break; case HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION: + case HOME_OR_OVERVIEW_CANCELLED: + fadeOutFakeTaskView(false, null); showFeedback(R.string.home_gesture_feedback_wrong_swipe_direction); break; } @@ -112,4 +241,94 @@ final class HomeGestureTutorialController extends TutorialController { break; } } + + @Override + public void setNavBarGestureProgress(@Nullable Float displacement) { + if (displacement == null || mTutorialType == HOME_NAVIGATION_COMPLETE) { + mFakeTaskView.setVisibility(View.INVISIBLE); + } else { + mFakeTaskView.setVisibility(View.VISIBLE); + if (mRunningWindowAnim == null) { + mViewSwipeUpAnimation.updateDisplacement(displacement); + } + } + } + + private class ViewSwipeUpAnimation extends SwipeUpAnimationLogic { + + ViewSwipeUpAnimation(Context context, RecentsAnimationDeviceState deviceState, + GestureState gestureState) { + super(context, deviceState, gestureState, new FakeTransformParams()); + } + + void initDp(DeviceProfile dp) { + initTransitionEndpoints(dp); + mTaskViewSimulator.setPreviewBounds( + new Rect(0, 0, dp.widthPx, dp.heightPx), dp.getInsets()); + } + + @Override + public void updateFinalShift() { + float progress = mCurrentShift.value / mDragLengthFactor; + mWindowTransitionController.setPlayFraction(progress); + mTaskViewSimulator.apply(mTransformParams); + } + + AnimatedFloat getCurrentShift() { + return mCurrentShift; + } + + RectFSpringAnim handleSwipeUpToHome(PointF velocity) { + PointF velocityPxPerMs = new PointF(velocity.x, velocity.y); + float currentShift = mCurrentShift.value; + final float startShift = Utilities.boundToRange(currentShift - velocityPxPerMs.y + * getSingleFrameMs(mContext) / mTransitionDragLength, 0, mDragLengthFactor); + float distanceToTravel = (1 - currentShift) * mTransitionDragLength; + + // we want the page's snap velocity to approximately match the velocity at + // which the user flings, so we scale the duration by a value near to the + // derivative of the scroll interpolator at zero, ie. 2. + long baseDuration = Math.round(Math.abs(distanceToTravel / velocityPxPerMs.y)); + long duration = Math.min(MAX_SWIPE_DURATION, 2 * baseDuration); + HomeAnimationFactory homeAnimFactory = new HomeAnimationFactory(null) { + @Override + public AnimatorPlaybackController createActivityAnimationToHome() { + return AnimatorPlaybackController.wrap(new AnimatorSet(), duration); + } + + @NonNull + @Override + public RectF getWindowTargetRect() { + int fakeHomeIconSizePx = mDp.allAppsIconSizePx; + int fakeHomeIconLeft = (mDp.widthPx - fakeHomeIconSizePx) / 2; + int fakeHomeIconTop = mDp.heightPx - (mDp.allAppsCellHeightPx * 3); + return new RectF(fakeHomeIconLeft, fakeHomeIconTop, + fakeHomeIconLeft + fakeHomeIconSizePx, + fakeHomeIconTop + fakeHomeIconSizePx); + } + }; + RectFSpringAnim windowAnim = createWindowAnimationToHome(startShift, homeAnimFactory); + windowAnim.start(mContext, velocityPxPerMs); + return windowAnim; + } + } + + private class FakeTransformParams extends TransformParams { + + @Override + public SurfaceParams[] createSurfaceParams(BuilderProxy proxy) { + SurfaceParams.Builder builder = new SurfaceParams.Builder((SurfaceControl) null); + proxy.onBuildTargetParams(builder, null, this); + return new SurfaceParams[] {builder.build()}; + } + + @Override + public void applySurfaceParams(SurfaceParams[] params) { + SurfaceParams p = params[0]; + mFakeTaskView.setAnimationMatrix(p.matrix); + mFakeTaskViewRect.set(p.windowCrop); + mFakeTaskViewRadius = p.cornerRadius; + mFakeTaskView.invalidateOutline(); + } + } } diff --git a/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java index 6d8caa2ec5..4069c09a28 100644 --- a/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java +++ b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java @@ -17,6 +17,7 @@ package com.android.quickstep.interaction; import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.HOME_GESTURE_COMPLETED; import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.HOME_NOT_STARTED_TOO_FAR_FROM_EDGE; +import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.HOME_OR_OVERVIEW_CANCELLED; import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION; import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.OVERVIEW_GESTURE_COMPLETED; import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.OVERVIEW_NOT_STARTED_TOO_FAR_FROM_EDGE; @@ -24,19 +25,23 @@ import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestu import android.content.Context; import android.content.res.Resources; import android.graphics.Point; +import android.graphics.PointF; import android.view.Display; import android.view.MotionEvent; import android.view.Surface; import android.view.View; import android.view.View.OnTouchListener; +import androidx.annotation.Nullable; + import com.android.launcher3.ResourceUtils; import com.android.quickstep.SysUINavigationMode.Mode; import com.android.quickstep.util.NavBarPosition; import com.android.quickstep.util.TriggerSwipeUpTouchTracker; /** Utility class to handle home gestures. */ -public class NavBarGestureHandler implements OnTouchListener { +public class NavBarGestureHandler implements OnTouchListener, + TriggerSwipeUpTouchTracker.OnSwipeUpListener { private static final String LOG_TAG = "NavBarGestureHandler"; @@ -44,6 +49,7 @@ public class NavBarGestureHandler implements OnTouchListener { private final TriggerSwipeUpTouchTracker mSwipeUpTouchTracker; private int mBottomGestureHeight; private boolean mTouchCameFromNavBar; + private float mDownY; private NavBarGestureAttemptCallback mGestureCallback; NavBarGestureHandler(Context context) { @@ -55,10 +61,11 @@ public class NavBarGestureHandler implements OnTouchListener { displayRotation = display.getRotation(); display.getRealSize(mDisplaySize); } + mDownY = mDisplaySize.y; mSwipeUpTouchTracker = new TriggerSwipeUpTouchTracker(context, true /*disableHorizontalSwipe*/, new NavBarPosition(Mode.NO_BUTTON, displayRotation), - null /*onInterceptTouch*/, this::onSwipeUp); + null /*onInterceptTouch*/, this); final Resources resources = context.getResources(); mBottomGestureHeight = @@ -73,16 +80,26 @@ public class NavBarGestureHandler implements OnTouchListener { mGestureCallback = null; } - private void onSwipeUp(boolean wasFling) { + @Override + public void onSwipeUp(boolean wasFling, PointF finalVelocity) { if (mGestureCallback == null) { return; } + finalVelocity.set(finalVelocity.x / 1000, finalVelocity.y / 1000); if (mTouchCameFromNavBar) { mGestureCallback.onNavBarGestureAttempted(wasFling - ? HOME_GESTURE_COMPLETED : OVERVIEW_GESTURE_COMPLETED); + ? HOME_GESTURE_COMPLETED : OVERVIEW_GESTURE_COMPLETED, finalVelocity); } else { mGestureCallback.onNavBarGestureAttempted(wasFling - ? HOME_NOT_STARTED_TOO_FAR_FROM_EDGE : OVERVIEW_NOT_STARTED_TOO_FAR_FROM_EDGE); + ? HOME_NOT_STARTED_TOO_FAR_FROM_EDGE : OVERVIEW_NOT_STARTED_TOO_FAR_FROM_EDGE, + finalVelocity); + } + } + + @Override + public void onSwipeUpCancelled() { + if (mGestureCallback != null) { + mGestureCallback.onNavBarGestureAttempted(HOME_OR_OVERVIEW_CANCELLED, new PointF()); } } @@ -91,15 +108,22 @@ public class NavBarGestureHandler implements OnTouchListener { int action = motionEvent.getAction(); boolean intercepted = mSwipeUpTouchTracker.interceptedTouch(); if (action == MotionEvent.ACTION_DOWN) { - mTouchCameFromNavBar = motionEvent.getRawY() >= mDisplaySize.y - mBottomGestureHeight; + mDownY = motionEvent.getY(); + mTouchCameFromNavBar = mDownY >= mDisplaySize.y - mBottomGestureHeight; + if (!mTouchCameFromNavBar) { + mGestureCallback.setNavBarGestureProgress(null); + } mSwipeUpTouchTracker.init(); } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { if (mGestureCallback != null && !intercepted && mTouchCameFromNavBar) { mGestureCallback.onNavBarGestureAttempted( - HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION); + HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION, new PointF()); intercepted = true; } } + if (mTouchCameFromNavBar && mGestureCallback != null) { + mGestureCallback.setNavBarGestureProgress(motionEvent.getY() - mDownY); + } mSwipeUpTouchTracker.onMotionEvent(motionEvent); return intercepted; } @@ -110,12 +134,16 @@ public class NavBarGestureHandler implements OnTouchListener { OVERVIEW_GESTURE_COMPLETED, HOME_NOT_STARTED_TOO_FAR_FROM_EDGE, OVERVIEW_NOT_STARTED_TOO_FAR_FROM_EDGE, - HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION // Side swipe on nav bar. + HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION, // Side swipe on nav bar. + HOME_OR_OVERVIEW_CANCELLED } /** Callback to let the UI react to attempted nav bar gestures. */ interface NavBarGestureAttemptCallback { /** Called whenever any touch is completed. */ - void onNavBarGestureAttempted(NavBarGestureResult result); + void onNavBarGestureAttempted(NavBarGestureResult result, PointF finalVelocity); + + /** Indicates how far a touch originating in the nav bar has moved from the nav bar. */ + void setNavBarGestureProgress(@Nullable Float displacement); } } diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialController.java b/quickstep/src/com/android/quickstep/interaction/TutorialController.java index 1e29f44704..f27d500eff 100644 --- a/quickstep/src/com/android/quickstep/interaction/TutorialController.java +++ b/quickstep/src/com/android/quickstep/interaction/TutorialController.java @@ -15,6 +15,7 @@ */ package com.android.quickstep.interaction; +import android.content.Context; import android.graphics.drawable.RippleDrawable; import android.view.View; import android.view.View.OnClickListener; @@ -39,11 +40,13 @@ abstract class TutorialController implements BackGestureAttemptCallback, final TutorialFragment mTutorialFragment; TutorialType mTutorialType; + final Context mContext; final ImageButton mCloseButton; final TextView mTitleTextView; final TextView mSubtitleTextView; final TextView mFeedbackView; + final View mFakeTaskView; final View mRippleView; final RippleDrawable mRippleDrawable; final TutorialHandAnimation mHandCoachingAnimation; @@ -55,6 +58,7 @@ abstract class TutorialController implements BackGestureAttemptCallback, TutorialController(TutorialFragment tutorialFragment, TutorialType tutorialType) { mTutorialFragment = tutorialFragment; mTutorialType = tutorialType; + mContext = mTutorialFragment.getContext(); View rootView = tutorialFragment.getRootView(); mCloseButton = rootView.findViewById(R.id.gesture_tutorial_fragment_close_button); @@ -62,6 +66,7 @@ abstract class TutorialController implements BackGestureAttemptCallback, mTitleTextView = rootView.findViewById(R.id.gesture_tutorial_fragment_title_view); mSubtitleTextView = rootView.findViewById(R.id.gesture_tutorial_fragment_subtitle_view); mFeedbackView = rootView.findViewById(R.id.gesture_tutorial_fragment_feedback_view); + mFakeTaskView = rootView.findViewById(R.id.gesture_tutorial_fake_task_view); mRippleView = rootView.findViewById(R.id.gesture_tutorial_ripple_view); mRippleDrawable = (RippleDrawable) mRippleView.getBackground(); mHandCoachingAnimation = tutorialFragment.getHandAnimation(); diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java index 44c1a5da04..a3881cffb0 100644 --- a/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java +++ b/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java @@ -37,11 +37,6 @@ import com.android.quickstep.interaction.TutorialController.TutorialType; abstract class TutorialFragment extends Fragment implements OnTouchListener { private static final String LOG_TAG = "TutorialFragment"; - private static final String SYSTEM_NAVIGATION_SETTING_INTENT = - "#Intent;action=com.android.settings.SEARCH_RESULT_TRAMPOLINE;S" - + ".:settings:fragment_args_key=gesture_system_navigation_input_summary;S" - + ".:settings:show_fragment=com.android.settings.gestures" - + ".SystemNavigationGestureSettings;end"; static final String KEY_TUTORIAL_TYPE = "tutorial_type"; TutorialType mTutorialType; diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java index 69812b6154..d4d46fbb2c 100644 --- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java +++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java @@ -65,10 +65,10 @@ public class StatsLogCompatManager extends StatsLogManager { } /** - * Logs a {@link LauncherEvent}. + * Logs a {@link EventEnum}. */ @Override - public void log(LauncherEvent event) { + public void log(EventEnum event) { log(event, DEFAULT_INSTANCE_ID, LauncherAtom.ItemInfo.getDefaultInstance()); } @@ -76,7 +76,7 @@ public class StatsLogCompatManager extends StatsLogManager { * Logs an event and accompanying {@link InstanceId}. */ @Override - public void log(LauncherEvent event, InstanceId instanceId) { + public void log(EventEnum event, InstanceId instanceId) { log(event, instanceId, LauncherAtom.ItemInfo.getDefaultInstance()); } @@ -84,7 +84,7 @@ public class StatsLogCompatManager extends StatsLogManager { * Logs an event and accompanying {@link ItemInfo}. */ @Override - public void log(LauncherEvent event, @Nullable LauncherAtom.ItemInfo info) { + public void log(EventEnum event, @Nullable LauncherAtom.ItemInfo info) { log(event, DEFAULT_INSTANCE_ID, info); } @@ -92,7 +92,7 @@ public class StatsLogCompatManager extends StatsLogManager { * Logs an event and accompanying {@link InstanceId} and {@link LauncherAtom.ItemInfo}. */ @Override - public void log(LauncherEvent event, InstanceId instanceId, + public void log(EventEnum event, InstanceId instanceId, @Nullable LauncherAtom.ItemInfo info) { logInternal(event, instanceId, info, LAUNCHER_UICHANGED__DST_STATE__HOME, @@ -102,14 +102,17 @@ public class StatsLogCompatManager extends StatsLogManager { /** * Logs an event and accompanying {@link InstanceId} and {@link LauncherAtom.ItemInfo}. */ - private void logInternal(LauncherEvent event, InstanceId instanceId, + private void logInternal(EventEnum event, InstanceId instanceId, @Nullable LauncherAtom.ItemInfo info, int startState, int endState) { info = info == null ? LauncherAtom.ItemInfo.getDefaultInstance() : info; if (IS_VERBOSE) { + String name = (event instanceof LauncherEvent) ? ((LauncherEvent) event).name() : + event.getId() + ""; + Log.d(TAG, instanceId == DEFAULT_INSTANCE_ID - ? String.format("\n%s\n%s", event.name(), info) - : String.format("%s(InstanceId:%s)\n%s", event.name(), instanceId, info)); + ? String.format("\n%s\n%s", name, info) + : String.format("%s(InstanceId:%s)\n%s", name, instanceId, info)); } if (!Utilities.ATLEAST_R) { diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java index 5745990614..498c232ec3 100644 --- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java +++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java @@ -208,7 +208,7 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre mDisplayRotation = displayRotation; mTouchRotation = touchRotation; - if (mLauncherRotation == mTouchRotation) { + if (mLauncherRotation == mTouchRotation || canLauncherRotate()) { mOrientationHandler = PagedOrientationHandler.HOME_ROTATED; if (DEBUG) { Log.d(TAG, "current RecentsOrientedState: " + this); @@ -240,7 +240,7 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre private void setFlag(int mask, boolean enabled) { boolean wasRotationEnabled = !TestProtocol.sDisableSensorRotation - && mFlags == VALUE_ROTATION_WATCHER_ENABLED; + && (mFlags & VALUE_ROTATION_WATCHER_ENABLED) == VALUE_ROTATION_WATCHER_ENABLED; if (enabled) { mFlags |= mask; } else { @@ -248,7 +248,7 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre } boolean isRotationEnabled = !TestProtocol.sDisableSensorRotation - && mFlags == VALUE_ROTATION_WATCHER_ENABLED; + && (mFlags & VALUE_ROTATION_WATCHER_ENABLED) == VALUE_ROTATION_WATCHER_ENABLED; if (wasRotationEnabled != isRotationEnabled) { UI_HELPER_EXECUTOR.execute(() -> { if (isRotationEnabled) { diff --git a/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java b/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java index 34eb7f8177..79ddf7a399 100644 --- a/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java +++ b/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java @@ -58,9 +58,9 @@ public class RecentTasksListTest { } @Test - public void onTaskRemoved_reloadsAllTasks() { + public void onTaskRemoved_doesNotFetchTasks() { mRecentTasksList.onTaskRemoved(0); - verify(mockActivityManagerWrapper, times(1)) + verify(mockActivityManagerWrapper, times(0)) .getRecentTasks(anyInt(), anyInt()); } @@ -77,7 +77,7 @@ public class RecentTasksListTest { when(mockActivityManagerWrapper.getRecentTasks(anyInt(), anyInt())) .thenReturn(Collections.singletonList(recentTaskInfo)); - List taskList = mRecentTasksList.loadTasksInBackground(Integer.MAX_VALUE, true); + List taskList = mRecentTasksList.loadTasksInBackground(Integer.MAX_VALUE, -1, true); assertEquals(1, taskList.size()); assertNull(taskList.get(0).taskDescription.getLabel()); @@ -91,7 +91,7 @@ public class RecentTasksListTest { when(mockActivityManagerWrapper.getRecentTasks(anyInt(), anyInt())) .thenReturn(Collections.singletonList(recentTaskInfo)); - List taskList = mRecentTasksList.loadTasksInBackground(Integer.MAX_VALUE, false); + List taskList = mRecentTasksList.loadTasksInBackground(Integer.MAX_VALUE, -1, false); assertEquals(1, taskList.size()); assertEquals(taskDescription, taskList.get(0).taskDescription.getLabel()); diff --git a/res/values/colors.xml b/res/values/colors.xml index c9c893e511..c4ec7dd7f0 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -43,6 +43,7 @@ #99000000 #FF000000 #A0C2F9 + #6DA1FF #FFFFFFFF #1A73E8 diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java index 0ac278457a..d1d5e2624d 100644 --- a/src/com/android/launcher3/BaseDraggingActivity.java +++ b/src/com/android/launcher3/BaseDraggingActivity.java @@ -24,6 +24,7 @@ import android.content.ActivityNotFoundException; import android.content.Intent; import android.content.pm.LauncherApps; import android.content.res.Configuration; +import android.graphics.Insets; import android.graphics.Point; import android.graphics.Rect; import android.os.Bundle; @@ -35,6 +36,8 @@ import android.view.ActionMode; import android.view.Display; import android.view.View; import android.view.View.OnClickListener; +import android.view.WindowInsets.Type; +import android.view.WindowMetrics; import android.widget.Toast; import androidx.annotation.Nullable; @@ -51,6 +54,7 @@ import com.android.launcher3.util.DefaultDisplay.Info; import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.util.Themes; import com.android.launcher3.util.TraceHelper; +import com.android.launcher3.util.WindowBounds; /** * Extension of BaseActivity allowing support for drag-n-drop @@ -272,15 +276,19 @@ public abstract class BaseDraggingActivity extends BaseActivity protected abstract void reapplyUi(); - protected Rect getMultiWindowDisplaySize() { + protected WindowBounds getMultiWindowDisplaySize() { if (Utilities.ATLEAST_R) { - return new Rect(getWindowManager().getCurrentWindowMetrics().getBounds()); + WindowMetrics wm = getWindowManager().getCurrentWindowMetrics(); + + Insets insets = wm.getWindowInsets().getInsets(Type.systemBars()); + return new WindowBounds(wm.getBounds(), + new Rect(insets.left, insets.top, insets.right, insets.bottom)); } // Note: Calls to getSize() can't rely on our cached DefaultDisplay since it can return // the app window size Display display = getWindowManager().getDefaultDisplay(); Point mwSize = new Point(); display.getSize(mwSize); - return new Rect(0, 0, mwSize.x, mwSize.y); + return new WindowBounds(new Rect(0, 0, mwSize.x, mwSize.y), new Rect()); } } diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java index 51b21aa795..72831f4d09 100644 --- a/src/com/android/launcher3/DeviceProfile.java +++ b/src/com/android/launcher3/DeviceProfile.java @@ -29,6 +29,7 @@ import com.android.launcher3.graphics.IconShape; import com.android.launcher3.icons.DotRenderer; import com.android.launcher3.icons.IconNormalizer; import com.android.launcher3.util.DefaultDisplay; +import com.android.launcher3.util.WindowBounds; public class DeviceProfile { @@ -265,19 +266,16 @@ public class DeviceProfile { /** * TODO: Move this to the builder as part of setMultiWindowMode */ - public DeviceProfile getMultiWindowProfile(Context context, Rect windowPosition) { + public DeviceProfile getMultiWindowProfile(Context context, WindowBounds windowBounds) { // We take the minimum sizes of this profile and it's multi-window variant to ensure that // the system decor is always excluded. - Point mwSize = new Point(Math.min(availableWidthPx, windowPosition.width()), - Math.min(availableHeightPx, windowPosition.height())); + Point mwSize = new Point(Math.min(availableWidthPx, windowBounds.availableSize.x), + Math.min(availableHeightPx, windowBounds.availableSize.y)); - // 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 = toBuilder(context) .setSizeRange(mwSize, mwSize) - .setSize(mwSize.x, mwSize.y) - .setWindowPosition(windowPosition.left, windowPosition.top) + .setSize(windowBounds.bounds.width(), windowBounds.bounds.height()) + .setWindowPosition(windowBounds.bounds.left, windowBounds.bounds.top) .setMultiWindowMode(true) .build(); @@ -299,7 +297,7 @@ public class DeviceProfile { } /** - * Inverse of {@link #getMultiWindowProfile(Context, Rect)} + * Inverse of {@link #getMultiWindowProfile(Context, WindowBounds)} * @return device profile corresponding to the current orientation in non multi-window mode. */ public DeviceProfile getFullScreenProfile() { diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java index 8951674879..60abc66b2b 100644 --- a/src/com/android/launcher3/InvariantDeviceProfile.java +++ b/src/com/android/launcher3/InvariantDeviceProfile.java @@ -200,7 +200,7 @@ public class InvariantDeviceProfile { DefaultDisplay.INSTANCE.get(context).getInfo(), getPredefinedDeviceProfiles(context, gridName)); - Info myInfo = new Info(display); + Info myInfo = new Info(context, display); DisplayOption myDisplayOption = invDistWeightedInterpolate( myInfo, getPredefinedDeviceProfiles(context, gridName)); diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java index 67890726b1..2cb3910793 100644 --- a/src/com/android/launcher3/LauncherSettings.java +++ b/src/com/android/launcher3/LauncherSettings.java @@ -154,9 +154,9 @@ public class LauncherSettings { public static final int CONTAINER_HOTSEAT_PREDICTION = -103; public static final int CONTAINER_ALL_APPS = -104; public static final int CONTAINER_WIDGETS_TRAY = -105; - // Represents search results view. public static final int CONTAINER_SEARCH_RESULTS = -106; + public static final int CONTAINER_SHORTCUTS = -107; public static final String containerToString(int container) { switch (container) { @@ -166,6 +166,7 @@ public class LauncherSettings { case CONTAINER_ALL_APPS: return "all_apps"; case CONTAINER_WIDGETS_TRAY: return "widgets_tray"; case CONTAINER_SEARCH_RESULTS: return "search_result"; + case CONTAINER_SHORTCUTS: return "shortcuts"; default: return String.valueOf(container); } } diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java index aa9edda582..99ed0ad52b 100644 --- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java +++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java @@ -6,6 +6,8 @@ import static com.android.launcher3.LauncherState.APPS_VIEW_ITEM_MASK; import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.LauncherState.VERTICAL_SWIPE_INDICATOR; import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN; +import static com.android.launcher3.anim.Interpolators.FINAL_FRAME; +import static com.android.launcher3.anim.Interpolators.INSTANT; import static com.android.launcher3.anim.Interpolators.LINEAR; import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER; import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE; @@ -227,7 +229,9 @@ public class AllAppsTransitionController implements StateHandler, setter.setInt(mScrimView, ScrimView.DRAG_HANDLE_ALPHA, (visibleElements & VERTICAL_SWIPE_INDICATOR) != 0 ? 255 : 0, allAppsFade); - setter.setViewAlpha(mAppsView, hasAnyVisibleItem ? 1 : 0, allAppsFade); + // Set visibility of the container at the very beginning or end of the transition. + setter.setViewAlpha(mAppsView, hasAnyVisibleItem ? 1 : 0, + hasAnyVisibleItem ? INSTANT : FINAL_FRAME); } public AnimatorListenerAdapter getProgressAnimatorListener() { diff --git a/src/com/android/launcher3/anim/Interpolators.java b/src/com/android/launcher3/anim/Interpolators.java index fccc120900..860ccebef4 100644 --- a/src/com/android/launcher3/anim/Interpolators.java +++ b/src/com/android/launcher3/anim/Interpolators.java @@ -61,6 +61,11 @@ public class Interpolators { public static final Interpolator EXAGGERATED_EASE; public static final Interpolator INSTANT = t -> 1; + /** + * All values of t map to 0 until t == 1. This is primarily useful for setting view visibility, + * which should only happen at the very end of the animation (when it's already hidden). + */ + public static final Interpolator FINAL_FRAME = t -> t < 1 ? 0 : 1; private static final int MIN_SETTLE_DURATION = 200; private static final float OVERSHOOT_FACTOR = 0.9f; diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java index f7fe535a48..fdf0ea46f2 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -466,6 +466,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo if (!isEmpty(firstLabel)) { mFolderName.setHint(""); mFolderName.setText(firstLabel); + mFolderName.selectAll(); } } mFolderName.showKeyboard(); diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java index 4c2c79d6dd..dda39e8a51 100644 --- a/src/com/android/launcher3/logging/StatsLogManager.java +++ b/src/com/android/launcher3/logging/StatsLogManager.java @@ -32,7 +32,7 @@ import com.android.launcher3.util.ResourceBasedOverride; */ public class StatsLogManager implements ResourceBasedOverride { - interface EventEnum { + public interface EventEnum { int getId(); } @@ -63,6 +63,18 @@ public class StatsLogManager implements ResourceBasedOverride { + "new/same value.") LAUNCHER_FOLDER_LABEL_UPDATED(460), + @UiEvent(doc = "User long pressed on the workspace empty space.") + LAUNCHER_WORKSPACE_LONGPRESS(461), + + @UiEvent(doc = "User tapped or long pressed on a wallpaper icon inside launcher settings.") + LAUNCHER_WALLPAPER_BUTTON_TAP_OR_LONGPRESS(462), + + @UiEvent(doc = "User tapped or long pressed on settings icon inside launcher settings.") + LAUNCHER_SETTINGS_BUTTON_TAP_OR_LONGPRESS(463), + + @UiEvent(doc = "User tapped or long pressed on widget tray icon inside launcher settings.") + LAUNCHER_WIDGETSTRAY_BUTTON_TAP_OR_LONGPRESS(464), + @UiEvent(doc = "A dragged item is dropped on 'Remove' button in the target bar") LAUNCHER_ITEM_DROPPED_ON_REMOVE(465), @@ -82,7 +94,15 @@ public class StatsLogManager implements ResourceBasedOverride { @UiEvent(doc = "User cancelled uninstalling the package after dropping on " + "the icon onto 'Uninstall' button in the target bar") - LAUNCHER_ITEM_UNINSTALL_CANCELLED(470); + LAUNCHER_ITEM_UNINSTALL_CANCELLED(470), + + @UiEvent(doc = "User opened package specific widgets list by tapping on widgets system " + + "shortcut within longpress popup window.") + LAUNCHER_SYSTEM_SHORTCUT_WIDGETS_TAP(514), + + @UiEvent(doc = "User opened app info of the package by tapping on appinfo system shortcut " + + "within longpress popup window.") + LAUNCHER_SYSTEM_SHORTCUT_APP_INFO_TAP(515); // ADD MORE private final int mId; @@ -113,27 +133,27 @@ public class StatsLogManager implements ResourceBasedOverride { } /** - * Logs a {@link LauncherEvent}. + * Logs a {@link EventEnum}. */ - public void log(LauncherEvent event) { + public void log(EventEnum event) { } /** * Logs an event and accompanying {@link InstanceId}. */ - public void log(LauncherEvent event, InstanceId instanceId) { + public void log(EventEnum event, InstanceId instanceId) { } /** * Logs an event and accompanying {@link ItemInfo}. */ - public void log(LauncherEvent event, @Nullable ItemInfo info) { + public void log(EventEnum event, @Nullable ItemInfo itemInfo) { } /** * Logs an event and accompanying {@link InstanceId} and {@link ItemInfo}. */ - public void log(LauncherEvent event, InstanceId instanceId, @Nullable ItemInfo info) { + public void log(EventEnum event, InstanceId instanceId, @Nullable ItemInfo itemInfo) { } /** diff --git a/src/com/android/launcher3/model/data/ItemInfo.java b/src/com/android/launcher3/model/data/ItemInfo.java index 3a89236138..0c815d145a 100644 --- a/src/com/android/launcher3/model/data/ItemInfo.java +++ b/src/com/android/launcher3/model/data/ItemInfo.java @@ -22,6 +22,7 @@ import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION; import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PREDICTION; import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_SEARCH_RESULTS; +import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_SHORTCUTS; import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_TRAY; import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET; @@ -45,6 +46,7 @@ import com.android.launcher3.logger.LauncherAtom.AllAppsContainer; import com.android.launcher3.logger.LauncherAtom.ContainerInfo; import com.android.launcher3.logger.LauncherAtom.PredictionContainer; import com.android.launcher3.logger.LauncherAtom.SearchResultContainer; +import com.android.launcher3.logger.LauncherAtom.ShortcutsContainer; import com.android.launcher3.util.ContentWriter; import java.util.Optional; @@ -363,6 +365,10 @@ public class ItemInfo { return ContainerInfo.newBuilder() .setSearchResultContainer(SearchResultContainer.getDefaultInstance()) .build(); + case CONTAINER_SHORTCUTS: + return ContainerInfo.newBuilder() + .setShortcutsContainer(ShortcutsContainer.getDefaultInstance()) + .build(); } return ContainerInfo.getDefaultInstance(); } diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java index 331298f414..614cf1401d 100644 --- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java +++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java @@ -16,6 +16,7 @@ package com.android.launcher3.popup; +import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_SHORTCUTS; import static com.android.launcher3.Utilities.squaredHypot; import static com.android.launcher3.Utilities.squaredTouchSlop; import static com.android.launcher3.logging.LoggerUtils.newContainerTarget; @@ -61,6 +62,7 @@ import com.android.launcher3.dragndrop.DragView; import com.android.launcher3.dragndrop.DraggableView; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.ItemInfoWithIcon; +import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.notification.NotificationInfo; import com.android.launcher3.notification.NotificationItemView; import com.android.launcher3.notification.NotificationKeyData; @@ -675,8 +677,10 @@ public class PopupContainerWithArrow extends Arr iconShift.y = mIconLastTouchPos.y - mLauncher.getDeviceProfile().iconSizePx; DraggableView draggableView = DraggableView.ofType(DraggableView.DRAGGABLE_ICON); + WorkspaceItemInfo itemInfo = sv.getFinalInfo(); + itemInfo.container = CONTAINER_SHORTCUTS; DragView dv = mLauncher.getWorkspace().beginDragShared(sv.getIconView(), draggableView, - mContainer, sv.getFinalInfo(), + mContainer, itemInfo, new ShortcutDragPreviewProvider(sv.getIconView(), iconShift), new DragOptions()); dv.animateShift(-iconShift.x, -iconShift.y); diff --git a/src/com/android/launcher3/popup/PopupPopulator.java b/src/com/android/launcher3/popup/PopupPopulator.java index 7da86cc0af..6d3bc1451a 100644 --- a/src/com/android/launcher3/popup/PopupPopulator.java +++ b/src/com/android/launcher3/popup/PopupPopulator.java @@ -16,6 +16,8 @@ package com.android.launcher3.popup; +import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_SHORTCUTS; + import android.content.ComponentName; import android.content.pm.ShortcutInfo; import android.os.Handler; @@ -160,6 +162,7 @@ public class PopupPopulator { final WorkspaceItemInfo si = new WorkspaceItemInfo(shortcut, launcher); cache.getUnbadgedShortcutIcon(si, shortcut); si.rank = i; + si.container = CONTAINER_SHORTCUTS; final DeepShortcutView view = shortcutViews.get(i); uiHandler.post(() -> view.applyShortcutInfo(si, shortcut, container)); diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java index ae35d4c594..58ed5e8132 100644 --- a/src/com/android/launcher3/popup/SystemShortcut.java +++ b/src/com/android/launcher3/popup/SystemShortcut.java @@ -1,5 +1,7 @@ package com.android.launcher3.popup; +import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_APP_INFO_TAP; +import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_WIDGETS_TAP; import android.app.ActivityOptions; import android.content.Context; @@ -103,7 +105,6 @@ public abstract class SystemShortcut extends Ite }; public static class Widgets extends SystemShortcut { - public Widgets(Launcher target, ItemInfo itemInfo) { super(R.drawable.ic_widget, R.string.widget_button_text, target, itemInfo); } @@ -117,6 +118,9 @@ public abstract class SystemShortcut extends Ite widgetsBottomSheet.populateAndShow(mItemInfo); mTarget.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP, ControlType.WIDGETS_BUTTON, view); + // TODO(thiruram): Fix missing container info when item is inside folder. + mTarget.getStatsLogManager().log(LAUNCHER_SYSTEM_SHORTCUT_WIDGETS_TAP, + mItemInfo.buildProto()); } } @@ -137,6 +141,9 @@ public abstract class SystemShortcut extends Ite mItemInfo, sourceBounds, ActivityOptions.makeBasic().toBundle()); mTarget.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP, ControlType.APPINFO_TARGET, view); + // TODO(thiruram): Fix missing container info when item is inside folder. + mTarget.getStatsLogManager().log(LAUNCHER_SYSTEM_SHORTCUT_APP_INFO_TAP, + mItemInfo.buildProto()); } } diff --git a/src/com/android/launcher3/touch/WorkspaceTouchListener.java b/src/com/android/launcher3/touch/WorkspaceTouchListener.java index e6de06d6f0..7270ce28fb 100644 --- a/src/com/android/launcher3/touch/WorkspaceTouchListener.java +++ b/src/com/android/launcher3/touch/WorkspaceTouchListener.java @@ -22,6 +22,7 @@ import static android.view.MotionEvent.ACTION_POINTER_UP; import static android.view.MotionEvent.ACTION_UP; import static com.android.launcher3.LauncherState.NORMAL; +import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WORKSPACE_LONGPRESS; import android.graphics.PointF; import android.graphics.Rect; @@ -41,8 +42,6 @@ import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.testing.TestLogging; import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.views.OptionsPopupView; -import com.android.launcher3.userevent.nano.LauncherLogProto.Action; -import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; /** * Helper class to handle touch on empty space in workspace and show options popup on long press @@ -175,9 +174,7 @@ public class WorkspaceTouchListener extends GestureDetector.SimpleOnGestureListe mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); - mLauncher.getUserEventDispatcher().logActionOnContainer(Action.Touch.LONGPRESS, - Action.Direction.NONE, ContainerType.WORKSPACE, - mWorkspace.getCurrentPage()); + mLauncher.getStatsLogManager().log(LAUNCHER_WORKSPACE_LONGPRESS); OptionsPopupView.showDefaultOptions(mLauncher, mTouchDownPoint.x, mTouchDownPoint.y); } else { cancelLongPress(); diff --git a/src/com/android/launcher3/util/DefaultDisplay.java b/src/com/android/launcher3/util/DefaultDisplay.java index fabdb4e86c..150fb5b043 100644 --- a/src/com/android/launcher3/util/DefaultDisplay.java +++ b/src/com/android/launcher3/util/DefaultDisplay.java @@ -145,10 +145,11 @@ public class DefaultDisplay implements DisplayListener { } private Info(Context context) { - this(context.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY)); + this(context, context.getSystemService(DisplayManager.class) + .getDisplay(DEFAULT_DISPLAY)); } - public Info(Display display) { + public Info(Context context, Display display) { id = display.getDisplayId(); rotation = display.getRotation(); @@ -161,8 +162,8 @@ public class DefaultDisplay implements DisplayListener { display.getRealSize(realSize); display.getCurrentSizeRange(smallestSize, largestSize); - metrics = new DisplayMetrics(); - display.getMetrics(metrics); + Context defaultDisplayContext = context.createDisplayContext(display); + metrics = defaultDisplayContext.getResources().getDisplayMetrics(); } private boolean hasDifferentSize(Info info) { diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java index 6915953068..c37c47c0a0 100644 --- a/src/com/android/launcher3/views/BaseDragLayer.java +++ b/src/com/android/launcher3/views/BaseDragLayer.java @@ -181,6 +181,11 @@ public abstract class BaseDragLayer } private TouchController findControllerToHandleTouch(MotionEvent ev) { + if (TestProtocol.sDebugTracing) { + Log.d(TestProtocol.PAUSE_NOT_DETECTED, "findControllerToHandleTouch ev=" + ev + + ", isEventInLauncher=" + isEventInLauncher(ev) + + ", topOpenView=" + AbstractFloatingView.getTopOpenView(mActivity)); + } if (isEventInLauncher(ev)) { AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity); if (topView != null && topView.onControllerInterceptTouchEvent(ev)) { diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java index bd12e06b60..177aff4a89 100644 --- a/src/com/android/launcher3/views/FloatingIconView.java +++ b/src/com/android/launcher3/views/FloatingIconView.java @@ -59,6 +59,7 @@ import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.popup.SystemShortcut; import com.android.launcher3.shortcuts.DeepShortcutView; +import com.android.launcher3.testing.TestProtocol; /** * A view that is created to look like another view with the purpose of creating fluid animations. @@ -560,6 +561,11 @@ public class FloatingIconView extends FrameLayout implements view.setVisibility(INVISIBLE); parent.addView(view); dragLayer.addView(view.mListenerView); + if (TestProtocol.sDebugTracing) { + Log.d(TestProtocol.PAUSE_NOT_DETECTED, "getFloatingIconView. listenerView " + + "added to dragLayer. listenerView=" + view.mListenerView + ", fiv=" + view, + new Exception()); + } view.mListenerView.setListener(view::fastFinish); view.mEndRunnable = () -> { @@ -639,6 +645,10 @@ public class FloatingIconView extends FrameLayout implements private void finish(DragLayer dragLayer) { ((ViewGroup) dragLayer.getParent()).removeView(this); dragLayer.removeView(mListenerView); + if (TestProtocol.sDebugTracing) { + Log.d(TestProtocol.PAUSE_NOT_DETECTED, "listenerView removed from dragLayer. " + + "listenerView=" + mListenerView + ", fiv=" + this, new Exception()); + } recycle(); mLauncher.getViewCache().recycleView(R.layout.floating_icon_view, this); } diff --git a/src/com/android/launcher3/views/ListenerView.java b/src/com/android/launcher3/views/ListenerView.java index 263f7c4c32..575f86454c 100644 --- a/src/com/android/launcher3/views/ListenerView.java +++ b/src/com/android/launcher3/views/ListenerView.java @@ -17,18 +17,20 @@ package com.android.launcher3.views; import android.content.Context; import android.util.AttributeSet; +import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import com.android.launcher3.AbstractFloatingView; +import com.android.launcher3.testing.TestProtocol; /** * An invisible AbstractFloatingView that can run a callback when it is being closed. */ public class ListenerView extends AbstractFloatingView { - public Runnable mCloseListener; + private Runnable mCloseListener; public ListenerView(Context context, AttributeSet attrs) { super(context, attrs); @@ -36,12 +38,20 @@ public class ListenerView extends AbstractFloatingView { } public void setListener(Runnable listener) { + if (TestProtocol.sDebugTracing) { + Log.d(TestProtocol.PAUSE_NOT_DETECTED, "ListenerView setListener lv=" + this + + ", listener=" + listener, new Exception()); + } mCloseListener = listener; } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); + if (TestProtocol.sDebugTracing) { + Log.d(TestProtocol.PAUSE_NOT_DETECTED, "ListenerView onAttachedToWindow lv=" + this, + new Exception()); + } mIsOpen = true; } @@ -49,10 +59,19 @@ public class ListenerView extends AbstractFloatingView { protected void onDetachedFromWindow() { super.onDetachedFromWindow(); mIsOpen = false; + if (TestProtocol.sDebugTracing) { + Log.d(TestProtocol.PAUSE_NOT_DETECTED, "ListenerView onDetachedFromView lv=" + this, + new Exception()); + } } @Override protected void handleClose(boolean animate) { + if (TestProtocol.sDebugTracing) { + Log.d(TestProtocol.PAUSE_NOT_DETECTED, "ListenerView handeClose lv=" + this + + ", mIsOpen=" + mIsOpen + ", mCloseListener=" + mCloseListener + + ", getParent()=" + getParent(), new Exception()); + } if (mIsOpen) { if (mCloseListener != null) { mCloseListener.run(); @@ -77,6 +96,10 @@ public class ListenerView extends AbstractFloatingView { @Override public boolean onControllerInterceptTouchEvent(MotionEvent ev) { + if (TestProtocol.sDebugTracing) { + Log.d(TestProtocol.PAUSE_NOT_DETECTED, "ListenerView touchEvent lv=" + this + + ", ev=" + ev, new Exception()); + } if (ev.getAction() == MotionEvent.ACTION_DOWN) { handleClose(false); } diff --git a/src/com/android/launcher3/views/OptionsPopupView.java b/src/com/android/launcher3/views/OptionsPopupView.java index d5c3c1d30e..7467186721 100644 --- a/src/com/android/launcher3/views/OptionsPopupView.java +++ b/src/com/android/launcher3/views/OptionsPopupView.java @@ -17,6 +17,9 @@ package com.android.launcher3.views; import static com.android.launcher3.Utilities.EXTRA_WALLPAPER_FLAVOR; import static com.android.launcher3.Utilities.EXTRA_WALLPAPER_OFFSET; +import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SETTINGS_BUTTON_TAP_OR_LONGPRESS; +import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WALLPAPER_BUTTON_TAP_OR_LONGPRESS; +import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WIDGETSTRAY_BUTTON_TAP_OR_LONGPRESS; import android.content.Context; import android.content.Intent; @@ -37,13 +40,12 @@ import androidx.annotation.VisibleForTesting; import com.android.launcher3.Launcher; import com.android.launcher3.R; import com.android.launcher3.Utilities; +import com.android.launcher3.logging.StatsLogManager.EventEnum; import com.android.launcher3.model.WidgetsModel; import com.android.launcher3.popup.ArrowPopup; import com.android.launcher3.shortcuts.DeepShortcutView; import com.android.launcher3.testing.TestLogging; import com.android.launcher3.testing.TestProtocol; -import com.android.launcher3.userevent.nano.LauncherLogProto.Action; -import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType; import com.android.launcher3.widget.WidgetsFullSheet; import java.util.ArrayList; @@ -68,21 +70,21 @@ public class OptionsPopupView extends ArrowPopup @Override public void onClick(View view) { - handleViewClick(view, Action.Touch.TAP); + handleViewClick(view); } @Override public boolean onLongClick(View view) { - return handleViewClick(view, Action.Touch.LONGPRESS); + return handleViewClick(view); } - private boolean handleViewClick(View view, int action) { + private boolean handleViewClick(View view) { OptionItem item = mItemMap.get(view); if (item == null) { return false; } - if (item.mControlTypeForLog > 0) { - logTap(action, item.mControlTypeForLog); + if (item.mEventId.getId() > 0) { + mLauncher.getStatsLogManager().log(item.mEventId); } if (item.mClickListener.onLongClick(view)) { close(true); @@ -91,10 +93,6 @@ public class OptionsPopupView extends ArrowPopup return false; } - private void logTap(int action, int controlType) { - mLauncher.getUserEventDispatcher().logActionOnControl(action, controlType); - } - @Override public boolean onControllerInterceptTouchEvent(MotionEvent ev) { if (ev.getAction() != MotionEvent.ACTION_DOWN) { @@ -159,13 +157,16 @@ public class OptionsPopupView extends ArrowPopup int resDrawable = Utilities.existsStyleWallpapers(launcher) ? R.drawable.ic_palette : R.drawable.ic_wallpaper; options.add(new OptionItem(resString, resDrawable, - ControlType.WALLPAPER_BUTTON, OptionsPopupView::startWallpaperPicker)); + LAUNCHER_WALLPAPER_BUTTON_TAP_OR_LONGPRESS, + OptionsPopupView::startWallpaperPicker)); if (!WidgetsModel.GO_DISABLE_WIDGETS) { options.add(new OptionItem(R.string.widget_button_text, R.drawable.ic_widget, - ControlType.WIDGETS_BUTTON, OptionsPopupView::onWidgetsClicked)); + LAUNCHER_WIDGETSTRAY_BUTTON_TAP_OR_LONGPRESS, + OptionsPopupView::onWidgetsClicked)); } options.add(new OptionItem(R.string.settings_button_text, R.drawable.ic_setting, - ControlType.SETTINGS_BUTTON, OptionsPopupView::startSettings)); + LAUNCHER_SETTINGS_BUTTON_TAP_OR_LONGPRESS, + OptionsPopupView::startSettings)); show(launcher, target, options); } @@ -224,14 +225,14 @@ public class OptionsPopupView extends ArrowPopup private final int mLabelRes; private final int mIconRes; - private final int mControlTypeForLog; + private final EventEnum mEventId; private final OnLongClickListener mClickListener; - public OptionItem(int labelRes, int iconRes, int controlTypeForLog, + public OptionItem(int labelRes, int iconRes, EventEnum eventId, OnLongClickListener clickListener) { mLabelRes = labelRes; mIconRes = iconRes; - mControlTypeForLog = controlTypeForLog; + mEventId = eventId; mClickListener = clickListener; } } diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/PreviewSurfaceRenderer.java b/src_ui_overrides/com/android/launcher3/uioverrides/PreviewSurfaceRenderer.java deleted file mode 100644 index 4913cadb5a..0000000000 --- a/src_ui_overrides/com/android/launcher3/uioverrides/PreviewSurfaceRenderer.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher3.uioverrides; - -import android.content.Context; -import android.os.Bundle; - -/** Render preview using surface view. */ -public class PreviewSurfaceRenderer { - - /** Handle a received surface view request. */ - public static void render(Context context, Bundle bundle) { } -} From f903d2c910e76432ebbaa3a803b8d4e817f9ea7f Mon Sep 17 00:00:00 2001 From: Adam Cohen Date: Wed, 3 Jun 2020 18:56:27 -0400 Subject: [PATCH 0020/1707] Merging from ub-launcher3-rvc-dev @ build 6557059 Bug:150504032 Test: manual, presubmit on the source branch x20/teams/android-launcher/merge/ub-launcher3-rvc-dev_rvc-dev_6557059.html Change-Id: I280025d9e58626fe725fe24ca60c28e89d0cee74 --- .../hybridhotseat/HotseatEduController.java | 8 +- .../hybridhotseat/HotseatEduDialog.java | 3 +- .../HotseatPredictionController.java | 10 +- .../hybridhotseat/HotseatRestoreHelper.java | 100 +++++++ .../QuickstepAtomicAnimationFactory.java | 2 + .../AppToOverviewAnimationProvider.java | 5 +- .../android/quickstep/BaseSwipeUpHandler.java | 21 +- .../quickstep/LauncherActivityInterface.java | 14 +- .../quickstep/TaskShortcutFactory.java | 26 +- .../com/android/quickstep/TaskViewUtils.java | 7 +- .../quickstep/TouchInteractionService.java | 48 ++-- .../quickstep/util/RectFSpringAnim.java | 9 +- .../util/StaggeredWorkspaceAnim.java | 7 +- .../util/SurfaceTransactionApplier.java | 136 ++++++++++ .../quickstep/util/TransformParams.java | 4 +- .../quickstep/views/OverviewActionsView.java | 19 +- .../android/quickstep/views/RecentsView.java | 11 +- .../com/android/quickstep/views/TaskView.java | 6 +- .../res/layout/gesture_tutorial_fragment.xml | 3 +- quickstep/res/values/colors.xml | 1 + quickstep/res/values/strings.xml | 14 +- quickstep/res/values/styles.xml | 6 +- .../launcher3/BaseQuickstepLauncher.java | 4 +- .../QuickstepAppTransitionManagerImpl.java | 14 +- .../launcher3/model/WellbeingModel.java | 8 +- .../statehandlers/DepthController.java | 4 +- .../OrientationTouchTransformer.java | 11 + .../RecentsAnimationDeviceState.java | 8 +- .../quickstep/RemoteAnimationTargets.java | 73 ++++-- .../com/android/quickstep/SystemUiProxy.java | 5 + .../BackGestureTutorialController.java | 8 - .../interaction/EdgeBackGesturePanel.java | 6 +- .../HomeGestureTutorialController.java | 232 +---------------- .../OverviewGestureTutorialController.java | 124 +++++++++ .../OverviewGestureTutorialFragment.java | 37 +++ .../SwipeUpGestureTutorialController.java | 246 ++++++++++++++++++ .../interaction/TutorialController.java | 19 +- .../interaction/TutorialFragment.java | 3 + res/drawable/gesture_tutorial_ripple.xml | 5 +- res/values/colors.xml | 6 - res/values/styles.xml | 1 - .../config/robolectric.properties | 2 +- ...a => ShadowSurfaceTransactionApplier.java} | 6 +- .../android/launcher3/LauncherProvider.java | 12 + .../android/launcher3/LauncherSettings.java | 7 + .../launcher3/allapps/SearchUiManager.java | 9 + .../search/AppsSearchContainerLayout.java | 3 +- .../launcher3/config/FeatureFlags.java | 2 +- .../launcher3/logging/StatsLogManager.java | 12 +- .../launcher3/model/GridBackupTable.java | 26 ++ .../settings/DeveloperOptionsFragment.java | 9 + .../states/StateAnimationConfig.java | 6 +- .../launcher3/testing/TestProtocol.java | 1 + 53 files changed, 1018 insertions(+), 351 deletions(-) create mode 100644 quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatRestoreHelper.java create mode 100644 quickstep/recents_ui_overrides/src/com/android/quickstep/util/SurfaceTransactionApplier.java create mode 100644 quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java create mode 100644 quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialFragment.java create mode 100644 quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java rename robolectric_tests/src/com/android/launcher3/shadows/{ShadowSyncRtSurfaceTransactionApplierCompat.java => ShadowSurfaceTransactionApplier.java} (83%) diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java index 7c4f3ecd01..f1ce72e708 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java @@ -52,6 +52,7 @@ public class HotseatEduController { private final Launcher mLauncher; private final Hotseat mHotseat; + private final HotseatRestoreHelper mRestoreHelper; private List mPredictedApps; private HotseatEduDialog mActiveDialog; @@ -59,9 +60,10 @@ public class HotseatEduController { private IntArray mNewScreens = null; private Runnable mOnOnboardingComplete; - HotseatEduController(Launcher launcher, Runnable runnable) { + HotseatEduController(Launcher launcher, HotseatRestoreHelper restoreHelper, Runnable runnable) { mLauncher = launcher; mHotseat = launcher.getHotseat(); + mRestoreHelper = restoreHelper; mOnOnboardingComplete = runnable; } @@ -69,11 +71,14 @@ public class HotseatEduController { * Checks what type of migration should be used and migrates hotseat */ void migrate() { + mRestoreHelper.createBackup(); if (FeatureFlags.HOTSEAT_MIGRATE_TO_FOLDER.get()) { migrateToFolder(); } else { migrateHotseatWhole(); } + Snackbar.show(mLauncher, R.string.hotsaet_tip_prediction_enabled, R.string.hotseat_turn_off, + null, () -> mLauncher.startActivity(new Intent(SETTINGS_ACTION))); } /** @@ -84,7 +89,6 @@ public class HotseatEduController { */ private int migrateToFolder() { ArrayDeque folders = new ArrayDeque<>(); - ArrayList putIntoFolder = new ArrayList<>(); //separate folders and items that can get in folders diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java index 66c60bca1f..fdd3562a2e 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java @@ -16,7 +16,8 @@ package com.android.launcher3.hybridhotseat; import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent; -import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType.HYBRID_HOTSEAT_CANCELED; +import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType + .HYBRID_HOTSEAT_CANCELED; import android.animation.PropertyValuesHolder; import android.content.Context; 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 05bcb57adc..725f516335 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 @@ -92,6 +92,8 @@ public class HotseatPredictionController implements DragController.DragListener, private Launcher mLauncher; private final Hotseat mHotseat; + private final HotseatRestoreHelper mRestoreHelper; + private List mComponentKeyMappers = new ArrayList<>(); private DynamicItemCache mDynamicItemCache; @@ -129,6 +131,7 @@ public class HotseatPredictionController implements DragController.DragListener, mHotSeatItemsCount = mLauncher.getDeviceProfile().inv.numHotseatIcons; launcher.getDeviceProfile().inv.addOnChangeListener(this); mHotseat.addOnAttachStateChangeListener(this); + mRestoreHelper = new HotseatRestoreHelper(mLauncher); if (mHotseat.isAttachedToWindow()) { onViewAttachedToWindow(mHotseat); } @@ -297,7 +300,8 @@ public class HotseatPredictionController implements DragController.DragListener, }); setPauseUIUpdate(false); if (!isEduSeen()) { - mHotseatEduController = new HotseatEduController(mLauncher, this::createPredictor); + mHotseatEduController = new HotseatEduController(mLauncher, mRestoreHelper, + this::createPredictor); } } @@ -320,9 +324,11 @@ public class HotseatPredictionController implements DragController.DragListener, updateDependencies(); bindItems(items, false, null); } - private void setPredictedApps(List appTargets) { mComponentKeyMappers.clear(); + if (appTargets.isEmpty() && mRestoreHelper.shouldRestoreToBackup()) { + mRestoreHelper.restoreBackup(); + } StringBuilder predictionLog = new StringBuilder("predictedApps: [\n"); ArrayList componentKeys = new ArrayList<>(); for (AppTarget appTarget : appTargets) { diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatRestoreHelper.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatRestoreHelper.java new file mode 100644 index 0000000000..c95ff7a2d1 --- /dev/null +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatRestoreHelper.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.hybridhotseat; + +import static com.android.launcher3.LauncherSettings.Favorites.HYBRID_HOTSEAT_BACKUP_TABLE; +import static com.android.launcher3.provider.LauncherDbUtils.tableExists; + +import com.android.launcher3.InvariantDeviceProfile; +import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherSettings; +import com.android.launcher3.model.GridBackupTable; +import com.android.launcher3.provider.LauncherDbUtils; + +/** + * A helper class to manage migration revert restoration for hybrid hotseat + */ +public class HotseatRestoreHelper { + private final Launcher mLauncher; + private boolean mBackupExists; + + HotseatRestoreHelper(Launcher context) { + mLauncher = context; + setupBackupTable(); + } + + /** + * Creates a snapshot backup of Favorite table for future restoration use. + */ + public synchronized void createBackup() { + try (LauncherDbUtils.SQLiteTransaction transaction = (LauncherDbUtils.SQLiteTransaction) + LauncherSettings.Settings.call( + mLauncher.getContentResolver(), + LauncherSettings.Settings.METHOD_NEW_TRANSACTION) + .getBinder(LauncherSettings.Settings.EXTRA_VALUE)) { + InvariantDeviceProfile idp = mLauncher.getDeviceProfile().inv; + GridBackupTable backupTable = new GridBackupTable(mLauncher, + transaction.getDb(), idp.numHotseatIcons, idp.numColumns, + idp.numRows); + backupTable.createCustomBackupTable(HYBRID_HOTSEAT_BACKUP_TABLE); + transaction.commit(); + LauncherSettings.Settings.call(mLauncher.getContentResolver(), + LauncherSettings.Settings.METHOD_REFRESH_HOTSEAT_RESTORE_TABLE); + mBackupExists = true; + } + } + + /** + * Finds and restores a previously saved snapshow of Favorites table + */ + public void restoreBackup() { + try (LauncherDbUtils.SQLiteTransaction transaction = (LauncherDbUtils.SQLiteTransaction) + LauncherSettings.Settings.call( + mLauncher.getContentResolver(), + LauncherSettings.Settings.METHOD_NEW_TRANSACTION) + .getBinder(LauncherSettings.Settings.EXTRA_VALUE)) { + if (!tableExists(transaction.getDb(), HYBRID_HOTSEAT_BACKUP_TABLE)) { + mBackupExists = false; + return; + } + InvariantDeviceProfile idp = mLauncher.getDeviceProfile().inv; + GridBackupTable backupTable = new GridBackupTable(mLauncher, + transaction.getDb(), idp.numHotseatIcons, idp.numColumns, + idp.numRows); + backupTable.restoreFromCustomBackupTable(HYBRID_HOTSEAT_BACKUP_TABLE, true); + transaction.commit(); + mBackupExists = false; + mLauncher.getModel().forceReload(); + } + } + + /** + * Returns if prediction controller should attempt restoring a backup + */ + public synchronized boolean shouldRestoreToBackup() { + return mBackupExists; + } + + private synchronized void setupBackupTable() { + try (LauncherDbUtils.SQLiteTransaction transaction = (LauncherDbUtils.SQLiteTransaction) + LauncherSettings.Settings.call( + mLauncher.getContentResolver(), + LauncherSettings.Settings.METHOD_NEW_TRANSACTION) + .getBinder(LauncherSettings.Settings.EXTRA_VALUE)) { + mBackupExists = tableExists(transaction.getDb(), HYBRID_HOTSEAT_BACKUP_TABLE); + } + } +} diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java index a487869316..79dc3e25c3 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java @@ -37,6 +37,7 @@ import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_7; import static com.android.launcher3.anim.Interpolators.clampToProgress; import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS; import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE; +import static com.android.launcher3.states.StateAnimationConfig.ANIM_DEPTH; import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_SCALE; import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_TRANSLATE; import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE; @@ -210,6 +211,7 @@ public class QuickstepAtomicAnimationFactory extends } config.setInterpolator(ANIM_WORKSPACE_FADE, OVERSHOOT_1_2); config.setInterpolator(ANIM_OVERVIEW_SCALE, OVERSHOOT_1_2); + config.setInterpolator(ANIM_DEPTH, OVERSHOOT_1_2); Interpolator translationInterpolator = ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(mActivity) ? OVERSHOOT_1_2 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 c18a0fdebb..e5782e7a45 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java @@ -33,11 +33,11 @@ import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.statehandlers.DepthController; import com.android.launcher3.statemanager.StatefulActivity; import com.android.quickstep.util.RemoteAnimationProvider; +import com.android.quickstep.util.SurfaceTransactionApplier; import com.android.quickstep.util.TaskViewSimulator; import com.android.quickstep.util.TransformParams; import com.android.quickstep.views.RecentsView; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; -import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat; /** * Provider for the atomic (for 3-button mode) remote window animation from the app to the overview. @@ -132,8 +132,7 @@ final class AppToOverviewAnimationProvider> extend TransformParams params = new TransformParams() .setTargetSet(targets) - .setSyncTransactionApplier( - new SyncRtSurfaceTransactionApplierCompat(mActivity.getRootView())); + .setSyncTransactionApplier(new SurfaceTransactionApplier(mActivity.getRootView())); AnimatedFloat recentsAlpha = new AnimatedFloat(() -> { }); params.setBaseBuilderProxy((builder, app, p) 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 737d837ac7..6cbe794860 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java @@ -40,13 +40,14 @@ import com.android.launcher3.util.WindowBounds; import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener; import com.android.quickstep.util.ActiveGestureLog; import com.android.quickstep.util.ActivityInitListener; +import com.android.quickstep.util.RectFSpringAnim; +import com.android.quickstep.util.SurfaceTransactionApplier; import com.android.quickstep.util.TransformParams; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.TaskView; import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.system.InputConsumerController; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; -import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat; import java.util.ArrayList; import java.util.function.Consumer; @@ -114,10 +115,10 @@ public abstract class BaseSwipeUpHandler, Q extend public abstract Intent getLaunchIntent(); protected void linkRecentsViewScroll() { - SyncRtSurfaceTransactionApplierCompat.create(mRecentsView, applier -> { + SurfaceTransactionApplier.create(mRecentsView, applier -> { mTransformParams.setSyncTransactionApplier(applier); runOnRecentsAnimationStart(() -> - mRecentsAnimationTargets.addDependentTransactionApplier(applier)); + mRecentsAnimationTargets.addReleaseCheck(applier)); }); mRecentsView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> { @@ -356,7 +357,8 @@ public abstract class BaseSwipeUpHandler, Q extend if (mWindowTransitionController != null) { float progress = mCurrentShift.value / mDragLengthFactor; mWindowTransitionController.setPlayFraction(progress); - + } + if (mRecentsAnimationTargets != null) { if (mRecentsViewScrollLinked) { mTaskViewSimulator.setScroll(mRecentsView.getScrollOffset()); } @@ -364,6 +366,17 @@ public abstract class BaseSwipeUpHandler, Q extend } } + @Override + protected RectFSpringAnim createWindowAnimationToHome(float startProgress, + HomeAnimationFactory homeAnimationFactory) { + RectFSpringAnim anim = + super.createWindowAnimationToHome(startProgress, homeAnimationFactory); + if (mRecentsAnimationTargets != null) { + mRecentsAnimationTargets.addReleaseCheck(anim); + } + return anim; + } + public interface Factory { BaseSwipeUpHandler newHandler( 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 62eb235251..4801d71779 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java @@ -310,8 +310,8 @@ public final class LauncherActivityInterface extends @Override protected float getExtraSpace(Context context, DeviceProfile dp, PagedOrientationHandler orientationHandler) { - if (dp.isVerticalBarLayout() || - hideShelfInTwoButtonLandscape(context, orientationHandler)) { + if ((dp.isVerticalBarLayout() && !showOverviewActions(context)) + || hideShelfInTwoButtonLandscape(context, orientationHandler)) { return 0; } else { Resources res = context.getResources(); @@ -319,12 +319,14 @@ public final class LauncherActivityInterface extends //TODO: this needs to account for the swipe gesture height and accessibility // UI when shown. float actionsBottomMargin = 0; - if (getMode(context) == Mode.THREE_BUTTONS) { - actionsBottomMargin = res.getDimensionPixelSize( + if (!dp.isVerticalBarLayout()) { + if (getMode(context) == Mode.THREE_BUTTONS) { + actionsBottomMargin = res.getDimensionPixelSize( R.dimen.overview_actions_bottom_margin_three_button); - } else { - actionsBottomMargin = res.getDimensionPixelSize( + } else { + actionsBottomMargin = res.getDimensionPixelSize( R.dimen.overview_actions_bottom_margin_gesture); + } } float actionsHeight = actionsBottomMargin + res.getDimensionPixelSize(R.dimen.overview_actions_height); diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskShortcutFactory.java index 021d39dd6e..cdaa6555a0 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskShortcutFactory.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskShortcutFactory.java @@ -18,6 +18,8 @@ package com.android.quickstep; import static android.view.Display.DEFAULT_DISPLAY; +import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_FREE_FORM_TAP; +import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_SPLIT_SCREEN_TAP; import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.TAP; import android.app.Activity; @@ -35,6 +37,7 @@ import android.view.View; import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.DeviceProfile; import com.android.launcher3.R; +import com.android.launcher3.logging.StatsLogManager.LauncherEvent; import com.android.launcher3.model.WellbeingModel; import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.popup.SystemShortcut; @@ -83,10 +86,12 @@ public interface TaskShortcutFactory { private final int mIconRes; private final int mTextRes; + private final LauncherEvent mLauncherEvent; - MultiWindowFactory(int iconRes, int textRes) { + MultiWindowFactory(int iconRes, int textRes, LauncherEvent launcherEvent) { mIconRes = iconRes; mTextRes = textRes; + mLauncherEvent = launcherEvent; } protected abstract boolean isAvailable(BaseDraggingActivity activity, int displayId); @@ -102,7 +107,8 @@ public interface TaskShortcutFactory { if (!isAvailable(activity, task.key.displayId)) { return null; } - return new MultiWindowSystemShortcut(mIconRes, mTextRes, activity, taskView, this); + return new MultiWindowSystemShortcut(mIconRes, mTextRes, activity, taskView, this, + mLauncherEvent); } } @@ -114,11 +120,12 @@ public interface TaskShortcutFactory { private final TaskThumbnailView mThumbnailView; private final TaskView mTaskView; private final MultiWindowFactory mFactory; + private final LauncherEvent mLauncherEvent; - public MultiWindowSystemShortcut(int iconRes, int textRes, - BaseDraggingActivity activity, TaskView taskView, MultiWindowFactory factory) { + public MultiWindowSystemShortcut(int iconRes, int textRes, BaseDraggingActivity activity, + TaskView taskView, MultiWindowFactory factory, LauncherEvent launcherEvent) { super(iconRes, textRes, activity, dummyInfo(taskView)); - + mLauncherEvent = launcherEvent; mHandler = new Handler(Looper.getMainLooper()); mTaskView = taskView; mRecentsView = activity.getOverviewPanel(); @@ -203,12 +210,13 @@ public interface TaskShortcutFactory { WindowManagerWrapper.getInstance().overridePendingAppTransitionMultiThumbFuture( future, animStartedListener, mHandler, true /* scaleUp */, taskKey.displayId); + mTarget.getStatsLogManager().log(mLauncherEvent, mTaskView.buildProto()); } } } - TaskShortcutFactory SPLIT_SCREEN = new MultiWindowFactory( - R.drawable.ic_split_screen, R.string.recent_task_option_split_screen) { + TaskShortcutFactory SPLIT_SCREEN = new MultiWindowFactory(R.drawable.ic_split_screen, + R.string.recent_task_option_split_screen, LAUNCHER_SYSTEM_SHORTCUT_SPLIT_SCREEN_TAP) { @Override protected boolean isAvailable(BaseDraggingActivity activity, int displayId) { @@ -241,8 +249,8 @@ public interface TaskShortcutFactory { } }; - TaskShortcutFactory FREE_FORM = new MultiWindowFactory( - R.drawable.ic_split_screen, R.string.recent_task_option_freeform) { + TaskShortcutFactory FREE_FORM = new MultiWindowFactory(R.drawable.ic_split_screen, + R.string.recent_task_option_freeform, LAUNCHER_SYSTEM_SHORTCUT_FREE_FORM_TAP) { @Override protected boolean isAvailable(BaseDraggingActivity activity, int displayId) { diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java index c68d6e2e0d..e2e25f32d3 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java @@ -42,6 +42,7 @@ import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.statehandlers.DepthController; import com.android.launcher3.util.DefaultDisplay; +import com.android.quickstep.util.SurfaceTransactionApplier; import com.android.quickstep.util.TaskViewSimulator; import com.android.quickstep.util.TransformParams; import com.android.quickstep.views.RecentsView; @@ -49,7 +50,6 @@ 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.system.RemoteAnimationTargetCompat; -import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat; /** * Utility class for helpful methods related to {@link TaskView} objects and their tasks. @@ -128,11 +128,10 @@ public final class TaskViewUtils { RemoteAnimationTargetCompat[] wallpaperTargets, DepthController depthController, PendingAnimation out) { - SyncRtSurfaceTransactionApplierCompat applier = - new SyncRtSurfaceTransactionApplierCompat(v); + SurfaceTransactionApplier applier = new SurfaceTransactionApplier(v); final RemoteAnimationTargets targets = new RemoteAnimationTargets(appTargets, wallpaperTargets, MODE_OPENING); - targets.addDependentTransactionApplier(applier); + targets.addReleaseCheck(applier); TransformParams params = new TransformParams() .setSyncTransactionApplier(applier) 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 c8619bdd12..d6c65efe53 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java @@ -30,6 +30,7 @@ import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYS import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TRACING_ENABLED; import android.annotation.TargetApi; +import android.app.ActivityManager; import android.app.PendingIntent; import android.app.RemoteAction; import android.app.Service; @@ -135,10 +136,10 @@ public class TouchInteractionService extends Service implements PluginListener RECT_CENTER_X = new FloatPropertyCompat("rectCenterXSpring") { @@ -116,6 +117,7 @@ public class RectFSpringAnim { ResourceProvider rp = DynamicResource.provider(context); mMinVisChange = rp.getDimension(R.dimen.swipe_up_fling_min_visible_change); mYOvershoot = rp.getDimension(R.dimen.swipe_up_y_overshoot); + setCanRelease(true); } public void onTargetPositionChanged() { @@ -190,10 +192,12 @@ public class RectFSpringAnim { maybeOnEnd(); }); + setCanRelease(false); + mAnimsStarted = true; + mRectXAnim.start(); mRectYAnim.start(); mRectScaleAnim.start(); - mAnimsStarted = true; for (Animator.AnimatorListener animatorListener : mAnimatorListeners) { animatorListener.onAnimationStart(null); } @@ -245,6 +249,7 @@ public class RectFSpringAnim { private void maybeOnEnd() { if (mAnimsStarted && isEnded()) { mAnimsStarted = false; + setCanRelease(true); for (Animator.AnimatorListener animatorListener : mAnimatorListeners) { animatorListener.onAnimationEnd(null); } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java index ebc9f96394..70903935bf 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java @@ -121,8 +121,11 @@ public class StaggeredWorkspaceAnim { addStaggeredAnimationForView(child, grid.inv.numRows + 1, totalRows); } - View qsb = launcher.findViewById(R.id.search_container_all_apps); - addStaggeredAnimationForView(qsb, grid.inv.numRows + 2, totalRows); + if (launcher.getAppsView().getSearchUiManager() + .isQsbVisible(NORMAL.getVisibleElements(launcher))) { + addStaggeredAnimationForView(launcher.getAppsView().getSearchView(), + grid.inv.numRows + 2, totalRows); + } } if (animateOverviewScrim) { diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SurfaceTransactionApplier.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SurfaceTransactionApplier.java new file mode 100644 index 0000000000..0a3e3ecf11 --- /dev/null +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SurfaceTransactionApplier.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.quickstep.util; + +import static com.android.systemui.shared.system.TransactionCompat.deferTransactionUntil; +import static com.android.systemui.shared.system.TransactionCompat.setEarlyWakeup; + +import android.annotation.TargetApi; +import android.os.Build; +import android.os.Handler; +import android.os.Message; +import android.view.SurfaceControl; +import android.view.SurfaceControl.Transaction; +import android.view.View; + +import com.android.quickstep.RemoteAnimationTargets.ReleaseCheck; +import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams; +import com.android.systemui.shared.system.ViewRootImplCompat; + +import java.util.function.Consumer; + + +/** + * Helper class to apply surface transactions in sync with RenderThread similar to + * android.view.SyncRtSurfaceTransactionApplier + * with some Launcher specific utility methods + */ +@TargetApi(Build.VERSION_CODES.R) +public class SurfaceTransactionApplier extends ReleaseCheck { + + private static final int MSG_UPDATE_SEQUENCE_NUMBER = 0; + + private final SurfaceControl mBarrierSurfaceControl; + private final ViewRootImplCompat mTargetViewRootImpl; + private final Handler mApplyHandler; + + private int mLastSequenceNumber = 0; + + /** + * @param targetView The view in the surface that acts as synchronization anchor. + */ + public SurfaceTransactionApplier(View targetView) { + mTargetViewRootImpl = new ViewRootImplCompat(targetView); + mBarrierSurfaceControl = mTargetViewRootImpl.getRenderSurfaceControl(); + mApplyHandler = new Handler(this::onApplyMessage); + } + + protected boolean onApplyMessage(Message msg) { + if (msg.what == MSG_UPDATE_SEQUENCE_NUMBER) { + setCanRelease(msg.arg1 == mLastSequenceNumber); + return true; + } + return false; + } + + /** + * Schedules applying surface parameters on the next frame. + * + * @param params The surface parameters to apply. DO NOT MODIFY the list after passing into + * this method to avoid synchronization issues. + */ + public void scheduleApply(final SurfaceParams... params) { + View view = mTargetViewRootImpl.getView(); + if (view == null) { + return; + } + + mLastSequenceNumber++; + final int toApplySeqNo = mLastSequenceNumber; + setCanRelease(false); + mTargetViewRootImpl.registerRtFrameCallback(frame -> { + if (mBarrierSurfaceControl == null || !mBarrierSurfaceControl.isValid()) { + Message.obtain(mApplyHandler, MSG_UPDATE_SEQUENCE_NUMBER, toApplySeqNo, 0) + .sendToTarget(); + return; + } + Transaction t = new Transaction(); + for (int i = params.length - 1; i >= 0; i--) { + SurfaceParams surfaceParams = params[i]; + if (surfaceParams.surface.isValid()) { + deferTransactionUntil(t, surfaceParams.surface, mBarrierSurfaceControl, frame); + surfaceParams.applyTo(t); + } + } + setEarlyWakeup(t); + t.apply(); + Message.obtain(mApplyHandler, MSG_UPDATE_SEQUENCE_NUMBER, toApplySeqNo, 0) + .sendToTarget(); + }); + + // Make sure a frame gets scheduled. + view.invalidate(); + } + + /** + * Creates an instance of SyncRtSurfaceTransactionApplier, deferring until the target view is + * attached if necessary. + */ + public static void create( + final View targetView, final Consumer callback) { + if (targetView == null) { + // No target view, no applier + callback.accept(null); + } else if (new ViewRootImplCompat(targetView).isValid()) { + // Already attached, we're good to go + callback.accept(new SurfaceTransactionApplier(targetView)); + } else { + // Haven't been attached before we can get the view root + targetView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(View v) { + targetView.removeOnAttachStateChangeListener(this); + callback.accept(new SurfaceTransactionApplier(targetView)); + } + + @Override + public void onViewDetachedFromWindow(View v) { + // Do nothing + } + }); + } + } +} diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TransformParams.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TransformParams.java index 9bb508eb37..7a62e83c39 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TransformParams.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TransformParams.java @@ -57,7 +57,7 @@ public class TransformParams { private float mTargetAlpha; private float mCornerRadius; private RemoteAnimationTargets mTargetSet; - private SyncRtSurfaceTransactionApplierCompat mSyncTransactionApplier; + private SurfaceTransactionApplier mSyncTransactionApplier; private BuilderProxy mHomeBuilderProxy = BuilderProxy.ALWAYS_VISIBLE; private BuilderProxy mBaseBuilderProxy = BuilderProxy.ALWAYS_VISIBLE; @@ -112,7 +112,7 @@ public class TransformParams { * are computed based on these TransformParams. */ public TransformParams setSyncTransactionApplier( - SyncRtSurfaceTransactionApplierCompat applier) { + SurfaceTransactionApplier applier) { mSyncTransactionApplier = applier; return this; } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java index 83287c463a..08c3dc9c27 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java @@ -21,6 +21,7 @@ import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_SHARE; import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview; import android.content.Context; +import android.content.res.Configuration; import android.util.AttributeSet; import android.util.Log; import android.view.View; @@ -33,6 +34,7 @@ import androidx.annotation.Nullable; import com.android.launcher3.R; import com.android.launcher3.util.MultiValueAlpha; import com.android.launcher3.util.MultiValueAlpha.AlphaProperty; +import com.android.quickstep.SysUINavigationMode; import com.android.quickstep.SysUINavigationMode.Mode; import com.android.quickstep.TaskOverlayFactory.OverlayUICallbacks; @@ -129,6 +131,12 @@ public class OverviewActionsView extends FrameLayo updateHiddenFlags(HIDDEN_UNSUPPORTED_NAVIGATION, !removeShelfFromOverview(getContext())); } + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + updateVerticalMargin(SysUINavigationMode.getMode(getContext())); + } + public void updateHiddenFlags(@ActionsHiddenFlags int visibilityFlags, boolean enable) { if (enable) { mHiddenFlags |= visibilityFlags; @@ -152,10 +160,13 @@ public class OverviewActionsView extends FrameLayo return mMultiValueAlpha.getProperty(INDEX_FULLSCREEN_ALPHA); } - /** Updates vertical margins for different navigation mode. */ - public void updateVerticalMarginForNavModeChange(Mode mode) { - int bottomMargin = 0; - if (mode == Mode.THREE_BUTTONS) { + /** Updates vertical margins for different navigation mode or configuration changes. */ + public void updateVerticalMargin(Mode mode) { + int bottomMargin; + int orientation = getResources().getConfiguration().orientation; + if (orientation == Configuration.ORIENTATION_LANDSCAPE) { + bottomMargin = 0; + } else if (mode == Mode.THREE_BUTTONS) { bottomMargin = getResources() .getDimensionPixelSize(R.dimen.overview_actions_bottom_margin_three_button); } else { 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 324aaecf0e..64d90cf4f7 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 @@ -127,6 +127,7 @@ import com.android.quickstep.ViewUtils; import com.android.quickstep.util.LayoutUtils; import com.android.quickstep.util.RecentsOrientedState; import com.android.quickstep.util.SplitScreenBounds; +import com.android.quickstep.util.SurfaceTransactionApplier; import com.android.quickstep.util.TransformParams; import com.android.systemui.plugins.ResourceProvider; import com.android.systemui.shared.recents.IPinnedStackAnimationListener; @@ -135,7 +136,6 @@ import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.LauncherEventUtil; import com.android.systemui.shared.system.PackageManagerWrapper; -import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat; import com.android.systemui.shared.system.TaskStackChangeListener; import java.util.ArrayList; @@ -211,7 +211,7 @@ public abstract class RecentsView extends PagedView impl protected final BaseActivityInterface mSizeStrategy; protected RecentsAnimationController mRecentsAnimationController; protected RecentsAnimationTargets mRecentsAnimationTargets; - protected SyncRtSurfaceTransactionApplierCompat mSyncTransactionApplier; + protected SurfaceTransactionApplier mSyncTransactionApplier; protected int mTaskWidth; protected int mTaskHeight; protected boolean mEnableDrawingLiveTile = false; @@ -492,6 +492,7 @@ public abstract class RecentsView extends PagedView impl public void init(OverviewActionsView actionsView) { mActionsView = actionsView; + mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, getTaskViewCount() == 0); } @Override @@ -501,7 +502,7 @@ public abstract class RecentsView extends PagedView impl mModel.getThumbnailCache().getHighResLoadingState().addCallback(this); mActivity.addMultiWindowModeChangedListener(mMultiWindowModeChangedListener); ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener); - mSyncTransactionApplier = new SyncRtSurfaceTransactionApplierCompat(this); + mSyncTransactionApplier = new SurfaceTransactionApplier(this); RecentsModel.INSTANCE.get(getContext()).addThumbnailChangeListener(this); mIdp.addOnChangeListener(this); mIPinnedStackAnimationListener.setActivity(mActivity); @@ -1626,8 +1627,10 @@ public abstract class RecentsView extends PagedView impl : View.LAYOUT_DIRECTION_RTL); mClearAllButton.setRotation(mOrientationHandler.getDegreesRotated()); mActivity.getDragLayer().recreateControllers(); + boolean isInLandscape = touchRotation != 0 + || mOrientationState.getLauncherRotation() != ROTATION_0; mActionsView.updateHiddenFlags(HIDDEN_NON_ZERO_ROTATION, - touchRotation != 0 || mOrientationState.getLauncherRotation() != ROTATION_0); + !mOrientationState.canLauncherRotate() && isInLandscape); resetPaddingFromTaskSize(); requestLayout(); } 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 6b759ba43a..af9d7f76bc 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 @@ -30,6 +30,7 @@ import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN; import static com.android.launcher3.anim.Interpolators.LINEAR; import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR; import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; +import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_ICON_TAP_OR_LONGPRESS; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_TAP; import android.animation.Animator; @@ -222,8 +223,8 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { setOutlineProvider(mOutlineProvider); } - /* Builds proto for logging */ - protected LauncherAtom.ItemInfo buildProto() { + /** Builds proto for logging */ + public LauncherAtom.ItemInfo buildProto() { ComponentKey componentKey = TaskUtils.getLaunchComponentKeyForTask(getTask().key); LauncherAtom.ItemInfo.Builder itemBuilder = LauncherAtom.ItemInfo.newBuilder(); itemBuilder.setIsWork(componentKey.user != Process.myUserHandle()); @@ -424,6 +425,7 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { private boolean showTaskMenu(int action) { getRecentsView().snapToPage(getRecentsView().indexOfChild(this)); mMenuView = TaskMenuView.showForTask(this); + mActivity.getStatsLogManager().log(LAUNCHER_TASK_ICON_TAP_OR_LONGPRESS, buildProto()); UserEventDispatcher.newInstance(getContext()).logActionOnItem(action, Direction.NONE, LauncherLogProto.ItemType.TASK_ICON); if (mMenuView != null) { diff --git a/quickstep/res/layout/gesture_tutorial_fragment.xml b/quickstep/res/layout/gesture_tutorial_fragment.xml index 74197bef40..459d65faf7 100644 --- a/quickstep/res/layout/gesture_tutorial_fragment.xml +++ b/quickstep/res/layout/gesture_tutorial_fragment.xml @@ -16,7 +16,7 @@ + android:background="?android:attr/colorBackground"> + #FFFFFFFF #99000000 \ No newline at end of file diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml index 37d5ca3715..1ed3b2457c 100644 --- a/quickstep/res/values/strings.xml +++ b/quickstep/res/values/strings.xml @@ -83,6 +83,8 @@ Drag apps off the bottom row to get app suggestions App suggestions added to empty space + + App suggestions Enabled Predicted app: %1$s @@ -110,7 +112,6 @@ To change the sensitivity of the back gesture, go to Settings - Tutorial: Go Home @@ -122,6 +123,17 @@ Make sure you swipe straight up + + Tutorial: Switch Apps + + Swipe up from the bottom of the screen and hold + + Make sure you swipe from the bottom edge of the screen + + Try holding the window for longer before releasing + + Make sure you swipe straight up and pause + All set diff --git a/quickstep/res/values/styles.xml b/quickstep/res/values/styles.xml index 90957e42ff..8d054b4be5 100644 --- a/quickstep/res/values/styles.xml +++ b/quickstep/res/values/styles.xml @@ -35,14 +35,14 @@ @@ -50,7 +50,7 @@ diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java index 0968d8e4ad..48743070d8 100644 --- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java @@ -93,7 +93,7 @@ public abstract class BaseQuickstepLauncher extends Launcher public void onNavigationModeChanged(Mode newMode) { getDragLayer().recreateControllers(); if (mActionsView != null && isOverviewActionsEnabled()) { - mActionsView.updateVerticalMarginForNavModeChange(newMode); + mActionsView.updateVerticalMargin(newMode); } } @@ -175,7 +175,7 @@ public abstract class BaseQuickstepLauncher extends Launcher // Overview is above all other launcher elements, including qsb, so move it to the top. getOverviewPanel().bringToFront(); mActionsView.bringToFront(); - mActionsView.updateVerticalMarginForNavModeChange(SysUINavigationMode.getMode(this)); + mActionsView.updateVerticalMargin(SysUINavigationMode.getMode(this)); } } diff --git a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java index c0d16c9ba8..10f789d428 100644 --- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java +++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java @@ -82,6 +82,7 @@ import com.android.quickstep.RemoteAnimationTargets; import com.android.quickstep.util.MultiValueUpdateListener; import com.android.quickstep.util.RemoteAnimationProvider; import com.android.quickstep.util.StaggeredWorkspaceAnim; +import com.android.quickstep.util.SurfaceTransactionApplier; import com.android.systemui.shared.system.ActivityCompat; import com.android.systemui.shared.system.ActivityOptionsCompat; import com.android.systemui.shared.system.QuickStepContract; @@ -89,7 +90,6 @@ import com.android.systemui.shared.system.RemoteAnimationAdapterCompat; import com.android.systemui.shared.system.RemoteAnimationDefinitionCompat; import com.android.systemui.shared.system.RemoteAnimationRunnerCompat; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; -import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat; import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams; import com.android.systemui.shared.system.WindowManagerWrapper; @@ -455,9 +455,9 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans RemoteAnimationTargets openingTargets = new RemoteAnimationTargets(appTargets, wallpaperTargets, MODE_OPENING); - SyncRtSurfaceTransactionApplierCompat surfaceApplier = - new SyncRtSurfaceTransactionApplierCompat(floatingView); - openingTargets.addDependentTransactionApplier(surfaceApplier); + SurfaceTransactionApplier surfaceApplier = + new SurfaceTransactionApplier(floatingView); + openingTargets.addReleaseCheck(surfaceApplier); // Scale the app icon to take up the entire screen. This simplifies the math when // animating the app window position / scale. @@ -714,8 +714,7 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans */ private Animator getUnlockWindowAnimator(RemoteAnimationTargetCompat[] appTargets, RemoteAnimationTargetCompat[] wallpaperTargets) { - SyncRtSurfaceTransactionApplierCompat surfaceApplier = - new SyncRtSurfaceTransactionApplierCompat(mDragLayer); + SurfaceTransactionApplier surfaceApplier = new SurfaceTransactionApplier(mDragLayer); ValueAnimator unlockAnimator = ValueAnimator.ofFloat(0, 1); unlockAnimator.setDuration(CLOSING_TRANSITION_DURATION_MS); float cornerRadius = mDeviceProfile.isMultiWindowMode ? 0 : @@ -743,8 +742,7 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans */ private Animator getClosingWindowAnimators(RemoteAnimationTargetCompat[] appTargets, RemoteAnimationTargetCompat[] wallpaperTargets) { - SyncRtSurfaceTransactionApplierCompat surfaceApplier = - new SyncRtSurfaceTransactionApplierCompat(mDragLayer); + SurfaceTransactionApplier surfaceApplier = new SurfaceTransactionApplier(mDragLayer); Matrix matrix = new Matrix(); Point tmpPos = new Point(); ValueAnimator closingAnimator = ValueAnimator.ofFloat(0, 1); diff --git a/quickstep/src/com/android/launcher3/model/WellbeingModel.java b/quickstep/src/com/android/launcher3/model/WellbeingModel.java index 2181aa826a..f42b124fd6 100644 --- a/quickstep/src/com/android/launcher3/model/WellbeingModel.java +++ b/quickstep/src/com/android/launcher3/model/WellbeingModel.java @@ -77,6 +77,7 @@ public final class WellbeingModel { 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 final String EXTRA_SUCCESS = "success"; public static final MainThreadInitializedObject INSTANCE = new MainThreadInitializedObject<>(WellbeingModel::new); @@ -221,6 +222,7 @@ public final class WellbeingModel { params.putInt(EXTRA_MAX_NUM_ACTIONS_SHOWN, 1); // Perform wellbeing call . remoteActionBundle = client.call(METHOD_GET_ACTIONS, null, params); + if (!remoteActionBundle.getBoolean(EXTRA_SUCCESS, true)) return false; synchronized (mModelLock) { // Remove the entries for requested packages, and then update the fist with what we @@ -281,9 +283,9 @@ public final class WellbeingModel { // 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); + .getActivityList(null, Process.myUserHandle()).stream() + .map(li -> li.getApplicationInfo().packageName).distinct() + .toArray(String[]::new); if (!updateActions(packageNames)) { scheduleRefreshRetry(msg); } diff --git a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java index fcffaedfd8..ebe9e26c02 100644 --- a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java +++ b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java @@ -17,6 +17,7 @@ package com.android.launcher3.statehandlers; import static com.android.launcher3.anim.Interpolators.LINEAR; +import static com.android.launcher3.states.StateAnimationConfig.ANIM_DEPTH; import static com.android.launcher3.states.StateAnimationConfig.SKIP_DEPTH_CONTROLLER; import android.os.IBinder; @@ -191,11 +192,12 @@ public class DepthController implements StateHandler { float toDepth = toState.getDepth(mLauncher); if (Float.compare(mDepth, toDepth) != 0) { - animation.setFloat(this, DEPTH, toDepth, LINEAR); + animation.setFloat(this, DEPTH, toDepth, config.getInterpolator(ANIM_DEPTH, LINEAR)); } } private void setDepth(float depth) { + depth = Utilities.boundToRange(depth, 0, 1); // Round out the depth to dedupe frequent, non-perceptable updates int depthI = (int) (depth * 256); float depthF = depthI / 256f; diff --git a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java index b59f0118af..b49bc5a378 100644 --- a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java +++ b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java @@ -36,6 +36,7 @@ import android.view.Surface; import com.android.launcher3.R; import com.android.launcher3.ResourceUtils; +import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.util.DefaultDisplay; import java.io.PrintWriter; @@ -252,6 +253,10 @@ class OrientationTouchTransformer { } boolean touchInValidSwipeRegions(float x, float y) { + if (TestProtocol.sDebugTracing) { + Log.d(TestProtocol.NO_SWIPE_TO_HOME, "touchInValidSwipeRegions " + x + "," + y + " in " + + mLastRectTouched); + } if (mLastRectTouched != null) { return mLastRectTouched.contains(x, y); } @@ -293,10 +298,16 @@ class OrientationTouchTransformer { for (int i = 0; i < MAX_ORIENTATIONS; i++) { OrientationRectF rect = mSwipeTouchRegions.get(i); + if (TestProtocol.sDebugTracing) { + Log.d(TestProtocol.NO_SWIPE_TO_HOME, "transform:DOWN, rect=" + rect); + } if (rect == null) { continue; } if (rect.applyTransform(event, false)) { + if (TestProtocol.sDebugTracing) { + Log.d(TestProtocol.NO_SWIPE_TO_HOME, "setting mLastRectTouched"); + } mLastRectTouched = rect; mLastRectRotation = rect.mRotation; if (mEnableMultipleRegions && mCurrentDisplayRotation == mLastRectRotation) { diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java index c83de13618..995e9187fa 100644 --- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java +++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java @@ -546,13 +546,15 @@ public class RecentsAnimationDeviceState implements /** * @param ev An ACTION_DOWN motion event - * @return whether the given motion event can trigger the assistant. + * @param task Info for the currently running task + * @return whether the given motion event can trigger the assistant over the current task. */ - public boolean canTriggerAssistantAction(MotionEvent ev) { + public boolean canTriggerAssistantAction(MotionEvent ev, ActivityManager.RunningTaskInfo task) { return mAssistantAvailable && !QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags) && mOrientationTouchTransformer.touchInAssistantRegion(ev) - && !isLockToAppActive(); + && !isLockToAppActive() + && !isGestureBlockedActivity(task); } /** diff --git a/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java b/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java index f90df4563f..ab5e3ba0cc 100644 --- a/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java +++ b/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java @@ -16,19 +16,16 @@ package com.android.quickstep; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; -import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat; -import java.util.ArrayDeque; import java.util.ArrayList; -import java.util.Queue; +import java.util.concurrent.CopyOnWriteArrayList; /** * Holds a collection of RemoteAnimationTargets, filtered by different properties. */ public class RemoteAnimationTargets { - private final Queue mDependentTransactionAppliers = - new ArrayDeque<>(1); + private final CopyOnWriteArrayList mReleaseChecks = new CopyOnWriteArrayList<>(); public final RemoteAnimationTargetCompat[] unfilteredApps; public final RemoteAnimationTargetCompat[] apps; @@ -36,6 +33,8 @@ public class RemoteAnimationTargets { public final int targetMode; public final boolean hasRecents; + private boolean mReleased = false; + public RemoteAnimationTargets(RemoteAnimationTargetCompat[] apps, RemoteAnimationTargetCompat[] wallpapers, int targetMode) { ArrayList filteredApps = new ArrayList<>(); @@ -76,21 +75,65 @@ public class RemoteAnimationTargets { return false; } - public void addDependentTransactionApplier(SyncRtSurfaceTransactionApplierCompat delay) { - mDependentTransactionAppliers.add(delay); + public void addReleaseCheck(ReleaseCheck check) { + mReleaseChecks.add(check); } public void release() { - SyncRtSurfaceTransactionApplierCompat applier = mDependentTransactionAppliers.poll(); - if (applier == null) { - for (RemoteAnimationTargetCompat target : unfilteredApps) { - target.release(); + if (mReleased) { + return; + } + for (ReleaseCheck check : mReleaseChecks) { + if (!check.mCanRelease) { + check.addOnSafeToReleaseCallback(this::release); + return; } - for (RemoteAnimationTargetCompat target : wallpapers) { - target.release(); + } + mReleaseChecks.clear(); + mReleased = true; + + for (RemoteAnimationTargetCompat target : unfilteredApps) { + target.release(); + } + for (RemoteAnimationTargetCompat target : wallpapers) { + target.release(); + } + } + + /** + * Interface for intercepting surface release method + */ + public static class ReleaseCheck { + + boolean mCanRelease = false; + private Runnable mAfterApplyCallback; + + protected void setCanRelease(boolean canRelease) { + mCanRelease = canRelease; + if (mCanRelease && mAfterApplyCallback != null) { + Runnable r = mAfterApplyCallback; + mAfterApplyCallback = null; + r.run(); + } + } + + /** + * Adds a callback to notify when the surface can safely be released + */ + void addOnSafeToReleaseCallback(Runnable callback) { + if (mCanRelease) { + callback.run(); + } else { + if (mAfterApplyCallback == null) { + mAfterApplyCallback = callback; + } else { + final Runnable oldCallback = mAfterApplyCallback; + mAfterApplyCallback = () -> { + callback.run(); + oldCallback.run(); + }; + } } - } else { - applier.addAfterApplyCallback(this::release); } } } diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java index a3eabc966c..2ca5dc68ac 100644 --- a/quickstep/src/com/android/quickstep/SystemUiProxy.java +++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java @@ -31,6 +31,7 @@ import android.view.MotionEvent; import com.android.launcher3.util.MainThreadInitializedObject; import com.android.systemui.shared.recents.IPinnedStackAnimationListener; import com.android.systemui.shared.recents.ISystemUiProxy; +import com.android.systemui.shared.recents.model.Task; /** * Holds the reference to SystemUI. @@ -366,4 +367,8 @@ public class SystemUiProxy implements ISystemUiProxy { } } } + + public void handleImageBundleAsScreenshot(Bundle screenImageBundle, Rect locationInScreen, + Insets visibleInsets, Task.TaskKey task) { + } } diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java index 41e86e00b6..1f398fc5ea 100644 --- a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java +++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java @@ -34,14 +34,6 @@ final class BackGestureTutorialController extends TutorialController { super(fragment, tutorialType); } - @Override - void transitToController() { - super.transitToController(); - if (mTutorialType != BACK_NAVIGATION_COMPLETE) { - showHandCoachingAnimation(); - } - } - @Override Integer getTitleStringId() { switch (mTutorialType) { diff --git a/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java b/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java index 5bf50260aa..0521db4d1b 100644 --- a/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java +++ b/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java @@ -280,7 +280,11 @@ public class EdgeBackGesturePanel extends View { new SpringForce() .setStiffness(SpringForce.STIFFNESS_MEDIUM) .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)); - mPaint.setColor(context.getColor(R.color.back_arrow_color_dark)); + int currentNightMode = + context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; + mPaint.setColor(context.getColor(currentNightMode == Configuration.UI_MODE_NIGHT_YES + ? R.color.back_arrow_color_light + : R.color.back_arrow_color_dark)); loadDimens(); updateArrowDirection(); diff --git a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java index 65f41a4a6c..0edabd45c5 100644 --- a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java +++ b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java @@ -15,143 +15,23 @@ */ package com.android.quickstep.interaction; -import static com.android.launcher3.anim.Interpolators.ACCEL; -import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs; -import static com.android.quickstep.BaseSwipeUpHandlerV2.MAX_SWIPE_DURATION; import static com.android.quickstep.interaction.TutorialController.TutorialType.HOME_NAVIGATION_COMPLETE; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; import android.annotation.TargetApi; -import android.content.Context; -import android.graphics.Insets; -import android.graphics.Outline; import android.graphics.PointF; -import android.graphics.Rect; -import android.graphics.RectF; import android.os.Build; -import android.view.SurfaceControl; import android.view.View; -import android.view.ViewOutlineProvider; -import android.view.WindowInsets.Type; -import android.view.WindowManager; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.android.launcher3.DeviceProfile; -import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.R; -import com.android.launcher3.Utilities; -import com.android.launcher3.anim.AnimationSuccessListener; -import com.android.launcher3.anim.AnimatorPlaybackController; -import com.android.launcher3.anim.PendingAnimation; -import com.android.quickstep.AnimatedFloat; -import com.android.quickstep.GestureState; -import com.android.quickstep.OverviewComponentObserver; -import com.android.quickstep.RecentsAnimationDeviceState; -import com.android.quickstep.SwipeUpAnimationLogic; -import com.android.quickstep.SwipeUpAnimationLogic.RunningWindowAnim; import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureResult; import com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult; -import com.android.quickstep.util.RectFSpringAnim; -import com.android.quickstep.util.TransformParams; -import com.android.systemui.shared.system.QuickStepContract; -import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams; /** A {@link TutorialController} for the Home tutorial. */ @TargetApi(Build.VERSION_CODES.R) -final class HomeGestureTutorialController extends TutorialController { - - private float mFakeTaskViewRadius; - private Rect mFakeTaskViewRect = new Rect(); - - private final ViewSwipeUpAnimation mViewSwipeUpAnimation; - private RunningWindowAnim mRunningWindowAnim; +final class HomeGestureTutorialController extends SwipeUpGestureTutorialController { HomeGestureTutorialController(HomeGestureTutorialFragment fragment, TutorialType tutorialType) { super(fragment, tutorialType); - - RecentsAnimationDeviceState deviceState = new RecentsAnimationDeviceState(mContext); - OverviewComponentObserver observer = new OverviewComponentObserver(mContext, deviceState); - mViewSwipeUpAnimation = new ViewSwipeUpAnimation(mContext, deviceState, - new GestureState(observer, -1)); - observer.onDestroy(); - deviceState.destroy(); - - DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(mContext) - .getDeviceProfile(mContext) - .copy(mContext); - Insets insets = mContext.getSystemService(WindowManager.class) - .getCurrentWindowMetrics() - .getWindowInsets() - .getInsets(Type.systemBars()); - dp.updateInsets(new Rect(insets.left, insets.top, insets.right, insets.bottom)); - mViewSwipeUpAnimation.initDp(dp); - - mFakeTaskViewRadius = QuickStepContract.getWindowCornerRadius(mContext.getResources()); - - mFakeTaskView.setClipToOutline(true); - mFakeTaskView.setOutlineProvider(new ViewOutlineProvider() { - @Override - public void getOutline(View view, Outline outline) { - outline.setRoundRect(mFakeTaskViewRect, mFakeTaskViewRadius); - } - }); - } - - private void cancelRunningAnimation() { - if (mRunningWindowAnim != null) { - mRunningWindowAnim.cancel(); - } - mRunningWindowAnim = null; - } - - /** Fades the task view, optionally after animating to a fake Overview. */ - private void fadeOutFakeTaskView(boolean toOverviewFirst, @Nullable Runnable onEndRunnable) { - cancelRunningAnimation(); - PendingAnimation anim = new PendingAnimation(300); - AnimatorListenerAdapter resetTaskView = new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation, boolean isReverse) { - mFakeTaskView.setVisibility(View.INVISIBLE); - mFakeTaskView.setAlpha(1); - mRunningWindowAnim = null; - } - }; - if (toOverviewFirst) { - anim.setFloat(mViewSwipeUpAnimation.getCurrentShift(), AnimatedFloat.VALUE, 1, ACCEL); - anim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation, boolean isReverse) { - PendingAnimation fadeAnim = new PendingAnimation(300); - fadeAnim.setViewAlpha(mFakeTaskView, 0, ACCEL); - fadeAnim.addListener(resetTaskView); - AnimatorSet animset = fadeAnim.buildAnim(); - animset.setStartDelay(100); - animset.start(); - mRunningWindowAnim = RunningWindowAnim.wrap(animset); - } - }); - } else { - anim.setViewAlpha(mFakeTaskView, 0, ACCEL); - anim.addListener(resetTaskView); - } - if (onEndRunnable != null) { - anim.addListener(AnimationSuccessListener.forRunnable(onEndRunnable)); - } - AnimatorSet animset = anim.buildAnim(); - animset.start(); - mRunningWindowAnim = RunningWindowAnim.wrap(animset); - } - - @Override - void transitToController() { - super.transitToController(); - if (mTutorialType != HOME_NAVIGATION_COMPLETE) { - showHandCoachingAnimation(); - } } @Override @@ -190,6 +70,14 @@ final class HomeGestureTutorialController extends TutorialController { public void onBackGestureAttempted(BackGestureResult result) { switch (mTutorialType) { case HOME_NAVIGATION: + switch (result) { + case BACK_COMPLETED_FROM_LEFT: + case BACK_COMPLETED_FROM_RIGHT: + case BACK_CANCELLED_FROM_LEFT: + case BACK_CANCELLED_FROM_RIGHT: + showFeedback(R.string.home_gesture_feedback_swipe_too_far_from_edge); + break; + } break; case HOME_NAVIGATION_COMPLETE: if (result == BackGestureResult.BACK_COMPLETED_FROM_LEFT @@ -206,17 +94,8 @@ final class HomeGestureTutorialController extends TutorialController { case HOME_NAVIGATION: switch (result) { case HOME_GESTURE_COMPLETED: { - hideFeedback(); - cancelRunningAnimation(); - hideHandCoachingAnimation(); - RectFSpringAnim rectAnim = - mViewSwipeUpAnimation.handleSwipeUpToHome(finalVelocity); - // After home animation finishes, fade out and then move to the next screen. - rectAnim.addAnimatorListener(AnimationSuccessListener.forRunnable( - () -> fadeOutFakeTaskView(false, - () -> mTutorialFragment.changeController( - HOME_NAVIGATION_COMPLETE)))); - mRunningWindowAnim = RunningWindowAnim.wrap(rectAnim); + animateFakeTaskViewHome(finalVelocity, () -> + mTutorialFragment.changeController(HOME_NAVIGATION_COMPLETE)); break; } case HOME_NOT_STARTED_TOO_FAR_FROM_EDGE: @@ -242,93 +121,4 @@ final class HomeGestureTutorialController extends TutorialController { } } - @Override - public void setNavBarGestureProgress(@Nullable Float displacement) { - if (displacement == null || mTutorialType == HOME_NAVIGATION_COMPLETE) { - mFakeTaskView.setVisibility(View.INVISIBLE); - } else { - mFakeTaskView.setVisibility(View.VISIBLE); - if (mRunningWindowAnim == null) { - mViewSwipeUpAnimation.updateDisplacement(displacement); - } - } - } - - private class ViewSwipeUpAnimation extends SwipeUpAnimationLogic { - - ViewSwipeUpAnimation(Context context, RecentsAnimationDeviceState deviceState, - GestureState gestureState) { - super(context, deviceState, gestureState, new FakeTransformParams()); - } - - void initDp(DeviceProfile dp) { - initTransitionEndpoints(dp); - mTaskViewSimulator.setPreviewBounds( - new Rect(0, 0, dp.widthPx, dp.heightPx), dp.getInsets()); - } - - @Override - public void updateFinalShift() { - float progress = mCurrentShift.value / mDragLengthFactor; - mWindowTransitionController.setPlayFraction(progress); - mTaskViewSimulator.apply(mTransformParams); - } - - AnimatedFloat getCurrentShift() { - return mCurrentShift; - } - - RectFSpringAnim handleSwipeUpToHome(PointF velocity) { - PointF velocityPxPerMs = new PointF(velocity.x, velocity.y); - float currentShift = mCurrentShift.value; - final float startShift = Utilities.boundToRange(currentShift - velocityPxPerMs.y - * getSingleFrameMs(mContext) / mTransitionDragLength, 0, mDragLengthFactor); - float distanceToTravel = (1 - currentShift) * mTransitionDragLength; - - // we want the page's snap velocity to approximately match the velocity at - // which the user flings, so we scale the duration by a value near to the - // derivative of the scroll interpolator at zero, ie. 2. - long baseDuration = Math.round(Math.abs(distanceToTravel / velocityPxPerMs.y)); - long duration = Math.min(MAX_SWIPE_DURATION, 2 * baseDuration); - HomeAnimationFactory homeAnimFactory = new HomeAnimationFactory(null) { - @Override - public AnimatorPlaybackController createActivityAnimationToHome() { - return AnimatorPlaybackController.wrap(new AnimatorSet(), duration); - } - - @NonNull - @Override - public RectF getWindowTargetRect() { - int fakeHomeIconSizePx = mDp.allAppsIconSizePx; - int fakeHomeIconLeft = (mDp.widthPx - fakeHomeIconSizePx) / 2; - int fakeHomeIconTop = mDp.heightPx - (mDp.allAppsCellHeightPx * 3); - return new RectF(fakeHomeIconLeft, fakeHomeIconTop, - fakeHomeIconLeft + fakeHomeIconSizePx, - fakeHomeIconTop + fakeHomeIconSizePx); - } - }; - RectFSpringAnim windowAnim = createWindowAnimationToHome(startShift, homeAnimFactory); - windowAnim.start(mContext, velocityPxPerMs); - return windowAnim; - } - } - - private class FakeTransformParams extends TransformParams { - - @Override - public SurfaceParams[] createSurfaceParams(BuilderProxy proxy) { - SurfaceParams.Builder builder = new SurfaceParams.Builder((SurfaceControl) null); - proxy.onBuildTargetParams(builder, null, this); - return new SurfaceParams[] {builder.build()}; - } - - @Override - public void applySurfaceParams(SurfaceParams[] params) { - SurfaceParams p = params[0]; - mFakeTaskView.setAnimationMatrix(p.matrix); - mFakeTaskViewRect.set(p.windowCrop); - mFakeTaskViewRadius = p.cornerRadius; - mFakeTaskView.invalidateOutline(); - } - } } diff --git a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java new file mode 100644 index 0000000000..c636ebaea9 --- /dev/null +++ b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.quickstep.interaction; + +import static com.android.quickstep.interaction.TutorialController.TutorialType.OVERVIEW_NAVIGATION_COMPLETE; + +import android.annotation.TargetApi; +import android.graphics.PointF; +import android.os.Build; +import android.view.View; + +import com.android.launcher3.R; +import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureResult; +import com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult; + +/** A {@link TutorialController} for the Overview tutorial. */ +@TargetApi(Build.VERSION_CODES.R) +final class OverviewGestureTutorialController extends SwipeUpGestureTutorialController { + + OverviewGestureTutorialController(OverviewGestureTutorialFragment fragment, + TutorialType tutorialType) { + super(fragment, tutorialType); + } + + @Override + Integer getTitleStringId() { + switch (mTutorialType) { + case OVERVIEW_NAVIGATION: + return R.string.overview_gesture_tutorial_playground_title; + case OVERVIEW_NAVIGATION_COMPLETE: + return R.string.gesture_tutorial_confirm_title; + } + return null; + } + + @Override + Integer getSubtitleStringId() { + if (mTutorialType == TutorialType.OVERVIEW_NAVIGATION) { + return R.string.overview_gesture_tutorial_playground_subtitle; + } + return null; + } + + @Override + Integer getActionButtonStringId() { + if (mTutorialType == OVERVIEW_NAVIGATION_COMPLETE) { + return R.string.gesture_tutorial_action_button_label_done; + } + return null; + } + + @Override + void onActionButtonClicked(View button) { + mTutorialFragment.closeTutorial(); + } + + @Override + public void onBackGestureAttempted(BackGestureResult result) { + switch (mTutorialType) { + case OVERVIEW_NAVIGATION: + switch (result) { + case BACK_COMPLETED_FROM_LEFT: + case BACK_COMPLETED_FROM_RIGHT: + case BACK_CANCELLED_FROM_LEFT: + case BACK_CANCELLED_FROM_RIGHT: + showFeedback(R.string.overview_gesture_feedback_swipe_too_far_from_edge); + break; + } + break; + case OVERVIEW_NAVIGATION_COMPLETE: + if (result == BackGestureResult.BACK_COMPLETED_FROM_LEFT + || result == BackGestureResult.BACK_COMPLETED_FROM_RIGHT) { + mTutorialFragment.closeTutorial(); + } + break; + } + } + + @Override + public void onNavBarGestureAttempted(NavBarGestureResult result, PointF finalVelocity) { + switch (mTutorialType) { + case OVERVIEW_NAVIGATION: + switch (result) { + case HOME_GESTURE_COMPLETED: { + animateFakeTaskViewHome(finalVelocity, () -> + showFeedback(R.string.overview_gesture_feedback_home_detected)); + break; + } + case HOME_NOT_STARTED_TOO_FAR_FROM_EDGE: + case OVERVIEW_NOT_STARTED_TOO_FAR_FROM_EDGE: + showFeedback(R.string.overview_gesture_feedback_swipe_too_far_from_edge); + break; + case OVERVIEW_GESTURE_COMPLETED: + fadeOutFakeTaskView(true, () -> + mTutorialFragment.changeController(OVERVIEW_NAVIGATION_COMPLETE)); + break; + case HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION: + case HOME_OR_OVERVIEW_CANCELLED: + fadeOutFakeTaskView(false, null); + showFeedback(R.string.overview_gesture_feedback_wrong_swipe_direction); + break; + } + break; + case OVERVIEW_NAVIGATION_COMPLETE: + if (result == NavBarGestureResult.HOME_GESTURE_COMPLETED) { + mTutorialFragment.closeTutorial(); + } + break; + } + } +} diff --git a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialFragment.java new file mode 100644 index 0000000000..3357b70497 --- /dev/null +++ b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialFragment.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.quickstep.interaction; + +import com.android.launcher3.R; +import com.android.quickstep.interaction.TutorialController.TutorialType; + +/** Shows the Overview gesture interactive tutorial. */ +public class OverviewGestureTutorialFragment extends TutorialFragment { + @Override + int getHandAnimationResId() { + return R.drawable.overview_gesture; + } + + @Override + TutorialController createController(TutorialType type) { + return new OverviewGestureTutorialController(this, type); + } + + @Override + Class getControllerClass() { + return OverviewGestureTutorialController.class; + } +} diff --git a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java new file mode 100644 index 0000000000..14e00dce7a --- /dev/null +++ b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.quickstep.interaction; + +import static com.android.launcher3.anim.Interpolators.ACCEL; +import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs; +import static com.android.quickstep.BaseSwipeUpHandlerV2.MAX_SWIPE_DURATION; +import static com.android.quickstep.interaction.TutorialController.TutorialType.HOME_NAVIGATION_COMPLETE; +import static com.android.quickstep.interaction.TutorialController.TutorialType.OVERVIEW_NAVIGATION_COMPLETE; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.annotation.TargetApi; +import android.content.Context; +import android.graphics.Insets; +import android.graphics.Outline; +import android.graphics.PointF; +import android.graphics.Rect; +import android.graphics.RectF; +import android.os.Build; +import android.view.SurfaceControl; +import android.view.View; +import android.view.ViewOutlineProvider; +import android.view.WindowInsets; +import android.view.WindowManager; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.launcher3.DeviceProfile; +import com.android.launcher3.InvariantDeviceProfile; +import com.android.launcher3.Utilities; +import com.android.launcher3.anim.AnimationSuccessListener; +import com.android.launcher3.anim.AnimatorPlaybackController; +import com.android.launcher3.anim.PendingAnimation; +import com.android.quickstep.AnimatedFloat; +import com.android.quickstep.GestureState; +import com.android.quickstep.OverviewComponentObserver; +import com.android.quickstep.RecentsAnimationDeviceState; +import com.android.quickstep.SwipeUpAnimationLogic; +import com.android.quickstep.SwipeUpAnimationLogic.RunningWindowAnim; +import com.android.quickstep.util.RectFSpringAnim; +import com.android.quickstep.util.TransformParams; +import com.android.systemui.shared.system.QuickStepContract; +import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams; + +@TargetApi(Build.VERSION_CODES.R) +abstract class SwipeUpGestureTutorialController extends TutorialController { + private final ViewSwipeUpAnimation mViewSwipeUpAnimation; + private float mFakeTaskViewRadius; + private Rect mFakeTaskViewRect = new Rect(); + private RunningWindowAnim mRunningWindowAnim; + + SwipeUpGestureTutorialController(TutorialFragment tutorialFragment, TutorialType tutorialType) { + super(tutorialFragment, tutorialType); + RecentsAnimationDeviceState deviceState = new RecentsAnimationDeviceState(mContext); + OverviewComponentObserver observer = new OverviewComponentObserver(mContext, deviceState); + mViewSwipeUpAnimation = new ViewSwipeUpAnimation(mContext, deviceState, + new GestureState(observer, -1)); + observer.onDestroy(); + deviceState.destroy(); + + DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(mContext) + .getDeviceProfile(mContext) + .copy(mContext); + Insets insets = mContext.getSystemService(WindowManager.class) + .getCurrentWindowMetrics() + .getWindowInsets() + .getInsets(WindowInsets.Type.systemBars()); + dp.updateInsets(new Rect(insets.left, insets.top, insets.right, insets.bottom)); + mViewSwipeUpAnimation.initDp(dp); + + mFakeTaskViewRadius = QuickStepContract.getWindowCornerRadius(mContext.getResources()); + mFakeTaskView.setClipToOutline(true); + mFakeTaskView.setOutlineProvider(new ViewOutlineProvider() { + @Override + public void getOutline(View view, Outline outline) { + outline.setRoundRect(mFakeTaskViewRect, mFakeTaskViewRadius); + } + }); + } + + private void cancelRunningAnimation() { + if (mRunningWindowAnim != null) { + mRunningWindowAnim.cancel(); + } + mRunningWindowAnim = null; + } + + /** Fades the task view, optionally after animating to a fake Overview. */ + void fadeOutFakeTaskView(boolean toOverviewFirst, @Nullable Runnable onEndRunnable) { + hideFeedback(); + hideHandCoachingAnimation(); + cancelRunningAnimation(); + PendingAnimation anim = new PendingAnimation(300); + AnimatorListenerAdapter resetTaskView = new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation, boolean isReverse) { + mFakeTaskView.setVisibility(View.INVISIBLE); + mFakeTaskView.setAlpha(1); + mRunningWindowAnim = null; + } + }; + if (toOverviewFirst) { + anim.setFloat(mViewSwipeUpAnimation.getCurrentShift(), AnimatedFloat.VALUE, 1, ACCEL); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation, boolean isReverse) { + PendingAnimation fadeAnim = new PendingAnimation(300); + fadeAnim.setViewAlpha(mFakeTaskView, 0, ACCEL); + fadeAnim.addListener(resetTaskView); + AnimatorSet animset = fadeAnim.buildAnim(); + animset.setStartDelay(100); + animset.start(); + mRunningWindowAnim = RunningWindowAnim.wrap(animset); + } + }); + } else { + anim.setViewAlpha(mFakeTaskView, 0, ACCEL); + anim.addListener(resetTaskView); + } + if (onEndRunnable != null) { + anim.addListener(AnimationSuccessListener.forRunnable(onEndRunnable)); + } + AnimatorSet animset = anim.buildAnim(); + animset.start(); + mRunningWindowAnim = RunningWindowAnim.wrap(animset); + } + + void animateFakeTaskViewHome(PointF finalVelocity, @Nullable Runnable onEndRunnable) { + hideFeedback(); + hideHandCoachingAnimation(); + cancelRunningAnimation(); + RectFSpringAnim rectAnim = + mViewSwipeUpAnimation.handleSwipeUpToHome(finalVelocity); + // After home animation finishes, fade out and run onEndRunnable. + rectAnim.addAnimatorListener(AnimationSuccessListener.forRunnable( + () -> fadeOutFakeTaskView(false, onEndRunnable))); + mRunningWindowAnim = RunningWindowAnim.wrap(rectAnim); + } + + @Override + public void setNavBarGestureProgress(@Nullable Float displacement) { + if (displacement == null || mTutorialType == HOME_NAVIGATION_COMPLETE + || mTutorialType == OVERVIEW_NAVIGATION_COMPLETE) { + mFakeTaskView.setVisibility(View.INVISIBLE); + } else { + mFakeTaskView.setVisibility(View.VISIBLE); + if (mRunningWindowAnim == null) { + mViewSwipeUpAnimation.updateDisplacement(displacement); + } + } + } + + class ViewSwipeUpAnimation extends SwipeUpAnimationLogic { + + ViewSwipeUpAnimation(Context context, RecentsAnimationDeviceState deviceState, + GestureState gestureState) { + super(context, deviceState, gestureState, new FakeTransformParams()); + } + + void initDp(DeviceProfile dp) { + initTransitionEndpoints(dp); + mTaskViewSimulator.setPreviewBounds( + new Rect(0, 0, dp.widthPx, dp.heightPx), dp.getInsets()); + } + + @Override + public void updateFinalShift() { + float progress = mCurrentShift.value / mDragLengthFactor; + mWindowTransitionController.setPlayFraction(progress); + mTaskViewSimulator.apply(mTransformParams); + } + + AnimatedFloat getCurrentShift() { + return mCurrentShift; + } + + RectFSpringAnim handleSwipeUpToHome(PointF velocity) { + PointF velocityPxPerMs = new PointF(velocity.x, velocity.y); + float currentShift = mCurrentShift.value; + final float startShift = Utilities.boundToRange(currentShift - velocityPxPerMs.y + * getSingleFrameMs(mContext) / mTransitionDragLength, 0, mDragLengthFactor); + float distanceToTravel = (1 - currentShift) * mTransitionDragLength; + + // we want the page's snap velocity to approximately match the velocity at + // which the user flings, so we scale the duration by a value near to the + // derivative of the scroll interpolator at zero, ie. 2. + long baseDuration = Math.round(Math.abs(distanceToTravel / velocityPxPerMs.y)); + long duration = Math.min(MAX_SWIPE_DURATION, 2 * baseDuration); + HomeAnimationFactory homeAnimFactory = new HomeAnimationFactory(null) { + @Override + public AnimatorPlaybackController createActivityAnimationToHome() { + return AnimatorPlaybackController.wrap(new AnimatorSet(), duration); + } + + @NonNull + @Override + public RectF getWindowTargetRect() { + int fakeHomeIconSizePx = mDp.allAppsIconSizePx; + int fakeHomeIconLeft = (mDp.widthPx - fakeHomeIconSizePx) / 2; + int fakeHomeIconTop = mDp.heightPx - (mDp.allAppsCellHeightPx * 3); + return new RectF(fakeHomeIconLeft, fakeHomeIconTop, + fakeHomeIconLeft + fakeHomeIconSizePx, + fakeHomeIconTop + fakeHomeIconSizePx); + } + }; + RectFSpringAnim windowAnim = createWindowAnimationToHome(startShift, homeAnimFactory); + windowAnim.start(mContext, velocityPxPerMs); + return windowAnim; + } + } + + private class FakeTransformParams extends TransformParams { + + @Override + public SurfaceParams[] createSurfaceParams(BuilderProxy proxy) { + SurfaceParams.Builder builder = new SurfaceParams.Builder((SurfaceControl) null); + proxy.onBuildTargetParams(builder, null, this); + return new SurfaceParams[] {builder.build()}; + } + + @Override + public void applySurfaceParams(SurfaceParams[] params) { + SurfaceParams p = params[0]; + mFakeTaskView.setAnimationMatrix(p.matrix); + mFakeTaskViewRect.set(p.windowCrop); + mFakeTaskViewRadius = p.cornerRadius; + mFakeTaskView.invalidateOutline(); + } + } +} diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialController.java b/quickstep/src/com/android/quickstep/interaction/TutorialController.java index f27d500eff..511c8b6dc6 100644 --- a/quickstep/src/com/android/quickstep/interaction/TutorialController.java +++ b/quickstep/src/com/android/quickstep/interaction/TutorialController.java @@ -140,6 +140,9 @@ abstract class TutorialController implements BackGestureAttemptCallback, void onActionTextButtonClicked(View button) {} void showHandCoachingAnimation() { + if (isComplete()) { + return; + } mHandCoachingAnimation.startLoopedAnimation(mTutorialType); } @@ -153,6 +156,12 @@ abstract class TutorialController implements BackGestureAttemptCallback, hideFeedback(); updateTitles(); updateActionButtons(); + + if (isComplete()) { + hideHandCoachingAnimation(); + } else { + showHandCoachingAnimation(); + } } private void updateTitles() { @@ -190,12 +199,20 @@ abstract class TutorialController implements BackGestureAttemptCallback, button.setOnClickListener(listener); } + private boolean isComplete() { + return mTutorialType == TutorialType.BACK_NAVIGATION_COMPLETE + || mTutorialType == TutorialType.HOME_NAVIGATION_COMPLETE + || mTutorialType == TutorialType.OVERVIEW_NAVIGATION_COMPLETE; + } + /** Denotes the type of the tutorial. */ enum TutorialType { RIGHT_EDGE_BACK_NAVIGATION, LEFT_EDGE_BACK_NAVIGATION, BACK_NAVIGATION_COMPLETE, HOME_NAVIGATION, - HOME_NAVIGATION_COMPLETE + HOME_NAVIGATION_COMPLETE, + OVERVIEW_NAVIGATION, + OVERVIEW_NAVIGATION_COMPLETE } } diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java index a3881cffb0..da6815d776 100644 --- a/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java +++ b/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java @@ -68,6 +68,9 @@ abstract class TutorialFragment extends Fragment implements OnTouchListener { case HOME_NAVIGATION: case HOME_NAVIGATION_COMPLETE: return new HomeGestureTutorialFragment(); + case OVERVIEW_NAVIGATION: + case OVERVIEW_NAVIGATION_COMPLETE: + return new OverviewGestureTutorialFragment(); default: Log.e(LOG_TAG, "Failed to find an appropriate fragment for " + tutorialType.name()); } diff --git a/res/drawable/gesture_tutorial_ripple.xml b/res/drawable/gesture_tutorial_ripple.xml index ca456627c5..782af33ab3 100644 --- a/res/drawable/gesture_tutorial_ripple.xml +++ b/res/drawable/gesture_tutorial_ripple.xml @@ -1,6 +1,3 @@ - - \ No newline at end of file + xmlns:android="http://schemas.android.com/apk/res/android"/> \ No newline at end of file diff --git a/res/values/colors.xml b/res/values/colors.xml index c4ec7dd7f0..043ad9a9d5 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -38,14 +38,8 @@ #E5E5E5 #9AA0A6 - #FFFFFFFF - #FF000000 - #99000000 - #FF000000 #A0C2F9 #6DA1FF #FFFFFFFF #1A73E8 - - diff --git a/res/values/styles.xml b/res/values/styles.xml index 26b72052fd..e470c42459 100644 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -81,7 +81,6 @@ #CDFFFFFF #FF80868B ?attr/workspaceTextColor - + + + + - diff --git a/res/drawable/drag_handle_indicator_no_shadow.xml b/res/drawable/drag_handle_indicator_no_shadow.xml deleted file mode 100644 index 341e60cda9..0000000000 --- a/res/drawable/drag_handle_indicator_no_shadow.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - diff --git a/res/layout/launcher.xml b/res/layout/launcher.xml index a13790826b..0c18c8a2ba 100644 --- a/res/layout/launcher.xml +++ b/res/layout/launcher.xml @@ -28,6 +28,12 @@ android:clipToPadding="false" android:importantForAccessibility="no"> + + 1dp 0dp - - 4dp - 18dp - 6dp - 1dp - 48dp - 16dp - 48dp 20dp diff --git a/res/values/drawables.xml b/res/values/drawables.xml index 7d631426cb..9c57ec1214 100644 --- a/res/values/drawables.xml +++ b/res/values/drawables.xml @@ -18,5 +18,4 @@ @drawable/ic_remove_no_shadow @drawable/ic_uninstall_no_shadow @drawable/ic_block_no_shadow - @drawable/drag_handle_indicator_no_shadow \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index 80b511a54c..12beeeb566 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -36,7 +36,7 @@ Shortcut isn\'t available - Home screen + Home Custom actions @@ -88,10 +88,6 @@ Personal apps list Work apps list - - Home - Remove diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index e49c4559f0..f9fba6f80f 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -453,10 +453,10 @@ public class Launcher extends StatefulActivity implements Launche mAppsView.getAlphaProperty(APPS_VIEW_ALPHA_CHANNEL_INDEX).setValue(alpha); } else if (finalState == OVERVIEW || finalState == OVERVIEW_PEEK) { mAppsView.getAlphaProperty(APPS_VIEW_ALPHA_CHANNEL_INDEX).setValue(alpha); - mScrimView.getAlphaProperty(SCRIM_VIEW_ALPHA_CHANNEL_INDEX).setValue(alpha); + mScrimView.setAlpha(alpha); } else { mAppsView.getAlphaProperty(APPS_VIEW_ALPHA_CHANNEL_INDEX).setValue(1f); - mScrimView.getAlphaProperty(SCRIM_VIEW_ALPHA_CHANNEL_INDEX).setValue(1f); + mScrimView.setAlpha(1f); } } }); @@ -553,7 +553,7 @@ public class Launcher extends StatefulActivity implements Launche mAppsView.getAlphaProperty(APPS_VIEW_ALPHA_CHANNEL_INDEX).setValue(alpha); } else if (state == OVERVIEW || state == OVERVIEW_PEEK) { mAppsView.getAlphaProperty(APPS_VIEW_ALPHA_CHANNEL_INDEX).setValue(alpha); - mScrimView.getAlphaProperty(SCRIM_VIEW_ALPHA_CHANNEL_INDEX).setValue(alpha); + mScrimView.setAlpha(alpha); } } @@ -1936,7 +1936,7 @@ public class Launcher extends StatefulActivity implements Launche // Populate event with a fake title based on the current state. // TODO: When can workspace be null? text.add(mWorkspace == null - ? getString(R.string.all_apps_home_button_label) + ? getString(R.string.home_screen) : mStateManager.getState().getDescription(this)); return result; } diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 15e0daa8ea..3be9ac7e07 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -3263,7 +3263,7 @@ public class Workspace extends PagedView } if (nScreens == 0) { // When the workspace is not loaded, we do not know how many screen will be bound. - return getContext().getString(R.string.all_apps_home_button_label); + return getContext().getString(R.string.home_screen); } return getContext().getString(R.string.workspace_scroll_format, page + 1, nScreens); } diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java index 5b00631659..1224cb0080 100644 --- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java +++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java @@ -19,7 +19,6 @@ 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.APPS_VIEW_ITEM_MASK; import static com.android.launcher3.LauncherState.OVERVIEW; -import static com.android.launcher3.LauncherState.VERTICAL_SWIPE_INDICATOR; import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN; import static com.android.launcher3.anim.Interpolators.FINAL_FRAME; import static com.android.launcher3.anim.Interpolators.INSTANT; @@ -222,9 +221,6 @@ public class AllAppsTransitionController implements StateHandler, mAppsView.getSearchUiManager().setContentVisibility(visibleElements, setter, allAppsFade); - setter.setInt(mScrimView, ScrimView.DRAG_HANDLE_ALPHA, - (visibleElements & VERTICAL_SWIPE_INDICATOR) != 0 ? 255 : 0, allAppsFade); - // Set visibility of the container at the very beginning or end of the transition. setter.setViewAlpha(mAppsView, hasAnyVisibleItem ? 1 : 0, hasAnyVisibleItem ? INSTANT : FINAL_FRAME); diff --git a/src/com/android/launcher3/util/OnboardingPrefs.java b/src/com/android/launcher3/util/OnboardingPrefs.java index 26313e5a21..6e5e7d9355 100644 --- a/src/com/android/launcher3/util/OnboardingPrefs.java +++ b/src/com/android/launcher3/util/OnboardingPrefs.java @@ -36,7 +36,6 @@ public class OnboardingPrefs { public static final String SHELF_BOUNCE_SEEN = "launcher.shelf_bounce_seen"; public static final String HOME_BOUNCE_COUNT = "launcher.home_bounce_count"; public static final String SHELF_BOUNCE_COUNT = "launcher.shelf_bounce_count"; - public static final String ALL_APPS_COUNT = "launcher.all_apps_count"; public static final String HOTSEAT_DISCOVERY_TIP_COUNT = "launcher.hotseat_discovery_tip_count"; public static final String HOTSEAT_LONGPRESS_TIP_SEEN = "launcher.hotseat_longpress_tip_seen"; @@ -56,7 +55,6 @@ public class OnboardingPrefs { @StringDef(value = { HOME_BOUNCE_COUNT, SHELF_BOUNCE_COUNT, - ALL_APPS_COUNT, HOTSEAT_DISCOVERY_TIP_COUNT }) @Retention(RetentionPolicy.SOURCE) @@ -67,7 +65,6 @@ public class OnboardingPrefs { Map maxCounts = new ArrayMap<>(4); maxCounts.put(HOME_BOUNCE_COUNT, 3); maxCounts.put(SHELF_BOUNCE_COUNT, 3); - maxCounts.put(ALL_APPS_COUNT, 5); maxCounts.put(HOTSEAT_DISCOVERY_TIP_COUNT, 5); MAX_COUNTS = Collections.unmodifiableMap(maxCounts); } diff --git a/src/com/android/launcher3/views/AccessibilityActionsView.java b/src/com/android/launcher3/views/AccessibilityActionsView.java new file mode 100644 index 0000000000..0eacaa3009 --- /dev/null +++ b/src/com/android/launcher3/views/AccessibilityActionsView.java @@ -0,0 +1,97 @@ +/* + * 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.views; + +import static com.android.launcher3.LauncherState.ALL_APPS; +import static com.android.launcher3.LauncherState.NORMAL; + +import android.content.Context; +import android.os.Bundle; +import android.util.AttributeSet; +import android.view.View; +import android.view.accessibility.AccessibilityNodeInfo; +import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; + +import androidx.annotation.Nullable; + +import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherState; +import com.android.launcher3.R; +import com.android.launcher3.statemanager.StateManager.StateListener; +import com.android.launcher3.views.OptionsPopupView.OptionItem; + +/** + * Placeholder view to expose additional Launcher actions via accessibility actions + */ +public class AccessibilityActionsView extends View implements StateListener { + + public AccessibilityActionsView(Context context) { + this(context, null); + } + + public AccessibilityActionsView(Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public AccessibilityActionsView(Context context, @Nullable AttributeSet attrs, + int defStyleAttr) { + super(context, attrs, defStyleAttr); + Launcher.getLauncher(context).getStateManager().addStateListener(this); + setWillNotDraw(true); + } + + @Override + public void onStateTransitionComplete(LauncherState finalState) { + setImportantForAccessibility(finalState == NORMAL + ? IMPORTANT_FOR_ACCESSIBILITY_YES : IMPORTANT_FOR_ACCESSIBILITY_NO); + } + + @Override + public AccessibilityNodeInfo createAccessibilityNodeInfo() { + AccessibilityNodeInfo info = super.createAccessibilityNodeInfo(); + Launcher l = Launcher.getLauncher(getContext()); + info.addAction(new AccessibilityAction( + R.string.all_apps_button_label, l.getText(R.string.all_apps_button_label))); + for (OptionItem item : OptionsPopupView.getOptions(l)) { + info.addAction(new AccessibilityAction(item.labelRes, l.getText(item.labelRes))); + } + return info; + } + + @Override + public boolean performAccessibilityAction(int action, Bundle arguments) { + if (super.performAccessibilityAction(action, arguments)) { + return true; + } + Launcher l = Launcher.getLauncher(getContext()); + if (action == R.string.all_apps_button_label) { + l.getStateManager().goToState(ALL_APPS); + return true; + } + for (OptionItem item : OptionsPopupView.getOptions(l)) { + if (item.labelRes == action) { + if (item.eventId.getId() > 0) { + l.getStatsLogManager().logger().log(item.eventId); + } + if (item.clickListener.onLongClick(this)) { + return true; + } + } + } + return false; + } +} diff --git a/src/com/android/launcher3/views/OptionsPopupView.java b/src/com/android/launcher3/views/OptionsPopupView.java index 049a1acbf3..e95dc5b83f 100644 --- a/src/com/android/launcher3/views/OptionsPopupView.java +++ b/src/com/android/launcher3/views/OptionsPopupView.java @@ -85,10 +85,10 @@ public class OptionsPopupView extends ArrowPopup if (item == null) { return false; } - if (item.mEventId.getId() > 0) { - mLauncher.getStatsLogManager().logger().log(item.mEventId); + if (item.eventId.getId() > 0) { + mLauncher.getStatsLogManager().logger().log(item.eventId); } - if (item.mClickListener.onLongClick(view)) { + if (item.clickListener.onLongClick(view)) { close(true); return true; } @@ -130,8 +130,8 @@ public class OptionsPopupView extends ArrowPopup for (OptionItem item : items) { DeepShortcutView view = (DeepShortcutView) popup.inflateAndAdd(R.layout.system_shortcut, popup); - view.getIconView().setBackgroundResource(item.mIconRes); - view.getBubbleText().setText(item.mLabelRes); + view.getIconView().setBackgroundResource(item.iconRes); + view.getBubbleText().setText(item.labelRes); view.setDividerVisibility(View.INVISIBLE); view.setOnClickListener(popup); view.setOnLongClickListener(popup); @@ -152,7 +152,13 @@ public class OptionsPopupView extends ArrowPopup y = launcher.getDragLayer().getHeight() / 2; } RectF target = new RectF(x - halfSize, y - halfSize, x + halfSize, y + halfSize); + show(launcher, target, getOptions(launcher)); + } + /** + * Returns the list of supported actions + */ + public static ArrayList getOptions(Launcher launcher) { ArrayList options = new ArrayList<>(); int resString = Utilities.existsStyleWallpapers(launcher) ? R.string.styles_wallpaper_button_text : R.string.wallpaper_button_text; @@ -170,10 +176,10 @@ public class OptionsPopupView extends ArrowPopup LAUNCHER_SETTINGS_BUTTON_TAP_OR_LONGPRESS, OptionsPopupView::startSettings)); - show(launcher, target, options); + return options; } - public static boolean onWidgetsClicked(View view) { + private static boolean onWidgetsClicked(View view) { return openWidgets(Launcher.getLauncher(view.getContext())) != null; } @@ -188,7 +194,7 @@ public class OptionsPopupView extends ArrowPopup } } - public static boolean startSettings(View view) { + private static boolean startSettings(View view) { TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "start: startSettings"); Launcher launcher = Launcher.getLauncher(view.getContext()); launcher.startActivity(new Intent(Intent.ACTION_APPLICATION_PREFERENCES) @@ -201,7 +207,7 @@ public class OptionsPopupView extends ArrowPopup * Event handler for the wallpaper picker button that appears after a long press * on the home screen. */ - public static boolean startWallpaperPicker(View v) { + private static boolean startWallpaperPicker(View v) { Launcher launcher = Launcher.getLauncher(v.getContext()); if (!Utilities.isWallpaperAllowed(launcher)) { Toast.makeText(launcher, R.string.msg_disabled_by_admin, Toast.LENGTH_SHORT).show(); @@ -233,17 +239,17 @@ public class OptionsPopupView extends ArrowPopup public static class OptionItem { - private final int mLabelRes; - private final int mIconRes; - private final EventEnum mEventId; - private final OnLongClickListener mClickListener; + public final int labelRes; + public final int iconRes; + public final EventEnum eventId; + public final OnLongClickListener clickListener; public OptionItem(int labelRes, int iconRes, EventEnum eventId, OnLongClickListener clickListener) { - mLabelRes = labelRes; - mIconRes = iconRes; - mEventId = eventId; - mClickListener = clickListener; + this.labelRes = labelRes; + this.iconRes = iconRes; + this.eventId = eventId; + this.clickListener = clickListener; } } } diff --git a/src/com/android/launcher3/views/ScrimView.java b/src/com/android/launcher3/views/ScrimView.java index 22faf97cc4..7f0765becd 100644 --- a/src/com/android/launcher3/views/ScrimView.java +++ b/src/com/android/launcher3/views/ScrimView.java @@ -15,119 +15,37 @@ */ package com.android.launcher3.views; -import static android.content.Context.ACCESSIBILITY_SERVICE; -import static android.view.MotionEvent.ACTION_DOWN; - import static androidx.core.graphics.ColorUtils.compositeColors; -import static com.android.launcher3.LauncherState.ALL_APPS; -import static com.android.launcher3.LauncherState.NORMAL; -import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL; -import static com.android.launcher3.anim.Interpolators.DEACCEL; -import static com.android.launcher3.anim.Interpolators.LINEAR; -import static com.android.launcher3.anim.Interpolators.clampToProgress; import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound; import static com.android.launcher3.util.SystemUiController.UI_STATE_SCRIM_VIEW; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.Keyframe; -import android.animation.ObjectAnimator; -import android.animation.PropertyValuesHolder; -import android.animation.RectEvaluator; import android.content.Context; -import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.Color; -import android.graphics.Point; import android.graphics.Rect; -import android.graphics.RectF; -import android.graphics.drawable.Drawable; -import android.os.Bundle; import android.util.AttributeSet; -import android.util.IntProperty; -import android.view.KeyEvent; -import android.view.MotionEvent; import android.view.View; -import android.view.accessibility.AccessibilityManager; -import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import androidx.core.graphics.ColorUtils; -import androidx.core.view.ViewCompat; -import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; -import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat; -import androidx.customview.widget.ExploreByTouchHelper; -import com.android.launcher3.DeviceProfile; import com.android.launcher3.Insettable; import com.android.launcher3.Launcher; -import com.android.launcher3.LauncherState; import com.android.launcher3.R; -import com.android.launcher3.Utilities; -import com.android.launcher3.statemanager.StateManager; -import com.android.launcher3.statemanager.StateManager.StateListener; import com.android.launcher3.uioverrides.WallpaperColorInfo; import com.android.launcher3.uioverrides.WallpaperColorInfo.OnChangeListener; -import com.android.launcher3.userevent.nano.LauncherLogProto.Action; -import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType; -import com.android.launcher3.util.MultiValueAlpha; -import com.android.launcher3.util.MultiValueAlpha.AlphaProperty; import com.android.launcher3.util.Themes; -import com.android.launcher3.widget.WidgetsFullSheet; - -import java.util.List; /** * Simple scrim which draws a flat color */ -public class ScrimView extends View implements Insettable, OnChangeListener, - AccessibilityStateChangeListener { - - public static final IntProperty DRAG_HANDLE_ALPHA = - new IntProperty("dragHandleAlpha") { - - @Override - public Integer get(ScrimView scrimView) { - return scrimView.mDragHandleAlpha; - } - - @Override - public void setValue(ScrimView scrimView, int value) { - scrimView.setDragHandleAlpha(value); - } - }; - private static final int WALLPAPERS = R.string.wallpaper_button_text; - private static final int WIDGETS = R.string.widget_button_text; - private static final int SETTINGS = R.string.settings_button_text; - private static final int ALPHA_CHANNEL_COUNT = 1; - - private static final long DRAG_HANDLE_BOUNCE_DURATION_MS = 300; - // How much to delay before repeating the bounce. - private static final long DRAG_HANDLE_BOUNCE_DELAY_MS = 200; - // Repeat this many times (i.e. total number of bounces is 1 + this). - private static final int DRAG_HANDLE_BOUNCE_REPEAT_COUNT = 2; - - private final Rect mTempRect = new Rect(); - private final int[] mTempPos = new int[2]; +public class ScrimView extends View implements Insettable, OnChangeListener { protected final T mLauncher; private final WallpaperColorInfo mWallpaperColorInfo; - private final AccessibilityManager mAM; protected final int mEndScrim; protected final boolean mIsScrimDark; - private final StateListener mAccessibilityLauncherStateListener = - new StateListener() { - @Override - public void onStateTransitionComplete(LauncherState finalState) { - setImportantForAccessibility(finalState == ALL_APPS - ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS - : IMPORTANT_FOR_ACCESSIBILITY_AUTO); - } - }; - protected float mMaxScrimAlpha; protected float mProgress = 1; @@ -137,22 +55,6 @@ public class ScrimView extends View implements Insettable, O protected int mEndFlatColor; protected int mEndFlatColorAlpha; - protected final Point mDragHandleSize; - private final int mDragHandleTouchSize; - private final int mDragHandlePaddingInVerticalBarLayout; - protected float mDragHandleOffset; - private final Rect mDragHandleBounds; - private final RectF mHitRect = new RectF(); - private ObjectAnimator mDragHandleAnim; - - private final MultiValueAlpha mMultiValueAlpha; - - private final AccessibilityHelper mAccessibilityHelper; - @Nullable - protected Drawable mDragHandle; - - private int mDragHandleAlpha = 255; - public ScrimView(Context context, AttributeSet attrs) { super(context, attrs); mLauncher = Launcher.cast(Launcher.getLauncher(context)); @@ -161,59 +63,24 @@ public class ScrimView extends View implements Insettable, O mIsScrimDark = ColorUtils.calculateLuminance(mEndScrim) < 0.5f; mMaxScrimAlpha = 0.7f; - - Resources res = context.getResources(); - mDragHandleSize = new Point(res.getDimensionPixelSize(R.dimen.vertical_drag_handle_width), - res.getDimensionPixelSize(R.dimen.vertical_drag_handle_height)); - mDragHandleBounds = new Rect(0, 0, mDragHandleSize.x, mDragHandleSize.y); - mDragHandleTouchSize = res.getDimensionPixelSize(R.dimen.vertical_drag_handle_touch_size); - mDragHandlePaddingInVerticalBarLayout = context.getResources() - .getDimensionPixelSize(R.dimen.vertical_drag_handle_padding_in_vertical_bar_layout); - - mAccessibilityHelper = createAccessibilityHelper(); - ViewCompat.setAccessibilityDelegate(this, mAccessibilityHelper); - - mAM = (AccessibilityManager) context.getSystemService(ACCESSIBILITY_SERVICE); setFocusable(false); - mMultiValueAlpha = new MultiValueAlpha(this, ALPHA_CHANNEL_COUNT); - } - - public AlphaProperty getAlphaProperty(int index) { - return mMultiValueAlpha.getProperty(index); - } - - @NonNull - protected AccessibilityHelper createAccessibilityHelper() { - return new AccessibilityHelper(); } @Override - public void setInsets(Rect insets) { - updateDragHandleBounds(); - updateDragHandleVisibility(); - } + public void setInsets(Rect insets) { } - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - updateDragHandleBounds(); - } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); mWallpaperColorInfo.addOnChangeListener(this); onExtractedColorsChanged(mWallpaperColorInfo); - - mAM.addAccessibilityStateChangeListener(this); - onAccessibilityStateChanged(mAM.isEnabled()); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); mWallpaperColorInfo.removeOnChangeListener(this); - mAM.removeAccessibilityStateChangeListener(this); } @Override @@ -234,10 +101,8 @@ public class ScrimView extends View implements Insettable, O public void setProgress(float progress) { if (mProgress != progress) { mProgress = progress; - stopDragHandleEducationAnim(); updateColors(); updateSysUiColors(); - updateDragHandleAlpha(); invalidate(); } } @@ -260,286 +125,10 @@ public class ScrimView extends View implements Insettable, O } } - protected void updateDragHandleAlpha() { - if (mDragHandle != null) { - mDragHandle.setAlpha(mDragHandleAlpha); - } - } - - private void setDragHandleAlpha(int alpha) { - if (alpha != mDragHandleAlpha) { - mDragHandleAlpha = alpha; - if (mDragHandle != null) { - mDragHandle.setAlpha(mDragHandleAlpha); - invalidate(); - } - } - } - @Override protected void onDraw(Canvas canvas) { if (mCurrentFlatColor != 0) { canvas.drawColor(mCurrentFlatColor); } - drawDragHandle(canvas); - } - - protected void drawDragHandle(Canvas canvas) { - if (mDragHandle != null) { - canvas.translate(0, -mDragHandleOffset); - mDragHandle.draw(canvas); - canvas.translate(0, mDragHandleOffset); - } - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - boolean superHandledTouch = super.onTouchEvent(event); - if (event.getAction() == ACTION_DOWN) { - if (!superHandledTouch && mHitRect.contains(event.getX(), event.getY())) { - if (startDragHandleEducationAnim()) { - return true; - } - } - stopDragHandleEducationAnim(); - } - return superHandledTouch; - } - - /** - * Animates the drag handle to demonstrate how to get to all apps. - * @return Whether the animation was started (false if drag handle is invisible). - */ - public boolean startDragHandleEducationAnim() { - stopDragHandleEducationAnim(); - - if (mDragHandle == null || mDragHandle.getAlpha() != 255) { - return false; - } - - final Drawable drawable = mDragHandle; - mDragHandle = null; - - Rect bounds = new Rect(mDragHandleBounds); - bounds.offset(0, -(int) mDragHandleOffset); - drawable.setBounds(bounds); - - Rect topBounds = new Rect(bounds); - topBounds.offset(0, -bounds.height()); - - Rect invalidateRegion = new Rect(bounds); - invalidateRegion.top = topBounds.top; - - final float progressToReachTop = 0.6f; - Keyframe frameTop = Keyframe.ofObject(progressToReachTop, topBounds); - frameTop.setInterpolator(DEACCEL); - Keyframe frameBot = Keyframe.ofObject(1, bounds); - frameBot.setInterpolator(ACCEL_DEACCEL); - PropertyValuesHolder holder = PropertyValuesHolder.ofKeyframe("bounds", - Keyframe.ofObject(0, bounds), frameTop, frameBot); - holder.setEvaluator(new RectEvaluator()); - - mDragHandleAnim = ObjectAnimator.ofPropertyValuesHolder(drawable, holder); - long totalBounceDuration = DRAG_HANDLE_BOUNCE_DURATION_MS + DRAG_HANDLE_BOUNCE_DELAY_MS; - // The bounce finishes by this progress, the rest of the duration just delays next bounce. - float delayStartProgress = 1f - (float) DRAG_HANDLE_BOUNCE_DELAY_MS / totalBounceDuration; - mDragHandleAnim.addUpdateListener((v) -> invalidate(invalidateRegion)); - mDragHandleAnim.setDuration(totalBounceDuration); - mDragHandleAnim.setInterpolator(clampToProgress(LINEAR, 0, delayStartProgress)); - mDragHandleAnim.setRepeatCount(DRAG_HANDLE_BOUNCE_REPEAT_COUNT); - getOverlay().add(drawable); - - mDragHandleAnim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mDragHandleAnim = null; - getOverlay().remove(drawable); - updateDragHandleVisibility(drawable); - } - }); - mDragHandleAnim.start(); - return true; - } - - private void stopDragHandleEducationAnim() { - if (mDragHandleAnim != null) { - mDragHandleAnim.end(); - } - } - - protected void updateDragHandleBounds() { - DeviceProfile grid = mLauncher.getDeviceProfile(); - final int left; - final int width = getMeasuredWidth(); - final int top = getMeasuredHeight() - mDragHandleSize.y - grid.getInsets().bottom; - final int topMargin; - - if (grid.isVerticalBarLayout()) { - topMargin = grid.workspacePadding.bottom + mDragHandlePaddingInVerticalBarLayout; - if (grid.isSeascape()) { - left = width - grid.getInsets().right - mDragHandleSize.x - - mDragHandlePaddingInVerticalBarLayout; - } else { - left = grid.getInsets().left + mDragHandlePaddingInVerticalBarLayout; - } - } else { - left = Math.round((width - mDragHandleSize.x) / 2f); - topMargin = grid.hotseatBarSizePx; - } - mDragHandleBounds.offsetTo(left, top - topMargin); - mHitRect.set(mDragHandleBounds); - // Inset outwards to increase touch size. - mHitRect.inset((mDragHandleSize.x - mDragHandleTouchSize) / 2f, - (mDragHandleSize.y - mDragHandleTouchSize) / 2f); - - if (mDragHandle != null) { - mDragHandle.setBounds(mDragHandleBounds); - } - } - - @Override - public void onAccessibilityStateChanged(boolean enabled) { - StateManager stateManager = mLauncher.getStateManager(); - stateManager.removeStateListener(mAccessibilityLauncherStateListener); - - if (enabled) { - stateManager.addStateListener(mAccessibilityLauncherStateListener); - mAccessibilityLauncherStateListener.onStateTransitionComplete(stateManager.getState()); - } else { - setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); - } - updateDragHandleVisibility(); - } - - public void updateDragHandleVisibility() { - updateDragHandleVisibility(null); - } - - private void updateDragHandleVisibility(@Nullable Drawable recycle) { - boolean visible = shouldDragHandleBeVisible(); - boolean wasVisible = mDragHandle != null; - if (visible != wasVisible) { - if (visible) { - mDragHandle = recycle != null ? recycle : - mLauncher.getDrawable(R.drawable.drag_handle_indicator_shadow); - mDragHandle.setBounds(mDragHandleBounds); - - updateDragHandleAlpha(); - } else { - mDragHandle = null; - } - invalidate(); - } - } - - protected boolean shouldDragHandleBeVisible() { - return mLauncher.getDeviceProfile().isVerticalBarLayout() || mAM.isEnabled(); - } - - @Override - public boolean dispatchHoverEvent(MotionEvent event) { - return mAccessibilityHelper.dispatchHoverEvent(event) || super.dispatchHoverEvent(event); - } - - @Override - public boolean dispatchKeyEvent(KeyEvent event) { - return mAccessibilityHelper.dispatchKeyEvent(event) || super.dispatchKeyEvent(event); - } - - @Override - public void onFocusChanged(boolean gainFocus, int direction, - Rect previouslyFocusedRect) { - super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); - mAccessibilityHelper.onFocusChanged(gainFocus, direction, previouslyFocusedRect); - } - - protected class AccessibilityHelper extends ExploreByTouchHelper { - - private static final int DRAG_HANDLE_ID = 1; - - public AccessibilityHelper() { - super(ScrimView.this); - } - - @Override - protected int getVirtualViewAt(float x, float y) { - return mHitRect.contains((int) x, (int) y) - ? DRAG_HANDLE_ID : INVALID_ID; - } - - @Override - protected void getVisibleVirtualViews(List virtualViewIds) { - virtualViewIds.add(DRAG_HANDLE_ID); - } - - @Override - protected void onPopulateNodeForVirtualView(int virtualViewId, - AccessibilityNodeInfoCompat node) { - node.setContentDescription(getContext().getString(R.string.all_apps_button_label)); - node.setBoundsInParent(mDragHandleBounds); - - getLocationOnScreen(mTempPos); - mTempRect.set(mDragHandleBounds); - mTempRect.offset(mTempPos[0], mTempPos[1]); - node.setBoundsInScreen(mTempRect); - - node.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK); - node.setClickable(true); - node.setFocusable(true); - - if (mLauncher.isInState(NORMAL)) { - Context context = getContext(); - if (Utilities.isWallpaperAllowed(context)) { - node.addAction( - new AccessibilityActionCompat(WALLPAPERS, context.getText(WALLPAPERS))); - } - node.addAction(new AccessibilityActionCompat(WIDGETS, context.getText(WIDGETS))); - node.addAction(new AccessibilityActionCompat(SETTINGS, context.getText(SETTINGS))); - } - } - - @Override - protected boolean onPerformActionForVirtualView( - int virtualViewId, int action, Bundle arguments) { - if (action == AccessibilityNodeInfoCompat.ACTION_CLICK) { - mLauncher.getUserEventDispatcher().logActionOnControl( - Action.Touch.TAP, ControlType.ALL_APPS_BUTTON, - mLauncher.getStateManager().getState().containerType); - mLauncher.getStateManager().goToState(ALL_APPS); - return true; - } else if (action == WALLPAPERS) { - return OptionsPopupView.startWallpaperPicker(ScrimView.this); - } else if (action == WIDGETS) { - int originalImportanceForAccessibility = getImportantForAccessibility(); - setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); - WidgetsFullSheet widgetsFullSheet = OptionsPopupView.openWidgets(mLauncher); - if (widgetsFullSheet == null) { - setImportantForAccessibility(originalImportanceForAccessibility); - return false; - } - widgetsFullSheet.addOnAttachStateChangeListener(new OnAttachStateChangeListener() { - @Override - public void onViewAttachedToWindow(View view) {} - - @Override - public void onViewDetachedFromWindow(View view) { - setImportantForAccessibility(originalImportanceForAccessibility); - widgetsFullSheet.removeOnAttachStateChangeListener(this); - } - }); - return true; - } else if (action == SETTINGS) { - return OptionsPopupView.startSettings(ScrimView.this); - } - - return false; - } - } - - /** - * @return The top of this scrim view, or {@link Float#MAX_VALUE} if there's no distinct top. - */ - public float getVisualTop() { - return Float.MAX_VALUE; } } From df10ff46bd916e7bd0f685f8c59af8783d52906d Mon Sep 17 00:00:00 2001 From: Samuel Fufa Date: Sun, 26 Jul 2020 19:11:14 -0700 Subject: [PATCH 0063/1707] Migrate AllAppsSearch [part 1/3] [Video attached to bug report] Setup DeviceSearchAlgorithm to handle on device search Bug: 161801950 Test: Manual Change-Id: Ib55f415f9992ceab687bbbfe904d153157541648 --- res/layout/search_section_title.xml | 25 +++++ res/values/strings.xml | 4 + .../launcher3/allapps/AllAppsGridAdapter.java | 9 ++ .../allapps/AllAppsRecyclerView.java | 1 + .../allapps/AlphabeticalAppsList.java | 94 ++++++++++--------- .../search/AllAppsSearchBarController.java | 7 +- .../search/AppsSearchContainerLayout.java | 13 +-- .../allapps/search/AppsSearchPipeline.java | 92 ++++++++++++++++++ .../search/DefaultAppSearchAlgorithm.java | 34 ++----- .../allapps/search/SearchPipeline.java | 32 +++++++ .../allapps/search/SearchSectionInfo.java | 36 +++++++ 11 files changed, 267 insertions(+), 80 deletions(-) create mode 100644 res/layout/search_section_title.xml create mode 100644 src/com/android/launcher3/allapps/search/AppsSearchPipeline.java create mode 100644 src/com/android/launcher3/allapps/search/SearchPipeline.java create mode 100644 src/com/android/launcher3/allapps/search/SearchSectionInfo.java diff --git a/res/layout/search_section_title.xml b/res/layout/search_section_title.xml new file mode 100644 index 0000000000..9ab27c1620 --- /dev/null +++ b/res/layout/search_section_title.xml @@ -0,0 +1,25 @@ + + + \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index 80b511a54c..0ec0581457 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -67,6 +67,10 @@ App + + + Apps + Notifications diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java index d65316090c..dec92df2d3 100644 --- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java +++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java @@ -66,6 +66,8 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter mSearchResults; + private ArrayList mSearchResults; private AllAppsGridAdapter mAdapter; private AppInfoComparator mAppNameComparator; private final int mNumAppsPerRow; @@ -210,10 +225,10 @@ public class AlphabeticalAppsList implements AllAppsStore.OnUpdateListener { } /** - * Sets the sorted list of filtered components. + * Sets results list for search */ - public boolean setOrderedFilter(ArrayList f) { - if (mSearchResults != f) { + public boolean setSearchResults(ArrayList f) { + if (f == null || mSearchResults != f) { boolean same = mSearchResults != null && mSearchResults.equals(f); mSearchResults = f; onAppsUpdated(); @@ -298,35 +313,42 @@ public class AlphabeticalAppsList implements AllAppsStore.OnUpdateListener { // Recreate the filtered and sectioned apps (for convenience for the grid layout) from the // ordered set of sections - for (AppInfo info : getFiltersAppInfos()) { - String sectionName = info.sectionName; + if (!hasFilter()) { + for (AppInfo info : mApps) { + String sectionName = info.sectionName; - // Create a new section if the section names do not match - if (!sectionName.equals(lastSectionName)) { - lastSectionName = sectionName; - lastFastScrollerSectionInfo = new FastScrollSectionInfo(sectionName); - mFastScrollerSections.add(lastFastScrollerSectionInfo); + // Create a new section if the section names do not match + if (!sectionName.equals(lastSectionName)) { + lastSectionName = sectionName; + lastFastScrollerSectionInfo = new FastScrollSectionInfo(sectionName); + mFastScrollerSections.add(lastFastScrollerSectionInfo); + } + + // Create an app item + AdapterItem appItem = AdapterItem.asApp(position++, sectionName, info, appIndex++); + if (lastFastScrollerSectionInfo.fastScrollToItem == null) { + lastFastScrollerSectionInfo.fastScrollToItem = appItem; + } + mAdapterItems.add(appItem); + mFilteredApps.add(info); } + } else { + mAdapterItems.addAll(mSearchResults); + List appInfos = mSearchResults.stream().filter( + i -> AllAppsGridAdapter.isIconViewType(i.viewType)).map(i -> i.appInfo).collect( + Collectors.toList()); + mFilteredApps.addAll(appInfos); + if (!FeatureFlags.ENABLE_DEVICE_SEARCH.get()) { + // Append the search market item + if (hasNoFilteredResults()) { + mAdapterItems.add(AdapterItem.asEmptySearch(position++)); + } else { + mAdapterItems.add(AdapterItem.asAllAppsDivider(position++)); + } + mAdapterItems.add(AdapterItem.asMarketSearch(position++)); - // Create an app item - AdapterItem appItem = AdapterItem.asApp(position++, sectionName, info, appIndex++); - if (lastFastScrollerSectionInfo.fastScrollToItem == null) { - lastFastScrollerSectionInfo.fastScrollToItem = appItem; } - mAdapterItems.add(appItem); - mFilteredApps.add(info); } - - if (hasFilter()) { - // Append the search market item - if (hasNoFilteredResults()) { - mAdapterItems.add(AdapterItem.asEmptySearch(position++)); - } else { - mAdapterItems.add(AdapterItem.asAllAppsDivider(position++)); - } - mAdapterItems.add(AdapterItem.asMarketSearch(position++)); - } - if (mNumAppsPerRow != 0) { // Update the number of rows in the adapter after we do all the merging (otherwise, we // would have to shift the values again) @@ -381,18 +403,4 @@ public class AlphabeticalAppsList implements AllAppsStore.OnUpdateListener { } } } - - private List getFiltersAppInfos() { - if (mSearchResults == null) { - return mApps; - } - ArrayList result = new ArrayList<>(); - for (ComponentKey key : mSearchResults) { - AppInfo match = mAllAppsStore.getApp(key); - if (match != null) { - result.add(match); - } - } - return result; - } } diff --git a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java index df1cd26da4..db94e8b6e8 100644 --- a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java +++ b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java @@ -28,7 +28,7 @@ import android.widget.TextView.OnEditorActionListener; import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.ExtendedEditText; import com.android.launcher3.Utilities; -import com.android.launcher3.util.ComponentKey; +import com.android.launcher3.allapps.AlphabeticalAppsList; import com.android.launcher3.util.PackageManagerHelper; import java.util.ArrayList; @@ -50,6 +50,7 @@ public class AllAppsSearchBarController public void setVisibility(int visibility) { mInput.setVisibility(visibility); } + /** * Sets the references to the apps model and the search result callback. */ @@ -164,9 +165,9 @@ public class AllAppsSearchBarController /** * Called when the search is complete. * - * @param apps sorted list of matching components or null if in case of failure. + * @param items sorted list of search result adapter items. */ - void onSearchResult(String query, ArrayList apps); + void onSearchResult(String query, ArrayList items); /** * Called when the search results should be cleared. diff --git a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java index 356c52ca39..df6a89bcb1 100644 --- a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java +++ b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java @@ -38,13 +38,13 @@ import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.DeviceProfile; import com.android.launcher3.ExtendedEditText; import com.android.launcher3.Insettable; +import com.android.launcher3.LauncherAppState; import com.android.launcher3.R; import com.android.launcher3.allapps.AllAppsContainerView; import com.android.launcher3.allapps.AllAppsStore; import com.android.launcher3.allapps.AlphabeticalAppsList; import com.android.launcher3.allapps.SearchUiManager; import com.android.launcher3.anim.PropertySetter; -import com.android.launcher3.util.ComponentKey; import java.util.ArrayList; @@ -135,7 +135,8 @@ public class AppsSearchContainerLayout extends ExtendedEditText mApps = appsView.getApps(); mAppsView = appsView; mSearchBarController.initialize( - new DefaultAppSearchAlgorithm(mApps.getApps()), this, mLauncher, this); + new DefaultAppSearchAlgorithm(LauncherAppState.getInstance(mLauncher)), this, + mLauncher, this); } @Override @@ -168,9 +169,9 @@ public class AppsSearchContainerLayout extends ExtendedEditText } @Override - public void onSearchResult(String query, ArrayList apps) { - if (apps != null) { - mApps.setOrderedFilter(apps); + public void onSearchResult(String query, ArrayList items) { + if (items != null) { + mApps.setSearchResults(items); notifyResultChanged(); mAppsView.setLastSearchQuery(query); } @@ -178,7 +179,7 @@ public class AppsSearchContainerLayout extends ExtendedEditText @Override public void clearSearchResult() { - if (mApps.setOrderedFilter(null)) { + if (mApps.setSearchResults(null)) { notifyResultChanged(); } diff --git a/src/com/android/launcher3/allapps/search/AppsSearchPipeline.java b/src/com/android/launcher3/allapps/search/AppsSearchPipeline.java new file mode 100644 index 0000000000..5beb956079 --- /dev/null +++ b/src/com/android/launcher3/allapps/search/AppsSearchPipeline.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.allapps.search; + +import androidx.annotation.WorkerThread; + +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.R; +import com.android.launcher3.allapps.AlphabeticalAppsList.AdapterItem; +import com.android.launcher3.model.AllAppsList; +import com.android.launcher3.model.BaseModelUpdateTask; +import com.android.launcher3.model.BgDataModel; +import com.android.launcher3.model.data.AppInfo; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +/** + * A device search section for handling app searches + */ +public class AppsSearchPipeline implements SearchPipeline { + + private static final int MAX_RESULTS_COUNT = 5; + + private final SearchSectionInfo mSearchSectionInfo; + private final LauncherAppState mLauncherAppState; + + public AppsSearchPipeline(LauncherAppState launcherAppState) { + mLauncherAppState = launcherAppState; + mSearchSectionInfo = new SearchSectionInfo(R.string.search_corpus_apps); + } + + @Override + @WorkerThread + public void performSearch(String query, Consumer> callback) { + mLauncherAppState.getModel().enqueueModelUpdateTask(new BaseModelUpdateTask() { + @Override + public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) { + callback.accept(getAdapterItems(getTitleMatchResult(apps.data, query))); + } + }); + } + + /** + * Filters {@link AppInfo}s matching specified query + */ + public static ArrayList getTitleMatchResult(List apps, String query) { + // Do an intersection of the words in the query and each title, and filter out all the + // apps that don't match all of the words in the query. + final String queryTextLower = query.toLowerCase(); + final ArrayList result = new ArrayList<>(); + DefaultAppSearchAlgorithm.StringMatcher matcher = + DefaultAppSearchAlgorithm.StringMatcher.getInstance(); + for (AppInfo info : apps) { + if (DefaultAppSearchAlgorithm.matches(info, queryTextLower, matcher)) { + result.add(info); + } + } + return result; + } + + private ArrayList getAdapterItems(List matchingApps) { + ArrayList items = new ArrayList<>(); + if (matchingApps.isEmpty()) { + return items; + } + items.add(AdapterItem.asSearchTitle(mSearchSectionInfo, 0)); + int existingItems = items.size(); + int searchResultsCount = Math.min(matchingApps.size(), MAX_RESULTS_COUNT); + for (int i = 0; i < searchResultsCount; i++) { + AdapterItem appItem = AdapterItem.asApp(i + existingItems, "", matchingApps.get(i), i); + appItem.searchSectionInfo = mSearchSectionInfo; + items.add(appItem); + } + + return items; + } +} diff --git a/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java b/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java index f72a9888cf..db10311675 100644 --- a/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java +++ b/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java @@ -17,24 +17,22 @@ package com.android.launcher3.allapps.search; import android.os.Handler; +import com.android.launcher3.LauncherAppState; import com.android.launcher3.model.data.AppInfo; -import com.android.launcher3.util.ComponentKey; import java.text.Collator; -import java.util.ArrayList; -import java.util.List; /** * The default search implementation. */ public class DefaultAppSearchAlgorithm implements SearchAlgorithm { - private final List mApps; protected final Handler mResultHandler; + private final AppsSearchPipeline mAppsSearchPipeline; - public DefaultAppSearchAlgorithm(List apps) { - mApps = apps; + public DefaultAppSearchAlgorithm(LauncherAppState launcherAppState) { mResultHandler = new Handler(); + mAppsSearchPipeline = new AppsSearchPipeline(launcherAppState); } @Override @@ -47,28 +45,8 @@ public class DefaultAppSearchAlgorithm implements SearchAlgorithm { @Override public void doSearch(final String query, final AllAppsSearchBarController.Callbacks callback) { - final ArrayList result = getTitleMatchResult(query); - mResultHandler.post(new Runnable() { - - @Override - public void run() { - callback.onSearchResult(query, result); - } - }); - } - - private ArrayList getTitleMatchResult(String query) { - // Do an intersection of the words in the query and each title, and filter out all the - // apps that don't match all of the words in the query. - final String queryTextLower = query.toLowerCase(); - final ArrayList result = new ArrayList<>(); - StringMatcher matcher = StringMatcher.getInstance(); - for (AppInfo info : mApps) { - if (matches(info, queryTextLower, matcher)) { - result.add(info.toComponentKey()); - } - } - return result; + mAppsSearchPipeline.performSearch(query, + results -> mResultHandler.post(() -> callback.onSearchResult(query, results))); } public static boolean matches(AppInfo info, String query, StringMatcher matcher) { diff --git a/src/com/android/launcher3/allapps/search/SearchPipeline.java b/src/com/android/launcher3/allapps/search/SearchPipeline.java new file mode 100644 index 0000000000..321674025a --- /dev/null +++ b/src/com/android/launcher3/allapps/search/SearchPipeline.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.allapps.search; + +import com.android.launcher3.allapps.AlphabeticalAppsList; + +import java.util.ArrayList; +import java.util.function.Consumer; + +/** + * An interface for handling search within pipeline + */ +public interface SearchPipeline { + + /** + * Perform query + */ + void performSearch(String query, Consumer> cb); +} diff --git a/src/com/android/launcher3/allapps/search/SearchSectionInfo.java b/src/com/android/launcher3/allapps/search/SearchSectionInfo.java new file mode 100644 index 0000000000..880b2466dc --- /dev/null +++ b/src/com/android/launcher3/allapps/search/SearchSectionInfo.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.allapps.search; + +import android.content.Context; + +/** + * Info class for a search section + */ +public class SearchSectionInfo { + private final int mTitleResId; + + public SearchSectionInfo(int titleResId) { + mTitleResId = titleResId; + } + + /** + * Returns the section's title + */ + public String getTitle(Context context) { + return context.getString(mTitleResId); + } +} From 261d817a95a039a49f971254140b4ce07d53ccb4 Mon Sep 17 00:00:00 2001 From: Vadim Tryshev Date: Wed, 29 Jul 2020 02:50:35 +0000 Subject: [PATCH 0064/1707] Revert "Recycling already running activities" This reverts commit 0bc32dcce64b20459ff6f8eff960b217810d59d8. Reason for revert: Tests are broken, perhaps caused by this one Change-Id: Ie409f04931a27bd6da616c1e52e279eb07479631 --- .../quickstep/StartLauncherViaGestureTests.java | 4 +--- .../android/quickstep/TaplTestsQuickstep.java | 16 +++++++--------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java index 664958a18e..0c5b9ada5d 100644 --- a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java +++ b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java @@ -48,9 +48,7 @@ public class StartLauncherViaGestureTests extends AbstractQuickStepTest { // b/143488140 mLauncher.pressHome(); // Start an activity where the gestures start. - if (mLauncher.getRecentTasks().isEmpty()) { - startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR)); - } + startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR)); } private void runTest(String... eventSequence) { diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java index ed25cc71ca..bf093fdabf 100644 --- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java +++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java @@ -69,16 +69,14 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest { } private void startTestApps() throws Exception { - if (mLauncher.getRecentTasks().size() < 3) { - startAppFast(getAppPackageName()); - startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR)); - startTestActivity(2); + startAppFast(getAppPackageName()); + startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR)); + startTestActivity(2); - executeOnLauncher(launcher -> assertTrue( - "Launcher activity is the top activity; expecting another activity to be the " - + "top one", - isInBackground(launcher))); - } + executeOnLauncher(launcher -> assertTrue( + "Launcher activity is the top activity; expecting another activity to be the top " + + "one", + isInBackground(launcher))); } @Test From 12baaadeca97160ab567f4380991470ff536ff5f Mon Sep 17 00:00:00 2001 From: Yuchen He Date: Wed, 29 Jul 2020 17:15:03 +0000 Subject: [PATCH 0065/1707] Enable memory dump for Launcher Bug: 154772063 Change-Id: I3b2bc2e341037746891658f2596999248ec272ef --- tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java index 858e183ab7..a1a2ed2a72 100644 --- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java +++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java @@ -116,8 +116,7 @@ public abstract class AbstractLauncherUiTest { if (TestHelpers.isInLauncherProcess()) { StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder() -// b/154772063 -// .detectActivityLeaks() + .detectActivityLeaks() .penaltyLog() .penaltyListener(Runnable::run, violation -> { if (sStrictmodeDetectedActivityLeak == null) { From 7383d4e6d2769937f8a8fe33e82e960aeb5771f4 Mon Sep 17 00:00:00 2001 From: Tony Wickham Date: Tue, 7 Jul 2020 19:25:25 -0700 Subject: [PATCH 0066/1707] Fix adjacent task offset distance Instead of calculating an overall distance for tasks to translate based on RecentsView width, calculate the distance for the tasks to the left and right of the midpoint based on how far the first adjacent tasks in those directions are from being offscreen. Changes made to make "distance to offscreen" calculations possible: - Update TaskView curve scale to reach final scale as soon as it is completely offscreen. Before, it would reach its final scale just shy of that point (calculations were off). - As we update RecentsView scale, calculate how much the new scale will push out tasks that are just offscreen. - With both above, we can calculate the scale and position of a TaskView such that it is just offscreen, and interpolate between its current position and that position. Tests: - Task comes in immediately when quick switching from home, and doesn't shift as you swipe directly upwards. - When swiping far up from an app, tasks come in from all the way offscreen, and cover distance appropriately (e.g. if you're scrolled a bit to the right when you pause, the left adjacent app will move faster to cover the farther distance). - Task modalness: entering Select mode now animates adjacent tasks at the same rate as the scaling up, because they move only the distance needed to get offscreen (before they moved way too far and thus seemed to be much faster than the rest of the animation). Bug: 149934536 Change-Id: Ie3fffe0e5c304cb16e7637f058f5ce72cee40aeb --- .../QuickstepAtomicAnimationFactory.java | 4 +- .../NoButtonQuickSwitchTouchController.java | 7 +- .../FallbackRecentsStateController.java | 4 +- .../quickstep/util/TaskViewSimulator.java | 6 +- .../android/quickstep/views/RecentsView.java | 126 ++++++++++++++++-- .../BaseRecentsViewStateController.java | 6 +- .../quickstep/BaseActivityInterface.java | 4 +- src/com/android/launcher3/PagedView.java | 21 ++- .../touch/LandscapePagedViewHandler.java | 1 + .../touch/PagedOrientationHandler.java | 1 + .../touch/PortraitPagedViewHandler.java | 1 + 11 files changed, 146 insertions(+), 35 deletions(-) diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java index a0af79743b..131fcbfbf1 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java @@ -17,7 +17,6 @@ package com.android.launcher3.uioverrides.states; import static android.view.View.VISIBLE; -import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; import static com.android.launcher3.LauncherState.BACKGROUND_APP; import static com.android.launcher3.LauncherState.HINT_STATE; import static com.android.launcher3.LauncherState.HOTSEAT_ICONS; @@ -52,6 +51,7 @@ import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_S import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_TRANSLATE; import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON; import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview; +import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY; import android.animation.Animator; import android.animation.AnimatorSet; @@ -212,7 +212,7 @@ public class QuickstepAtomicAnimationFactory extends // Scale up the recents, if it is not coming from the side RecentsView overview = mActivity.getOverviewPanel(); if (overview.getVisibility() != VISIBLE || overview.getContentAlpha() == 0) { - SCALE_PROPERTY.set(overview, RECENTS_PREPARE_SCALE); + RECENTS_SCALE_PROPERTY.set(overview, RECENTS_PREPARE_SCALE); } } config.setInterpolator(ANIM_WORKSPACE_FADE, OVERSHOOT_1_2); diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java index 1b439d1d31..821ada400e 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java @@ -15,7 +15,6 @@ */ package com.android.launcher3.uioverrides.touchcontrollers; -import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; import static com.android.launcher3.LauncherState.HOTSEAT_ICONS; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.OVERVIEW; @@ -47,6 +46,7 @@ import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.HIDE; import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.PEEK; import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET; import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS; +import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED; import android.animation.Animator; @@ -244,7 +244,7 @@ public class NoButtonQuickSwitchTouchController implements TouchController, final LauncherState toState = OVERVIEW; // Set RecentView's initial properties. - SCALE_PROPERTY.set(mRecentsView, fromState.getOverviewScaleAndOffset(mLauncher)[0]); + RECENTS_SCALE_PROPERTY.set(mRecentsView, fromState.getOverviewScaleAndOffset(mLauncher)[0]); ADJACENT_PAGE_OFFSET.set(mRecentsView, 1f); mRecentsView.setContentAlpha(1); mRecentsView.setFullscreenProgress(fromState.getOverviewFullscreenProgress()); @@ -266,7 +266,8 @@ public class NoButtonQuickSwitchTouchController implements TouchController, // - RecentsView scale // - RecentsView fullscreenProgress PendingAnimation yAnim = new PendingAnimation((long) (mYRange * 2)); - yAnim.setFloat(mRecentsView, SCALE_PROPERTY, scaleAndOffset[0], SCALE_DOWN_INTERPOLATOR); + yAnim.setFloat(mRecentsView, RECENTS_SCALE_PROPERTY, scaleAndOffset[0], + SCALE_DOWN_INTERPOLATOR); yAnim.setFloat(mRecentsView, FULLSCREEN_PROGRESS, toState.getOverviewFullscreenProgress(), SCALE_DOWN_INTERPOLATOR); mYOverviewAnim = yAnim.createPlaybackController(); diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsStateController.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsStateController.java index 3f1e7bad9b..163c23268b 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsStateController.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsStateController.java @@ -15,7 +15,6 @@ */ package com.android.quickstep.fallback; -import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; import static com.android.launcher3.anim.Interpolators.LINEAR; import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_MODAL; import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE; @@ -25,6 +24,7 @@ import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVER import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW; import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET; import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS; +import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY; import static com.android.quickstep.views.RecentsView.TASK_MODALNESS; import com.android.launcher3.anim.PendingAnimation; @@ -82,7 +82,7 @@ public class FallbackRecentsStateController implements StateHandler extends PagedView } }; + /** Same as normal SCALE_PROPERTY, but also updates page offsets that depend on this scale. */ + public static final FloatProperty RECENTS_SCALE_PROPERTY = + new FloatProperty("recentsScale") { + @Override + public void setValue(RecentsView view, float scale) { + view.setScaleX(scale); + view.setScaleY(scale); + view.mLastComputedTaskPushOutDistance = null; + view.updatePageOffsets(); + } + + @Override + public Float get(RecentsView view) { + return view.getScaleX(); + } + }; + protected RecentsOrientedState mOrientationState; protected final BaseActivityInterface mSizeStrategy; protected RecentsAnimationController mRecentsAnimationController; @@ -219,8 +236,12 @@ public abstract class RecentsView extends PagedView protected int mTaskHeight; protected final TransformParams mLiveTileParams = new TransformParams(); protected final TaskViewSimulator mLiveTileTaskViewSimulator; + protected final Rect mLastComputedTaskSize = new Rect(); + // How much a task that is directly offscreen will be pushed out due to RecentsView scale/pivot. + protected Float mLastComputedTaskPushOutDistance = null; protected boolean mEnableDrawingLiveTile = false; protected final Rect mTempRect = new Rect(); + protected final RectF mTempRectF = new RectF(); private final PointF mTempPointF = new PointF(); private static final int DISMISS_TASK_DURATION = 300; @@ -887,6 +908,7 @@ public abstract class RecentsView extends PagedView public void getTaskSize(Rect outRect) { mSizeStrategy.calculateTaskSize(mActivity, mActivity.getDeviceProfile(), outRect, mOrientationHandler); + mLastComputedTaskSize.set(outRect); } /** Gets the task size for modal state. */ @@ -934,8 +956,8 @@ public abstract class RecentsView extends PagedView final int pageCount = getPageCount(); for (int i = 0; i < pageCount; i++) { View page = getPageAt(i); - mScrollState.updateInterpolation(mOrientationHandler.getChildStartWithTranslation(page), - mPageSpacing); + mScrollState.updateInterpolation( + mOrientationHandler.getChildStartWithTranslation(page)); ((PageCallbacks) page).onPageScroll(mScrollState); } } @@ -1357,10 +1379,14 @@ public abstract class RecentsView extends PagedView /** * Updates linearInterpolation for the provided child position */ - public void updateInterpolation(float childStart, int pageSpacing) { - float pageCenter = childStart + halfPageSize; + public void updateInterpolation(float childStart) { + float scaledHalfPageSize = halfPageSize / pageParentScale; + float pageCenter = childStart + scaledHalfPageSize; float distanceFromScreenCenter = screenCenter - pageCenter; - float distanceToReachEdge = halfScreenSize + halfPageSize + pageSpacing; + // How far the page has to move from the center to be offscreen, taking into account + // the EDGE_SCALE_DOWN_FACTOR that will be applied at that position. + float distanceToReachEdge = halfScreenSize + + scaledHalfPageSize * (1 - TaskView.EDGE_SCALE_DOWN_FACTOR); linearInterpolation = Math.min(1, Math.abs(distanceFromScreenCenter) / distanceToReachEdge); } @@ -1802,14 +1828,15 @@ public abstract class RecentsView extends PagedView setPivotX(mTempPointF.x); setPivotY(mTempPointF.y); setTaskModalness(mTaskModalness); + mLastComputedTaskPushOutDistance = null; updatePageOffsets(); setImportantForAccessibility(isModal() ? IMPORTANT_FOR_ACCESSIBILITY_NO : IMPORTANT_FOR_ACCESSIBILITY_AUTO); } private void updatePageOffsets() { - float offset = mAdjacentPageOffset * getWidth(); - float modalOffset = ACCEL_0_75.getInterpolation(mTaskModalness) * getWidth(); + float offset = mAdjacentPageOffset; + float modalOffset = ACCEL_0_75.getInterpolation(mTaskModalness); if (mIsRtl) { offset = -offset; modalOffset = -modalOffset; @@ -1818,18 +1845,89 @@ public abstract class RecentsView extends PagedView TaskView runningTask = mRunningTaskId == -1 || !mRunningTaskTileHidden ? null : getTaskView(mRunningTaskId); - int midPoint = runningTask == null ? -1 : indexOfChild(runningTask); - int currentPage = getCurrentPage(); + int midpoint = runningTask == null ? -1 : indexOfChild(runningTask); + int modalMidpoint = getCurrentPage(); + + float midpointOffsetSize = 0; + float leftOffsetSize = midpoint - 1 >= 0 + ? -getOffsetSize(midpoint - 1, midpoint, offset) + : 0; + float rightOffsetSize = midpoint + 1 < count + ? getOffsetSize(midpoint + 1, midpoint, offset) + : 0; + + float modalMidpointOffsetSize = 0; + float modalLeftOffsetSize = modalMidpoint - 1 >= 0 + ? -getOffsetSize(modalMidpoint - 1, modalMidpoint, modalOffset) + : 0; + float modalRightOffsetSize = modalMidpoint + 1 < count + ? getOffsetSize(modalMidpoint + 1, modalMidpoint, modalOffset) + : 0; for (int i = 0; i < count; i++) { - float translation = i == midPoint ? 0 : (i < midPoint ? -offset : offset); - float modalTranslation = - i == currentPage ? 0 : (i < currentPage ? -modalOffset : modalOffset); + float translation = i == midpoint + ? midpointOffsetSize + : i < midpoint + ? leftOffsetSize + : rightOffsetSize; + float modalTranslation = i == modalMidpoint + ? modalMidpointOffsetSize + : i < modalMidpoint + ? modalLeftOffsetSize + : modalRightOffsetSize; getChildAt(i).setTranslationX(translation + modalTranslation); } updateCurveProperties(); } + /** + * Computes the distance to offset the given child such that it is completely offscreen when + * translating away from the given midpoint. + * @param offsetProgress From 0 to 1 where 0 means no offset and 1 means offset offscreen. + */ + private float getOffsetSize(int childIndex, int midpointIndex, float offsetProgress) { + if (offsetProgress == 0) { + // Don't bother calculating everything below if we won't offset anyway. + return 0; + } + // First, get the position of the task relative to the midpoint. If there is no midpoint + // then we just use the normal (centered) task position. + mTempRectF.set(mLastComputedTaskSize); + RectF taskPosition = mTempRectF; + float desiredLeft = getWidth(); + float distanceToOffscreen = desiredLeft - taskPosition.left; + // Used to calculate the scale of the task view based on its new offset. + float centerToOffscreenProgress = Math.abs(offsetProgress); + if (midpointIndex > -1) { + // When there is a midpoint reference task, adjacent tasks have less distance to travel + // to reach offscreen. Offset the task position to the task's starting point. + View child = getChildAt(childIndex); + View midpointChild = getChildAt(midpointIndex); + int distanceFromMidpoint = Math.abs(mOrientationHandler.getChildStart(child) + - mOrientationHandler.getChildStart(midpointChild) + + getDisplacementFromScreenCenter(midpointIndex)); + taskPosition.offset(distanceFromMidpoint, 0); + centerToOffscreenProgress = Utilities.mapRange(centerToOffscreenProgress, + distanceFromMidpoint / distanceToOffscreen, 1); + } + // Find the task's scale based on its offscreen progress, then see how far it still needs to + // move to be completely offscreen. + Utilities.scaleRectFAboutCenter(taskPosition, + TaskView.getCurveScaleForInterpolation(centerToOffscreenProgress)); + distanceToOffscreen = desiredLeft - taskPosition.left; + // Finally, we need to account for RecentsView scale, because it moves tasks based on its + // pivot. To do this, we move the task position to where it would be offscreen at scale = 1 + // (computed above), then we apply the scale via getMatrix() to determine how much that + // moves the task from its desired position, and adjust the computed distance accordingly. + if (mLastComputedTaskPushOutDistance == null) { + taskPosition.offsetTo(desiredLeft, 0); + getMatrix().mapRect(taskPosition); + mLastComputedTaskPushOutDistance = (taskPosition.left - desiredLeft) / getScaleX(); + } + distanceToOffscreen -= mLastComputedTaskPushOutDistance; + return distanceToOffscreen * offsetProgress; + } + /** * TODO: Do not assume motion across X axis for adjacent page */ @@ -1928,7 +2026,7 @@ public abstract class RecentsView extends PagedView float toScale = getMaxScaleForFullScreen(); if (launchingCenterTask) { RecentsView recentsView = tv.getRecentsView(); - anim.play(ObjectAnimator.ofFloat(recentsView, SCALE_PROPERTY, toScale)); + anim.play(ObjectAnimator.ofFloat(recentsView, RECENTS_SCALE_PROPERTY, toScale)); anim.play(ObjectAnimator.ofFloat(recentsView, FULLSCREEN_PROGRESS, 1)); } else { // We are launching an adjacent task, so parallax the center and other adjacent task. diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java index ec3a4905f5..6e120e8bd8 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java @@ -16,7 +16,6 @@ package com.android.launcher3.uioverrides; -import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE_IN_OUT; import static com.android.launcher3.anim.Interpolators.LINEAR; import static com.android.launcher3.graphics.Scrim.SCRIM_PROGRESS; @@ -29,6 +28,7 @@ import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVER import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_SCALE; import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW; import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET; +import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY; import android.util.FloatProperty; @@ -61,7 +61,7 @@ public abstract class BaseRecentsViewStateController @Override public void setState(@NonNull LauncherState state) { float[] scaleAndOffset = state.getOverviewScaleAndOffset(mLauncher); - SCALE_PROPERTY.set(mRecentsView, scaleAndOffset[0]); + RECENTS_SCALE_PROPERTY.set(mRecentsView, scaleAndOffset[0]); ADJACENT_PAGE_OFFSET.set(mRecentsView, scaleAndOffset[1]); getContentAlphaProperty().set(mRecentsView, state.overviewUi ? 1f : 0); @@ -93,7 +93,7 @@ public abstract class BaseRecentsViewStateController void setStateWithAnimationInternal(@NonNull final LauncherState toState, @NonNull StateAnimationConfig config, @NonNull PendingAnimation setter) { float[] scaleAndOffset = toState.getOverviewScaleAndOffset(mLauncher); - setter.setFloat(mRecentsView, SCALE_PROPERTY, scaleAndOffset[0], + setter.setFloat(mRecentsView, RECENTS_SCALE_PROPERTY, scaleAndOffset[0], config.getInterpolator(ANIM_OVERVIEW_SCALE, LINEAR)); setter.setFloat(mRecentsView, ADJACENT_PAGE_OFFSET, scaleAndOffset[1], config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_X, LINEAR)); diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java index 0ae386f39a..9dc2132683 100644 --- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java +++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java @@ -15,7 +15,6 @@ */ package com.android.quickstep; -import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; import static com.android.launcher3.anim.Interpolators.ACCEL_2; import static com.android.launcher3.anim.Interpolators.INSTANT; import static com.android.launcher3.anim.Interpolators.LINEAR; @@ -28,6 +27,7 @@ import static com.android.quickstep.util.RecentsAtomicAnimationFactory.INDEX_REC import static com.android.quickstep.util.RecentsAtomicAnimationFactory.INDEX_RECENTS_TRANSLATE_X_ANIM; import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET; import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS; +import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY; import android.animation.Animator; import android.annotation.TargetApi; @@ -393,7 +393,7 @@ public abstract class BaseActivityInterface extends ViewGrou int minDistanceFromScreenCenterIndex = -1; final int childCount = getChildCount(); for (int i = 0; i < childCount; ++i) { - View layout = getPageAt(i); - int childSize = mOrientationHandler.getMeasuredSize(layout); - int halfChildSize = (childSize / 2); - int childCenter = getChildOffset(i) + halfChildSize; - int distanceFromScreenCenter = Math.abs(childCenter - screenCenter); + int distanceFromScreenCenter = Math.abs( + getDisplacementFromScreenCenter(i, screenCenter)); if (distanceFromScreenCenter < minDistanceFromScreenCenter) { minDistanceFromScreenCenter = distanceFromScreenCenter; minDistanceFromScreenCenterIndex = i; @@ -1461,6 +1458,20 @@ public abstract class PagedView extends ViewGrou return minDistanceFromScreenCenterIndex; } + private int getDisplacementFromScreenCenter(int childIndex, int screenCenter) { + View layout = getPageAt(childIndex); + int childSize = mOrientationHandler.getMeasuredSize(layout); + int halfChildSize = (childSize / 2); + int childCenter = getChildOffset(childIndex) + halfChildSize; + return childCenter - screenCenter; + } + + protected int getDisplacementFromScreenCenter(int childIndex) { + int pageOrientationSize = mOrientationHandler.getMeasuredSize(this); + int screenCenter = mOrientationHandler.getPrimaryScroll(this) + (pageOrientationSize / 2); + return getDisplacementFromScreenCenter(childIndex, screenCenter); + } + protected void snapToDestination() { snapToPage(getPageNearestToCenterOfScreen(), getPageSnapDuration()); } diff --git a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java index ac1ade260d..1aaa608657 100644 --- a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java +++ b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java @@ -72,6 +72,7 @@ public class LandscapePagedViewHandler implements PagedOrientationHandler { out.halfPageSize = view.getNormalChildHeight() / 2; out.halfScreenSize = view.getMeasuredHeight() / 2; out.screenCenter = insets.top + view.getPaddingTop() + out.scroll + out.halfPageSize; + out.pageParentScale = view.getScaleY(); } @Override diff --git a/src/com/android/launcher3/touch/PagedOrientationHandler.java b/src/com/android/launcher3/touch/PagedOrientationHandler.java index d4f5cba40e..f88cdb3b0d 100644 --- a/src/com/android/launcher3/touch/PagedOrientationHandler.java +++ b/src/com/android/launcher3/touch/PagedOrientationHandler.java @@ -109,6 +109,7 @@ public interface PagedOrientationHandler { public int halfPageSize; public int screenCenter; public int halfScreenSize; + public float pageParentScale; } class ChildBounds { diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java index 3341996383..f18b109515 100644 --- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java +++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java @@ -70,6 +70,7 @@ public class PortraitPagedViewHandler implements PagedOrientationHandler { out.halfPageSize = view.getNormalChildWidth() / 2; out.halfScreenSize = view.getMeasuredWidth() / 2; out.screenCenter = insets.left + view.getPaddingLeft() + out.scroll + out.halfPageSize; + out.pageParentScale = view.getScaleX(); } @Override From 8b74cc761b5a3185f3cfffe0a3833bbf7dd32636 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Mon, 27 Jul 2020 17:50:33 -0700 Subject: [PATCH 0067/1707] Moving all-apps predictions to Launcher model Predictions are loaded and managed by Launcher model and follow the model lifecycle. They are then bound to the callback which handles the UI Bug: 160748731 Change-Id: I4a3ea0698d80fafe94afb4ce66ffa7f4a6a91c68 --- .../res/values/override.xml | 5 +- .../appprediction/PredictionRowView.java | 78 +--- .../PredictionUiStateManager.java | 348 ------------------ .../AppEventProducer.java} | 125 +------ .../launcher3/model/PredictionUpdateTask.java | 117 ++++++ .../model/QuickstepModelDelegate.java | 138 +++++++ .../uioverrides/QuickstepLauncher.java | 39 +- .../quickstep/LauncherActivityInterface.java | 1 - ...EventDispatcherAppPredictionExtension.java | 49 --- .../quickstep/AppPredictionsUITests.java | 162 -------- res/values/config.xml | 1 + src/com/android/launcher3/Launcher.java | 5 +- .../android/launcher3/LauncherAppState.java | 3 +- src/com/android/launcher3/LauncherModel.java | 30 +- .../graphics/LauncherPreviewRenderer.java | 3 +- .../launcher3/model/AppLaunchTracker.java | 33 -- .../android/launcher3/model/BgDataModel.java | 7 - .../android/launcher3/model/LoaderTask.java | 7 +- .../launcher3/model/ModelDelegate.java | 75 ++++ .../launcher3/ui/AbstractLauncherUiTest.java | 3 - 20 files changed, 424 insertions(+), 805 deletions(-) delete mode 100644 quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java rename quickstep/recents_ui_overrides/src/com/android/launcher3/{appprediction/PredictionAppTracker.java => model/AppEventProducer.java} (65%) create mode 100644 quickstep/recents_ui_overrides/src/com/android/launcher3/model/PredictionUpdateTask.java create mode 100644 quickstep/recents_ui_overrides/src/com/android/launcher3/model/QuickstepModelDelegate.java delete mode 100644 quickstep/recents_ui_overrides/src/com/android/quickstep/logging/UserEventDispatcherAppPredictionExtension.java delete mode 100644 quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java delete mode 100644 src/com/android/launcher3/model/AppLaunchTracker.java create mode 100644 src/com/android/launcher3/model/ModelDelegate.java diff --git a/quickstep/recents_ui_overrides/res/values/override.xml b/quickstep/recents_ui_overrides/res/values/override.xml index 6aa9619cc9..1937164b7d 100644 --- a/quickstep/recents_ui_overrides/res/values/override.xml +++ b/quickstep/recents_ui_overrides/res/values/override.xml @@ -25,8 +25,11 @@ com.android.quickstep.QuickstepProcessInitializer - com.android.quickstep.logging.UserEventDispatcherAppPredictionExtension + com.android.quickstep.logging.UserEventDispatcherExtension com.android.launcher3.hybridhotseat.HotseatPredictionModel + + com.android.launcher3.model.QuickstepModelDelegate + diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java index e11c701640..55384af96b 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java @@ -30,7 +30,6 @@ import android.graphics.Rect; import android.os.Build; import android.util.AttributeSet; import android.util.IntProperty; -import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.animation.Interpolator; @@ -44,19 +43,15 @@ import com.android.launcher3.DeviceProfile; import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppState; -import com.android.launcher3.LauncherSettings; import com.android.launcher3.LauncherState; import com.android.launcher3.R; -import com.android.launcher3.allapps.AllAppsStore; import com.android.launcher3.allapps.FloatingHeaderRow; import com.android.launcher3.allapps.FloatingHeaderView; import com.android.launcher3.anim.AlphaUpdateListener; import com.android.launcher3.anim.PropertySetter; -import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.keyboard.FocusIndicatorHelper; import com.android.launcher3.keyboard.FocusIndicatorHelper.SimpleFocusIndicatorHelper; import com.android.launcher3.logging.StatsLogUtils.LogContainerProvider; -import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.ItemInfoWithIcon; import com.android.launcher3.model.data.WorkspaceItemInfo; @@ -67,15 +62,12 @@ import com.android.launcher3.util.Themes; import com.android.quickstep.AnimatedFloat; import java.util.ArrayList; -import java.util.Collections; import java.util.List; @TargetApi(Build.VERSION_CODES.P) public class PredictionRowView extends LinearLayout implements LogContainerProvider, OnDeviceProfileChangeListener, FloatingHeaderRow { - private static final String TAG = "PredictionRowView"; - private static final IntProperty TEXT_ALPHA = new IntProperty("textAlpha") { @Override @@ -93,16 +85,14 @@ public class PredictionRowView extends LinearLayout implements (t) -> (t < 0.8f) ? 0 : (t - 0.8f) / 0.2f; private final Launcher mLauncher; - private final PredictionUiStateManager mPredictionUiStateManager; private int mNumPredictedAppsPerRow; - // The set of predicted app component names - private final List mPredictedAppComponents = new ArrayList<>(); - // The set of predicted apps resolved from the component names and the current set of apps - private final ArrayList mPredictedApps = new ArrayList<>(); // Helper to drawing the focus indicator. private final FocusIndicatorHelper mFocusHelper; + // The set of predicted apps resolved from the component names and the current set of apps + private final List mPredictedApps = new ArrayList<>(); + private final int mIconTextColor; private final int mIconFullTextAlpha; private int mIconLastSetTextAlpha; @@ -134,8 +124,6 @@ public class PredictionRowView extends LinearLayout implements mLauncher = Launcher.getLauncher(context); mLauncher.addOnDeviceProfileChangeListener(this); - mPredictionUiStateManager = PredictionUiStateManager.INSTANCE.get(context); - mIconTextColor = Themes.getAttrColor(context, android.R.attr.textColorSecondary); mIconFullTextAlpha = Color.alpha(mIconTextColor); mIconCurrentTextAlpha = mIconFullTextAlpha; @@ -146,24 +134,9 @@ public class PredictionRowView extends LinearLayout implements @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); - - mPredictionUiStateManager.setTargetAppsView(mLauncher.getAppsView()); - getAppsStore().registerIconContainer(this); AllAppsTipView.scheduleShowIfNeeded(mLauncher); } - private AllAppsStore getAppsStore() { - return mLauncher.getAppsView().getAppsStore(); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - - mPredictionUiStateManager.setTargetAppsView(null); - getAppsStore().unregisterIconContainer(this); - } - public void setup(FloatingHeaderView parent, FloatingHeaderRow[] rows, boolean tabsHidden) { mParent = parent; } @@ -205,7 +178,7 @@ public class PredictionRowView extends LinearLayout implements * Returns the predicted apps. */ public List getPredictedApps() { - return mPredictedApps; + return new ArrayList<>(mPredictedApps); } /** @@ -217,12 +190,12 @@ public class PredictionRowView extends LinearLayout implements * If the number of predicted apps is the same as the previous list of predicted apps, * we can optimize by swapping them in place. */ - public void setPredictedApps(List apps) { - mPredictedAppComponents.clear(); - mPredictedAppComponents.addAll(apps); - + public void setPredictedApps(List items) { mPredictedApps.clear(); - mPredictedApps.addAll(processPredictedAppComponents(mPredictedAppComponents)); + items.stream() + .filter(itemInfo -> itemInfo instanceof WorkspaceItemInfo) + .map(itemInfo -> (WorkspaceItemInfo) itemInfo) + .forEach(mPredictedApps::add); applyPredictionApps(); } @@ -264,11 +237,7 @@ public class PredictionRowView extends LinearLayout implements icon.reset(); if (predictionCount > i) { icon.setVisibility(View.VISIBLE); - if (mPredictedApps.get(i) instanceof AppInfo) { - icon.applyFromApplicationInfo((AppInfo) mPredictedApps.get(i)); - } else if (mPredictedApps.get(i) instanceof WorkspaceItemInfo) { - icon.applyFromWorkspaceItem((WorkspaceItemInfo) mPredictedApps.get(i)); - } + icon.applyFromWorkspaceItem(mPredictedApps.get(i)); icon.setTextColor(iconColor); } else { icon.setVisibility(predictionCount == 0 ? GONE : INVISIBLE); @@ -284,33 +253,6 @@ public class PredictionRowView extends LinearLayout implements mParent.onHeightUpdated(); } - private List processPredictedAppComponents( - List components) { - if (getAppsStore().getApps().length == 0) { - // Apps have not been bound yet. - return Collections.emptyList(); - } - - List predictedApps = new ArrayList<>(); - for (ComponentKeyMapper mapper : components) { - ItemInfoWithIcon info = mapper.getApp(getAppsStore()); - if (info != null) { - ItemInfoWithIcon predictedApp = info.clone(); - predictedApp.container = LauncherSettings.Favorites.CONTAINER_PREDICTION; - predictedApps.add(predictedApp); - } else { - if (FeatureFlags.IS_STUDIO_BUILD) { - Log.e(TAG, "Predicted app not found: " + mapper); - } - } - // Stop at the number of predicted apps - if (predictedApps.size() == mNumPredictedAppsPerRow) { - break; - } - } - return predictedApps; - } - @Override public void fillInLogContainerData(ItemInfo childInfo, LauncherLogProto.Target child, ArrayList parents) { 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 deleted file mode 100644 index 830c1032ba..0000000000 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java +++ /dev/null @@ -1,348 +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.appprediction; - -import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; -import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT; -import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT; -import static com.android.launcher3.LauncherState.BACKGROUND_APP; -import static com.android.launcher3.LauncherState.OVERVIEW; - -import android.app.prediction.AppPredictor; -import android.app.prediction.AppTarget; -import android.content.ComponentName; -import android.content.Context; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.android.launcher3.InvariantDeviceProfile; -import com.android.launcher3.InvariantDeviceProfile.OnIDPChangeListener; -import com.android.launcher3.Launcher; -import com.android.launcher3.LauncherAppState; -import com.android.launcher3.LauncherSettings; -import com.android.launcher3.LauncherState; -import com.android.launcher3.Utilities; -import com.android.launcher3.allapps.AllAppsContainerView; -import com.android.launcher3.allapps.AllAppsStore.OnUpdateListener; -import com.android.launcher3.hybridhotseat.HotseatPredictionController; -import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver; -import com.android.launcher3.model.data.ItemInfo; -import com.android.launcher3.model.data.ItemInfoWithIcon; -import com.android.launcher3.shortcuts.ShortcutKey; -import com.android.launcher3.statemanager.StateManager.StateListener; -import com.android.launcher3.userevent.nano.LauncherLogProto; -import com.android.launcher3.util.ComponentKey; -import com.android.launcher3.util.MainThreadInitializedObject; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.OptionalInt; -import java.util.stream.IntStream; - -/** - * Handler responsible to updating the UI due to predicted apps changes. Operations: - * 1) Pushes the predicted apps to all-apps. If all-apps is visible, waits until it becomes - * invisible again before applying the changes. This ensures that the UI does not change abruptly - * in front of the user, even if an app launched and user pressed back button to return to the - * all-apps UI again. - * 2) Prefetch high-res icons for predicted apps. This ensures that we have the icons in memory - * even if all-apps is not opened as they are shown in search UI as well - * 3) Load instant app if it is not already in memory. As predictions are persisted on disk, - * instant app will not be in memory when launcher starts. - * 4) Maintains the current active client id (for the predictions) and all updates are performed on - * that client id. - */ -public class PredictionUiStateManager implements StateListener, - ItemInfoUpdateReceiver, OnIDPChangeListener, OnUpdateListener { - - public static final String LAST_PREDICTION_ENABLED_STATE = "last_prediction_enabled_state"; - - // TODO (b/129421797): Update the client constants - public enum Client { - HOME("home"); - - public final String id; - - Client(String id) { - this.id = id; - } - } - - public static final MainThreadInitializedObject INSTANCE = - new MainThreadInitializedObject<>(PredictionUiStateManager::new); - - private final Context mContext; - - private final DynamicItemCache mDynamicItemCache; - private List mPredictionServicePredictions = Collections.emptyList(); - - private int mMaxIconsPerRow; - - private AllAppsContainerView mAppsView; - - private PredictionState mPendingState; - private PredictionState mCurrentState; - - private boolean mGettingValidPredictionResults; - - private PredictionUiStateManager(Context context) { - mContext = context; - - mDynamicItemCache = new DynamicItemCache(context, this::onAppsUpdated); - - InvariantDeviceProfile idp = LauncherAppState.getIDP(context); - mMaxIconsPerRow = idp.numColumns; - - idp.addOnChangeListener(this); - mGettingValidPredictionResults = Utilities.getDevicePrefs(context) - .getBoolean(LAST_PREDICTION_ENABLED_STATE, true); - - // Call this last - mCurrentState = parseLastState(); - } - - @Override - public void onIdpChanged(int changeFlags, InvariantDeviceProfile profile) { - mMaxIconsPerRow = profile.numColumns; - } - - public void setTargetAppsView(AllAppsContainerView appsView) { - if (mAppsView != null) { - mAppsView.getAppsStore().removeUpdateListener(this); - } - mAppsView = appsView; - if (mAppsView != null) { - mAppsView.getAppsStore().addUpdateListener(this); - } - if (mPendingState != null) { - applyState(mPendingState); - mPendingState = null; - } else { - applyState(mCurrentState); - } - updateDependencies(mCurrentState); - } - - @Override - public void reapplyItemInfo(ItemInfoWithIcon info) { } - - @Override - public void onStateTransitionComplete(LauncherState state) { - if (mAppsView == null) { - return; - } - if (mPendingState != null && canApplyPredictions(mPendingState)) { - applyState(mPendingState); - mPendingState = null; - } - if (mPendingState == null) { - Launcher.getLauncher(mAppsView.getContext()).getStateManager() - .removeStateListener(this); - } - } - - private void scheduleApplyPredictedApps(PredictionState state) { - boolean registerListener = mPendingState == null; - mPendingState = state; - if (registerListener) { - // Add a listener and wait until appsView is invisible again. - Launcher.getLauncher(mAppsView.getContext()).getStateManager().addStateListener(this); - } - } - - private void applyState(PredictionState state) { - mCurrentState = state; - if (mAppsView != null) { - mAppsView.getFloatingHeaderView().findFixedRowByType(PredictionRowView.class) - .setPredictedApps(mCurrentState.apps); - } - } - - private void updatePredictionStateAfterCallback() { - boolean validResults = mPredictionServicePredictions != null - && !mPredictionServicePredictions.isEmpty(); - if (validResults != mGettingValidPredictionResults) { - mGettingValidPredictionResults = validResults; - Utilities.getDevicePrefs(mContext).edit() - .putBoolean(LAST_PREDICTION_ENABLED_STATE, true) - .apply(); - } - dispatchOnChange(true); - } - - public AppPredictor.Callback appPredictorCallback(Client client) { - return targets -> { - mPredictionServicePredictions = targets; - updatePredictionStateAfterCallback(); - }; - } - - private void dispatchOnChange(boolean changed) { - PredictionState newState = changed - ? parseLastState() - : mPendingState != null && canApplyPredictions(mPendingState) - ? mPendingState - : mCurrentState; - if (changed && mAppsView != null && !canApplyPredictions(newState)) { - scheduleApplyPredictedApps(newState); - } else { - applyState(newState); - } - } - - private PredictionState parseLastState() { - PredictionState state = new PredictionState(); - state.isEnabled = mGettingValidPredictionResults; - if (!state.isEnabled) { - state.apps = Collections.EMPTY_LIST; - return state; - } - - state.apps = new ArrayList<>(); - - List appTargets = mPredictionServicePredictions; - if (!appTargets.isEmpty()) { - 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()); - } - state.apps.add(new ComponentKeyMapper(key, mDynamicItemCache)); - } - } - updateDependencies(state); - return state; - } - - private void updateDependencies(PredictionState state) { - if (!state.isEnabled || mAppsView == null) { - return; - } - mDynamicItemCache.updateDependencies(state.apps, mAppsView.getAppsStore(), this, - mMaxIconsPerRow); - } - - @Override - public void onAppsUpdated() { - dispatchOnChange(false); - } - - private boolean canApplyPredictions(PredictionState newState) { - if (mAppsView == null) { - // If there is no apps view, no need to schedule. - return true; - } - Launcher launcher = Launcher.getLauncher(mAppsView.getContext()); - PredictionRowView predictionRow = mAppsView.getFloatingHeaderView(). - findFixedRowByType(PredictionRowView.class); - if (!predictionRow.isShown() || predictionRow.getAlpha() == 0 || - launcher.isForceInvisible()) { - return true; - } - - if (mCurrentState.isEnabled != newState.isEnabled - || mCurrentState.apps.isEmpty() != newState.apps.isEmpty()) { - // If the visibility of the prediction row is changing, apply immediately. - return true; - } - - if (launcher.getDeviceProfile().isVerticalBarLayout()) { - // If we are here & mAppsView.isShown() = true, we are probably in all-apps or mid way - return false; - } - if (!launcher.isInState(OVERVIEW) && !launcher.isInState(BACKGROUND_APP)) { - // Just a fallback as we dont need to apply instantly, if we are not in the swipe-up UI - return false; - } - - // Instead of checking against 1, we should check against (1 + delta), where delta accounts - // for the nav-bar height (as app icon can still be visible under the nav-bar). Checking - // against 1, keeps the logic simple :) - return launcher.getAllAppsController().getProgress() > 1; - } - - public PredictionState getCurrentState() { - return mCurrentState; - } - - /** - * Returns ranking info for the app within all apps prediction. - * Only applicable when {@link ItemInfo#itemType} is one of the followings: - * {@link LauncherSettings.Favorites#ITEM_TYPE_APPLICATION}, - * {@link LauncherSettings.Favorites#ITEM_TYPE_SHORTCUT}, - * {@link LauncherSettings.Favorites#ITEM_TYPE_DEEP_SHORTCUT} - */ - public OptionalInt getAllAppsRank(@Nullable ItemInfo itemInfo) { - if (itemInfo == null || itemInfo.getTargetComponent() == null || itemInfo.user == null) { - return 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(); - } - - /** - * Fill in predicted_rank field based on app prediction. - * Only applicable when {@link ItemInfo#itemType} is one of the followings: - * {@link LauncherSettings.Favorites#ITEM_TYPE_APPLICATION}, - * {@link LauncherSettings.Favorites#ITEM_TYPE_SHORTCUT}, - * {@link LauncherSettings.Favorites#ITEM_TYPE_DEEP_SHORTCUT} - */ - public static void fillInPredictedRank( - @NonNull ItemInfo itemInfo, @NonNull LauncherLogProto.Target target) { - - final PredictionUiStateManager manager = PredictionUiStateManager.INSTANCE.getNoCreate(); - if (manager == null || itemInfo.getTargetComponent() == null || itemInfo.user == null - || (itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_APPLICATION - && itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT - && itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT)) { - return; - } - if (itemInfo.container != LauncherSettings.Favorites.CONTAINER_PREDICTION) { - HotseatPredictionController.encodeHotseatLayoutIntoPredictionRank(itemInfo, target); - return; - } - - final ComponentKey k = new ComponentKey(itemInfo.getTargetComponent(), itemInfo.user); - final List predictedApps = manager.getCurrentState().apps; - IntStream.range(0, predictedApps.size()) - .filter((i) -> k.equals(predictedApps.get(i).getComponentKey())) - .findFirst() - .ifPresent((rank) -> target.predictedRank = 0 - rank); - } - - public static class PredictionState { - - public boolean isEnabled; - public List apps; - } -} diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/model/AppEventProducer.java similarity index 65% rename from quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java rename to quickstep/recents_ui_overrides/src/com/android/launcher3/model/AppEventProducer.java index 8cabe3d80b..8e4c43f464 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/model/AppEventProducer.java @@ -13,175 +13,76 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.launcher3.appprediction; +package com.android.launcher3.model; -import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_GRID; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROPPED_ON_DONT_SUGGEST; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_LEFT; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_RIGHT; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_SWIPE_DOWN; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_TAP; -import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; +import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; import android.annotation.TargetApi; -import android.app.prediction.AppPredictionContext; -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.content.Context; import android.os.Build; -import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.Process; import android.os.SystemClock; import android.os.UserHandle; import android.text.TextUtils; -import android.util.Log; import androidx.annotation.AnyThread; import androidx.annotation.Nullable; -import androidx.annotation.UiThread; import androidx.annotation.WorkerThread; -import com.android.launcher3.InvariantDeviceProfile; -import com.android.launcher3.appprediction.PredictionUiStateManager.Client; import com.android.launcher3.logger.LauncherAtom; import com.android.launcher3.logger.LauncherAtom.ContainerInfo; import com.android.launcher3.logger.LauncherAtom.FolderContainer; import com.android.launcher3.logger.LauncherAtom.HotseatContainer; import com.android.launcher3.logger.LauncherAtom.WorkspaceContainer; import com.android.launcher3.logging.StatsLogManager.EventEnum; -import com.android.launcher3.model.AppLaunchTracker; import com.android.launcher3.pm.UserCache; -import com.android.quickstep.logging.StatsLogCompatManager; import com.android.quickstep.logging.StatsLogCompatManager.StatsLogConsumer; import java.util.Locale; +import java.util.function.Consumer; import java.util.function.Predicate; /** - * Subclass of app tracker which publishes the data to the prediction engine and gets back results. + * Utility class to track stats log and emit corresponding app events */ -@TargetApi(Build.VERSION_CODES.Q) -public class PredictionAppTracker extends AppLaunchTracker implements StatsLogConsumer { +@TargetApi(Build.VERSION_CODES.R) +public class AppEventProducer implements StatsLogConsumer { - private static final String TAG = "PredictionAppTracker"; - private static final boolean DBG = false; + private static final int MSG_LAUNCH = 0; - private static final int MSG_INIT = 0; - private static final int MSG_DESTROY = 1; - private static final int MSG_LAUNCH = 2; - private static final int MSG_PREDICT = 3; - - protected final Context mContext; + private final Context mContext; private final Handler mMessageHandler; + private final Consumer mCallback; - // Accessed only on worker thread - private AppPredictor mHomeAppPredictor; - - public PredictionAppTracker(Context context) { + public AppEventProducer(Context context, Consumer callback) { mContext = context; - mMessageHandler = new Handler(UI_HELPER_EXECUTOR.getLooper(), this::handleMessage); - InvariantDeviceProfile.INSTANCE.get(mContext).addOnChangeListener(this::onIdpChanged); - - mMessageHandler.sendEmptyMessage(MSG_INIT); - } - - @UiThread - private void onIdpChanged(int changeFlags, InvariantDeviceProfile profile) { - if ((changeFlags & CHANGE_FLAG_GRID) != 0) { - // Reinitialize everything - mMessageHandler.sendEmptyMessage(MSG_INIT); - } - } - - @WorkerThread - private void destroy() { - if (mHomeAppPredictor != null) { - mHomeAppPredictor.destroy(); - mHomeAppPredictor = null; - } - StatsLogCompatManager.LOGS_CONSUMER.remove(this); - } - - @WorkerThread - private AppPredictor createPredictor(Client client, int count) { - AppPredictionManager apm = mContext.getSystemService(AppPredictionManager.class); - - if (apm == null) { - return null; - } - - AppPredictor predictor = apm.createAppPredictionSession( - new AppPredictionContext.Builder(mContext) - .setUiSurface(client.id) - .setPredictedTargetCount(count) - .setExtras(getAppPredictionContextExtras(client)) - .build()); - predictor.registerPredictionUpdates(mContext.getMainExecutor(), - PredictionUiStateManager.INSTANCE.get(mContext).appPredictorCallback(client)); - predictor.requestPredictionUpdate(); - return predictor; - } - - /** - * Override to add custom extras. - */ - @WorkerThread - @Nullable - public Bundle getAppPredictionContextExtras(Client client) { - return null; + mMessageHandler = new Handler(MODEL_EXECUTOR.getLooper(), this::handleMessage); + mCallback = callback; } @WorkerThread private boolean handleMessage(Message msg) { switch (msg.what) { - case MSG_INIT: { - // Destroy any existing clients - destroy(); - - // Initialize the clients - int count = InvariantDeviceProfile.INSTANCE.get(mContext).numAllAppsColumns; - mHomeAppPredictor = createPredictor(Client.HOME, count); - StatsLogCompatManager.LOGS_CONSUMER.add(this); - return true; - } - case MSG_DESTROY: { - destroy(); - return true; - } case MSG_LAUNCH: { - if (mHomeAppPredictor != null) { - mHomeAppPredictor.notifyAppTargetEvent((AppTargetEvent) msg.obj); - } - return true; - } - case MSG_PREDICT: { - if (mHomeAppPredictor != null) { - mHomeAppPredictor.requestPredictionUpdate(); - } + mCallback.accept((AppTargetEvent) msg.obj); return true; } } return false; } - @Override - @UiThread - public void onReturnedToHome() { - String client = Client.HOME.id; - mMessageHandler.removeMessages(MSG_PREDICT, client); - Message.obtain(mMessageHandler, MSG_PREDICT, client).sendToTarget(); - if (DBG) { - Log.d(TAG, String.format("Sent immediate message to update %s", client)); - } - } - @AnyThread private void sendEvent(LauncherAtom.ItemInfo atomInfo, int eventId) { AppTarget target = toAppTarget(atomInfo); diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/model/PredictionUpdateTask.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/model/PredictionUpdateTask.java new file mode 100644 index 0000000000..721e2bebd5 --- /dev/null +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/model/PredictionUpdateTask.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.model; + +import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT; +import static com.android.launcher3.model.QuickstepModelDelegate.LAST_PREDICTION_ENABLED_STATE; +import static com.android.quickstep.InstantAppResolverImpl.COMPONENT_CLASS_MARKER; + +import android.app.prediction.AppTarget; +import android.content.ComponentName; +import android.content.pm.LauncherActivityInfo; +import android.content.pm.LauncherApps; +import android.content.pm.ShortcutInfo; +import android.os.UserHandle; + +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.Utilities; +import com.android.launcher3.model.BgDataModel.FixedContainerItems; +import com.android.launcher3.model.data.AppInfo; +import com.android.launcher3.model.data.WorkspaceItemInfo; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Task to update model as a result of predicted apps update + */ +public class PredictionUpdateTask extends BaseModelUpdateTask { + + private final List mTargets; + private final int mContainerId; + + PredictionUpdateTask(int containerId, List targets) { + mContainerId = containerId; + mTargets = targets; + } + + @Override + public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) { + // TODO: persist the whole list + Utilities.getDevicePrefs(app.getContext()).edit() + .putBoolean(LAST_PREDICTION_ENABLED_STATE, !mTargets.isEmpty()).apply(); + + FixedContainerItems fci; + synchronized (dataModel) { + fci = dataModel.extraItems.get(mContainerId); + if (fci == null) { + return; + } + } + + Set usersForChangedShortcuts = new HashSet<>(fci.items.stream() + .filter(info -> info.itemType == ITEM_TYPE_DEEP_SHORTCUT) + .map(info -> info.user) + .collect(Collectors.toSet())); + fci.items.clear(); + + for (AppTarget target : mTargets) { + WorkspaceItemInfo itemInfo; + ShortcutInfo si = target.getShortcutInfo(); + if (si != null) { + usersForChangedShortcuts.add(si.getUserHandle()); + itemInfo = new WorkspaceItemInfo(si, app.getContext()); + app.getIconCache().getShortcutIcon(itemInfo, si); + } else { + String className = target.getClassName(); + if (COMPONENT_CLASS_MARKER.equals(className)) { + // TODO: Implement this + continue; + } + ComponentName cn = new ComponentName(target.getPackageName(), className); + UserHandle user = target.getUser(); + itemInfo = apps.data.stream() + .filter(info -> user.equals(info.user) && cn.equals(info.componentName)) + .map(AppInfo::makeWorkspaceItem) + .findAny() + .orElseGet(() -> { + LauncherActivityInfo lai = app.getContext() + .getSystemService(LauncherApps.class) + .resolveActivity(AppInfo.makeLaunchIntent(cn), user); + if (lai == null) { + return null; + } + AppInfo ai = new AppInfo(app.getContext(), lai, user); + app.getIconCache().getTitleAndIcon(ai, lai, false); + return ai.makeWorkspaceItem(); + }); + + if (itemInfo == null) { + continue; + } + } + + itemInfo.container = mContainerId; + fci.items.add(itemInfo); + } + + bindExtraContainerItems(fci); + usersForChangedShortcuts.forEach( + u -> dataModel.updateShortcutPinnedState(app.getContext(), u)); + } +} diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/model/QuickstepModelDelegate.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/model/QuickstepModelDelegate.java new file mode 100644 index 0000000000..b5164695d5 --- /dev/null +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/model/QuickstepModelDelegate.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.model; + +import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_GRID; +import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PREDICTION; + +import android.app.prediction.AppPredictionContext; +import android.app.prediction.AppPredictionManager; +import android.app.prediction.AppPredictor; +import android.app.prediction.AppTarget; +import android.app.prediction.AppTargetEvent; +import android.content.Context; + +import androidx.annotation.WorkerThread; + +import com.android.launcher3.InvariantDeviceProfile; +import com.android.launcher3.InvariantDeviceProfile.OnIDPChangeListener; +import com.android.launcher3.model.BgDataModel.FixedContainerItems; +import com.android.launcher3.util.Executors; +import com.android.quickstep.logging.StatsLogCompatManager; + +import java.util.List; + +/** + * Model delegate which loads prediction items + */ +public class QuickstepModelDelegate extends ModelDelegate implements OnIDPChangeListener { + + public static final String LAST_PREDICTION_ENABLED_STATE = "last_prediction_enabled_state"; + + private final InvariantDeviceProfile mIDP; + private final AppEventProducer mAppEventProducer; + + private AppPredictor mAllAppsPredictor; + private boolean mActive = false; + + public QuickstepModelDelegate(Context context) { + mAppEventProducer = new AppEventProducer(context, this::onAppTargetEvent); + + mIDP = InvariantDeviceProfile.INSTANCE.get(context); + mIDP.addOnChangeListener(this); + StatsLogCompatManager.LOGS_CONSUMER.add(mAppEventProducer); + } + + @Override + public void loadItems() { + // TODO: Implement caching and preloading + super.loadItems(); + mDataModel.extraItems.put( + CONTAINER_PREDICTION, new FixedContainerItems(CONTAINER_PREDICTION)); + + mActive = true; + recreatePredictors(); + } + + @Override + public void validateData() { + super.validateData(); + if (mAllAppsPredictor != null) { + mAllAppsPredictor.requestPredictionUpdate(); + } + } + + @Override + public void destroy() { + super.destroy(); + mActive = false; + StatsLogCompatManager.LOGS_CONSUMER.remove(mAppEventProducer); + + destroyPredictors(); + mIDP.removeOnChangeListener(this); + } + + private void destroyPredictors() { + if (mAllAppsPredictor != null) { + mAllAppsPredictor.destroy(); + mAllAppsPredictor = null; + } + } + + @WorkerThread + private void recreatePredictors() { + destroyPredictors(); + if (!mActive) { + return; + } + + Context context = mApp.getContext(); + AppPredictionManager apm = context.getSystemService(AppPredictionManager.class); + if (apm == null) { + return; + } + + int count = mIDP.numAllAppsColumns; + + mAllAppsPredictor = apm.createAppPredictionSession( + new AppPredictionContext.Builder(context) + .setUiSurface("home") + .setPredictedTargetCount(count) + .build()); + mAllAppsPredictor.registerPredictionUpdates( + Executors.MODEL_EXECUTOR, this::onAllAppsPredictionChanged); + mAllAppsPredictor.requestPredictionUpdate(); + } + + private void onAllAppsPredictionChanged(List targets) { + mApp.getModel().enqueueModelUpdateTask( + new PredictionUpdateTask(CONTAINER_PREDICTION, targets)); + } + + @Override + public void onIdpChanged(int changeFlags, InvariantDeviceProfile profile) { + if ((changeFlags & CHANGE_FLAG_GRID) != 0) { + // Reinitialize everything + Executors.MODEL_EXECUTOR.execute(this::recreatePredictors); + } + } + + private void onAppTargetEvent(AppTargetEvent event) { + if (mAllAppsPredictor != null) { + mAllAppsPredictor.notifyAppTargetEvent(event); + } + } +} diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java index f87138e536..23a05295bf 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java @@ -17,6 +17,9 @@ package com.android.launcher3.uioverrides; import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED; +import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; +import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT; +import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK; @@ -37,16 +40,18 @@ import android.view.View; import com.android.launcher3.BaseQuickstepLauncher; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.LauncherState; import com.android.launcher3.Workspace; import com.android.launcher3.allapps.DiscoveryBounce; import com.android.launcher3.anim.AnimatorPlaybackController; -import com.android.launcher3.appprediction.PredictionUiStateManager; +import com.android.launcher3.appprediction.PredictionRowView; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.folder.Folder; import com.android.launcher3.hybridhotseat.HotseatPredictionController; import com.android.launcher3.logging.InstanceId; import com.android.launcher3.logging.StatsLogManager.StatsLogger; +import com.android.launcher3.model.BgDataModel.FixedContainerItems; import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; @@ -79,7 +84,7 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; -import java.util.OptionalInt; +import java.util.Objects; import java.util.stream.Stream; public class QuickstepLauncher extends BaseQuickstepLauncher { @@ -91,6 +96,8 @@ public class QuickstepLauncher extends BaseQuickstepLauncher { public static final AsyncCommand SET_SHELF_HEIGHT = (context, arg1, arg2) -> SystemUiProxy.INSTANCE.get(context).setShelfHeight(arg1 != 0, arg2); + private FixedContainerItems mAllAppsPredictions; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -111,8 +118,23 @@ public class QuickstepLauncher extends BaseQuickstepLauncher { protected void logAppLaunch(ItemInfo info, InstanceId instanceId) { StatsLogger logger = getStatsLogManager() .logger().withItemInfo(info).withInstanceId(instanceId); - OptionalInt allAppsRank = PredictionUiStateManager.INSTANCE.get(this).getAllAppsRank(info); - allAppsRank.ifPresent(logger::withRank); + + if (mAllAppsPredictions != null + && (info.itemType == ITEM_TYPE_APPLICATION + || info.itemType == ITEM_TYPE_SHORTCUT + || info.itemType == ITEM_TYPE_DEEP_SHORTCUT)) { + int count = mAllAppsPredictions.items.size(); + for (int i = 0; i < count; i++) { + ItemInfo targetInfo = mAllAppsPredictions.items.get(i); + if (targetInfo.itemType == info.itemType + && targetInfo.user.equals(info.user) + && Objects.equals(targetInfo.getIntent(), info.getIntent())) { + logger.withRank(i); + break; + } + + } + } logger.log(LAUNCHER_APP_LAUNCH_TAP); if (mHotseatPredictionController != null) { @@ -199,6 +221,15 @@ public class QuickstepLauncher extends BaseQuickstepLauncher { } } + @Override + public void bindExtraContainerItems(FixedContainerItems item) { + if (item.containerId == Favorites.CONTAINER_PREDICTION) { + mAllAppsPredictions = item; + getAppsView().getFloatingHeaderView().findFixedRowByType(PredictionRowView.class) + .setPredictedApps(item.items); + } + } + @Override public void onDestroy() { super.onDestroy(); 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 b02035516d..5a35eb5eb2 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java @@ -41,7 +41,6 @@ import com.android.launcher3.R; import com.android.launcher3.allapps.DiscoveryBounce; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.PendingAnimation; -import com.android.launcher3.appprediction.PredictionUiStateManager; import com.android.launcher3.statehandlers.DepthController; import com.android.launcher3.statehandlers.DepthController.ClampedDepthProperty; import com.android.launcher3.statemanager.StateManager; diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/logging/UserEventDispatcherAppPredictionExtension.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/logging/UserEventDispatcherAppPredictionExtension.java deleted file mode 100644 index e0008033b4..0000000000 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/logging/UserEventDispatcherAppPredictionExtension.java +++ /dev/null @@ -1,49 +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.logging; - -import android.content.Context; - -import androidx.annotation.NonNull; - -import com.android.launcher3.appprediction.PredictionUiStateManager; -import com.android.launcher3.model.data.ItemInfo; -import com.android.launcher3.userevent.nano.LauncherLogProto; - -import java.util.ArrayList; - -/** - * This class handles AOSP MetricsLogger function calls and logging around - * quickstep interactions and app launches. - */ -@SuppressWarnings("unused") -public class UserEventDispatcherAppPredictionExtension extends UserEventDispatcherExtension { - - public static final int ALL_APPS_PREDICTION_TIPS = 2; - - private static final String TAG = "UserEventDispatcher"; - - public UserEventDispatcherAppPredictionExtension(Context context) { - super(context); - } - - @Override - protected void onFillInLogContainerData( - @NonNull ItemInfo itemInfo, @NonNull LauncherLogProto.Target target, - @NonNull ArrayList targets) { - PredictionUiStateManager.fillInPredictedRank(itemInfo, target); - } -} diff --git a/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java b/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java deleted file mode 100644 index 5904fcd691..0000000000 --- a/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java +++ /dev/null @@ -1,162 +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 org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; - -import android.app.prediction.AppPredictor; -import android.app.prediction.AppTarget; -import android.app.prediction.AppTargetId; -import android.content.ComponentName; -import android.content.pm.LauncherActivityInfo; -import android.content.pm.LauncherApps; -import android.os.Process; -import android.view.View; - -import androidx.test.filters.LargeTest; -import androidx.test.runner.AndroidJUnit4; - -import com.android.launcher3.BubbleTextView; -import com.android.launcher3.Launcher; -import com.android.launcher3.appprediction.PredictionRowView; -import com.android.launcher3.appprediction.PredictionUiStateManager; -import com.android.launcher3.appprediction.PredictionUiStateManager.Client; -import com.android.launcher3.model.AppLaunchTracker; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.ArrayList; -import java.util.List; - -@LargeTest -@RunWith(AndroidJUnit4.class) -public class AppPredictionsUITests extends AbstractQuickStepTest { - - private LauncherActivityInfo mSampleApp1; - private LauncherActivityInfo mSampleApp2; - private LauncherActivityInfo mSampleApp3; - - private AppPredictor.Callback mCallback; - - @Before - public void setUp() throws Exception { - super.setUp(); - - List activities = mTargetContext.getSystemService(LauncherApps.class) - .getActivityList(null, Process.myUserHandle()); - mSampleApp1 = activities.get(0); - mSampleApp2 = activities.get(1); - mSampleApp3 = activities.get(2); - - // Disable app tracker - AppLaunchTracker.INSTANCE.initializeForTesting(new AppLaunchTracker()); - PredictionUiStateManager.INSTANCE.initializeForTesting(null); - - mCallback = PredictionUiStateManager.INSTANCE.get(mTargetContext).appPredictorCallback( - Client.HOME); - - mDevice.setOrientationNatural(); - } - - @After - public void tearDown() throws Throwable { - AppLaunchTracker.INSTANCE.initializeForTesting(null); - PredictionUiStateManager.INSTANCE.initializeForTesting(null); - mDevice.unfreezeRotation(); - } - - /** - * Test that prediction UI is updated as soon as we get predictions from the system - */ - @Test - public void testPredictionExistsInAllApps() { - mLauncher.pressHome().switchToAllApps(); - - // Dispatch an update - sendPredictionUpdate(mSampleApp1, mSampleApp2); - // The first update should apply immediately. - waitForLauncherCondition("Predictions were not updated in loading state", - launcher -> getPredictedApp(launcher).size() == 2); - } - - /** - * Test that prediction update is deferred if it is already visible - */ - @Test - public void testPredictionsDeferredUntilHome() { - mDevice.pressHome(); - sendPredictionUpdate(mSampleApp1, mSampleApp2); - mLauncher.pressHome().switchToAllApps(); - waitForLauncherCondition("Predictions were not updated in loading state", - launcher -> getPredictedApp(launcher).size() == 2); - - // Update predictions while all-apps is visible - sendPredictionUpdate(mSampleApp1, mSampleApp2, mSampleApp3); - assertEquals(2, getFromLauncher(this::getPredictedApp).size()); - - // Go home and go back to all-apps - mLauncher.pressHome().switchToAllApps(); - assertEquals(3, getFromLauncher(this::getPredictedApp).size()); - } - - @Test - public void testPredictionsDisabled() { - mDevice.pressHome(); - sendPredictionUpdate(); - mLauncher.pressHome().switchToAllApps(); - - waitForLauncherCondition("Predictions were not updated in loading state", - launcher -> launcher.getAppsView().getFloatingHeaderView() - .findFixedRowByType(PredictionRowView.class).getVisibility() == View.GONE); - assertFalse(PredictionUiStateManager.INSTANCE.get(mTargetContext) - .getCurrentState().isEnabled); - } - - public ArrayList getPredictedApp(Launcher launcher) { - PredictionRowView container = launcher.getAppsView().getFloatingHeaderView() - .findFixedRowByType(PredictionRowView.class); - - ArrayList predictedAppViews = new ArrayList<>(); - for (int i = 0; i < container.getChildCount(); i++) { - View view = container.getChildAt(i); - if (view instanceof BubbleTextView && view.getVisibility() == View.VISIBLE) { - predictedAppViews.add((BubbleTextView) view); - } - } - return predictedAppViews; - } - - private void sendPredictionUpdate(LauncherActivityInfo... activities) { - getOnUiThread(() -> { - List targets = new ArrayList<>(activities.length); - for (LauncherActivityInfo info : activities) { - ComponentName cn = info.getComponentName(); - AppTarget target = new AppTarget.Builder( - new AppTargetId("app:" + cn), cn.getPackageName(), info.getUser()) - .setClassName(cn.getClassName()) - .build(); - targets.add(target); - } - mCallback.onTargetsAvailable(targets); - return null; - }); - } -} diff --git a/res/values/config.xml b/res/values/config.xml index 75fcc907db..dc8bdffca3 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -70,6 +70,7 @@ + diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index e49c4559f0..1123c79b37 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -119,7 +119,6 @@ import com.android.launcher3.logger.LauncherAtom; import com.android.launcher3.logging.FileLog; import com.android.launcher3.logging.StatsLogManager; import com.android.launcher3.logging.UserEventDispatcher; -import com.android.launcher3.model.AppLaunchTracker; import com.android.launcher3.model.BgDataModel.Callbacks; import com.android.launcher3.model.ModelWriter; import com.android.launcher3.model.data.AppInfo; @@ -911,14 +910,12 @@ public class Launcher extends StatefulActivity implements Launche logStopAndResume(Action.Command.RESUME); getUserEventDispatcher().startSession(); - AppLaunchTracker.INSTANCE.get(this).onReturnedToHome(); - // Process any items that were added while Launcher was away. InstallShortcutReceiver.disableAndFlushInstallQueue( InstallShortcutReceiver.FLAG_ACTIVITY_PAUSED, this); // Refresh shortcuts if the permission changed. - mModel.refreshShortcutsIfRequired(); + mModel.validateModelDataOnResume(); // Set the notification listener and fetch updated notifications when we resume NotificationListener.setNotificationsChangedListener(mPopupDataProvider); diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java index 53e5274c0c..e3fe87d1e9 100644 --- a/src/com/android/launcher3/LauncherAppState.java +++ b/src/com/android/launcher3/LauncherAppState.java @@ -128,7 +128,7 @@ public class LauncherAppState { mInvariantDeviceProfile = InvariantDeviceProfile.INSTANCE.get(context); mIconCache = new IconCache(mContext, mInvariantDeviceProfile, iconCacheFileName); mWidgetCache = new WidgetPreviewLoader(mContext, mIconCache); - mModel = new LauncherModel(this, mIconCache, AppFilter.newInstance(mContext)); + mModel = new LauncherModel(context, this, mIconCache, AppFilter.newInstance(mContext)); mPredictionModel = PredictionModel.newInstance(mContext); } @@ -157,6 +157,7 @@ public class LauncherAppState { * Call from Application.onTerminate(), which is not guaranteed to ever be called. */ public void onTerminate() { + mModel.destroy(); if (mModelChangeReceiver != null) { mContext.unregisterReceiver(mModelChangeReceiver); } diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index ff4b545a06..e2568d5ef6 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -20,7 +20,6 @@ import static com.android.launcher3.LauncherAppState.ACTION_FORCE_ROLOAD; import static com.android.launcher3.config.FeatureFlags.IS_STUDIO_BUILD; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; -import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission; import android.content.Context; import android.content.Intent; @@ -46,6 +45,7 @@ import com.android.launcher3.model.BgDataModel.Callbacks; import com.android.launcher3.model.CacheDataUpdatedTask; import com.android.launcher3.model.LoaderResults; import com.android.launcher3.model.LoaderTask; +import com.android.launcher3.model.ModelDelegate; import com.android.launcher3.model.ModelWriter; import com.android.launcher3.model.PackageInstallStateChangedTask; import com.android.launcher3.model.PackageUpdatedTask; @@ -112,20 +112,22 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi */ private final BgDataModel mBgDataModel = new BgDataModel(); + private final ModelDelegate mModelDelegate; + // Runnable to check if the shortcuts permission has changed. - private final Runnable mShortcutPermissionCheckRunnable = new Runnable() { + private final Runnable mDataValidationCheck = new Runnable() { @Override public void run() { - if (mModelLoaded && hasShortcutsPermission(mApp.getContext()) - != mBgAllAppsList.hasShortcutHostPermission()) { - forceReload(); + if (mModelLoaded) { + mModelDelegate.validateData(); } } }; - LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter) { + LauncherModel(Context context, LauncherAppState app, IconCache iconCache, AppFilter appFilter) { mApp = app; mBgAllAppsList = new AllAppsList(iconCache, appFilter); + mModelDelegate = ModelDelegate.newInstance(context, app, mBgAllAppsList, mBgDataModel); } /** @@ -217,6 +219,13 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi } } + /** + * Called when the model is destroyed + */ + public void destroy() { + MODEL_EXECUTOR.execute(mModelDelegate::destroy); + } + public void onBroadcastIntent(Intent intent) { if (DEBUG_RECEIVER) Log.d(TAG, "onReceive intent=" + intent); final String action = intent.getAction(); @@ -372,7 +381,8 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi public void startLoaderForResults(LoaderResults results) { synchronized (mLock) { stopLoader(); - mLoaderTask = new LoaderTask(mApp, mBgAllAppsList, mBgDataModel, results); + mLoaderTask = new LoaderTask( + mApp, mBgAllAppsList, mBgDataModel, mModelDelegate, results); // Always post the loader task, instead of running directly (even on same thread) so // that we exit any nested synchronized blocks @@ -491,9 +501,9 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi * Current implementation simply reloads the workspace, but it can be optimized to * use partial updates similar to {@link UserCache} */ - public void refreshShortcutsIfRequired() { - MODEL_EXECUTOR.getHandler().removeCallbacks(mShortcutPermissionCheckRunnable); - MODEL_EXECUTOR.post(mShortcutPermissionCheckRunnable); + public void validateModelDataOnResume() { + MODEL_EXECUTOR.getHandler().removeCallbacks(mDataValidationCheck); + MODEL_EXECUTOR.post(mDataValidationCheck); } /** diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java index a424f8458a..d8eb838ed5 100644 --- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java +++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java @@ -74,6 +74,7 @@ import com.android.launcher3.model.BgDataModel; import com.android.launcher3.model.BgDataModel.Callbacks; import com.android.launcher3.model.LoaderResults; import com.android.launcher3.model.LoaderTask; +import com.android.launcher3.model.ModelDelegate; import com.android.launcher3.model.WidgetItem; import com.android.launcher3.model.WidgetsModel; import com.android.launcher3.model.data.AppInfo; @@ -579,7 +580,7 @@ public class LauncherPreviewRenderer { private final FutureTask mTask = new FutureTask<>(this); WorkspaceItemsInfoFromPreviewFetcher(LauncherAppState app) { - super(app, null, new BgDataModel(), null); + super(app, null, new BgDataModel(), new ModelDelegate(), null); } @Override diff --git a/src/com/android/launcher3/model/AppLaunchTracker.java b/src/com/android/launcher3/model/AppLaunchTracker.java deleted file mode 100644 index a93c0dd77c..0000000000 --- a/src/com/android/launcher3/model/AppLaunchTracker.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.launcher3.model; - -import static com.android.launcher3.util.MainThreadInitializedObject.forOverride; - -import com.android.launcher3.R; -import com.android.launcher3.util.MainThreadInitializedObject; -import com.android.launcher3.util.ResourceBasedOverride; - -/** - * Callback for receiving various app launch events - */ -public class AppLaunchTracker implements ResourceBasedOverride { - - public static final MainThreadInitializedObject INSTANCE = - forOverride(AppLaunchTracker.class, R.string.app_launch_tracker_class); - - public void onReturnedToHome() { } -} diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java index dfdc1387ff..6158a9c6b4 100644 --- a/src/com/android/launcher3/model/BgDataModel.java +++ b/src/com/android/launcher3/model/BgDataModel.java @@ -108,13 +108,6 @@ public class BgDataModel { */ public final ArrayList cachedPredictedItems = new ArrayList<>(); - /** - * @see Callbacks#FLAG_HAS_SHORTCUT_PERMISSION - * @see Callbacks#FLAG_QUIET_MODE_ENABLED - * @see Callbacks#FLAG_QUIET_MODE_CHANGE_PERMISSION - */ - public int flags; - /** * Maps all launcher activities to counts of their shortcuts. */ diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java index bea00869ee..b0679095f3 100644 --- a/src/com/android/launcher3/model/LoaderTask.java +++ b/src/com/android/launcher3/model/LoaderTask.java @@ -109,6 +109,7 @@ public class LoaderTask implements Runnable { protected final LauncherAppState mApp; private final AllAppsList mBgAllAppsList; protected final BgDataModel mBgDataModel; + private final ModelDelegate mModelDelegate; private FirstScreenBroadcast mFirstScreenBroadcast; @@ -128,10 +129,11 @@ public class LoaderTask implements Runnable { private boolean mStopped; public LoaderTask(LauncherAppState app, AllAppsList bgAllAppsList, BgDataModel dataModel, - LoaderResults results) { + ModelDelegate modelDelegate, LoaderResults results) { mApp = app; mBgAllAppsList = bgAllAppsList; mBgDataModel = dataModel; + mModelDelegate = modelDelegate; mResults = results; mLauncherApps = mApp.getContext().getSystemService(LauncherApps.class); @@ -767,6 +769,9 @@ public class LoaderTask implements Runnable { IOUtils.closeSilently(c); } + // Load delegate items + mModelDelegate.loadItems(); + // Break early if we've stopped loading if (mStopped) { mBgDataModel.clear(); diff --git a/src/com/android/launcher3/model/ModelDelegate.java b/src/com/android/launcher3/model/ModelDelegate.java new file mode 100644 index 0000000000..ce4eed5bbd --- /dev/null +++ b/src/com/android/launcher3/model/ModelDelegate.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.model; + +import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission; + +import android.content.Context; + +import androidx.annotation.WorkerThread; + +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.R; +import com.android.launcher3.util.ResourceBasedOverride; + +/** + * Class to extend LauncherModel functionality to provide extra data + */ +public class ModelDelegate implements ResourceBasedOverride { + + /** + * Creates and initializes a new instance of the delegate + */ + public static ModelDelegate newInstance( + Context context, LauncherAppState app, AllAppsList appsList, BgDataModel dataModel) { + ModelDelegate delegate = Overrides.getObject( + ModelDelegate.class, context, R.string.model_delegate_class); + + delegate.mApp = app; + delegate.mAppsList = appsList; + delegate.mDataModel = dataModel; + return delegate; + } + + protected LauncherAppState mApp; + protected AllAppsList mAppsList; + protected BgDataModel mDataModel; + + public ModelDelegate() { } + + /** + * Called periodically to validate and update any data + */ + @WorkerThread + public void validateData() { + if (hasShortcutsPermission(mApp.getContext()) + != mAppsList.hasShortcutHostPermission()) { + mApp.getModel().forceReload(); + } + } + + /** + * Load delegate items if any in the data model + */ + @WorkerThread + public void loadItems() { } + + /** + * Called when the delegate is no loner needed + */ + @WorkerThread + public void destroy() { } +} diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java index 858e183ab7..201218b79a 100644 --- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java +++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java @@ -53,7 +53,6 @@ import com.android.launcher3.LauncherSettings; import com.android.launcher3.LauncherState; import com.android.launcher3.Utilities; import com.android.launcher3.common.WidgetUtils; -import com.android.launcher3.model.AppLaunchTracker; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.statemanager.StateManager; import com.android.launcher3.tapl.LauncherInstrumentation; @@ -272,8 +271,6 @@ public abstract class AbstractLauncherUiTest { } mLauncherPid = 0; - // Disable app tracker - AppLaunchTracker.INSTANCE.initializeForTesting(new AppLaunchTracker()); mTargetContext = InstrumentationRegistry.getTargetContext(); mTargetPackage = mTargetContext.getPackageName(); From eaf7a955467a5d189b982ee436a8eefaa30e3006 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Wed, 29 Jul 2020 16:54:20 -0700 Subject: [PATCH 0068/1707] Changing minimum supported Launcher version to 26 Change-Id: I49fcf874430ac53c3246371e179fbd828e14e4da --- Android.mk | 6 +- AndroidManifest-common.xml | 18 --- AndroidManifest.xml | 2 +- .../QuickstepAppTransitionManagerImpl.java | 3 - res/animator-v23/discovery_bounce.xml | 43 ------- res/animator/discovery_bounce.xml | 40 ++++--- .../all_apps_bg_hand_fill.xml | 0 .../all_apps_bg_hand_fill_dark.xml | 0 .../ic_deepshortcut_placeholder.xml | 20 ---- res/drawable-v26/ic_launcher_home.xml | 21 ---- .../ic_block_shadow.xml | 0 res/drawable/ic_deepshortcut_placeholder.xml | 14 +-- res/drawable/ic_launcher_home.xml | 9 +- .../ic_remove_shadow.xml | 0 .../ic_setup_shadow.xml | 0 .../ic_uninstall_shadow.xml | 0 res/mipmap-hdpi/ic_launcher_home.png | Bin 236 -> 0 bytes res/mipmap-mdpi/ic_launcher_home.png | Bin 216 -> 0 bytes res/mipmap-xhdpi/ic_launcher_home.png | Bin 235 -> 0 bytes res/mipmap-xxhdpi/ic_launcher_home.png | Bin 266 -> 0 bytes .../styles.xml | 0 res/values-v22/styles.xml | 26 ---- res/values-v26/bools.xml | 23 ---- res/values-v26/styles.xml | 31 ----- res/values/bools.xml | 23 ---- res/values/colors.xml | 3 - res/values/drawables.xml | 21 ---- res/values/styles.xml | 16 +-- .../launcher3/AbstractFloatingView.java | 3 +- src/com/android/launcher3/CellLayout.java | 4 +- .../launcher3/InstallShortcutReceiver.java | 29 +---- src/com/android/launcher3/Launcher.java | 5 +- .../android/launcher3/LauncherAppState.java | 14 +-- .../android/launcher3/LauncherProvider.java | 1 - .../launcher3/MainProcessInitializer.java | 1 - src/com/android/launcher3/PagedView.java | 4 +- .../launcher3/SessionCommitReceiver.java | 111 +----------------- src/com/android/launcher3/Utilities.java | 24 ---- .../android/launcher3/dragndrop/DragView.java | 3 - .../android/launcher3/graphics/IconShape.java | 8 +- .../launcher3/model/PackageUpdatedTask.java | 10 +- .../android/launcher3/model/data/AppInfo.java | 3 +- .../launcher3/pm/InstallSessionHelper.java | 23 +++- .../pm/ShortcutConfigActivityInfo.java | 49 +++----- .../popup/PopupContainerWithArrow.java | 5 +- .../launcher3/settings/SettingsActivity.java | 9 -- .../launcher3/statemanager/StateManager.java | 5 +- .../AbstractStateChangeTouchController.java | 10 +- .../launcher3/util/PackageManagerHelper.java | 38 +----- .../launcher3/util/SystemUiController.java | 12 +- .../launcher3/views/FloatingIconView.java | 4 +- .../widget/LauncherAppWidgetHostView.java | 4 +- .../launcher3/widget/WidgetManagerHelper.java | 20 +--- .../dynamicui/WallpaperManagerCompat.java | 5 +- .../ui/widget/RequestPinItemTest.java | 4 - 55 files changed, 130 insertions(+), 597 deletions(-) delete mode 100644 res/animator-v23/discovery_bounce.xml rename res/{color-v24 => color}/all_apps_bg_hand_fill.xml (100%) rename res/{color-v24 => color}/all_apps_bg_hand_fill_dark.xml (100%) delete mode 100644 res/drawable-v26/ic_deepshortcut_placeholder.xml delete mode 100644 res/drawable-v26/ic_launcher_home.xml rename res/{drawable-v24 => drawable}/ic_block_shadow.xml (100%) rename res/{drawable-v24 => drawable}/ic_remove_shadow.xml (100%) rename res/{drawable-v24 => drawable}/ic_setup_shadow.xml (100%) rename res/{drawable-v24 => drawable}/ic_uninstall_shadow.xml (100%) delete mode 100644 res/mipmap-hdpi/ic_launcher_home.png delete mode 100644 res/mipmap-mdpi/ic_launcher_home.png delete mode 100644 res/mipmap-xhdpi/ic_launcher_home.png delete mode 100644 res/mipmap-xxhdpi/ic_launcher_home.png rename res/{values-night-v26 => values-night}/styles.xml (100%) delete mode 100644 res/values-v22/styles.xml delete mode 100644 res/values-v26/bools.xml delete mode 100644 res/values-v26/styles.xml delete mode 100644 res/values/bools.xml delete mode 100644 res/values/drawables.xml diff --git a/Android.mk b/Android.mk index 7805b32e8e..91ecccb1e4 100644 --- a/Android.mk +++ b/Android.mk @@ -48,7 +48,7 @@ LOCAL_PROTOC_FLAGS := --proto_path=$(LOCAL_PATH)/protos/ --proto_path=$(LOCAL_PA LOCAL_PROTO_JAVA_OUTPUT_PARAMS := enum_style=java LOCAL_SDK_VERSION := current -LOCAL_MIN_SDK_VERSION := 21 +LOCAL_MIN_SDK_VERSION := 26 LOCAL_MODULE := Launcher3CommonDepsLib LOCAL_PRIVILEGED_MODULE := true LOCAL_MANIFEST_FILE := AndroidManifest-common.xml @@ -77,7 +77,7 @@ LOCAL_PROGUARD_FLAG_FILES := proguard.flags LOCAL_PROGUARD_ENABLED := disabled LOCAL_SDK_VERSION := current -LOCAL_MIN_SDK_VERSION := 21 +LOCAL_MIN_SDK_VERSION := 26 LOCAL_PACKAGE_NAME := Launcher3 LOCAL_PRIVILEGED_MODULE := true LOCAL_SYSTEM_EXT_MODULE := true @@ -108,7 +108,7 @@ LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/go/res LOCAL_PROGUARD_FLAG_FILES := proguard.flags LOCAL_SDK_VERSION := current -LOCAL_MIN_SDK_VERSION := 21 +LOCAL_MIN_SDK_VERSION := 26 LOCAL_PACKAGE_NAME := Launcher3Go LOCAL_PRIVILEGED_MODULE := true LOCAL_SYSTEM_EXT_MODULE := true diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml index 19a16e341a..97e3786a36 100644 --- a/AndroidManifest-common.xml +++ b/AndroidManifest-common.xml @@ -29,12 +29,6 @@ at compile time. Note that the components defined in AndroidManifest.xml are also required, with some minor changed based on the derivative app. --> - @@ -79,17 +73,6 @@ android:restoreAnyVersion="true" android:supportsRtl="true" > - - - - - - - diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 4664c9315a..97bce9c1d7 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -20,7 +20,7 @@ - + - - - - - - - - - diff --git a/res/animator/discovery_bounce.xml b/res/animator/discovery_bounce.xml index f02ebdbace..f5548537b1 100644 --- a/res/animator/discovery_bounce.xml +++ b/res/animator/discovery_bounce.xml @@ -16,20 +16,28 @@ ** limitations under the License. */ --> - - + - - + android:valueType="floatType"> + + + + + + + diff --git a/res/color-v24/all_apps_bg_hand_fill.xml b/res/color/all_apps_bg_hand_fill.xml similarity index 100% rename from res/color-v24/all_apps_bg_hand_fill.xml rename to res/color/all_apps_bg_hand_fill.xml diff --git a/res/color-v24/all_apps_bg_hand_fill_dark.xml b/res/color/all_apps_bg_hand_fill_dark.xml similarity index 100% rename from res/color-v24/all_apps_bg_hand_fill_dark.xml rename to res/color/all_apps_bg_hand_fill_dark.xml diff --git a/res/drawable-v26/ic_deepshortcut_placeholder.xml b/res/drawable-v26/ic_deepshortcut_placeholder.xml deleted file mode 100644 index 3fa85065bd..0000000000 --- a/res/drawable-v26/ic_deepshortcut_placeholder.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - diff --git a/res/drawable-v26/ic_launcher_home.xml b/res/drawable-v26/ic_launcher_home.xml deleted file mode 100644 index 7038775f35..0000000000 --- a/res/drawable-v26/ic_launcher_home.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - diff --git a/res/drawable-v24/ic_block_shadow.xml b/res/drawable/ic_block_shadow.xml similarity index 100% rename from res/drawable-v24/ic_block_shadow.xml rename to res/drawable/ic_block_shadow.xml diff --git a/res/drawable/ic_deepshortcut_placeholder.xml b/res/drawable/ic_deepshortcut_placeholder.xml index 85a9694501..3fa85065bd 100644 --- a/res/drawable/ic_deepshortcut_placeholder.xml +++ b/res/drawable/ic_deepshortcut_placeholder.xml @@ -1,5 +1,6 @@ - - - - - + + + + diff --git a/res/drawable/ic_launcher_home.xml b/res/drawable/ic_launcher_home.xml index a6f2519954..7038775f35 100644 --- a/res/drawable/ic_launcher_home.xml +++ b/res/drawable/ic_launcher_home.xml @@ -13,6 +13,9 @@ See the License for the specific language governing permissions and limitations under the License. --> - + + + + + + diff --git a/res/drawable-v24/ic_remove_shadow.xml b/res/drawable/ic_remove_shadow.xml similarity index 100% rename from res/drawable-v24/ic_remove_shadow.xml rename to res/drawable/ic_remove_shadow.xml diff --git a/res/drawable-v24/ic_setup_shadow.xml b/res/drawable/ic_setup_shadow.xml similarity index 100% rename from res/drawable-v24/ic_setup_shadow.xml rename to res/drawable/ic_setup_shadow.xml diff --git a/res/drawable-v24/ic_uninstall_shadow.xml b/res/drawable/ic_uninstall_shadow.xml similarity index 100% rename from res/drawable-v24/ic_uninstall_shadow.xml rename to res/drawable/ic_uninstall_shadow.xml diff --git a/res/mipmap-hdpi/ic_launcher_home.png b/res/mipmap-hdpi/ic_launcher_home.png deleted file mode 100644 index d068d92361a06550fb1bcdf36fbc4bf56242d54e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 236 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~M1W6-E06|){rmU-|Ns9C14G(O z28NkujJG~Lkd;*dlwd3g@(X5gcy=QV$Z7I)aSW-r_4dL>z6Jx1mc(q=`~Q1QL!@ST zvx^@#{d2kXq4bSSI^01QuQhjd%Q~fAzm?V=urg{pU$2PM(daz_S{a5;E6(U}afk}8 z47;BZ=+{T*7@8zVPfLG>P=&IiV(vR zKTbn8<}=KmM{as0#4%>w<_hqP=C9KIV#aG9#kVxgf8%cH=Mf+Wdb;|#taD0e0sys( BO-=v+ diff --git a/res/mipmap-xhdpi/ic_launcher_home.png b/res/mipmap-xhdpi/ic_launcher_home.png deleted file mode 100644 index 8b2671bdd80f36d23f2f3d45842dfb78092feec3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 235 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeE3?v1%WpM*3i2$DvS0D`p`}gnv|Ns9P28Ohm z3=A{R7;k-gASrHabI#5x}f zrrE1+d3>F*Mn=YdO~U8??v%F<%Vx|LtWoD~o66j6_Tr_4iL*dP62FU^fCO{!!Tn9@ cJd9I<>I_fVT@aRE69aOyr>mdKI;Vst0EsD5ZU6uP diff --git a/res/mipmap-xxhdpi/ic_launcher_home.png b/res/mipmap-xxhdpi/ic_launcher_home.png deleted file mode 100644 index 43d8b7d886c9a928c666b0af551a1ab35c365aae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 266 zcmeAS@N?(olHy`uVBq!ia0vp^6F``S8Ays|{O<-*5&=FTu0R?H_V3^S|Ns9p3=C;A z85m}sG2Z(0Kvq@-P=c`}$S;_|;n|HeAZLN6i(^Oybs)2ELra~Vb3uF0>TroB60O9PkFq+(Sw=ihUjiyf9KJfCE$ zyPMIKX@$*_(+#(pT#hU#bue`n6A-xYec^LvEq*~k!LJttIo_Q0KfmWG6Ua-Ru6{1- HoD!M<4rE|^ diff --git a/res/values-night-v26/styles.xml b/res/values-night/styles.xml similarity index 100% rename from res/values-night-v26/styles.xml rename to res/values-night/styles.xml diff --git a/res/values-v22/styles.xml b/res/values-v22/styles.xml deleted file mode 100644 index f86db7a493..0000000000 --- a/res/values-v22/styles.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/res/values-v26/bools.xml b/res/values-v26/bools.xml deleted file mode 100644 index ad8c7a1865..0000000000 --- a/res/values-v26/bools.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - true - - false - \ No newline at end of file diff --git a/res/values-v26/styles.xml b/res/values-v26/styles.xml deleted file mode 100644 index d2f0802fa5..0000000000 --- a/res/values-v26/styles.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - diff --git a/res/values/bools.xml b/res/values/bools.xml deleted file mode 100644 index bc2c678874..0000000000 --- a/res/values/bools.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - false - - true - \ No newline at end of file diff --git a/res/values/colors.xml b/res/values/colors.xml index 043ad9a9d5..f56fbaaace 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -35,9 +35,6 @@ #E0E0E0 - #E5E5E5 - #9AA0A6 - #A0C2F9 #6DA1FF #FFFFFFFF diff --git a/res/values/drawables.xml b/res/values/drawables.xml deleted file mode 100644 index 9c57ec1214..0000000000 --- a/res/values/drawables.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - @drawable/ic_setting - @drawable/ic_remove_no_shadow - @drawable/ic_uninstall_no_shadow - @drawable/ic_block_no_shadow - \ No newline at end of file diff --git a/res/values/styles.xml b/res/values/styles.xml index 3b9532eeee..fd3d873635 100644 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -143,7 +143,7 @@ @@ -171,14 +171,16 @@ ?attr/workspaceTextColor - + + - - + + + + + + + \ No newline at end of file diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java index 09a3cfd184..cbe0eb0795 100644 --- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java @@ -264,6 +264,11 @@ public abstract class BaseQuickstepLauncher extends Launcher return mTaskbarStateHandler; } + @Override + public boolean isViewInTaskbar(View v) { + return mTaskbarController != null && mTaskbarController.isViewInTaskbar(v); + } + @Override public void useFadeOutAnimationForLauncherStart(CancellationSignal signal) { QuickstepAppTransitionManagerImpl appTransitionManager = @@ -350,6 +355,10 @@ public abstract class BaseQuickstepLauncher extends Launcher // populating workspace. // TODO: Find a better place for this WellbeingModel.INSTANCE.get(this); + + if (mTaskbarController != null) { + mTaskbarController.onHotseatUpdated(); + } } @Override diff --git a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java index 36c8bb8b99..876cabc61b 100644 --- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java +++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java @@ -202,9 +202,10 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans } @Override - public boolean supportsAdaptiveIconAnimation() { + public boolean supportsAdaptiveIconAnimation(View clickedView) { return hasControlRemoteAppTransitionPermission() - && FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM.get(); + && FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM.get() + && !mLauncher.isViewInTaskbar(clickedView); } /** @@ -972,9 +973,12 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans launcherIsATargetWithMode(appTargets, MODE_CLOSING); final boolean launchingFromRecents = isLaunchingFromRecents(mV, appTargets); + final boolean launchingFromTaskbar = mLauncher.isViewInTaskbar(mV); if (launchingFromRecents) { composeRecentsLaunchAnimator(anim, mV, appTargets, wallpaperTargets, launcherClosing); + } else if (launchingFromTaskbar) { + // TODO } else { composeIconLaunchAnimator(anim, mV, appTargets, wallpaperTargets, launcherClosing); diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java index b2de4c95ad..aa6601b8c8 100644 --- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java +++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java @@ -37,7 +37,6 @@ import com.android.launcher3.DragSource; import com.android.launcher3.DropTarget; import com.android.launcher3.Hotseat; import com.android.launcher3.InvariantDeviceProfile; -import com.android.launcher3.Launcher; import com.android.launcher3.LauncherSettings; import com.android.launcher3.R; import com.android.launcher3.Utilities; @@ -75,7 +74,7 @@ public class HotseatPredictionController implements DragController.DragListener, private int mHotSeatItemsCount; - private Launcher mLauncher; + private QuickstepLauncher mLauncher; private final Hotseat mHotseat; private List mPredictedItems = Collections.emptyList(); @@ -108,7 +107,7 @@ public class HotseatPredictionController implements DragController.DragListener, return true; }; - public HotseatPredictionController(Launcher launcher) { + public HotseatPredictionController(QuickstepLauncher launcher) { mLauncher = launcher; mHotseat = launcher.getHotseat(); mHotSeatItemsCount = mLauncher.getDeviceProfile().inv.numHotseatIcons; @@ -229,6 +228,10 @@ public class HotseatPredictionController implements DragController.DragListener, } else { if (callback != null) callback.run(); } + + if (mLauncher.getTaskbarController() != null) { + mLauncher.getTaskbarController().onHotseatUpdated(); + } } /** @@ -242,6 +245,10 @@ public class HotseatPredictionController implements DragController.DragListener, * start and pauses predicted apps update on the hotseat */ public void setPauseUIUpdate(boolean paused) { + if (mLauncher.getTaskbarController() != null) { + // Taskbar is present, always allow updates since hotseat is still visible. + return; + } mUIUpdatePaused = paused; if (!paused) { fillGapsWithPrediction(); diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java index bdf7f8ad08..f91bfb78fc 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java @@ -26,6 +26,7 @@ import android.animation.Animator; import android.graphics.PixelFormat; import android.graphics.Point; import android.view.Gravity; +import android.view.View; import android.view.WindowManager; import androidx.annotation.Nullable; @@ -36,7 +37,9 @@ import com.android.launcher3.QuickstepAppTransitionManagerImpl; import com.android.launcher3.R; import com.android.launcher3.anim.AlphaUpdateListener; import com.android.launcher3.anim.PendingAnimation; +import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.states.StateAnimationConfig; +import com.android.launcher3.touch.ItemClickHandler; import com.android.quickstep.AnimatedFloat; import com.android.systemui.shared.system.WindowManagerWrapper; @@ -55,6 +58,7 @@ public class TaskbarController { private final Point mTaskbarSize; private final TaskbarStateHandler mTaskbarStateHandler; private final TaskbarVisibilityController mTaskbarVisibilityController; + private final TaskbarHotseatController mHotseatController; // Initialized in init(). private WindowManager.LayoutParams mWindowLayoutParams; @@ -64,12 +68,15 @@ public class TaskbarController { mLauncher = launcher; mTaskbarContainerView = taskbarContainerView; mTaskbarView = mTaskbarContainerView.findViewById(R.id.taskbar_view); + mTaskbarView.setCallbacks(createTaskbarViewCallbacks()); mWindowManager = mLauncher.getWindowManager(); mTaskbarSize = new Point(MATCH_PARENT, mLauncher.getResources().getDimensionPixelSize(R.dimen.taskbar_size)); mTaskbarStateHandler = mLauncher.getTaskbarStateHandler(); mTaskbarVisibilityController = new TaskbarVisibilityController(mLauncher, createTaskbarVisibilityControllerCallbacks()); + mHotseatController = new TaskbarHotseatController(mLauncher, + createTaskbarHotseatControllerCallbacks()); } private TaskbarVisibilityControllerCallbacks createTaskbarVisibilityControllerCallbacks() { @@ -87,13 +94,33 @@ public class TaskbarController { }; } + private TaskbarViewCallbacks createTaskbarViewCallbacks() { + return new TaskbarViewCallbacks() { + @Override + public View.OnClickListener getItemOnClickListener() { + return ItemClickHandler.INSTANCE; + } + }; + } + + private TaskbarHotseatControllerCallbacks createTaskbarHotseatControllerCallbacks() { + return new TaskbarHotseatControllerCallbacks() { + @Override + public void updateHotseatItems(ItemInfo[] hotseatItemInfos) { + mTaskbarView.updateHotseatItems(hotseatItemInfos); + } + }; + } + /** * Initializes the Taskbar, including adding it to the screen. */ public void init() { + mTaskbarView.init(mHotseatController.getNumHotseatIcons()); addToWindowManager(); mTaskbarStateHandler.setTaskbarCallbacks(createTaskbarStateHandlerCallbacks()); mTaskbarVisibilityController.init(); + mHotseatController.init(); } private TaskbarStateHandlerCallbacks createTaskbarStateHandlerCallbacks() { @@ -109,9 +136,11 @@ public class TaskbarController { * Removes the Taskbar from the screen, and removes any obsolete listeners etc. */ public void cleanup() { + mTaskbarView.cleanup(); removeFromWindowManager(); mTaskbarStateHandler.setTaskbarCallbacks(null); mTaskbarVisibilityController.cleanup(); + mHotseatController.cleanup(); } private void removeFromWindowManager() { @@ -190,6 +219,20 @@ public class TaskbarController { mTaskbarVisibilityController.animateToVisibilityForIme(isImeVisible ? 0 : 1); } + /** + * Should be called when one or more items in the Hotseat have changed. + */ + public void onHotseatUpdated() { + mHotseatController.onHotseatUpdated(); + } + + /** + * @return Whether the given View is in the same window as Taskbar. + */ + public boolean isViewInTaskbar(View v) { + return mTaskbarContainerView.getWindowId().equals(v.getWindowId()); + } + /** * Contains methods that TaskbarStateHandler can call to interface with TaskbarController. */ @@ -205,4 +248,18 @@ public class TaskbarController { void updateTaskbarBackgroundAlpha(float alpha); void updateTaskbarVisibilityAlpha(float alpha); } + + /** + * Contains methods that TaskbarView can call to interface with TaskbarController. + */ + protected interface TaskbarViewCallbacks { + View.OnClickListener getItemOnClickListener(); + } + + /** + * Contains methods that TaskbarHotseatController can call to interface with TaskbarController. + */ + protected interface TaskbarHotseatControllerCallbacks { + void updateHotseatItems(ItemInfo[] hotseatItemInfos); + } } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarHotseatController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarHotseatController.java new file mode 100644 index 0000000000..4dc051aaa9 --- /dev/null +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarHotseatController.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.taskbar; + +import android.view.View; + +import com.android.launcher3.BaseQuickstepLauncher; +import com.android.launcher3.CellLayout; +import com.android.launcher3.DropTarget; +import com.android.launcher3.Hotseat; +import com.android.launcher3.ShortcutAndWidgetContainer; +import com.android.launcher3.dragndrop.DragController; +import com.android.launcher3.dragndrop.DragOptions; +import com.android.launcher3.model.data.ItemInfo; + +/** + * Works with TaskbarController to update the TaskbarView's Hotseat items. + */ +public class TaskbarHotseatController { + + private final BaseQuickstepLauncher mLauncher; + private final Hotseat mHotseat; + private final TaskbarController.TaskbarHotseatControllerCallbacks mTaskbarCallbacks; + private final int mNumHotseatIcons; + + private final DragController.DragListener mDragListener = new DragController.DragListener() { + @Override + public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) { + } + + @Override + public void onDragEnd() { + onHotseatUpdated(); + } + }; + + public TaskbarHotseatController(BaseQuickstepLauncher launcher, + TaskbarController.TaskbarHotseatControllerCallbacks taskbarCallbacks) { + mLauncher = launcher; + mHotseat = mLauncher.getHotseat(); + mTaskbarCallbacks = taskbarCallbacks; + mNumHotseatIcons = mLauncher.getDeviceProfile().inv.numHotseatIcons; + } + + protected void init() { + mLauncher.getDragController().addDragListener(mDragListener); + } + + protected void cleanup() { + mLauncher.getDragController().removeDragListener(mDragListener); + } + + /** + * Called when any Hotseat item changes, and reports the new list of items to TaskbarController. + */ + protected void onHotseatUpdated() { + ShortcutAndWidgetContainer shortcutsAndWidgets = mHotseat.getShortcutsAndWidgets(); + ItemInfo[] hotseatItemInfos = new ItemInfo[mNumHotseatIcons]; + for (int i = 0; i < shortcutsAndWidgets.getChildCount(); i++) { + View child = shortcutsAndWidgets.getChildAt(i); + Object tag = shortcutsAndWidgets.getChildAt(i).getTag(); + if (tag instanceof ItemInfo) { + ItemInfo itemInfo = (ItemInfo) tag; + CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); + // Since the hotseat might be laid out vertically or horizontally, use whichever + // index is higher. + hotseatItemInfos[Math.max(lp.cellX, lp.cellY)] = itemInfo; + } + } + + mTaskbarCallbacks.updateHotseatItems(hotseatItemInfos); + } + + protected int getNumHotseatIcons() { + return mNumHotseatIcons; + } +} diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java index bf6e946f0d..0d82810935 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java @@ -16,19 +16,35 @@ package com.android.launcher3.taskbar; import android.content.Context; +import android.content.res.Resources; import android.graphics.drawable.ColorDrawable; import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; import android.widget.LinearLayout; +import androidx.annotation.LayoutRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.launcher3.BubbleTextView; +import com.android.launcher3.R; +import com.android.launcher3.model.data.ItemInfo; +import com.android.launcher3.model.data.WorkspaceItemInfo; + /** * Hosts the Taskbar content such as Hotseat and Recent Apps. Drawn on top of other apps. */ public class TaskbarView extends LinearLayout { private final ColorDrawable mBackgroundDrawable; + private final int mItemMarginLeftRight; + + // Initialized in init(). + private int mHotseatStartIndex; + private int mHotseatEndIndex; + + private TaskbarController.TaskbarViewCallbacks mControllerCallbacks; public TaskbarView(@NonNull Context context) { this(context, null); @@ -46,7 +62,24 @@ public class TaskbarView extends LinearLayout { public TaskbarView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); + + Resources resources = getResources(); mBackgroundDrawable = (ColorDrawable) getBackground(); + mItemMarginLeftRight = resources.getDimensionPixelSize(R.dimen.taskbar_icon_spacing); + } + + protected void setCallbacks(TaskbarController.TaskbarViewCallbacks taskbarViewCallbacks) { + mControllerCallbacks = taskbarViewCallbacks; + } + + protected void init(int numHotseatIcons) { + mHotseatStartIndex = 0; + mHotseatEndIndex = mHotseatStartIndex + numHotseatIcons - 1; + updateHotseatItems(new ItemInfo[numHotseatIcons]); + } + + protected void cleanup() { + removeAllViews(); } /** @@ -56,4 +89,47 @@ public class TaskbarView extends LinearLayout { public void setBackgroundAlpha(float alpha) { mBackgroundDrawable.setAlpha((int) (alpha * 255)); } + + /** + * Inflates/binds the Hotseat views to show in the Taskbar given their ItemInfos. + */ + protected void updateHotseatItems(ItemInfo[] hotseatItemInfos) { + for (int i = 0; i < hotseatItemInfos.length; i++) { + ItemInfo hotseatItemInfo = hotseatItemInfos[i]; + int hotseatIndex = mHotseatStartIndex + i; + View hotseatView = getChildAt(hotseatIndex); + + // Replace any Hotseat views with the appropriate type if it's not already that type. + final int expectedLayoutResId; + if (hotseatItemInfo != null && hotseatItemInfo.isPredictedItem()) { + expectedLayoutResId = R.layout.taskbar_predicted_app_icon; + } else { + expectedLayoutResId = R.layout.taskbar_app_icon; + } + if (hotseatView == null || hotseatView.getSourceLayoutResId() != expectedLayoutResId) { + removeView(hotseatView); + BubbleTextView btv = (BubbleTextView) inflate(expectedLayoutResId); + LayoutParams lp = new LayoutParams(btv.getIconSize(), btv.getIconSize()); + lp.setMargins(mItemMarginLeftRight, 0, mItemMarginLeftRight, 0); + hotseatView = btv; + addView(hotseatView, hotseatIndex, lp); + } + + // Apply the Hotseat ItemInfos, or hide the view if there is none for a given index. + if (hotseatView instanceof BubbleTextView + && hotseatItemInfo instanceof WorkspaceItemInfo) { + ((BubbleTextView) hotseatView).applyFromWorkspaceItem( + (WorkspaceItemInfo) hotseatItemInfo); + hotseatView.setVisibility(VISIBLE); + hotseatView.setOnClickListener(mControllerCallbacks.getItemOnClickListener()); + } else { + hotseatView.setVisibility(GONE); + hotseatView.setOnClickListener(null); + } + } + } + + private View inflate(@LayoutRes int layoutResId) { + return LayoutInflater.from(getContext()).inflate(layoutResId, this, false); + } } diff --git a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java index 597c17b4e2..22c4a7eba3 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java +++ b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java @@ -184,7 +184,10 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView implements } private int getOutlineOffsetY() { - return getPaddingTop() + mDeviceProfile.folderIconOffsetYPx; + if (mDisplay != DISPLAY_TASKBAR) { + return getPaddingTop() + mDeviceProfile.folderIconOffsetYPx; + } + return (getMeasuredHeight() / 2) - mNormalizedIconRadius; } private void drawEffect(Canvas canvas, boolean isBadged) { diff --git a/res/values/attrs.xml b/res/values/attrs.xml index 96c30b5f03..e593fb497d 100644 --- a/res/values/attrs.xml +++ b/res/values/attrs.xml @@ -57,6 +57,7 @@ + diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index ecda501f1f..140340af62 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -92,6 +92,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, private static final int DISPLAY_ALL_APPS = 1; private static final int DISPLAY_FOLDER = 2; private static final int DISPLAY_HERO_APP = 5; + protected static final int DISPLAY_TASKBAR = 6; private static final int[] STATE_PRESSED = new int[]{android.R.attr.state_pressed}; private static final float HIGHLIGHT_SCALE = 1.16f; @@ -140,7 +141,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, private Drawable mIcon; private boolean mCenterVertically; - private final int mDisplay; + protected final int mDisplay; private final CheckLongPressHelper mLongPressHelper; @@ -206,6 +207,8 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, defaultIconSize = grid.folderChildIconSizePx; } else if (mDisplay == DISPLAY_HERO_APP) { defaultIconSize = grid.allAppsIconSizePx; + } else if (mDisplay == DISPLAY_TASKBAR) { + defaultIconSize = grid.iconSizePx; } else { // widget_selection or shortcut_popup defaultIconSize = grid.iconSizePx; @@ -487,6 +490,10 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, * @param canvas The canvas to draw to. */ protected void drawDotIfNecessary(Canvas canvas) { + if (mDisplay == DISPLAY_TASKBAR) { + // TODO: support notification dots in Taskbar + return; + } if (!mForceHideDot && (hasDot() || mDotParams.scale > 0)) { getIconBounds(mDotParams.iconBounds); Utilities.scaleRectAboutCenter(mDotParams.iconBounds, diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 0274775244..2334267fd9 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -1917,6 +1917,13 @@ public class Launcher extends StatefulActivity implements Launche @Override public boolean startActivitySafely(View v, Intent intent, ItemInfo item) { + if (isViewInTaskbar(v)) { + // Start the activity without the hacky workarounds below, which assume the View was + // clicked when Launcher was resumed and will be hidden until Launcher is re-resumed + // (this isn't the case for Taskbar). + return super.startActivitySafely(v, intent, item); + } + if (!hasBeenResumed()) { // Workaround an issue where the WM launch animation is clobbered when finishing the // recents animation into launcher. Defer launching the activity until Launcher is @@ -2818,6 +2825,13 @@ public class Launcher extends StatefulActivity implements Launche .start(); } + /** + * @return Whether the View is in the same window as the Taskbar window. + */ + public boolean isViewInTaskbar(View v) { + return false; + } + private static class NonConfigInstance { public Configuration config; public Bitmap snapshot; diff --git a/src/com/android/launcher3/LauncherAppTransitionManager.java b/src/com/android/launcher3/LauncherAppTransitionManager.java index ac3ad9f8fd..0fa441a279 100644 --- a/src/com/android/launcher3/LauncherAppTransitionManager.java +++ b/src/com/android/launcher3/LauncherAppTransitionManager.java @@ -50,7 +50,7 @@ public class LauncherAppTransitionManager implements ResourceBasedOverride { return ActivityOptions.makeClipRevealAnimation(v, left, top, width, height); } - public boolean supportsAdaptiveIconAnimation() { + public boolean supportsAdaptiveIconAnimation(View clickedView) { return false; } diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java index 3122c68304..2647d6fec4 100644 --- a/src/com/android/launcher3/touch/ItemClickHandler.java +++ b/src/com/android/launcher3/touch/ItemClickHandler.java @@ -302,7 +302,7 @@ public class ItemClickHandler { intent.setPackage(null); } } - if (v != null && launcher.getAppTransitionManager().supportsAdaptiveIconAnimation()) { + if (v != null && launcher.getAppTransitionManager().supportsAdaptiveIconAnimation(v)) { // Preload the icon to reduce latency b/w swapping the floating view with the original. FloatingIconView.fetchIcon(launcher, v, item, true /* isOpening */); } From b11e4d517d5f80f26ceb22e79030aace712ff5e2 Mon Sep 17 00:00:00 2001 From: Tony Wickham Date: Fri, 22 Jan 2021 11:49:11 -0800 Subject: [PATCH 0481/1707] Delegate Taskbar touches to nearest view to ensure 48x48dp touch size In TaskbarView#onTouchEvent(), which is only reached if a Taskbar icon didn't already consuem the event, check each child to see if the event occurs within a 48x48dp bounding box, and delegate the event and subsequent events to it until UP or CANCEL. Bug: 171917176 Change-Id: I7afafe0835828ab9213ec6abfe4e88ad7b9af3c4 --- quickstep/res/values/dimens.xml | 1 + .../launcher3/taskbar/TaskbarView.java | 81 +++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml index f082b83c4a..39a7d09649 100644 --- a/quickstep/res/values/dimens.xml +++ b/quickstep/res/values/dimens.xml @@ -123,6 +123,7 @@ 48dp 32dp + 48dp 14dp diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java index 0d82810935..adcfaec102 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java @@ -17,10 +17,13 @@ package com.android.launcher3.taskbar; import android.content.Context; import android.content.res.Resources; +import android.graphics.RectF; import android.graphics.drawable.ColorDrawable; import android.util.AttributeSet; import android.view.LayoutInflater; +import android.view.MotionEvent; import android.view.View; +import android.view.ViewConfiguration; import android.widget.LinearLayout; import androidx.annotation.LayoutRes; @@ -39,6 +42,10 @@ public class TaskbarView extends LinearLayout { private final ColorDrawable mBackgroundDrawable; private final int mItemMarginLeftRight; + private final int mIconTouchSize; + private final int mTouchSlop; + private final RectF mTempDelegateBounds = new RectF(); + private final RectF mDelegateSlopBounds = new RectF(); // Initialized in init(). private int mHotseatStartIndex; @@ -46,6 +53,10 @@ public class TaskbarView extends LinearLayout { private TaskbarController.TaskbarViewCallbacks mControllerCallbacks; + // Delegate touches to the closest view if within mIconTouchSize. + private boolean mDelegateTargeted; + private View mDelegateView; + public TaskbarView(@NonNull Context context) { this(context, null); } @@ -66,6 +77,8 @@ public class TaskbarView extends LinearLayout { Resources resources = getResources(); mBackgroundDrawable = (ColorDrawable) getBackground(); mItemMarginLeftRight = resources.getDimensionPixelSize(R.dimen.taskbar_icon_spacing); + mIconTouchSize = resources.getDimensionPixelSize(R.dimen.taskbar_icon_touch_size); + mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); } protected void setCallbacks(TaskbarController.TaskbarViewCallbacks taskbarViewCallbacks) { @@ -129,6 +142,74 @@ public class TaskbarView extends LinearLayout { } } + @Override + public boolean onTouchEvent(MotionEvent event) { + boolean handled = delegateTouchIfNecessary(event); + return super.onTouchEvent(event) || handled; + } + + /** + * User touched the Taskbar background. Determine whether the touch is close enough to a view + * that we should forward the touches to it. + * @return Whether a delegate view was chosen and it handled the touch event. + */ + private boolean delegateTouchIfNecessary(MotionEvent event) { + final float x = event.getX(); + final float y = event.getY(); + if (mDelegateView == null && event.getAction() == MotionEvent.ACTION_DOWN) { + for (int i = 0; i < getChildCount(); i++) { + View child = getChildAt(i); + if (!child.isShown() || !child.isClickable()) { + continue; + } + int childCenterX = child.getLeft() + child.getWidth() / 2; + int childCenterY = child.getTop() + child.getHeight() / 2; + mTempDelegateBounds.set( + childCenterX - mIconTouchSize / 2f, + childCenterY - mIconTouchSize / 2f, + childCenterX + mIconTouchSize / 2f, + childCenterY + mIconTouchSize / 2f); + mDelegateTargeted = mTempDelegateBounds.contains(x, y); + if (mDelegateTargeted) { + mDelegateView = child; + mDelegateSlopBounds.set(mTempDelegateBounds); + mDelegateSlopBounds.inset(-mTouchSlop, -mTouchSlop); + break; + } + } + } + + boolean sendToDelegate = mDelegateTargeted; + boolean inBounds = true; + switch (event.getAction()) { + case MotionEvent.ACTION_MOVE: + inBounds = mDelegateSlopBounds.contains(x, y); + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + mDelegateTargeted = false; + break; + } + + boolean handled = false; + if (sendToDelegate) { + if (inBounds) { + // Offset event coordinates to be inside the target view + event.setLocation(mDelegateView.getWidth() / 2f, mDelegateView.getHeight() / 2f); + } else { + // Offset event coordinates to be outside the target view (in case it does + // something like tracking pressed state) + event.setLocation(-mTouchSlop * 2, -mTouchSlop * 2); + } + handled = mDelegateView.dispatchTouchEvent(event); + // Cleanup if this was the last event to send to the delegate. + if (!mDelegateTargeted) { + mDelegateView = null; + } + } + return handled; + } + private View inflate(@LayoutRes int layoutResId) { return LayoutInflater.from(getContext()).inflate(layoutResId, this, false); } From e747278ee8e81bcb23715d471876fa6a861422c2 Mon Sep 17 00:00:00 2001 From: Tony Wickham Date: Fri, 22 Jan 2021 18:45:04 -0800 Subject: [PATCH 0482/1707] Support drag and drop from Taskbar - Long clicking a BubbleTextView in Taskbar will start a system drag and drop operation, setting the original view invisible meanwhile. - Defer gesture navigation when starting over a Taskbar item, and cancel any started gesture if a Taskbar drag and drop starts. Bug: 171917176 Change-Id: If5049071fbf1755f545ee937daa4edabd869f00d --- quickstep/res/values/dimens.xml | 1 + .../launcher3/taskbar/TaskbarController.java | 21 +++ .../taskbar/TaskbarDragController.java | 133 ++++++++++++++++++ .../launcher3/taskbar/TaskbarView.java | 86 ++++++++--- .../quickstep/BaseActivityInterface.java | 7 + .../quickstep/LauncherActivityInterface.java | 19 +++ .../quickstep/TouchInteractionService.java | 7 +- 7 files changed, 254 insertions(+), 20 deletions(-) create mode 100644 quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml index 39a7d09649..39cc0b891c 100644 --- a/quickstep/res/values/dimens.xml +++ b/quickstep/res/values/dimens.xml @@ -124,6 +124,7 @@ 48dp 32dp 48dp + 54dp 14dp diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java index f91bfb78fc..7608645a63 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java @@ -26,6 +26,7 @@ import android.animation.Animator; import android.graphics.PixelFormat; import android.graphics.Point; import android.view.Gravity; +import android.view.MotionEvent; import android.view.View; import android.view.WindowManager; @@ -59,6 +60,7 @@ public class TaskbarController { private final TaskbarStateHandler mTaskbarStateHandler; private final TaskbarVisibilityController mTaskbarVisibilityController; private final TaskbarHotseatController mHotseatController; + private final TaskbarDragController mDragController; // Initialized in init(). private WindowManager.LayoutParams mWindowLayoutParams; @@ -77,6 +79,7 @@ public class TaskbarController { createTaskbarVisibilityControllerCallbacks()); mHotseatController = new TaskbarHotseatController(mLauncher, createTaskbarHotseatControllerCallbacks()); + mDragController = new TaskbarDragController(mLauncher); } private TaskbarVisibilityControllerCallbacks createTaskbarVisibilityControllerCallbacks() { @@ -100,6 +103,11 @@ public class TaskbarController { public View.OnClickListener getItemOnClickListener() { return ItemClickHandler.INSTANCE; } + + @Override + public View.OnLongClickListener getItemOnLongClickListener() { + return mDragController::startDragOnLongClick; + } }; } @@ -226,6 +234,18 @@ public class TaskbarController { mHotseatController.onHotseatUpdated(); } + /** + * @param ev MotionEvent in screen coordinates. + * @return Whether any Taskbar item could handle the given MotionEvent if given the chance. + */ + public boolean isEventOverAnyTaskbarItem(MotionEvent ev) { + return mTaskbarView.isEventOverAnyItem(ev); + } + + public boolean isDraggingItem() { + return mTaskbarView.isDraggingItem(); + } + /** * @return Whether the given View is in the same window as Taskbar. */ @@ -254,6 +274,7 @@ public class TaskbarController { */ protected interface TaskbarViewCallbacks { View.OnClickListener getItemOnClickListener(); + View.OnLongClickListener getItemOnLongClickListener(); } /** diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java new file mode 100644 index 0000000000..2318ff96b6 --- /dev/null +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.taskbar; + +import static android.view.View.INVISIBLE; +import static android.view.View.VISIBLE; + +import android.content.ClipData; +import android.content.ClipDescription; +import android.content.Intent; +import android.content.pm.LauncherApps; +import android.content.res.Resources; +import android.graphics.Canvas; +import android.graphics.Point; +import android.view.DragEvent; +import android.view.View; + +import com.android.launcher3.BaseQuickstepLauncher; +import com.android.launcher3.BubbleTextView; +import com.android.launcher3.LauncherSettings; +import com.android.launcher3.R; +import com.android.launcher3.model.data.WorkspaceItemInfo; +import com.android.systemui.shared.system.ClipDescriptionCompat; +import com.android.systemui.shared.system.LauncherAppsCompat; + +/** + * Handles long click on Taskbar items to start a system drag and drop operation. + */ +public class TaskbarDragController { + + private final BaseQuickstepLauncher mLauncher; + private final int mDragIconSize; + + public TaskbarDragController(BaseQuickstepLauncher launcher) { + mLauncher = launcher; + Resources resources = mLauncher.getResources(); + mDragIconSize = resources.getDimensionPixelSize(R.dimen.taskbar_icon_drag_icon_size); + } + + /** + * Attempts to start a system drag and drop operation for the given View, using its tag to + * generate the ClipDescription and Intent. + * @return Whether {@link View#startDragAndDrop} started successfully. + */ + protected boolean startDragOnLongClick(View view) { + if (!(view instanceof BubbleTextView)) { + return false; + } + + BubbleTextView btv = (BubbleTextView) view; + + View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view) { + @Override + public void onProvideShadowMetrics(Point shadowSize, Point shadowTouchPoint) { + shadowSize.set(mDragIconSize, mDragIconSize); + // TODO: should be based on last touch point on the icon. + shadowTouchPoint.set(shadowSize.x / 2, shadowSize.y / 2); + } + + @Override + public void onDrawShadow(Canvas canvas) { + canvas.save(); + float scale = (float) mDragIconSize / btv.getIconSize(); + canvas.scale(scale, scale); + btv.getIcon().draw(canvas); + canvas.restore(); + } + }; + + Object tag = view.getTag(); + ClipDescription clipDescription = null; + Intent intent = null; + if (tag instanceof WorkspaceItemInfo) { + WorkspaceItemInfo item = (WorkspaceItemInfo) tag; + LauncherApps launcherApps = mLauncher.getSystemService(LauncherApps.class); + clipDescription = new ClipDescription(item.title, + new String[] { + item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT + ? ClipDescriptionCompat.MIMETYPE_APPLICATION_SHORTCUT + : ClipDescriptionCompat.MIMETYPE_APPLICATION_ACTIVITY + }); + intent = new Intent(); + if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) { + intent.putExtra(Intent.EXTRA_PACKAGE_NAME, item.getIntent().getPackage()); + intent.putExtra(Intent.EXTRA_SHORTCUT_ID, item.getDeepShortcutId()); + } else { + intent.putExtra(ClipDescriptionCompat.EXTRA_PENDING_INTENT, + LauncherAppsCompat.getMainActivityLaunchIntent(launcherApps, + item.getIntent().getComponent(), null, item.user)); + } + intent.putExtra(Intent.EXTRA_USER, item.user); + } + + if (clipDescription != null && intent != null) { + ClipData clipData = new ClipData(clipDescription, new ClipData.Item(intent)); + view.setOnDragListener(getDraggedViewDragListener()); + return view.startDragAndDrop(clipData, shadowBuilder, null /* localState */, + View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_OPAQUE); + } + return false; + } + + /** + * Hide the original Taskbar item while it is being dragged. + */ + private View.OnDragListener getDraggedViewDragListener() { + return (view, dragEvent) -> { + switch (dragEvent.getAction()) { + case DragEvent.ACTION_DRAG_STARTED: + view.setVisibility(INVISIBLE); + return true; + case DragEvent.ACTION_DRAG_ENDED: + view.setVisibility(VISIBLE); + view.setOnDragListener(null); + return true; + } + return false; + }; + } +} diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java index adcfaec102..c98f09ca0f 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java @@ -20,6 +20,7 @@ import android.content.res.Resources; import android.graphics.RectF; import android.graphics.drawable.ColorDrawable; import android.util.AttributeSet; +import android.view.DragEvent; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; @@ -46,6 +47,7 @@ public class TaskbarView extends LinearLayout { private final int mTouchSlop; private final RectF mTempDelegateBounds = new RectF(); private final RectF mDelegateSlopBounds = new RectF(); + private final int[] mTempOutLocation = new int[2]; // Initialized in init(). private int mHotseatStartIndex; @@ -57,6 +59,8 @@ public class TaskbarView extends LinearLayout { private boolean mDelegateTargeted; private View mDelegateView; + private boolean mIsDraggingItem; + public TaskbarView(@NonNull Context context) { this(context, null); } @@ -135,9 +139,12 @@ public class TaskbarView extends LinearLayout { (WorkspaceItemInfo) hotseatItemInfo); hotseatView.setVisibility(VISIBLE); hotseatView.setOnClickListener(mControllerCallbacks.getItemOnClickListener()); + hotseatView.setOnLongClickListener( + mControllerCallbacks.getItemOnLongClickListener()); } else { hotseatView.setVisibility(GONE); hotseatView.setOnClickListener(null); + hotseatView.setOnLongClickListener(null); } } } @@ -157,25 +164,12 @@ public class TaskbarView extends LinearLayout { final float x = event.getX(); final float y = event.getY(); if (mDelegateView == null && event.getAction() == MotionEvent.ACTION_DOWN) { - for (int i = 0; i < getChildCount(); i++) { - View child = getChildAt(i); - if (!child.isShown() || !child.isClickable()) { - continue; - } - int childCenterX = child.getLeft() + child.getWidth() / 2; - int childCenterY = child.getTop() + child.getHeight() / 2; - mTempDelegateBounds.set( - childCenterX - mIconTouchSize / 2f, - childCenterY - mIconTouchSize / 2f, - childCenterX + mIconTouchSize / 2f, - childCenterY + mIconTouchSize / 2f); - mDelegateTargeted = mTempDelegateBounds.contains(x, y); - if (mDelegateTargeted) { - mDelegateView = child; - mDelegateSlopBounds.set(mTempDelegateBounds); - mDelegateSlopBounds.inset(-mTouchSlop, -mTouchSlop); - break; - } + View delegateView = findDelegateView(x, y); + if (delegateView != null) { + mDelegateTargeted = true; + mDelegateView = delegateView; + mDelegateSlopBounds.set(mTempDelegateBounds); + mDelegateSlopBounds.inset(-mTouchSlop, -mTouchSlop); } } @@ -210,6 +204,60 @@ public class TaskbarView extends LinearLayout { return handled; } + /** + * Return an item whose touch bounds contain the given coordinates, + * or null if no such item exists. + * + * Also sets {@link #mTempDelegateBounds} to be the touch bounds of the chosen delegate view. + */ + private @Nullable View findDelegateView(float x, float y) { + for (int i = 0; i < getChildCount(); i++) { + View child = getChildAt(i); + if (!child.isShown() || !child.isClickable()) { + continue; + } + int childCenterX = child.getLeft() + child.getWidth() / 2; + int childCenterY = child.getTop() + child.getHeight() / 2; + mTempDelegateBounds.set( + childCenterX - mIconTouchSize / 2f, + childCenterY - mIconTouchSize / 2f, + childCenterX + mIconTouchSize / 2f, + childCenterY + mIconTouchSize / 2f); + if (mTempDelegateBounds.contains(x, y)) { + return child; + } + } + return null; + } + + /** + * Returns whether the given MotionEvent, *in screen coorindates*, is within any Taskbar item's + * touch bounds. + */ + public boolean isEventOverAnyItem(MotionEvent ev) { + getLocationOnScreen(mTempOutLocation); + float xInOurCoordinates = ev.getX() - mTempOutLocation[0]; + float yInOurCoorindates = ev.getY() - mTempOutLocation[1]; + return findDelegateView(xInOurCoordinates, yInOurCoorindates) != null; + } + + @Override + public boolean onDragEvent(DragEvent event) { + switch (event.getAction()) { + case DragEvent.ACTION_DRAG_STARTED: + mIsDraggingItem = true; + return true; + case DragEvent.ACTION_DRAG_ENDED: + mIsDraggingItem = false; + break; + } + return super.onDragEvent(event); + } + + public boolean isDraggingItem() { + return mIsDraggingItem; + } + private View inflate(@LayoutRes int layoutResId) { return LayoutInflater.from(getContext()).inflate(layoutResId, this, false); } diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java index 5f6e59fa11..ce14197c2e 100644 --- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java +++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java @@ -153,6 +153,13 @@ public abstract class BaseActivityInterface Date: Tue, 2 Feb 2021 01:19:18 -0800 Subject: [PATCH 0483/1707] Fix issue of slice icon being rendered on non-UIthread Bug: 178989579 Test: Manual Change-Id: I23aa5055c8f07ee9877c1f3ce51cb280ac38a2b7 --- .../android/launcher3/search/SearchResultIconSlice.java | 3 ++- src/com/android/launcher3/BubbleTextView.java | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/quickstep/src/com/android/launcher3/search/SearchResultIconSlice.java b/quickstep/src/com/android/launcher3/search/SearchResultIconSlice.java index 0ea2f8b86e..c441e2291b 100644 --- a/quickstep/src/com/android/launcher3/search/SearchResultIconSlice.java +++ b/quickstep/src/com/android/launcher3/search/SearchResultIconSlice.java @@ -15,6 +15,7 @@ */ package com.android.launcher3.search; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; import android.app.search.SearchTarget; @@ -104,7 +105,7 @@ public class SearchResultIconSlice extends LinearLayout implements SearchTargetH PackageItemInfo pkgItem = new PackageItemInfo(parentTarget.getPackageName()); pkgItem.user = parentTarget.getUserHandle(); appState.getIconCache().getTitleAndIconForApp(pkgItem, false); - mIcon.applyFromItemInfoWithIcon(pkgItem); + MAIN_EXECUTOR.post(() -> mIcon.applyFromItemInfoWithIcon(pkgItem)); }); } } diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index 140340af62..5007ffc7be 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -51,6 +51,7 @@ import android.view.ViewDebug; import android.widget.TextView; import androidx.annotation.Nullable; +import androidx.annotation.UiThread; import androidx.core.graphics.ColorUtils; import com.android.launcher3.Launcher.OnResumeCallback; @@ -271,6 +272,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, mDotScaleAnim.start(); } + @UiThread public void applyFromWorkspaceItem(WorkspaceItemInfo info) { applyFromWorkspaceItem(info, false); } @@ -287,6 +289,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, } } + @UiThread public void applyFromWorkspaceItem(WorkspaceItemInfo info, boolean promiseStateChanged) { applyIconAndLabel(info); setTag(info); @@ -295,6 +298,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, setDownloadStateContentDescription(info, info.getProgressLevel()); } + @UiThread public void applyFromApplicationInfo(AppInfo info) { applyIconAndLabel(info); @@ -314,6 +318,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, /** * Apply label and tag using a generic {@link ItemInfoWithIcon} */ + @UiThread public void applyFromItemInfoWithIcon(ItemInfoWithIcon info) { applyIconAndLabel(info); // We don't need to check the info since it's not a WorkspaceItemInfo @@ -328,11 +333,13 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, /** * Apply label and tag using a {@link SearchActionItemInfo} */ + @UiThread public void applyFromSearchActionItemInfo(SearchActionItemInfo searchActionItemInfo) { applyIconAndLabel(searchActionItemInfo); setTag(searchActionItemInfo); } + @UiThread protected void applyIconAndLabel(ItemInfoWithIcon info) { FastBitmapDrawable iconDrawable = newIcon(getContext(), info); mDotParams.color = IconPalette.getMutedColor(info.bitmap.color, 0.54f); @@ -341,6 +348,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, applyLabel(info); } + @UiThread private void applyLabel(ItemInfoWithIcon info) { setText(info.title); if (info.contentDescription != null) { From 035bb22298b0e8705cf1a67017a923a2c98bafc6 Mon Sep 17 00:00:00 2001 From: Tracy Zhou Date: Mon, 1 Feb 2021 20:34:27 -0800 Subject: [PATCH 0484/1707] [Live Tile] Fix a bunch of issues related to touch input Here are the issues - Touch down again during transition of quick switch brings back the previous app - During transition to overview, touch down on task / in the background area results in weird behaviors These issues related to touch input are caused by the z-ordering of app and launcher. Because in the current implementation we place launcher on top upon initial swipe up, during the transition (to overview / quick switch), any touch event goes directly to launcher instead of the overview input consumer (where it normally goes to in the non-live tile case), resulting in these weird behaviors mentioned above. The reason we put app below launcher upon swipe up is to render task view icon earlier (before swipe up settles into overview), which, is not as critical (we don't have the feature now), and we can find other ways to make that happen. So here are the proposed changes - Place app on top upon swipe up. Only place launcher on top after transition to overview. - Remove the temporary fix to disable click for live tile task during transition. - Remove icon logic in live tile overlay (in this change I only removed the call, but let me know if it's preferred that they are completely removed from LiveTileOverlay in this change) Test: manual Fixes: 178640174 Fixes: 175039524 Change-Id: Ib97804c8bbc487bc55160719375c0f2ff3ba7fd3 --- .../src/com/android/quickstep/AbsSwipeUpHandler.java | 10 ---------- .../com/android/quickstep/views/LiveTileOverlay.java | 5 +++++ .../src/com/android/quickstep/views/TaskView.java | 4 ---- 3 files changed, 5 insertions(+), 14 deletions(-) diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java index 6bcc4bfdce..36b51cdf1c 100644 --- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java +++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java @@ -731,7 +731,6 @@ public abstract class AbsSwipeUpHandler, Q extends setIsLikelyToStartNewTask(isLikelyToStartNewTask, false /* animate */); mStateCallback.setStateOnUiThread(STATE_GESTURE_STARTED); mGestureStarted = true; - mTaskViewSimulator.setDrawsBelowRecents(true); } /** @@ -958,7 +957,6 @@ public abstract class AbsSwipeUpHandler, Q extends if (endTarget == HOME) { duration = Math.max(MIN_OVERSHOOT_DURATION, duration); } else if (endTarget == RECENTS) { - LiveTileOverlay.INSTANCE.startIconAnimation(); if (mRecentsView != null) { int nearestPage = mRecentsView.getPageNearestToCenterOfScreen(); if (mRecentsView.getNextPage() != nearestPage) { @@ -971,9 +969,6 @@ public abstract class AbsSwipeUpHandler, Q extends } duration = Math.max(duration, mRecentsView.getScroller().getDuration()); } - if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { - mRecentsView.getRunningTaskView().setIsClickableAsLiveTile(false); - } } // Let RecentsView handle the scrolling to the task, which we launch in startNewTask() @@ -1067,7 +1062,6 @@ public abstract class AbsSwipeUpHandler, Q extends } if (mGestureState.getEndTarget() == HOME) { - mTaskViewSimulator.setDrawsBelowRecents(false); getOrientationHandler().adjustFloatingIconStartVelocity(velocityPxPerMs); final RemoteAnimationTargetCompat runningTaskTarget = mRecentsAnimationTargets != null ? mRecentsAnimationTargets.findTask(mGestureState.getRunningTaskId()) @@ -1453,10 +1447,6 @@ public abstract class AbsSwipeUpHandler, Q extends private void finishCurrentTransitionToRecents() { if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED); - final TaskView runningTaskView = mRecentsView.getRunningTaskView(); - if (runningTaskView != null) { - runningTaskView.setIsClickableAsLiveTile(true); - } } 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); diff --git a/quickstep/src/com/android/quickstep/views/LiveTileOverlay.java b/quickstep/src/com/android/quickstep/views/LiveTileOverlay.java index 747c3f28a1..8210ab0ca8 100644 --- a/quickstep/src/com/android/quickstep/views/LiveTileOverlay.java +++ b/quickstep/src/com/android/quickstep/views/LiveTileOverlay.java @@ -85,6 +85,11 @@ public class LiveTileOverlay extends Drawable { mIcon = icon; } + // TODO: consider cleaning this up and drawing icon in another way. Previously we place app + // below launcher during the initial swipe up and render the icon in this live tile overlay. + // However, this resulted in a bunch of touch input issues caused by Launcher getting the input + // events during transition (to overview / to another app (quick switch). So now our new + // solution places app on top in live tile until it fully settles in Overview. public void startIconAnimation() { if (mIconAnimator != null) { mIconAnimator.cancel(); diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java index e8f590f28d..59cf3b2b8b 100644 --- a/quickstep/src/com/android/quickstep/views/TaskView.java +++ b/quickstep/src/com/android/quickstep/views/TaskView.java @@ -356,10 +356,6 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { return false; } - public void setIsClickableAsLiveTile(boolean isClickableAsLiveTile) { - mIsClickableAsLiveTile = isClickableAsLiveTile; - } - private void computeAndSetIconTouchDelegate() { float iconHalfSize = mIconView.getWidth() / 2f; mIconCenterCoords[0] = mIconCenterCoords[1] = iconHalfSize; From 2e292b634366e3a0ec453b5f0e271eac76cb3c8a Mon Sep 17 00:00:00 2001 From: Schneider Victor-tulias Date: Wed, 6 Jan 2021 11:02:39 -0500 Subject: [PATCH 0485/1707] Update PreloadIconDrawable colors when an app is not startable. Updated the progress bar and progress track colors when the app is not startable to increase contrast. Demo: https://drive.google.com/file/d/1iHYBeHgWsItY6edO3NT1RYuXjdKNlEKH/view?usp=sharing Test: manual Bug: 171005849 Change-Id: I97bc4e3299108b1e8ef47b498fb3431293f57ad2 --- .../graphics/PreloadIconDrawable.java | 88 +++++++++++++++---- src/com/android/launcher3/util/Themes.java | 5 ++ 2 files changed, 78 insertions(+), 15 deletions(-) diff --git a/src/com/android/launcher3/graphics/PreloadIconDrawable.java b/src/com/android/launcher3/graphics/PreloadIconDrawable.java index 304d49692e..ce824df3e6 100644 --- a/src/com/android/launcher3/graphics/PreloadIconDrawable.java +++ b/src/com/android/launcher3/graphics/PreloadIconDrawable.java @@ -24,6 +24,7 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.content.Context; +import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Matrix; @@ -34,10 +35,12 @@ import android.graphics.Rect; import android.util.Pair; import android.util.Property; import android.util.SparseArray; +import android.view.ContextThemeWrapper; import com.android.launcher3.FastBitmapDrawable; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.model.data.ItemInfoWithIcon; +import com.android.launcher3.util.Themes; import java.lang.ref.WeakReference; @@ -77,6 +80,9 @@ public class PreloadIconDrawable extends FastBitmapDrawable { private static final SparseArray>> sShadowCache = new SparseArray<>(); + private static final int PRELOAD_ACCENT_COLOR_INDEX = 0; + private static final int PRELOAD_BACKGROUND_COLOR_INDEX = 1; + private final Matrix mTmpMatrix = new Matrix(); private final PathMeasure mPathMeasure = new PathMeasure(); @@ -91,6 +97,9 @@ public class PreloadIconDrawable extends FastBitmapDrawable { private Bitmap mShadowBitmap; private final int mIndicatorColor; + private final int mSystemAccentColor; + private final int mSystemBackgroundColor; + private final boolean mIsDarkMode; private int mTrackAlpha; private float mTrackLength; @@ -104,11 +113,23 @@ public class PreloadIconDrawable extends FastBitmapDrawable { private ObjectAnimator mCurrentAnim; + private boolean mIsStartable; + public PreloadIconDrawable(ItemInfoWithIcon info, Context context) { - this(info, IconPalette.getPreloadProgressColor(context, info.bitmap.color)); + this( + info, + IconPalette.getPreloadProgressColor(context, info.bitmap.color), + getPreloadColors(context), + (context.getResources().getConfiguration().uiMode + & Configuration.UI_MODE_NIGHT_MASK + & Configuration.UI_MODE_NIGHT_YES) != 0) /* isDarkMode */; } - public PreloadIconDrawable(ItemInfoWithIcon info, int indicatorColor) { + public PreloadIconDrawable( + ItemInfoWithIcon info, + int indicatorColor, + int[] preloadColors, + boolean isDarkMode) { super(info.bitmap); mItem = info; mShapePath = getShapePath(); @@ -120,9 +141,12 @@ public class PreloadIconDrawable extends FastBitmapDrawable { mProgressPaint.setStrokeCap(Paint.Cap.ROUND); mIndicatorColor = indicatorColor; - setInternalProgress(0); + mSystemAccentColor = preloadColors[PRELOAD_ACCENT_COLOR_INDEX]; + mSystemBackgroundColor = preloadColors[PRELOAD_BACKGROUND_COLOR_INDEX]; + mIsDarkMode = isDarkMode; - setIsDisabled(!info.isAppStartable()); + setInternalProgress(info.getProgressLevel()); + setIsStartable(info.isAppStartable()); } @Override @@ -148,7 +172,7 @@ public class PreloadIconDrawable extends FastBitmapDrawable { } private Bitmap getShadowBitmap(int width, int height, float shadowRadius) { - int key = (width << 16) | height; + int key = ((width << 16) | height) * (mIsDarkMode ? -1 : 1); WeakReference> shadowRef = sShadowCache.get(key); Pair cache = shadowRef != null ? shadowRef.get() : null; Bitmap shadow = cache != null && cache.first.equals(mShapePath) ? cache.second : null; @@ -157,8 +181,9 @@ public class PreloadIconDrawable extends FastBitmapDrawable { } shadow = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); Canvas c = new Canvas(shadow); - mProgressPaint.setShadowLayer(shadowRadius, 0, 0, COLOR_SHADOW); - mProgressPaint.setColor(COLOR_TRACK); + mProgressPaint.setShadowLayer(shadowRadius, 0, 0, mIsStartable + ? COLOR_SHADOW : mSystemAccentColor); + mProgressPaint.setColor(mIsStartable ? COLOR_TRACK : mSystemBackgroundColor); mProgressPaint.setAlpha(MAX_PAINT_ALPHA); c.drawPath(mScaledTrackPath, mProgressPaint); mProgressPaint.clearShadowLayer(); @@ -176,7 +201,7 @@ public class PreloadIconDrawable extends FastBitmapDrawable { } // Draw track. - mProgressPaint.setColor(mIndicatorColor); + mProgressPaint.setColor(mIsStartable ? mIndicatorColor : mSystemAccentColor); mProgressPaint.setAlpha(mTrackAlpha); if (mShadowBitmap != null) { canvas.drawBitmap(mShadowBitmap, bounds.left, bounds.top, mProgressPaint); @@ -215,6 +240,14 @@ public class PreloadIconDrawable extends FastBitmapDrawable { return !mRanFinishAnimation; } + /** Sets whether this icon should display the startable app UI. */ + public void setIsStartable(boolean isStartable) { + if (mIsStartable != isStartable) { + mIsStartable = isStartable; + setIsDisabled(!isStartable); + } + } + private void updateInternalState(float finalProgress, boolean shouldAnimate, boolean isFinish) { if (mCurrentAnim != null) { mCurrentAnim.cancel(); @@ -295,6 +328,18 @@ public class PreloadIconDrawable extends FastBitmapDrawable { invalidateSelf(); } + private static int[] getPreloadColors(Context context) { + Context dayNightThemeContext = new ContextThemeWrapper( + context, android.R.style.Theme_DeviceDefault_DayNight); + int[] preloadColors = new int[2]; + + preloadColors[PRELOAD_ACCENT_COLOR_INDEX] = Themes.getColorAccent(dayNightThemeContext); + preloadColors[PRELOAD_BACKGROUND_COLOR_INDEX] = Themes.getColorBackgroundFloating( + dayNightThemeContext); + + return preloadColors; + } + /** * Returns a FastBitmapDrawable with the icon. */ @@ -305,13 +350,21 @@ public class PreloadIconDrawable extends FastBitmapDrawable { @Override public ConstantState getConstantState() { return new PreloadIconConstantState( - mBitmap, mIconColor, !mItem.isAppStartable(), mItem, mIndicatorColor); + mBitmap, + mIconColor, + !mItem.isAppStartable(), + mItem, + mIndicatorColor, + new int[] {mSystemAccentColor, mSystemBackgroundColor}, + mIsDarkMode); } protected static class PreloadIconConstantState extends FastBitmapConstantState { protected final ItemInfoWithIcon mInfo; protected final int mIndicatorColor; + protected final int[] mPreloadColors; + protected final boolean mIsDarkMode; protected final int mLevel; public PreloadIconConstantState( @@ -319,19 +372,24 @@ public class PreloadIconDrawable extends FastBitmapDrawable { int iconColor, boolean isDisabled, ItemInfoWithIcon info, - int indicatorcolor) { + int indicatorColor, + int[] preloadColors, + boolean isDarkMode) { super(bitmap, iconColor, isDisabled); mInfo = info; - mIndicatorColor = indicatorcolor; + mIndicatorColor = indicatorColor; + mPreloadColors = preloadColors; + mIsDarkMode = isDarkMode; mLevel = info.getProgressLevel(); } @Override public PreloadIconDrawable newDrawable() { - PreloadIconDrawable drawable = new PreloadIconDrawable(mInfo, mIndicatorColor); - drawable.setLevel(mLevel); - drawable.setIsDisabled(mIsDisabled); - return drawable; + return new PreloadIconDrawable( + mInfo, + mIndicatorColor, + mPreloadColors, + mIsDarkMode); } @Override diff --git a/src/com/android/launcher3/util/Themes.java b/src/com/android/launcher3/util/Themes.java index b74686fbd3..55d17fc9c0 100644 --- a/src/com/android/launcher3/util/Themes.java +++ b/src/com/android/launcher3/util/Themes.java @@ -81,6 +81,11 @@ public class Themes { return getAttrColor(context, android.R.attr.colorAccent); } + /** Returns the floating background color attribute. */ + public static int getColorBackgroundFloating(Context context) { + return getAttrColor(context, android.R.attr.colorBackgroundFloating); + } + public static int getAttrColor(Context context, int attr) { TypedArray ta = context.obtainStyledAttributes(new int[]{attr}); int colorAccent = ta.getColor(0, 0); From 50165042edc5954bc71c3e66b60cb87748784f8f Mon Sep 17 00:00:00 2001 From: Hyunyoung Song Date: Fri, 29 Jan 2021 23:35:22 -0800 Subject: [PATCH 0486/1707] Logs stack trace when IME doesn't get attached when all apps swipes up Bug: 178904132 Test: Manual Change-Id: I3b62a1a8e927ca8bac4db07053f781260fbff515 --- .../allapps/AllAppsInsetTransitionController.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/com/android/launcher3/allapps/AllAppsInsetTransitionController.java b/src/com/android/launcher3/allapps/AllAppsInsetTransitionController.java index 1cf98e1c9b..f6e54aaec4 100644 --- a/src/com/android/launcher3/allapps/AllAppsInsetTransitionController.java +++ b/src/com/android/launcher3/allapps/AllAppsInsetTransitionController.java @@ -164,8 +164,11 @@ public class AllAppsInsetTransitionController { @Override public void onCancelled(@Nullable WindowInsetsAnimationController controller) { if (DEBUG) { - Log.d(TAG, "Listener.onCancelled ctrl=" + controller - + " mAnimationController=" + mAnimationController); + // Keep the verbose logging to chase down IME not showing up issue. + // b/178904132 + Log.e(TAG, "Listener.onCancelled ctrl=" + controller + + " mAnimationController=" + mAnimationController, + new Exception()); } if (mState == State.DRAG_START_BOTTOM) { mState = State.DRAG_START_BOTTOM_IME_CANCELLED; From 7239df725803e965d6e5ee6c6136e4904e939d83 Mon Sep 17 00:00:00 2001 From: Hyunyoung Song Date: Mon, 1 Feb 2021 21:18:15 -0800 Subject: [PATCH 0487/1707] Support shorter height row layout / Disable ICON_SLICE Bug: 178815297 Bug: 178128837 Bug: 178823469 Test: manual, attached screenshot on the bugreport Change-Id: Ie69c8928f5acc430320cfc5c85547195866e829e --- .../res/layout/search_result_icon_row.xml | 8 +- .../layout/search_result_small_icon_row.xml | 73 ++++++++ .../search/DeviceSearchAdapterProvider.java | 19 +- .../launcher3/search/SearchAdapterItem.java | 24 +-- .../launcher3/search/SearchResultIconRow.java | 14 +- .../search/SearchResultSmallIconRow.java | 142 ++++++++++++++ .../launcher3/search/SearchTargetUtil.java | 36 ++-- .../launcher3/config/FeatureFlags.java | 6 +- .../systemui/plugins/AllAppsSearchPlugin.java | 105 ----------- .../shared/SearchTargetEventLegacy.java | 95 ---------- .../plugins/shared/SearchTargetLegacy.java | 175 ------------------ 11 files changed, 279 insertions(+), 418 deletions(-) create mode 100644 quickstep/res/layout/search_result_small_icon_row.xml create mode 100644 quickstep/src/com/android/launcher3/search/SearchResultSmallIconRow.java delete mode 100644 src_plugins/com/android/systemui/plugins/AllAppsSearchPlugin.java delete mode 100644 src_plugins/com/android/systemui/plugins/shared/SearchTargetEventLegacy.java delete mode 100644 src_plugins/com/android/systemui/plugins/shared/SearchTargetLegacy.java diff --git a/quickstep/res/layout/search_result_icon_row.xml b/quickstep/res/layout/search_result_icon_row.xml index 81190cfc56..084920a8e6 100644 --- a/quickstep/res/layout/search_result_icon_row.xml +++ b/quickstep/res/layout/search_result_icon_row.xml @@ -20,10 +20,10 @@ android:padding="@dimen/dynamic_grid_edge_margin"> + android:layout_width="wrap_content" + android:layout_height="wrap_content" + launcher:iconDisplay="hero_app" /> + + + + + + + + + + + + \ No newline at end of file diff --git a/quickstep/src/com/android/launcher3/search/DeviceSearchAdapterProvider.java b/quickstep/src/com/android/launcher3/search/DeviceSearchAdapterProvider.java index acf6c150cf..4a656c1acb 100644 --- a/quickstep/src/com/android/launcher3/search/DeviceSearchAdapterProvider.java +++ b/quickstep/src/com/android/launcher3/search/DeviceSearchAdapterProvider.java @@ -16,6 +16,7 @@ package com.android.launcher3.search; +import static com.android.launcher3.allapps.AllAppsGridAdapter.VIEW_TYPE_ALL_APPS_DIVIDER; import static com.android.launcher3.allapps.AllAppsGridAdapter.VIEW_TYPE_ICON; import android.app.search.SearchTarget; @@ -31,6 +32,7 @@ import com.android.launcher3.R; import com.android.launcher3.allapps.AllAppsContainerView; import com.android.launcher3.allapps.AllAppsGridAdapter; import com.android.launcher3.allapps.search.SearchAdapterProvider; +import com.android.launcher3.config.FeatureFlags; /** * Provides views for on-device search results @@ -41,11 +43,12 @@ public class DeviceSearchAdapterProvider extends SearchAdapterProvider { public static final int VIEW_TYPE_SEARCH_SLICE = 1 << 7; public static final int VIEW_TYPE_SEARCH_ICON = (1 << 8) | VIEW_TYPE_ICON; public static final int VIEW_TYPE_SEARCH_ICON_ROW = (1 << 9); + public static final int VIEW_TYPE_SEARCH_SMALL_ICON_ROW = (1 << 10); public static final int VIEW_TYPE_SEARCH_THUMBNAIL = 1 << 12; public static final int VIEW_TYPE_SEARCH_WIDGET_LIVE = 1 << 15; public static final int VIEW_TYPE_SEARCH_WIDGET_PREVIEW = 1 << 16; - private static final String TAG = "SearchServiceAdapterProvider"; + private static final String TAG = "SearchServiceAdapter"; private final AllAppsContainerView mAppsView; private final SparseIntArray mViewTypeToLayoutMap = new SparseIntArray(); @@ -57,11 +60,13 @@ public class DeviceSearchAdapterProvider extends SearchAdapterProvider { mViewTypeToLayoutMap.put(VIEW_TYPE_SEARCH_CORPUS_TITLE, R.layout.search_section_title); mViewTypeToLayoutMap.put(VIEW_TYPE_SEARCH_ICON, R.layout.search_result_icon); mViewTypeToLayoutMap.put(VIEW_TYPE_SEARCH_ICON_ROW, R.layout.search_result_icon_row); + mViewTypeToLayoutMap.put(VIEW_TYPE_SEARCH_SMALL_ICON_ROW, R.layout.search_result_small_icon_row); mViewTypeToLayoutMap.put(VIEW_TYPE_SEARCH_SLICE, R.layout.search_result_slice); mViewTypeToLayoutMap.put(VIEW_TYPE_SEARCH_THUMBNAIL, R.layout.search_result_thumbnail); mViewTypeToLayoutMap.put(VIEW_TYPE_SEARCH_WIDGET_LIVE, R.layout.search_result_widget_live); mViewTypeToLayoutMap.put(VIEW_TYPE_SEARCH_WIDGET_PREVIEW, R.layout.search_result_widget_preview); + mViewTypeToLayoutMap.put(VIEW_TYPE_ALL_APPS_DIVIDER, R.layout.all_apps_divider); } @Override @@ -116,25 +121,33 @@ public class DeviceSearchAdapterProvider extends SearchAdapterProvider { case LayoutType.ICON_SINGLE_VERTICAL_TEXT: return VIEW_TYPE_SEARCH_ICON; case LayoutType.ICON_SLICE: + if (FeatureFlags.DISABLE_SLICE_IN_ALLAPPS.get()) { + return -1; + } if (t.getSliceUri() != null) { return VIEW_TYPE_SEARCH_SLICE; } - Log.w(TAG, "Dropping as LayoutType.ICON_SLICE target doesn't contain sliceUri."); + Log.w(TAG, "LayoutType.ICON_SLICE target doesn't contain sliceUri."); break; case LayoutType.ICON_DOUBLE_HORIZONTAL_TEXT: case LayoutType.ICON_SINGLE_HORIZONTAL_TEXT: case LayoutType.ICON_DOUBLE_HORIZONTAL_TEXT_BUTTON: + case LayoutType.ICON_HORIZONTAL_TEXT: return VIEW_TYPE_SEARCH_ICON_ROW; + case LayoutType.SMALL_ICON_HORIZONTAL_TEXT: + return VIEW_TYPE_SEARCH_SMALL_ICON_ROW; case LayoutType.THUMBNAIL: if (t.getSearchAction() != null) { return VIEW_TYPE_SEARCH_THUMBNAIL; } - Log.w(TAG, "Dropping as LayoutType.THUMBNAIL target doesn't contain searchAction."); + Log.w(TAG, "LayoutType.THUMBNAIL target doesn't contain searchAction."); break; case LayoutType.WIDGET_PREVIEW: return VIEW_TYPE_SEARCH_WIDGET_PREVIEW; case LayoutType.WIDGET_LIVE: return VIEW_TYPE_SEARCH_WIDGET_LIVE; + case LayoutType.DIVIDER: + return VIEW_TYPE_ALL_APPS_DIVIDER; } return -1; diff --git a/quickstep/src/com/android/launcher3/search/SearchAdapterItem.java b/quickstep/src/com/android/launcher3/search/SearchAdapterItem.java index b402f61979..8983c4f3b8 100644 --- a/quickstep/src/com/android/launcher3/search/SearchAdapterItem.java +++ b/quickstep/src/com/android/launcher3/search/SearchAdapterItem.java @@ -18,6 +18,7 @@ package com.android.launcher3.search; import static com.android.launcher3.search.DeviceSearchAdapterProvider.VIEW_TYPE_SEARCH_ICON; import static com.android.launcher3.search.DeviceSearchAdapterProvider.VIEW_TYPE_SEARCH_ICON_ROW; +import static com.android.launcher3.search.DeviceSearchAdapterProvider.VIEW_TYPE_SEARCH_SMALL_ICON_ROW; import static com.android.launcher3.search.DeviceSearchAdapterProvider.VIEW_TYPE_SEARCH_SLICE; import static com.android.launcher3.search.DeviceSearchAdapterProvider.VIEW_TYPE_SEARCH_THUMBNAIL; import static com.android.launcher3.search.DeviceSearchAdapterProvider.VIEW_TYPE_SEARCH_WIDGET_LIVE; @@ -26,7 +27,6 @@ import static com.android.launcher3.search.DeviceSearchAdapterProvider.VIEW_TYPE import android.app.search.SearchTarget; import com.android.launcher3.allapps.AllAppsGridAdapter; -import com.android.systemui.plugins.shared.SearchTargetLegacy; import java.util.ArrayList; import java.util.List; @@ -35,31 +35,23 @@ import java.util.List; * Extension of AdapterItem that contains an extra payload specific to item */ public class SearchAdapterItem extends AllAppsGridAdapter.AdapterItem { - private SearchTargetLegacy mSearchTargetLegacy; private SearchTarget mSearchTarget; private List mInlineItems = new ArrayList<>(); - private static final int AVAILABLE_FOR_ACCESSIBILITY = - VIEW_TYPE_SEARCH_SLICE | VIEW_TYPE_SEARCH_THUMBNAIL | VIEW_TYPE_SEARCH_ICON_ROW - | VIEW_TYPE_SEARCH_ICON | VIEW_TYPE_SEARCH_WIDGET_PREVIEW - | VIEW_TYPE_SEARCH_WIDGET_LIVE; - - - public SearchAdapterItem(SearchTargetLegacy searchTargetLegacy, int type) { - mSearchTargetLegacy = searchTargetLegacy; - viewType = type; - } + private static final int AVAILABLE_FOR_ACCESSIBILITY = VIEW_TYPE_SEARCH_SLICE + | VIEW_TYPE_SEARCH_THUMBNAIL + | VIEW_TYPE_SEARCH_ICON_ROW + | VIEW_TYPE_SEARCH_ICON + | VIEW_TYPE_SEARCH_SMALL_ICON_ROW + | VIEW_TYPE_SEARCH_WIDGET_PREVIEW + | VIEW_TYPE_SEARCH_WIDGET_LIVE; public SearchAdapterItem(SearchTarget searchTarget, int type) { mSearchTarget = searchTarget; viewType = type; } - public SearchTargetLegacy getSearchTargetLegacy() { - return mSearchTargetLegacy; - } - public SearchTarget getSearchTarget() { return mSearchTarget; } diff --git a/quickstep/src/com/android/launcher3/search/SearchResultIconRow.java b/quickstep/src/com/android/launcher3/search/SearchResultIconRow.java index 7c3ed69a00..12a1a1cd34 100644 --- a/quickstep/src/com/android/launcher3/search/SearchResultIconRow.java +++ b/quickstep/src/com/android/launcher3/search/SearchResultIconRow.java @@ -46,12 +46,12 @@ public class SearchResultIconRow extends LinearLayout implements SearchTargetHan public static final int MAX_INLINE_ITEMS = 3; protected final Launcher mLauncher; - private final LauncherAppState mLauncherAppState; - protected SearchResultIcon mResultIcon; + protected final SearchResultIcon[] mInlineIcons = new SearchResultIcon[MAX_INLINE_ITEMS]; + private SearchResultIcon mResultIcon; + private final LauncherAppState mLauncherAppState; private TextView mTitleView; private TextView mSubTitleView; - protected final SearchResultIcon[] mInlineIcons = new SearchResultIcon[MAX_INLINE_ITEMS]; private PackageItemInfo mProviderInfo; @@ -77,13 +77,14 @@ public class SearchResultIconRow extends LinearLayout implements SearchTargetHan @Override protected void onFinishInflate() { super.onFinishInflate(); - int iconSize = getIconSize(); mResultIcon = findViewById(R.id.icon); + mTitleView = findViewById(R.id.title); mSubTitleView = findViewById(R.id.subtitle); mSubTitleView.setVisibility(GONE); + mResultIcon.getLayoutParams().height = iconSize; mResultIcon.getLayoutParams().width = iconSize; mResultIcon.setTextVisibility(false); @@ -94,15 +95,16 @@ public class SearchResultIconRow extends LinearLayout implements SearchTargetHan for (SearchResultIcon inlineIcon : mInlineIcons) { inlineIcon.getLayoutParams().width = getIconSize(); } - setOnClickListener(mResultIcon); setOnLongClickListener(mResultIcon); } @Override public void apply(SearchTarget parentTarget, List children) { - showSubtitleIfNeeded(null); mResultIcon.apply(parentTarget, children, this::onItemInfoCreated); + + showSubtitleIfNeeded(null); + if (parentTarget.getShortcutInfo() != null) { updateWithShortcutInfo(parentTarget.getShortcutInfo()); } else if (parentTarget.getSearchAction() != null) { diff --git a/quickstep/src/com/android/launcher3/search/SearchResultSmallIconRow.java b/quickstep/src/com/android/launcher3/search/SearchResultSmallIconRow.java new file mode 100644 index 0000000000..ca8aa81ecb --- /dev/null +++ b/quickstep/src/com/android/launcher3/search/SearchResultSmallIconRow.java @@ -0,0 +1,142 @@ +/* + * 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.search; + +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; +import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; + +import android.app.search.SearchTarget; +import android.content.Context; +import android.content.pm.ShortcutInfo; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.annotation.Nullable; + +import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.R; +import com.android.launcher3.model.data.ItemInfoWithIcon; +import com.android.launcher3.model.data.PackageItemInfo; + +import java.util.List; + +/** + * A full width representation of {@link SearchResultIcon} with a secondary label and inline + * SearchTargets + */ +public class SearchResultSmallIconRow extends LinearLayout implements SearchTargetHandler { + + protected final Launcher mLauncher; + private final LauncherAppState mLauncherAppState; + protected SearchResultIcon mResultIcon; + + private TextView mTitleView; + private TextView mDelimeterView; + private TextView mSubTitleView; + + private PackageItemInfo mProviderInfo; + + public SearchResultSmallIconRow(Context context) { + this(context, null, 0); + } + + public SearchResultSmallIconRow(Context context, + @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public SearchResultSmallIconRow(Context context, + @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + mLauncher = Launcher.getLauncher(getContext()); + mLauncherAppState = LauncherAppState.getInstance(getContext()); + } + + protected int getIconSize() { + return mLauncher.getDeviceProfile().allAppsIconSizePx; + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + int iconSize = getIconSize(); + + mResultIcon = findViewById(R.id.icon); + + mTitleView = findViewById(R.id.title); + mDelimeterView = findViewById(R.id.delimeter); + mDelimeterView.setVisibility(GONE); + mSubTitleView = findViewById(R.id.subtitle); + mSubTitleView.setVisibility(GONE); + + mResultIcon.getLayoutParams().height = iconSize; + mResultIcon.getLayoutParams().width = iconSize; + mResultIcon.setTextVisibility(false); + + setOnClickListener(mResultIcon); + setOnLongClickListener(mResultIcon); + } + + @Override + public void apply(SearchTarget parentTarget, List children) { + mResultIcon.apply(parentTarget, children, this::onItemInfoCreated); + + showSubtitleIfNeeded(null); + + if (parentTarget.getShortcutInfo() != null) { + updateWithShortcutInfo(parentTarget.getShortcutInfo()); + } else if (parentTarget.getSearchAction() != null) { + showSubtitleIfNeeded(parentTarget.getSearchAction().getSubtitle()); + } + } + + @Override + public boolean quickSelect() { + this.performClick(); + return true; + } + + private void updateWithShortcutInfo(ShortcutInfo shortcutInfo) { + PackageItemInfo packageItemInfo = new PackageItemInfo(shortcutInfo.getPackage()); + if (packageItemInfo.equals(mProviderInfo)) return; + MODEL_EXECUTOR.post(() -> { + mLauncherAppState.getIconCache().getTitleAndIconForApp(packageItemInfo, true); + MAIN_EXECUTOR.post(() -> { + showSubtitleIfNeeded(packageItemInfo.title); + mProviderInfo = packageItemInfo; + }); + }); + } + + protected void showSubtitleIfNeeded(CharSequence subTitle) { + if (!TextUtils.isEmpty(subTitle)) { + mSubTitleView.setText(subTitle); + mSubTitleView.setVisibility(VISIBLE); + mDelimeterView.setVisibility(VISIBLE); + + } else { + mSubTitleView.setVisibility(GONE); + } + } + + protected void onItemInfoCreated(ItemInfoWithIcon info) { + setTag(info); + mTitleView.setText(info.title); + } +} diff --git a/quickstep/src/com/android/launcher3/search/SearchTargetUtil.java b/quickstep/src/com/android/launcher3/search/SearchTargetUtil.java index 0abed036e3..ede3b9d143 100644 --- a/quickstep/src/com/android/launcher3/search/SearchTargetUtil.java +++ b/quickstep/src/com/android/launcher3/search/SearchTargetUtil.java @@ -13,10 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package com.android.launcher3.search; -import static com.android.app.search.LayoutType.ICON_DOUBLE_HORIZONTAL_TEXT; -import static com.android.app.search.LayoutType.ICON_SINGLE_HORIZONTAL_TEXT; +import static com.android.app.search.LayoutType.DIVIDER; +import static com.android.app.search.LayoutType.ICON_HORIZONTAL_TEXT; +import static com.android.app.search.LayoutType.SMALL_ICON_HORIZONTAL_TEXT; import static com.android.app.search.LayoutType.THUMBNAIL; import static com.android.app.search.ResultType.ACTION; import static com.android.app.search.ResultType.SCREENSHOT; @@ -54,17 +56,17 @@ public class SearchTargetUtil { /** - * Generate SearchTargetUtil for ICON_DOUBLE_HORIZONTAL_TEXT layout type. + * Generate SearchTargetUtil for ICON_HORIZONTAL_TEXT layout type. * * targets.add(SearchTargetUtil.generateIconDoubleHorizontalText_SearchAction( * mContext, "red", Color.RED)); * targets.add(SearchTargetUtil.generateIconDoubleHorizontalText_SearchAction( * mContext, "yellow", Color.YELLOW)); */ - public static SearchTarget generateIconDoubleHorizontalText_SearchAction( + public static SearchTarget generateIcoHorizontalText_usingSearchAction( Context context, String id, int color) { SearchTarget.Builder builder = - new SearchTarget.Builder(ACTION, ICON_DOUBLE_HORIZONTAL_TEXT, id) + new SearchTarget.Builder(ACTION, ICON_HORIZONTAL_TEXT, id) .setPackageName(PACKAGE2) /* required */ .setUserHandle(USERHANDLE); /* required */ @@ -102,7 +104,7 @@ public class SearchTargetUtil { * targets.add(SearchTargetUtil.generateThumbnail_SearchAction("red", Color.RED)); * targets.add(SearchTargetUtil.generateThumbnail_SearchAction("green", Color.GREEN)); */ - public static SearchTarget generateThumbnail_SearchAction(String id, int color) { + public static SearchTarget generateThumbnail_usingSearchAction(String id, int color) { SearchTarget.Builder builder = new SearchTarget.Builder(SCREENSHOT, THUMBNAIL, id) .setPackageName(PACKAGE2) /* required */ @@ -130,16 +132,19 @@ public class SearchTargetUtil { } /** + * Generate SearchTargetUtil for SMALL_ICON_HORIZONTAL_TEXT layout type. + * * targets.add(SearchTargetUtil.generateIconHorizontalText_SearchAction( * mContext, "red", Color.RED)); * targets.add(SearchTargetUtil.generateIconHorizontalText_SearchAction( * mContext, "yellow", Color.YELLOW)); */ - public static SearchTarget generateIconHorizontalText_SearchAction( + public static SearchTarget generateSmallIconHorizontalText_usingSearchAction( Context context, String id, int color) { - String fallbackQuery = "How to make cookie"; + String title = "Ask the assistant"; + String fallbackQuery = "sourdough bread"; SearchTarget.Builder builder = - new SearchTarget.Builder(SUGGEST, ICON_SINGLE_HORIZONTAL_TEXT, id) + new SearchTarget.Builder(SUGGEST, SMALL_ICON_HORIZONTAL_TEXT, id) .setPackageName(PACKAGE2) /* required */ .setUserHandle(USERHANDLE); /* required */ @@ -159,7 +164,8 @@ public class SearchTargetUtil { Bundle extra = new Bundle(); extra.putBoolean(BUNDLE_EXTRA_SHOULD_START_FOR_RESULT, true); - SearchAction searchAction = new SearchAction.Builder(id, fallbackQuery) + SearchAction searchAction = new SearchAction.Builder(id, title) + .setSubtitle(fallbackQuery) .setPendingIntent(pendingIntent3) .setIcon(icon) .setExtras(extra) @@ -167,6 +173,14 @@ public class SearchTargetUtil { return builder.setSearchAction(searchAction).build(); } + public static SearchTarget generateDivider() { + SearchTarget.Builder builder = + new SearchTarget.Builder(SUGGEST, DIVIDER, "divider") + .setPackageName("") /* required but not used*/ + .setUserHandle(USERHANDLE); /* required */ + return builder.build(); + } + /** * Generate SearchTargetUtil for ICON_DOUBLE_HORIZONTAL_TEXT layout type. @@ -174,7 +188,7 @@ public class SearchTargetUtil { public static SearchTarget generateIconDoubleHorizontalText_ShortcutInfo(Context context) { String id = "23456"; SearchTarget.Builder builder = - new SearchTarget.Builder(ResultType.SHORTCUT, ICON_DOUBLE_HORIZONTAL_TEXT, id) + new SearchTarget.Builder(ResultType.SHORTCUT, SMALL_ICON_HORIZONTAL_TEXT, id) .setPackageName("com.google.android.gm") /* required */ .setUserHandle(UserHandle.CURRENT); /* required */ diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java index 086d665fcb..b61799f79c 100644 --- a/src/com/android/launcher3/config/FeatureFlags.java +++ b/src/com/android/launcher3/config/FeatureFlags.java @@ -98,12 +98,12 @@ public final class FeatureFlags { public static final BooleanFlag ENABLE_DEVICE_SEARCH = new DeviceFlag( "ENABLE_DEVICE_SEARCH", false, "Allows on device search in all apps"); - public static final BooleanFlag USE_SEARCH_API = getDebugFlag( - "USE_SEARCH_API", true, "Use SearchUIManager api for device search"); - public static final BooleanFlag DISABLE_INITIAL_IME_IN_ALLAPPS = getDebugFlag( "DISABLE_INITIAL_IME_IN_ALLAPPS", false, "Disable default IME state in all apps"); + public static final BooleanFlag DISABLE_SLICE_IN_ALLAPPS = getDebugFlag( + "DISABLE_SLICE_IN_ALLAPPS", true, "Disable slice in all apps"); + public static final BooleanFlag FOLDER_NAME_SUGGEST = new DeviceFlag( "FOLDER_NAME_SUGGEST", true, "Suggests folder names instead of blank text."); diff --git a/src_plugins/com/android/systemui/plugins/AllAppsSearchPlugin.java b/src_plugins/com/android/systemui/plugins/AllAppsSearchPlugin.java deleted file mode 100644 index 0b48c07d01..0000000000 --- a/src_plugins/com/android/systemui/plugins/AllAppsSearchPlugin.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.plugins; - -import android.app.Activity; -import android.os.Bundle; -import android.os.CancellationSignal; -import android.os.Parcelable; -import android.view.View; - -import com.android.systemui.plugins.annotations.ProvidesInterface; -import com.android.systemui.plugins.shared.SearchTargetEventLegacy; -import com.android.systemui.plugins.shared.SearchTargetLegacy; - -import java.util.List; -import java.util.function.Consumer; - -/** - * Implement this plugin interface to fetch search result data from the plugin side. - */ -@ProvidesInterface(action = AllAppsSearchPlugin.ACTION, version = AllAppsSearchPlugin.VERSION) -public interface AllAppsSearchPlugin extends Plugin { - String ACTION = "com.android.systemui.action.PLUGIN_ALL_APPS_SEARCH_ACTIONS"; - int VERSION = 9; - - /** - * init plugin - */ - void setup(Activity activity, View view, boolean useLegacy); - - /** - * Send launcher state related signals. - */ - void onStateTransitionStart(int fromState, int toState); - - void onStateTransitionComplete(int state); - - /** - * Send launcher window focus and visibility changed signals. - */ - void onWindowFocusChanged(boolean hasFocus); - - void onWindowVisibilityChanged(int visibility); - - /** - * Send signal when user starts typing, perform search, notify search target - * event when search ends. - */ - void startedSearchSession(); - - /** - * Main function that triggers search. - * - * @param input string that has been typed by a user - * @param inputArgs extra info that may be relevant for the input query - * @param results contains the result that will be rendered in all apps search - * surface - * @param cancellationSignal {@link CancellationSignal} can be used to share status of current - */ - void queryLegacy(String input, Bundle inputArgs, Consumer> results, - CancellationSignal cancellationSignal); - - /** - * Main function that triggers search. - * - * @param input string that has been typed by a user - * @param inputArgs extra info that may be relevant for the input query - * @param results contains the result that will be rendered in all apps search - * surface - * @param cancellationSignal {@link CancellationSignal} can be used to share status of current - */ - void query(String input, Bundle inputArgs, Consumer> results, - CancellationSignal cancellationSignal); - - /** - * Send over search target interaction events to Plugin - */ - void notifySearchTargetEventLegacy(SearchTargetEventLegacy event); - - /** - * Send over search target interaction events to Plugin - */ - void notifySearchTargetEvent(Parcelable event); - - /** - * Launcher activity lifecycle callbacks - */ - void onResume(int state); - - void onStop(int state); -} \ No newline at end of file diff --git a/src_plugins/com/android/systemui/plugins/shared/SearchTargetEventLegacy.java b/src_plugins/com/android/systemui/plugins/shared/SearchTargetEventLegacy.java deleted file mode 100644 index 0fc61f04dc..0000000000 --- a/src_plugins/com/android/systemui/plugins/shared/SearchTargetEventLegacy.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.systemui.plugins.shared; - -import android.os.Bundle; - -/** - * Event used for the feedback loop to the plugin. (and future aiai) - * - * @deprecated Use {@link android.app.search.SearchTargetEvent} - */ -@Deprecated -public class SearchTargetEventLegacy { - public static final int POSITION_NONE = -1; - - public static final int SELECT = 0; - public static final int QUICK_SELECT = 1; - public static final int LONG_PRESS = 2; - public static final int CHILD_SELECT = 3; - - private final SearchTargetLegacy mSearchTarget; - private final int mEventType; - private final int mShortcutPosition; - private final Bundle mExtras; - - public SearchTargetEventLegacy(SearchTargetLegacy searchTarget, int eventType, - int shortcutPosition, - Bundle extras) { - mSearchTarget = searchTarget; - mEventType = eventType; - mShortcutPosition = shortcutPosition; - mExtras = extras; - } - - - public SearchTargetLegacy getSearchTarget() { - return mSearchTarget; - } - - public int getShortcutPosition() { - return mShortcutPosition; - } - - public int getEventType() { - return mEventType; - } - - public Bundle getExtras() { - return mExtras; - } - - /** - * A builder for {@link SearchTargetLegacy} - */ - public static final class Builder { - private final SearchTargetLegacy mSearchTarget; - private final int mEventType; - private int mShortcutPosition = POSITION_NONE; - private Bundle mExtras; - - public Builder(SearchTargetLegacy searchTarget, int eventType) { - mSearchTarget = searchTarget; - mEventType = eventType; - } - - public Builder setShortcutPosition(int shortcutPosition) { - mShortcutPosition = shortcutPosition; - return this; - } - - public Builder setExtras(Bundle extras) { - mExtras = extras; - return this; - } - - public SearchTargetEventLegacy build() { - return new SearchTargetEventLegacy(mSearchTarget, mEventType, mShortcutPosition, - mExtras); - } - } - -} diff --git a/src_plugins/com/android/systemui/plugins/shared/SearchTargetLegacy.java b/src_plugins/com/android/systemui/plugins/shared/SearchTargetLegacy.java deleted file mode 100644 index 2a6ba8829f..0000000000 --- a/src_plugins/com/android/systemui/plugins/shared/SearchTargetLegacy.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.systemui.plugins.shared; - -import android.app.RemoteAction; -import android.content.ComponentName; -import android.content.pm.ShortcutInfo; -import android.os.Bundle; -import android.os.UserHandle; - -import java.util.List; - -/** - * Used to return all apps search targets. - * - * @deprecated Use SearchTarget - */ -@Deprecated -public class SearchTargetLegacy implements Comparable { - - private final String mItemId; - private final String mItemType; - private final float mScore; - - private final ComponentName mComponentName; - private final UserHandle mUserHandle; - private final List mShortcutInfos; - //TODO: (sfufa) replace with a list of a custom type - private final RemoteAction mRemoteAction; - private final Bundle mExtras; - - private SearchTargetLegacy(String itemId, String itemType, float score, - ComponentName componentName, UserHandle userHandle, List shortcutInfos, - RemoteAction remoteAction, Bundle extras) { - mItemId = itemId; - mItemType = itemType; - mScore = score; - mComponentName = componentName; - mUserHandle = userHandle; - mShortcutInfos = shortcutInfos; - mExtras = extras; - mRemoteAction = remoteAction; - } - - public String getItemId() { - return mItemId; - } - - public String getItemType() { - return mItemType; - } - - public ComponentName getComponentName() { - return mComponentName; - } - - public UserHandle getUserHandle() { - return mUserHandle; - } - - public float getScore() { - return mScore; - } - - public List getShortcutInfos() { - return mShortcutInfos; - } - - public Bundle getExtras() { - return mExtras; - } - - public RemoteAction getRemoteAction() { - return mRemoteAction; - } - - @Override - public int compareTo(SearchTargetLegacy o) { - return Float.compare(o.mScore, mScore); - } - - /** - * A builder for {@link SearchTargetLegacy} - */ - public static final class Builder { - - - private String mItemId; - - private final String mItemType; - private final float mScore; - - - private ComponentName mComponentName; - private UserHandle mUserHandle; - private List mShortcutInfos; - private Bundle mExtras; - private RemoteAction mRemoteAction; - - public Builder(String itemType, float score) { - this(itemType, score, null, null); - } - - public Builder(String itemType, float score, ComponentName cn, - UserHandle user) { - mItemType = itemType; - mScore = score; - mComponentName = cn; - mUserHandle = user; - } - - public String getItemId() { - return mItemId; - } - - public float getScore() { - return mScore; - } - - public Builder setItemId(String itemId) { - mItemId = itemId; - return this; - } - - public Builder setComponentName(ComponentName componentName) { - mComponentName = componentName; - return this; - } - - public Builder setUserHandle(UserHandle userHandle) { - mUserHandle = userHandle; - return this; - } - - public Builder setShortcutInfos(List shortcutInfos) { - mShortcutInfos = shortcutInfos; - return this; - } - - public Builder setExtras(Bundle extras) { - mExtras = extras; - return this; - } - - public Builder setRemoteAction(RemoteAction remoteAction) { - mRemoteAction = remoteAction; - return this; - } - - /** - * Builds a {@link SearchTargetLegacy} - */ - public SearchTargetLegacy build() { - if (mItemId == null) { - throw new IllegalStateException("Item ID is required for building SearchTarget"); - } - return new SearchTargetLegacy(mItemId, mItemType, mScore, mComponentName, mUserHandle, - mShortcutInfos, - mRemoteAction, mExtras); - } - } -} From 7786595ae565ccd48efc87422508f2ff93b1b7e8 Mon Sep 17 00:00:00 2001 From: Wale Ogunwale Date: Wed, 3 Feb 2021 07:39:20 -0800 Subject: [PATCH 0488/1707] Launcher3: Added shared lib APIs for Launcher to use split-screen APIs. Bug: 179176511 Test: presubmits! Change-Id: Ib66cd533676ec28c3b18549d2b9931cdb2263654 --- .../com/android/quickstep/SystemUiProxy.java | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java index ca55468a9f..85b21e09aa 100644 --- a/quickstep/src/com/android/quickstep/SystemUiProxy.java +++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java @@ -17,6 +17,7 @@ package com.android.quickstep; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; +import android.app.PendingIntent; import android.app.PictureInPictureParams; import android.content.ComponentName; import android.content.Context; @@ -24,15 +25,18 @@ import android.content.pm.ActivityInfo; import android.graphics.Bitmap; import android.graphics.Insets; import android.graphics.Rect; +import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.os.IBinder.DeathRecipient; import android.os.RemoteException; +import android.os.UserHandle; import android.util.Log; import android.view.MotionEvent; import com.android.launcher3.util.MainThreadInitializedObject; import com.android.systemui.shared.recents.IPinnedStackAnimationListener; +import com.android.systemui.shared.recents.ISplitScreenListener; import com.android.systemui.shared.recents.ISystemUiProxy; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.system.RemoteTransitionCompat; @@ -431,4 +435,84 @@ public class SystemUiProxy implements ISystemUiProxy { } } } + + @Override + public void registerSplitScreenListener(ISplitScreenListener listener) { + if (mSystemUiProxy != null) { + try { + mSystemUiProxy.registerSplitScreenListener(listener); + } catch (RemoteException e) { + Log.w(TAG, "Failed call registerSplitScreenListener"); + } + } + } + + @Override + public void unregisterSplitScreenListener(ISplitScreenListener listener) { + if (mSystemUiProxy != null) { + try { + mSystemUiProxy.unregisterSplitScreenListener(listener); + } catch (RemoteException e) { + Log.w(TAG, "Failed call unregisterSplitScreenListener"); + } + } + } + + @Override + public void setSideStageVisibility(boolean visible) { + if (mSystemUiProxy != null) { + try { + mSystemUiProxy.setSideStageVisibility(visible); + } catch (RemoteException e) { + Log.w(TAG, "Failed call setSideStageVisibility"); + } + } + } + + @Override + public void exitSplitScreen() { + if (mSystemUiProxy != null) { + try { + mSystemUiProxy.exitSplitScreen(); + } catch (RemoteException e) { + Log.w(TAG, "Failed call exitSplitScreen"); + } + } + } + + @Override + public void startTask(int taskId, int stage, int position, Bundle options) { + if (mSystemUiProxy != null) { + try { + mSystemUiProxy.startTask(taskId, stage, position, options); + } catch (RemoteException e) { + Log.w(TAG, "Failed call startTask"); + } + } + } + + @Override + public void startShortcut(String packageName, String shortcutId, int stage, int position, + Bundle options, UserHandle user) { + if (mSystemUiProxy != null) { + try { + mSystemUiProxy.startShortcut(packageName, shortcutId, stage, position, options, + user); + } catch (RemoteException e) { + Log.w(TAG, "Failed call startShortcut"); + } + } + } + + @Override + public void startIntent(PendingIntent intent, int stage, int position, Bundle options) { + if (mSystemUiProxy != null) { + try { + mSystemUiProxy.startIntent(intent, stage, position, options); + } catch (RemoteException e) { + Log.w(TAG, "Failed call startIntent"); + } + } + } + } From cbeb13d6c7f93ec0d510a7d77e7bcdb2b46beb2b Mon Sep 17 00:00:00 2001 From: thiruram Date: Wed, 27 Jan 2021 14:45:58 -0800 Subject: [PATCH 0489/1707] [AA+] Log app launches from AA+ search result container. This change will update westworld logs for app launches from AA+ search result. Updates log's container info from AllAppsContainer -> AllAppsPlusSearchResultContainer. Bug: 178562918 Change-Id: I9ffca27fea42951a57640ef36717c04ff0251506 --- Android.bp | 20 +++++++++ Android.mk | 10 ++--- build.gradle | 4 +- protos/launcher_atom.proto | 3 ++ .../launcher_atom_extension.proto | 31 +++++++++++++ .../launcher_atom_extension.proto | 34 +++++++++++++++ .../launcher3/search/SearchResultIcon.java | 43 ++++++++++++++++--- .../logging/StatsLogCompatManager.java | 5 +++ .../launcher3/model/data/ItemInfo.java | 2 +- 9 files changed, 138 insertions(+), 14 deletions(-) create mode 100644 protos_overrides/launcher_atom_extension.proto create mode 100644 quickstep/protos_overrides/launcher_atom_extension.proto diff --git a/Android.bp b/Android.bp index b20b307a5c..a720658f9f 100644 --- a/Android.bp +++ b/Android.bp @@ -36,17 +36,37 @@ java_library_static { name: "launcher_log_protos_lite", srcs: [ "protos/*.proto", + "protos_overrides/*.proto", ], sdk_version: "current", proto: { type: "lite", local_include_dirs:[ "protos", + "protos_overrides", ], }, static_libs: ["libprotobuf-java-lite"], } +java_library_static { + name: "launcher_quickstep_log_protos_lite", + srcs: [ + "quickstep/protos_overrides/*.proto", + ], + sdk_version: "current", + proto: { + type: "lite", + local_include_dirs:[ + "quickstep/protos_overrides", + ], + }, + static_libs: [ + "libprotobuf-java-lite", + "launcher_log_protos_lite" + ], +} + java_library { name: "LauncherPluginLib", diff --git a/Android.mk b/Android.mk index ed420393dc..19ad328187 100644 --- a/Android.mk +++ b/Android.mk @@ -33,7 +33,7 @@ LOCAL_STATIC_ANDROID_LIBRARIES := \ LOCAL_STATIC_JAVA_LIBRARIES := \ LauncherPluginLib \ - launcher_log_protos_lite \ + launcher_quickstep_log_protos_lite \ search_ui LOCAL_SRC_FILES := \ @@ -65,7 +65,7 @@ LOCAL_SRC_FILES := \ $(call all-java-files-under, src_shortcuts_overrides) \ $(call all-java-files-under, src_ui_overrides) \ $(call all-java-files-under, ext_tests/src) - + LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/ext_tests/res LOCAL_PROGUARD_FLAG_FILES := proguard.flags @@ -129,8 +129,7 @@ LOCAL_MODULE_TAGS := optional LOCAL_STATIC_JAVA_LIBRARIES := \ SystemUI-statsd \ - SystemUISharedLib \ - launcher_log_protos_lite + SystemUISharedLib ifneq (,$(wildcard frameworks/base)) LOCAL_PRIVATE_PLATFORM_APIS := true else @@ -196,8 +195,7 @@ LOCAL_MODULE_TAGS := optional LOCAL_STATIC_JAVA_LIBRARIES := \ SystemUI-statsd \ - SystemUISharedLib \ - launcher_log_protos_lite + SystemUISharedLib ifneq (,$(wildcard frameworks/base)) LOCAL_PRIVATE_PLATFORM_APIS := true else diff --git a/build.gradle b/build.gradle index 28a05d569c..a7eef13a3d 100644 --- a/build.gradle +++ b/build.gradle @@ -81,7 +81,7 @@ android { java.srcDirs = ['src', 'src_plugins'] manifest.srcFile 'AndroidManifest-common.xml' proto { - srcDir 'protos/' + srcDirs = ['protos/', 'protos_overrides/'] } } @@ -181,4 +181,4 @@ protobuf { } } } -} \ No newline at end of file +} diff --git a/protos/launcher_atom.proto b/protos/launcher_atom.proto index cd229ae015..b4c6138671 100644 --- a/protos/launcher_atom.proto +++ b/protos/launcher_atom.proto @@ -18,6 +18,8 @@ syntax = "proto2"; option java_package = "com.android.launcher3.logger"; option java_outer_classname = "LauncherAtom"; +import "launcher_atom_extension.proto"; + // // ItemInfos message ItemInfo { @@ -55,6 +57,7 @@ message ContainerInfo { SettingsContainer settings_container = 9; PredictedHotseatContainer predicted_hotseat_container = 10; TaskSwitcherContainer task_switcher_container = 11; + ExtendedContainers extended_containers = 20; } } diff --git a/protos_overrides/launcher_atom_extension.proto b/protos_overrides/launcher_atom_extension.proto new file mode 100644 index 0000000000..a07daf844c --- /dev/null +++ b/protos_overrides/launcher_atom_extension.proto @@ -0,0 +1,31 @@ +/* + * 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. + */ +syntax = "proto2"; + +option java_package = "com.android.launcher3.logger"; +option java_outer_classname = "LauncherAtomExtensions"; + + +// This proto file contains placeholder messages that can be overridden by +// other Launcher variants. +// Variants could have its own launcher_atom_extension.proto file(should have +// same messages declared here but with different implementation); when building +// variant's apk launcher_atom.proto will reference variant's extension file, +// essentially overriding this file. + + +// Wrapper message for additional containers used in variants. +message ExtendedContainers {} diff --git a/quickstep/protos_overrides/launcher_atom_extension.proto b/quickstep/protos_overrides/launcher_atom_extension.proto new file mode 100644 index 0000000000..2766acf539 --- /dev/null +++ b/quickstep/protos_overrides/launcher_atom_extension.proto @@ -0,0 +1,34 @@ +/* + * 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. + */ +syntax = "proto2"; + +option java_package = "com.android.launcher3.logger"; +option java_outer_classname = "LauncherAtomExtensions"; + + +// Wrapper message for containers used at the quickstep level. +// Message name should match with launcher_atom_extension.proto message at +// the AOSP level. +message ExtendedContainers { + + oneof Container{ + DeviceSearchResultContainer device_search_result_container = 1; + } +} + +// Represents on-device search result container. +message DeviceSearchResultContainer{ +} diff --git a/quickstep/src/com/android/launcher3/search/SearchResultIcon.java b/quickstep/src/com/android/launcher3/search/SearchResultIcon.java index c353d7aa3c..521174be32 100644 --- a/quickstep/src/com/android/launcher3/search/SearchResultIcon.java +++ b/quickstep/src/com/android/launcher3/search/SearchResultIcon.java @@ -49,6 +49,9 @@ import com.android.launcher3.allapps.AllAppsStore; import com.android.launcher3.icons.BitmapInfo; import com.android.launcher3.icons.BitmapRenderer; import com.android.launcher3.icons.LauncherIcons; +import com.android.launcher3.logger.LauncherAtom.ContainerInfo; +import com.android.launcher3.logger.LauncherAtomExtensions.DeviceSearchResultContainer; +import com.android.launcher3.logger.LauncherAtomExtensions.ExtendedContainers; import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.model.data.ItemInfoWithIcon; import com.android.launcher3.model.data.PackageItemInfo; @@ -142,8 +145,14 @@ public class SearchResultIcon extends BubbleTextView implements SearchActionItemInfo itemInfo = new SearchActionItemInfo(searchAction.getIcon(), searchTarget.getPackageName(), searchTarget.getUserHandle(), - searchAction.getTitle() - ); + searchAction.getTitle()) { + // Workaround to log ItemInfo with DeviceSearchResultContainer without + // updating ItemInfo.container field. + @Override + protected ContainerInfo getContainerInfo() { + return buildDeviceSearchResultContainer(); + } + }; itemInfo.setIntent(searchAction.getIntent()); itemInfo.setPendingIntent(searchAction.getPendingIntent()); @@ -243,7 +252,15 @@ public class SearchResultIcon extends BubbleTextView implements private void prepareUsingApp(ComponentName componentName, UserHandle userHandle) { AllAppsStore appsStore = mLauncher.getAppsView().getAppsStore(); - AppInfo appInfo = appsStore.getApp(new ComponentKey(componentName, userHandle)); + AppInfo appInfo = new AppInfo( + appsStore.getApp(new ComponentKey(componentName, userHandle))) { + // Workaround to log ItemInfo with DeviceSearchResultContainer without + // updating ItemInfo.container field. + @Override + protected ContainerInfo getContainerInfo() { + return buildDeviceSearchResultContainer(); + } + }; if (appInfo == null) { setVisibility(GONE); @@ -253,9 +270,15 @@ public class SearchResultIcon extends BubbleTextView implements notifyItemInfoChanged(appInfo); } - private void prepareUsingShortcutInfo(ShortcutInfo shortcutInfo) { - WorkspaceItemInfo workspaceItemInfo = new WorkspaceItemInfo(shortcutInfo, getContext()); + WorkspaceItemInfo workspaceItemInfo = new WorkspaceItemInfo(shortcutInfo, getContext()) { + // Workaround to log ItemInfo with DeviceSearchResultContainer without + // updating ItemInfo.container field. + @Override + protected ContainerInfo getContainerInfo() { + return buildDeviceSearchResultContainer(); + } + }; notifyItemInfoChanged(workspaceItemInfo); LauncherAppState launcherAppState = LauncherAppState.getInstance(getContext()); MODEL_EXECUTOR.execute(() -> { @@ -293,4 +316,14 @@ public class SearchResultIcon extends BubbleTextView implements mOnItemInfoChanged = null; } } + + private static ContainerInfo buildDeviceSearchResultContainer() { + return ContainerInfo.newBuilder().setExtendedContainers( + ExtendedContainers + .newBuilder() + .setDeviceSearchResultContainer( + DeviceSearchResultContainer + .newBuilder())) + .build(); + } } diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java index d949126981..d22496d56f 100644 --- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java +++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java @@ -16,6 +16,7 @@ package com.android.quickstep.logging; +import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.EXTENDED_CONTAINERS; import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.FOLDER; import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.SEARCH_RESULT_CONTAINER; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WORKSPACE_SNAPSHOT; @@ -72,6 +73,7 @@ public class StatsLogCompatManager extends StatsLogManager { private static final int DEFAULT_PAGE_INDEX = -2; private static final int FOLDER_HIERARCHY_OFFSET = 100; private static final int SEARCH_RESULT_HIERARCHY_OFFSET = 200; + private static final int EXTENDED_CONTAINERS_HIERARCHY_OFFSET = 300; public static final CopyOnWriteArrayList LOGS_CONSUMER = new CopyOnWriteArrayList<>(); @@ -397,6 +399,9 @@ public class StatsLogCompatManager extends StatsLogManager { } else if (info.getContainerInfo().getContainerCase() == SEARCH_RESULT_CONTAINER) { return info.getContainerInfo().getSearchResultContainer().getParentContainerCase() .getNumber() + SEARCH_RESULT_HIERARCHY_OFFSET; + } else if (info.getContainerInfo().getContainerCase() == EXTENDED_CONTAINERS) { + return info.getContainerInfo().getExtendedContainers().getContainerCase().getNumber() + + EXTENDED_CONTAINERS_HIERARCHY_OFFSET; } else { return info.getContainerInfo().getContainerCase().getNumber(); } diff --git a/src/com/android/launcher3/model/data/ItemInfo.java b/src/com/android/launcher3/model/data/ItemInfo.java index b11b419dde..2fc902336d 100644 --- a/src/com/android/launcher3/model/data/ItemInfo.java +++ b/src/com/android/launcher3/model/data/ItemInfo.java @@ -225,7 +225,7 @@ public class ItemInfo { protected String dumpProperties() { return "id=" + id + " type=" + LauncherSettings.Favorites.itemTypeToString(itemType) - + " container=" + LauncherSettings.Favorites.containerToString(container) + + " container=" + getContainerInfo() + " targetComponent=" + getTargetComponent() + " screen=" + screenId + " cell(" + cellX + "," + cellY + ")" From f4d19f4614cf2f15a5f9f1ad46e7204bc515dbb5 Mon Sep 17 00:00:00 2001 From: thiruram Date: Wed, 27 Jan 2021 19:05:12 -0800 Subject: [PATCH 0490/1707] [AA+] Log app launches from DeviceSearchResultContainer with instance ID. This InstanceId is used to recreate the AA+ session on the server side. Bug: 178562918 Change-Id: I1bba94417d3a142351e2470bb6153707d2cadb11 --- .../android/launcher3/search/SearchResultIcon.java | 6 +++--- .../launcher3/uioverrides/QuickstepLauncher.java | 12 ++++++++++++ .../launcher3/allapps/search/LiveSearchManager.java | 13 +++++++++++++ src/com/android/launcher3/model/data/ItemInfo.java | 5 ++++- 4 files changed, 32 insertions(+), 4 deletions(-) diff --git a/quickstep/src/com/android/launcher3/search/SearchResultIcon.java b/quickstep/src/com/android/launcher3/search/SearchResultIcon.java index 521174be32..f7d5f45bfa 100644 --- a/quickstep/src/com/android/launcher3/search/SearchResultIcon.java +++ b/quickstep/src/com/android/launcher3/search/SearchResultIcon.java @@ -149,7 +149,7 @@ public class SearchResultIcon extends BubbleTextView implements // Workaround to log ItemInfo with DeviceSearchResultContainer without // updating ItemInfo.container field. @Override - protected ContainerInfo getContainerInfo() { + public ContainerInfo getContainerInfo() { return buildDeviceSearchResultContainer(); } }; @@ -257,7 +257,7 @@ public class SearchResultIcon extends BubbleTextView implements // Workaround to log ItemInfo with DeviceSearchResultContainer without // updating ItemInfo.container field. @Override - protected ContainerInfo getContainerInfo() { + public ContainerInfo getContainerInfo() { return buildDeviceSearchResultContainer(); } }; @@ -275,7 +275,7 @@ public class SearchResultIcon extends BubbleTextView implements // Workaround to log ItemInfo with DeviceSearchResultContainer without // updating ItemInfo.container field. @Override - protected ContainerInfo getContainerInfo() { + public ContainerInfo getContainerInfo() { return buildDeviceSearchResultContainer(); } }; diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java index a00ce56e6a..ee00ed2207 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java @@ -24,6 +24,8 @@ import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK; import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent; +import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.EXTENDED_CONTAINERS; +import static com.android.launcher3.logger.LauncherAtomExtensions.ExtendedContainers.ContainerCase.DEVICE_SEARCH_RESULT_CONTAINER; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP; import static com.android.launcher3.testing.TestProtocol.HINT_STATE_ORDINAL; import static com.android.launcher3.testing.TestProtocol.OVERVIEW_STATE_ORDINAL; @@ -81,6 +83,7 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Objects; +import java.util.Optional; import java.util.stream.Stream; public class QuickstepLauncher extends BaseQuickstepLauncher { @@ -105,6 +108,15 @@ public class QuickstepLauncher extends BaseQuickstepLauncher { @Override protected void logAppLaunch(ItemInfo info, InstanceId instanceId) { + // If the app launch is from DeviceSearchResultContainer then add the InstanceId from + // LiveSearchManager to recreate the AllApps search session on the server side. + Optional logInstanceId = this.getLiveSearchManager().getLogInstanceId(); + if (info.getContainerInfo().getContainerCase() == EXTENDED_CONTAINERS + && info.getContainerInfo().getExtendedContainers().getContainerCase() + == DEVICE_SEARCH_RESULT_CONTAINER && logInstanceId.isPresent()) { + instanceId = logInstanceId.get(); + } + StatsLogger logger = getStatsLogManager() .logger().withItemInfo(info).withInstanceId(instanceId); diff --git a/src/com/android/launcher3/allapps/search/LiveSearchManager.java b/src/com/android/launcher3/allapps/search/LiveSearchManager.java index ec33908c75..c2f0b960c8 100644 --- a/src/com/android/launcher3/allapps/search/LiveSearchManager.java +++ b/src/com/android/launcher3/allapps/search/LiveSearchManager.java @@ -33,10 +33,13 @@ import androidx.slice.widget.SliceLiveData; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppWidgetProviderInfo; +import com.android.launcher3.logging.InstanceId; +import com.android.launcher3.logging.InstanceIdSequence; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.widget.PendingAddWidgetInfo; import java.util.HashMap; +import java.util.Optional; /** * Manages Lifecycle for Live search results @@ -51,6 +54,7 @@ public class LiveSearchManager { new HashMap<>(); private final HashMap> mUriSliceMap = new HashMap<>(); private SearchWidgetHost mSearchWidgetHost; + private InstanceId mLogInstanceId; public LiveSearchManager(Launcher launcher) { mLauncher = launcher; @@ -113,6 +117,7 @@ public class LiveSearchManager { */ public void start() { stop(); + mLogInstanceId = new InstanceIdSequence().newInstanceId(); mSearchWidgetHost = new SearchWidgetHost(mLauncher); mSearchWidgetHost.startListening(); } @@ -136,6 +141,14 @@ public class LiveSearchManager { mUriSliceMap.clear(); } + /** + * Returns {@link InstanceId} that should be used for logging events within search session, if + * available. + */ + public Optional getLogInstanceId() { + return Optional.ofNullable(mLogInstanceId); + } + static class SearchWidgetHost extends AppWidgetHost { SearchWidgetHost(Context context) { super(context, SEARCH_APPWIDGET_HOST_ID); diff --git a/src/com/android/launcher3/model/data/ItemInfo.java b/src/com/android/launcher3/model/data/ItemInfo.java index 2fc902336d..3851ab044d 100644 --- a/src/com/android/launcher3/model/data/ItemInfo.java +++ b/src/com/android/launcher3/model/data/ItemInfo.java @@ -352,7 +352,10 @@ public class ItemInfo { return itemBuilder; } - protected ContainerInfo getContainerInfo() { + /** + * Returns {@link ContainerInfo} used when logging this item. + */ + public ContainerInfo getContainerInfo() { switch (container) { case CONTAINER_HOTSEAT: return ContainerInfo.newBuilder() From b245c552d4b6a11e60d16777521f77ee1a4e591c Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Wed, 3 Feb 2021 12:55:57 -0800 Subject: [PATCH 0491/1707] Updating predictions if hotseat items get removed/added as a result of model callback Bug: 179284787 Test: Verified on device Change-Id: Iecfc7f291f54aacccc6d2afe674ccd2f20e2035d --- .../launcher3/BaseQuickstepLauncher.java | 4 - .../HotseatPredictionController.java | 90 ++++++++++++------- .../uioverrides/QuickstepLauncher.java | 15 +++- 3 files changed, 74 insertions(+), 35 deletions(-) diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java index cbe0eb0795..edcd0a203a 100644 --- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java @@ -355,10 +355,6 @@ public abstract class BaseQuickstepLauncher extends Launcher // populating workspace. // TODO: Find a better place for this WellbeingModel.INSTANCE.get(this); - - if (mTaskbarController != null) { - mTaskbarController.onHotseatUpdated(); - } } @Override diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java index aa6601b8c8..88cfacb3a6 100644 --- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java +++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java @@ -20,6 +20,7 @@ import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.hybridhotseat.HotseatEduController.getSettingsIntent; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_PREDICTION_PINNED; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_RANKED; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import android.animation.Animator; import android.animation.AnimatorSet; @@ -70,7 +71,11 @@ import java.util.stream.Collectors; */ public class HotseatPredictionController implements DragController.DragListener, SystemShortcut.Factory, InvariantDeviceProfile.OnIDPChangeListener, - DragSource { + DragSource, ViewGroup.OnHierarchyChangeListener { + + private static final int FLAG_UPDATE_PAUSED = 1 << 0; + private static final int FLAG_DRAG_IN_PROGRESS = 1 << 1; + private static final int FLAG_FILL_IN_PROGRESS = 1 << 2; private int mHotSeatItemsCount; @@ -80,8 +85,7 @@ public class HotseatPredictionController implements DragController.DragListener, private List mPredictedItems = Collections.emptyList(); private AnimatorSet mIconRemoveAnimators; - private boolean mUIUpdatePaused = false; - private boolean mDragInProgress = false; + private int mPauseFlags = 0; private List mOutlineDrawings = new ArrayList<>(); @@ -114,6 +118,30 @@ public class HotseatPredictionController implements DragController.DragListener, mLauncher.getDragController().addDragListener(this); launcher.getDeviceProfile().inv.addOnChangeListener(this); + mHotseat.getShortcutsAndWidgets().setOnHierarchyChangeListener(this); + } + + @Override + public void onChildViewAdded(View parent, View child) { + onHotseatHierarchyChanged(); + } + + @Override + public void onChildViewRemoved(View parent, View child) { + onHotseatHierarchyChanged(); + } + + private void onHotseatHierarchyChanged() { + if (mPauseFlags == 0 && !mLauncher.isWorkspaceLoading()) { + // Post update after a single frame to avoid layout within layout + MAIN_EXECUTOR.getHandler().post(this::updateFillIfNotLoading); + } + } + + private void updateFillIfNotLoading() { + if (mPauseFlags == 0 && !mLauncher.isWorkspaceLoading()) { + fillGapsWithPrediction(true); + } } /** @@ -160,11 +188,11 @@ public class HotseatPredictionController implements DragController.DragListener, } private void fillGapsWithPrediction() { - fillGapsWithPrediction(false, null); + fillGapsWithPrediction(false); } - private void fillGapsWithPrediction(boolean animate, Runnable callback) { - if (mUIUpdatePaused || mDragInProgress) { + private void fillGapsWithPrediction(boolean animate) { + if (mPauseFlags != 0) { return; } @@ -175,12 +203,14 @@ public class HotseatPredictionController implements DragController.DragListener, mIconRemoveAnimators.addListener(new AnimationSuccessListener() { @Override public void onAnimationSuccess(Animator animator) { - fillGapsWithPrediction(animate, callback); + fillGapsWithPrediction(animate); mIconRemoveAnimators.removeListener(this); } }); return; } + + mPauseFlags |= FLAG_FILL_IN_PROGRESS; for (int rank = 0; rank < mHotSeatItemsCount; rank++) { View child = mHotseat.getChildAt( mHotseat.getCellXFromOrder(rank), @@ -207,10 +237,12 @@ public class HotseatPredictionController implements DragController.DragListener, } preparePredictionInfo(predictedItem, rank); } - bindItems(newItems, animate, callback); + bindItems(newItems, animate); + + mPauseFlags &= ~FLAG_FILL_IN_PROGRESS; } - private void bindItems(List itemsToAdd, boolean animate, Runnable callback) { + private void bindItems(List itemsToAdd, boolean animate) { AnimatorSet animationSet = new AnimatorSet(); for (WorkspaceItemInfo item : itemsToAdd) { PredictedAppIcon icon = PredictedAppIcon.createIcon(mHotseat, item); @@ -221,12 +253,11 @@ public class HotseatPredictionController implements DragController.DragListener, } } if (animate) { - if (callback != null) { - animationSet.addListener(AnimationSuccessListener.forRunnable(callback)); - } + animationSet.addListener(AnimationSuccessListener + .forRunnable(this::removeOutlineDrawings)); animationSet.start(); } else { - if (callback != null) callback.run(); + removeOutlineDrawings(); } if (mLauncher.getTaskbarController() != null) { @@ -234,6 +265,16 @@ public class HotseatPredictionController implements DragController.DragListener, } } + private void removeOutlineDrawings() { + if (mOutlineDrawings.isEmpty()) return; + for (PredictedAppIcon.PredictedIconOutlineDrawing outlineDrawing : mOutlineDrawings) { + mHotseat.removeDelegatedCellDrawing(outlineDrawing); + } + mHotseat.invalidate(); + mOutlineDrawings.clear(); + } + + /** * Unregisters callbacks and frees resources */ @@ -245,11 +286,9 @@ public class HotseatPredictionController implements DragController.DragListener, * start and pauses predicted apps update on the hotseat */ public void setPauseUIUpdate(boolean paused) { - if (mLauncher.getTaskbarController() != null) { - // Taskbar is present, always allow updates since hotseat is still visible. - return; - } - mUIUpdatePaused = paused; + mPauseFlags = paused + ? (mPauseFlags | FLAG_UPDATE_PAUSED) + : (mPauseFlags & ~FLAG_UPDATE_PAUSED); if (!paused) { fillGapsWithPrediction(); } @@ -365,14 +404,14 @@ public class HotseatPredictionController implements DragController.DragListener, for (PredictedAppIcon.PredictedIconOutlineDrawing outlineDrawing : mOutlineDrawings) { mHotseat.addDelegatedCellDrawing(outlineDrawing); } - mDragInProgress = true; + mPauseFlags |= FLAG_DRAG_IN_PROGRESS; mHotseat.invalidate(); } @Override public void onDragEnd() { - mDragInProgress = false; - fillGapsWithPrediction(true, this::removeOutlineDrawings); + mPauseFlags &= ~FLAG_DRAG_IN_PROGRESS; + fillGapsWithPrediction(true); } @Nullable @@ -393,15 +432,6 @@ public class HotseatPredictionController implements DragController.DragListener, itemInfo.screenId = rank; } - private void removeOutlineDrawings() { - if (mOutlineDrawings.isEmpty()) return; - for (PredictedAppIcon.PredictedIconOutlineDrawing outlineDrawing : mOutlineDrawings) { - mHotseat.removeDelegatedCellDrawing(outlineDrawing); - } - mHotseat.invalidate(); - mOutlineDrawings.clear(); - } - @Override public void onIdpChanged(int changeFlags, InvariantDeviceProfile profile) { this.mHotSeatItemsCount = profile.numHotseatIcons; diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java index a00ce56e6a..3a26f25e56 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java @@ -17,6 +17,7 @@ package com.android.launcher3.uioverrides; import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED; +import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT; import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT; import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT; @@ -52,6 +53,7 @@ import com.android.launcher3.logging.InstanceId; import com.android.launcher3.logging.StatsLogManager.StatsLogger; import com.android.launcher3.model.BgDataModel.FixedContainerItems; import com.android.launcher3.model.data.ItemInfo; +import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.popup.SystemShortcut; import com.android.launcher3.search.DeviceSearchAdapterProvider; import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory; @@ -80,6 +82,7 @@ import com.android.quickstep.views.TaskView; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.List; import java.util.Objects; import java.util.stream.Stream; @@ -152,7 +155,8 @@ public class QuickstepLauncher extends BaseQuickstepLauncher { @Override public boolean startActivitySafely(View v, Intent intent, ItemInfo item) { if (mHotseatPredictionController != null) { - mHotseatPredictionController.setPauseUIUpdate(true); + // Only pause is taskbar controller is not present + mHotseatPredictionController.setPauseUIUpdate(getTaskbarController() == null); } return super.startActivitySafely(v, intent, item); } @@ -217,6 +221,15 @@ public class QuickstepLauncher extends BaseQuickstepLauncher { } } + @Override + public void bindWorkspaceItemsChanged(List updated) { + super.bindWorkspaceItemsChanged(updated); + if (getTaskbarController() != null && updated.stream() + .filter(w -> w.container == CONTAINER_HOTSEAT).findFirst().isPresent()) { + getTaskbarController().onHotseatUpdated(); + } + } + @Override public void onDestroy() { super.onDestroy(); From 0983b8070d5e22a5d0b83bad8d318dc4ad76ae3a Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Fri, 29 Jan 2021 14:00:38 -0800 Subject: [PATCH 0492/1707] Catching generic app start exceptions in ProxyActivity Bug: 178815863 Test: None Change-Id: I39ae2879e107160d552625db2946a439ee14440b --- .../launcher3/proxy/ProxyActivityStarter.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/quickstep/src/com/android/launcher3/proxy/ProxyActivityStarter.java b/quickstep/src/com/android/launcher3/proxy/ProxyActivityStarter.java index e302b4f152..4d7cc85ed1 100644 --- a/quickstep/src/com/android/launcher3/proxy/ProxyActivityStarter.java +++ b/quickstep/src/com/android/launcher3/proxy/ProxyActivityStarter.java @@ -17,6 +17,7 @@ package com.android.launcher3.proxy; import android.app.Activity; +import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.content.IntentSender.SendIntentException; @@ -48,19 +49,20 @@ public class ProxyActivityStarter extends Activity { return; } - if (mParams.intent != null) { - startActivityForResult(mParams.intent, mParams.requestCode, mParams.options); - return; - } else if (mParams.intentSender != null) { - try { + try { + if (mParams.intent != null) { + startActivityForResult(mParams.intent, mParams.requestCode, mParams.options); + return; + } else if (mParams.intentSender != null) { startIntentSenderForResult(mParams.intentSender, mParams.requestCode, mParams.fillInIntent, mParams.flagsMask, mParams.flagsValues, mParams.extraFlags, mParams.options); return; - } catch (SendIntentException e) { - mParams.deliverResult(this, RESULT_CANCELED, null); } + } catch (NullPointerException | ActivityNotFoundException | SecurityException + | SendIntentException e) { + mParams.deliverResult(this, RESULT_CANCELED, null); } finishAndRemoveTask(); } From ce79488572b5c5cbc1f2710eee195937dfbcad74 Mon Sep 17 00:00:00 2001 From: Tracy Zhou Date: Wed, 3 Feb 2021 13:35:42 -0800 Subject: [PATCH 0493/1707] Make sure that RemoteAnimationTargets is not null when redrawLiveTile() Fixes: 179289159 Test: manual Change-Id: I3a5d283582f0899d98717bbbdd146925cc0a4a93 --- quickstep/src/com/android/quickstep/views/RecentsView.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java index 2f2b566e32..d9d0a936b2 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -2277,7 +2277,9 @@ public abstract class RecentsView extends PagedView } public void redrawLiveTile() { - mLiveTileTaskViewSimulator.apply(mLiveTileParams); + if (mLiveTileParams.getTargetSet() != null) { + mLiveTileTaskViewSimulator.apply(mLiveTileParams); + } } public TaskViewSimulator getLiveTileTaskViewSimulator() { From 04ebcf468627c87e4a103959735260fe2bea5998 Mon Sep 17 00:00:00 2001 From: vadimt Date: Wed, 3 Feb 2021 15:31:51 -0800 Subject: [PATCH 0494/1707] Postponing registering widget listener For the swipe-to-home gesture, the call will happen after the animation. This fixes jank during swiping up from an ap to home. Also renaming some methods for clarity. Bug: 175048504, 173072373 Test: tracing Change-Id: I070b23edb633d2ac0985d5eb9f75c789f8569772 --- src/com/android/launcher3/Launcher.java | 8 +- .../launcher3/LauncherAppWidgetHost.java | 77 +++++++++---------- 2 files changed, 43 insertions(+), 42 deletions(-) diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 0274775244..c7d17a858a 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -922,7 +922,7 @@ public class Launcher extends StatefulActivity implements Launche } logStopAndResume(false /* isResume */); - mAppWidgetHost.setListenIfResumed(false); + mAppWidgetHost.setActivityStarted(false); NotificationListener.removeNotificationsChangedListener(); } @@ -935,7 +935,7 @@ public class Launcher extends StatefulActivity implements Launche mOverlayManager.onActivityStarted(this); } - mAppWidgetHost.setListenIfResumed(true); + mAppWidgetHost.setActivityStarted(true); TraceHelper.INSTANCE.endSection(traceToken); mLifecycleRegistry.setCurrentState(Lifecycle.State.STARTED); } @@ -956,6 +956,7 @@ public class Launcher extends StatefulActivity implements Launche NotificationListener.setNotificationsChangedListener(mPopupDataProvider); DiscoveryBounce.showForHomeIfNeeded(this); + mAppWidgetHost.setActivityResumed(true); } private void logStopAndResume(boolean isResume) { @@ -1049,7 +1050,7 @@ public class Launcher extends StatefulActivity implements Launche @Override public void onStateSetEnd(LauncherState state) { super.onStateSetEnd(state); - getAppWidgetHost().setResumed(state == LauncherState.NORMAL); + getAppWidgetHost().setStateIsNormal(state == LauncherState.NORMAL); getWorkspace().setClipChildren(!state.hasFlag(FLAG_MULTI_PAGE)); finishAutoCancelActionMode(); @@ -1108,6 +1109,7 @@ public class Launcher extends StatefulActivity implements Launche if (!mDeferOverlayCallbacks) { mOverlayManager.onActivityPaused(this); } + mAppWidgetHost.setActivityResumed(false); } class LauncherOverlayCallbacksImpl implements LauncherOverlayCallbacks { diff --git a/src/com/android/launcher3/LauncherAppWidgetHost.java b/src/com/android/launcher3/LauncherAppWidgetHost.java index 7ea68512ee..fea26df7da 100644 --- a/src/com/android/launcher3/LauncherAppWidgetHost.java +++ b/src/com/android/launcher3/LauncherAppWidgetHost.java @@ -49,8 +49,11 @@ import java.util.function.IntConsumer; public class LauncherAppWidgetHost extends AppWidgetHost { private static final int FLAG_LISTENING = 1; - private static final int FLAG_RESUMED = 1 << 1; - private static final int FLAG_LISTEN_IF_RESUMED = 1 << 2; + private static final int FLAG_STATE_IS_NORMAL = 1 << 1; + private static final int FLAG_ACTIVITY_STARTED = 1 << 2; + private static final int FLAG_ACTIVITY_RESUMED = 1 << 3; + private static final int FLAGS_SHOULD_LISTEN = + FLAG_STATE_IS_NORMAL | FLAG_ACTIVITY_STARTED | FLAG_ACTIVITY_RESUMED; public static final int APPWIDGET_HOST_ID = 1024; @@ -59,7 +62,7 @@ public class LauncherAppWidgetHost extends AppWidgetHost { private final SparseArray mPendingViews = new SparseArray<>(); private final Context mContext; - private int mFlags = FLAG_RESUMED; + private int mFlags = FLAG_STATE_IS_NORMAL; private IntConsumer mAppWidgetRemovedCallback = null; @@ -130,49 +133,45 @@ public class LauncherAppWidgetHost extends AppWidgetHost { } /** - * Updates the resumed state of the host. - * When a host is not resumed, it defers calls to startListening until host is resumed again. - * But if the host was already listening, it will not call stopListening. - * - * @see #setListenIfResumed(boolean) + * Sets or unsets a flag the can change whether the widget host should be in the listening + * state. */ - public void setResumed(boolean isResumed) { - if (isResumed == ((mFlags & FLAG_RESUMED) != 0)) { - return; - } - if (isResumed) { - mFlags |= FLAG_RESUMED; - // Start listening if we were supposed to start listening on resume - if ((mFlags & FLAG_LISTEN_IF_RESUMED) != 0 && (mFlags & FLAG_LISTENING) == 0) { - startListening(); - } + private void setShouldListenFlag(int flag, boolean on) { + if (on) { + mFlags |= flag; } else { - mFlags &= ~FLAG_RESUMED; + mFlags &= ~flag; + } + + final boolean listening = isListening(); + if (!listening && (mFlags & FLAGS_SHOULD_LISTEN) == FLAGS_SHOULD_LISTEN) { + // Postpone starting listening until all flags are on. + startListening(); + } else if (listening && (mFlags & FLAG_ACTIVITY_STARTED) == 0) { + // Postpone stopping listening until the activity is stopped. + stopListening(); } } /** - * Updates the listening state of the host. If the host is not resumed, startListening is - * deferred until next resume. - * - * @see #setResumed(boolean) + * Registers an "entering/leaving Normal state" event. */ - public void setListenIfResumed(boolean listenIfResumed) { - if (listenIfResumed == ((mFlags & FLAG_LISTEN_IF_RESUMED) != 0)) { - return; - } - if (listenIfResumed) { - mFlags |= FLAG_LISTEN_IF_RESUMED; - if ((mFlags & FLAG_RESUMED) != 0) { - // If we are resumed, start listening immediately. Note we do not check for - // duplicate calls before calling startListening as startListening is safe to call - // multiple times. - startListening(); - } - } else { - mFlags &= ~FLAG_LISTEN_IF_RESUMED; - stopListening(); - } + public void setStateIsNormal(boolean isNormal) { + setShouldListenFlag(FLAG_STATE_IS_NORMAL, isNormal); + } + + /** + * Registers an "activity started/stopped" event. + */ + public void setActivityStarted(boolean isStarted) { + setShouldListenFlag(FLAG_ACTIVITY_STARTED, isStarted); + } + + /** + * Registers an "activity paused/resumed" event. + */ + public void setActivityResumed(boolean isResumed) { + setShouldListenFlag(FLAG_ACTIVITY_RESUMED, isResumed); } @Override From 1b4d5034fbf56b227ec9697d0d43b3e3d085670d Mon Sep 17 00:00:00 2001 From: Schneider Victor-tulias Date: Wed, 3 Feb 2021 13:05:21 -0800 Subject: [PATCH 0495/1707] Fix icon flicker on transition from installing, to installed, to downloading incrementally. When an app finished downloading, we assumed that its progress level was 100%. This caused flicker in apps that supported incremental downloads. Added a check for the total download progress on app installation. demo: https://drive.google.com/file/d/11NwwhHj_4rmDFwpsb8gTX-uA5YSinjSj/view?usp=sharing Fixes: 178745816 Test: manual Change-Id: I08328296db2ce2c2242fc4fe87158ff4b911c659 --- .../android/launcher3/model/AllAppsList.java | 16 ++++++++++++---- .../launcher3/model/PackageUpdatedTask.java | 18 +++++++++++++++--- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/src/com/android/launcher3/model/AllAppsList.java b/src/com/android/launcher3/model/AllAppsList.java index d4fa2789d5..e3e4b6911f 100644 --- a/src/com/android/launcher3/model/AllAppsList.java +++ b/src/com/android/launcher3/model/AllAppsList.java @@ -203,11 +203,16 @@ public class AllAppsList { /** * Add the icons for the supplied apk called packageName. */ - public void addPackage(Context context, String packageName, UserHandle user) { - for (LauncherActivityInfo info : context.getSystemService(LauncherApps.class) - .getActivityList(packageName, user)) { + public List addPackage( + Context context, String packageName, UserHandle user) { + List activities = context.getSystemService(LauncherApps.class) + .getActivityList(packageName, user); + + for (LauncherActivityInfo info : activities) { add(new AppInfo(context, info, user), info); } + + return activities; } /** @@ -250,7 +255,8 @@ public class AllAppsList { /** * Add and remove icons for this package which has been updated. */ - public void updatePackage(Context context, String packageName, UserHandle user) { + public List updatePackage( + Context context, String packageName, UserHandle user) { final List matches = context.getSystemService(LauncherApps.class) .getActivityList(packageName, user); if (matches.size() > 0) { @@ -297,6 +303,8 @@ public class AllAppsList { } } } + + return matches; } /** diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java index 3275d59488..f13a109926 100644 --- a/src/com/android/launcher3/model/PackageUpdatedTask.java +++ b/src/com/android/launcher3/model/PackageUpdatedTask.java @@ -22,6 +22,7 @@ import static com.android.launcher3.model.data.WorkspaceItemInfo.FLAG_RESTORED_I import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.pm.LauncherActivityInfo; import android.content.pm.LauncherApps; import android.content.pm.ShortcutInfo; import android.os.UserHandle; @@ -51,6 +52,7 @@ import com.android.launcher3.util.SafeCloseable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -95,6 +97,7 @@ public class PackageUpdatedTask extends BaseModelUpdateTask { ? ItemInfoMatcher.ofUser(mUser) // We want to update all packages for this user : ItemInfoMatcher.ofPackages(packageSet, mUser); final HashSet removedComponents = new HashSet<>(); + final HashMap> activitiesLists = new HashMap<>(); switch (mOp) { case OP_ADD: { @@ -104,7 +107,8 @@ public class PackageUpdatedTask extends BaseModelUpdateTask { if (FeatureFlags.PROMISE_APPS_IN_ALL_APPS.get()) { appsList.removePackage(packages[i], mUser); } - appsList.addPackage(context, packages[i], mUser); + activitiesLists.put( + packages[i], appsList.addPackage(context, packages[i], mUser)); } flagOp = FlagOp.removeFlag(WorkspaceItemInfo.FLAG_DISABLED_NOT_AVAILABLE); break; @@ -115,7 +119,8 @@ public class PackageUpdatedTask extends BaseModelUpdateTask { for (int i = 0; i < N; i++) { if (DEBUG) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]); iconCache.updateIconsForPkg(packages[i], mUser); - appsList.updatePackage(context, packages[i], mUser); + activitiesLists.put( + packages[i], appsList.updatePackage(context, packages[i], mUser)); app.getWidgetCache().removePackage(packages[i], mUser); } } @@ -247,7 +252,14 @@ public class PackageUpdatedTask extends BaseModelUpdateTask { if (isNewApkAvailable && si.itemType == Favorites.ITEM_TYPE_APPLICATION) { - si.setProgressLevel(100, PackageInstallInfo.STATUS_INSTALLED); + List activities = activitiesLists.get( + packageName); + si.setProgressLevel( + activities == null || activities.isEmpty() + ? 100 + : PackageManagerHelper.getLoadingProgress( + activities.get(0)), + PackageInstallInfo.STATUS_INSTALLED_DOWNLOADING); iconCache.getTitleAndIcon(si, si.usingLowResIcon()); infoUpdated = true; } From aaa42bd0e85f21c78d999c20adb819d77b6df731 Mon Sep 17 00:00:00 2001 From: Tony Wickham Date: Wed, 27 Jan 2021 16:27:15 -0800 Subject: [PATCH 0496/1707] Add recent tasks to Taskbar - Also adds a divider between Hotseat and Recents - Dedupes Recents from Hotseat Test: Open some recent tasks, ensure they are deduped from Hotseat and also handle < 2 tasks. Bug: 171917176 Change-Id: Ia782c6ccbcda94cfd844aad04dc3d25a3f072c2b --- quickstep/res/layout/taskbar_divider.xml | 23 ++++ quickstep/res/values/colors.xml | 1 + quickstep/res/values/dimens.xml | 2 + .../launcher3/taskbar/TaskbarController.java | 102 +++++++++++++++- .../taskbar/TaskbarDragController.java | 11 ++ .../taskbar/TaskbarRecentsController.java | 114 ++++++++++++++++++ .../launcher3/taskbar/TaskbarView.java | 101 +++++++++++++++- src/com/android/launcher3/BubbleTextView.java | 10 ++ 8 files changed, 361 insertions(+), 3 deletions(-) create mode 100644 quickstep/res/layout/taskbar_divider.xml create mode 100644 quickstep/src/com/android/launcher3/taskbar/TaskbarRecentsController.java diff --git a/quickstep/res/layout/taskbar_divider.xml b/quickstep/res/layout/taskbar_divider.xml new file mode 100644 index 0000000000..6e1aa1e3d5 --- /dev/null +++ b/quickstep/res/layout/taskbar_divider.xml @@ -0,0 +1,23 @@ + + + + \ No newline at end of file diff --git a/quickstep/res/values/colors.xml b/quickstep/res/values/colors.xml index 3bc8ddc0a8..54730f100e 100644 --- a/quickstep/res/values/colors.xml +++ b/quickstep/res/values/colors.xml @@ -27,4 +27,5 @@ #101010 + #C0C0C0 \ No newline at end of file diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml index 39cc0b891c..0f40775995 100644 --- a/quickstep/res/values/dimens.xml +++ b/quickstep/res/values/dimens.xml @@ -127,4 +127,6 @@ 54dp 14dp + 1dp + 24dp diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java index 7608645a63..6a74aac2ba 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java @@ -23,6 +23,8 @@ import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_BOTT import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_EXTRA_NAVIGATION_BAR; import android.animation.Animator; +import android.app.ActivityOptions; +import android.content.ComponentName; import android.graphics.PixelFormat; import android.graphics.Point; import android.view.Gravity; @@ -42,8 +44,13 @@ import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.states.StateAnimationConfig; import com.android.launcher3.touch.ItemClickHandler; import com.android.quickstep.AnimatedFloat; +import com.android.systemui.shared.recents.model.Task; +import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.WindowManagerWrapper; +import java.util.ArrayList; +import java.util.List; + /** * Interfaces with Launcher/WindowManager/SystemUI to determine what to show in TaskbarView. */ @@ -60,11 +67,17 @@ public class TaskbarController { private final TaskbarStateHandler mTaskbarStateHandler; private final TaskbarVisibilityController mTaskbarVisibilityController; private final TaskbarHotseatController mHotseatController; + private final TaskbarRecentsController mRecentsController; private final TaskbarDragController mDragController; // Initialized in init(). private WindowManager.LayoutParams mWindowLayoutParams; + // Contains all loaded Tasks, not yet deduped from Hotseat items. + private List mLatestLoadedRecentTasks; + // Contains all loaded Hotseat items. + private ItemInfo[] mLatestLoadedHotseatItems; + public TaskbarController(BaseQuickstepLauncher launcher, TaskbarContainerView taskbarContainerView) { mLauncher = launcher; @@ -79,6 +92,8 @@ public class TaskbarController { createTaskbarVisibilityControllerCallbacks()); mHotseatController = new TaskbarHotseatController(mLauncher, createTaskbarHotseatControllerCallbacks()); + mRecentsController = new TaskbarRecentsController(mLauncher, + createTaskbarRecentsControllerCallbacks()); mDragController = new TaskbarDragController(mLauncher); } @@ -101,7 +116,16 @@ public class TaskbarController { return new TaskbarViewCallbacks() { @Override public View.OnClickListener getItemOnClickListener() { - return ItemClickHandler.INSTANCE; + return view -> { + Object tag = view.getTag(); + if (tag instanceof Task) { + Task task = (Task) tag; + ActivityManagerWrapper.getInstance().startActivityFromRecents(task.key, + ActivityOptions.makeBasic()); + } else { + ItemClickHandler.INSTANCE.onClick(view); + } + }; } @Override @@ -116,6 +140,23 @@ public class TaskbarController { @Override public void updateHotseatItems(ItemInfo[] hotseatItemInfos) { mTaskbarView.updateHotseatItems(hotseatItemInfos); + mLatestLoadedHotseatItems = hotseatItemInfos; + dedupeAndUpdateRecentItems(); + } + }; + } + + private TaskbarRecentsControllerCallbacks createTaskbarRecentsControllerCallbacks() { + return new TaskbarRecentsControllerCallbacks() { + @Override + public void updateRecentItems(ArrayList recentTasks) { + mLatestLoadedRecentTasks = recentTasks; + dedupeAndUpdateRecentItems(); + } + + @Override + public void updateRecentTaskAtIndex(int taskIndex, Task task) { + mTaskbarView.updateRecentTaskAtIndex(taskIndex, task); } }; } @@ -124,11 +165,13 @@ public class TaskbarController { * Initializes the Taskbar, including adding it to the screen. */ public void init() { - mTaskbarView.init(mHotseatController.getNumHotseatIcons()); + mTaskbarView.init(mHotseatController.getNumHotseatIcons(), + mRecentsController.getNumRecentIcons()); addToWindowManager(); mTaskbarStateHandler.setTaskbarCallbacks(createTaskbarStateHandlerCallbacks()); mTaskbarVisibilityController.init(); mHotseatController.init(); + mRecentsController.init(); } private TaskbarStateHandlerCallbacks createTaskbarStateHandlerCallbacks() { @@ -149,6 +192,7 @@ public class TaskbarController { mTaskbarStateHandler.setTaskbarCallbacks(null); mTaskbarVisibilityController.cleanup(); mHotseatController.cleanup(); + mRecentsController.cleanup(); } private void removeFromWindowManager() { @@ -246,6 +290,52 @@ public class TaskbarController { return mTaskbarView.isDraggingItem(); } + private void dedupeAndUpdateRecentItems() { + if (mLatestLoadedRecentTasks == null || mLatestLoadedHotseatItems == null) { + return; + } + + final int numRecentIcons = mRecentsController.getNumRecentIcons(); + + // From most recent to least recently opened. + List dedupedTasksInDescendingOrder = new ArrayList<>(); + for (int i = mLatestLoadedRecentTasks.size() - 1; i >= 0; i--) { + Task task = mLatestLoadedRecentTasks.get(i); + boolean isTaskInHotseat = false; + for (ItemInfo hotseatItem : mLatestLoadedHotseatItems) { + if (hotseatItem == null) { + continue; + } + ComponentName hotseatActivity = hotseatItem.getTargetComponent(); + if (hotseatActivity != null && task.key.sourceComponent.getPackageName() + .equals(hotseatActivity.getPackageName())) { + isTaskInHotseat = true; + break; + } + } + if (!isTaskInHotseat) { + dedupedTasksInDescendingOrder.add(task); + if (dedupedTasksInDescendingOrder.size() == numRecentIcons) { + break; + } + } + } + + // TaskbarView expects an array of all the recent tasks to show, in the order to show them. + // So we create an array of the proper size, then fill it in such that the most recent items + // are at the end. If there aren't enough elements to fill the array, leave them null. + Task[] tasksArray = new Task[numRecentIcons]; + for (int i = 0; i < tasksArray.length; i++) { + Task task = i >= dedupedTasksInDescendingOrder.size() + ? null + : dedupedTasksInDescendingOrder.get(i); + tasksArray[tasksArray.length - 1 - i] = task; + } + + mTaskbarView.updateRecentTasks(tasksArray); + mRecentsController.loadIconsForTasks(tasksArray); + } + /** * @return Whether the given View is in the same window as Taskbar. */ @@ -283,4 +373,12 @@ public class TaskbarController { protected interface TaskbarHotseatControllerCallbacks { void updateHotseatItems(ItemInfo[] hotseatItemInfos); } + + /** + * Contains methods that TaskbarRecentsController can call to interface with TaskbarController. + */ + protected interface TaskbarRecentsControllerCallbacks { + void updateRecentItems(ArrayList recentTasks); + void updateRecentTaskAtIndex(int taskIndex, Task task); + } } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java index 2318ff96b6..baec8998f3 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java @@ -25,6 +25,7 @@ import android.content.pm.LauncherApps; import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.Point; +import android.os.UserHandle; import android.view.DragEvent; import android.view.View; @@ -33,6 +34,7 @@ import com.android.launcher3.BubbleTextView; import com.android.launcher3.LauncherSettings; import com.android.launcher3.R; import com.android.launcher3.model.data.WorkspaceItemInfo; +import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.system.ClipDescriptionCompat; import com.android.systemui.shared.system.LauncherAppsCompat; @@ -102,6 +104,15 @@ public class TaskbarDragController { item.getIntent().getComponent(), null, item.user)); } intent.putExtra(Intent.EXTRA_USER, item.user); + } else if (tag instanceof Task) { + Task task = (Task) tag; + clipDescription = new ClipDescription(task.titleDescription, + new String[] { + ClipDescriptionCompat.MIMETYPE_APPLICATION_TASK + }); + intent = new Intent(); + intent.putExtra(ClipDescriptionCompat.EXTRA_TASK_ID, task.key.id); + intent.putExtra(Intent.EXTRA_USER, UserHandle.of(task.key.userId)); } if (clipDescription != null && intent != null) { diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentsController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentsController.java new file mode 100644 index 0000000000..9d4e000da0 --- /dev/null +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentsController.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.taskbar; + +import com.android.launcher3.BaseQuickstepLauncher; +import com.android.quickstep.RecentsModel; +import com.android.quickstep.util.CancellableTask; +import com.android.systemui.shared.recents.model.Task; +import com.android.systemui.shared.system.TaskStackChangeListener; +import com.android.systemui.shared.system.TaskStackChangeListeners; + +import java.util.ArrayList; + +/** + * Works with TaskbarController to update the TaskbarView's Recent items. + */ +public class TaskbarRecentsController { + + private final int mNumRecentIcons = 2; + private final BaseQuickstepLauncher mLauncher; + private final TaskbarController.TaskbarRecentsControllerCallbacks mTaskbarCallbacks; + private final RecentsModel mRecentsModel; + + private final TaskStackChangeListener mTaskStackChangeListener = new TaskStackChangeListener() { + @Override + public void onTaskStackChanged() { + reloadRecentTasksIfNeeded(); + } + }; + + // TODO: add TaskbarVisualsChangedListener as well (for calendar/clock?) + + // Used to keep track of the last requested task list id, so that we do not request to load the + // tasks again if we have already requested it and the task list has not changed + private int mTaskListChangeId = -1; + + // The current background requests to load the task icons + private CancellableTask[] mIconLoadRequests = new CancellableTask[mNumRecentIcons]; + + public TaskbarRecentsController(BaseQuickstepLauncher launcher, + TaskbarController.TaskbarRecentsControllerCallbacks taskbarCallbacks) { + mLauncher = launcher; + mTaskbarCallbacks = taskbarCallbacks; + mRecentsModel = RecentsModel.INSTANCE.get(mLauncher); + } + + protected void init() { + TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackChangeListener); + reloadRecentTasksIfNeeded(); + } + + protected void cleanup() { + TaskStackChangeListeners.getInstance().unregisterTaskStackListener( + mTaskStackChangeListener); + cancelAllPendingIconLoadTasks(); + } + + private void reloadRecentTasksIfNeeded() { + if (!mRecentsModel.isTaskListValid(mTaskListChangeId)) { + mTaskListChangeId = mRecentsModel.getTasks(this::onRecentTasksChanged); + } + } + + private void cancelAllPendingIconLoadTasks() { + for (int i = 0; i < mIconLoadRequests.length; i++) { + if (mIconLoadRequests[i] != null) { + mIconLoadRequests[i].cancel(); + } + mIconLoadRequests[i] = null; + } + } + + private void onRecentTasksChanged(ArrayList tasks) { + mTaskbarCallbacks.updateRecentItems(tasks); + } + + /** + * For each Task, loads its icon from the cache in the background, then calls + * {@link TaskbarController.TaskbarRecentsControllerCallbacks#updateRecentTaskAtIndex}. + */ + protected void loadIconsForTasks(Task[] tasks) { + cancelAllPendingIconLoadTasks(); + for (int i = 0; i < tasks.length; i++) { + Task task = tasks[i]; + if (task == null) { + continue; + } + final int taskIndex = i; + mIconLoadRequests[i] = mRecentsModel.getIconCache().updateIconInBackground( + task, updatedTask -> onTaskIconLoaded(task, taskIndex)); + } + } + + private void onTaskIconLoaded(Task task, int taskIndex) { + mTaskbarCallbacks.updateRecentTaskAtIndex(taskIndex, task); + } + + protected int getNumRecentIcons() { + return mNumRecentIcons; + } +} diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java index c98f09ca0f..d8f3bb595e 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java @@ -19,6 +19,7 @@ import android.content.Context; import android.content.res.Resources; import android.graphics.RectF; import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.DragEvent; import android.view.LayoutInflater; @@ -35,6 +36,7 @@ import com.android.launcher3.BubbleTextView; import com.android.launcher3.R; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; +import com.android.systemui.shared.recents.model.Task; /** * Hosts the Taskbar content such as Hotseat and Recent Apps. Drawn on top of other apps. @@ -52,6 +54,9 @@ public class TaskbarView extends LinearLayout { // Initialized in init(). private int mHotseatStartIndex; private int mHotseatEndIndex; + private View mHotseatRecentsDivider; + private int mRecentsStartIndex; + private int mRecentsEndIndex; private TaskbarController.TaskbarViewCallbacks mControllerCallbacks; @@ -89,10 +94,17 @@ public class TaskbarView extends LinearLayout { mControllerCallbacks = taskbarViewCallbacks; } - protected void init(int numHotseatIcons) { + protected void init(int numHotseatIcons, int numRecentIcons) { mHotseatStartIndex = 0; mHotseatEndIndex = mHotseatStartIndex + numHotseatIcons - 1; updateHotseatItems(new ItemInfo[numHotseatIcons]); + + int dividerIndex = mHotseatEndIndex + 1; + mHotseatRecentsDivider = addDivider(dividerIndex); + + mRecentsStartIndex = dividerIndex + 1; + mRecentsEndIndex = mRecentsStartIndex + numRecentIcons - 1; + updateRecentTasks(new Task[numRecentIcons]); } protected void cleanup() { @@ -147,6 +159,93 @@ public class TaskbarView extends LinearLayout { hotseatView.setOnLongClickListener(null); } } + + updateHotseatRecentsDividerVisibility(); + } + + private View addDivider(int dividerIndex) { + View divider = inflate(R.layout.taskbar_divider); + addView(divider, dividerIndex); + return divider; + } + + /** + * Inflates/binds the Recents items to show in the Taskbar given their Tasks. + */ + protected void updateRecentTasks(Task[] tasks) { + for (int i = 0; i < tasks.length; i++) { + Task task = tasks[i]; + int recentsIndex = mRecentsStartIndex + i; + View recentsView = getChildAt(recentsIndex); + + // Inflate empty icon Views. + if (recentsView == null) { + BubbleTextView btv = (BubbleTextView) inflate(R.layout.taskbar_app_icon); + LayoutParams lp = new LayoutParams(btv.getIconSize(), btv.getIconSize()); + lp.setMargins(mItemMarginLeftRight, 0, mItemMarginLeftRight, 0); + recentsView = btv; + addView(recentsView, recentsIndex, lp); + } + + // Apply the Task, or hide the view if there is none for a given index. + if (recentsView instanceof BubbleTextView && task != null) { + applyTaskToBubbleTextView((BubbleTextView) recentsView, task); + recentsView.setVisibility(VISIBLE); + recentsView.setOnClickListener(mControllerCallbacks.getItemOnClickListener()); + recentsView.setOnLongClickListener( + mControllerCallbacks.getItemOnLongClickListener()); + } else { + recentsView.setVisibility(GONE); + recentsView.setOnClickListener(null); + recentsView.setOnLongClickListener(null); + } + } + + updateHotseatRecentsDividerVisibility(); + } + + private void applyTaskToBubbleTextView(BubbleTextView btv, Task task) { + if (task.icon != null) { + Drawable icon = task.icon.getConstantState().newDrawable().mutate(); + btv.applyIconAndLabel(icon, task.titleDescription); + } + btv.setTag(task); + } + + protected void updateRecentTaskAtIndex(int taskIndex, Task task) { + View taskView = getChildAt(mRecentsStartIndex + taskIndex); + if (taskView instanceof BubbleTextView) { + applyTaskToBubbleTextView((BubbleTextView) taskView, task); + } + } + + /** + * Make the divider VISIBLE between the Hotseat and Recents if there is at least one icon in + * each, otherwise make it GONE. + */ + private void updateHotseatRecentsDividerVisibility() { + if (mHotseatRecentsDivider == null) { + return; + } + + boolean hasAtLeastOneHotseatItem = false; + for (int i = mHotseatStartIndex; i <= mHotseatEndIndex; i++) { + if (getChildAt(i).getVisibility() != GONE) { + hasAtLeastOneHotseatItem = true; + break; + } + } + + boolean hasAtLeastOneRecentItem = false; + for (int i = mRecentsStartIndex; i <= mRecentsEndIndex; i++) { + if (getChildAt(i).getVisibility() != GONE) { + hasAtLeastOneRecentItem = true; + break; + } + } + + mHotseatRecentsDivider.setVisibility(hasAtLeastOneHotseatItem && hasAtLeastOneRecentItem + ? VISIBLE : GONE); } @Override diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index 5007ffc7be..6f446ad910 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -358,6 +358,16 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, } } + /** + * Directly set the icon and label. + */ + @UiThread + public void applyIconAndLabel(Drawable icon, CharSequence label) { + setIcon(icon); + setText(label); + setContentDescription(label); + } + /** * Overrides the default long press timeout. */ From 4e2c25a788f1b0eae6a472c6749fdadfcf046b84 Mon Sep 17 00:00:00 2001 From: Alex Chau Date: Wed, 3 Feb 2021 16:20:04 +0000 Subject: [PATCH 0497/1707] Ensure isTablet is only enable when simulated landscape is disabled - Tablet UI (e.g. grid overview and two panel workspace) is built with assumption that simulated landscape is disabled, make sure this is the case in DeviceProfile - This is usually true as swDPs is generally smaller than sw used to determine xml files Bug: 174464656 Test: Try different combination of smallest width and Display Size, isTablet is only set when allow_rotation is true Change-Id: I3ee1c76909f29b6f14c4032be812bb8c9ea7e827 --- src/com/android/launcher3/DeviceProfile.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java index f681d75eef..4d5bd5d95f 100644 --- a/src/com/android/launcher3/DeviceProfile.java +++ b/src/com/android/launcher3/DeviceProfile.java @@ -168,8 +168,10 @@ public class DeviceProfile { // Constants from resources float swDPs = Utilities.dpiFromPx( Math.min(info.smallestSize.x, info.smallestSize.y), info.metrics); - isTablet = swDPs >= TABLET_MIN_DPS; - isLargeTablet = swDPs >= LARGE_TABLET_MIN_DPS; + boolean allowRotation = context.getResources().getBoolean(R.bool.allow_rotation); + // Tablet UI is built with assumption that simulated landscape is disabled. + isTablet = allowRotation && swDPs >= TABLET_MIN_DPS; + isLargeTablet = isTablet && swDPs >= LARGE_TABLET_MIN_DPS; isPhone = !isTablet && !isLargeTablet; aspectRatio = ((float) Math.max(widthPx, heightPx)) / Math.min(widthPx, heightPx); boolean isTallDevice = Float.compare(aspectRatio, TALL_DEVICE_ASPECT_RATIO_THRESHOLD) >= 0; From 63fc59b80108125eb405af7f245cc9711634ec5e Mon Sep 17 00:00:00 2001 From: Tony Wickham Date: Thu, 4 Feb 2021 08:36:08 -0800 Subject: [PATCH 0498/1707] Use SYSTEM_APPLICATION_OVERLAY instead of SYSTEM_ALERT_WINDOW for Taskbar Test: Taskbar still shows up and works, can't hide it from Settings Bug: 169054709 Change-Id: I0d2f9be8a0af2242472d8406bbc31a6aecaa9285 --- quickstep/AndroidManifest.xml | 2 +- .../src/com/android/launcher3/taskbar/TaskbarController.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml index 9c14b8510e..7431551710 100644 --- a/quickstep/AndroidManifest.xml +++ b/quickstep/AndroidManifest.xml @@ -41,7 +41,7 @@ - + Date: Thu, 4 Feb 2021 13:05:07 -0600 Subject: [PATCH 0499/1707] Only show OOBE once Bug: 179333425 Test: Manual Change-Id: I23de8fffa31ae06b0f4feccfb75144c7c55143c2 --- .../launcher3/search/DeviceSearchEdu.java | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/quickstep/src/com/android/launcher3/search/DeviceSearchEdu.java b/quickstep/src/com/android/launcher3/search/DeviceSearchEdu.java index 425e557ecb..71270cc94a 100644 --- a/quickstep/src/com/android/launcher3/search/DeviceSearchEdu.java +++ b/quickstep/src/com/android/launcher3/search/DeviceSearchEdu.java @@ -73,11 +73,9 @@ public class DeviceSearchEdu extends AbstractSlideInView implements } - private void close(boolean animate, boolean markAsSeen) { - handleClose(animate); - if (markAsSeen) { - mLauncher.getOnboardingPrefs().markChecked(SEARCH_EDU_SEEN); - } + private void dismiss() { + handleClose(true); + mLauncher.getOnboardingPrefs().markChecked(SEARCH_EDU_SEEN); } @Override @@ -110,7 +108,7 @@ public class DeviceSearchEdu extends AbstractSlideInView implements findViewById(R.id.dismiss_edu).setOnClickListener((view) -> { mSwitchFocusOnDismiss = true; - close(true, true); + dismiss(); }); } @@ -176,7 +174,7 @@ public class DeviceSearchEdu extends AbstractSlideInView implements @Override public void onStateTransitionStart(LauncherState toState) { - close(true, false); + dismiss(); } @Override @@ -203,7 +201,7 @@ public class DeviceSearchEdu extends AbstractSlideInView implements if (mSearchInput != null) { mSearchInput.setText(charSequence.toString()); mSwitchFocusOnDismiss = true; - close(true, true); + dismiss(); } } @@ -215,7 +213,7 @@ public class DeviceSearchEdu extends AbstractSlideInView implements @Override public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) { mSearchInput.onEditorAction(i); - close(true, true); + dismiss(); return true; } } From 79e52fc23b68d300b497c1506aeeb9727ac25f39 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Wed, 3 Feb 2021 10:22:28 -0800 Subject: [PATCH 0500/1707] Moving slice loading on a background thread > Also fixing slice icon loading, such that the request is cancelled if views are recycled quickly > Loading widget label on dg thread and cancelling request if views are recycled quickly. Bug: 179068415 Test: verified locally Change-Id: Id5a524e2bf596862330a8170394aef9ffd708544 --- .../search/SearchResultIconSlice.java | 38 +-- .../launcher3/search/SearchResultWidget.java | 31 ++- src/com/android/launcher3/BubbleTextView.java | 4 +- src/com/android/launcher3/Launcher.java | 27 +-- .../allapps/AllAppsTransitionController.java | 3 - .../allapps/search/LiveSearchManager.java | 220 ++++++++++++++---- .../search/SearchWidgetInfoContainer.java | 6 - .../android/launcher3/icons/IconCache.java | 35 ++- 8 files changed, 235 insertions(+), 129 deletions(-) diff --git a/quickstep/src/com/android/launcher3/search/SearchResultIconSlice.java b/quickstep/src/com/android/launcher3/search/SearchResultIconSlice.java index c441e2291b..4bf34324a7 100644 --- a/quickstep/src/com/android/launcher3/search/SearchResultIconSlice.java +++ b/quickstep/src/com/android/launcher3/search/SearchResultIconSlice.java @@ -15,28 +15,22 @@ */ package com.android.launcher3.search; -import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; -import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; - import android.app.search.SearchTarget; import android.app.search.SearchTargetEvent; import android.content.Context; import android.util.AttributeSet; -import android.util.Log; import android.widget.LinearLayout; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.lifecycle.LiveData; -import androidx.slice.Slice; import androidx.slice.SliceItem; import androidx.slice.widget.EventInfo; import androidx.slice.widget.SliceView; import com.android.launcher3.Launcher; -import com.android.launcher3.LauncherAppState; import com.android.launcher3.R; import com.android.launcher3.model.data.PackageItemInfo; +import com.android.launcher3.util.SafeCloseable; import java.util.ArrayList; import java.util.List; @@ -47,13 +41,11 @@ import java.util.List; public class SearchResultIconSlice extends LinearLayout implements SearchTargetHandler, SliceView.OnSliceActionListener { - private static final String TAG = "SearchSliceController"; - private final Launcher mLauncher; private SliceView mSliceView; private SearchResultIcon mIcon; - private LiveData mSliceLiveData; + private SafeCloseable mSliceSession; private String mTargetId; public SearchResultIconSlice(Context context) { @@ -87,26 +79,20 @@ public class SearchResultIconSlice extends LinearLayout implements SearchTargetH mTargetId = parentTarget.getId(); reset(); updateIcon(parentTarget, children); - try { - mSliceLiveData = mLauncher.getLiveSearchManager().getSliceForUri( - parentTarget.getSliceUri()); - mSliceLiveData.observe(mLauncher, mSliceView); - } catch (Exception ex) { - Log.e(TAG, "unable to bind slice", ex); - } + mSliceSession = mLauncher.getLiveSearchManager() + .addObserver(parentTarget.getSliceUri(), mSliceView); } private void updateIcon(SearchTarget parentTarget, List children) { if (children.size() == 1) { mIcon.apply(children.get(0), new ArrayList<>()); } else { - LauncherAppState appState = LauncherAppState.getInstance(getContext()); - MODEL_EXECUTOR.post(() -> { - PackageItemInfo pkgItem = new PackageItemInfo(parentTarget.getPackageName()); - pkgItem.user = parentTarget.getUserHandle(); - appState.getIconCache().getTitleAndIconForApp(pkgItem, false); - MAIN_EXECUTOR.post(() -> mIcon.applyFromItemInfoWithIcon(pkgItem)); - }); + PackageItemInfo pkgItem = new PackageItemInfo(parentTarget.getPackageName()); + pkgItem.user = parentTarget.getUserHandle(); + if (!pkgItem.equals(mIcon.getTag())) { + // The icon will load and apply high res icon automatically + mIcon.applyFromItemInfoWithIcon(pkgItem); + } } } @@ -124,8 +110,8 @@ public class SearchResultIconSlice extends LinearLayout implements SearchTargetH private void reset() { mSliceView.setOnSliceActionListener(null); - if (mSliceLiveData != null) { - mSliceLiveData.removeObservers(mLauncher); + if (mSliceSession != null) { + mSliceSession.close(); } } diff --git a/quickstep/src/com/android/launcher3/search/SearchResultWidget.java b/quickstep/src/com/android/launcher3/search/SearchResultWidget.java index 4c64265cc3..e22f6ab992 100644 --- a/quickstep/src/com/android/launcher3/search/SearchResultWidget.java +++ b/quickstep/src/com/android/launcher3/search/SearchResultWidget.java @@ -15,6 +15,9 @@ */ package com.android.launcher3.search; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; +import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; + import android.app.search.SearchTarget; import android.app.search.SearchTargetEvent; import android.appwidget.AppWidgetHostView; @@ -36,16 +39,19 @@ import com.android.launcher3.BubbleTextView; import com.android.launcher3.CheckLongPressHelper; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.LauncherAppWidgetProviderInfo; import com.android.launcher3.R; +import com.android.launcher3.Utilities; import com.android.launcher3.allapps.search.SearchWidgetInfoContainer; import com.android.launcher3.dragndrop.DraggableView; +import com.android.launcher3.icons.cache.HandlerRunnable; import com.android.launcher3.model.data.PackageItemInfo; import com.android.launcher3.touch.ItemLongClickListener; import com.android.launcher3.widget.PendingAddWidgetInfo; import java.util.List; - /** * displays live version of a widget upon receiving {@link AppWidgetProviderInfo} from Search * provider @@ -63,6 +69,7 @@ public class SearchResultWidget extends LinearLayout implements SearchTargetHand private final float mScaleToFit; private SearchWidgetInfoContainer mInfoContainer; + private HandlerRunnable mLabelRequest; private BubbleTextView mWidgetProvider; private TextView mWidgetLabel; @@ -124,11 +131,18 @@ public class SearchResultWidget extends LinearLayout implements SearchTargetHand } private void showWidgetInfo(AppWidgetProviderInfo providerInfo) { - String title = providerInfo.loadLabel(mLauncher.getPackageManager()); PackageItemInfo pinfo = new PackageItemInfo(providerInfo.provider.getPackageName()); pinfo.user = providerInfo.getProfile(); mWidgetProvider.applyFromItemInfoWithIcon(pinfo); - mWidgetLabel.setText(title); + + mLabelRequest = new HandlerRunnable<>( + MODEL_EXECUTOR.getHandler(), + () -> LauncherAppState.getInstance(mLauncher).getIconCache() + .getTitleNoCache(LauncherAppWidgetProviderInfo + .fromProviderInfo(mLauncher, providerInfo)), + MAIN_EXECUTOR, + mWidgetLabel::setText); + Utilities.postAsyncCallback(MODEL_EXECUTOR.getHandler(), mLabelRequest); } /** @@ -137,7 +151,18 @@ public class SearchResultWidget extends LinearLayout implements SearchTargetHand public void removeListener() { if (mInfoContainer != null) { mInfoContainer.detachWidget(mHostView); + mInfoContainer = null; } + if (mLabelRequest != null) { + mLabelRequest.cancel(); + mLabelRequest = null; + } + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + removeListener(); } private void reportEvent(int eventType) { diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index 5007ffc7be..2367249163 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -66,9 +66,9 @@ import com.android.launcher3.graphics.IconShape; import com.android.launcher3.graphics.PlaceHolderIconDrawable; import com.android.launcher3.graphics.PreloadIconDrawable; import com.android.launcher3.icons.DotRenderer; -import com.android.launcher3.icons.IconCache.IconLoadRequest; import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver; import com.android.launcher3.icons.LauncherIcons; +import com.android.launcher3.icons.cache.HandlerRunnable; import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.ItemInfoWithIcon; @@ -171,7 +171,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, @ViewDebug.ExportedProperty(category = "launcher") private boolean mDisableRelayout = false; - private IconLoadRequest mIconLoadRequest; + private HandlerRunnable mIconLoadRequest; private boolean mEnableIconUpdateAnimation = false; diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 2334267fd9..36963f19dd 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -98,13 +98,9 @@ import android.widget.ImageView; import android.widget.Toast; import androidx.annotation.CallSuper; -import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.annotation.VisibleForTesting; -import androidx.lifecycle.Lifecycle; -import androidx.lifecycle.LifecycleOwner; -import androidx.lifecycle.LifecycleRegistry; import com.android.launcher3.DropTarget.DragObject; import com.android.launcher3.accessibility.LauncherAccessibilityDelegate; @@ -211,8 +207,7 @@ import java.util.stream.Stream; * Default launcher application. */ public class Launcher extends StatefulActivity implements LauncherExterns, - Callbacks, InvariantDeviceProfile.OnIDPChangeListener, PluginListener, - LifecycleOwner { + Callbacks, InvariantDeviceProfile.OnIDPChangeListener, PluginListener { public static final String TAG = "Launcher"; public static final ActivityTracker ACTIVITY_TRACKER = new ActivityTracker<>(); @@ -275,8 +270,6 @@ public class Launcher extends StatefulActivity implements Launche private LauncherAppTransitionManager mAppTransitionManager; private Configuration mOldConfig; - private LifecycleRegistry mLifecycleRegistry; - private LiveSearchManager mLiveSearchManager; @Thunk @@ -392,12 +385,12 @@ public class Launcher extends StatefulActivity implements Launche mIconCache = app.getIconCache(); mAccessibilityDelegate = new LauncherAccessibilityDelegate(this); - mLiveSearchManager = new LiveSearchManager(this); - mDragController = new DragController(this); mAllAppsController = new AllAppsTransitionController(this); mStateManager = new StateManager<>(this, NORMAL); + mLiveSearchManager = new LiveSearchManager(this); + mOnboardingPrefs = createOnboardingPrefs(mSharedPrefs); mAppWidgetManager = new WidgetManagerHelper(this); @@ -486,15 +479,6 @@ public class Launcher extends StatefulActivity implements Launche if (Utilities.ATLEAST_R) { getWindow().setSoftInputMode(LayoutParams.SOFT_INPUT_ADJUST_NOTHING); } - - mLifecycleRegistry = new LifecycleRegistry(this); - mLifecycleRegistry.setCurrentState(Lifecycle.State.CREATED); - } - - @NonNull - @Override - public Lifecycle getLifecycle() { - return mLifecycleRegistry; } public LiveSearchManager getLiveSearchManager() { @@ -913,7 +897,6 @@ public class Launcher extends StatefulActivity implements Launche @Override protected void onStop() { - mLifecycleRegistry.setCurrentState(Lifecycle.State.CREATED); super.onStop(); if (mDeferOverlayCallbacks) { checkIfOverlayStillDeferred(); @@ -937,7 +920,6 @@ public class Launcher extends StatefulActivity implements Launche mAppWidgetHost.setListenIfResumed(true); TraceHelper.INSTANCE.endSection(traceToken); - mLifecycleRegistry.setCurrentState(Lifecycle.State.STARTED); } @Override @@ -1091,7 +1073,6 @@ public class Launcher extends StatefulActivity implements Launche } TraceHelper.INSTANCE.endSection(traceToken); - mLifecycleRegistry.setCurrentState(Lifecycle.State.RESUMED); } @Override @@ -1099,7 +1080,6 @@ public class Launcher extends StatefulActivity implements Launche // Ensure that items added to Launcher are queued until Launcher returns ItemInstallQueue.INSTANCE.get(this).pauseModelPush(FLAG_ACTIVITY_PAUSED); - mLifecycleRegistry.setCurrentState(Lifecycle.State.STARTED); super.onPause(); mDragController.cancelDrag(); mLastTouchUpTime = -1; @@ -1598,7 +1578,6 @@ public class Launcher extends StatefulActivity implements Launche mAppTransitionManager.unregisterRemoteAnimations(); mAppTransitionManager.unregisterRemoteTransitions(); mUserChangedCallbackCloseable.close(); - mLifecycleRegistry.setCurrentState(Lifecycle.State.DESTROYED); mLiveSearchManager.stop(); } diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java index 2f805fd550..a4e1f271fb 100644 --- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java +++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java @@ -268,13 +268,10 @@ public class AllAppsTransitionController implements StateHandler, && !FeatureFlags.DISABLE_INITIAL_IME_IN_ALLAPPS.get() && BuildCompat.isAtLeastR()) { mInsetController.onAnimationEnd(mProgress); if (Float.compare(mProgress, 0f) == 0) { - mLauncher.getLiveSearchManager().start(); EditText editText = mAppsView.getSearchUiManager().getEditText(); if (editText != null && !mInsetController.showSearchEduIfNecessary()) { editText.requestFocus(); } - } else { - mLauncher.getLiveSearchManager().stop(); } // TODO: should make the controller hide synchronously } diff --git a/src/com/android/launcher3/allapps/search/LiveSearchManager.java b/src/com/android/launcher3/allapps/search/LiveSearchManager.java index c2f0b960c8..71aedb8272 100644 --- a/src/com/android/launcher3/allapps/search/LiveSearchManager.java +++ b/src/com/android/launcher3/allapps/search/LiveSearchManager.java @@ -15,8 +15,14 @@ */ package com.android.launcher3.allapps.search; +import static com.android.launcher3.LauncherState.ALL_APPS; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; +import static com.android.launcher3.util.Executors.THREAD_POOL_EXECUTOR; +import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import static com.android.launcher3.widget.WidgetHostViewLoader.getDefaultOptionsForWidget; +import android.app.Activity; +import android.app.Application.ActivityLifecycleCallbacks; import android.appwidget.AppWidgetHost; import android.appwidget.AppWidgetHostView; import android.appwidget.AppWidgetManager; @@ -26,39 +32,49 @@ import android.content.Context; import android.net.Uri; import android.os.Bundle; import android.os.UserHandle; +import android.util.Log; -import androidx.lifecycle.LiveData; +import androidx.annotation.Nullable; +import androidx.annotation.UiThread; +import androidx.annotation.WorkerThread; +import androidx.lifecycle.Observer; import androidx.slice.Slice; -import androidx.slice.widget.SliceLiveData; +import androidx.slice.SliceViewManager; +import androidx.slice.SliceViewManager.SliceCallback; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppWidgetProviderInfo; +import com.android.launcher3.LauncherState; import com.android.launcher3.logging.InstanceId; -import com.android.launcher3.logging.InstanceIdSequence; +import com.android.launcher3.statemanager.StateManager.StateListener; import com.android.launcher3.util.ComponentKey; +import com.android.launcher3.util.SafeCloseable; import com.android.launcher3.widget.PendingAddWidgetInfo; +import java.util.ArrayList; import java.util.HashMap; import java.util.Optional; /** * Manages Lifecycle for Live search results */ -public class LiveSearchManager { +public class LiveSearchManager implements StateListener { + + private static final String TAG = "LiveSearchManager"; public static final int SEARCH_APPWIDGET_HOST_ID = 2048; private final Launcher mLauncher; - private final AppWidgetManager mAppWidgetManger; + private final HashMap mUriSliceMap = new HashMap<>(); + private final HashMap mWidgetPlaceholders = new HashMap<>(); - private final HashMap> mUriSliceMap = new HashMap<>(); private SearchWidgetHost mSearchWidgetHost; private InstanceId mLogInstanceId; public LiveSearchManager(Launcher launcher) { mLauncher = launcher; - mAppWidgetManger = AppWidgetManager.getInstance(launcher); + mLauncher.getStateManager().addStateListener(this); } /** @@ -67,78 +83,80 @@ public class LiveSearchManager { */ public SearchWidgetInfoContainer getPlaceHolderWidget(AppWidgetProviderInfo providerInfo) { if (mSearchWidgetHost == null) { - throw new RuntimeException("AppWidgetHost has not been created yet"); + mSearchWidgetHost = new SearchWidgetHost(mLauncher); + mSearchWidgetHost.startListening(); } ComponentName provider = providerInfo.provider; UserHandle userHandle = providerInfo.getProfile(); ComponentKey key = new ComponentKey(provider, userHandle); - SearchWidgetInfoContainer view = mWidgetPlaceholders.getOrDefault(key, null); if (mWidgetPlaceholders.containsKey(key)) { return mWidgetPlaceholders.get(key); } + LauncherAppWidgetProviderInfo pinfo = LauncherAppWidgetProviderInfo.fromProviderInfo( mLauncher, providerInfo); PendingAddWidgetInfo pendingAddWidgetInfo = new PendingAddWidgetInfo(pinfo); Bundle options = getDefaultOptionsForWidget(mLauncher, pendingAddWidgetInfo); int appWidgetId = mSearchWidgetHost.allocateAppWidgetId(); - boolean success = mAppWidgetManger.bindAppWidgetIdIfAllowed(appWidgetId, userHandle, - provider, options); + boolean success = AppWidgetManager.getInstance(mLauncher) + .bindAppWidgetIdIfAllowed(appWidgetId, userHandle, provider, options); if (!success) { + mSearchWidgetHost.deleteAppWidgetId(appWidgetId); mWidgetPlaceholders.put(key, null); return null; } - view = (SearchWidgetInfoContainer) mSearchWidgetHost.createView(mLauncher, appWidgetId, - providerInfo); + SearchWidgetInfoContainer view = (SearchWidgetInfoContainer) mSearchWidgetHost.createView( + mLauncher, appWidgetId, providerInfo); view.setTag(pendingAddWidgetInfo); mWidgetPlaceholders.put(key, view); return view; } - /** - * Creates {@link LiveData} from Slice Uri. Caches created live data to be reused - * within the same search session. Removes previous observers when new SliceView request a - * live data for observation. - */ - public LiveData getSliceForUri(Uri sliceUri) { - LiveData sliceLiveData = mUriSliceMap.getOrDefault(sliceUri, null); - if (sliceLiveData == null) { - sliceLiveData = SliceLiveData.fromUri(mLauncher, sliceUri); - mUriSliceMap.put(sliceUri, sliceLiveData); - } - return sliceLiveData; - } - - /** - * Start search session - */ - public void start() { - stop(); - mLogInstanceId = new InstanceIdSequence().newInstanceId(); - mSearchWidgetHost = new SearchWidgetHost(mLauncher); - mSearchWidgetHost.startListening(); - } - /** * Stop search session */ public void stop() { + clearWidgetHost(); + } + + private void clearWidgetHost() { if (mSearchWidgetHost != null) { mSearchWidgetHost.stopListening(); + mSearchWidgetHost.clearViews(); mSearchWidgetHost.deleteHost(); - for (SearchWidgetInfoContainer placeholder : mWidgetPlaceholders.values()) { - placeholder.clearListeners(); - } mWidgetPlaceholders.clear(); mSearchWidgetHost = null; } - for (LiveData liveData : mUriSliceMap.values()) { - liveData.removeObservers(mLauncher); + } + + @Override + public void onStateTransitionComplete(LauncherState finalState) { + if (finalState != ALL_APPS) { + // Clear all search session related objects + mUriSliceMap.values().forEach(SliceLifeCycle::destroy); + mUriSliceMap.clear(); + + clearWidgetHost(); } - mUriSliceMap.clear(); + } + + /** + * Adds a new observer for the provided uri and returns a callback to cancel this observer + */ + public SafeCloseable addObserver(Uri uri, Observer listener) { + SliceLifeCycle slc = mUriSliceMap.get(uri); + if (slc == null) { + slc = new SliceLifeCycle(uri, mLauncher); + mUriSliceMap.put(uri, slc); + } + slc.addListener(listener); + + final SliceLifeCycle sliceLifeCycle = slc; + return () -> sliceLifeCycle.removeListener(listener); } /** @@ -159,5 +177,121 @@ public class LiveSearchManager { AppWidgetProviderInfo appWidget) { return new SearchWidgetInfoContainer(context); } + + @Override + public void clearViews() { + super.clearViews(); + } + } + + private static class SliceLifeCycle + implements ActivityLifecycleCallbacks, SliceCallback { + + private final Uri mUri; + private final Launcher mLauncher; + private final SliceViewManager mSliceViewManager; + private final ArrayList> mListeners = new ArrayList<>(); + + private boolean mDestroyed = false; + private boolean mWasListening = false; + + SliceLifeCycle(Uri uri, Launcher launcher) { + mUri = uri; + mLauncher = launcher; + mSliceViewManager = SliceViewManager.getInstance(launcher); + launcher.registerActivityLifecycleCallbacks(this); + + if (launcher.isDestroyed()) { + onActivityDestroyed(launcher); + } else if (launcher.isStarted()) { + onActivityStarted(launcher); + } + } + + @Override + public void onActivityDestroyed(Activity activity) { + destroy(); + } + + @Override + public void onActivityStarted(Activity activity) { + updateListening(); + } + + @Override + public void onActivityStopped(Activity activity) { + updateListening(); + } + + private void updateListening() { + boolean isListening = mDestroyed + ? false + : (mLauncher.isStarted() && !mListeners.isEmpty()); + UI_HELPER_EXECUTOR.execute(() -> uploadListeningBg(isListening)); + } + + @WorkerThread + private void uploadListeningBg(boolean isListening) { + if (mWasListening != isListening) { + mWasListening = isListening; + if (isListening) { + mSliceViewManager.registerSliceCallback(mUri, MAIN_EXECUTOR, this); + // Update slice one-time on the different thread so that we can display + // multiple slices in parallel + THREAD_POOL_EXECUTOR.execute(this::updateSlice); + } else { + mSliceViewManager.unregisterSliceCallback(mUri, this); + } + } + } + + @UiThread + private void addListener(Observer listener) { + mListeners.add(listener); + updateListening(); + } + + @UiThread + private void removeListener(Observer listener) { + mListeners.remove(listener); + updateListening(); + } + + @WorkerThread + private void updateSlice() { + try { + Slice s = mSliceViewManager.bindSlice(mUri); + MAIN_EXECUTOR.execute(() -> onSliceUpdated(s)); + } catch (Exception e) { + Log.d(TAG, "Error fetching slice", e); + } + } + + @UiThread + @Override + public void onSliceUpdated(@Nullable Slice s) { + mListeners.forEach(l -> l.onChanged(s)); + } + + private void destroy() { + if (mDestroyed) { + return; + } + mDestroyed = true; + mLauncher.unregisterActivityLifecycleCallbacks(this); + mListeners.clear(); + } + + @Override + public void onActivityCreated(Activity activity, Bundle bundle) { } + + @Override + public void onActivityPaused(Activity activity) { } + + @Override + public void onActivityResumed(Activity activity) { } + + @Override + public void onActivitySaveInstanceState(Activity activity, Bundle bundle) { } } } diff --git a/src/com/android/launcher3/allapps/search/SearchWidgetInfoContainer.java b/src/com/android/launcher3/allapps/search/SearchWidgetInfoContainer.java index b5c22688e5..8e5f8cb9c0 100644 --- a/src/com/android/launcher3/allapps/search/SearchWidgetInfoContainer.java +++ b/src/com/android/launcher3/allapps/search/SearchWidgetInfoContainer.java @@ -70,10 +70,4 @@ public class SearchWidgetInfoContainer extends AppWidgetHostView { mListeners.remove(hostView); } - /** - * Removes all AppWidgetHost update listeners - */ - public void clearListeners() { - mListeners.clear(); - } } diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java index 80135571b2..61f2c2adae 100644 --- a/src/com/android/launcher3/icons/IconCache.java +++ b/src/com/android/launcher3/icons/IconCache.java @@ -31,7 +31,6 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ShortcutInfo; import android.graphics.drawable.Drawable; -import android.os.Handler; import android.os.Process; import android.os.UserHandle; import android.util.Log; @@ -134,7 +133,7 @@ public class IconCache extends BaseIconCache { * Fetches high-res icon for the provided ItemInfo and updates the caller when done. * @return a request ID that can be used to cancel the request. */ - public IconLoadRequest updateIconInBackground(final ItemInfoUpdateReceiver caller, + public HandlerRunnable updateIconInBackground(final ItemInfoUpdateReceiver caller, final ItemInfoWithIcon info) { Preconditions.assertUIThread(); if (mPendingIconRequestCount <= 0) { @@ -142,20 +141,18 @@ public class IconCache extends BaseIconCache { } mPendingIconRequestCount ++; - IconLoadRequest request = new IconLoadRequest(mWorkerHandler, this::onIconRequestEnd) { - @Override - public void run() { - if (info instanceof AppInfo || info instanceof WorkspaceItemInfo) { - getTitleAndIcon(info, false); - } else if (info instanceof PackageItemInfo) { - getTitleAndIconForApp((PackageItemInfo) info, false); - } - MAIN_EXECUTOR.execute(() -> { - caller.reapplyItemInfo(info); - onEnd(); - }); - } - }; + HandlerRunnable request = new HandlerRunnable<>(mWorkerHandler, + () -> { + if (info instanceof AppInfo || info instanceof WorkspaceItemInfo) { + getTitleAndIcon(info, false); + } else if (info instanceof PackageItemInfo) { + getTitleAndIconForApp((PackageItemInfo) info, false); + } + return info; + }, + MAIN_EXECUTOR, + caller::reapplyItemInfo, + this::onIconRequestEnd); Utilities.postAsyncCallback(mWorkerHandler, request); return request; } @@ -336,12 +333,6 @@ public class IconCache extends BaseIconCache { return super.getEntryFromDB(cacheKey, entry, lowRes); } - public static abstract class IconLoadRequest extends HandlerRunnable { - IconLoadRequest(Handler handler, Runnable endRunnable) { - super(handler, endRunnable); - } - } - /** * Interface for receiving itemInfo with high-res icon. */ From 4fc871f9776606799538fe08bfd62069e692d206 Mon Sep 17 00:00:00 2001 From: thiruram Date: Wed, 3 Feb 2021 14:55:23 -0800 Subject: [PATCH 0501/1707] [AA+] Add LAUNCHER_ALLAPPS_ENTRY, LAUNCHER_ALLAPPS_EXIT events. This would help us to define clear start and end events for AllApps sessions on the server side. Bug: 178562918 Change-Id: I77f236c9bc1a21e868545b1309e47d91d6fc1d96 --- .../allapps/search/LiveSearchManager.java | 19 +++++++++++++++++++ .../launcher3/logging/StatsLogManager.java | 6 ++++++ 2 files changed, 25 insertions(+) diff --git a/src/com/android/launcher3/allapps/search/LiveSearchManager.java b/src/com/android/launcher3/allapps/search/LiveSearchManager.java index 71aedb8272..e52c79049f 100644 --- a/src/com/android/launcher3/allapps/search/LiveSearchManager.java +++ b/src/com/android/launcher3/allapps/search/LiveSearchManager.java @@ -16,6 +16,8 @@ package com.android.launcher3.allapps.search; import static com.android.launcher3.LauncherState.ALL_APPS; +import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_ENTRY; +import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_EXIT; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.THREAD_POOL_EXECUTOR; import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; @@ -46,6 +48,8 @@ import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppWidgetProviderInfo; import com.android.launcher3.LauncherState; import com.android.launcher3.logging.InstanceId; +import com.android.launcher3.logging.InstanceIdSequence; +import com.android.launcher3.logging.StatsLogManager.StatsLogger; import com.android.launcher3.statemanager.StateManager.StateListener; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.SafeCloseable; @@ -71,6 +75,7 @@ public class LiveSearchManager implements StateListener { new HashMap<>(); private SearchWidgetHost mSearchWidgetHost; private InstanceId mLogInstanceId; + private LauncherState mPrevLauncherState; public LiveSearchManager(Launcher launcher) { mLauncher = launcher; @@ -133,6 +138,11 @@ public class LiveSearchManager implements StateListener { } } + @Override + public void onStateTransitionStart(LauncherState toState) { + mPrevLauncherState = mLauncher.getStateManager().getCurrentStableState(); + } + @Override public void onStateTransitionComplete(LauncherState finalState) { if (finalState != ALL_APPS) { @@ -142,6 +152,15 @@ public class LiveSearchManager implements StateListener { clearWidgetHost(); } + + StatsLogger logger = mLauncher.getStatsLogManager().logger(); + if (finalState.equals(ALL_APPS)) { + mLogInstanceId = new InstanceIdSequence().newInstanceId(); + logger.withInstanceId(mLogInstanceId).log(LAUNCHER_ALLAPPS_ENTRY); + } else if (mPrevLauncherState.equals(ALL_APPS)) { + logger.withInstanceId(mLogInstanceId).log(LAUNCHER_ALLAPPS_EXIT); + mLogInstanceId = null; + } } /** diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java index 2066cd3d71..0292d20ef5 100644 --- a/src/com/android/launcher3/logging/StatsLogManager.java +++ b/src/com/android/launcher3/logging/StatsLogManager.java @@ -344,6 +344,12 @@ public class StatsLogManager implements ResourceBasedOverride { @UiEvent(doc = "Current grid size is changed to 2.") LAUNCHER_GRID_SIZE_2(665), + + @UiEvent(doc = "Launcher entered into AllApps state.") + LAUNCHER_ALLAPPS_ENTRY(692), + + @UiEvent(doc = "Launcher exited from AllApps state.") + LAUNCHER_ALLAPPS_EXIT(693), ; // ADD MORE From 993fed23465a10bc76f46948f8910154fafa2496 Mon Sep 17 00:00:00 2001 From: Lucas Dupin Date: Thu, 4 Feb 2021 15:40:45 -0800 Subject: [PATCH 0502/1707] Style updates - Make pop ups use system colors - Text colors in dark theme should also use system colors Test: visual Bug: 179427049 Change-Id: I9c0eb2b6970ff9bf66fe1d5c0040b10bfa2087b0 --- res/values-v31/colors.xml | 33 +++++++++++++++++++++++++++++++++ res/values/colors.xml | 15 +++++++++++++++ res/values/styles.xml | 24 ++++++++++++------------ 3 files changed, 60 insertions(+), 12 deletions(-) create mode 100644 res/values-v31/colors.xml diff --git a/res/values-v31/colors.xml b/res/values-v31/colors.xml new file mode 100644 index 0000000000..b8600a60ed --- /dev/null +++ b/res/values-v31/colors.xml @@ -0,0 +1,33 @@ + + + + @android:color/system_main_50 + @android:color/system_main_100 + @android:color/system_main_300 + @android:color/system_main_800 + @android:color/system_main_900 + @android:color/system_main_700 + + @android:color/system_main_50 + @android:color/system_main_900 + + @android:color/system_main_50 + @android:color/system_main_200 + @android:color/system_main_400 + \ No newline at end of file diff --git a/res/values/colors.xml b/res/values/colors.xml index 78c2df69e0..0b30253327 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -40,4 +40,19 @@ #9CCC65 #FFFFFFFF #1A73E8 + + #FFF + #F1F3F4 + #E0E0E0 + #3C4043 + #202124 + #757575 + + #FFF + #FF212121 + + #FFFFFFFF + #FFFFFFFF + #CCFFFFFF + diff --git a/res/values/styles.xml b/res/values/styles.xml index dc7182fe90..adc22381a8 100644 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -35,12 +35,12 @@ 46 #66FFFFFF @style/AllAppsTheme - #FFF - #F1F3F4 - #E0E0E0 + @color/popup_color_primary_light + @color/popup_color_secondary_light + @color/popup_color_tertiary_light false false - @android:color/white + @color/workspace_text_color_light #B0000000 #33000000 #44000000 @@ -74,7 +74,7 @@ \ No newline at end of file diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java index edcd0a203a..d1fa2fd3ef 100644 --- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java @@ -30,7 +30,6 @@ import android.content.Intent; import android.content.IntentSender; import android.os.Bundle; import android.os.CancellationSignal; -import android.view.LayoutInflater; import android.view.View; import androidx.annotation.Nullable; @@ -43,7 +42,7 @@ import com.android.launcher3.proxy.StartActivityParams; import com.android.launcher3.statehandlers.BackButtonAlphaHandler; import com.android.launcher3.statehandlers.DepthController; import com.android.launcher3.statemanager.StateManager.StateHandler; -import com.android.launcher3.taskbar.TaskbarContainerView; +import com.android.launcher3.taskbar.TaskbarActivityContext; import com.android.launcher3.taskbar.TaskbarController; import com.android.launcher3.taskbar.TaskbarStateHandler; import com.android.launcher3.uioverrides.RecentsViewStateController; @@ -207,6 +206,7 @@ public abstract class BaseQuickstepLauncher extends Launcher mActionsView.updateVerticalMargin(SysUINavigationMode.getMode(this)); addTaskbarIfNecessary(); + addOnDeviceProfileChangeListener(newDp -> addTaskbarIfNecessary()); } @Override @@ -223,9 +223,9 @@ public abstract class BaseQuickstepLauncher extends Launcher mTaskbarController = null; } if (FeatureFlags.ENABLE_TASKBAR.get() && mDeviceProfile.isTablet) { - TaskbarContainerView taskbarContainer = (TaskbarContainerView) LayoutInflater.from(this) - .inflate(R.layout.taskbar, null, false); - mTaskbarController = new TaskbarController(this, taskbarContainer); + TaskbarActivityContext taskbarActivityContext = new TaskbarActivityContext(this); + mTaskbarController = new TaskbarController(this, + taskbarActivityContext.getTaskbarContainerView()); mTaskbarController.init(); } } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java new file mode 100644 index 0000000000..06372fe22f --- /dev/null +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.taskbar; + +import android.content.ContextWrapper; +import android.graphics.Rect; +import android.view.LayoutInflater; + +import com.android.launcher3.BaseQuickstepLauncher; +import com.android.launcher3.DeviceProfile; +import com.android.launcher3.R; +import com.android.launcher3.views.ActivityContext; +import com.android.launcher3.views.BaseDragLayer; + +/** + * The {@link ActivityContext} with which we inflate Taskbar-related Views. This allows UI elements + * that are used by both Launcher and Taskbar (such as Folder) to reference a generic + * ActivityContext and BaseDragLayer instead of the Launcher activity and its DragLayer. + */ +public class TaskbarActivityContext extends ContextWrapper implements ActivityContext { + + private final DeviceProfile mDeviceProfile; + private final LayoutInflater mLayoutInflater; + private final TaskbarContainerView mTaskbarContainerView; + + public TaskbarActivityContext(BaseQuickstepLauncher launcher) { + super(launcher); + mDeviceProfile = launcher.getDeviceProfile().copy(this); + float taskbarIconSize = getResources().getDimension(R.dimen.taskbar_icon_size); + float iconScale = taskbarIconSize / mDeviceProfile.iconSizePx; + mDeviceProfile.updateIconSize(iconScale, getResources()); + + mLayoutInflater = LayoutInflater.from(this).cloneInContext(this); + + mTaskbarContainerView = (TaskbarContainerView) mLayoutInflater + .inflate(R.layout.taskbar, null, false); + } + + public TaskbarContainerView getTaskbarContainerView() { + return mTaskbarContainerView; + } + + /** + * @return A LayoutInflater to use in this Context. Views inflated with this LayoutInflater will + * be able to access this TaskbarActivityContext via ActivityContext.lookupContext(). + */ + public LayoutInflater getLayoutInflater() { + return mLayoutInflater; + } + + @Override + public BaseDragLayer getDragLayer() { + return mTaskbarContainerView; + } + + @Override + public DeviceProfile getDeviceProfile() { + return mDeviceProfile; + } + + @Override + public Rect getFolderBoundingBox() { + return mTaskbarContainerView.getFolderBoundingBox(); + } +} diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java index 3b361c4a38..ddd0d15442 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java @@ -19,19 +19,29 @@ import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsI import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_REGION; import android.content.Context; +import android.graphics.Rect; import android.util.AttributeSet; -import android.widget.FrameLayout; +import android.view.View; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.launcher3.R; import com.android.launcher3.anim.AlphaUpdateListener; +import com.android.launcher3.util.TouchController; +import com.android.launcher3.views.BaseDragLayer; import com.android.systemui.shared.system.ViewTreeObserverWrapper; /** * Top-level ViewGroup that hosts the TaskbarView as well as Views created by it such as Folder. */ -public class TaskbarContainerView extends FrameLayout { +public class TaskbarContainerView extends BaseDragLayer { + + private final int[] mTempLoc = new int[2]; + private final int mFolderMargin; + + // Initialized in TaskbarController constructor. + private TaskbarController.TaskbarContainerViewCallbacks mControllerCallbacks; // Initialized in init. private TaskbarView mTaskbarView; @@ -52,12 +62,23 @@ public class TaskbarContainerView extends FrameLayout { public TaskbarContainerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); + super(context, attrs, 1 /* alphaChannelCount */); + mFolderMargin = getResources().getDimensionPixelSize(R.dimen.taskbar_folder_margin); + } + + protected void construct(TaskbarController.TaskbarContainerViewCallbacks callbacks) { + mControllerCallbacks = callbacks; } protected void init(TaskbarView taskbarView) { mTaskbarView = taskbarView; mTaskbarInsetsComputer = createTaskbarInsetsComputer(); + recreateControllers(); + } + + @Override + public void recreateControllers() { + mControllers = new TouchController[0]; } private ViewTreeObserverWrapper.OnComputeInsetsListener createTaskbarInsetsComputer() { @@ -70,6 +91,17 @@ public class TaskbarContainerView extends FrameLayout { // We're visible again, accept touches anywhere in our bounds. insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_FRAME); } + + // TaskbarContainerView provides insets to other apps based on contentInsets. These + // insets should stay consistent even if we expand TaskbarContainerView's bounds, e.g. + // to show a floating view like Folder. Thus, we set the contentInsets to be where + // mTaskbarView is, since its position never changes and insets rather than overlays. + int[] loc = mTempLoc; + mTaskbarView.getLocationInWindow(loc); + insetsInfo.contentInsets.left = loc[0]; + insetsInfo.contentInsets.top = loc[1]; + insetsInfo.contentInsets.right = getWidth() - (loc[0] + mTaskbarView.getWidth()); + insetsInfo.contentInsets.bottom = getHeight() - (loc[1] + mTaskbarView.getHeight()); }; } @@ -91,4 +123,30 @@ public class TaskbarContainerView extends FrameLayout { cleanup(); } + + @Override + protected boolean canFindActiveController() { + // Unlike super class, we want to be able to find controllers when touches occur in the + // gesture area. For example, this allows Folder to close itself when touching the Taskbar. + return true; + } + + @Override + public void onViewRemoved(View child) { + super.onViewRemoved(child); + mControllerCallbacks.onViewRemoved(); + } + + /** + * @return Bounds (in our coordinates) where an opened Folder can display. + */ + protected Rect getFolderBoundingBox() { + Rect boundingBox = new Rect(0, 0, getWidth(), getHeight() - mTaskbarView.getHeight()); + boundingBox.inset(mFolderMargin, mFolderMargin); + return boundingBox; + } + + protected TaskbarActivityContext getTaskbarActivityContext() { + return mActivity; + } } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java index 260428dbc2..ab05fbf036 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java @@ -34,11 +34,15 @@ import android.view.WindowManager; import androidx.annotation.Nullable; +import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.BaseQuickstepLauncher; import com.android.launcher3.LauncherState; import com.android.launcher3.QuickstepAppTransitionManagerImpl; import com.android.launcher3.R; import com.android.launcher3.anim.PendingAnimation; +import com.android.launcher3.folder.Folder; +import com.android.launcher3.folder.FolderIcon; +import com.android.launcher3.model.data.FolderInfo; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.states.StateAnimationConfig; import com.android.launcher3.touch.ItemClickHandler; @@ -81,8 +85,9 @@ public class TaskbarController { TaskbarContainerView taskbarContainerView) { mLauncher = launcher; mTaskbarContainerView = taskbarContainerView; + mTaskbarContainerView.construct(createTaskbarContainerViewCallbacks()); mTaskbarView = mTaskbarContainerView.findViewById(R.id.taskbar_view); - mTaskbarView.setCallbacks(createTaskbarViewCallbacks()); + mTaskbarView.construct(createTaskbarViewCallbacks()); mWindowManager = mLauncher.getWindowManager(); mTaskbarSize = new Point(MATCH_PARENT, mLauncher.getResources().getDimensionPixelSize(R.dimen.taskbar_size)); @@ -110,6 +115,18 @@ public class TaskbarController { }; } + private TaskbarContainerViewCallbacks createTaskbarContainerViewCallbacks() { + return new TaskbarContainerViewCallbacks() { + @Override + public void onViewRemoved() { + if (mTaskbarContainerView.getChildCount() == 1) { + // Only TaskbarView remains. + setTaskbarWindowFullscreen(false); + } + } + }; + } + private TaskbarViewCallbacks createTaskbarViewCallbacks() { return new TaskbarViewCallbacks() { @Override @@ -120,9 +137,29 @@ public class TaskbarController { Task task = (Task) tag; ActivityManagerWrapper.getInstance().startActivityFromRecents(task.key, ActivityOptions.makeBasic()); + } else if (tag instanceof FolderInfo) { + FolderIcon folderIcon = (FolderIcon) view; + Folder folder = folderIcon.getFolder(); + + setTaskbarWindowFullscreen(true); + + mTaskbarContainerView.post(() -> { + folder.animateOpen(); + + folder.iterateOverItems((itemInfo, itemView) -> { + itemView.setOnClickListener(getItemOnClickListener()); + itemView.setOnLongClickListener(getItemOnLongClickListener()); + // To play haptic when dragging, like other Taskbar items do. + itemView.setHapticFeedbackEnabled(true); + return false; + }); + }); } else { ItemClickHandler.INSTANCE.onClick(view); } + + AbstractFloatingView.closeAllOpenViews( + mTaskbarContainerView.getTaskbarActivityContext()); }; } @@ -344,6 +381,20 @@ public class TaskbarController { return mTaskbarContainerView.getWindowId().equals(v.getWindowId()); } + /** + * Updates the TaskbarContainer to MATCH_PARENT vs original Taskbar size. + */ + private void setTaskbarWindowFullscreen(boolean fullscreen) { + if (fullscreen) { + mWindowLayoutParams.width = MATCH_PARENT; + mWindowLayoutParams.height = MATCH_PARENT; + } else { + mWindowLayoutParams.width = mTaskbarSize.x; + mWindowLayoutParams.height = mTaskbarSize.y; + } + mWindowManager.updateViewLayout(mTaskbarContainerView, mWindowLayoutParams); + } + /** * Contains methods that TaskbarStateHandler can call to interface with TaskbarController. */ @@ -360,6 +411,13 @@ public class TaskbarController { void updateTaskbarVisibilityAlpha(float alpha); } + /** + * Contains methods that TaskbarContainerView can call to interface with TaskbarController. + */ + protected interface TaskbarContainerViewCallbacks { + void onViewRemoved(); + } + /** * Contains methods that TaskbarView can call to interface with TaskbarController. */ diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java index d8f3bb595e..df77f8778e 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java @@ -22,7 +22,6 @@ import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.DragEvent; -import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; @@ -32,10 +31,14 @@ import androidx.annotation.LayoutRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.BubbleTextView; import com.android.launcher3.R; +import com.android.launcher3.folder.FolderIcon; +import com.android.launcher3.model.data.FolderInfo; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; +import com.android.launcher3.views.ActivityContext; import com.android.systemui.shared.recents.model.Task; /** @@ -51,6 +54,9 @@ public class TaskbarView extends LinearLayout { private final RectF mDelegateSlopBounds = new RectF(); private final int[] mTempOutLocation = new int[2]; + // Initialized in TaskbarController constructor. + private TaskbarController.TaskbarViewCallbacks mControllerCallbacks; + // Initialized in init(). private int mHotseatStartIndex; private int mHotseatEndIndex; @@ -58,8 +64,6 @@ public class TaskbarView extends LinearLayout { private int mRecentsStartIndex; private int mRecentsEndIndex; - private TaskbarController.TaskbarViewCallbacks mControllerCallbacks; - // Delegate touches to the closest view if within mIconTouchSize. private boolean mDelegateTargeted; private View mDelegateView; @@ -90,7 +94,7 @@ public class TaskbarView extends LinearLayout { mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); } - protected void setCallbacks(TaskbarController.TaskbarViewCallbacks taskbarViewCallbacks) { + protected void construct(TaskbarController.TaskbarViewCallbacks taskbarViewCallbacks) { mControllerCallbacks = taskbarViewCallbacks; } @@ -130,17 +134,37 @@ public class TaskbarView extends LinearLayout { // Replace any Hotseat views with the appropriate type if it's not already that type. final int expectedLayoutResId; + boolean isFolder = false; + boolean needsReinflate = false; if (hotseatItemInfo != null && hotseatItemInfo.isPredictedItem()) { expectedLayoutResId = R.layout.taskbar_predicted_app_icon; + } else if (hotseatItemInfo instanceof FolderInfo) { + expectedLayoutResId = R.layout.folder_icon; + isFolder = true; + // Unlike for BubbleTextView, we can't reapply a new FolderInfo after inflation, so + // if the info changes we need to reinflate. This should only happen if a new folder + // is dragged to the position that another folder previously existed. + needsReinflate = hotseatView != null && hotseatView.getTag() != hotseatItemInfo; } else { expectedLayoutResId = R.layout.taskbar_app_icon; } - if (hotseatView == null || hotseatView.getSourceLayoutResId() != expectedLayoutResId) { + if (hotseatView == null || hotseatView.getSourceLayoutResId() != expectedLayoutResId + || needsReinflate) { removeView(hotseatView); - BubbleTextView btv = (BubbleTextView) inflate(expectedLayoutResId); - LayoutParams lp = new LayoutParams(btv.getIconSize(), btv.getIconSize()); + TaskbarActivityContext activityContext = + ActivityContext.lookupContext(getContext()); + if (isFolder) { + FolderInfo folderInfo = (FolderInfo) hotseatItemInfo; + FolderIcon folderIcon = FolderIcon.inflateFolderAndIcon(expectedLayoutResId, + activityContext, this, folderInfo); + folderIcon.setTextVisible(false); + hotseatView = folderIcon; + } else { + hotseatView = inflate(expectedLayoutResId); + } + int iconSize = activityContext.getDeviceProfile().iconSizePx; + LayoutParams lp = new LayoutParams(iconSize, iconSize); lp.setMargins(mItemMarginLeftRight, 0, mItemMarginLeftRight, 0); - hotseatView = btv; addView(hotseatView, hotseatIndex, lp); } @@ -153,6 +177,11 @@ public class TaskbarView extends LinearLayout { hotseatView.setOnClickListener(mControllerCallbacks.getItemOnClickListener()); hotseatView.setOnLongClickListener( mControllerCallbacks.getItemOnLongClickListener()); + } else if (isFolder) { + hotseatView.setVisibility(VISIBLE); + hotseatView.setOnClickListener(mControllerCallbacks.getItemOnClickListener()); + hotseatView.setOnLongClickListener( + mControllerCallbacks.getItemOnLongClickListener()); } else { hotseatView.setVisibility(GONE); hotseatView.setOnClickListener(null); @@ -345,6 +374,7 @@ public class TaskbarView extends LinearLayout { switch (event.getAction()) { case DragEvent.ACTION_DRAG_STARTED: mIsDraggingItem = true; + AbstractFloatingView.closeAllOpenViews(ActivityContext.lookupContext(getContext())); return true; case DragEvent.ACTION_DRAG_ENDED: mIsDraggingItem = false; @@ -358,6 +388,7 @@ public class TaskbarView extends LinearLayout { } private View inflate(@LayoutRes int layoutResId) { - return LayoutInflater.from(getContext()).inflate(layoutResId, this, false); + TaskbarActivityContext taskbarActivityContext = ActivityContext.lookupContext(getContext()); + return taskbarActivityContext.getLayoutInflater().inflate(layoutResId, this, false); } } diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index edc7e9bb8a..e5a4335dcd 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -505,7 +505,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, * @param canvas The canvas to draw to. */ protected void drawDotIfNecessary(Canvas canvas) { - if (mDisplay == DISPLAY_TASKBAR) { + if (mActivity instanceof Launcher && ((Launcher) mActivity).isViewInTaskbar(this)) { // TODO: support notification dots in Taskbar return; } diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java index 19915b7026..83a7d77601 100644 --- a/src/com/android/launcher3/DeviceProfile.java +++ b/src/com/android/launcher3/DeviceProfile.java @@ -375,7 +375,7 @@ public class DeviceProfile { * iconTextSizePx, iconDrawablePaddingPx, cellWidth/Height, allApps* variants, * hotseat sizes, workspaceSpringLoadedShrinkFactor, folderIconSizePx, and folderIconOffsetYPx. */ - private void updateIconSize(float scale, Resources res) { + public void updateIconSize(float scale, Resources res) { // Workspace final boolean isVerticalLayout = isVerticalBarLayout(); float invIconSizeDp = isVerticalLayout ? inv.landscapeIconSize : inv.iconSize; diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java index 9efbc7dcab..504b29e75a 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -1616,6 +1616,11 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo return false; } + @Override + public boolean canInterceptEventsInSystemGestureRegion() { + return true; + } + /** * Alternative to using {@link #getClipToOutline()} as it only works with derivatives of * rounded rect. diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java index 65a4fba63c..75d8f2296c 100644 --- a/src/com/android/launcher3/folder/FolderIcon.java +++ b/src/com/android/launcher3/folder/FolderIcon.java @@ -754,6 +754,9 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel } public void clearLeaveBehindIfExists() { + if (!(getLayoutParams() instanceof CellLayout.LayoutParams)) { + return; + } ((CellLayout.LayoutParams) getLayoutParams()).canReorder = true; if (isInHotseat()) { CellLayout cl = (CellLayout) getParent().getParent(); @@ -762,6 +765,9 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel } public void drawLeaveBehindIfExists() { + if (!(getLayoutParams() instanceof CellLayout.LayoutParams)) { + return; + } CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams(); // While the folder is open, the position of the icon cannot change. lp.canReorder = false; diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java index 15f7730fd7..1939d15776 100644 --- a/src/com/android/launcher3/views/BaseDragLayer.java +++ b/src/com/android/launcher3/views/BaseDragLayer.java @@ -206,15 +206,19 @@ public abstract class BaseDragLayer protected boolean findActiveController(MotionEvent ev) { mActiveController = null; - if ((mTouchDispatchState & (TOUCH_DISPATCHING_FROM_VIEW_GESTURE_REGION - | TOUCH_DISPATCHING_FROM_PROXY)) == 0) { - // Only look for controllers if we are not dispatching from gesture area and proxy is - // not active + if (canFindActiveController()) { mActiveController = findControllerToHandleTouch(ev); } return mActiveController != null; } + protected boolean canFindActiveController() { + // Only look for controllers if we are not dispatching from gesture area and proxy is + // not active + return (mTouchDispatchState & (TOUCH_DISPATCHING_FROM_VIEW_GESTURE_REGION + | TOUCH_DISPATCHING_FROM_PROXY)) == 0; + } + @Override public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) { // Shortcuts can appear above folder From bb4de85c253baf31ff120e006941fc1cfe410047 Mon Sep 17 00:00:00 2001 From: Tony Wickham Date: Wed, 10 Feb 2021 12:35:28 -0800 Subject: [PATCH 0558/1707] Draw folder leave-behind when opening folder in taskbar Previously there was only support for CellLayout to draw the leave-behind. Added FolderIconParent interface so any parent can draw the leave-behind, and implement that by TaskbarView. Test: Open folder in Taskber, ensure leave-behind draws Bug: 171917176 Change-Id: I08d1d939a34f971f893994fe05cac972d165ef53 --- .../launcher3/taskbar/TaskbarView.java | 32 ++++++++++++++++- .../launcher3/ShortcutAndWidgetContainer.java | 23 +++++++++++- .../android/launcher3/folder/FolderIcon.java | 35 ++++++++++--------- 3 files changed, 72 insertions(+), 18 deletions(-) diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java index df77f8778e..7a13b89d25 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java @@ -17,6 +17,7 @@ package com.android.launcher3.taskbar; import android.content.Context; import android.content.res.Resources; +import android.graphics.Canvas; import android.graphics.RectF; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; @@ -44,7 +45,7 @@ import com.android.systemui.shared.recents.model.Task; /** * Hosts the Taskbar content such as Hotseat and Recent Apps. Drawn on top of other apps. */ -public class TaskbarView extends LinearLayout { +public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconParent { private final ColorDrawable mBackgroundDrawable; private final int mItemMarginLeftRight; @@ -69,6 +70,8 @@ public class TaskbarView extends LinearLayout { private View mDelegateView; private boolean mIsDraggingItem; + // Only non-null when the corresponding Folder is open. + private @Nullable FolderIcon mLeaveBehindFolderIcon; public TaskbarView(@NonNull Context context) { this(context, null); @@ -387,6 +390,33 @@ public class TaskbarView extends LinearLayout { return mIsDraggingItem; } + // FolderIconParent implemented methods. + + @Override + public void drawFolderLeaveBehindForIcon(FolderIcon child) { + mLeaveBehindFolderIcon = child; + invalidate(); + } + + @Override + public void clearFolderLeaveBehind(FolderIcon child) { + mLeaveBehindFolderIcon = null; + invalidate(); + } + + // End FolderIconParent implemented methods. + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + if (mLeaveBehindFolderIcon != null) { + canvas.save(); + canvas.translate(mLeaveBehindFolderIcon.getLeft(), mLeaveBehindFolderIcon.getTop()); + mLeaveBehindFolderIcon.getFolderBackground().drawLeaveBehind(canvas); + canvas.restore(); + } + } + private View inflate(@LayoutRes int layoutResId) { TaskbarActivityContext taskbarActivityContext = ActivityContext.lookupContext(getContext()); return taskbarActivityContext.getLayoutInflater().inflate(layoutResId, this, false); diff --git a/src/com/android/launcher3/ShortcutAndWidgetContainer.java b/src/com/android/launcher3/ShortcutAndWidgetContainer.java index 1c5081cfca..eab8272431 100644 --- a/src/com/android/launcher3/ShortcutAndWidgetContainer.java +++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java @@ -26,10 +26,11 @@ import android.view.View; import android.view.ViewGroup; import com.android.launcher3.CellLayout.ContainerType; +import com.android.launcher3.folder.FolderIcon; import com.android.launcher3.views.ActivityContext; import com.android.launcher3.widget.LauncherAppWidgetHostView; -public class ShortcutAndWidgetContainer extends ViewGroup { +public class ShortcutAndWidgetContainer extends ViewGroup implements FolderIcon.FolderIconParent { static final String TAG = "ShortcutAndWidgetContainer"; // These are temporary variables to prevent having to allocate a new object just to @@ -228,4 +229,24 @@ public class ShortcutAndWidgetContainer extends ViewGroup { child.cancelLongPress(); } } + + @Override + public void drawFolderLeaveBehindForIcon(FolderIcon child) { + CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); + // While the folder is open, the position of the icon cannot change. + lp.canReorder = false; + if (mContainerType == CellLayout.HOTSEAT) { + CellLayout cl = (CellLayout) getParent(); + cl.setFolderLeaveBehindCell(lp.cellX, lp.cellY); + } + } + + @Override + public void clearFolderLeaveBehind(FolderIcon child) { + ((CellLayout.LayoutParams) child.getLayoutParams()).canReorder = true; + if (mContainerType == CellLayout.HOTSEAT) { + CellLayout cl = (CellLayout) getParent(); + cl.clearFolderLeaveBehind(); + } + } } diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java index 75d8f2296c..6b02021ed3 100644 --- a/src/com/android/launcher3/folder/FolderIcon.java +++ b/src/com/android/launcher3/folder/FolderIcon.java @@ -754,26 +754,14 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel } public void clearLeaveBehindIfExists() { - if (!(getLayoutParams() instanceof CellLayout.LayoutParams)) { - return; - } - ((CellLayout.LayoutParams) getLayoutParams()).canReorder = true; - if (isInHotseat()) { - CellLayout cl = (CellLayout) getParent().getParent(); - cl.clearFolderLeaveBehind(); + if (getParent() instanceof FolderIconParent) { + ((FolderIconParent) getParent()).clearFolderLeaveBehind(this); } } public void drawLeaveBehindIfExists() { - if (!(getLayoutParams() instanceof CellLayout.LayoutParams)) { - return; - } - CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams(); - // While the folder is open, the position of the icon cannot change. - lp.canReorder = false; - if (isInHotseat()) { - CellLayout cl = (CellLayout) getParent().getParent(); - cl.setFolderLeaveBehindCell(lp.cellX, lp.cellY); + if (getParent() instanceof FolderIconParent) { + ((FolderIconParent) getParent()).drawFolderLeaveBehindForIcon(this); } } @@ -842,4 +830,19 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel MAX_NUM_ITEMS_IN_PREVIEW); } } + + /** + * Interface that provides callbacks to a parent ViewGroup that hosts this FolderIcon. + */ + public interface FolderIconParent { + /** + * Tells the FolderIconParent to draw a "leave-behind" when the Folder is open and leaving a + * gap where the FolderIcon would be when the Folder is closed. + */ + void drawFolderLeaveBehindForIcon(FolderIcon child); + /** + * Tells the FolderIconParent to stop drawing the "leave-behind" as the Folder is closed. + */ + void clearFolderLeaveBehind(FolderIcon child); + } } From a9740ecc8f190af6829446d3c76f43f4188087f1 Mon Sep 17 00:00:00 2001 From: vadimt Date: Fri, 12 Feb 2021 10:08:25 -0800 Subject: [PATCH 0559/1707] Switching overlays more correctly Test: presubmit Bug: 180114721 Change-Id: I0d8d0c70ef34ec941e28704d06f9df5f17782155 --- .../src/com/android/quickstep/NavigationModeSwitchRule.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java index 0f6671d18c..35383d2831 100644 --- a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java +++ b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java @@ -168,7 +168,7 @@ public class NavigationModeSwitchRule implements TestRule { Log.d(TAG, "setActiveOverlay: " + overlayPackage + "..."); UiDevice.getInstance(getInstrumentation()).executeShellCommand( - "cmd overlay enable-exclusive " + overlayPackage); + "cmd overlay enable-exclusive --category " + overlayPackage); if (currentSysUiNavigationMode() != expectedMode) { final CountDownLatch latch = new CountDownLatch(1); From 2cdc6bae3942aedee975981009b11f5d9e95c049 Mon Sep 17 00:00:00 2001 From: Lucas Dupin Date: Tue, 16 Feb 2021 17:47:33 -0800 Subject: [PATCH 0560/1707] Conform to new naming Resources were renamed from main/accent to primary/secondary. Test: make Bug: 171916625 Change-Id: Ic71cf716eba9f57d27869dffa501219c41c999ec --- res/values-v31/colors.xml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/res/values-v31/colors.xml b/res/values-v31/colors.xml index b8600a60ed..6baf39e034 100644 --- a/res/values-v31/colors.xml +++ b/res/values-v31/colors.xml @@ -17,17 +17,17 @@ */ --> - @android:color/system_main_50 - @android:color/system_main_100 - @android:color/system_main_300 - @android:color/system_main_800 - @android:color/system_main_900 - @android:color/system_main_700 + @android:color/system_primary_50 + @android:color/system_primary_100 + @android:color/system_primary_300 + @android:color/system_primary_800 + @android:color/system_primary_900 + @android:color/system_primary_700 - @android:color/system_main_50 - @android:color/system_main_900 + @android:color/system_primary_50 + @android:color/system_primary_900 - @android:color/system_main_50 - @android:color/system_main_200 - @android:color/system_main_400 + @android:color/system_primary_50 + @android:color/system_primary_200 + @android:color/system_primary_400 \ No newline at end of file From e1a8440d78ad2437c5b69eb4df02f0a91cc4ff02 Mon Sep 17 00:00:00 2001 From: thiruram Date: Fri, 12 Feb 2021 16:02:39 -0800 Subject: [PATCH 0561/1707] Add @Nullable annotation to AllAppsStore.getApp. Test: Manual Bug: 180120291 Change-Id: I3f29d478e39b46e307f33f2f354720bc2915b550 --- src/com/android/launcher3/allapps/AllAppsStore.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/com/android/launcher3/allapps/AllAppsStore.java b/src/com/android/launcher3/allapps/AllAppsStore.java index 769cb5e31b..355ccadeba 100644 --- a/src/com/android/launcher3/allapps/AllAppsStore.java +++ b/src/com/android/launcher3/allapps/AllAppsStore.java @@ -21,6 +21,8 @@ import static com.android.launcher3.model.data.AppInfo.EMPTY_ARRAY; import android.view.View; import android.view.ViewGroup; +import androidx.annotation.Nullable; + import com.android.launcher3.BubbleTextView; import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.model.data.ItemInfo; @@ -78,6 +80,11 @@ public class AllAppsStore { return (mModelFlags & mask) != 0; } + /** + * Returns {@link AppInfo} if any apps matches with provided {@link ComponentKey}, otherwise + * null. + */ + @Nullable public AppInfo getApp(ComponentKey key) { mTempInfo.componentName = key.componentName; mTempInfo.user = key.user; From a34af18b0efd6bfc6b1aa0f0c3b63d6751b282fc Mon Sep 17 00:00:00 2001 From: Vinit Nayak Date: Thu, 11 Feb 2021 15:48:54 -0800 Subject: [PATCH 0562/1707] Update System settings content observer to cache values. Existing ContentObserver will cache values and overwrite them when the value itself is modified or caller force updates. Bug: 149571513 Test: Wrote unit tests, mostly for caching logic. Not meant to test ContentObserver contract for registering an observer. Change-Id: I12835f6c2be27ce17f65a55c51c4ef85c63b4487 --- Android.bp | 1 - .../RecentsAnimationDeviceState.java | 47 ++--- .../logging/SettingsChangeLogger.java | 13 +- .../quickstep/util/RecentsOrientedState.java | 26 +-- .../launcher3/util/SettingsCacheTest.java | 115 +++++++++++ .../android/launcher3/LauncherAppState.java | 21 +- .../notification/NotificationListener.java | 19 +- .../settings/NotificationDotsPreference.java | 4 +- .../launcher3/settings/SettingsActivity.java | 35 ++-- .../util/SecureSettingsObserver.java | 104 ---------- .../android/launcher3/util/SettingsCache.java | 179 ++++++++++++++++++ tests/Android.mk | 1 - 12 files changed, 379 insertions(+), 186 deletions(-) create mode 100644 robolectric_tests/src/com/android/launcher3/util/SettingsCacheTest.java delete mode 100644 src/com/android/launcher3/util/SecureSettingsObserver.java create mode 100644 src/com/android/launcher3/util/SettingsCache.java diff --git a/Android.bp b/Android.bp index a720658f9f..9d675a4b3e 100644 --- a/Android.bp +++ b/Android.bp @@ -24,7 +24,6 @@ android_library { ], srcs: [ "tests/tapl/**/*.java", - "src/com/android/launcher3/util/SecureSettingsObserver.java", "src/com/android/launcher3/ResourceUtils.java", "src/com/android/launcher3/testing/TestProtocol.java", ], diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java index 4301377808..f99b7e6af4 100644 --- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java +++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java @@ -17,14 +17,16 @@ package com.android.quickstep; import static android.content.Intent.ACTION_USER_UNLOCKED; +import static com.android.launcher3.util.SettingsCache.ONE_HANDED_ENABLED; +import static com.android.launcher3.util.SettingsCache.ONE_HANDED_SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED; import static com.android.launcher3.util.DisplayController.DisplayHolder.CHANGE_ALL; import static com.android.launcher3.util.DisplayController.DisplayHolder.CHANGE_FRAME_DELAY; import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON; import static com.android.quickstep.SysUINavigationMode.Mode.THREE_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_ASSIST_GESTURE_CONSTRAINED; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_GLOBAL_ACTIONS_SHOWING; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED; @@ -44,6 +46,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.res.Resources; import android.graphics.Region; +import android.net.Uri; import android.os.Process; import android.os.SystemProperties; import android.os.UserManager; @@ -57,11 +60,11 @@ import androidx.annotation.BinderThread; import com.android.launcher3.R; import com.android.launcher3.Utilities; +import com.android.launcher3.util.SettingsCache; import com.android.launcher3.util.DisplayController; import com.android.launcher3.util.DisplayController.DisplayHolder; import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener; import com.android.launcher3.util.DisplayController.Info; -import com.android.launcher3.util.SecureSettingsObserver; import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener; import com.android.quickstep.SysUINavigationMode.OneHandedModeChangeListener; import com.android.quickstep.util.NavBarPosition; @@ -177,33 +180,33 @@ public class RecentsAnimationDeviceState implements } } + SettingsCache settingsCache = SettingsCache.INSTANCE.get(mContext); if (mIsOneHandedModeSupported) { - SecureSettingsObserver oneHandedEnabledObserver = - SecureSettingsObserver.newOneHandedSettingsObserver( - mContext, enabled -> mIsOneHandedModeEnabled = enabled); - oneHandedEnabledObserver.register(); - oneHandedEnabledObserver.dispatchOnChange(); - runOnDestroy(oneHandedEnabledObserver::unregister); + Uri oneHandedUri = Settings.Secure.getUriFor(ONE_HANDED_ENABLED); + SettingsCache.OnChangeListener onChangeListener = + enabled -> mIsOneHandedModeEnabled = enabled; + settingsCache.register(oneHandedUri, onChangeListener); + settingsCache.dispatchOnChange(oneHandedUri); + runOnDestroy(() -> settingsCache.unregister(oneHandedUri, onChangeListener)); } else { mIsOneHandedModeEnabled = false; } - SecureSettingsObserver swipeBottomEnabledObserver = - SecureSettingsObserver.newSwipeToNotificationSettingsObserver( - mContext, enabled -> mIsSwipeToNotificationEnabled = enabled); - swipeBottomEnabledObserver.register(); - swipeBottomEnabledObserver.dispatchOnChange(); - runOnDestroy(swipeBottomEnabledObserver::unregister); - SecureSettingsObserver userSetupObserver = new SecureSettingsObserver( - context.getContentResolver(), - e -> mIsUserSetupComplete = e, - Settings.Secure.USER_SETUP_COMPLETE, - 0); - mIsUserSetupComplete = userSetupObserver.getValue(); + Uri swipeBottomNotificationUri = + Settings.Secure.getUriFor(ONE_HANDED_SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED); + SettingsCache.OnChangeListener onChangeListener = + enabled -> mIsSwipeToNotificationEnabled = enabled; + settingsCache.register(swipeBottomNotificationUri, onChangeListener); + settingsCache.dispatchOnChange(swipeBottomNotificationUri); + runOnDestroy(() -> settingsCache.unregister(swipeBottomNotificationUri, onChangeListener)); + + Uri setupCompleteUri = Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE); + mIsUserSetupComplete = settingsCache.getValue(setupCompleteUri, 0); if (!mIsUserSetupComplete) { - userSetupObserver.register(); - runOnDestroy(userSetupObserver::unregister); + SettingsCache.OnChangeListener userSetupChangeListener = e -> mIsUserSetupComplete = e; + settingsCache.register(setupCompleteUri, userSetupChangeListener); + runOnDestroy(() -> settingsCache.unregister(setupCompleteUri, userSetupChangeListener)); } } diff --git a/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java b/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java index 3157865f78..f336bf5260 100644 --- a/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java +++ b/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java @@ -28,7 +28,7 @@ import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCH import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_NOTIFICATION_DOT_DISABLED; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_NOTIFICATION_DOT_ENABLED; import static com.android.launcher3.model.QuickstepModelDelegate.LAST_PREDICTION_ENABLED_STATE; -import static com.android.launcher3.util.SecureSettingsObserver.newNotificationSettingsObserver; +import static com.android.launcher3.util.SettingsCache.NOTIFICATION_BADGING_URI; import android.content.Context; import android.content.SharedPreferences; @@ -43,7 +43,7 @@ import com.android.launcher3.R; import com.android.launcher3.logging.InstanceIdSequence; import com.android.launcher3.logging.StatsLogManager; import com.android.launcher3.logging.StatsLogManager.StatsLogger; -import com.android.launcher3.util.SecureSettingsObserver; +import com.android.launcher3.util.SettingsCache; import com.android.quickstep.SysUINavigationMode; import com.android.quickstep.SysUINavigationMode.Mode; import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener; @@ -77,11 +77,10 @@ public class SettingsChangeLogger implements getPrefs(context).registerOnSharedPreferenceChangeListener(this); getDevicePrefs(context).registerOnSharedPreferenceChangeListener(this); - SecureSettingsObserver dotsObserver = - newNotificationSettingsObserver(context, this::onNotificationDotsChanged); - mNotificationDotsEnabled = dotsObserver.getValue(); - dispatchUserEvent(); - + SettingsCache mSettingsCache = SettingsCache.INSTANCE.get(context); + mSettingsCache.register(NOTIFICATION_BADGING_URI, + this::onNotificationDotsChanged); + mSettingsCache.dispatchOnChange(NOTIFICATION_BADGING_URI); } private static ArrayMap loadPrefKeys(Context context) { diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java index a3ee9122a7..215f05ae4c 100644 --- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java +++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java @@ -23,22 +23,18 @@ import static android.view.Surface.ROTATION_180; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; -import static com.android.launcher3.Utilities.newContentObserver; +import static com.android.launcher3.util.SettingsCache.ROTATION_SETTING_URI; import static com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY; import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import static java.lang.annotation.RetentionPolicy.SOURCE; -import android.content.ContentResolver; import android.content.Context; import android.content.SharedPreferences; import android.content.res.Resources; -import android.database.ContentObserver; import android.graphics.Matrix; import android.graphics.PointF; import android.graphics.Rect; -import android.os.Handler; -import android.provider.Settings; import android.util.Log; import android.view.MotionEvent; import android.view.OrientationEventListener; @@ -51,6 +47,7 @@ import com.android.launcher3.DeviceProfile; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.R; import com.android.launcher3.Utilities; +import com.android.launcher3.util.SettingsCache; import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.touch.PagedOrientationHandler; import com.android.launcher3.util.WindowBounds; @@ -72,9 +69,6 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre private static final String TAG = "RecentsOrientedState"; private static final boolean DEBUG = false; - private ContentObserver mSystemAutoRotateObserver = - newContentObserver(new Handler(), t -> updateAutoRotateSetting()); - @Retention(SOURCE) @IntDef({ROTATION_0, ROTATION_90, ROTATION_180, ROTATION_270}) public @interface SurfaceRotation {} @@ -118,9 +112,11 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre | FLAG_SWIPE_UP_NOT_RUNNING; private final Context mContext; - private final ContentResolver mContentResolver; private final SharedPreferences mSharedPrefs; private final OrientationEventListener mOrientationListener; + private final SettingsCache mSettingsCache; + private final SettingsCache.OnChangeListener mRotationChangeListener = + isEnabled -> updateAutoRotateSetting(); private final Matrix mTmpMatrix = new Matrix(); @@ -138,7 +134,6 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre public RecentsOrientedState(Context context, BaseActivityInterface sizeStrategy, IntConsumer rotationChangeListener) { mContext = context; - mContentResolver = context.getContentResolver(); mSharedPrefs = Utilities.getPrefs(context); mOrientationListener = new OrientationEventListener(context) { @Override @@ -162,6 +157,7 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre mFlags |= FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_DENSITY; } mFlags |= FLAG_SWIPE_UP_NOT_RUNNING; + mSettingsCache = SettingsCache.INSTANCE.get(mContext); initFlags(); } @@ -271,8 +267,8 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre } private void updateAutoRotateSetting() { - setFlag(FLAG_SYSTEM_ROTATION_ALLOWED, Settings.System.getInt(mContentResolver, - Settings.System.ACCELEROMETER_ROTATION, 1) == 1); + setFlag(FLAG_SYSTEM_ROTATION_ALLOWED, + mSettingsCache.getValue(ROTATION_SETTING_URI, 1)); } private void updateHomeRotationSetting() { @@ -295,9 +291,7 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre public void initListeners() { if (isMultipleOrientationSupportedByDevice()) { mSharedPrefs.registerOnSharedPreferenceChangeListener(this); - mContentResolver.registerContentObserver( - Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION), - false, mSystemAutoRotateObserver); + mSettingsCache.register(ROTATION_SETTING_URI, mRotationChangeListener); } initFlags(); } @@ -308,7 +302,7 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre public void destroyListeners() { if (isMultipleOrientationSupportedByDevice()) { mSharedPrefs.unregisterOnSharedPreferenceChangeListener(this); - mContentResolver.unregisterContentObserver(mSystemAutoRotateObserver); + mSettingsCache.unregister(ROTATION_SETTING_URI, mRotationChangeListener); } setRotationWatcherEnabled(false); } diff --git a/robolectric_tests/src/com/android/launcher3/util/SettingsCacheTest.java b/robolectric_tests/src/com/android/launcher3/util/SettingsCacheTest.java new file mode 100644 index 0000000000..fbf4c63e65 --- /dev/null +++ b/robolectric_tests/src/com/android/launcher3/util/SettingsCacheTest.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.util; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.net.Uri; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +import java.util.Collections; + +@RunWith(RobolectricTestRunner.class) +public class SettingsCacheTest { + + public static final Uri KEY_SYSTEM_URI_TEST1 = Uri.parse("content://settings/system/test1"); + public static final Uri KEY_SYSTEM_URI_TEST2 = Uri.parse("content://settings/system/test2");; + + private SettingsCache.OnChangeListener mChangeListener; + private SettingsCache mSettingsCache; + + @Before + public void setup() { + mChangeListener = mock(SettingsCache.OnChangeListener.class); + Context targetContext = RuntimeEnvironment.application; + mSettingsCache = SettingsCache.INSTANCE.get(targetContext); + mSettingsCache.register(KEY_SYSTEM_URI_TEST1, mChangeListener); + } + + @Test + public void listenerCalledOnChange() { + mSettingsCache.onChange(true, KEY_SYSTEM_URI_TEST1); + verify(mChangeListener, times(1)).onSettingsChanged(true); + } + + @Test + public void getValueRespectsDefaultValue() { + // Case of key not found + boolean val = mSettingsCache.getValue(KEY_SYSTEM_URI_TEST1, 0); + assertFalse(val); + } + + @Test + public void getValueHitsCache() { + mSettingsCache.setKeyCache(Collections.singletonMap(KEY_SYSTEM_URI_TEST1, true)); + boolean val = mSettingsCache.getValue(KEY_SYSTEM_URI_TEST1, 0); + assertTrue(val); + } + + @Test + public void getValueUpdatedCache() { + // First ensure there's nothing in cache + boolean val = mSettingsCache.getValue(KEY_SYSTEM_URI_TEST1, 0); + assertFalse(val); + + mSettingsCache.setKeyCache(Collections.singletonMap(KEY_SYSTEM_URI_TEST1, true)); + val = mSettingsCache.getValue(KEY_SYSTEM_URI_TEST1, 0); + assertTrue(val); + } + + @Test + public void multipleListenersSingleKey() { + SettingsCache.OnChangeListener secondListener = mock(SettingsCache.OnChangeListener.class); + mSettingsCache.register(KEY_SYSTEM_URI_TEST1, secondListener); + + mSettingsCache.onChange(true, KEY_SYSTEM_URI_TEST1); + verify(mChangeListener, times(1)).onSettingsChanged(true); + verify(secondListener, times(1)).onSettingsChanged(true); + } + + @Test + public void singleListenerMultipleKeys() { + SettingsCache.OnChangeListener secondListener = mock(SettingsCache.OnChangeListener.class); + mSettingsCache.register(KEY_SYSTEM_URI_TEST2, secondListener); + + mSettingsCache.onChange(true, KEY_SYSTEM_URI_TEST1); + mSettingsCache.onChange(true, KEY_SYSTEM_URI_TEST2); + verify(mChangeListener, times(1)).onSettingsChanged(true); + verify(secondListener, times(1)).onSettingsChanged(true); + } + + @Test + public void sameListenerMultipleKeys() { + SettingsCache.OnChangeListener secondListener = mock(SettingsCache.OnChangeListener.class); + mSettingsCache.register(KEY_SYSTEM_URI_TEST2, mChangeListener); + + mSettingsCache.onChange(true, KEY_SYSTEM_URI_TEST1); + mSettingsCache.onChange(true, KEY_SYSTEM_URI_TEST2); + verify(mChangeListener, times(2)).onSettingsChanged(true); + verify(secondListener, times(0)).onSettingsChanged(true); + } +} diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java index a4181c53c9..57d76005a6 100644 --- a/src/com/android/launcher3/LauncherAppState.java +++ b/src/com/android/launcher3/LauncherAppState.java @@ -17,8 +17,8 @@ package com.android.launcher3; import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_ICON_PARAMS; +import static com.android.launcher3.util.SettingsCache.NOTIFICATION_BADGING_URI; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; -import static com.android.launcher3.util.SecureSettingsObserver.newNotificationSettingsObserver; import android.content.ComponentName; import android.content.Context; @@ -37,10 +37,10 @@ import com.android.launcher3.notification.NotificationListener; import com.android.launcher3.pm.InstallSessionHelper; import com.android.launcher3.pm.InstallSessionTracker; import com.android.launcher3.pm.UserCache; +import com.android.launcher3.util.SettingsCache; 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,8 +57,9 @@ public class LauncherAppState { private final IconCache mIconCache; private final WidgetPreviewLoader mWidgetCache; private final InvariantDeviceProfile mInvariantDeviceProfile; + private SettingsCache.OnChangeListener mNotificationSettingsChangedListener; - private SecureSettingsObserver mNotificationDotsObserver; + private SettingsCache mSettingsCache; private InstallSessionTracker mInstallSessionTracker; private SimpleBroadcastReceiver mModelChangeReceiver; private SafeCloseable mCalendarChangeTracker; @@ -108,10 +109,11 @@ public class LauncherAppState { .registerInstallTracker(mModel); // Register an observer to rebind the notification listener when dots are re-enabled. - mNotificationDotsObserver = - newNotificationSettingsObserver(mContext, this::onNotificationSettingsChanged); - mNotificationDotsObserver.register(); - mNotificationDotsObserver.dispatchOnChange(); + mSettingsCache = SettingsCache.INSTANCE.get(mContext); + mNotificationSettingsChangedListener = this::onNotificationSettingsChanged; + mSettingsCache.register(NOTIFICATION_BADGING_URI, + mNotificationSettingsChangedListener); + mSettingsCache.dispatchOnChange(NOTIFICATION_BADGING_URI); } public LauncherAppState(Context context, @Nullable String iconCacheFileName) { @@ -166,8 +168,9 @@ public class LauncherAppState { } CustomWidgetManager.INSTANCE.get(mContext).setWidgetRefreshCallback(null); - if (mNotificationDotsObserver != null) { - mNotificationDotsObserver.unregister(); + if (mSettingsCache != null) { + mSettingsCache.unregister(NOTIFICATION_BADGING_URI, + mNotificationSettingsChangedListener); } } diff --git a/src/com/android/launcher3/notification/NotificationListener.java b/src/com/android/launcher3/notification/NotificationListener.java index 059ad18971..2905dc322a 100644 --- a/src/com/android/launcher3/notification/NotificationListener.java +++ b/src/com/android/launcher3/notification/NotificationListener.java @@ -16,9 +16,9 @@ package com.android.launcher3.notification; +import static com.android.launcher3.util.SettingsCache.NOTIFICATION_BADGING_URI; 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; import android.annotation.TargetApi; import android.app.Notification; @@ -37,8 +37,8 @@ import androidx.annotation.AnyThread; import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; +import com.android.launcher3.util.SettingsCache; import com.android.launcher3.util.PackageUserKey; -import com.android.launcher3.util.SecureSettingsObserver; import java.util.ArrayList; import java.util.Arrays; @@ -81,7 +81,8 @@ public class NotificationListener extends NotificationListenerService { /** The last notification key that was dismissed from launcher UI */ private String mLastKeyDismissedByLauncher; - private SecureSettingsObserver mNotificationDotsObserver; + private SettingsCache mSettingsCache; + private SettingsCache.OnChangeListener mNotificationSettingsChangedListener; public NotificationListener() { mWorkerHandler = new Handler(MODEL_EXECUTOR.getLooper(), this::handleWorkerMessage); @@ -207,10 +208,12 @@ public class NotificationListener extends NotificationListenerService { super.onListenerConnected(); sIsConnected = true; - mNotificationDotsObserver = - newNotificationSettingsObserver(this, this::onNotificationSettingsChanged); - mNotificationDotsObserver.register(); - mNotificationDotsObserver.dispatchOnChange(); + // Register an observer to rebind the notification listener when dots are re-enabled. + mSettingsCache = SettingsCache.INSTANCE.get(this); + mNotificationSettingsChangedListener = this::onNotificationSettingsChanged; + mSettingsCache.register(NOTIFICATION_BADGING_URI, + mNotificationSettingsChangedListener); + mSettingsCache.dispatchOnChange(NOTIFICATION_BADGING_URI); onNotificationFullRefresh(); } @@ -229,7 +232,7 @@ public class NotificationListener extends NotificationListenerService { public void onListenerDisconnected() { super.onListenerDisconnected(); sIsConnected = false; - mNotificationDotsObserver.unregister(); + mSettingsCache.unregister(NOTIFICATION_BADGING_URI, mNotificationSettingsChangedListener); onNotificationFullRefresh(); } diff --git a/src/com/android/launcher3/settings/NotificationDotsPreference.java b/src/com/android/launcher3/settings/NotificationDotsPreference.java index a91303a529..a35416915f 100644 --- a/src/com/android/launcher3/settings/NotificationDotsPreference.java +++ b/src/com/android/launcher3/settings/NotificationDotsPreference.java @@ -35,14 +35,14 @@ import androidx.preference.PreferenceViewHolder; import com.android.launcher3.R; import com.android.launcher3.notification.NotificationListener; -import com.android.launcher3.util.SecureSettingsObserver; +import com.android.launcher3.util.SettingsCache; /** * A {@link Preference} for indicating notification dots status. * Also has utility methods for updating UI based on dots status changes. */ public class NotificationDotsPreference extends Preference - implements SecureSettingsObserver.OnChangeListener { + implements SettingsCache.OnChangeListener { private boolean mWidgetFrameVisible = false; diff --git a/src/com/android/launcher3/settings/SettingsActivity.java b/src/com/android/launcher3/settings/SettingsActivity.java index 922425fca1..ac8dac5f7e 100644 --- a/src/com/android/launcher3/settings/SettingsActivity.java +++ b/src/com/android/launcher3/settings/SettingsActivity.java @@ -18,13 +18,13 @@ package com.android.launcher3.settings; import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS; +import static com.android.launcher3.util.SettingsCache.NOTIFICATION_BADGING_URI; +import static com.android.launcher3.util.SettingsCache.NOTIFICATION_ENABLED_LISTENERS; import static com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY; import static com.android.launcher3.states.RotationHelper.getAllowRotationDefaultValue; -import static com.android.launcher3.util.SecureSettingsObserver.newNotificationSettingsObserver; import android.content.SharedPreferences; import android.os.Bundle; -import android.provider.Settings; import android.text.TextUtils; import androidx.annotation.NonNull; @@ -45,8 +45,8 @@ import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.model.WidgetsModel; +import com.android.launcher3.util.SettingsCache; import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper; -import com.android.launcher3.util.SecureSettingsObserver; /** * Settings activity for Launcher. Currently implements the following setting: Allow rotation @@ -59,8 +59,6 @@ public class SettingsActivity extends FragmentActivity private static final String FLAGS_PREFERENCE_KEY = "flag_toggler"; private static final String NOTIFICATION_DOTS_PREFERENCE_KEY = "pref_icon_badging"; - /** Hidden field Settings.Secure.ENABLED_NOTIFICATION_LISTENERS */ - private static final String NOTIFICATION_ENABLED_LISTENERS = "enabled_notification_listeners"; public static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key"; public static final String EXTRA_SHOW_FRAGMENT_ARGS = ":settings:show_fragment_args"; @@ -126,10 +124,11 @@ public class SettingsActivity extends FragmentActivity */ public static class LauncherSettingsFragment extends PreferenceFragmentCompat { - private SecureSettingsObserver mNotificationDotsObserver; + private SettingsCache mSettingsCache; private String mHighLightKey; private boolean mPreferenceHighlighted = false; + private NotificationDotsPreference mNotificationSettingsChangedListener; @Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { @@ -177,14 +176,16 @@ public class SettingsActivity extends FragmentActivity } // Listen to system notification dot settings while this UI is active. - mNotificationDotsObserver = newNotificationSettingsObserver( - getActivity(), (NotificationDotsPreference) preference); - mNotificationDotsObserver.register(); + mSettingsCache = SettingsCache.INSTANCE.get(getActivity()); + mNotificationSettingsChangedListener = + ((NotificationDotsPreference) preference); + mSettingsCache.register(NOTIFICATION_BADGING_URI, + (NotificationDotsPreference) mNotificationSettingsChangedListener); // Also listen if notification permission changes - mNotificationDotsObserver.getResolver().registerContentObserver( - Settings.Secure.getUriFor(NOTIFICATION_ENABLED_LISTENERS), false, - mNotificationDotsObserver); - mNotificationDotsObserver.dispatchOnChange(); + mSettingsCache.register(NOTIFICATION_ENABLED_LISTENERS, + mNotificationSettingsChangedListener); + mSettingsCache.dispatchOnChange(NOTIFICATION_BADGING_URI); + mSettingsCache.dispatchOnChange(NOTIFICATION_ENABLED_LISTENERS); return true; case ALLOW_ROTATION_PREFERENCE_KEY: @@ -251,9 +252,11 @@ public class SettingsActivity extends FragmentActivity @Override public void onDestroy() { - if (mNotificationDotsObserver != null) { - mNotificationDotsObserver.unregister(); - mNotificationDotsObserver = null; + if (mSettingsCache != null) { + mSettingsCache.unregister(NOTIFICATION_BADGING_URI, + mNotificationSettingsChangedListener); + mSettingsCache.unregister(NOTIFICATION_ENABLED_LISTENERS, + mNotificationSettingsChangedListener); } super.onDestroy(); } diff --git a/src/com/android/launcher3/util/SecureSettingsObserver.java b/src/com/android/launcher3/util/SecureSettingsObserver.java deleted file mode 100644 index 9fe72ad80a..0000000000 --- a/src/com/android/launcher3/util/SecureSettingsObserver.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher3.util; - -import android.content.ContentResolver; -import android.content.Context; -import android.database.ContentObserver; -import android.os.Handler; -import android.provider.Settings; - -/** - * Utility class to listen for secure settings changes - */ -public class SecureSettingsObserver extends ContentObserver { - - /** Hidden field Settings.Secure.NOTIFICATION_BADGING */ - public static final String NOTIFICATION_BADGING = "notification_badging"; - /** Hidden field Settings.Secure.ONE_HANDED_MODE_ENABLED */ - public static final String ONE_HANDED_ENABLED = "one_handed_mode_enabled"; - /** Hidden field Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED */ - public static final String ONE_HANDED_SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED = - "swipe_bottom_to_notification_enabled"; - - private final ContentResolver mResolver; - private final String mKeySetting; - private final int mDefaultValue; - private final OnChangeListener mOnChangeListener; - - public SecureSettingsObserver(ContentResolver resolver, OnChangeListener listener, - String keySetting, int defaultValue) { - super(new Handler()); - - mResolver = resolver; - mOnChangeListener = listener; - mKeySetting = keySetting; - mDefaultValue = defaultValue; - } - - @Override - public void onChange(boolean selfChange) { - mOnChangeListener.onSettingsChanged(getValue()); - } - - public boolean getValue() { - return Settings.Secure.getInt(mResolver, mKeySetting, mDefaultValue) == 1; - } - - public void register() { - mResolver.registerContentObserver(Settings.Secure.getUriFor(mKeySetting), false, this); - } - - public ContentResolver getResolver() { - return mResolver; - } - - public void dispatchOnChange() { - onChange(true); - } - - public void unregister() { - mResolver.unregisterContentObserver(this); - } - - public interface OnChangeListener { - void onSettingsChanged(boolean isEnabled); - } - - public static SecureSettingsObserver newNotificationSettingsObserver(Context context, - OnChangeListener listener) { - return new SecureSettingsObserver( - context.getContentResolver(), listener, NOTIFICATION_BADGING, 1); - } - - public static SecureSettingsObserver newOneHandedSettingsObserver(Context context, - OnChangeListener listener) { - return new SecureSettingsObserver( - context.getContentResolver(), listener, ONE_HANDED_ENABLED, 0); - } - - /** - * Constructs settings observer for {@link #ONE_HANDED_SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED} - * preference. - */ - public static SecureSettingsObserver newSwipeToNotificationSettingsObserver(Context context, - OnChangeListener listener) { - return new SecureSettingsObserver( - context.getContentResolver(), listener, - ONE_HANDED_SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED, 1); - } -} diff --git a/src/com/android/launcher3/util/SettingsCache.java b/src/com/android/launcher3/util/SettingsCache.java new file mode 100644 index 0000000000..22b4d38891 --- /dev/null +++ b/src/com/android/launcher3/util/SettingsCache.java @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.util; + +import static android.provider.Settings.System.ACCELEROMETER_ROTATION; + +import android.content.ContentResolver; +import android.content.Context; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Handler; +import android.provider.Settings; + +import androidx.annotation.VisibleForTesting; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * ContentObserver over Settings keys that also has a caching layer. + * Consumers can register for callbacks via {@link #register(Uri, OnChangeListener)} and + * {@link #unregister(Uri, OnChangeListener)} methods. + * + * This can be used as a normal cache without any listeners as well via the + * {@link #getValue(Uri, int)} and {@link #dispatchOnChange(Uri)} to update (and subsequently call + * get) + * + * The cache will be invalidated/updated through the normal + * {@link ContentObserver#onChange(boolean)} calls + * or can be force updated by calling {@link #dispatchOnChange(Uri)}. + * + * Cache will also be updated if a key queried is missing (even if it has no listeners registered). + */ +public class SettingsCache extends ContentObserver { + + /** Hidden field Settings.Secure.NOTIFICATION_BADGING */ + public static final Uri NOTIFICATION_BADGING_URI = + Settings.Secure.getUriFor("notification_badging"); + /** Hidden field Settings.Secure.ONE_HANDED_MODE_ENABLED */ + public static final String ONE_HANDED_ENABLED = "one_handed_mode_enabled"; + /** Hidden field Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED */ + public static final String ONE_HANDED_SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED = + "swipe_bottom_to_notification_enabled"; + /** Hidden field Settings.Secure.ENABLED_NOTIFICATION_LISTENERS */ + public static final Uri NOTIFICATION_ENABLED_LISTENERS = + Settings.Secure.getUriFor("enabled_notification_listeners"); + public static final Uri ROTATION_SETTING_URI = + Settings.System.getUriFor(ACCELEROMETER_ROTATION); + + private static final String SYSTEM_URI_PREFIX = Settings.System.CONTENT_URI.toString(); + + /** + * Caches the last seen value for registered keys. + */ + private Map mKeyCache = new ConcurrentHashMap<>(); + private final Map> mListenerMap = new HashMap<>(); + protected final ContentResolver mResolver; + + + /** + * Singleton instance + */ + public static MainThreadInitializedObject INSTANCE = + new MainThreadInitializedObject<>(SettingsCache::new); + + private SettingsCache(Context context) { + super(new Handler()); + mResolver = context.getContentResolver(); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + // We use default of 1, but if we're getting an onChange call, can assume a non-default + // value will exist + boolean newVal = updateValue(uri, 1 /* Effectively Unused */); + if (!mListenerMap.containsKey(uri)) { + return; + } + + for (OnChangeListener listener : mListenerMap.get(uri)) { + listener.onSettingsChanged(newVal); + } + } + + /** + * Returns the value for this classes key from the cache. If not in cache, will call + * {@link #updateValue(Uri, int)} to fetch. + */ + public boolean getValue(Uri keySetting, int defaultValue) { + if (mKeyCache.containsKey(keySetting)) { + return mKeyCache.get(keySetting); + } else { + return updateValue(keySetting, defaultValue); + } + } + + /** + * Does not de-dupe if you add same listeners for the same key multiple times. + * Unregister once complete using {@link #unregister(Uri, OnChangeListener)} + */ + public void register(Uri uri, OnChangeListener changeListener) { + if (mListenerMap.containsKey(uri)) { + mListenerMap.get(uri).add(changeListener); + } else { + CopyOnWriteArrayList l = new CopyOnWriteArrayList<>(); + l.add(changeListener); + mListenerMap.put(uri, l); + mResolver.registerContentObserver(uri, false, this); + } + } + + private boolean updateValue(Uri keyUri, int defaultValue) { + String key = keyUri.getLastPathSegment(); + boolean newVal; + if (keyUri.toString().startsWith(SYSTEM_URI_PREFIX)) { + newVal = Settings.System.getInt(mResolver, key, defaultValue) == 1; + } else { // SETTING_SECURE + newVal = Settings.Secure.getInt(mResolver, key, defaultValue) == 1; + } + + mKeyCache.put(keyUri, newVal); + return newVal; + } + + /** + * Force update a change for a given URI and have all listeners for that URI receive callbacks + * even if the value is unchanged. + */ + public void dispatchOnChange(Uri uri) { + onChange(true, uri); + } + + /** + * Call to stop receiving updates on the given {@param listener}. + * This Uri/Listener pair must correspond to the same pair called with for + * {@link #register(Uri, OnChangeListener)} + */ + public void unregister(Uri uri, OnChangeListener listener) { + List listenersToRemoveFrom = mListenerMap.get(uri); + if (!listenersToRemoveFrom.contains(listener)) { + return; + } + + listenersToRemoveFrom.remove(listener); + if (listenersToRemoveFrom.isEmpty()) { + mListenerMap.remove(uri); + } + } + + /** + * Don't use this. Ever. + * @param keyCache Cache to replace {@link #mKeyCache} + */ + @VisibleForTesting + void setKeyCache(Map keyCache) { + mKeyCache = keyCache; + } + + public interface OnChangeListener { + void onSettingsChanged(boolean isEnabled); + } +} diff --git a/tests/Android.mk b/tests/Android.mk index 3d9077d4bd..43d51fca01 100644 --- a/tests/Android.mk +++ b/tests/Android.mk @@ -32,7 +32,6 @@ else LOCAL_SRC_FILES := $(call all-java-files-under, tapl) \ ../src/com/android/launcher3/ResourceUtils.java \ - ../src/com/android/launcher3/util/SecureSettingsObserver.java \ ../src/com/android/launcher3/testing/TestProtocol.java endif From 3a06568d8bcfb8a28eb0c5f631bf1b9c79084956 Mon Sep 17 00:00:00 2001 From: Alex Chau Date: Thu, 11 Feb 2021 13:54:27 +0000 Subject: [PATCH 0563/1707] Use PersistedTaskSnapshotData to calculate TaskView size - Disabled variable width for running task, as PersistedTaskSnapshotData is not always up to date for current task after rotating the device Bug: 174464656 Test: Test tablet overview with different comination of portrait/landscape wide/narrow thumbnails in portrait/landscape Change-Id: Ifa01e72020ccaf422e3975b092462de85fa19efd --- .../android/quickstep/RecentTasksList.java | 5 ++-- .../android/quickstep/views/RecentsView.java | 11 +++++--- .../quickstep/views/TaskThumbnailView.java | 28 ------------------- .../com/android/quickstep/views/TaskView.java | 13 ++++++--- 4 files changed, 18 insertions(+), 39 deletions(-) diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java index 70b4f20c2b..1c5dc4c829 100644 --- a/quickstep/src/com/android/quickstep/RecentTasksList.java +++ b/quickstep/src/com/android/quickstep/RecentTasksList.java @@ -191,6 +191,7 @@ public class RecentTasksList extends TaskStackChangeListener { } else { task = new Task(taskKey); } + task.setLastSnapshotData(rawTask); allTasks.add(task); } @@ -200,9 +201,7 @@ public class RecentTasksList extends TaskStackChangeListener { private ArrayList copyOf(ArrayList tasks) { ArrayList newTasks = new ArrayList<>(); for (int i = 0; i < tasks.size(); i++) { - Task t = tasks.get(i); - newTasks.add(new Task(t.key, t.colorPrimary, t.colorBackground, t.isDockable, - t.isLocked, t.taskDescription, t.topActivity)); + newTasks.add(new Task(tasks.get(i))); } return newTasks; } diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java index 5d492acf84..deb1388e96 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -646,9 +646,9 @@ public abstract class RecentsView extends PagedView public TaskView getTaskView(int taskId) { for (int i = 0; i < getTaskViewCount(); i++) { - TaskView tv = getTaskViewAt(i); - if (tv.getTask() != null && tv.getTask().key != null && tv.getTask().key.id == taskId) { - return tv; + TaskView taskView = getTaskViewAt(i); + if (taskView.hasTaskId(taskId)) { + return taskView; } } return null; @@ -808,6 +808,7 @@ public abstract class RecentsView extends PagedView final Task task = tasks.get(i); final TaskView taskView = (TaskView) getChildAt(pageIndex); taskView.bind(task, mOrientationState); + taskView.updateTaskSize(!taskView.hasTaskId(mRunningTaskId)); } if (mNextPage == INVALID_PAGE) { @@ -942,7 +943,8 @@ public abstract class RecentsView extends PagedView // Force TaskView to update size from thumbnail final int taskCount = getTaskViewCount(); for (int i = 0; i < taskCount; i++) { - getTaskViewAt(i).updateTaskSize(); + TaskView taskView = getTaskViewAt(i); + taskView.updateTaskSize(!taskView.hasTaskId(mRunningTaskId)); } } @@ -1252,6 +1254,7 @@ public abstract class RecentsView extends PagedView // gesture and the task list is loaded and applied mTmpRunningTask = Task.from(new TaskKey(runningTaskInfo), runningTaskInfo, false); taskView.bind(mTmpRunningTask, mOrientationState); + taskView.updateTaskSize(false); // Measure and layout immediately so that the scroll values is updated instantly // as the user might be quick-switching diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java index 3bd883ddc4..f2f4bc1238 100644 --- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java +++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java @@ -111,9 +111,6 @@ public class TaskThumbnailView extends View implements PluginListener Date: Wed, 17 Feb 2021 11:40:07 +0000 Subject: [PATCH 0564/1707] Extract common codes for personal / work profile tabs These codes can be reused in the FullWidgetsSheet which we will be adding tabs for personal / work profile. Test: Set up work profile and then switch person / work profile tabs in the AllAppsContainerView. Bug: 179797520 Change-Id: Ib7eb1190e1384a664cbe3e34411c9362f1f6db03 --- res/layout/all_apps.xml | 30 +----- res/layout/personal_work_tabs.xml | 47 ++++++++++ res/layout/secondary_launcher.xml | 4 +- .../allapps/AllAppsContainerView.java | 18 ++-- .../launcher3/allapps/AllAppsPagedView.java | 60 ++---------- .../allapps/LauncherAllAppsContainerView.java | 9 +- .../workprofile/PersonalWorkPagedView.java | 92 +++++++++++++++++++ .../PersonalWorkSlidingTabStrip.java | 25 +++-- 8 files changed, 178 insertions(+), 107 deletions(-) create mode 100644 res/layout/personal_work_tabs.xml create mode 100644 src/com/android/launcher3/workprofile/PersonalWorkPagedView.java rename src/com/android/launcher3/{allapps => workprofile}/PersonalWorkSlidingTabStrip.java (85%) diff --git a/res/layout/all_apps.xml b/res/layout/all_apps.xml index 0041c9a7c8..8ed16c7493 100644 --- a/res/layout/all_apps.xml +++ b/res/layout/all_apps.xml @@ -40,35 +40,7 @@ - - -